|
2 | 2 |
|
3 | 3 | import asyncio |
4 | 4 | from collections import defaultdict, deque |
| 5 | +import re |
5 | 6 | import time |
6 | 7 | from datetime import datetime, timedelta |
7 | 8 |
|
|
42 | 43 | "http://memact.com", |
43 | 44 | "http://www.memact.com", |
44 | 45 | ] |
| 46 | +INTRO_TOPIC_KEYWORDS: tuple[tuple[str, tuple[str, ...]], ...] = ( |
| 47 | + ("builder/dev", ("app", "bot", "code", "coding", "developer", "javascript", "programming", "python", "tech")), |
| 48 | + ("creative", ("art", "artist", "design", "drawing", "editing", "music", "photography", "video", "writer")), |
| 49 | + ("gaming", ("fortnite", "game", "gaming", "minecraft", "roblox", "steam", "valorant")), |
| 50 | + ("student", ("college", "school", "student", "study", "university")), |
| 51 | + ("anime/media", ("anime", "manga", "movie", "series", "show")), |
| 52 | + ("Memact", ("community", "memact", "server")), |
| 53 | +) |
| 54 | +INTRO_ACK_TEMPLATES = ( |
| 55 | + "Welcome, {name}. {detail} Glad to have you in {guild}.", |
| 56 | + "Appreciate the intro, {name}. {detail} Settle in and make yourself at home.", |
| 57 | + "{name}, intro received. {detail} You are officially more than just a username here.", |
| 58 | + "Nice to meet you, {name}. {detail} The server has a little more context now.", |
| 59 | +) |
| 60 | + |
| 61 | + |
| 62 | +def _intro_word_count(content: str) -> int: |
| 63 | + return len(re.findall(r"[A-Za-z0-9][A-Za-z0-9_'-]*", content)) |
| 64 | + |
| 65 | + |
| 66 | +def _contains_intro_keyword(content: str, keyword: str) -> bool: |
| 67 | + return re.search(rf"(?<![a-z0-9]){re.escape(keyword.casefold())}(?![a-z0-9])", content) is not None |
| 68 | + |
| 69 | + |
| 70 | +def _intro_detail(content: str) -> str: |
| 71 | + normalized = content.casefold() |
| 72 | + for label, keywords in INTRO_TOPIC_KEYWORDS: |
| 73 | + if any(_contains_intro_keyword(normalized, keyword) for keyword in keywords): |
| 74 | + return f"Noted the {label} angle from your intro." |
| 75 | + |
| 76 | + word_count = _intro_word_count(content) |
| 77 | + if word_count >= 45: |
| 78 | + return "That was a proper intro, not a drive-by hello." |
| 79 | + if word_count >= 18: |
| 80 | + return "Clean intro, just enough signal for people to say hi back." |
| 81 | + return "Short and sweet intro, which still counts." |
45 | 82 |
|
46 | 83 |
|
47 | 84 | class AutomodCog(commands.Cog): |
@@ -298,10 +335,26 @@ async def _acknowledge_intro_message(self, message: nextcord.Message) -> None: |
298 | 335 | return |
299 | 336 | if not self.bot.db.mark_intro_acknowledgement(message.guild.id, message.author.id, message_id=message.id): |
300 | 337 | return |
| 338 | + display_name = getattr(message.author, "display_name", message.author.name).strip() or message.author.name |
| 339 | + safe_name = display_name[:40] |
| 340 | + template = INTRO_ACK_TEMPLATES[(message.author.id + message.id) % len(INTRO_ACK_TEMPLATES)] |
| 341 | + acknowledgement = template.format( |
| 342 | + name=safe_name, |
| 343 | + guild=message.guild.name, |
| 344 | + detail=_intro_detail(message.clean_content or message.content), |
| 345 | + ) |
301 | 346 | try: |
302 | 347 | await message.add_reaction("\U0001f44b") |
303 | 348 | except (nextcord.Forbidden, nextcord.HTTPException): |
304 | 349 | pass |
| 350 | + try: |
| 351 | + await message.reply( |
| 352 | + acknowledgement, |
| 353 | + mention_author=False, |
| 354 | + allowed_mentions=nextcord.AllowedMentions.none(), |
| 355 | + ) |
| 356 | + except (nextcord.Forbidden, nextcord.HTTPException): |
| 357 | + pass |
305 | 358 |
|
306 | 359 | def _age_hours(self, then: datetime | None) -> float: |
307 | 360 | if then is None: |
|
0 commit comments