// ==UserScript==
// @name B站防沉迷
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @license MIT
// @icon https://www.bilibili.com/favicon.ico
// @description 根据根据关键词和正则表达式,自动关闭在标题或标签中包含这些关键词的B站视频
// @author Vz
// @match *://*.bilibili.com/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
//默认参数
const defaultBlockedWords = [
'这是关键词',
'/这是正则表达式/'
];
const defaultStartTime = "16:00";
const defaultEndTime = "22:00";
const defaultEnableTimeRange = true;
const defaultShowTips = true;
// 获取当前时间
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
// 从存储中加载关键词列表和时间设置
let blockedWords = GM_getValue('blockedWords',defaultBlockedWords );
let startTime = GM_getValue('startTime', defaultStartTime);
let endTime = GM_getValue('endTime', defaultEndTime);
let showTips = GM_getValue('showTips', defaultShowTips);
let enableTimeRange = GM_getValue('enableTimeRange', defaultEnableTimeRange);
//运行时更新参数
function getCustomValue () {
blockedWords = GM_getValue('blockedWords',defaultBlockedWords );
startTime = GM_getValue('startTime', defaultStartTime);
endTime = GM_getValue('endTime', defaultEndTime);
showTips = GM_getValue('showTips', defaultShowTips);
enableTimeRange = GM_getValue('enableTimeRange', defaultEnableTimeRange);
}
if(!enableTimeRange){
//不勾选停用时段直接执行主逻辑
mainLogic();
}else{
// 将时间字符串转换为分钟数
function timeToMinutes(timeString) {
const [hours, minutes] = timeString.split(':').map(Number);
return hours * 60 + minutes;
}
// 检查当前时间是否在停用区间内
const currentTimeInMinutes = currentHour * 60 + currentMinute;
const startTimeInMinutes = timeToMinutes(startTime);
const endTimeInMinutes = timeToMinutes(endTime);
if (!(currentTimeInMinutes >= startTimeInMinutes && currentTimeInMinutes < endTimeInMinutes)) {
// 不在停用时间内,才执行主逻辑
mainLogic();
}
}
// 获取当前页面的标题
function getTitle() {
const url = new URL(window.location.href);
const path = url.pathname;
// 根据路径选择不同的标题元素类名
let titleElement;
if (path.startsWith('/video/')) {
titleElement = document.querySelector('.video-title');
} else if (path.startsWith('/list/')) {
titleElement = document.querySelector('.video-title-href');
}
//alert(titleElement ? titleElement.innerHTML : 'No title found');
return titleElement ? titleElement.innerHTML : '';
}
// 获取视频的 BV 号
function getBvNumber() {
const url = new URL(window.location.href);
const path = url.pathname;
const searchParams = url.searchParams;
// 从路径中获取 BV 号
if (path.startsWith('/video/')) {
const bvMatch = path.match(/\/video\/(BV\w+)/);
return bvMatch ? bvMatch[1] : null;
} else if (path.startsWith('/list/')) {
return searchParams.get('bvid');
}
return null;
}
// 获取视频标签
function getVideoApiTags(videoBv) {
fetch(`https://api.bilibili.com/x/web-interface/view/detail/tag?bvid=${videoBv}`)
.then((response) => response.json())
.then((data) => {
const tags = data.data ? data.data.map(tag => tag.tag_name) : [];
checkBlockedWords(getTitle(), tags);
})
.catch((error) => console.error('Error fetching video tags:', error));
}
// 检查标题和标签是否包含屏蔽关键词
function checkBlockedWords(title, tags) {
//调试用
//alert('title: ' + title + '\n' + 'tags: ' + tags + '\n' + 'bvNumber: ' + bvNumber);
// 将标题和标签转换为小写
const allText = [title.toLowerCase(), ...tags.map(tag => tag.toLowerCase())].join(' ');
for (const word of blockedWords) {
if (typeof word === 'string' && word.trim() === '') {
// 跳过空串的检查
continue;
}
if (typeof word === 'string'&& !word.startsWith('/')) {
if (allText.includes(word.toLowerCase())) {
if(showTips){
alert('Blocked by keyword: ' + word);
}
window.close();
window.stop();
window.location.href = 'about:blank';
return;
}
} else if (word.startsWith('/')) {
// 修改正则表达式以不区分大小写
const insensitiveWord = new RegExp(word.slice(1, -1), 'ius');
if (insensitiveWord.test(allText)) {
if(showTips){
alert('Blocked by regex: ' + word);
}
window.close();
window.stop();
window.location.href = 'about:blank';
return;
}
}
}
}
// 主逻辑
function mainLogic() {
// 获取当前页面的完整 URL
const currentUrl = window.location.href;
if (currentUrl.includes('/video/') || currentUrl.includes('/list/')) {
const bvNumber = getBvNumber();
if (bvNumber) {
getVideoApiTags(bvNumber);
} else {
checkBlockedWords(getTitle(), []);
}
}}
// 创建模态对话框
function createModal() {
// 从存储中加载关键词列表和时间设置
getCustomValue();
const modal = document.createElement('div');
modal.id = 'keywordModal';
modal.innerHTML = `
<div id="modalContent">
<h2>关键词列表</h2>
<ul>
<li>每行一个关键词或正则,不区分大小写</li>
<li>请勿使用过于激进的关键词或正则</li>
<li>正则默认 ius 模式,无需 flag,语法:/abc小d+/</li>
</ul>
<textarea id="keywordList"></textarea>
<div id="timeSelector">
<label><input type="checkbox" id="enableTimeRange" ${enableTimeRange ? 'checked' : ''}> 停用时段: </label>
<div style="display: inline-block;margin-top: 5px;">
<input type="time" id="startTime" name="startTime" value="${startTime}">
<span> - </span>
<input type="time" id="endTime" name="endTime" value="${endTime}">
</div>
</div>
<div style="margin-bottom: 10px;">
<label><input type="checkbox" id="showTips" ${showTips ? 'checked' : ''}> 网页关闭提示 (仅提示,不会阻止网页关闭)</label>
</div>
<button id="saveButton">保存</button>
<button id="closeButton">关闭</button>
<div id="saveStatus">保存成功!</div>
</div>
`;
// 填充文本域
const keywordListTextArea = modal.querySelector('#keywordList');
keywordListTextArea.value = blockedWords.join('\n');
// 保存按钮事件
modal.querySelector('#saveButton').addEventListener('click', () => {
const newKeywords = keywordListTextArea.value.split('\n').map(line => {
if (line.startsWith('/')) {
try {
new RegExp(line.slice(1, -1), 'ius');
return line
} catch (e) {
alert(`Invalid regex: ${line}`);
return null;
}
}
return line;
}).filter(Boolean);
const newStartTime = modal.querySelector('#startTime').value;
const newEndTime = modal.querySelector('#endTime').value;
const newEnableTimeRange = modal.querySelector('#enableTimeRange').checked;
const newShowTips = modal.querySelector('#showTips').checked;
GM_setValue('blockedWords', newKeywords);
GM_setValue('startTime', newStartTime);
GM_setValue('endTime', newEndTime);
GM_setValue('enableTimeRange', newEnableTimeRange);
GM_setValue('showTips', newShowTips);
//alert("enableTimeRange: " + newEnableTimeRange);
const saveStatus = modal.querySelector('#saveStatus')
saveStatus.style.color = "#00AEEC";
// 定义一个变量来存储 setTimeout 的 ID
let timeoutId = null;
// 函数来设置定时器
function setMyTimeout() {
// 清除之前的定时器(如果存在)
if (timeoutId) {
clearTimeout(timeoutId);
}
// 设置新的定时器
timeoutId = setTimeout(() => {
setTimeout(() =>{saveStatus.style.color = "rgba(0,0,0,0)";
mainLogic();
},1200);
}, 1200);
}
// 调用函数来设置定时器
setMyTimeout();
});
// 关闭按钮事件
modal.querySelector('#closeButton').addEventListener('click', () => {
closeModal();
});
// 添加模态对话框到页面
document.body.appendChild(modal);
}
// 关闭模态对话框
function closeModal() {
const modal = document.getElementById('keywordModal');
if (modal) {
modal.remove();
}
}
// 注册菜单命令
GM_registerMenuCommand('🔑 关键词设置', createModal);
const style = document.createElement('style');
style.innerHTML = `
#keywordModal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.2);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
#modalContent {
background-color: white;
padding: 20px;
border-radius: 8px;
width: 30%;
max-width: 300px;
min-width:240px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
font-size: 14px;
}
#modalContent h2 {
margin-bottom: 10px;
font-weight: bold;
font-size: 16px;
}
#modalContent ul {
margin-bottom: 10px;
font-size: 12px;
color: #555555;
}
#modalContent #keywordList {
max-width: 100%;
width: 100%;
height: 200px;
box-sizing: border-box;
padding: 6px;
border-radius: 4px;
border: 2px solid #D1D5DB;
font-family: inherit;
}
#modalContent #timeSelector {
padding: 10px 0;
}
#modalContent input {
vertical-align: -1.5px;
}
/* 设置选中状态下的checkbox样式 */
#modalContent input:checked {
background-color: #00AEEC; /* 你可以更改这个颜色为你想要的颜色 */
}
#modalContent #startTime, #modalContent #endTime {
padding:0 4px;
border-radius: 4px;
border: 2px solid #D1D5DB;
font-family: inherit;
}
#modalContent button {
margin-top: 10px;
padding: 5px 16px;
cursor: pointer;
border-radius: 4px;
border: none;
outline: 2px solid #D1D5DB;
font-size: 14px;
}
#modalContent #saveButton {
margin-right: 10px;
color: white;
background-color: #00AEEC;
outline: 2px solid #00AEEC;
}
#modalContent #saveStatus {
float: right;
margin-top: 10px;
color: rgba(0,0,0,0);
transition: all 0.2s ease-out;
font-size: 16px;
}
`;
document.head.appendChild(style);
})();