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

urlspamcog.py 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import re
  2. from datetime import timedelta
  3. from discord import Member, Message
  4. from discord.ext import commands
  5. from cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
  6. from config import CONFIG
  7. from rbutils import describe_timedelta
  8. class URLSpamContext:
  9. """
  10. Data about a suspected spam message containing a URL.
  11. """
  12. def __init__(self, spam_message: Message):
  13. self.spam_message = spam_message
  14. self.is_deleted = False
  15. self.is_kicked = False
  16. self.is_banned = False
  17. class URLSpamCog(BaseCog, name='URL Spam'):
  18. """
  19. Detects users posting URLs who just joined recently: a common spam pattern.
  20. Can be configured to take immediate action or just warn the mods.
  21. """
  22. SETTING_ENABLED = CogSetting('enabled', bool,
  23. brief='URL spam detection',
  24. description='Whether URLs posted soon after joining are flagged.')
  25. SETTING_ACTION = CogSetting('action', str,
  26. brief='action to take on spam',
  27. description='The action to take on detected URL spam.',
  28. enum_values=set(['nothing', 'modwarn', 'delete', 'kick', 'ban']))
  29. SETTING_JOIN_AGE = CogSetting('joinage', float,
  30. brief='seconds since member joined',
  31. description='The minimum seconds since the user joined the ' + \
  32. 'server before they can post URLs. URLs posted by users ' + \
  33. 'who joined too recently will be flagged. Keep in mind ' + \
  34. 'many servers have a minimum 10 minute cooldown before ' + \
  35. 'new members can say anything. Setting to 0 effectively ' + \
  36. 'disables URL spam detection.',
  37. usage='<seconds:int>',
  38. min_value=0)
  39. def __init__(self, bot):
  40. super().__init__(bot)
  41. self.add_setting(URLSpamCog.SETTING_ENABLED)
  42. self.add_setting(URLSpamCog.SETTING_ACTION)
  43. self.add_setting(URLSpamCog.SETTING_JOIN_AGE)
  44. @commands.group(
  45. brief='Manages URL spam detection',
  46. )
  47. @commands.has_permissions(ban_members=True)
  48. @commands.guild_only()
  49. async def urlspam(self, context: commands.Context):
  50. 'URL spam command group'
  51. if context.invoked_subcommand is None:
  52. await context.send_help()
  53. @commands.Cog.listener()
  54. async def on_message(self, message: Message):
  55. 'Event listener'
  56. if message.author is None or \
  57. message.author.bot or \
  58. message.guild is None or \
  59. message.channel is None or \
  60. message.content is None:
  61. return
  62. if not self.get_guild_setting(message.guild, self.SETTING_ENABLED):
  63. return
  64. action = self.get_guild_setting(message.guild, self.SETTING_ACTION)
  65. join_seconds = self.get_guild_setting(message.guild, self.SETTING_JOIN_AGE)
  66. min_join_age = timedelta(seconds=join_seconds)
  67. if action == 'nothing':
  68. return
  69. if not self.__contains_url(message.content):
  70. return
  71. join_age = message.created_at - message.author.joined_at
  72. join_age_str = describe_timedelta(join_age)
  73. if join_age < min_join_age:
  74. context = URLSpamContext(message)
  75. needs_attention = False
  76. if action == 'modwarn':
  77. needs_attention = True
  78. self.log(message.guild, f'New user {message.author.name} ' + \
  79. f'({message.author.id}) posted URL {join_age_str} after ' + \
  80. 'joining. Mods alerted.')
  81. elif action == 'delete':
  82. await message.delete()
  83. context.is_deleted = True
  84. self.log(message.guild, f'New user {message.author.name} ' + \
  85. f'({message.author.id}) posted URL {join_age_str} after ' + \
  86. 'joining. Message deleted.')
  87. elif action == 'kick':
  88. await message.delete()
  89. context.is_deleted = True
  90. await message.author.kick(
  91. reason=f'Rocketbot: Posted a link {join_age_str} after joining')
  92. context.is_kicked = True
  93. self.log(message.guild, f'New user {message.author.name} ' + \
  94. f'({message.author.id}) posted URL {join_age_str} after ' + \
  95. 'joining. User kicked.')
  96. elif action == 'ban':
  97. await message.author.ban(
  98. reason=f'Rocketbot: User posted a link {join_age_str} after joining',
  99. delete_message_days=1)
  100. context.is_deleted = True
  101. context.is_kicked = True
  102. context.is_banned = True
  103. self.log(message.guild, f'New user {message.author.name} ' + \
  104. f'({message.author.id}) posted URL {join_age_str} after ' + \
  105. 'joining. User banned.')
  106. bm = BotMessage(
  107. message.guild,
  108. f'User {message.author.mention} posted a URL ' + \
  109. f'{join_age_str} after joining.',
  110. type = BotMessage.TYPE_MOD_WARNING if needs_attention else BotMessage.TYPE_INFO,
  111. context = context)
  112. bm.quote = message.content
  113. await bm.set_reactions(BotMessageReaction.standard_set(
  114. did_delete=context.is_deleted,
  115. did_kick=context.is_kicked,
  116. did_ban=context.is_banned))
  117. await self.post_message(bm)
  118. async def on_mod_react(self,
  119. bot_message: BotMessage,
  120. reaction: BotMessageReaction,
  121. reacted_by: Member) -> None:
  122. context: URLSpamContext = bot_message.context
  123. if context is None:
  124. return
  125. sm: Message = context.spam_message
  126. if reaction.emoji == CONFIG['trash_emoji']:
  127. if not context.is_deleted:
  128. await sm.delete()
  129. context.is_deleted = True
  130. self.log(sm.guild, f'URL spam by {sm.author.name} deleted ' + \
  131. f'by {reacted_by.name}')
  132. elif reaction.emoji == CONFIG['kick_emoji']:
  133. if not context.is_deleted:
  134. await sm.delete()
  135. context.is_deleted = True
  136. if not context.is_kicked:
  137. await sm.author.kick(
  138. reason=f'Rocketbot: Kicked for URL spam by {reacted_by.name}')
  139. context.is_kicked = True
  140. self.log(sm.guild, f'URL spammer {sm.author.name} kicked ' + \
  141. f'by {reacted_by.name}')
  142. elif reaction.emoji == CONFIG['ban_emoji']:
  143. if not context.is_banned:
  144. await sm.author.ban(
  145. reason=f'Rocketbot: Banned for URL spam by {reacted_by.name}',
  146. delete_message_days=1)
  147. context.is_deleted = True
  148. context.is_kicked = True
  149. context.is_banned = True
  150. self.log(sm.guild, f'URL spammer {sm.author.name} banned ' + \
  151. f'by {reacted_by.name}')
  152. else:
  153. return
  154. await bot_message.set_reactions(BotMessageReaction.standard_set(
  155. did_delete=context.is_deleted,
  156. did_kick=context.is_kicked,
  157. did_ban=context.is_banned))
  158. @classmethod
  159. def __contains_url(cls, text: str) -> bool:
  160. p = re.compile(r'http(?:s)?://[^\s]+')
  161. return p.search(text) is not None