-
Notifications
You must be signed in to change notification settings - Fork 0
/
cog.py
754 lines (675 loc) · 26.4 KB
/
cog.py
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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
import logging
from typing import Any, Optional
import discord
import discord.ext.commands as commands
from discord.ext.commands import BadArgument
from .misc import fuzzy_match_timezone
from ..common.discord import *
from ..common.embeds import Embeds
from ..common.misc import join
from ..common.time import format_date
from ..user_data.cog import UserData, DatabaseUnavailable
from ..user_data.database import Database, DatabaseError
from ..user_data.enums import PrivacyType
__all__ = ['Bios']
logger = logging.getLogger('sandpiper.bios')
privacy_emojis = {
PrivacyType.PRIVATE: '⛔',
PrivacyType.PUBLIC: '✅'
}
def user_info_str(field_name: str, value: Any, privacy: PrivacyType):
privacy_emoji = privacy_emojis[privacy]
privacy = privacy.name.capitalize()
return f'{privacy_emoji} `{privacy:7}` | **{field_name}** • {value}'
async def user_names_str(
ctx: commands.Context, db: Database, user_id: int,
*, preferred_name: str = None, username: str = None,
display_name: str = None
):
"""
Create a string with a user's names (preferred name, Discord username,
guild display names). You can supply ``preferred_name``, ``username``,
or ``display_name`` to optimize the number of operations this function
has to perform.
"""
# Get pronouns
privacy_pronouns = await db.get_privacy_pronouns(user_id)
if privacy_pronouns == PrivacyType.PUBLIC:
pronouns = await db.get_pronouns(user_id)
else:
pronouns = None
# Get preferred name
if preferred_name is None:
privacy_preferred_name = await db.get_privacy_preferred_name(user_id)
if privacy_preferred_name == PrivacyType.PUBLIC:
preferred_name = await db.get_preferred_name(user_id)
if preferred_name is None:
preferred_name = '`No preferred name`'
else:
preferred_name = '`No preferred name`'
# Get discord username and discriminator
if username is None:
user: discord.User = ctx.bot.get_user(user_id)
if user is not None:
username = f'{user.name}#{user.discriminator}'
else:
username = '`User not found`'
if ctx.guild is None:
# Find the user's nicknames on servers they share with the executor
# of the whois command
members = find_user_in_mutual_guilds(ctx.bot, ctx.author.id, user_id)
display_names = ', '.join(m.display_name for m in members)
else:
if display_name is None:
# Find the user's nickname in the current guild ONLY
display_names = ctx.guild.get_member(user_id).display_name
else:
# Use the passed-in display name
display_names = display_name
return join(
join(preferred_name, pronouns and f'({pronouns})', sep=' '),
username, display_names,
sep=' • '
)
class Bios(commands.Cog):
__cog_cleaned_doc__ = (
"Store some info about yourself to help your friends get to know you "
"more easily! Most of these commands can only be used in DMs with "
"Sandpiper for your privacy."
"\n\n"
"Some of this info is used by other Sandpiper features, such as "
"time conversion and birthday notifications."
)
_show_aliases = ('get',)
_set_aliases = ()
_delete_aliases = ('clear', 'remove')
auto_order = AutoOrder()
def __init__(self, bot: commands.Bot):
self.bot = bot
async def _get_database(self) -> Database:
user_data: Optional[UserData] = self.bot.get_cog('UserData')
if user_data is None:
raise RuntimeError('UserData cog is not loaded.')
return await user_data.get_database()
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context,
error: commands.CommandError):
if isinstance(error, commands.CommandInvokeError):
if isinstance(error.original, DatabaseUnavailable):
await Embeds.error(ctx, str(DatabaseUnavailable))
elif isinstance(error.original, DatabaseError):
await Embeds.error(ctx, "Error during database operation.")
else:
logger.error(
f'Unexpected error in "{ctx.command}" ('
f'content={ctx.message.content!r} '
f'message={ctx.message!r})',
exc_info=error.original
)
await Embeds.error(ctx, "Unexpected error.")
else:
await Embeds.error(ctx, str(error))
@commands.Cog.listener()
async def on_command(self, ctx: commands.Context):
logger.info(
f'Running command "{ctx.command}" (author={ctx.author} '
f'content={ctx.message.content!r})'
)
@auto_order
@commands.group(
brief="Personal info commands.",
help="Commands for managing all of your personal info at once."
)
async def bio(self, ctx: commands.Context):
pass
@auto_order
@bio.command(
name='show', aliases=_show_aliases,
brief="Show all stored info.",
help="Display all of your personal info stored in Sandpiper."
)
@commands.dm_only()
async def bio_show(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
preferred_name = await db.get_preferred_name(user_id)
pronouns = await db.get_pronouns(user_id)
birthday = await db.get_birthday(user_id)
birthday = format_date(birthday)
age = await db.get_age(user_id)
age = age if age is not None else 'N/A'
timezone = await db.get_timezone(user_id)
p_preferred_name = await db.get_privacy_preferred_name(user_id)
p_pronouns = await db.get_privacy_pronouns(user_id)
p_birthday = await db.get_privacy_birthday(user_id)
p_age = await db.get_privacy_age(user_id)
p_timezone = await db.get_privacy_timezone(user_id)
await Embeds.info(ctx, message=(
user_info_str('Name', preferred_name, p_preferred_name),
user_info_str('Pronouns', pronouns, p_pronouns),
user_info_str('Birthday', birthday, p_birthday),
user_info_str('Age', age, p_age),
user_info_str('Timezone', timezone, p_timezone)
))
@auto_order
@bio.command(
name='delete', aliases=_delete_aliases,
brief="Delete all stored info.",
help="Delete all of your personal info stored in Sandpiper."
)
@commands.dm_only()
async def bio_delete(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
await db.delete_user(user_id)
await Embeds.success(ctx, "Deleted all of your personal info!")
# Privacy setters
@auto_order
@commands.group(
name='privacy', invoke_without_command=False,
brief="Personal info privacy commands.",
help="Commands for setting the privacy of your personal info."
)
async def privacy(self, ctx: commands.Context):
pass
@auto_order
@privacy.command(
name='all',
brief="Set all privacies at once.",
help=(
"Set the privacy of all of your personal info at once to either "
"'private' or 'public'."
),
example="privacy all public"
)
async def privacy_all(
self, ctx: commands.Context, new_privacy: privacy_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_privacy_preferred_name(user_id, new_privacy)
await db.set_privacy_pronouns(user_id, new_privacy)
await db.set_privacy_birthday(user_id, new_privacy)
await db.set_privacy_age(user_id, new_privacy)
await db.set_privacy_timezone(user_id, new_privacy)
await Embeds.success(ctx, "All privacies set!")
@auto_order
@privacy.command(
name='name',
brief="Set preferred name privacy.",
help=(
"Set the privacy of your preferred name to either 'private' or "
"'public'."
),
example="privacy name public"
)
async def privacy_name(
self, ctx: commands.Context, new_privacy: privacy_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_privacy_preferred_name(user_id, new_privacy)
await Embeds.success(ctx, "Name privacy set!")
@auto_order
@privacy.command(
name='pronouns',
brief="Set pronouns privacy.",
help=(
"Set the privacy of your pronouns to either 'private' or 'public'."
),
example="privacy pronouns public"
)
async def privacy_pronouns(
self, ctx: commands.Context, new_privacy: privacy_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_privacy_pronouns(user_id, new_privacy)
await Embeds.success(ctx, "Pronouns privacy set!")
@auto_order
@privacy.command(
name='birthday',
brief="Set birthday privacy.",
help=(
"Set the privacy of your birthday to either 'private' or 'public'."
),
example="privacy birthday public"
)
async def privacy_birthday(
self, ctx: commands.Context, new_privacy: privacy_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_privacy_birthday(user_id, new_privacy)
await Embeds.success(ctx, "Birthday privacy set!")
@auto_order
@privacy.command(
name='age',
brief="Set age privacy.",
help=(
"Set the privacy of your age to either 'private' or 'public'."
),
example="privacy age public"
)
async def privacy_age(
self, ctx: commands.Context, new_privacy: privacy_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_privacy_age(user_id, new_privacy)
await Embeds.success(ctx, "Age privacy set!")
@auto_order
@privacy.command(
name='timezone',
brief="Set timezone privacy.",
help=(
"Set the privacy of your timezone to either 'private' or 'public'."
),
example="privacy timezone public"
)
async def privacy_timezone(
self, ctx: commands.Context, new_privacy: privacy_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_privacy_timezone(user_id, new_privacy)
await Embeds.success(ctx, "Timezone privacy set!")
# Name
@auto_order
@commands.group(
name='name', invoke_without_command=False,
brief="Preferred name commands.",
help="Commands for managing your preferred name."
)
async def name(self, ctx: commands.Context):
pass
@auto_order
@name.command(
name='show', aliases=_show_aliases,
help="Display your preferred name."
)
@commands.dm_only()
async def name_show(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
preferred_name = await db.get_preferred_name(user_id)
privacy = await db.get_privacy_preferred_name(user_id)
await Embeds.info(ctx, user_info_str('Name', preferred_name, privacy))
@auto_order
@name.command(
name='set', aliases=_set_aliases,
brief="Set your preferred name.",
help="Set your preferred name. Must be 64 characters or less.",
example="name set Hawk"
)
@commands.dm_only()
async def name_set(self, ctx: commands.Context, *, new_name: str):
user_id: int = ctx.author.id
db = await self._get_database()
if len(new_name) > 64:
raise BadArgument(f"Name must be 64 characters or less "
f"(yours: {len(new_name)}).")
await db.set_preferred_name(user_id, new_name)
await Embeds.success(ctx, "Preferred name set!")
if await db.get_privacy_preferred_name(user_id) == PrivacyType.PRIVATE:
await Embeds.warning(
ctx,
"Your preferred name is set to private. If you want others to "
"be able to see it through Sandpiper, set it to public with "
"the command `privacy name public`."
)
@auto_order
@name.command(
name='delete', aliases=_delete_aliases,
help="Delete your preferred name."
)
@commands.dm_only()
async def name_delete(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_preferred_name(user_id, None)
await Embeds.success(ctx, "Preferred name deleted!")
# Pronouns
@auto_order
@commands.group(
name='pronouns', invoke_without_command=False,
brief="Pronouns commands.",
help="Commands for managing your pronouns."
)
async def pronouns(self, ctx: commands.Context):
pass
@auto_order
@pronouns.command(
name='show', aliases=_show_aliases,
help="Display your pronouns."
)
@commands.dm_only()
async def pronouns_show(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
pronouns = await db.get_pronouns(user_id)
privacy = await db.get_privacy_pronouns(user_id)
await Embeds.info(ctx, user_info_str('Pronouns', pronouns, privacy))
@auto_order
@pronouns.command(
name='set', aliases=_set_aliases,
brief="Set your pronouns.",
help="Set your pronouns. Must be 64 characters or less.",
example="pronouns set She/Her"
)
@commands.dm_only()
async def pronouns_set(self, ctx: commands.Context, *, new_pronouns: str):
user_id: int = ctx.author.id
db = await self._get_database()
if len(new_pronouns) > 64:
raise BadArgument(f"Pronouns must be 64 characters or less "
f"(yours: {len(new_pronouns)}).")
await db.set_pronouns(user_id, new_pronouns)
await Embeds.success(ctx, 'Pronouns set!')
if await db.get_privacy_pronouns(user_id) == PrivacyType.PRIVATE:
await Embeds.warning(
ctx,
"Your pronouns are set to private. If you want others to be "
"able to see them through Sandpiper, set them to public with "
"the command `privacy pronouns public`."
)
@auto_order
@pronouns.command(
name='delete', aliases=_delete_aliases,
help="Delete your pronouns."
)
@commands.dm_only()
async def pronouns_delete(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_pronouns(user_id, None)
await Embeds.success(ctx, "Pronouns deleted!")
# Birthday
@auto_order
@commands.group(
name='birthday', invoke_without_command=False,
brief="Birthday commands.",
help="Commands for managing your birthday."
)
async def birthday(self, ctx: commands.Context):
pass
@auto_order
@birthday.command(
name='show', aliases=_show_aliases,
help="Display your birthday."
)
@commands.dm_only()
async def birthday_show(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
birthday = await db.get_birthday(user_id)
birthday = format_date(birthday)
privacy = await db.get_privacy_birthday(user_id)
await Embeds.info(ctx, user_info_str('Birthday', birthday, privacy))
@auto_order
@birthday.command(
name='set', aliases=_set_aliases,
brief="Set your birthday.",
help=(
"Set your birthday. There are several allowed formats, and some "
"allow you to omit your birth year if you are not comfortable with "
"adding it (your age will not be calculated)."
"\n\n"
"See the examples below for valid formats."
),
example=(
"birthday set 1997-08-27",
"birthday set 8 August 1997",
"birthday set Aug 8 1997",
"birthday set August 8",
"birthday set 8 Aug",
)
)
@commands.dm_only()
async def birthday_set(self, ctx: commands.Context, *,
new_birthday: date_handler):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_birthday(user_id, new_birthday)
await Embeds.success(ctx, "Birthday set!")
if await db.get_privacy_birthday(user_id) == PrivacyType.PRIVATE:
await Embeds.warning(
ctx,
"Your birthday is set to private. If you want others to be "
"able to see it through Sandpiper, set it to public with "
"the command `privacy birthday public`. If you want others to "
"know your age but not your birthday, you may set that to "
"public with the command `privacy age public`."
)
@auto_order
@birthday.command(
name='delete', aliases=_delete_aliases,
help="Delete your birthday."
)
@commands.dm_only()
async def birthday_delete(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_birthday(user_id, None)
await Embeds.success(ctx, "Birthday deleted!")
# Age
@auto_order
@commands.group(
name='age', invoke_without_command=False,
brief="Age commands.",
help="Commands for managing your age."
)
async def age(self, ctx: commands.Context):
pass
@auto_order
@age.command(
name='show', aliases=_show_aliases,
brief="Display your age.",
help="Display your age (calculated automatically using your birthday)."
)
@commands.dm_only()
async def age_show(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
age = await db.get_age(user_id)
age = age if age is not None else 'N/A'
privacy = await db.get_privacy_age(user_id)
await Embeds.info(ctx, user_info_str('Age', age, privacy))
# noinspection PyUnusedLocal
@auto_order
@age.command(
name='set', aliases=_set_aliases, hidden=True,
brief="This command does nothing.",
help=(
"Age is automatically calculated using your birthday. This "
"command exists only to let you know that you don't have to set it."
)
)
@commands.dm_only()
async def age_set(self, ctx: commands.Context):
await Embeds.error(
ctx,
"Age is automatically calculated using your birthday. "
"You don't need to set it!"
)
# noinspection PyUnusedLocal
@auto_order
@age.command(
name='delete', aliases=_delete_aliases, hidden=True,
brief="This command does nothing.",
help=(
"Age is automatically calculated using your birthday. This command "
"exists only to let you know that you can only delete your birthday."
)
)
@commands.dm_only()
async def age_delete(self, ctx: commands.Context):
await Embeds.error(
ctx,
"Age is automatically calculated using your birthday. You can "
"either delete your birthday with `birthday delete` or set your "
"age to private so others can't see it with "
"`privacy age private`."
)
# Timezone
@auto_order
@commands.group(
name='timezone', invoke_without_command=False,
brief="Timezone commands.",
help="Commands for managing your timezone."
)
async def timezone(self, ctx: commands.Context):
pass
@auto_order
@timezone.command(
name='show', aliases=_show_aliases,
help="Display your timezone."
)
@commands.dm_only()
async def timezone_show(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
timezone = await db.get_timezone(user_id)
privacy = await db.get_privacy_timezone(user_id)
await Embeds.info(ctx, user_info_str('Timezone', timezone, privacy))
@auto_order
@timezone.command(
name='set', aliases=_set_aliases,
brief="Set your timezone.",
help=(
"Set your timezone. Don't worry about formatting. Typing the "
"name of the nearest major city should be good enough, but you can "
"also try your state/country if that doesn't work."
"\n\n"
"If you're confused, use this website to find your full timezone "
"name: http://kevalbhatt.github.io/timezone-picker"
),
example=(
"timezone set America/New_York",
"timezone set new york",
"timezone set amsterdam",
"timezone set london",
)
)
@commands.dm_only()
async def timezone_set(self, ctx: commands.Context, *, new_timezone: str):
user_id: int = ctx.author.id
db = await self._get_database()
tz_matches = fuzzy_match_timezone(
new_timezone, best_match_threshold=50, lower_score_cutoff=50,
limit=5
)
if not tz_matches.matches:
# No matches
raise BadArgument(
"Timezone provided doesn't have any close matches. Try "
"typing the name of a major city near you or your "
"state/country name.\n\n"
"If you're stuck, try using this "
"[timezone picker](http://kevalbhatt.github.io/timezone-picker/)."
)
if tz_matches.best_match:
# Display best match with other possible matches
await db.set_timezone(user_id, tz_matches.best_match)
await Embeds.success(ctx, message=(
f"Timezone set to **{tz_matches.best_match}**!",
tz_matches.matches[1:] and "\nOther possible matches:",
'\n'.join([f'- {name}' for name, _ in tz_matches.matches[1:]])
))
else:
# No best match; display other possible matches
await Embeds.error(ctx, message=(
"Couldn't find a good match for the timezone you entered.",
"\nPossible matches:",
'\n'.join([f'- {name}' for name, _ in tz_matches.matches])
))
if await db.get_privacy_timezone(user_id) == PrivacyType.PRIVATE:
await Embeds.warning(
ctx,
"Your timezone is set to private. If you want others to be "
"able to see it through Sandpiper, set it to public with "
"the command `privacy timezone public`."
)
@auto_order
@timezone.command(
name='delete', aliases=_delete_aliases,
help="Delete your timezone."
)
@commands.dm_only()
async def timezone_delete(self, ctx: commands.Context):
user_id: int = ctx.author.id
db = await self._get_database()
await db.set_timezone(user_id, None)
await Embeds.success(ctx, "Timezone deleted!")
# Extra commands
@auto_order
@commands.command(
name='whois',
brief="Search for a user.",
help=(
"Search for a user by one of their names. Outputs a list of "
"matching users, showing their preferred name, Discord username, "
"and nicknames in servers you share with them."
),
example="whois hawk"
)
async def whois(self, ctx: commands.Context, name: str):
if len(name) < 2:
raise BadArgument("Name must be at least 2 characters.")
db = await self._get_database()
user_strs = []
seen_users = set()
def should_skip_user(user_id: int, *, skip_guild_check=False):
"""
Filter out users that have already been seen or who aren't in the
guild.
:param user_id: the target user that's been found by the search
functions
:param skip_guild_check: whether to skip the process of ensuring
the target and executor exist in mutual guilds (for
optimization)
"""
if user_id in seen_users:
return True
seen_users.add(user_id)
if not skip_guild_check:
if ctx.guild:
# We're in a guild, so don't allow users from other guilds
# to be found
if not ctx.guild.get_member(user_id):
return True
else:
# We're in DMs, so check if the executor shares a guild
# with the target
if not find_user_in_mutual_guilds(
ctx.bot, ctx.author.id, user_id,
short_circuit=True):
# Executor doesn't share a guild with target
return True
return False
for user_id, preferred_name in await db.find_users_by_preferred_name(name):
# Get preferred names from database
if should_skip_user(user_id):
continue
names = await user_names_str(
ctx, db, user_id, preferred_name=preferred_name
)
user_strs.append(names)
for user_id, display_name in find_users_by_display_name(
ctx.bot, ctx.author.id, name, guild=ctx.guild):
# Get display names from guilds
# This search function filters out non-mutual-guild users as part
# of its optimization, so we don't need to do that again
if should_skip_user(user_id, skip_guild_check=True):
continue
names = await user_names_str(
ctx, db, user_id, display_name=display_name
)
user_strs.append(names)
for user_id, username in find_users_by_username(ctx.bot, name):
# Get usernames from client
if should_skip_user(user_id):
continue
names = await user_names_str(
ctx, db, user_id, username=username
)
user_strs.append(names)
if user_strs:
await Embeds.info(ctx, message=user_strs)
else:
await Embeds.error(ctx, "No users found with this name.")
del auto_order