|
|
@@ -2,353 +2,16 @@
|
|
2
|
2
|
Base cog class and helper classes.
|
|
3
|
3
|
"""
|
|
4
|
4
|
from datetime import datetime, timedelta
|
|
5
|
|
-from discord import Guild, Member, Message, PartialEmoji, RawReactionActionEvent, TextChannel
|
|
|
5
|
+from discord import Guild, Member, Message, RawReactionActionEvent
|
|
6
|
6
|
from discord.abc import GuildChannel
|
|
7
|
7
|
from discord.ext import commands
|
|
8
|
8
|
|
|
9
|
9
|
from config import CONFIG
|
|
|
10
|
+from rocketbot.botmessage import BotMessage, BotMessageReaction
|
|
|
11
|
+from rocketbot.cogsetting import CogSetting
|
|
10
|
12
|
from rocketbot.collections import AgeBoundDict
|
|
11
|
|
-from rocketbot.storage import ConfigKey, Storage
|
|
12
|
|
-
|
|
13
|
|
-class BotMessageReaction:
|
|
14
|
|
- """
|
|
15
|
|
- A possible reaction to a bot message that will trigger an action. The list
|
|
16
|
|
- of available reactions will be listed at the end of a BotMessage. When a
|
|
17
|
|
- mod reacts to the message with the emote, something can happen.
|
|
18
|
|
-
|
|
19
|
|
- If the reaction is disabled, reactions will not register. The description
|
|
20
|
|
- will still show up in the message, but no emoji is shown. This can be used
|
|
21
|
|
- to explain why an action is no longer available.
|
|
22
|
|
- """
|
|
23
|
|
- def __init__(self, emoji: str, is_enabled: bool, description: str):
|
|
24
|
|
- self.emoji = emoji
|
|
25
|
|
- self.is_enabled = is_enabled
|
|
26
|
|
- self.description = description
|
|
27
|
|
-
|
|
28
|
|
- def __eq__(self, other):
|
|
29
|
|
- return other is not None and \
|
|
30
|
|
- other.emoji == self.emoji and \
|
|
31
|
|
- other.is_enabled == self.is_enabled and \
|
|
32
|
|
- other.description == self.description
|
|
33
|
|
-
|
|
34
|
|
- @classmethod
|
|
35
|
|
- def standard_set(cls,
|
|
36
|
|
- did_delete: bool = None,
|
|
37
|
|
- message_count: int = 1,
|
|
38
|
|
- did_kick: bool = None,
|
|
39
|
|
- did_ban: bool = None,
|
|
40
|
|
- user_count: int = 1) -> list:
|
|
41
|
|
- """
|
|
42
|
|
- Convenience factory for generating any of the three most common
|
|
43
|
|
- commands: delete message(s), kick user(s), and ban user(s). All
|
|
44
|
|
- arguments are optional. Resulting list can be passed directly to
|
|
45
|
|
- `BotMessage.set_reactions()`.
|
|
46
|
|
-
|
|
47
|
|
- Params
|
|
48
|
|
- - did_delete Whether the message(s) have been deleted. Pass True or
|
|
49
|
|
- False if this applies, omit to leave out delete action.
|
|
50
|
|
- - message_count How many messages there are. Used for pluralizing
|
|
51
|
|
- description. Defaults to 1. Omit if n/a.
|
|
52
|
|
- - did_kick Whether the user(s) have been kicked. Pass True or
|
|
53
|
|
- False if this applies, omit to leave out kick action.
|
|
54
|
|
- - did_ban Whether the user(s) have been banned. Pass True or
|
|
55
|
|
- False if this applies, omit to leave out ban action.
|
|
56
|
|
- - user_count How many users there are. Used for pluralizing
|
|
57
|
|
- description. Defaults to 1. Omit if n/a.
|
|
58
|
|
- """
|
|
59
|
|
- reactions = []
|
|
60
|
|
- if did_delete is not None:
|
|
61
|
|
- if did_delete:
|
|
62
|
|
- reactions.append(BotMessageReaction(
|
|
63
|
|
- CONFIG['trash_emoji'],
|
|
64
|
|
- False,
|
|
65
|
|
- 'Message deleted' if message_count == 1 else 'Messages deleted'))
|
|
66
|
|
- else:
|
|
67
|
|
- reactions.append(BotMessageReaction(
|
|
68
|
|
- CONFIG['trash_emoji'],
|
|
69
|
|
- True,
|
|
70
|
|
- 'Delete message' if message_count == 1 else 'Delete messages'))
|
|
71
|
|
- if did_kick is not None:
|
|
72
|
|
- if did_ban is not None and did_ban:
|
|
73
|
|
- # Don't show kick option at all if we also banned
|
|
74
|
|
- pass
|
|
75
|
|
- elif did_kick:
|
|
76
|
|
- reactions.append(BotMessageReaction(
|
|
77
|
|
- CONFIG['kick_emoji'],
|
|
78
|
|
- False,
|
|
79
|
|
- 'User kicked' if user_count == 1 else 'Users kicked'))
|
|
80
|
|
- else:
|
|
81
|
|
- reactions.append(BotMessageReaction(
|
|
82
|
|
- CONFIG['kick_emoji'],
|
|
83
|
|
- True,
|
|
84
|
|
- 'Kick user' if user_count == 1 else 'Kick users'))
|
|
85
|
|
- if did_ban is not None:
|
|
86
|
|
- if did_ban:
|
|
87
|
|
- reactions.append(BotMessageReaction(
|
|
88
|
|
- CONFIG['ban_emoji'],
|
|
89
|
|
- False,
|
|
90
|
|
- 'User banned' if user_count == 1 else 'Users banned'))
|
|
91
|
|
- else:
|
|
92
|
|
- reactions.append(BotMessageReaction(
|
|
93
|
|
- CONFIG['ban_emoji'],
|
|
94
|
|
- True,
|
|
95
|
|
- 'Ban user' if user_count == 1 else 'Ban users'))
|
|
96
|
|
- return reactions
|
|
97
|
|
-
|
|
98
|
|
-class BotMessage:
|
|
99
|
|
- """
|
|
100
|
|
- Holds state for a bot-generated message. A message is composed, sent via
|
|
101
|
|
- `BaseCog.post_message()`, and can later be updated.
|
|
102
|
|
-
|
|
103
|
|
- A message consists of a type (e.g. info, warning), text, optional quoted
|
|
104
|
|
- text (such as the content of a flagged message), and an optional list of
|
|
105
|
|
- actions that can be taken via a mod reacting to the message.
|
|
106
|
|
- """
|
|
107
|
|
-
|
|
108
|
|
- TYPE_DEFAULT = 0
|
|
109
|
|
- TYPE_INFO = 1
|
|
110
|
|
- TYPE_MOD_WARNING = 2
|
|
111
|
|
- TYPE_SUCCESS = 3
|
|
112
|
|
- TYPE_FAILURE = 4
|
|
113
|
|
-
|
|
114
|
|
- def __init__(self,
|
|
115
|
|
- guild: Guild,
|
|
116
|
|
- text: str,
|
|
117
|
|
- type: int = TYPE_DEFAULT,
|
|
118
|
|
- context = None,
|
|
119
|
|
- reply_to: Message = None):
|
|
120
|
|
- self.guild = guild
|
|
121
|
|
- self.text = text
|
|
122
|
|
- self.type = type
|
|
123
|
|
- self.context = context
|
|
124
|
|
- self.quote = None
|
|
125
|
|
- self.source_cog = None # Set by `BaseCog.post_message()`
|
|
126
|
|
- self.__posted_text = None # last text posted, to test for changes
|
|
127
|
|
- self.__posted_emoji = set()
|
|
128
|
|
- self.__message = None # Message
|
|
129
|
|
- self.__reply_to = reply_to
|
|
130
|
|
- self.__reactions = [] # BotMessageReaction[]
|
|
131
|
|
-
|
|
132
|
|
- def is_sent(self) -> bool:
|
|
133
|
|
- """
|
|
134
|
|
- Returns whether this message has been sent to the guild. This may
|
|
135
|
|
- continue returning False even after calling BaseCog.post_message if
|
|
136
|
|
- the guild has no configured warning channel.
|
|
137
|
|
- """
|
|
138
|
|
- return self.__message is not None
|
|
139
|
|
-
|
|
140
|
|
- def message_id(self):
|
|
141
|
|
- 'Returns the Message id or None if not sent.'
|
|
142
|
|
- return self.__message.id if self.__message else None
|
|
143
|
|
-
|
|
144
|
|
- def message_sent_at(self) -> datetime:
|
|
145
|
|
- 'Returns when the message was sent or None if not sent.'
|
|
146
|
|
- return self.__message.created_at if self.__message else None
|
|
147
|
|
-
|
|
148
|
|
- def has_reactions(self) -> bool:
|
|
149
|
|
- 'Whether this message has any reactions defined.'
|
|
150
|
|
- return len(self.__reactions) > 0
|
|
151
|
|
-
|
|
152
|
|
- async def set_text(self, new_text: str) -> None:
|
|
153
|
|
- """
|
|
154
|
|
- Replaces the text of this message. If the message has been sent, it will
|
|
155
|
|
- be updated.
|
|
156
|
|
- """
|
|
157
|
|
- self.text = new_text
|
|
158
|
|
- await self.update_if_sent()
|
|
159
|
|
-
|
|
160
|
|
- async def set_reactions(self, reactions: list) -> None:
|
|
161
|
|
- """
|
|
162
|
|
- Replaces all BotMessageReactions with a new list. If the message has
|
|
163
|
|
- been sent, it will be updated.
|
|
164
|
|
- """
|
|
165
|
|
- if reactions == self.__reactions:
|
|
166
|
|
- # No change
|
|
167
|
|
- return
|
|
168
|
|
- self.__reactions = reactions.copy() if reactions is not None else []
|
|
169
|
|
- await self.update_if_sent()
|
|
170
|
|
-
|
|
171
|
|
- async def add_reaction(self, reaction: BotMessageReaction) -> None:
|
|
172
|
|
- """
|
|
173
|
|
- Adds one BotMessageReaction to this message. If a reaction already
|
|
174
|
|
- exists for the given emoji it is replaced with the new one. If the
|
|
175
|
|
- message has been sent, it will be updated.
|
|
176
|
|
- """
|
|
177
|
|
- # Alias for update. Makes for clearer intent.
|
|
178
|
|
- await self.update_reaction(reaction)
|
|
179
|
|
-
|
|
180
|
|
- async def update_reaction(self, reaction: BotMessageReaction) -> None:
|
|
181
|
|
- """
|
|
182
|
|
- Updates or adds a BotMessageReaction. If the message has been sent, it
|
|
183
|
|
- will be updated.
|
|
184
|
|
- """
|
|
185
|
|
- found = False
|
|
186
|
|
- for i, existing in enumerate(self.__reactions):
|
|
187
|
|
- if existing.emoji == reaction.emoji:
|
|
188
|
|
- if reaction == self.__reactions[i]:
|
|
189
|
|
- # No change
|
|
190
|
|
- return
|
|
191
|
|
- self.__reactions[i] = reaction
|
|
192
|
|
- found = True
|
|
193
|
|
- break
|
|
194
|
|
- if not found:
|
|
195
|
|
- self.__reactions.append(reaction)
|
|
196
|
|
- await self.update_if_sent()
|
|
197
|
|
-
|
|
198
|
|
- async def remove_reaction(self, reaction_or_emoji) -> None:
|
|
199
|
|
- """
|
|
200
|
|
- Removes a reaction. Can pass either a BotMessageReaction or just the
|
|
201
|
|
- emoji string. If the message has been sent, it will be updated.
|
|
202
|
|
- """
|
|
203
|
|
- for i, existing in enumerate(self.__reactions):
|
|
204
|
|
- if (isinstance(reaction_or_emoji, str) and existing.emoji == reaction_or_emoji) or \
|
|
205
|
|
- (isinstance(reaction_or_emoji, BotMessageReaction) and \
|
|
206
|
|
- existing.emoji == reaction_or_emoji.emoji):
|
|
207
|
|
- self.__reactions.pop(i)
|
|
208
|
|
- await self.update_if_sent()
|
|
209
|
|
- return
|
|
210
|
|
-
|
|
211
|
|
- def reaction_for_emoji(self, emoji) -> BotMessageReaction:
|
|
212
|
|
- """
|
|
213
|
|
- Finds the BotMessageReaction for the given emoji or None if not found.
|
|
214
|
|
- Accepts either a PartialEmoji or str.
|
|
215
|
|
- """
|
|
216
|
|
- for reaction in self.__reactions:
|
|
217
|
|
- if isinstance(emoji, PartialEmoji) and reaction.emoji == emoji.name:
|
|
218
|
|
- return reaction
|
|
219
|
|
- if isinstance(emoji, str) and reaction.emoji == emoji:
|
|
220
|
|
- return reaction
|
|
221
|
|
- return None
|
|
222
|
|
-
|
|
223
|
|
- async def update_if_sent(self) -> None:
|
|
224
|
|
- """
|
|
225
|
|
- Updates the text and/or reactions on a message if it was sent to
|
|
226
|
|
- the guild, otherwise does nothing. Does not need to be called by
|
|
227
|
|
- BaseCog subclasses.
|
|
228
|
|
- """
|
|
229
|
|
- if self.__message:
|
|
230
|
|
- await self.update()
|
|
231
|
|
-
|
|
232
|
|
- async def update(self) -> None:
|
|
233
|
|
- """
|
|
234
|
|
- Sends or updates an already sent message based on BotMessage state.
|
|
235
|
|
- Does not need to be called by BaseCog subclasses.
|
|
236
|
|
- """
|
|
237
|
|
- content: str = self.__formatted_message()
|
|
238
|
|
- if self.__message:
|
|
239
|
|
- if content != self.__posted_text:
|
|
240
|
|
- await self.__message.edit(content=content)
|
|
241
|
|
- self.__posted_text = content
|
|
242
|
|
- else:
|
|
243
|
|
- if self.__reply_to:
|
|
244
|
|
- self.__message = await self.__reply_to.reply(content=content, mention_author=False)
|
|
245
|
|
- self.__posted_text = content
|
|
246
|
|
- else:
|
|
247
|
|
- channel_id = Storage.get_config_value(self.guild, ConfigKey.WARNING_CHANNEL_ID)
|
|
248
|
|
- if channel_id is None:
|
|
249
|
|
- BaseCog.log(self.guild, '\u0007No warning channel set! No warning issued.')
|
|
250
|
|
- return
|
|
251
|
|
- channel: TextChannel = self.guild.get_channel(channel_id)
|
|
252
|
|
- if channel is None:
|
|
253
|
|
- BaseCog.log(self.guild, '\u0007Configured warning channel does not exist!')
|
|
254
|
|
- return
|
|
255
|
|
- self.__message = await channel.send(content=content)
|
|
256
|
|
- self.__posted_text = content
|
|
257
|
|
- emoji_to_remove = self.__posted_emoji.copy()
|
|
258
|
|
- for reaction in self.__reactions:
|
|
259
|
|
- if reaction.is_enabled:
|
|
260
|
|
- if reaction.emoji not in self.__posted_emoji:
|
|
261
|
|
- await self.__message.add_reaction(reaction.emoji)
|
|
262
|
|
- self.__posted_emoji.add(reaction.emoji)
|
|
263
|
|
- if reaction.emoji in emoji_to_remove:
|
|
264
|
|
- emoji_to_remove.remove(reaction.emoji)
|
|
265
|
|
- for emoji in emoji_to_remove:
|
|
266
|
|
- await self.__message.clear_reaction(emoji)
|
|
267
|
|
- if emoji in self.__posted_emoji:
|
|
268
|
|
- self.__posted_emoji.remove(emoji)
|
|
269
|
|
-
|
|
270
|
|
- def __formatted_message(self) -> str:
|
|
271
|
|
- s: str = ''
|
|
272
|
|
-
|
|
273
|
|
- if self.type == self.TYPE_INFO:
|
|
274
|
|
- s += CONFIG['info_emoji'] + ' '
|
|
275
|
|
- elif self.type == self.TYPE_MOD_WARNING:
|
|
276
|
|
- mention: str = Storage.get_config_value(self.guild, ConfigKey.WARNING_MENTION)
|
|
277
|
|
- if mention:
|
|
278
|
|
- s += mention + ' '
|
|
279
|
|
- s += CONFIG['warning_emoji'] + ' '
|
|
280
|
|
- elif self.type == self.TYPE_SUCCESS:
|
|
281
|
|
- s += CONFIG['success_emoji'] + ' '
|
|
282
|
|
- elif self.type == self.TYPE_FAILURE:
|
|
283
|
|
- s += CONFIG['failure_emoji'] + ' '
|
|
284
|
|
-
|
|
285
|
|
- s += self.text
|
|
286
|
|
-
|
|
287
|
|
- if self.quote:
|
|
288
|
|
- s += f'\n\n> {self.quote}'
|
|
289
|
|
-
|
|
290
|
|
- if len(self.__reactions) > 0:
|
|
291
|
|
- s += '\n\nAvailable actions:'
|
|
292
|
|
- for reaction in self.__reactions:
|
|
293
|
|
- if reaction.is_enabled:
|
|
294
|
|
- s += f'\n {reaction.emoji} {reaction.description}'
|
|
295
|
|
- else:
|
|
296
|
|
- s += f'\n {reaction.description}'
|
|
297
|
|
-
|
|
298
|
|
- return s
|
|
299
|
|
-
|
|
300
|
|
-class CogSetting:
|
|
301
|
|
- """
|
|
302
|
|
- Describes a configuration setting for a guild that can be edited by the
|
|
303
|
|
- mods of those guilds. BaseCog can generate "get" and "set" commands
|
|
304
|
|
- automatically, reducing the boilerplate of generating commands manually.
|
|
305
|
|
- Offers simple validation rules.
|
|
306
|
|
- """
|
|
307
|
|
- def __init__(self,
|
|
308
|
|
- name: str,
|
|
309
|
|
- datatype,
|
|
310
|
|
- brief: str = None,
|
|
311
|
|
- description: str = None,
|
|
312
|
|
- usage: str = None,
|
|
313
|
|
- min_value = None,
|
|
314
|
|
- max_value = None,
|
|
315
|
|
- enum_values: set = None):
|
|
316
|
|
- """
|
|
317
|
|
- Params:
|
|
318
|
|
- - name Setting identifier. Must follow variable naming
|
|
319
|
|
- conventions.
|
|
320
|
|
- - datatype Datatype of the setting. E.g. int, float, str
|
|
321
|
|
- - brief Description of the setting, starting with lower case.
|
|
322
|
|
- Will be inserted into phrases like "Sets <brief>" and
|
|
323
|
|
- "Gets <brief".
|
|
324
|
|
- - description Long-form description. Min, max, and enum values will be
|
|
325
|
|
- appended to the end, so does not need to include these.
|
|
326
|
|
- - usage Description of the value argument in a set command, e.g.
|
|
327
|
|
- "<maxcount:int>"
|
|
328
|
|
- - min_value Smallest allowable value. Must be of the same datatype as
|
|
329
|
|
- the value. None for no minimum.
|
|
330
|
|
- - max_value Largest allowable value. None for no maximum.
|
|
331
|
|
- - enum_values Set of allowed values. None if unconstrained.
|
|
332
|
|
- """
|
|
333
|
|
- self.name = name
|
|
334
|
|
- self.datatype = datatype
|
|
335
|
|
- self.brief = brief
|
|
336
|
|
- self.description = description or '' # Can't be None
|
|
337
|
|
- self.usage = usage
|
|
338
|
|
- self.min_value = min_value
|
|
339
|
|
- self.max_value = max_value
|
|
340
|
|
- self.enum_values = enum_values
|
|
341
|
|
- if self.enum_values or self.min_value is not None or self.max_value is not None:
|
|
342
|
|
- self.description += '\n'
|
|
343
|
|
- if self.enum_values:
|
|
344
|
|
- allowed_values = '`' + ('`, `'.join(enum_values)) + '`'
|
|
345
|
|
- self.description += f'\nAllowed values: {allowed_values}'
|
|
346
|
|
- if self.min_value is not None:
|
|
347
|
|
- self.description += f'\nMin value: {self.min_value}'
|
|
348
|
|
- if self.max_value is not None:
|
|
349
|
|
- self.description += f'\nMax value: {self.max_value}'
|
|
350
|
|
- if self.usage is None:
|
|
351
|
|
- self.usage = f'<{self.name}>'
|
|
|
13
|
+from rocketbot.storage import Storage
|
|
|
14
|
+from rocketbot.utils import bot_log
|
|
352
|
15
|
|
|
353
|
16
|
class BaseCog(commands.Cog):
|
|
354
|
17
|
"""
|
|
|
@@ -419,212 +82,16 @@ class BaseCog(commands.Cog):
|
|
419
|
82
|
will be raised if the new value does not pass validation specified in
|
|
420
|
83
|
the CogSetting.
|
|
421
|
84
|
"""
|
|
422
|
|
- if setting.min_value is not None and new_value < setting.min_value:
|
|
423
|
|
- raise ValueError(f'{setting.name} must be at least {setting.min_value}')
|
|
424
|
|
- if setting.max_value is not None and new_value > setting.max_value:
|
|
425
|
|
- raise ValueError(f'{setting.name} must be no more than {setting.max_value}')
|
|
426
|
|
- if setting.enum_values and new_value not in setting.enum_values:
|
|
427
|
|
- raise ValueError(f'{setting.name} must be one of {setting.enum_values}')
|
|
|
85
|
+ setting.validate_value(new_value)
|
|
428
|
86
|
key = f'{cls.__name__}.{setting.name}'
|
|
429
|
87
|
Storage.set_config_value(guild, key, new_value)
|
|
430
|
88
|
|
|
431
|
89
|
@commands.Cog.listener()
|
|
432
|
90
|
async def on_ready(self):
|
|
433
|
91
|
'Event listener'
|
|
434
|
|
- self.__set_up_setting_commands()
|
|
435
|
|
-
|
|
436
|
|
- def __set_up_setting_commands(self):
|
|
437
|
|
- """
|
|
438
|
|
- Sets up commands for editing all registered cog settings. This method
|
|
439
|
|
- only runs once.
|
|
440
|
|
- """
|
|
441
|
|
- if self.are_settings_setup:
|
|
442
|
|
- return
|
|
443
|
|
- self.are_settings_setup = True
|
|
444
|
|
-
|
|
445
|
|
- # See if the cog has a command group. Currently only supporting one max.
|
|
446
|
|
- group: commands.core.Group = None
|
|
447
|
|
- for member_name in dir(self):
|
|
448
|
|
- member = getattr(self, member_name)
|
|
449
|
|
- if isinstance(member, commands.core.Group):
|
|
450
|
|
- group = member
|
|
451
|
|
- break
|
|
452
|
|
-
|
|
453
|
|
- for setting in self.settings:
|
|
454
|
|
- if setting.name == 'enabled' or setting.name == 'is_enabled':
|
|
455
|
|
- self.__make_enable_disable_commands(setting, group)
|
|
456
|
|
- else:
|
|
457
|
|
- self.__make_getter_setter_commands(setting, group)
|
|
458
|
|
-
|
|
459
|
|
- def __make_getter_setter_commands(self,
|
|
460
|
|
- setting: CogSetting,
|
|
461
|
|
- group: commands.core.Group) -> None:
|
|
462
|
|
- """
|
|
463
|
|
- Creates a "get..." and "set..." command for the given setting and
|
|
464
|
|
- either registers them as subcommands under the given command group or
|
|
465
|
|
- under the bot if `None`.
|
|
466
|
|
- """
|
|
467
|
|
- # Manually constructing equivalent of:
|
|
468
|
|
- # @commands.command()
|
|
469
|
|
- # @commands.has_permissions(ban_members=True)
|
|
470
|
|
- # @commands.guild_only()
|
|
471
|
|
- # async def getvar(self, context):
|
|
472
|
|
- async def getter(self, context):
|
|
473
|
|
- await self.__get_setting_command(context, setting)
|
|
474
|
|
- async def setter_int(self, context, new_value: int):
|
|
475
|
|
- await self.__set_setting_command(context, new_value, setting)
|
|
476
|
|
- async def setter_float(self, context, new_value: float):
|
|
477
|
|
- await self.__set_setting_command(context, new_value, setting)
|
|
478
|
|
- async def setter_str(self, context, new_value: str):
|
|
479
|
|
- await self.__set_setting_command(context, new_value, setting)
|
|
480
|
|
- async def setter_bool(self, context, new_value: bool):
|
|
481
|
|
- await self.__set_setting_command(context, new_value, setting)
|
|
482
|
|
-
|
|
483
|
|
- setter = None
|
|
484
|
|
- if setting.datatype == int:
|
|
485
|
|
- setter = setter_int
|
|
486
|
|
- elif setting.datatype == float:
|
|
487
|
|
- setter = setter_float
|
|
488
|
|
- elif setting.datatype == str:
|
|
489
|
|
- setter = setter_str
|
|
490
|
|
- elif setting.datatype == bool:
|
|
491
|
|
- setter = setter_bool
|
|
492
|
|
- else:
|
|
493
|
|
- raise RuntimeError(f'Datatype {setting.datatype} unsupported')
|
|
494
|
|
-
|
|
495
|
|
- get_command = commands.Command(
|
|
496
|
|
- getter,
|
|
497
|
|
- name=f'get{setting.name}',
|
|
498
|
|
- brief=f'Shows {setting.brief}',
|
|
499
|
|
- description=setting.description,
|
|
500
|
|
- checks=[
|
|
501
|
|
- commands.has_permissions(ban_members=True),
|
|
502
|
|
- commands.guild_only(),
|
|
503
|
|
- ])
|
|
504
|
|
- set_command = commands.Command(
|
|
505
|
|
- setter,
|
|
506
|
|
- name=f'set{setting.name}',
|
|
507
|
|
- brief=f'Sets {setting.brief}',
|
|
508
|
|
- description=setting.description,
|
|
509
|
|
- usage=setting.usage,
|
|
510
|
|
- checks=[
|
|
511
|
|
- commands.has_permissions(ban_members=True),
|
|
512
|
|
- commands.guild_only(),
|
|
513
|
|
- ])
|
|
514
|
|
-
|
|
515
|
|
- # Passing `cog` in init gets ignored and set to `None` so set after.
|
|
516
|
|
- # This ensures the callback is passed `self`.
|
|
517
|
|
- get_command.cog = self
|
|
518
|
|
- set_command.cog = self
|
|
519
|
|
-
|
|
520
|
|
- if group:
|
|
521
|
|
- group.add_command(get_command)
|
|
522
|
|
- group.add_command(set_command)
|
|
523
|
|
- else:
|
|
524
|
|
- self.bot.add_command(get_command)
|
|
525
|
|
- self.bot.add_command(set_command)
|
|
526
|
|
-
|
|
527
|
|
- def __make_enable_disable_commands(self,
|
|
528
|
|
- setting: CogSetting,
|
|
529
|
|
- group: commands.core.Group) -> None:
|
|
530
|
|
- """
|
|
531
|
|
- Creates "enable" and "disable" commands.
|
|
532
|
|
- """
|
|
533
|
|
- async def enabler(self, context):
|
|
534
|
|
- await self.__enable_command(context, setting)
|
|
535
|
|
- async def disabler(self, context):
|
|
536
|
|
- await self.__disable_command(context, setting)
|
|
537
|
|
-
|
|
538
|
|
- enable_command = commands.Command(
|
|
539
|
|
- enabler,
|
|
540
|
|
- name='enable',
|
|
541
|
|
- brief=f'Enables {setting.brief}',
|
|
542
|
|
- description=setting.description,
|
|
543
|
|
- checks=[
|
|
544
|
|
- commands.has_permissions(ban_members=True),
|
|
545
|
|
- commands.guild_only(),
|
|
546
|
|
- ])
|
|
547
|
|
- disable_command = commands.Command(
|
|
548
|
|
- disabler,
|
|
549
|
|
- name='disable',
|
|
550
|
|
- brief=f'Disables {setting.brief}',
|
|
551
|
|
- description=setting.description,
|
|
552
|
|
- checks=[
|
|
553
|
|
- commands.has_permissions(ban_members=True),
|
|
554
|
|
- commands.guild_only(),
|
|
555
|
|
- ])
|
|
556
|
|
-
|
|
557
|
|
- enable_command.cog = self
|
|
558
|
|
- disable_command.cog = self
|
|
559
|
|
-
|
|
560
|
|
- if group:
|
|
561
|
|
- group.add_command(enable_command)
|
|
562
|
|
- group.add_command(disable_command)
|
|
563
|
|
- else:
|
|
564
|
|
- self.bot.add_command(enable_command)
|
|
565
|
|
- self.bot.add_command(disable_command)
|
|
566
|
|
-
|
|
567
|
|
- async def __set_setting_command(self, context, new_value, setting) -> None:
|
|
568
|
|
- setting_name = setting.name
|
|
569
|
|
- if context.command.parent:
|
|
570
|
|
- setting_name = f'{context.command.parent.name}.{setting_name}'
|
|
571
|
|
- if setting.min_value is not None and new_value < setting.min_value:
|
|
572
|
|
- await context.message.reply(
|
|
573
|
|
- f'{CONFIG["failure_emoji"]} `{setting_name}` must be >= {setting.min_value}',
|
|
574
|
|
- mention_author=False)
|
|
575
|
|
- return
|
|
576
|
|
- if setting.max_value is not None and new_value > setting.max_value:
|
|
577
|
|
- await context.message.reply(
|
|
578
|
|
- f'{CONFIG["failure_emoji"]} `{setting_name}` must be <= {setting.max_value}',
|
|
579
|
|
- mention_author=False)
|
|
580
|
|
- return
|
|
581
|
|
- if setting.enum_values is not None and new_value not in setting.enum_values:
|
|
582
|
|
- allowed_values = '`' + ('`, `'.join(setting.enum_values)) + '`'
|
|
583
|
|
- await context.message.reply(
|
|
584
|
|
- f'{CONFIG["failure_emoji"]} `{setting_name}` must be one of {allowed_values}',
|
|
585
|
|
- mention_author=False)
|
|
586
|
|
- return
|
|
587
|
|
- key = f'{self.__class__.__name__}.{setting.name}'
|
|
588
|
|
- Storage.set_config_value(context.guild, key, new_value)
|
|
589
|
|
- await context.message.reply(
|
|
590
|
|
- f'{CONFIG["success_emoji"]} `{setting_name}` is now set to `{new_value}`',
|
|
591
|
|
- mention_author=False)
|
|
592
|
|
- await self.on_setting_updated(context.guild, setting)
|
|
593
|
|
- self.log(context.guild, f'{context.author.name} set {key} to {new_value}')
|
|
594
|
|
-
|
|
595
|
|
- async def __get_setting_command(self, context, setting) -> None:
|
|
596
|
|
- setting_name = setting.name
|
|
597
|
|
- if context.command.parent:
|
|
598
|
|
- setting_name = f'{context.command.parent.name}.{setting_name}'
|
|
599
|
|
- key = f'{self.__class__.__name__}.{setting.name}'
|
|
600
|
|
- value = Storage.get_config_value(context.guild, key)
|
|
601
|
|
- if value is None:
|
|
602
|
|
- value = self.get_cog_default(setting.name)
|
|
603
|
|
- await context.message.reply(
|
|
604
|
|
- f'{CONFIG["info_emoji"]} `{setting_name}` is using default of `{value}`',
|
|
605
|
|
- mention_author=False)
|
|
606
|
|
- else:
|
|
607
|
|
- await context.message.reply(
|
|
608
|
|
- f'{CONFIG["info_emoji"]} `{setting_name}` is set to `{value}`',
|
|
609
|
|
- mention_author=False)
|
|
610
|
|
-
|
|
611
|
|
- async def __enable_command(self, context, setting) -> None:
|
|
612
|
|
- key = f'{self.__class__.__name__}.{setting.name}'
|
|
613
|
|
- Storage.set_config_value(context.guild, key, True)
|
|
614
|
|
- await context.message.reply(
|
|
615
|
|
- f'{CONFIG["success_emoji"]} {setting.brief.capitalize()} enabled.',
|
|
616
|
|
- mention_author=False)
|
|
617
|
|
- await self.on_setting_updated(context.guild, setting)
|
|
618
|
|
- self.log(context.guild, f'{context.author.name} enabled {self.__class__.__name__}')
|
|
619
|
|
-
|
|
620
|
|
- async def __disable_command(self, context, setting) -> None:
|
|
621
|
|
- key = f'{self.__class__.__name__}.{setting.name}'
|
|
622
|
|
- Storage.set_config_value(context.guild, key, False)
|
|
623
|
|
- await context.message.reply(
|
|
624
|
|
- f'{CONFIG["success_emoji"]} {setting.brief.capitalize()} disabled.',
|
|
625
|
|
- mention_author=False)
|
|
626
|
|
- await self.on_setting_updated(context.guild, setting)
|
|
627
|
|
- self.log(context.guild, f'{context.author.name} disabled {self.__class__.__name__}')
|
|
|
92
|
+ if not self.are_settings_setup:
|
|
|
93
|
+ self.are_settings_setup = True
|
|
|
94
|
+ CogSetting.set_up_all(self, self.bot, self.settings)
|
|
628
|
95
|
|
|
629
|
96
|
async def on_setting_updated(self, guild: Guild, setting: CogSetting) -> None:
|
|
630
|
97
|
"""
|
|
|
@@ -713,5 +180,4 @@ class BaseCog(commands.Cog):
|
|
713
|
180
|
"""
|
|
714
|
181
|
Writes a message to the console. Intended for significant events only.
|
|
715
|
182
|
"""
|
|
716
|
|
- now = datetime.now()
|
|
717
|
|
- print(f'[{now.strftime("%Y-%m-%dT%H:%M:%S")}|{cls.__name__}|{guild.name}] {message}')
|
|
|
183
|
+ bot_log(guild, cls, message)
|