Skip to content

Commit

Permalink
Add support for silently filtered words (#42)
Browse files Browse the repository at this point in the history
* Add example config file

* Add /silent_filter command to mark/unmark commands as silently filtered

* Show silent filtered words in /filter list

* Ignore silently words in commands that stop when filter triggered

* For silently filtered words, show a report and don't delete the word

* Rename `ignorable_words` to `has_only_silent_filtered_words` for clarity

* Add missing newline to config example
  • Loading branch information
SlimShadyIAm authored Jun 5, 2024
1 parent b45725a commit 5222b03
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 10 deletions.
8 changes: 5 additions & 3 deletions cogs/commands/misc/canister.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from utils import GIRContext, canister_search_package, cfg, transform_context
from utils.fetchers import canister_fetch_repos
from utils.framework import gatekeeper, whisper_in_general, find_triggered_filters, find_triggered_raid_phrases
from utils.framework.filter import has_only_silent_filtered_words
from utils.views import TweakDropdown, default_repos, repo_autocomplete


Expand All @@ -22,7 +23,6 @@ async def on_message(self, message):
author = message.guild.get_member(message.author.id)
if author is None:
return

if not gatekeeper.has(message.guild, author, 5) and message.channel.id == cfg.channels.general:
return

Expand All @@ -31,8 +31,10 @@ async def on_message(self, message):
if not pattern.match(message.content):
return

if await find_triggered_filters(message.content, message.author) or await find_triggered_raid_phrases(message.content, message.author):
return
if filter_words := await find_triggered_filters(message.content, message.author) or await find_triggered_raid_phrases(message.content, message.author):
# if any of the triggered filtered words are not silently filtered, don't show results
if not has_only_silent_filtered_words(filter_words):
return

matches = pattern.findall(message.content)
if not matches:
Expand Down
6 changes: 4 additions & 2 deletions cogs/commands/misc/memes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
find_triggered_filters,
find_triggered_raid_phrases, gatekeeper,
memed_and_up, mempro_and_up, mod_and_up, whisper)
from utils.framework.filter import has_only_silent_filtered_words
from utils.views import GenericDescriptionModal, Menu, memes_autocomplete


Expand Down Expand Up @@ -463,8 +464,9 @@ async def aitext(self, ctx: GIRContext, prompt: str):
data = await resp.json()
text = data.get("choices")[0].get("text")
text = discord.utils.escape_markdown(text)
if await find_triggered_filters(text, ctx.author) or await find_triggered_raid_phrases(text, ctx.author):
text = "A filter was triggered by this response. Please try a different prompt."
if filter_words := await find_triggered_filters(text, ctx.author) or await find_triggered_raid_phrases(text, ctx.author):
if not has_only_silent_filtered_words(filter_words):
text = "A filter was triggered by this response. Please try a different prompt."

embed = discord.Embed(color=discord.Color.random())
prompt_formatted = discord.utils.escape_markdown(prompt)
Expand Down
27 changes: 27 additions & 0 deletions cogs/commands/mod/filter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import List
import discord
from discord import app_commands
from data.model import FilterWord
Expand Down Expand Up @@ -30,6 +31,12 @@ def format_filter_page(_, entries, current_page, all_pages):
embed = discord.Embed(
title=f'Filtered words', color=discord.Color.blurple())
for word in entries:
if word.silent_filter:
embed.add_field(
name=word.word, value=f"🤫 silent filtered"
)
continue

notify_flag = ""
piracy_flag = ""
flags_check = ""
Expand Down Expand Up @@ -107,6 +114,26 @@ async def _list(self, ctx: GIRContext):
menu = Menu(ctx, filters, per_page=12,
page_formatter=format_filter_page, whisper=False)
await menu.start()

@mod_and_up()
@app_commands.guilds(cfg.guild_id)
@app_commands.command(description="Silently filter a word (ping mods without removing message)")
@app_commands.describe(word="The word to mark as silently filtered")
@app_commands.autocomplete(word=filterwords_autocomplete)
@transform_context
async def silent_filter(self, ctx: GIRContext, word: str):
word = word.lower()

words: List[FilterWord] = await guild_service.get_filtered_words()
words = list(filter(lambda w: w.word.lower() == word.lower(), words))

if len(words) > 0:
words[0].silent_filter = not words[0].silent_filter
await guild_service.update_filtered_word(words[0])

await ctx.send_success("Marked as a silently filtered word!" if words[0].silent_filter else "Removed as a silently filtered word!")
else:
await ctx.send_warning("You must filter that word before it can be marked as silently filtered.", delete_after=5)

@mod_and_up()
@app_commands.guilds(cfg.guild_id)
Expand Down
3 changes: 2 additions & 1 deletion cogs/monitors/misc/songs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from utils import cfg
from utils.framework import find_triggered_filters, gatekeeper
from utils.framework.filter import has_only_silent_filtered_words
from utils.logging import logger
from datetime import timezone

Expand Down Expand Up @@ -111,7 +112,7 @@ async def generate_view(self, message: discord.Message, link: str):
triggered_words = await find_triggered_filters(
title, message.author)

if triggered_words:
if triggered_words and not has_only_silent_filtered_words(triggered_words):
title = "<:fr:959135064657109012>"

view = discord.ui.View()
Expand Down
15 changes: 12 additions & 3 deletions cogs/monitors/mod/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from discord.ext import commands
from utils import cfg, logger, scam_cache
from utils.framework import gatekeeper, find_triggered_filters
from utils.framework.filter import has_only_silent_filtered_words
from utils.mod import mute
from utils.views import manual_report, report

Expand Down Expand Up @@ -102,6 +103,9 @@ async def nick_filter(self, member):
if not triggered_words:
return

if has_only_silent_filtered_words(triggered_words):
return

await member.edit(nick="change name pls")
embed = discord.Embed(title="Nickname changed",
color=discord.Color.orange())
Expand All @@ -117,21 +121,26 @@ async def bad_word_filter(self, message) -> bool:
if not triggered_words:
return

dev_role = message.guild.get_role(cfg.roles.developer)

triggered = False
for word in triggered_words:
if word.piracy:
# ignore if it's a dev saying piracy in #development
if message.channel.id == cfg.roles.developer in message.author.roles:
continue

if word.notify:
if word.silent_filter:
# don't delete the word if it's silently filtered,
# just notify the mods
await report(self.bot, message, word.word)
return
elif word.notify:
await self.delete(message)
await self.ratelimit(message)
await self.do_filter_notify(message, word)

if not word.piracy:
await report(self.bot, message, word.word)

return

triggered = True
Expand Down
33 changes: 33 additions & 0 deletions config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"channels": {
"applenews": 123,
"booster_emoji": 123,
"emoji_logs": 123,
"private_logs": 123,
"public_logs": 123,
"bot_commands": 123,
"jailbreak": 123,
"general": 123,
"development": 123,
"genius_bar": 123,
"reports": 123,
"rules": 123,
"common_issues": 123,
"sub_news": 123
},
"roles": {
"administrator": 123,
"moderator": 123,
"sub_mod": 123,
"genius": 123,
"developer": 123,
"birthday": 123,
"member_ultra": 123,
"member_one": 123,
"member_edition": 123,
"member_pro": 123,
"member_plus": 123,
"sub_news": 123,
"aaron_role": 123
}
}
2 changes: 1 addition & 1 deletion data/model/filterword.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import mongoengine

class FilterWord(mongoengine.EmbeddedDocument):
# _id = mongoengine.ObjectIdField(required=True, default=mongoengine.ObjectId., unique=True, primary_key=True)
notify = mongoengine.BooleanField(required=True)
silent_filter = mongoengine.BooleanField(default=False)
bypass = mongoengine.IntField(required=True)
word = mongoengine.StringField(required=True)
false_positive = mongoengine.BooleanField(default=False)
Expand Down
7 changes: 7 additions & 0 deletions utils/framework/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ async def find_triggered_filters(input, member: discord.Member) -> List[FilterWo
words_found.append(word)
return words_found

def has_only_silent_filtered_words(triggered_filter_words: List[FilterWord]):
"""
We don't want to trigger the filter if the words are silently filtered
return True if all triggered filtered words are silently filtered
"""
return all(filter_word.silent_filter for filter_word in triggered_filter_words)


async def find_triggered_raid_phrases(input, member):
symbols = (u"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ",
Expand Down

0 comments on commit 5222b03

Please sign in to comment.