|
|
@@ -1,170 +1,340 @@
|
|
1
|
1
|
import re
|
|
2
|
2
|
from typing import Optional, TypedDict
|
|
3
|
3
|
|
|
4
|
|
-from discord import Interaction, Guild, Message, TextChannel
|
|
5
|
|
-from discord.app_commands import Group
|
|
|
4
|
+from discord import Interaction, Guild, Message, TextChannel, SelectOption, TextStyle
|
|
|
5
|
+from discord.app_commands import Group, Choice, autocomplete
|
|
6
|
6
|
from discord.ext.commands import Cog
|
|
|
7
|
+from discord.ui import Modal, Label, Select, TextInput
|
|
7
|
8
|
|
|
8
|
9
|
from config import CONFIG
|
|
9
|
10
|
from rocketbot.bot import Rocketbot
|
|
10
|
11
|
from rocketbot.cogs.basecog import BaseCog
|
|
11
|
12
|
from rocketbot.cogsetting import CogSetting
|
|
12
|
13
|
from rocketbot.ui.pagedcontent import PAGE_BREAK, update_paged_content, paginate
|
|
13
|
|
-from rocketbot.utils import MOD_PERMISSIONS, blockquote_markdown, indent_markdown
|
|
|
14
|
+from rocketbot.utils import MOD_PERMISSIONS, blockquote_markdown, indent_markdown, dump_stacktrace
|
|
14
|
15
|
|
|
15
|
16
|
_CURRENT_DATA_VERSION = 1
|
|
|
17
|
+_MAX_CONTENT_LENGTH = 2000
|
|
16
|
18
|
|
|
17
|
19
|
class BangCommand(TypedDict):
|
|
18
|
|
- content: str
|
|
19
|
|
- mod_only: bool
|
|
20
|
|
- version: int
|
|
|
20
|
+ content: str
|
|
|
21
|
+ mod_only: bool
|
|
|
22
|
+ version: int
|
|
|
23
|
+
|
|
|
24
|
+async def command_autocomplete(interaction: Interaction, text: str) -> list[Choice[str]]:
|
|
|
25
|
+ cmds = BangCommandCog.shared.get_saved_commands(interaction.guild)
|
|
|
26
|
+ return [
|
|
|
27
|
+ Choice(name=f'!{name}', value=name)
|
|
|
28
|
+ for name, cmd in sorted(cmds.items())
|
|
|
29
|
+ if len(text) == 0 or text.lower() in name
|
|
|
30
|
+ ]
|
|
21
|
31
|
|
|
22
|
32
|
class BangCommandCog(BaseCog, name='Bang Commands'):
|
|
23
|
|
- SETTING_COMMANDS = CogSetting(
|
|
24
|
|
- name='commands',
|
|
25
|
|
- datatype=dict[str, BangCommand],
|
|
26
|
|
- default_value={},
|
|
27
|
|
- )
|
|
28
|
|
-
|
|
29
|
|
- def __init__(self, bot: Rocketbot):
|
|
30
|
|
- super().__init__(
|
|
31
|
|
- bot,
|
|
32
|
|
- config_prefix='bangcommand',
|
|
33
|
|
- short_description='Provides custom informational chat !commands.',
|
|
34
|
|
- long_description='Bang commands are simple one-word messages starting with an exclamation '
|
|
35
|
|
- '(bang) that will make the bot respond with simple informational replies. '
|
|
36
|
|
- 'Useful for posting answers to frequently asked questions, reminding users '
|
|
37
|
|
- 'of rules, and similar.'
|
|
38
|
|
- )
|
|
39
|
|
-
|
|
40
|
|
- def _get_commands(self, guild: Guild) -> dict[str, BangCommand]:
|
|
41
|
|
- return self.get_guild_setting(guild, BangCommandCog.SETTING_COMMANDS)
|
|
42
|
|
-
|
|
43
|
|
- def _set_commands(self, guild: Guild, commands: dict[str, BangCommand]) -> None:
|
|
44
|
|
- self.set_guild_setting(guild, BangCommandCog.SETTING_COMMANDS, commands)
|
|
45
|
|
-
|
|
46
|
|
- bang = Group(
|
|
47
|
|
- name='bangcommand',
|
|
48
|
|
- description='Provides custom informational chat !commands.',
|
|
49
|
|
- guild_only=True,
|
|
50
|
|
- default_permissions=MOD_PERMISSIONS,
|
|
51
|
|
- )
|
|
52
|
|
-
|
|
53
|
|
- @bang.command()
|
|
54
|
|
- async def define(self, interaction: Interaction, name: str, definition: str, mod_only: bool = False) -> None:
|
|
55
|
|
- """
|
|
56
|
|
- Defines or redefines a bang command.
|
|
57
|
|
-
|
|
58
|
|
- Parameters
|
|
59
|
|
- ----------
|
|
60
|
|
- interaction: Interaction
|
|
61
|
|
- name: string
|
|
62
|
|
- name of the command (lowercase a-z, underscores, and hyphens)
|
|
63
|
|
- definition: string
|
|
64
|
|
- content of the command
|
|
65
|
|
- mod_only: bool
|
|
66
|
|
- whether the command will only be recognized when a mod uses it
|
|
67
|
|
- """
|
|
68
|
|
- if not BangCommandCog._is_valid_name(name):
|
|
69
|
|
- self.log(interaction.guild, f'{interaction.user.name} used command /bangcommand define {name} {definition}')
|
|
70
|
|
- await interaction.response.send_message(
|
|
71
|
|
- f'{CONFIG["failure_emoji"]} Invalid command name. Names must consist of lowercase letters, underscores, and hyphens (no spaces).',
|
|
72
|
|
- ephemeral=True,
|
|
73
|
|
- )
|
|
74
|
|
- return
|
|
75
|
|
- name = BangCommandCog._normalize_name(name)
|
|
76
|
|
- cmds = self._get_commands(interaction.guild)
|
|
77
|
|
- cmds[name] = {
|
|
78
|
|
- 'content': definition,
|
|
79
|
|
- 'mod_only': mod_only,
|
|
80
|
|
- 'version': _CURRENT_DATA_VERSION,
|
|
81
|
|
- }
|
|
82
|
|
- self._set_commands(interaction.guild, cmds)
|
|
83
|
|
- await interaction.response.send_message(
|
|
84
|
|
- f'{CONFIG["success_emoji"]} Command `!{name}` has been defined.\n\n{blockquote_markdown(definition)}',
|
|
85
|
|
- ephemeral=True,
|
|
86
|
|
- )
|
|
87
|
|
-
|
|
88
|
|
- @bang.command()
|
|
89
|
|
- async def undefine(self, interaction: Interaction, name: str) -> None:
|
|
90
|
|
- """
|
|
91
|
|
- Removes a bang command.
|
|
92
|
|
-
|
|
93
|
|
- Parameters
|
|
94
|
|
- ----------
|
|
95
|
|
- interaction: Interaction
|
|
96
|
|
- name: string
|
|
97
|
|
- name of the previously defined command
|
|
98
|
|
- """
|
|
99
|
|
- name = BangCommandCog._normalize_name(name)
|
|
100
|
|
- cmds = self._get_commands(interaction.guild)
|
|
101
|
|
- if name not in cmds:
|
|
102
|
|
- await interaction.response.send_message(
|
|
103
|
|
- f'{CONFIG["failure_emoji"]} Command `!{name}` does not exist.',
|
|
104
|
|
- ephemeral=True,
|
|
105
|
|
- )
|
|
106
|
|
- return
|
|
107
|
|
- del cmds[name]
|
|
108
|
|
- self._set_commands(interaction.guild, cmds)
|
|
109
|
|
- await interaction.response.send_message(
|
|
110
|
|
- f'{CONFIG["success_emoji"]} Command `!{name}` removed.',
|
|
111
|
|
- ephemeral=True,
|
|
112
|
|
- )
|
|
113
|
|
-
|
|
114
|
|
- @bang.command()
|
|
115
|
|
- async def list(self, interaction: Interaction) -> None:
|
|
116
|
|
- """
|
|
117
|
|
- Lists all defined bang commands.
|
|
118
|
|
-
|
|
119
|
|
- Parameters
|
|
120
|
|
- ----------
|
|
121
|
|
- interaction: Interaction
|
|
122
|
|
- """
|
|
123
|
|
- cmds = self._get_commands(interaction.guild)
|
|
124
|
|
- if cmds is None or len(cmds) == 0:
|
|
125
|
|
- await interaction.response.send_message(
|
|
126
|
|
- f'{CONFIG["info_emoji"]} No commands defined.',
|
|
127
|
|
- ephemeral=True,
|
|
128
|
|
- )
|
|
129
|
|
- return
|
|
130
|
|
- text = '## Commands'
|
|
131
|
|
- for name, cmd in sorted(cmds.items()):
|
|
132
|
|
- text += PAGE_BREAK + f'\n- `!{name}`'
|
|
133
|
|
- if cmd['mod_only']:
|
|
134
|
|
- text += ' - **mod only**'
|
|
135
|
|
- text += f'\n{indent_markdown(cmd["content"])}'
|
|
136
|
|
- pages = paginate(text)
|
|
137
|
|
- await update_paged_content(interaction, None, 0, pages)
|
|
138
|
|
-
|
|
139
|
|
- @Cog.listener()
|
|
140
|
|
- async def on_message(self, message: Message) -> None:
|
|
141
|
|
- if message.guild is None or message.channel is None or not isinstance(message.channel, TextChannel):
|
|
142
|
|
- return
|
|
143
|
|
- content = message.content
|
|
144
|
|
- if content is None or not content.startswith('!') or not BangCommandCog._is_valid_name(content):
|
|
145
|
|
- return
|
|
146
|
|
- name = BangCommandCog._normalize_name(content)
|
|
147
|
|
- cmds = self._get_commands(message.guild)
|
|
148
|
|
- cmd = cmds.get(name, None)
|
|
149
|
|
- if cmd is None:
|
|
150
|
|
- return
|
|
151
|
|
- if cmd['mod_only'] and not message.author.guild_permissions.ban_members:
|
|
152
|
|
- return
|
|
153
|
|
- text = cmd["content"]
|
|
154
|
|
- # text = f'{text}\n\n-# {message.author.name} used `!{name}`'
|
|
155
|
|
- await message.channel.send(
|
|
156
|
|
- text,
|
|
157
|
|
- )
|
|
158
|
|
-
|
|
159
|
|
- @staticmethod
|
|
160
|
|
- def _normalize_name(name: str) -> str:
|
|
161
|
|
- name = name.lower().strip()
|
|
162
|
|
- if name.startswith('!'):
|
|
163
|
|
- name = name[1:]
|
|
164
|
|
- return name
|
|
165
|
|
-
|
|
166
|
|
- @staticmethod
|
|
167
|
|
- def _is_valid_name(name: Optional[str]) -> bool:
|
|
168
|
|
- if name is None:
|
|
169
|
|
- return False
|
|
170
|
|
- return re.match(r'^!?([a-z]+)([_-][a-z]+)*$', name) is not None
|
|
|
33
|
+ SETTING_COMMANDS = CogSetting(
|
|
|
34
|
+ name='commands',
|
|
|
35
|
+ datatype=dict[str, BangCommand],
|
|
|
36
|
+ default_value={},
|
|
|
37
|
+ )
|
|
|
38
|
+
|
|
|
39
|
+ shared: Optional['BangCommandCog'] = None
|
|
|
40
|
+
|
|
|
41
|
+ def __init__(self, bot: Rocketbot):
|
|
|
42
|
+ super().__init__(
|
|
|
43
|
+ bot,
|
|
|
44
|
+ config_prefix='bangcommand',
|
|
|
45
|
+ short_description='Provides custom informational chat !commands.',
|
|
|
46
|
+ long_description='Bang commands are simple one-word messages starting with an exclamation '
|
|
|
47
|
+ '(bang) that will make the bot respond with simple informational replies. '
|
|
|
48
|
+ 'Useful for posting answers to frequently asked questions, reminding users '
|
|
|
49
|
+ 'of rules, and similar.'
|
|
|
50
|
+ )
|
|
|
51
|
+ BangCommandCog.shared = self
|
|
|
52
|
+
|
|
|
53
|
+ def get_saved_commands(self, guild: Guild) -> dict[str, BangCommand]:
|
|
|
54
|
+ return self.get_guild_setting(guild, BangCommandCog.SETTING_COMMANDS)
|
|
|
55
|
+
|
|
|
56
|
+ def get_saved_command(self, guild: Guild, name: str) -> Optional[BangCommand]:
|
|
|
57
|
+ cmds = self.get_saved_commands(guild)
|
|
|
58
|
+ name = BangCommandCog._normalize_name(name)
|
|
|
59
|
+ return cmds.get(name, None)
|
|
|
60
|
+
|
|
|
61
|
+ def set_saved_commands(self, guild: Guild, commands: dict[str, BangCommand]) -> None:
|
|
|
62
|
+ self.set_guild_setting(guild, BangCommandCog.SETTING_COMMANDS, commands)
|
|
|
63
|
+
|
|
|
64
|
+ bang = Group(
|
|
|
65
|
+ name='command',
|
|
|
66
|
+ description='Provides custom informational chat !commands.',
|
|
|
67
|
+ guild_only=True,
|
|
|
68
|
+ default_permissions=MOD_PERMISSIONS,
|
|
|
69
|
+ )
|
|
|
70
|
+
|
|
|
71
|
+ @bang.command(
|
|
|
72
|
+ name='define',
|
|
|
73
|
+ extras={
|
|
|
74
|
+ 'long_description': 'Simple one-line content can be specified in the command. '
|
|
|
75
|
+ 'For multi-line content, run the command without content '
|
|
|
76
|
+ 'specified to use the editor popup.'
|
|
|
77
|
+ }
|
|
|
78
|
+ )
|
|
|
79
|
+ @autocomplete(name=command_autocomplete)
|
|
|
80
|
+ async def define_command(self, interaction: Interaction, name: str, definition: Optional[str] = None, mod_only: bool = False) -> None:
|
|
|
81
|
+ """
|
|
|
82
|
+ Defines or redefines a bang command.
|
|
|
83
|
+
|
|
|
84
|
+ Parameters
|
|
|
85
|
+ ----------
|
|
|
86
|
+ interaction: Interaction
|
|
|
87
|
+ name: string
|
|
|
88
|
+ name of the command (lowercase a-z, underscores, and hyphens)
|
|
|
89
|
+ definition: string
|
|
|
90
|
+ content of the command
|
|
|
91
|
+ mod_only: bool
|
|
|
92
|
+ whether the command will only be recognized when a mod uses it
|
|
|
93
|
+ """
|
|
|
94
|
+ self.log(interaction.guild, f'{interaction.user.name} used command /bangcommand define {name} {definition} {mod_only}')
|
|
|
95
|
+ name = BangCommandCog._normalize_name(name)
|
|
|
96
|
+ if definition is None:
|
|
|
97
|
+ cmd = self.get_saved_command(interaction.guild, name)
|
|
|
98
|
+ await interaction.response.send_modal(
|
|
|
99
|
+ _EditModal(
|
|
|
100
|
+ name,
|
|
|
101
|
+ content=cmd['content'] if cmd else None,
|
|
|
102
|
+ mod_only=cmd['mod_only'] if cmd else None,
|
|
|
103
|
+ exists=cmd is not None,
|
|
|
104
|
+ )
|
|
|
105
|
+ )
|
|
|
106
|
+ return
|
|
|
107
|
+ try:
|
|
|
108
|
+ self.define(interaction.guild, name, definition, mod_only)
|
|
|
109
|
+ await interaction.response.send_message(
|
|
|
110
|
+ f'{CONFIG["success_emoji"]} Command `!{name}` has been defined.\n\n{blockquote_markdown(definition)}',
|
|
|
111
|
+ ephemeral=True,
|
|
|
112
|
+ )
|
|
|
113
|
+ except ValueError as e:
|
|
|
114
|
+ await interaction.response.send_message(
|
|
|
115
|
+ f'{CONFIG["failure_emoji"]} {e}',
|
|
|
116
|
+ ephemeral=True,
|
|
|
117
|
+ )
|
|
|
118
|
+ return
|
|
|
119
|
+
|
|
|
120
|
+ @bang.command(
|
|
|
121
|
+ name='undefine'
|
|
|
122
|
+ )
|
|
|
123
|
+ @autocomplete(name=command_autocomplete)
|
|
|
124
|
+ async def undefine_command(self, interaction: Interaction, name: str) -> None:
|
|
|
125
|
+ """
|
|
|
126
|
+ Removes a bang command.
|
|
|
127
|
+
|
|
|
128
|
+ Parameters
|
|
|
129
|
+ ----------
|
|
|
130
|
+ interaction: Interaction
|
|
|
131
|
+ name: string
|
|
|
132
|
+ name of the previously defined command
|
|
|
133
|
+ """
|
|
|
134
|
+ try:
|
|
|
135
|
+ self.undefine(interaction.guild, name)
|
|
|
136
|
+ await interaction.response.send_message(
|
|
|
137
|
+ f'{CONFIG["success_emoji"]} Command `!{name}` removed.',
|
|
|
138
|
+ ephemeral=True,
|
|
|
139
|
+ )
|
|
|
140
|
+ except ValueError as e:
|
|
|
141
|
+ await interaction.response.send_message(
|
|
|
142
|
+ f'{CONFIG["failure_emoji"]} {e}',
|
|
|
143
|
+ ephemeral=True,
|
|
|
144
|
+ )
|
|
|
145
|
+
|
|
|
146
|
+ @bang.command(
|
|
|
147
|
+ name='list'
|
|
|
148
|
+ )
|
|
|
149
|
+ async def list_command(self, interaction: Interaction) -> None:
|
|
|
150
|
+ """
|
|
|
151
|
+ Lists all defined bang commands.
|
|
|
152
|
+
|
|
|
153
|
+ Parameters
|
|
|
154
|
+ ----------
|
|
|
155
|
+ interaction: Interaction
|
|
|
156
|
+ """
|
|
|
157
|
+ cmds = self.get_saved_commands(interaction.guild)
|
|
|
158
|
+ if cmds is None or len(cmds) == 0:
|
|
|
159
|
+ await interaction.response.send_message(
|
|
|
160
|
+ f'{CONFIG["info_emoji"]} No commands defined.',
|
|
|
161
|
+ ephemeral=True,
|
|
|
162
|
+ delete_after=15,
|
|
|
163
|
+ )
|
|
|
164
|
+ return
|
|
|
165
|
+ text = '## Commands'
|
|
|
166
|
+ for name, cmd in sorted(cmds.items()):
|
|
|
167
|
+ text += PAGE_BREAK + f'\n- `!{name}`'
|
|
|
168
|
+ if cmd['mod_only']:
|
|
|
169
|
+ text += ' - **mod only**'
|
|
|
170
|
+ text += f'\n{indent_markdown(cmd["content"])}'
|
|
|
171
|
+ pages = paginate(text)
|
|
|
172
|
+ await update_paged_content(interaction, None, 0, pages)
|
|
|
173
|
+
|
|
|
174
|
+ @bang.command(
|
|
|
175
|
+ name='invoke',
|
|
|
176
|
+ extras={
|
|
|
177
|
+ 'long_description': 'Useful when you do not want the command to show up in chat.',
|
|
|
178
|
+ }
|
|
|
179
|
+ )
|
|
|
180
|
+ @autocomplete(name=command_autocomplete)
|
|
|
181
|
+ async def invoke_command(self, interaction: Interaction, name: str) -> None:
|
|
|
182
|
+ """
|
|
|
183
|
+ Invokes a bang command without typing it in chat.
|
|
|
184
|
+
|
|
|
185
|
+ Parameters
|
|
|
186
|
+ ----------
|
|
|
187
|
+ interaction: Interaction
|
|
|
188
|
+ name: string
|
|
|
189
|
+ the bang command name
|
|
|
190
|
+ """
|
|
|
191
|
+ cmd = self.get_saved_command(interaction.guild, name)
|
|
|
192
|
+ if cmd is None:
|
|
|
193
|
+ await interaction.response.send_message(
|
|
|
194
|
+ f'{CONFIG["failure_emoji"]} Command `!{name}` does not exist.',
|
|
|
195
|
+ ephemeral=True,
|
|
|
196
|
+ )
|
|
|
197
|
+ return
|
|
|
198
|
+ resp = await interaction.response.defer(ephemeral=True, thinking=False)
|
|
|
199
|
+ await interaction.channel.send(
|
|
|
200
|
+ cmd['content']
|
|
|
201
|
+ )
|
|
|
202
|
+ if resp.resource:
|
|
|
203
|
+ await resp.resource.delete()
|
|
|
204
|
+
|
|
|
205
|
+ def define(self, guild: Guild, name: str, content: str, mod_only: bool, check_exists: bool = False) -> None:
|
|
|
206
|
+ if not BangCommandCog._is_valid_name(name):
|
|
|
207
|
+ raise ValueError('Invalid command name. Must consist of lowercase letters, underscores, and hyphens (no spaces).')
|
|
|
208
|
+ name = BangCommandCog._normalize_name(name)
|
|
|
209
|
+ if len(content) < 1 or len(content) > 2000:
|
|
|
210
|
+ raise ValueError(f'Content must be between 1 and {_MAX_CONTENT_LENGTH} characters.')
|
|
|
211
|
+ cmds = self.get_saved_commands(guild)
|
|
|
212
|
+ if check_exists:
|
|
|
213
|
+ if cmds.get(name, None) is not None:
|
|
|
214
|
+ raise ValueError(f'Command with name "{name}" already exists.')
|
|
|
215
|
+ cmds[name] = {
|
|
|
216
|
+ 'content': content,
|
|
|
217
|
+ 'mod_only': mod_only,
|
|
|
218
|
+ 'version': _CURRENT_DATA_VERSION,
|
|
|
219
|
+ }
|
|
|
220
|
+ self.set_saved_commands(guild, cmds)
|
|
|
221
|
+
|
|
|
222
|
+ def undefine(self, guild: Guild, name: str) -> None:
|
|
|
223
|
+ name = BangCommandCog._normalize_name(name)
|
|
|
224
|
+ cmds = self.get_saved_commands(guild)
|
|
|
225
|
+ if cmds.get(name, None) is None:
|
|
|
226
|
+ raise ValueError(f'Command with name "{name}" does not exist.')
|
|
|
227
|
+ del cmds[name]
|
|
|
228
|
+ self.set_saved_commands(guild, cmds)
|
|
|
229
|
+
|
|
|
230
|
+ @Cog.listener()
|
|
|
231
|
+ async def on_message(self, message: Message) -> None:
|
|
|
232
|
+ if message.guild is None or message.channel is None or not isinstance(message.channel, TextChannel):
|
|
|
233
|
+ return
|
|
|
234
|
+ content = message.content
|
|
|
235
|
+ if content is None or not content.startswith('!') or not BangCommandCog._is_valid_name(content):
|
|
|
236
|
+ return
|
|
|
237
|
+ name = BangCommandCog._normalize_name(content)
|
|
|
238
|
+ cmd = self.get_saved_command(message.guild, name)
|
|
|
239
|
+ if cmd is None:
|
|
|
240
|
+ return
|
|
|
241
|
+ if cmd['mod_only'] and not message.author.guild_permissions.ban_members:
|
|
|
242
|
+ return
|
|
|
243
|
+ text = cmd["content"]
|
|
|
244
|
+ # text = f'{text}\n\n-# {message.author.name} used `!{name}`'
|
|
|
245
|
+ await message.channel.send(
|
|
|
246
|
+ text,
|
|
|
247
|
+ )
|
|
|
248
|
+
|
|
|
249
|
+ @staticmethod
|
|
|
250
|
+ def _normalize_name(name: str) -> str:
|
|
|
251
|
+ name = name.lower().strip()
|
|
|
252
|
+ if name.startswith('!'):
|
|
|
253
|
+ name = name[1:]
|
|
|
254
|
+ return name
|
|
|
255
|
+
|
|
|
256
|
+ @staticmethod
|
|
|
257
|
+ def _is_valid_name(name: Optional[str]) -> bool:
|
|
|
258
|
+ if name is None:
|
|
|
259
|
+ return False
|
|
|
260
|
+ return re.match(r'^!?([a-z]+)([_-][a-z]+)*$', name) is not None
|
|
|
261
|
+
|
|
|
262
|
+class _EditModal(Modal, title='Edit Command'):
|
|
|
263
|
+ name_label = Label(
|
|
|
264
|
+ text='Command name',
|
|
|
265
|
+ description='What gets typed in chat to trigger the command. Must be a-z, underscores, and hyphens (no spaces).',
|
|
|
266
|
+ component=TextInput(
|
|
|
267
|
+ style=TextStyle.short, # one line
|
|
|
268
|
+ placeholder='!command_name',
|
|
|
269
|
+ min_length=1,
|
|
|
270
|
+ max_length=100,
|
|
|
271
|
+ )
|
|
|
272
|
+ )
|
|
|
273
|
+ content_label = Label(
|
|
|
274
|
+ text='Content',
|
|
|
275
|
+ description='The text the bot will respond with when someone uses the command. Can contain markdown.',
|
|
|
276
|
+ component=TextInput(
|
|
|
277
|
+ style=TextStyle.paragraph,
|
|
|
278
|
+ placeholder='Lorem ipsum dolor...',
|
|
|
279
|
+ min_length=1,
|
|
|
280
|
+ max_length=2000,
|
|
|
281
|
+ )
|
|
|
282
|
+ )
|
|
|
283
|
+ mod_only_label = Label(
|
|
|
284
|
+ text='Mod only?',
|
|
|
285
|
+ description='Whether mods are the only users who can invoke this command.',
|
|
|
286
|
+ component=Select(
|
|
|
287
|
+ options=[
|
|
|
288
|
+ SelectOption(label='No', value='False',
|
|
|
289
|
+ description='Anyone can invoke this command.'),
|
|
|
290
|
+ SelectOption(label='Yes', value='True',
|
|
|
291
|
+ description='Only mods can invoke this command.'),
|
|
|
292
|
+ ],
|
|
|
293
|
+ )
|
|
|
294
|
+ )
|
|
|
295
|
+
|
|
|
296
|
+ def __init__(self, name: Optional[str] = None, content: Optional[str] = None, mod_only: Optional[bool] = None, exists: bool = False):
|
|
|
297
|
+ super().__init__()
|
|
|
298
|
+ self.exists = exists
|
|
|
299
|
+ # noinspection PyTypeChecker
|
|
|
300
|
+ name_input: TextInput = self.name_label.component
|
|
|
301
|
+ # noinspection PyTypeChecker
|
|
|
302
|
+ content_input: TextInput = self.content_label.component
|
|
|
303
|
+ # noinspection PyTypeChecker
|
|
|
304
|
+ mod_only_input: Select = self.mod_only_label.component
|
|
|
305
|
+ name_input.default = name
|
|
|
306
|
+ content_input.default = content
|
|
|
307
|
+ mod_only_input.options[0].default = mod_only != True
|
|
|
308
|
+ mod_only_input.options[1].default = mod_only == True
|
|
|
309
|
+
|
|
|
310
|
+ async def on_submit(self, interaction: Interaction) -> None:
|
|
|
311
|
+ # noinspection PyTypeChecker
|
|
|
312
|
+ name_input: TextInput = self.name_label.component
|
|
|
313
|
+ # noinspection PyTypeChecker
|
|
|
314
|
+ content_input: TextInput = self.content_label.component
|
|
|
315
|
+ # noinspection PyTypeChecker
|
|
|
316
|
+ mod_only_input: Select = self.mod_only_label.component
|
|
|
317
|
+ name = name_input.value
|
|
|
318
|
+ content = content_input.value
|
|
|
319
|
+ mod_only = mod_only_input.values[0] == 'True'
|
|
|
320
|
+ try:
|
|
|
321
|
+ BangCommandCog.shared.define(interaction.guild, name, content, mod_only, not self.exists)
|
|
|
322
|
+ await interaction.response.send_message(
|
|
|
323
|
+ f'{CONFIG["success_emoji"]} Command `!{name}` has been defined.\n\n{blockquote_markdown(content)}',
|
|
|
324
|
+ ephemeral=True,
|
|
|
325
|
+ )
|
|
|
326
|
+ except ValueError as e:
|
|
|
327
|
+ await interaction.response.send_message(
|
|
|
328
|
+ f'{CONFIG["failure_emoji"]} {e}',
|
|
|
329
|
+ ephemeral=True,
|
|
|
330
|
+ )
|
|
|
331
|
+
|
|
|
332
|
+ async def on_error(self, interaction: Interaction, error: Exception) -> None:
|
|
|
333
|
+ dump_stacktrace(error)
|
|
|
334
|
+ try:
|
|
|
335
|
+ await interaction.response.send_message(
|
|
|
336
|
+ f'{CONFIG["failure_emoji"]} Save failed',
|
|
|
337
|
+ ephemeral=True,
|
|
|
338
|
+ )
|
|
|
339
|
+ except:
|
|
|
340
|
+ pass
|