|
|
@@ -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)
|