// ==UserScript==
// @name 【小破站必备2022】 哔哩哔哩(bilibili|B站)小助手--功能快捷键,每日任务等
// @namespace http://tampermonkey.net/
// @version 0.0.3
// @icon https://raw.githubusercontent.com/Anjude/tampermonkey/master/images/bilibili_tool.png
// @description 另一个宝藏B站工具箱脚本的重构版,去掉无用功能,增加更多实用功能!具体看脚本介绍~
// @author anjude
// @match https://*.bilibili.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_xmlhttpRequest
// @original-script https://github.com/Anjude/tampermonkey
// @require https://greasyfork.org/scripts/412159-mydrag/code/MyDrag.js?version=858320
// @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
// @downloadURL none
// ==/UserScript==
(function () {
'use strict'
// 检查版本
const RELEASE_VERSION = '0.0.4'
// const DEV = 'DEBUG'
const DEV = 'RELEASE'
let updateVersion = DEV === 'DEBUG' || RELEASE_VERSION !== GM_getValue('RELEASE_VERSION')
updateVersion && GM_setValue('RELEASE_VERSION', RELEASE_VERSION)
// resetScript()
/**
* 默认设置
* `${e.altKey}${e.ctrlKey}${e.shiftKey}${pressKey}`
*/
let defaultBili2sConf = {
shortcutMap: {
upToTop: '000U', // 回到顶部
takeNote: '000N', // 打开视频笔记
notePicShot: '101P', // 笔记-视频截图
noteTimePoint: '101T', // 笔记-时间标记
unlockBangumi: '100V', // 解锁视频
},
videoRecordMap: {}, // 视频记录
multiUnceasing: true, // 多集自动连播
singleUncreasing: false, // 单集自动连播
autoUnlockVideo: false, // 是否自动解锁视频
shareDate: '2022/1/1',
lastClearup: new Date()
}
let bili2sConf = GM_getValue('bili2sConf') || defaultBili2sConf
if (updateVersion) {
bili2sConf.shortcutMap = Object.assign(defaultBili2sConf.shortcutMap, bili2sConf.shortcutMap)
GM_setValue('bili2sConf', bili2sConf)
}
// 网站配置
const siteConfig = {
delayMs: 2000,
scrollBtnList: [
'div.item.back-top', // 首页
'div.item.backup', // up视频,
'div.tool-item.backup.iconfont.icon-up', //
'#app > div.to-top', // up主所有视频
'#cheese_guide > div > div' // 课堂
],
noteBtnList: [
'div.note-btn', // 普通up视频
'span.note-btn' // 课堂视频
],
notePanelList: [
'div.resizable-component.bili-note' // 普通up视频
],
picBtnList: ['span.ql-capture-btn'],
pointBtnList: ['span.ql-tag-btn'],
multiPageBox: ['#multi_page > div.cur-list'],
chapListItem: ['div.cur-list > ul > li.on'],
shareBtnList: ['div.share-btns > div:nth-child(6)'],
unceasingBtnList: ['span.switch-button'],
searchResBox: [
'#video-list > ul',
'div.mixin-list > ul.video-list', // 番剧
'div.flow-loader > ul',
'div.rcmd-box', // 首页推荐
'div.section.video > div', // UP主页
'#submit-video-list > ul.list-list' // UP主页,更多视频
],
playerBox: ['#player_module'],
parseApiList: [
{ id: 1, url: 'https://vip.parwix.com:4433/player/?url=' }
],
bangumiLi: ['li.ep-item.cursor.badge.visited'],
shortcutList: {
upToTop: '回到顶部',
takeNote: '打开/关闭笔记',
unlockBangumi: '打开/关闭视频解锁',
notePicShot: '笔记-视频截图',
noteTimePoint: '笔记-时间标志'
}, // shortcut list
scSetting: ''
}
const startHttpProxy = () => {
XMLHttpRequest.prototype.send = new Proxy(XMLHttpRequest.prototype.send, {
apply: (target, thisArg, args) => {
thisArg.addEventListener('load', event => {
try {
console.log(111, event.target.responseURL)
let { responseText, responseURL } = event.target
if (!/^{.*}$/.test(responseText)) return
const result = JSON.parse(responseText);
/\/player\/playurl/.test(responseURL)
&& chapListener(result);
(/x\/web-interface\/search/.test(responseURL)
|| /x\/web-interface\/index\/top\/rcmd/.test(responseURL)
|| /x\/space\/arc/.test(responseURL))
&& dealRead(result);
/pgc\/view\/web\/section\/order/.test(responseURL)
&& UnlockBangumi();
} catch (err) { }
})
return target.apply(thisArg, args)
}
})
}
GM_addStyle(getCss())
setCommand()
startHttpProxy()
const getElement = (list) => {
let btn = document.querySelector(list[0])
list.forEach(e => { btn = document.querySelector(e) || btn })
return btn
}
const getBvid = (href) => {
return /video\/([0-9|a-z|A-Z]*)/i.exec(href || document.location.href)[1]
}
const UpToTop = () => { // 回到顶部
let scrollBtn = getElement(siteConfig.scrollBtnList)
if (scrollBtn) scrollBtn.click()
}
const TakeNote = () => {
let noteBtn = getElement(siteConfig.noteBtnList)
let nodePanel = getElement(siteConfig.notePanelList)
let res = nodePanel || (() => {
noteBtn.click()
return false
})()
if (!res) return
nodePanel.style.display = nodePanel.style.display === 'none'
? '' : 'none'
}
const NotePicShot = () => {
let picBtn = getElement(siteConfig.picBtnList)
console.log(111, picBtn);
picBtn.click()
}
const NoteTimePoint = () => {
let pointBtn = getElement(siteConfig.pointBtnList)
pointBtn.click()
}
const keyCtrl = () => {
}
const blockKey = (e) => {
let isBlock = false
// do sth if isBlock should be true
return isBlock
}
const setShortcut = (command) => {
let commandString = getShortCut(command)
let scSetting = siteConfig.scSetting
console.log(111, commandString, scSetting)
let innerTextList = document.querySelector(`#${scSetting}`).innerHTML.split(':')
document.querySelector(`#${scSetting}`).innerHTML = innerTextList[0] + ': ' + commandString
siteConfig.scm[scSetting] = command
}
let focus = false // 输入中
$(document).ready(() => {
$(document).delegate('input, textarea',
'focus', () => { focus = true })
$(document).delegate('input, textarea',
'blur', () => { focus = false })
$(document).keydown((e) => {
// 如果正在打字或者特殊情况,屏蔽快捷键
if (!e.altKey && !e.shiftKey && !e.ctrlKey
&& (focus || blockKey(e))) {
return
}
const k = (key) => key ? 1 : 0
let pressKey = String.fromCharCode(e.keyCode)
let command = `${k(e.altKey)}${k(e.ctrlKey)}${k(e.shiftKey)}${pressKey}`
let keyMap = bili2sConf.shortcutMap
// console.log('键盘:', command, siteConfig.scSetting)
if (siteConfig.scSetting) { return setShortcut(command) }
switch (command) {
case keyMap.upToTop:
return UpToTop()
case keyMap.takeNote:
return TakeNote()
case keyMap.notePicShot:
return NotePicShot()
case keyMap.noteTimePoint:
return NoteTimePoint()
case keyMap.unlockBangumi:
return UnlockBangumi(true)
default:
keyCtrl(command) // 一些不常用的小操作,集中一个函数处理
}
})
})
const chapListener = (res) => {
let listItem = getElement(siteConfig.chapListItem).innerHTML
let regxList = /video\/([0-9|a-z|A-Z]*)\?p=(\d+).*title=.(.*?).>
{
let bvid = getBvid()
let videoHis = bili2sConf.videoRecordMap[bvid]
videoHis && (() => {
document.querySelector(`div.cur-list > ul > li:nth-child(${videoHis.p}) > a`).click()
Toast('小助手: 跳转上次观看集数')
})()
}
const setVideoRecord = () => {
let bvid = getBvid()
let videoRecord = bili2sConf.videoRecordMap[bvid] || {
docTitle: document.title,
p: 1
}
videoRecord.updateTime = new Date()
bili2sConf.videoRecordMap[bvid] = Object.assign(bili2sConf.videoRecordMap[bvid] || {}, videoRecord)
// console.log(bili2sConf.videoRecordMap[bvid], videoRecord)
GM_setValue('bili2sConf', bili2sConf)
}
const dealUnceasing = (isMultiPage) => {
// 处理连播
let switchCase = isMultiPage ? 'multiUnceasing' : 'singleUncreasing'
let unceasingBtn = getElement(siteConfig.unceasingBtnList)
let curUnceasing = /switch-button on/.test(unceasingBtn.getAttribute('class'))
curUnceasing === bili2sConf[switchCase]
|| unceasingBtn.click()
unceasingBtn.addEventListener("click", (e) => {
// 过滤脚本模拟点击
if (e.isTrusted) {
bili2sConf[switchCase] = !/switch-button on/.test(unceasingBtn.getAttribute('class'))
GM_setValue('bili2sConf', bili2sConf);
}
})
}
const doShare = () => {
console.log('[B站小助手]: 开始分享!')
let shareBtn = getElement(siteConfig.shareBtnList)
shareBtn.click()
document.body.lastChild.remove()
bili2sConf.shareDate = new Date().toLocaleDateString()
GM_setValue('bili2sConf', bili2sConf)
console.log('[B站小助手]: 分享完成!')
Toast('小助手: 今日分享任务达成')
}
const dealRead = (res) => {
let searchResBox = getElement(siteConfig.searchResBox)
console.log(searchResBox.childNodes)
let resList = searchResBox.childNodes
resList.forEach(e => {
let bvid = getBvid(e.innerHTML)
let addDiv = document.createElement("div")
addDiv.className = 'video-view'
if (bili2sConf.videoRecordMap[bvid]) {
addDiv.innerHTML = "看过";
addDiv.style.opacity = 1;
addDiv.style.color = 'red';
} else {
addDiv.innerHTML = "未看";
}
e.prepend(addDiv);
})
}
const UnlockBangumi = (setAutoUnlock, parseApi = siteConfig.parseApiList[0]) => {
if (setAutoUnlock) {
let set = !bili2sConf.autoUnlockVideo
bili2sConf.autoUnlockVideo = set
GM_setValue('bili2sConf', bili2sConf)
Toast(`[B站小助手]:${set ? '开启' : '关闭'}自动解锁!`)
}
let videoInfo = getElement(siteConfig.bangumiLi)?.innerHTML
if (!bili2sConf.autoUnlockVideo
|| videoInfo && !/>会员<\/div>/.test(videoInfo)
|| !videoInfo
) { return }
let newPlayer = document.createElement('iframe')
newPlayer.id = 'anjude-iframe'
newPlayer.height = '100%'
newPlayer.width = '100%'
newPlayer.src = parseApi.url + window.location.href
newPlayer.setAttribute('allow', 'autoplay')
newPlayer.setAttribute('frameborder', 'no')
newPlayer.setAttribute('border', '0')
newPlayer.setAttribute('allowfullscreen', 'allowfullscreen')
newPlayer.setAttribute('webkitallowfullscreen', 'webkitallowfullscreen')
let playerBox = getElement(siteConfig.playerBox)
playerBox.innerHTML = ''
playerBox.append(newPlayer)
}
const runScript = () => {
let date = new Date().toLocaleDateString()
let href = window.location.href
let isMultiPage = getElement(siteConfig.multiPageBox)
if (isMultiPage) {
setTimeout(() => { multiPageJump() }, siteConfig.delayMs)
}
if (/\/video\//.test(href)) {
setTimeout(() => {
setVideoRecord()
dealUnceasing(isMultiPage)
date === bili2sConf.shareDate || doShare()
}, siteConfig.delayMs);
}
if (/search.bilibili.com/.test(href)) {
setTimeout(() => {
dealRead()
}, siteConfig.delayMs)
}
}
// 执行脚本
try {
runScript()
clearupStore()
// console.log('[B站小助手]:', bili2sConf)
} catch (err) {
console.log('[B站小助手]:', err)
}
function resetScript() {
GM_deleteValue('bili2sConf')
}
function clearupStore() {
const getDayDiff = (d) => {
return (new Date() - new Date(d)) / (1000 * 60 * 60 * 24)
}
let dayDiff = getDayDiff(bili2sConf.lastClearup)
if (dayDiff < 30) return // 每月清理一次数据
console.log('[B站小助手]:开始清理!')
let recordMapKeys = Object.keys(bili2sConf.videoRecordMap)
recordMapKeys.forEach(e => {
let updateTime = bili2sConf.videoRecordMap[e].updateTime
if (getDayDiff(updateTime) > 365 * 2) {
delete bili2sConf.videoRecordMap[e]
}
})
bili2sConf.lastClearup = new Date()
GM_setValue('bili2sConf', bili2sConf)
}
function Toast(message = "已完成", time = 2000) {
/*设置信息框停留的默认时间*/
let el = document.createElement("div")
el.setAttribute("class", "web-toast")
el.innerHTML = message
document.body.appendChild(el);
el.classList.add("fadeIn");
setTimeout(() => {
el.classList.remove("fadeIn")
el.classList.add("fadeOut")
/*监听动画结束,移除提示信息元素*/
el.addEventListener("animationend", () => {
document.body.removeChild(el)
})
el.addEventListener("webkitAnimationEnd", () => {
document.body.removeChild(el)
})
}, time)
}
function getShortCut(command) {
// console.log(command);
let res = ''
if (parseInt(command[0])) res += 'Alt+'
if (parseInt(command[1])) res += 'Ctrl+'
if (parseInt(command[2])) res += 'Shift+'
res += command[3]
return res
}
function clearCommandStatus(SL) {
SL.forEach(e => { document.querySelector(`#${e}`).style.color = '' })
}
function setCommand() {
initSettingPanel()
GM_registerMenuCommand('设置快捷键', () => {
document.querySelector('#sc-box').style.display = ''
})
}
function initSettingPanel() {
let SCL = siteConfig.shortcutList
siteConfig.scm = bili2sConf.shortcutMap
let scItem = ''
Object.keys(SCL).forEach(e => {
scItem += `
设置${SCL[e]}快捷键: ${getShortCut(siteConfig.scm[e])}
`
})
let boxHtml = $(`
快捷键设置(点击选中)
${scItem}
`)
$(document.body).append(boxHtml)
new MyDrag($('#sc-box')[0], { handle: $('#sc-title')[0] })
Object.keys(SCL).forEach(v => {
document.querySelector(`#${v}`).addEventListener('click', function (e) {
siteConfig.scSetting = this.id
clearCommandStatus(Object.keys(SCL))
this.style.color = 'green'
})
})
document.querySelector('#anjude-scok-btn').addEventListener('click', function (e) {
// 设置快捷键,缓存数据
siteConfig.scSetting = ''
bili2sConf.shortcutMap = siteConfig.scm
GM_setValue('bili2sConf', bili2sConf)
document.querySelector('#sc-box').style.display = 'none'
})
}
function getCss() {
return `
#sc-box{
padding: 10px;border-radius: 5px;
background: #F6F6F6;border: #44b549 2px solid;
}
.video-view{
display:inline-block;
position:absolute;
left:0px;
top:0px;
background:#FFF;
color:#666;
opacity: 0.8;
padding:1px 5px;
z-index:999;
}
@keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
}
@-webkit-keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
}
@-moz-keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
}
@-o-keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
}
@-ms-keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
}
@keyframes fadeOut {
0% {opacity: 1}
100% {opacity: 0}
}
@-webkit-keyframes fadeOut {
0% {opacity: 1}
100% {opacity: 0}
}
@-moz-keyframes fadeOut {
0% {opacity: 1}
100% {opacity: 0}
}
@-o-keyframes fadeOut {
0% {opacity: 1}
100% {opacity: 0}
}
@-ms-keyframes fadeOut {
0% {opacity: 1}
100% {opacity: 0}
}
.web-toast{
position: fixed;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 14px;
line-height: 1;
padding:10px;
border-radius: 3px;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
-webkit-transform: translate(-50%,-50%);
-moz-transform: translate(-50%,-50%);
-o-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
z-index: 9999;
white-space: nowrap;
}
.fadeOut{
animation: fadeOut .5s;
}
.fadeIn{
animation:fadeIn .5s;
}
`}
})();