diff --git a/code/__HELPERS/chat_filter.dm b/code/__HELPERS/chat_filter.dm
index 8b5c706b71a..cb3346ab42c 100644
--- a/code/__HELPERS/chat_filter.dm
+++ b/code/__HELPERS/chat_filter.dm
@@ -1,6 +1,6 @@
// [2] is the group index of the blocked term when it is not using word bounds.
// This is sanity checked by unit tests.
-#define GET_MATCHED_GROUP(regex) (regex.group[2] || regex.match)
+#define GET_MATCHED_GROUP(regex) (lowertext(regex.group[2] || regex.match))
/// Given a text, will return what word is on the IC filter, with the reason.
/// Returns null if the message is OK.
@@ -35,4 +35,37 @@
return null
+/// Given a text, will return what word is on the soft IC filter, with the reason.
+/// Returns null if the message is OK.
+/proc/is_soft_ic_filtered(message)
+ if (config.soft_ic_filter_regex?.Find(message))
+ var/matched_group = GET_MATCHED_GROUP(config.soft_ic_filter_regex)
+ return list(
+ matched_group,
+ config.soft_ic_filter_reasons[matched_group] || config.soft_ic_outside_pda_filter_reasons[matched_group] || config.soft_shared_filter_reasons[matched_group],
+ )
+
+ return null
+
+/// Given a text, will return what word is on the soft IC filter, ignoring words allowed on the PDA, with the reason.
+/// Returns null if the message is OK.
+/proc/is_soft_ic_filtered_for_pdas(message)
+ if (config.soft_ic_outside_pda_filter_regex?.Find(message))
+ var/matched_group = GET_MATCHED_GROUP(config.soft_ic_outside_pda_filter_regex)
+ return list(
+ matched_group,
+ config.soft_ic_filter_reasons[matched_group] || config.soft_shared_filter_reasons[matched_group],
+ )
+
+ return null
+
+///Given a text, will return that word is on the soft OOC filter, with the reason.
+/// Returns null if the message is OK.
+/proc/is_soft_ooc_filtered(message)
+ if (config.soft_ooc_filter_regex?.Find(message))
+ var/matched_group = GET_MATCHED_GROUP(config.soft_ooc_filter_regex)
+ return list(matched_group, config.soft_shared_filter_reasons[matched_group])
+
+ return null
+
#undef GET_MATCHED_GROUP
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 3e75cc93d99..4cd2c5beff1 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -23,7 +23,7 @@
///returns nothing with an alert instead of the message if it contains something in the ic filter, and sanitizes normally if the name is fine. It returns nothing so it backs out of the input the same way as if you had entered nothing.
/proc/sanitize_name(t,allow_numbers=FALSE)
- if(is_ic_filtered(t))
+ if(is_ic_filtered(t) || is_soft_ic_filtered(t))
tgui_alert(usr, "You cannot set a name that contains a word prohibited in IC chat!")
return ""
var/r = reject_bad_name(t,allow_numbers=allow_numbers,strict=TRUE)
@@ -222,7 +222,7 @@
return //(not case sensitive)
// Protects against names containing IC chat prohibited words.
- if(is_ic_filtered(t_out))
+ if(is_ic_filtered(t_out) || is_soft_ic_filtered(t_out))
return
return t_out
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index aecf9ed1c6f..0c02cacffdc 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -32,6 +32,15 @@
/// A regex that matches words blocked IC, but not in PDAs
var/static/regex/ic_outside_pda_filter_regex
+
+ /// A regex that matches words soft blocked IC
+ var/static/regex/soft_ic_filter_regex
+
+ /// A regex that matches words soft blocked OOC
+ var/static/regex/soft_ooc_filter_regex
+
+ /// A regex that matches words soft blocked IC, but not in PDAs
+ var/static/regex/soft_ic_outside_pda_filter_regex
/// An assoc list of blocked IC words to their reasons
var/static/list/ic_filter_reasons
@@ -42,6 +51,15 @@
/// An assoc list of words that are blocked both IC and OOC to their reasons
var/static/list/shared_filter_reasons
+ /// An assoc list of soft blocked IC words to their reasons
+ var/static/list/soft_ic_filter_reasons
+
+ /// An assoc list of words that are soft blocked IC, but not in PDAs, to their reasons
+ var/static/list/soft_ic_outside_pda_filter_reasons
+
+ /// An assoc list of words that are soft blocked both IC and OOC to their reasons
+ var/static/list/soft_shared_filter_reasons
+
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
return
@@ -369,6 +387,9 @@ Example config:
ic_filter_reasons = try_extract_from_word_filter(word_filter, "ic")
ic_outside_pda_filter_reasons = try_extract_from_word_filter(word_filter, "ic_outside_pda")
shared_filter_reasons = try_extract_from_word_filter(word_filter, "shared")
+ soft_ic_filter_reasons = try_extract_from_word_filter(word_filter, "soft_ic")
+ soft_ic_outside_pda_filter_reasons = try_extract_from_word_filter(word_filter, "soft_ic_outside_pda")
+ soft_shared_filter_reasons = try_extract_from_word_filter(word_filter, "soft_shared")
update_chat_filter_regexes()
@@ -381,6 +402,9 @@ Example config:
ic_filter_reasons = list()
ic_outside_pda_filter_reasons = list()
shared_filter_reasons = list()
+ soft_ic_filter_reasons = list()
+ soft_ic_outside_pda_filter_reasons = list()
+ soft_shared_filter_reasons = list()
for (var/line in world.file2list("[directory]/in_character_filter.txt"))
if (!line)
@@ -397,6 +421,9 @@ Example config:
ic_filter_regex = compile_filter_regex(ic_filter_reasons + ic_outside_pda_filter_reasons + shared_filter_reasons)
ic_outside_pda_filter_regex = compile_filter_regex(ic_filter_reasons + shared_filter_reasons)
ooc_filter_regex = compile_filter_regex(shared_filter_reasons)
+ soft_ic_filter_regex = compile_filter_regex(soft_ic_filter_reasons + soft_ic_outside_pda_filter_reasons + soft_shared_filter_reasons)
+ soft_ic_outside_pda_filter_regex = compile_filter_regex(soft_ic_filter_reasons + soft_shared_filter_reasons)
+ soft_ooc_filter_regex = compile_filter_regex(soft_shared_filter_reasons)
/datum/controller/configuration/proc/try_extract_from_word_filter(list/word_filter, key)
var/list/banned_words = word_filter[key]
@@ -409,7 +436,11 @@ Example config:
DelayedMessageAdmins(message)
return list()
- return banned_words
+ var/list/formatted_banned_words = list()
+
+ for (var/banned_word in banned_words)
+ formatted_banned_words[lowertext(banned_word)] = banned_words[banned_word]
+ return formatted_banned_words
/datum/controller/configuration/proc/compile_filter_regex(list/banned_words)
if (isnull(banned_words) || banned_words.len == 0)
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 0a2196b4df3..d7e5b9aa761 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -180,7 +180,7 @@
client.images.Remove(human_image)
return ..()
-/mob/camera/imaginary_friend/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/camera/imaginary_friend/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if (!message)
return
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index 65f0d187aa0..f635ba70484 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -163,7 +163,7 @@
to_chat(src, span_notice("As a split personality, you cannot do anything but observe. However, you will eventually gain control of your body, switching places with the current personality."))
to_chat(src, span_warning("Do not commit suicide or put the body in a deadly position. Behave like you care about it as much as the owner."))
-/mob/living/split_personality/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/split_personality/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
to_chat(src, span_warning("You cannot speak, your other self is controlling your body!"))
return FALSE
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 56cc5363c55..12ae08306ad 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1315,7 +1315,7 @@
if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT))
var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text
// Check the new name against the chat filter. If it triggers the IC chat filter, give an option to confirm.
- if(newname && !(is_ic_filtered(newname) && tgui_alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", list("Confirm", "Cancel")) != "Confirm"))
+ if(newname && !(is_ic_filtered(newname) || is_soft_ic_filtered(newname) && tgui_alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", list("Confirm", "Cancel")) != "Confirm"))
vv_auto_rename(newname)
if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT))
diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm
index 52ee9417c6e..521991c8515 100644
--- a/code/game/objects/items/AI_modules.dm
+++ b/code/game/objects/items/AI_modules.dm
@@ -261,6 +261,12 @@ AI MODULES
if(is_ic_filtered(targName))
to_chat(user, span_warning("Error: Law contains invalid text.")) // AI LAW 2 SAY U W U WITHOUT THE SPACES
return
+ var/list/soft_filter_result = is_soft_ooc_filtered(targName)
+ if(soft_filter_result)
+ if(tgui_alert(user,"Your law contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to use it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for an AI law. Law: \"[html_encode(targName)]\"")
+ log_admin_private("[key_name(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for an AI law. Law: \"[targName]\"")
laws[1] = targName
..()
@@ -468,6 +474,12 @@ AI MODULES
if(is_ic_filtered(targName))
to_chat(user, span_warning("Error: Law contains invalid text."))
return
+ var/list/soft_filter_result = is_soft_ooc_filtered(targName)
+ if(soft_filter_result)
+ if(tgui_alert(user,"Your law contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to use it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for an AI law. Law: \"[html_encode(targName)]\"")
+ log_admin_private("[key_name(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for an AI law. Law: \"[targName]\"")
laws[1] = targName
..()
@@ -493,6 +505,12 @@ AI MODULES
if(is_ic_filtered(targName)) // not even the syndicate can uwu
to_chat(user, span_warning("Error: Law contains invalid text."))
return
+ var/list/soft_filter_result = is_soft_ooc_filtered(targName)
+ if(soft_filter_result)
+ if(tgui_alert(user,"Your law contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to use it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for an AI law. Law: \"[html_encode(targName)]\"")
+ log_admin_private("[key_name(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for an AI law. Law: \"[targName]\"")
laws[1] = targName
..()
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 975ed9fd52e..6a0b2ef115e 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -769,6 +769,13 @@ GLOBAL_LIST_EMPTY(PDAs)
REPORT_CHAT_FILTER_TO_USER(user, filter_result)
return
+ var/list/soft_filter_result = is_soft_ic_filtered_for_pdas(message)
+ if (soft_filter_result)
+ if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to send it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[html_encode(message)]\"")
+ log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[message]\"")
+
if(prob(1))
message += "\nSent from my PDA"
// Send the signal
diff --git a/code/game/say.dm b/code/game/say.dm
index 8ecbe1991a0..6fc7d9a5849 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -24,7 +24,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
"[FREQ_CTF_YELLOW]" = "yellowteamradio"
))
-/atom/movable/proc/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/atom/movable/proc/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(!can_speak())
return
if(sanitize)
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index 5aa82accde5..d656a09d8d4 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -32,7 +32,7 @@
// If the new name is something that would be restricted by IC chat filters,
// give the admin a warning but allow them to do it anyway if they want.
- if(is_ic_filtered(new_name) && tgui_alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", list("Confirm", "Cancel")) == "Cancel")
+ if(is_ic_filtered(new_name) || is_soft_ic_filtered(new_name) && tgui_alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", list("Confirm", "Cancel")) == "Cancel")
return
if( !new_name || !M )
diff --git a/code/modules/antagonists/blob/blob_mobs.dm b/code/modules/antagonists/blob/blob_mobs.dm
index a49ce6176cf..69ebebff208 100644
--- a/code/modules/antagonists/blob/blob_mobs.dm
+++ b/code/modules/antagonists/blob/blob_mobs.dm
@@ -72,7 +72,7 @@
return 1
return ..()
-/mob/living/simple_animal/hostile/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/simple_animal/hostile/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(sanitize)
message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
var/spanned_message = say_quote(message)
diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm
index 7af3cb2c240..5804f497311 100644
--- a/code/modules/antagonists/blob/overmind.dm
+++ b/code/modules/antagonists/blob/overmind.dm
@@ -243,7 +243,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
blob_points = clamp(blob_points + points, 0, max_blob_points)
hud_used.blobpwrdisplay.maptext = MAPTEXT("
[round(blob_points)]
")
-/mob/camera/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/camera/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if (!message)
return
diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm
index 65c8a336a00..b29a140570d 100644
--- a/code/modules/antagonists/cult/cult_comms.dm
+++ b/code/modules/antagonists/cult/cult_comms.dm
@@ -25,6 +25,13 @@
if(filter_result)
REPORT_CHAT_FILTER_TO_USER(usr, filter_result)
return
+
+ var/list/soft_filter_result = is_soft_ic_filtered(input)
+ if(soft_filter_result)
+ if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[html_encode(input)]\"")
+ log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[input]\"")
cultist_commune(usr, input)
/datum/action/innate/cult/comm/proc/cultist_commune(mob/living/user, message)
@@ -32,7 +39,7 @@
if(!message)
return
user.whisper("O bidai nabora se[pick("'","`")]sma!", language = /datum/language/common)
- user.whisper(html_decode(message))
+ user.whisper(html_decode(message), filterproof = TRUE)
var/title = "Acolyte"
var/span = "cult italic"
if(user.mind && user.mind.has_antag_datum(/datum/antagonist/cult/master))
diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm
index d696e1ceb2e..6185b91b30e 100644
--- a/code/modules/antagonists/disease/disease_mob.dm
+++ b/code/modules/antagonists/disease/disease_mob.dm
@@ -111,7 +111,7 @@ the new instance inside the host to be updated to the template's stats.
for(var/datum/disease_ability/ability in purchased_abilities)
. += span_notice("[ability.name]")
-/mob/camera/disease/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/camera/disease/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(!message)
return
if(sanitize)
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index 12f886bfd2f..934e6d8a3a5 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -158,7 +158,7 @@
/mob/living/simple_animal/revenant/med_hud_set_status()
return //we use no hud
-/mob/living/simple_animal/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/simple_animal/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(!message)
return
if(sanitize)
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index d7868326c7a..4cf58f9db79 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -36,6 +36,13 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
REPORT_CHAT_FILTER_TO_USER(usr, filter_result)
return
+ var/list/soft_filter_result = is_soft_ooc_filtered(msg)
+ if (soft_filter_result)
+ if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[msg]\"")
+ log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[msg]\"")
+
if(!msg)
return
diff --git a/code/modules/mob/dead/observer/observer_say.dm b/code/modules/mob/dead/observer/observer_say.dm
index d70f122a1c5..dff0145de2c 100644
--- a/code/modules/mob/dead/observer/observer_say.dm
+++ b/code/modules/mob/dead/observer/observer_say.dm
@@ -9,8 +9,21 @@
mods[RADIO_EXTENSION] = GLOB.department_radio_keys[mods[RADIO_KEY]]
return message
-/mob/dead/observer/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/dead/observer/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
message = trim(message) //trim now and sanitize after checking for special admin radio keys
+
+ var/list/filter_result = is_ooc_filtered(message)
+ if (filter_result)
+ REPORT_CHAT_FILTER_TO_USER(usr, filter_result)
+ return
+
+ var/list/soft_filter_result = is_soft_ooc_filtered(message)
+ if (soft_filter_result)
+ if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[message]\"")
+ log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[message]\"")
+
if(!message)
return
var/list/message_mods = list()
diff --git a/code/modules/mob/living/brain/brain_say.dm b/code/modules/mob/living/brain/brain_say.dm
index 8438e904302..71b73b940d0 100644
--- a/code/modules/mob/living/brain/brain_say.dm
+++ b/code/modules/mob/living/brain/brain_say.dm
@@ -1,4 +1,4 @@
-/mob/living/brain/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/brain/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterpoof = null)
if(!(container && istype(container, /obj/item/mmi)))
return //No MMI, can't speak, bucko./N
else
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index 7a5b712c689..64a263b130a 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -96,24 +96,36 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
return new_msg
-/mob/living/say(message, bubble_type,list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/say(message, bubble_type,list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof)
var/list/filter_result
- if(client && !forced)
+ var/list/soft_filter_result
+ if(client && !forced && !filterproof)
//The filter doesn't act on the sanitized message, but the raw message.
filter_result = is_ic_filtered(message)
+ if(!filter_result)
+ soft_filter_result = is_soft_ic_filtered(message)
if(sanitize)
message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
if(!message || message == "")
return
- if(filter_result)
+ if(filter_result && !filterproof)
//The filter warning message shows the sanitized message though.
to_chat(src, span_warning("That message contained a word prohibited in IC chat! Consider reviewing the server rules."))
to_chat(src, span_warning("\"[message]\""))
REPORT_CHAT_FILTER_TO_USER(src, filter_result)
SSblackbox.record_feedback("tally", "ic_blocked_words", 1, lowertext(config.ic_filter_regex.match))
return
+
+ if(soft_filter_result && !filterproof)
+ if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ SSblackbox.record_feedback("tally", "soft_ic_blocked_words", 1, lowertext(config.soft_ic_filter_regex.match))
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[message]\"")
+ log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[message]\"")
+ SSblackbox.record_feedback("tally", "passed_soft_ic_blocked_words", 1, lowertext(config.soft_ic_filter_regex.match))
+
var/list/message_mods = list()
var/original_message = message
message = get_message_mods(message, message_mods)
@@ -470,10 +482,10 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
else
. = ..()
-/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof)
if(!message)
return
- say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced)
+ say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced, filterproof)
/mob/living/get_language_holder(get_minds = TRUE)
if(get_minds && mind)
diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm
index 5b0b8561c38..c36ab3874bc 100644
--- a/code/modules/mob/living/silicon/ai/ai_say.dm
+++ b/code/modules/mob/living/silicon/ai/ai_say.dm
@@ -1,4 +1,4 @@
-/mob/living/silicon/ai/say(message, bubble_type,list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/silicon/ai/say(message, bubble_type,list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(parent && istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead.
return parent.say(arglist(args))
return ..()
diff --git a/code/modules/mob/living/silicon/pai/pai_say.dm b/code/modules/mob/living/silicon/pai/pai_say.dm
index d50e9497cd4..d98f615a879 100644
--- a/code/modules/mob/living/silicon/pai/pai_say.dm
+++ b/code/modules/mob/living/silicon/pai/pai_say.dm
@@ -1,4 +1,4 @@
-/mob/living/silicon/pai/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/silicon/pai/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(silent)
to_chat(src, span_warning("Communication circuits remain uninitialized."))
else
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index c68492985d6..d91a45d2026 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -57,7 +57,7 @@
M.take_damage(50, BRUTE, MELEE, 1)
//Elites can't talk (normally)!
-/mob/living/simple_animal/hostile/asteroid/elite/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/simple_animal/hostile/asteroid/elite/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
if(can_talk)
. = ..()
return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
index 86246326b7a..26e6485cd01 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -60,7 +60,7 @@
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/become_ghost()
icon_state = "herald_ghost"
-/mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
. = ..()
playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
diff --git a/code/modules/mob/living/simple_animal/hostile/netherworld.dm b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
index 9d26d9440e0..752d47813de 100644
--- a/code/modules/mob/living/simple_animal/hostile/netherworld.dm
+++ b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
@@ -109,7 +109,7 @@
. = ..()
migo_sounds = list('sound/items/bubblewrap.ogg', 'sound/items/change_jaws.ogg', 'sound/items/crowbar.ogg', 'sound/items/drink.ogg', 'sound/items/deconstruct.ogg', 'sound/items/carhorn.ogg', 'sound/items/change_drill.ogg', 'sound/items/dodgeball.ogg', 'sound/items/eatfood.ogg', 'sound/items/megaphone.ogg', 'sound/items/screwdriver.ogg', 'sound/items/weeoo1.ogg', 'sound/items/wirecutter.ogg', 'sound/items/welder.ogg', 'sound/items/zip.ogg', 'sound/items/rped.ogg', 'sound/items/ratchet.ogg', 'sound/items/polaroid1.ogg', 'sound/items/pshoom.ogg', 'sound/items/airhorn.ogg', 'sound/items/geiger/high1.ogg', 'sound/items/geiger/high2.ogg', 'sound/voice/beepsky/creep.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/ed209_20sec.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss6.ogg', 'sound/voice/medbot/patchedup.ogg', 'sound/voice/medbot/feelbetter.ogg', 'sound/voice/human/manlaugh1.ogg', 'sound/voice/human/womanlaugh.ogg', 'sound/weapons/sear.ogg', 'sound/ambience/antag/clockcultalr.ogg', 'sound/ambience/antag/ling_aler.ogg', 'sound/ambience/antag/tatoralert.ogg', 'sound/ambience/antag/monkey.ogg', 'sound/mecha/nominal.ogg', 'sound/mecha/weapdestr.ogg', 'sound/mecha/critdestr.ogg', 'sound/mecha/imag_enh.ogg', 'sound/effects/adminhelp.ogg', 'sound/effects/alert.ogg', 'sound/effects/attackblob.ogg', 'sound/effects/bamf.ogg', 'sound/effects/blobattack.ogg', 'sound/effects/break_stone.ogg', 'sound/effects/bubbles.ogg', 'sound/effects/bubbles2.ogg', 'sound/effects/clang.ogg', 'sound/effects/clockcult_gateway_disrupted.ogg', 'sound/effects/clownstep2.ogg', 'sound/effects/curse1.ogg', 'sound/effects/dimensional_rend.ogg', 'sound/effects/doorcreaky.ogg', 'sound/effects/empulse.ogg', 'sound/effects/explosion_distant.ogg', 'sound/effects/explosionfar.ogg', 'sound/effects/explosion1.ogg', 'sound/effects/grillehit.ogg', 'sound/effects/genetics.ogg', 'sound/effects/heart_beat.ogg', 'sound/runtime/hyperspace/hyperspace_begin.ogg', 'sound/runtime/hyperspace/hyperspace_end.ogg', 'sound/effects/his_grace_awaken.ogg', 'sound/effects/pai_boot.ogg', 'sound/effects/phasein.ogg', 'sound/effects/picaxe1.ogg', 'sound/effects/ratvar_reveal.ogg', 'sound/effects/sparks1.ogg', 'sound/effects/smoke.ogg', 'sound/effects/splat.ogg', 'sound/effects/snap.ogg', 'sound/effects/tendril_destroyed.ogg', 'sound/effects/supermatter.ogg', 'sound/misc/desecration-01.ogg', 'sound/misc/desecration-02.ogg', 'sound/misc/desecration-03.ogg', 'sound/misc/bloblarm.ogg', 'sound/misc/airraid.ogg', 'sound/misc/bang.ogg','sound/misc/highlander.ogg', 'sound/misc/interference.ogg', 'sound/misc/notice1.ogg', 'sound/misc/notice2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/misc/slip.ogg', 'sound/misc/splort.ogg', 'sound/weapons/armbomb.ogg', 'sound/weapons/beam_sniper.ogg', 'sound/weapons/chainsawhit.ogg', 'sound/weapons/emitter.ogg', 'sound/weapons/emitter2.ogg', 'sound/weapons/blade1.ogg', 'sound/weapons/bladeslice.ogg', 'sound/weapons/blastcannon.ogg', 'sound/weapons/blaster.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/weapons/circsawhit.ogg', 'sound/weapons/cqchit2.ogg', 'sound/weapons/drill.ogg', 'sound/weapons/genhit1.ogg', 'sound/weapons/gun/pistol/shot_suppressed.ogg', 'sound/weapons/gun/pistol/shot.ogg', 'sound/weapons/handcuffs.ogg', 'sound/weapons/homerun.ogg', 'sound/weapons/kenetic_accel.ogg', 'sound/machines/clockcult/steam_whoosh.ogg', 'sound/machines/fryer/deep_fryer_emerge.ogg', 'sound/machines/airlock.ogg', 'sound/machines/airlock_alien_prying.ogg', 'sound/machines/airlockclose.ogg', 'sound/machines/airlockforced.ogg', 'sound/machines/airlockopen.ogg', 'sound/machines/alarm.ogg', 'sound/machines/blender.ogg', 'sound/machines/boltsdown.ogg', 'sound/machines/boltsup.ogg', 'sound/machines/buzz-sigh.ogg', 'sound/machines/buzz-two.ogg', 'sound/machines/chime.ogg', 'sound/machines/cryo_warning.ogg', 'sound/machines/defib_charge.ogg', 'sound/machines/defib_failed.ogg', 'sound/machines/defib_ready.ogg', 'sound/machines/defib_zap.ogg', 'sound/machines/deniedbeep.ogg', 'sound/machines/ding.ogg', 'sound/machines/disposalflush.ogg', 'sound/machines/door_close.ogg', 'sound/machines/door_open.ogg', 'sound/machines/engine_alert1.ogg', 'sound/machines/engine_alert2.ogg', 'sound/machines/hiss.ogg', 'sound/machines/honkbot_evil_laugh.ogg', 'sound/machines/juicer.ogg', 'sound/machines/ping.ogg', 'sound/ambience/signal.ogg', 'sound/machines/synth_no.ogg', 'sound/machines/synth_yes.ogg', 'sound/machines/terminal_alert.ogg', 'sound/machines/triple_beep.ogg', 'sound/machines/twobeep.ogg', 'sound/machines/ventcrawl.ogg', 'sound/machines/warning-buzzer.ogg', 'sound/ai/default/outbreak5.ogg', 'sound/ai/default/outbreak7.ogg', 'sound/ai/default/poweroff.ogg', 'sound/ai/default/radiation.ogg', 'sound/ai/default/shuttlecalled.ogg', 'sound/ai/default/shuttledock.ogg', 'sound/ai/default/shuttlerecalled.ogg', 'sound/ai/default/aimalf.ogg') //hahahaha fuck you code divers
-/mob/living/simple_animal/hostile/netherworld/migo/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/simple_animal/hostile/netherworld/migo/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
..()
if(stat)
return
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 1b20279a602..294479c7c40 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -144,7 +144,7 @@
// Cannot talk
-/mob/living/simple_animal/hostile/statue/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+/mob/living/simple_animal/hostile/statue/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
return
// Turn to dust when gibbed
diff --git a/code/modules/unit_tests/chat_filter.dm b/code/modules/unit_tests/chat_filter.dm
index ac59328a2f3..6a2476f0639 100644
--- a/code/modules/unit_tests/chat_filter.dm
+++ b/code/modules/unit_tests/chat_filter.dm
@@ -1,6 +1,9 @@
#define BLOCKED_IC "This message is not allowed IC, please use a different weird test phrase."
#define BLOCKED_IC_OUTSIDE_PDA "Kirby dancing is strictly prohibited on this server."
#define BLOCKED_SHARED "This message is not allowed anywhere, please use a different weird test phrase."
+#define SOFT_BLOCKED_IC "This term is commonly considered OOC, only use it with context."
+#define SOFT_BLOCKED_IC_OUTSIDE_PDA "Stop saying debug phrases ICly."
+#define SOFT_BLOCKED_SHARED "You risk being banned if this term is used improperly."
/// Tests the sanity of the chat filter, ensuring it properly blocks words and gives the reason
/datum/unit_test/chat_filter_sanity
@@ -10,6 +13,9 @@
config.shared_filter_reasons = list("blockedinshared" = BLOCKED_SHARED)
config.ic_filter_reasons = list("blockedinic" = BLOCKED_IC)
config.ic_outside_pda_filter_reasons = list("<(0_0<)" = BLOCKED_IC_OUTSIDE_PDA)
+ config.soft_shared_filter_reasons = list("testsoftblocks" = SOFT_BLOCKED_SHARED)
+ config.soft_ic_filter_reasons = list("testsofterblocks" = SOFT_BLOCKED_IC)
+ config.soft_ic_outside_pda_filter_reasons = list("testsoftestblocks" = SOFT_BLOCKED_IC_OUTSIDE_PDA)
config.update_chat_filter_regexes()
test_filter(
@@ -50,14 +56,23 @@
ic_filter_result,
pda_filter_result,
ooc_filter_result,
+ soft_ic_filter_result,
+ soft_pda_filter_result,
+ soft_ooc_filter_result,
)
var/ic_filter = is_ic_filtered(message)
var/pda_filter = is_ic_filtered_for_pdas(message)
var/ooc_filter = is_ooc_filtered(message)
+ var/soft_ic_filter = is_soft_ic_filtered(message)
+ var/soft_pda_filter = is_soft_ic_filtered_for_pdas(message)
+ var/soft_ooc_filter = is_soft_ooc_filtered(message)
test_filter_result("IC", message, ic_filter, ic_filter_result, blocked_word)
test_filter_result("PDA", message, pda_filter, pda_filter_result, blocked_word)
test_filter_result("OOC", message, ooc_filter, ooc_filter_result, blocked_word)
+ test_filter_result("Soft_IC", message, soft_ic_filter, soft_ic_filter_result, blocked_word)
+ test_filter_result("Soft_PDA", message, soft_pda_filter, soft_pda_filter_result, blocked_word)
+ test_filter_result("Soft_OOC", message, soft_ooc_filter, soft_ooc_filter_result, blocked_word)
/datum/unit_test/chat_filter_sanity/proc/test_filter_result(
filter_type,
@@ -87,3 +102,6 @@
#undef BLOCKED_IC
#undef BLOCKED_IC_OUTSIDE_PDA
#undef BLOCKED_SHARED
+#undef SOFT_BLOCKED_IC
+#undef SOFT_BLOCKED_IC_OUTSIDE_PDA
+#undef SOFT_BLOCKED_SHARED
diff --git a/config/word_filter.toml b/config/word_filter.toml
index 11484b46a33..e2a5f1c05b9 100644
--- a/config/word_filter.toml
+++ b/config/word_filter.toml
@@ -1,5 +1,6 @@
# This configuration file forms the in-game chat filter.
# Values are stored as "word" = "reason for being banned".
+# Soft filter reasons should be kept short or the tgui gets messy.
# Anything in here will be blocked from IC interactions, dead chat, and OOC
[shared]
@@ -13,3 +14,15 @@
# On /tg/, netspeak is OK in PDAs.
[ic_outside_pda]
"<(0_0<)" = "Kirby dancing is strictly prohibited on this server."
+
+# Anything in here will warn the player if said in IC, dead chat or OOC, the player must approve their message to send it
+[soft_shared]
+"testsoftblocks" = "You risk being banned if this term is used improperly."
+
+# Anything in here will warn the player if said ICly, but is allowed in dead chat and OOC, the player must approve their message to send it
+[soft_ic]
+"testsofterblocks" = "This term is commonly considered OOC, only use it with context."
+
+# Anything in here will warn the player if said ICly *except* PDAs, the player must approve their message to send it
+[soft_ic_outside_pda]
+"testsoftestblocks" = "Stop saying debug phrases ICly."
diff --git a/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm b/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm
index ac862c65d26..991ddb96288 100644
--- a/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm
+++ b/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm
@@ -378,7 +378,7 @@
human_host = null
//borers shouldnt be able to whisper...
-/mob/living/simple_animal/cortical_borer/whisper(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced)
+/mob/living/simple_animal/cortical_borer/whisper(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced, filterproof)
to_chat(src, span_warning("You are not able to whisper!"))
return FALSE