|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+from discord import Guild, Message
|
|
|
2
|
+from discord.ext import commands
|
|
|
3
|
+import re
|
|
|
4
|
+from datetime import timedelta
|
|
|
5
|
+
|
|
|
6
|
+from cogs.basecog import BaseCog
|
|
|
7
|
+from config import CONFIG
|
|
|
8
|
+from storage import Storage
|
|
|
9
|
+
|
|
|
10
|
+class URLSpamCog(BaseCog):
|
|
|
11
|
+ CONFIG_KEY_EARLY_URL_TIMEOUT = "urlspam_early_url_timeout"
|
|
|
12
|
+ CONFIG_KEY_EARLY_URL_ACTION = "urlspam_early_url_action"
|
|
|
13
|
+
|
|
|
14
|
+ def __init__(self, bot):
|
|
|
15
|
+ super().__init__(bot)
|
|
|
16
|
+
|
|
|
17
|
+ def __early_url_timeout(self, guild: Guild) -> int:
|
|
|
18
|
+ return Storage.get_config_value(guild, self.CONFIG_KEY_EARLY_URL_TIMEOUT) or \
|
|
|
19
|
+ self.get_cog_default('early_url_timeout')
|
|
|
20
|
+
|
|
|
21
|
+ def __early_url_action(self, guild: Guild) -> str:
|
|
|
22
|
+ return Storage.get_config_value(guild, self.CONFIG_KEY_EARLY_URL_ACTION) or \
|
|
|
23
|
+ self.get_cog_default('early_url_action')
|
|
|
24
|
+
|
|
|
25
|
+ @commands.Cog.listener()
|
|
|
26
|
+ async def on_message(self, message: Message):
|
|
|
27
|
+ if message.guild is None or message.channel is None:
|
|
|
28
|
+ # DM or something
|
|
|
29
|
+ return
|
|
|
30
|
+ action = self.__early_url_action(message.guild)
|
|
|
31
|
+ if action == 'nothing':
|
|
|
32
|
+ return
|
|
|
33
|
+ if message.author.permissions_in(message.channel).ban_members:
|
|
|
34
|
+ # Mods are exempt
|
|
|
35
|
+ return
|
|
|
36
|
+ if not self.__contains_url(message.content):
|
|
|
37
|
+ return
|
|
|
38
|
+ join_age = message.created_at - message.author.joined_at
|
|
|
39
|
+ join_age_str = self.__format_timedelta(join_age)
|
|
|
40
|
+ if join_age.total_seconds() < self.__early_url_timeout(message.guild):
|
|
|
41
|
+ if action == 'modwarn':
|
|
|
42
|
+ await self.warn(message.guild, f'User {message.author.mention} ' +
|
|
|
43
|
+ f'posted a URL {join_age_str} after joining.\n\n> {message.content}')
|
|
|
44
|
+ # TODO: Emoji actions
|
|
|
45
|
+ elif action == 'delete':
|
|
|
46
|
+ await message.delete()
|
|
|
47
|
+ # TODO: Info to mods
|
|
|
48
|
+ elif action == 'kick':
|
|
|
49
|
+ await message.author.kick(reason=f'User posted a link {join_age_str} after joining')
|
|
|
50
|
+ # TODO: Info to mods
|
|
|
51
|
+ elif action == 'ban':
|
|
|
52
|
+ await message.author.ban(reason=f'User posted a link {join_age_str} after joining', delete_message_days=1)
|
|
|
53
|
+ # TODO: Info to mods
|
|
|
54
|
+
|
|
|
55
|
+ def __contains_url(self, text: str) -> bool:
|
|
|
56
|
+ p = re.compile(r'http[^\s]*')
|
|
|
57
|
+ return p.search(text) is not None
|
|
|
58
|
+
|
|
|
59
|
+ def __format_timedelta(self, timespan: timedelta) -> str:
|
|
|
60
|
+ parts = []
|
|
|
61
|
+ d = timespan.days
|
|
|
62
|
+ h = timespan.seconds // 3600
|
|
|
63
|
+ m = (timespan.seconds // 60) % 60
|
|
|
64
|
+ s = timespan.seconds % 60
|
|
|
65
|
+ if d > 0:
|
|
|
66
|
+ parts.append(f'{d}d')
|
|
|
67
|
+ if d > 0 or h > 0:
|
|
|
68
|
+ parts.append(f'{h}h')
|
|
|
69
|
+ if d > 0 or h > 0 or m > 0:
|
|
|
70
|
+ parts.append(f'{m}m')
|
|
|
71
|
+ parts.append(f'{s}s')
|
|
|
72
|
+ # Limit the precision to the two most significant elements
|
|
|
73
|
+ while len(parts) > 2:
|
|
|
74
|
+ parts.pop(-1)
|
|
|
75
|
+ return ' '.join(parts)
|