1import os
2import random
3import re
4import string
5from collections import deque
6
7import discord
8import dotenv
9from discord import Message
10from rapidfuzz import fuzz
11
12_DISCORD_MARKUP = re.compile(
13 (
14 r"<(?:[@#&!]\d+|a?:\w+:\d+)>|" # mentions, custom emoji
15 r"[\U0001f600-\U0001f64f" # emoticons
16 r"\U0001f300-\U0001f5ff" # symbols & pictographs
17 r"\U0001f680-\U0001f6ff" # transport & map
18 r"\U0001f1e0-\U0001f1ff]" # flags
19 ),
20 flags=re.UNICODE,
21)
22_PUNCT = string.punctuation.replace("?", "")
23
24RESPONSES = [
25 "Yes",
26 "Yeah",
27 "Yep",
28 "Mhm",
29 "Yea",
30 "Absolutely",
31 "Indeed",
32 "Without a doubt",
33 "Obviously",
34]
35
36KEY_PHRASES = [
37 "is this true",
38 "is that true",
39 "is it true",
40 "are you sure",
41 "is this correct",
42 "is that correct",
43 "is this real",
44 "can you verify",
45 "is that accurate",
46]
47
48PATTERNS = [
49 r"\bis (this|that|it) true\b",
50 r"\bare you sure\b",
51 r"\btrue\??$",
52]
53
54_last_replies: deque[str] = deque(maxlen=3)
55
56intents = discord.Intents.default()
57intents.message_content = True
58
59client = discord.Client(intents=intents)
60
61
62@client.event
63async def on_ready():
64 print(f"We have logged in as {client.user}")
65
66
67def strip_discord_markup(text: str) -> str:
68 return _DISCORD_MARKUP.sub(" ", text)
69
70
71def normalize(text: str) -> str:
72 text = text.lower()
73 # remove punctuation besides ? marks
74 for p in _PUNCT:
75 text = text.replace(p, " ")
76 return " ".join(text.split())
77
78
79FLEX_PATTERN = re.compile(r"is\s+(?:this|that|it)\s+\w*\s*true")
80ALT_PATTERN = re.compile(r"(?:this|that|it)\s+is\s+true")
81
82
83def contains_truth_question(text: str) -> bool:
84 text = normalize(strip_discord_markup(text))
85
86 # check direct patterns (strict)
87 for p in PATTERNS:
88 if re.search(p, text):
89 return True
90
91 # flexible phrasing
92 if re.search(FLEX_PATTERN, text):
93 return True
94
95 # reversed phrasing
96 if re.search(ALT_PATTERN, text):
97 return True
98
99 # fuzzy "contains" match
100 for p in KEY_PHRASES:
101 if fuzz.partial_ratio(text, p) >= 80:
102 return True
103
104 return False
105
106
107def pick_reply_simple() -> str:
108 candidates: list[str] = [
109 r for r in RESPONSES if r not in _last_replies
110 ] or RESPONSES
111 reply: str = random.choice(candidates)
112 _last_replies.append(reply)
113 return reply
114
115
116def reply_dispatch(text: str) -> str | None:
117 if contains_truth_question(text):
118 return pick_reply_simple()
119
120
121@client.event
122async def on_message(message: Message):
123 assert client.user is not None
124 if message.author == client.user:
125 return
126 if client.user.mentioned_in(message):
127 reply = reply_dispatch(message.content)
128 if reply:
129 _ = await message.reply(reply)
130
131
132def main():
133 if not dotenv.load_dotenv():
134 print("Missing .env file!")
135 exit(1)
136 TOKEN = os.getenv("DISCORD_TOKEN")
137 if not TOKEN:
138 print("Missing token.")
139 exit(1)
140 assert isinstance(TOKEN, str)
141 client.run(TOKEN)
142
143
144if __name__ == "__main__":
145 main()