Experimental Discord bot written in Python
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

basecog.py 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from discord import Guild, Message, PartialEmoji, RawReactionActionEvent, TextChannel
  2. from discord.ext import commands
  3. from datetime import timedelta
  4. from config import CONFIG
  5. from rscollections import AgeBoundDict
  6. from storage import ConfigKey, Storage
  7. import json
  8. class BaseCog(commands.Cog):
  9. def __init__(self, bot):
  10. self.bot = bot
  11. self.listened_mod_react_message_ids = AgeBoundDict(timedelta(minutes=5), lambda message_id, tpl : tpl[0])
  12. @classmethod
  13. def get_cog_default(cls, key: str):
  14. """
  15. Convenience method for getting a cog configuration default from
  16. `CONFIG['cogs'][<cogname>][<key>]`.
  17. """
  18. cogs: dict = CONFIG['cog_defaults']
  19. cog = cogs.get(cls.__name__)
  20. if cog is None:
  21. return None
  22. return cog.get(key)
  23. def listen_for_reactions_to(self, message: Message, context = None) -> None:
  24. """
  25. Registers a warning message as something a mod may react to to enact
  26. some action. `context` will be passed back in `on_mod_react` and can be
  27. any value that helps give the cog context about the action being
  28. performed.
  29. """
  30. self.listened_mod_react_message_ids[message.id] = (message.created_at, context)
  31. @commands.Cog.listener()
  32. async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
  33. 'Event handler'
  34. if payload.user_id == self.bot.user.id:
  35. # Ignore bot's own reactions
  36. return
  37. member: Member = payload.member
  38. if member is None:
  39. return
  40. guild: Guild = self.bot.get_guild(payload.guild_id)
  41. if guild is None:
  42. # Possibly a DM
  43. return
  44. channel: GuildChannel = guild.get_channel(payload.channel_id)
  45. if channel is None:
  46. # Possibly a DM
  47. return
  48. message: Message = await channel.fetch_message(payload.message_id)
  49. if message is None:
  50. # Message deleted?
  51. return
  52. if message.author.id != self.bot.user.id:
  53. # Bot didn't author this
  54. return
  55. if not member.permissions_in(channel).ban_members:
  56. # Not a mod
  57. return
  58. tpl = self.listened_mod_react_message_ids.get(message.id)
  59. if tpl is None:
  60. # Not a message we're listening for
  61. return
  62. context = tpl[1]
  63. await self.on_mod_react(message, payload.emoji, context)
  64. async def on_mod_react(self, message: Message, emoji: PartialEmoji, context) -> None:
  65. """
  66. Override point for getting a mod's emote on a bot message. Used to take
  67. action on a warning, such as banning an offending user. This event is
  68. only triggered for registered bot messages and reactions by members
  69. with the proper permissions. The given `context` value is whatever was
  70. passed in `listen_to_reactions_to()`.
  71. """
  72. pass
  73. async def validate_param(self, context: commands.Context, param_name: str, value,
  74. allowed_types: tuple = None,
  75. min_value = None,
  76. max_value = None) -> bool:
  77. """
  78. Convenience method for validating a command parameter is of the expected
  79. type and in the expected range. Bad values will cause a reply to be sent
  80. to the original message and a False will be returned. If all checks
  81. succeed, True will be returned.
  82. """
  83. if allowed_types is not None and not isinstance(value, allowed_types):
  84. if len(allowed_types) == 1:
  85. await context.message.reply(f'⚠️ `{param_name}` must be of type ' +
  86. f'{allowed_types[0]}.', mention_author=False)
  87. else:
  88. await context.message.reply(f'⚠️ `{param_name}` must be of types ' +
  89. f'{allowed_types}.', mention_author=False)
  90. return False
  91. if min_value is not None and value < min_value:
  92. await context.message.reply(f'⚠️ `{param_name}` must be >= {min_value}.',
  93. mention_author=False)
  94. return False
  95. if max_value is not None and value > max_value:
  96. await context.message.reply(f'⚠️ `{param_name}` must be <= {max_value}.',
  97. mention_author=False)
  98. return True
  99. @classmethod
  100. async def warn(cls, guild: Guild, message: str) -> Message:
  101. """
  102. Sends a warning message to the configured warning channel for the
  103. given guild. If no warning channel is configured no action is taken.
  104. Returns the Message if successful or None if not.
  105. """
  106. channel_id = Storage.get_config_value(guild, ConfigKey.WARNING_CHANNEL_ID)
  107. if channel_id is None:
  108. cls.guild_trace(guild, 'No warning channel set! No warning issued.')
  109. return None
  110. channel: TextChannel = guild.get_channel(channel_id)
  111. if channel is None:
  112. cls.guild_trace(guild, 'Configured warning channel does not exist!')
  113. return None
  114. mention: str = Storage.get_config_value(guild, ConfigKey.WARNING_MENTION)
  115. text: str = message
  116. if mention is not None:
  117. text = f'{mention} {text}'
  118. msg: Message = await channel.send(text)
  119. return msg
  120. @classmethod
  121. async def update_warn(cls, warn_message: Message, new_text: str) -> None:
  122. """
  123. Updates the text of a previously posted `warn`. Includes configured
  124. mentions if necessary.
  125. """
  126. text: str = new_text
  127. mention: str = Storage.get_config_value(
  128. warn_message.guild,
  129. ConfigKey.WARNING_MENTION)
  130. if mention is not None:
  131. text = f'{mention} {text}'
  132. await warn_message.edit(content=text)
  133. @classmethod
  134. def guild_trace(cls, guild: Guild, message: str) -> None:
  135. print(f'[guild {guild.id}|{guild.name}] {message}')