|
|
|
|
|
|
161
|
else:
|
161
|
else:
|
|
162
|
lines.append(f"- Backer role configured as `{backer_role.name}`")
|
162
|
lines.append(f"- Backer role configured as `{backer_role.name}`")
|
|
163
|
if stats.username_count > 0:
|
163
|
if stats.username_count > 0:
|
|
164
|
- lines.append(f"- **{stats.username_count}** Discord usernames imported")
|
|
|
|
165
|
- lines.append(f"- Last new import at <t:{stats.username_last_imported_at}:f>")
|
|
|
|
166
|
- lines.append(f"- **{stats.username_found_count}** members linked successfully")
|
|
|
|
|
|
164
|
+ lines.append(f"- **{stats.username_count:,}** Discord usernames imported")
|
|
|
|
165
|
+ lines.append("- Last import with new records on " \
|
|
|
|
166
|
+ f"<t:{stats.username_last_imported_at}:f>")
|
|
|
|
167
|
+ lines.append(f"- **{stats.username_found_count:,}** members linked successfully")
|
|
167
|
if stats.username_not_found_count > 0:
|
168
|
if stats.username_not_found_count > 0:
|
|
168
|
- lines.append(f"- **{stats.username_not_found_count}** imported " \
|
|
|
|
|
|
169
|
+ lines.append(f"- **{stats.username_not_found_count:,}** imported " \
|
|
169
|
"usernames not yet linked to Discord members")
|
170
|
"usernames not yet linked to Discord members")
|
|
170
|
if stats.username_unprocessed_count > 0:
|
171
|
if stats.username_unprocessed_count > 0:
|
|
171
|
- lines.append(f"- **{stats.username_unprocessed_count}** " \
|
|
|
|
172
|
- "usernames not yet attempted to sync")
|
|
|
|
|
|
172
|
+ lines.append(f"- **{stats.username_unprocessed_count:,}** " \
|
|
|
|
173
|
+ "usernames added since last sync")
|
|
173
|
else:
|
174
|
else:
|
|
174
|
lines.append("- No Discord usernames imported yet. Use " \
|
175
|
lines.append("- No Discord usernames imported yet. Use " \
|
|
175
|
f"`/{KickstarterCog.kickstarter.name} {KickstarterCog.upload.name}`")
|
176
|
f"`/{KickstarterCog.kickstarter.name} {KickstarterCog.upload.name}`")
|
|
|
|
|
|
|
324
|
text = f"{CONFIG['success_emoji']} Record for `@{normal_username}` reset. " \
|
325
|
text = f"{CONFIG['success_emoji']} Record for `@{normal_username}` reset. " \
|
|
325
|
"(No roles removed from member.)"
|
326
|
"(No roles removed from member.)"
|
|
326
|
if old_member_id is not None:
|
327
|
if old_member_id is not None:
|
|
327
|
- text += f"\n- member_id: `{old_member_id}` --> `{record.discord_member_id}`"
|
|
|
|
|
|
328
|
+ text += f"\n- `discord_member_id` changed from `{old_member_id}` to `{record.discord_member_id}`"
|
|
328
|
if old_lookup_status is not None:
|
329
|
if old_lookup_status is not None:
|
|
329
|
- text += f"\n- lookup_status: `{_describe_lookup_status(old_lookup_status)}`" \
|
|
|
|
330
|
- f" --> `{_describe_lookup_status(record.lookup_status)}`"
|
|
|
|
|
|
330
|
+ text += f"\n- `lookup_status` changed from `{_describe_lookup_status(old_lookup_status)}`" \
|
|
|
|
331
|
+ f" to `{_describe_lookup_status(record.lookup_status)}`"
|
|
331
|
await interaction.response.send_message(text, ephemeral=True)
|
332
|
await interaction.response.send_message(text, ephemeral=True)
|
|
332
|
|
333
|
|
|
333
|
@kickstarter.command(
|
334
|
@kickstarter.command(
|
|
|
|
|
|
|
339
|
|
340
|
|
|
340
|
await interaction.response.send_modal(_UploadUsernamesModal())
|
341
|
await interaction.response.send_modal(_UploadUsernamesModal())
|
|
341
|
|
342
|
|
|
|
|
343
|
+ # @override
|
|
342
|
async def interaction_check(self, interaction: Interaction) -> bool:
|
344
|
async def interaction_check(self, interaction: Interaction) -> bool:
|
|
343
|
if interaction.command is not None:
|
345
|
if interaction.command is not None:
|
|
344
|
self.__trace(interaction.guild, f"@{interaction.user.name} used " \
|
346
|
self.__trace(interaction.guild, f"@{interaction.user.name} used " \
|
|
|
|
|
|
|
406
|
) -> '_ImportResult':
|
408
|
) -> '_ImportResult':
|
|
407
|
"""Imports Discord usernames from an upload attachment."""
|
409
|
"""Imports Discord usernames from an upload attachment."""
|
|
408
|
self.__trace(guild, f"Download - start - {attachment.filename} " \
|
410
|
self.__trace(guild, f"Download - start - {attachment.filename} " \
|
|
409
|
- f"({attachment.size} bytes, {attachment.content_type})")
|
|
|
|
|
|
411
|
+ f"({attachment.size:,} bytes, {attachment.content_type})")
|
|
410
|
file_bytes = await attachment.read()
|
412
|
file_bytes = await attachment.read()
|
|
411
|
file_str = file_bytes.decode('utf-8')
|
413
|
file_str = file_bytes.decode('utf-8')
|
|
412
|
self.__trace(guild, "Download - complete")
|
414
|
self.__trace(guild, "Download - complete")
|
|
|
|
|
|
|
430
|
self.__trace(guild, f"Not a Discord username: \"{line}\"")
|
432
|
self.__trace(guild, f"Not a Discord username: \"{line}\"")
|
|
431
|
malformed_usernames.append(line)
|
433
|
malformed_usernames.append(line)
|
|
432
|
malformed_count += 1
|
434
|
malformed_count += 1
|
|
433
|
- self.__trace(guild, f"Parse - complete - {len(lines)} lines, {len(usernames)} " \
|
|
|
|
434
|
- f"valid usernames, {malformed_count} malformed usernames")
|
|
|
|
|
|
435
|
+ self.__trace(guild, f"Parse - complete - {len(lines):,} lines, {len(usernames):,} " \
|
|
|
|
436
|
+ f"valid usernames, {malformed_count:,} malformed usernames")
|
|
435
|
|
437
|
|
|
436
|
- self.__trace(guild, f"Storing - start - {len(usernames)} usernames")
|
|
|
|
|
|
438
|
+ self.__trace(guild, f"Storing - start - {len(usernames):,} usernames")
|
|
437
|
new_username_count = self.__store_kickstarter_usernames(guild.id, usernames)
|
439
|
new_username_count = self.__store_kickstarter_usernames(guild.id, usernames)
|
|
438
|
- self.__trace(guild, f"Storing - complete - {new_username_count} unique " \
|
|
|
|
|
|
440
|
+ self.__trace(guild, f"Storing - complete - {new_username_count:,} unique " \
|
|
439
|
"usernames stored")
|
441
|
"usernames stored")
|
|
440
|
|
442
|
|
|
441
|
return _ImportResult(
|
443
|
return _ImportResult(
|
|
|
|
|
|
|
459
|
_LookupStatus.unprocessed,
|
461
|
_LookupStatus.unprocessed,
|
|
460
|
_LookupStatus.username_not_found
|
462
|
_LookupStatus.username_not_found
|
|
461
|
})
|
463
|
})
|
|
462
|
- self.__trace(guild, f"Fetch usernames - complete - found {len(usernames)}")
|
|
|
|
|
|
464
|
+ self.__trace(guild, f"Fetch usernames - complete - found {len(usernames):,}")
|
|
463
|
username_to_member_id: dict[str, int] = {}
|
465
|
username_to_member_id: dict[str, int] = {}
|
|
464
|
if self.bot.intents.members:
|
466
|
if self.bot.intents.members:
|
|
465
|
self.__trace(guild, "Fetching guild members from API - start")
|
467
|
self.__trace(guild, "Fetching guild members from API - start")
|
|
466
|
async for member in guild.fetch_members(limit=None):
|
468
|
async for member in guild.fetch_members(limit=None):
|
|
467
|
username_to_member_id[member.name] = member.id
|
469
|
username_to_member_id[member.name] = member.id
|
|
468
|
self.__trace(guild, "Fetching guild members from API - complete - " \
|
470
|
self.__trace(guild, "Fetching guild members from API - complete - " \
|
|
469
|
- f"got {len(username_to_member_id)}")
|
|
|
|
|
|
471
|
+ f"got {len(username_to_member_id):,}")
|
|
470
|
async def username_loop_handler(username: _KickstarterDiscordUser):
|
472
|
async def username_loop_handler(username: _KickstarterDiscordUser):
|
|
471
|
nonlocal complete_count
|
473
|
nonlocal complete_count
|
|
472
|
nonlocal not_found_count
|
474
|
nonlocal not_found_count
|
|
|
|
|
|
|
491
|
self.__trace(guild, "Sync loop - start")
|
493
|
self.__trace(guild, "Sync loop - start")
|
|
492
|
failure_count = await self.__throttled_loop(guild, usernames,
|
494
|
failure_count = await self.__throttled_loop(guild, usernames,
|
|
493
|
username_loop_handler, update_seconds=10.0)
|
495
|
username_loop_handler, update_seconds=10.0)
|
|
494
|
- self.__trace(guild, f"Sync loop - complete - {complete_count} completed, " \
|
|
|
|
495
|
- f"{not_found_count} not found")
|
|
|
|
|
|
496
|
+ self.__trace(guild, f"Sync loop - complete - {complete_count:,} completed, " \
|
|
|
|
497
|
+ f"{not_found_count:,} not found")
|
|
496
|
return _SyncUsernamesResult(complete_count, not_found_count, failure_count)
|
498
|
return _SyncUsernamesResult(complete_count, not_found_count, failure_count)
|
|
497
|
|
499
|
|
|
498
|
async def __throttled_loop(self,
|
500
|
async def __throttled_loop(self,
|
|
|
|
|
|
|
523
|
f"retry_after={retry_header_value}")
|
525
|
f"retry_after={retry_header_value}")
|
|
524
|
await sleep(retry_after_millis / 1000.0)
|
526
|
await sleep(retry_after_millis / 1000.0)
|
|
525
|
sleep_length = 1.0 if sleep_length == 0.0 else sleep_length * 2.0
|
527
|
sleep_length = 1.0 if sleep_length == 0.0 else sleep_length * 2.0
|
|
526
|
- self.__trace(guild, f"Sleep increased to {sleep_length}s " \
|
|
|
|
|
|
528
|
+ self.__trace(guild, f"Sleep increased to {sleep_length:,}s " \
|
|
527
|
"due to rate limiting")
|
529
|
"due to rate limiting")
|
|
528
|
else:
|
530
|
else:
|
|
529
|
dump_stacktrace(ex)
|
531
|
dump_stacktrace(ex)
|
|
|
|
|
|
|
532
|
if not complete:
|
534
|
if not complete:
|
|
533
|
failure_count += 1
|
535
|
failure_count += 1
|
|
534
|
if update_seconds is not None and now_timestamp() - last_update_time >= update_seconds:
|
536
|
if update_seconds is not None and now_timestamp() - last_update_time >= update_seconds:
|
|
535
|
- self.__trace(guild, f"Completed {iter_count + 1} iterations")
|
|
|
|
|
|
537
|
+ self.__trace(guild, f"Completed {iter_count + 1:,} iterations")
|
|
536
|
last_update_time = now_timestamp()
|
538
|
last_update_time = now_timestamp()
|
|
537
|
return failure_count
|
539
|
return failure_count
|
|
538
|
|
540
|
|
|
|
|
|
|
|
827
|
def summary_markdown(self) -> str:
|
829
|
def summary_markdown(self) -> str:
|
|
828
|
lines: list[str] = []
|
830
|
lines: list[str] = []
|
|
829
|
if self.valid_record_count > 0:
|
831
|
if self.valid_record_count > 0:
|
|
830
|
- lines.append(f"- Read {self.valid_record_count} valid records")
|
|
|
|
|
|
832
|
+ lines.append(f"- Read {self.valid_record_count:,} valid records")
|
|
831
|
else:
|
833
|
else:
|
|
832
|
lines.append("- Upload contained **no valid records**")
|
834
|
lines.append("- Upload contained **no valid records**")
|
|
833
|
if self.malformed_record_count > 0:
|
835
|
if self.malformed_record_count > 0:
|
|
834
|
- lines.append(f"- Read **{self.malformed_record_count} malformed records**")
|
|
|
|
|
|
836
|
+ lines.append(f"- Read **{self.malformed_record_count:,} malformed records**")
|
|
835
|
if self.new_record_count > 0:
|
837
|
if self.new_record_count > 0:
|
|
836
|
- lines.append(f"- Imported {self.new_record_count} new unique records")
|
|
|
|
|
|
838
|
+ lines.append(f"- Imported {self.new_record_count:,} new unique records")
|
|
837
|
else:
|
839
|
else:
|
|
838
|
lines.append("- No new unique records (all previously imported)")
|
840
|
lines.append("- No new unique records (all previously imported)")
|
|
839
|
return "\n".join(lines)
|
841
|
return "\n".join(lines)
|
|
|
|
|
|
|
852
|
def summary_markdown(self) -> str:
|
854
|
def summary_markdown(self) -> str:
|
|
853
|
lines: list[str] = []
|
855
|
lines: list[str] = []
|
|
854
|
if self.complete_count > 0:
|
856
|
if self.complete_count > 0:
|
|
855
|
- lines.append(f"- Gave backer role to {self.complete_count} new members")
|
|
|
|
|
|
857
|
+ lines.append(f"- Gave backer role to {self.complete_count:,} new members")
|
|
856
|
else:
|
858
|
else:
|
|
857
|
lines.append("- No new members found")
|
859
|
lines.append("- No new members found")
|
|
858
|
if self.not_found_count > 0:
|
860
|
if self.not_found_count > 0:
|
|
859
|
- lines.append(f"- {self.not_found_count} members could not be found " \
|
|
|
|
|
|
861
|
+ lines.append(f"- {self.not_found_count:,} members could not be found " \
|
|
860
|
"by the provided username")
|
862
|
"by the provided username")
|
|
861
|
if self.failure_count > 0:
|
863
|
if self.failure_count > 0:
|
|
862
|
- lines.append(f"- Failed to link {self.failure_count} members")
|
|
|
|
|
|
864
|
+ lines.append(f"- Failed to link {self.failure_count:,} members")
|
|
863
|
return "\n".join(lines)
|
865
|
return "\n".join(lines)
|
|
864
|
|
866
|
|
|
865
|
class _UploadUsernamesModal(Modal):
|
867
|
class _UploadUsernamesModal(Modal):
|
|
|
|
|
|
|
875
|
def __init__(self):
|
877
|
def __init__(self):
|
|
876
|
super().__init__(title='Upload Discord Usernames', timeout=None)
|
878
|
super().__init__(title='Upload Discord Usernames', timeout=None)
|
|
877
|
|
879
|
|
|
|
|
880
|
+ # @override
|
|
878
|
async def on_submit(self, interaction: Interaction) -> None:
|
881
|
async def on_submit(self, interaction: Interaction) -> None:
|
|
879
|
# noinspection PyTypeChecker
|
882
|
# noinspection PyTypeChecker
|
|
880
|
upload_input: FileUpload = self.upload_label.component
|
883
|
upload_input: FileUpload = self.upload_label.component
|
|
|
|
|
|
|
885
|
attachment = upload_input.values[0]
|
888
|
attachment = upload_input.values[0]
|
|
886
|
await KickstarterCog.shared.on_username_upload_submit(interaction, attachment)
|
889
|
await KickstarterCog.shared.on_username_upload_submit(interaction, attachment)
|
|
887
|
|
890
|
|
|
|
|
891
|
+ # @override
|
|
888
|
async def on_error(self, interaction: Interaction, error: Exception) -> None:
|
892
|
async def on_error(self, interaction: Interaction, error: Exception) -> None:
|
|
889
|
dump_stacktrace(error)
|
893
|
dump_stacktrace(error)
|
|
890
|
try:
|
894
|
try:
|