
Bilibili-Evolved「自动移出稍后再看」功能全解析实现原理与源码级拆解【免费下载链接】Bilibili-Evolved强大的哔哩哔哩增强脚本项目地址: https://gitcode.com/gh_mirrors/bi/Bilibili-Evolved导读本文围绕 Bilibili-Evolved哔哩哔哩增强脚本中的「自动移出稍后再看」autoRemoveWatchlater功能组件展开完整解读其功能定义、使用前提与限制并结合仓库源码剖析其监听播放结束 → 查询稍后再看列表 → 调用移除接口的完整调用链。读完本文你将理解该组件如何借助videoChange观察器、PlayerAgent 查询器和稍后再看 API 封装协同工作以及必须真正播放结束这一行为约束在代码层面的落地方式。功能概述文档说了什么组件关联文档位于 registry/lib/components/video/auto-remove-watchlater/index.md全文内容简洁而明确在稍后再看页面播放结束时自动将当前视频移出稍后再看。文档同时给出两条关键注意事项一定要播放结束快结束时手动切走不算——只有视频触发原生的ended事件才算播放结束用户手动拖进度条跳到最后、或中途切换视频都不会触发移除逻辑B 站的稍后再看列表不会实时刷新——调用移除接口后页面上的列表 DOM 不会立即更新需要手动刷新页面或等待列表重新加载。这两条注意点揭示了该组件的行为边界它不是一个看完就删的激进工具而是严格以播放器的播放完成事件为唯一触发信号。组件注册与运行范围组件入口位于 registry/lib/components/video/auto-remove-watchlater/index.ts通过defineComponentMetadata声明元数据export const component defineComponentMetadata({ name: autoRemoveWatchlater, displayName: 自动移出稍后再看, tags: [componentsTags.video], urlInclude: [...watchlaterUrls, ...videoUrls], entry: () { ... }, })几个值得展开的字段name/displayName内部标识与用户可见名称autoRemoveWatchlater即自动移出稍后再看tags归入video分类在设置面板中按分类检索urlInclude声明该组件仅在匹配的页面生效。展开后为稍后再看页面地址与通用视频页面地址的并集。urlInclude 背后的 URL 匹配规则watchlaterUrls与videoUrls定义在 src/core/utils/urls.ts/** 稍后再看页面 */ export const watchlaterUrls [ //www.bilibili.com/medialist/play/watchlater, //www.bilibili.com/list/watchlater, ] /** 含有普通视频的页面 */ export const videoUrls [//www.bilibili.com/video/, ...festivalUrls, ...mediaListUrls]其中mediaListUrls又由稍后再看、收藏夹连播、UP 主视频连播以及合集类页面共同构成export const mediaListUrls [ ...watchlaterUrls, ...favoriteListUrls, ...upListUrls, /\/\/www\.bilibili\.com\/list\//, ]也就是说该组件不仅覆盖新版/list/watchlater与旧版/medialist/play/watchlater稍后再看入口也覆盖/video/普通视频页与各类连播/合集页面——只要这些页面上有来自稍后再看队列的视频在播放播放结束后都可能触发移除。核心实现从播放结束到移出列表整体调用链组件的entry逻辑完整展示了核心流程entry: () { videoChange(async ({ aid }) { const videoElement await playerAgent.query.video.element() if (listener ! null) { videoElement.removeEventListener(ended, listener) } listener async () { const list await getWatchlaterList() if (list.includes(parseInt(aid))) { await toggleWatchlater(aid) } } videoElement.addEventListener(ended, listener) }) },可拆解为四个环节监听视频切换videoChange在视频aid/cid变化时回调获取播放器 video 元素通过playerAgent.query.video.element()异步查询绑定/解绑ended监听器切换视频前先移除旧监听再为新 video 元素挂载新监听闭包捕获当前aid播放结束时执行移除先拉取稍后再看列表确认当前 aid 确实在队列中再调用移除接口。关键约束一videoChange观察器videoChange定义于 src/core/observer.ts它会在以下时机触发回调组件注册时立即触发一次callback(getId())之后通过allMutations监听 DOM 变化当unsafeWindow.cid发生变化时派发videoChange自定义事件并再次调用回调。源码中还特别处理了 B 站代码的一个神秘行为更换 cid 时临时把cid改成一个数组监听逻辑会显式忽略这种值if (Array.isArray(newCid)) return避免误触发。这解释了为什么该组件在连播切换包括稍后再看自动连播时能正确跟随当前播放的视频。此外videoChange内部会先通过matchCurrentPage(playerUrls)校验当前页面是否包含播放器因此在非视频页面该组件不会空转。关键约束二ended事件是唯一触发信号文档强调一定要播放结束快结束时手动切走不算代码层面正是靠监听 video 元素的原生ended事件实现的ended只在视频播放到结尾时触发用户拖动进度条到末尾、中途暂停切走都不会触发该事件切换视频前先解绑旧监听if (listener ! null) videoElement.removeEventListener(ended, listener)防止上一个视频的监听器残留避免跨视频误移除。这里playerAgent.query.video.element()来自播放器代理层PlayerAgent其底层通过elementQuery包装选择器见 src/components/video/player-agent/base.ts支持异步轮询等待元素出现select与同步查询sync两种方式可适配 B 站新老播放器。播放器代理的具体实现分布在 src/components/video/player-agent/ 目录下v2/v3/v4 及 bpx 等适配器。关键约束三移除前二次确认监听器回调中并不是拿到 aid 就直接移除而是先调用getWatchlaterList()获取当前稍后再看列表仅当list.includes(parseInt(aid))时才执行toggleWatchlater(aid)。这种先查再删的设计避免对不在列表中的视频发起无效删除请求保证即使videoChange事件在非稍后再看场景如普通视频页触发也不会误删用户其他列表中的内容由于在稍后再看页面之外组件同样生效urlInclude覆盖了videoUrls这一确认步骤是防止误操作的保险。底层支撑稍后再看 API 封装getWatchlaterList与toggleWatchlater均封装在 src/components/video/watchlater.ts 中是理解该组件底层行为的关键。获取列表getWatchlaterListexport async function getWatchlaterList(raw false): Promisenumber[] | RawWatchlaterItem[] { const { getUID } await import(/core/utils) if (!getUID()) { console.warn([稍后再看列表] 账号未登录) return [] } const api https://api.bilibili.com/x/v2/history/toview/web const { getJsonWithCredentials } await import(/core/ajax) const response await getJsonWithCredentials(api) ... }几个实现细节未登录直接返回空数组列表接口依赖账号身份未登录时组件静默失效并给出警告日志接口地址https://api.bilibili.com/x/v2/history/toview/web带凭据请求响应校验response.code ! 0时记录错误日志并返回空列表response.data.list为空已清空列表时同步清空内存缓存本地缓存同步模块级维护watchlaterList: number[]缓存数组每次拉取会做差量更新删除已不存在的 aid、追加新增的 aidtoggleWatchlater成功后会同步更新该缓存。文件末尾还有一段requestIdleCallback在空闲时预取列表保证打开页面后缓存尽早就绪。移除/添加toggleWatchlaterexport const toggleWatchlater async (aid: string | number, add?: boolean | undefined) { const id parseInt(aid.toString()) if (Number.isNaN(id)) return if (add undefined) { add !watchlaterList.includes(id) } const api add ? https://api.bilibili.com/x/v2/history/toview/add : https://api.bilibili.com/x/v2/history/toview/del const { getCsrf } await import(/core/utils) const csrf getCsrf() const { postTextWithCredentials } await import(/core/ajax) const responseText await postTextWithCredentials(api, aid${aid}csrf${csrf}) ... }实现要点双向切换不传add时根据当前缓存自动判断在列表中则删除不在则添加。本组件只传aid即依赖这一自动判断等价于删除CSRF 校验调用 B 站接口需要携带csrf从全局获取否则接口会拒绝请求方式POST表单提交字段为aid与csrf结果同步接口返回code 0后同步维护本地缓存watchlaterList失败则记录稍后再看操作失败错误日志。文件顶部注释还给出了相邻接口的说明移除已观看走/x/v2/history/toview/delviewedtruecsrfcsrf清空走/x/v2/history/toview/clear方便对比理解接口族。关于列表不实时刷新的说明文档第二条注意点B 站的稍后再看列表不会实时刷新在代码中同样有迹可循toggleWatchlater成功删除后组件只更新了内存中的watchlaterList缓存并未对页面上的列表 DOM 做任何主动刷新操作。因此播放完成后稍后再看页面上的条目仍会停留在原处直到用户手动刷新页面或列表数据被重新拉取。这也是该组件与即时可见反馈类功能如 toast 提示、DOM 移除在设计上的差异——它优先保证服务端数据正确而不承诺 UI 实时同步。使用者应当知晓播放完一个视频后建议刷新稍后再看页面确认队列变化。使用建议与适用场景结合文档与源码可给出以下实操建议在哪里生效在稍后再看页面/list/watchlater或/medialist/play/watchlater连播时效果最佳视频播完自动从队列移除实现清空队列式的观看体验触发条件必须让视频自然播放到结尾ended事件触发后才会移除拖动进度条到末尾或中途切走均无效账号要求需登录 B 站账号未登录时列表接口返回空功能静默不生效列表刷新移除后列表页不实时刷新需要手动刷新页面确认如有大量视频需清空可结合收藏夹连播页面/list/ml批量播完依赖关系该组件依赖播放器代理层对当前 video 元素的正确查询若 B 站播放器结构变更导致选择器失效ended监听将无法挂载可从开发者工具观察播放器 DOM 是否被正确识别。总结「自动移出稍后再看」是一个小而精的增强组件以 index.md 定义行为边界以 index.ts 串联videoChangesrc/core/observer.ts、播放器代理src/components/video/player-agent/与稍后再看 API 封装src/components/video/watchlater.ts。它的设计处处体现谨慎先确认视频确实在列表中再删除、切换视频先解绑旧监听、未登录静默降级、不主动刷新列表 DOM。理解这层实现既能帮助你正确使用该功能也能为阅读 Bilibili-Evolved 其他基于videoChange与playerAgent的组件提供现成的范式参考。【免费下载链接】Bilibili-Evolved强大的哔哩哔哩增强脚本项目地址: https://gitcode.com/gh_mirrors/bi/Bilibili-Evolved创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考