Pārlūkot izejas kodu

Slash commands registering but not receiving callbacks

pull/13/head
Rocketsoup 2 mēnešus atpakaļ
vecāks
revīzija
644f31cda0

+ 18
- 6
rocketbot/bot.py Parādīt failu

@@ -5,7 +5,10 @@ from discord import Intents
5 5
 from discord.ext import commands
6 6
 
7 7
 from config import CONFIG
8
-from rocketbot.utils import bot_log
8
+from rocketbot.cogs.basecog import BaseCog
9
+from rocketbot.cogsetting import CogSetting
10
+from rocketbot.utils import bot_log, dump_stacktrace
11
+
9 12
 
10 13
 class Rocketbot(commands.Bot):
11 14
 	"""
@@ -16,11 +19,7 @@ class Rocketbot(commands.Bot):
16 19
 
17 20
 	async def on_command_error(self, context: commands.Context, exception: BaseException) -> None:
18 21
 		bot_log(None, None, f'Command error')
19
-		ex = exception
20
-		while ex is not None:
21
-			print(f'Caused by {ex}')
22
-			traceback.print_exception(type(ex), ex, ex.__traceback__)
23
-			ex = ex.__cause__ if ex.__cause__ != ex else None
22
+		dump_stacktrace(exception)
24 23
 		if context.guild is None or \
25 24
 				context.message.channel is None or \
26 25
 				context.message.author.bot:
@@ -43,6 +42,19 @@ class Rocketbot(commands.Bot):
43 42
 			f'	event kwargs: {kwargs}' + \
44 43
 			traceback.format_exc())
45 44
 
45
+	async def on_ready(self):
46
+		for cog in self.cogs.values():
47
+			if isinstance(cog, BaseCog):
48
+				bcog: BaseCog = cog
49
+				if len(bcog.settings) > 0:
50
+					CogSetting.set_up_all(bcog, self, bcog.settings)
51
+		try:
52
+			synced_commands = await self.tree.sync()
53
+			for command in synced_commands:
54
+				bot_log(None, None, f'Synced command: /{command.name}')
55
+		except Exception as e:
56
+			dump_stacktrace(e)
57
+
46 58
 # Current active bot instance
47 59
 rocketbot: Optional[Rocketbot] = None
48 60
 

+ 5
- 4
rocketbot/cogs/basecog.py Parādīt failu

@@ -2,20 +2,21 @@
2 2
 Base cog class and helper classes.
3 3
 """
4 4
 from datetime import datetime, timedelta, timezone
5
-from typing import Optional
5
+from typing import Optional, ForwardRef
6 6
 
7 7
 from discord import Guild, Member, Message, RawReactionActionEvent, TextChannel
8 8
 from discord.abc import GuildChannel
9 9
 from discord.ext import commands
10 10
 
11 11
 from config import CONFIG
12
-from rocketbot.bot import Rocketbot
13 12
 from rocketbot.botmessage import BotMessage, BotMessageReaction
14 13
 from rocketbot.cogsetting import CogSetting
15 14
 from rocketbot.collections import AgeBoundDict
16 15
 from rocketbot.storage import Storage
17 16
 from rocketbot.utils import bot_log
18 17
 
18
+Rocketbot = ForwardRef('rocketbot.bot.Rocketbot')
19
+
19 20
 class WarningContext:
20 21
 	def __init__(self, member: Member, warn_time: datetime):
21 22
 		self.member = member
@@ -107,8 +108,8 @@ class BaseCog(commands.Cog):
107 108
 		key = f'{cls.__name__}.{setting.name}'
108 109
 		Storage.set_config_value(guild, key, new_value)
109 110
 
110
-	@commands.Cog.listener()
111
-	async def on_ready(self):
111
+	# @commands.Cog.listener()
112
+	async def __on_ready(self):
112 113
 		"""Event listener"""
113 114
 		if not self.are_settings_setup:
114 115
 			self.are_settings_setup = True

+ 0
- 6
rocketbot/cogs/generalcog.py Parādīt failu

@@ -53,12 +53,6 @@ class GeneralCog(BaseCog, name='General'):
53 53
 		"""Event handler"""
54 54
 		self.log(None, 'Bot done initializing')
55 55
 		self.is_ready = True
56
-		try:
57
-			synced_commands = await self.bot.tree.sync()
58
-			for command in synced_commands:
59
-				self.log(None, f'Synced command: {command.name}')
60
-		except Exception as e:
61
-			dump_stacktrace(e)
62 56
 		if self.is_first_ready:
63 57
 			print('----------------------------------------------------------')
64 58
 			self.is_first_ready = False

+ 4
- 2
rocketbot/cogs/urlspamcog.py Parādīt failu

@@ -3,6 +3,8 @@ Cog for detecting URLs posted by new users.
3 3
 """
4 4
 import re
5 5
 from datetime import timedelta
6
+from typing import Literal
7
+
6 8
 from discord import Member, Message, utils as discordutils
7 9
 from discord.ext import commands
8 10
 from discord.utils import escape_markdown
@@ -30,7 +32,7 @@ class URLSpamCog(BaseCog, name='URL Spam'):
30 32
 	SETTING_ENABLED = CogSetting('enabled', bool,
31 33
 			brief='URL spam detection',
32 34
 			description='Whether URLs posted soon after joining are flagged.')
33
-	SETTING_ACTION = CogSetting('action', str,
35
+	SETTING_ACTION = CogSetting('action', Literal['nothing', 'modwarn', 'delete', 'kick', 'ban'],
34 36
 			brief='action to take on spam',
35 37
 			description='The action to take on detected URL spam.',
36 38
 			enum_values={'nothing', 'modwarn', 'delete', 'kick', 'ban'})
@@ -44,7 +46,7 @@ class URLSpamCog(BaseCog, name='URL Spam'):
44 46
 				'disables URL spam detection.',
45 47
 			usage='<seconds:int>',
46 48
 			min_value=0)
47
-	SETTING_DECEPTIVE_ACTION = CogSetting('deceptiveaction', str,
49
+	SETTING_DECEPTIVE_ACTION = CogSetting('deceptiveaction', Literal['nothing', 'modwarn', 'modwarndelete', 'chatwarn', 'chatwarndelete', 'delete', 'kick', 'ban'],
48 50
 			brief='action to take on deceptive link markdown',
49 51
 			description='The action to take on chat messages with links ' + \
50 52
 				'where the text looks like a different URL than the actual link.',

+ 95
- 49
rocketbot/cogsetting.py Parādīt failu

@@ -1,16 +1,17 @@
1 1
 """
2 2
 A guild configuration setting available for editing via bot commands.
3 3
 """
4
-from __future__ import annotations
5
-from typing import Any, Optional, Type
4
+import inspect
5
+from typing import Any, Optional, Type, TypeVar, Coroutine, Literal
6 6
 
7
-from discord import Interaction, Permissions
8
-from discord.app_commands.commands import Command, Group
7
+from discord import Interaction, Permissions, permissions
8
+from discord.app_commands import Range, guild_only, default_permissions
9
+from discord.app_commands.commands import Command, Group, CommandCallback
9 10
 from discord.ext.commands import Bot
10 11
 
11 12
 from config import CONFIG
12
-from rocketbot.cogs.basecog import BaseCog
13 13
 from rocketbot.storage import Storage
14
+from rocketbot.utils import bot_log
14 15
 
15 16
 # def _fix_command(command: Command) -> None:
16 17
 # 	"""
@@ -21,7 +22,11 @@ from rocketbot.storage import Storage
21 22
 # 	del params['context']
22 23
 # 	command.params = params
23 24
 
25
+BaseCog = TypeVar('BaseCog', bound='rocketbot.cogs.BaseCog')
26
+
24 27
 class CogSetting:
28
+	permissions: Permissions = Permissions(Permissions.manage_messages.flag)
29
+
25 30
 	"""
26 31
 	Describes a configuration setting for a guild that can be edited by the
27 32
 	mods of those guilds. BaseCog can generate "get" and "set" commands (or
@@ -99,22 +104,23 @@ class CogSetting:
99 104
 		usually only be called by BaseCog.
100 105
 		"""
101 106
 		if self.name in ('enabled', 'is_enabled'):
102
-			bot.tree.add_command(self.__make_enable_command(cog))
103
-			bot.tree.add_command(self.__make_disable_command(cog))
107
+			self.__enable_group.add_command(self.__make_enable_command(cog))
108
+			self.__disable_group.add_command(self.__make_disable_command(cog))
104 109
 		else:
105
-			bot.tree.add_command(self.__make_getter_command(cog))
106
-			bot.tree.add_command(self.__make_setter_command(cog))
110
+			self.__get_group.add_command(self.__make_getter_command(cog))
111
+			self.__set_group.add_command(self.__make_setter_command(cog))
107 112
 
108 113
 	def __make_getter_command(self, cog: BaseCog) -> Command:
109 114
 		setting: CogSetting = self
110 115
 		setting_name = setting.name
111 116
 		if cog.config_prefix is not None:
112
-			setting_name = f'{cog.config_prefix}.{setting_name}'
113
-		async def getter(cog0: BaseCog, interaction: Interaction) -> None:
114
-			key = f'{cog0.__class__.__name__}.{setting.name}'
117
+			setting_name = f'{cog.config_prefix}_{setting_name}'
118
+		async def getter(self, interaction: Interaction) -> None:
119
+			print(f"invoking getter for {setting_name}")
120
+			key = f'{self.__class__.__name__}.{setting.name}'
115 121
 			value = Storage.get_config_value(interaction.guild, key)
116 122
 			if value is None:
117
-				value = cog0.get_cog_default(setting.name)
123
+				value = self.get_cog_default(setting.name)
118 124
 				await interaction.response.send_message(
119 125
 					f'{CONFIG["info_emoji"]} `{setting_name}` is using default of `{value}`',
120 126
 					ephemeral=True
@@ -124,11 +130,14 @@ class CogSetting:
124 130
 					f'{CONFIG["info_emoji"]} `{setting_name}` is set to `{value}`',
125 131
 					ephemeral=True
126 132
 				)
133
+		setattr(cog.__class__, f'_cmd_get_{setting.name}', getter)
134
+		getter.__qualname__ = f'{self.__class__.__name__}._cmd_get_{setting.name}'
135
+		bot_log(None, cog.__class__, f"Creating /get {setting_name}")
127 136
 		command = Command(
128 137
 			name=setting_name,
129
-			description=setting.description,
138
+			description=f'Shows value of {setting_name}',
130 139
 			callback=getter,
131
-			parent=CogSetting.__get_group
140
+			parent=CogSetting.__get_group,
132 141
 		)
133 142
 		return command
134 143
 
@@ -136,8 +145,9 @@ class CogSetting:
136 145
 		setting: CogSetting = self
137 146
 		setting_name = setting.name
138 147
 		if cog.config_prefix is not None:
139
-			setting_name = f'{cog.config_prefix}.{setting_name}'
140
-		async def setter(cog0: BaseCog, interaction: Interaction, new_value) -> None:
148
+			setting_name = f'{cog.config_prefix}_{setting_name}'
149
+		async def setter_general(self, interaction: Interaction, new_value) -> None:
150
+			print(f"invoking setter for {setting_name} with value {new_value}")
141 151
 			try:
142 152
 				setting.validate_value(new_value)
143 153
 			except ValueError as ve:
@@ -146,41 +156,65 @@ class CogSetting:
146 156
 					ephemeral=True
147 157
 				)
148 158
 				return
149
-			key = f'{cog0.__class__.__name__}.{setting.name}'
159
+			key = f'{self.__class__.__name__}.{setting.name}'
150 160
 			Storage.set_config_value(interaction.guild, key, new_value)
151 161
 			await interaction.response.send_message(
152 162
 				f'{CONFIG["success_emoji"]} `{setting_name}` is now set to `{new_value}`',
153 163
 				ephemeral=True
154 164
 			)
155
-			await cog0.on_setting_updated(interaction.guild, setting)
156
-			cog0.log(interaction.guild, f'{interaction.message.author.name} set {key} to {new_value}')
165
+			await self.on_setting_updated(interaction.guild, setting)
166
+			self.log(interaction.guild, f'{interaction.message.author.name} set {key} to {new_value}')
157 167
 
158 168
 		type_str: str = 'any'
169
+		setter: CommandCallback = setter_general
159 170
 		if self.datatype == int:
160 171
 			if self.min_value is not None or self.max_value is not None:
161 172
 				type_str = f'discord.app_commands.Range[int, {self.min_value}, {self.max_value}]'
173
+				r_min = self.min_value
174
+				r_max = self.max_value
175
+				async def setter_range(self, interaction: Interaction, new_value: Range[int, r_min, r_max]) -> None:
176
+					await self.setter_general(interaction, new_value)
177
+				setter = setter_range
162 178
 			else:
163 179
 				type_str = 'int'
164
-		elif setting.datatype == str:
180
+				async def setter_int(self, interaction: Interaction, new_value: int) -> None:
181
+					await self.setter_general(interaction, new_value)
182
+				setter = setter_int
183
+		elif self.datatype == float:
184
+			type_str = 'float'
185
+			async def setter_float(self, interaction: Interaction, new_value: float) -> None:
186
+				await self.setter_general(interaction, new_value)
187
+			setter = setter_float
188
+		elif getattr(self.datatype, '__origin__', None) == Literal:
189
+			value_list = '"' + '", "'.join(self.enum_values) + '"'
190
+			type_str = f'typing.Literal[{value_list}]'
191
+			values = self.enum_values
192
+			dt = self.datatype
193
+			async def setter_enum(self, interaction: Interaction, new_value: dt) -> None:
194
+				await self.setter_general(interaction, new_value)
195
+
196
+			setter = setter_enum
197
+		elif self.datatype == str:
165 198
 			if self.enum_values is not None:
166
-				value_list = '"' + '", "'.join(self.enum_values) + '"'
167
-				type_str = f'typing.Literal[{value_list}]'
199
+				raise ValueError('Type for a setting with enum values should be typing.Literal')
168 200
 			else:
169 201
 				type_str = 'str'
202
+				async def setter_str(self, interaction: Interaction, new_value: str) -> None:
203
+					await self.setter_general(interaction, new_value)
204
+				setter = setter_str
170 205
 		elif setting.datatype == bool:
171
-			type_str = f'typing.Literal["false", "true"]'
172
-
173
-		setter.__doc__ = f"""Sets {self.description}.
174
-
175
-Parameters
176
-----------
177
-cog: discord.ext.commands.Cog
178
-interaction: discord.Interaction
179
-new_value: {type_str}
180
-"""
206
+			type_str = f'bool'
207
+			async def setter_bool(self, interaction: Interaction, new_value: bool) -> None:
208
+				await self.setter_general(interaction, new_value)
209
+			setter = setter_bool
210
+		elif setting.datatype is not None:
211
+			raise ValueError(f'Invalid type {self.datatype}')
212
+		setattr(cog.__class__, f'_cmd_set_{setting.name}', setter)
213
+		setter.__qualname__ = f'{cog.__class__.__name__}._cmd_set_{setting.name}'
214
+		bot_log(None, cog.__class__, f"Creating /set {setting_name} {type_str}")
181 215
 		command = Command(
182
-			name=f'{setting.name}',
183
-			description=setting.description,
216
+			name=setting_name,
217
+			description=f'Sets value of {setting_name}',
184 218
 			callback=setter,
185 219
 			parent=CogSetting.__set_group,
186 220
 		)
@@ -192,19 +226,23 @@ new_value: {type_str}
192 226
 
193 227
 	def __make_enable_command(self, cog: BaseCog) -> Command:
194 228
 		setting: CogSetting = self
195
-		async def enabler(cog0: BaseCog, interaction: Interaction) -> None:
196
-			key = f'{cog0.__class__.__name__}.{setting.name}'
229
+		async def enabler(self, interaction: Interaction) -> None:
230
+			print(f"invoking enable for {self.config_prefix}")
231
+			key = f'{self.__class__.__name__}.{setting.name}'
197 232
 			Storage.set_config_value(interaction.guild, key, True)
198 233
 			await interaction.response.send_message(
199 234
 				f'{CONFIG["success_emoji"]} {setting.brief.capitalize()} enabled.',
200 235
 				ephemeral=True
201 236
 			)
202
-			await cog0.on_setting_updated(interaction.guild, setting)
203
-			cog0.log(interaction.guild, f'{interaction.message.author.name} enabled {cog0.__class__.__name__}')
237
+			await self.on_setting_updated(interaction.guild, setting)
238
+			self.log(interaction.guild, f'{interaction.message.author.name} enabled {self.__class__.__name__}')
239
+		setattr(cog.__class__, f'_cmd_enable', enabler)
240
+		enabler.__qualname__ = f'{cog.__class__.__name__}._cmd_enable'
241
+		bot_log(None, cog.__class__, f"Creating /enable {cog.config_prefix}")
204 242
 
205 243
 		command = Command(
206 244
 			name=cog.config_prefix,
207
-			description=setting.description,
245
+			description=f'Enables {cog.config_prefix} functionality',
208 246
 			callback=enabler,
209 247
 			parent=CogSetting.__enable_group,
210 248
 		)
@@ -214,19 +252,23 @@ new_value: {type_str}
214 252
 
215 253
 	def __make_disable_command(self, cog: BaseCog) -> Command:
216 254
 		setting: CogSetting = self
217
-		async def disabler(cog0: BaseCog, interaction: Interaction) -> None:
218
-			key = f'{cog0.__class__.__name__}.{setting.name}'
255
+		async def disabler(self, interaction: Interaction) -> None:
256
+			print(f"invoking disable for {self.config_prefix}")
257
+			key = f'{self.__class__.__name__}.{setting.name}'
219 258
 			Storage.set_config_value(interaction.guild, key, False)
220 259
 			await interaction.response.send_message(
221 260
 				f'{CONFIG["success_emoji"]} {setting.brief.capitalize()} disabled.',
222 261
 				ephemeral=True
223 262
 			)
224
-			await cog0.on_setting_updated(interaction.guild, setting)
225
-			cog0.log(interaction.guild, f'{interaction.message.author.name} disabled {cog0.__class__.__name__}')
263
+			await self.on_setting_updated(interaction.guild, setting)
264
+			self.log(interaction.guild, f'{interaction.message.author.name} disabled {self.__class__.__name__}')
265
+		setattr(cog.__class__, f'_cmd_disable', disabler)
266
+		disabler.__qualname__ = f'{cog.__class__.__name__}._cmd_disable'
267
+		bot_log(None, cog.__class__, f"Creating /disable {cog.config_prefix}")
226 268
 
227 269
 		command = Command(
228 270
 			name=cog.config_prefix,
229
-			description=setting.description,
271
+			description=f'Disables {cog.config_prefix} functionality',
230 272
 			callback=disabler,
231 273
 			parent=CogSetting.__disable_group,
232 274
 		)
@@ -248,8 +290,12 @@ new_value: {type_str}
248 290
 		Otherwise, they will be added at the top level.
249 291
 		"""
250 292
 		cls.__set_up_base_commands(bot)
293
+		if len(settings) == 0:
294
+			return
295
+		bot_log(None, cog.__class__, f"Setting up slash commands for {cog.__class__.__name__}")
251 296
 		for setting in settings:
252 297
 			setting.set_up(cog, bot)
298
+		bot_log(None, cog.__class__, f"Done setting up slash commands for {cog.__class__.__name__}")
253 299
 
254 300
 	@classmethod
255 301
 	def __set_up_base_commands(cls, bot: Bot) -> None:
@@ -259,22 +305,22 @@ new_value: {type_str}
259 305
 		cls.__set_group = Group(
260 306
 			name='set',
261 307
 			description='Sets a bot configuration value for this guild',
262
-			default_permissions=Permissions.manage_messages
308
+			default_permissions=cls.permissions
263 309
 		)
264 310
 		cls.__get_group = Group(
265 311
 			name='get',
266 312
 			description='Shows a configured bot value for this guild',
267
-			default_permissions=Permissions.manage_messages
313
+			default_permissions=cls.permissions
268 314
 		)
269 315
 		cls.__enable_group = Group(
270 316
 			name='enable',
271 317
 			description='Enables a set of bot functionality for this guild',
272
-			default_permissions=Permissions.manage_messages
318
+			default_permissions=cls.permissions
273 319
 		)
274 320
 		cls.__disable_group = Group(
275 321
 			name='disable',
276 322
 			description='Disables a set of bot functionality for this guild',
277
-			default_permissions=Permissions.manage_messages
323
+			default_permissions=cls.permissions
278 324
 		)
279 325
 		bot.tree.add_command(cls.__set_group)
280 326
 		bot.tree.add_command(cls.__get_group)

+ 1
- 1
rocketbot/utils.py Parādīt failu

@@ -11,7 +11,7 @@ import discord
11 11
 from discord import Guild
12 12
 from discord.ext.commands import Cog
13 13
 
14
-def dump_stacktrace(e: Exception) -> None:
14
+def dump_stacktrace(e: BaseException) -> None:
15 15
 	print(e, file=sys.stderr)
16 16
 	traceback.print_exception(type(e), e, e.__traceback__)
17 17
 

Notiek ielāde…
Atcelt
Saglabāt