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
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
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.comlib/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.comlib/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.comlib/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.comlib/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.comlib/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.comlib/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.预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
lohana20061023-29/dsh-media-downloader
DSH 媒体下载插件:粘一条链接自动弹选择器,B 站多画质 / 抖音无水印 / 小红书图文原图三套原生解析,YouTube 等站点交给 yt-dlp。封面可按原图、16:9、4:3 分开下,图文笔记整组图片可选…
相关插件 · 视觉能力
智能体工作台Devin-AXIS/iPolloWork
企业级、本地优先的智能体工作台,面向个人和智能体团队——一个工作区,容纳多个智能体引擎;一套统一体系,管理插件和技能;支…
★ 7,359已汉化DSH
72
开源设计工作台nexu-io/open-design
OpenDesign:首个协作式 AI 设计智能体工作区
★ 105,514已汉化DSH
71.8
多智能体编码工作区spacering-net/codeg
多智能体协作的 AI 编程工作区:汇总 Claude Code、Codex、OpenCode、Pi、Grok Build 等来源的会话。支持桌面应用、自托管服务器或 Docker。
★ 4,289已汉化DSH
71.6
GPT 图像提示词库freestylefly/awesome-gpt-image-2
Prompt as Code | GPT Image 2 / 2.5 提示词与案例库,530+ 个案例、20+…
★ 34,534已汉化DSH
71.5
架构图生成器tt-a1i/archify
把你想理解、规划或分享的事,变成可以互动的可视化作品。
★ 92,675已汉化DSH
71.2
AI 用量桌面小部件Javis603/token-monitor
EN | 简 | 繁 | KO | JA | PT
★ 3,522已汉化DSH
71