// ==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(); })();