import { searchAnime, getBangumi, getComment, getSegmentComment, matchSeason } from '../danmu_api/apis/dandan-api.js'; import { Globals } from '../danmu_api/configs/globals.js'; import { log } from '../danmu_api/utils/log-util.js'; import { simplized } from '../danmu_api/utils/zh-util.js'; const wv = typeof widgetVersion !== 'undefined' ? widgetVersion : Globals.VERSION; WidgetMetadata = { id: "forward.auto.danmu2", title: "自动链接弹幕v2", version: wv, requiredVersion: "0.0.2", description: "自动获取播放链接并从服务器获取弹幕【五折码:CHEAP.5;七折码:CHEAP】", author: "huangxd", site: "https://github.com/huangxd-/ForwardWidgets", globalParams: [ // 源配置 { name: "sourceOrder", title: "源排序配置,默认'360,vod,renren,hanjutv',可选['360', 'vod', 'tmdb', 'douban', 'tencent', 'youku', 'iqiyi', 'imgo', 'bilibili', 'migu', 'sohu', 'leshi', 'xigua', 'maiduidui', 'aiyifan', 'renren', 'hanjutv', 'bahamut', 'dandan', 'custom']", type: "input", placeholders: [ { title: "配置1", value: "tencent,iqiyi,imgo,bilibili,youku,renren,hanjutv", }, { title: "配置2", value: "douban,360,vod,renren,hanjutv", }, { title: "配置3", value: "360,vod,renren,hanjutv", }, { title: "配置4", value: "vod,360,renren,hanjutv,bahamut,dandan", }, ], }, { name: "otherServer", title: "第三方弹幕服务器,默认https://api.danmu.icu", type: "input", placeholders: [ { title: "icu", value: "https://api.danmu.icu", }, { title: "lyz05", value: "https://fc.lyz05.cn", }, { title: "hls", value: "https://dmku.hls.one", }, { title: "678", value: "https://se.678.ooo", }, { title: "56uxi", value: "https://danmu.56uxi.com", }, { title: "lxlad", value: "https://dm.lxlad.com", }, ], }, { name: "customSourceApiUrl", title: "自定义弹幕源API地址,默认为空,配置后还需在SOURCE_ORDER添加custom源", type: "input", placeholders: [ { title: "自定义", value: "", }, ], }, { name: "vodServers", title: "VOD站点配置,格式:名称@URL,名称@URL,默认金蝉'https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top'", type: "input", placeholders: [ { title: "配置1", value: "金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top", }, { title: "配置2", value: "金蝉@https://zy.jinchancaiji.com", }, { title: "配置3", value: "金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou", }, { title: "配置4", value: "金蝉@https://zy.jinchancaiji.com,听风@https://gctf.tfdh.top", }, ], }, { name: "vodReturnMode", title: "VOD返回模式:all(所有站点)或 fastest(最快的站点),默认fastest", type: "input", placeholders: [ { title: "fastest", value: "fastest", }, { title: "all", value: "all", }, ], }, { name: "vodRequestTimeout", title: "VOD请求超时时间,默认10000", type: "input", placeholders: [ { title: "10s", value: "10000", }, { title: "15s", value: "15000", }, { title: "20s", value: "20000", }, ], }, { name: "bilibiliCookie", title: "B站Cookie(填入后能抓取b站完整弹幕)", type: "input", placeholders: [ { title: "示例", value: "SESSDATA=xxxx", }, ], }, { name: "doubanCookie", title: "豆瓣Cookie(填入后可提升豆瓣搜索和详情请求稳定性)", type: "input", placeholders: [ { title: "示例", value: "", }, ], }, // 匹配配置 { name: "platformOrder", title: "平台优选配置,可选['qiyi', 'bilibili1', 'imgo', 'youku', 'qq', 'migu', 'sohu', 'leshi, 'xigua', 'maiduidui', 'aiyifan', 'renren', 'hanjutv', 'bahamut', 'dandan', 'custom']", type: "input", placeholders: [ { title: "配置1", value: "qq,qiyi,imgo,bilibili1,youku,migu,sohu,leshi,xigua,maiduidui,aiyifan,renren,hanjutv,bahamut,dandan,custom", }, { title: "配置2", value: "bilibili1,qq,qiyi,imgo", }, { title: "配置3", value: "dandan,bilibili1,bahamut", }, { title: "配置4", value: "imgo,qiyi,qq,youku,bilibili1", }, ], }, { name: "animeTitleFilter", title: "剧名过滤规则,用于控制剧名过滤规则,需开启过滤开关ENABLE_ANIME_EPISODE_FILTER", type: "input", placeholders: [ { title: "示例", value: "广场舞|预告", }, ], }, { name: "episodeTitleFilter", title: "剧集标题过滤规则", type: "input", placeholders: [ { title: "示例", value: "(特别|惊喜|纳凉)?企划(?!(书|案|部))|合伙人手记|超前(营业|vlog)?|速览|vlog|(?映射标题;原始标题->映射标题;... ,例如:\"唐朝诡事录->唐朝诡事录之西行;国色芳华->锦绣芳华\"", type: "input", placeholders: [ { title: "映射表示例", value: "原始标题->映射标题;原始标题->映射标题", }, ], }, { name: "animeTitleSimplified", title: "搜索的剧名标题自动繁转简,默认false", type: "input", placeholders: [ { title: "false", value: "false", }, { title: "true", value: "true", }, ], }, // 弹幕配置 { name: "blockedWords", title: "屏蔽词列表", type: "input", placeholders: [ { title: "示例", value: "/.{20,}/,/^\\d{2,4}[-/.]\\d{1,2}[-/.]\\d{1,2}([日号.]*)?$/,/^(?!哈+$)([a-zA-Z\u4e00-\u9fa5])\\1{2,}/,/[0-9]+\\.*[0-9]*\\s*(w|万)+\\s*(\\+|个|人|在看)+/,/^[a-z]{6,}$/,/^(?:qwertyuiop|asdfghjkl|zxcvbnm)$/,/^\\d{5,}$/,/^(\\d)\\1{2,}$/,/^\\d{1,4}$/,/(20[0-3][0-9])/,/(0?[1-9]|1[0-2])月/,/\\d{1,2}[.-]\\d{1,2}/,/[@#&$%^*+\\|/\\-_=<>°◆◇■□●○★☆▼▲♥♦♠♣①②③④⑤⑥⑦⑧⑨⑩]/,/[一二三四五六七八九十百\\d]+刷/,/第[一二三四五六七八九十百\\d]+/,/(全体成员|报到|报道|来啦|签到|刷|打卡|我在|来了|考古|爱了|挖坟|留念|你好|回来|哦哦|重温|复习|重刷|再看|在看|前排|沙发|有人看|板凳|末排|我老婆|我老公|撅了|后排|周目|重看|包养|DVD|同上|同样|我也是|俺也|算我|爱豆|我家爱豆|我家哥哥|加我|三连|币|新人|入坑|补剧|冲了|硬了|看完|舔屏|万人|牛逼|煞笔|傻逼|卧槽|tm|啊这|哇哦)/", }, ], }, { name: "groupMinute", title: "合并去重分钟数,表示按n分钟分组后对弹幕合并去重", type: "input", placeholders: [ { title: "1分钟", value: "1", }, { title: "2分钟", value: "2", }, { title: "5分钟", value: "5", }, { title: "10分钟", value: "10", }, { title: "20分钟", value: "20", }, { title: "30分钟", value: "30", }, ], }, { name: "danmuLimit", title: "弹幕数量限制,单位为k,即千:默认0,表示不限制弹幕数", type: "input", placeholders: [ { title: "不限制", value: "0", }, { title: "10k", value: "10", }, { title: "8k", value: "8", }, { title: "6k", value: "6", }, { title: "4k", value: "4", }, { title: "2k", value: "2", }, ], }, { name: "danmuSimplifiedTraditional", title: "弹幕简繁体转换设置:default(默认不转换)、simplified(繁转简)、traditional(简转繁)", type: "input", placeholders: [ { title: "不转换", value: "default", }, { title: "繁转简", value: "simplified", }, { title: "简转繁", value: "traditional", }, ], }, { name: "danmuOffset", title: "弹幕时间偏移配置,格式:剧名:秒 或 剧名/季:秒 或 剧名/季/集:秒,正数表示弹幕延后(向右),负数表示弹幕提前(向左),多条用逗号分隔,示例:overlord/S01:90,re-zero/S02:120,re-zero/S02/E03:10;支持百分比模式,在路径/来源末尾添加 `%`,例如:`东方/S03/E02@tencent%:11`,按 `原时间 * (视频时长 + 偏移秒数) / 视频时长` 计算新的弹幕发送时间", type: "input", placeholders: [ { title: "示例", value: "", }, ], }, { name: "convertTopBottomToScroll", title: "顶部/底部弹幕转换为浮动弹幕,默认false", type: "input", placeholders: [ { title: "false", value: "false", }, { title: "true", value: "true", }, ], }, { name: "convertColor", title: "弹幕转换颜色配置,默认default(不转换)", type: "input", placeholders: [ { title: "不转换", value: "default", }, { title: "白色", value: "white", }, { title: "随机颜色(包括白色)", value: "color", }, ], }, { name: "colorPool", title: "自定义颜色池(CONVERT_COLOR为color时生效),不配置使用默认颜色池,格式:十进制颜色值逗号分隔", type: "input", placeholders: [ { title: "默认颜色池", value: "", }, ], }, { name: "likeSwitch", title: "点赞功能开关,默认true", type: "input", placeholders: [ { title: "开启", value: "true", }, { title: "关闭", value: "false", }, ], }, // 系统配置 { name: "proxyUrl", title: "代理/反代地址,目前只对巴哈姆特和TMDB API生效", type: "input", placeholders: [ { title: "如果添加了巴哈源且访问不了,请填写", value: "", }, { title: "正常代理示例", value: "http://127.0.0.1:7890", }, { title: "万能反代示例", value: "@http://127.0.0.1", }, { title: "特定反代示例1", value: "bahamut@http://127.0.0.1", }, { title: "特定反代示例2", value: "tmdb@http://127.0.0.1", }, ], }, { name: "tmdbApiKey", title: "TMDB API密钥,目前只对巴哈姆特生效,配置后并行从TMDB获取日语原名搜索巴哈", type: "input", placeholders: [ { title: "如果添加了巴哈源,想自动获取日语原名搜索巴哈,请填写", value: "", }, { title: "示例", value: "a1b2xxxxxxxxxxxxxxxxxxx", }, ], } ], modules: [ { id: "searchDanmu", title: "搜索弹幕", functionName: "searchDanmu", type: "danmu", params: [], }, { id: "getDetail", title: "获取详情", functionName: "getDetailById", type: "danmu", params: [], }, { id: "getComments", title: "获取弹幕", functionName: "getCommentsById", type: "danmu", params: [], }, { id: "getDanmuWithSegmentTime", title: "获取指定时刻弹幕", functionName: "getDanmuWithSegmentTime", type: "danmu", params: [], } ], }; // 在浏览器环境中设置全局变量(ForwardWidget系统使用) if (typeof window !== 'undefined') { window.WidgetMetadata = WidgetMetadata; } // 初始化全局配置 let globals; async function initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch) { // 将传入的参数设置到环境变量中,以便Globals可以访问它们 const env = {}; if (sourceOrder !== undefined) env.SOURCE_ORDER = sourceOrder; if (otherServer !== undefined) env.OTHER_SERVER = otherServer; if (customSourceApiUrl !== undefined) env.CUSTOM_SOURCE_API_URL = customSourceApiUrl; if (vodServers !== undefined) env.VOD_SERVERS = vodServers; if (vodReturnMode !== undefined) env.VOD_RETURN_MODE = vodReturnMode; if (vodRequestTimeout !== undefined) env.VOD_REQUEST_TIMEOUT = vodRequestTimeout; if (bilibiliCookie !== undefined) env.BILIBILI_COOKIE = bilibiliCookie; if (doubanCookie !== undefined) env.DOUBAN_COOKIE = doubanCookie; if (platformOrder !== undefined) env.PLATFORM_ORDER = platformOrder; if (episodeTitleFilter !== undefined) env.EPISODE_TITLE_FILTER = episodeTitleFilter; if (enableAnimeEpisodeFilter !== undefined) env.ENABLE_ANIME_EPISODE_FILTER = enableAnimeEpisodeFilter; if (strictTitleMatch !== undefined) env.STRICT_TITLE_MATCH = strictTitleMatch; if (titleMappingTable !== undefined) env.TITLE_MAPPING_TABLE = titleMappingTable; if (animeTitleFilter !== undefined) env.ANIME_TITLE_FILTER = animeTitleFilter; if (animeTitleSimplified !== undefined) env.ANIME_TITLE_SIMPLIFIED = animeTitleSimplified; if (blockedWords !== undefined) env.BLOCKED_WORDS = blockedWords; if (groupMinute !== undefined) env.GROUP_MINUTE = groupMinute; if (danmuLimit !== undefined) env.DANMU_LIMIT = danmuLimit; if (danmuSimplifiedTraditional !== undefined) env.DANMU_SIMPLIFIED_TRADITIONAL = danmuSimplifiedTraditional; if (danmuOffset !== undefined) env.DANMU_OFFSET = danmuOffset; if (convertTopBottomToScroll !== undefined) env.CONVERT_TOP_BOTTOM_TO_SCROLL = convertTopBottomToScroll; if (convertColor !== undefined) env.CONVERT_COLOR = convertColor; if (colorPool !== undefined) env.COLOR_POOL = colorPool; if (likeSwitch !== undefined) env.LIKE_SWITCH = likeSwitch; if (proxyUrl !== undefined) env.PROXY_URL = proxyUrl; if (tmdbApiKey !== undefined) env.TMDB_API_KEY = tmdbApiKey; if (!globals) { globals = Globals.init(env); } await getCaches(); return globals; } // 获取变量数据 async function getCaches() { if (globals.animes.length === 0) { log("info", 'getCaches start.'); const [kv_animes, kv_episodeIds, kv_episodeNum, kv_logBuffer, kv_lastSelectMap] = await Promise.all([ Widget.storage.get('animes'), Widget.storage.get('episodeIds'), Widget.storage.get('episodeNum'), Widget.storage.get('logBuffer'), Widget.storage.get('lastSelectMap'), ]); globals.animes = kv_animes ? (typeof kv_animes === 'string' ? JSON.parse(kv_animes) : kv_animes) : globals.animes; globals.episodeIds = kv_episodeIds ? (typeof kv_episodeIds === 'string' ? JSON.parse(kv_episodeIds) : kv_episodeIds) : globals.episodeIds; globals.episodeNum = kv_episodeNum ? (typeof kv_episodeNum === 'string' ? JSON.parse(kv_episodeNum) : kv_episodeNum) : globals.episodeNum; globals.logBuffer = kv_logBuffer ? (typeof kv_logBuffer === 'string' ? JSON.parse(kv_logBuffer) : kv_logBuffer) : globals.logBuffer; // 特殊处理 Map if (kv_lastSelectMap) { const parsed = typeof kv_lastSelectMap === 'string' ? JSON.parse(kv_lastSelectMap) : kv_lastSelectMap; globals.lastSelectMap = new Map( Array.isArray(parsed) ? parsed : Object.entries(parsed) ); } } } // 存储更新后的变量 async function updateCaches() { log("info", 'updateCaches start.'); await Promise.all([ Widget.storage.set('animes', globals.animes), Widget.storage.set('episodeIds', globals.episodeIds), Widget.storage.set('episodeNum', globals.episodeNum), Widget.storage.set('logBuffer', globals.logBuffer), Widget.storage.set('lastSelectMap', JSON.stringify(Object.fromEntries(globals.lastSelectMap))) ]); } // 删除存储的变量 async function removeCaches() { log("info", 'removeCaches start.'); await Promise.all([ Widget.storage.remove('animes'), Widget.storage.remove('episodeIds'), Widget.storage.remove('episodeNum'), Widget.storage.remove('logBuffer'), Widget.storage.remove('lastSelectMap') ]); } const PREFIX_URL = "http://localhost:9321" async function searchDanmu(params) { const { tmdbId, type, title, season, link, videoUrl, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch } = params; await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch); let simplifiedTitle = title // 如果启用了搜索关键字繁转简,则进行转换 if (globals.animeTitleSimplified) { simplifiedTitle = simplized(title); log("info", `searchAnime converted traditional to simplified: ${title} -> ${simplifiedTitle}`); } const response = await searchAnime(new URL(`${PREFIX_URL}/api/v2/search/anime?keyword=${simplifiedTitle}`)); const resJson = await response.json(); const curAnimes = resJson.animes; // 开始排序数据,将匹配到季的数据挪到前面 let animes = []; if (curAnimes && curAnimes.length > 0) { animes = curAnimes; if (season) { // order by season const matchedAnimes = []; const nonMatchedAnimes = []; animes.forEach((anime) => { if (matchSeason(anime, simplifiedTitle, season) && !(anime.animeTitle.includes("电影") || anime.animeTitle.includes("movie"))) { matchedAnimes.push(anime); } else { nonMatchedAnimes.push(anime); } }); // Sort matched animes by title length (before first parenthesis) matchedAnimes.sort((a, b) => { const aLength = a.animeTitle.split('(')[0].length; const bLength = b.animeTitle.split('(')[0].length; return aLength - bLength; }); // Combine matched and non-matched animes, with matched ones at the front animes = [...matchedAnimes, ...nonMatchedAnimes]; } else { // order by type const matchedAnimes = []; const nonMatchedAnimes = []; animes.forEach((anime) => { if (anime.animeTitle.includes("电影") || anime.animeTitle.includes("movie")) { matchedAnimes.push(anime); } else { nonMatchedAnimes.push(anime); } }); // Sort matched animes by title length (before first parenthesis) matchedAnimes.sort((a, b) => { const aLength = a.animeTitle.split('(')[0].length; const bLength = b.animeTitle.split('(')[0].length; return aLength - bLength; }); // Combine matched and non-matched animes, with matched ones at the front animes = [...matchedAnimes, ...nonMatchedAnimes]; } } log("info", "animes: ", animes); await updateCaches(); return { animes: animes, }; } async function getDetailById(params) { const { animeId, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch } = params; await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch); const response = await getBangumi(`${PREFIX_URL}/api/v2/bangumi/${animeId}`); const resJson = await response.json(); log("info", "bangumi", resJson); await updateCaches(); return resJson.bangumi.episodes; } async function getCommentsById(params) { const { commentId, link, videoUrl, season, episode, tmdbId, type, title, segmentTime, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch } = params; await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch); if (commentId) { const storeKey = season && episode ? `${tmdbId}.${season}.${episode}` : `${tmdbId}`; const commentIdKey = `${storeKey}.${commentId}`; const segmentList = Widget.storage.get(storeKey); const lastCommentId = Widget.storage.get(commentIdKey); log("info", "storeKey:", storeKey); log("info", "commentIdKey:", commentIdKey); log("info", "commentId:", commentId); log("info", "lastCommentId:", lastCommentId); log("info", "segmentList:", segmentList); if (lastCommentId === commentId && segmentList) { return await getDanmuWithSegmentTime({ segmentTime, tmdbId, season, episode, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch }) } else { Widget.storage.remove(storeKey); Widget.storage.remove(commentIdKey); } const response = await getComment(`${PREFIX_URL}/api/v2/comment/${commentId}`, "json", true); const resJson = await response.json(); log("info", "segmentList:", resJson.comments.segmentList); Widget.storage.set(storeKey, resJson.comments.segmentList); Widget.storage.set(commentIdKey, commentId); console.log("segmentList", resJson.comments.segmentList); await updateCaches(); return resJson.comments.segmentList; } return null; } async function getDanmuWithSegmentTime(params) { const { segmentTime, tmdbId, season, episode, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch } = params; await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie, doubanCookie, platformOrder, episodeTitleFilter, enableAnimeEpisodeFilter, strictTitleMatch, titleMappingTable, animeTitleFilter, animeTitleSimplified, blockedWords, groupMinute, danmuLimit, danmuSimplifiedTraditional, danmuOffset, convertTopBottomToScroll, convertColor, colorPool, proxyUrl, tmdbApiKey, likeSwitch); const storeKey = season && episode ? `${tmdbId}.${season}.${episode}` : `${tmdbId}`; const segmentList = Widget.storage.get(storeKey); if (segmentList) { const segment = segmentList.find((item) => { const start = Number(item.segment_start); const end = Number(item.segment_end); const time = Number(segmentTime); return time >= start && time < end; }); log("info", "segment:", segment); const response = await getSegmentComment(segment); const resJson = await response.json(); await updateCaches(); return resJson; } return null; } // 导出函数以供ForwardWidgets调用 export { searchDanmu, getDetailById, getCommentsById, getDanmuWithSegmentTime };