// ==UserScript==
// @name PKU-treeHole优化脚本
// @author PKUer
// @namespace http://tampermonkey.net/
// @version 2.0.2
// @license GPL-3.0 License
// @description 优化PKU-treeHole的使用体验,具体功能见https://greasyfork.org/zh-CN/scripts/464053-pku-treehole优化脚本
// @match https://treehole.pku.edu.cn/web/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=pku.edu.cn
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @grant unsafeWindow
// @grant GM_setClipboard
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_notification
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect cn.bing.com
// @connect wallhaven.cc
// @run-at document-end
// ==/UserScript==
// 部分代码借鉴了项目https://greasyfork.org/zh-CN/scripts/419081-知乎增强 ,因此基于GPL-3.0 License进行开源
// 壁纸刷新小风车图片来自 https://infinityicon.infinitynewtab.com/assets/windmill.svg
console.log('PKUtreeHole优化脚本来啦!');
//================================================================================================================
// 显示脚本菜单 start (前800行)
//================================================================================================================
/** 配置 */
const Config = {
copyFullText: { ID: 'menu_copyFullText', text: '复制全文', value: GM_getValue('menu_copyFullText', true), shownInMainMenu: true, hasSetPage: true },
showCopyFullTextAlert: { ID: 'menu_showCopyFullTextAlert', text: '复制长文成功时显示提醒框', value: GM_getValue('menu_showCopyFullTextAlert', true) },
cancelMaxHeight: { ID: 'menu_cancelMaxHeight', text: '取消长文限制', value: GM_getValue('menu_cancelMaxHeight', true), shownInMainMenu: true, hasSetPage: false },
configureWallPaperHub: { ID: 'menu_configureWallPaperHub', text: '配置壁纸库', value: GM_getValue('menu_configureWallPaperHub', true), shownInMainMenu: true, hasSetPage: true },
wallPaperHubSite: { ID: 'menu_wallPaperHubSite', text: '壁纸仓库', value: GM_getValue('menu_wallPaperHubSite', 'Bing') },
WallHavenUrl: { ID: 'menu_WallHavenUrl', text: 'WallHaven壁纸分类地址', value: GM_getValue('menu_WallHavenUrl', 'https://wallhaven.cc/search?categories=100&purity=100&sorting=toplist&order=desc&ai_art_filter=1'), tips: '填写WallHaven对应类别的网址' },
addSiteButton: { ID: 'menu_addSiteButton', text: '添加网站图标', value: GM_getValue('menu_addSiteButton', true), shownInMainMenu: true, hasSetPage: true },
addCourseEvaluationSiteButton: { ID: 'menu_addCourseEvaluationSiteButton', text: '添加课程评估网站图标', value: GM_getValue('menu_addCourseEvaluationSiteButton', true) },
addBBSSiteButton: { ID: 'menu_addBBSSiteButton', text: '添加未名BBS网站按钮', value: GM_getValue('menu_addBBSSiteButton', true) },
recordAlias: { ID: 'menu_recordAlias', text: '收藏树洞', value: GM_getValue('menu_recordAlias', true), shownInMainMenu: true, hasSetPage: true },
showAliasSuggestions: { ID: 'menu_showAliasSuggestions', text: '在搜索时触发收藏建议', value: GM_getValue('menu_showAliasSuggestions', true) },
recordHistory: { ID: 'menu_recordHistory', text: '保存搜索记录(关闭该功能会同时清除搜索记录)', value: GM_getValue('menu_recordHistory', true), shownInMainMenu: true, hasSetPage: true },
showHistorySuggestions: { ID: 'menu_showHistorySuggestions', text: '在搜索时触发历史记录建议', value: GM_getValue('menu_showHistorySuggestions', true) },
maxHistorySize: { ID: 'menu_maxHistorySize', text: '最大历史记录数量', value: parseInt(GM_getValue('menu_maxHistorySize', '200')), tips: '默认200,建议100~500' },
maxShownHistorySize: { ID: 'menu_maxShownHistorySize', text: '最大展示历史记录数量', value: parseInt(GM_getValue('menu_maxShownHistorySize', '10')), tips: '默认10,建议5~20' },
blockKeywords: { ID: 'menu_blockKeywords', text: '屏蔽指定关键词', value: GM_getValue('menu_blockKeywords', true), shownInMainMenu: true, hasSetPage: true },
customBlockKeywords: { ID: 'menu_customBlockKeywords', text: '自定义屏蔽关键词', value: GM_getValue('menu_customBlockKeywords', []), tips: "关键词不分大小写,使用 '|' 分隔" },
showLastestReplyTime: { ID: 'menu_showLastestReplyTime', text: '显示最近回复时间', value: GM_getValue('menu_showLastestReplyTime', true), shownInMainMenu: true, hasSetPage: false },
clickAgreeServiceAgreement: { ID: 'menu_clickAgreeServiceAgreement', text: '点击“同意北大树洞服务协议”选中框', value: GM_getValue('menu_clickAgreeServiceAgreement', false), shownInMainMenu: true, hasSetPage: false },
openTheFuckingURLRightNow: { ID: 'menu_openTheFuckingURLRightNow', text: '点击其它链接时,关闭提醒,自动跳转外链', value: GM_getValue('menu_openTheFuckingURLRightNow', false), shownInMainMenu: true, hasSetPage: false },
}
const wallPaperHub = {
Bing: "https://cn.bing.com",
WallHaven: "https://wallhaven.cc/"
}
// 注册脚本菜单
function registerMenuCommand() {
GM_registerMenuCommand('功能菜单', raiseMenu)
GM_registerMenuCommand('💬 反馈 & 建议', function () { window.GM_openInTab('https://greasyfork.org/zh-CN/scripts/460666-pku-treehole%E4%BC%98%E5%8C%96%E8%84%9A%E6%9C%AC/feedback', { active: true, insert: true, setParent: true }) })
}
/** 唤起设置UI */
function raiseMenu() {
if (document.querySelector("#zhmMenu")) return /* Safari兼容 */
GM_addStyle(menuSetStyle)
const root_body = document.querySelector('body')
const zhmMenu = document.createElement('div')
zhmMenu.id = 'zhmMenu'
zhmMenu.innerHTML = sidebar_menu_html + main_menu_html
root_body.appendChild(zhmMenu)
addcustomEvents()
}
/** 给UI绑定各种点击事件 */
function addcustomEvents() {
/* 给所有开关型的标签绑定事件,包括主页面和侧边页面(侧边栏) */
const circular = document.querySelectorAll('.circular')
circular.forEach(function (item) {
item.addEventListener('click', function (_e) {
const buttonStyle = item.children[0].style // 判断按钮开关情况
let left = buttonStyle.left
left = parseInt(left)
let listLeftValue
if (left == 0) { // 点击之前按钮是关闭状态,改成打开
buttonStyle.left = '22px'
buttonStyle.background = '#fe6d73'
item.style.background = '#ffE5E5'
if (item.nextElementSibling && item.nextElementSibling.getAttribute('data')) { // 如果是有右侧栏的按钮,右侧栏开放
item.nextElementSibling.setAttribute('style', 'border: solid #ccc;border-width: 0 3px 3px 0;')
}
listLeftValue = true
} else { // 点击之前按钮是打开状态,改成关闭
buttonStyle.left = '0px'
buttonStyle.background = '#fff'
item.style.background = '#fff'
if (item.nextElementSibling) { // 如果有右侧栏,则关闭右侧栏
item.nextElementSibling.setAttribute('style', 'border: solid #EEE;border-width: 0 3px 3px 0;')
}
listLeftValue = false
}
const setListID = item.id // 去除Enter后缀
GM_setValue(setListID, listLeftValue) // 将开关状态回写
})
})
/* 给所有的右箭头标签绑定事件 */
const toRight = document.querySelectorAll('.to-right')
toRight.forEach(function (item) {
item.addEventListener('click', function (e) {
let left = item.previousElementSibling.children[0].style.left // 忽略文本节点,找到.circular节点
left = parseInt(left) // 根据按钮开关情况选择是否给右箭头绑定事件
if (left != 22) return
const setPageID = item.getAttribute('data')
const pageId = document.getElementById(setPageID)
if (pageId) pageId.className = 'iconSetPage toLeftMove' // 进入右侧侧边栏
})
})
/* 给所有的左上角返回键标签绑定事件 */
const toBack = document.querySelectorAll('.back')
toBack.forEach(function (item) {
item.addEventListener('click', function (e) {
const parentDom = item.parentNode.parentNode.parentNode
parentDom.className = 'iconSetPage toRightMove'
// document.querySelector('#zhmTakePlace').style = 'height:40px;'
})
})
/* 给所有的关闭按钮标签绑定事件 */
const setSave = document.querySelectorAll('.iconSetSave')
setSave.forEach(function (item) {
item.addEventListener('click', () => {
let _customBlockKeywords = document.getElementById(Config.customBlockKeywords.ID).value
_customBlockKeywords = _customBlockKeywords.length ? _customBlockKeywords.split("|") : [] // 判断屏蔽关键词是否为空
let _wallPaperHubSite = document.getElementById(Config.wallPaperHubSite.ID).value
let _wallHavenUrl = document.getElementById(Config.WallHavenUrl.ID).value
let _maxHistorySize = document.getElementById(Config.maxHistorySize.ID).value
let _maxShownHistorySize = document.getElementById(Config.maxShownHistorySize.ID).value
GM_setValue(Config.customBlockKeywords.ID, _customBlockKeywords) // 保存屏蔽的关键词
GM_setValue(Config.wallPaperHubSite.ID, _wallPaperHubSite) // 保存壁纸选项
GM_setValue(Config.WallHavenUrl.ID, _wallHavenUrl) // 保存壁纸类别URL
GM_setValue(Config.maxHistorySize.ID, _maxHistorySize) // 保存最大历史记录数量
GM_setValue(Config.maxShownHistorySize.ID, _maxShownHistorySize) // 保存最大显示历史记录数量
history.go(0)
})
})
}
/**侧边栏HTML */
let sidebar_menu_html = `
<div id='setMask' class='zhmMask'></div>
<div class='wrap-box' id='setWrap'>
<!-- ${Config.copyFullText.text} -->
<div class='zhm_set_page' id='${Config.copyFullText.ID + 'Enter'}'>
<ul class='iconSetUlHead'>
<li class='iconSetPageHead'>
<span class='back'></span>
<span>${Config.copyFullText.text}</span>
<span class='iconSetSave'>×</span>
</li>
</ul>
<ul class='zhm_set_page_list'>
<li>
<div class="zhm_set_page_content">
<span>${Config.showCopyFullTextAlert.text}</span>
<div class="circular" style="background-color:${Config.showCopyFullTextAlert.value ? '#FFE5E5' : '#FFF'}" id="${Config.showCopyFullTextAlert.ID}">
<div class="round-button" style="background: ${Config.showCopyFullTextAlert.value ? '#fe6d73' : '#fff'} ; left: ${Config.showCopyFullTextAlert.value ? '22' : '0'}px;"></div>
</div>
</div>
</li>
</ul>
</div>
<!-- ${Config.configureWallPaperHub.text} -->
<div class='zhm_set_page' id='${Config.configureWallPaperHub.ID + 'Enter'}'>
<ul class='iconSetUlHead'>
<li class='iconSetPageHead'>
<span class='back'></span>
<span>${Config.configureWallPaperHub.text}</span>
<span class='iconSetSave'>×</span>
</li>
</ul>
<ul class='zhm_set_page_list'>
<li style='display: inline-flex;'>
<span style='padding-top:4px;'>${Config.wallPaperHubSite.text}:</span>
<div class='select-box'>
<select class='select-box__body' id='${Config.wallPaperHubSite.ID}'>`
/* 这个for循环是遍历所有壁纸库 */
for (let item in wallPaperHub) {
let siteSelected = Config.wallPaperHubSite.value === item ? 'selected' : '';
sidebar_menu_html += `<option value=${item} ${siteSelected}>${item}</option>`
}
sidebar_menu_html += `
</select>
</div>
</li>
<li>
<div>${Config.WallHavenUrl.text}</div>
<div style="margin-top: 10px; display: block; padding: 5px 0px;" id="">
<span class="text-input">
<input value="${Config.WallHavenUrl.value}" id="${Config.WallHavenUrl.ID}" class="text-input__body" placeholder="${Config.WallHavenUrl.tips}" style="width:88%">
</span>
</div>
</li>
</ul>
</div>
<!-- ${Config.addSiteButton.text} -->
<div class='zhm_set_page' id='${Config.addSiteButton.ID + 'Enter'}'>
<ul class='iconSetUlHead'>
<li class='iconSetPageHead'>
<span class='back'></span>
<span>${Config.addSiteButton.text}</span>
<span class='iconSetSave'>×</span>
</li>
</ul>
<ul class='zhm_set_page_list'>
<li>
<div class="zhm_set_page_content">
<span>${Config.addBBSSiteButton.text}</span>
<div class="circular" style="background-color:${Config.addBBSSiteButton.value ? '#FFE5E5' : '#FFF'}" id="${Config.addBBSSiteButton.ID}">
<div class="round-button" style="background: ${Config.addBBSSiteButton.value ? '#fe6d73' : '#fff'}; left: ${Config.addBBSSiteButton.value ? '22' : '0'}px;"></div>
</div>
</div>
</li>
<li>
<div class="zhm_set_page_content">
<span>${Config.addCourseEvaluationSiteButton.text}</span>
<div class="circular" style="background-color:${Config.addCourseEvaluationSiteButton.value ? '#FFE5E5' : '#FFF'}" id="${Config.addCourseEvaluationSiteButton.ID}">
<div class="round-button" style="background: ${Config.addCourseEvaluationSiteButton.value ? '#fe6d73' : '#fff'}; left: ${Config.addCourseEvaluationSiteButton.value ? '22' : '0'}px;"></div>
</div>
</div>
</li>
</ul>
</div>
<!-- ${Config.recordAlias.text} -->
<div class='zhm_set_page' id='${Config.recordAlias.ID + 'Enter'}'>
<ul class='iconSetUlHead'>
<li class='iconSetPageHead'>
<span class='back'></span>
<span>${Config.recordAlias.text}</span>
<span class='iconSetSave'>×</span>
</li>
</ul>
<ul class='zhm_set_page_list'>
<li>
<div class="zhm_set_page_content">
<span>${Config.showAliasSuggestions.text}</span>
<div class="circular" style="background-color:${Config.showAliasSuggestions.value ? '#FFE5E5' : '#FFF'} id="${Config.showAliasSuggestions.ID}">
<div class="round-button" style="background:${Config.showAliasSuggestions.value ? '#fe6d73' : '#fff'}; left: ${Config.showAliasSuggestions.value ? '22' : '0'}px;"></div>
</div>
</div>
</li>
</ul>
</div>
<!-- ${Config.recordHistory.text} -->
<div class='zhm_set_page' id='${Config.recordHistory.ID + 'Enter'}'>
<ul class='iconSetUlHead'>
<li class='iconSetPageHead'>
<span class='back'></span>
<span>${Config.recordHistory.text.substring(0, 6)}</span>
<span class='iconSetSave'>×</span>
</li>
</ul>
<ul class='zhm_set_page_list'>
<li>
<div class="zhm_set_page_content">
<span>${Config.showHistorySuggestions.text}</span>
<div class="circular" style="background-color:#${Config.showHistorySuggestions.value ? '#FFE5E5' : '#FFF'}" id="${Config.showHistorySuggestions.ID}">
<div class="round-button" style="background: ${Config.showHistorySuggestions.value ? '#fe6d73' : '#fff'}; left: ${Config.showHistorySuggestions.value ? '22' : '0'}px;"></div>
</div>
</div>
</li>
<li>${Config.maxHistorySize.text}
<span class="text-input">
<input value="${Config.maxHistorySize.value}" id="${Config.maxHistorySize.ID}" class="text-input__body" placeholder="${Config.maxHistorySize.tips}" style="width:88%">
</span>
</li>
<li>
${Config.maxShownHistorySize.text}
<span class="text-input">
<input value="${Config.maxShownHistorySize.value}" id="${Config.maxShownHistorySize.ID}" class="text-input__body" placeholder="${Config.maxShownHistorySize.tips}" style="width:88%">
</span>
</li>
</ul>
</div>
<!-- ${Config.blockKeywords.text} -->
<div class='zhm_set_page' id='${Config.blockKeywords.ID + 'Enter'}'>
<ul class='iconSetUlHead'>
<li class='iconSetPageHead'>
<span class='back'></span>
<span>${Config.blockKeywords.text}</span>
<span class='iconSetSave'>×</span>
</li>
</ul>
<ul class='zhm_set_page_list'>
<li>
<div>${Config.customBlockKeywords.text}</div>
<div style="margin-top: 10px; display: block; padding: 5px 0px;" id="">
<span class="text-input">
<input value="${Config.customBlockKeywords.value.length ? Config.customBlockKeywords.value.join('|') : ''}" id="${Config.customBlockKeywords.ID}" class="text-input__body" placeholder="${Config.customBlockKeywords.tips}" style="width:88%">
</span>
</div>
</li>
<!-- 多行文本框的写法:(这种写法目前无法屏蔽表情包,先不用)
<li>
<div>${Config.customBlockKeywords.text}</div>
<div class="form__textarea">
<div class="textarea js-flexible-textarea" style="padding: 5px 0px;" id="">
<textarea rows="9" class="textarea__body zhm_scroll" placeholder="${Config.customBlockKeywords.tips}" style="width:250px;font-size:12px;padding:4px;resize:none;" id="playVideoLineTextarea"></textarea>
</div>
</div>
</li>
-->
</ul>
</div>
`
/** 主界面UI的HTML */
let main_menu_html = `
<!-- 下面是主界面UI -->
<ul class="iconSetUlHead">
<li class="iconSetPageHead">
<span></span>
<span>设置</span>
<span class="iconSetSave">×</span>
</li>
</ul>
<ul class="setWrapLi">`
/** 绑定右箭头的跳转页面 */
for (let item in Config) {
if (Config[item].shownInMainMenu) {
var listValue = Config[item].value; // 查找之前的记录
let backColor, arrowColor, switchBackCorlor;
if (!listValue) {
backColor = '#fff';
arrowColor = '#EEE';
switchBackCorlor = '#FFF';
} else {
backColor = '#fe6d73';
arrowColor = '#CCC';
switchBackCorlor = '#FFE5E5';
}
if (!Config[item].hasSetPage) {
arrowColor = '#EEE';
};
main_menu_html +=
`
<li><span>${Config[item].text}</span>
<div class='setWrapLiContent'>
<div class='circular' id='${Config[item].ID}' style='background-color: ${switchBackCorlor};'>
<div class='round-button' style='background: ${backColor}; left: ${listValue ? 22 : 0}px'></div>
</div>
<span class='to-right' data='${Config[item].hasSetPage ? Config[item].ID + 'Enter' : ""}' takePlace='${Config[item].takePlace ? 220 : 0}' style='border: solid ${arrowColor}; border-width: 0 3px 3px 0;'></span>
</div>
</li>
`
}
}
main_menu_html += `
<div style='height:40px;' id='zhmTakePlace'></div>
<div class='iconSetFoot' style=''>
<ul class='iconSetFootLi'>
<li></li>
</ul>
</ul>
</div>
</div>
`
/** UI的CSS样式 */
var menuSetStyle = `
.zhmMask{
z-index:999999999;
background-color:#000;
position: fixed;top: 0;right: 0;bottom: 0;left: 0;
opacity:0.8;
}
.wrap-box{
z-index:1000000000;
position:fixed;;top: 40%;left: 50%;transform: translate(-50%, -200px);
width: 300px;
color: #555;
background-color: #fff;
border-radius: 5px;
overflow:hidden;
font:16px numFont,PingFangSC-Regular,Tahoma,Microsoft Yahei,sans-serif !important;
font-weight:400 !important;
}
.setWrapHead{
background-color:#f24443;height:40px;color:#fff;text-align:center;line-height:40px;
}
.setWrapLi{
margin:0px;padding:0px;
}
.setWrapLi li{
background-color: #fff;
border-bottom:1px solid #eee;
margin:0px !important;
padding:12px 20px;
display: flex;
justify-content: space-between;align-items: center;
list-style: none;
}
.setWrapLiContent{
display: flex;justify-content: space-between;align-items: center;
}
.setWrapSave{
position:absolute;top:-2px;right:10px;font-size:24px;cursor:pointer
}
.iconSetFoot{
position:absolute;bottom:0px;padding:10px 20px;width:100%;
z-index:1000000009;background:#fef9ef;
}
.iconSetFootLi{
margin:0px;padding:0px;
}
.iconSetFootLi li{
display: inline-flex;
padding:0px 2px;
justify-content: space-between;align-items: center;
font-size: 12px;
}
.iconSetFootLi li a{
color:#555;
}
.iconSetFootLi a:hover {
color:#fe6d73;
}
.iconSetPage{
z-index:1000000001;
position:absolute;top:0px;left:300px;
background:#fff;
width:300px;
height:100%;
}
.iconSetUlHead{
padding:0px;
margin:0px;
}
.iconSetPageHead{
border-bottom:1px solid #ccc;
height:40px;
line-height:40px;
display: flex;
justify-content: space-between;
align-items: center;
background-color:#fe6d73;
color:#fff;
font-size: 15px;
}
.iconSetPageLi{
margin:0px;padding:0px;
}
.iconSetPageLi li{
list-style: none;
padding:8px 20px;
border-bottom:1px solid #eee;
}
.zhihuSetPage{
z-index:1000000002;position:absolute;top:0px;left:300px;background:#fff;width:300px;height:100%;
}
.iconSetPageInput{
display: flex !important;justify-content: space-between;align-items: center;
}
.zhihuSetPageLi{
margin:0px;padding:0px;
height:258px;
overflow-y: scroll;
}
.zhihuSetPageContent{
display: flex !important;justify-content: space-between;align-items: center;
}
.circular{
width: 40px;height: 20px;border-radius: 16px;transition: .3s;cursor: pointer;box-shadow: 0 0 3px #999 inset;
}
.round-button{
width: 20px;height: 20px;;border-radius: 50%;box-shadow: 0 1px 5px rgba(0,0,0,.5);transition: .3s;position: relative;
}
.back{
border: solid #FFF; border-width: 0 3px 3px 0; display: inline-block; padding: 3px;transform: rotate(135deg); -webkit-transform: rotate(135deg);margin-left:10px;cursor:pointer;
}
.to-right{
margin-left:20px; display: inline-block; padding: 3px;transform: rotate(-45deg); -webkit-transform: rotate(-45deg);cursor:pointer;
}
.iconSetSave{
font-size:24px;cursor:pointer;margin-right:5px;margin-bottom:4px;color:#FFF;
}
.zhm_set_page{
z-index:1000000003;
position:absolute;
top:0px;left:300px;
background:#fff;
width:300px;
height:100%;
}
.zhm_set_page_header{
border-bottom:1px solid #ccc;
height:40px;
line-height:40px;
display: flex;
justify-content: space-between;
align-items: center;
background-color:#fe6d73;
color:#fff;
font-size: 15px;
}
.zhm_set_page_content{
display: flex !important;justify-content: space-between;align-items: center;
}
.zhm_set_page_list{
margin:0px;padding:0px;
height: 220px;
overflow-y: scroll;
}
.zhm_set_page_list::-webkit-scrollbar {
/*滚动条整体样式*/
width : 0px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
.zhm_set_page_list::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius : 2px;
background-color: #fe6d73;
}
.zhm_set_page_list::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow : inset 0 0 5px rgba(0, 0, 0, 0.2);
background : #ededed;
border-radius: 10px;
}
.zhm_set_page_list li{
/*border-bottom:1px solid #ccc;*/
padding:12px 20px;
display:block;
border-bottom:1px solid #eee;
}
li:last-child{
border-bottom:none;
}
.zhm_scroll{
overflow-y: scroll !important;
}
.zhm_scroll::-webkit-scrollbar {
/*滚动条整体样式*/
width : 0px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
.zhm_scroll::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius : 2px;
background-color: #fe6d73;
}
.zhm_scroll::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow : inset 0 0 5px rgba(0, 0, 0, 0.2);
background : #ededed;
border-radius: 10px;
}
/*-form-*/
:root {
--base-color: #434a56;
--white-color-primary: #f7f8f8;
--white-color-secondary: #fefefe;
--gray-color-primary: #c2c2c2;
--gray-color-secondary: #c2c2c2;
--gray-color-tertiary: #676f79;
--active-color: #227c9d;
--valid-color: #c2c2c2;
--invalid-color: #f72f47;
--invalid-icon: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%20%3Cpath%20d%3D%22M13.41%2012l4.3-4.29a1%201%200%201%200-1.42-1.42L12%2010.59l-4.29-4.3a1%201%200%200%200-1.42%201.42l4.3%204.29-4.3%204.29a1%201%200%200%200%200%201.42%201%201%200%200%200%201.42%200l4.29-4.3%204.29%204.3a1%201%200%200%200%201.42%200%201%201%200%200%200%200-1.42z%22%20fill%3D%22%23f72f47%22%20%2F%3E%3C%2Fsvg%3E");
}
.text-input {
font-size: 16px;
position: relative;
right:0px;
z-index: 0;
}
.text-input__body {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: transparent;
border: 1px solid var(--gray-color-primary);
border-radius: 3px;
height: 1.7em;
line-height: 1.7;
overflow: hidden;
padding: 2px 1em;
text-overflow: ellipsis;
transition: background-color 0.3s;
width:55%;
font-size:14px;
}
.text-input__body:-ms-input-placeholder {
color: var(--gray-color-secondary);
}
.text-input__body::-moz-placeholder {
color: var(--gray-color-secondary);
}
.text-input__body::placeholder {
color: var(--gray-color-secondary);
}
*, ::after, ::before {
box-sizing: initial !important;
}
.text-input__body[data-is-valid] {
padding-right: 1em;
}
.text-input__body[data-is-valid=true] {
border-color: var(--valid-color);
}
.text-input__body[data-is-valid=false] {
border-color: var(--invalid-color);
box-shadow: inset 0 0 0 1px var(--invalid-color);
}
.text-input__body:focus {
border-color: var(--active-color);
box-shadow: inset 0 0 0 1px var(--active-color);
outline: none;
}
.text-input__body:-webkit-autofill {
transition-delay: 9999s;
-webkit-transition-property: background-color;
transition-property: background-color;
}
.text-input__validator {
background-position: right 0.5em center;
background-repeat: no-repeat;
background-size: 1.5em;
display: inline-block;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: -1;
}
.text-input__body[data-is-valid=false] + .text-input__validator {
background-image: var(--invalid-icon);
}
.select-box {
box-sizing: inherit;
font-size: 16px;
position: relative;
transition: background-color 0.5s ease-out;
width:90px;
}
.select-box::after {
border-color: var(--gray-color-secondary) transparent transparent transparent;
border-style: solid;
border-width: 6px 4px 0;
bottom: 0;
content: "";
display: inline-block;
height: 0;
margin: auto 0;
pointer-events: none;
position: absolute;
right: -72px;
top: 0;
width: 0;
z-index: 1;
}
.select-box__body {
box-sizing: inherit;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: transparent;
border: 1px solid var(--gray-color-primary);
border-radius: 3px;
cursor: pointer;
height: 1.7em;
line-height: 1.7;
padding-left: 1em;
padding-right: calc(1em + 16px);
width: 140%;
font-size:14px;
padding-top:2px;
padding-bottom:2px;
}
.select-box__body[data-is-valid=true] {
border-color: var(--valid-color);
box-shadow: inset 0 0 0 1px var(--valid-color);
}
.select-box__body[data-is-valid=false] {
border-color: var(--invalid-color);
box-shadow: inset 0 0 0 1px var(--invalid-color);
}
.select-box__body.focus-visible {
border-color: var(--active-color);
box-shadow: inset 0 0 0 1px var(--active-color);
outline: none;
}
.select-box__body:-webkit-autofill {
transition-delay: 9999s;
-webkit-transition-property: background-color;
transition-property: background-color;
}
.textarea__body {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: transparent;
border: 1px solid var(--gray-color-primary);
border-radius: 0;
box-sizing: border-box;
font: inherit;
left: 0;
letter-spacing: inherit;
overflow: hidden;
padding: 1em;
position: absolute;
resize: none;
top: 0;
transition: background-color 0.5s ease-out;
width: 100%;
}
.textarea__body:only-child {
position: relative;
resize: vertical;
}
.textarea__body:focus {
border-color: var(--active-color);
box-shadow: inset 0 0 0 1px var(--active-color);
outline: none;
}
.textarea__body[data-is-valid=true] {
border-color: var(--valid-color);
box-shadow: inset 0 0 0 1px var(--valid-color);
}
.textarea__body[data-is-valid=false] {
border-color: var(--invalid-color);
box-shadow: inset 0 0 0 1px var(--invalid-color);
}
.textarea ._dummy-box {
border: 1px solid;
box-sizing: border-box;
min-height: 240px;
overflow: hidden;
overflow-wrap: break-word;
padding: 1em;
visibility: hidden;
white-space: pre-wrap;
word-wrap: break-word;
}
.toLeftMove{
nimation:moveToLeft 0.5s infinite;
-webkit-animation:moveToLeft 0.5s infinite; /*Safari and Chrome*/
animation-iteration-count:1;
animation-fill-mode: forwards;
}
@keyframes moveToLeft{
from {left:200px;}
to {left:0px;}
}
@-webkit-keyframes moveToLeft /*Safari and Chrome*/{
from {left:200px;}
to {left:0px;}
}
.toRightMove{
nimation:moveToRight 2s infinite;
-webkit-animation:moveToRight 2s infinite; /*Safari and Chrome*/
animation-iteration-count:1;
animation-fill-mode: forwards;
}
@keyframes moveToRight{
from {left:0px;}
to {left:2000px;}
}
@-webkit-keyframes moveToRight /*Safari and Chrome*/{
from {left:0px;}
to {left:2000px;}
}
`;
/** 注册功能开关 */
registerMenuCommand()
//================================================================================================================
// 显示脚本菜单 end
//================================================================================================================
//================================================================================================================
// 功能函数,用于对付树洞版本更新带来的节点属性data-v-xxxx的改变 start
//================================================================================================================
/**
* 返回节点的 data-v-xxxx 属性名称
* @param {Node} Node
* @return {String|undefined} attribute
* 由于树洞在更新版本之后,会将节点属性data-v-xxxx作更新,对应的css属性也会更新。为了更好的兼容后续版本,需要将对应节点的data-v也作实时更新
*/
function getDataVersionByNode(Node) {
if (Node.hasAttributes()) {
const attributes_names_array = Node.getAttributeNames();
for (var i = 0; i < attributes_names_array.length; ++i) {
if (attributes_names_array[i].startsWith("data-v-")) {
return attributes_names_array[i]
}
}
}
return undefined
}
/** 将cookie转换成对象 */
function _getCookieObj() {
var cookieObj = {};
var cookieStr = document.cookie;
var pairList = cookieStr.split(';');
for (var _i = 0, pairList_1 = pairList; _i < pairList_1.length; _i++) {
var pair = pairList_1[_i];
var _a = pair.trim().split('='), key = _a[0], value = _a[1];
cookieObj[key] = value;
}
return cookieObj;
}
//================================================================================================================
// 功能函数,用于对付树洞版本更新带来的节点属性data-v-xxxx的改变 end
//================================================================================================================
//================================================================================================================
// 取消最大高度限制 start
//================================================================================================================
function cancelMaxHeight() {
if (!Config.cancelMaxHeight.value) return
console.log('取消最大高度限制')
document.styleSheets[0].insertRule('.left-container .sidebar .box-content { max-height: none !important }', 0)
}
//================================================================================================================
// 取消最大高度限制 end
//================================================================================================================
//================================================================================================================
// 添加“复制全文”标签 start
//================================================================================================================
// 添加“复制全文”css图标
function addCopyFullTextButtonIcon() {
if (!Config.copyFullText.value) return
// 判断是否为夜晚模式
var dark_mode = document.querySelector("body").getAttribute("style").indexOf("--theme_bgc_color:rgba(31,31,31,0.8); --theme_font_color:#ededed;") > -1;
GM_addStyle(`div.box-header.box-header-top-icon{
overflow: visible;
}`) // 这一步是为了“复制全文”标签超出header框之后仍然能显示
GM_addStyle(`
div:nth-child(3) > div.box-header.box-header-top-icon > code:hover:before {
content: "复制全文";
position: relative;
width: 5em;
height: 1.3em;
line-height: 1.3em;
margin-bottom: -1.3em;
border-radius: 3px;
text-align: center;
top: -1.5em;
display: block;
color: #fff;
background-color: rgba(0,0,0,.6);
pointer-events: none;
} `
)// “复制全文”标签
GM_addStyle(`
div:nth-child(3) > div.box-header.box-header-top-icon > code:hover{
text-decoration: underline; /* 下划线 */
color: ${dark_mode ? "#9BF" : "#00C"};
}
`)
}
// 使用MutationObserver观察器观察dom子节点变动,判断是否进入树洞详情页,从而判断是否添加“复制全文”点击事件
function addCopyFullTextButton() {
if (!Config.copyFullText.value) return
console.log('新增复制全文按钮');
waitForKeyElements('#eagleMapContainer div.sidebar > div.sidebar-content.sidebar-content-show > div > div:nth-child(3) > div.box-header.box-header-top-icon > code',
(codeNodes) => {
let codeNode = codeNodes[0]
var triggered = false;
codeNode.addEventListener("click", async (event) => {
if (triggered) { console.log("请耐心等待复制结果,不要多次点击复制全文"); return }
triggered = true; // 防止重复触发
event.stopPropagation(); // 停止点击事件传播
const content = await _copy_content_now(codeNode.innerText.replace("#", "").trim());
GM_setClipboard(content); // 复制文本进入剪切板
if (Config.showCopyFullTextAlert.value) // 判断复制成功是否需要提醒
alert("复制成功!");
else
console.log("复制成功!")
triggered = false; // 重置触发状态
}, { capture: true })
}
)
}
// 获取树洞的内容,以及关注数、回复数
async function _getBoxIdDetail(box_id) {
const content = await new Promise((resolve, reject) => {
fetch("https://treehole.pku.edu.cn/api/pku/" + box_id, {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"authorization": "Bearer " + _getCookieObj()["pku_token"],
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"uuid": localStorage.getItem("pku-uuid"),
},
"referrer": "https://treehole.pku.edu.cn/web/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
}).then(response => {
resolve(response.json())
}).catch(err => {
reject(err)
})
})
return content;
}
async function _getReplys(box_id, pages, sort = "asc") {
try {
var fetch_list = []
var timeout = 0
// 每隔一段时间进行一次请求,避免被封,也减小服务器压力
for (let page = 1; page <= pages; ++page) {
timeout = timeout + 200;
fetch_list.push(new Promise((resolve, reject) => {
setTimeout(() => {
fetch(
`https://treehole.pku.edu.cn/api/pku_comment_v3/${box_id}?page=${page}&limit=15&sort=${sort}`, {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"authorization": "Bearer " + _getCookieObj()["pku_token"],
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"uuid": localStorage.getItem("pku-uuid"),
},
"referrer": "https://treehole.pku.edu.cn/web/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
}
).then((response) => resolve(response.json())).catch((err) => reject(err))
}, timeout);
}))
}
const data_list = await Promise.all(fetch_list)
return data_list
} catch (error) {
console.error(error);
throw error;
}
}
async function _copy_content_now(box_id) {
const box_id_detail = await _getBoxIdDetail(box_id);
var copy_content = ""
if (box_id_detail.code === 20000) {
const box_id_reply_num = box_id_detail.data.reply // 回复数
const box_id_like_num = box_id_detail.data.likenum // 关注数
const box_id_content = box_id_detail.data.text // 内容
const box_id_time = box_id_detail.data.timestamp // 时间
let box_id_replys = await _getReplys(box_id, Math.ceil(box_id_reply_num / 15)) // 回复列表
box_id_replys = box_id_replys.map((reply) => reply.data.data).flat(1)
copy_content = `#${box_id} ${timeFormat(box_id_time)} 关注数:${box_id_like_num} 回复数:${box_id_reply_num}\n${box_id_content}\n`
box_id_replys.forEach((reply) => {
copy_content += `#${reply.cid} ${timeFormat(reply.timestamp)}\n[${reply.name}] ${reply.text}\n`
})
}
return copy_content;
}
//================================================================================================================
// 添加“复制全文”标签 end
//================================================================================================================
//================================================================================================================
// 设置随机壁纸 start
// ===============================================================================================================
/**
* 在页面的右下角插入一张风车svg图片,点击风车会切换壁纸
*
*/
var angle = 0; // 旋转角度
var speed_up_acceleration = 0.05; // 加速加速度
var speed_down_acceleration = 0.02; // 减速加速度
var speed = 0.5; // 初始旋转速度
var min_speed = 0.1; // 最小旋转速度,小于该速度风车停下
var max_speed = 5.4; // 最大旋转速度(一秒钟转一圈半),大于该速度风车不再加速
var is_rotating = false; // 是否正在旋转,防止多次点击风车
/**
* Define a function to rotate the image with easing
* @param {HTMLImageElement} img
* @returns {number} setInterval's ID
*/
function rotateImage(img) {
speed = 0.5;
is_rotating = true;
const rotateInterval = setInterval(() => {
if (speed < max_speed) {
angle = (angle - speed) % 360;
speed += speed_up_acceleration;
}
else {
angle = (angle - max_speed) % 360;
}
img.style.transform = `rotate(${angle}deg)`;
}, 10);
return rotateInterval;
}
/**
* Define a function to stop the rotation with easing
* @param {number} rotateInterval
* @param {HTMLImageElement} img
*/
function stopRotation(rotateInterval, img) {
clearInterval(rotateInterval);
const stopRotateInterval = setInterval(() => {
if (speed >= min_speed) {
angle = (angle - speed) % 360;
speed -= speed_down_acceleration;
}
else {
speed = 0;
clearInterval(stopRotateInterval);
}
img.style.transform = `rotate(${angle}deg)`;
}, 10);
is_rotating = false;
}
// 配置随机壁纸库
function configureWallPaperHub() {
if (!Config.configureWallPaperHub.value) return
// 添加右下角风车图标
var windMillNode = document.createElement("div")
windMillNode.setAttribute("class", "windmill")
var windMillImageNode = document.createElement("img")
windMillImageNode.id = "windmill"
windMillImageNode.src = "https://infinityicon.infinitynewtab.com/assets/windmill.svg"
var windMillStickNode = document.createElement("span")
windMillNode.appendChild(windMillImageNode)
windMillNode.appendChild(windMillStickNode)
document.querySelector("body").appendChild(windMillNode)
// 给风车添加css样式
GM_addStyle(`
.windmill {
position: absolute;
right: 60px;
bottom: 0px;
display: flex;
flex-direction: column;
}
.windmill img {
z-index: 1;
cursor: pointer;
width: 40px;
height: 40px;
}
.windmill span {
display: block;
margin-top: -30px;
margin-left: auto;
margin-right: auto;
width: 5px;
height: 56px;
background-color: rgb(254, 254, 254);
}
`)
if (localStorage.getItem("pku_background_id") != "6") {
localStorage.setItem("pku_background_id", "6") // 设置背景图片来自图片网址
}
if (Config.wallPaperHubSite.value == 'Bing') {
windMillImageNode.addEventListener("click", function () {
if (!is_rotating) {
const rotateInterval = rotateImage(windMillImageNode);
getOneBingWallPaper(callback = () => { stopRotation(rotateInterval, windMillImageNode) });
}
})
} else if (Config.wallPaperHubSite.value == 'WallHaven') {
windMillImageNode.addEventListener("click", function () {
if (!is_rotating) {
const rotateInterval = rotateImage(windMillImageNode);
getOneWallHavenPaper(callback = () => { stopRotation(rotateInterval, windMillImageNode) });
}
})
}
}
/** 获取一张Bing随机壁纸 */
function getOneBingWallPaper(callback) {
GM_xmlhttpRequest({
method: "GET",
url: "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=10",
onload: function (res) {
try {
if (res.status == 200) {
var text = res.responseText;
var json = JSON.parse(text);
// console.log(json);
const img = json.images[Math.round(Math.random() * (8))];
const figure_href = `https://cn.bing.com${img.url.split("_1920x1080")[0]}_UHD.jpg`;
// 先请求看图片网址是否有效,如果有效再将图片网址存入localStorage
// 这一步利用了HTTP的缓存机制,网页不会重复发送请求
GM_xmlhttpRequest({
method: "GET",
url: figure_href,
onload: function (response) {
if (response.status == 200) {
let tempImgNode = document.createElement('img');
tempImgNode.src = figure_href;
tempImgNode.onload = function () {
localStorage.setItem("pku_background_data", figure_href);
document.querySelector("#app > div.bg-img").style = `background: url("${figure_href}") center center / cover rgb(29, 71, 134);`
console.log(`从Bing获取壁纸成功:网址:${figure_href}`);
callback(); // 停止风车旋转
}
} else {
throw new Error(`wallhaven网址 ${figure_dic.href} 请求失败:状态码 + ${response.status}`);
}
}
});
} else {
throw new Error(`Bing网址 ${url} 请求失败:状态码 + ${res.status}`);
}
} catch (error) {
console.error(error)
console.log("无法从Bing获取最新壁纸!");
callback(); // 停止风车旋转
}
}
});
}
/**获取一张WallHaven随机壁纸 */
function getOneWallHavenPaper(callback) {
if (Config.WallHavenUrl.value) {
var _url = Config.WallHavenUrl.value;
var given_page = false; // 是否指定了页数
// 如果url以“&page=数字”结尾,则去直接在该页获取壁纸
if (_url.match(/&page=\d+$/)) {
given_page = true;
}
// 根据url请求壁纸的第一页
GM_xmlhttpRequest({
method: "GET",
url: _url,
onload: async function (res) {
try {
if (res.status == 200) {
var text = res.responseText;
const figure_dic = await _getRandomOneHref(text, given_page); // 获取随机一页的随机一张图片
// 先请求看图片网址是否有效,如果有效再将图片网址存入localStorage
// 这一步利用了HTTP的缓存机制,网页不会重复发送请求
GM_xmlhttpRequest({
method: "GET",
url: figure_dic.href,
onload: function (response) {
if (response.status == 200) {
// 使用img节点监听图片是否加载完成,完成了再显示壁纸
let tempImgNode = document.createElement('img');
tempImgNode.src = figure_dic.href;
tempImgNode.onload = function () {
localStorage.setItem("pku_background_data", figure_dic.href);
document.querySelector("#app > div.bg-img").style = `background: url("${figure_dic.href}") center center / cover rgb(29, 71, 134);`
console.log(`从wallhaven获取壁纸成功:第${figure_dic.page}页,第${figure_dic.index}张壁纸,网址:${figure_dic.href}`);
callback(); // 停止风车旋转
}
} else {
throw new Error(`wallhaven网址 ${figure_dic.href} 请求失败:状态码 + ${response.status}`);
}
}
})
}
else {
throw new Error(`wallhaven网址 ${figure_dic.href} 请求失败:状态码 + ${response.status}`);
}
} catch (error) {
console.error(error)
console.log("无法从wallhaven获取最新壁纸!请尝试手动访问Wallhaven网站,以确保访问正常!");
callback(); // 停止风车旋转
}
}
})
} else {
console.log("请在功能开关里输入WallHaven分类地址!")
}
/**
* 随机一张壁纸
* @param {string} text 从网页获取的html文本
* @param {boolean} given_page 是否指定了页数;若指定,则text为指定页数的网页html文本,否则text为主页的网页html文本
*/
async function _getRandomOneHref(text, given_page = false) {
/* 使用doc节点的加载方式解析第一页(主页) */
var WallHavenDoc = document.createElement('wallhaven')
WallHavenDoc.innerHTML = text
// 如果没有给定页数,则随机一个页数
if (!given_page) {
// 获取总页数
var total_pages = 1 // 总页数
// 解析总页数所在json字符串
try {
var pages_json_string = WallHavenDoc.querySelector("ul.pagination").getAttribute("data-pagination")
let pages_json = JSON.parse(pages_json_string)
total_pages = pages_json.total
} catch (error) {
console.log(error)
console.log("解析总页数失败(可能是因为总页数为1),只返回第一页的壁纸。")
}
// 在总页数中随机一个页数(如果只有一页,这种计算方式下page=1)
var page = Math.floor(Math.random() * total_pages) + 1
// 如果随机到第一页(主页),由于已经请求过了,就不再做请求
// 如果随机到的不是第一页(主页),需要再做一次请求
if (page != 1) {
_url = _url + `&page=${page}`
// 做一次fetch,请求随机的一页
async function fetchOnePage(_url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: _url,
onload: function (response) {
resolve(response);
},
onerror: function (error) {
reject(error);
}
});
});
}
var res = await fetchOnePage(_url)
if (res.status == 200) {
WallHavenDoc.innerHTML = res.responseText // 重新加载doc节点为第page页的html文本
}
}
}
// 随机获取该页的一张图片
var thumbs = WallHavenDoc.querySelectorAll("div.thumb-info")
var arr = []
for (let thumb_info of thumbs) {
let id = thumb_info.querySelector("a.jsAnchor.thumb-tags-toggle.tagged").getAttribute("data-href", "").replace('https://wallhaven.cc/wallpaper/tags/', '').trim()
if (id && thumb_info.querySelector('span.png')) {
arr.push(`https://w.wallhaven.cc/full/${id.substring(0, 2)}/wallhaven-${id}.png`)
} else {
arr.push(`https://w.wallhaven.cc/full/${id.substring(0, 2)}/wallhaven-${id}.jpg`)
}
}
let index = Math.floor(Math.random() * arr.length)
// 返回壁纸信息
const figure_dic = {
index: index,
page: page,
href: arr[index],
}
return figure_dic
}
}
//================================================================================================================
// 设置随机壁纸 end
// ===============================================================================================================
//================================================================================================================
// 屏蔽关键词 start
// ===============================================================================================================
// 屏蔽主页面树洞关键词
function blockChunkKeywords(item) {
if (!Config.blockKeywords.value) return
if (!Config.customBlockKeywords.value || Config.customBlockKeywords.value.length < 1) return
_checkNode(item)
}
// 屏蔽侧边栏(详情页)关键词
function blockSidebarKeywords() {
if (!Config.blockKeywords.value) return
if (!Config.customBlockKeywords.value || Config.customBlockKeywords.value.length < 1) return
const callback = (mutationsList, observer) => {
for (const mutation of mutationsList) {
for (const item of mutation.addedNodes) { _checkNode(item) }
}
};
const sidebar_container_observer = new MutationObserver(callback) // 这个观察器是用来观察侧边栏的box
waitForKeyElements("div.sidebar-content.sidebar-content-show > div", (sidebar_container) => {
sidebar_container_observer.observe(sidebar_container[0], { childList: true });
for (let item of sidebar_container[0].childNodes) { _checkNode(item) } // 这个for循环是为了避免在加载评论时,观察器晚加载导致前面的节点没进行关键词检测
})
}
// 检查节点中是否含有屏蔽词
function _checkNode(Node) {
for (const keyword of Config.customBlockKeywords.value) { // 遍历关键词黑名单
let text = Node.content || Node.textContent;
if (keyword != '' && text.toLowerCase().indexOf(keyword.toLowerCase()) > -1) { // 找到就删除该信息流
console.log('已屏蔽:' + text);
Node.hidden = true;
Node.style.display = 'none';
break;
}
}
}
//================================================================================================================
// 屏蔽关键词 end
// ===============================================================================================================
//================================================================================================================
// 记录别名 start
// ===============================================================================================================
/**
* 这个函数的作用是在“账户”侧边栏里显示收藏的树洞别名
*/
function recordAlias(params) {
if (!Config.recordAlias.value) return
if (!GM_getValue("Alias_json", "")) GM_setValue("Alias_json", "{}");
waitForKeyElements('#eagleMapContainer > div.title-bar > div.control-bar > div > div:nth-child(2) > div > a:nth-child(3)', (abouts) => {
const callback = () => {
// 在sidebar区添加文本框
var text_area_nodes = _create_text_area_nodes()
// 等待侧边栏唤起
waitForKeyElements('#eagleMapContainer div.box.box-right',
(nodes) => {
var box_right_node = nodes[0]
var hr = document.createElement('hr') // 空行
hr.setAttribute('style', 'margin: 16px 0')
box_right_node.appendChild(hr) // 添加空行
box_right_node.appendChild(text_area_nodes) // 添加别名文本框节点
load_alias()
let config_text_area_node = text_area_nodes.querySelector('.config-textarea')
config_text_area_node.addEventListener("change", save_alias)
}
)
}
var about = abouts[0] // “搜索”右侧的“账户”按钮的选择器
about.addEventListener('click', callback) // 点击“账户”按钮,唤起侧边栏时,给侧边栏添加别名文本框
function _create_text_area_nodes() {
var text_area_nodes = document.createElement('div')
text_area_nodes.innerHTML = `
<div>
<div class="row-bg el-row" style="margin-left: -5px; margin-right: -5px;">
<div class="el-col el-col-24 el-col-xs-24 el-col-sm-5 el-col-md-5 el-col-lg-5 el-col-xl-5" style="padding-left: 5px; padding-right: 5px;">
<b >洞号别名:</b>
</div>
<div class="el-col el-col-24 el-col-xs-24 el-col-sm-19 el-col-md-19 el-col-lg-19 el-col-xl-19" style="padding-left: 5px; padding-right: 5px;">
<textarea name="config-alias" id="config-t extarea-alias" class="config-textarea" style="
margin-top: 0.5em; \
width: 100%; \
max-width: 100%; \
min-width: 100%; \
height: 7em; \
min-height: 2em;">
</textarea>
</div>
</div>
<p class="config-description">在这里记录别名和洞号(请用“别名+空格+洞号”的格式记录,以支持搜索提醒)</p>
</div>
`
return text_area_nodes
}
}
)
}
/** 将收藏的树洞加载到“账户”的侧边栏详情页中 */
function load_alias() {
let alias_json_str = GM_getValue("Alias_json", "{}");
if (alias_json_str) {
try {
let alias_dic = JSON.parse(alias_json_str)
let config_text_area_node = document.querySelector('.config-textarea')
let alias = "";
for (let key in alias_dic) {
alias = alias + key + " " + alias_dic[key] + "\n"
}
config_text_area_node.value = alias;
} catch (error) {
console.log("解析洞号别名错误!" + error)
}
} else {
let config_text_area_node = document.querySelector('.config-textarea')
let alias = "";
config_text_area_node.value = alias;
}
}
/** 将“账户”侧边栏的收藏树洞保存 */
function save_alias() {
let config_text_area_node = document.querySelector('.config-textarea');
let alias = config_text_area_node.value;
let alias_arr = alias.split("\n")
let alias_dic = {}
for (let alia_str of alias_arr) {
if (alia_str.length > 0) { // 不是空行
// alia_str = alia_str.trim()
// let alia_arr = alia_str.split(" ").filter(d => d) // 去掉中间多余的空格
let alia_arr = alia_str.split(" ")
if (alia_arr.length) {
let key = "", value = ""
if (alia_arr.length == 2) { key = alia_arr[0], value = alia_arr[1]; } // 刚好是key-value键值对
else if (alia_arr.length == 1) { key = alia_arr[0], value = ""; } // 这一行没有空格
else { key = alia_arr.slice(0, alia_arr.length - 1).join(" "), value = alia_arr[alia_arr.length - 1] } // 这一行有多个空格,以最后一个空格为分界
alias_dic[key] = value
}
}
}
try {
let alias_json_str = JSON.stringify(alias_dic)
GM_setValue("Alias_json", alias_json_str)
} catch (error) {
console("保存Alias_json发生错误!" + error)
}
}
/**
* 添加一条树洞别名
* @param {string} comment 树洞描述
* @param {string} hole_id 洞号
*/
function addOneAlia(comment, hole_id) {
let alias_json_str = GM_getValue("Alias_json", "{}");
if (!comment) { console.log("请输入树洞描述/别名!"); return }
try {
let alias_dic = JSON.parse(alias_json_str)
if (typeof (comment) != 'string')
throw "comment is not string"
else if (comment in alias_dic)
throw "现有描述(别名)已存在!"
else {
alias_dic[comment] = hole_id;
try {
alias_json_str = JSON.stringify(alias_dic)
GM_setValue("Alias_json", alias_json_str)
} catch (error) {
alert("保存Alias_json发生错误:" + error)
}
}
} catch (error) {
alert("存储洞号别名失败:" + error)
}
}
/**
* 在树洞详情页添加“添加别名”按钮
*/
function addAddAliaButton() {
if (!Config.recordAlias.value) return
let toolbar = document.querySelector("div.box.box-tip.sidebar-toolbar")
if (toolbar) {
let add_alia_button = document.createElement("span")
add_alia_button.classList.add("sidebar-toolbar-item")
add_alia_button.innerHTML = `
<a>
<span class="icon icon-star"></span>
<label>添加别名</label>
</a>
`
add_alia_button.addEventListener("click", () => {
let newComment = prompt('请输入对该洞的描述(不需要输入洞号):', '');
if (newComment === '') {
alert("未输入别名,无法储存")
} else {
var hole_id = document.querySelector("#eagleMapContainer > div:nth-child(3) > div > div:nth-child(2) > div.sidebar > div.sidebar-content.sidebar-content-show > div > div:nth-child(3) > div.box-header.box-header-top-icon > code")
if (hole_id)
addOneAlia(newComment, hole_id.innerText)
else
alert("洞号不存在,无法储存")
}
})
toolbar.insertBefore(add_alia_button, toolbar.lastChild)
}
}
//================================================================================================================
// 记录别名 end
// ===============================================================================================================
//================================================================================================================
// 添加课程评估网站、bbs网站按钮 start
// ===============================================================================================================
function addSiteButton() {
if (!Config.addSiteButton.value) return
waitForKeyElements("#eagleMapContainer > div.title-bar > div.app-switcher", (app_switcher_list) => {
let app_switcher = app_switcher_list[0]
if (Config.addCourseEvaluationSiteButton.value) {
let app_switcher_item = document.createElement("a")
let app_switcher_right = document.querySelector("#eagleMapContainer > div.title-bar > div.app-switcher > span.app-switcher-desc.app-switcher-right") // 参考位置节点
app_switcher_item.setAttribute(`${getDataVersionByNode(app_switcher_right)}`, "")
app_switcher_item.setAttribute("class", "app-switcher-item ")
app_switcher_item.setAttribute("href", "https://courses.pinzhixiaoyuan.com/")
// course_evalution_site_icon_base64 是课程网站的图标icon,这里直接用base64给出是为了减少跨域请求
let course_evalution_site_icon_base64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALAAAACwBAMAAABdmfltAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAqUExURUdwTPOWmvOWmfOWmfOWmfOWmvOWmfOWmv////e9wPShpfWrrv3r6/rU1tbOSnAAAAAHdFJOUwDpI6NMyHnu6qNXAAAHmklEQVRo3sVbS1MiSRA28HFnfHD1tXN2Rx2uOsPqecZwuers7HommKGCZrg3IZyFEc7Eip5xUc+L6JwFw/kvS1c30FWVWZ3dNLF5MqKbz+qsrzLzq6yamvq/LbK6nkgk3q0uhwq6/tfmYTTGGItGDzc/vwsJdnVv28IcWfTgcwjjjrz/wFRbHBt6D4K1bP7TOLAzf8YYZqnfgw/69Qems/mgs/g+xvT29VOwWfPC7btjKwDwb4xg/pEjJNy+bU1ivAGQ12Jk4NQbPzyj4/a5cURfF0nmx9I71Ik7Zv5saTnsiRu4+SMJdy7GfCNT3ByJM/82vzwBR3D7OAlHcGd4MuOYBbMlryXHgtpR+DPnLJPlyQyYMV3MiCTHANYN+RfGJjLksQasGzLRw9mWzyFTKXFZ7MHQaQR4mvjJ16ZZ7NV9cPmECFw2+/YIQS+NFyXaJrcf53VaxNgl4uYcYAuaEuTIXMuaQyvK0F+Xg08dy5umG7rlNX3UqWPfTMFE7n1RU744dQYOfGuaOHRqWRsmjNqLOjEC20R7bA6fbug8YVStt3sIcLf/rPSzLSCfDZ9mdJ5wPvYZxDUsyFPj7xe3M+qoL9yeyDqjKTZBGluPOv0//hlBl1zPN1BPGEMnPqA0fuIvDqELrueiLyIxaAGAQ867fJpzoE/RNTIN0+kJCprCf7yoDjwDr5FdadaBT3SzzTVZWWUAr9zASWlyzMe2NNsi26R1eIaF+xnZh/etBh94Ew6ap5hn5Ng5K/nQAszCTuZBsyOkE7MovuIi3L6YePincoQnDdtAz1i2ALqYAxcGK6wDs20EbHTVOU6DLuazXhg4swOyzeV6/l0PWIKaZoArkBFfA2z7lyFO3pXDbXFAuyeQxiV4HapMjivf2n/1CvqJPVkF1DNiuIjElGkvVWrt4U8uXNaQfMo907qAU+oMkt2dbzYqLqtJoYEv8IqcbXbU5SGmngcYWKaxArwBViqXUnQTgKui5zl1VOBXYOIf+aJUV4DLYmjg1LlXgL+o604IyHbSE4C7YmjIw8BpqKIYDdkesApckIB7CrCdUecUrl7ZwdhxpQDcBmhcUYBtWgBF213/94/fGQAMsk0F5rRYAfJPo1IZlU03o/VxJYUGzrZWqwWWs/s+hFEeYBuUGBeUSOGla4Cg2QHey0Bs89A1+hTt4lvEzwYFJWgOwtCMH/FJCZqDJDLnA9eQcv81UntwIk/7AM4BNC6Bb274U/yEFD0ChtaHcQdLhbzoU6ON1brWCgHWByoVbkWf5kyExjwi76O66BmmMfOmMY/IJ6j0VOtuz0rTDRzH9daDB9tuUaFirek4rpXlH0FsKzJsTySpUZ5P3pVmAQNWRT8uFeTQIHlGCBZqDLKlQhGSCtSgCQMLUuEsUNCEgQeiAvgZFDQR4JSa/HGpYFCDpg2MxXK18DagSrNOBr5GpUJOStF40PQ54jydxoypwLhUgHL/A33EuFS4BCrNDh3YlgrnFVUqlIFK89l+RKEbLhWggrDHn5wDwGpZ0cWkAhA0zYoP4G+IVIB1DQIMLGlUKsC6BgdO4luBolSoAkFTAwzUhIhUqEpB05YfCHAaLDZhqQBtBumAofIYlArSiPMjtgHAGWR7t1EZ7bAaNw1ud6KPOY3PW5bdtGh1hW6n+75OCZpcK5AliLNvNugm6IImL7FWyMBiN6GtC5pWUTjrq+oeQmdNPPfbZSy98O66uwlVaDNI2OCkSwX3HnrRxAtCRyrMBAI29cCWSqfLsUsZt4Syje8KAWsa7ioowB10DGm4s4JIhbzc/2iiwBlYpCNSgTPsx4up3cJ3ifQVqlRw4lnt0WvqnG0FmchoVyFnOqG9je6zCxshc1Sp4OyF2TkKrTNHWzcy31CpYDhZrqo0VUC2KXzDpULXzsttTcNI2Jo+gaIjJBXshF81TW0oHm3oreBSoalUuGal68W14RbkNKhgAKnAH/003ZlWQwqZFpquAv+YF0+uDTfpxZpF01XIE+KaU60As6fpKmTBBh4SKZQNZE1XIWeKxZd2QSuzN9jsALoKw6yn5Zqr8SYU37quQts7rjGhbRondhWcRVn8fiEZ0hISnKzrKjjApYpk51gTa5bYVXCAe3rgDaQrrekqlOEBi8DCIYs4ratgx597PXAaPbSBdxU4cLGiBxaasXO0rkINHrAIfIQfB0G7CjV+6EEPLB0KOaF1FcCpE4EXtAdN3F0FoyE03Sx1IK+PFkw2sH2DVbIFjzeU4zE0KVLWJn3LloIduimX6h5vKEdNaNXsbcevJ4i+uGp6vLAQ8ACS4fXCUejH84A4Ec6BQinbBaAypaAI6fSqdurgHqffAWPHWONjAmcmcYAV4drYR25RroUx5DcTOSXscc57bRIeHpMYmYmcHSccpT8Od9EFvgAxDPDeFyHWwqZa4LsVYAoF5y85CUcEcwb1dsxx2IwIuLIpdzaCuJnoYN/XhFL+LpCRLzb5utZEuuoWEJeIHACXhBzsgt7Uay9uLAa9B7n+qxb3YGcqqOluFaa2xroRur4NQ6cOxr0fG9kDoFPh3I1NbArXQlOLb9+FdVV4NfHH9mE0mopGDw/eJlYpP/kPFTPisdwfrLcAAAAASUVORK5CYII="
app_switcher_item.innerHTML = `
<img ${getDataVersionByNode(app_switcher_right)}="" src="${course_evalution_site_icon_base64}" class="app-switcher-logo-normal" />
<img ${getDataVersionByNode(app_switcher_right)}="" src="${course_evalution_site_icon_base64}" class="app-switcher-logo-hover" />
<span ${getDataVersionByNode(app_switcher_right)}="">课程测评</span>
`
app_switcher.insertBefore(app_switcher_item, app_switcher_right)
}
if (Config.addBBSSiteButton.value) {
let app_switcher_item = document.createElement("a")
let app_switcher_right = document.querySelector("#eagleMapContainer > div.title-bar > div.app-switcher > span.app-switcher-desc.app-switcher-right") // 参考位置节点
app_switcher_item.setAttribute(`${getDataVersionByNode(app_switcher_right)}`, "")
app_switcher_item.setAttribute("class", "app-switcher-item ")
app_switcher_item.setAttribute("href", "https://bbs.pku.edu.cn/v2/home.php") //未名bbs网站
let bbs_site_icon_src = "https://bbs.pku.edu.cn/v2/images/logo.jpg"
app_switcher_item.innerHTML = `
<img ${getDataVersionByNode(app_switcher_right)}="" src="${bbs_site_icon_src}" class="app-switcher-logo-normal" />
<img ${getDataVersionByNode(app_switcher_right)}="" src="${bbs_site_icon_src}" class="app-switcher-logo-hover" />
<span ${getDataVersionByNode(app_switcher_right)}="">未名BBS</span>
`
app_switcher.insertBefore(app_switcher_item, app_switcher_right)
}
})
}
//================================================================================================================
// 添加课程评估网站按钮 end
// ===============================================================================================================
//================================================================================================================
// 搜索建议功能(历史记录+收藏) begin
// ===============================================================================================================
function showSuggestionBlock() {
var record_history = Config.recordHistory.value
var show_history_suggestion = Config.showHistorySuggestions.value && record_history // 显示历史记录必须先打开保存历史记录功能
var show_alias_suggestion = Config.showAliasSuggestions.value
if (!record_history) clearAllHistory();
if (!record_history && !show_history_suggestion && !show_alias_suggestion) { // 如果三个功能都没开启,那就直接返回
return
}
if (show_history_suggestion && (!localStorage.getItem("search_history"))) { // 如果没有历史记录,则创建一个空的历史记录
localStorage.setItem("search_history", "")
}
addSuggestionBlockCSS()
var search_box; // 搜索框
waitForKeyElements("#eagleMapContainer > div.title-bar > div.control-bar > div > div:nth-child(1) > div > input",
(search_boxs) => {
search_box = search_boxs[0];
let search_box_cache = ""
//<div id="selectedId" /> 用来显示历史记录栏
var selectedId = document.createElement("div")
selectedId.setAttribute("id", "selectedId")
search_box.oninput = function refreshItem() {
if (!document.querySelector("#selectedId"))
search_box.parentNode.parentNode.appendChild(selectedId) // 添加历史记录栏
//删除ul
var drop = document.getElementById("drop");
if (drop) selectedId.removeChild(drop);
//把ul添加回来
var originalUl = document.createElement("ul");
originalUl.id = "drop";
selectedId.appendChild(originalUl);
showList(search_box, show_alias_suggestion, show_history_suggestion);
}
// 添加获取焦点事件
search_box.onfocus = function () {
// 初始下拉列表
if (!document.querySelector("#selectedId"))
search_box.parentNode.parentNode.appendChild(selectedId) // 添加历史记录栏
var originalUl = document.createElement("ul");
originalUl.id = "drop";
selectedId.appendChild(originalUl);
showList(search_box, show_alias_suggestion, show_history_suggestion);
}
//添加失去焦点事件
search_box.onblur = function () {
// console.log("soutsout")
var drop = document.getElementById("drop");
if (drop) {
selectedId.removeChild(drop);
search_box.parentNode.parentNode.removeChild(selectedId)
}
}
// 添加键盘按键事件,按下按键时,下拉列表的选中框会移动,输入框的文本会填充
search_box.onkeydown = function (event) {
var drop = document.getElementById("drop");
if (drop) {
var lis = drop.getElementsByTagName("li");
var len = lis.length;
var index = -1;
// 查找当前上下键选中框
for (var i = 0; i < len; i++) {
if (lis[i].style.backgroundColor == "darkgrey") {
index = i;
break;
}
}
if (event.keyCode === 38) { // 上箭头
if (index == -1) { // 当前没有选中框
search_box_cache = search_box.value // 暂存当前值
search_box.value = lis[len - 1].querySelector("span").innerText;
lis[len - 1].style.backgroundColor = "darkgrey";
} else if (index == 0) {// 当前选中框为第一个
search_box.value = search_box_cache
lis[index].style.backgroundColor = "";
} else {
search_box.value = lis[index - 1].querySelector("span").innerText;
lis[index - 1].style.backgroundColor = "darkgrey";
lis[index].style.backgroundColor = ""
}
} else if (event.keyCode === 40) { // 下箭头
if (index == -1) { // 当前没有选中框
search_box_cache = search_box.value // 暂存当前值
search_box.value = lis[index + 1].querySelector("span").innerText;
lis[index + 1].style.backgroundColor = "darkgrey";
} else if (index == len - 1) {
search_box.value = search_box_cache
lis[index].style.backgroundColor = "";
} else {
search_box.value = lis[index + 1].querySelector("span").innerText;
lis[index + 1].style.backgroundColor = "darkgrey";
lis[index].style.backgroundColor = "";
}
} else if (event.keyCode === 13) { // 回车,真正选中(此时触发input事件,文本框的值真正被修改)
search_box.dispatchEvent(new Event('input'))
if (Config.recordHistory.value)
addOneHistory(search_box.value);
}
}
}
// 给搜索按钮也绑定click事件,点击后存储搜索记录
waitForKeyElements("#eagleMapContainer > div.title-bar > div.control-bar > div > div:nth-child(2) > div > div > button", (buttons) => {
let button = buttons[0];
button.addEventListener('mousedown', () => {
search_box.dispatchEvent(new Event('input'))
})
button.addEventListener('click', () => {
if (Config.recordHistory.value)
addOneHistory(search_box.value);
})
})
});
}
/**
* 添加历史记录框的CSS
*/
function addSuggestionBlockCSS() {
let historyBlockCSS = `
/* 搜索下拉框*/
ul#drop {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
padding: 0;
margin: 0;
list-style-type: disc;
margin-block-start: 0em;
margin-block-end: 0em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
ul#drop li {
display: flex;
min-width: 0;
max-height: none;
padding: 0;
flex-direction: column;
border-radius: 4px;
}
ul#drop li:hover {
background-color: darkgrey;
border-radius: 4px;
}
ul#drop li div.li_block{
padding-left:10px;
padding-right:10px;
display: inline-block;
float: left
}
ul#drop li div.li_content{
font-size:15px;
display: block;
}
ul#drop li span{
float: left
}
ul#drop li span.alias{
}
ul#drop li span.history.light_mode{
color: #52188c; /* 亮色主题下历史记录文字用紫色显示 */
}
ul#drop li span.history.dark_mode{
color: #00CCFF; /* 暗色主题下历史记录文字用天蓝色显示 */
}
ul#drop li div.delete_item{
display: block;
float: right;
}
ul#drop li div.delete_button{
color: #70757a;
font-size: 15px;
cursor: pointer;
align-self: center;
opacity: 0 /*光标没有悬浮在“删除”按钮上时,透明化(相当于不显示) */
}
ul#drop li div.delete_button:hover{
color: #1558d6;
text-decoration: underline;
text-decoration-line: underline;
text-decoration-thickness: initial;
text-decoration-style: initial;
text-decoration-color: initial;
}
ul#drop li:hover div.delete_button{
opacity: 1;
}
ul#drop li div.alias{ /* 别名显示框 */
display: inline-block;
float: right;
}
ul#drop li div.alias span{
color: #D3D3D3;
font-size: 12px;
width: 100%;
width: -moz-available; /* WebKit-based browsers will ignore this. */
width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
width: fill-available;
text-align: right
}
div#selectedId {
position: relative;
text-align: left;
-webkit-user-select: none;
width: 100%;
padding-left: 144px;
}
@media screen and (max-width: 900px){ /* 页面缩小时自适应 */
div#selectedId{
padding-left: 80px;
}
}
`
GM_addStyle(historyBlockCSS)
}
/**
* 显示li标签(显示搜索时的下拉列表)
* @param search_box {Node} 搜索文本框的dom节点
* @param show_alias_suggestion {bool} 是否显示别名/收藏建议
* @param show_history_suggestion {bool} 是否显示历史记录建议
*/
function showList(search_box, show_alias_suggestion, show_history_suggestion) {
if (show_alias_suggestion && search_box.value) {
// 解析别名字典
var alias_json_str = GM_getValue("Alias_json", "{}");
if (alias_json_str) {
try {
var alias_dic = JSON.parse(alias_json_str)
var alias = "";
} catch (error) {
console.log("解析洞号别名错误!" + error)
}
}
// alias_arr是别名数组
var alias_arr = Object.keys(alias_dic)
var alias_res = searchByIndexOf(search_box.value, alias_arr);
for (var i = 0; i < alias_res.length; i++) {
var li = document.createElement("li");
li.innerHTML = `
<div class="li_block">
<div class="li_content">
<span class="suggestion alias">${alias_dic[alias_res[i]]}</span>
</div>
<div class="alias">
<span class="alias_suggestion_content">${alias_res[i]}</span>
</div>
</div>
`
li.addEventListener("click", (event) => {
event.stopPropagation();
if (event.target.nodeName === "SPAN") // 如果直接点击的是文本span标签,则
search_box.value = event.target.innerText;
else
search_box.value = event.target.querySelector("span.suggestion").innerText;
search_box.dispatchEvent(new Event('input'))
$("#eagleMapContainer > div.title-bar > div.control-bar > div > div:nth-child(2) > div > div > button").click()
})
// 添加mousedown事件,防止blur事件先于click发生
li.addEventListener("mousedown", (event) => {
event.preventDefault()
})
document.getElementById("drop").appendChild(li);
}
}
if (show_history_suggestion) {
// history_arr是历史记录
var history_arr = getALLHistory();
var history_res = searchByIndexOf(search_box.value, history_arr);
var dark_mode = document.querySelector("body").getAttribute("style").indexOf("--theme_bgc_color:rgba(31,31,31,0.8); --theme_font_color:#ededed;") > -1;
for (var i = 0; i < history_res.length && i < Config.maxShownHistorySize.value; i++) {
let li = document.createElement("li");
li.innerHTML = `
<div class="li_block">
<div class="li_content">
<span class="suggestion history ${dark_mode ? 'dark_mode' : 'light_mode'}">${history_res[i]}</span>
</div>
<div class="delete_item">
<div class="delete_button">删除</div>
</div>
</div>
`
li.addEventListener("click", (event) => {
event.stopPropagation();
if (event.target.nodeName === "SPAN") // 如果直接点击的是文本span标签,则直接输入文本
search_box.value = event.target.innerText;
else // 如果是文本span标签的父标签,则查找span标签
search_box.value = event.target.querySelector("span.suggestion").innerText;
search_box.dispatchEvent(new Event('input'))
$("#eagleMapContainer > div.title-bar > div.control-bar > div > div:nth-child(2) > div > div > button").click()
})
// 添加mousedown事件,防止blur事件先于click发生
li.addEventListener("mousedown", (event) => {
event.preventDefault()
})
var delete_button = li.querySelector(".delete_button");
delete_button.addEventListener("click", (event) => { // 点击删除按钮
event.stopPropagation()
let parent_li = $(event.target).parents("li")
deleteOneHistory(parent_li.find("span").text())
parent_li.remove()
})
document.getElementById("drop").appendChild(li);
}
}
}
/**
* 模糊查询:利用字符串的indexOf方法
*/
function searchByIndexOf(keyWord, list) {
if (!(list instanceof Array)) {
return;
}
if (keyWord == "") {
return list;
} else {
var len = list.length;
var arr = [];
for (var i = 0; i < len; i++) {
//如果字符串中不包含目标字符会返回-1
if (list[i].toLowerCase().indexOf(keyWord.toLowerCase()) >= 0) { // 查找字符串方法不区分大小写
arr.push(list[i]);
}
}
return arr;
}
}
/**
*
* 获取搜索关键词设置历史记录
* 过滤一个结果的空记录添加,过滤空搜索 默认保存200条记录 可修改
* (这种记录方式有个小bug,就是搜索记录里不能有|,先用着吧)
*/
function addOneHistory(keyword) {
keyword = keyword.trim(); // 过滤字符串左右的空格(不过滤字符串中间的空格)
if (!keyword) {
return false; // 字符串为空时禁止
}
let historyIndexSearchItems = localStorage.getItem("search_history"); // 获取历史记录的字符串
if (!historyIndexSearchItems) {
localStorage.setItem("search_history", keyword);
} else {
const onlyItem = historyIndexSearchItems.split('|').filter(e => e != keyword);
if (onlyItem.length > 0) {
historyIndexSearchItems = keyword + '|' + onlyItem.slice(0, Config.maxHistorySize.value).join('|');
}
localStorage.setItem("search_history", historyIndexSearchItems)
}
}
/**
* 删除一条历史记录
*/
function deleteOneHistory(keyword) {
keyword = keyword.trim(); // 过滤字符串左右的空格(不过滤字符串中间的空格)
if (!keyword) {
return false; // 字符串为空时禁止
}
let historyIndexSearchItems = localStorage.getItem("search_history"); // 获取历史记录的字符串
if (!historyIndexSearchItems) {
return false;
} else {
const onlyItem = historyIndexSearchItems.split('|').filter(e => e);
if (onlyItem.length > 0) {
const index = onlyItem.indexOf(keyword);
if (index > -1) {
onlyItem.splice(index, 1);
historyIndexSearchItems = onlyItem.slice(0, Config.maxHistorySize.value).join('|');
localStorage.setItem("search_history", historyIndexSearchItems);
return true;
}
return false;
}
return false;
}
}
/**
* 获取所有历史记录
* @return: String Array
*/
function getALLHistory() {
let historyIndexSearchItems = localStorage.getItem("search_history");
if (historyIndexSearchItems)
return historyIndexSearchItems.split('|').filter(e => e)
else
return []
}
/**
* 清除所有历史记录
*/
function clearAllHistory() {
localStorage.removeItem('search_history');
}
//================================================================================================================
// 搜索历史记录功能 end
// ===============================================================================================================
//================================================================================================================
// 显示最新回复时间 start
// ===============================================================================================================
/**
* 该函数将一个时间戳转换为时间格式
* dateTimeStamp是时间戳,10位
* @param {string} dateTimeStamp
* @returns 标准时间格式字符串
*/
function timeFormat(dateTimeStamp) {
let datetime = new Date(dateTimeStamp * 1000);
let Nyear = datetime.getFullYear();
let Nmonth = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
let Ndate = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
let Nhour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours();
let Nminute = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes();
let Nsecond = datetime.getSeconds() < 10 ? "0" + datetime.getSeconds() : datetime.getSeconds();
result = Nyear + "-" + Nmonth + "-" + Ndate + " " + Nhour + ":" + Nminute
return result;
}
/**
* dateTimeStamp是评论的发送时间戳,10位
* @author https://blog.csdn.net/Aurora_____/article/details/110390353
* @param dateTimeStamp
* @returns {string}
*/
function timeAgo(dateTimeStamp) {
let result = "";
let minute = 1000 * 60; //把分,时,天,周,半个月,一个月用毫秒表示
let hour = minute * 60;
let day = hour * 24;
let week = day * 7;
let halfamonth = day * 15;
let month = day * 30;
let now = new Date().getTime(); //获取当前时间毫秒
let diffValue = now - dateTimeStamp * 1000;//时间差
if (diffValue < 0) {
return result;
}
let minC = Math.floor(diffValue / minute); //计算时间差的分,时,天,周,月
let hourC = Math.floor(diffValue / hour);
let dayC = Math.floor(diffValue / day);
let weekC = Math.floor(diffValue / week);
let monthC = Math.floor(diffValue / month);
if (monthC >= 1 && monthC <= 11) {
result = " " + parseInt(monthC) + "月前"
} else if (weekC >= 1 && weekC <= 3) {
result = " " + parseInt(weekC) + "周前"
} else if (dayC >= 1 && dayC <= 6) {
result = " " + parseInt(dayC) + "天前"
} else if (hourC >= 1 && hourC <= 23) {
result = " " + parseInt(hourC) + "小时前"
} else if (minC >= 1 && minC <= 59) {
result = " " + parseInt(minC) + "分钟前"
} else if (diffValue >= 0 && diffValue <= minute) {
result = "刚刚"
} else {
result = timeFormat(dateTimeStamp)
}
return result;
}
// 最新回复时间记录字典
var last_reply_time_dic = {}
/** 修改xhr的open方法,监听返回值,截取最近回复时间 */
function listenLastestReplyTime() {
if (!Config.showLastestReplyTime.value) return
const url_regexp = RegExp('/api/pku_comment_v3/(\\d+)\\?limit=10');
// 重写open方法,截取最近回复时间
const originOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (_, url) {
const matched = url.match(url_regexp)
if (matched) {
this.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
const res = JSON.parse(this.response);
const box_id = matched[1]
const total_reply = res.data.total // 总回复数
var last_page = res.data.last_page
var lastest_reply_time
if (last_page == 1) { // 如果总评论少于10条,可以直接提取出最近回复
lastest_reply_time = res.data.data[res.data.data.length - 1]['timestamp']
last_reply_time_dic[box_id] = lastest_reply_time
// console.log(box_id + "最新回复时间:" + lastest_reply_time)
}
else { // 如果总评论多于10条,需要再次发请求
setTimeout(function () {
GM_xmlhttpRequest({
method: "GET",
url: "https://treehole.pku.edu.cn/api/pku_comment_v3/" + box_id + "?limit=50000",
headers: {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"authorization": "Bearer " + _getCookieObj()["pku_token"],
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"uuid": localStorage.getItem("pku-uuid"),
},
"referrer": "https://treehole.pku.edu.cn/web/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"mode": "cors",
"credentials": "include",
onload: function (again_res) {
if (again_res.status == 200) {
try {
const again_res_json = JSON.parse(again_res.responseText);
lastest_reply_time = again_res_json.data.data[again_res_json.data.data.length - 1]['timestamp']
last_reply_time_dic[box_id] = lastest_reply_time
// console.log(box_id + "最新回复时间:" + lastest_reply_time)
} catch (error) {
console.log(error)
console.log("无法获取最新回复时间!" + box_id);
}
} else {
console.log("请求失败:状态码" + again_res.status);
}
}
})
}, Math.random() * 5000); // 平均延迟2.5秒,防止请求过于频繁
}
this.response = JSON.stringify(res);
}
});
}
originOpen.apply(this, arguments);
};
}
//================================================================================================================
// 显示最新回复时间 end
// ===============================================================================================================
//================================================================================================================
// 点击同意服务协议 start
// ===============================================================================================================
/** 点击登录页面的“同意北大树洞服务协议”,回车登录 */
function clickToAgreeToTheServiceAgreement() {
if (Config.clickAgreeServiceAgreement.value) {
waitForKeyElements('div.main-login > p.bottom-footer > input[type=checkbox]', (check_boxs) => {
let check_box = check_boxs[0]
if (!check_box.checked)
check_box.click()
// setTimeout(()=>{document.querySelector("#app button").click()}, 1000)
document.querySelector("#app input[type=text]").focus()
document.querySelector("#app input[type=text]").addEventListener("keydown", (event) => {
if (event.keyCode === 13) {
// console.log("监听到回车键,点击登录!")
document.querySelector("#app button").click()
}
}, true)
}, true)
}
}
//================================================================================================================
// 点击同意服务协议 end
// ===============================================================================================================
//================================================================================================================
// 点击其它链接时,关闭提醒,自动跳转外链 start
// ===============================================================================================================
function clickToCloseAndJump() {
if (Config.openTheFuckingURLRightNow.value) {
waitForKeyElements('body > div.v-modal', (dialog) => {
document.querySelector("body > div.el-dialog__wrapper > div > div.el-dialog__footer > span > button.el-button.el-button--primary").click()
document.querySelector("body > div.v-modal").remove()
document.querySelector("body > div.el-dialog__wrapper").remove()
}
)
}
}
//================================================================================================================
// 点击其它链接时,关闭提醒,自动跳转外链 end
// ===============================================================================================================
//================================================================================================================
// 滚动穿透:解除点击详情页时不能滚动外部树洞列表的问题 begin
// ===============================================================================================================
// !!!!!!!!!!!!!!!!!!开发中,尚未实现
function cancelScrollLimit(params) {
const callback = (mutationsList, observer) => {
for (const mutation of mutationsList) {
for (const target of mutation.addedNodes) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0 && mutation.addedNodes && mutation.addedNodes[0].nodeName === 'DIV')
var sidebar_shadow = document.querySelector(".sidebar-shadow").remove()
break
}
}
};
const observer = new MutationObserver(callback);
observer.observe(document, { childList: true, subtree: true });
}
//================================================================================================================
// 滚动穿透:解除点击详情页时不能滚动外部树洞列表的问题 end
// ===============================================================================================================
(function () {
// 登录页面优化
if (location.pathname.indexOf("/web/login") > -1)
clickToAgreeToTheServiceAgreement()
// 登录之后页面优化
if (location.pathname.endsWith("/web/")) {
addCopyFullTextButtonIcon() // 添加复制全文图标
cancelMaxHeight() // 取消全文最大长度限制
configureWallPaperHub() // 随机壁纸功能
recordAlias() // 别名记录
addSiteButton() // 添加课程测评按钮
showSuggestionBlock() // 显示搜索建议(包括历史记录和收藏)
listenLastestReplyTime() // 监听最后一次回复的时间chunk
// 使用MutationObserver持续监听是否跳出了外链警告弹窗
const dialog_observer = new MutationObserver((mutationsList, observer) => {
if(document.querySelector("body > div.v-modal")) // 如果有弹窗,关闭弹窗
clickToCloseAndJump(); // 点击其它链接时,关闭提醒,自动跳转外链
});
dialog_observer.observe(document.body, { childList: true, subtree: false });
GM_addStyle(`.box-footer{opacity:0.5;}`)
// 这个是监听网页插入事件,用来判断后续网页动态插入的元素(动态插入树洞)
waitForKeyElements("div.flow-chunk", (chunk) => {
var flow_chunk = chunk[0];
// 这个观察器是用来观察主页面的box流
let chunk_observer = new MutationObserver((mutationsList, observer) => {
// console.log(`debug`)
for (const mutation of mutationsList) {
for (const item of mutation.addedNodes) {
blockChunkKeywords(item)
}
if (mutation.addedNodes) {
setTimeout(() => { // 懒得用查找了,直接延时
let flow_item_row_list = document.querySelectorAll("#table_list > div.flow-chunk > div > div")
for (let flow_item_row of flow_item_row_list) {
// console.log(flow_item_row)
let exist_reply_icon = flow_item_row.querySelector("div.box-header > span:nth-child(3)") // 回复数图标
let exist_box_footer = flow_item_row.querySelector("div.flow-item > div.box > div.box-footer") // 最新回复时间文本
if (!exist_reply_icon || exist_box_footer) // 已经没有回复,或者已经有最新回复时间,跳过
continue;
else {
let box_id = flow_item_row.querySelector("div.flow-item > div.box > div.box-header > code.box-id.--box-id-copy-content").innerText.replace("#", "").trim() // 洞号
let box = flow_item_row.querySelector("div.flow-item > div.box")
// console.log(box)
if (last_reply_time_dic[box_id]) {
let box_footer = document.createElement("div")
box_footer.setAttribute(`${getDataVersionByNode(box.querySelector("div.box-header"))}`, "")
box_footer.classList.add("box-footer")
box_footer.innerText = "最新回复时间:" + timeAgo(last_reply_time_dic[box_id])
box.appendChild(box_footer)
}
}
}
}, 1000)
}
}
}
);
chunk_observer.observe(flow_chunk, { childList: true, subtree: true });
})
// 绑定侧边栏观察器
waitForKeyElements('.left-container', (value) => {
var left_container = value[0];
let sidebar_rise_observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.addedNodes.length && mutation.addedNodes[0].nodeName != "#comment" && document.querySelector("div.box.box-tip.sidebar-toolbar")) { // 确实唤起了详情页侧边栏(而不是关闭侧边栏,以及打开账户、发表树洞的侧边栏)
addCopyFullTextButton();
blockSidebarKeywords();
addAddAliaButton()
break
}
}
});
sidebar_rise_observer.observe(left_container, { childList: true }); // 观察子节点的变动
})
}
}())
/**
* 这段代码来自于 https://gist.github.com/BrockA/2625891 ,将其复制下来是因为油猴脚本对github的跨域访问不太友好,不然可以直接在最前面require
此外还有一些安全性的考虑
*/
/**--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
that detects and handles AJAXed content.
Usage example:
waitForKeyElements (
"div.comments"
, commentCallbackFunction
);
//--- Page-specific function to do what we want when the node is found.
function commentCallbackFunction (jNode) {
jNode.text ("This comment changed by waitForKeyElements().");
}
IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements(
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents()
.find(selectorTxt);
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each(function () {
var jThis = $(this);
var alreadyFound = jThis.data('alreadyFound') || false;
if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction(jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data('alreadyFound', true);
}
});
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace(/[^\w]/g, "_");
var timeControl = controlObj[controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval(timeControl);
delete controlObj[controlKey]
}
else {
//--- Set a timer, if needed.
if (!timeControl) {
timeControl = setInterval(function () {
waitForKeyElements(selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj[controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}