/ RiddleBot / riddle_bot.py
riddle_bot.py
  1  import io
  2  import json
  3  import os
  4  import random
  5  import re
  6  import time
  7  from typing import Optional, List, Tuple
  8  
  9  from discord import (
 10      Client,
 11      Message,
 12      Member,
 13      Guild,
 14      CategoryChannel,
 15      utils,
 16      Embed,
 17      Role,
 18      PermissionOverwrite,
 19      User,
 20      DMChannel,
 21      Color,
 22      TextChannel,
 23      File,
 24  )
 25  
 26  BELL = "🔔"
 27  THUMBSUP = "👍"
 28  THUMBSDOWN = "👎"
 29  
 30  config: dict = json.load(open("config.json"))
 31  GUILD: int = config["guild"]
 32  NOTIFICATION_ROLE: int = config["notification_role"]
 33  SETTINGS_CHANNEL: int = config["settings_channel"]
 34  MASTER_OF_EVERYTHING_ROLE: int = config["master_of_everything_role"]
 35  GENERAL_CHAT: int = config["general_chat"]
 36  PREFIX = config["prefix"]
 37  
 38  
 39  def create_embed(**kwargs):
 40      embed = Embed(**kwargs)
 41      embed.set_footer(text="Bot by @Defelo#2022")
 42      return embed
 43  
 44  
 45  def level_name(level_id):
 46      return f"level-{level_id}"
 47  
 48  
 49  def solution_name(level_id):
 50      return f"solution-{level_id}"
 51  
 52  
 53  def role_name(category, level_id):
 54      return f"{category} - Level {level_id}"
 55  
 56  
 57  def riddle_master_name(category):
 58      return f"Master of {category}"
 59  
 60  
 61  def category_name(category_id, category, escaped=False):
 62      if escaped:
 63          return fr"\[{category_id}\] {category} - Levels"
 64      else:
 65          return f"[{category_id}] {category} - Levels"
 66  
 67  
 68  class Bot(Client):
 69      def __init__(self):
 70          super().__init__()
 71  
 72          self.guild: Optional[Guild] = None
 73          self.notification_role: Optional[Role] = None
 74          self.settings_channel: Optional[TextChannel] = None
 75          self.master_of_everything_role: Optional[Role] = None
 76          self.general_chat: Optional[TextChannel] = None
 77          self.settings_message: Optional[Message] = None
 78  
 79          self.cooldowns = {}
 80  
 81      async def on_ready(self):
 82          print(f"Logged in as {self.user}")
 83  
 84          self.guild: Guild = self.get_guild(GUILD)
 85          self.notification_role: Role = self.guild.get_role(NOTIFICATION_ROLE)
 86          self.settings_channel: TextChannel = self.guild.get_channel(SETTINGS_CHANNEL)
 87          self.master_of_everything_role: Role = self.guild.get_role(MASTER_OF_EVERYTHING_ROLE)
 88          self.general_chat: TextChannel = self.guild.get_channel(GENERAL_CHAT)
 89          async for msg in self.settings_channel.history():
 90              self.settings_message: Message = msg
 91              break
 92  
 93      def get_levels(self, category: str) -> List[int]:
 94          out = []
 95          for role in self.guild.roles:
 96              if role.id == MASTER_OF_EVERYTHING_ROLE:
 97                  continue
 98              match = re.match("^" + role_name(category, r"(\d+)") + "$", role.name)
 99              if match:
100                  out.append(int(match.group(1)))
101          return sorted(out)
102  
103      def get_categories(self) -> List[Tuple[int, str]]:
104          out = []
105          for category in self.guild.categories:
106              match = re.match("^" + category_name(r"(\d+)", "(.*)", escaped=True) + "$", category.name)
107              if match:
108                  category_id, name = match.groups()
109                  out.append((int(category_id), name))
110          return out
111  
112      def get_next_category_id(self) -> int:
113          return max((cat_id for cat_id, _ in self.get_categories()), default=0) + 1
114  
115      def get_max_level_id(self, category: str) -> int:
116          return max(self.get_levels(category), default=0)
117  
118      def get_level_count(self, category: str) -> int:
119          return len(self.get_levels(category))
120  
121      async def is_authorized(self, user: User) -> bool:
122          member: Optional[Member] = self.guild.get_member(user.id)
123          return member and member.guild_permissions.administrator
124  
125      def get_level(self, category, level_id):
126          _, _, category_channel, _, _ = self.get_category(name=category)
127          if category_channel is None:
128              return None, None, None
129  
130          level_channel: Optional[TextChannel] = utils.get(category_channel.channels, name=level_name(level_id))
131          solution_channel: Optional[TextChannel] = utils.get(category_channel.channels, name=solution_name(level_id))
132          role: Optional[Role] = utils.get(self.guild.roles, name=role_name(category, level_id))
133          return level_channel, solution_channel, role
134  
135      def get_category(self, *, name=None, category_id=None):
136          assert name is not None or category_id is not None
137          category_channel: Optional[CategoryChannel] = None
138  
139          regex_id = str(category_id or r"\d+")
140          regex_name = name or ".*"
141          for cat in self.guild.categories:
142              match = re.match("^" + category_name(f"({regex_id})", f"({regex_name})", escaped=True) + "$", cat.name)
143              if match:
144                  category_id, name = match.groups()
145                  category_id = int(category_id)
146                  category_channel = cat
147          riddle_master_role: Optional[Role] = utils.get(self.guild.roles, name=riddle_master_name(name))
148          leaderboard_channel: Optional[TextChannel] = category_channel and utils.get(
149              category_channel.channels, name="leaderboard"
150          )
151          return category_id, name, category_channel, riddle_master_role, leaderboard_channel
152  
153      async def on_member_join(self, member: Member):
154          if member.guild.id != self.guild.id:
155              return
156  
157          await member.send(open("texts/welcome_dm.txt").read().format(user=member.mention))
158  
159          master_of_everything = True
160          for _, cat_name in self.get_categories():
161              _, _, role = self.get_level(cat_name, 1)
162              if role is not None:
163                  master_of_everything = False
164                  await member.add_roles(role)
165              else:
166                  _, _, _, riddle_master_role, _ = self.get_category(name=cat_name)
167                  await member.add_roles(riddle_master_role)
168              await self.update_leaderboard(cat_name)
169          if master_of_everything:
170              await member.add_roles(self.master_of_everything_role)
171  
172      async def on_raw_reaction_add(self, payload):
173          if self.settings_message is None or self.settings_message.id != payload.message_id:
174              return
175          if str(payload.emoji) != BELL or payload.user_id == self.user.id:
176              return
177  
178          member: Message = self.guild.get_member(payload.user_id)
179          await member.add_roles(self.notification_role)
180  
181      async def on_raw_reaction_remove(self, payload):
182          if self.settings_message is None or self.settings_message.id != payload.message_id:
183              return
184          if str(payload.emoji) != BELL or payload.user_id == self.user.id:
185              return
186  
187          member: Message = self.guild.get_member(payload.user_id)
188          await member.remove_roles(self.notification_role)
189  
190      async def update_leaderboard(self, category):
191          _, _, _, riddle_master_role, leaderboard_channel = self.get_category(name=category)
192          async for message in leaderboard_channel.history():
193              if message.author == self.user:
194                  break
195          else:
196              message = await leaderboard_channel.send(embed=create_embed())
197  
198          level_count = self.get_level_count(category)
199  
200          leaderboard = []
201          for member in self.guild.members:
202              if await self.is_authorized(member):
203                  continue
204  
205              for role in member.roles:
206                  match = re.match("^" + role_name(category, r"(\d+)") + "$", role.name)
207                  if role.id == riddle_master_role.id:
208                      leaderboard.append((level_count, f"@{member}"))
209                  elif match:
210                      leaderboard.append((int(match.group(1)) - 1, f"@{member}"))
211          leaderboard.sort(reverse=True)
212          max_width = max((len(member) for _, member in leaderboard), default=0)
213          description = ["```", "MEMBER".ljust(max_width) + "    SCORE"]
214          for score, member in leaderboard[:20]:
215              description.append(member.ljust(max_width) + f"    {score}")
216          description.append("```")
217  
218          embed = create_embed(title="Leaderboard", description="\n".join(description))
219  
220          await message.edit(embed=embed)
221  
222      async def update_master_of_everything_role(self, member: Member):
223          master_of_everything = True
224          for _, cat_name in self.get_categories():
225              _, _, _, riddle_master_role, _ = self.get_category(name=cat_name)
226              if riddle_master_role not in member.roles:
227                  master_of_everything = False
228                  break
229          if master_of_everything:
230              await member.add_roles(self.master_of_everything_role)
231          else:
232              await member.remove_roles(self.master_of_everything_role)
233  
234      async def on_message(self, message: Message):
235          if message.author == self.user:
236              return
237  
238          if message.content.startswith(PREFIX):
239              cmd, *args = message.content[1:].split()
240              if cmd == "add":
241                  if not await self.is_authorized(message.author):
242                      await message.channel.send("You are not authorized to use this command!")
243                      return
244  
245                  if len(args) < 2 or args[0] not in ("category", "level"):
246                      await message.channel.send(f"usage: {PREFIX}add category|level <category>")
247                      return
248                  category = " ".join(args[1:])
249                  if args[0] == "level":
250                      _, cat_name, category_channel, riddle_master_role, _ = self.get_category(category_id=category)
251                      if category_channel is None:
252                          await message.channel.send("Category does not exist!")
253                          return
254  
255                      level_id = self.get_max_level_id(cat_name) + 1
256                      await message.channel.send(f"Creating Level {level_id}")
257  
258                      role: Role = await self.guild.create_role(name=role_name(cat_name, level_id))
259  
260                      for level in self.get_levels(cat_name):
261                          if level == level_id:
262                              continue
263  
264                          level_channel, _, _ = self.get_level(cat_name, level)
265                          await level_channel.set_permissions(
266                              role, read_messages=True, send_messages=False,
267                          )
268  
269                      level_channel: TextChannel = await category_channel.create_text_channel(
270                          level_name(level_id),
271                          overwrites={
272                              self.guild.default_role: PermissionOverwrite(read_messages=False),
273                              role: PermissionOverwrite(read_messages=True, send_messages=False, add_reactions=False),
274                              riddle_master_role: PermissionOverwrite(
275                                  read_messages=True, send_messages=False, add_reactions=False
276                              ),
277                              self.guild.me: PermissionOverwrite(read_messages=True, send_messages=True),
278                          },
279                      )
280                      solution_channel: TextChannel = await category_channel.create_text_channel(
281                          solution_name(level_id),
282                          overwrites={
283                              self.guild.default_role: PermissionOverwrite(read_messages=False),
284                              self.guild.me: PermissionOverwrite(read_messages=True),
285                          },
286                      )
287                      await message.channel.send(
288                          f"Level {level_id} has been created.\n"
289                          f"Level channel: {level_channel.mention}\n"
290                          f"Solution channel: {solution_channel.mention}\n"
291                          f"Role: {role.mention}"
292                      )
293                      await message.channel.send("Now send me the riddle!")
294                      riddle = await self.wait_for(
295                          "message", check=lambda m: m.channel == message.channel and m.author == message.author
296                      )
297                      riddle_message: Message = await level_channel.send(
298                          embed=(
299                              create_embed(
300                                  title=f"[{category}] {cat_name} - Level {level_id}", description=riddle.content
301                              )
302                          )
303                      )
304                      await riddle_message.add_reaction(THUMBSUP)
305                      await riddle_message.add_reaction(THUMBSDOWN)
306                      await message.channel.send("Riddle has been created! :+1:")
307                      await message.channel.send(f"Now go to {solution_channel.mention} and send the solution.")
308                      await message.channel.send(
309                          f"After that type `{PREFIX}notify {category} {level_id}` to notify the Riddle Masters :wink:"
310                      )
311                  else:
312                      category_channel: CategoryChannel = await self.guild.create_category(
313                          category_name(self.get_next_category_id(), category)
314                      )
315                      await category_channel.create_text_channel(
316                          "leaderboard",
317                          overwrites={
318                              self.guild.default_role: PermissionOverwrite(read_messages=True, send_messages=False),
319                              self.guild.me: PermissionOverwrite(read_messages=True, send_messages=True),
320                          },
321                      )
322  
323                      riddle_master_role: Role = await self.guild.create_role(
324                          name=riddle_master_name(category), color=Color(random.randint(0, 0xFFFFFF)), hoist=True
325                      )
326                      for member in self.guild.members:
327                          if member.id != self.user.id:
328                              await member.add_roles(riddle_master_role)
329                      await self.update_leaderboard(category)
330                      await message.channel.send("Category has been created!")
331              elif cmd == "notify":
332                  if not await self.is_authorized(message.author):
333                      await message.channel.send("You are not authorized to use this command!")
334                      return
335  
336                  if len(args) != 2:
337                      await message.channel.send(f"usage: {PREFIX}notify <category-id> <level-id>")
338                      return
339                  else:
340                      if not args[-1].isnumeric():
341                          await message.channel.send("Level ID has to be numeric!")
342                          return
343                      category = args[0]
344                      level_id = int(args[1])
345  
346                  _, cat_name, _, riddle_master_role, _ = self.get_category(category_id=category)
347                  level_channel, _, role = self.get_level(cat_name, level_id)
348                  notify_count = 0
349                  for member in self.guild.members:
350                      if riddle_master_role in member.roles:
351                          await member.remove_roles(riddle_master_role, self.master_of_everything_role)
352                          await member.add_roles(role)
353                          if self.notification_role in member.roles:
354                              await member.send(
355                                  "Hey! Es gibt jetzt ein neues RĂ€tsel auf dem Riddle Server :wink:\n"
356                                  f"Schau mal hier: {level_channel.mention}"
357                              )
358                              notify_count += 1
359                  await self.update_leaderboard(cat_name)
360                  await message.channel.send(
361                      f"{notify_count} member{[' has', 's have'][notify_count != 1]} been notified about the new level."
362                  )
363              elif cmd == "delete":
364                  if not await self.is_authorized(message.author):
365                      await message.channel.send("You are not authorized to use this command!")
366                      return
367  
368                  if not (
369                      (len(args) == 2 and args[0] == "category")
370                      or (len(args) == 3 and args[0] == "level")
371                      or (len(args) == 4 and args[0] == "levels")
372                  ):
373                      await message.channel.send(
374                          f"usage: {PREFIX}delete category <category-id>\n"
375                          f"   or: {PREFIX}delete level[s] <category-id> <level-id> [<level-id>]"
376                      )
377                      return
378  
379                  category = args[1]
380                  _, cat_name, category_channel, riddle_master_role, leaderboard = self.get_category(category_id=category)
381                  if args[0] == "category":
382                      for level in self.get_levels(cat_name):
383                          level_channel, solution_channel, role = self.get_level(cat_name, level)
384                          if level_channel:
385                              await level_channel.delete()
386                          if solution_channel:
387                              await solution_channel.delete()
388                          if role:
389                              await role.delete()
390                      if leaderboard:
391                          await leaderboard.delete()
392                      if category_channel:
393                          await category_channel.delete()
394                      if riddle_master_role:
395                          await riddle_master_role.delete()
396  
397                      await message.channel.send("Category has been deleted")
398                  else:
399                      if args[0] == "level":
400                          if not args[2].isnumeric():
401                              await message.channel.send("Level ID has to be numeric!")
402                              return
403                          from_level_id = int(args[2])
404                          to_level_id = from_level_id
405                      else:
406                          if (not args[2].isnumeric()) or (not args[3].isnumeric()):
407                              await message.channel.send("Level ID has to be numeric!")
408                              return
409                          from_level_id = int(args[2])
410                          to_level_id = int(args[3])
411  
412                      for level_id in range(from_level_id, to_level_id + 1):
413                          level_channel, solution_channel, role = self.get_level(cat_name, level_id)
414                          existed = False
415                          if level_channel:
416                              await level_channel.delete()
417                              existed = True
418                          if solution_channel:
419                              await solution_channel.delete()
420                              existed = True
421                          if role:
422                              await role.delete()
423                              existed = True
424  
425                          if existed:
426                              await message.channel.send(f"Level {level_id} has been deleted")
427                          else:
428                              await message.channel.send(f"Level {level_id} does not exist")
429                      for level in self.get_levels(cat_name):
430                          if level <= to_level_id:
431                              continue
432                          level_channel, solution_channel, role = self.get_level(cat_name, level)
433                          await level_channel.edit(name=level_name(level - (to_level_id - from_level_id + 1)))
434                          await solution_channel.edit(name=solution_name(level - (to_level_id - from_level_id + 1)))
435                          await role.edit(name=role_name(cat_name, level - (to_level_id - from_level_id + 1)))
436                      await self.update_leaderboard(cat_name)
437  
438                  for member in self.guild.members:
439                      if member != self.user:
440                          await self.update_master_of_everything_role(member)
441  
442                  await message.channel.send("Done")
443              elif cmd == "rename":
444                  if not await self.is_authorized(message.author):
445                      await message.channel.send("You are not authorized to use this command!")
446                      return
447  
448                  if len(args) < 2 or not args[0].isnumeric():
449                      await message.channel.send(f"usage: {PREFIX}rename <category-id> <name>")
450                      return
451  
452                  category = args[0]
453                  new_name = " ".join(args[1:])
454                  cat_id, cat_name, category_channel, riddle_master_role, leaderboard = self.get_category(
455                      category_id=category
456                  )
457                  for level in self.get_levels(cat_name):
458                      level_channel, _, role = self.get_level(cat_name, level)
459                      await role.edit(name=role_name(new_name, level))
460                      async for msg in level_channel.history(oldest_first=True):
461                          if msg.author == self.user and msg.embeds:
462                              embed: Embed = msg.embeds[0]
463                              embed.title = f"[{category}] {new_name} - Level {level}"
464                              await msg.edit(embed=embed)
465                              break
466  
467                  await category_channel.edit(name=category_name(cat_id, new_name))
468                  await riddle_master_role.edit(name=riddle_master_name(new_name))
469                  await message.channel.send("Done!")
470              elif cmd == "info":
471                  embed = create_embed(title="Info")
472                  for cat_id, cat_name in self.get_categories():
473                      count = self.get_level_count(cat_name)
474                      embed.add_field(
475                          name=f"[{cat_id}] {cat_name}", value=f"{count} Level" + "s" * (count != 1), inline=False
476                      )
477                  await message.channel.send(embed=embed)
478              elif cmd == "setup":
479                  if not await self.is_authorized(message.author):
480                      await message.channel.send("You are not authorized to use this command!")
481                      return
482  
483                  self.settings_message = await self.settings_channel.send(
484                      embed=create_embed(title="Settings", description=open("texts/settings.txt").read())
485                  )
486                  await self.settings_message.add_reaction(BELL)
487              elif cmd in ("solve", "lösen"):
488                  if not isinstance(message.channel, DMChannel):
489                      await message.delete()
490                      await message.channel.send(
491                          f"Hey, {message.author.mention}! Schick mir deine Lösung bitte privat :wink:"
492                      )
493                      return
494  
495                  if not args:
496                      await message.channel.send(f"usage: {PREFIX}solve <category-id> [<solution>]")
497                      return
498  
499                  member: Member = self.guild.get_member(message.author.id)
500  
501                  now = time.time()
502                  cooldown, wrong_answers = self.cooldowns.get(member.id, (0, 0))
503                  seconds = round(cooldown - now)
504                  if seconds > 0:
505                      minutes, seconds = divmod(seconds, 60)
506                      hours, minutes = divmod(minutes, 60)
507                      await message.channel.send(
508                          f"Da deine letzte Antwort falsch war, musst du noch etwas warten, "
509                          f"bevor du es noch einmal versuchen kannst.\n"
510                          f"Verbleibende Zeit: `{hours:02d}:{minutes:02d}:{seconds:02d}`"
511                      )
512                      return
513  
514                  answer = " ".join(args[1:])
515                  _, cat_name, _, riddle_master_role, _ = self.get_category(category_id=args[0])
516                  if riddle_master_role is None:
517                      await message.channel.send("Tut mir leid, diese Kategorie kenne ich nicht :shrug:")
518                      return
519  
520                  for role in member.roles:
521                      if role.id == riddle_master_role.id:
522                          await message.channel.send("Hey, du hast bereits alle RÀtsel in dieser Kategorie gelöst :wink:")
523                          return
524  
525                      if re.match("^" + role_name(cat_name, r"(\d+)") + "$", role.name):
526                          level_id = int(re.match("^" + role_name(cat_name, r"(\d+)") + "$", role.name).group(1))
527                          break
528                  else:
529                      level_channel, _, role = self.get_level(cat_name, 1)
530                      if role is not None:
531                          await member.add_roles(role)
532                          await message.channel.send(
533                              "Sorry, du hattest anscheinend noch keine Level-Rolle.\n"
534                              f"Schau jetzt mal in {level_channel.mention} :wink:"
535                          )
536                      return
537  
538                  if not answer:
539                      await message.channel.send("Ok, jetzt schick mir bitte die Lösung!")
540                      answer = (
541                          await self.wait_for(
542                              "message", check=lambda m: m.channel == message.channel and m.author == message.author
543                          )
544                      ).content
545  
546                  _, solution_channel, old_role = self.get_level(cat_name, level_id)
547                  async for msg in solution_channel.history():
548                      if re.match(f"^{msg.content.lower()}$", answer.lower()):
549                          level_channel, _, new_role = self.get_level(cat_name, level_id + 1)
550                          await member.remove_roles(old_role)
551                          if new_role is not None:
552                              await member.add_roles(new_role)
553                              await message.channel.send(f"Richtig! Du hast jetzt Zugriff auf {level_channel.mention}!")
554                          else:
555                              await member.add_roles(riddle_master_role)
556                              await self.update_master_of_everything_role(member)
557                              await message.channel.send(
558                                  f"Richtig! Leider war das aber schon das letzte RĂ€tsel dieser Kategorie."
559                              )
560                              if self.master_of_everything_role in member.roles:
561                                  await self.general_chat.send(
562                                      f"{member.mention} hat jetzt **alle RÀtsel aller Kategorien gelöst!**\n"
563                                      f"**Herzlichen GlĂŒckwunsch!** :tada:"
564                                  )
565                              else:
566                                  await self.general_chat.send(
567                                      f"{member.mention} hat jetzt alle RÀtsel der Kategorie {cat_name} gelöst! :tada:"
568                                  )
569                          cooldown = wrong_answers = 0
570                          break
571                  else:
572                      await message.channel.send(f"Deine Antwort zu Level {level_id} ist leider falsch.")
573                      cooldown = now + min(2 ** wrong_answers, 24 * 60 * 60)
574                      wrong_answers += 1
575                  self.cooldowns[member.id] = (cooldown, wrong_answers)
576                  await self.update_leaderboard(cat_name)
577              elif cmd == "fix":
578                  await self.fix_member(self.guild.get_member(message.author.id))
579                  for _, cat_name in self.get_categories():
580                      await self.update_leaderboard(cat_name)
581                  await message.channel.send("Done")
582              elif cmd == "fixall":
583                  if not await self.is_authorized(message.author):
584                      await message.channel.send("You are not authorized to use this command!")
585                      return
586  
587                  for member in self.guild.members:
588                      if member != self.user:
589                          await self.fix_member(member)
590                  for _, cat_name in self.get_categories():
591                      await self.update_leaderboard(cat_name)
592                  await message.channel.send("Done")
593              elif cmd == "score":
594                  member: Member = self.guild.get_member(message.author.id)
595                  embed = create_embed(title=f"Score of @{member}")
596                  total = 0
597                  for _, cat_name in self.get_categories():
598                      _, _, _, riddle_master_role, _ = self.get_category(name=cat_name)
599                      level_count = self.get_level_count(cat_name)
600                      for role in member.roles:
601                          match = re.match("^" + role_name(cat_name, r"(\d+)") + "$", role.name)
602                          points = None
603                          if role.id == riddle_master_role.id:
604                              points = level_count
605                          elif match:
606                              points = int(match.group(1)) - 1
607                          if points is not None:
608                              embed.add_field(name=cat_name, value=f"{points} Points", inline=False)
609                              total += points
610                              break
611                  embed.add_field(name="TOTAL", value=f"{total} Points", inline=False)
612                  await message.channel.send(embed=embed)
613              elif cmd == "send":
614                  if not await self.is_authorized(message.author):
615                      await message.channel.send("You are not authorized to use this command!")
616                      return
617  
618                  if len(args) != 2 or args[0] not in ("text", "embed"):
619                      await message.channel.send(f"usage: {PREFIX}send text|embed <channel>")
620                      return
621  
622                  channel_id = int(re.match(r"^(<#)?(\d+)(?(1)>)$", args[1]).group(2))
623                  channel: TextChannel = self.get_channel(channel_id)
624                  if channel is None:
625                      await message.channel.send("Channel does not exist.")
626                      return
627  
628                  if args[0] == "text":
629                      await message.channel.send("Now send me the message!")
630                      msg: Message = await self.wait_for(
631                          "message", check=lambda m: m.channel == message.channel and m.author == message.author
632                      )
633                      files = []
634                      for attachment in msg.attachments:
635                          file = io.BytesIO()
636                          await attachment.save(file)
637                          files.append(File(file, filename=attachment.filename, spoiler=attachment.is_spoiler()))
638                      await channel.send(content=msg.content, files=files)
639                  else:
640                      await message.channel.send("Send me the title of the embed!")
641                      title = (
642                          await self.wait_for(
643                              "message", check=lambda m: m.channel == message.channel and m.author == message.author
644                          )
645                      ).content
646                      await message.channel.send("Ok, now send me the content of the embed!")
647                      content = (
648                          await self.wait_for(
649                              "message", check=lambda m: m.channel == message.channel and m.author == message.author
650                          )
651                      ).content
652                      await channel.send(embed=create_embed(title=title, description=content))
653              elif cmd == "edit":
654                  if not await self.is_authorized(message.author):
655                      await message.channel.send("You are not authorized to use this command!")
656                      return
657  
658                  if len(args) != 3 or args[0] not in ("text", "embed") or not args[2].isnumeric():
659                      await message.channel.send(f"usage: {PREFIX}edit text|embed <channel> <message-id>")
660                      return
661  
662                  channel_id = int(re.match(r"^(<#)?(\d+)(?(1)>)$", args[1]).group(2))
663                  channel: TextChannel = self.get_channel(channel_id)
664                  if channel is None:
665                      await message.channel.send("Channel does not exist.")
666                      return
667                  msg_to_edit: Optional[Message] = await channel.fetch_message(int(args[2]))
668                  if msg_to_edit is None:
669                      await msg_to_edit.channel.send("Message does not exist.")
670                      return
671  
672                  if args[0] == "text":
673                      await message.channel.send("Now send me the new message!")
674                      msg: Message = await self.wait_for(
675                          "message", check=lambda m: m.channel == message.channel and m.author == message.author
676                      )
677                      files = []
678                      for attachment in msg.attachments:
679                          file = io.BytesIO()
680                          await attachment.save(file)
681                          files.append(File(file, filename=attachment.filename, spoiler=attachment.is_spoiler()))
682                      await msg_to_edit.edit(content=msg.content, files=files)
683                  else:
684                      await message.channel.send("Send me the new title of the embed!")
685                      title = (
686                          await self.wait_for(
687                              "message", check=lambda m: m.channel == message.channel and m.author == message.author
688                          )
689                      ).content
690                      await message.channel.send("Ok, now send me the new content of the embed!")
691                      content = (
692                          await self.wait_for(
693                              "message", check=lambda m: m.channel == message.channel and m.author == message.author
694                          )
695                      ).content
696                      await msg_to_edit.edit(embed=create_embed(title=title, description=content))
697              elif cmd == "help":
698                  response = "```\n"
699                  if await self.is_authorized(message.author):
700                      response += open("texts/admin_commands.txt").read().format(prefix=PREFIX) + "\n"
701                  response += open("texts/user_commands.txt").read().format(prefix=PREFIX) + "\n```"
702                  await message.channel.send(response)
703              else:
704                  await message.channel.send(f"Unknown command! Type `{PREFIX}help` to get a list of commands!")
705  
706      async def fix_member(self, member: Member):
707          for cat_id, cat_name in self.get_categories():
708              _, _, _, riddle_master_role, _ = self.get_category(category_id=cat_id)
709  
710              for role in member.roles:
711                  if role.id == riddle_master_role.id:
712                      break
713                  elif re.match("^" + role_name(cat_name, r"(\d+)") + "$", role.name):
714                      break
715              else:
716                  _, _, role = self.get_level(cat_name, 1)
717                  await member.add_roles(role or riddle_master_role)
718  
719  
720  Bot().run(os.environ["TOKEN"])