Sleazy Fork is available in English.
Hides AI-disclosed listings on itch.io. Uses ?exclude= server-side AND DOM scraping on visible disclosure links for full coverage.
// ==UserScript==
// @name itch.io — AI Filter
// @namespace https://github.com/moonbun/itchio-ai-filter
// @version 4.0.0
// @description Hides AI-disclosed listings on itch.io. Uses ?exclude= server-side AND DOM scraping on visible disclosure links for full coverage.
// @author moonbun
// @match https://itch.io/games*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
if (!location.pathname.startsWith('/games')) return;
// ── URL helpers (?exclude= server-side filter) ───────────────────────────────
const EXCLUDE_TAGS = ['ai-assisted', 'ai-generated'];
function isFiltered() {
const ex = new URLSearchParams(location.search).getAll('exclude');
return EXCLUDE_TAGS.some(t => ex.includes(`tg.${t}`));
}
function filteredUrl() {
const params = new URLSearchParams(location.search);
params.delete('exclude');
EXCLUDE_TAGS.forEach(t => params.append('exclude', `tg.${t}`));
return location.pathname + '?' + params.toString() + location.hash;
}
function unfilteredUrl() {
const params = new URLSearchParams(location.search);
params.delete('exclude');
const qs = params.toString();
return location.pathname + (qs ? '?' + qs : '') + location.hash;
}
// ── DOM scraping (catches what ?exclude= misses) ─────────────────────────────
// The disclosure row is visible in game cards as linked text.
// We match on href patterns like /games/ai-assisted and /games/ai-generated.
const AI_HREF_PATTERN = /\/games\/(ai-assisted|ai-generated|ai-graphics|ai-audio|ai-code|ai-text)/;
// Also catch plain text in case itch.io changes link structure
const AI_TEXT_PATTERN = /\bai[- ](assisted|generated|graphics|audio|code|text)\b/i;
const CARD_SELECTOR = '.game_cell, .game_thumb, [class*="game_cell"]';
let hiddenCount = 0;
function cardHasAI(card) {
// Check links by href (most reliable)
for (const a of card.querySelectorAll('a[href]')) {
if (AI_HREF_PATTERN.test(a.getAttribute('href'))) return true;
}
// Fallback: check visible text in info/meta sections
const meta = card.querySelector('[class*="meta"], [class*="info"], [class*="disclosure"], [class*="detail"]');
if (meta && AI_TEXT_PATTERN.test(meta.textContent)) return true;
return false;
}
function hideCard(card) {
if (!card.dataset.aiFiltered) {
card.dataset.aiFiltered = '1';
card.style.setProperty('display', 'none', 'important');
hiddenCount++;
}
}
function scan(root = document) {
root.querySelectorAll(CARD_SELECTOR).forEach(card => {
if (cardHasAI(card)) hideCard(card);
});
updateBadge();
}
const observer = new MutationObserver(mutations => {
for (const mut of mutations) {
for (const node of mut.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (node.matches?.(CARD_SELECTOR)) {
if (cardHasAI(node)) hideCard(node);
} else {
scan(node);
}
}
}
});
// ── Badge ────────────────────────────────────────────────────────────────────
const badge = document.createElement('div');
Object.assign(badge.style, {
position: 'fixed', bottom: '16px', right: '16px', zIndex: '999999',
display: 'flex', alignItems: 'center', gap: '8px',
background: '#1d1d1f', color: '#e0e0e0',
fontSize: '12px', fontFamily: 'system-ui, sans-serif',
borderRadius: '20px', padding: '6px 12px',
boxShadow: '0 2px 8px rgba(0,0,0,.45)',
userSelect: 'none', opacity: '0.88', transition: 'opacity .2s',
});
const dot = document.createElement('span');
dot.style.cssText = 'width:8px;height:8px;border-radius:50%;flex-shrink:0;';
const lbl = document.createElement('span');
const btn = document.createElement('a');
Object.assign(btn.style, {
background: '#444', color: '#ddd', borderRadius: '10px',
padding: '2px 8px', fontSize: '11px', textDecoration: 'none',
fontFamily: 'inherit', cursor: 'pointer',
});
function updateBadge() {
const serverFiltered = isFiltered();
dot.style.background = serverFiltered ? '#5db86a' : '#e0a040';
lbl.textContent = serverFiltered
? `AI filter on · ${hiddenCount} hidden`
: `AI filter (login needed for full coverage) · ${hiddenCount} hidden`;
btn.textContent = serverFiltered ? 'Turn off' : 'Filter AI';
btn.href = serverFiltered ? unfilteredUrl() : filteredUrl();
}
badge.append(dot, lbl, btn);
badge.addEventListener('mouseenter', () => badge.style.opacity = '1');
badge.addEventListener('mouseleave', () => badge.style.opacity = '0.88');
document.body.appendChild(badge);
// ── Boot ─────────────────────────────────────────────────────────────────────
updateBadge();
observer.observe(document.body, { childList: true, subtree: true });
scan();
})();