Spaces:
Running
Running
Download safety.js from CyberMax-tools/hushfox: direct link, hf CLI and curl.
- Browser
- Download file 3.38 kB
-
https://huggingface.co/spaces/CyberMax-tools/hushfox/resolve/main/safety.js
- Command line
-
hf download hf://spaces/CyberMax-tools/hushfox/safety.js
-
curl -L -o safety.js https://huggingface.co/spaces/CyberMax-tools/hushfox/resolve/main/safety.js
3.38 kB
| // Hushfox safety filter: keeps every deck family-safe. Used by tests/words.test.mjs on the hand-written library and | |
| // by the room Worker on every AI-generated category before it is cached or shown. Whole-word match on a normalised | |
| // copy (accents stripped, lower case), so "glass" or "Scunthorpe"-style words are not caught by accident. | |
| // (c) 2026 CyberMax. All rights reserved. | |
| const BLOCK = [ | |
| // profanity and insults | |
| 'damn', 'hell', 'crap', 'shit', 'fuck', 'fucking', 'bitch', 'bastard', 'ass', 'arse', 'asshole', 'dick', 'cock', 'piss', | |
| 'bollocks', 'bugger', 'wanker', 'twat', 'prick', 'slut', 'whore', 'idiot', 'moron', 'stupid', 'retard', 'loser', 'dumb', | |
| // sexual | |
| 'sex', 'sexy', 'porn', 'nude', 'naked', 'boobs', 'boob', 'breast', 'penis', 'vagina', 'orgasm', 'condom', 'erotic', | |
| 'stripper', 'strip club', 'brothel', 'fetish', 'kinky', 'lingerie', 'hooker', 'escort', 'viagra', 'onlyfans', | |
| // drugs, alcohol excess, gambling | |
| 'cocaine', 'heroin', 'meth', 'weed', 'marijuana', 'cannabis', 'joint', 'bong', 'crack', 'ecstasy', 'lsd', 'drunk', | |
| 'hangover', 'vodka shot', 'shots', 'casino', 'gambling', 'cigarette', 'vape', 'tobacco', | |
| // violence, weapons and death | |
| 'gun', 'rifle', 'pistol', 'shotgun', 'bullet', 'bomb', 'grenade', 'kill', 'killer', 'murder', 'suicide', 'blood', | |
| 'gore', 'corpse', 'torture', 'terrorist', 'massacre', 'stab', 'shoot', 'shooting', 'war crime', 'execution', | |
| // hate and extremism | |
| 'nazi', 'hitler', 'kkk', 'racist', 'slave', 'slavery', 'genocide', 'jihad', | |
| ]; | |
| const N = (s) => String(s).normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim(); | |
| const SET = new Set(BLOCK.map(N)); | |
| // Harmless everyday phrases that contain a blocked word. | |
| const ALLOW = new Set(['staple gun', 'glue gun', 'water gun', 'pop gun', 'water pistol', 'shooting star', 'bullet train'].map(N)); | |
| /** The blocked term found in text, or null. Checks single words and two-word phrases. */ | |
| export function blocked(text) { | |
| if (ALLOW.has(N(text))) return null; | |
| const w = N(text).split(' ').filter(Boolean); | |
| for (let i = 0; i < w.length; i++) { | |
| if (SET.has(w[i])) return w[i]; | |
| if (i + 1 < w.length && SET.has(`${w[i]} ${w[i + 1]}`)) return `${w[i]} ${w[i + 1]}`; | |
| } | |
| return null; | |
| } | |
| /** A word (or, with max 40, a category) is acceptable: letters (any language), spaces and simple punctuation, 1-max chars, not blocked. */ | |
| export function okEntry(s, max = 32) { | |
| const t = String(s || '').trim(); | |
| return t.length >= 1 && t.length <= max && /^[\p{L}\p{N}][\p{L}\p{N} '’\-&.]*$/u.test(t) && !blocked(t); | |
| } | |
| /** Clean an AI answer {category, words[]} -> {cat, words[]} or null. Needs 8+ safe, distinct words. */ | |
| export function cleanAi(obj) { | |
| if (!obj || typeof obj !== 'object') return null; | |
| const cat = String(obj.category || '').trim().replace(/\s+/g, ' '); | |
| if (!okEntry(cat, 40)) return null; | |
| const seen = new Set(); | |
| const words = []; | |
| for (const raw of Array.isArray(obj.words) ? obj.words : []) { | |
| const w = String(raw || '').trim().replace(/\s+/g, ' ').replace(/^[-*\d.\s]+/, ''); | |
| const k = N(w); | |
| if (!okEntry(w) || seen.has(k) || k === N(cat)) continue; | |
| seen.add(k); | |
| words.push(w.charAt(0).toUpperCase() + w.slice(1)); | |
| if (words.length >= 16) break; | |
| } | |
| return words.length >= 8 ? { cat: cat.charAt(0).toUpperCase() + cat.slice(1), words } : null; | |
| } | |