🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

媒体下载器 lohana20061023-29/dsh-media-downloader

DeepSeek Harness
lohana20061023-297 天前有提交已提供中文文档扫描:含敏感能力

DSH 媒体下载插件:粘一条链接自动弹选择器,B 站多画质 / 抖音无水印 / 小红书图文原图三套原生解析,YouTube 等站点交给 yt-dlp。封面可按原图、16:9、4:3 分开下,图文笔记整组图片可选…

31.5/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add lohana20061023-29/dsh-media-downloader
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · vision
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 105 处,展示前 94 条)
  • 读取本地文件
    index.js:34
    return JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version || '0.0.0';
  • 执行外部命令固定命令
    index.js:84
    execFile('reg', ['query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'],
  • 读取本地文件
    index.js:123
    const raw = JSON.parse(await readFile(STATE_FILE, 'utf8'));
  • 写入本地文件
    index.js:139
    await mkdir(dirname(STATE_FILE), { recursive: true });
  • 写入本地文件
    index.js:140
    await writeFile(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
  • 写入本地文件
    index.js:153
    writeFileSyncSync(file, JSON.stringify(t), 'utf8');
  • 写入本地文件
    index.js:175
    writeFileSyncSync(vbs, content, 'utf8');
  • 执行外部命令固定命令
    index.js:176
    toastProc = spawn('wscript.exe', ['//B', '//nologo', vbs], { windowsHide: true, stdio: 'ignore', detached: true });
  • 读取本地文件
    index.js:191
    try { return existsSyncSync(join(tmpdir(), 'dsh-media-safe-mode')); } catch { return true; }
  • 写入本地文件
    index.js:199
    try { writeFileSyncSync(join(tmpdir(), 'dsh-toast-enabled.txt'), on ? '1' : '0', 'utf8'); } catch {}
  • 读取本地文件
    index.js:213
    if (existsSyncSync(join(tmpdir(), 'dsh-media-no-watcher'))) return false;
  • 读取本地文件
    index.js:219
    const age = Date.now() - statSyncSync(hb).mtimeMs;
  • 写入本地文件
    index.js:235
    writeFileSyncSync(vbs, content, 'utf8');
  • 执行外部命令固定命令
    index.js:236
    clipWatchProc = spawn('wscript.exe', ['//B', '//nologo', vbs], { windowsHide: true, stdio: 'ignore', detached: true });
  • 删除本地文件
    index.js:472
    await rm(COOKIE_FILE, { force: true }).catch(() => {});
  • 读取本地文件
    index.js:552
    const age = Date.now() - statSyncSync(join(tmpdir(), 'dsh-corner-watch.hb')).mtimeMs;
  • 执行外部命令固定命令
    index.js:629
    spawn('explorer.exe', isDir ? [body.path] : ['/select,', body.path], { detached: true, stdio: 'ignore' }).unref();
  • 发起网络请求
    client.js:12
    const res = await fetch(API + path, body === undefined ? {} : {
  • 读取剪贴板
    client.js:303
    async function readClipboard() {
  • 读取剪贴板
    client.js:306
    return await navigator.clipboard.readText();
  • 读取剪贴板
    client.js:321
    const link = pickLink(await readClipboard());
  • 执行外部命令固定命令
    lib/bili.mjs:80
    const child = spawn('curl.exe', [
  • 发起网络请求
    lib/bili.mjs:100
    const res = await fetch(url, { headers: headers() });
  • 请求外部主机api.bilibili.com
    lib/bili.mjs:114
    const res = await fetch('https://api.bilibili.com/x/web-interface/nav', { headers: headers() });
  • 发起网络请求
    lib/bili.mjs:114
    const res = await fetch('https://api.bilibili.com/x/web-interface/nav', { headers: headers() });
  • 请求外部主机passport.bilibili.com
    lib/bili.mjs:125
    const res = await fetch('https://passport.bilibili.com/x/passport-login/web/qrcode/generate', {
  • 发起网络请求
    lib/bili.mjs:125
    const res = await fetch('https://passport.bilibili.com/x/passport-login/web/qrcode/generate', {
  • 发起网络请求
    lib/bili.mjs:145
    const res = await fetch(
  • 请求外部主机api.bilibili.com
    lib/bili.mjs:179
    const res = await fetch('https://api.bilibili.com/x/web-interface/nav', { headers: headers() });
  • 发起网络请求
    lib/bili.mjs:179
    const res = await fetch('https://api.bilibili.com/x/web-interface/nav', { headers: headers() });
  • 请求外部主机api.bilibili.com
    lib/bili.mjs:204
    const res = await fetch(`https://api.bilibili.com${path}?${q}`, {
  • 发起网络请求
    lib/bili.mjs:204
    const res = await fetch(`https://api.bilibili.com${path}?${q}`, {
  • 写入本地文件
    lib/bili.mjs:357
    const out = createWriteStream(filePath);
  • 写入本地文件
    lib/bili.mjs:378
    await writeFile(filePath, Buffer.from(await res.arrayBuffer()));
  • 执行外部命令来自配置/变量
    lib/bili.mjs:419
    try { child = spawn(exe, ['-hide_banner', '-version'], { windowsHide: true, stdio: 'ignore' }); }
  • 执行外部命令固定命令
    lib/bili.mjs:451
    execFile('where', [cmd], { windowsHide: true }, (err, stdout) => {
  • 执行外部命令来自配置/变量
    lib/bili.mjs:460
    const child = spawn(exe, args, { windowsHide: true, signal });
  • 写入本地文件
    lib/bili.mjs:474
    await rename(from, to);
  • 写入本地文件
    lib/bili.mjs:477
    await copyFile(from, to);
  • 删除本地文件
    lib/bili.mjs:478
    await rm(from, { force: true });
  • 写入本地文件
    lib/bili.mjs:573
    await mkdir(req.outDir, { recursive: true });
  • 删除本地文件
    lib/bili.mjs:609
    await rm(tmpV, { force: true });
  • 删除本地文件
    lib/bili.mjs:620
    await rm(tmpA, { force: true });
  • 写入本地文件
    lib/bili.mjs:646
    await writeFile(file, img.body);
  • 删除本地文件
    lib/bili.mjs:679
    await rm(tmpA, { force: true });
  • 执行外部命令来自配置/变量
    lib/ytdlp.mjs:42
    try { child = spawn(exe, ['--version'], { windowsHide: true, stdio: 'ignore' }); }
  • 执行外部命令来自配置/变量
    lib/ytdlp.mjs:60
    const child = spawn(exe, args, { windowsHide: true, signal });
  • 读取本地文件
    lib/ytdlp.mjs:188
    try { names = await readdir(dir); } catch { return null; }
  • 读取本地文件
    lib/ytdlp.mjs:194
    try { st = await stat(full); } catch { continue; }
  • 写入本地文件
    lib/ytdlp.mjs:217
    await mkdir(req.outDir, { recursive: true });
  • 发起网络请求
    lib/ytdlp.mjs:338
    const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
  • 读取本地文件
    lib/browser-login.mjs:80
    for (const b of BROWSERS) if (b.exe && existsSync(b.exe)) return b;
  • 发起网络请求
    lib/browser-login.mjs:121
    const info = await (await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(1200) })).json();
  • 执行外部命令固定命令
    lib/browser-login.mjs:132
    execFile('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true }, () => {});
  • 读取本地文件
    lib/browser-login.mjs:180
    try { return JSON.parse(await readFile(NAMES_FILE, 'utf8')); } catch { return {}; }
  • 写入本地文件
    lib/browser-login.mjs:189
    await writeFile(NAMES_FILE, JSON.stringify(all, null, 2), 'utf8');
  • 发起网络请求
    lib/browser-login.mjs:196
    const targets = await (await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(2500) })).json();
  • 请求外部主机www.xiaohongshu.com
    lib/browser-login.mjs:229
    const res = await fetch('https://www.xiaohongshu.com/explore', {
  • 发起网络请求
    lib/browser-login.mjs:229
    const res = await fetch('https://www.xiaohongshu.com/explore', {
  • 发起网络请求
    lib/browser-login.mjs:297
    const res = await fetch(`http://127.0.0.1:${p}/json/version`, { signal: AbortSignal.timeout(300) });
  • 写入本地文件
    lib/browser-login.mjs:319
    await mkdir(PROFILE_DIR, { recursive: true });
  • 执行外部命令来自配置/变量
    lib/browser-login.mjs:322
    const child = spawn(browser.exe, [
  • 发起网络请求
    lib/browser-login.mjs:355
    const r = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(500) });
  • 发起网络请求
    lib/browser-login.mjs:424
    targets = await (await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(2000) })).json();
  • 写入本地文件
    lib/browser-login.mjs:444
    await mkdir(ROOT, { recursive: true });
  • 读取本地文件
    lib/browser-login.mjs:453
    const text = await readFile(COOKIE_FILE, 'utf8');
  • 写入本地文件
    lib/browser-login.mjs:469
    await writeFile(COOKIE_FILE, header.concat(rows.map((r) => r.join('\t'))).join('\n') + '\n', 'utf8');
  • 读取本地文件
    lib/browser-login.mjs:480
    try { text = await readFile(COOKIE_FILE, 'utf8'); } catch { return {}; }
  • 读取本地文件
    lib/douyin.mjs:25
    const text = readFileSync(COOKIE_FILE, 'utf8');
  • 读取本地文件
    lib/douyin.mjs:42
    const saved = JSON.parse(readFileSync(TTWID_FILE, 'utf8'));
  • 请求外部主机ttwid.bytedance.com
    lib/douyin.mjs:56
    const res = await fetch('https://ttwid.bytedance.com/ttwid/union/register/', {
  • 发起网络请求
    lib/douyin.mjs:56
    const res = await fetch('https://ttwid.bytedance.com/ttwid/union/register/', {
  • 写入本地文件
    lib/douyin.mjs:70
    try { writeFileSync(TTWID_FILE, JSON.stringify({ ttwid: m[1], at: Date.now() }), 'utf8'); } catch { }
  • 发起网络请求
    lib/douyin.mjs:87
    const res = await fetch(current, {
  • 发起网络请求
    lib/douyin.mjs:120
    const res = await fetch(shareUrl, {
  • 发起网络请求
    lib/douyin.mjs:208
    const res = await fetch(url, { headers: STREAM_HEADERS, signal, redirect: 'follow' });
  • 写入本地文件
    lib/douyin.mjs:211
    const out = createWriteStream(filePath);
  • 写入本地文件
    lib/douyin.mjs:239
    await mkdir(req.outDir, { recursive: true });
  • 发起网络请求
    lib/douyin.mjs:263
    const res = await fetch(img.url, { headers: STREAM_HEADERS, signal: controller.signal });
  • 发起网络请求
    lib/douyin.mjs:282
    const res = await fetch(c.url, { headers: STREAM_HEADERS, signal: controller.signal });
  • 发起网络请求
    lib/xhs.mjs:53
    const res = await fetch(current, { redirect: 'manual', headers: { 'User-Agent': UA_MOBILE }, signal: AbortSignal.timeout(12000) });
  • 发起网络请求
    lib/xhs.mjs:96
    const res = await fetch(url, {
  • 发起网络请求
    lib/xhs.mjs:215
    const r = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(400) });
  • 读取本地文件
    lib/xhs.mjs:224
    const exe = BROWSERS.find((p) => existsSync(p));
  • 写入本地文件
    lib/xhs.mjs:227
    await mkdir(ROOT, { recursive: true });
  • 执行外部命令来自配置/变量
    lib/xhs.mjs:228
    const child = spawn(exe, [
  • 执行外部命令固定命令
    lib/xhs.mjs:235
    execFile('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true }, () => {});
  • 发起网络请求
    lib/xhs.mjs:240
    const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json();
  • 写入本地文件
    lib/xhs.mjs:319
    await mkdir(req.outDir, { recursive: true });
  • 发起网络请求
    lib/xhs.mjs:330
    const res = await fetch(c.url, { headers: H, signal: controller.signal });
  • 写入本地文件
    lib/xhs.mjs:334
    await writeFile(file, buf);
  • 发起网络请求
    lib/xhs.mjs:349
    const res = await fetch(url, { headers: H, signal: controller.signal, redirect: 'follow' });
  • 写入本地文件
    lib/xhs.mjs:361
    await writeFile(file, buf);
  • 写入本地文件
    lib/xhs.mjs:382
    await writeFile(file, buf);
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
涉及外部主机:api.bilibili.com、passport.bilibili.com、www.xiaohongshu.com、ttwid.bytedance.com

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
媒体下载 · dsh-media 给 DeepSeek Harness 的多平台下载插件:粘一条链接,弹出选择器,视频、图片、封面、头像按需勾。 B 站、抖音、小红书各有一套原生解析器,不依赖 yt-dlp 的站点适配;其余站点仍走 yt-dlp,所以覆盖面不缩水。 抖音和小红书不需要登录。B 站高清、YouTube 会员内容才需要。 媒体下载面板 中文优先。English readers: see English at the bottom. 为什么用它 官方界面本身没有下载能力,而网页版 B 站匿名只给 480P、抖音和小红书又各有一套风控。 这个插件做的是把这些差异藏起来——你只负责粘链接,剩下的按平台自动选对路子: - 一个输入口:面板图标、输入框、页面任意位置粘贴,三种方式都能唤起 - 该有的都能勾:视频画质、独立音轨(含转 MP3)、封面、图文图片(多张)、作者头像 - 不需要登录就别让你登录:抖音签匿名 ttwid、小红书用链接自带的 xsec_token - 登录不污染日常浏览器:独立 profile,cookie 只写进 ~/.dsh/bili-dl/ 支持的东西 | 平台 | 视频 | 图片 | 封面 | 作者头像 | 解析需要登录 |
展开完整文档(README 剩余 1 节)
媒体下载 · dsh-media

给 DeepSeek Harness 的多平台下载插件:粘一条链接,弹出选择器,视频、图片、封面、头像按需勾。

B 站、抖音、小红书各有一套原生解析器,不依赖 yt-dlp 的站点适配;其余站点仍走 yt-dlp,所以覆盖面不缩水。

抖音和小红书不需要登录。B 站高清、YouTube 会员内容才需要。

媒体下载面板

中文优先。English readers: see English at the bottom.

为什么用它

官方界面本身没有下载能力,而网页版 B 站匿名只给 480P、抖音和小红书又各有一套风控。
这个插件做的是把这些差异藏起来——你只负责粘链接,剩下的按平台自动选对路子:

- 一个输入口:面板图标、输入框、页面任意位置粘贴,三种方式都能唤起
- 该有的都能勾:视频画质、独立音轨(含转 MP3)、封面、图文图片(多张)、作者头像
- 不需要登录就别让你登录:抖音签匿名 ttwid、小红书用链接自带的 xsec_token
- 登录不污染日常浏览器:独立 profile,cookie 只写进 ~/.dsh/bili-dl/

支持的东西

| 平台 | 视频 | 图片 | 封面 | 作者头像 | 解析需要登录 |
| B 站 | 多画质 / 多编码 | — | 16:9 原图 | ✅ | 高清需要(匿名封顶 480P) |
| 抖音 | 无水印 | 图文多张 | ✅ | ✅ | 否 |
| 小红书 | H.264 / H.265 多档 | 图文原图 | ✅ | ✅ | 否 |
| YouTube | 由 yt-dlp 决定 | — | ✅ | ✅ | 会员/年龄限制内容需要 |
| X | 由 yt-dlp 决定 | — | ✅ | ✅ | 受保护账号需要 |
| TikTok | 由 yt-dlp 决定 | — | ✅ | ✅ | 部分视频需要 |
| 其它 | yt-dlp 支持的站点均可 | — | ✅ | — | 视站点而定 |

音频:B 站可单独下(多码率 / 转 MP3);视频一律自带音轨。

安装

这是 DSH 插件,不是独立程序。

git clone https://github.com/lohana20061023-29/dsh-media-downloader.git

然后在 DSH 里挂成本地链接:

plugin_manager install_bundle target="link:/path/to/dsh-media-downloader"

包名 dsh-media-downloader(仓库名是 dsh-media-downloader,两者不同属正常),宿主入口 index.js,客户端入口 client.js。

依赖:

- ffmpeg — 合流与转码。优先用 PATH 里的,找不到会回退到内置候选路径。
- yt-dlp — 非原生平台。首次使用会自动下载到 ~/.dsh/bili-dl/bin/。
- 系统代理会被自动识别(Windows「Internet 选项」),也可在面板里改。

更新与卸载

更新:拉取新版本后,在 DSH 里重新挂载一次即可,插件不写自己的安装目录。

cd <插件目录> && git pull
plugin_manager install_bundle target="link:<插件目录>"

卸载:

plugin_manager remove_bundle target="dsh-media-downloader"

下载记录、cookie、登录态都放在 ~/.dsh/bili-dl/,卸载不会删除。要彻底清干净就删掉这个目录。

使用

面板:点输入框旁的小图标,或直接在页面任意位置粘贴链接(自动弹出)。

工具调用(给 Agent 用):

{ "url": "https://www.bilibili.com/video/BV...", "quality": "1080" }

工具名 media_fetch,返回落地文件的绝对路径。

三个原生引擎怎么工作

| 平台 | 入口 | 关键点 |
| B 站 | 官方 web API + WBI 签名 | x/player/wbi/playurl 取 DASH 流,ffmpeg -c copy 合流 |
| 抖音 | 移动端分享页 SSR | _ROUTER_DATA → play_addr,/playwm/ 改 /play/ 去水印;只需匿名 ttwid |
| 小红书 | 移动端分享页 SSR | __INITIAL_STATE__ → noteData.data.noteData;靠链接里的 xsec_token,不需要登录 |

登录窗口是独立 profile 的浏览器实例,登录态只写进 ~/.dsh/bili-dl/,不碰日常浏览器的数据。

已知限制

- B 站匿名最高 480P,高清必须登录。
- B 站封面:只提供 16:9 原图。B 站另有一份 4:3 素材(cover43),但只对稿件作者本人可见,公开链接拿不到;按比例裁切会损失画面,所以不再提供裁切选项。
- 小红书链接必须带 xsec_token(App 里「分享 → 复制链接」得到的就有);只有笔记 ID 时会退回浏览器读取,较慢。
- 抖音首次解析要签一个匿名 ttwid(约 1 秒),之后缓存 7 天。
平台改版会让 SSR 字段路径失效,此时会退回 yt-dlp 或直接报错,不会静默返回错误数据。

兼容性

| 项 | 要求 |
| DSH | Web GUI,需要 webServer 与 tools 服务 |
| Node | ≥ 18(用到 AbortSignal.timeout、原生 fetch) |
| 系统 | Windows 优先;ffmpeg 与 yt-dlp 的自动探测按 Windows 路径写,其它系统需自行装好并加进 PATH |
| 浏览器 | 登录窗口依赖 Edge 或 Chrome |

合规

仅供个人离线观看/备份使用。请遵守各平台的服务条款与当地法律,不要用于二次分发或商业用途。

本项目不涉及 DRM 绕过,也不破解加密流;仅使用平台公开下发的页面数据与播放地址。

目录

index.js 宿主:路由注册、工具定义、图片代理 client.js 客户端:面板 UI(受限 React 子集) lib/bili.mjs B 站:WBI、probe、下载 lib/douyin.mjs 抖音:SSR 解析 + ttwid 签发 lib/xhs.mjs 小红书:移动端 SSR 解析 lib/ytdlp.mjs yt-dlp 封装 lib/browser-login.mjs 登录窗口(CDP) lib/qr.mjs 零依赖二维码编码 docs/架构说明.md 模块划分与数据流

开发

git clone https://github.com/lohana20061023-29/dsh-media-downloader.git
plugin_manager install_bundle target="link:<克隆目录>"

改完 client.js 刷新页面即可;改 index.js 或 lib/ 需要重载插件(remove_bundle + install_bundle)。

改文件时注意编码:client.js 若带上 UTF-8 BOM,模块会加载失败、界面整个消失且宿主无报错。用编辑器保存为「UTF-8 无 BOM」。

目录职责见 docs/架构说明.md,发版流程见 docs/发版流程.md。

更新日志

见 CHANGELOG.md。

许可

MIT © 2026 dsh-media contributors

用、改、再分发都可以,保留版权声明即可。

赞助

如果这个插件帮你省了时间,可以请我喝杯咖啡。两个码长期有效,金额随意。

dsh-media-downloader is a DeepSeek Harness plugin that downloads media from Bilibili, Douyin (TikTok China), Xiaohongshu (RED) and any site yt-dlp supports.

- Bilibili — official web API with WBI signing; up to 4K when logged in, DASH streams remuxed losslessly with ffmpeg
- Douyin — share-page SSR; watermark-free video, image posts, covers, author avatars. No login needed (an anonymous ttwid is minted automatically)
- Xiaohongshu — mobile share page; image posts in original resolution and H.264/H.265 video streams. No login needed (uses the xsec_token embedded in the share link)
- Everything else — delegated to yt-dlp

Paste a link anywhere in the DSH web UI and a picker appears. Pick video quality, audio track, cover ratio, images or the author avatar; files land in your configured folder.

Install: plugin_manager install_bundle target="link:/path/to/repo".
Agents can call the media_fetch tool with {"url": "...", "quality": "1080"}.

License: MIT.
预览分享卡片长什么样 ↓lohana20061023-29/dsh-media-downloader
分享 · DeepSeek Harness Hub
lohana20061023-29/dsh-media-downloader

DSH 媒体下载插件:粘一条链接自动弹选择器,B 站多画质 / 抖音无水印 / 小红书图文原图三套原生解析,YouTube 等站点交给 yt-dlp。封面可按原图、16:9、4:3 分开下,图文笔记整组图片可选…

回到顶部