// ==UserScript==
// @name 【小破站必备2022】 哔哩哔哩(bilibili|B站)自动增强--功能快捷键,视频智能解析,每日任务等
// @namespace http://tampermonkey.net/
// @version 0.0.12
// @icon https://gitee.com/anjude/public-resource/raw/md-img/1.png
// @description 🔥🔥🔥推荐! 浸入式虚拟会员体验,功能智能自动化,让你的 B站 比别人的更强。自动跳转多 P 视频(UP 上传视频)上次观看进度,快捷键增强,每日任务(签到&分享),会员番剧无感解析,视频已看标签等等,具体看脚本介绍~
// @author 豆小匠Coding
// @match https://*.bilibili.com/*
// @grant GM_openInTab
// @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://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.2.1/jquery.min.js
// @require https://greasyfork.org/scripts/412159-mydrag/code/MyDrag.js?version=858320
// @downloadURL none
// ==/UserScript==
(function () {
'use strict'
// @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
// 检查版本
const RELEASE_VERSION = '0.0.12'
let ENV = 'RELEASE'
// ENV = 'DEBUG'
const updateVersion = ENV === 'DEBUG' || RELEASE_VERSION !== GM_getValue('RELEASE_VERSION')
updateVersion && GM_setValue('RELEASE_VERSION', RELEASE_VERSION)
startHttpProxy()
/**
* 默认设置
* `${e.altKey}${e.ctrlKey}${e.shiftKey}${pressKey}`
*/
let defaultBili2sConf = {
shortcutMap: {
upToTop: '000U', // 回到顶部
takeNote: '000N', // 打开视频笔记
lightOff: '000L', // 开关宽屏模式
notePicShot: '101P', // 笔记-视频截图
noteTimePoint: '101T', // 笔记-时间标记
changeParseApi: '100V', // 解锁视频
showMenu: '100M', // 打开菜单
},
videoRecordMap: {}, // 视频记录
multiUnceasing: true, // 多集自动连播
singleUncreasing: false, // 单集自动连播
autoUnlockVideo: false, // 是否自动解锁视频
shareDate: '2022/1/1',
lastClearup: new Date(),
parseApiIndex: 0, // 解析接口选择
pretendVip: false,
installTime: null
}
// 网站配置
const siteConfig = {
delay2s: 2000,
scrollBtnList: [
'div.item.back-top', // 首页
'button.primary-btn.top-btn', // 新版首页
'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视频
],
lightOffBtn: ['div.squirtle-single-setting-other-choice.squirtle-lightoff',
'div.bilibili-player-fl.bilibili-player-video-btn-setting-right-others-content-lightoff.bui.bui-checkbox.bui-dark > input'],
wideScreenBtn: ['div.squirtle-widescreen-wrap.squirtle-block-wrap > div', // bangumi 视频
'div.bilibili-player-video-btn.bilibili-player-video-btn-widescreen' // up 视频
],
videoSettingBtn: ['div.bilibili-player-video-btn.bilibili-player-video-btn-setting'],
picBtnList: ['span.ql-capture-btn'],
pointBtnList: ['span.ql-tag-btn'],
multiPageBox: ['#multi_page > div.cur-list'],
chapListItem: ['div.cur-list > ul > li.on'],
trendBtnList: ['div.share-btns > div:nth-child(6)',
'div.share-info > div > div > span'],
shareBtnList: ['div.share-info'],
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主页,更多视频
'#reco_list > div.rec-list', // 相关视频
],
vipIcon: 'bili-avatar-icon--big-vip',
vipSpan: [
'div.avatar-container > div > div > span',
'div.big-avatar-container--default > a > div > span',
'a.header-entry-avatar > div > span',
],
vipLabel: 'div.h-vipType',
playerBox: ['#player_module'],
videoBox: ['video'],
vipAdClose: ['div.twp-mask > div > i'],
parseApiList: [ // 解析链接均收集自网络,经过简单测试
{ url: 'https://vip.parwix.com:4433/player/?url=', name: 'Parwix解析系统' },
{ url: 'https://www.yemu.xyz/?url=', name: '夜幕解析' },
{ url: 'https://vip.bljiex.cc/?v=', name: 'BL解析' },
{ url: 'https://www.1717yun.com/jx/ty.php?url=', name: '1717云解析' },
{ url: 'https://jx.rdhk.net/?v=', name: '4080视频解析' },
{ url: 'https://go.yh0523.cn/y.cy?url=', name: '盘古云解析' },
{ url: 'https://yparse.jn1.cc/index.php?url=', name: '云解析' },
{ url: 'https://vip.mmkv.cn/tv.php?url=', name: 'mmkv' },
{ url: 'https://z1.m1907.cn/?jx=', name: 'm1907' },
{ url: 'https://17kyun.com/api.php?url=', name: '17kyun' },
// { url: 'https://www.mtosz.com/m3u8.php?url=', name: 'mtosz' },
{ url: 'https://lecurl.cn/?url=', name: 'dplayer - by-le' },
{ url: 'https://vip5.jiexi.one/?url=', name: '爱爱蓝光解析' },
],
bangumiLi: ['li.ep-item.cursor.badge.visited'],
shortcutList: {
upToTop: '回到顶部',
takeNote: '打开/关闭笔记',
changeParseApi: '切换视频解析接口',
showMenu: '打开菜单',
notePicShot: '笔记-视频截图',
noteTimePoint: '笔记-时间标志',
lightOff: '开关宽屏模式'
}, // shortcut list
scSetting: ''
}
let bili2sConf = GM_getValue('bili2sConf') || defaultBili2sConf
if (updateVersion) {
let shortcutMap = Object.assign({}, defaultBili2sConf.shortcutMap)
bili2sConf = Object.assign(defaultBili2sConf, bili2sConf)
bili2sConf.shortcutMap = Object.assign(shortcutMap, bili2sConf.shortcutMap)
console.log(shortcutMap, defaultBili2sConf.shortcutMap, bili2sConf.shortcutMap)
GM_setValue('bili2sConf', bili2sConf)
Toast('脚本已更新')
}
if (!bili2sConf.installTime) {
bili2sConf.installTime = new Date()
GM_setValue('bili2sConf', bili2sConf)
if (confirm('首次使用,前往微信小程序,随时反馈!')) {
window.GM_openInTab(
'https://gitee.com/anjude/public-resource/raw/md-img/TW-TamperMonkey.png',
{ active: true, insert: true, setParent: true }
)
}
}
const delayExecute = (execution, delayMs) => {
setTimeout(() => {
execution()
}, delayMs || siteConfig.delay2s);
}
const getElement = (list) => {
if (typeof list === 'string') return document.querySelector(list)
let btn = document.querySelector(list[0])
list.forEach(e => { btn = document.querySelector(e) || btn })
return btn
}
const getBvid = (href) => {
let res = /video\/([0-9|a-z|A-Z]*)/ig.exec(href || document.location.href)
return res === null ? false : res[1]
}
// 改编自 github 网友贡献的代码,详情请参见 github 的提交记录
const LightOff = () => {
let settingBtn = getElement(siteConfig.videoSettingBtn)
settingBtn?.dispatchEvent(new MouseEvent('mouseover'))
settingBtn?.dispatchEvent(new MouseEvent('mouseout'))
let wideScreenBtn = getElement(siteConfig.wideScreenBtn)
let lightOffBtn = getElement(siteConfig.lightOffBtn)
let scrollDistance = window.location.href.match('bangumi') ? 50 : 100
wideScreenBtn.click()
lightOffBtn.click()
window.scrollTo(0, scrollDistance)
}
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)
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
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.lightOff:
return LightOff()
case keyMap.takeNote:
return TakeNote()
case keyMap.changeParseApi:
return ChangeParseApi()
case keyMap.showMenu:
return document.querySelector('#sc-box').style.display = ''
case keyMap.notePicShot:
return NotePicShot()
case keyMap.noteTimePoint:
return NoteTimePoint()
default:
keyCtrl(command) // 一些不常用的小操作,集中一个函数处理
}
})
})
const chapListener = (res) => {
let listItem = getElement(siteConfig.chapListItem).innerHTML
let regxList = /video\/([0-9a-zA-Z]*)\?p=(\d+).*title=.(.*?).>
{
let bvid = getBvid()
let videoHis = bili2sConf.videoRecordMap[bvid]
videoHis && (() => {
let hrefRegexp = new RegExp(`${bvid}\\?p=\\d+`, 'i')
if (hrefRegexp.test(window.location.href)) { return }
let curChapLi = document.querySelector(`div.cur-list > ul > li:nth-child(${videoHis.p}) > a > div`)
if (!curChapLi) {
return delayExecute(multiPageJump)
}
curChapLi.click()
Toast(`小助手: 跳转上次观看 P${videoHis.p}`)
})()
}
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)
if (!unceasingBtn) {
return delayExecute(dealUnceasing)
}
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?.dispatchEvent(new MouseEvent('mouseover'))
let trendBtn = getElement(siteConfig.trendBtnList)
if (!trendBtn) {
return delayExecute(doShare)
}
trendBtn.click()
document.body.lastChild.remove()
shareBtn?.dispatchEvent(new MouseEvent('mouseout'))
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 => {
if (!e.innerHTML) return
e.style.position = 'relative'
let bvid = getBvid(e.innerHTML)
if (!bvid) return
let addDiv = document.createElement("div")
addDiv.className = 'video-view'
if (bili2sConf.videoRecordMap[bvid]) {
addDiv.innerHTML = '已看'
addDiv.style.opacity = 0.9;
addDiv.style.color = 'red';
} else {
// addDiv.innerHTML = "未看";
}
e.prepend(addDiv);
})
}
const ChangeParseApi = () => {
let curIndex = bili2sConf.parseApiIndex
bili2sConf.parseApiIndex = (curIndex + 1) % siteConfig.parseApiList.length
UnlockBangumi(bili2sConf.parseApiIndex, false, true)
GM_setValue('bili2sConf', bili2sConf)
Toast(`B站小助手: 解析接口${bili2sConf.parseApiIndex + 1} ${siteConfig.parseApiList[bili2sConf.parseApiIndex].name}`)
}
const UnlockBangumi = (parseApiIndex = 0, setAutoUnlock, forceUnlock) => {
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 && !forceUnlock)
|| videoInfo && !/>(会员|付费)<\/div>/.test(videoInfo)
|| !videoInfo
) { return $('#anjude-iframe').length && location.reload() }
let parseApi = siteConfig.parseApiList[parseApiIndex]
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', 'true')
newPlayer.setAttribute('webkitallowfullscreen', 'webkitallowfullscreen')
let playerBox = getElement(siteConfig.playerBox)
let videoBox = getElement(siteConfig.videoBox)
videoBox && (videoBox.muted = true) && videoBox.pause()
playerBox.innerHTML = ''
playerBox.append(newPlayer)
let monitorTimes = 0
let vipAdMonitor = setInterval(() => {
monitorTimes++
let closeAd = getElement(siteConfig.vipAdClose)
if (closeAd || monitorTimes >= (5 * 60 * 1000 / 200)) {
closeAd.click()
clearInterval(vipAdMonitor)
}
}, 200);
// Toast(`B站小助手: 解析完成`, 500)
}
const pretendVip = () => {
siteConfig.vipSpan.forEach(e => {
let vipSpan = getElement(e)
vipSpan && vipSpan.classList.add(siteConfig.vipIcon)
})
let vipLabel = getElement(siteConfig.vipLabel)
if (vipLabel) {
let newClass = vipLabel.getAttribute('class').replace('disable', '')
vipLabel.setAttribute('class', newClass)
}
}
const executeByUri = (responseURL, result) => {
/\/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)
|| /pgc\/season\/episode\/web\/info/.test(responseURL))
&& UnlockBangumi(bili2sConf.parseApiIndex);
}
const runScript = () => {
let date = new Date().toLocaleDateString()
let href = window.location.href
let isMultiPage = getElement(siteConfig.multiPageBox)
if (isMultiPage) {
multiPageJump()
}
if (/\/video\//.test(href)) {
setVideoRecord()
dealUnceasing(isMultiPage)
dealRead()
date === bili2sConf.shareDate || doShare()
}
if (/bilibili.com\/bangumi/.test(href)) {
// date === bili2sConf.shareDate || doShare()
}
if (/search.bilibili.com/.test(href)) {
dealRead()
}
bili2sConf.pretendVip && pretendVip()
}
// 执行脚本
try {
// console.log('[B站小助手]:', bili2sConf)
GM_addStyle(getCss())
setCommand()
setTimeout(() => {
runScript()
}, siteConfig.delay2s);
clearupStore()
} catch (err) {
console.log('[B站小助手]:', err.name, err.message)
if (confirm(`【B站小助手】: 请截图(到 我的 - 客服 处)反馈 ${err}`)) {
window.GM_openInTab(
'https://gitee.com/anjude/public-resource/raw/md-img/TW-TamperMonkey.png',
{ active: true, insert: true, setParent: true }
)
}
}
function resetScript() {
GM_deleteValue('bili2sConf')
}
function helper() {
let str = ``
let list = str.match(/https?:\/\/[0-9a-zA-Z./?_-]*=/ig)
list.forEach((e, i) => {
setTimeout(() => {
console.log(i, i === list.length - 1);
window.open(`${e}https://www.bilibili.com/bangumi/play/ep457778?spm_id_from=333.999.0.0`, 'target')
}, i * 15000)
})
}
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 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);
executeByUri(responseURL, result)
} catch (err) { }
})
return target.apply(thisArg, args)
}
})
}
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 = ''
})
GM_registerMenuCommand('重置脚本', () => {
if (confirm('重置后观看记录、快捷键修改等数据将清空!')) {
resetScript()
}
})
}
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 = $(`
`)
$(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'
})
document.querySelector('#auto-unlockvideo').addEventListener('click', function (e) {
UnlockBangumi(bili2sConf.parseApiIndex, true)
})
document.querySelector('#pretend-vip').addEventListener('click', function (e) {
bili2sConf.pretendVip = !bili2sConf.pretendVip
GM_setValue('bili2sConf', bili2sConf)
Toast('小助手: 刷新页面后生效')
})
document.querySelector('#badguy').addEventListener('click', function (e) {
let cur = document.querySelector('#miniprogram').style.display
document.querySelector('#miniprogram').style.display = cur ? '' : 'none'
})
updateVersion && (document.querySelector('#sc-box').style.display = '')
}
function getCss() {
return `
a{text-decoration:none;}
#pretend-vip,
#auto-unlockvideo{
background-color: initial;
cursor: default;
appearance: checkbox;
box-sizing: border-box;
padding: initial;
border: initial;
}
#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}
}
@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%);
z-index: 9999;
white-space: nowrap;
}
.fadeOut{
animation: fadeOut .5s;
}
.fadeIn{
animation:fadeIn .5s;
}
`}
})();