From 5ad8258d6d5990148d2d4e96b6cde60385449d6f Mon Sep 17 00:00:00 2001 From: huangxd- <649924757@qq.com> Date: Sun, 1 Feb 2026 22:20:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A5=BF=E7=93=9C=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=BA=90=EF=BC=8C=E5=8E=BB=E9=99=A4douyin=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=9B=BD=E5=A4=96=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E7=94=9F=E6=95=88=20(#177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - danmu_api/configs/globals.js | 2 +- danmu_api/sources/xigua.js | 134 +++++++---------------------------- danmu_api/worker.test.js | 6 +- 4 files changed, 30 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index a51124f..613d6a4 100644 --- a/README.md +++ b/README.md @@ -583,7 +583,6 @@ API 支持返回 Bilibili 标准 XML 格式的弹幕数据,通过查询参数 - 弹幕分片下载请求已加入重试机制,重试次数为1次 - 如果同时配置了本地缓存和redis缓存,则以redis缓存优先 - 弹弹play源用的是第三方接口,感谢开源项目 [danmaku-anywhere](https://github.com/Mr-Quin/danmaku-anywhere) -- 西瓜视频源因为部分调用了douyin接口,所以在国外环境部署的情况下会获取不到弹幕 - 有任何问题,如部署/环境变量配置等,可通过deepwiki对本项目进行提问,链接入口:https://deepwiki.com/huangxd-/danmu_api,其中项目内容一般每周刷新一次 ### 关联项目 diff --git a/danmu_api/configs/globals.js b/danmu_api/configs/globals.js index 58dff76..b35ba07 100644 --- a/danmu_api/configs/globals.js +++ b/danmu_api/configs/globals.js @@ -13,7 +13,7 @@ export const Globals = { accessedEnvVars: {}, // 静态常量 - VERSION: '1.13.5', + VERSION: '1.13.6', MAX_LOGS: 1000, // 日志存储,最多保存 1000 行 MAX_ANIMES: 100, diff --git a/danmu_api/sources/xigua.js b/danmu_api/sources/xigua.js index ec21e58..ccbedbb 100644 --- a/danmu_api/sources/xigua.js +++ b/danmu_api/sources/xigua.js @@ -12,73 +12,6 @@ import { SegmentListResponse } from '../models/dandan-model.js'; // 获取西瓜视频弹幕 // ===================== class XiguaSource extends BaseSource { - static get DEFAULT_PARAMS() { - return { - aid: '6383', - browser_language: 'zh-CN', - browser_name: 'Chrome', - browser_online: 'true', - browser_platform: 'Win32', - browser_version: '143.0.0.0', - channel: 'channel_pc_web', - cookie_enabled: 'true', - count: '5', - cpu_core_num: '8', - device_memory: '8', - device_platform: 'webapp', - disable_rs: '0', - downlink: '10', - effective_type: '4g', - enable_history: '1', - engine_name: 'Blink', - engine_version: '143.0.0.0', - from_group_id: '', - is_filter_search: '0', - keyword: '', - list_type: '', - need_filter_settings: '1', - offset: '0', - os_name: 'Windows', - os_version: '10', - pc_client_type: '1', - pc_libra_divert: 'Windows', - pc_search_top_1_params: '{"enable_ai_search_top_1":1}', - platform: 'PC', - query_correct_type: '1', - round_trip_time: '50', - screen_height: '1000', - screen_width: '1500', - search_channel: 'aweme_general', - search_source: 'search_history', - support_dash: '1', - support_h265: '1', - uifid: '5bdad390e71fd6e6e69e3cafe6018169c2447c8bc0b8484cc0f203a274f99fdb768a8c316d9404279513fcca88f12e4acf3daf31b4c0934dcd4d46cd5920c9d89bf45649141617920d4cdb2f3fdda79ac60881104f74e9b14137002479d35d2fd5e856ea254237dc0354a8b6ace97e28f2691a588ec6473fa26738653822bf3d5351084975e04ed4a489cf56fe39c626b0a45ade1ab50aeb053a0dd5bc5c8de1', - update_version_code: '0', - version_code: '190600', - version_name: '19.6.0', - webid: '7596462376888731182', - msToken: '0l5NrxqxQknMMk-jYG9YLxmAGb2EuNqXAR6hI6PBKHT900nrwhVgUr2Qsx34KjMmSRbr0SovUrGe_ZsMGOd9h9COVa_sX-L9prlQ2gbXTiN5IM8OZrLz244mGWcsGN1MrAwlEf_yZGJdAhqM3dHgY-IzlCvb6pt-d0R3F6al1BE72jxhrgnJoQ==', - a_bogus: 'xjURkz77ddR5FdFtmKO6HIQlwe2MNB8y0qT2W9VP7OYycHea7YPQ/NtDnoLtJVVU0mpzhedHBdBAGnxc0tXTZq9pzmkfuEwbFUQ99uvLMqNgTFkmLr8LewszKw0F0cTwl5cREARRIs0r2d5AVrIYlpIae5FqQYYdbrq6dZzbb9AxdSjH9xdXtBLAYqg=' - }; - } - - static get DEFAULT_HEADERS() { - return { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", - "priority": "u=1, i", - "referer": "https://www.douyin.com/", - "sec-ch-ua": `"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"`, - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": `"Windows"`, - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0 Safari/537.36", - "cookie": "hevc_supported=true; theme=%22light%22; enter_pc_once=1; UIFID_TEMP=5bdad390e71fd6e6e69e3cafe6018169c2447c8bc0b8484cc0f203a274f99fdb326bf4acda898300593c7e1f623bbba6500fe7dcbd7580d07a0e68fae95833a2f13084b12c6516e48ba9dd9ac43afcbe; douyin.com; device_web_cpu_core=8; device_web_memory_size=8; architecture=amd64; dy_swidth=1500; dy_sheight=1000; s_v_web_id=verify_mkivx1g8_9KjTnLMj_TDJH_4fz2_8ToU_y8BzctbKlL1r; odin_tt=41ae383e3bd114cabe012357d66de720f737aa8e393883e9d4b2c99ced2439c5a7b54adc3acaa44b77a6433841f9ad26c4b81619621774ab36f28a176791898336ffb503984b2cf1c9c7f6b4459ea044; strategyABtestKey=%221768689261.175%22; passport_csrf_token=ed476f2b5f5e931e1e17614c5cde4d9f; passport_csrf_token_default=ed476f2b5f5e931e1e17614c5cde4d9f; fpk1=U2FsdGVkX1/JAy5WNCrhFBjMkHT7B/7RlpR7d+vVcwXZczWmM9lcMBs5CwLEsScBe7Dsatiu6TIin3dMn36ujA==; fpk2=89db729cfcdc129111f017b0e7ac324a; __security_mc_1_s_sdk_crypt_sdk=edb86c34-4aca-ac4b; bd_ticket_guard_client_web_domain=2; is_dash_user=1; __ac_nonce=0696c0e7100ef6667155b; __ac_signature=_02B4Z6wo00f01VLtuUQAAIDC30UAzY-BQSlSzb3AAD3b83; UIFID=5bdad390e71fd6e6e69e3cafe6018169c2447c8bc0b8484cc0f203a274f99fdb768a8c316d9404279513fcca88f12e4acf3daf31b4c0934dcd4d46cd5920c9d89bf45649141617920d4cdb2f3fdda79ac60881104f74e9b14137002479d35d2fd5e856ea254237dc0354a8b6ace97e28f2691a588ec6473fa26738653822bf3d5351084975e04ed4a489cf56fe39c626b0a45ade1ab50aeb053a0dd5bc5c8de1; download_guide=%221%2F20260118%2F0%22; IsDouyinActive=true; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1500%2C%5C%22screen_height%5C%22%3A1000%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A50%7D%22; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCT29LQnc3cGI1UDA3SXVjSGF3Q1VzdWJIbTNKQW9iRzBoc3JkUmEyVTZ3d3NSbXhaTm1hL0c0N0JpSTQyc0JDaW0xQXBValJWZVBCV0RralIzRm5rd3M9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D; bd_ticket_guard_client_data_v2=eyJyZWVfcHVibGljX2tleSI6IkJPb0tCdzdwYjVQMDdJdWNIYXdDVXN1YkhtM0pBb2JHMGhzcmRSYTJVNnd3c1JteFpObWEvRzQ3QmlJNDJzQkNpbTFBcFVqUlZlUEJXRGtqUjNGbmt3cz0iLCJyZXFfY29udGVudCI6InNlY190cyIsInJlcV9zaWduIjoiaUZHdTNQNWhVM1p4bTE3cTlkUmJVbllZVUpjSGJGb2E2eHR6RnZNOVBYOD0iLCJzZWNfdHMiOiIjSHhKR3JqV1k5Q0tDVUdnL0M4b1d1ZGlYaVRxdHQxRDAxRmw4cDQ2T1lMVXkxWHp6a0tDeVBkRGVGV3NxIn0%3D; ttwid=1%7CFWlhAsF-KoGxsgQ9EqefnyQvtJIYKld-ph__Q9cOo1s%7C1768689835%7C35d17bdcfb25121d74d9ab3c196dbbf8c4b018635513ea702dae85eee8ab6ebc; biz_trace_id=5228a940; sdk_source_info=7e276470716a68645a606960273f276364697660272927676c715a6d6069756077273f276364697660272927666d776a68605a607d71606b766c6a6b5a7666776c7571273f275e5927666d776a686028607d71606b766c6a6b3f2a2a6a6f646363756d67636861646c66616e646d6b686c6d6c7566696860756a6e2a767164716c662a6f762a726a776e6077762b686c6b2b6f765927295927666d776a686028607d71606b766c6a6b3f2a2a6a6b607568647563676f6a6d6b60626168636d6b617560636f6e7575676f6e682a76682b6706b6169602b6f76592758272927666a6b766a69605a696c6061273f27636469766027292762696a6764695a7364776c6467696076273f275e582729277672715a646971273f2763646976602729277f6b5a666475273f2763646976602729276d6a6e5a6b6a716c273f2763646976602729276c6b6f5a7f6367273f27636469766027292771273f2733323531313d3c3d333d33234272927676c715a75776a716a666a69273f2763646976602778; bit_env=fhHsgC-D59fxwo1htOX9PqtjoLeQTGMPs2xSgYg3wgjDYNIPeyod0n9nJjPeUCICSQo38bu_1StrlP84kRKr27KShE047uVirtsW_9dyC7WalmzVk515ngDsuKgRvMry5oMQBd__NcymHfpIupKQeULTWwmserVKe-FaoX_nKu95PXEaxodj84i13Z7zaMPwjw5P558BGuTNiwYXBGeB7vbu4joQzz6o8LV-wimw_E-GgYi1W-9b01g9p9UV4wLh3ifbqyXmat3Uiub2d9T9PWFnQe2HEWdNjD3dGqXbi4D59lI4UopPnF99x4uJ_BNQF_-jY_eT6aML11ViPnTrZgGrhfq62UxsMWdj3MtrPGyK1C0MvWd4O-SqKUOMpImcAmqI2UGj1RCiX6q1_YCxTUP0SnqZcAYccCHWKEgdxVvmKIhjXBXtGFaCUd9nCPhXlaASWXPtw1pcim1RVIZLGngLSGnOYF4lhcQ0qFpRXAeWgwHSBEHGXPxglAJb6IxW; gulu_source_res=eyJwX2luIjoiMzRlYjBiNWI5YTNlY2RkMjY3ZGQzOTBkNjhjMjk1MGIzMjY2YmUyMDc3MWViYmZlMTIzNDM4ZDMxZmNkYTVjOCJ9; passport_auth_mix_state=sf4nq687herp6h2dofu6564iq8n4yc6kkgsnkhk53xivv6ux; home_can_add_dy_2_desktop=%221%22" - }; - } - async search(keyword) { try { const searchUrl = `https://m.ixigua.com/s/${keyword}`; @@ -166,12 +99,13 @@ class XiguaSource extends BaseSource { try { // https://www.douyin.com/lvdetail/6551333775337325060 // https://m.ixigua.com/video/6551333775337325060 - const albumId = id.split('/').pop(); - const detailQueryString = buildQueryString({...XiguaSource.DEFAULT_PARAMS, episode_id: albumId}); - const detailUrl = `https://www.douyin.com/aweme/v1/web/long/video/detail/?${detailQueryString}`; + const itemId = id.split('/').pop(); + const detailUrl = `https://m.ixigua.com/video/${itemId}`; const resp = await httpGet(detailUrl, { - headers: XiguaSource.DEFAULT_HEADERS + headers: { + "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/17.5 Mobile/15A5370a Safari/602.1" + }, }); // 判断 resp 和 resp.data 是否存在 @@ -180,13 +114,8 @@ class XiguaSource extends BaseSource { return []; } - // 判断 aweme_detail 是否存在 - if (!resp.data.aweme_detail) { - log("info", "getXiguaDetail: aweme_detail 不存在"); - return []; - } - - return resp.data.aweme_detail; + const match = resp.data.match(/"duration"\s*:\s*([\d.]+)/); + return match ? parseFloat(match[1]) : 0; } catch (error) { // 捕获请求中的错误 log("error", "getXiguaDetail error:", { @@ -200,18 +129,6 @@ class XiguaSource extends BaseSource { async getEpisodes(id) { try { - // const metaQueryString = buildQueryString({ - // ...XiguaSource.DEFAULT_PARAMS, - // album_id: "6551332131417620995", - // aweme_id_current: "7129807005509815588", - // episode_id_current: "6551333775349907981", - // }); - // const metaUrl = `https://www.douyin.com/aweme/v1/web/long/video/meta/?${metaQueryString}`; - - // const metaResp = await httpGet(metaUrl, { - // headers: XiguaSource.DEFAULT_HEADERS - // }); - const detailUrl = `https://m.ixigua.com/video/${id}`; const detailResp = await httpGet(detailUrl, { @@ -383,24 +300,32 @@ class XiguaSource extends BaseSource { async getEpisodeDanmuSegments(id) { log("info", "获取西瓜视频弹幕分段列表...", id); - const awemeDetail = await this.getDetail(id); - const awemeId = awemeDetail?.aweme_id; - const duration = awemeDetail?.duration; - log("info", "awemeId:", awemeId); + const itemId = id.split('/').pop(); + const duration = await this.getDetail(id) * 1000; + log("info", "itemId:", itemId); log("info", "duration:", duration); - const segmentDuration = 32000; // 每个分片32秒 + const segmentDuration = 300000; // 每个分片5分钟 const segmentList = []; for (let i = 0; i < duration; i += segmentDuration) { const segmentStart = i; // 转换为毫秒 const segmentEnd = Math.min(i + segmentDuration, duration); // 不超过总时长 + + const danmuQueryString = buildQueryString({ + item_id: itemId, + start_time: segmentStart, + end_time: segmentEnd, + format: "json" + }); + + const danmuUrl = `https://ib.snssdk.com/vapp/danmaku/list/v1/?${danmuQueryString}`; segmentList.push({ "type": "xigua", "segment_start": segmentStart, "segment_end": segmentEnd, - "url": awemeId + "url": danmuUrl }); } @@ -412,17 +337,10 @@ class XiguaSource extends BaseSource { async getEpisodeSegmentDanmu(segment) { try { - const danmuQueryString = buildQueryString({ - ...XiguaSource.DEFAULT_PARAMS, - group_id: segment.url, - item_id: segment.url, - start_time: segment.segment_start, - }); - - const danmuUrl = `https://www.douyin.com/aweme/v1/web/danmaku/get_v2/?${danmuQueryString}`; - - const response = await httpGet(danmuUrl, { - headers: XiguaSource.DEFAULT_HEADERS, + const response = await httpGet(segment.url, { + headers: { + "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/17.5 Mobile/15A5370a Safari/602.1" + }, retries: 1, }); @@ -430,7 +348,7 @@ class XiguaSource extends BaseSource { let contents = []; if (response && response.data) { const parsedData = typeof response.data === "string" ? JSON.parse(response.data) : response.data; - const danmakuList = parsedData.danmaku_list ?? []; + const danmakuList = parsedData.data ?? []; contents.push(...danmakuList); } diff --git a/danmu_api/worker.test.js b/danmu_api/worker.test.js index 258d53c..e7b3cf5 100644 --- a/danmu_api/worker.test.js +++ b/danmu_api/worker.test.js @@ -269,9 +269,9 @@ test('worker.js API endpoints', async (t) => { // await t.test('GET xigua segment danmu', async () => { // const segment = Segment.fromJson({ // type: 'xigua', - // segment_start: 64000, - // segment_end: 96000, - // url: '7129807005509815588' + // segment_start: 1200000, + // segment_end: 1500000, + // url: 'https://ib.snssdk.com/vapp/danmaku/list/v1/?item_id=6551333775341519368&start_time=1200000&end_time=1500000&format=json' // }); // const res = await xiguaSource.getSegmentComments(segment); // assert(res.length >= 0, `Expected res.length >= 0, but got ${res.length}`);