Quellcode durchsuchen

Adding username pattern cog

master
Rocketsoup vor 3 Jahren
Ursprung
Commit
a85124b51c
3 geänderte Dateien mit 188 neuen und 0 gelöschten Zeilen
  1. 2
    0
      bot.py
  2. 1
    0
      config.py.sample
  3. 185
    0
      rocketbot/cogs/usernamecog.py

+ 2
- 0
bot.py Datei anzeigen

@@ -16,6 +16,7 @@ from rocketbot.cogs.generalcog import GeneralCog
16 16
 from rocketbot.cogs.joinraidcog import JoinRaidCog
17 17
 from rocketbot.cogs.patterncog import PatternCog
18 18
 from rocketbot.cogs.urlspamcog import URLSpamCog
19
+from rocketbot.cogs.usernamecog import UsernamePatternCog
19 20
 
20 21
 CURRENT_CONFIG_VERSION = 3
21 22
 if (CONFIG.get('__config_version') or 0) < CURRENT_CONFIG_VERSION:
@@ -65,6 +66,7 @@ bot.add_cog(CrossPostCog(bot))
65 66
 bot.add_cog(JoinRaidCog(bot))
66 67
 bot.add_cog(PatternCog(bot))
67 68
 bot.add_cog(URLSpamCog(bot))
69
+bot.add_cog(UsernamePatternCog(bot))
68 70
 
69 71
 bot.run(CONFIG['client_token'], bot=True, reconnect=True)
70 72
 print('\nBot aborted')

+ 1
- 0
config.py.sample Datei anzeigen

@@ -10,6 +10,7 @@ CONFIG = {
10 10
 	'failure_emoji': '❌',
11 11
 	'warning_emoji': '⚠️',
12 12
 	'info_emoji': 'ℹ️',
13
+	'ignore_emoji': '👍',
13 14
 	'config_path': 'config/',
14 15
 	'cog_defaults': {
15 16
 		'CrossPostCog': {

+ 185
- 0
rocketbot/cogs/usernamecog.py Datei anzeigen

@@ -0,0 +1,185 @@
1
+"""
2
+Cog for detecting username patterns.
3
+"""
4
+from discord import Guild, Member
5
+from discord.ext import commands
6
+
7
+from config import CONFIG
8
+from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
9
+from rocketbot.storage import Storage
10
+
11
+class UsernamePatternContext:
12
+	"""
13
+	BotMessage context for a flagged username
14
+	"""
15
+	def __init__(self, member: Member) -> None:
16
+		self.member: Member = member
17
+		self.kicked_by: Member = None
18
+		self.banned_by: Member = None
19
+		self.ignored_by: Member = None
20
+
21
+	def reactions(self) -> list[BotMessageReaction]:
22
+		"""
23
+		Generates updated BotMessageReactions based on context state.
24
+		"""
25
+		r: list[BotMessageReaction] = []
26
+		if self.ignored_by:
27
+			r.append(BotMessageReaction(CONFIG['ignore_emoji'], False, f'Ignored by {self.ignored_by.name}'))
28
+		elif self.banned_by:
29
+			r.append(BotMessageReaction(CONFIG['ban_emoji'], False, f'Banned by {self.banned_by.name}'))
30
+		elif self.kicked_by:
31
+			r.append(BotMessageReaction(CONFIG['kick_emoji'], False, f'Kicked by {self.kicked_by.name}'))
32
+			r.append(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
33
+		else:
34
+			r.append(BotMessageReaction(CONFIG['kick_emoji'], True, 'Kick user'))
35
+			r.append(BotMessageReaction(CONFIG['ban_emoji'], True, 'Ban user'))
36
+			r.append(BotMessageReaction(CONFIG['ignore_emoji'], True, 'Ignore warning'))
37
+		return r
38
+
39
+class UsernamePatternCog(BaseCog, name='Username Pattern'):
40
+	"""
41
+	Detects usernames that match certain flagged patterns. Posts a mod warning
42
+	message on a match.
43
+	"""
44
+
45
+	SETTING_ENABLED = CogSetting('enabled', bool,
46
+			brief='username pattern detection',
47
+			description='Whether new users are checked for common patterns.')
48
+
49
+	SETTING_PATTERNS = CogSetting('patterns', None)
50
+
51
+	def __init__(self, bot):
52
+		super().__init__(bot)
53
+		self.add_setting(UsernamePatternCog.SETTING_ENABLED)
54
+
55
+	def __get_patterns(self, guild: Guild) -> list[str]:
56
+		"""
57
+		Returns an array of username patterns.
58
+		"""
59
+		patterns: list[str] = self.get_guild_setting(guild, self.SETTING_PATTERNS)
60
+		if patterns is None:
61
+			patterns = []
62
+			Storage.set_config_value(guild, 'UsernamePatternCog.patterns', patterns)
63
+		return patterns
64
+
65
+	@classmethod
66
+	def __save_patterns(cls,
67
+			guild: Guild,
68
+			patterns: list[str]) -> None:
69
+		"""
70
+		Saves username pattern array.
71
+		"""
72
+		cls.set_guild_setting(guild, cls.SETTING_PATTERNS, patterns)
73
+
74
+	@commands.group(
75
+		brief='Manages username pattern detection'
76
+	)
77
+	@commands.has_permissions(ban_members=True)
78
+	@commands.guild_only()
79
+	async def username(self, context: commands.Context):
80
+		'Username pattern command group'
81
+		if context.invoked_subcommand is None:
82
+			await context.send_help()
83
+
84
+	@username.command(
85
+		brief='Adds a username pattern',
86
+		description='Adds a username pattern.',
87
+		usage='<pattern>'
88
+	)
89
+	async def add(self, context: commands.Context, pattern: str) -> None:
90
+		'Command handler'
91
+		norm_pattern = pattern.lower()
92
+		patterns: list[str] = self.__get_patterns(context.guild)
93
+		if norm_pattern in patterns:
94
+			await context.reply(f'Pattern `{norm_pattern}` already added.', mention_author=False)
95
+			return
96
+		patterns.append(norm_pattern)
97
+		self.__save_patterns(context.guild, patterns)
98
+		await context.reply(f'Pattern `{norm_pattern}` added.', mention_author=False)
99
+
100
+	@username.command(
101
+		brief='Removes a username pattern',
102
+		description='Removes an existing username pattern',
103
+		usage='<pattern>'
104
+	)
105
+	async def remove(self, context: commands.Context, pattern: str) -> None:
106
+		'Command handler'
107
+		norm_pattern = pattern.lower()
108
+		guild: Guild = context.guild
109
+		patterns: list[str] = self.__get_patterns(guild)
110
+		len_before = len(patterns)
111
+		patterns = list(filter(lambda p: p != norm_pattern, patterns))
112
+		if len(patterns) == len_before:
113
+			await context.reply(f'Pattern `{norm_pattern}` not found.', mention_author=False)
114
+			return
115
+		self.__save_patterns(guild, patterns)
116
+		await context.reply(f'Pattern `{norm_pattern}` removed.', mention_author=False)
117
+
118
+	@username.command(
119
+		brief='Lists username patterns'
120
+	)
121
+	async def list(self, context: commands.Context) -> None:
122
+		'Command handler'
123
+		guild: Guild = context.guild
124
+		patterns: list[str] = self.__get_patterns(guild)
125
+		if len(patterns) == 0:
126
+			await context.reply('No patterns defined', mention_author=False)
127
+		else:
128
+			msg = 'Patterns:\n\n> `' + '`\n> `'.join(patterns) + '`'
129
+			await context.reply(msg, mention_author=False)
130
+
131
+	@commands.Cog.listener()
132
+	async def on_member_join(self, member: Member) -> None:
133
+		'Event handler'
134
+		for pattern in self.__get_patterns(member.guild):
135
+			if self.matches(pattern, member.name):
136
+				await self.handle_match(member, pattern)
137
+			elif self.matches(pattern, member.display_name):
138
+				await self.handle_match(member, pattern)
139
+
140
+	def matches(self, pattern: str, subject: str) -> bool:
141
+		'Checks if a username matches a given pattern'
142
+		if pattern is None:
143
+			return False
144
+		if subject is None:
145
+			return False
146
+		return pattern.lower() in subject.lower()
147
+
148
+	async def handle_match(self, member: Member, pattern: str) -> None:
149
+		"""
150
+		Handles a username match.
151
+		"""
152
+		# TODO: Prevent double handling?
153
+		self.log(member.guild, f'User {member.id} {member.display_name} matches pattern "{pattern}"')
154
+		context = UsernamePatternContext(member)
155
+		bm = BotMessage(
156
+			member.guild,
157
+			f'User {member.mention} ({str(member.id)}, {member.display_name}) has ' +
158
+			f'username matching pattern `{pattern}`.',
159
+			BotMessage.TYPE_MOD_WARNING,
160
+			context)
161
+		await bm.set_reactions(context.reactions())
162
+		await self.post_message(bm)
163
+
164
+	async def on_mod_react(self,
165
+			bot_message: BotMessage,
166
+			reaction: BotMessageReaction,
167
+			reacted_by: Member) -> None:
168
+		context: UsernamePatternContext = bot_message.context
169
+		if reaction.emoji == CONFIG['kick_emoji']:
170
+			await context.member.kick(
171
+				reason=f'Rocketbot: Flagged username pattern. Kicked by {reacted_by.name}.')
172
+			context.kicked_by = reacted_by
173
+			self.log(context.member.guild, f'User {context.member.name} kicked by {reacted_by.name}')
174
+			await bot_message.set_reactions(context.reactions())
175
+		elif reaction.emoji == CONFIG['ban_emoji']:
176
+			await context.member.ban(
177
+				reason=f'Rocketbot: Flagged username pattern. Banned by {reacted_by.name}.',
178
+				delete_message_days=0)
179
+			context.banned_by = reacted_by
180
+			self.log(context.member.guild, f'User {context.member.name} banned by {reacted_by.name}')
181
+			await bot_message.set_reactions(context.reactions())
182
+		elif reaction.emoji == CONFIG['ignore_emoji']:
183
+			context.ignored_by = reacted_by
184
+			self.log(context.member.guild, f'Warning ignored by {reacted_by.name}')
185
+			await bot_message.set_reactions(context.reactions())

Laden…
Abbrechen
Speichern