您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
检测收藏夹中被UP主设置为仅自己可见的视频
当前为
// ==UserScript== // @name bilibili favlist hidden video detection // @name:zh-CN 哔哩哔哩(B站|Bilibili)收藏夹Fix(隐藏视频检测) // @name:zh-TW 嗶哩嗶哩(B站|Bilibili)收藏夾Fix(隱藏影片檢測) // @namespace http://tampermonkey.net/ // @version 7 // @description detect videos in favlist that only visiable to upper // @description:zh-CN 检测收藏夹中被UP主设置为仅自己可见的视频 // @description:zh-TW 檢測收藏夾中被UP主設定為僅自己可見的影片 // @author YTB0710 // @match https://space.bilibili.com/* // @connect bilibili.com // @grant GM_openInTab // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_cookie // ==/UserScript== (function () { 'use strict'; const localizedText = { 'UPDATES': { 'zh-CN': '更新内容:<br>其他: 优化脚本部分逻辑', 'zh-TW': '更新內容:<br>其他: 優化腳本部分邏輯' }, 'VIDEOS_PER_PAGE': { 'zh-CN': '每页展示视频数量', 'zh-TW': '每頁顯示影片數量' }, 'VIDEOS_PER_PAGE_WITH_PROMPT': { 'zh-CN': '每页最多可以展示的视频数量(一般为36或40)', 'zh-TW': '每頁最多可以顯示的影片數量(一般為36或40)' }, 'INPUT_AV_OR_BV_HERE': { 'zh-CN': '在此输入AV号或BV号', 'zh-TW': '在此輸入AV號或BV號' }, 'DETECT_HIDDEN_VIDEO': { 'zh-CN': '检测隐藏视频', 'zh-TW': '檢測隱藏影片' }, 'GET_VIDEO_INFO': { 'zh-CN': '查询视频信息', 'zh-TW': '查詢影片資訊' }, 'REMOVE_VIDEO': { 'zh-CN': '取消收藏', 'zh-TW': '取消收藏' }, 'ADD_VIDEO': { 'zh-CN': '添加收藏', 'zh-TW': '新增收藏' }, 'INPUT_AV': { 'zh-CN': '请输入AV号', 'zh-TW': '請輸入AV號' }, 'INPUT_BV': { 'zh-CN': '请输入BV号', 'zh-TW': '請輸入BV號' }, 'POSSIBLE_OPERATIONS_IF_ERROR': { 'zh-CN': '如果出现问题, 以下操作可能有帮助: 切换至按最近收藏排序, 刷新页面', 'zh-TW': '如果出現問題, 以下操作可能有幫助: 切換至按最近收藏排序, 重新載入頁面' }, 'POSSIBLE_OPERATIONS_IF_ERROR_NEW_FRESH_PAGE': { 'zh-CN': '如果出现问题, 以下操作可能有帮助: 检查每页展示视频数量是否正确, 切换至按最近收藏排序, 刷新页面', 'zh-TW': '如果出現問題, 以下操作可能有幫助: 檢查每頁顯示影片數量是否正確, 切換至按最近收藏排序, 重新載入頁面' }, 'NO_HIDDEN_VIDEO_ON_THIS_PAGE': { 'zh-CN': '本页没有隐藏的视频', 'zh-TW': '本頁沒有隱藏的影片' }, 'POSITION_ON_THIS_PAGE': { 'zh-CN': '在本页的位置', 'zh-TW': '在本頁的位置' }, 'POSITION_ON_THIS_PAGE_WITH_PROMPT': { 'zh-CN': '在本页的位置(从1开始)', 'zh-TW': '在本頁的位置(從1開始)' }, 'AV': { 'zh-CN': 'AV号', 'zh-TW': 'AV號' }, 'BV': { 'zh-CN': 'BV号', 'zh-TW': 'BV號' }, 'API_RESPONSE_CONTENT': { 'zh-CN': 'B站接口响应内容:', 'zh-TW': 'B站介面回應內容:' }, 'FID_NOT_FOUND_ERROR': { 'zh-CN': '无法获取当前收藏夹的fid, 刷新页面可能有帮助', 'zh-TW': '無法獲取當前收藏夾的fid, 重新載入頁面可能有幫助' }, 'INVALID_VIDEOS_PER_PAGE_ERROR': { 'zh-CN': '每页展示视频数量不正确, 请重新输入', 'zh-TW': '每頁顯示影片數量不正確, 請重新輸入' }, 'REQUEST_TIMEOUT_ERROR': { 'zh-CN': '请求超时', 'zh-TW': '請求逾時' }, 'REQUEST_FAILED_ERROR': { 'zh-CN': '请求失败', 'zh-TW': '請求失敗' }, 'COOKIE_READ_ERROR': { 'zh-CN': '无法读取cookie, 更新Tampermonkey可能有帮助', 'zh-TW': '無法讀取cookie, 更新Tampermonkey可能有幫助' }, 'UNKNOWN_ERROR': { 'zh-CN': '发生未知错误, 请反馈该问题', 'zh-TW': '發生未知錯誤, 請反饋該問題' }, }; const currentVersion = 7; const preferredLanguage = getPreferredLanguage(); const AVRegex = /^[1-9]\d*$/; const BVRegex = /^BV[A-Za-z0-9]{10}$/; const startsWithAVRegex = /^av/i; const favlistURLRegex = /https:\/\/space\.bilibili\.com\/\d+\/favlist.*/; const pagenationCountRegex = /共 (\d+) 页 \/ (\d+) 个/; const fidFromURLRegex = /fid=(\d+)/; const BVFromURLRegex = /video\/(\w{12})/; let onFavlistPage = false; const sideObserver = new MutationObserver((mutations, observer) => { if (document.querySelector('div.favlist-aside')) { observer.disconnect(); main(true); return; } if (document.querySelector('div.fav-sidenav')) { observer.disconnect(); main(false); return; } }); checkURL(); const originalPushState = history.pushState; history.pushState = function (...args) { originalPushState.apply(this, args); checkURL(); }; const originalReplaceState = history.replaceState; history.replaceState = function (...args) { originalReplaceState.apply(this, args); checkURL(); }; window.addEventListener('popstate', checkURL); function checkURL() { if (favlistURLRegex.test(location.href)) { if (!onFavlistPage) { onFavlistPage = true; sideObserver.observe(document.body, { subtree: true, childList: true, attributes: false, characterData: false }); } } else { if (onFavlistPage) { onFavlistPage = false; sideObserver.disconnect(); } } } function getPreferredLanguage() { const languages = navigator.languages || [navigator.language]; for (const lang of languages) { if (lang === 'zh-CN') { return 'zh-CN'; } if (lang === 'zh-TW') { return 'zh-TW'; } if (lang === 'zh-HK') { return 'zh-TW'; } } return 'zh-CN'; } function getLocalizedText(key) { return localizedText[key][preferredLanguage]; } function main(newFreshSpace) { const newFreshSpaceAppend = newFreshSpace ? '-newFreshSpace' : ''; let videosPerPage; if (newFreshSpace) { videosPerPage = window.innerWidth < 1760 ? 40 : 36; } else { videosPerPage = 20; } const storedVersion = GM_getValue('version', 0); let displayUpdate = false; if (storedVersion !== currentVersion) { GM_setValue('version', currentVersion); if (storedVersion) { displayUpdate = true; } } const usageCount = GM_getValue(newFreshSpace ? 'usageCountNewFreshSpace' : 'usageCount', 0); const displayPrompt = usageCount < 10 ? '_WITH_PROMPT' : ''; if (displayPrompt) { GM_setValue(newFreshSpace ? 'usageCountNewFreshSpace' : 'usageCount', usageCount + 1); } const style = document.createElement('style'); style.textContent = ` .fix-div { padding: 2px; } .fix-div-newFreshSpace { padding: 2px 0; } .fix-divControls { border-top: 1px solid #e4e9f0; } .fix-divMessage { line-height: 1.5; } .fix-inputTextA-newFreshSpace, .fix-inputTextB, .fix-inputTextB-newFreshSpace { box-sizing: content-box; border: 1px solid #cccccc; border-radius: 3px; line-height: 1; } .fix-inputTextA-newFreshSpace { width: 20px; height: 16px; padding: 4px; font-size: 16px; } .fix-inputTextB { width: 150px; height: 14px; padding: 3px; font-size: 14px; } .fix-inputTextB-newFreshSpace { width: 170px; height: 16px; padding: 4px; font-size: 16px; } .fix-labelA-newFreshSpace { line-height: 1; } .fix-button, .fix-button-newFreshSpace { border: 1px solid #cccccc; border-radius: 3px; line-height: 1; cursor: pointer; } .fix-button { padding: 3px; font-size: 14px; } .fix-button-newFreshSpace { padding: 4px; font-size: 16px; } `; document.head.appendChild(style); const divSide = document.querySelector(newFreshSpace ? 'div.favlist-aside' : 'div.fav-sidenav'); if (!newFreshSpace) { divSide.querySelector('a.watch-later').style.borderBottom = '1px solid #eeeeee'; } const divControls = document.createElement('div'); divControls.classList.add('fix-div' + newFreshSpaceAppend); if (!newFreshSpace) { divControls.classList.add('fix-divControls'); } divSide.appendChild(divControls); let inputTextA; if (newFreshSpace) { const divVideosPerPage = document.createElement('div'); divVideosPerPage.classList.add('fix-div-newFreshSpace'); divControls.appendChild(divVideosPerPage); const labelA = document.createElement('label'); labelA.classList.add('fix-labelA-newFreshSpace'); labelA.innerText = getLocalizedText('VIDEOS_PER_PAGE' + displayPrompt); divVideosPerPage.appendChild(labelA); inputTextA = document.createElement('input'); inputTextA.type = 'text'; inputTextA.classList.add('fix-inputTextA-newFreshSpace'); inputTextA.value = videosPerPage; labelA.insertAdjacentElement('beforeend', inputTextA); } const divInputTextB = document.createElement('div'); divInputTextB.classList.add('fix-div' + newFreshSpaceAppend); divControls.appendChild(divInputTextB); const inputTextB = document.createElement('input'); inputTextB.type = 'text'; inputTextB.classList.add('fix-inputTextB' + newFreshSpaceAppend); inputTextB.placeholder = getLocalizedText('INPUT_AV_OR_BV_HERE'); divInputTextB.appendChild(inputTextB); const divButtonA = document.createElement('div'); divButtonA.classList.add('fix-div' + newFreshSpaceAppend); divControls.appendChild(divButtonA); const buttonA = document.createElement('button'); buttonA.type = 'button'; buttonA.classList.add('fix-button' + newFreshSpaceAppend); buttonA.innerText = getLocalizedText('DETECT_HIDDEN_VIDEO'); buttonA.addEventListener('click', async () => { try { clearMessage(); let fid; if (newFreshSpace) { const fidFromURLMatch = location.href.match(fidFromURLRegex); if (fidFromURLMatch) { fid = fidFromURLMatch[1]; } else { addMessage(getLocalizedText('FID_NOT_FOUND_ERROR')); return; } } else { fid = document.querySelector('.fav-item.cur').getAttribute('fid'); } let currentPageActualVideos; if (newFreshSpace) { currentPageActualVideos = document.querySelectorAll('div.bili-video-card__wrap'); } else { currentPageActualVideos = document.querySelectorAll('li.small-item'); } if (newFreshSpace) { // const pagenationBtnNums = document.querySelectorAll('button.vui_pagenation--btn-num'); // const totalPages = parseInt(pagenationBtnNums[pagenationBtnNums.length - 1].innerText, 10); // const totalVideos = parseInt(document.querySelector('.vui_sidebar-item--active > div.vui_sidebar-item-right').innerText, 10); const spanPagenationGoCount = document.querySelector('span.vui_pagenation-go__count'); if (spanPagenationGoCount) { const pagenationCountMatch = spanPagenationGoCount.innerText.match(pagenationCountRegex); const totalPages = parseInt(pagenationCountMatch[1], 10); const totalVideos = parseInt(pagenationCountMatch[2], 10); if (totalPages !== 1) { videosPerPage = parseInt(inputTextA.value, 10); if (videosPerPage < currentPageActualVideos.length || videosPerPage < Math.ceil(totalVideos / totalPages) || videosPerPage > Math.floor((totalVideos - 1) / (totalPages - 1))) { addMessage(getLocalizedText('INVALID_VIDEOS_PER_PAGE_ERROR')); return; } } } } let currentPage; if (newFreshSpace) { const pagenation = document.querySelector('button.vui_pagenation--btn-num.vui_button--active'); if (!pagenation) { currentPage = 1; } else { currentPage = parseInt(pagenation.innerText, 10); } } else { currentPage = parseInt(document.querySelector('li.be-pager-item-active > a').innerText, 10); } addMessage(getLocalizedText('POSSIBLE_OPERATIONS_IF_ERROR' + (newFreshSpace ? '_NEW_FRESH_PAGE' : '')), true); const response = await new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: 'GET', url: `https://api.bilibili.com/x/v3/fav/resource/ids?media_id=${fid}`, timeout: 5000, responseType: 'json', onload: (res) => resolve(res), onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))), ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR'))) }); }); const currentFavlistAVBVs = response.response.data; const startIndex = (currentPage - 1) * videosPerPage; const currentPageExpectedAVBVs = currentFavlistAVBVs.slice(startIndex, startIndex + videosPerPage); let currentPageActualBVs; if (newFreshSpace) { currentPageActualBVs = Array.from(currentPageActualVideos).map(video => video.querySelector('a.bili-cover-card').getAttribute('href').match(BVFromURLRegex)[1]); } else { currentPageActualBVs = Array.from(currentPageActualVideos).map(video => video.getAttribute('data-aid')); } const hiddenAVBVs = currentPageExpectedAVBVs.filter(currentPageExpectedAVBV => !currentPageActualBVs.includes(currentPageExpectedAVBV.bvid)); if (!hiddenAVBVs.length) { addMessage(getLocalizedText('NO_HIDDEN_VIDEO_ON_THIS_PAGE')); return; } hiddenAVBVs.forEach(hiddenAVBV => { addMessage(`${getLocalizedText('POSITION_ON_THIS_PAGE' + displayPrompt)}: ${currentPageExpectedAVBVs.findIndex(currentPageExpectedAVBV => currentPageExpectedAVBV.bvid === hiddenAVBV.bvid) + 1}`); addMessage(`${getLocalizedText('AV')}: ${hiddenAVBV.id}`); addMessage(`${getLocalizedText('BV')}: ${hiddenAVBV.bvid}`); }); } catch (error) { catchUnknownError(error); } }); divButtonA.appendChild(buttonA); const divButtonB = document.createElement('div'); divButtonB.classList.add('fix-div' + newFreshSpaceAppend); divControls.appendChild(divButtonB); const buttonB = document.createElement('button'); buttonB.type = 'button'; buttonB.classList.add('fix-button' + newFreshSpaceAppend); buttonB.innerText = getLocalizedText('GET_VIDEO_INFO'); buttonB.addEventListener('click', () => { try { const BV = inputTextB.value; if (!BVRegex.test(BV)) { addMessage(getLocalizedText('INPUT_BV')); return; } GM_openInTab(`https://www.biliplus.com/video/${BV}`, { active: true, insert: false, setParent: true }); GM_openInTab(`https://xbeibeix.com/video/${BV}`, { insert: false, setParent: true }); GM_openInTab(`https://www.jijidown.com/video/${BV}`, { insert: false, setParent: true }); } catch (error) { catchUnknownError(error); } }); divButtonB.appendChild(buttonB); const divButtonC = document.createElement('div'); divButtonC.classList.add('fix-div' + newFreshSpaceAppend); divControls.appendChild(divButtonC); const buttonC = document.createElement('button'); buttonC.type = 'button'; buttonC.classList.add('fix-button' + newFreshSpaceAppend); buttonC.innerText = getLocalizedText('REMOVE_VIDEO'); buttonC.addEventListener('click', () => { try { GM_cookie.list({ name: 'bili_jct' }, async (cookies, error) => { if (error) { addMessage(getLocalizedText('COOKIE_READ_ERROR')); addMessage(error.stack, true); console.error(error); return; } try { let AV = inputTextB.value; if (startsWithAVRegex.test(AV)) { AV = AV.slice(2); } if (!AVRegex.test(AV)) { addMessage(getLocalizedText('INPUT_AV')); return; } let fid; if (newFreshSpace) { const fidFromURLMatch = location.href.match(fidFromURLRegex); if (fidFromURLMatch) { fid = fidFromURLMatch[1]; } else { addMessage(getLocalizedText('FID_NOT_FOUND_ERROR')); return; } } else { fid = document.querySelector('.fav-item.cur').getAttribute('fid'); } const csrf = cookies[0].value; const data = `resources=${AV}%3A2&media_id=${fid}&platform=web&csrf=${csrf}`; const response = await new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: 'POST', url: 'https://api.bilibili.com/x/v3/fav/resource/batch-del', data: data, timeout: 5000, headers: { 'Content-Length': data.length, 'Content-Type': 'application/x-www-form-urlencoded' }, onload: (res) => resolve(res), onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))), ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR'))) }); }); const json = response.response; addMessage(getLocalizedText('API_RESPONSE_CONTENT')); addMessage(json, true); } catch (error) { catchUnknownError(error); } }); } catch (error) { catchUnknownError(error); } }); divButtonC.appendChild(buttonC); const divButtonD = document.createElement('div'); divButtonD.classList.add('fix-div' + newFreshSpaceAppend); divControls.appendChild(divButtonD); const buttonD = document.createElement('button'); buttonD.type = 'button'; buttonD.classList.add('fix-button' + newFreshSpaceAppend); buttonD.innerText = getLocalizedText('ADD_VIDEO'); buttonD.addEventListener('click', () => { try { GM_cookie.list({ name: 'bili_jct' }, async (cookies, error) => { if (error) { addMessage(getLocalizedText('COOKIE_READ_ERROR')); addMessage(error.stack, true); console.error(error); return; } try { let AV = inputTextB.value; if (startsWithAVRegex.test(AV)) { AV = AV.slice(2); } if (!AVRegex.test(AV)) { addMessage(getLocalizedText('INPUT_AV')); return; } let fid; if (newFreshSpace) { const fidFromURLMatch = location.href.match(fidFromURLRegex); if (fidFromURLMatch) { fid = fidFromURLMatch[1]; } else { addMessage(getLocalizedText('FID_NOT_FOUND_ERROR')); return; } } else { fid = document.querySelector('.fav-item.cur').getAttribute('fid'); } const csrf = cookies[0].value; const data = `rid=${AV}&type=2&add_media_ids=${fid}&csrf=${csrf}`; const response = await new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: 'POST', url: 'https://api.bilibili.com/x/v3/fav/resource/deal', data: data, timeout: 5000, headers: { 'Content-Length': data.length, 'Content-Type': 'application/x-www-form-urlencoded' }, onload: (res) => resolve(res), onerror: () => reject(Error(getLocalizedText('REQUEST_FAILED_ERROR'))), ontimeout: () => reject(Error(getLocalizedText('REQUEST_TIMEOUT_ERROR'))) }); }); const json = response.response; addMessage(getLocalizedText('API_RESPONSE_CONTENT')); addMessage(json, true); } catch (error) { catchUnknownError(error); } }); } catch (error) { catchUnknownError(error); } }); divButtonD.appendChild(buttonD); const divMessage = document.createElement('div'); divMessage.classList.add('fix-div' + newFreshSpaceAppend); divMessage.classList.add('fix-divMessage'); divControls.appendChild(divMessage); if (displayUpdate) { setTimeout(() => { addMessage(getLocalizedText('UPDATES')); }, 300); } function addMessage(msg, smallFontSize) { let px; if (smallFontSize) { px = newFreshSpace ? 11 : 10; } else { px = newFreshSpace ? 13 : 12; } const p = document.createElement('p'); p.innerHTML = msg; p.style.fontSize = `${px}px`; divMessage.appendChild(p); p.scrollIntoView({ behavior: 'instant', block: 'nearest' }); } function clearMessage() { while (divMessage.firstChild) { divMessage.removeChild(divMessage.firstChild); } } function catchUnknownError(error) { addMessage(getLocalizedText('UNKNOWN_ERROR')); addMessage(error.stack, true); console.error(error); } } })();