Skip to content

Commit 0c817c9

Browse files
authored
update user point profile picture fetcher for expired CDNs link retrieval (#672)
* updating how user profile pics are updated so that there's bigger emphasis on retrieving users based on whether their discord CDN has expired rather than their bucket number * also resettng the number of attempts when a user's bucket is reset * adding a separation of function for getting users with expired images and users whose bucket_number has come * separating out the current logic for syncing users in the background into their own functions * updating reset command so it also resets the avatar discord channel
1 parent bed3aad commit 0c817c9

5 files changed

Lines changed: 184 additions & 102 deletions

File tree

.wall_e_models

wall_e/extensions/leveling.py

Lines changed: 160 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def __init__(self):
3939
self.bucket_update_in_progress = False
4040
self.ensure_xp_roles_exist_and_have_right_users.start()
4141
self.process_leveling_profile_data_for_lurkers.start()
42-
self.process_leveling_profile_data_for_active_users.start()
42+
# self.process_leveling_profile_data_for_active_users.start() # will re-enable when all the current users have
43+
# buckets again
4344

4445
@commands.Cog.listener(name="on_ready")
4546
async def get_guild(self):
@@ -472,15 +473,124 @@ async def assign_roles_on_member_join(self, member: discord.Member):
472473
@tasks.loop(hours=1)
473474
async def process_leveling_profile_data_for_lurkers(self):
474475
"""
475-
Goes through all the UserPoint objects that have been marked indicating their profile has been updated
476-
and needs to have wall_e's database updated for the leveling website
477-
:return:
476+
Goes through all the UserPoint objects whose avatar CDN link has expired or who don't yet have a bucket number
477+
and ensure their information has been updated for the leveling website
478478
"""
479-
if self.user_points is None or self.levelling_website_avatar_channel is None or self.guild is None:
479+
not_ready_to_process_lurkers = (
480+
self.user_points is None or self.levelling_website_avatar_channel is None or self.guild is None or
481+
self.bucket_update_in_progress
482+
)
483+
if not_ready_to_process_lurkers:
480484
return
481485
self.logger.debug("[Leveling process_leveling_profile_data_for_lurkers()] background task starting")
482486
await self._set_bucket_numbers()
483487
self.logger.debug("[Leveling process_leveling_profile_data_for_lurkers()] null bucket_number has been set")
488+
489+
entry = await self._get_current_bucket_number()
490+
491+
user_ids_to_update = set()
492+
user_ids_to_update.update(await UserPoint.get_users_with_current_bucket_number(entry.bucket_number_completed))
493+
494+
user_ids_to_update.update(await UserPoint.get_users_with_expired_images())
495+
self.logger.debug(
496+
f"[Leveling process_leveling_profile_data_for_lurkers()] {user_ids_to_update} "
497+
f"potential updates retrieved for bucket {entry.bucket_number_completed}"
498+
)
499+
await self._update_users(user_ids_to_update)
500+
await ProfileBucketInProgress.async_save(entry)
501+
502+
async def _set_bucket_numbers(self):
503+
"""
504+
Assigns a bucket_number to any new UserPoints that don't yet have one
505+
506+
The logic is implemented by creating a dictionary with a bucket for each hour of the month with the lowest
507+
number of days so that this algorithm can also work on leap years.
508+
The idea is that each user will be set a certain hour of the month when it should be updated. And it will
509+
only be updated in that time ASSUMING that the user is a lurker who is not regularly sending messages.
510+
Because if the user is regular sending messages, chances are any changes in their profile will be caught
511+
by get_updated_user_logs
512+
:return:
513+
"""
514+
if self.bucket_update_in_progress:
515+
return
516+
self.bucket_update_in_progress = True
517+
user_points = [user_point for user_point in self.user_points.values() if user_point.bucket_number is None]
518+
if len(user_points) == 0:
519+
return
520+
521+
users_to_update = self._setup_bucket_number_for_new_users()
522+
523+
self.logger.debug(
524+
f"[Leveling _set_bucket_numbers()] updating {len(users_to_update)} user_point objects' bucket_number"
525+
)
526+
await UserPoint.async_bulk_update(users_to_update, ["bucket_number"])
527+
self.bucket_update_in_progress = False
528+
self.logger.debug(
529+
f"[Leveling _set_bucket_numbers()] updated {len(users_to_update)} user_point objects' date_to_check"
530+
)
531+
532+
def _setup_bucket_number_for_new_users(self) -> list:
533+
"""
534+
:return: the users who need to have their bucket_number updated
535+
"""
536+
date_buckets = self._initialize_bucket_with_number_of_current_users()
537+
users_to_update = []
538+
for user_id in self.user_points.keys():
539+
if self.user_points[user_id].bucket_number is None:
540+
lowest_bucket_number = self._get_bucket_number_with_lowest_number_of_users(date_buckets)
541+
date_buckets[lowest_bucket_number] += 1
542+
self.user_points[user_id].bucket_number = lowest_bucket_number
543+
users_to_update.append(self.user_points[user_id])
544+
return users_to_update
545+
546+
def _initialize_bucket_with_number_of_current_users(self) -> dict:
547+
"""
548+
:return: the bucket dict with the users that currently have a bucket_number attached
549+
already reflected on it
550+
"""
551+
date_buckets = Leveling._initialize_blank_bucket()
552+
553+
# populate the date_buckets values with the number of users that currently exist in those buckets
554+
for user_id in self.user_points.keys():
555+
if self.user_points[user_id].bucket_number is not None:
556+
date_buckets[self.user_points[user_id].bucket_number] += 1
557+
return date_buckets
558+
559+
@staticmethod
560+
def _initialize_blank_bucket() -> dict:
561+
"""
562+
:return: a blank bucket dict that has the slots necessary to determine when people should be divided into
563+
slots/buckets within a 2-week period
564+
"""
565+
date_buckets = {}
566+
bucket_number = 1
567+
for day in range(1, 14): # discord CDN links apparently expire after 2 weeks and need to be re-retrieved
568+
for hour in range(1, 24):
569+
date_buckets[bucket_number] = 0
570+
bucket_number += 1
571+
return date_buckets
572+
573+
@staticmethod
574+
def _get_bucket_number_with_lowest_number_of_users(data_buckets):
575+
"""
576+
:param data_buckets:
577+
:return: the bucket_number which has the lowest number of users
578+
"""
579+
low_load_bucket_number = None
580+
min_value = None
581+
for curr_bucket_number, number_of_user_to_checks in data_buckets.items():
582+
if min_value is None:
583+
low_load_bucket_number = curr_bucket_number
584+
min_value = number_of_user_to_checks
585+
elif min_value > number_of_user_to_checks:
586+
low_load_bucket_number = curr_bucket_number
587+
min_value = number_of_user_to_checks
588+
return low_load_bucket_number
589+
590+
async def _get_current_bucket_number(self) -> ProfileBucketInProgress:
591+
"""
592+
:return: the bucket_number to work on in the current iteration
593+
"""
484594
entry = await ProfileBucketInProgress.retrieve_entry()
485595
if entry is None:
486596
entry = await ProfileBucketInProgress.create_entry()
@@ -494,12 +604,15 @@ async def process_leveling_profile_data_for_lurkers(self):
494604
entry.bucket_number_completed += 1
495605
if entry.bucket_number_completed > max_bucket_number:
496606
entry.bucket_number_completed = 1
497-
updated_user_ids = await UserPoint.get_users_that_need_leveling_info_updated(entry.bucket_number_completed)
607+
return entry
608+
609+
async def _update_users(self, updated_user_ids):
610+
"""
611+
iterates through the given list of user_ids and updates them
612+
:param updated_user_ids:
613+
:return:
614+
"""
498615
total_number_of_updates_needed = len(updated_user_ids)
499-
self.logger.debug(
500-
f"[Leveling process_leveling_profile_data_for_lurkers()] {total_number_of_updates_needed} "
501-
f"potential updates retrieved for bucket {entry.bucket_number_completed}"
502-
)
503616
for index, user_id in enumerate(updated_user_ids):
504617
self.logger.debug(
505618
f"[Leveling process_leveling_profile_data_for_lurkers()] attempting to get updated "
@@ -516,62 +629,6 @@ async def process_leveling_profile_data_for_lurkers(self):
516629
pass
517630
if member:
518631
await self._update_member_profile_data(member, user_id, index, total_number_of_updates_needed)
519-
await ProfileBucketInProgress.async_save(entry)
520-
521-
async def _set_bucket_numbers(self):
522-
"""
523-
Takes any [new] UserPoints that don't yet have a bucket_number set
524-
525-
The logic is implemented by creating a dictionary with a bucket for each hour of the month with the lowest
526-
number of days so that this algorithm can also work on leap years.
527-
The idea is that each user will be set a certain hour of the month when it should be updated. And it will
528-
only be updated in that time ASSUMING that the user is a lurker who is not regularly sending messages.
529-
Because if the user is regular sending messages, chances are any changes in their profile will be caught
530-
by get_updated_user_logs
531-
:return:
532-
"""
533-
user_points = [user_point for user_point in self.user_points.values() if user_point.bucket_number is None]
534-
if len(user_points) == 0:
535-
return
536-
if self.bucket_update_in_progress:
537-
return
538-
self.bucket_update_in_progress = True
539-
date_buckets = {}
540-
bucket_number = 1
541-
for day in range(1, 14): # discord CDN links apparently expire after 2 weeks and need to be re-retrieved
542-
for hour in range(1, 24):
543-
date_buckets[bucket_number] = 0
544-
bucket_number += 1
545-
for user_id in self.user_points.keys():
546-
if self.user_points[user_id].bucket_number is not None:
547-
date_buckets[self.user_points[user_id].bucket_number] += 1
548-
549-
def get_bucket_number_with_lowest_user_points(data_buckets_local):
550-
low_load_bucket_number = None
551-
min_value = None
552-
for curr_bucket_number, number_of_user_to_checks in data_buckets_local.items():
553-
if min_value is None:
554-
low_load_bucket_number = curr_bucket_number
555-
min_value = number_of_user_to_checks
556-
elif min_value > number_of_user_to_checks:
557-
low_load_bucket_number = curr_bucket_number
558-
min_value = number_of_user_to_checks
559-
return low_load_bucket_number
560-
users_to_update = []
561-
for user_id in self.user_points.keys():
562-
if self.user_points[user_id].bucket_number is None:
563-
lowest_bucket_number = get_bucket_number_with_lowest_user_points(date_buckets)
564-
date_buckets[lowest_bucket_number] += 1
565-
self.user_points[user_id].bucket_number = lowest_bucket_number
566-
users_to_update.append(self.user_points[user_id])
567-
self.logger.debug(
568-
f"[Leveling _set_bucket_numbers()] updating {len(users_to_update)} user_point objects' bucket_number"
569-
)
570-
await UserPoint.async_bulk_update(users_to_update, ["bucket_number"])
571-
self.bucket_update_in_progress = False
572-
self.logger.debug(
573-
f"[Leveling _set_bucket_numbers()] updated {len(users_to_update)} user_point objects' date_to_check"
574-
)
575632

576633
@tasks.loop(seconds=2)
577634
async def process_leveling_profile_data_for_active_users(self):
@@ -619,20 +676,23 @@ async def _update_member_profile_data(self, member, updated_user_id, index, tota
619676
if member:
620677
try:
621678
if self.user_points[member.id].leveling_update_attempt >= 5:
622-
self.logger.warn(
679+
self.logger.error(
623680
f"[Leveling _update_member_profile_data()] "
624681
f"attempt {self.user_points[member.id].leveling_update_attempt} to update the member profile"
625682
f" data in the database for member {member} {index + 1}/{total_number_of_updates_needed}"
626683
)
627-
user_updated = await self.user_points[member.id].update_leveling_profile_info(
628-
self.logger, member, self.levelling_website_avatar_channel,
629-
updated_user_log_id=updated_user_log_id
630-
)
631-
if user_updated:
632-
self.logger.debug(
633-
f"[Leveling _update_member_profile_data()] updated the member profile data"
634-
f" in the database for member {member} {index + 1}/{total_number_of_updates_needed}"
684+
else:
685+
# leveling_update_attempt is reset to 0 in update_leveling_profile_info if member is successfully
686+
# updated THIS time
687+
user_updated = await self.user_points[member.id].update_leveling_profile_info(
688+
self.logger, member, self.levelling_website_avatar_channel,
689+
updated_user_log_id=updated_user_log_id
635690
)
691+
if user_updated:
692+
self.logger.debug(
693+
f"[Leveling _update_member_profile_data()] updated the member profile data"
694+
f" in the database for member {member} {index + 1}/{total_number_of_updates_needed}"
695+
)
636696
except Exception as e:
637697
self.logger.error(
638698
f"[Leveling _update_member_profile_data()] unable to update the member profile"
@@ -648,9 +708,9 @@ async def _update_member_profile_data(self, member, updated_user_id, index, tota
648708
)
649709
await self.user_points[member.id].async_save()
650710

651-
@app_commands.command(name="reset_bucket_number")
711+
@app_commands.command(name="reset_user_profiles")
652712
@app_commands.checks.has_any_role("Bot_manager")
653-
async def reset_bucket_number(self, interaction: discord.Interaction):
713+
async def reset_user_profiles(self, interaction: discord.Interaction):
654714
await interaction.response.defer()
655715
if self.bucket_update_in_progress:
656716
e_obj = await embed(
@@ -666,8 +726,18 @@ async def reset_bucket_number(self, interaction: discord.Interaction):
666726
users_to_update = []
667727
for user_id in self.user_points.keys():
668728
self.user_points[user_id].bucket_number = None
729+
self.user_points[user_id].leveling_update_attempt = 0
730+
self.user_points[user_id].avatar_url = None
731+
self.user_points[user_id].avatar_url_message_id = None
732+
self.user_points[user_id].leveling_message_avatar_url = None
733+
self.user_points[user_id].discord_avatar_link_expiry_date = None
669734
users_to_update.append(self.user_points[user_id])
670-
await UserPoint.async_bulk_update(users_to_update, ["bucket_number"])
735+
await UserPoint.async_bulk_update(
736+
users_to_update,
737+
["bucket_number", "leveling_update_attempt", "avatar_url", "avatar_url_message_id",
738+
"leveling_message_avatar_url", "discord_avatar_link_expiry_date"
739+
]
740+
)
671741
e_obj = await embed(
672742
self.logger, interaction=interaction,
673743
description=f'{len(users_to_update)} bucket_numbers reset to None'
@@ -676,6 +746,18 @@ async def reset_bucket_number(self, interaction: discord.Interaction):
676746
await interaction.followup.send(embed=e_obj)
677747
await asyncio.sleep(5)
678748
await interaction.delete_original_response()
749+
if self.levelling_website_avatar_channel is not None:
750+
await self.levelling_website_avatar_channel.delete()
751+
leveling_website_avatar_images_channel_id = await bot.bot_channel_manager.create_or_get_channel_id(
752+
self.logger, self.guild, wall_e_config.get_config_value('basic_config', 'ENVIRONMENT'),
753+
'leveling_website_avatar_images'
754+
)
755+
self.levelling_website_avatar_channel: discord.TextChannel = discord.utils.get(
756+
self.guild.channels, id=leveling_website_avatar_images_channel_id
757+
)
758+
self.logger.debug(
759+
f"[Leveling get_leveling_avatar_channel()] bot channel {self.levelling_website_avatar_channel} acquired."
760+
)
679761
self.bucket_update_in_progress = False
680762

681763
@commands.command(

0 commit comments

Comments
 (0)