@@ -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