// ==UserScript==
// @name Young People in Yaohuo
// @description 青少年模式
// @version 0.8
// @author Polygon
// @match https://yaohuo.me/*
// @icon https://yaohuo.me/css/favicon.png
// @grant GM_addStyle
// @grant GM_info
// @run-at document-end
// @namespace https://greasyfork.org/users/788115
// ==/UserScript==
(function() {
'use strict';
// 添加一个style,PC端字体有点看不清
GM_addStyle(`
body, html {
font-family: Arial, SimHei !important;
}
`)
let forbiddenLocal
if (localStorage.getItem("forbidden") == null) {
forbiddenLocal = {keywords: [], usernames: []}
localStorage.setItem("forbidden", JSON.stringify(forbiddenLocal))
}
forbiddenLocal = JSON.parse(localStorage.getItem("forbidden"))
var keywords = forbiddenLocal.keywords
var usernames = forbiddenLocal.usernames
// 在record中读取k,读取过程中可能会初始化record[k]
let getRecord = (itemid) => {
if (localStorage.getItem("record-version") != GM_info.script.version) {
localStorage.removeItem("record")
alert("清空老版本record成功")
localStorage.setItem("record-version", GM_info.script.version)
}
// 读取最新localStorage的record
let record = localStorage.getItem("record")
record = JSON.parse(record)
if (record == null) {
// 第一次初始化
record = {}
}
if (!Object.keys(record).includes(itemid)) {
record[itemid] = {
"latest_time": null, // 最近一次访问时间戳
"latest_comment": 0, // 上一次访问时的评论数量
"latest_read": 0, // 上一次访问时的阅读数量
"count": 0, // 访问次数
}
}
return record
}
// 指定k,添加一个时间戳
let recordAdd = (itemid, comment=null, read=null) => {
let record
record = getRecord(itemid)
// 防止重复添加
if (comment != null && read != null) {
// 这俩应该在帖子打开后解析更新
record[itemid]["latest_comment"] = comment
record[itemid]["latest_read"] = read
} else {
// 没有这两个参数就只更新次数
if (record[itemid]["latest_time"] && (new Date()).valueOf() - record[itemid]["latest_time"] < 1e3) return
record[itemid]["latest_time"] = (new Date()).valueOf()
record[itemid]["count"] ++
}
// 记录更新后record
localStorage.setItem("record", JSON.stringify(record))
}
function timestampToTime(timestamp) {
// https://www.byteblogs.com/article/259
var date = new Date(timestamp)
var Y = date.getFullYear() + '-'
var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-'
var D = date.getDate() + ' '
var h = date.getHours() + ':'
var m = date.getMinutes() + ':'
var s = date.getSeconds()
return Y+M+D+h+m+s
}
let getTimeInfo = (t) => {
let delta = ((new Date()).valueOf() - t) / 1000
if (delta < 60) {
// 小于60秒
delta = `${parseInt(delta)}秒前`
} else if (delta / (60 ** 2) < 1) {
// 小于一小时
delta = `${parseInt(delta / 60)}分前`
} else if (delta / (60 ** 3) < 24) {
// 小于一天
delta = `${parseInt(delta / (60 ** 2))}时前`
} else {
// 直接显示日期
delta = timestampToTime(t)
}
return delta
}
let parseElement = (ele) => {
let itemid = ele.querySelector("a").href.match(/bbs-(\d+)/)[1]
let [text, info, _] = ele.innerText.split("\n")
let [username, comment, read] = info.split("/")
comment = parseInt(comment.replace("回", ""))
read = parseInt(read.replace("阅", ""))
return [itemid, text, username, comment, read]
}
let validate = (text) => {
return keywords.filter((keyword) => {
return text.match(new RegExp(keyword, "gi"))
}).length
}
let setting = () => {
// 当前搜索屏蔽词settingText
let div = document.createElement("div")
div.setAttribute("id", "setting")
let maxWidth = parseInt(getComputedStyle(document.querySelector(".btBox")).width.replace("px", ""))
div.innerHTML = `
<button id="switch" class="close">
<svg viewBox="0 0 100 100" width="20" height="20" id="open" style="display: none;">
<path d="M5,70 L50,30 95,70" style="
fill: none;
stroke: #1abc9c;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 10;
"/>
</svg>
<svg viewBox="0 0 100 100" width="20" height="20" id="close">
<path d="M5,30 L50,70 95,30" style="
fill: none;
stroke: #1abc9c;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 10;
"/>
</svg>
</button>
<div class="forbidden-selection" style="
display: flex;
flex-flow: column;
align-items: center;
display: none;
">
<div class="input-box">
<div class="forbidden-tag" value="[发晒看]工资">
[发晒看]工资
<div class="cancel">×</div>
</div><div class="input">
<input placeholder="按回车键Enter创建屏蔽词" type="text">
</div>
</div>
<div class="forecommend-forbidden-tags">
<div class="forbidden-tag forbidden-tag-selected">[发晒看]工资</div>
<div class="forbidden-tag">(拼多多|并夕夕|pdd)</div>
<div class="forbidden-tag">话费</div>
<div class="forbidden-tag">"张三"</div>
<div class="forbidden-tag" style="cursor: not-allowed;">敬请期待</div>
</div>
<div id="forbidden-list" style="width: 100%;"></div>
</div>
<style>
*, *::after, *::before {
box-sizing: border-box;
--bakground-color: #2c3e50;
--selected-color: #3498db;
--pic-opacity: 23%;
--title-height: 50px;
--number-color: #00cec9;
--forbidden-input-box-width: ${maxWidth * 0.9}px;
--forbidden-input-box-border-width: 1.5px;
}
#switch {
width: 100%;
height: 20px;
background: none;
border: 0;
cursor: pointer;
}
#setting {
display: flex;
justify-content: center;
flex-flow: column;
padding: 10px;
border: 1px solid rgb(26, 188, 156);
border-radius: 8px;
width: 97%;
margin: 0 auto;
margin-bottom: 8px;
}
.forbidden-selection {
margin-top: 10px;
}
.forbidden-selection > .input-box {
width: var(--forbidden-input-box-width);
display: flex;
align-items: center;
flex-wrap: wrap;
padding: 0px;
margin-bottom: 5px;
outline: solid var(--forbidden-input-box-border-width) rgba(0, 0, 0, .2);
font-size: 14px;
}
.forbidden-selection > .input-box[active] {
outline: solid calc(1.2*var(--forbidden-input-box-border-width)) var(--number-color);
}
.forbidden-selection > .input-box .input {
display: block;
min-width: calc(var(--forbidden-input-box-width) / 2);
flex: 1;
margin: 0 10px;
height: 40px;
}
.forbidden-selection > .input-box .input input {
width: 100%;
height: 100%;
outline: none;
border: none;
}
.forbidden-selection > .input-box:not([active]):hover {
outline: solid var(--forbidden-input-box-border-width) rgba(0, 0, 0, 1);
}
.forecommend-forbidden-tags {
display: flex;
align-items: center;
flex-wrap: wrap;
flex-direction: row;
width: var(--forbidden-input-box-width);
}
.forecommend-forbidden-tags .forbidden-tag, .input-box .forbidden-tag {
border: solid 1px white;
border-radius: 5px;
padding: 4px 10px;
margin: 5px 0 5px 5px;
background-color: #f6f6f6;
cursor: pointer;
font-size: 14px;
line-height: 1.5em;
user-select: none;
}
.input-box .forbidden-tag:active {
background-color: #FF7D7D;
}
.forecommend-forbidden-tags {
display: flex;
align-items: center;
flex-wrap: wrap;
flex-direction: row;
width: var(--forbidden-input-box-width);
}
.forecommend-forbidden-tags .forbidden-tag, .input-box .forbidden-tag {
border: solid 1px white;
border-radius: 5px;
padding: 4px 10px;
margin: 5px 0 5px 5px;
background-color: #f6f6f6;
cursor: pointer;
font-size: 14px;
}
.forecommend-forbidden-tags .forbidden-tag-selected {
background-color: var(--number-color);
color: #fff;
cursor: not-allowed;
}
.input-box .forbidden-tag {
display: flex;
flex-direction: row;
justify-content: space-around;
background-color: var(--number-color);
color: #fff;
padding-right: 5px;
}
.forbidden-selection > .input-box .forbidden-tag .cancel {
margin-left: 5px;
}
</style>
`
document.body.insertBefore(div, document.querySelector(".btbox"))
setTimeout(() => {
// 按钮展开
document.querySelector("#switch").addEventListener("click", function (e) {
if (this.classList.contains("open")) {
// 关闭
this.classList.remove("open")
this.classList.add("close")
this.querySelector("svg#close").style.display = ""
this.querySelector("svg#open").style.display = "none"
document.querySelector(".forbidden-selection").style.display = "none"
} else if (this.classList.contains("close")){
// 打开
this.classList.remove("close")
this.classList.add("open")
this.querySelector("svg#close").style.display = "none"
this.querySelector("svg#open").style.display = ""
document.querySelector(".forbidden-selection").style.display = "flex"
}
})
// 数据选择
document.querySelector(".forbidden-selection input").addEventListener('click', (event) => {
// 输入状态input-box边框变色
document.querySelector(".forbidden-selection .input-box").setAttribute('active', '')
})
document.querySelector(".forbidden-selection input").addEventListener('blur', (event) => {
// 失去焦点恢复
document.querySelector(".forbidden-selection .input-box").removeAttribute('active')
})
let getSelectedForbiddenTags = () => {
let forbiddenTags = []
let selectedNodes = document.querySelectorAll('.input-box .forbidden-tag')
if (selectedNodes.length) {
selectedNodes.forEach((ele) => {
forbiddenTags.push(ele.getAttribute('value'))
})
}
return forbiddenTags
}
let isUseranme = (text) => {
return text.search(/["'“].+["'”]/) != -1
}
let removeForbiddenTag = (forbiddenTag) => {
// 取消要从数据库删除
let forbiddenLocal = JSON.parse(localStorage.getItem("forbidden"))
if (isUseranme(forbiddenTag)) {
// 是用户
forbiddenLocal.usernames = forbiddenLocal.usernames.filter(tag=>{
return tag != forbiddenTag.slice(1, -1)
})
} else {
forbiddenLocal.keywords = forbiddenLocal.keywords.filter(tag=>{
return tag != forbiddenTag
})
}
keywords = forbiddenLocal.keywords
usernames = forbiddenLocal.usernames
localStorage.setItem("forbidden", JSON.stringify(forbiddenLocal))
}
let saveForbiddenTag = (forbiddenTag) => {
// 更新到本地
let forbiddenLocal = JSON.parse(localStorage.getItem("forbidden"))
// 判断新增类型是否为用户
if (isUseranme(forbiddenTag)) {
// 是用户
if (!forbiddenLocal.usernames.includes(forbiddenTag)) {
forbiddenLocal.usernames.push(forbiddenTag.slice(1,-1))
}
} else {
if (!forbiddenLocal.keywords.includes(forbiddenTag)) {
forbiddenLocal.keywords.push(forbiddenTag)
}
}
// 储存
keywords = forbiddenLocal.keywords
usernames = forbiddenLocal.usernames
localStorage.setItem("forbidden", JSON.stringify(forbiddenLocal))
}
let createForbiddenTag = (forbiddenTag) => {
// 判断是否存在
if (getSelectedForbiddenTags().includes(forbiddenTag)) return
let div = document.createElement('div')
div.className = 'forbidden-tag'
div.setAttribute('value', forbiddenTag) // 方便取变量
div.innerHTML = `
${forbiddenTag}
<div class="cancel">×</div>
`
document.querySelector('.input-box').insertBefore(div, document.querySelector('.input-box .input'))
// 绑定取消选择事件
div.querySelector('.cancel').addEventListener('click', (e) => {
div.remove()
// 取消选择对应的推荐标签
document.querySelectorAll('.forecommend-forbidden-tags .forbidden-tag').forEach((item) => {
if (item.innerText == forbiddenTag) {
item.classList.remove('forbidden-tag-selected')
}
})
removeForbiddenTag(forbiddenTag)
e.stopPropagation()
})
// 点击标签编辑
div.addEventListener("click", (e) => {
let tag = div.getAttribute("value")
div.querySelector(".cancel").click()
document.querySelector(".forbidden-selection input").value = tag
})
// 保存
saveForbiddenTag(forbiddenTag)
// 检查推荐里面是否有同名
document.querySelectorAll(".forecommend-forbidden-tags>.forbidden-tag").forEach(ele=>{
if (ele.innerText == forbiddenTag) {
ele.click()
}
})
}
// 从已有变量选择
document.querySelectorAll(".forecommend-forbidden-tags .forbidden-tag").forEach((item) => {
item.addEventListener('click', (event) => {
if (item.innerText == "敬请期待") return
// 选中颜色高亮
item.classList.add('forbidden-tag-selected')
// 向数据框添加元素
createForbiddenTag(item.innerText)
})
})
// 输入框输入变量
document.querySelector(".forbidden-selection input").addEventListener('keyup', function(event) {
if (event.keyCode == 13) {
let inputValue = this.value
createForbiddenTag(inputValue)
this.value = ""
}
})
// 首次进入需要从本地内存读取关键词
let forbiddenLocal
forbiddenLocal = JSON.parse(localStorage.getItem("forbidden"))
if (localStorage.getItem("forbidden") == null) {
forbiddenLocal = {keywords: [], usernames: []}
localStorage.setItem("forbidden", JSON.stringify(forbiddenLocal))
}
forbiddenLocal.keywords.forEach(forbiddenTag=>{
createForbiddenTag(forbiddenTag)
})
forbiddenLocal.usernames.forEach(forbiddenTag=>{
createForbiddenTag(`"${forbiddenTag}"`)
})
}, 1e3);
}
let currentURL = window.location.href
// 判断当前网址是否为主页
if (currentURL == 'https://yaohuo.me/' || currentURL.search(/bbs-\d+/) != -1) {
console.log("主页/新帖")
// 主页移除关键词
let items = []
document.querySelectorAll('.list a').forEach((e) => {
// 每个节点有两个关键信息:1.href;2.innerText
let href = e.href
let text = e.innerText
if (validate(text)) {
console.log(`remove ${text}`)
} else {
items.push(`${items.length+1}.<a href="${href}">${text}</a>`)
}
})
document.querySelector('.list').innerHTML = items.join("<br>")
// 添加监控点击
document.querySelectorAll('.list>a').forEach((a) => {
a.addEventListener("click", (e) => {
let itemid = a.href.match(/bbs-(\d+)/)[1]
recordAdd(itemid)
})
})
} else if (currentURL.startsWith('https://yaohuo.me/bbs/book_list.aspx?')) {
setting()
// 可能是新帖,也可能是搜索页
if (currentURL.includes('key=')) {
console.log("搜索页")
let searchText = decodeURI(currentURL.match(/key=(.+?)&/)[1])
console.log(searchText)
if (validate(searchText)) {
setTimeout(() => {
alert("你可以遗忘屏蔽词,本脚本将永远不会!\nYou can forget about blocking words, this script will never!")
}, 233);
}
} else {
console.log("新帖页")
}
if (!document.querySelector("#KL_show_next_list")) {
let div = document.createElement("div")
div.setAttribute('id', 'KL_show_next_list')
document.body.insertBefore(div, document.querySelector(".btBox"))
}
// 把body下的条目统一放到KL_show_next_list下,便于统一操作
document.querySelector("#KL_show_next_list").style.display = ""
document.querySelectorAll(".listdata").forEach((ele) => {
document.querySelector("#KL_show_next_list").appendChild(ele.cloneNode(true))
ele.remove()
})
let filterList = (mutations, observer) => {
document.querySelectorAll("#KL_show_next_list>div").forEach((ele) => {
// 每个节点有很多元素
let [itemid, text, username, comment, read] = parseElement(ele)
if (validate(text) || usernames.includes(username.replace(" /", ""))) {
// 这是被过滤掉的e,需要移除
console.log(`remove ${text} - ${username}`)
document.querySelector("#forbidden-list").appendChild(ele.cloneNode(true))
ele.remove()
} else {
// 这是保留下来的ele,先绑定一个点击事件
ele.querySelector("a").addEventListener("click", function(e) {
// 更新次数
recordAdd(itemid)
})
let record = getRecord(itemid)[itemid]
if (record["count"] > 0) {
// 有浏览记录才处理
ele.style.position = "relative"
let add_comment = comment - record["latest_comment"]
let add_read = read - record["latest_read"]
if (ele.querySelector("#record")) {
ele.querySelector("#count>span").innerText = record["count"]
ele.querySelector("#time_info").innerText = getTimeInfo(record["latest_time"])
if (add_comment > 0) {
ele.querySelector("#add_comment").style.display = 'inline-block'
} else {
ele.querySelector("#add_comment").style.display = 'none'
}
if (add_read > 0) {
ele.querySelector("#add_read").style.display = 'inline-block'
} else {
ele.querySelector("#add_read").style.display = 'none'
}
ele.querySelector("#add_comment>span").innerText = (add_comment > 99) ? "+" : add_comment
ele.querySelector("#add_read>span").innerText = (add_read > 99) ? "+" : add_read
} else {
let div = document.createElement('div')
div.setAttribute("id", "record")
div.style = `
position: absolute;
right: 8px;
top: 8px;
color: #999;
font-size: 10px;
opacity: .7;
`
div.innerHTML = `
<div id="count" style="
display: inline-block;
text-align: center;
line-height: 15px;
width: 15px;
height: 15px;
background-color: #FF7D7D;
border-radius: 50%;
">
<span style="font-size: 12px; color: white;">${record["count"]}</span>
</div>
<div id="add_comment" style="
display: inline-block;
text-align: center;
line-height: 15px;
width: 15px;
height: 15px;
background-color: #54BAB9;
border-radius: 50%;
display: ${(add_comment<=0) ? 'none' : ''};
">
<span style="font-size: 12px; color: white;">${(add_comment > 99) ? "+" : add_comment}</span>
</div>
<div id="add_read" style="
display: inline-block;
text-align: center;
line-height: 15px;
width: 15px;
height: 15px;
background-color: #636e72;
border-radius: 50%;
display: ${(add_read <= 0) ? 'none' : ''};
">
<span style="font-size: 12px; color: white;">${(add_read > 99) ? "+" : add_read}</span>
</div>
<div style="
width: 50px;
display: inline-block;
text-align: right;
">
<span id="time_info">${getTimeInfo(record["latest_time"])}</span>
</div>
`
ele.appendChild(div)
}
}
}
})
}
filterList(null, null)
var observer = new MutationObserver(filterList)
var node = document.querySelector('#KL_show_tip')
if (node) {
observer.observe(node, {childList: true})
}
setInterval(() => {
// 5s一次更新时间
filterList(null, null)
}, 1e3)
}
// 监控浏览页面数据变化及时更新
if (currentURL.search(/bbs-\d+/) != -1) {
let update = (mutations, observer) => {
let itemid = currentURL.match(/bbs-(\d+)/)[1]
let comment
// 根据最新回复显示楼层判断回复数量
let text = document.querySelector(".reline").innerText
let res = text.match(/\[(\d+)楼\]/)
if (res) {
comment = parseInt(res[1])
} else {
comment = document.querySelectorAll(".reline").length
}
let read = parseInt(document.querySelector(".content").innerText.match(/阅(\d+)/)[1])
recordAdd(itemid, comment, read)
console.log(itemid, comment, read)
}
setTimeout(() => {
update(null, null)
let observer = new MutationObserver(update)
const config = {childList: true, subtree: true, characterDataOldValue: true}
document.querySelectorAll(".content").forEach(ele=>{
observer.observe(ele, config)
})
}, 1000)
}
// 添加搜索按钮监听
if (currentURL == 'https://yaohuo.me/') {
document.querySelector("input[type=submit]").addEventListener("click", (e) => {
let text = document.querySelector("input[type=text]").value
console.log(text)
if (validate(text)) {
e.preventDefault()
document.querySelector("input[type=text]").value = "请重新组织你的语言!"
setTimeout(() => {
document.querySelector("input[type=text]").value = ""
}, 1000);
}
})
}
})();