您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Detects suspected bot and AI-generated posts/comments on Reddit using advanced heuristics. Highlights flagged elements, shows a styled popup, and lists clickable bot/AI accounts in the dropdown.
当前为
// ==UserScript== // @name Reddit AI BotBuster // @namespace http://tampermonkey.net/ // @version 2.8 // @description Detects suspected bot and AI-generated posts/comments on Reddit using advanced heuristics. Highlights flagged elements, shows a styled popup, and lists clickable bot/AI accounts in the dropdown. // @match https://www.reddit.com/* // @match https://old.reddit.com/* // @grant none // ==/UserScript== (function() { 'use strict'; /*********************** * 1. Inject CSS for Bot Username Styling ***********************/ const style = document.createElement("style"); style.innerHTML = ` .botUsername { color: orange !important; font-size: 14px !important; } /* Smooth scrolling for anchor jumps */ html { scroll-behavior: smooth; } `; document.head.appendChild(style); /*********************** * CONFIGURATION ***********************/ const RED_FLAG_THRESHOLD = 5; // Combined score threshold // Bot-related heuristics const suspiciousUserPatterns = [ /bot/i, /^[A-Za-z]+-[A-Za-z]+\d{4}$/, /^[A-Za-z]+[A-Za-z]+\d+$/, /^[A-Z][a-z]+[A-Z][a-z]+s{2,}$/ ]; function isRandomString(username) { if (username.length < 8) return false; const vowels = username.match(/[aeiou]/gi); const ratio = vowels ? vowels.length / username.length : 0; return ratio < 0.3; } const genericResponses = [ "i agree dude", "yes you are right", "well said", "totally agree", "i agree", "right you are", "well spoken, you are", "perfectly said this is" ]; const scamLinkRegex = /\.(live|life|shop)\b/i; function isNewAccount(userElem) { const titleAttr = userElem.getAttribute('title') || ""; return /redditor for.*\b(day|week|month)\b/i.test(titleAttr); } // Parse account age in months from user tooltip function getAccountAge(userElem) { const titleAttr = userElem.getAttribute('title') || ""; const match = titleAttr.match(/redditor for (\d+)\s*(day|week|month|year)s?/i); if (match) { let value = parseInt(match[1]); let unit = match[2].toLowerCase(); if (unit === "day") return value / 30; if (unit === "week") return (value * 7) / 30; if (unit === "month") return value; if (unit === "year") return value * 12; } return null; } // Track duplicates const commentTextMap = new Map(); /*********************** * AI DETECTION HEURISTICS * (Slightly lowered weights to reduce false positives) ***********************/ function computeAIScore(text) { let score = 0; let lowerText = text.toLowerCase(); // 1. AI disclaimers (slightly reduced) if (lowerText.includes("as an ai language model") || lowerText.includes("i am not a human")) { score += 1.9; // from 2.0 } // 2. Lack of contractions in long texts (slightly reduced) let contractions = lowerText.match(/\b(i'm|you're|they're|we're|can't|won't|didn't|isn't|aren't)\b/g); let words = lowerText.split(/\s+/); let wordCount = words.length; if (wordCount > 150 && (!contractions || contractions.length === 0)) { score += 1.2; // from 1.3 } // 3. Formal filler phrases const aiPhrases = ["in conclusion", "furthermore", "moreover", "on the other hand"]; aiPhrases.forEach(phrase => { if (lowerText.includes(phrase)) { score += 0.5; } }); // 4. Common AI indicator phrases (slightly reduced) const aiIndicators = [ "i do not have personal experiences", "my training data", "i cannot", "i do not have the ability", "apologies if", "i apologize", "i'm unable", "as an ai", "as an artificial intelligence" ]; aiIndicators.forEach(phrase => { if (lowerText.includes(phrase)) { score += 0.9; // from 1.0 } }); // 5. Repetitiveness / low burstiness (slightly reduced) let sentences = lowerText.split(/[.!?]+/).map(s => s.trim()).filter(s => s.length > 0); if (sentences.length > 1) { let lengths = sentences.map(s => s.split(/\s+/).length); let avg = lengths.reduce((a, b) => a + b, 0) / lengths.length; let variance = lengths.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / lengths.length; if (variance < 4) { score += 0.9; // from 1.0 } } // 6. Lexical diversity (slightly reduced) let uniqueWords = new Set(words); let typeTokenRatio = uniqueWords.size / words.length; if (words.length > 20 && typeTokenRatio < 0.3) { score += 0.9; // from 1.0 } // 7. Bigram uniqueness (slightly reduced) function getBigrams(arr) { let bigrams = []; for (let i = 0; i < arr.length - 1; i++) { bigrams.push(arr[i] + " " + arr[i+1]); } return bigrams; } let bigrams = getBigrams(words); if (bigrams.length > 0) { let uniqueBigrams = new Set(bigrams); let bigramRatio = uniqueBigrams.size / bigrams.length; if (bigramRatio < 0.5) { score += 0.9; // from 1.0 } } // 8. Trigram uniqueness (slightly reduced) function getTrigrams(arr) { let trigrams = []; for (let i = 0; i < arr.length - 2; i++) { trigrams.push(arr[i] + " " + arr[i+1] + " " + arr[i+2]); } return trigrams; } let trigrams = getTrigrams(words); if (trigrams.length > 0) { let uniqueTrigrams = new Set(trigrams); let trigramRatio = uniqueTrigrams.size / trigrams.length; if (trigramRatio < 0.6) { score += 0.7; // from 0.8 } } // Enhancements score += computeSemanticCoherenceScore(text); // 0.7 score += computeProperNounConsistencyScore(text); // 0.4 score += computeContextShiftScore(text); // 0.7 score += computeSyntaxScore(text); // 1.0 return score; } function computeSemanticCoherenceScore(text) { let sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(s => s.length > 0); if (sentences.length < 2) return 0; let similarities = []; for (let i = 0; i < sentences.length - 1; i++) { let s1 = new Set(sentences[i].toLowerCase().split(/\s+/)); let s2 = new Set(sentences[i+1].toLowerCase().split(/\s+/)); let intersection = new Set([...s1].filter(x => s2.has(x))); let union = new Set([...s1, ...s2]); let jaccard = union.size ? intersection.size / union.size : 0; similarities.push(jaccard); } let avgSim = similarities.reduce((a, b) => a + b, 0) / similarities.length; return avgSim < 0.2 ? 0.7 : 0; } function computeProperNounConsistencyScore(text) { let sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(s => s.length > 0); if (sentences.length < 2) return 0; let counts = sentences.map(sentence => { let words = sentence.split(/\s+/); let properNouns = words.slice(1).filter(word => /^[A-Z][a-z]+/.test(word)); return properNouns.length; }); let avg = counts.reduce((a, b) => a + b, 0) / counts.length; let variance = counts.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / counts.length; return variance > 2 ? 0.4 : 0; } function computeContextShiftScore(text) { let words = text.split(/\s+/).filter(w => w.length > 0); if (words.length < 10) return 0; let half = Math.floor(words.length / 2); let firstHalf = words.slice(0, half); let secondHalf = words.slice(half); let freq = arr => { let f = {}; arr.forEach(word => { f[word] = (f[word] || 0) + 1; }); return f; }; let f1 = freq(firstHalf), f2 = freq(secondHalf); let allWords = new Set([...Object.keys(f1), ...Object.keys(f2)]); let dot = 0, norm1 = 0, norm2 = 0; allWords.forEach(word => { let v1 = f1[word] || 0; let v2 = f2[word] || 0; dot += v1 * v2; norm1 += v1 * v1; norm2 += v2 * v2; }); let cosSim = (norm1 && norm2) ? dot / (Math.sqrt(norm1) * Math.sqrt(norm2)) : 0; return cosSim < 0.3 ? 0.7 : 0; } function computeSyntaxScore(text) { let sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(s => s.length > 0); if (sentences.length === 0) return 0; let punctuationMatches = text.match(/[,\;:\-]/g); let punctuationCount = punctuationMatches ? punctuationMatches.length : 0; let avgPunctuation = punctuationCount / sentences.length; return avgPunctuation < 1 ? 1.0 : 0; } /*********************** * BOT SCORE ***********************/ function computeBotScore(elem) { let score = 0; // Attempt multiple possible selectors for user links const userElem = elem.querySelector('a[href*="/user/"], a[href*="/u/"], a.author, a[data-click-id="user"]'); if (userElem) { const username = userElem.innerText.trim(); if (username) { if (username.toLowerCase().includes("bot")) { score++; } suspiciousUserPatterns.forEach(pattern => { if (pattern.test(username)) { score++; } }); if (isRandomString(username)) { score++; } } if (isNewAccount(userElem)) { score++; } let ageInMonths = getAccountAge(userElem); if (ageInMonths !== null && ageInMonths < 2) { score += 0.5; } } let textContent = elem.innerText.toLowerCase().replace(/\s+/g, ' ').trim(); genericResponses.forEach(phrase => { if (textContent === phrase) { score++; } }); if (textContent.split(' ').length < 3) { score++; } if (textContent.includes("&") && !textContent.includes("& ")) { score++; } if (textContent.startsWith('>') && textContent.split(' ').length < 5) { score++; } if (textContent.length > 0) { const count = commentTextMap.get(textContent) || 0; if (count > 0) { score++; } } const links = elem.querySelectorAll('a'); links.forEach(link => { if (scamLinkRegex.test(link.href)) { score++; } if (link.parentElement && link.parentElement.innerText.includes("Powered by Gearlaunch")) { score++; } }); return score; } function countRedFlags(elem) { const botScore = computeBotScore(elem); const aiScore = computeAIScore(elem.innerText); return { botScore, aiScore, totalScore: botScore + aiScore }; } /*********************** * DETECTIONS & POPUP ***********************/ let botCount = 0; let detectedBots = []; let detectionIndex = 0; // For anchor ID assignment function createPopup() { let popup = document.getElementById("botCounterPopup"); if (!popup) { popup = document.createElement("div"); popup.id = "botCounterPopup"; popup.style.position = "fixed"; popup.style.top = "40px"; popup.style.right = "10px"; popup.style.backgroundColor = "rgba(248,248,248,0.5)"; popup.style.border = "1px solid #ccc"; popup.style.padding = "10px"; popup.style.zIndex = "9999"; popup.style.fontFamily = "Roboto, sans-serif"; popup.style.fontSize = "12px"; popup.style.cursor = "pointer"; popup.style.backdropFilter = "blur(5px)"; popup.style.webkitBackdropFilter = "blur(5px)"; popup.style.width = "250px"; let header = document.createElement("div"); header.id = "botPopupHeader"; header.innerText = "Detected bot/AI content: " + botCount; if (botCount < 10) { header.style.color = "green"; } else if (botCount < 30) { header.style.color = "yellow"; } else { header.style.color = "red"; } popup.appendChild(header); let dropdown = document.createElement("div"); dropdown.id = "botDropdown"; dropdown.style.display = "none"; dropdown.style.maxHeight = "300px"; dropdown.style.overflowY = "auto"; dropdown.style.marginTop = "10px"; dropdown.style.borderTop = "1px solid #ccc"; dropdown.style.paddingTop = "5px"; popup.appendChild(dropdown); popup.addEventListener("click", function(e) { e.stopPropagation(); dropdown.style.display = (dropdown.style.display === "none") ? "block" : "none"; }); document.body.appendChild(popup); } } function updatePopup() { let header = document.getElementById("botPopupHeader"); let dropdown = document.getElementById("botDropdown"); if (header) { header.innerText = "Detected bot/AI content: " + botCount; if (botCount < 10) { header.style.color = "green"; } else if (botCount < 30) { header.style.color = "yellow"; } else { header.style.color = "red"; } } if (dropdown) { dropdown.innerHTML = ""; if (detectedBots.length === 0) { let emptyMsg = document.createElement("div"); emptyMsg.innerText = "No bots/AI detected."; dropdown.appendChild(emptyMsg); } else { detectedBots.forEach(function(item) { let entry = document.createElement("div"); entry.style.marginBottom = "5px"; // Link to jump to the flagged comment let link = document.createElement("a"); link.href = "#" + item.elemID; // anchor link link.style.color = "inherit"; link.style.textDecoration = "none"; link.style.cursor = "pointer"; link.innerText = item.username; entry.appendChild(link); dropdown.appendChild(entry); }); } } } createPopup(); updatePopup(); function highlightIfSuspected(elem) { if (elem.getAttribute("data-bot-detected") === "true") return; const scores = countRedFlags(elem); if (scores.totalScore >= RED_FLAG_THRESHOLD) { let reason = ""; if (scores.botScore > 0 && scores.aiScore > 0) { reason = "Bot/AI"; } else if (scores.aiScore > 0) { reason = "AI"; } else { reason = "Bot"; } // Outline if (reason === "Bot") { elem.style.outline = "3px dashed red"; } else if (reason === "AI") { elem.style.outline = "3px dashed blue"; } else { elem.style.outline = "3px dashed purple"; } elem.setAttribute("data-bot-detected", "true"); botCount++; // Assign an ID so we can anchor to it detectionIndex++; const elemID = "botbuster-detected-" + detectionIndex; elem.setAttribute("id", elemID); // If reason includes "bot", style the username if (reason.toLowerCase().includes("bot")) { const userLinks = elem.querySelectorAll( 'a[href*="/user/"], a[href*="/u/"], a.author, a[data-click-id="user"]' ); userLinks.forEach(link => { link.classList.add("botUsername"); }); } // Record if (!elem.getAttribute("data-bot-recorded")) { let userElemForRecord = elem.querySelector( 'a[href*="/user/"], a[href*="/u/"], a.author, a[data-click-id="user"]' ); let username = userElemForRecord ? userElemForRecord.innerText.trim() : "Unknown"; // We only store the username and an anchor ID detectedBots.push({ username: username, elemID: elemID }); elem.setAttribute("data-bot-recorded", "true"); } updatePopup(); console.log("BotBuster: Highlighted element with", scores.totalScore, "red flags. Reason:", reason); } } function scanForBots(root = document) { const selectors = [ 'div[data-testid="post-container"]', 'div[data-testid="comment"]', 'div.thing', 'div.Comment' ]; const candidates = root.querySelectorAll(selectors.join(', ')); candidates.forEach(candidate => { let textContent = candidate.innerText.toLowerCase().replace(/\s+/g, ' ').trim(); if (textContent.length > 0) { const currentCount = commentTextMap.get(textContent) || 0; commentTextMap.set(textContent, currentCount + 1); } highlightIfSuspected(candidate); }); } /*********************** * OBSERVATION ***********************/ scanForBots(); const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { scanForBots(node); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); setInterval(() => { scanForBots(document); }, 3000); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址