Experimental Discord bot written in Python
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

basecog.py 4.7KB

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