├── README.md
├── deepwiki.js
├── image
├── deepwiki.png
└── linuxdo-level.png
└── linuxdo-level.js
/README.md:
--------------------------------------------------------------------------------
1 | # 油猴脚本
2 |
3 | ## 脚本列表
4 |
5 | 脚本名字 | 作用 | 效果展示
6 | ---|---|---
7 | [deepwiki](./deepwiki.js) | 在 GitHub 项目页添加 DeepWiki 跳转按钮,一键打开对应 DeepWiki 页面 | 
8 | [linux.do level](./linuxdo-level.js) | 进入 linux.do 没有登录注册按钮时,右侧显示等级浮窗,支持0-3级用户 | 
9 |
10 | ## 使用方法
11 |
12 | 1. 安装 [Tampermonkey](https://www.tampermonkey.net/) 浏览器插件。
13 | 2. 新建脚本,将 [xxx.js](./deepwiki.js) 的内容粘贴进去并保存。
14 | 3. 访问指定网页,即可看到效果
15 |
16 |
17 | ---
18 | 如需反馈或贡献,欢迎访问 [GitHub 仓库](https://github.com/quint11/tampermonkey-script.git)。
--------------------------------------------------------------------------------
/deepwiki.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name DeepWikiJump
3 | // @namespace https://github.com/quint11/tampermonkey-script.git
4 | // @version 1.0
5 | // @description Add a button to open the corresponding wiki page on DeepWiki
6 | // @author quintus
7 | // @icon https://www.google.com/s2/favicons?domain=github.com
8 | // @match https://github.com/*/*
9 | // @grant none
10 | // ==/UserScript==
11 |
12 | (function() {
13 | 'use strict';
14 |
15 | // 检查当前URL是否符合GitHub项目页面格式
16 | function isProjectPage() {
17 | const path = window.location.pathname;
18 | const parts = path.split('/').filter(part => part);
19 |
20 | // 检查是否至少有用户名和仓库名两部分
21 | return parts.length >= 2;
22 | }
23 |
24 | function addLinkToCurrentPage() {
25 | // 如果不是项目页面,则不执行
26 | if (!isProjectPage()) {
27 | return;
28 | }
29 |
30 | // 获取指定类名的元素
31 | const targetElement = document.querySelector('.UnderlineNav-body');
32 |
33 | // 检测GitHub界面版本并选择合适的元素
34 | function detectGitHubUIVersion() {
35 | // 检查是否为新版GitHub界面
36 | return document.querySelector('header[role="banner"]') !== null;
37 | }
38 |
39 | // 根据GitHub界面版本选择不同的选择器
40 | const isNewUI = detectGitHubUIVersion();
41 | const newUISelectors = [
42 | '.UnderlineNav-body',
43 | '.UnderlineNav ul',
44 | 'nav[aria-label="Repository"] ul'
45 | ];
46 |
47 | const oldUISelectors = [
48 | '.reponav',
49 | 'nav.js-repo-nav ul',
50 | 'ul.pagehead-actions'
51 | ];
52 |
53 | // 根据界面版本选择合适的选择器列表
54 | const selectorsToTry = isNewUI ? newUISelectors : oldUISelectors;
55 |
56 | // 尝试使用备选选择器
57 | let finalTargetElement = targetElement;
58 | if (!finalTargetElement) {
59 | for (const selector of selectorsToTry) {
60 | const element = document.querySelector(selector);
61 | if (element) {
62 | finalTargetElement = element;
63 | break;
64 | }
65 | }
66 | }
67 |
68 | if (!finalTargetElement) {
69 | return;
70 | }
71 |
72 | // 检查是否已经添加了DeepWiki按钮,避免重复添加
73 | const existingButton = document.querySelector('.deepwiki-button');
74 | if (existingButton) {
75 | return;
76 | }
77 |
78 | // 获取当前页面路径
79 | const path = window.location.pathname;
80 | const parts = path.split('/').filter(part => part);
81 |
82 | const username = parts[0];
83 | const repo = parts[1];
84 |
85 | // 创建按钮元素
86 | const wikiButton = document.createElement('a');
87 | // 增大按钮尺寸
88 | wikiButton.classList.add('btn', 'btn-lg', 'btn-outline', 'ml-2', 'deepwiki-button');
89 | // 使用蓝绿渐变背景
90 | wikiButton.style.background = 'linear-gradient(90deg, #2193b0 0%, #6dd5ed 100%)';
91 | // 改变按钮文字颜色
92 | wikiButton.style.color = 'white';
93 | // 添加阴影效果
94 | wikiButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
95 | wikiButton.href = `https://deepwiki.com/${username}/${repo}`;
96 | wikiButton.target = '_blank';
97 | wikiButton.title = 'Open Wiki on DeepWiki';
98 | // 添加 alt 属性
99 | wikiButton.alt = 'Open Wiki on DeepWiki';
100 | // 添加 cursor 属性
101 | wikiButton.style.cursor = 'pointer';
102 | // 设置按钮文字
103 | wikiButton.textContent = 'DeepWiki';
104 |
105 | // 添加悬停动画
106 | wikiButton.style.transition = 'all 0.3s ease';
107 | wikiButton.addEventListener('mouseover', function() {
108 | this.style.transform = 'scale(1.1)';
109 | this.style.boxShadow = '0 6px 12px rgba(0, 0, 0, 0.3)';
110 | });
111 | wikiButton.addEventListener('mouseout', function() {
112 | this.style.transform = 'scale(1)';
113 | this.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
114 | });
115 |
116 | // 创建列表项元素并将按钮添加到其中
117 | const listItem = document.createElement('li');
118 | listItem.appendChild(wikiButton);
119 |
120 | // 将列表项添加到目标元素的最后
121 | try {
122 | finalTargetElement.appendChild(listItem);
123 | } catch (error) {
124 | // 静默处理错误
125 | }
126 | }
127 |
128 | // 使用MutationObserver监听DOM变化
129 | function setupObserver() {
130 | // 如果不是项目页面,则不设置观察器
131 | if (!isProjectPage()) {
132 | return;
133 | }
134 |
135 | // 创建一个观察器实例
136 | const observer = new MutationObserver(function() {
137 | // 当DOM变化时尝试添加按钮
138 | addLinkToCurrentPage();
139 | });
140 |
141 | // 配置观察选项
142 | const config = { childList: true, subtree: true };
143 |
144 | // 开始观察document.body的变化
145 | observer.observe(document.body, config);
146 |
147 | // 页面加载完成后也尝试添加按钮
148 | addLinkToCurrentPage();
149 | }
150 |
151 | // 监听URL变化(GitHub是SPA应用,页面跳转不会重新加载页面)
152 | function setupURLChangeListener() {
153 | let lastURL = location.href;
154 |
155 | // 创建一个新的MutationObserver来监听URL变化
156 | const urlObserver = new MutationObserver(() => {
157 | if (location.href !== lastURL) {
158 | lastURL = location.href;
159 | // URL变化后,如果是项目页面则设置观察器
160 | if (isProjectPage()) {
161 | setupObserver();
162 | }
163 | }
164 | });
165 |
166 | // 配置观察选项
167 | urlObserver.observe(document, { subtree: true, childList: true });
168 | }
169 |
170 | // 添加延迟重试机制
171 | function retryAddButton() {
172 | // 延迟1秒后再次尝试添加按钮
173 | setTimeout(() => {
174 | addLinkToCurrentPage();
175 |
176 | // 如果仍然没有找到按钮,再次延迟尝试
177 | if (!document.querySelector('.deepwiki-button')) {
178 | setTimeout(addLinkToCurrentPage, 2000);
179 | }
180 | }, 1000);
181 | }
182 |
183 | // 页面加载完成后设置观察器
184 | window.addEventListener('load', () => {
185 | setupObserver();
186 | setupURLChangeListener();
187 | retryAddButton(); // 添加延迟重试
188 | });
189 |
190 | // 对于已经加载完成的页面,立即设置观察器
191 | if (document.readyState === 'complete' || document.readyState === 'interactive') {
192 | setupObserver();
193 | setupURLChangeListener();
194 | retryAddButton(); // 对于已加载页面也使用延迟重试
195 | }
196 | })();
--------------------------------------------------------------------------------
/image/deepwiki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quint11/tampermonkey-script/6c61597095639a7df4ea1a8e2d6bad616e8410d2/image/deepwiki.png
--------------------------------------------------------------------------------
/image/linuxdo-level.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quint11/tampermonkey-script/6c61597095639a7df4ea1a8e2d6bad616e8410d2/image/linuxdo-level.png
--------------------------------------------------------------------------------
/linuxdo-level.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name linux.do 等级监控浮窗
3 | // @namespace http://tampermonkey.net/
4 | // @version 2.2
5 | // @description 进入 linux.do 没有登录注册按钮时,右侧显示等级浮窗,支持0-3级用户
6 | // @author quintus
7 | // @match https://linux.do/*
8 | // @grant GM_xmlhttpRequest
9 | // @grant GM_addStyle
10 | // @grant GM_setValue
11 | // @grant GM_getValue
12 | // @grant GM_log
13 | // @connect connect.linux.do
14 | // @connect linux.do
15 | // @connect *
16 | // @run-at document-end
17 | // ==/UserScript==
18 |
19 | (function() {
20 | 'use strict';
21 |
22 | // 存储数据的键名
23 | const STORAGE_KEY = 'linux_do_user_trust_level_data_v3';
24 | const LAST_CHECK_KEY = 'linux_do_last_check_v3';
25 |
26 | // 0级和1级用户的升级要求
27 | const LEVEL_REQUIREMENTS = {
28 | 0: { // 0级升1级
29 | topics_entered: 5,
30 | posts_read_count: 30,
31 | time_read: 600 // 10分钟 = 600秒
32 | },
33 | 1: { // 1级升2级
34 | days_visited: 15,
35 | likes_given: 1,
36 | likes_received: 1,
37 | replies_to_different_topics: 3, // 特殊字段,需要单独获取
38 | topics_entered: 20,
39 | posts_read_count: 100,
40 | time_read: 3600 // 60分钟 = 3600秒
41 | }
42 | };
43 |
44 | // 直接在页面上添加调试浮窗
45 | const debugDiv = document.createElement('div');
46 | debugDiv.style.position = 'fixed';
47 | debugDiv.style.bottom = '10px';
48 | debugDiv.style.right = '10px';
49 | debugDiv.style.width = '300px';
50 | debugDiv.style.maxHeight = '200px';
51 | debugDiv.style.overflow = 'auto';
52 | debugDiv.style.background = 'rgba(0,0,0,0.8)';
53 | debugDiv.style.color = '#0f0';
54 | debugDiv.style.padding = '10px';
55 | debugDiv.style.borderRadius = '5px';
56 | debugDiv.style.zIndex = '10000';
57 | debugDiv.style.fontFamily = 'monospace';
58 | debugDiv.style.fontSize = '12px';
59 | debugDiv.style.display = 'none'; // 默认隐藏
60 | document.body.appendChild(debugDiv);
61 |
62 | // 调试函数
63 | function debugLog(message) {
64 | const time = new Date().toLocaleTimeString();
65 | console.log(`[Linux.do脚本] ${message}`);
66 | GM_log(`[Linux.do脚本] ${message}`);
67 |
68 | const logLine = document.createElement('div');
69 | logLine.textContent = `${time}: ${message}`;
70 | debugDiv.appendChild(logLine);
71 | debugDiv.scrollTop = debugDiv.scrollHeight;
72 | }
73 |
74 | // 按Alt+D显示/隐藏调试窗口
75 | document.addEventListener('keydown', function(e) {
76 | if (e.altKey && e.key === 'd') {
77 | debugDiv.style.display = debugDiv.style.display === 'none' ? 'block' : 'none';
78 | }
79 | });
80 |
81 | debugLog('脚本开始执行');
82 |
83 | // 添加全局样式 - 全新设计
84 | GM_addStyle(`
85 | /* 新的悬浮按钮样式 */
86 | .ld-floating-container {
87 | position: fixed;
88 | top: 50%;
89 | right: 0;
90 | transform: translateY(-50%);
91 | z-index: 9999;
92 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
93 | }
94 |
95 | .ld-floating-btn {
96 | background: white;
97 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
98 | border: 1px solid #e5e7eb;
99 | border-radius: 8px 0 0 8px;
100 | border-right: none;
101 | transition: all 0.3s ease;
102 | cursor: pointer;
103 | width: 48px;
104 | padding: 12px 0;
105 | display: flex;
106 | flex-direction: column;
107 | align-items: center;
108 | gap: 4px;
109 | user-select: none;
110 | }
111 |
112 | .ld-floating-btn:hover {
113 | width: 64px;
114 | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
115 | }
116 |
117 | .ld-btn-icon {
118 | width: 16px;
119 | height: 16px;
120 | color: #6b7280;
121 | }
122 |
123 | .ld-btn-level {
124 | font-size: 12px;
125 | font-weight: bold;
126 | color: #ea580c;
127 | }
128 |
129 | .ld-btn-progress-bar {
130 | width: 32px;
131 | height: 4px;
132 | background: #e5e7eb;
133 | border-radius: 2px;
134 | overflow: hidden;
135 | }
136 |
137 | .ld-btn-progress-fill {
138 | height: 100%;
139 | background: #ea580c;
140 | border-radius: 2px;
141 | transition: width 0.3s ease;
142 | }
143 |
144 | .ld-btn-stats {
145 | font-size: 10px;
146 | color: #6b7280;
147 | }
148 |
149 | .ld-btn-chevron {
150 | width: 12px;
151 | height: 12px;
152 | color: #9ca3af;
153 | opacity: 0;
154 | transition: opacity 0.3s ease;
155 | }
156 |
157 | .ld-floating-btn:hover .ld-btn-chevron {
158 | opacity: 1;
159 | animation: pulse 1s infinite;
160 | }
161 |
162 | @keyframes pulse {
163 | 0%, 100% { opacity: 1; }
164 | 50% { opacity: 0.5; }
165 | }
166 |
167 | /* 弹出窗口样式 */
168 | .ld-popup {
169 | position: absolute;
170 | top: 50%;
171 | right: 100%;
172 | margin-right: 8px;
173 | width: 384px;
174 | max-height: 80vh;
175 | background: white;
176 | border-radius: 12px;
177 | box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
178 | border: 1px solid #e5e7eb;
179 | opacity: 0;
180 | transform: translate(20px, -50%);
181 | transition: all 0.2s ease;
182 | pointer-events: none;
183 | overflow: hidden;
184 | overflow-y: auto;
185 | }
186 |
187 | .ld-popup.show {
188 | opacity: 1;
189 | transform: translate(0, -50%);
190 | pointer-events: auto;
191 | }
192 |
193 | /* 当弹出窗口可能超出屏幕时的调整 */
194 | .ld-popup.adjust-top {
195 | top: 10px;
196 | max-height: calc(100vh - 20px);
197 | transform: translate(20px, 0);
198 | }
199 |
200 | .ld-popup.adjust-top.show {
201 | transform: translate(0, 0);
202 | }
203 |
204 | .ld-popup.adjust-bottom {
205 | top: auto;
206 | bottom: 10px;
207 | max-height: calc(100vh - 20px);
208 | transform: translate(20px, 0);
209 | }
210 |
211 | .ld-popup.adjust-bottom.show {
212 | transform: translate(0, 0);
213 | }
214 |
215 | /* Header 样式 */
216 | .ld-popup-header {
217 | padding: 16px;
218 | border-bottom: 1px solid #f3f4f6;
219 | }
220 |
221 | .ld-header-top {
222 | display: flex;
223 | align-items: center;
224 | justify-content: space-between;
225 | margin-bottom: 8px;
226 | }
227 |
228 | .ld-user-info {
229 | display: flex;
230 | align-items: center;
231 | gap: 8px;
232 | }
233 |
234 | .ld-user-dot {
235 | width: 12px;
236 | height: 12px;
237 | background: #ea580c;
238 | border-radius: 50%;
239 | }
240 |
241 | .ld-user-name {
242 | font-size: 14px;
243 | font-weight: 500;
244 | color: #374151;
245 | }
246 |
247 | .ld-level-badge {
248 | font-size: 12px;
249 | background: #fed7aa;
250 | color: #c2410c;
251 | padding: 4px 8px;
252 | border-radius: 9999px;
253 | }
254 |
255 | .ld-progress-section {
256 | margin-top: 12px;
257 | }
258 |
259 | .ld-progress-header {
260 | display: flex;
261 | justify-content: space-between;
262 | align-items: center;
263 | margin-bottom: 4px;
264 | }
265 |
266 | .ld-progress-label {
267 | font-size: 12px;
268 | color: #6b7280;
269 | }
270 |
271 | .ld-progress-stats {
272 | font-size: 12px;
273 | color: #4b5563;
274 | }
275 |
276 | .ld-progress-bar-container {
277 | width: 100%;
278 | height: 8px;
279 | background: #e5e7eb;
280 | border-radius: 4px;
281 | overflow: hidden;
282 | }
283 |
284 | .ld-progress-bar {
285 | height: 100%;
286 | background: linear-gradient(90deg, #fb923c, #ea580c);
287 | border-radius: 4px;
288 | transition: width 0.3s ease;
289 | }
290 |
291 | /* 快速状态卡片 */
292 | .ld-status-cards {
293 | padding: 16px;
294 | display: grid;
295 | grid-template-columns: 1fr 1fr;
296 | gap: 12px;
297 | }
298 |
299 | .ld-status-card {
300 | border-radius: 8px;
301 | padding: 8px;
302 | }
303 |
304 | .ld-status-card.failed {
305 | background: #fef2f2;
306 | }
307 |
308 | .ld-status-card.passed {
309 | background: #f0fdf4;
310 | }
311 |
312 | .ld-card-header {
313 | display: flex;
314 | align-items: center;
315 | gap: 4px;
316 | margin-bottom: 4px;
317 | }
318 |
319 | .ld-card-icon {
320 | width: 12px;
321 | height: 12px;
322 | }
323 |
324 | .ld-card-header.failed {
325 | color: #dc2626;
326 | }
327 |
328 | .ld-card-header.passed {
329 | color: #16a34a;
330 | }
331 |
332 | .ld-card-title {
333 | font-size: 12px;
334 | font-weight: 500;
335 | }
336 |
337 | .ld-card-label {
338 | font-size: 12px;
339 | color: #4b5563;
340 | }
341 |
342 | .ld-card-value {
343 | font-size: 14px;
344 | font-weight: 500;
345 | color: #1f2937;
346 | }
347 |
348 | .ld-card-subtitle {
349 | font-size: 12px;
350 | margin-top: 2px;
351 | }
352 |
353 | .ld-card-subtitle.failed {
354 | color: #dc2626;
355 | }
356 |
357 | .ld-card-subtitle.passed {
358 | color: #16a34a;
359 | }
360 |
361 | /* 详细列表 */
362 | .ld-details-section {
363 | border-top: 1px solid #f3f4f6;
364 | }
365 |
366 | .ld-details-list {
367 | padding: 12px;
368 | max-height: 256px;
369 | overflow-y: auto;
370 | }
371 |
372 | .ld-detail-item {
373 | display: flex;
374 | align-items: center;
375 | justify-content: space-between;
376 | padding: 4px 8px;
377 | border-radius: 4px;
378 | transition: background 0.2s ease;
379 | }
380 |
381 | .ld-detail-item:hover {
382 | background: #f9fafb;
383 | }
384 |
385 | .ld-detail-left {
386 | display: flex;
387 | align-items: center;
388 | gap: 8px;
389 | flex: 1;
390 | min-width: 0;
391 | }
392 |
393 | .ld-detail-icon {
394 | width: 12px;
395 | height: 12px;
396 | color: #9ca3af;
397 | flex-shrink: 0;
398 | }
399 |
400 | .ld-detail-label {
401 | font-size: 12px;
402 | color: #4b5563;
403 | white-space: nowrap;
404 | overflow: hidden;
405 | text-overflow: ellipsis;
406 | }
407 |
408 | .ld-detail-right {
409 | display: flex;
410 | align-items: center;
411 | gap: 12px;
412 | flex-shrink: 0;
413 | }
414 |
415 | .ld-detail-current {
416 | font-size: 12px;
417 | font-weight: 500;
418 | color: #1f2937;
419 | text-align: right;
420 | }
421 |
422 | .ld-detail-target {
423 | font-size: 12px;
424 | color: #9ca3af;
425 | text-align: right;
426 | }
427 |
428 | .ld-detail-status {
429 | width: 12px;
430 | height: 12px;
431 | }
432 |
433 | .ld-detail-status.passed {
434 | color: #16a34a;
435 | }
436 |
437 | .ld-detail-status.failed {
438 | color: #dc2626;
439 | }
440 |
441 | /* Footer */
442 | .ld-popup-footer {
443 | padding: 12px;
444 | background: #f9fafb;
445 | border-top: 1px solid #f3f4f6;
446 | text-align: center;
447 | }
448 |
449 | .ld-footer-message {
450 | font-size: 12px;
451 | font-weight: 500;
452 | margin-bottom: 4px;
453 | }
454 |
455 | .ld-footer-message.failed {
456 | color: #dc2626;
457 | }
458 |
459 | .ld-footer-message.passed {
460 | color: #16a34a;
461 | }
462 |
463 | .ld-footer-time {
464 | font-size: 12px;
465 | color: #6b7280;
466 | }
467 |
468 | /* 刷新按钮 */
469 | .ld-reload-btn {
470 | display: block;
471 | width: calc(100% - 24px);
472 | margin: 0 12px 12px;
473 | padding: 8px;
474 | background: #f3f4f6;
475 | color: #374151;
476 | border: none;
477 | border-radius: 6px;
478 | font-weight: 500;
479 | cursor: pointer;
480 | transition: background 0.2s;
481 | font-size: 12px;
482 | }
483 |
484 | .ld-reload-btn:hover {
485 | background: #e5e7eb;
486 | }
487 |
488 | .ld-reload-btn:disabled {
489 | opacity: 0.5;
490 | cursor: not-allowed;
491 | }
492 |
493 | /* 错误状态 */
494 | .ld-error-container {
495 | padding: 24px;
496 | text-align: center;
497 | color: #6b7280;
498 | }
499 |
500 | .ld-error-icon {
501 | font-size: 24px;
502 | color: #dc2626;
503 | margin-bottom: 12px;
504 | }
505 |
506 | .ld-error-title {
507 | font-weight: 500;
508 | margin-bottom: 8px;
509 | color: #dc2626;
510 | font-size: 14px;
511 | }
512 |
513 | .ld-error-message {
514 | margin-bottom: 16px;
515 | font-size: 12px;
516 | line-height: 1.5;
517 | }
518 |
519 | /* 隐藏的iframe */
520 | .ld-hidden-iframe {
521 | position: absolute;
522 | width: 0;
523 | height: 0;
524 | border: 0;
525 | visibility: hidden;
526 | }
527 |
528 | /* 响应式调整 */
529 | @media (max-height: 600px) {
530 | .ld-details-list {
531 | max-height: 200px;
532 | }
533 | }
534 | `);
535 |
536 | // 工具函数:根据XPath查找元素
537 | function getElementByXpath(xpath) {
538 | return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
539 | }
540 |
541 | // 检查是否有注册和登录按钮
542 | const loginBtnXpath = '//*[@id="ember3"]/div[2]/header/div/div/div[3]/span/span';
543 | const loginBtn = getElementByXpath(loginBtnXpath);
544 |
545 | debugLog('检查登录按钮: ' + (loginBtn ? '存在' : '不存在'));
546 |
547 | if (loginBtn) {
548 | // 有登录注册按钮,不执行后续逻辑
549 | debugLog('已检测到登录按钮,不显示等级浮窗');
550 | return;
551 | }
552 |
553 | // 尝试从缓存获取数据
554 | const cachedData = GM_getValue(STORAGE_KEY);
555 | const lastCheck = GM_getValue(LAST_CHECK_KEY, 0);
556 | const now = Date.now();
557 | const oneHourMs = 60 * 60 * 1000; // 一小时的毫秒数
558 |
559 | debugLog(`上次检查时间: ${new Date(lastCheck).toLocaleString()}`);
560 |
561 | // 创建右侧悬浮按钮容器
562 | const container = document.createElement('div');
563 | container.className = 'ld-floating-container';
564 |
565 | // 创建悬浮按钮
566 | const btn = document.createElement('div');
567 | btn.className = 'ld-floating-btn';
568 | btn.innerHTML = `
569 |
572 |
L?
573 |
576 | 0/0
577 |
580 | `;
581 |
582 | // 创建浮窗
583 | const popup = document.createElement('div');
584 | popup.className = 'ld-popup';
585 |
586 | // 设置默认内容
587 | popup.innerHTML = `
588 |
606 |
630 | `;
631 |
632 | // 添加到容器
633 | container.appendChild(btn);
634 | container.appendChild(popup);
635 |
636 | // 变量用于跟踪悬停状态
637 | let isHovered = false;
638 | let hoverTimeout = null;
639 |
640 | // 智能调整弹出窗口位置的函数
641 | function adjustPopupPosition() {
642 | const containerRect = container.getBoundingClientRect();
643 | const viewportHeight = window.innerHeight;
644 |
645 | // 移除之前的调整类
646 | popup.classList.remove('adjust-top', 'adjust-bottom');
647 |
648 | // 强制重新计算布局
649 | popup.offsetHeight;
650 |
651 | // 获取弹出窗口的实际高度
652 | const popupHeight = popup.scrollHeight;
653 | const margin = 20; // 上下边距
654 |
655 | // 计算弹出窗口的理想位置(居中对齐按钮)
656 | const buttonCenterY = containerRect.top + containerRect.height / 2;
657 | const idealTop = buttonCenterY - popupHeight / 2;
658 | const idealBottom = idealTop + popupHeight;
659 |
660 | debugLog(`视口高度: ${viewportHeight}, 弹窗高度: ${popupHeight}, 按钮中心Y: ${buttonCenterY}`);
661 | debugLog(`理想顶部: ${idealTop}, 理想底部: ${idealBottom}`);
662 |
663 | // 检查是否超出屏幕顶部
664 | if (idealTop < margin) {
665 | popup.classList.add('adjust-top');
666 | debugLog('弹出窗口调整到顶部对齐');
667 | }
668 | // 检查是否超出屏幕底部
669 | else if (idealBottom > viewportHeight - margin) {
670 | popup.classList.add('adjust-bottom');
671 | debugLog('弹出窗口调整到底部对齐');
672 | }
673 | // 否则使用居中对齐(默认)
674 | else {
675 | debugLog('弹出窗口使用居中对齐');
676 | }
677 | }
678 |
679 | // 鼠标进入容器时
680 | container.addEventListener('mouseenter', () => {
681 | clearTimeout(hoverTimeout);
682 | isHovered = true;
683 | hoverTimeout = setTimeout(() => {
684 | if (isHovered) {
685 | // 调整位置
686 | adjustPopupPosition();
687 |
688 | // 显示弹出窗口
689 | popup.classList.add('show');
690 | }
691 | }, 150); // 稍微延迟显示,避免误触
692 | });
693 |
694 | // 鼠标离开容器时
695 | container.addEventListener('mouseleave', () => {
696 | clearTimeout(hoverTimeout);
697 | isHovered = false;
698 | hoverTimeout = setTimeout(() => {
699 | if (!isHovered) {
700 | popup.classList.remove('show');
701 | }
702 | }, 100); // 稍微延迟隐藏,允许鼠标在按钮和弹窗间移动
703 | });
704 |
705 | // 监听窗口大小变化,重新调整位置
706 | window.addEventListener('resize', () => {
707 | if (popup.classList.contains('show')) {
708 | adjustPopupPosition();
709 | }
710 | });
711 |
712 | document.body.appendChild(container);
713 |
714 | debugLog('新版按钮和浮窗已添加到页面');
715 |
716 | // 如果有缓存数据且时间不超过一小时,直接使用缓存
717 | if (cachedData && (now - lastCheck < oneHourMs)) {
718 | debugLog('使用缓存数据');
719 | updateInfo(
720 | cachedData.username,
721 | cachedData.currentLevel,
722 | cachedData.targetLevel,
723 | cachedData.trustLevelDetails,
724 | new Date(lastCheck),
725 | cachedData.originalHtml || '',
726 | true // isFromCache
727 | );
728 | } else {
729 | debugLog('缓存过期或不存在,准备安排获取新数据');
730 | // 延迟后再执行,给页面一点时间稳定
731 | const delay = 3000; // Increased delay to 3 seconds
732 | debugLog(`将在 ${delay / 1000} 秒后尝试获取数据...`);
733 | setTimeout(() => {
734 | debugLog('Timeout结束,准备调用 fetchDataWithGM');
735 | fetchDataWithGM();
736 | }, delay);
737 | }
738 |
739 | // 解析信任级别详情
740 | function parseTrustLevelDetails(targetInfoDivElement) {
741 | const details = {
742 | items: [],
743 | summaryText: '',
744 | achievedCount: 0,
745 | totalCount: 0,
746 | targetLevelInSummary: null // 从 "不符合信任级别 X 要求" 中提取
747 | };
748 |
749 | if (!targetInfoDivElement) {
750 | debugLog('parseTrustLevelDetails: targetInfoDivElement为空');
751 | return details;
752 | }
753 |
754 | // 解析表格
755 | const table = targetInfoDivElement.querySelector('table');
756 | if (table) {
757 | const rows = table.querySelectorAll('tbody tr');
758 | rows.forEach((row, index) => {
759 | if (index === 0) return; // 跳过表头行
760 |
761 | const cells = row.querySelectorAll('td');
762 | if (cells.length >= 3) {
763 | const label = cells[0].textContent.trim();
764 | const currentText = cells[1].textContent.trim();
765 | const requiredText = cells[2].textContent.trim();
766 | const isMet = cells[1].classList.contains('text-green-500');
767 |
768 | details.items.push({
769 | label: label,
770 | current: currentText,
771 | required: requiredText,
772 | isMet: isMet
773 | });
774 |
775 | if (isMet) {
776 | details.achievedCount++;
777 | }
778 | }
779 | });
780 | details.totalCount = details.items.length;
781 | } else {
782 | debugLog('parseTrustLevelDetails: 未找到表格');
783 | }
784 |
785 | // 解析总结文本,例如 "不符合信任级别 3 要求,继续加油。"
786 | const paragraphs = targetInfoDivElement.querySelectorAll('p');
787 | paragraphs.forEach(p => {
788 | const text = p.textContent.trim();
789 | if (text.includes('要求') || text.includes('已满足') || text.includes('信任级别')) {
790 | details.summaryText = text;
791 | const levelMatch = text.match(/信任级别\s*(\d+)/);
792 | if (levelMatch) {
793 | details.targetLevelInSummary = levelMatch[1];
794 | }
795 | }
796 | });
797 | if (!details.summaryText) {
798 | debugLog('parseTrustLevelDetails: 未找到总结文本段落');
799 | }
800 |
801 | debugLog(`parseTrustLevelDetails: 解析完成, ${details.achievedCount}/${details.totalCount} 项达标. 总结: ${details.summaryText}. 目标等级从总结文本: ${details.targetLevelInSummary}`);
802 | return details;
803 | }
804 |
805 | // 使用 GM_xmlhttpRequest 获取 connect.linux.do 的信息
806 | function fetchDataWithGM() {
807 | debugLog('进入 fetchDataWithGM 函数,准备发起 GM_xmlhttpRequest');
808 | try {
809 | GM_xmlhttpRequest({
810 | method: "GET",
811 | url: "https://connect.linux.do/",
812 | timeout: 15000, // 15秒超时
813 | onload: function(response) {
814 | debugLog(`GM_xmlhttpRequest 成功: status ${response.status}`);
815 | if (response.status === 200) {
816 | const responseText = response.responseText;
817 | debugLog(`GM_xmlhttpRequest 响应状态 200,准备解析HTML。响应体长度: ${responseText.length}`);
818 |
819 | const tempDiv = document.createElement('div');
820 | tempDiv.innerHTML = responseText;
821 |
822 | // 1. 解析全局用户名和当前等级 (从 )
823 | let globalUsername = '用户';
824 | let currentLevel = '未知';
825 | const h1 = tempDiv.querySelector('h1');
826 | if (h1) {
827 | const h1Text = h1.textContent.trim();
828 | // 例如: "你好,一剑万生 (YY_WD) 2级用户" 或 "你好, (yy2025) 0级用户"
829 | const welcomeMatch = h1Text.match(/你好,\s*([^(\s]*)\s*\(?([^)]*)\)?\s*(\d+)级用户/i);
830 | if (welcomeMatch) {
831 | // 优先使用括号内的用户名,如果没有则使用前面的
832 | globalUsername = welcomeMatch[2] || welcomeMatch[1] || '用户';
833 | currentLevel = welcomeMatch[3];
834 | debugLog(`从解析: 全局用户名='${globalUsername}', 当前等级='${currentLevel}'`);
835 | } else {
836 | debugLog(`从解析: 未匹配到欢迎信息格式: "${h1Text}"`);
837 | }
838 | } else {
839 | debugLog('未在响应中找到 标签');
840 | }
841 |
842 | // 检查用户等级,决定使用哪种数据获取方式
843 | const userLevel = parseInt(currentLevel);
844 | if (userLevel === 0 || userLevel === 1) {
845 | debugLog(`检测到${userLevel}级用户,使用summary.json获取数据`);
846 | fetchLowLevelUserData(globalUsername, userLevel);
847 | } else if (userLevel >= 2) {
848 | debugLog(`检测到${userLevel}级用户,使用connect.linux.do页面数据`);
849 | // 继续原有逻辑处理2级及以上用户
850 | processHighLevelUserData(tempDiv, globalUsername, currentLevel);
851 | } else {
852 | debugLog('无法确定用户等级,显示错误');
853 | showError('无法确定用户等级,请检查登录状态');
854 | }
855 |
856 | } else {
857 | debugLog(`请求失败,状态码: ${response.status} - ${response.statusText}`);
858 | handleRequestError(response);
859 | }
860 | },
861 | onerror: function(error) {
862 | debugLog(`GM_xmlhttpRequest 错误: ${JSON.stringify(error)}`);
863 | showError('网络请求错误,请检查连接和油猴插件权限');
864 | },
865 | ontimeout: function() {
866 | debugLog('GM_xmlhttpRequest 超时');
867 | showError('请求超时,请检查网络连接');
868 | },
869 | onabort: function() {
870 | debugLog('GM_xmlhttpRequest 请求被中止 (onabort)');
871 | showError('请求被中止,可能是网络问题或扩展冲突');
872 | }
873 | });
874 | debugLog('GM_xmlhttpRequest 已调用,等待回调');
875 | } catch (e) {
876 | debugLog(`调用 GM_xmlhttpRequest 时发生同步错误: ${e.message}`);
877 | showError('调用请求时出错,请查看日志');
878 | }
879 | }
880 |
881 | // 将数据保存到缓存
882 | function saveDataToCache(username, currentLevel, targetLevel, trustLevelDetails, originalHtml) {
883 | debugLog('保存数据到缓存');
884 | const dataToCache = {
885 | username,
886 | currentLevel,
887 | targetLevel,
888 | trustLevelDetails,
889 | originalHtml,
890 | cacheTimestamp: Date.now() // 添加一个缓存内的时间戳,方便调试
891 | };
892 | GM_setValue(STORAGE_KEY, dataToCache);
893 | GM_setValue(LAST_CHECK_KEY, Date.now());
894 | }
895 |
896 | // 更新信息显示
897 | function updateInfo(username, currentLevel, targetLevel, trustLevelDetails, updateTime, originalHtml, isFromCache = false) {
898 | debugLog(`更新信息: 用户='${username}', 当前L=${currentLevel}, 目标L=${targetLevel}, 详情获取=${trustLevelDetails && trustLevelDetails.items.length > 0}, 更新时间=${updateTime.toLocaleString()}`);
899 |
900 | // 计算进度
901 | const achievedCount = trustLevelDetails ? trustLevelDetails.achievedCount : 0;
902 | const totalCount = trustLevelDetails ? trustLevelDetails.totalCount : 0;
903 | const progressPercent = totalCount > 0 ? Math.round((achievedCount / totalCount) * 100) : 0;
904 |
905 | // 更新按钮显示
906 | const levelElement = btn.querySelector('.ld-btn-level');
907 | const progressFill = btn.querySelector('.ld-btn-progress-fill');
908 | const statsElement = btn.querySelector('.ld-btn-stats');
909 |
910 | if (levelElement) levelElement.textContent = `L${currentLevel || '?'}`;
911 | if (progressFill) progressFill.style.width = `${progressPercent}%`;
912 | if (statsElement) statsElement.textContent = `${achievedCount}/${totalCount}`;
913 |
914 | // 更新浮窗内容
915 | updatePopupContent(username, currentLevel, targetLevel, trustLevelDetails, updateTime, originalHtml, isFromCache);
916 | }
917 |
918 | // 更新浮窗内容 - 适配新UI结构
919 | function updatePopupContent(username, currentLevel, targetLevel, trustLevelDetails, updateTime, originalHtml, isFromCache = false) {
920 | // 如果加载失败或无数据,显示错误状态
921 | if (!trustLevelDetails || !trustLevelDetails.items || trustLevelDetails.items.length === 0) {
922 | showPopupError('无法加载数据', '未能获取到信任级别详情数据,请刷新重试。', updateTime);
923 | return;
924 | }
925 |
926 | // 计算进度
927 | const achievedCount = trustLevelDetails.achievedCount;
928 | const totalCount = trustLevelDetails.totalCount;
929 | const progressPercent = Math.round((achievedCount / totalCount) * 100);
930 |
931 | // 找到未达标的项目
932 | const failedItems = trustLevelDetails.items.filter(item => !item.isMet);
933 | const failedItem = failedItems.length > 0 ? failedItems[0] : null;
934 |
935 | // 获取图标函数
936 | function getIconSvg(type) {
937 | const icons = {
938 | user: '',
939 | message: '',
940 | eye: '',
941 | thumbsUp: '',
942 | warning: '',
943 | shield: ''
944 | };
945 | return icons[type] || icons.user;
946 | }
947 |
948 | function getItemIcon(label) {
949 | if (label.includes('访问次数')) return 'user';
950 | if (label.includes('回复') || label.includes('话题')) return 'message';
951 | if (label.includes('浏览') || label.includes('已读')) return 'eye';
952 | if (label.includes('举报')) return 'warning';
953 | if (label.includes('点赞') || label.includes('获赞')) return 'thumbsUp';
954 | if (label.includes('禁言') || label.includes('封禁')) return 'shield';
955 | return 'user';
956 | }
957 |
958 | // 构建新UI HTML
959 | let html = `
960 |
978 |
979 |
980 |
981 |
987 |
${failedItem ? failedItem.label : '无'}
988 |
${failedItem ? failedItem.current : '所有要求均已满足'}
989 | ${failedItem ? `
需要 ${failedItem.required}
` : ''}
990 |
991 |
992 |
998 |
其他要求
999 |
${achievedCount} / ${totalCount}
1000 |
1001 |
1002 |
1003 |
1004 |
`;
1005 |
1006 | // 为每个指标生成HTML
1007 | trustLevelDetails.items.forEach(item => {
1008 | const iconType = getItemIcon(item.label);
1009 | html += `
1010 |
1011 |
1012 |
1015 | ${item.label}
1016 |
1017 |
1018 |
${item.current}
1019 |
/${item.required}
1020 |
1026 |
1027 |
`;
1028 | });
1029 |
1030 | // 添加底部状态和更新时间
1031 | html += `
1032 |
1033 |
1034 |
1035 |
1041 |
1042 | `;
1043 |
1044 | // 设置内容
1045 | popup.innerHTML = html;
1046 |
1047 | // 添加事件监听器
1048 | setTimeout(() => {
1049 | // 刷新按钮
1050 | const reloadBtn = popup.querySelector('.ld-reload-btn');
1051 | if (reloadBtn) {
1052 | reloadBtn.addEventListener('click', function() {
1053 | this.textContent = '加载中...';
1054 | this.disabled = true;
1055 | fetchDataWithGM();
1056 | setTimeout(() => {
1057 | if (!this.isConnected) return; // 检查按钮是否还在DOM中
1058 | this.textContent = '刷新数据';
1059 | this.disabled = false;
1060 | }, 3000);
1061 | });
1062 | }
1063 | }, 100);
1064 | }
1065 |
1066 | // 显示错误状态的浮窗
1067 | function showPopupError(title, message, updateTime) {
1068 | popup.innerHTML = `
1069 |
1070 |
❌
1071 |
${title}
1072 |
${message}
1073 |
1074 |
1075 |
1076 | `;
1077 |
1078 | // 添加重试按钮事件
1079 | setTimeout(() => {
1080 | const retryBtn = popup.querySelector('.ld-reload-btn');
1081 | if (retryBtn) {
1082 | retryBtn.addEventListener('click', function() {
1083 | this.textContent = '加载中...';
1084 | this.disabled = true;
1085 | fetchDataWithGM();
1086 | setTimeout(() => {
1087 | if (!this.isConnected) return;
1088 | this.textContent = '重试';
1089 | this.disabled = false;
1090 | }, 3000);
1091 | });
1092 | }
1093 | }, 100);
1094 | }
1095 |
1096 | // 显示错误信息 (保留向下兼容)
1097 | function showError(message) {
1098 | debugLog(`显示错误: ${message}`);
1099 | showPopupError('出错了', message, new Date());
1100 | }
1101 |
1102 | // 处理请求错误
1103 | function handleRequestError(response) {
1104 | let responseBody = response.responseText || "";
1105 | debugLog(`响应内容 (前500字符): ${responseBody.substring(0, 500)}`);
1106 |
1107 | if (response.status === 429) {
1108 | showError('请求过于频繁 (429),请稍后重试。Cloudflare可能暂时限制了访问。');
1109 | } else if (responseBody.includes('Cloudflare') || responseBody.includes('challenge-platform') || responseBody.includes('Just a moment')) {
1110 | showError('Cloudflare拦截或验证页面。请等待或手动访问connect.linux.do完成验证。');
1111 | } else if (responseBody.includes('登录') || responseBody.includes('注册')) {
1112 | showError('获取数据失败,可能是需要登录 connect.linux.do。');
1113 | } else {
1114 | showError(`获取数据失败 (状态: ${response.status})`);
1115 | }
1116 | }
1117 |
1118 | // 处理2级及以上用户数据(原有逻辑)
1119 | function processHighLevelUserData(tempDiv, globalUsername, currentLevel) {
1120 | let targetInfoDiv = null;
1121 | const potentialDivs = tempDiv.querySelectorAll('div.bg-white.p-6.rounded-lg.mb-4.shadow');
1122 | debugLog(`找到了 ${potentialDivs.length} 个潜在的 'div.bg-white.p-6.rounded-lg.mb-4.shadow' 元素。`);
1123 |
1124 | for (let i = 0; i < potentialDivs.length; i++) {
1125 | const div = potentialDivs[i];
1126 | const h2 = div.querySelector('h2.text-xl.mb-4.font-bold');
1127 | if (h2 && h2.textContent.includes('信任级别')) {
1128 | targetInfoDiv = div;
1129 | debugLog(`找到包含"信任级别"标题的目标div,其innerHTML (前200字符): ${targetInfoDiv.innerHTML.substring(0,200)}`);
1130 | break;
1131 | }
1132 | }
1133 |
1134 | if (!targetInfoDiv) {
1135 | debugLog('通过遍历和内容检查,未找到包含"信任级别"标题的目标div。');
1136 | showError('未找到包含等级信息的数据块。请检查控制台日志 (Alt+D) 中的HTML内容,并提供一个准确的选择器。');
1137 | return;
1138 | }
1139 |
1140 | debugLog('通过内容匹配,在响应中找到目标信息div。');
1141 | const originalHtml = targetInfoDiv.innerHTML;
1142 |
1143 | // 从目标div的解析用户名和目标等级
1144 | let specificUsername = globalUsername;
1145 | let targetLevel = '未知';
1146 | const h2InDiv = targetInfoDiv.querySelector('h2.text-xl.mb-4.font-bold');
1147 | if (h2InDiv) {
1148 | const h2Text = h2InDiv.textContent.trim();
1149 | const titleMatch = h2Text.match(/^(.+?)\s*-\s*信任级别\s*(\d+)\s*的要求/i);
1150 | if (titleMatch) {
1151 | specificUsername = titleMatch[1].trim();
1152 | targetLevel = titleMatch[2];
1153 | debugLog(`从解析: 特定用户名='${specificUsername}', 目标等级='${targetLevel}'`);
1154 | } else {
1155 | debugLog(`从解析: 未匹配到标题格式: "${h2Text}"`);
1156 | }
1157 | } else {
1158 | debugLog('目标div中未找到标签');
1159 | }
1160 |
1161 | // 解析信任级别详情
1162 | const trustLevelDetails = parseTrustLevelDetails(targetInfoDiv);
1163 |
1164 | debugLog(`最终提取信息: 用户名='${specificUsername}', 当前等级='${currentLevel}', 目标等级='${targetLevel}'`);
1165 | updateInfo(specificUsername, currentLevel, targetLevel, trustLevelDetails, new Date(), originalHtml);
1166 | saveDataToCache(specificUsername, currentLevel, targetLevel, trustLevelDetails, originalHtml);
1167 | }
1168 |
1169 | // 处理0级和1级用户数据
1170 | function fetchLowLevelUserData(username, currentLevel) {
1171 | debugLog(`开始获取${currentLevel}级用户 ${username} 的数据`);
1172 |
1173 | // 首先获取summary.json数据
1174 | GM_xmlhttpRequest({
1175 | method: "GET",
1176 | url: `https://linux.do/u/${username}/summary.json`,
1177 | timeout: 15000,
1178 | onload: function(response) {
1179 | debugLog(`summary.json请求成功: status ${response.status}`);
1180 | if (response.status === 200) {
1181 | try {
1182 | const data = JSON.parse(response.responseText);
1183 | const userSummary = data.user_summary;
1184 | debugLog(`获取到用户摘要数据: ${JSON.stringify(userSummary)}`);
1185 |
1186 | if (currentLevel === 1) {
1187 | // 1级用户需要额外获取回复数据
1188 | fetchUserRepliesData(username, currentLevel, userSummary);
1189 | } else {
1190 | // 0级用户直接处理数据
1191 | processLowLevelUserData(username, currentLevel, userSummary, null);
1192 | }
1193 | } catch (e) {
1194 | debugLog(`解析summary.json失败: ${e.message}`);
1195 | showError('解析用户数据失败');
1196 | }
1197 | } else {
1198 | debugLog(`summary.json请求失败: ${response.status}`);
1199 | showError(`获取用户数据失败 (状态: ${response.status})`);
1200 | }
1201 | },
1202 | onerror: function(error) {
1203 | debugLog(`summary.json请求错误: ${JSON.stringify(error)}`);
1204 | showError('获取用户数据时网络错误');
1205 | },
1206 | ontimeout: function() {
1207 | debugLog('summary.json请求超时');
1208 | showError('获取用户数据超时');
1209 | }
1210 | });
1211 | }
1212 |
1213 | // 获取用户回复数据(仅1级用户需要)
1214 | function fetchUserRepliesData(username, currentLevel, userSummary) {
1215 | debugLog(`获取用户 ${username} 的回复数据`);
1216 |
1217 | GM_xmlhttpRequest({
1218 | method: "GET",
1219 | url: `https://linux.do/u/${username}/activity/replies`,
1220 | timeout: 15000,
1221 | onload: function(response) {
1222 | debugLog(`replies页面请求成功: status ${response.status}`);
1223 | if (response.status === 200) {
1224 | const tempDiv = document.createElement('div');
1225 | tempDiv.innerHTML = response.responseText;
1226 |
1227 | // 统计回复的不同话题数量
1228 | const replyContainer = tempDiv.querySelector('#main-outlet div:nth-child(3) section div');
1229 | let repliesCount = 0;
1230 |
1231 | if (replyContainer) {
1232 | const replyItems = replyContainer.querySelectorAll('#user-content > div > div:nth-child(1) > div');
1233 | repliesCount = Math.min(replyItems.length, 3); // 最多统计3个,满足要求即可
1234 | debugLog(`找到 ${replyItems.length} 个回复项,统计 ${repliesCount} 个`);
1235 | } else {
1236 | debugLog('未找到回复容器');
1237 | }
1238 |
1239 | processLowLevelUserData(username, currentLevel, userSummary, repliesCount);
1240 | } else {
1241 | debugLog(`replies页面请求失败: ${response.status}`);
1242 | // 即使获取回复数据失败,也继续处理其他数据,回复数设为0
1243 | processLowLevelUserData(username, currentLevel, userSummary, 0);
1244 | }
1245 | },
1246 | onerror: function(error) {
1247 | debugLog(`replies页面请求错误: ${JSON.stringify(error)}`);
1248 | processLowLevelUserData(username, currentLevel, userSummary, 0);
1249 | },
1250 | ontimeout: function() {
1251 | debugLog('replies页面请求超时');
1252 | processLowLevelUserData(username, currentLevel, userSummary, 0);
1253 | }
1254 | });
1255 | }
1256 |
1257 | // 处理0级和1级用户的数据
1258 | function processLowLevelUserData(username, currentLevel, userSummary, repliesCount) {
1259 | debugLog(`处理${currentLevel}级用户数据: ${username}`);
1260 |
1261 | const targetLevel = currentLevel + 1; // 目标等级
1262 | const requirements = LEVEL_REQUIREMENTS[currentLevel];
1263 |
1264 | if (!requirements) {
1265 | showError(`未找到等级${currentLevel}的升级要求配置`);
1266 | return;
1267 | }
1268 |
1269 | // 构建升级详情数据
1270 | const trustLevelDetails = {
1271 | items: [],
1272 | summaryText: '',
1273 | achievedCount: 0,
1274 | totalCount: 0,
1275 | targetLevelInSummary: targetLevel.toString()
1276 | };
1277 |
1278 | // 检查各项要求
1279 | Object.entries(requirements).forEach(([key, requiredValue]) => {
1280 | let currentValue = 0;
1281 | let label = '';
1282 | let isMet = false;
1283 |
1284 | switch (key) {
1285 | case 'topics_entered':
1286 | currentValue = userSummary.topics_entered || 0;
1287 | label = '浏览的话题';
1288 | isMet = currentValue >= requiredValue;
1289 | break;
1290 | case 'posts_read_count':
1291 | currentValue = userSummary.posts_read_count || 0;
1292 | label = '已读帖子';
1293 | isMet = currentValue >= requiredValue;
1294 | break;
1295 | case 'time_read':
1296 | currentValue = Math.floor((userSummary.time_read || 0) / 60); // 转换为分钟
1297 | label = '阅读时间(分钟)';
1298 | isMet = (userSummary.time_read || 0) >= requiredValue;
1299 | break;
1300 | case 'days_visited':
1301 | currentValue = userSummary.days_visited || 0;
1302 | label = '访问天数';
1303 | isMet = currentValue >= requiredValue;
1304 | break;
1305 | case 'likes_given':
1306 | currentValue = userSummary.likes_given || 0;
1307 | label = '给出的赞';
1308 | isMet = currentValue >= requiredValue;
1309 | break;
1310 | case 'likes_received':
1311 | currentValue = userSummary.likes_received || 0;
1312 | label = '收到的赞';
1313 | isMet = currentValue >= requiredValue;
1314 | break;
1315 | case 'replies_to_different_topics':
1316 | currentValue = repliesCount || 0;
1317 | label = '回复不同话题';
1318 | isMet = currentValue >= requiredValue;
1319 | break;
1320 | }
1321 |
1322 | if (label) {
1323 | trustLevelDetails.items.push({
1324 | label: label,
1325 | current: currentValue.toString(),
1326 | required: key === 'time_read' ? Math.floor(requiredValue / 60).toString() : requiredValue.toString(),
1327 | isMet: isMet
1328 | });
1329 |
1330 | if (isMet) {
1331 | trustLevelDetails.achievedCount++;
1332 | }
1333 | trustLevelDetails.totalCount++;
1334 | }
1335 | });
1336 |
1337 | // 生成总结文本
1338 | if (trustLevelDetails.achievedCount === trustLevelDetails.totalCount) {
1339 | trustLevelDetails.summaryText = `已满足信任级别 ${targetLevel} 要求`;
1340 | } else {
1341 | trustLevelDetails.summaryText = `不符合信任级别 ${targetLevel} 要求,继续加油`;
1342 | }
1343 |
1344 | debugLog(`${currentLevel}级用户数据处理完成: ${trustLevelDetails.achievedCount}/${trustLevelDetails.totalCount} 项达标`);
1345 |
1346 | // 更新显示
1347 | updateInfo(username, currentLevel.toString(), targetLevel.toString(), trustLevelDetails, new Date(), '', false);
1348 | saveDataToCache(username, currentLevel.toString(), targetLevel.toString(), trustLevelDetails, '');
1349 | }
1350 | })();
1351 |
--------------------------------------------------------------------------------