/* * CSB Chat Filter * Copyright (C) 2026 counter-strike-boost.com * * Cleans public chat: censors words from a blacklist (with word-boundary and * simple leetspeak folding), blocks messages that contain IP addresses or URLs * (anti-advertising), and gags repeat offenders for an escalating duration. * Clean messages are re-emitted so normal chat still works. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. It is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See for details. */ #include new const PLUGIN[] = "CSB Chat Filter" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define MAX_WORDS 16 new const g_szBadWords[][] = { "fuck", "shit", "bitch", "asshole", "bastard", "cunt", "nigger", "faggot" } new g_pEnabled, g_pAntiAd, g_pGagBase new g_iOffenses[33] new Float:g_fGagUntil[33] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_filter_enabled", "1") g_pAntiAd = register_cvar("csb_filter_antiad", "1") g_pGagBase = register_cvar("csb_filter_gagbase", "20") register_clcmd("say", "cmdSay") register_clcmd("say_team", "cmdSayTeam") } public client_putinserver(id) { g_iOffenses[id] = 0 g_fGagUntil[id] = 0.0 } public cmdSay(id) { return handleChat(id, false) } public cmdSayTeam(id) { return handleChat(id, true) } handleChat(id, bool:team) { if (!get_pcvar_num(g_pEnabled) || !is_user_connected(id)) return PLUGIN_CONTINUE new said[192] read_args(said, charsmax(said)) remove_quotes(said) trim(said) if (!said[0]) return PLUGIN_HANDLED /* still gagged? */ if (get_gametime() < g_fGagUntil[id]) { new left = floatround(g_fGagUntil[id] - get_gametime(), floatround_ceil) client_print(id, print_chat, "[CSB] You are gagged for %d more second(s).", left) return PLUGIN_HANDLED } /* anti-advertising: block IPs / urls outright and escalate */ if (get_pcvar_num(g_pAntiAd) && looksLikeAd(said)) { punish(id) client_print(id, print_chat, "[CSB] Advertising is not allowed here.") return PLUGIN_HANDLED } /* censor bad words in place */ new bool:censored = censorMessage(said) if (censored) punish(id) new name[32] get_user_name(id, name, charsmax(name)) reBroadcast(id, name, said, team) return PLUGIN_HANDLED } bool:looksLikeAd(const msg[]) { /* url schemes / common tlds */ if (containi(msg, "http") != -1 || containi(msg, "www.") != -1) return true if (containi(msg, ".com") != -1 || containi(msg, ".net") != -1 || containi(msg, ".ro") != -1 || containi(msg, ".org") != -1) return true /* dotted-quad IP like 12.34.56.78 */ new dots = 0, digits = 0, run = 0 for (new i = 0; msg[i]; i++) { new c = msg[i] if (c >= '0' && c <= '9') { digits++ run++ } else if (c == '.') { if (run > 0) dots++ run = 0 } else { run = 0 } } if (dots >= 3 && digits >= 4) return true return false } /* fold a single character for leetspeak-insensitive matching */ foldChar(c) { switch (c) { case 'A', 'a', '@', '4': return 'a' case 'E', 'e', '3': return 'e' case 'I', 'i', '1', '!': return 'i' case 'O', 'o', '0': return 'o' case 'S', 's', '$', '5': return 's' case 'T', 't', '7': return 't' } if (c >= 'A' && c <= 'Z') return c + ('a' - 'A') return c } bool:censorMessage(msg[]) { new folded[192] new len = strlen(msg) if (len > charsmax(folded)) len = charsmax(folded) for (new i = 0; i < len; i++) folded[i] = foldChar(msg[i]) folded[len] = 0 new bool:hit = false for (new w = 0; w < sizeof(g_szBadWords); w++) { new wlen = strlen(g_szBadWords[w]) new start = 0, pos while ((pos = containi(folded[start], g_szBadWords[w])) != -1) { new at = start + pos /* overwrite the matched span with asterisks */ for (new k = 0; k < wlen; k++) msg[at + k] = '*' hit = true start = at + wlen if (start >= len) break } } return hit } punish(id) { g_iOffenses[id]++ new base = get_pcvar_num(g_pGagBase) if (base < 1) base = 1 /* escalate: 1st strike warns, later strikes gag for base * (n-1) */ if (g_iOffenses[id] >= 2) { new secs = base * (g_iOffenses[id] - 1) g_fGagUntil[id] = get_gametime() + float(secs) client_print(id, print_chat, "[CSB] Watch your language. Gagged for %d second(s).", secs) log_amx("[CSB Filter] gagged player id %d for %d s (offense #%d)", id, secs, g_iOffenses[id]) } else { client_print(id, print_chat, "[CSB] Please keep the chat clean. This is a warning.") } } reBroadcast(id, const name[], const msg[], bool:team) { new prefix[24] if (team) { new t = get_user_team(id) switch (t) { case 1: copy(prefix, charsmax(prefix), "(T) ") case 2: copy(prefix, charsmax(prefix), "(CT) ") default: copy(prefix, charsmax(prefix), "(Spec) ") } } new out[224] formatex(out, charsmax(out), "^x03%s%s^x01 : %s", prefix, name, msg) new msgid = get_user_msgid("SayText") new players[32], num get_players(players, num, "ch") for (new i = 0; i < num; i++) { message_begin(MSG_ONE, msgid, _, players[i]) write_byte(id) write_string(out) message_end() } }