您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
One‑click “Like All” for the current GTA World forum page + live post counter.
// ==UserScript== // @name GTA World Forums – Like All + Post Counter // @version 1.0 // @description One‑click “Like All” for the current GTA World forum page + live post counter. // @author blanco // @license All Rights Reserved // @match https://forum.gta.world/* // @grant none // @namespace https://gf.qytechs.cn/users/1496525 // ==/UserScript== // © 2025 blanco. All rights reserved. // This script is proprietary. You may not copy, modify, distribute, or use any part of it without explicit written permission. (() => { 'use strict'; const CONFIG = { likeDelay: 500, badgeBottom: 150, counterDebounce: 250, hotkey: 'L', maxRetries: 3, rateLimitPause: 30000 }; const notifEl = (() => { const el = document.createElement('div'); el.id = 'forum-like-notification'; el.setAttribute('role', 'alert'); Object.assign(el.style, { position: 'fixed', top: '20px', right: '20px', padding: '12px 24px', borderRadius: '6px', fontSize: '16px', zIndex: 10001, boxShadow: '0 2px 8px rgba(0,0,0,.18)', opacity: '.98', pointerEvents: 'none', transition: 'opacity .4s' }); document.body.appendChild(el); return el; })(); function showNotification(msg, dur = 3000) { const dark = matchMedia('(prefers-color-scheme: dark)').matches; notifEl.style.background = dark ? '#222' : '#f5f5f5'; notifEl.style.color = dark ? '#fff' : '#222'; notifEl.textContent = msg; notifEl.style.display = 'block'; setTimeout(() => { notifEl.style.display = 'none'; }, dur); } const badgeEl = (() => { const el = document.createElement('div'); el.id = 'forum-post-count'; Object.assign(el.style, { position: 'fixed', right: '20px', bottom: `${CONFIG.badgeBottom}px`, padding: '8px 16px', borderRadius: '8px', fontSize: '15px', zIndex: 10001, boxShadow: '0 2px 8px rgba(0,0,0,.18)', pointerEvents: 'none', userSelect: 'none' }); document.body.appendChild(el); return el; })(); const debounce = (fn, wait) => { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn.apply(null, args), wait); }; }; function updateBadge() { const cnt = document.querySelectorAll('article[id^="elComment_"]').length; const dark = matchMedia('(prefers-color-scheme: dark)').matches; badgeEl.style.background = dark ? '#222' : '#f5f5f5'; badgeEl.style.color = dark ? '#fff' : '#222'; badgeEl.textContent = `Posts: ${cnt}`; } const debouncedUpdate = debounce(updateBadge, CONFIG.counterDebounce); window.addEventListener('load', updateBadge); const ROOT = document.getElementById('elContent') || document.body; new MutationObserver(mutations => { for (const rec of mutations) { if (rec.addedNodes.length || rec.removedNodes.length) { debouncedUpdate(); break; } } }).observe(ROOT, { childList: true, subtree: true }); const sleep = ms => new Promise(r => setTimeout(r, ms)); function fetchUpvoteLinks() { return Array.from(document.querySelectorAll('span.ipsReact_button[data-action="reactLaunch"]:not(.ipsReact_button_selected):not(.ipsReact_button--selected) a.ipsReact_reaction')).filter(a => a.querySelector('img[alt="Upvote"]')); } const abortCtrl = new AbortController(); window.addEventListener('beforeunload', () => abortCtrl.abort()); async function likeViaAjax(link, attempt = 0) { try { const r = await fetch(link.href, { method: 'GET', credentials: 'include', signal: abortCtrl.signal, headers: { 'X-Requested-With': 'XMLHttpRequest', 'Referer': location.href } }); if (r.status === 429) { showNotification('Rate‑limit hit – pausing 30 s'); await sleep(CONFIG.rateLimitPause); return likeViaAjax(link, attempt + 1); } if (!r.ok) throw new Error(); return true; } catch { if (attempt < CONFIG.maxRetries) { const backoff = 2 ** attempt * 1000; await sleep(backoff); return likeViaAjax(link, attempt + 1); } return false; } } const likeBtnImg = (() => { const img = document.createElement('img'); img.src = 'https://i.imgur.com/b7IZU6X.gif'; img.width = 56; img.height = 56; img.alt = 'Like All'; Object.assign(img.style, { position: 'fixed', right: '20px', bottom: '80px', zIndex: 10001, borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,.18)', padding: '2px', cursor: 'pointer', background: matchMedia('(prefers-color-scheme: dark)').matches ? '#222' : '#fff' }); img.setAttribute('aria-label', 'Like all posts on this page'); img.tabIndex = 0; document.body.appendChild(img); return img; })(); function toggleBtn(disabled, label) { likeBtnImg.style.opacity = disabled ? '0.5' : '1'; likeBtnImg.setAttribute('aria-label', label); likeBtnImg.title = label; } async function likeAll() { const links = fetchUpvoteLinks(); if (!links.length) { showNotification('No un‑liked posts found on this page.'); return; } toggleBtn(true, `Liking 0 / ${links.length}`); let ok = 0, fail = 0; for (let i = 0; i < links.length; i++) { toggleBtn(true, `Liking ${i + 1} / ${links.length}`); (await likeViaAjax(links[i])) ? ok++ : fail++; await sleep(CONFIG.likeDelay); } toggleBtn(false, 'Like all posts on this page'); showNotification(`Finished! ✔️ ${ok} ❌ ${fail}`); } likeBtnImg.addEventListener('click', likeAll); likeBtnImg.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') likeAll(); }); window.addEventListener('keydown', e => { if (e.shiftKey && e.key.toUpperCase() === CONFIG.hotkey && !e.repeat) { e.preventDefault(); likeAll(); } }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址