// ==UserScript== // @name Bilibili 收藏夹 JSON 查看/保存 // @namespace https://space.bilibili.com/398910090 // @version 2.0 // @description 仅在非私密收藏夹界面可用,私密收藏夹不可用,可查看和保存收藏夹界面的json,可点击收藏夹视频卡片菜单的查看json按钮来查看单个视频的json数据 // @author Ace // @match https://space.bilibili.com/*/favlist* // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (() => { 'use strict'; const cfg = { api: 'https://api.bilibili.com/x/v3/fav/resource/list', ps: 40 }; const getMid = () => new URLSearchParams(location.search).get('fid'); const buildUrl = (mid, pn = 1) => `${cfg.api}?media_id=${mid}&pn=${pn}&ps=${cfg.ps}&keyword=&order=mtime&type=0&tid=0&platform=web&web_location=333.1387`; const getHeaders = () => ({ 'User-Agent': navigator.userAgent, 'Referer': location.href }); const fetchJSON = async (mid, pn = 1) => { const res = await fetch(buildUrl(mid, pn), { headers: getHeaders() }); const json = await res.json(); if (json.code !== 0) throw new Error(json.message || '接口错误'); return json; }; const fetchAll = async (mid) => { const list = []; let pn = 1, hasMore = true; while (hasMore) { const { data } = await fetchJSON(mid, pn); list.push(...data.medias); hasMore = data.has_more; pn++; } return { code: 0, data: { medias: list } }; }; const openTab = (obj) => { const html = ` JSON 浏览器
`; const w = window.open('', '_blank'); w.document.write(html); w.document.close(); }; const saveFile = (obj) => { const name = prompt('文件名:', GM_getValue('fname', 'favlist.json')) || 'favlist.json'; GM_setValue('fname', name); const blob = new Blob([JSON.stringify(obj, null, 2)], { type: 'application/json' }); const u = URL.createObjectURL(blob); const a = Object.assign(document.createElement('a'), { href: u, download: name }); a.click(); URL.revokeObjectURL(u); }; const addJsonButton = () => { const observer = new MutationObserver(() => { document.querySelectorAll('.bili-video-card__info--right').forEach((menu) => { if (!menu.querySelector('.view-json-btn')) { const btn = document.createElement('button'); btn.textContent = '查看 JSON 数据'; btn.className = 'view-json-btn'; btn.style.cssText = 'margin-left: 10px; cursor: pointer; color: #00a1d6; border: none; background: none;'; btn.onclick = async () => { const videoCard = menu.closest('.bili-video-card'); const bvid = videoCard?.querySelector('.bili-video-card__info--tit a')?.href.match(/\/video\/(BV\w+)/)?.[1]; if (!bvid) return alert('无法获取视频 bvid'); const mid = getMid(); if (!mid) return alert('无法获取当前收藏夹 ID'); try { const allData = await fetchAll(mid); const videoData = allData.data.medias.find((item) => item.bvid === bvid); if (!videoData) return alert('未找到匹配的视频 JSON 数据'); openTab(videoData); } catch (e) { alert(e.message); } }; menu.appendChild(btn); } }); }); observer.observe(document.body, { childList: true, subtree: true }); }; const observeDynamicMenu = () => { const done = new WeakSet(); const injectBtn = (card) => { // 等菜单真正出现再塞按钮 const tryInject = () => { const menu = document.querySelector('.bili-card-dropdown-popper'); if (!menu || done.has(menu)) return; const btn = document.createElement('div'); btn.className = 'bili-card-dropdown-popper__item'; btn.textContent = '🔍 查看JSON'; btn.style.cssText = 'color:#00a1d6;cursor:pointer;white-space:nowrap;'; btn.onclick = async () => { const bvid = card.dataset.bsbBvid || card.querySelector('a[href*="/video/BV"]')?.href.match(/BV\w+/)?.[0]; if (!bvid) return alert('无法获取 bvid'); const mid = getMid(); if (!mid) return alert('无法获取收藏夹 ID'); try { const allData = await fetchAll(mid); const item = allData.data.medias.find(m => m.bvid === bvid); if (!item) return alert('未找到该视频 JSON'); openTab(item); } catch (e) { alert(e.message); } }; menu.appendChild(btn); done.add(menu); }; // 每 50ms 检查一次,最多 1 秒 let t = 0; const id = setInterval(() => { if (document.querySelector('.bili-card-dropdown-popper') || t++ > 20) { clearInterval(id); tryInject(); } }, 50); }; /* 悬停即注入,不用点击 */ document.body.addEventListener('mouseenter', (e) => { const card = e.target.closest('.bili-video-card'); if (card) injectBtn(card); }, true); }; /* ---------- 入口(每次点击都重新读取 fid) ---------- */ GM_registerMenuCommand('📖 查看 JSON(当前页)', async () => { const mid = getMid(); if (!mid) return alert('无法获取当前收藏夹 ID'); try { openTab(await fetchJSON(mid)); } catch (e) { alert(e.message); } }); GM_registerMenuCommand('💾 保存 JSON(全部)', async () => { const mid = getMid(); if (!mid) return alert('无法获取当前收藏夹 ID'); try { saveFile(await fetchAll(mid)); } catch (e) { alert(e.message); } }); // 启动时添加 JSON 按钮 addJsonButton(); // 启动时监听动态菜单 observeDynamicMenu(); })();