Initial commit

This commit is contained in:
Longtail committed 2026-10-31 14:23:39 +08:00
commit acc075f14b
1 file changed
+133
+133
View File
@@ -0,0 +1,133 @@
// ==UserScript==
// @name 广谈大湾网 · 今日发帖数
// @namespace local.dww.today-threads
// @version 1.0.0
// @description 在论坛主题管理的主题总数右侧显示北京时间当日新增主题数(不含回复)。
// @match https://www.dww11.com/admin.php*
// @match https://dww11.com/admin.php*
// @run-at document-idle
// @grant none
// ==/UserScript==
(() => {
'use strict';
// 不要添加 @noframes:管理列表就在后台的 main iframe 内。
const params = new URLSearchParams(location.search);
if (params.get('action') !== 'threads' || params.get('operation')) return;
const header = [...document.querySelectorAll('#cpform th.partition')]
.find(el => /^\s*(?:最新主题数|符合条件的主题数)\s*\d+/.test(el.textContent));
const form = document.querySelector('#threadforum');
if (!header || !form || document.getElementById('dww-today-threads')) return;
const badge = document.createElement('span');
badge.id = 'dww-today-threads';
badge.style.cssText = 'display:inline-block;margin-left:20px;color:#008640;white-space:nowrap;';
const label = document.createElement('span');
label.setAttribute('role', 'status');
label.setAttribute('aria-live', 'polite');
label.textContent = '今日发帖数:统计中…';
const refresh = document.createElement('button');
refresh.type = 'button';
refresh.textContent = '刷新';
refresh.style.cssText = 'margin-left:8px;padding:0;border:0;background:none;color:#2369a0;cursor:pointer;font:inherit;font-weight:normal;';
badge.append(label, refresh);
header.append(badge);
function dayRange(now = new Date()) {
// 用 UTC 计算固定东八区日期,避免电脑时区及夏令时影响。
const offset = 8 * 60 * 60 * 1000;
const localDay = new Date(now.getTime() + offset).toISOString().slice(0, 10);
const previousDay = new Date(Date.parse(localDay + 'T00:00:00Z') - 86400000)
.toISOString().slice(0, 10);
// Discuz 原生查询使用 dateline > starttime 且 <= endtime。
// 从前一天最后一秒之后开始,完整涵盖今日 00:00:00 至 23:59:59。
return [localDay, `${previousDay} 23:59:59`, `${localDay} 23:59:59`];
}
async function queryCount(start, end) {
const hash = form.querySelector('input[name="formhash"]')?.value;
if (!hash) throw new Error('未找到后台查询凭据,请重新登录后刷新');
// 只提交查询字段,不读取/提交 #cpform 的批量修改、删除字段。
// 不继承当前筛选条件,今日数始终表示全部可查询版块的新增主题。
const body = new URLSearchParams({
formhash: hash, searchsubmit: '1', detail: '1',
page: '1', pp: '', perpage: '20',
inforum: 'all', intype: '', insort: 'all',
starttime: start, endtime: end,
users: '', keywords: '', viewsmore: '', viewsless: '',
repliesmore: '', repliesless: '', readpermmore: '',
pricemore: '', noreplydays: '', savethread: '0', hidethread: '0'
});
const endpoint = new URL('admin.php?action=threads', location.href);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 25000);
try {
const response = await fetch(endpoint.href, {
method: 'POST', credentials: 'same-origin', cache: 'no-store',
body, signal: controller.signal
});
if (!response.ok) throw new Error(`后台返回 HTTP ${response.status}`);
const buffer = await response.arrayBuffer();
const charset = response.headers.get('content-type')?.match(/charset=([^;\s]+)/i)?.[1]
|| document.characterSet || 'utf-8';
const html = new TextDecoder(charset).decode(buffer);
const result = new DOMParser().parseFromString(html, 'text/html');
const summary = [...result.querySelectorAll('#cpform th.partition')]
.map(el => el.textContent.match(/^\s*符合条件的主题数\s*([\d,]+)/))
.find(Boolean);
if (summary) {
// 必须是按指定日期查询的结果,避免把默认最新主题数误报成今日数。
const returnedStart = result.querySelector('#threadforum [name="starttime"]')?.value;
const returnedEnd = result.querySelector('#threadforum [name="endtime"]')?.value;
if (returnedStart !== start || returnedEnd !== end) {
throw new Error('后台未返回指定日期条件,无法确认统计结果');
}
return Number(summary[1].replaceAll(',', ''));
}
throw new Error('未读取到统计结果,可能登录已过期或后台查询异常');
} finally {
clearTimeout(timer);
}
}
let busy = false;
let lastDay = '';
async function update() {
if (busy) return;
busy = true;
refresh.disabled = true;
const [day, start, end] = dayRange();
label.textContent = '今日发帖数:统计中…';
badge.style.color = '#008640';
try {
const count = await queryCount(start, end);
if (dayRange()[0] !== day) {
// 请求恰好跨过午夜,不把昨日结果标为今日。
label.textContent = '今日发帖数:请刷新';
lastDay = '';
return;
}
lastDay = day;
label.textContent = `今日发帖数:${count}`;
badge.title = `北京时间 ${day} 新增主题,不含回复;以后台正常主题查询口径为准。更新于 ${new Date().toLocaleTimeString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false })}`;
} catch (error) {
label.textContent = '今日发帖数:读取失败';
badge.style.color = '#c0392b';
badge.title = error.name === 'AbortError' ? '查询超时,请点击刷新重试' : error.message;
} finally {
busy = false;
refresh.disabled = false;
}
}
refresh.addEventListener('click', update);
// 每分钟一条后台只读查询;后台标签页暂停轮询。
setInterval(() => { if (!document.hidden) update(); }, 60000);
document.addEventListener('visibilitychange', () => {
if (!document.hidden && lastDay !== dayRange()[0]) update();
});
update();
})();