您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
进入视频页面后, 自动添加视频到收藏夹中.
当前为
// ==UserScript== // @name BiliBili自动添加视频收藏 // @version 0.3.1 // @license GPL-3 // @namespace https://github.com/AliubYiero/TamperMonkeyScripts // @run-at document-start // @author Yiero // @homepage https://github.com/AliubYiero/TamperMonkeyScripts // @description 进入视频页面后, 自动添加视频到收藏夹中. // @match https://www.bilibili.com/video/* // @match https://www.bilibili.com/s/video/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant CAT_userConfig // @connect api.bilibili.com // @icon https://www.bilibili.com/favicon.ico // ==/UserScript== /* ==UserConfig== 配置项: favouriteTitle: title: 指定收藏夹标题 description: 更改指定收藏夹标题 type: text default: fun userUid: title: 用户uid description: 设置用户uid type: text default: "" ==/UserConfig== */ function getElement( parent, selector, timeout = 0 ) { return new Promise( ( resolve => { let result = parent.querySelector( selector ); if ( result ) { return resolve( result ); } let timer; const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver; if ( mutationObserver ) { const observer = new mutationObserver( ( mutations => { for ( let mutation of mutations ) { for ( let addedNode of mutation.addedNodes ) { if ( addedNode instanceof Element ) { result = addedNode.matches( selector ) ? addedNode : addedNode.querySelector( selector ); if ( result ) { observer.disconnect(); timer && clearTimeout( timer ); setTimeout( ( () => resolve( result ) ), 300 ); } } } } } ) ); observer.observe( parent, { childList: true, subtree: true, } ); if ( timeout > 0 ) { timer = setTimeout( ( () => { observer.disconnect(); return resolve( null ); } ), timeout ); } } else { const listener = e => { if ( e.target instanceof Element ) { result = e.target.matches( selector ) ? e.target : e.target.querySelector( selector ); if ( result ) { parent.removeEventListener( 'DOMNodeInserted', listener, true ); timer && clearTimeout( timer ); return resolve( result ); } } }; parent.addEventListener( 'DOMNodeInserted', listener, true ); if ( timeout > 0 ) { timer = setTimeout( ( () => { parent.removeEventListener( 'DOMNodeInserted', listener, true ); return resolve( null ); } ), timeout ); } } } ) ); } const userUidConfig = { key: 'userUid', }; const UserConfigs = { '\u914d\u7f6e\u9879': { favouriteTitle: { title: '\u6307\u5b9a\u6536\u85cf\u5939\u6807\u9898', description: '\u66f4\u6539\u6307\u5b9a\u6536\u85cf\u5939\u6807\u9898', type: 'text', default: 'fun', }, userUid: { title: '\u7528\u6237uid', description: '\u8bbe\u7f6e\u7528\u6237uid', type: 'text', default: '', }, }, }; class GMStorageExtra extends Storage { constructor() { super(); } static get length() { return this.keys().length; } static getItem( key, defaultValue, group ) { ( () => {} )( this.createKey( key, group ) ); return GM_getValue( this.createKey( key, group ), defaultValue ); } static hasItem( key, group ) { return Boolean( this.getItem( key, group ) ); } static setItem( key, value, group ) { GM_setValue( this.createKey( key, group ), value ); } static removeItem( key, group ) { GM_deleteValue( this.createKey( key, group ) ); } static clear() { const keyList = GM_listValues(); for ( const key of keyList ) { GM_deleteValue( key ); } } static key( index ) { return this.keys()[index]; } static keys() { return GM_listValues(); } static groups() { const keyList = this.keys(); return keyList.map( ( key => { const splitKeyList = key.split( '.' ); if ( splitKeyList.length === 2 ) { return splitKeyList[0]; } return ''; } ) ).filter( ( item => item ) ); } static createKey( key, group ) { if ( group ) { return `${ group }.${ key }`; } for ( let groupName in UserConfigs ) { const configGroup = UserConfigs[groupName]; for ( let configKey in configGroup ) { if ( configKey === key ) { return `${ groupName }.${ key }`; } } } return key; } } const getUserUid = async () => { let userUid = GMStorageExtra.getItem( userUidConfig.key, '' ); if ( !userUid ) { const selector = 'a.header-entry-mini[href^="//space.bilibili.com/"]'; await getElement( document, selector, 6e4 ); const userDom = document.querySelector( selector ); if ( !userDom ) { return ''; } userUid = new URL( userDom.href ).pathname.split( '/' )[1]; } return Promise.resolve( userUid ); }; const setUserUid = uid => { GMStorageExtra.setItem( userUidConfig.key, uid ); }; const favouriteTitleConfig = { key: 'favouriteTitle', title: 'fun', }; const getFavouriteTitle = () => GMStorageExtra.getItem( favouriteTitleConfig.key, favouriteTitleConfig.title ); const setFavouriteTitle = title => { GMStorageExtra.setItem( favouriteTitleConfig.key, title ); }; const hasFavouriteTitle = () => GMStorageExtra.hasItem( favouriteTitleConfig.key ); const codeConfig = { XOR_CODE: 23442827791579n, MASK_CODE: 2251799813685247n, MAX_AID: 1n << 51n, BASE: 58n, data: 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf', }; function bvToAv( bvid ) { const { MASK_CODE: MASK_CODE, XOR_CODE: XOR_CODE, data: data, BASE: BASE, } = codeConfig; const bvidArr = Array.from( bvid ); [ bvidArr[3], bvidArr[9] ] = [ bvidArr[9], bvidArr[3] ]; [ bvidArr[4], bvidArr[7] ] = [ bvidArr[7], bvidArr[4] ]; bvidArr.splice( 0, 3 ); const tmp = bvidArr.reduce( ( ( pre, bvidChar ) => pre * BASE + BigInt( data.indexOf( bvidChar ) ) ), 0n ); return Number( tmp & MASK_CODE ^ XOR_CODE ); } const checkScriptCatEnvironment = () => { let isScriptCatEnvironment = false; try { CAT_userConfig.toString(); isScriptCatEnvironment = true; } catch ( e ) { isScriptCatEnvironment = false; } return isScriptCatEnvironment; }; const requestConfig = { baseURL: 'https://api.bilibili.com', csrf: new URLSearchParams( document.cookie.split( '; ' ).join( '&' ) ).get( 'bili_jct' ) || '', }; const xhrRequest = ( url, method, data ) => { if ( !url.startsWith( 'http' ) ) { url = requestConfig.baseURL + url; } const xhr = new XMLHttpRequest; xhr.open( method, url ); xhr.withCredentials = true; xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); return new Promise( ( ( resolve, reject ) => { xhr.addEventListener( 'load', ( () => { const response = JSON.parse( xhr.response ); if ( response.code !== 0 ) { return reject( response.message ); } return resolve( response.data ); } ) ); xhr.addEventListener( 'error', ( () => reject( xhr.status ) ) ); xhr.send( new URLSearchParams( data ) ); } ) ); }; const api_collectVideoToFavorite = ( videoId, favoriteId ) => { const formData = { rid: videoId, type: '2', add_media_ids: favoriteId, csrf: requestConfig.csrf, }; return xhrRequest( '/x/v3/fav/resource/deal', 'POST', formData ); }; const api_createFavorites = favTitle => xhrRequest( '/x/v3/fav/folder/add', 'POST', { title: favTitle, privacy: 1, csrf: requestConfig.csrf, } ); function request( url, method = 'GET', paramOrData, GMXmlHttpRequestConfig = {} ) { if ( !url.startsWith( 'http' ) ) { url = requestConfig.baseURL + url; } if ( paramOrData && method === 'GET' ) { url += '?' + new URLSearchParams( paramOrData ).toString(); } else if ( paramOrData && method === 'POST' ) { GMXmlHttpRequestConfig.data = JSON.stringify( paramOrData ); } return new Promise( ( ( resolve, reject ) => { const newGMXmlHttpRequestConfig = { timeout: 2e4, ...GMXmlHttpRequestConfig, url: url, method: method, onload( response ) { resolve( JSON.parse( response.responseText ) ); }, onerror( error ) { reject( error ); }, ontimeout() { reject( new Error( 'Request timed out' ) ); }, }; GM_xmlhttpRequest( newGMXmlHttpRequestConfig ); } ) ); } const api_listAllFavorites = upUid => request( '/x/v3/fav/folder/created/list-all', 'GET', { up_mid: upUid, } ).then( ( res => { if ( res.code !== 0 ) { throw new Error( res.message ); } return res.data.list; } ) ); const getReadFavouriteList = async userUid => { const favoriteList = await api_listAllFavorites( userUid ); const favouriteTitle = getFavouriteTitle(); const readFavouriteList = favoriteList.filter( ( favoriteInfo => favoriteInfo.title.match( new RegExp( `^${favouriteTitle}\\d+$` ) ) ) ); readFavouriteList.sort( ( ( a, b ) => { const aIndex = Number( a.title.slice( favouriteTitle.length ) ); const bIndex = Number( b.title.slice( favouriteTitle.length ) ); return bIndex - aIndex; } ) ); return readFavouriteList; }; const menuList = [ { title: '\u8f93\u5165\u60a8\u7684uid', onClick: async () => { const uid = prompt( '\u8bf7\u8f93\u5165\u60a8\u7684\u7528\u6237uid (\u9ed8\u8ba4\u5c06\u4ece\u9875\u9762\u4e2d\u83b7\u53d6uid)\n\u5982\u679c\u8bbe\u7f6e\u4e86\u7528\u6237uid\u4f1a\u8ba9\u811a\u672c\u54cd\u5e94\u901f\u5ea6\u66f4\u5feb, \u4e0d\u7528\u7b49\u5f85\u9875\u9762\u8f7d\u5165\u83b7\u53d6uid\n(\u5982\u679c\u60a8\u4e0d\u77e5\u9053uid\u662f\u4ec0\u4e48, \u8bf7\u4e0d\u8981\u968f\u610f\u8f93\u5165)\n(\u7528\u6237uid\u662f\u60a8\u7684\u4e3b\u9875\u4e0a\u7f51\u5740\u7684\u4e00\u4e32\u6570\u5b57 \'https://space.bilibili.com/<uid>\')', await getUserUid() ); if ( !uid ) { return; } setUserUid( uid ); }, }, { title: '\u8bbe\u7f6e\u6536\u85cf\u5939\u6807\u9898', onClick: () => { if ( hasFavouriteTitle() ) { setFavouriteTitle( getFavouriteTitle() ); } const title = prompt( '\u8bf7\u8f93\u5165\u6536\u85cf\u5939\u6807\u9898 (\u9ed8\u8ba4\u4f7f\u7528 \'fun\'\u4f5c\u4e3a\u6536\u85cf\u5939\u6807\u9898)\n', getFavouriteTitle() ); if ( !title ) { return; } setFavouriteTitle( title ); }, } ]; const registerMenu = () => { menuList.forEach( ( menuInfo => { const { title: title, onClick: onClick } = menuInfo; GM_registerMenuCommand( title, onClick ); } ) ); }; const checkFavoriteIsFull = favoriteInfo => favoriteInfo.media_count === 1e3; const getVideoAvId = () => { const urlPathNameList = new URL( window.location.href ).pathname.split( '/' ); let videoId = urlPathNameList.find( ( urlPathName => urlPathName.startsWith( 'BV1' ) || urlPathName.startsWith( 'av' ) ) ); if ( !videoId ) { throw new Error( '\u6ca1\u6709\u83b7\u53d6\u5230\u89c6\u9891id' ); } if ( videoId.startsWith( 'BV1' ) ) { videoId = String( bvToAv( videoId ) ); } if ( videoId.startsWith( 'av' ) ) { videoId = videoId.slice( 2 ); } return videoId; }; const addVideoToFavorite = async ( videoId, latestFavorite ) => { const latestFavoriteId = String( latestFavorite.id ); const res = await api_collectVideoToFavorite( videoId, latestFavoriteId ); const successfullyAdd = ( res == null ? void 0 : res.success_num ) === 0; if ( !successfullyAdd ) { return; } ( () => {} )( `\u5f53\u524d\u89c6\u9891\u5df2\u6dfb\u52a0\u81f3\u6536\u85cf\u5939 [${ latestFavorite.title }]` ); }; const createNewFavorite = title => api_createFavorites( title ); const api_isFavorVideo = () => request( '/x/v2/fav/video/favoured', 'GET', { aid: getVideoAvId(), } ).then( ( res => { if ( res.code !== 0 ) { throw new Error( res.message ); } return res.data.favoured; } ) ); const autoAddVideoToFavourites = async () => { let isFavorVideo = await api_isFavorVideo(); if ( isFavorVideo ) { return; } const userUid = await getUserUid(); if ( !userUid ) { throw new Error( '\u83b7\u53d6\u7528\u6237uid\u5931\u8d25' ); } const readFavouriteList = await getReadFavouriteList( userUid ); if ( !readFavouriteList.length ) { const favoriteTitle = getFavouriteTitle(); await createNewFavorite( favoriteTitle + '1' ); await autoAddVideoToFavourites(); return; } const videoId = getVideoAvId(); const latestFavourite = readFavouriteList[0]; const isFullInFavorite = checkFavoriteIsFull( readFavouriteList[0] ); if ( !isFullInFavorite ) { await addVideoToFavorite( videoId, latestFavourite ); } if ( isFullInFavorite ) { const favoriteTitle = getFavouriteTitle(); const latestFavouriteId = Number( latestFavourite.title.slice( favoriteTitle.length ) ); await createNewFavorite( favoriteTitle + latestFavouriteId + 1 ); await autoAddVideoToFavourites(); return; } isFavorVideo = await api_isFavorVideo(); if ( !isFavorVideo ) { throw new Error( '\u6536\u85cf\u5931\u8d25' ); } const favButtonSelector = '.video-fav.video-toolbar-left-item:not(.on)'; const favButtonDom = await getElement( document, favButtonSelector ); if ( favButtonDom ) { favButtonDom.classList.add( 'on' ); } }; !checkScriptCatEnvironment() && registerMenu(); autoAddVideoToFavourites();