From 4a47f89e64fb066ccbd76f758d52dad179008753 Mon Sep 17 00:00:00 2001 From: fretuks Date: Sat, 15 Nov 2025 14:24:05 +0100 Subject: [PATCH] Fix ping logic --- helper_moderation.py | 220 ++++++++++++++++++++++++++----------------- tars_bot.py | 39 +++++--- 2 files changed, 161 insertions(+), 98 deletions(-) diff --git a/helper_moderation.py b/helper_moderation.py index 030cecb..c11cfdf 100644 --- a/helper_moderation.py +++ b/helper_moderation.py @@ -12,6 +12,7 @@ from config import STAFF_ROLES_FOR_PING, recent_messages, recent_message_timesta NWORD_PATTERN, SUICIDE_PATTERNS, DRUG_KEYWORDS, DB_FILE logger = logging.getLogger("tars") +WARN_THRESHOLD = 3 async def handle_moderation(message): @@ -20,55 +21,62 @@ async def handle_moderation(message): if any(role.name in STAFF_ROLES_FOR_PING for role in message.author.roles): return text = message.content or "" + uid = str(message.author.id) + g = message.guild banned_words = await get_banned_words() for word in banned_words: if re.search(rf"\b{re.escape(word)}\b", text, re.IGNORECASE): - count = await get_warnings(str(message.author.id)) - count += 1 - await set_warnings(str(message.author.id), count) - await add_warn_log(str(message.author.id), f"Use of banned word: {word}") + count = await increment_warning(uid) + await add_warn_log(uid, f"Use of banned word: {word}") + try: await message.delete() except Exception as e: logger.warning(f"Failed to delete banned word message: {e}") + await message.channel.send( - tars_text(f"{message.author.mention}, watch your language. [Warning {count}/3]")) + tars_text(f"{message.author.mention}, watch your language. [Warning {count}/3]") + ) await dm_send_safe( message.author, f"T.A.R.S. Warning {count}/3: Use of banned word (‘{word}’). Message deleted." ) await send_mod_log( - f"Banned word '{word}' used by {message.author} in {message.channel.mention}. Warnings {count}." + g, + f"Banned word '{word}' used by {message.author} in {message.channel.mention}. Warnings {count}.", + ping_staff=(count >= WARN_THRESHOLD) ) return - g = message.guild if len(text.splitlines()) > 10 or len(text) > 500: - count = await get_warnings(str(message.author.id)) - count += 1 - await set_warnings(str(message.author.id), count) - await add_warn_log(str(message.author.id), "Text wall / spam") + count = await increment_warning(uid) + await add_warn_log(uid, "Text wall / spam") await message.channel.send( - tars_text(f"{message.author.mention}, sending large text walls or spam is prohibited. [Warning {count}/3]")) + tars_text(f"{message.author.mention}, sending large text walls or spam is prohibited. [Warning {count}/3]") + ) await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Text wall or spam detected.") - await send_mod_log(g, - f"Text wall by {message.author} in {message.channel.mention}: {len(text)} chars / {len(text.splitlines())} lines. Warnings {count}") + await send_mod_log( + g, + f"Text wall by {message.author} in {message.channel.mention}: " + f"{len(text)} chars / {len(text.splitlines())} lines. Warnings {count}", + ping_staff=(count >= WARN_THRESHOLD) + ) return - ping_count = 0 - for role in g.roles: - if role.name in STAFF_ROLES_FOR_PING and role.mention in text: - ping_count += text.count(role.mention) + + ping_count = sum(text.count(role.mention) for role in g.roles if role.name in STAFF_ROLES_FOR_PING) if ping_count > 4: - count = await get_warnings(str(message.author.id)) - count += 1 - await set_warnings(str(message.author.id), count) - await add_warn_log(str(message.author.id), "Excessive staff pinging") + count = await increment_warning(uid) + await add_warn_log(uid, "Excessive staff pinging") await message.channel.send( - tars_text(f"{message.author.mention}, excessive staff pinging is not allowed. [Warning {count}/3]")) + tars_text(f"{message.author.mention}, excessive staff pinging is not allowed. [Warning {count}/3]") + ) await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Excessive staff pinging.") - await send_mod_log(g, - f"Excessive staff pings by {message.author} in {message.channel.mention}. Count: {ping_count}. Warnings {count}") + await send_mod_log( + g, + f"Excessive staff pings by {message.author} in {message.channel.mention}. " + f"Count: {ping_count}. Warnings {count}", + ping_staff=(count >= WARN_THRESHOLD) + ) return - uid = str(message.author.id) now = datetime.utcnow() msg_list = recent_messages.get(uid, []) ts_list = recent_message_timestamps.get(uid, []) @@ -77,75 +85,113 @@ async def handle_moderation(message): recent_messages[uid] = msg_list[-6:] recent_message_timestamps[uid] = ts_list[-6:] if len(set(recent_messages[uid])) == 1 and len(recent_messages[uid]) >= 3: - count = await get_warnings(uid) - count += 1 - await set_warnings(uid, count) + count = await increment_warning(uid) await add_warn_log(uid, "Repeated message spam") await message.channel.send( - tars_text(f"{message.author.mention}, repeated messages detected. [Warning {count}/3]")) + tars_text(f"{message.author.mention}, repeated messages detected. [Warning {count}/3]") + ) await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Repeated message spam.") - await send_mod_log(g, f"Repeated messages by {message.author} in {message.channel.mention}. Warnings {count}") + await send_mod_log( + g, + f"Repeated messages by {message.author} in {message.channel.mention}. Warnings {count}", + ping_staff=(count >= WARN_THRESHOLD) + ) return - links = re.findall(r"https?://\S+", text) if len(links) > 2: - count = await get_warnings(uid) - count += 1 - await set_warnings(uid, count) + count = await increment_warning(uid) await add_warn_log(uid, "Link spam") - await message.channel.send( - tars_text(f"{message.author.mention}, excessive links detected. [Warning {count}/3]")) - await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Excessive link posting.") - await send_mod_log(g, - f"Link spam by {message.author} in {message.channel.mention}. Links: {len(links)}. Warnings {count}") - return + await message.channel.send( + tars_text(f"{message.author.mention}, excessive links detected. [Warning {count}/3]") + ) + await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Excessive link posting.") + + await send_mod_log( + g, + f"Link spam by {message.author} in {message.channel.mention}. " + f"Links: {len(links)}. Warnings {count}", + ping_staff=(count >= WARN_THRESHOLD) + ) + return if NWORD_PATTERN.search(text): - reaction = random.choice( - ["Interesting choice of words… not recommended.", "Attempting human chaos detected. Deleting."]) + reaction = random.choice([ + "Interesting choice of words… not recommended.", + "Attempting human chaos detected. Deleting." + ]) count = await helper_warn(message, reaction, uid) await add_warn_log(uid, "Prohibited slur") - await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Use of prohibited slur.") - await send_mod_log(g, - f"Banned word by {message.author} in {message.channel.mention}: \"{text}\". Warnings {count}") - return + await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Use of prohibited slur.") + + await send_mod_log( + g, + f"Banned word by {message.author} in {message.channel.mention}: \"{text}\". Warnings {count}", + ping_staff=(count >= WARN_THRESHOLD) + ) + return for pat in SUICIDE_PATTERNS: if pat.search(text): - reaction = random.choice(["Protocol violation. That’s a negative.", - "Error detected: inappropriate content. Executing deletion."]) + reaction = random.choice([ + "Protocol violation. That’s a negative.", + "Error detected: inappropriate content. Executing deletion." + ]) count = await helper_warn(message, reaction, uid) await add_warn_log(uid, "Self-harm encouragement") - await dm_send_safe(message.author, - f"T.A.R.S. Warning {count}/3: Promoting self-harm is prohibited. If you are in danger, contact emergency services or the suicide hotline.") - await send_mod_log(g, - f"Self-harm phrase by {message.author} in {message.channel.mention}. Warnings {count}. Message: \"{text}\"") + await dm_send_safe( + message.author, + f"T.A.R.S. Warning {count}/3: Promoting self-harm is prohibited." + ) + await send_mod_log( + g, + f"Self-harm phrase by {message.author} in {message.channel.mention}. " + f"Warnings {count}. Message: \"{text}\"", + ping_staff=(count >= WARN_THRESHOLD) + ) return - if any(w in text for w in DRUG_KEYWORDS): - count = await get_warnings(uid) - count += 1 - await set_warnings(uid, count) + count = await increment_warning(uid) await add_warn_log(uid, "Drug mention") await message.channel.send( - tars_text(f"{message.author.mention}, discussion of drugs is prohibited. [Warning {count}/3]")) + tars_text(f"{message.author.mention}, discussion of drugs is prohibited. [Warning {count}/3]") + ) await dm_send_safe(message.author, f"T.A.R.S. Warning {count}/3: Discussion of drugs prohibited.") - await send_mod_log(g, f"Drug mention by {message.author} in {message.channel.mention}. Warnings {count}.") + await send_mod_log( + g, + f"Drug mention by {message.author} in {message.channel.mention}. Warnings {count}.", + ping_staff=(count >= WARN_THRESHOLD) + ) return current_warnings = await get_warnings(uid) - if current_warnings >= 3: + if current_warnings >= WARN_THRESHOLD: try: - await message.author.timeout(discord.utils.utcnow() + timedelta(minutes=10), - reason="T.A.R.S. automated enforcement") - await send_mod_log(g, f"{message.author} was timed out for 10 minutes (3 warnings).") + await message.author.timeout( + discord.utils.utcnow() + timedelta(minutes=10), + reason="T.A.R.S. automated enforcement" + ) + await send_mod_log( + g, + f"{message.author} was timed out for 10 minutes (3 warnings).", + ping_staff=True + ) await set_warnings(uid, 0) except Exception as e: - await send_mod_log(g, f"Failed to timeout {message.author}: " + str(e)) + await send_mod_log( + g, + f"Failed to timeout {message.author}: {e}", + ping_staff=False + ) return +async def increment_warning(user_id: str) -> int: + count = await get_warnings(user_id) + count += 1 + await set_warnings(user_id, count) + return count + + async def get_banned_words(): - """Fetch the banned word list from config (SQLite).""" async with aiosqlite.connect(DB_FILE) as db: cur = await db.execute("SELECT value FROM config WHERE key = 'banned_words'") row = await cur.fetchone() @@ -166,14 +212,19 @@ async def get_warnings(user_id: str) -> int: async def set_warnings(user_id: str, count: int): async with aiosqlite.connect(DB_FILE) as db: - await db.execute("INSERT OR REPLACE INTO warnings(user_id, count) VALUES(?,?)", (user_id, count)) + await db.execute( + "INSERT OR REPLACE INTO warnings(user_id, count) VALUES(?,?)", + (user_id, count) + ) await db.commit() async def add_warn_log(user_id: str, reason: str, moderator: str = "T.A.R.S."): async with aiosqlite.connect(DB_FILE) as db: - await db.execute("INSERT INTO warns_log(user_id, reason, time, moderator) VALUES(?,?,?,?)", - (user_id, reason, datetime.utcnow().isoformat(), moderator)) + await db.execute( + "INSERT INTO warns_log(user_id, reason, time, moderator) VALUES(?,?,?,?)", + (user_id, reason, datetime.utcnow().isoformat(), moderator) + ) await db.commit() @@ -181,7 +232,7 @@ async def dm_send_safe(user: discord.User, text: str): try: await user.send(tars_text(text)) except Exception as e: - logger.info(f"Could not DM user {user}: " + str(e)) + logger.info(f"Could not DM user {user}: {e}") async def helper_warn(message, reaction, uid): @@ -190,23 +241,16 @@ async def helper_warn(message, reaction, uid): try: await message.delete() except Exception as e: - e.args = (f"{e.args[0]} (while deleting message)",) - logger.exception("Could not delete message: " + str(e)) + logger.exception(f"Could not delete message: {e}") count = await get_warnings(uid) count += 1 await set_warnings(uid, count) return count -async def dm_send_safe(user: discord.User, text: str): - try: - await user.send(tars_text(text)) - except Exception as e: - logger.info(f"Could not DM user {user}: " + str(e)) - - -async def send_mod_log(guild: discord.Guild, message: str): +async def send_mod_log(guild: discord.Guild, message: str, ping_staff: bool = False): log_channel = discord.utils.get(guild.text_channels, name="╰-︰🤖tars-logs") + if not log_channel: if not guild.me.guild_permissions.manage_channels: logger.warning("Bot lacks permission to create log channel.") @@ -216,14 +260,16 @@ async def send_mod_log(guild: discord.Guild, message: str): guild.default_role: discord.PermissionOverwrite(view_channel=False), guild.me: discord.PermissionOverwrite(view_channel=True) } - log_channel = await guild.create_text_channel("tars-logs", overwrites=overwrites) + log_channel = await guild.create_text_channel("╰-︰🤖tars-logs", overwrites=overwrites) except Exception as e: logger.exception(f"Could not create log channel: {e}") return - mentions = [] - for rname in ("Moderator", "Admin", "Senior Admin", "Head Admin", "Co Owner", "Owner"): - r = discord.utils.get(guild.roles, name=rname) - if r: - mentions.append(r.mention) - mention_text = " ".join(mentions) + ("\n" if mentions else "") - await log_channel.send(f"{mention_text}{message}") + if ping_staff: + mentions = [] + for rname in STAFF_ROLES_FOR_PING: + role = discord.utils.get(guild.roles, name=rname) + if role: + mentions.append(role.mention) + await log_channel.send(f"{' '.join(mentions)}\n{message}") + else: + await log_channel.send(message) diff --git a/tars_bot.py b/tars_bot.py index 97ee01b..82ddec7 100644 --- a/tars_bot.py +++ b/tars_bot.py @@ -260,7 +260,8 @@ async def on_member_update(before: discord.Member, after: discord.Member): await add_boost_points(after.id, points_awarded) await helper_moderation.send_mod_log( after.guild, - f"{after.mention} boosted the server! (+{points_awarded} Boost Points)" + f"{after.mention} boosted the server! (+{points_awarded} Boost Points)", + ping_staff=False ) try: await after.send( @@ -270,7 +271,8 @@ async def on_member_update(before: discord.Member, after: discord.Member): elif before.premium_since and not after.premium_since: await helper_moderation.send_mod_log( after.guild, - f"{after.mention} stopped boosting the server." + f"{after.mention} stopped boosting the server.", + ping_staff=False ) @@ -371,14 +373,20 @@ async def on_member_join(member: discord.Member): while recent_joins and (now - recent_joins[0][0]).total_seconds() > 60: recent_joins.pop(0) if len(recent_joins) >= 6: - await helper_moderation.send_mod_log(member.guild, - f"Possible raid detected: {len(recent_joins)} joins in last minute.") + await helper_moderation.send_mod_log( + member.guild, + f"Possible raid detected: {len(recent_joins)} joins in last minute.", + ping_staff=True + ) @bot.event async def on_member_remove(member: discord.Member): - await helper_moderation.send_mod_log(member.guild, - f"**Leave**: {member} ({member.id}) at {datetime.utcnow().isoformat()}") + await helper_moderation.send_mod_log( + member.guild, + f"**Leave**: {member} ({member.id}) at {datetime.utcnow().isoformat()}", + ping_staff=False + ) BAD_NICK_PATTERN = re.compile(r"(nigg|fag|cum|sex)", re.IGNORECASE) @@ -390,11 +398,18 @@ async def on_member_update(before: discord.Member, after: discord.Member): if BAD_NICK_PATTERN.search(after.display_name): try: await after.edit(nick=None, reason="Inappropriate nickname filtered by T.A.R.S.") - await helper_moderation.send_mod_log(after.guild, - f"Reverted nickname for {after} due to inappropriate content: {after.display_name}") + await helper_moderation.send_mod_log( + after.guild, + f"Reverted nickname for {after} due to inappropriate content: {after.display_name}", + ping_staff=True + ) except Exception as e: await on_error(e) - await helper_moderation.send_mod_log(after.guild, f"Could not revert nickname for {after}: {e}") + await helper_moderation.send_mod_log( + after.guild, + f"Could not revert nickname for {after}: {e}", + ping_staff=True + ) await on_error(e) @@ -611,7 +626,8 @@ async def slash_roleinfo(interaction: discord.Interaction, role: discord.Role): async def slash_report(interaction: discord.Interaction, user: discord.User, reason: str): await interaction.response.send_message(tars_text("Your report has been submitted to staff."), ephemeral=True) await helper_moderation.send_mod_log(interaction.guild, - f"**Report**\nReporter: {interaction.user} ({interaction.user.id})\nReported: {user} ({user.id})\nReason: {reason}\nChannel: {interaction.channel.mention}") + f"**Report**\nReporter: {interaction.user} ({interaction.user.id})\nReported: {user} ({user.id})\nReason: {reason}\nChannel: {interaction.channel.mention}", + ping_staff=True) @tree.command(name="quote", description="Quote a message by link or ID") @@ -1010,7 +1026,8 @@ async def slash_boostpoints_add(interaction: discord.Interaction, member: discor ) await helper_moderation.send_mod_log( interaction.guild, - f"Admin {interaction.user.mention} added **{amount} Boost Points** to {member.mention}. New total: {points}." + f"Admin {interaction.user.mention} added **{amount} Boost Points** to {member.mention}. New total: {points}.", + ping_staff=False )