您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Cleans up youtube live chat for simple spam - Repeated messages, overused emojis
当前为
// ==UserScript== // @name Cleanup YouTube live chat for spam // @namespace http://tampermonkey.net/ // @version 0.1 // @description Cleans up youtube live chat for simple spam - Repeated messages, overused emojis // @author Zallist // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?domain=youtube.com // @grant none // ==/UserScript== (function() { 'use strict'; var originalFetch = window.fetch; let hyperCleanup = { options: { // How many milliseconds back to check for spam spamTime: 120000, // How many times can the message be repeated before it looks like spam spamAllowedAmount: 2, // Just strip out all-emoji messages? removeEmojiDuplicates: true }, cache: {}, messageCountSince: (messageText, time) => 0, logMessage: (messageText, message) => { }, isSpamMessage: (message) => false, cleanJson: (json) => json }; // Prepare ahead of time so it compiles hyperCleanup.stripSpaceChars = /\s/g; hyperCleanup.getMessageSymbol = (messageText) => { // Ignore case & remove spaces // We could PROBABLY also just strip out punctuation return Symbol.for( messageText.toLowerCase().replace(hyperCleanup.stripSpaceChars, '') ); }; hyperCleanup.messageCountSince = (messageText, time, deleteIfOlderThan) => { let count = 0; let symbol = hyperCleanup.getMessageSymbol(messageText); let cache = hyperCleanup.cache[symbol]; if (cache) { for (let i = cache.length - 1; i >= 0; i--) { if (cache[i] >= time) { count++; } else if (deleteIfOlderThan && cache[i] < deleteIfOlderThan) { // We can just delete it since we shouldn't care about it again cache.splice(i, 1); } } } return count; }; hyperCleanup.logMessage = (messageText, message) => { let symbol = hyperCleanup.getMessageSymbol(messageText); let cache = hyperCleanup.cache[symbol]; if (!cache) { hyperCleanup.cache[symbol] = [Date.now()]; } else { hyperCleanup.cache[symbol].push(Date.now()); } }; hyperCleanup.isSpamMessage = (message) => { // Algorithm // if (message is all emoji) and (seen in x minutes) spam // else if (seen >y times in x minutes) spam let isSpam = false; let messageText = ""; let isAllEmoji = true; // Build up message for (let iRun = 0; iRun < message.runs.length; iRun++) { let run = message.runs[iRun]; if (run.text) { isAllEmoji = false; messageText += run.text; } else if (run.emoji) { // We COULD look at the url but id works better to be honest messageText += "[emoji:" + run.emoji.emojiId + "]"; } } // It's not a message we can read? More research required. // Might also just be a superchat which we allow - Superchat spam is part of the chat experience. if (messageText.length < 1) return false; let spamStartCheck = Date.now() - hyperCleanup.options.spamTime; let spamCount = hyperCleanup.messageCountSince(messageText, spamStartCheck, spamStartCheck); if (hyperCleanup.options.removeEmojiDuplicates && isAllEmoji && spamCount > 0) { isSpam = true; } else if (spamCount > hyperCleanup.options.spamAllowedAmount) { isSpam = true; } if (isSpam) { // Performance improvement: don't log when spam detected // Left in for now console.log('Spam detected: ' + messageText); } // Make sure we actually log the message AFTER we've checked for it hyperCleanup.logMessage(messageText, message); return isSpam; }; hyperCleanup.cleanJson = (json) => { // Potential paths: // .continuationContents.liveChatContinuation.actions[0].replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.message.runs[0].text // .continuationContents.liveChatContinuation.actions[0].replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.message.runs[0].emoji if (!json || !json.continuationContents || !json.continuationContents.liveChatContinuation || !json.continuationContents.liveChatContinuation.actions) return json; try { // Loop all message actions and remove stuff that looks like spam // We go FORWARDS so that we catch the first spam message and then clean from there // This is a bit more performance heavy than going backwards (splicing from start, resetting indexes) but the alternative is reversing twice...which is heavier for (let iContAction = 0; iContAction < json.continuationContents.liveChatContinuation.actions.length; iContAction++) { let contAction = json.continuationContents.liveChatContinuation.actions[iContAction]; if (contAction.replayChatItemAction) { // If is a chat replay for (let iChatAction = 0; iChatAction < contAction.replayChatItemAction.actions.length; iChatAction++) { let chatAction = contAction.replayChatItemAction.actions[iChatAction]; if (chatAction.addChatItemAction && chatAction.addChatItemAction.item && chatAction.addChatItemAction.item.liveChatTextMessageRenderer) { if (hyperCleanup.isSpamMessage(chatAction.addChatItemAction.item.liveChatTextMessageRenderer.message)) { // Remove & reset contAction.replayChatItemAction.actions.splice(iChatAction, 1); iChatAction--; } } } if (contAction.replayChatItemAction.actions.length === 0) { // Remove & reset json.continuationContents.liveChatContinuation.actions.splice(iContAction, 1); iContAction--; } } else if (contAction.addChatItemAction && contAction.addChatItemAction.item && contAction.addChatItemAction.item.liveChatTextMessageRenderer) { // If is actually a live chat if (hyperCleanup.isSpamMessage(contAction.addChatItemAction.item.liveChatTextMessageRenderer.message)) { // Remove & reset json.continuationContents.liveChatContinuation.actions.splice(iContAction, 1); iContAction--; } } } } catch (exception) { // If an error happens, let's just ignore it and pretend we did nothing console.error('An error occurred while cleaning up chat'); console.error(exception); } return json; }; // This is borrowed from HyperChat - YouTube makes fetch requests when it gets messages window.fetch = async (...args) => { const url = args[0].url; const result = await originalFetch(...args); // Only do stuff if we actually are a live chat request if (url.startsWith('https://www.youtube.com/youtubei/v1/live_chat/get_live_chat')) { // Clone it because otherwise weird stuff happens let newResult = await result.clone(); let json = await newResult.json(); // If you want to do more digging into the json //console.log(json); json = hyperCleanup.cleanJson(json); // New response object to pass back, which is read by the parent fetch() // No need to work out the init obj, since we can just use our base result let madeResult = new Response(JSON.stringify(json), newResult); return madeResult; } return result; }; })(); // message object looks like: /* { "runs": [ { "text": "hello" }, { "emoji": { "emojiId": "UCyl1z3jo3XHR1riLFKG5UAg/LpZtX7zMKcmu_APKm7rAAw", "shortcuts": [ ":_hic1:", ":hic1:" ], "searchTerms": [ "_hic1", "hic1" ], "image": { "thumbnails": [ { "url": "https://yt3.ggpht.com/EAnKSTeFBCc0gzNVIgkZGU5OZiLwQVb3gVjySef6GRhTq2Mp6HJ5r1rQo7QBX2iUppf9Xi_C=w24-h24-c-k-nd", "width": 24, "height": 24 }, { "url": "https://yt3.ggpht.com/EAnKSTeFBCc0gzNVIgkZGU5OZiLwQVb3gVjySef6GRhTq2Mp6HJ5r1rQo7QBX2iUppf9Xi_C=w48-h48-c-k-nd", "width": 48, "height": 48 } ], "accessibility": { "accessibilityData": { "label": "hic1" } } }, "isCustomEmoji": true } } ] } */