The spam filter now flags image and quote spam. Try this thread to test it.
>>>/soy/10710928[code]
// UserScript
// @name Spam Filter for Soyjak.st
// @namespace
http://tampermonkey.net/// @version 3.0
// @description Hide spammy posts automatically
// @match *://*soyjak.st/*
// /UserScript
const thresholds = {
repeatThreshold: 10,
densityThreshold: 0.22,
elongationThreshold: 10,
maxImagesThreshold: 5,
imageSrcRepeatThreshold: 5,
};
// isElongated function
function isElongated(word, threshold) {
let elongation = {};
for (let i = 1; i < word.length; i++) {
elongation[word[i]] = (elongation[word[i]] || 0) + 1;
if (elongation[word[i]] >= threshold) {
return true;
}
}
return false;
}
// Checks for word, quote, and image spam
function isSpam(element, thresholds) {
// Check text content for repeated words and density
const message = element.textContent.toLowerCase();
const words = message.split(/\s+/);
const wordCount = {};
let totalQuotes = 0;
let totalWords = 0;
for (let word of words) {
if (!word.trim()) continue;
let quoteMatch = word.match(/^>>(\d{1,9})/);
while (quoteMatch) {
if (++totalQuotes > thresholds.repeatThreshold) {
return true;
}
word = word.slice(quoteMatch[0].length);
quoteMatch = word.match(/^>>(\d{1,9})/);
if (!word) continue;
}
totalWords++;
if (isElongated(word, thresholds.elongationThreshold)) {
return true;
}
wordCount[word] = (wordCount[word] || 0) + 1;
}
for (const count of Object.values(wordCount)) {
const density = count / totalWords;
if (count >= thresholds.repeatThreshold && density >= thresholds.densityThreshold) {
return true;
}
}
// Check for <img> tags
const images = element.querySelectorAll('img');
const imageSrcCount = {};
images.forEach((img) => {
const src = img.getAttribute('src') || '';
imageSrcCount[src] = (imageSrcCount[src] || 0) + 1;
});
if (images.length > thresholds.maxImagesThreshold) {
return true;
}
for (const count of Object.values(imageSrcCount)) {
if (count >= thresholds.imageSrcRepeatThreshold) {
return true;
}
}
return false;
}
// Function to process a single post element
function processPostElement(el) {
if (!isSpam(el, thresholds)) {
return;
}
const postContainer = el.closest('.post');
if (!postContainer) {
return;
}
postContainer.style.display = 'none'; // Hide the whole post
const next = postContainer.nextSibling;
if (next && next.nodeName === 'BR') {
next.remove();
}
}
// Set up MutationObserver to watch for new posts
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type !
'childList' || mutation.addedNodes.length = 0) return;
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const newPostBodies = node.matches('div.post.reply div.body')
? [node]
: node.querySelectorAll('div.post.reply div.body');
newPostBodies.forEach(processPostElement);
}
});
});
});
// Observe the parent container of posts
const threadContainer = document.querySelector('div.thread') || document.body;
observer.observe(threadContainer, {
childList: true,
subtree: true
});
// Process existing posts, main logic
const postElements = document.querySelectorAll('div.post.reply div.body');
postElements.forEach(processPostElement);
[/code]