用ai魔改了 @uhhhh 的 魔改了个论坛脚本,能看 TL(TrustLevel) 升级进度 !!仅供参考 实际还有一些暗坑 详见描述!! 脚本,现在不需要装油猴,只需要建立浏览器书签把以下code加入地址栏然后点一下就可以看到论坛trust level升级状态了,祝大家早日白金
非悬浮窗版:
javascript:(() => {'use strict';function getApiBase(){const{origin,pathname}=location;const match=pathname.match(/^\/([^/]+)\/u\//);const baseDir=match?`/${match[1]}`:%27%27;return origin+baseDir;}const API_BASE=getApiBase();function getUsername(){const m=location.pathname.match(/\/u\/([^/?#]+)\//);return m?m[1]:null;}const TL_REQUIREMENTS={0:{topics_entered:5,posts_read_count:30,time_read:600},1:{days_visited:15,likes_given:1,likes_received:1,posts_count:3,topics_entered:20,posts_read_count:100,time_read:3600},2:{days_visited:50,posts_read_count:0,topics_entered:0,likes_given:30,likes_received:20,posts_count:10}};const TL3_MAINTAIN_IDX=2;const SELECTOR={days_visited:'li.stats-days-visited > div > span > span',likes_given:'li.stats-likes-given > a > div > span > span',likes_received:'li.stats-likes-received > div > span > span',posts_count:'li.stats-posts-count > a > div > span > span',topics_entered:'li.stats-topics-entered > div > span > span',posts_read_count:'li.stats-posts-read > div > span > span',time_read:'li.stats-time-read > div > span'};const fetchSiteStats=()=>fetch(%60${API_BASE}/about.json%60,{credentials:'same-origin'}).then(r=>r.ok?r.json():Promise.reject(r.status));const fetchUserDirStats=username=>fetch(%60${API_BASE}/directory_items?period=quarterly&order=days_visited%60,{credentials:'same-origin',headers:{'Accept':'application/json'}}).then(r=>r.ok?r.json():Promise.reject(r.status)).then(({directory_items})=>{const item=directory_items.find(i=>i.user?.username===username)||directory_items[0];if(!item)throw new Error('user not found');return{days_visited:item.days_visited,likes_given:item.likes_given,likes_received:item.likes_received,posts_count:item.post_count,topics_entered:item.topics_entered,posts_read_count:item.posts_read,time_read:null,trust_level:item.user?.trust_level??0};});const fetchUserSummary=username=>fetch(%60${API_BASE}/u/${username}/summary.json%60,{credentials:'same-origin',headers:{'Accept':'application/json'}}).then(r=>(r.ok?r.json():Promise.reject(r.status))).then(res=>({stats:res.user_summary,trust_level:res.users?.[0]?.trust_level??0}));function paintStats(trustLevel,stats){const target=TL_REQUIREMENTS[trustLevel]||TL_REQUIREMENTS[0];Object.keys(target).forEach(k=>{if(stats[k]==null)return;const el=document.querySelector(SELECTOR[k]);if(!el)return;el.textContent=k==='time_read'?%60${stats[k]} / ${target[k]} s%60:%60${stats[k]} / ${target[k]}%60;el.style.color=Number(stats[k])>=Number(target[k])?'green':'red';});}async function refresh(){const username=getUsername();if(!username)return;try{const[site,summaryObj,dir]=await Promise.all([fetchSiteStats(),fetchUserSummary(username),fetchUserDirStats(username)]);const stats={...summaryObj.stats};Object.entries(dir).forEach(([k,v])=>{if(v!=null&&k!=='trust_level')stats[k]=v;});const trustLevel=dir.trust_level??summaryObj.trust_level??0;const isMaintain=trustLevel>=3;const tierIndex=isMaintain?TL3_MAINTAIN_IDX:trustLevel;if(tierIndex===2){TL_REQUIREMENTS[2].posts_read_count=Math.min(Math.floor(site.about.stats.posts_30_days/4),20000);TL_REQUIREMENTS[2].topics_entered=Math.min(Math.floor(site.about.stats.topics_30_days/4),500);}paintStats(tierIndex,stats);}catch(e){console.error('[TL-Tracker] refresh error',e);}}refresh();})();
悬浮窗版:
javascript: (function() { 'use strict'; const TL_REQUIREMENTS = { 0: { topics_entered: 5, posts_read_count: 30, time_read: 600 }, 1: { days_visited: 15, likes_given: 1, likes_received: 1, posts_count: 3, topics_entered: 20, posts_read_count: 100, time_read: 3600, replies_to_different_topics: 3 }, 2: { days_visited: 50, likes_given: 30, likes_received: 20, posts_read_count: 0, topics_entered: 0, posts_count: 10 } }; const TL3_MAINTAIN_IDX = 2; const $ = sel => document.querySelector(sel); const html = (el, tpl) => el.insertAdjacentHTML('beforeend', tpl); const apiBase = (() => { const { origin, pathname } = location; const m = pathname.match(/^\/([^/]+)\/u\//); return origin + (m ? `/${m[1]}` : %27%27); })(); async function getCurrentUsername() { const tag = $(%27meta[name="current-user-username"]%27); if (tag?.content) return tag.content; try { const r = await fetch(`${apiBase}/session/current.json`, { credentials: %27same-origin%27, headers: { Accept: %27application/json%27 } }); if (r.ok) { const js = await r.json(); return js?.current_user?.username || null; } } catch (e) { console.error("Could not fetch current user:", e); } return null; } const applyDark = el => matchMedia(%27(prefers-color-scheme: dark)%27).matches && el.classList.add(%27ld-dark%27); function buildUI() { if ($(%27#ld-container')) { console.log('UI already built.'); return; } const style = document.createElement('style'); style.textContent = %60:root{--ld-bg:#fff;--ld-fg:#1f2937;--ld-muted:#6b7280;--ld-good:#16a34a;--ld-bad:#dc2626;--ld-bar:#e5e7eb;--ld-accent:#fb923c;}.ld-dark{--ld-bg:#262626;--ld-fg:#e5e7eb;--ld-muted:#9ca3af;--ld-bar:#525252;}#ld-container{position:fixed;top:50%;right:0;transform:translateY(-50%);font-family:system-ui,sans-serif;z-index:9999;}#ld-btn{background:var(--ld-bg);border:1px solid var(--ld-bar);border-right:none;border-radius:8px 0 0 8px;padding:8px;width:56px;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.1);}#ld-btn:hover{width:72px}#ld-lvl{font-weight:700;color:var(--ld-accent)}#ld-bar{width:40px;height:4px;background:var(--ld-bar);border-radius:2px;overflow:hidden}#ld-fill{height:100%;background:linear-gradient(90deg,#fb923c,#f97316)}#ld-stat{font-size:10px;color:var(--ld-muted)}#ld-popup{position:absolute;right:100%;top:50%;transform:translateY(-50%);background:var(--ld-bg);border:1px solid var(--ld-bar);border-radius:12px;width:320px;opacity:0;pointer-events:none;transition:.2s;box-shadow:0 10px 20px rgba(0,0,0,.15);}#ld-popup.show{opacity:1;pointer-events:auto;transform:translate(-8px,-50%)}header{padding:12px;border-bottom:1px solid var(--ld-bar);font-size:14px;display:flex;justify-content:space-between;align-items:center}#ld-list{padding:12px;max-height:250px;overflow-y:auto;font-size:12px}.ld-row{display:flex;justify-content:space-between;padding:2px 0}footer{font-size:12px;color:var(--ld-muted);padding:8px 12px 12px;border-top:1px solid var(--ld-bar);text-align:center}#ld-reload{margin-top:6px;padding:4px 8px;border:0;border-radius:6px;background:var(--ld-bar);cursor:pointer;color:var(--ld-fg);}%60; document.head.appendChild(style); html(document.body, %60<div id="ld-container"><div id="ld-btn"><span id="ld-lvl">L?</span><div id="ld-bar"><div id="ld-fill"></div></div><span id="ld-stat">0/0</span></div><div id="ld-popup"><header><span id="ld-name">…</span><span id="ld-badge">TL → ?</span></header><div id="ld-main"><div id="ld-list"></div></div><footer><small id="ld-msg">Loading…</small><br><button id="ld-reload">Reload</button></footer></div></div>%60); applyDark($('#ld-container')); $('#ld-btn').addEventListener('mouseenter', () => $('#ld-popup').classList.add('show')); $('#ld-container').addEventListener('mouseleave', () => $('#ld-popup').classList.remove('show')); $('#ld-reload').onclick = refresh; } const fetchSiteStats = () => fetch(%60${apiBase}/about.json%60, { headers: { Accept: 'application/json' } }).then(r => r.json()).then(({ about }) => about.stats); const fetchUserSummary = u => fetch(%60${apiBase}/u/${u}/summary.json%60, { credentials: 'same-origin', headers: { Accept: 'application/json' } }).then(r => r.json()); const fetchUserDirStats = u => fetch(%60${apiBase}/directory_items?period=quarterly&order=days_visited%60, { credentials: 'same-origin', headers: { Accept: 'application/json' } }).then(r => r.json()).then(({ directory_items }) => directory_items.find(i => i.user?.username === u) || null); async function refresh() { $('#ld-msg').innerText = 'Updating…'; try { const uname = await getCurrentUsername(); if (!uname) throw new Error('Not logged in'); const [siteStats, sumRaw, dirItem] = await Promise.all([fetchSiteStats(), fetchUserSummary(uname), fetchUserDirStats(uname)]); if (!dirItem) throw new Error('directory_items lookup failed'); const sumStats = sumRaw.user_summary; const tl = dirItem.user?.trust_level ?? sumRaw.users?.[0]?.trust_level ?? 0; if (tl >= 4) { $('#ld-msg').innerText = 'TL4+ widget hidden'; return; } const isMaintain = tl >= 3; const idx = isMaintain ? TL3_MAINTAIN_IDX : tl; if (idx === 2) { TL_REQUIREMENTS[2].posts_read_count = Math.min(Math.floor(siteStats.posts_30_days / 4), 20000); TL_REQUIREMENTS[2].topics_entered = Math.min(Math.floor(siteStats.topics_30_days / 4), 500); } const stats = { ...sumStats }; [['days_visited', dirItem.days_visited], ['likes_given', dirItem.likes_given], ['likes_received', dirItem.likes_received], ['posts_count', dirItem.post_count], ['topics_entered', dirItem.topics_entered], ['posts_read_count', dirItem.posts_read]].forEach(([k, v]) => { if (v != null) stats[k] = v; }); const req = TL_REQUIREMENTS[idx]; const items = Object.entries(req).map(([k, need]) => { const cur = stats[k] ?? 0; const fmt = v => k === 'time_read' ? Math.round(v / 60) + 'm' : v; return { label: k.replace(/_/g, ' '), cur, need, ok: +cur >= +need, curShow: fmt(cur), needShow: fmt(need) }; }); const done = items.filter(i => i.ok).length, pct = Math.round(done / items.length * 100); $('#ld-name').innerText = uname; $('#ld-badge').innerText = isMaintain ? 'Keep TL3' : %60→ TL${tl + 1}%60; $('#ld-lvl').innerText = %60L${tl}%60; $('#ld-fill').style.width = pct + '%'; $('#ld-stat').innerText = %60${done}/${items.length}%60; const list = $('#ld-list'); list.innerHTML = ''; items.forEach(i => { const color = i.ok ? 'var(--ld-good)' : 'var(--ld-bad)'; html(list, %60<div class="ld-row"><span>${i.label}</span><span style="color:${color}">${i.curShow} / ${i.needShow}</span></div>%60); }); $('#ld-msg').innerText = done === items.length ? (isMaintain ? 'You have secured TL3.' : %60Congrats! You meet TL${tl + 1}.%60) : (isMaintain ? %60Need ${items.length - done} more to keep TL3.%60 : %60Need ${items.length - done} more target(s).%60); } catch (e) { $('#ld-msg').innerText = 'Error: ' + e.message; console.error(e); } } buildUI(); refresh();})();





