| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- from discord import Guild, Member, Message
- from discord.ext import commands
- import re
- from datetime import timedelta
-
- from cogs.basecog import BaseCog, BotMessage, BotMessageReaction
- from config import CONFIG
- from storage import Storage
-
- class URLSpamContext:
- def __init__(self, spam_message: Message):
- self.spam_message = spam_message
- self.is_deleted = False
- self.is_kicked = False
- self.is_banned = False
-
- class URLSpamCog(BaseCog):
- CONFIG_KEY_EARLY_URL_TIMEOUT = "urlspam_early_url_timeout"
- CONFIG_KEY_EARLY_URL_ACTION = "urlspam_early_url_action"
-
- def __init__(self, bot):
- super().__init__(bot)
-
- def __early_url_timeout(self, guild: Guild) -> int:
- return Storage.get_config_value(guild, self.CONFIG_KEY_EARLY_URL_TIMEOUT) or \
- self.get_cog_default('early_url_timeout')
-
- def __early_url_action(self, guild: Guild) -> str:
- return Storage.get_config_value(guild, self.CONFIG_KEY_EARLY_URL_ACTION) or \
- self.get_cog_default('early_url_action')
-
- @commands.Cog.listener()
- async def on_message(self, message: Message):
- if message.author is None or \
- message.author.bot or \
- message.guild is None or \
- message.channel is None or \
- message.content is None:
- return
-
- action = self.__early_url_action(message.guild)
- if action == 'nothing':
- return
- if not self.__contains_url(message.content):
- return
- join_age = message.created_at - message.author.joined_at
- join_age_str = self.__format_timedelta(join_age)
- if join_age.total_seconds() < self.__early_url_timeout(message.guild):
- if action == 'modwarn':
- bm = BotMessage(
- message.guild,
- f'User {message.author.mention} posted a URL ' + \
- f'{join_age_str} after joining.',
- type = BotMessage.TYPE_MOD_WARNING,
- context = URLSpamContext(message))
- bm.quote = message.content
- await bm.add_reaction(BotMessageReaction(CONFIG['trash_emoji'], True, 'Delete message'))
- await bm.add_reaction(BotMessageReaction(CONFIG['kick_emoji'], True, 'Kick user'))
- await bm.add_reaction(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
- await self.post_message(bm)
- self.log(message.guild, f'New user {message.author.name} ' + \
- f'({message.author.id}) posted URL. Mods alerted.')
- elif action == 'delete':
- await message.delete()
- bm = BotMessage(
- message.guild,
- f'User {message.author.mention} posted a URL ' + \
- f'{join_age_str} after joining. Message deleted.',
- type = BotMessage.TYPE_INFO,
- context = URLSpamContext(message))
- bm.quote = message.content
- await bm.add_reaction(BotMessageReaction(CONFIG['kick_emoji'], True, 'Kick user'))
- await bm.add_reaction(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
- await self.post_message(bm)
- self.log(message.guild, f'New user {message.author.name} ' + \
- f'({message.author.id}) posted URL. Message deleted.')
- elif action == 'kick':
- await message.author.kick(reason='User posted a link ' + \
- f'{join_age_str} after joining')
- bm = BotMessage(
- message.guild,
- f'User {message.author.mention} posted a URL ' + \
- f'{join_age_str} after joining. Kicked by bot.',
- type = BotMessage.TYPE_INFO,
- context = URLSpamContext(message))
- bm.quote = message.content
- await bm.add_reaction(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
- await self.post_message(bm)
- self.log(message.guild, f'New user {message.author.name} ' + \
- f'({message.author.id}) posted URL. User kicked.')
- elif action == 'ban':
- await message.author.ban(reason='User posted a link ' + \
- f'{join_age_str} after joining', delete_message_days=1)
- bm = BotMessage(
- message.guild,
- f'User {message.author.mention} posted a URL ' + \
- f'{join_age_str} after joining. Banned by bot.',
- type = BotMessage.TYPE_INFO,
- context = URLSpamContext(message))
- bm.quote = message.content
- await self.post_message(bm)
- self.log(message.guild, f'New user {message.author.name} ' + \
- f'({message.author.id}) posted URL. User banned.')
-
- async def on_mod_react(self,
- bot_message: BotMessage,
- reaction: BotMessageReaction,
- reacted_by: Member) -> None:
- context: URLSpamContext = bot_message.context
- if context is None:
- return
- sm: Message = context.spam_message
- if reaction.emoji == CONFIG['trash_emoji']:
- if not context.is_deleted:
- await sm.delete()
- context.is_deleted = True
- self.log(sm.guild, f'URL spam by {sm.author.name} deleted by {reacted_by.name}')
- elif reaction.emoji == CONFIG['kick_emoji']:
- if not context.is_deleted:
- await sm.delete()
- context.is_deleted = True
- if not context.is_kicked:
- await sm.author.kick(reason=f'Rocketbot: Kicked for URL spam by {reacted_by.name}')
- context.is_kicked = True
- self.log(sm.guild, f'URL spammer {sm.author.name} kicked by {reacted_by.name}')
- elif reaction.emoji == CONFIG['ban_emoji']:
- if not context.is_banned:
- await sm.author.ban(reason=f'Rocketbot: Banned for URL spam by {reacted_by.name}', delete_message_days=1)
- context.is_deleted = True
- context.is_kicked = True
- context.is_banned = True
- self.log(sm.guild, f'URL spammer {sm.author.name} banned by {reacted_by.name}')
- else:
- return
- await bot_message.set_reactions(BotMessageReaction.standard_set(
- did_delete=context.is_deleted,
- did_kick=context.is_kicked,
- did_ban=context.is_banned))
-
- def __contains_url(self, text: str) -> bool:
- p = re.compile(r'http(?:s)?://[^\s]+')
- return p.search(text) is not None
-
- def __format_timedelta(self, timespan: timedelta) -> str:
- parts = []
- d = timespan.days
- h = timespan.seconds // 3600
- m = (timespan.seconds // 60) % 60
- s = timespan.seconds % 60
- if d > 0:
- parts.append(f'{d} days')
- if d > 0 or h > 0:
- parts.append(f'{h} hours')
- if d > 0 or h > 0 or m > 0:
- parts.append(f'{m} minutes')
- parts.append(f'{s} seconds')
- # Limit the precision to the two most significant elements
- while len(parts) > 2:
- parts.pop(-1)
- return ' '.join(parts)
|