Experimental Discord bot written in Python
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

logcog.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """
  2. Cog for detecting large numbers of guild joins in a short period of time.
  3. """
  4. import weakref
  5. from collections.abc import Sequence
  6. from datetime import datetime, timedelta
  7. from discord import Emoji, Guild, GuildChannel, GuildSticker, Invite, Member, Message, Role, Thread, User
  8. from discord.ext import commands
  9. from typing import List, Union
  10. from config import CONFIG
  11. from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
  12. from rocketbot.collections import AgeBoundList
  13. from rocketbot.storage import Storage
  14. class LogCog(BaseCog, name='Logging'):
  15. """
  16. Cog for logging notable events to a designated logging channel.
  17. """
  18. SETTING_ENABLED = CogSetting('enabled', bool,
  19. brief='logging',
  20. description='Whether this cog is enabled for a guild.')
  21. SETTING_EDITS_ENABLED = CogSetting('edits_enabled', bool,
  22. brief='post edits',
  23. description='Whether to log when users edit their posts.')
  24. SETTING_JOINS_ENABLED = CogSetting('joins_enabled', bool,
  25. brief='joins',
  26. description='Whether to log when new users join the server.')
  27. SETTING_LEAVES_ENABLED = CogSetting('leaves_enabled', bool,
  28. brief='leaves',
  29. description='Whether to log when users leave the server.')
  30. def __init__(self, bot):
  31. super().__init__(bot)
  32. self.add_setting(LogCog.SETTING_ENABLED)
  33. self.add_setting(LogCog.SETTING_EDITS_ENABLED)
  34. self.add_setting(LogCog.SETTING_JOINS_ENABLED)
  35. self.add_setting(LogCog.SETTING_LEAVES_ENABLED)
  36. @commands.group(
  37. brief='Manages event logging',
  38. )
  39. @commands.has_permissions(ban_members=True)
  40. @commands.guild_only()
  41. async def log(self, context: commands.Context):
  42. 'Logging command group'
  43. if context.invoked_subcommand is None:
  44. await context.send_help()
  45. # Events - Channels
  46. @commands.Cog.listener()
  47. async def on_guild_channel_delete(self, channel: GuildChannel) -> None:
  48. pass
  49. @commands.Cog.listener()
  50. async def on_guild_channel_create(self, channel: GuildChannel) -> None:
  51. pass
  52. @commands.Cog.listener()
  53. async def on_guild_channel_update(self, before: GuildChannel, after: GuildChannel) -> None:
  54. pass
  55. # Events - Guilds
  56. @commands.Cog.listener()
  57. async def on_guild_available(self, guild: Guild) -> None:
  58. pass
  59. @commands.Cog.listener()
  60. async def on_guild_unavailable(self, guild: Guild) -> None:
  61. pass
  62. @commands.Cog.listener()
  63. async def on_guild_update(self, before: Guild, after: Guild) -> None:
  64. pass
  65. @commands.Cog.listener()
  66. async def on_guild_emojis_update(self, guild: Guild, before: Sequence[Emoji], after: Sequence[Emoji]) -> None:
  67. pass
  68. @commands.Cog.listener()
  69. async def on_guild_stickers_update(self, guild: Guild, before: Sequence[GuildSticker], after: Sequence[GuildSticker]) -> None:
  70. pass
  71. @commands.Cog.listener()
  72. async def on_invite_create(self, invite: Invite) -> None:
  73. pass
  74. @commands.Cog.listener()
  75. async def on_invite_delete(self, invite: Invite) -> None:
  76. pass
  77. # Events - Members
  78. @commands.Cog.listener()
  79. async def on_member_join(self, member: Member) -> None:
  80. pass
  81. @commands.Cog.listener()
  82. async def on_member_remove(self, member: Member) -> None:
  83. pass
  84. @commands.Cog.listener()
  85. async def on_member_update(self, before: Member, after: Member) -> None:
  86. pass
  87. @commands.Cog.listener()
  88. async def on_user_update(self, before: User, after: User) -> None:
  89. pass
  90. @commands.Cog.listener()
  91. async def on_member_ban(self, user: Union[User, Member]) -> None:
  92. pass
  93. @commands.Cog.listener()
  94. async def on_member_unban(self, guild: Guild, user: Union[User, Member]) -> None:
  95. pass
  96. # Events - Messages
  97. @commands.Cog.listener()
  98. async def on_message_edit(self, before: Message, after: Message) -> None:
  99. pass
  100. @commands.Cog.listener()
  101. async def on_message_delete(self, message: Message) -> None:
  102. pass
  103. @commands.Cog.listener()
  104. async def on_bulk_message_delete(self, messages: List[Message]) -> None:
  105. pass
  106. # Events - Roles
  107. @commands.Cog.listener()
  108. async def on_guild_role_create(self, role: Role) -> None:
  109. pass
  110. @commands.Cog.listener()
  111. async def on_guild_role_delete(self, role: Role) -> None:
  112. pass
  113. @commands.Cog.listener()
  114. async def on_guild_role_update(self, before: Role, after: Role) -> None:
  115. pass
  116. # Events - Threads
  117. @commands.Cog.listener()
  118. async def on_thread_create(self, thread: Thread) -> None:
  119. pass
  120. @commands.Cog.listener()
  121. async def on_thread_update(self, before: Thread, after: Thread) -> None:
  122. pass
  123. @commands.Cog.listener()
  124. async def on_thread_delete(self, thread: Thread) -> None:
  125. pass
  126. def remove_me():
  127. pass
  128. @commands.Cog.listener()
  129. async def on_member_join(self, member: Member) -> None:
  130. 'Event handler'
  131. guild: Guild = member.guild
  132. if not self.get_guild_setting(guild, self.SETTING_ENABLED):
  133. return
  134. min_count = self.get_guild_setting(guild, self.SETTING_JOIN_COUNT)
  135. seconds = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
  136. timespan: timedelta = timedelta(seconds=seconds)
  137. last_raid: JoinRaidContext = Storage.get_state_value(guild, self.STATE_KEY_LAST_RAID)
  138. recent_joins: AgeBoundList = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
  139. if recent_joins is None:
  140. recent_joins = AgeBoundList(timespan, lambda i, member : member.joined_at)
  141. Storage.set_state_value(guild, self.STATE_KEY_RECENT_JOINS, recent_joins)
  142. if last_raid:
  143. if member.joined_at - last_raid.last_join_time() > timespan:
  144. # Last raid is over
  145. Storage.set_state_value(guild, self.STATE_KEY_LAST_RAID, None)
  146. recent_joins.append(member)
  147. return
  148. # Add join to existing raid
  149. last_raid.join_members.append(member)
  150. self.record_warning(member)
  151. if len(last_raid.banned_members) > 0:
  152. self.log(guild, f'Banning as part of last join raid: {member.name}')
  153. await member.ban(
  154. reason='Rocketbot: Part of join raid.',
  155. delete_message_days=0)
  156. last_raid.banned_members.add(member)
  157. elif len(last_raid.kicked_members) > 0:
  158. self.log(guild, f'Kicking as part of last join raid: {member.name}')
  159. await member.kick(
  160. reason='Rocketbot: Part of join raid.')
  161. last_raid.kicked_members.add(member)
  162. await self.__update_warning_message(last_raid)
  163. else:
  164. # Add join to the general, non-raid recent join list
  165. recent_joins.append(member)
  166. if len(recent_joins) >= min_count:
  167. self.log(guild, '\u0007Join raid detected')
  168. last_raid = JoinRaidContext(recent_joins)
  169. Storage.set_state_value(guild, self.STATE_KEY_LAST_RAID, last_raid)
  170. recent_joins.clear()
  171. msg = BotMessage(guild,
  172. text='',
  173. type=BotMessage.TYPE_MOD_WARNING,
  174. context=last_raid)
  175. self.record_warnings(recent_joins)
  176. last_raid.warning_message_ref = weakref.ref(msg)
  177. await self.__update_warning_message(last_raid)
  178. await self.post_message(msg)
  179. async def on_setting_updated(self, guild: Guild, setting: CogSetting) -> None:
  180. if setting is self.SETTING_JOIN_TIME:
  181. seconds = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
  182. timespan: timedelta = timedelta(seconds=seconds)
  183. recent_joins: AgeBoundList = Storage.get_state_value(guild,
  184. self.STATE_KEY_RECENT_JOINS)
  185. if recent_joins:
  186. recent_joins.max_age = timespan
  187. recent_joins.purge_old_elements()