-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Expand file tree
/
Copy path_cli.py
More file actions
365 lines (343 loc) · 12.4 KB
/
_cli.py
File metadata and controls
365 lines (343 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
import argparse
import asyncio
import logging
import sys
from enum import IntEnum
from typing import Optional
import discord
from discord import __version__ as discord_version
from redbot.core.utils._internal_utils import cli_level_to_log_level
# This needs to be an int enum to be used
# with sys.exit
class ExitCodes(IntEnum):
#: Clean shutdown (through signals, keyboard interrupt, [p]shutdown, etc.).
SHUTDOWN = 0
#: An unrecoverable error occurred during application's runtime.
CRITICAL = 1
#: The CLI command was used incorrectly, such as when the wrong number of arguments are given.
INVALID_CLI_USAGE = 2
#: Restart was requested by the bot owner (probably through [p]restart command).
RESTART = 26
#: Some kind of configuration error occurred.
CONFIGURATION_ERROR = 78 # Exit code borrowed from os.EX_CONFIG.
def confirm(text: str, default: Optional[bool] = None) -> bool:
if default is None:
options = "y/n"
elif default is True:
options = "Y/n"
elif default is False:
options = "y/N"
else:
raise TypeError(f"expected bool, not {type(default)}")
while True:
try:
value = input(f"{text}: [{options}] ").lower().strip()
except KeyboardInterrupt:
print("\nAborted!")
sys.exit(ExitCodes.SHUTDOWN)
except EOFError:
print("\nAborted!")
sys.exit(ExitCodes.INVALID_CLI_USAGE)
if value in ("y", "yes"):
return True
if value in ("n", "no"):
return False
if value == "":
if default is not None:
return default
print("Error: invalid input")
async def interactive_config(red, token_set, prefix_set, *, print_header=True):
token = None
if print_header:
print("Red - Discord Bot | Configuration process\n")
if not token_set:
print(
"Please enter a valid token.\n"
"You can find out how to obtain a token with this guide:\n"
"https://docs.discord.red/en/stable/bot_application_guide.html#creating-a-bot-account"
)
while not token:
token = input("> ")
if not len(token) >= 50:
print("That doesn't look like a valid token.")
token = None
if token:
await red._config.token.set(token)
if not prefix_set:
prefix = ""
print(
"\nPick a prefix. A prefix is what you type before a "
"command. Example:\n"
"!help\n^ The exclamation mark (!) is the prefix in this case.\n"
"The prefix can be multiple characters. You will be able to change it "
"later and add more of them.\nChoose your prefix:\n"
)
while not prefix:
prefix = input("Prefix> ")
if len(prefix) > 10:
if not confirm("Your prefix seems overly long. Are you sure that it's correct?"):
prefix = ""
if prefix.startswith("/"):
print(
"Prefixes cannot start with '/', as it conflicts with Discord's slash commands."
)
prefix = ""
if prefix and not confirm(
f'You chose "{prefix}" as your prefix. To run the help command,'
f" you will have to send:\n{prefix}help\n\n"
"Do you want to continue with this prefix?"
):
prefix = ""
if prefix:
await red._config.prefix.set([prefix])
return token
def non_negative_int(arg: str) -> int:
try:
x = int(arg)
except ValueError:
raise argparse.ArgumentTypeError("The argument has to be a number.")
if x < 0:
raise argparse.ArgumentTypeError("The argument has to be a non-negative integer.")
if x > sys.maxsize:
raise argparse.ArgumentTypeError(
f"The argument has to be lower than or equal to {sys.maxsize}."
)
return x
def message_cache_size_int(arg: str) -> int:
x = non_negative_int(arg)
if x < 1000:
raise argparse.ArgumentTypeError(
"Message cache size has to be greater than or equal to 1000."
)
return x
def parse_cli_flags(args):
parser = argparse.ArgumentParser(
description="Red - Discord Bot", usage="redbot <instance_name> [arguments]"
)
parser.add_argument("--version", "-V", action="store_true", help="Show Red's current version")
parser.add_argument("--debuginfo", action="store_true", help="Show debug information.")
parser.add_argument(
"--enable-debug-events", action="store_true", help="Enable discord.py socket debug events."
)
parser.add_argument(
"--list-instances",
action="store_true",
help="List all instance names setup with 'redbot-setup'",
)
parser.add_argument(
"--edit",
action="store_true",
help="Edit the instance. This can be done without console interaction "
"by passing --no-prompt and arguments that you want to change (available arguments: "
"--edit-instance-name, --edit-data-path, --copy-data, --owner, --token, --prefix).",
)
parser.add_argument(
"--edit-instance-name",
type=str,
help="New name for the instance. This argument only works with --edit argument passed.",
)
parser.add_argument(
"--overwrite-existing-instance",
action="store_true",
help="Confirm overwriting of existing instance when changing name."
" This argument only works with --edit argument passed.",
)
parser.add_argument(
"--edit-data-path",
type=str,
help=(
"New data path for the instance. This argument only works with --edit argument passed."
),
)
parser.add_argument(
"--copy-data",
action="store_true",
help="Copy data from old location. This argument only works "
"with --edit and --edit-data-path arguments passed.",
)
parser.add_argument(
"--owner",
type=int,
help="ID of the owner. Only who hosts "
"Red should be owner, this has "
"serious security implications if misused.",
)
parser.add_argument(
"--co-owner",
type=int,
default=[],
nargs="+",
action="extend",
help="ID of a co-owner. Only people who have access "
"to the system that is hosting Red should be "
"co-owners, as this gives them complete access "
"to the system's data. This has serious "
"security implications if misused. Can be "
"multiple.",
)
parser.add_argument(
"--prefix", "-p", action="append", help="Global prefix. Can be multiple", default=[]
)
parser.add_argument(
"--no-prompt",
action="store_true",
help="Disables console inputs. Features requiring "
"console interaction could be disabled as a "
"result",
)
parser.add_argument(
"--no-cogs", action="store_true", help="Starts Red with no cogs loaded, only core"
)
parser.add_argument(
"--load-cogs",
type=str,
nargs="+",
action="extend",
help="Force loading specified cogs from the installed packages. "
"Can be used with the --no-cogs flag to load these cogs exclusively.",
)
parser.add_argument(
"--unload-cogs",
type=str,
nargs="+",
action="extend",
help="Force unloading specified cogs.",
)
parser.add_argument(
"--cog-path",
type=str,
default=[],
nargs="+",
action="extend",
help="Add a specific path to the list of cog paths. "
"This can be used multiple times to add multiple paths.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Makes Red quit with code 0 just before the "
"login. This is useful for testing the boot "
"process.",
)
parser.add_argument(
"-v",
"--verbose",
"--debug",
action="count",
default=0,
dest="logging_level",
help="Increase the verbosity of the logs, each usage of this flag increases the verbosity level by 1.",
)
parser.add_argument("--dev", action="store_true", help="Enables developer mode")
parser.add_argument(
"--mentionable",
action="store_true",
help="Allows mentioning the bot as an alternative to using the bot prefix",
)
parser.add_argument(
"--rpc",
action="store_true",
help="Enables the built-in RPC server. Please read the docs prior to enabling this!",
)
parser.add_argument(
"--rpc-port",
type=int,
default=6133,
help="The port of the built-in RPC server to use. Default to 6133.",
)
parser.add_argument("--token", type=str, help="Run Red with the given token.")
parser.add_argument(
"--no-instance",
action="store_true",
help=(
"Run Red without any existing instance. "
"The data will be saved under a temporary folder "
"and deleted on next system restart."
),
)
parser.add_argument(
"instance_name", nargs="?", help="Name of the bot instance created during `redbot-setup`."
)
parser.add_argument(
"--team-members-are-owners",
"--team-developers-are-owners",
action="store_true",
dest="use_team_features",
default=False,
help=(
"Treat application team members as owners, if their team role is Owner, "
"Admin, or Developer. "
"This is off by default. Owners can load and run arbitrary code. "
"Do not enable if you would not trust all of your team members with "
"all of the data on the host machine."
),
)
parser.add_argument(
"--message-cache-size",
type=message_cache_size_int,
default=1000,
help="Set the maximum number of messages to store in the internal message cache.",
)
parser.add_argument(
"--no-message-cache", action="store_true", help="Disable the internal message cache."
)
parser.add_argument(
"--disable-intent",
action="append",
choices=list(discord.Intents.VALID_FLAGS), # DEP-WARN
default=[],
help="Unsupported flag that allows disabling the given intent."
" Currently NOT SUPPORTED (and not covered by our version guarantees)"
" as Red is not prepared to work without all intents.\n"
f"Go to https://discordpy.readthedocs.io/en/v{discord_version}/api.html#discord.Intents"
" to see what each intent does.\n"
"This flag can be used multiple times to specify multiple intents.",
)
parser.add_argument(
"--force-rich-logging",
action="store_true",
dest="rich_logging",
default=None,
help="Forcefully enables the Rich logging handlers. This is normally enabled for supported active terminals.",
)
parser.add_argument(
"--force-disable-rich-logging",
action="store_false",
dest="rich_logging",
default=None,
help="Forcefully disables the Rich logging handlers.",
)
# DEP-WARN: use argparse.BooleanOptionalAction when we drop support for Python 3.8
parser.add_argument(
"--rich-tracebacks",
action="store_true",
default=False,
help="Format the Python exception tracebacks using Rich (with syntax highlighting)."
" *May* be useful to increase traceback readability during development.",
)
parser.add_argument(
"--no-rich-tracebacks",
action="store_false",
dest="rich_tracebacks",
)
parser.add_argument(
"--rich-traceback-extra-lines",
type=non_negative_int,
default=0,
help="Set the number of additional lines of code before and after the executed line"
" that should be shown in tracebacks generated by Rich.\n"
"Useful for development.",
)
parser.add_argument(
"--rich-traceback-show-locals",
action="store_true",
help="Enable showing local variables in tracebacks generated by Rich.\n"
"Useful for development.",
)
args = parser.parse_args(args)
if args.prefix:
args.prefix = sorted(args.prefix, reverse=True)
else:
args.prefix = []
args.logging_level = cli_level_to_log_level(args.logging_level)
return args