您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动在多P分集中切换下一P或跳过进度
// ==UserScript== // @name [Bilibili] 自动切P // @namespace ckylin-bilibili-auto-next-part // @version 0.1 // @description 自动在多P分集中切换下一P或跳过进度 // @author CKylinMC // @match https://*.bilibili.com/video/av* // @match https://*.bilibili.com/video/BV* // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com // @require https://greasyfork.org/scripts/429720-cktools/code/CKTools.js?version=1023553 // @grant unsafeWindow // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; class Logger { constructor(prefix = '[logUtil]') { this.prefix = prefix; } log(...args) { console.log(this.prefix, ...args); } info(...args) { console.info(this.prefix, ...args); } warn(...args) { console.warn(this.prefix, ...args); } error(...args) { console.error(this.prefix, ...args); } } const logger = new Logger("[AUTOP]"); if (CKTools.ver < 1.2) { logger.warn("Library script 'CKTools' was loaded incompatible version " + CKTools.ver + ", so that SNI may couldn't work correctly. Please consider update your scripts."); } const { get, getAll, domHelper, wait, waitForDom, waitForPageVisible, addStyle, modal, bili } = CKTools; const getVideoID = () => { let id = new URL(location.href).pathname.replace('/video/', '') if (id.endsWith('/')) { id = id.substring(0, id.length - 2); } if (id.startsWith('av')) { return { type: 'aid', id }; } if (id.startsWith('BV')) { return { type: 'bvid', id }; } return { type: 'unknown', id }; } const getCurrentTime = () => dataStore.vid?.currentTime??-1; const getCurrentPart = () => { let part = new URL(location.href).searchParams.get('p'); if (!part) part = '1'; return +part; } async function playerReady() { let i = 150; while (--i > 0) { await wait(100); if (unsafeWindow.player?.isInitialized() ?? false) break; } if (i < 0) return false; await waitForPageVisible(); while (1) { await wait(200); if (document.querySelector(".bilibili-player-video-control-wrap, .bpx-player-control-wrap")) return true; } } const dataStore = unsafeWindow.autonextpart = { p: 0, id: null, vid: null, config: { autoNextAt: -1,//>0 to enable partsDefined: [ null, /* 1: *//*{ startsAt: -1, endsAt: -1, ignoreGlobal: false, skip: [ { from: -1, to: -1 } ] }*/ ] }, next: () => { unsafeWindow.dispatchEvent(new KeyboardEvent("keydown", { key: "]", keyCode: 221, code: "BracketRight", which: 221, shiftKey: false, ctrlKey: false, metaKey: false })); }, hasNext: () => { } }; function parseDesc(desc) { const rootRegex = /AP:=(GP!(?<GP>\d+)!GP;){0,1}(?<parts>.+)*=:AP/m; let rootResult = rootRegex.exec(desc); if (!rootRegex || !rootResult.groups) return false; const { GP, parts } = rootResult.groups; if (!isNaN(+GP)) dataStore.config.autoNextAt = +GP; if (parts.length) { let partsSplited = parts.split(';').filter(i => i.trim().length); for (let part of partsSplited) { let [partName, start, end, subs, ignoreGlobal] = part.split('!'); let partId = +(partName.substring(1)); if (isNaN(partId)) continue; let config = { startAt: -1, endsAt: -1, skip: [] }; if (!isNaN(+start)) config.startAt = +start; if (!isNaN(+end)) config.endsAt = +end; let subsParts = subs.split("+").filter(i => i.trim().length); for (let sub of subsParts) try { const [from, to] = JSON.parse(sub); if (!isNaN(+from) && !isNaN(+to)) config.skip.push({ from, to }); } catch (e) { continue; } if (ignoreGlobal) config.ignoreGlobal = true; logger.info("发现配置: 分P", partId, "设定", config); dataStore.config.partsDefined[+partId] = config; } } return true; } async function tryInject() { logger.log("注入开始"); dataStore.vid = document.querySelector('.bpx-player-video-wrap>video'); if (!dataStore.vid) { logger.error("未能找到播放器..."); return false; } logger.info("已找到播放器:", dataStore.vid); dataStore.id = getVideoID(); if (dataStore.id.type == "unknown") { logger.error("无法识别的视频ID:", dataStore.id.id); // return; } else { logger.log("视频ID", dataStore.id); } dataStore.p = getCurrentPart(); if (isNaN(dataStore.p)) { logger.error("未知分P:", dataStore.p); return; } else { logger.log("视频分P", dataStore.p); } dataStore.vid.removeEventListener("timeupdate", onTimeUpdate); dataStore.vid.addEventListener("timeupdate", onTimeUpdate); logger.log("视频进度已hook"); try { let desc = document.querySelector('.desc-info-text'); if (!desc) throw ""; let descTxt = desc.textContent; // let descTxt = `AP:=GP!5!GP;=:AP`; if (descTxt.includes("AP:=")) { let startIdx = descTxt.indexOf("AP:="); let endIdx = descTxt.indexOf("=:AP"); if (startIdx === -1 || endIdx === -1) throw ""; parseDesc(descTxt); } else throw ""; unsafeWindow.player?.toast.create({text:"自动切P已启用"}) } catch (e) { logger.log("没有在描述中发现信息", e); } logger.log("注入完成"); } function onTimeUpdate(event) { let t = getCurrentTime(); if (t == -1) return; if (dataStore.config.partsDefined[dataStore.p]) { let cfg = dataStore.config.partsDefined[dataStore.p]; if (cfg.startsAt > -1 && t < cfg.startsAt) { if (unsafeWindow.player) unsafeWindow.player.seek?.(cfg.startsAt); else if (dataStore.vid) dataStore.vid.currentTime = cfg.startsAt; return; } if (cfg.endsAt > -1 && t >= cfg.endsAt) { if (unsafeWindow.player){ unsafeWindow.player.pause(); unsafeWindow.player.toast.create({text:"正在切换下一P"}) } else if (dataStore.vid) dataStore.vid.pause(); dataStore.next(); dataStore.p++; return; } //skip } if (dataStore.config.autoNextAt > -1 && t >= dataStore.config.autoNextAt) { if (unsafeWindow.player){ unsafeWindow.player.pause(); unsafeWindow.player.toast.create({text:"正在切换下一P"}) } else if (dataStore.vid) dataStore.vid.pause(); dataStore.next(); dataStore.p++; return; } } function run() { logger.log("等待播放器..."); playerReady().then(tryInject); } run(); })();