// ==UserScript==
// @name NGA检查帖子可见状态
// @namespace https://github.com/stone5265/GreasyFork-NGA-Check-Post-Status
// @version 0.3.1
// @author stone5265
// @description 检查自己发布的"主题/回复"别人是否能看见,并且可以关注任意人发布的"主题/回复"可见状态,当不可见时给予提示
// @license MIT
// @require https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-y/localforage/1.10.0/localforage.min.js#sha512=+BMamP0e7wn39JGL8nKAZ3yAQT2dL5oaXWr4ZYlTGkKOaoXM/Yj7c4oy50Ngz5yoUutAG17flueD4F6QpTlPng==
// @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.4.0/jquery.min.js#sha512=Pa4Jto+LuCGBHy2/POQEbTh0reuoiEXQWXGn8S7aRlhcwpVkO8+4uoZVSOqUjdCsE+77oygfu2Tl+7qGHGIWsw==
// @match *://bbs.nga.cn/*
// @match *://ngabbs.com/*
// @match *://nga.178.com/*
// @exclude */nuke.php*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @inject-into content
// ==/UserScript==
(function () {
const debounce = (fn, delay = 500) => {
let id
let pendingPromise
let resolvePending
return (...args) => {
clearTimeout(id)
if (pendingPromise) {
resolvePending(new Error("Debounced call cancelled"))
}
pendingPromise = new Promise((resolve) => {
resolvePending = resolve
});
id = setTimeout(async () => {
try {
const result = await fn(...args)
resolvePending(result)
} catch (err) {
resolvePending(err)
} finally {
pendingPromise = null
}
}, delay)
return pendingPromise
};
}
'use strict';
const CheckPostStatus = {
name: 'CheckPostStatus',
title: 'NGA检查帖子可见状态',
desc: '检查自己发布的 主题/回复 别人是否能看见',
settings: [
{
type: 'advanced',
key: 'expireDays',
title: '关注过期天数',
desc: '关注过期的天数,过期的关注在“检查全部”时不会进行检查\n(-1为永不过期)',
default: 120,
menu: 'left'
}, {
type: 'advanced',
key: 'autoDeleteAfterDays',
title: '关注过期后自动删除的天数',
desc: '关注过期的天数,过期的关注在“检查全部”时不会进行检查\n(-1为不进行自动删除)',
default: 1,
menu: 'left'
}, {
type: 'advanced',
key: 'autoCheckInterval',
title: '自动检查关注列表的间隔 (分钟)',
desc: '自动检查关注列表的间隔(最短间隔为5分钟),当处于帖子列表页时触发\n(建议不少于30分钟)\n(-1为不进行自动不进行自动检查)',
default: -1,
menu: 'left'
}
],
store: null,
cacheFid: {},
lastWarningTid: -1,
lastVisibleCheckUrl: '',
lastMissingCheckUrl: '',
visibleFloors: new Set(),
lock: new Promise(() => {}),
locks: new Array(20).fill(new Promise(() => {})),
initFunc() {
// const $ = this.mainScript.libs.$
const $ = script.libs.$
const this_ = this
// 创建储存实例
// this.store = this.mainScript.createStorageInstance('NGA_BBS_Script__CheckPostStatus')
this.store = script.createStorageInstance('NGA_BBS_Script__CheckPostStatus')
// 初始化的时候清除超过一定天数的过期关注
const currentTime = Math.floor(Date.now() / 1000)
let removedCount = 0
this.store.iterate((record, key) => {
const isPermanent = record.expireTime === -1
if (!isPermanent && currentTime >= record.expireTime) {
const expireDays = Math.floor((record.expireTime - currentTime) / 60 / 60 / 12)
const isAutoDelete = script.setting.advanced.autoDeleteAfterDays >= 0
if (isAutoDelete && expireDays >= script.setting.advanced.autoDeleteAfterDays) {
this_.store.removeItem(key)
removedCount += 1
}
}
})
.then(() => {
// this.mainScript.printLog(`${this.title}: 已清除${removedCount}条过期关注`)
script.printLog(`${this.title}: 已清除${removedCount}条过期关注`)
})
.catch(err => {
console.error(`${this.title}清除超期数据失败,错误原因:`, err)
})
// 点击"关注该楼层可见状态"按钮
$('body').on('click', '.cps__watch_icon', function () {
// 找到同一个容器内的另一个按钮
const $container = $(this).parent()
const $otherButton = $container.find('.cps__watch_icon').not($(this))
// 切换显示状态
$(this).hide()
$otherButton.show()
const type = $(this).data('type')
const href = $(this).data('href')
const floorNum = $(this).data('floor')
const params = this_.getUrlParams(href)
const key = `tid=${params['tid']}&pid=${params['pid']}`
if (type === 'unwatch') {
// 添加关注
const isPermanent = script.setting.advanced.expireDays < 0
const expireTime = isPermanent ? -1 : Math.floor(Date.now() / 1000) + script.setting.advanced.expireDays * 24 * 60 * 60
this_.store.setItem(key, {
topicName: document.title.replace(/\sNGA玩家社区/g, ''),
floorNum: parseInt(floorNum),
isVisible: null,
checkTime: null,
expireTime: expireTime
})
.then(() => {
this_.reloadWatchlist()
})
} else {
// 取消关注
this_.store.removeItem(key)
.then(() => {
this_.reloadWatchlist()
})
}
})
// 点击"重置时间"或者"永久关注"按钮
$('body').on('click', '.cps__wl-change-expire-time', async function() {
const key = $(this).data('key')
const time = $(this).data('time')
const expireTime = time === -1 ? -1 : Math.floor(Date.now() / 1000) + script.setting.advanced.expireDays * 24 * 60 * 60
await this_.store.getItem(key)
.then(record => {
this_.store.setItem(key, {
...record,
expireTime: expireTime
})
})
this_.reloadWatchlist()
})
// 点击"检查"按钮
$('body').on('click', '.cps__wl-check', async function() {
const key = $(this).data('key')
const isVisible = await this_.checkRowVisible(key)
// this_.mainScript.popMsg(`检查完成,目标位于${isVisible ? '可见' : '不可见'}状态`)
script.popMsg(`检查完成,目标位于${isVisible ? '可见' : '不可见'}状态`)
this_.reloadWatchlist()
})
// 点击"删除"按钮
$('body').on('click', '.cps__wl-del', function() {
const key = $(this).data('key')
this_.store.removeItem(key)
this_.reloadWatchlist()
})
// 点击"刷新"按钮
$('body').on('click', '.cps__panel-refresh', function() {
this_.reloadWatchlist()
})
// 点击"检查全部"按钮
$('body').on('click', '.cps__panel-checkall', async function() {
const $button = $(this)
const currentTime = Math.floor(Date.now() / 1000)
$button.text('检查中...').prop('disabled', true)
try {
const rows = []
await this_.store.iterate((record, key) => {
const isPermanent = record.expireTime === -1
const isSurvival = isPermanent || currentTime < record.expireTime
if (isSurvival) {
rows.push(key)
}
})
if (rows.length === 0) return
let invisibleNum = 0
let processed = 0
for (const key of rows) {
const isVisible = await this_.checkRowVisible(key)
if (!isVisible) {
invisibleNum++
}
processed++
this_.reloadWatchlist()
$button.text(`检查中... (${processed}/${rows.length})`)
if (processed < rows.length) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
// this_.mainScript.popMsg(`检查完成,总共检查了${rows.length}个楼层,其中${invisibleNum}个位于不可见状态`)
script.popMsg(`检查完成,总共检查了${rows.length}个楼层,其中${invisibleNum}个位于不可见状态`)
} catch (err) {
// this_.mainScript.popMsg(`失败!${err.message}`)
script.popMsg(`失败!${err.message}`)
} finally {
$button.text('检查所有').prop('disabled', false)
}
})
// 点击"清除过期关注"按钮
$('body').on('click', '.cps__panel-clean-expired', async function() {
await this_.cleanExpiredData()
this_.reloadWatchlist()
})
// 点击"清空*所有*关注"按钮
$('body').on('click', '.cps__panel-clean-all', function() {
this_.cleanLocalData()
this_.reloadWatchlist()
})
// 关闭面板
$('body').on('click', '.cps__list-panel .cps__panel-close', function () {
if ($(this).attr('close-type') == 'hide') {
$(this).parent().hide()
} else {
$(this).parent().remove()
}
})
// 关注列表
GM_registerMenuCommand('关注列表', function () {
if($('#cps__watchlist_panel').length > 0) return
$('body').append(`
<div id="cps__watchlist_panel" class="cps__list-panel animated fadeInUp">
<a href="javascript:void(0)" class="cps__panel-close">×</a>
<div class="cps__tab-header"><span class="cps__tab-active">关注列表(全部)</span><span>关注列表(不可见)</span></div>
<div class="cps__tab-content cps__tab-active">
<div class="cps__list-c">
<button class="cps__panel-refresh hld_cps_help" help="手动刷新列表的时间显示">刷新</button>
<button class="cps__panel-checkall">检查所有</button>
<button class="cps__panel-clean-expired hld_cps_help" help="过期超过${script.setting.advanced.autoDeleteAfterDays}天会自动删除">清除过期关注</button>
<button class="cps__panel-clean-all">清空*所有*关注</button>
<div class="cps__scroll-area">
<table class="cps__table">
<thead>
<tr>
<th width=55%>主题</th>
<th width=5%>楼层</th>
<th width=5%>状态</th>
<th width=5%>上次检查</th>
<th width=5%>剩余时间</th>
<th width=25%>操作</th>
</tr>
</thead>
<tbody id="cps__watchlist"></tbody>
</table>
</div>
</div>
</div>
<div class="cps__tab-content">
<div class="cps__list-c">
<button class="cps__panel-refresh hld_cps_help" help="手动刷新列表的时间显示">刷新</button>
<button disabled class="cps__panel-checkall" style="opacity: 0.6; cursor: not-allowed;">检查所有</button>
<button class="cps__panel-clean-expired hld_cps_help" help="过期超过${script.setting.advanced.autoDeleteAfterDays}天会自动删除">清除过期关注</button>
<button disabled class="cps__panel-clean-all" style="opacity: 0.6; cursor: not-allowed;">清空*所有*关注</button>
<div class="cps__scroll-area">
<table class="cps__table">
<thead>
<tr>
<th width=55%>主题</th>
<th width=5%>楼层</th>
<th width=5%>状态</th>
<th width=5%>上次检查</th>
<th width=5%>剩余时间</th>
<th width=25%>操作</th>
</tr>
</thead>
<tbody id="cps__watchlist-invisible"></tbody>
</table>
</div>
</div>
</div>
</div>
`)
// 切换选项卡
$('body').on('click', '.cps__tab-header > span', function(){
$('.cps__tab-header > span, .cps__tab-content').removeClass('cps__tab-active')
$(this).addClass('cps__tab-active')
$('.cps__tab-content').eq($(this).index()).addClass('cps__tab-active')
this_.reloadWatchlist()
})
//重载名单
this_.reloadWatchlist()
})
},
// 位于帖子列表页时自动检查关注列表
async renderThreadsFunc($el) {
// 位于列表页第一页的第一个帖子时才触发自动检查
if ($el.find('a').attr('id') !== 't_rc1_0') {
return
}
let autoCheckInterval = script.setting.advanced.autoCheckInterval
if (autoCheckInterval >= 0) {
// 最短间隔为5分钟
autoCheckInterval = Math.max(autoCheckInterval, 5)
} else {
// 当间隔为负数时不进行自动检查
return
}
const this_ = this
const $ = script.libs.$
const lastAutoCheckTime = await GM_getValue('cps__lastAutoCheckTime')
const currentTime = Math.floor(Date.now() / 1000) / 60
const deltaTime = (currentTime - parseFloat(lastAutoCheckTime)) / 60
// return
// 距离上次自动检查小于设置的间隔
if (lastAutoCheckTime && deltaTime < autoCheckInterval) {
return
}
// 进行自动检查
GM_setValue('cps__lastAutoCheckTime', String(currentTime))
try {
const rows = []
await this_.store.iterate((record, key) => {
const isPermanent = record.expireTime === -1
const isSurvival = isPermanent || currentTime < record.expireTime
if (isSurvival) {
rows.push(key)
}
})
if (rows.length === 0) return
let invisibleNum = 0
let processed = 0
for (const key of rows) {
const isVisible = await this_.checkRowVisible(key)
if (!isVisible) {
invisibleNum++
}
processed++
this_.reloadWatchlist()
const $button = $(document).find('.cps__tab-active .cps__panel-checkall')
if ($button.length) $button.text(`检查中... (${processed}/${rows.length})`).prop('disabled', true)
if (processed < rows.length) {
await new Promise(resolve => setTimeout(resolve, 1500))
}
}
// this_.mainScript.popMsg(`检查完成,总共检查了${rows.length}个楼层,其中${invisibleNum}个位于不可见状态`)
script.popMsg(`[自动检查]总共检查了${rows.length}个楼层,其中${invisibleNum}个位于不可见状态`)
} catch (err) {
// this_.mainScript.popMsg(`失败!${err.message}`)
script.popMsg(`[自动检查]失败!${err.message}`)
} finally {
const $button = $(document).find('.cps__tab-active .cps__panel-checkall')
if ($button.length) $button.text('检查所有').prop('disabled', false)
}
},
async renderFormsFunc($el) {
// const $ = this.mainScript.libs.$
const $ = script.libs.$
const checkUrl = document.baseURI
// 检查该页面缺失的楼层 (目前账号无法看到的楼层)
this.checkMissingFloors(checkUrl)
/**
* "tid={}(&authorid={})(&page={})"
*/
const queryString = checkUrl.split('?')[1]
const uid = parseInt($el.find('a[name="uid"]').text())
/**
* "pid{}Anchor"
*/
const pid = $el.find('td.c2').find('a')[0].id
/**
* "l{}"
*/
const floorName = $el.find('td.c2').find('a')[1].name
const currentFloor = parseInt(floorName.slice(1))
/**
* "/read.php?tid={}(&authorid={})&page={}#pid{}Anchor"
*/
const href = `/read.php?${queryString}${queryString.includes('&page=') ? '' : '&page=1'}#${pid}`
const params = this.getUrlParams(href)
// 检查该版面是否需要登录(不可用)才能查看
const isLimit = await this.checkFidLimit(__CURRENT_FID)
// 若该版面需要登录(不可用)才能访问, 则不支持部分功能
if (isLimit) {
// 当前帖子只提示一次
if (params['tid'] !== this.lastWarningTid) {
this.lastWarningTid = params['tid']
script.popMsg('该版面需要登陆才能访问,不支持[关注按钮]', 'warn')
}
// return
}
// 添加"关注该楼层可见状态"按钮
if (!isLimit) {
const key = `tid=${params['tid']}&pid=${params['pid']}`
const watching = await this.store.getItem(key) !== null
$el.find('.small_colored_text_btn.block_txt_c2.stxt').each(function () {
const mbDom = `
<a class="cps__watch_icon hld_cps_help"
help="关注该楼层可见状态"
data-type="unwatch"
data-href="${href}"
data-floor="${currentFloor}"
style="${!watching ? '' : 'display: none;'}">⚪</a>
<a class="cps__watch_icon hld_cps_help"
help="取消关注该楼层可见状态"
data-type="watch"
data-href="${href}"
data-floor="${currentFloor}"
style="${watching ? '' : 'display: none;'}">🔵</a>
`
$(this).append(mbDom)
})
}
// 检查该页面下登录(不可用)用户的发言
if (!isNaN(__CURRENT_UID) && uid === __CURRENT_UID) {
const this_ = this
if (!isLimit) {
// (正常区) 使用游客状态对当前页可见楼层进行标记
if (checkUrl !== this.lastVisibleCheckUrl) {
this.lastVisibleCheckUrl = checkUrl
// 记录当前页游客可见楼层号
this.visibleFloors = new Set()
const execute = debounce(async () => {
const result = this_.requestWithoutAuth(checkUrl)
.then(({ success, $html }) => {
if (success) {
// 记录当前页面所有游客能看到的楼层号
for (const floor of $html.find('td.c2')) {
const visibleFloor = parseInt($(floor).find('a')[1].name.slice(1))
this_.visibleFloors.add(visibleFloor)
}
}
})
return result
}, 1500)
this.lock = execute()
}
await this.lock
} else {
// (需要登录(不可用)才能进的区) 单独向每个属于登录(不可用)用户的楼层发送一条编辑请求
if (checkUrl !== this.lastVisibleCheckUrl) {
this.lastVisibleCheckUrl = checkUrl
this.visibleFloors = new Set()
this.locks = Array(20).fill().map(() => {
let resolveFn
const promise = new Promise(resolve => resolveFn = resolve)
return { promise, resolveFn }
})
const floors = Object.keys(commonui.postArg.data)
for (let floor of floors) {
if (isNaN(floor)) continue
floor = parseInt(floor)
// 如果处理完已经切换到其他页面, 则放弃对该页的后续操作
if (!(floor in commonui.postArg.data)) {
this.locks.forEach(lock => lock.resolveFn())
return
}
const data = commonui.postArg.data[floor]
if (parseInt(data.pAid, 10) !== __CURRENT_UID) continue
const { success } = await new Promise((resolve) => {
fetch(`/post.php?lite=js&action=modify&tid=${data.tid}&pid=${data.pid}`)
.then((res) => res.blob())
.then((blob) => {
const reader = new FileReader()
reader.onload = () => {
const text = reader.result;
const result = JSON.parse(
text.replace("window.script_muti_get_var_store=", "")
)
const { data, error } = result
if (error) {
// resolve(error[0])
resolve({ success: false })
return
}
if (data && data['post_type'] & 2) {
// resolve('只有作者/版主可见')
resolve({ success: false })
return
}
resolve({ success: true })
}
reader.readAsText(blob, "GBK")
})
.catch(() => {
// resolve("")
resolve({ success: false })
})
})
if (success) {
this.visibleFloors.add(floor)
}
this.locks[floor % 20].resolveFn()
await new Promise(resolve => setTimeout(resolve, 500))
}
}
await this.locks[currentFloor % 20].promise
}
const isVisible = this.visibleFloors.has(currentFloor)
// 如果楼层切换的比较快,等这页的游客访问完早已切换到另一页,则放弃对该楼的后续操作
if ($(document).find($el).length === 0) {
// console.log(`抛弃${floorName}`)
return
}
// 对不可见的楼层添加标记并提示
let mbDom
if (!isVisible) {
const floorName = currentFloor === 0 ? '主楼' : `${currentFloor}楼`
mbDom = '<span class="visibility_text hld_cps_help" help="若该状态持续超过30分钟,请联系版务协助处理" style="color: red; font-weight: bold;"> [不可见] </span>'
// this.mainScript.popNotification(`当前页检测到${floor}不可见`, 4000)
script.popNotification(`当前页检测到${floorName}其他人不可见`, 4000)
} else {
mbDom = '<span class="visibility_text" style="font-weight: bold;"> 可见 </span>'
}
$el.find('.small_colored_text_btn.block_txt_c2.stxt').each(function () {
$(this).append(mbDom)
})
}
},
/**
* 游客状态访问
*/
requestWithoutAuth(url) {
// const $ = this.mainScript.libs.$
const $ = script.libs.$
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
anonymous: true,
responseType: 'arraybuffer',
onload: function(response) {
const text = response.response instanceof ArrayBuffer ? new TextDecoder('gbk').decode(response.response) : response.response
if (response.status === 200) {
resolve({
success: true,
$html: $(text)
})
}
// 获取错误信息
let errorCode
let errorMessage
if (response.response instanceof ArrayBuffer) {
errorCode = text.match(/(ERROR:<!--msgcodestart-->([\d]+)<!--msgcodeend-->)/)[2]
errorMessage = text.match(/<title>([^<]+)<\/title>/)[1]
} else {
errorCode = text.match(/(ERROR:<!--msgcodestart-->([\d]+)<!--msgcodeend-->)/)[2]
errorMessage = `(ERROR:${errorCode})`
}
// "(ERROR:15)访客不能直接访问" 进行跳转后可访问
if (errorCode === '15') {
// 跳转所需要用到的游客cookie
const lastvisit = response.responseHeaders.match(/lastvisit=([^;]+)/)[0]
const ngaPassportUid = response.responseHeaders.match(/ngaPassportUid=([^;]+)/)[0]
const guestJs = text.match(/guestJs=([^;]+)/)[0]
// 添加随机参数防止缓存
const r = Math.floor(Math.random()*1000)
const finalUrl = response.finalUrl.replace(/(?:\?|&)rand=\d+/,'')+'&rand=' + r
// 携带游客cookie后再次访问
GM_xmlhttpRequest({
method: 'GET',
url: finalUrl,
headers: {
"Cookie": `${lastvisit}; lastpath=0; ${ngaPassportUid}; ${guestJs}`,
'Referer': response.finalUrl
},
anonymous: true,
onload: function(response) {
if (response.status === 200) {
resolve({
success: true,
$html: $(response.responseText)
})
} else {
const errorCode = text.match(/(ERROR:<!--msgcodestart-->([\d]+)<!--msgcodeend-->)/)[2]
console.error(`(ERROR:${errorCode})`)
resolve({ success: false })
}
},
onerror: function(error) {
console.error(error)
resolve({ success: false })
}
})
} else {
console.error(errorMessage)
resolve({ success: false })
}
},
onerror: function(error) {
console.error(error)
resolve({ success: false })
}
})
})
},
/**
* 检查该页面缺失的楼层 (目前账号无法看到的楼层)
* @method checkMissingFloors
* @param {string} checkUrl
* @returns
*/
checkMissingFloors(checkUrl) {
const $ = script.libs.$
if ((checkUrl === this.lastMissingCheckUrl)) return
this.lastMissingCheckUrl = checkUrl
// 倒序模式
const isReversed = commonui.postArg.def.tmBit1 & 262144
// 只看作者模式
const isOnlyAuthor = checkUrl.match(/authorid=/) !== null
// 该贴总回帖数
const maxFloor = commonui.postArg.def.tReplies
// 获取当前所在页的页数 (注: 使用 __PAGE[2] 获取的当前页数 在点击"加载下一页"按钮时 获取的还是当前页而非新加载出来的一页的页数)
const pageMatch = checkUrl.match(/page=([\d]+)/)
let currentPage = pageMatch ? parseInt(pageMatch[1]) : 1
// 正序模式回帖或者编辑, 前者page=e, 后者不会出现page=
if (!pageMatch && __PAGE !== undefined) {
currentPage = __PAGE[2]
}
// 是否为最后一页
const isLastPage = pageMatch ? currentPage === __PAGE[1] : true
// 该页开始楼层号
let startFloor
// 该页截止楼层号
let endFloor
// 记录当前页目前账号能看到的楼层
const currPageFloors = new Set()
$(document).find('.forumbox .postrow').each((index, dom) => {
const floor = parseInt($(dom).attr('id').split('strow')[1])
currPageFloors.add(floor)
})
if (isOnlyAuthor) {
// 不支持倒序模式下的只看作者
if (isReversed) {
script.popMsg('[检查缺失楼层]不支持倒序模式下的只看作者', 'warn')
return
}
// 只看作者模式的最后一页只能使用该页能看到的楼层中最大楼层号
if (isLastPage) {
startFloor = Math.max(1, (currentPage - 1) * 20)
endFloor = Math.max(...currPageFloors)
}
}
else {
if (!isReversed) {
// 正序模式通过该页页数来计算范围 (并对其进行阻断来保证最后一页范围计算正确)
startFloor = Math.max(1, (currentPage - 1) * 20)
endFloor = Math.min(maxFloor, currentPage * 20 - 1)
} else {
// 倒序模式通过模拟来计算当前页楼层号的范围
// 第一页跳过主楼
let iPage = 1
endFloor = maxFloor
startFloor = endFloor - 18
// 第二页到当前页
++iPage
while (iPage <= currentPage) {
endFloor -= 20
startFloor -= 20
++iPage
}
// 截断最后一页的开始楼号
startFloor = Math.max(1, startFloor)
}
}
// 主楼检查 (用于只看作者模式)
if (currentPage === 1 && !currPageFloors.has(0)) {
script.popNotification(`当前页检测到0楼缺失`, 4000)
}
if (!isReversed) {
// 正序提示
for (let i = Math.max(1, startFloor); i <= Math.min(maxFloor, endFloor); ++i) {
if (!currPageFloors.has(i)) {
script.popNotification(`当前页检测到${i}楼缺失`, 4000)
}
}
} else {
// 倒序提示
for (let i = Math.min(maxFloor, endFloor); i >= Math.max(1, startFloor); --i) {
if (!currPageFloors.has(i)) {
script.popNotification(`当前页检测到${i}楼缺失`, 4000)
}
}
}
},
/**
* 检查该版面是否需要登录(不可用)才能查看
* @method checkFidLimit
* @param {number} fid
*/
async checkFidLimit(fid) {
// 对版面限制进行缓存
if (this.cacheFid[fid] === undefined) {
this.cacheFid[fid] = new Promise((resolve) => {
fetch(`/thread.php?fid=${fid}&lite=js`, {
method: 'GET',
credentials: 'omit'
})
.then((res) => res.blob())
.then((blob) => {
const reader = new FileReader()
reader.onload = () => {
const text = reader.result
const result = JSON.parse(
text.replace("window.script_muti_get_var_store=", "")
)
const { data, error } = result
if (error) {
resolve(error[0])
} else {
resolve('')
}
}
reader.readAsText(blob, "GBK")
})
.catch((err) => {
resolve(err.message)
})
})
}
const error = await this.cacheFid[fid]
return error === '1:未登录(不可用)'
},
/**
* 获取URL参数对象
* @method getUrlParams
* @param {string} url"/read.php?tid={}(&authorid={})&page={}#pid{}Anchor"
* @return {Object} 参数对象
*/
getUrlParams(url) {
let params = {}
const $ = url.split('#')
const url_ = $[0]
const pid = parseInt($[1].slice(3, -6))
const queryString = url_.split('?')[1]
queryString.split('&').forEach(item => {
const $ = item.split('=')
if ($[0] && $[1]) {
params[$[0]] = parseInt($[1])
}
})
params['pid'] = pid
return params
},
/**
* 重新渲染关注列表
* @method reloadWatchlist
*/
reloadWatchlist() {
// const $ = this.mainScript.libs.$
const $ = script.libs.$
if($('#cps__watchlist_panel').length === 0) return
let $watchlist
let isWatchlistInbisible
const $watchlistAll = $('.cps__tab-active #cps__watchlist')
const $watchlistInbisible = $('.cps__tab-active #cps__watchlist-invisible')
if ($watchlistAll.length === 0 && $watchlistInbisible.length === 0) {
return
} else {
if ($watchlistAll.length) {
$watchlist = $watchlistAll
isWatchlistInbisible = false
} else {
$watchlist = $watchlistInbisible
isWatchlistInbisible = true
}
}
let expiredRows = []
let rows = []
this.store.iterate((record, key) => {
if (isWatchlistInbisible && record.isVisible !== false) return
const currentTime = Math.ceil(Date.now() / 1000)
const isPermanent = record.expireTime === -1
const isSurvival = isPermanent || currentTime < record.expireTime
let timeLeft
if (isSurvival) {
if (isPermanent) {
timeLeft = '永久'
} else {
timeLeft = Math.floor((record.expireTime - currentTime) / 60 / 60)
if (timeLeft === 0) {
timeLeft = '<1小时'
} else if (timeLeft < 24) {
timeLeft = `${timeLeft}小时`
} else {
timeLeft = `${Math.floor(timeLeft / 24)}天`
}
}
} else {
timeLeft = Math.floor((currentTime - record.expireTime) / 60 / 60)
if (timeLeft === 0) {
timeLeft = '已过期(<1小时)'
} else if (timeLeft < 24) {
timeLeft = `已过期(${timeLeft}小时)`
} else {
timeLeft = `已过期(${Math.floor(timeLeft / 24)}天)`
}
}
let timeSinceLastCheck
let visibleStatus
if (record.checkTime !== null) {
timeSinceLastCheck = Math.floor((currentTime - record.checkTime) / 60)
if (timeSinceLastCheck === 0) {
timeSinceLastCheck = '<1分钟'
} else if (timeSinceLastCheck < 60 * 3) {
timeSinceLastCheck = `${timeSinceLastCheck}分钟前`
} else if (timeSinceLastCheck < 60 * 24) {
timeSinceLastCheck = `${Math.floor(timeLeft / 60)}小时前`
} else {
timeSinceLastCheck = '超过1天'
}
visibleStatus = record.isVisible ? '可见' : '<p style="color: red; font-weight: bold;">不可见</p>'
} else {
timeSinceLastCheck = '-'
visibleStatus = '-'
}
const floor = record.floorNum === 0 ? '主楼' : `${record.floorNum}楼`
const keywords = key.split('&') // key='tid={}&pid={}'
const query = keywords[1] === 'pid=0' ? keywords[0] : keywords[1]
// 对应楼层跳转链接
const href = `/read.php?${query}&opt=128`
const context = `
<tr>
<td title="${record.topicName}">${record.topicName}</td>
<td title="${floor}"><a href="${href}" class="urlincontent">${floor}</a></td>
<td>${visibleStatus}</td>
<td title="${timeSinceLastCheck}">${timeSinceLastCheck}</td>
<td title="${timeLeft}">${timeLeft}</td>
<td>
<button class="cps__wl-change-expire-time hld_cps_help" help="重置剩余时间为设置的关注过期天数" data-key="${key}" data-time="reset" >重置</span>
<button class="cps__wl-change-expire-time hld_cps_help" help="将剩余时间设置为永不过期" data-key="${key}" data-time=-1 help="将剩余时间设置为永不过期">永久</span>
<button class="cps__wl-check" data-key="${key}">检查</span>
<button class="cps__wl-del" data-key="${key}">删除</span>
</td>
</tr>
`
if (isSurvival) {
rows.push([key, context])
} else {
expiredRows.push([key, context])
}
})
.then(() => {
$watchlist.empty()
// 按照tid进行排序
expiredRows.sort((a, b) => a[0].localeCompare(b[0]))
rows.sort((a, b) => a[0].localeCompare(b[0]))
// 将过期关注放在最上面
expiredRows.forEach(row => $watchlist.append(row[1]))
rows.forEach((row) => $watchlist.append(row[1]))
})
},
/**
* 检查关注列表中某一行的可见状态
* @method checkRowVisible
*/
async checkRowVisible(key) {
const keywords = key.split('&') // key='tid={}&pid={}'
const query = keywords[1] === 'pid=0' ? keywords[0] : keywords[1]
const href = `/read.php?${query}`
const { success, $html } = await this.requestWithoutAuth(href)
const isVisible = success && $html.find('table.forumbox.postbox').length > 0
const record = await this.store.getItem(key)
await this.store.setItem(key, {
...record,
isVisible: isVisible,
checkTime: Math.floor(Date.now() / 1000)
})
return isVisible
},
/**
* 清除过期关注
* @method cleanLocalData
*/
async cleanExpiredData() {
this.store.iterate((record, key) => {
const currentTime = Math.ceil(Date.now() / 1000)
const isPermanent = record.expireTime === -1
const isSurvival = isPermanent || currentTime < record.expireTime
if (!isSurvival) {
this.store.removeItem(key)
}
})
},
/**
* 清空关注列表
* @method cleanLocalData
*/
cleanLocalData() {
if (window.confirm('确定要清理所有关注吗?')) {
this.store.clear()
alert('操作成功')
}
},
style: `
.cps__watch_icon {position: relative;padding:0 1px;text-decoration:none;cursor:pointer;}
.cps__watch_icon {text-decoration:none !important;}
.cps__tab-header {height:40px}
.cps__tab-header>span {margin-right:10px;padding:5px;cursor:pointer}
.cps__tab-header .cps__tab-active,.cps__tab-header>span:hover {color:#591804;font-weight:700;border-bottom:3px solid #591804}
.cps__tab-content {display:flex;justify-content:space-between;flex-wrap: wrap;}
.cps__tab-content {display:none}
.cps__tab-content.cps__tab-active {display:flex}
.cps__list-panel {position:fixed;top:50px;left:50%;transform:translate(-50%, -50%);width:80%;overflow:auto;max-height:60%;background:#fff8e7;padding:15px 20px;border-radius:10px;box-shadow:0 0 10px #666;border:1px solid #591804;z-index:9999;}
.cps__list-panel .cps__list-c {width:100%;height:100%}
.cps__list-panel .cps__list-c textarea {box-sizing:border-box;padding:0;margin:0;height:100%;width:100%;resize:none;}
.cps__list-panel .cps__list-c > p:first-child {font-weight:bold;font-size:14px;margin-bottom:10px;}
.cps__panel-close {position:absolute;top:5px;right:5px;padding:3px 6px;background:#fff0cd;color:#591804;transition:all .2s ease;cursor:pointer;border-radius:4px;text-decoration:none;z-index:9999;}
.cps__panel-close:hover {background:#591804;color:#fff0cd;text-decoration:none;}
.cps__table {table-layout:fixed;width:100%;height:100%;border-top:1px solid #ead5bc;border-left:1px solid #ead5bc}
.cps__table thead {background:#591804;border:1px solid #591804;color:#fff}
.cps__table td,.cps__table th {padding:3px 5px;border-bottom:1px solid #ead5bc;border-right:1px solid #ead5bc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cps__scroll-area {position:relative;height:100%;overflow:auto;border:1px solid #ead5bc}
.cps__scroll-area::-webkit-scrollbar {width:6px;height:6px}
.cps__scroll-area::-webkit-scrollbar-thumb {border-radius:10px;box-shadow:inset 0 0 5px rgba(0,0,0,.2);background:#591804}
.cps__scroll-area::-webkit-scrollbar-track {box-shadow:inset 0 0 5px rgba(0,0,0,.2);border-radius:10px;background:#ededed}
`
}
////////////////////////////////////////////////////////////////
class NGABBSScript_CheckPostStatus {
constructor() {
// 配置
this.setting = {
original: [],
normal: {},
advanced: {}
}
// 模块
this.modules = []
// 样式
this.style = ''
// 数据存储
this.store = {}
// 引用库
this.libs = {$, localforage}
}
/**
* 获取模块对象
* @method getModule
* @param {String} name 模块name
* @return {Object} 模块对象
*/
getModule(name) {
for (const m of this.modules) {
if (m.name && m.name === name) {
return m
}
}
return null
}
// /**
// * 全程渲染函数
// * @method renderAlways
// */
// renderAlways() {
// for (const module of this.modules) {
// try {
// module.renderAlwaysFunc && module.renderAlwaysFunc(this)
// } catch (error) {
// this.printLog(`[${module.name}]模块在[renderAlwaysFunc()]中运行失败!`)
// console.log(error)
// }
// }
// }
/**
* 列表页渲染函数
* @method renderThreads
*/
renderThreads() {
$('.topicrow[hld-cps-threads-render!=ok]').each((index, dom) => {
const $el = $(dom)
for (const module of this.modules) {
try {
module.renderThreadsFunc && module.renderThreadsFunc($el, this)
} catch (error) {
this.printLog(`[${module.name}]模块在[renderThreadsFunc()]中运行失败!`)
console.log(error)
}
}
$el.attr('hld-cps-threads-render', 'ok')
})
}
/**
* 详情页渲染函数
* @method renderForms
*/
renderForms() {
$('.forumbox.postbox[hld-cps-forms-render!=ok]').each((index, dom) => {
const $el = $(dom)
// 等待NGA页面渲染完成
if ($el.find('.small_colored_text_btn').length == 0) return true
for (const module of this.modules) {
try {
module.renderFormsFunc && module.renderFormsFunc($el, this)
} catch (error) {
this.printLog(`[${module.name}]模块在[renderFormsFunc()]中运行失败!`)
console.log(error)
}
}
$el.attr('hld-cps-forms-render', 'ok')
})
}
/**
* 添加模块
* @method addModule
* @param {Object} module 模块对象
* @param {Boolean} plugin 是否为插件
*/
addModule(module) {
// 组件预处理函数
if (module.preProcFunc) {
try {
module.preProcFunc(this)
} catch (error) {
this.printLog(`[${module.name}]模块在[preProcFunc()]中运行失败!`)
console.log(error)
}
}
// 添加设置
const addSetting = setting => {
// 标准模块配置
if (setting.shortCutCode && this.setting.normal.shortcutKeys) {
this.setting.normal.shortcutKeys.push(setting.shortCutCode)
}
if (setting.key) {
this.setting[setting.type || 'normal'][setting.key] = setting.default ?? ''
this.setting.original.push(setting)
}
}
// 功能板块
if (module.setting && !Array.isArray(module.setting)) {
addSetting(module.setting)
}
if (module.settings && Array.isArray(module.settings)) {
for (const setting of module.settings) {
addSetting(setting)
}
}
// 添加样式
if (module.style) {
this.style += module.style
}
this.modules.push(module)
}
/**
* 判断当前页面是否为列表页
* @method isThreads
* @return {Boolean} 判断状态
*/
isThreads() {
return $('#m_threads').length > 0
}
/**
* 判断当前页面是否为详情页
* @method isForms
* @return {Boolean} 判断状态
*/
isForms() {
return $('#m_posts').length > 0
}
/**
* 抛出异常
* @method throwError
* @param {String} msg 异常信息
*/
throwError(msg) {
alert(msg)
throw(msg)
}
/**
* 初始化
* @method init
*/
init() {
// 开始初始化
this.printLog('初始化...')
localforage.config({name: 'NGA BBS Script DB'})
const startInitTime = new Date().getTime()
const modulesTable = []
//同步配置
this.loadSetting()
// 组件初始化函数
for (const module of this.modules) {
if (module.initFunc) {
try {
module.initFunc(this)
} catch (error) {
this.printLog(`[${module.name}]模块在[initFunc()]中运行失败!`)
console.log(error)
}
}
}
// 组件后处理函数
for (const module of this.modules) {
if (module.postProcFunc) {
try {
module.postProcFunc(this)
} catch (error) {
this.printLog(`[${module.name}]模块在[postProcFunc()]中运行失败!`)
console.log(error)
}
}
}
// 动态样式
for (const module of this.modules) {
if (module.asyncStyle) {
try {
this.style += module.asyncStyle(this)
} catch (error) {
this.printLog(`[${module.name}]模块在[asyncStyle()]中运行失败!`)
console.log(error)
}
}
modulesTable.push({
name: module.title || module.name || 'UNKNOW',
type: module.type == 'plugin' ? '插件' : '标准模块',
version: module.version || '-'
})
}
// 插入样式
const style = document.createElement("style")
style.appendChild(document.createTextNode(this.style))
document.getElementsByTagName('head')[0].appendChild(style)
// 初始化完成
const endInitTime = new Date().getTime()
console.table(modulesTable)
this.printLog(`[v${this.getInfo().version}] 初始化完成: 共加载${this.modules.length}个模块,总耗时${endInitTime-startInitTime}ms`)
console.log('%c反馈问题请前往: https://github.com/stone5265/GreasyFork-NGA-Check-Post-Status/issues', 'color:orangered;font-weight:bolder')
}
/**
* 通知弹框
* @method popNotification
* @param {String} msg 消息内容
* @param {Number} duration 显示时长(ms)
*/
popNotification(msg, duration=1000) {
$('#hld_cps_noti_container').length == 0 && $('body').append('<div id="hld_cps_noti_container"></div>')
let $msgBox = $(`<div class="hld_cps_noti-msg">${msg}</div>`)
$('#hld_cps_noti_container').append($msgBox)
$msgBox.slideDown(100)
setTimeout(() => { $msgBox.fadeOut(500) }, duration)
setTimeout(() => { $msgBox.remove() }, duration + 500)
}
/**
* 消息弹框
* @method popMsg
* @param {String} msg 消息内容
* @param {String} type 消息类型 [ok, err, warn]
*/
popMsg(msg, type='ok') {
$('.hld_cps_msg').length > 0 && $('.hld_cps_msg').remove()
let $msg = $(`<div class="hld_cps_msg hld_cps_msg-${type}">${msg}</div>`)
$('body').append($msg)
$msg.slideDown(200)
setTimeout(() => { $msg.fadeOut(500) }, type == 'ok' ? 2000 : 5000)
setTimeout(() => { $msg.remove() }, type == 'ok' ? 2500 : 5500)
}
/**
* 打印控制台消息
* @method printLog
* @param {String} msg 消息内容
*/
printLog(msg) {
// console.log(`%cNGA%cScript%c ${msg}`,
// 'background: #222;color: #fff;font-weight:bold;padding:2px 2px 2px 4px;border-radius:4px 0 0 4px;',
// 'background: #fe9a00;color: #000;font-weight:bold;padding:2px 4px 2px 2px;border-radius:0px 4px 4px 0px;',
// 'background:none;color:#000;'
// )
console.log(msg)
}
/**
* 读取值
* @method saveSetting
* @param {String} key
*/
getValue(key) {
try {
return GM_getValue(key) || window.localStorage.getItem(key)
} catch {
// 兼容性代码: 计划将在5.0之后废弃
return window.localStorage.getItem(key)
}
}
/**
* 写入值
* @method setValue
* @param {String} key
* @param {String} value
*/
setValue(key, value) {
try {
GM_setValue(key, value)
} catch {}
}
/**
* 删除值
* @method deleteValue
* @param {String} key
*/
deleteValue(key) {
try {
GM_deleteValue(key)
} catch {}
// 兼容性代码: 计划将在5.0之后废弃
window.localStorage.removeItem(key)
}
/**
* 保存配置到本地
* @method saveSetting
* @param {String} msg 自定义消息信息
*/
saveSetting(msg='保存配置成功,刷新页面生效') {
// // 基础设置
// for (let k in this.setting.normal) {
// $('input#hld_cps_cb_' + k).length > 0 && (this.setting.normal[k] = $('input#hld_cps_cb_' + k)[0].checked)
// }
// script.setValue('hld_cps_NGA_setting', JSON.stringify(this.setting.normal))
// 高级设置
for (let k in this.setting.advanced) {
if ($('#hld_cps_adv_' + k).length > 0) {
const originalSetting = this.setting.original.find(s => s.type == 'advanced' && s.key == k)
const valueType = typeof originalSetting.default
const inputType = $('#hld_cps_adv_' + k)[0].nodeName
if (inputType == 'SELECT') {
this.setting.advanced[k] = $('#hld_cps_adv_' + k).val()
} else {
if (valueType == 'boolean') {
this.setting.advanced[k] = $('#hld_cps_adv_' + k)[0].checked
}
if (valueType == 'number') {
this.setting.advanced[k] = +$('#hld_cps_adv_' + k).val()
}
if (valueType == 'string') {
this.setting.advanced[k] = $('#hld_cps_adv_' + k).val()
}
}
}
}
script.setValue('hld_cps_NGA_advanced_setting', JSON.stringify(this.setting.advanced))
msg && this.popMsg(msg)
}
/**
* 从本地读取配置
* @method loadSetting
*/
loadSetting() {
// 基础设置
try {
// const settingStr = script.getValue('hld_cps_NGA_setting')
// if (settingStr) {
// let localSetting = JSON.parse(settingStr)
// for (let k in this.setting.normal) {
// !localSetting.hasOwnProperty(k) && (localSetting[k] = this.setting.normal[k])
// if (k == 'shortcutKeys') {
// if (localSetting[k].length < this.setting.normal[k].length) {
// const offset_count = this.setting.normal[k].length - localSetting[k].length
// localSetting[k] = localSetting[k].concat(this.setting.normal[k].slice(-offset_count))
// }
// // 更改默认按键
// let index = 0
// for (const module of this.modules) {
// if (module.setting && module.setting.shortCutCode) {
// if (localSetting[k][index] != module.setting.shortCutCode) {
// module.setting.rewriteShortCutCode = localSetting[k][index]
// }
// index += 1
// }else if (module.settings) {
// for (const setting of module.settings) {
// if (setting.shortCutCode) {
// if (localSetting[k][index] != setting.shortCutCode) {
// setting.rewriteShortCutCode = localSetting[k][index]
// }
// index += 1
// }
// }
// }
// }
// }
// }
// for (let k in localSetting) {
// !this.setting.normal.hasOwnProperty(k) && delete localSetting[k]
// }
// this.setting.normal = localSetting
// }
// 高级设置
const advancedSettingStr = script.getValue('hld_cps_NGA_advanced_setting')
if (advancedSettingStr) {
let localAdvancedSetting = JSON.parse(advancedSettingStr)
for (let k in this.setting.advanced) {
!localAdvancedSetting.hasOwnProperty(k) && (localAdvancedSetting[k] = this.setting.advanced[k])
}
for (let k in localAdvancedSetting) {
!this.setting.advanced.hasOwnProperty(k) && delete localAdvancedSetting[k]
}
this.setting.advanced = localAdvancedSetting
}
} catch(e) {
script.throwError(`读取配置文件出现错误,无法加载配置文件!\n错误问题: ${e}\n\n请尝试使用【修复脚本】来修复此问题`)
}
}
// /**
// * 检查是否更新
// * @method checkUpdate
// */
// checkUpdate() {
// // 字符串版本转数字
// const vstr2num = str => {
// let num = 0
// str.split('.').forEach((n, i) => num += i < 2 ? +n * 1000 / Math.pow(10, i) : +n)
// return num
// }
// // 字符串中版本截取
// const vstr2mid = str => {
// return str.substring(0, str.lastIndexOf('.'))
// }
// //检查更新
// const cver = script.getValue('hld_cps_NGA_version')
// if (cver) {
// const local_version = vstr2num(cver)
// const current_version = vstr2num(GM_info.script.version)
// if (current_version > local_version) {
// const lv_mid = +vstr2mid(cver)
// const cv_mid = +vstr2mid(GM_info.script.version)
// script.setValue('hld_cps_NGA_version', GM_info.script.version)
// if (cv_mid > lv_mid) {
// const focus = ''
// $('body').append(`<div id="hld_cps_updated" class="animated-1s bounce"><p><a href="javascript:void(0)" class="hld_cps_setting-close">×</a><b>NGA-Script已更新至v${GM_info.script.version}</b></p>${focus}<p><a class="hld_cps_readme" href="https://greasyfork.dpdns.org/zh-CN/scripts/393991-nga%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C" target="_blank">查看更新内容</a></p></div>`)
// $('body').on('click', '#hld_cps_updated a', function () {
// $(this).parents('#hld_cps_updated').remove()
// })
// }
// }
// } else script.setValue('hld_cps_NGA_version', GM_info.script.version)
// }
/**
* 创建储存对象实例
* @param {String} instanceName 实例名称
*/
createStorageInstance(instanceName) {
if (!instanceName || Object.keys(this.store).includes(instanceName)) {
this.throwError('创建储存对象实例失败,实例名称不能为空或实例名称已存在')
}
const lfInstance = localforage.createInstance({name: instanceName})
this.store[instanceName] = lfInstance
return lfInstance
}
/**
* 运行脚本
* @method run
*/
run() {
// this.checkUpdate()
this.init()
setInterval(() => {
// this.renderAlways()
this.isThreads() && this.renderThreads()
this.isForms() && this.renderForms()
}, 100)
}
/**
* 获取脚本信息
* @method getInfo
* @return {Object} 脚本信息对象
*/
getInfo() {
return {
version: GM_info.script.version,
author: 'stone5265',
github: 'https://github.com/stone5265/GreasyFork-NGA-Check-Post-Status',
}
}
}
const SVG_ICON_MSG = "data:image/svg+xml,%3Csvg t='1595842925125' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='2280' width='200' height='200'%3E%3Cpath d='M89.216226 575.029277c-6.501587-7.223986-10.47478-15.892769-12.641975-26.367549-1.805996-10.47478-0.722399-20.22716 3.973192-29.257143l4.695591-10.47478c5.05679-8.307584 11.558377-13.725573 19.865961-15.892769 7.946384-2.167196 15.892769-0.361199 23.477954 5.417989L323.995767 639.322751c8.307584 5.779189 17.698765 8.668783 27.812346 8.307584 10.11358-0.361199 18.782363-3.611993 26.006349-10.11358L898.302646 208.411993c7.585185-5.779189 16.253968-8.307584 26.006349-7.585185 9.752381 0.722399 18.059965 4.334392 24.922751 10.47478l-12.641975-12.641975c6.501587 7.223986 9.752381 15.17037 9.752381 24.561552 0 9.391182-3.250794 17.337566-9.752381 24.561552L376.008466 816.310406c-7.223986 7.223986-15.17037 10.47478-24.200353 10.47478-9.029982 0-16.976367-3.250794-24.200353-9.752381L89.216226 575.029277z' p-id='2281' fill='%23ffffff'%3E%3C/path%3E%3C/svg%3E";
/**
* 设置模块
* @name SettingPanel
* @description 提供脚本的设置面板,提供配置修改,保存等基础功能
*/
const SettingPanel = {
name: 'SettingPanel',
title: '设置模块',
initFunc() {
//设置面板
let $panelDom = $(`
<div id="hld_cps_setting_cover" class="animated zoomIn">
<div id="hld_cps_setting_panel">
<a href="javascript:void(0)" id="hld_cps_setting_close" class="hld_cps_setting-close" close-type="hide">×</a>
<p class="hld_cps_sp-title">NGA检查帖子可见状态<span class="hld_cps_script-info">v${script.getInfo().version}</span><span class="hld_cps_script-info"> - 基于NGA优化摸鱼体验v4.5.4引擎</span></p>
<div style="clear:both"></div>
<div class="hld_cps_advanced-setting">
<div class="hld_cps_advanced-setting-panel">
<p>⚠ 鼠标停留在<span class="hld_cps_help" title="详细描述">选项文字</span>上可以显示详细描述,设置有误可能会导致插件异常或者无效!</p>
<table id="hld_cps_advanced_left"></table>
<table id="hld_cps_advanced_right"></table>
</div>
</div>
<div class="hld_cps_buttons">
<span id="hld_setting_panel_buttons"></span>
<span>
<button class="hld_cps_btn" id="hld_cps_save__data">保存设置</button>
</span>
</div>
</div>
</div>
`)
const insertDom = setting => {
if (setting.type === 'normal') {
$panelDom.find(`#hld_cps_normal_${setting.menu || 'left'}`).append(`
<p><label ${setting.desc ? 'class="hld_cps_help" help="'+setting.desc+'"' : ''}><input type="checkbox" id="hld_cps_cb_${setting.key}"> ${setting.title || setting.key}${setting.shortCutCode ? '(快捷键切换[<b>'+script.getModule('ShortCutKeys').getCodeName(setting.rewriteShortCutCode || setting.shortCutCode)+'</b>])' : ''}</label></p>
`)
if (setting.extra) {
$panelDom.find(`#hld_cps_cb_${setting.key}`).attr('enable', `hld_cps_${setting.key}_${setting.extra.mode || 'fold'}`)
$panelDom.find(`#hld_cps_normal_${setting.menu || 'left'}`).append(`
<div class="hld_cps_sp-${setting.extra.mode || 'fold'}" id="hld_cps_${setting.key}_${setting.extra.mode || 'fold'}" data-id="hld_cps_${setting.key}">
<p><button id="${setting.extra.id}">${setting.extra.label}</button></p>
</div>
`)
}
}
if (setting.type === 'advanced') {
let formItem = ''
const valueType = typeof setting.default
if (valueType === 'boolean') {
formItem = `<input type="checkbox" id="hld_cps_adv_${setting.key}">`
}
if (valueType === 'number') {
formItem = `<input type="number" id="hld_cps_adv_${setting.key}">`
}
if (valueType === 'string') {
if (setting.options) {
let t = ''
for (const option of setting.options) {
t += `<option value="${option.value}">${option.label}</option>`
}
formItem = `<select id="hld_cps_adv_${setting.key}">${t}</select>`
} else {
formItem = `<input type="text" id="hld_cps_adv_${setting.key}">`
}
}
$panelDom.find(`#hld_cps_advanced_${setting.menu || 'left'}`).append(`
<tr>
<td><span class="hld_cps_help" help="${setting.desc || ''}">${setting.title || setting.key}</span></td>
<td>${formItem}</td>
</tr>`)
}
}
for (const module of script.modules) {
if (module.setting && module.setting.key) {
insertDom(module.setting)
}
if (module.settings) {
for (const setting of module.settings) {
setting.key && insertDom(setting)
}
}
}
/**
* Bind:Mouseover Mouseout
* 提示信息Tips
*/
$('body').on('mouseover', '.hld_cps_help', function(e){
if (!$(this).attr('help')) return
const $help = $(`<div class="hld_cps_help-tips">${$(this).attr('help').replace(/\n/g, '<br>')}</div>`)
$help.css({
top: ($(this).offset().top + $(this).height() + 5) + 'px',
left: $(this).offset().left + 'px'
})
$('body').append($help)
}).on('mouseout', '.hld_cps_help', ()=>$('.hld_cps_help-tips').remove())
$('body').append($panelDom)
//本地恢复设置
//基础设置
// for (let k in script.setting.normal) {
// if ($('#hld_cps_cb_' + k).length > 0) {
// $('#hld_cps_cb_' + k)[0].checked = script.setting.normal[k]
// const enableDomID = $('#hld_cps_cb_' + k).attr('enable')
// if (enableDomID) {
// script.setting.normal[k] ? $('#' + enableDomID).show() : $('#' + enableDomID).hide()
// $('#' + enableDomID).find('input').each(function () {
// $(this).val() == script.setting.normal[$(this).attr('name').substring(8)] && ($(this)[0].checked = true)
// })
// $('#hld_cps_cb_' + k).on('click', function () {
// $(this)[0].checked ? $('#' + enableDomID).slideDown() : $('#' + enableDomID).slideUp()
// })
// }
// }
// }
//高级设置
for (let k in script.setting.advanced) {
if ($('#hld_cps_adv_' + k).length > 0) {
const valueType = typeof script.setting.advanced[k]
if (valueType == 'boolean') {
$('#hld_cps_adv_' + k)[0].checked = script.setting.advanced[k]
}
if (valueType == 'number' || valueType == 'string') {
$('#hld_cps_adv_' + k).val(script.setting.advanced[k])
}
}
}
// /**
// * Bind:Click
// * 设置面板-展开切换高级设置
// */
// $('body').on('click', '#hld_cps_advanced_button', function () {
// if ($('.hld_cps_advanced-setting-panel').is(':hidden')) {
// $('.hld_cps_advanced-setting-panel').css('display', 'flex')
// $(this).text('-')
// } else {
// $('.hld_cps_advanced-setting-panel').css('display', 'none')
// $(this).text('+')
// }
// })
/**
* Bind:Click
* 关闭设置面板
*/
$('body').on('click', '.hld_cps_setting-close', function () {
if ($(this).attr('close-type') == 'hide') {
$(this).parent().hide()
$(this).parent().parent().hide()
} else {
$(this).parent().remove()
}
})
/**
* Bind:Click
* 保存配置
*/
$('body').on('click', '#hld_cps_save__data', () => {
script.saveSetting()
$('#hld_cps_setting_cover').fadeOut(200)
})
},
// renderAlwaysFunc() {
// if($('.hld_cps_setting-box').length == 0) {
// $('#startmenu > tbody > tr > td.last').append('<div><div class="item hld_cps_setting-box"></div></div>')
// let $entry = $('<a id="hld_cps_setting" title="打开NGA优化摸鱼插件设置面板">NGA优化摸鱼插件设置</a>')
// $entry.click(()=>{
// $('#hld_cps_setting_cover').css('display', 'block')
// $('html, body').animate({scrollTop: 0}, 500)
// })
// $('#hld_cps_setting_close').click(()=>$('#hld_cps_setting_cover').fadeOut(200))
// $('.hld_cps_setting-box').append($entry)
// }
// },
addButton(button) {
const $button = $(`<button class="hld_cps_btn" id="${button.id}" title="${button.desc}">${button.title}</button>`)
if (typeof button.click == 'function') {
$button.on('click', function() {
button.click($(this))
})
}
$('#hld_setting_panel_buttons').append($button)
},
style: `
.animated {animation-duration:.3s;animation-fill-mode:both;}
.animated-1s {animation-duration:1s;animation-fill-mode:both;}
.zoomIn {animation-name:zoomIn;}
.bounce {-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom;}
.fadeInUp {-webkit-animation-name:fadeInUp;animation-name:fadeInUp;}
#loader {display:none;position:absolute;top:50%;left:50%;margin-top:-10px;margin-left:-10px;width:20px;height:20px;border:6px dotted #FFF;border-radius:50%;-webkit-animation:1s loader linear infinite;animation:1s loader linear infinite;}
@keyframes loader {0% {-webkit-transform:rotate(0deg);transform:rotate(0deg);}100% {-webkit-transform:rotate(360deg);transform:rotate(360deg);}}
@keyframes zoomIn {from {opacity:0;-webkit-transform:scale3d(0.3,0.3,0.3);transform:scale3d(0.3,0.3,0.3);}50% {opacity:1;}}
@keyframes bounce {from,20%,53%,80%,to {-webkit-animation-timing-function:cubic-bezier(0.215,0.61,0.355,1);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%,43% {-webkit-animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}
@keyframes fadeInUp {from {opacity:0;-webkit-transform:translate3d(-50%,100%,0);transform:translate3d(-50%,100%,0);}to {opacity:1;-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0);}}
.hld_cps_msg{display:none;position:fixed;top:10px;left:50%;transform:translateX(-50%);color:#fff;text-align:center;z-index:99996;padding:10px 30px 10px 45px;font-size:16px;border-radius:10px;background-image:url("${SVG_ICON_MSG}");background-size:25px;background-repeat:no-repeat;background-position:15px}
.hld_cps_msg a{color:#fff;text-decoration: underline;}
.hld_cps_msg-ok{background:#4bcc4b}
.hld_cps_msg-err{background:#c33}
.hld_cps_msg-warn{background:#FF9900}
.hld_cps_flex{display:flex;}
.hld_cps_float-left{float: left;}
.clearfix {clear: both;}
#hld_cps_noti_container {position:fixed;top:10px;left:10px;z-index:99;}
.hld_cps_noti-msg {display:none;padding:10px 20px;font-size:14px;font-weight:bold;color:#fff;margin-bottom:10px;background:rgba(0,0,0,0.6);border-radius:10px;cursor:pointer;}
.hld_cps_btn-groups {display:flex;justify-content:center !important;margin-top:10px;}
button.hld_cps_btn {padding:3px 8px;border:1px solid #591804;background:#fff8e7;color:#591804;}
button.hld_cps_btn:hover {background:#591804;color:#fff0cd;}
button.hld_cps_btn[disabled] {opacity:.5;}
#hld_cps_updated {position:fixed;top:20px;right:20px;width:230px;padding:10px;border-radius:5px;box-shadow:0 0 15px #666;border:1px solid #591804;background:#fff8e7;z-index: 9999;}
#hld_cps_updated .hld_cps_readme {text-decoration:underline;color:#591804;}
.hld_cps_script-info {margin-left:4px;font-size:70%;color:#666;}
#hld_cps_setting {color:#6666CC;cursor:pointer;}
#hld_cps_setting_cover {display:none;padding-top: 70px;position:absolute;top:0;left:0;right:0;bottom:0;z-index:999;}
#hld_cps_setting_panel {position:relative;background:#fff8e7;width:600px;left: 50%;transform: translateX(-50%);padding:15px 20px;border-radius:10px;box-shadow:0 0 10px #666;border:1px solid #591804;}
#hld_cps_setting_panel > div.hld_cps_field {float:left;width:50%;}
#hld_cps_setting_panel p {margin-bottom:10px;}
#hld_cps_setting_panel .hld_cps_sp-title {font-size:15px;font-weight:bold;text-align:center;}
#hld_cps_setting_panel .hld_cps_sp-section {font-weight:bold;margin-top:20px;}
.hld_cps_setting-close {position:absolute;top:5px;right:5px;padding:3px 6px;background:#fff0cd;color:#591804;transition:all .2s ease;cursor:pointer;border-radius:4px;text-decoration:none;z-index:9999;}
.hld_cps_setting-close:hover {background:#591804;color:#fff0cd;text-decoration:none;}
#hld_cps_setting_panel button {transition:all .2s ease;cursor:pointer;}
.hld_cps_advanced-setting {border-top: 1px solid #e0c19e;border-bottom: 1px solid #e0c19e;padding: 3px 0;margin-top:25px;}
.hld_cps_advanced-setting >span {font-weight:bold}
.hld_cps_advanced-setting >button {padding: 0px;margin-right:5px;width: 18px;text-align: center;}
.hld_cps_advanced-setting-panel {padding:5px 0;flex-wrap: wrap;}
.hld_cps_advanced-setting-panel>p {width:100%;}
.hld_cps_advanced-setting-panel>table {width:50%;}
.hld_cps_advanced-setting-panel>p {margin: 7px 0 !important;font-weight:bold;}
.hld_cps_advanced-setting-panel>p svg {height:16px;width:16px;vertical-align: top;margin-right:3px;}
.hld_cps_advanced-setting-panel>table td {padding-right:10px}
.hld_cps_advanced-setting-panel input[type=text],.hld_cps_advanced-setting-panel input[type=number] {width:80px}
.hld_cps_advanced-setting-panel input[type=number] {border: 1px solid #e6c3a8;box-shadow: 0 0 2px 0 #7c766d inset;border-radius: 0.25em;}
.hld_cps_help {cursor:help;text-decoration: underline;}
.hld_cps_buttons {clear:both;display:flex;justify-content:space-between;padding-top:15px;}
button.hld_cps_btn {padding:3px 8px;border:1px solid #591804;background:#fff8e7;color:#591804;}
button.hld_cps_btn:hover {background:#591804;color:#fff0cd;}
.hld_cps_sp-fold {padding-left:23px;}
.hld_cps_sp-fold .hld_cps_f-title {font-weight:bold;}
.hld_cps_help-tips {position: absolute;padding: 5px 10px;background: rgba(0,0,0,.8);color: #FFF;border-radius: 5px;z-index: 9999;}
`
}
/**
* 初始化脚本
*/
const script = new NGABBSScript_CheckPostStatus()
/**
* 添加模块
*/
script.addModule(SettingPanel)
script.addModule(CheckPostStatus)
/**
* 注册(不可用)菜单按钮
*/
try {
// 设置面板
GM_registerMenuCommand('设置面板', function () {
$('#hld_cps_setting_cover').css('display', 'block').css('position', 'fixed')
$('#hld_cps_setting_panel').css('display', 'block')
// $('html, body').animate({scrollTop: 0}, 500)
})
// 修复脚本
GM_registerMenuCommand('修复脚本', function () {
if (window.confirm('如脚本运行失败或无效,尝试修复脚本,这会清除脚本的所有数据\n* 数据包含配置,各种名单等\n* 此操作不可逆转,请谨慎操作\n\n继续请点击【确定】')) {
try {
GM_listValues().forEach(key => GM_deleteValue(key))
} catch {}
// 兼容性代码: 计划将在5.0之后废弃
window.localStorage.clear()
alert('操作成功,请刷新页面重试')
}
})
} catch {
// 不支持此命令
console.warn(`警告: 此脚本管理器不支持菜单按钮,可能会导致新特性无法正常使用,建议更改脚本管理器为
Tampermonkey[https://www.tampermonkey.net/] 或 Violentmonkey[https://violentmonkey.github.io/]`)
}
/**
* 运行脚本
*/
script.run()
})();