From c01cc11b76fe6f0bb75ec196011734783b57393a Mon Sep 17 00:00:00 2001
From: Michael Kane <15847202+jhhd88@users.noreply.github.com>
Date: Thu, 10 Apr 2025 13:41:19 +0800
Subject: [PATCH 1/3] Update LDStatus.user.js
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. DOM 操作优化:
- 使用 DocumentFragment 进行批量 DOM 操作,减少重绘
- 用直接创建元素替代 innerHTML,提高安全性和性能
- 添加了 clearContent 函数清空内容区域
2. 内存和存储优化:
- 为每日统计数据实施存储限制(MAX_ENTRIES_PER_STAT = 50)
- 清理超过24小时的旧数据
3. 性能优化:
- 添加拖动操作节流函数(throttle)
- 使用 requestAnimationFrame 实现更流畅的拖动动画
4. 数据处理优化:
- 使用更高效的选择器查找信任级别section
- 将大函数拆分成专注的小函数:findTrustLevelSection, extractUserInfo, checkRequirementStatus, extractRequirements 等
6. 代码结构改进:
- 重构了 parseTrustLevelData 函数,拆分为多个专注的小函数
- 提高了代码的可维护性和可读性
7. 错误处理增强:
- 添加了完整的错误处理流程
- 为 GM_xmlhttpRequest 添加了超时处理
- 添加了错误信息显示函数
8. 资源使用优化:
- 实现了闲置检测功能,页面不可见时停止自动刷新
- 使用 setupRefreshInterval 管理刷新间隔
---
LDStatus.user.js | 553 +++++++++++++++++++++++++++++++----------------
1 file changed, 364 insertions(+), 189 deletions(-)
diff --git a/LDStatus.user.js b/LDStatus.user.js
index 3ba9164..a6b11f4 100644
--- a/LDStatus.user.js
+++ b/LDStatus.user.js
@@ -1,7 +1,7 @@
// ==UserScript==
// @name LDStatus
// @namespace http://tampermonkey.net/
-// @version 1.7
+// @version 1.8
// @description 在 Linux.do 页面显示信任级别进度
// @author 1e0n
// @match https://linux.do/*
@@ -210,6 +210,7 @@
// 定义存储键
const STORAGE_KEY_POSITION = 'ld_panel_position';
const STORAGE_KEY_COLLAPSED = 'ld_panel_collapsed';
+ const STORAGE_KEY_LAST_UPDATE_CHECK = 'ld_last_update_check';
// 创建面板
const panel = document.createElement('div');
@@ -221,26 +222,73 @@
// 创建面板头部
const header = document.createElement('div');
header.id = 'ld-trust-level-header';
- header.innerHTML = `
-
- `;
+
+ const headerContent = document.createElement('div');
+ headerContent.className = 'ld-header-content';
+
+ const statusSpan = document.createElement('span');
+ statusSpan.textContent = 'Status';
+ headerContent.appendChild(statusSpan);
+
+ const versionSpan = document.createElement('span');
+ versionSpan.className = 'ld-version';
+ versionSpan.textContent = `v${scriptVersion}`;
+ headerContent.appendChild(versionSpan);
+
+ const updateBtn = document.createElement('button');
+ updateBtn.className = 'ld-update-btn';
+ updateBtn.title = '检查更新';
+ updateBtn.textContent = '🔎';
+ headerContent.appendChild(updateBtn);
+
+ const refreshBtn = document.createElement('button');
+ refreshBtn.className = 'ld-refresh-btn';
+ refreshBtn.title = '刷新数据';
+ refreshBtn.textContent = '🔄';
+ headerContent.appendChild(refreshBtn);
+
+ const toggleBtn = document.createElement('button');
+ toggleBtn.className = 'ld-toggle-btn';
+ toggleBtn.title = '展开/收起';
+ toggleBtn.textContent = '◀';
+ headerContent.appendChild(toggleBtn);
+
+ header.appendChild(headerContent);
// 创建内容区域
const content = document.createElement('div');
content.id = 'ld-trust-level-content';
- content.innerHTML = '加载中...
';
-
+
// 组装面板
panel.appendChild(header);
panel.appendChild(content);
document.body.appendChild(panel);
+ // 显示加载信息的函数
+ function showLoading() {
+ clearContent();
+ const loadingDiv = document.createElement('div');
+ loadingDiv.className = 'ld-loading';
+ loadingDiv.textContent = '加载中...';
+ content.appendChild(loadingDiv);
+ }
+
+ // 显示错误信息的函数
+ function showErrorMessage(message) {
+ clearContent();
+ const errorDiv = document.createElement('div');
+ errorDiv.className = 'ld-loading';
+ errorDiv.textContent = message;
+ content.appendChild(errorDiv);
+ }
+
+ // 清空内容区域的函数
+ function clearContent() {
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+ }
+
// 保存窗口位置的函数
function savePanelPosition() {
const transform = window.getComputedStyle(panel).transform;
@@ -274,6 +322,20 @@
}
}
+ // 实现节流函数
+ function throttle(func, limit) {
+ let inThrottle;
+ return function() {
+ const args = arguments;
+ const context = this;
+ if (!inThrottle) {
+ func.apply(context, args);
+ inThrottle = true;
+ setTimeout(() => inThrottle = false, limit);
+ }
+ };
+ }
+
// 拖动功能
let isDragging = false;
let lastX, lastY;
@@ -290,24 +352,22 @@
document.body.style.userSelect = 'none';
});
- document.addEventListener('mousemove', (e) => {
+ document.addEventListener('mousemove', throttle((e) => {
if (!isDragging) return;
- // 使用 transform 而不是改变 left/top 属性,性能更好
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
-
- const currentTransform = window.getComputedStyle(panel).transform;
- const matrix = new DOMMatrix(currentTransform === 'none' ? '' : currentTransform);
-
- const newX = matrix.e + dx;
- const newY = matrix.f + dy;
-
- panel.style.transform = `translate(${newX}px, ${newY}px)`;
-
lastX = e.clientX;
lastY = e.clientY;
- });
+
+ requestAnimationFrame(() => {
+ const currentTransform = window.getComputedStyle(panel).transform;
+ const matrix = new DOMMatrix(currentTransform === 'none' ? '' : currentTransform);
+ const newX = matrix.e + dx;
+ const newY = matrix.f + dy;
+ panel.style.transform = `translate(${newX}px, ${newY}px)`;
+ });
+ }, 16));
document.addEventListener('mouseup', () => {
if (!isDragging) return;
@@ -321,7 +381,6 @@
});
// 展开/收起功能
- const toggleBtn = header.querySelector('.ld-toggle-btn');
toggleBtn.addEventListener('click', () => {
panel.classList.toggle('ld-collapsed');
toggleBtn.textContent = panel.classList.contains('ld-collapsed') ? '▶' : '◀';
@@ -331,15 +390,27 @@
});
// 刷新按钮
- const refreshBtn = header.querySelector('.ld-refresh-btn');
refreshBtn.addEventListener('click', fetchTrustLevelData);
// 检查更新按钮
- const updateBtn = header.querySelector('.ld-update-btn');
updateBtn.addEventListener('click', checkForUpdates);
// 检查脚本更新
function checkForUpdates() {
+ const lastCheck = GM_getValue(STORAGE_KEY_LAST_UPDATE_CHECK, 0);
+ const now = Date.now();
+
+ // 一天只检查一次
+ if (now - lastCheck < 86400000) {
+ updateBtn.textContent = '⏱️';
+ updateBtn.title = '今天已检查过更新';
+ setTimeout(() => {
+ updateBtn.textContent = '🔎';
+ updateBtn.title = '检查更新';
+ }, 2000);
+ return;
+ }
+
const updateURL = 'https://raw.githubusercontent.com/1e0n/LinuxDoStatus/master/LDStatus.user.js';
// 显示正在检查的状态
@@ -349,50 +420,64 @@
GM_xmlhttpRequest({
method: 'GET',
url: updateURL,
+ timeout: 10000, // 添加超时设置
onload: function(response) {
if (response.status === 200) {
- // 提取远程脚本的版本号
- const versionMatch = response.responseText.match(/@version\s+([\d\.]+)/);
- if (versionMatch && versionMatch[1]) {
- const remoteVersion = versionMatch[1];
-
- // 比较版本
- if (remoteVersion > scriptVersion) {
- // 有新版本
- updateBtn.textContent = '⚠️'; // 警告图标
- updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`;
- updateBtn.style.color = '#ffd700'; // 黄色
-
- // 点击按钮跳转到更新页面
- updateBtn.onclick = function() {
- window.open(updateURL, '_blank');
- };
+ try {
+ // 提取远程脚本的版本号
+ const versionMatch = response.responseText.match(/@version\s+([\d\.]+)/);
+ if (versionMatch && versionMatch[1]) {
+ const remoteVersion = versionMatch[1];
+
+ // 比较版本
+ if (remoteVersion > scriptVersion) {
+ // 有新版本
+ updateBtn.textContent = '⚠️'; // 警告图标
+ updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`;
+ updateBtn.style.color = '#ffd700'; // 黄色
+
+ // 点击按钮跳转到更新页面
+ updateBtn.onclick = function() {
+ window.open(updateURL, '_blank');
+ };
+ } else {
+ // 已是最新版本
+ updateBtn.textContent = '✔'; // 勾选图标
+ updateBtn.title = '已是最新版本';
+ updateBtn.style.color = '#68d391'; // 绿色
+
+ // 3秒后恢复原样式
+ setTimeout(() => {
+ updateBtn.textContent = '🔎'; // 放大镜图标
+ updateBtn.title = '检查更新';
+ updateBtn.style.color = 'white';
+ updateBtn.onclick = checkForUpdates;
+ }, 3000);
+ }
} else {
- // 已是最新版本
- updateBtn.textContent = '✔'; // 勾选图标
- updateBtn.title = '已是最新版本';
- updateBtn.style.color = '#68d391'; // 绿色
-
- // 3秒后恢复原样式
- setTimeout(() => {
- updateBtn.textContent = '🔎'; // 放大镜图标
- updateBtn.title = '检查更新';
- updateBtn.style.color = 'white';
- updateBtn.onclick = checkForUpdates;
- }, 3000);
+ handleUpdateError('无法解析版本信息');
}
- } else {
- handleUpdateError();
+ } catch (error) {
+ handleUpdateError('处理更新信息时出错: ' + error.message);
}
} else {
- handleUpdateError();
+ handleUpdateError(`请求失败 (${response.status})`);
}
+
+ // 更新检查时间
+ GM_setValue(STORAGE_KEY_LAST_UPDATE_CHECK, now);
},
- onerror: handleUpdateError
+ onerror: function(error) {
+ handleUpdateError('网络请求失败');
+ },
+ ontimeout: function() {
+ handleUpdateError('请求超时');
+ }
});
// 处理更新检查错误
- function handleUpdateError() {
+ function handleUpdateError(message) {
+ console.error('检查更新失败:', message);
updateBtn.textContent = '❌'; // 错误图标
updateBtn.title = '检查更新失败,请稍后再试';
updateBtn.style.color = '#fc8181'; // 红色
@@ -408,48 +493,60 @@
// 获取信任级别数据
function fetchTrustLevelData() {
- content.innerHTML = '加载中...
';
+ showLoading();
GM_xmlhttpRequest({
method: 'GET',
url: 'https://connect.linux.do',
+ timeout: 10000, // 添加超时设置
onload: function(response) {
if (response.status === 200) {
- parseTrustLevelData(response.responseText);
+ try {
+ parseTrustLevelData(response.responseText);
+ } catch (error) {
+ console.error('解析数据时出错:', error);
+ showErrorMessage('解析数据时出错: ' + error.message);
+ }
} else {
- content.innerHTML = '获取数据失败,请稍后再试
';
+ showErrorMessage(`获取数据失败 (${response.status})`);
}
},
- onerror: function() {
- content.innerHTML = '获取数据失败,请稍后再试
';
+ onerror: function(error) {
+ console.error('请求错误:', error);
+ showErrorMessage('网络请求失败');
+ },
+ ontimeout: function() {
+ showErrorMessage('请求超时');
}
});
}
- // 解析信任级别数据
- function parseTrustLevelData(html) {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
-
- // 查找信任级别区块
- const trustLevelSection = Array.from(doc.querySelectorAll('.bg-white.p-6.rounded-lg')).find(div => {
- const heading = div.querySelector('h2');
- return heading && heading.textContent.includes('信任级别');
- });
-
- if (!trustLevelSection) {
- content.innerHTML = '未找到信任级别数据,请确保已登录
';
- return;
- }
+ // 查找信任级别区块
+ function findTrustLevelSection(doc) {
+ const headers = doc.querySelectorAll('h2');
+ const trustHeader = Array.from(headers).find(h => h.textContent.includes('信任级别'));
+ return trustHeader ? trustHeader.closest('.bg-white.p-6.rounded-lg') : null;
+ }
- // 获取用户名和当前级别
- const heading = trustLevelSection.querySelector('h2').textContent.trim();
+ // 提取用户信息
+ function extractUserInfo(section) {
+ const heading = section.querySelector('h2').textContent.trim();
const match = heading.match(/(.*) - 信任级别 (\d+) 的要求/);
- const username = match ? match[1] : '未知用户';
- const targetLevel = match ? match[2] : '未知';
+ return {
+ username: match ? match[1] : '未知用户',
+ targetLevel: match ? match[2] : '未知'
+ };
+ }
- // 获取表格数据
- const tableRows = trustLevelSection.querySelectorAll('table tr');
+ // 检查需求状态
+ function checkRequirementStatus(section) {
+ const resultText = section.querySelector('p.text-red-500, p.text-green-500');
+ return resultText ? !resultText.classList.contains('text-red-500') : false;
+ }
+
+ // 提取需求数据
+ function extractRequirements(section, previousRequirements) {
+ const tableRows = section.querySelectorAll('table tr');
const requirements = [];
for (let i = 1; i < tableRows.length; i++) { // 跳过表头
@@ -497,9 +594,30 @@
}
}
+ return requirements;
+ }
+
+ // 解析信任级别数据
+ function parseTrustLevelData(html) {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+
+ // 查找信任级别区块
+ const trustLevelSection = findTrustLevelSection(doc);
+
+ if (!trustLevelSection) {
+ showErrorMessage('未找到信任级别数据,请确保已登录');
+ return;
+ }
+
+ // 获取用户名和当前级别
+ const { username, targetLevel } = extractUserInfo(trustLevelSection);
+
+ // 获取表格数据
+ const requirements = extractRequirements(trustLevelSection, previousRequirements);
+
// 获取总体结果
- const resultText = trustLevelSection.querySelector('p.text-red-500, p.text-green-500');
- const isMeetingRequirements = resultText ? !resultText.classList.contains('text-red-500') : false;
+ const isMeetingRequirements = checkRequirementStatus(trustLevelSection);
// 存储24小时内的数据变化
const dailyChanges = saveDailyStats(requirements);
@@ -513,15 +631,26 @@
// 渲染信任级别数据
function renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges = {}) {
- let html = `
-
- ${username} - 信任级别 ${targetLevel}
-
-
- ${isMeetingRequirements ? '已' : '未'}符合信任级别 ${targetLevel} 要求
-
- `;
-
+ clearContent();
+
+ const fragment = document.createDocumentFragment();
+
+ // 创建用户和级别信息
+ const headerDiv = document.createElement('div');
+ headerDiv.style.marginBottom = '8px';
+ headerDiv.style.fontWeight = 'bold';
+ headerDiv.textContent = `${username} - 信任级别 ${targetLevel}`;
+ fragment.appendChild(headerDiv);
+
+ // 创建需求状态信息
+ const statusDiv = document.createElement('div');
+ statusDiv.style.marginBottom = '10px';
+ statusDiv.style.color = isMeetingRequirements ? '#68d391' : '#fc8181';
+ statusDiv.style.fontSize = '11px';
+ statusDiv.textContent = `${isMeetingRequirements ? '已' : '未'}符合信任级别 ${targetLevel} 要求`;
+ fragment.appendChild(statusDiv);
+
+ // 创建需求列表
requirements.forEach(req => {
// 简化项目名称
let name = req.name;
@@ -543,32 +672,49 @@
if (currentMatch) current = currentMatch[1];
if (requiredMatch) required = requiredMatch[1];
-
- // 添加目标完成数变化的标识
- let changeIndicator = '';
+
+ // 创建需求项
+ const reqDiv = document.createElement('div');
+ reqDiv.className = `ld-trust-level-item ${req.isSuccess ? 'ld-success' : 'ld-fail'}`;
+
+ const nameSpan = document.createElement('span');
+ nameSpan.className = 'ld-name';
+ nameSpan.textContent = name;
+ reqDiv.appendChild(nameSpan);
+
+ const valueSpan = document.createElement('span');
+ valueSpan.className = 'ld-value';
+
+ // 添加目标完成数
+ valueSpan.textContent = `${current} / ${required}`;
+
+ // 添加变化指示器
if (req.hasChanged) {
+ const changeIndicator = document.createElement('span');
const diff = req.changeValue;
if (diff > 0) {
- changeIndicator = ` ▲${diff}`; // 增加标识,黄色
+ changeIndicator.className = 'ld-increase';
+ changeIndicator.textContent = ` ▲${diff}`;
} else if (diff < 0) {
- changeIndicator = ` ▼${Math.abs(diff)}`; // 减少标识,蓝色
+ changeIndicator.className = 'ld-decrease';
+ changeIndicator.textContent = ` ▼${Math.abs(diff)}`;
}
+ valueSpan.appendChild(changeIndicator);
}
-
- html += `
-
- ${name}
- ${current}${changeIndicator} / ${required}
-
- `;
+
+ reqDiv.appendChild(valueSpan);
+ fragment.appendChild(reqDiv);
});
-
- // 添加24小时内的活动数据显示
- html += `
-
-
24小时内的活动
- `;
-
+
+ // 创建24小时活动数据
+ const dailyStatsDiv = document.createElement('div');
+ dailyStatsDiv.className = 'ld-daily-stats';
+
+ const dailyStatsTitleDiv = document.createElement('div');
+ dailyStatsTitleDiv.className = 'ld-daily-stats-title';
+ dailyStatsTitleDiv.textContent = '24小时内的活动';
+ dailyStatsDiv.appendChild(dailyStatsTitleDiv);
+
// 添加每个数据项
const dailyStatsItems = [
{ name: '浏览话题', key: '浏览的话题(所有时间)' },
@@ -580,17 +726,25 @@
dailyStatsItems.forEach(item => {
const value = dailyChanges[item.key] || 0;
- html += `
-
- ${item.name}
- ${value}
-
- `;
+
+ const statsItemDiv = document.createElement('div');
+ statsItemDiv.className = 'ld-daily-stats-item';
+
+ const nameSpan = document.createElement('span');
+ nameSpan.className = 'ld-name';
+ nameSpan.textContent = item.name;
+ statsItemDiv.appendChild(nameSpan);
+
+ const valueSpan = document.createElement('span');
+ valueSpan.className = 'ld-value';
+ valueSpan.textContent = value;
+ statsItemDiv.appendChild(valueSpan);
+
+ dailyStatsDiv.appendChild(statsItemDiv);
});
-
- html += `
`;
-
- content.innerHTML = html;
+
+ fragment.appendChild(dailyStatsDiv);
+ content.appendChild(fragment);
}
// 存储上一次获取的数据,用于比较变化
@@ -603,85 +757,106 @@
'浏览的话题(所有时间)', // 浏览话题总数
'回复的话题', // 回复话题数
'已读帖子(所有时间)', // 已读帖子总数
- '获赞:点赞用户数量', // 获赞数
- '点赞的帖子' // 点赞数
+ '获赞:点赞用户数量', // 获得点赞
+ '点赞的帖子' // 点赞帖子
];
- // 获取当前时间
- const now = new Date().getTime();
+ // 从存储中获取之前的记录
+ let dailyStats = GM_getValue('ld_daily_stats', []);
+
+ // 获取当前时间戳
+ const now = Date.now();
- // 从 localStorage 中获取已存储的数据
- let dailyStats = JSON.parse(localStorage.getItem('ld_daily_stats') || '[]');
+ // 清理超过24小时的旧数据
+ dailyStats = dailyStats.filter(stat => now - stat.timestamp < 24 * 60 * 60 * 1000);
- // 删除超过24小时的数据
- const oneDayAgo = now - 24 * 60 * 60 * 1000;
- dailyStats = dailyStats.filter(item => item.timestamp > oneDayAgo);
+ // 提取要跟踪的数据项
+ const trackedStats = requirements.filter(req => statsToTrack.includes(req.name));
- // 对于每个要跟踪的数据项,找到当前值并添加到历史记录中
+ // 为每个要跟踪的项目添加新记录
+ trackedStats.forEach(stat => {
+ dailyStats.push({
+ name: stat.name,
+ value: stat.currentValue,
+ timestamp: now
+ });
+ });
+
+ // 限制每种统计类型的条目数,防止过度存储
+ const MAX_ENTRIES_PER_STAT = 50;
statsToTrack.forEach(statName => {
- const req = requirements.find(r => r.name === statName);
- if (req) {
- // 添加新的数据点
- dailyStats.push({
- name: statName,
- value: req.currentValue,
- timestamp: now
- });
+ const statEntries = dailyStats.filter(item => item.name === statName);
+ if (statEntries.length > MAX_ENTRIES_PER_STAT) {
+ // 只保留最新的 MAX_ENTRIES_PER_STAT 条记录
+ const sortedEntries = statEntries.sort((a, b) => b.timestamp - a.timestamp);
+ const toKeep = sortedEntries.slice(0, MAX_ENTRIES_PER_STAT);
+ // 移除多余条目
+ dailyStats = dailyStats.filter(item => item.name !== statName || toKeep.includes(item));
}
});
- // 将更新后的数据保存回 localStorage
- localStorage.setItem('ld_daily_stats', JSON.stringify(dailyStats));
-
- return calculateDailyChanges(dailyStats);
- }
-
- // 计箞24小时内的变化量
- function calculateDailyChanges(dailyStats) {
- // 定义要跟踪的数据项
- const statsToTrack = [
- '浏览的话题(所有时间)', // 浏览话题总数
- '回复的话题', // 回复话题数
- '已读帖子(所有时间)', // 已读帖子总数
- '获赞:点赞用户数量', // 获赞数
- '点赞的帖子' // 点赞数
- ];
-
- const result = {};
+ // 保存更新后的数据
+ GM_setValue('ld_daily_stats', dailyStats);
- // 对于每个要跟踪的数据项,计算24小时内的变化
+ // 计算24小时内每项的变化量
+ let changes = {};
statsToTrack.forEach(statName => {
- // 过滤出当前数据项的所有记录,并按时间戳排序
- const statRecords = dailyStats
- .filter(item => item.name === statName)
- .sort((a, b) => a.timestamp - b.timestamp);
-
- if (statRecords.length >= 2) {
- // 获取最早和最新的记录
- const oldest = statRecords[0];
- const newest = statRecords[statRecords.length - 1];
-
- // 计算变化量
- const change = newest.value - oldest.value;
-
- // 存储结果
- result[statName] = change;
- } else {
- // 如果没有足够的数据点,设置为0
- result[statName] = 0;
+ const stats = dailyStats.filter(stat => stat.name === statName);
+ if (stats.length >= 2) {
+ // 排序数据,最新的在前面
+ stats.sort((a, b) => b.timestamp - a.timestamp);
+
+ // 获取最新的值
+ const latestValue = stats[0].value;
+
+ // 获取最老的,但不超过24小时的值
+ const oldestStats = stats.filter(stat => now - stat.timestamp < 24 * 60 * 60 * 1000);
+ if (oldestStats.length > 0) {
+ oldestStats.sort((a, b) => a.timestamp - b.timestamp);
+ const oldestValue = oldestStats[0].value;
+
+ // 计算变化
+ changes[statName] = latestValue - oldestValue;
+ }
}
});
- return result;
+ return changes;
}
- // 初始加载
- fetchTrustLevelData();
+ // 实现闲置检测,避免页面不活跃时进行不必要的刷新
+ let refreshInterval;
+ let visibilityState = true;
- // 恢复窗口状态
- // 在所有DOM操作完成后执行,确保 toggleBtn 已经定义
- setTimeout(restorePanelState, 100);
+ function setupRefreshInterval() {
+ clearInterval(refreshInterval);
+ if (visibilityState) {
+ refreshInterval = setInterval(fetchTrustLevelData, 120000); // 2分钟刷新一次
+ }
+ }
- // 定时刷新(每两分钟)
- setInterval(fetchTrustLevelData, 120000);
+ // 监听可见性变化
+ document.addEventListener('visibilitychange', () => {
+ visibilityState = document.visibilityState === 'visible';
+ setupRefreshInterval();
+ });
+
+ // 初始化
+ function initialize() {
+ // 恢复面板状态
+ restorePanelState();
+
+ // 首次获取数据
+ fetchTrustLevelData();
+
+ // 设置刷新间隔
+ setupRefreshInterval();
+ }
+
+ // 页面加载完成后初始化
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initialize);
+ } else {
+ initialize();
+ }
})();
From 3bcb6aa9ba34441e359a7c9488efc7d232664203 Mon Sep 17 00:00:00 2001
From: Michael Kane <15847202+jhhd88@users.noreply.github.com>
Date: Wed, 16 Apr 2025 12:37:33 +0800
Subject: [PATCH 2/3] Update LDStatus.user.js
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
模块化结构:将代码组织为清晰的模块,包括配置、UI、数据处理、事件处理和存储管理,提高了代码的可维护性和可读性。
性能优化:
使用 transform 而非直接修改 left/top 属性进行拖拽,减少重排
合理缓存数据,减少不必要的网络请求
清理过时的历史数据,避免存储溢出
用户体验增强:
添加进度条直观显示完成情况
添加深色/浊色主题切换功能
数据变化高亮显示
改进数据加载和错误处理流程
添加24小时和48小时统计对比,以及趋势指示
可靠性提升:
添加错误处理和离线模式支持
数据请求增加超时处理
自动检查脚本更新功能
代码质量提升:
使用常量配置而非硬编码值
添加详细注释
统一代码风格和命名规范
---
LDStatus.user.js | 1846 ++++++++++++++++++++++++++--------------------
1 file changed, 1059 insertions(+), 787 deletions(-)
diff --git a/LDStatus.user.js b/LDStatus.user.js
index a6b11f4..89ab78c 100644
--- a/LDStatus.user.js
+++ b/LDStatus.user.js
@@ -1,7 +1,7 @@
// ==UserScript==
// @name LDStatus
// @namespace http://tampermonkey.net/
-// @version 1.8
+// @version 2.0
// @description 在 Linux.do 页面显示信任级别进度
// @author 1e0n
// @match https://linux.do/*
@@ -19,844 +19,1116 @@
(function() {
'use strict';
- // 创建样式 - 使用更特定的选择器以避免影响帖子界面的按钮
- const style = document.createElement('style');
- style.textContent = `
- #ld-trust-level-panel {
- position: fixed;
- left: 10px;
- top: 100px;
- width: 210px;
- background-color: #2d3748;
- border-radius: 8px;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
- z-index: 9999;
- font-family: Arial, sans-serif;
- transition: all 0.3s ease;
- overflow: hidden;
- color: #e2e8f0;
- font-size: 12px;
- }
+ // 模块化结构
+ const LDStatus = {
+ // 配置
+ config: {
+ refreshInterval: 300000, // 5分钟刷新一次
+ storageKeys: {
+ position: 'ld_panel_position',
+ collapsed: 'ld_panel_collapsed',
+ theme: 'ld_panel_theme',
+ lastData: 'ld_last_successful_data'
+ },
+ maxStorageItems: 500,
+ statsToTrack: [
+ '浏览的话题(所有时间)',
+ '回复的话题',
+ '已读帖子(所有时间)',
+ '获赞:点赞用户数量',
+ '点赞'
+ ],
+ nameMapping: {
+ '已读帖子(所有时间)': '已读帖子(总)',
+ '浏览的话题(所有时间)': '浏览话题(总)',
+ '获赞:点赞用户数量': '点赞用户数',
+ '获赞:单日最高数量': '单日最高获赞',
+ '被禁言(过去 6 个月)': '被禁言',
+ '被封禁(过去 6 个月)': '被封禁'
+ }
+ },
+
+ // 变量
+ vars: {
+ panel: null,
+ header: null,
+ content: null,
+ toggleBtn: null,
+ refreshBtn: null,
+ updateBtn: null,
+ themeBtn: null,
+ isDragging: false,
+ lastX: 0,
+ lastY: 0,
+ refreshTimer: null,
+ previousRequirements: [],
+ isDarkTheme: true
+ },
+
+ // UI相关方法
+ ui: {
+ // 创建CSS样式
+ createStyles: function() {
+ const style = document.createElement('style');
+ style.textContent = `
+ /* CSS变量 - 主题颜色 */
+ :root {
+ --ld-bg-color: #ffffff;
+ --ld-text-color: #1a202c;
+ --ld-header-bg: #3182ce;
+ --ld-header-color: #ffffff;
+ --ld-success-color: #276749;
+ --ld-fail-color: #c53030;
+ --ld-border-color: #e2e8f0;
+ --ld-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+ --ld-secondary-color: #4a5568;
+ --ld-increase-color: #d69e2e;
+ --ld-decrease-color: #2b6cb0;
+ --ld-day1-color: #276749;
+ --ld-day2-color: #2d3748;
+ }
- #ld-trust-level-header {
- background-color: #1a202c;
- color: white;
- padding: 8px 10px;
- cursor: move;
- display: flex;
- justify-content: space-between;
- align-items: center;
- user-select: none;
- }
+ .ld-dark-theme {
+ --ld-bg-color: #2d3748;
+ --ld-text-color: #e2e8f0;
+ --ld-header-bg: #1a202c;
+ --ld-header-color: #ffffff;
+ --ld-success-color: #68d391;
+ --ld-fail-color: #fc8181;
+ --ld-border-color: #4a5568;
+ --ld-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
+ --ld-secondary-color: #a0aec0;
+ --ld-increase-color: #ffd700;
+ --ld-decrease-color: #4299e1;
+ --ld-day1-color: #68d391;
+ --ld-day2-color: #cbd5e1;
+ }
- .ld-header-content {
- display: flex;
- width: 100%;
- align-items: center;
- justify-content: space-between;
- white-space: nowrap;
- }
+ /* 面板基础样式 */
+ #ld-trust-level-panel {
+ position: fixed;
+ left: 10px;
+ top: 100px;
+ width: 210px;
+ border-radius: 8px;
+ z-index: 9999;
+ font-family: Arial, sans-serif;
+ transition: all 0.3s ease;
+ overflow: hidden;
+ font-size: 12px;
+ background-color: var(--ld-bg-color);
+ color: var(--ld-text-color);
+ box-shadow: var(--ld-shadow);
+ border: 1px solid var(--ld-border-color);
+ }
- .ld-header-content > span:first-child {
- margin-right: auto;
- font-weight: bold;
- }
+ #ld-trust-level-header {
+ padding: 8px 10px;
+ cursor: move;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ user-select: none;
+ background-color: var(--ld-header-bg);
+ color: var(--ld-header-color);
+ }
- #ld-trust-level-content {
- padding: 10px;
- max-height: none;
- overflow-y: visible;
- }
+ .ld-header-content {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+ white-space: nowrap;
+ }
- .ld-trust-level-item {
- margin-bottom: 6px;
- display: flex;
- white-space: nowrap;
- width: 100%;
- justify-content: space-between;
- }
+ .ld-header-content > span:first-child {
+ margin-right: auto;
+ font-weight: bold;
+ }
- .ld-trust-level-item .ld-name {
- flex: 0 1 auto;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 60%;
- }
+ #ld-trust-level-content {
+ padding: 10px;
+ max-height: none;
+ overflow-y: visible;
+ }
- .ld-trust-level-item .ld-value {
- font-weight: bold;
- flex: 0 0 auto;
- text-align: right;
- min-width: 70px;
- }
+ .ld-trust-level-item {
+ margin-bottom: 6px;
+ display: flex;
+ white-space: nowrap;
+ width: 100%;
+ justify-content: space-between;
+ }
- .ld-trust-level-item.ld-success .ld-value {
- color: #68d391;
- }
+ .ld-trust-level-item .ld-name {
+ flex: 0 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 60%;
+ }
- .ld-trust-level-item.ld-fail .ld-value {
- color: #fc8181;
- }
+ .ld-trust-level-item .ld-value {
+ font-weight: bold;
+ flex: 0 0 auto;
+ text-align: right;
+ min-width: 70px;
+ }
- .ld-toggle-btn, .ld-refresh-btn, .ld-update-btn {
- background: none;
- border: none;
- color: white;
- cursor: pointer;
- font-size: 14px;
- margin-left: 5px;
- }
+ .ld-trust-level-item.ld-success .ld-value {
+ color: var(--ld-success-color);
+ }
- .ld-version {
- font-size: 10px;
- color: #a0aec0;
- margin-left: 5px;
- font-weight: normal;
- }
+ .ld-trust-level-item.ld-fail .ld-value {
+ color: var(--ld-fail-color);
+ }
- .ld-collapsed {
- width: 40px !important;
- height: 40px !important;
- min-width: 40px !important;
- max-width: 40px !important;
- border-radius: 8px;
- overflow: hidden;
- transform: none !important;
- }
+ .ld-toggle-btn, .ld-refresh-btn, .ld-update-btn, .ld-theme-btn {
+ background: none;
+ border: none;
+ color: var(--ld-header-color);
+ cursor: pointer;
+ font-size: 14px;
+ margin-left: 5px;
+ }
- .ld-collapsed #ld-trust-level-header {
- justify-content: center;
- width: 40px !important;
- height: 40px !important;
- min-width: 40px !important;
- max-width: 40px !important;
- padding: 0;
- display: flex;
- align-items: center;
- }
+ .ld-version {
+ font-size: 10px;
+ color: var(--ld-secondary-color);
+ margin-left: 5px;
+ font-weight: normal;
+ }
- .ld-collapsed #ld-trust-level-header > div {
- justify-content: center;
- width: 100%;
- height: 100%;
- }
+ .ld-collapsed {
+ width: 40px !important;
+ height: 40px !important;
+ min-width: 40px !important;
+ max-width: 40px !important;
+ border-radius: 8px;
+ overflow: hidden;
+ transform: none !important;
+ }
- .ld-collapsed #ld-trust-level-content {
- display: none !important;
- }
+ .ld-collapsed #ld-trust-level-header {
+ justify-content: center;
+ width: 40px !important;
+ height: 40px !important;
+ min-width: 40px !important;
+ max-width: 40px !important;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ }
- .ld-collapsed .ld-header-content > span,
- .ld-collapsed .ld-refresh-btn,
- .ld-collapsed .ld-update-btn,
- .ld-collapsed .ld-version {
- display: none !important;
- }
+ .ld-collapsed #ld-trust-level-header > div {
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ }
- .ld-collapsed .ld-toggle-btn {
- margin: 0;
- font-size: 16px;
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
- }
+ .ld-collapsed #ld-trust-level-content {
+ display: none !important;
+ }
- .ld-loading {
- text-align: center;
- padding: 10px;
- color: #a0aec0;
- }
+ .ld-collapsed .ld-header-content > span,
+ .ld-collapsed .ld-refresh-btn,
+ .ld-collapsed .ld-update-btn,
+ .ld-collapsed .ld-theme-btn,
+ .ld-collapsed .ld-version {
+ display: none !important;
+ }
- .ld-increase {
- color: #ffd700; /* 黄色 */
- }
+ .ld-collapsed .ld-toggle-btn {
+ margin: 0;
+ font-size: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ }
- .ld-decrease {
- color: #4299e1; /* 蓝色 */
- }
+ .ld-loading {
+ text-align: center;
+ padding: 10px;
+ color: var(--ld-secondary-color);
+ }
- .ld-daily-stats {
- margin-top: 15px;
- padding-top: 10px;
- border-top: 1px solid #4a5568;
- font-size: 11px;
- }
+ .ld-increase {
+ color: var(--ld-increase-color);
+ }
- .ld-daily-stats-title {
- font-weight: bold;
- margin-bottom: 5px;
- color: #a0aec0;
- }
+ .ld-decrease {
+ color: var(--ld-decrease-color);
+ }
- .ld-daily-stats-item {
- display: flex;
- justify-content: space-between;
- margin-bottom: 4px;
- }
+ /* 活动数据区域 */
+ .ld-daily-stats {
+ margin-top: 10px;
+ font-size: 11px;
+ border-top: 1px solid var(--ld-border-color);
+ padding-top: 10px;
+ }
- .ld-daily-stats-item .ld-name {
- flex: 0 1 auto;
- }
+ .ld-daily-stats-title {
+ font-weight: bold;
+ margin-bottom: 5px;
+ color: var(--ld-secondary-color);
+ }
- .ld-daily-stats-item .ld-value {
- flex: 0 0 auto;
- font-weight: bold;
- color: #68d391;
- }
- `;
- document.head.appendChild(style);
-
- // 定义存储键
- const STORAGE_KEY_POSITION = 'ld_panel_position';
- const STORAGE_KEY_COLLAPSED = 'ld_panel_collapsed';
- const STORAGE_KEY_LAST_UPDATE_CHECK = 'ld_last_update_check';
-
- // 创建面板
- const panel = document.createElement('div');
- panel.id = 'ld-trust-level-panel';
-
- // 获取脚本版本号
- const scriptVersion = GM_info.script.version;
-
- // 创建面板头部
- const header = document.createElement('div');
- header.id = 'ld-trust-level-header';
-
- const headerContent = document.createElement('div');
- headerContent.className = 'ld-header-content';
-
- const statusSpan = document.createElement('span');
- statusSpan.textContent = 'Status';
- headerContent.appendChild(statusSpan);
-
- const versionSpan = document.createElement('span');
- versionSpan.className = 'ld-version';
- versionSpan.textContent = `v${scriptVersion}`;
- headerContent.appendChild(versionSpan);
-
- const updateBtn = document.createElement('button');
- updateBtn.className = 'ld-update-btn';
- updateBtn.title = '检查更新';
- updateBtn.textContent = '🔎';
- headerContent.appendChild(updateBtn);
-
- const refreshBtn = document.createElement('button');
- refreshBtn.className = 'ld-refresh-btn';
- refreshBtn.title = '刷新数据';
- refreshBtn.textContent = '🔄';
- headerContent.appendChild(refreshBtn);
-
- const toggleBtn = document.createElement('button');
- toggleBtn.className = 'ld-toggle-btn';
- toggleBtn.title = '展开/收起';
- toggleBtn.textContent = '◀';
- headerContent.appendChild(toggleBtn);
-
- header.appendChild(headerContent);
-
- // 创建内容区域
- const content = document.createElement('div');
- content.id = 'ld-trust-level-content';
-
- // 组装面板
- panel.appendChild(header);
- panel.appendChild(content);
- document.body.appendChild(panel);
-
- // 显示加载信息的函数
- function showLoading() {
- clearContent();
- const loadingDiv = document.createElement('div');
- loadingDiv.className = 'ld-loading';
- loadingDiv.textContent = '加载中...';
- content.appendChild(loadingDiv);
- }
-
- // 显示错误信息的函数
- function showErrorMessage(message) {
- clearContent();
- const errorDiv = document.createElement('div');
- errorDiv.className = 'ld-loading';
- errorDiv.textContent = message;
- content.appendChild(errorDiv);
- }
-
- // 清空内容区域的函数
- function clearContent() {
- while (content.firstChild) {
- content.removeChild(content.firstChild);
- }
- }
-
- // 保存窗口位置的函数
- function savePanelPosition() {
- const transform = window.getComputedStyle(panel).transform;
- if (transform && transform !== 'none') {
- const matrix = new DOMMatrix(transform);
- GM_setValue(STORAGE_KEY_POSITION, { x: matrix.e, y: matrix.f });
- }
- }
-
- // 保存窗口折叠状态的函数
- function savePanelCollapsedState() {
- GM_setValue(STORAGE_KEY_COLLAPSED, panel.classList.contains('ld-collapsed'));
- }
-
- // 恢复窗口状态
- function restorePanelState() {
- // 恢复折叠状态
- const isCollapsed = GM_getValue(STORAGE_KEY_COLLAPSED, false);
- if (isCollapsed) {
- panel.classList.add('ld-collapsed');
- toggleBtn.textContent = '▶'; // 右箭头
- } else {
- panel.classList.remove('ld-collapsed');
- toggleBtn.textContent = '◀'; // 左箭头
- }
+ .ld-daily-stats-item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 4px;
+ }
- // 恢复位置
- const position = GM_getValue(STORAGE_KEY_POSITION, null);
- if (position) {
- panel.style.transform = `translate(${position.x}px, ${position.y}px)`;
- }
- }
-
- // 实现节流函数
- function throttle(func, limit) {
- let inThrottle;
- return function() {
- const args = arguments;
- const context = this;
- if (!inThrottle) {
- func.apply(context, args);
- inThrottle = true;
- setTimeout(() => inThrottle = false, limit);
+ .ld-daily-stats-item .ld-name {
+ flex: 0 1 auto;
+ color: inherit;
+ }
+
+ .ld-daily-stats-item .ld-value {
+ flex: 0 0 auto;
+ font-weight: bold;
+ color: inherit;
+ }
+
+ /* 两天数据的样式 */
+ .ld-dual-stats {
+ display: flex;
+ justify-content: flex-end;
+ gap: 5px;
+ min-width: 70px;
+ text-align: right;
+ }
+
+ .ld-day-stat {
+ min-width: 25px;
+ width: 25px;
+ text-align: right;
+ display: inline-block;
+ }
+
+ .ld-day1 {
+ color: var(--ld-day1-color);
+ }
+
+ .ld-day2 {
+ color: var(--ld-day2-color);
+ }
+
+ .ld-trend-indicator {
+ margin-left: 2px;
+ display: inline-block;
+ min-width: 25px;
+ width: 25px;
+ text-align: left;
+ }
+
+ .ld-stats-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 6px;
+ font-size: 10px;
+ color: inherit;
+ }
+
+ .ld-stats-header-cols {
+ display: flex;
+ gap: 5px;
+ min-width: 70px;
+ justify-content: flex-end;
+ }
+
+ .ld-stats-header-col {
+ min-width: 25px;
+ width: 25px;
+ text-align: center;
+ }
+
+ .ld-stats-header-trend {
+ min-width: 25px;
+ width: 25px;
+ text-align: center;
+ }
+
+ /* 进度条样式 */
+ .ld-progress-container {
+ height: 3px;
+ background-color: var(--ld-border-color);
+ border-radius: 2px;
+ margin-top: 3px;
+ overflow: hidden;
+ }
+
+ .ld-progress-bar {
+ height: 100%;
+ background-color: var(--ld-success-color);
+ border-radius: 2px;
+ transition: width 0.3s;
+ }
+
+ .ld-fail .ld-progress-bar {
+ background-color: var(--ld-fail-color);
+ }
+
+ .ld-notice {
+ font-size: 10px;
+ color: var(--ld-secondary-color);
+ text-align: center;
+ margin-top: 5px;
+ padding-top: 5px;
+ border-top: 1px dashed var(--ld-border-color);
+ }
+ `;
+ document.head.appendChild(style);
+ },
+
+ // 创建面板
+ createPanel: function() {
+ // 创建主面板
+ const panel = document.createElement('div');
+ panel.id = 'ld-trust-level-panel';
+ LDStatus.vars.panel = panel;
+
+ // 设置默认主题
+ const currentTheme = GM_getValue(LDStatus.config.storageKeys.theme, 'dark');
+ LDStatus.vars.isDarkTheme = currentTheme === 'dark';
+ panel.classList.add(currentTheme === 'dark' ? 'ld-dark-theme' : 'ld-light-theme');
+
+ // 获取脚本版本号
+ const scriptVersion = GM_info.script.version;
+
+ // 创建面板头部
+ const header = document.createElement('div');
+ header.id = 'ld-trust-level-header';
+ header.innerHTML = `
+
+ `;
+ LDStatus.vars.header = header;
+
+ // 创建内容区域
+ const content = document.createElement('div');
+ content.id = 'ld-trust-level-content';
+ content.innerHTML = '加载中...
';
+ LDStatus.vars.content = content;
+
+ // 组装面板
+ panel.appendChild(header);
+ panel.appendChild(content);
+ document.body.appendChild(panel);
+
+ // 设置按钮引用
+ LDStatus.vars.toggleBtn = header.querySelector('.ld-toggle-btn');
+ LDStatus.vars.refreshBtn = header.querySelector('.ld-refresh-btn');
+ LDStatus.vars.updateBtn = header.querySelector('.ld-update-btn');
+ LDStatus.vars.themeBtn = header.querySelector('.ld-theme-btn');
+
+ // 更新主题按钮图标
+ this.updateThemeButtonIcon();
+ },
+
+ // 更新主题按钮图标
+ updateThemeButtonIcon: function() {
+ const themeBtn = LDStatus.vars.themeBtn;
+ if (!themeBtn) return;
+
+ const isDarkTheme = LDStatus.vars.panel.classList.contains('ld-dark-theme');
+ themeBtn.textContent = isDarkTheme ? '🌙' : '☀️'; // 月亮或太阳图标
+ themeBtn.title = isDarkTheme ? '切换为亮色主题' : '切换为深色主题';
+ },
+
+ // 显示通知
+ showNotice: function(message, type = 'info', duration = 3000) {
+ const noticeEl = document.createElement('div');
+ noticeEl.className = `ld-notice ld-notice-${type}`;
+ noticeEl.textContent = message;
+ LDStatus.vars.content.appendChild(noticeEl);
+
+ if (duration > 0) {
+ setTimeout(() => {
+ if (noticeEl.parentNode) {
+ noticeEl.parentNode.removeChild(noticeEl);
+ }
+ }, duration);
+ }
+
+ return noticeEl;
+ },
+
+ // 创建进度条
+ createProgressBar: function(current, required) {
+ const currentNum = parseInt(current.match(/\d+/)[0], 10);
+ const requiredNum = parseInt(required.match(/\d+/)[0], 10);
+ const percent = Math.min(100, Math.floor((currentNum / requiredNum) * 100));
+
+ return `
+
+ `;
+ },
+
+ // 渲染信任级别数据
+ renderTrustLevelData: function(username, targetLevel, requirements, isMeetingRequirements, dailyChanges = {}) {
+ const content = LDStatus.vars.content;
+
+ let html = `
+
+ ${username} - 信任级别 ${targetLevel}
+
+
+ ${isMeetingRequirements ? '已' : '未'}符合信任级别 ${targetLevel} 要求
+
+ `;
+
+ requirements.forEach(req => {
+ // 简化项目名称
+ let name = req.name;
+ // 使用配置中的名称映射简化名称
+ Object.entries(LDStatus.config.nameMapping).forEach(([original, simplified]) => {
+ name = name.replace(original, simplified);
+ });
+
+ // 提取数字部分以简化显示
+ let current = req.current;
+ let required = req.required;
+
+ // 尝试从字符串中提取数字
+ const currentMatch = req.current.match(/(\d+)/);
+ const requiredMatch = req.required.match(/(\d+)/);
+
+ if (currentMatch) current = currentMatch[1];
+ if (requiredMatch) required = requiredMatch[1];
+
+ // 添加目标完成数变化的标识
+ let changeIndicator = '';
+ if (req.hasChanged) {
+ const diff = req.changeValue;
+ if (diff > 0) {
+ changeIndicator = ` ▲${diff}`;
+ } else if (diff < 0) {
+ changeIndicator = ` ▼${Math.abs(diff)}`;
+ }
+ }
+
+ html += `
+
+ ${name}
+ ${current}${changeIndicator} / ${required}
+
+ ${LDStatus.ui.createProgressBar(current, required)}
+ `;
+ });
+
+ // 添加近期活动数据显示
+ html += `
+
+
近期的活动
+
+ `;
+
+ // 添加每个数据项
+ const dailyStatsItems = [
+ { name: '浏览话题', key: '浏览的话题(所有时间)' },
+ { name: '回复话题', key: '回复的话题' },
+ { name: '已读帖子', key: '已读帖子(所有时间)' },
+ { name: '获得点赞', key: '获赞:点赞用户数量' },
+ { name: '点赞帖子', key: '点赞' }
+ ];
+
+ dailyStatsItems.forEach(item => {
+ const data = dailyChanges[item.key] || { day1: 0, day2: 0, trend: 0 };
+
+ // 创建趋势指示器
+ let trendIndicator = '';
+ if (data.trend > 0) {
+ trendIndicator = `
▲${Math.abs(data.trend)}`;
+ } else if (data.trend < 0) {
+ trendIndicator = `
▼${Math.abs(data.trend)}`;
+ } else {
+ trendIndicator = `
0`;
+ }
+
+ html += `
+
+ ${item.name}
+
+
+ ${data.day2}
+ ${data.day1}
+ ${trendIndicator}
+
+
+
+ `;
+ });
+
+ html += `
`;
+
+ // 检查是否使用缓存数据
+ const cachedData = GM_getValue(LDStatus.config.storageKeys.lastData, null);
+ if (cachedData && !navigator.onLine) {
+ html += `
+
+ 使用缓存数据,最后更新: ${new Date(cachedData.timestamp).toLocaleString()}
+
+ `;
+ }
+
+ content.innerHTML = html;
+ },
+
+ // 渲染缓存数据
+ renderCachedData: function() {
+ const cachedData = GM_getValue(LDStatus.config.storageKeys.lastData, null);
+ if (cachedData) {
+ this.renderTrustLevelData(
+ cachedData.username,
+ cachedData.targetLevel,
+ cachedData.requirements,
+ cachedData.isMeetingRequirements,
+ cachedData.dailyChanges
+ );
+ this.showNotice(`使用缓存数据,最后更新: ${new Date(cachedData.timestamp).toLocaleString()}`);
+ } else {
+ LDStatus.vars.content.innerHTML = '无可用数据,请检查网络连接
';
+ }
}
- };
- }
-
- // 拖动功能
- let isDragging = false;
- let lastX, lastY;
-
- header.addEventListener('mousedown', (e) => {
- if (panel.classList.contains('ld-collapsed')) return;
-
- isDragging = true;
- lastX = e.clientX;
- lastY = e.clientY;
-
- // 添加拖动时的样式
- panel.style.transition = 'none';
- document.body.style.userSelect = 'none';
- });
-
- document.addEventListener('mousemove', throttle((e) => {
- if (!isDragging) return;
-
- const dx = e.clientX - lastX;
- const dy = e.clientY - lastY;
- lastX = e.clientX;
- lastY = e.clientY;
-
- requestAnimationFrame(() => {
- const currentTransform = window.getComputedStyle(panel).transform;
- const matrix = new DOMMatrix(currentTransform === 'none' ? '' : currentTransform);
- const newX = matrix.e + dx;
- const newY = matrix.f + dy;
- panel.style.transform = `translate(${newX}px, ${newY}px)`;
- });
- }, 16));
-
- document.addEventListener('mouseup', () => {
- if (!isDragging) return;
-
- isDragging = false;
- panel.style.transition = '';
- document.body.style.userSelect = '';
-
- // 保存窗口位置
- savePanelPosition();
- });
-
- // 展开/收起功能
- toggleBtn.addEventListener('click', () => {
- panel.classList.toggle('ld-collapsed');
- toggleBtn.textContent = panel.classList.contains('ld-collapsed') ? '▶' : '◀';
-
- // 保存折叠状态
- savePanelCollapsedState();
- });
-
- // 刷新按钮
- refreshBtn.addEventListener('click', fetchTrustLevelData);
-
- // 检查更新按钮
- updateBtn.addEventListener('click', checkForUpdates);
-
- // 检查脚本更新
- function checkForUpdates() {
- const lastCheck = GM_getValue(STORAGE_KEY_LAST_UPDATE_CHECK, 0);
- const now = Date.now();
-
- // 一天只检查一次
- if (now - lastCheck < 86400000) {
- updateBtn.textContent = '⏱️';
- updateBtn.title = '今天已检查过更新';
- setTimeout(() => {
- updateBtn.textContent = '🔎';
- updateBtn.title = '检查更新';
- }, 2000);
- return;
- }
-
- const updateURL = 'https://raw.githubusercontent.com/1e0n/LinuxDoStatus/master/LDStatus.user.js';
-
- // 显示正在检查的状态
- updateBtn.textContent = '⌛'; // 沙漏图标
- updateBtn.title = '正在检查更新...';
-
- GM_xmlhttpRequest({
- method: 'GET',
- url: updateURL,
- timeout: 10000, // 添加超时设置
- onload: function(response) {
- if (response.status === 200) {
- try {
- // 提取远程脚本的版本号
- const versionMatch = response.responseText.match(/@version\s+([\d\.]+)/);
- if (versionMatch && versionMatch[1]) {
- const remoteVersion = versionMatch[1];
-
- // 比较版本
- if (remoteVersion > scriptVersion) {
- // 有新版本
- updateBtn.textContent = '⚠️'; // 警告图标
- updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`;
- updateBtn.style.color = '#ffd700'; // 黄色
-
- // 点击按钮跳转到更新页面
- updateBtn.onclick = function() {
- window.open(updateURL, '_blank');
- };
+ },
+
+ // 数据处理相关方法
+ data: {
+ // 获取信任级别数据
+ fetchTrustLevelData: function() {
+ LDStatus.vars.content.innerHTML = '加载中...
';
+
+ // 如果离线,使用缓存数据
+ if (!navigator.onLine) {
+ LDStatus.ui.renderCachedData();
+ return;
+ }
+
+ GM_xmlhttpRequest({
+ method: 'GET',
+ url: 'https://connect.linux.do',
+ timeout: 10000, // 设置超时时间
+ onload: function(response) {
+ try {
+ if (response.status === 200) {
+ LDStatus.data.parseTrustLevelData(response.responseText);
} else {
- // 已是最新版本
- updateBtn.textContent = '✔'; // 勾选图标
- updateBtn.title = '已是最新版本';
- updateBtn.style.color = '#68d391'; // 绿色
-
- // 3秒后恢复原样式
- setTimeout(() => {
- updateBtn.textContent = '🔎'; // 放大镜图标
- updateBtn.title = '检查更新';
- updateBtn.style.color = 'white';
- updateBtn.onclick = checkForUpdates;
- }, 3000);
+ throw new Error(`HTTP错误: ${response.status}`);
}
- } else {
- handleUpdateError('无法解析版本信息');
+ } catch (error) {
+ console.error('数据处理错误:', error);
+ LDStatus.vars.content.innerHTML = `处理数据时出错: ${error.message}
`;
+ // 尝试使用缓存数据
+ LDStatus.ui.renderCachedData();
}
- } catch (error) {
- handleUpdateError('处理更新信息时出错: ' + error.message);
+ },
+ onerror: function(error) {
+ console.error('请求错误:', error);
+ LDStatus.vars.content.innerHTML = '网络请求失败,请检查网络连接
';
+ // 尝试使用缓存数据
+ LDStatus.ui.renderCachedData();
+ },
+ ontimeout: function() {
+ LDStatus.vars.content.innerHTML = '请求超时,请稍后再试
';
+ // 尝试使用缓存数据
+ LDStatus.ui.renderCachedData();
}
- } else {
- handleUpdateError(`请求失败 (${response.status})`);
- }
-
- // 更新检查时间
- GM_setValue(STORAGE_KEY_LAST_UPDATE_CHECK, now);
- },
- onerror: function(error) {
- handleUpdateError('网络请求失败');
+ });
},
- ontimeout: function() {
- handleUpdateError('请求超时');
- }
- });
-
- // 处理更新检查错误
- function handleUpdateError(message) {
- console.error('检查更新失败:', message);
- updateBtn.textContent = '❌'; // 错误图标
- updateBtn.title = '检查更新失败,请稍后再试';
- updateBtn.style.color = '#fc8181'; // 红色
-
- // 3秒后恢复原样式
- setTimeout(() => {
- updateBtn.textContent = '🔎'; // 放大镜图标
- updateBtn.title = '检查更新';
- updateBtn.style.color = 'white';
- }, 3000);
- }
- }
-
- // 获取信任级别数据
- function fetchTrustLevelData() {
- showLoading();
-
- GM_xmlhttpRequest({
- method: 'GET',
- url: 'https://connect.linux.do',
- timeout: 10000, // 添加超时设置
- onload: function(response) {
- if (response.status === 200) {
- try {
- parseTrustLevelData(response.responseText);
- } catch (error) {
- console.error('解析数据时出错:', error);
- showErrorMessage('解析数据时出错: ' + error.message);
+
+ // 解析信任级别数据
+ parseTrustLevelData: function(html) {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+
+ // 查找信任级别区块
+ const trustLevelSection = Array.from(doc.querySelectorAll('.bg-white.p-6.rounded-lg')).find(div => {
+ const heading = div.querySelector('h2');
+ return heading && heading.textContent.includes('信任级别');
+ });
+
+ if (!trustLevelSection) {
+ LDStatus.vars.content.innerHTML = '未找到信任级别数据,请确保已登录
';
+ return;
+ }
+
+ // 获取用户名和当前级别
+ const heading = trustLevelSection.querySelector('h2').textContent.trim();
+ const match = heading.match(/(.*) - 信任级别 (\d+) 的要求/);
+ const username = match ? match[1] : '未知用户';
+ const targetLevel = match ? match[2] : '未知';
+
+ // 获取表格数据
+ const tableRows = trustLevelSection.querySelectorAll('table tr');
+ const requirements = [];
+
+ for (let i = 1; i < tableRows.length; i++) { // 跳过表头
+ const row = tableRows[i];
+ const cells = row.querySelectorAll('td');
+
+ if (cells.length >= 3) {
+ const name = cells[0].textContent.trim();
+ const current = cells[1].textContent.trim();
+ const required = cells[2].textContent.trim();
+ const isSuccess = cells[1].classList.contains('text-green-500');
+
+ // 提取当前完成数的数字部分
+ const currentMatch = current.match(/(\d+)/);
+ const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0;
+ // 查找上一次的数据记录
+ let changeValue = 0;
+ let hasChanged = false;
+
+ if (LDStatus.vars.previousRequirements.length > 0) {
+ const prevReq = LDStatus.vars.previousRequirements.find(pr => pr.name === name);
+ if (prevReq) {
+ // 如果完成数有变化,更新变化值
+ if (currentValue !== prevReq.currentValue) {
+ changeValue = currentValue - prevReq.currentValue;
+ hasChanged = true;
+ } else if (prevReq.changeValue) {
+ // 如果完成数没有变化,但之前有变化值,保留之前的变化值
+ changeValue = prevReq.changeValue;
+ hasChanged = true;
+ }
+ }
+ }
+
+ requirements.push({
+ name,
+ current,
+ required,
+ isSuccess,
+ currentValue,
+ changeValue, // 变化值
+ hasChanged // 是否有变化
+ });
}
- } else {
- showErrorMessage(`获取数据失败 (${response.status})`);
}
+
+ // 获取总体结果
+ const resultText = trustLevelSection.querySelector('p.text-red-500, p.text-green-500');
+ const isMeetingRequirements = resultText ? !resultText.classList.contains('text-red-500') : false;
+
+ // 存储48小时内的数据变化
+ const dailyChanges = this.saveDailyStats(requirements);
+
+ // 缓存数据,以备网络问题时使用
+ GM_setValue(LDStatus.config.storageKeys.lastData, {
+ username,
+ targetLevel,
+ requirements,
+ isMeetingRequirements,
+ dailyChanges,
+ timestamp: new Date().getTime()
+ });
+
+ // 渲染数据
+ LDStatus.ui.renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges);
+
+ // 保存当前数据作为下次比较的基准
+ LDStatus.vars.previousRequirements = [...requirements];
},
- onerror: function(error) {
- console.error('请求错误:', error);
- showErrorMessage('网络请求失败');
+
+ // 存储48小时内的数据变化
+ saveDailyStats: function(requirements) {
+ const statsToTrack = LDStatus.config.statsToTrack;
+
+ // 获取当前时间
+ const now = new Date().getTime();
+
+ // 从 localStorage 中获取已存储的数据
+ let dailyStats = JSON.parse(localStorage.getItem('ld_daily_stats') || '[]');
+
+ // 删除超过48小时的数据
+ const twoDaysAgo = now - 48 * 60 * 60 * 1000;
+ dailyStats = dailyStats.filter(item => item.timestamp > twoDaysAgo);
+
+ // 对于每个要跟踪的数据项,找到当前值并添加到历史记录中
+ statsToTrack.forEach(statName => {
+ const req = requirements.find(r => r.name === statName);
+ if (req) {
+ // 提取数字值
+ const currentMatch = req.current.match(/(\d+)/);
+ const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0;
+
+ // 添加新的数据点
+ dailyStats.push({
+ name: statName,
+ value: currentValue,
+ timestamp: now
+ });
+ }
+ });
+
+ // 清理过量的历史数据
+ this.cleanupStorage(dailyStats);
+
+ // 将更新后的数据保存回 localStorage
+ localStorage.setItem('ld_daily_stats', JSON.stringify(dailyStats));
+
+ return this.calculateDailyChanges(dailyStats);
},
- ontimeout: function() {
- showErrorMessage('请求超时');
- }
- });
- }
-
- // 查找信任级别区块
- function findTrustLevelSection(doc) {
- const headers = doc.querySelectorAll('h2');
- const trustHeader = Array.from(headers).find(h => h.textContent.includes('信任级别'));
- return trustHeader ? trustHeader.closest('.bg-white.p-6.rounded-lg') : null;
- }
-
- // 提取用户信息
- function extractUserInfo(section) {
- const heading = section.querySelector('h2').textContent.trim();
- const match = heading.match(/(.*) - 信任级别 (\d+) 的要求/);
- return {
- username: match ? match[1] : '未知用户',
- targetLevel: match ? match[2] : '未知'
- };
- }
-
- // 检查需求状态
- function checkRequirementStatus(section) {
- const resultText = section.querySelector('p.text-red-500, p.text-green-500');
- return resultText ? !resultText.classList.contains('text-red-500') : false;
- }
-
- // 提取需求数据
- function extractRequirements(section, previousRequirements) {
- const tableRows = section.querySelectorAll('table tr');
- const requirements = [];
-
- for (let i = 1; i < tableRows.length; i++) { // 跳过表头
- const row = tableRows[i];
- const cells = row.querySelectorAll('td');
-
- if (cells.length >= 3) {
- const name = cells[0].textContent.trim();
- const current = cells[1].textContent.trim();
- const required = cells[2].textContent.trim();
- const isSuccess = cells[1].classList.contains('text-green-500');
-
- // 提取当前完成数的数字部分
- const currentMatch = current.match(/(\d+)/);
- const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0;
-
- // 查找上一次的数据记录
- let changeValue = 0;
- let hasChanged = false;
-
- if (previousRequirements.length > 0) {
- const prevReq = previousRequirements.find(pr => pr.name === name);
- if (prevReq) {
- // 如果完成数有变化,更新变化值
- if (currentValue !== prevReq.currentValue) {
- changeValue = currentValue - prevReq.currentValue;
- hasChanged = true;
- } else if (prevReq.changeValue) {
- // 如果完成数没有变化,但之前有变化值,保留之前的变化值
- changeValue = prevReq.changeValue;
- hasChanged = true;
+
+ // 清理过量的存储数据
+ cleanupStorage: function(stats) {
+ const maxItems = LDStatus.config.maxStorageItems;
+
+ if (stats.length > maxItems) {
+ // 按时间戳排序并只保留最新的数据
+ stats.sort((a, b) => b.timestamp - a.timestamp);
+ return stats.slice(0, maxItems);
+ }
+
+ return stats;
+ },
+
+ // 计算近两天内的变化量
+ calculateDailyChanges: function(dailyStats) {
+ const statsToTrack = LDStatus.config.statsToTrack;
+ const result = {};
+ const now = new Date().getTime();
+ const oneDayAgo = now - 24 * 60 * 60 * 1000;
+ const twoDaysAgo = now - 48 * 60 * 60 * 1000;
+
+ // 对于每个要跟踪的数据项,计算两天内的变化
+ statsToTrack.forEach(statName => {
+ // 过滤出当前数据项的所有记录,并按时间戳排序
+ const statRecords = dailyStats
+ .filter(item => item.name === statName)
+ .sort((a, b) => a.timestamp - b.timestamp);
+
+ // 初始化结果对象结构
+ result[statName] = {
+ day1: 0, // 最近24小时
+ day2: 0, // 24-48小时
+ trend: 0 // 趋势:day1 - day2
+ };
+
+ if (statRecords.length >= 2) {
+ // 找出最新记录和其前面两个时间段的记录
+ const newest = statRecords[statRecords.length - 1];
+
+ // 找最近24小时内最早的记录
+ const oldestDay1 = statRecords.filter(item => item.timestamp > oneDayAgo)[0];
+
+ // 找24-48小时内最早的记录和最新的记录
+ const recordsDay2 = statRecords.filter(item =>
+ item.timestamp <= oneDayAgo && item.timestamp > twoDaysAgo);
+
+ const oldestDay2 = recordsDay2.length > 0 ? recordsDay2[0] : null;
+ const newestDay2 = recordsDay2.length > 0 ?
+ recordsDay2[recordsDay2.length - 1] : null;
+
+ // 计算最近24小时的变化
+ if (oldestDay1) {
+ result[statName].day1 = newest.value - oldestDay1.value;
+ }
+
+ // 计算24-48小时的变化
+ if (oldestDay2 && newestDay2) {
+ result[statName].day2 = newestDay2.value - oldestDay2.value;
}
+
+ // 计算趋势(今天和昨天的变化差异)
+ result[statName].trend = result[statName].day1 - result[statName].day2;
}
- }
+ });
- requirements.push({
- name,
- current,
- required,
- isSuccess,
- currentValue,
- changeValue, // 变化值
- hasChanged // 是否有变化
+ return result;
+ },
+
+ // 检查脚本更新
+ checkForUpdates: function() {
+ const updateURL = 'https://raw.githubusercontent.com/1e0n/LinuxDoStatus/master/LDStatus.user.js';
+ const updateBtn = LDStatus.vars.updateBtn;
+
+ // 显示正在检查的状态
+ updateBtn.textContent = '⌛'; // 沙漏图标
+ updateBtn.title = '正在检查更新...';
+
+ GM_xmlhttpRequest({
+ method: 'GET',
+ url: updateURL,
+ onload: function(response) {
+ if (response.status === 200) {
+ // 提取远程脚本的版本号
+ const versionMatch = response.responseText.match(/@version\s+([\d\.]+)/);
+ if (versionMatch && versionMatch[1]) {
+ const remoteVersion = versionMatch[1];
+ const scriptVersion = GM_info.script.version;
+
+ // 比较版本
+ if (remoteVersion > scriptVersion) {
+ // 有新版本
+ updateBtn.textContent = '⚠️'; // 警告图标
+ updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`;
+ updateBtn.style.color = 'var(--ld-increase-color)'; // 黄色
+
+ // 点击按钮跳转到更新页面
+ updateBtn.onclick = function() {
+ window.open(updateURL, '_blank');
+ };
+ } else {
+ // 已是最新版本
+ updateBtn.textContent = '✔'; // 勾选图标
+ updateBtn.title = '已是最新版本';
+ updateBtn.style.color = 'var(--ld-success-color)'; // 绿色
+
+ // 3秒后恢复原样式
+ setTimeout(() => {
+ updateBtn.textContent = '🔎'; // 放大镜图标
+ updateBtn.title = '检查更新';
+ updateBtn.style.color = '';
+ updateBtn.onclick = LDStatus.events.onUpdateBtnClick;
+ }, 3000);
+ }
+ } else {
+ LDStatus.data.handleUpdateError();
+ }
+ } else {
+ LDStatus.data.handleUpdateError();
+ }
+ },
+ onerror: LDStatus.data.handleUpdateError
});
+ },
+
+ // 处理更新检查错误
+ handleUpdateError: function() {
+ const updateBtn = LDStatus.vars.updateBtn;
+ updateBtn.textContent = '❌'; // 错误图标
+ updateBtn.title = '检查更新失败,请稍后再试';
+ updateBtn.style.color = 'var(--ld-fail-color)'; // 红色
+
+ // 3秒后恢复原样式
+ setTimeout(() => {
+ updateBtn.textContent = '🔎'; // 放大镜图标
+ updateBtn.title = '检查更新';
+ updateBtn.style.color = '';
+ }, 3000);
}
- }
+ },
+
+ // 事件处理相关方法
+ events: {
+ // 注册所有事件监听
+ registerEvents: function() {
+ // 拖动面板相关事件
+ this.setupDragEvents();
+
+ // 按钮点击事件
+ LDStatus.vars.toggleBtn.addEventListener('click', this.onToggleBtnClick);
+ LDStatus.vars.refreshBtn.addEventListener('click', this.onRefreshBtnClick);
+ LDStatus.vars.updateBtn.addEventListener('click', this.onUpdateBtnClick);
+ LDStatus.vars.themeBtn.addEventListener('click', this.onThemeBtnClick);
+
+ // 页面可见性变化时刷新数据
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
+ },
- return requirements;
- }
+ // 设置拖动面板的事件
+ setupDragEvents: function() {
+ const header = LDStatus.vars.header;
- // 解析信任级别数据
- function parseTrustLevelData(html) {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
+ header.addEventListener('mousedown', this.onPanelDragStart);
+ document.addEventListener('mousemove', this.onPanelDragMove);
+ document.addEventListener('mouseup', this.onPanelDragEnd);
+ },
- // 查找信任级别区块
- const trustLevelSection = findTrustLevelSection(doc);
+ // 面板拖动开始
+ onPanelDragStart: function(e) {
+ if (LDStatus.vars.panel.classList.contains('ld-collapsed')) return;
- if (!trustLevelSection) {
- showErrorMessage('未找到信任级别数据,请确保已登录');
- return;
- }
+ LDStatus.vars.isDragging = true;
+ LDStatus.vars.lastX = e.clientX;
+ LDStatus.vars.lastY = e.clientY;
+
+ // 添加拖动时的样式
+ LDStatus.vars.panel.style.transition = 'none';
+ document.body.style.userSelect = 'none';
+ },
+
+ // 面板拖动中
+ onPanelDragMove: function(e) {
+ if (!LDStatus.vars.isDragging) return;
+
+ // 使用 transform 而不是改变 left/top 属性,性能更好
+ const dx = e.clientX - LDStatus.vars.lastX;
+ const dy = e.clientY - LDStatus.vars.lastY;
+
+ const currentTransform = window.getComputedStyle(LDStatus.vars.panel).transform;
+ const matrix = new DOMMatrix(currentTransform === 'none' ? '' : currentTransform);
+
+ const newX = matrix.e + dx;
+ const newY = matrix.f + dy;
+
+ LDStatus.vars.panel.style.transform = `translate(${newX}px, ${newY}px)`;
+
+ LDStatus.vars.lastX = e.clientX;
+ LDStatus.vars.lastY = e.clientY;
+ },
+
+ // 面板拖动结束
+ onPanelDragEnd: function() {
+ if (!LDStatus.vars.isDragging) return;
+
+ LDStatus.vars.isDragging = false;
+ LDStatus.vars.panel.style.transition = '';
+ document.body.style.userSelect = '';
+
+ // 保存窗口位置
+ LDStatus.storage.savePanelPosition();
+ },
+
+ // 折叠/展开面板按钮点击
+ onToggleBtnClick: function() {
+ const panel = LDStatus.vars.panel;
+ panel.classList.toggle('ld-collapsed');
+ LDStatus.vars.toggleBtn.textContent = panel.classList.contains('ld-collapsed') ? '▶' : '◀';
+
+ // 保存折叠状态
+ LDStatus.storage.savePanelCollapsedState();
+ },
+
+ // 刷新按钮点击
+ onRefreshBtnClick: function() {
+ LDStatus.data.fetchTrustLevelData();
+ },
+
+ // 更新按钮点击
+ onUpdateBtnClick: function() {
+ LDStatus.data.checkForUpdates();
+ },
+
+ // 主题按钮点击
+ onThemeBtnClick: function() {
+ const panel = LDStatus.vars.panel;
+ const isDarkTheme = panel.classList.contains('ld-dark-theme');
+
+ // 切换主题类
+ panel.classList.remove(isDarkTheme ? 'ld-dark-theme' : 'ld-light-theme');
+ panel.classList.add(isDarkTheme ? 'ld-light-theme' : 'ld-dark-theme');
- // 获取用户名和当前级别
- const { username, targetLevel } = extractUserInfo(trustLevelSection);
-
- // 获取表格数据
- const requirements = extractRequirements(trustLevelSection, previousRequirements);
-
- // 获取总体结果
- const isMeetingRequirements = checkRequirementStatus(trustLevelSection);
-
- // 存储24小时内的数据变化
- const dailyChanges = saveDailyStats(requirements);
-
- // 渲染数据
- renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges);
-
- // 保存当前数据作为下次比较的基准
- previousRequirements = [...requirements];
- }
-
- // 渲染信任级别数据
- function renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges = {}) {
- clearContent();
-
- const fragment = document.createDocumentFragment();
-
- // 创建用户和级别信息
- const headerDiv = document.createElement('div');
- headerDiv.style.marginBottom = '8px';
- headerDiv.style.fontWeight = 'bold';
- headerDiv.textContent = `${username} - 信任级别 ${targetLevel}`;
- fragment.appendChild(headerDiv);
-
- // 创建需求状态信息
- const statusDiv = document.createElement('div');
- statusDiv.style.marginBottom = '10px';
- statusDiv.style.color = isMeetingRequirements ? '#68d391' : '#fc8181';
- statusDiv.style.fontSize = '11px';
- statusDiv.textContent = `${isMeetingRequirements ? '已' : '未'}符合信任级别 ${targetLevel} 要求`;
- fragment.appendChild(statusDiv);
-
- // 创建需求列表
- requirements.forEach(req => {
- // 简化项目名称
- let name = req.name;
- // 将一些常见的长名称缩短
- name = name.replace('已读帖子(所有时间)', '已读帖子(总)');
- name = name.replace('浏览的话题(所有时间)', '浏览话题(总)');
- name = name.replace('获赞:点赞用户数量', '点赞用户数');
- name = name.replace('获赞:单日最高数量', '单日最高获赞');
- name = name.replace('被禁言(过去 6 个月)', '被禁言');
- name = name.replace('被封禁(过去 6 个月)', '被封禁');
-
- // 提取数字部分以简化显示
- let current = req.current;
- let required = req.required;
-
- // 尝试从字符串中提取数字
- const currentMatch = req.current.match(/(\d+)/);
- const requiredMatch = req.required.match(/(\d+)/);
-
- if (currentMatch) current = currentMatch[1];
- if (requiredMatch) required = requiredMatch[1];
-
- // 创建需求项
- const reqDiv = document.createElement('div');
- reqDiv.className = `ld-trust-level-item ${req.isSuccess ? 'ld-success' : 'ld-fail'}`;
-
- const nameSpan = document.createElement('span');
- nameSpan.className = 'ld-name';
- nameSpan.textContent = name;
- reqDiv.appendChild(nameSpan);
-
- const valueSpan = document.createElement('span');
- valueSpan.className = 'ld-value';
-
- // 添加目标完成数
- valueSpan.textContent = `${current} / ${required}`;
-
- // 添加变化指示器
- if (req.hasChanged) {
- const changeIndicator = document.createElement('span');
- const diff = req.changeValue;
- if (diff > 0) {
- changeIndicator.className = 'ld-increase';
- changeIndicator.textContent = ` ▲${diff}`;
- } else if (diff < 0) {
- changeIndicator.className = 'ld-decrease';
- changeIndicator.textContent = ` ▼${Math.abs(diff)}`;
+ // 更新主题变量
+ LDStatus.vars.isDarkTheme = !isDarkTheme;
+
+ // 更新按钮图标
+ LDStatus.ui.updateThemeButtonIcon();
+
+ // 保存主题设置
+ GM_setValue(LDStatus.config.storageKeys.theme, LDStatus.vars.isDarkTheme ? 'dark' : 'light');
+ },
+
+ // 页面可见性变化处理
+ onVisibilityChange: function() {
+ if (!document.hidden) {
+ // 检查上次刷新时间,如果超过指定间隔则刷新数据
+ const lastRefreshTime = LDStatus.vars.lastRefreshTime || 0;
+ const now = Date.now();
+
+ if (now - lastRefreshTime > LDStatus.config.refreshInterval) {
+ LDStatus.data.fetchTrustLevelData();
+ LDStatus.vars.lastRefreshTime = now;
+ }
}
- valueSpan.appendChild(changeIndicator);
- }
-
- reqDiv.appendChild(valueSpan);
- fragment.appendChild(reqDiv);
- });
-
- // 创建24小时活动数据
- const dailyStatsDiv = document.createElement('div');
- dailyStatsDiv.className = 'ld-daily-stats';
-
- const dailyStatsTitleDiv = document.createElement('div');
- dailyStatsTitleDiv.className = 'ld-daily-stats-title';
- dailyStatsTitleDiv.textContent = '24小时内的活动';
- dailyStatsDiv.appendChild(dailyStatsTitleDiv);
-
- // 添加每个数据项
- const dailyStatsItems = [
- { name: '浏览话题', key: '浏览的话题(所有时间)' },
- { name: '回复话题', key: '回复的话题' },
- { name: '已读帖子', key: '已读帖子(所有时间)' },
- { name: '获得点赞', key: '获赞:点赞用户数量' },
- { name: '点赞帖子', key: '点赞的帖子' }
- ];
-
- dailyStatsItems.forEach(item => {
- const value = dailyChanges[item.key] || 0;
-
- const statsItemDiv = document.createElement('div');
- statsItemDiv.className = 'ld-daily-stats-item';
-
- const nameSpan = document.createElement('span');
- nameSpan.className = 'ld-name';
- nameSpan.textContent = item.name;
- statsItemDiv.appendChild(nameSpan);
-
- const valueSpan = document.createElement('span');
- valueSpan.className = 'ld-value';
- valueSpan.textContent = value;
- statsItemDiv.appendChild(valueSpan);
-
- dailyStatsDiv.appendChild(statsItemDiv);
- });
-
- fragment.appendChild(dailyStatsDiv);
- content.appendChild(fragment);
- }
-
- // 存储上一次获取的数据,用于比较变化
- let previousRequirements = [];
-
- // 存储24小时内的数据变化
- function saveDailyStats(requirements) {
- // 定义要跟踪的数据项
- const statsToTrack = [
- '浏览的话题(所有时间)', // 浏览话题总数
- '回复的话题', // 回复话题数
- '已读帖子(所有时间)', // 已读帖子总数
- '获赞:点赞用户数量', // 获得点赞
- '点赞的帖子' // 点赞帖子
- ];
-
- // 从存储中获取之前的记录
- let dailyStats = GM_getValue('ld_daily_stats', []);
-
- // 获取当前时间戳
- const now = Date.now();
-
- // 清理超过24小时的旧数据
- dailyStats = dailyStats.filter(stat => now - stat.timestamp < 24 * 60 * 60 * 1000);
-
- // 提取要跟踪的数据项
- const trackedStats = requirements.filter(req => statsToTrack.includes(req.name));
-
- // 为每个要跟踪的项目添加新记录
- trackedStats.forEach(stat => {
- dailyStats.push({
- name: stat.name,
- value: stat.currentValue,
- timestamp: now
- });
- });
-
- // 限制每种统计类型的条目数,防止过度存储
- const MAX_ENTRIES_PER_STAT = 50;
- statsToTrack.forEach(statName => {
- const statEntries = dailyStats.filter(item => item.name === statName);
- if (statEntries.length > MAX_ENTRIES_PER_STAT) {
- // 只保留最新的 MAX_ENTRIES_PER_STAT 条记录
- const sortedEntries = statEntries.sort((a, b) => b.timestamp - a.timestamp);
- const toKeep = sortedEntries.slice(0, MAX_ENTRIES_PER_STAT);
- // 移除多余条目
- dailyStats = dailyStats.filter(item => item.name !== statName || toKeep.includes(item));
}
- });
-
- // 保存更新后的数据
- GM_setValue('ld_daily_stats', dailyStats);
-
- // 计算24小时内每项的变化量
- let changes = {};
- statsToTrack.forEach(statName => {
- const stats = dailyStats.filter(stat => stat.name === statName);
- if (stats.length >= 2) {
- // 排序数据,最新的在前面
- stats.sort((a, b) => b.timestamp - a.timestamp);
-
- // 获取最新的值
- const latestValue = stats[0].value;
-
- // 获取最老的,但不超过24小时的值
- const oldestStats = stats.filter(stat => now - stat.timestamp < 24 * 60 * 60 * 1000);
- if (oldestStats.length > 0) {
- oldestStats.sort((a, b) => a.timestamp - b.timestamp);
- const oldestValue = oldestStats[0].value;
-
- // 计算变化
- changes[statName] = latestValue - oldestValue;
+ },
+
+ // 存储相关方法
+ storage: {
+ // 初始化存储
+ initStorage: function() {
+ // 恢复面板位置
+ this.restorePanelPosition();
+
+ // 恢复折叠状态
+ this.restorePanelCollapsedState();
+ },
+
+ // 保存面板位置
+ savePanelPosition: function() {
+ const style = window.getComputedStyle(LDStatus.vars.panel);
+ const transform = style.transform;
+
+ if (transform !== 'none') {
+ GM_setValue(LDStatus.config.storageKeys.position, transform);
+ }
+ },
+
+ // 恢复面板位置
+ restorePanelPosition: function() {
+ const savedPosition = GM_getValue(LDStatus.config.storageKeys.position, null);
+
+ if (savedPosition) {
+ LDStatus.vars.panel.style.transform = savedPosition;
+ }
+ },
+
+ // 保存面板折叠状态
+ savePanelCollapsedState: function() {
+ const isCollapsed = LDStatus.vars.panel.classList.contains('ld-collapsed');
+ GM_setValue(LDStatus.config.storageKeys.collapsed, isCollapsed);
+ },
+
+ // 恢复面板折叠状态
+ restorePanelCollapsedState: function() {
+ const isCollapsed = GM_getValue(LDStatus.config.storageKeys.collapsed, false);
+
+ if (isCollapsed) {
+ LDStatus.vars.panel.classList.add('ld-collapsed');
+ LDStatus.vars.toggleBtn.textContent = '▶';
+ } else {
+ LDStatus.vars.panel.classList.remove('ld-collapsed');
+ LDStatus.vars.toggleBtn.textContent = '◀';
}
}
- });
+ },
+
+ // 初始化方法
+ init: function() {
+ // 初始化 UI
+ this.ui.createStyles();
+ this.ui.createPanel();
- return changes;
- }
+ // 初始化存储
+ this.storage.initStorage();
- // 实现闲置检测,避免页面不活跃时进行不必要的刷新
- let refreshInterval;
- let visibilityState = true;
+ // 注册事件
+ this.events.registerEvents();
- function setupRefreshInterval() {
- clearInterval(refreshInterval);
- if (visibilityState) {
- refreshInterval = setInterval(fetchTrustLevelData, 120000); // 2分钟刷新一次
+ // 获取数据
+ this.data.fetchTrustLevelData();
+
+ // 设置定时刷新
+ LDStatus.vars.refreshTimer = setInterval(function() {
+ LDStatus.data.fetchTrustLevelData();
+ LDStatus.vars.lastRefreshTime = Date.now();
+ }, this.config.refreshInterval);
+
+ // 记录初始化时间
+ LDStatus.vars.lastRefreshTime = Date.now();
}
- }
-
- // 监听可见性变化
- document.addEventListener('visibilitychange', () => {
- visibilityState = document.visibilityState === 'visible';
- setupRefreshInterval();
- });
-
- // 初始化
- function initialize() {
- // 恢复面板状态
- restorePanelState();
-
- // 首次获取数据
- fetchTrustLevelData();
-
- // 设置刷新间隔
- setupRefreshInterval();
- }
-
- // 页面加载完成后初始化
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initialize);
- } else {
- initialize();
- }
+ };
+
+ // 初始化脚本
+ LDStatus.init();
})();
From 885b562cc297ba714b41ad21cbf925c769156e4f Mon Sep 17 00:00:00 2001
From: Michael Kane <15847202+jhhd88@users.noreply.github.com>
Date: Wed, 21 May 2025 08:46:32 +0800
Subject: [PATCH 3/3] Update LDStatus.user.js
---
LDStatus.user.js | 376 +++++++++++++++++++++++++++++++----------------
1 file changed, 249 insertions(+), 127 deletions(-)
diff --git a/LDStatus.user.js b/LDStatus.user.js
index 89ab78c..cacb656 100644
--- a/LDStatus.user.js
+++ b/LDStatus.user.js
@@ -45,6 +45,23 @@
'获赞:单日最高数量': '单日最高获赞',
'被禁言(过去 6 个月)': '被禁言',
'被封禁(过去 6 个月)': '被封禁'
+ },
+ icons: {
+ SEARCH: '🔎',
+ REFRESH: '🔄',
+ THEME_DARK: '🌙',
+ THEME_LIGHT: '☀️',
+ COLLAPSE: '◀',
+ EXPAND: '▶',
+ LOADING: '⌛',
+ UPDATE_AVAILABLE: '⚠️',
+ UP_TO_DATE: '✔',
+ ERROR: '❌',
+ ARROW_UP: '▲',
+ ARROW_DOWN: '▼',
+ TREND_INCREASE: '▲',
+ TREND_DECREASE: '▼',
+ TREND_STABLE: '–' // Using '–' for better visual than '0'
}
},
@@ -405,10 +422,10 @@
`;
LDStatus.vars.header = header;
@@ -440,7 +457,7 @@
if (!themeBtn) return;
const isDarkTheme = LDStatus.vars.panel.classList.contains('ld-dark-theme');
- themeBtn.textContent = isDarkTheme ? '🌙' : '☀️'; // 月亮或太阳图标
+ themeBtn.textContent = isDarkTheme ? LDStatus.config.icons.THEME_DARK : LDStatus.config.icons.THEME_LIGHT;
themeBtn.title = isDarkTheme ? '切换为亮色主题' : '切换为深色主题';
},
@@ -464,9 +481,21 @@
// 创建进度条
createProgressBar: function(current, required) {
- const currentNum = parseInt(current.match(/\d+/)[0], 10);
- const requiredNum = parseInt(required.match(/\d+/)[0], 10);
- const percent = Math.min(100, Math.floor((currentNum / requiredNum) * 100));
+ // Ensure current and required are strings before trying to match
+ const currentStr = String(current || ''); // Default to empty string if null/undefined
+ const requiredStr = String(required || ''); // Default to empty string
+
+ const currentNumMatch = currentStr.match(/\d+/);
+ const requiredNumMatch = requiredStr.match(/\d+/);
+
+ // Default to 0 if parsing fails to avoid NaN errors or errors from null[0]
+ const currentNum = (currentNumMatch && currentNumMatch[0]) ? parseInt(currentNumMatch[0], 10) : 0;
+ const requiredNum = (requiredNumMatch && requiredNumMatch[0]) ? parseInt(requiredNumMatch[0], 10) : 0;
+
+ // Avoid division by zero if requiredNum is 0
+ const percent = (requiredNum > 0)
+ ? Math.min(100, Math.floor((currentNum / requiredNum) * 100))
+ : (currentNum > 0 ? 100 : 0); // If required is 0, 100% if current > 0, else 0%
return `
@@ -496,34 +525,29 @@
name = name.replace(original, simplified);
});
- // 提取数字部分以简化显示
- let current = req.current;
- let required = req.required;
-
- // 尝试从字符串中提取数字
- const currentMatch = req.current.match(/(\d+)/);
- const requiredMatch = req.required.match(/(\d+)/);
-
- if (currentMatch) current = currentMatch[1];
- if (requiredMatch) required = requiredMatch[1];
+ // For display, use the text directly from req.current and req.required (which are already strings)
+ // The numerical parsing for logic (currentValue) is done in parseTrustLevelData
+ // The createProgressBar function will also safely parse numbers from these strings.
+ let currentTextForDisplay = req.current;
+ let requiredTextForDisplay = req.required;
// 添加目标完成数变化的标识
let changeIndicator = '';
if (req.hasChanged) {
const diff = req.changeValue;
if (diff > 0) {
- changeIndicator = `
▲${diff}`;
+ changeIndicator = `
${LDStatus.config.icons.ARROW_UP}${diff}`;
} else if (diff < 0) {
- changeIndicator = `
▼${Math.abs(diff)}`;
+ changeIndicator = `
${LDStatus.config.icons.ARROW_DOWN}${Math.abs(diff)}`;
}
}
html += `
${name}
- ${current}${changeIndicator} / ${required}
+ ${currentTextForDisplay}${changeIndicator} / ${requiredTextForDisplay}
- ${LDStatus.ui.createProgressBar(current, required)}
+ ${LDStatus.ui.createProgressBar(req.current, req.required)}
`;
});
@@ -551,16 +575,16 @@
];
dailyStatsItems.forEach(item => {
- const data = dailyChanges[item.key] || { day1: 0, day2: 0, trend: 0 };
+ const data = dailyChanges[item.key] || { changeLast24h: 0, change24hTo48hAgo: 0, trend: 0 };
// 创建趋势指示器
let trendIndicator = '';
if (data.trend > 0) {
- trendIndicator = `
▲${Math.abs(data.trend)}`;
+ trendIndicator = `
${LDStatus.config.icons.TREND_INCREASE}${Math.abs(data.trend)}`;
} else if (data.trend < 0) {
- trendIndicator = `
▼${Math.abs(data.trend)}`;
+ trendIndicator = `
${LDStatus.config.icons.TREND_DECREASE}${Math.abs(data.trend)}`;
} else {
- trendIndicator = `
0`;
+ trendIndicator = `
${LDStatus.config.icons.TREND_STABLE}`;
}
html += `
@@ -568,8 +592,8 @@
${item.name}
- ${data.day2}
- ${data.day1}
+ ${data.change24hTo48hAgo}
+ ${data.changeLast24h}
${trendIndicator}
@@ -655,11 +679,16 @@
},
// 解析信任级别数据
+ // Purpose: Fetches HTML from the connect page, parses it to find trust level data,
+ // calculates changes from previously stored data (previousRequirements),
+ // and prepares the data object for rendering and caching.
parseTrustLevelData: function(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
- // 查找信任级别区块
+ // HTML Structure Assumption:
+ // Assumes the trust level data is within a div with classes '.bg-white.p-6.rounded-lg'.
+ // and contains a h2 element with text including '信任级别'.
const trustLevelSection = Array.from(doc.querySelectorAll('.bg-white.p-6.rounded-lg')).find(div => {
const heading = div.querySelector('h2');
return heading && heading.textContent.includes('信任级别');
@@ -671,67 +700,131 @@
}
// 获取用户名和当前级别
- const heading = trustLevelSection.querySelector('h2').textContent.trim();
- const match = heading.match(/(.*) - 信任级别 (\d+) 的要求/);
- const username = match ? match[1] : '未知用户';
- const targetLevel = match ? match[2] : '未知';
+ let username = '未知用户';
+ let targetLevel = '未知';
+ const headingElement = trustLevelSection.querySelector('h2');
+
+ if (headingElement && headingElement.textContent) {
+ const headingText = headingElement.textContent.trim();
+ const match = headingText.match(/(.*) - 信任级别 (\d+) 的要求/);
+ if (match && match[1] && match[2]) {
+ username = match[1];
+ targetLevel = match[2];
+ } else {
+ console.warn(`LDStatus: Could not parse username and targetLevel from heading: "${headingText}". Using defaults.`);
+ LDStatus.vars.content.innerHTML = '
数据格式错误(标题解析失败),请检查页面结构或脚本。
';
+ // Depending on how critical this is, you might return or allow proceeding with defaults.
+ }
+ } else {
+ console.warn('LDStatus: Trust level heading element not found or has no text content.');
+ LDStatus.vars.content.innerHTML = '
数据格式错误(未找到标题),请检查页面结构或脚本。
';
+ return; // Stop if critical heading info is missing
+ }
- // 获取表格数据
- const tableRows = trustLevelSection.querySelectorAll('table tr');
+ // HTML Structure Assumption:
+ // Assumes requirements are listed in a
within the trustLevelSection.
+ // Each requirement is a , with | elements for name, current progress, and required progress.
+ const tableElement = trustLevelSection.querySelector('table');
+ if (!tableElement) {
+ console.warn('LDStatus: Trust level table element not found.');
+ LDStatus.vars.content.innerHTML = ' 数据格式错误(未找到表格),请检查页面结构或脚本。 ';
+ return; // Stop if table is missing
+ }
+ const tableRows = tableElement.querySelectorAll('tr');
const requirements = [];
for (let i = 1; i < tableRows.length; i++) { // 跳过表头
const row = tableRows[i];
const cells = row.querySelectorAll('td');
- if (cells.length >= 3) {
+ // Ensure cells and their textContent are valid
+ if (cells.length >= 3 &&
+ cells[0] && cells[0].textContent &&
+ cells[1] && cells[1].textContent &&
+ cells[2] && cells[2].textContent) {
+
const name = cells[0].textContent.trim();
- const current = cells[1].textContent.trim();
- const required = cells[2].textContent.trim();
- const isSuccess = cells[1].classList.contains('text-green-500');
-
- // 提取当前完成数的数字部分
- const currentMatch = current.match(/(\d+)/);
- const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0;
- // 查找上一次的数据记录
+ const currentText = cells[1].textContent.trim(); // Keep original text for display
+ const requiredText = cells[2].textContent.trim(); // Keep original text for display
+ const isSuccess = cells[1].classList ? cells[1].classList.contains('text-green-500') : false;
+
+ // Robustly extract current value as a number for logic
+ let currentValue = 0;
+ const currentNumMatch = currentText.match(/(\d+)/);
+ if (currentNumMatch && currentNumMatch[0]) {
+ currentValue = parseInt(currentNumMatch[0], 10);
+ } else {
+ console.warn(`LDStatus: Could not parse current value number from: "${currentText}" for item "${name}"`);
+ }
+
+ // (Optional: Robustly extract required value as a number if needed for logic later)
+ // let requiredValue = 0;
+ // const requiredNumMatch = requiredText.match(/(\d+)/);
+ // if (requiredNumMatch && requiredNumMatch[0]) {
+ // requiredValue = parseInt(requiredNumMatch[0], 10);
+ // } else {
+ // console.warn(`LDStatus: Could not parse required value number from: "${requiredText}" for item "${name}"`);
+ // }
+
+ // `previousRequirements` Usage:
+ // `LDStatus.vars.previousRequirements` stores the full `requirements` array from the *last successful parse*.
+ // This allows comparison to detect changes in `currentValue` for each requirement.
let changeValue = 0;
let hasChanged = false;
if (LDStatus.vars.previousRequirements.length > 0) {
const prevReq = LDStatus.vars.previousRequirements.find(pr => pr.name === name);
if (prevReq) {
- // 如果完成数有变化,更新变化值
+ // If current numeric value differs from the previous one, calculate the new change.
if (currentValue !== prevReq.currentValue) {
changeValue = currentValue - prevReq.currentValue;
hasChanged = true;
} else if (prevReq.changeValue) {
- // 如果完成数没有变化,但之前有变化值,保留之前的变化值
+ // Persisted Change Logic:
+ // If current value matches previous, but previous had a change indicator (non-zero changeValue),
+ // persist that indicator (changeValue and hasChanged flag).
+ // This aligns with the feature: "Even if the value does not change after refresh, the indicator will be preserved."
changeValue = prevReq.changeValue;
hasChanged = true;
}
+ // If currentValue matches prevReq.currentValue AND prevReq.changeValue was 0,
+ // then changeValue remains 0 and hasChanged remains false (no new change, no persisted change).
}
}
requirements.push({
name,
- current,
- required,
+ current: currentText, // Use original text for display
+ required: requiredText, // Use original text for display
isSuccess,
- currentValue,
- changeValue, // 变化值
- hasChanged // 是否有变化
+ currentValue, // Parsed number for logic
+ changeValue,
+ hasChanged
});
+ } else {
+ console.warn(`LDStatus: Skipping table row at index ${i} due to insufficient cells or missing text content. Cells found: ${cells.length}.`);
}
}
// 获取总体结果
- const resultText = trustLevelSection.querySelector('p.text-red-500, p.text-green-500');
- const isMeetingRequirements = resultText ? !resultText.classList.contains('text-red-500') : false;
+ const resultTextElement = trustLevelSection.querySelector('p.text-red-500, p.text-green-500');
+ let isMeetingRequirements = false; // Default to false
+
+ if (resultTextElement && resultTextElement.classList) {
+ isMeetingRequirements = !resultTextElement.classList.contains('text-red-500');
+ } else {
+ console.warn('LDStatus: Result text (overall status) element not found or classList is not available. Defaulting to "not meeting requirements".');
+ // Optionally, inform the user in the panel if this is critical
+ // LDStatus.ui.showNotice("无法确定总体状态", "warn");
+ }
// 存储48小时内的数据变化
- const dailyChanges = this.saveDailyStats(requirements);
+ const dailyChanges = this.saveDailyStats(requirements); // This calculates and stores daily activity.
- // 缓存数据,以备网络问题时使用
+ // Data Caching:
+ // The full parsed data (including calculated changes and daily activity) is cached to GM_setValue.
+ // This allows the panel to display the last known data if the user is offline
+ // or if a subsequent fetch from connect.linux.do fails.
GM_setValue(LDStatus.config.storageKeys.lastData, {
username,
targetLevel,
@@ -744,34 +837,46 @@
// 渲染数据
LDStatus.ui.renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges);
- // 保存当前数据作为下次比较的基准
+ // `previousRequirements` Update:
+ // Save the current set of requirements (with their currentValues, changeValues, and hasChanged flags)
+ // to `LDStatus.vars.previousRequirements` for the next fetch cycle's comparison.
+ // A shallow copy is made to prevent modifications to the rendered data from affecting the next comparison.
LDStatus.vars.previousRequirements = [...requirements];
},
// 存储48小时内的数据变化
saveDailyStats: function(requirements) {
const statsToTrack = LDStatus.config.statsToTrack;
-
- // 获取当前时间
const now = new Date().getTime();
+ const twoDaysAgo = now - 48 * 60 * 60 * 1000;
- // 从 localStorage 中获取已存储的数据
- let dailyStats = JSON.parse(localStorage.getItem('ld_daily_stats') || '[]');
+ // 1. Load Existing Data
+ let allDailyStats = [];
+ try {
+ const storedStats = localStorage.getItem('ld_daily_stats');
+ if (storedStats) {
+ allDailyStats = JSON.parse(storedStats);
+ if (!Array.isArray(allDailyStats)) { // Basic validation
+ console.warn("LDStatus: ld_daily_stats from localStorage was not an array. Resetting.");
+ allDailyStats = [];
+ }
+ }
+ } catch (e) {
+ console.error("LDStatus: Error parsing ld_daily_stats from localStorage:", e);
+ allDailyStats = []; // Start fresh if parsing fails
+ }
- // 删除超过48小时的数据
- const twoDaysAgo = now - 48 * 60 * 60 * 1000;
- dailyStats = dailyStats.filter(item => item.timestamp > twoDaysAgo);
+ // 2. Filter Old Data
+ allDailyStats = allDailyStats.filter(item => item.timestamp > twoDaysAgo);
- // 对于每个要跟踪的数据项,找到当前值并添加到历史记录中
+ // 3. Add New Data
statsToTrack.forEach(statName => {
const req = requirements.find(r => r.name === statName);
if (req) {
- // 提取数字值
- const currentMatch = req.current.match(/(\d+)/);
- const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0;
+ // req.currentValue is already parsed as a number in parseTrustLevelData
+ const currentValue = req.currentValue;
- // 添加新的数据点
- dailyStats.push({
+ allDailyStats.push({
name: statName,
value: currentValue,
timestamp: now
@@ -779,13 +884,14 @@
}
});
- // 清理过量的历史数据
- this.cleanupStorage(dailyStats);
+ // 4. Clean Up/Limit (ensure cleanupStorage returns the cleaned array)
+ allDailyStats = this.cleanupStorage(allDailyStats); // cleanupStorage sorts and slices
- // 将更新后的数据保存回 localStorage
- localStorage.setItem('ld_daily_stats', JSON.stringify(dailyStats));
+ // 5. Save Data
+ localStorage.setItem('ld_daily_stats', JSON.stringify(allDailyStats));
- return this.calculateDailyChanges(dailyStats);
+ // Return the result of calculateDailyChanges based on the new allDailyStats
+ return this.calculateDailyChanges(allDailyStats);
},
// 清理过量的存储数据
@@ -793,63 +899,79 @@
const maxItems = LDStatus.config.maxStorageItems;
if (stats.length > maxItems) {
- // 按时间戳排序并只保留最新的数据
+ // 按时间戳排序并只保留最新的数据 (descending to keep newest)
stats.sort((a, b) => b.timestamp - a.timestamp);
- return stats.slice(0, maxItems);
+ return stats.slice(0, maxItems); // Important: it returns the sliced array
}
-
- return stats;
+ return stats; // Return the original array if not over limit
},
// 计算近两天内的变化量
+ // Purpose: Calculates the net change in tracked statistics over two distinct 24-hour periods:
+ // 1. The most recent 24 hours (Last 24h).
+ // 2. The 24-hour period immediately preceding the "Last 24h" (24h-48h Ago).
+ // It also calculates the 'trend', which is the difference between these two net changes.
+ // Input: dailyStats - Array of objects: { name: string, value: number, timestamp: number }
+ // Output: An object where keys are statNames and values are { changeLast24h, change24hTo48hAgo, trend }.
calculateDailyChanges: function(dailyStats) {
const statsToTrack = LDStatus.config.statsToTrack;
const result = {};
const now = new Date().getTime();
+
+ // Time Period Definitions:
+ // `oneDayAgo` marks the boundary between "Last 24h" and "24h-48h Ago".
+ // `twoDaysAgo` marks the older boundary for the "24h-48h Ago" period.
const oneDayAgo = now - 24 * 60 * 60 * 1000;
const twoDaysAgo = now - 48 * 60 * 60 * 1000;
- // 对于每个要跟踪的数据项,计算两天内的变化
statsToTrack.forEach(statName => {
- // 过滤出当前数据项的所有记录,并按时间戳排序
+ // Filter records for the current stat and sort them by timestamp (ascending).
+ // This makes it easier to find the earliest and latest records in a period.
const statRecords = dailyStats
.filter(item => item.name === statName)
.sort((a, b) => a.timestamp - b.timestamp);
- // 初始化结果对象结构
result[statName] = {
- day1: 0, // 最近24小时
- day2: 0, // 24-48小时
- trend: 0 // 趋势:day1 - day2
+ changeLast24h: 0, // Net change in value over the most recent 24 hours.
+ change24hTo48hAgo: 0, // Net change in value over the 24-hour period before the most recent one.
+ trend: 0 // Difference: changeLast24h - change24hTo48hAgo.
};
if (statRecords.length >= 2) {
- // 找出最新记录和其前面两个时间段的记录
+ // Record Identification:
+ // `newest`: The absolute latest record available for this statistic.
const newest = statRecords[statRecords.length - 1];
- // 找最近24小时内最早的记录
- const oldestDay1 = statRecords.filter(item => item.timestamp > oneDayAgo)[0];
+ // `oldestLast24h`: The earliest record that falls within the "Last 24h" window (timestamp > oneDayAgo).
+ // Used as the starting point to calculate change during the most recent 24 hours.
+ const oldestLast24h = statRecords.filter(item => item.timestamp > oneDayAgo)[0];
- // 找24-48小时内最早的记录和最新的记录
- const recordsDay2 = statRecords.filter(item =>
+ // Records for the "24h-48h Ago" period.
+ const records24hTo48hAgo = statRecords.filter(item =>
item.timestamp <= oneDayAgo && item.timestamp > twoDaysAgo);
- const oldestDay2 = recordsDay2.length > 0 ? recordsDay2[0] : null;
- const newestDay2 = recordsDay2.length > 0 ?
- recordsDay2[recordsDay2.length - 1] : null;
-
- // 计算最近24小时的变化
- if (oldestDay1) {
- result[statName].day1 = newest.value - oldestDay1.value;
+ // `oldest24hTo48hAgo`: Earliest record in the "24h-48h Ago" window.
+ // `newest24hTo48hAgo`: Latest record in the "24h-48h Ago" window.
+ // These are used to calculate the net change *within* that specific 24-hour slot.
+ const oldest24hTo48hAgo = records24hTo48hAgo.length > 0 ? records24hTo48hAgo[0] : null;
+ const newest24hTo48hAgo = records24hTo48hAgo.length > 0 ?
+ records24hTo48hAgo[records24hTo48hAgo.length - 1] : null;
+
+ // Change Calculation:
+ // `changeLast24h`: Net change from the start of the "Last 24h" period to the `newest` record.
+ if (oldestLast24h) {
+ result[statName].changeLast24h = newest.value - oldestLast24h.value;
}
- // 计算24-48小时的变化
- if (oldestDay2 && newestDay2) {
- result[statName].day2 = newestDay2.value - oldestDay2.value;
+ // `change24hTo48hAgo`: Net change from the start to the end of the "24h-48h Ago" period.
+ if (oldest24hTo48hAgo && newest24hTo48hAgo) {
+ result[statName].change24hTo48hAgo = newest24hTo48hAgo.value - oldest24hTo48hAgo.value;
}
- // 计算趋势(今天和昨天的变化差异)
- result[statName].trend = result[statName].day1 - result[statName].day2;
+ // `trend`: Difference between the net change in the last 24h and the net change in the 24h before that.
+ // A positive trend means activity is increasing more (or decreasing less) in the most recent 24h
+ // compared to the prior 24h period.
+ result[statName].trend = result[statName].changeLast24h - result[statName].change24hTo48hAgo;
}
});
@@ -862,7 +984,7 @@
const updateBtn = LDStatus.vars.updateBtn;
// 显示正在检查的状态
- updateBtn.textContent = '⌛'; // 沙漏图标
+ updateBtn.textContent = LDStatus.config.icons.LOADING;
updateBtn.title = '正在检查更新...';
GM_xmlhttpRequest({
@@ -879,7 +1001,7 @@
// 比较版本
if (remoteVersion > scriptVersion) {
// 有新版本
- updateBtn.textContent = '⚠️'; // 警告图标
+ updateBtn.textContent = LDStatus.config.icons.UPDATE_AVAILABLE;
updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`;
updateBtn.style.color = 'var(--ld-increase-color)'; // 黄色
@@ -889,42 +1011,40 @@
};
} else {
// 已是最新版本
- updateBtn.textContent = '✔'; // 勾选图标
- updateBtn.title = '已是最新版本';
+ updateBtn.textContent = LDStatus.config.icons.UP_TO_DATE;
+ updateBtn.title = '已是最新版本,点击再次检查'; // Updated title for clarity
updateBtn.style.color = 'var(--ld-success-color)'; // 绿色
-
- // 3秒后恢复原样式
- setTimeout(() => {
- updateBtn.textContent = '🔎'; // 放大镜图标
- updateBtn.title = '检查更新';
- updateBtn.style.color = '';
- updateBtn.onclick = LDStatus.events.onUpdateBtnClick;
- }, 3000);
+ // Ensure onclick allows re-checking
+ updateBtn.onclick = LDStatus.events.onUpdateBtnClick;
+ // Removed setTimeout to make the "up-to-date" state persistent
}
} else {
- LDStatus.data.handleUpdateError();
+ // This case means versionMatch failed, treat as an error in parsing response
+ console.warn("LDStatus: Could not parse version from update check response.");
+ LDStatus.data.handleUpdateError(); // Call existing error handler
}
} else {
+ // HTTP error
+ console.warn(`LDStatus: Update check HTTP error: ${response.status}`);
LDStatus.data.handleUpdateError();
}
},
- onerror: LDStatus.data.handleUpdateError
+ onerror: function(error) { // Network error
+ console.warn("LDStatus: Update check network error.", error);
+ LDStatus.data.handleUpdateError();
+ }
});
},
// 处理更新检查错误
handleUpdateError: function() {
const updateBtn = LDStatus.vars.updateBtn;
- updateBtn.textContent = '❌'; // 错误图标
- updateBtn.title = '检查更新失败,请稍后再试';
+ updateBtn.textContent = LDStatus.config.icons.ERROR;
+ updateBtn.title = '检查更新失败,点击再次检查'; // Updated title for clarity
updateBtn.style.color = 'var(--ld-fail-color)'; // 红色
-
- // 3秒后恢复原样式
- setTimeout(() => {
- updateBtn.textContent = '🔎'; // 放大镜图标
- updateBtn.title = '检查更新';
- updateBtn.style.color = '';
- }, 3000);
+ // Ensure onclick allows re-checking
+ updateBtn.onclick = LDStatus.events.onUpdateBtnClick;
+ // Removed setTimeout to make the error state persistent until user interaction
}
},
@@ -1003,7 +1123,9 @@
onToggleBtnClick: function() {
const panel = LDStatus.vars.panel;
panel.classList.toggle('ld-collapsed');
- LDStatus.vars.toggleBtn.textContent = panel.classList.contains('ld-collapsed') ? '▶' : '◀';
+ LDStatus.vars.toggleBtn.textContent = panel.classList.contains('ld-collapsed')
+ ? LDStatus.config.icons.EXPAND
+ : LDStatus.config.icons.COLLAPSE;
// 保存折叠状态
LDStatus.storage.savePanelCollapsedState();
@@ -1095,10 +1217,10 @@
if (isCollapsed) {
LDStatus.vars.panel.classList.add('ld-collapsed');
- LDStatus.vars.toggleBtn.textContent = '▶';
+ LDStatus.vars.toggleBtn.textContent = LDStatus.config.icons.EXPAND;
} else {
LDStatus.vars.panel.classList.remove('ld-collapsed');
- LDStatus.vars.toggleBtn.textContent = '◀';
+ LDStatus.vars.toggleBtn.textContent = LDStatus.config.icons.COLLAPSE;
}
}
},
|