|
|
@@ -3,7 +3,7 @@ from discord.ext import commands
|
|
3
|
3
|
import re
|
|
4
|
4
|
from datetime import timedelta
|
|
5
|
5
|
|
|
6
|
|
-from cogs.basecog import BaseCog, BotMessage, BotMessageReaction
|
|
|
6
|
+from cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
|
|
7
|
7
|
from config import CONFIG
|
|
8
|
8
|
from storage import Storage
|
|
9
|
9
|
|
|
|
@@ -15,19 +15,35 @@ class URLSpamContext:
|
|
15
|
15
|
self.is_banned = False
|
|
16
|
16
|
|
|
17
|
17
|
class URLSpamCog(BaseCog):
|
|
18
|
|
- CONFIG_KEY_EARLY_URL_TIMEOUT = "urlspam_early_url_timeout"
|
|
19
|
|
- CONFIG_KEY_EARLY_URL_ACTION = "urlspam_early_url_action"
|
|
|
18
|
+ SETTING_ACTION = CogSetting('action',
|
|
|
19
|
+ brief='action to take on spam',
|
|
|
20
|
+ description='The action to take on detected URL spam.',
|
|
|
21
|
+ enum_values=set(['nothing', 'modwarn', 'delete', 'kick', 'ban']))
|
|
|
22
|
+ SETTING_JOIN_AGE = CogSetting('joinage',
|
|
|
23
|
+ brief='seconds since member joined',
|
|
|
24
|
+ description='The minimum seconds since the user joined the ' + \
|
|
|
25
|
+ 'server before they can post URLs. URLs posted by users ' + \
|
|
|
26
|
+ 'who joined too recently will be flagged. Keep in mind ' + \
|
|
|
27
|
+ 'many servers have a minimum 10 minute cooldown before ' + \
|
|
|
28
|
+ 'new members can say anything. Setting to 0 effectively ' + \
|
|
|
29
|
+ 'disables URL spam detection.',
|
|
|
30
|
+ usage='<seconds:int>',
|
|
|
31
|
+ min_value=0)
|
|
20
|
32
|
|
|
21
|
33
|
def __init__(self, bot):
|
|
22
|
34
|
super().__init__(bot)
|
|
|
35
|
+ self.add_setting(URLSpamCog.SETTING_ACTION)
|
|
|
36
|
+ self.add_setting(URLSpamCog.SETTING_JOIN_AGE)
|
|
23
|
37
|
|
|
24
|
|
- def __early_url_timeout(self, guild: Guild) -> int:
|
|
25
|
|
- return Storage.get_config_value(guild, self.CONFIG_KEY_EARLY_URL_TIMEOUT) or \
|
|
26
|
|
- self.get_cog_default('early_url_timeout')
|
|
27
|
|
-
|
|
28
|
|
- def __early_url_action(self, guild: Guild) -> str:
|
|
29
|
|
- return Storage.get_config_value(guild, self.CONFIG_KEY_EARLY_URL_ACTION) or \
|
|
30
|
|
- self.get_cog_default('early_url_action')
|
|
|
38
|
+ @commands.group(
|
|
|
39
|
+ brief='Manages URL spam detection',
|
|
|
40
|
+ )
|
|
|
41
|
+ @commands.has_permissions(ban_members=True)
|
|
|
42
|
+ @commands.guild_only()
|
|
|
43
|
+ async def urlspam(self, context: commands.Context):
|
|
|
44
|
+ 'Command group'
|
|
|
45
|
+ if context.invoked_subcommand is None:
|
|
|
46
|
+ await context.send_help()
|
|
31
|
47
|
|
|
32
|
48
|
@commands.Cog.listener()
|
|
33
|
49
|
async def on_message(self, message: Message):
|
|
|
@@ -38,69 +54,54 @@ class URLSpamCog(BaseCog):
|
|
38
|
54
|
message.content is None:
|
|
39
|
55
|
return
|
|
40
|
56
|
|
|
41
|
|
- action = self.__early_url_action(message.guild)
|
|
|
57
|
+ action = self.get_guild_setting(message.guild, self.SETTING_ACTION)
|
|
|
58
|
+ min_join_age = timedelta(seconds=self.get_guild_setting(message.guild, self.SETTING_JOIN_AGE))
|
|
42
|
59
|
if action == 'nothing':
|
|
43
|
60
|
return
|
|
44
|
61
|
if not self.__contains_url(message.content):
|
|
45
|
62
|
return
|
|
46
|
63
|
join_age = message.created_at - message.author.joined_at
|
|
47
|
64
|
join_age_str = self.__format_timedelta(join_age)
|
|
48
|
|
- if join_age.total_seconds() < self.__early_url_timeout(message.guild):
|
|
|
65
|
+ if join_age < min_join_age:
|
|
|
66
|
+ context = URLSpamContext(message)
|
|
|
67
|
+ needs_attention = False
|
|
49
|
68
|
if action == 'modwarn':
|
|
50
|
|
- bm = BotMessage(
|
|
51
|
|
- message.guild,
|
|
52
|
|
- f'User {message.author.mention} posted a URL ' + \
|
|
53
|
|
- f'{join_age_str} after joining.',
|
|
54
|
|
- type = BotMessage.TYPE_MOD_WARNING,
|
|
55
|
|
- context = URLSpamContext(message))
|
|
56
|
|
- bm.quote = message.content
|
|
57
|
|
- await bm.add_reaction(BotMessageReaction(CONFIG['trash_emoji'], True, 'Delete message'))
|
|
58
|
|
- await bm.add_reaction(BotMessageReaction(CONFIG['kick_emoji'], True, 'Kick user'))
|
|
59
|
|
- await bm.add_reaction(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
|
|
60
|
|
- await self.post_message(bm)
|
|
|
69
|
+ needs_attention = True
|
|
61
|
70
|
self.log(message.guild, f'New user {message.author.name} ' + \
|
|
62
|
71
|
f'({message.author.id}) posted URL. Mods alerted.')
|
|
63
|
72
|
elif action == 'delete':
|
|
64
|
73
|
await message.delete()
|
|
65
|
|
- bm = BotMessage(
|
|
66
|
|
- message.guild,
|
|
67
|
|
- f'User {message.author.mention} posted a URL ' + \
|
|
68
|
|
- f'{join_age_str} after joining. Message deleted.',
|
|
69
|
|
- type = BotMessage.TYPE_INFO,
|
|
70
|
|
- context = URLSpamContext(message))
|
|
71
|
|
- bm.quote = message.content
|
|
72
|
|
- await bm.add_reaction(BotMessageReaction(CONFIG['kick_emoji'], True, 'Kick user'))
|
|
73
|
|
- await bm.add_reaction(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
|
|
74
|
|
- await self.post_message(bm)
|
|
|
74
|
+ context.is_deleted = True
|
|
75
|
75
|
self.log(message.guild, f'New user {message.author.name} ' + \
|
|
76
|
76
|
f'({message.author.id}) posted URL. Message deleted.')
|
|
77
|
77
|
elif action == 'kick':
|
|
|
78
|
+ await message.delete()
|
|
|
79
|
+ context.is_deleted = True
|
|
78
|
80
|
await message.author.kick(reason='User posted a link ' + \
|
|
79
|
81
|
f'{join_age_str} after joining')
|
|
80
|
|
- bm = BotMessage(
|
|
81
|
|
- message.guild,
|
|
82
|
|
- f'User {message.author.mention} posted a URL ' + \
|
|
83
|
|
- f'{join_age_str} after joining. Kicked by bot.',
|
|
84
|
|
- type = BotMessage.TYPE_INFO,
|
|
85
|
|
- context = URLSpamContext(message))
|
|
86
|
|
- bm.quote = message.content
|
|
87
|
|
- await bm.add_reaction(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
|
|
88
|
|
- await self.post_message(bm)
|
|
|
82
|
+ context.is_kicked = True
|
|
89
|
83
|
self.log(message.guild, f'New user {message.author.name} ' + \
|
|
90
|
84
|
f'({message.author.id}) posted URL. User kicked.')
|
|
91
|
85
|
elif action == 'ban':
|
|
92
|
86
|
await message.author.ban(reason='User posted a link ' + \
|
|
93
|
87
|
f'{join_age_str} after joining', delete_message_days=1)
|
|
94
|
|
- bm = BotMessage(
|
|
95
|
|
- message.guild,
|
|
96
|
|
- f'User {message.author.mention} posted a URL ' + \
|
|
97
|
|
- f'{join_age_str} after joining. Banned by bot.',
|
|
98
|
|
- type = BotMessage.TYPE_INFO,
|
|
99
|
|
- context = URLSpamContext(message))
|
|
100
|
|
- bm.quote = message.content
|
|
101
|
|
- await self.post_message(bm)
|
|
|
88
|
+ context.is_deleted = True
|
|
|
89
|
+ context.is_kicked = True
|
|
|
90
|
+ context.is_banned = True
|
|
102
|
91
|
self.log(message.guild, f'New user {message.author.name} ' + \
|
|
103
|
92
|
f'({message.author.id}) posted URL. User banned.')
|
|
|
93
|
+ bm = BotMessage(
|
|
|
94
|
+ message.guild,
|
|
|
95
|
+ f'User {message.author.mention} posted a URL ' + \
|
|
|
96
|
+ f'{join_age_str} after joining.',
|
|
|
97
|
+ type = BotMessage.TYPE_MOD_WARNING if needs_attention else BotMessage.TYPE_INFO,
|
|
|
98
|
+ context = context)
|
|
|
99
|
+ bm.quote = message.content
|
|
|
100
|
+ await bm.set_reactions(BotMessageReaction.standard_set(
|
|
|
101
|
+ did_delete=context.is_deleted,
|
|
|
102
|
+ did_kick=context.is_kicked,
|
|
|
103
|
+ did_ban=context.is_banned))
|
|
|
104
|
+ await self.post_message(bm)
|
|
104
|
105
|
|
|
105
|
106
|
async def on_mod_react(self,
|
|
106
|
107
|
bot_message: BotMessage,
|
|
|
@@ -137,11 +138,13 @@ class URLSpamCog(BaseCog):
|
|
137
|
138
|
did_kick=context.is_kicked,
|
|
138
|
139
|
did_ban=context.is_banned))
|
|
139
|
140
|
|
|
140
|
|
- def __contains_url(self, text: str) -> bool:
|
|
|
141
|
+ @classmethod
|
|
|
142
|
+ def __contains_url(cls, text: str) -> bool:
|
|
141
|
143
|
p = re.compile(r'http(?:s)?://[^\s]+')
|
|
142
|
144
|
return p.search(text) is not None
|
|
143
|
145
|
|
|
144
|
|
- def __format_timedelta(self, timespan: timedelta) -> str:
|
|
|
146
|
+ @classmethod
|
|
|
147
|
+ def __format_timedelta(cls, timespan: timedelta) -> str:
|
|
145
|
148
|
parts = []
|
|
146
|
149
|
d = timespan.days
|
|
147
|
150
|
h = timespan.seconds // 3600
|