diff --git a/backend/community_manager/handlers/chat.py b/backend/community_manager/handlers/chat.py index a8d7e327..abca1719 100644 --- a/backend/community_manager/handlers/chat.py +++ b/backend/community_manager/handlers/chat.py @@ -30,6 +30,7 @@ async def handle_chat_action(event: events.ChatAction.Event): if not telegram_chat_service.check_exists(event.chat_id): logger.debug( "Chat doesn't exist, but bot was not added to the chat: %d. Skipping event...", + event.chat_id, ) return @@ -43,7 +44,9 @@ async def handle_chat_action(event: events.ChatAction.Event): session, telethon_client=event.client ) logo_path = await telegram_chat_action.fetch_and_push_profile_photo( - event.chat + event.chat, + # We definitely know here that the new photo was set - no need to fetch the current value + current_logo_path=None, ) if logo_path: logger.debug( diff --git a/backend/core/actions/chat/__init__.py b/backend/core/actions/chat/__init__.py index 76468d74..4c137bed 100644 --- a/backend/core/actions/chat/__init__.py +++ b/backend/core/actions/chat/__init__.py @@ -164,8 +164,9 @@ async def _load_participants(self, chat_identifier: int) -> None: last_name=participant_user.last_name, username=participant_user.username, is_premium=participant_user.premium or False, - language_code=participant_user.lang_code - or core_settings.default_language, + language_code=( + participant_user.lang_code or core_settings.default_language + ), ) ) self.telegram_chat_user_service.create_or_update( @@ -192,10 +193,13 @@ async def index(self, chat: ChatPeerType) -> None: logger.info(f"Creating a new chat invite link for the chat {chat_id!r}...") invite_link = await self.telethon_service.get_invite_link(chat) self.telegram_chat_service.refresh_invite_link(chat_id, invite_link.link) - logger.info(f"Chat {chat_id!r} created successfully") await self._load_participants(telegram_chat.id) - async def fetch_and_push_profile_photo(self, chat: ChatPeerType) -> Path | None: + async def fetch_and_push_profile_photo( + self, + chat: ChatPeerType, + current_logo_path: str | None, + ) -> Path | None: """ Fetches the profile photo of a chat and uploads it for hosting. This function handles the download of the profile photo from the given chat and then pushes @@ -203,15 +207,19 @@ async def fetch_and_push_profile_photo(self, chat: ChatPeerType) -> Path | None: be returned as a Path object; otherwise, None is returned. :param chat: The chat from which the profile photo is to be fetched. + :param current_logo_path: The current logo path in the database. :return: The local path of the fetched profile photo or None """ - logo_path = await self.telethon_service.download_profile_photo(chat) + logo_path = await self.telethon_service.download_profile_photo( + entity=chat, + current_logo_path=current_logo_path, + ) if logo_path: await self.cdn_service.upload_file( file_path=logo_path, object_name=logo_path.name, ) - logger.info(f"Profile photo for chat {chat.id!r} uploaded") + logger.info(f"New profile photo for chat {chat.id!r} uploaded") return logo_path async def _create( @@ -230,7 +238,9 @@ async def _create( :return: A DTO containing the details of the created Telegram chat. :raises TelegramChatAlreadyExists: If the chat already exists in the database. """ - logo_path = await self.fetch_and_push_profile_photo(chat) + logo_path = await self.fetch_and_push_profile_photo( + chat, current_logo_path=None + ) try: chat_id = get_peer_id(chat, add_mark=True) telegram_chat = self.telegram_chat_service.create( @@ -325,7 +335,7 @@ async def _refresh(self, chat: TelegramChat) -> TelegramChat: :raises TelegramChatNotSufficientPrivileges: If the bot lacks functionality privileges within the chat """ try: - chat_entity, logo_path = await self._get_chat_data(chat.id) + chat_entity = await self._get_chat_data(chat.id) except ( TelegramChatNotSufficientPrivileges, # happens when bot has no rights to function in the chat @@ -343,10 +353,16 @@ async def _refresh(self, chat: TelegramChat) -> TelegramChat: self.telegram_chat_service.delete(chat_id=chat.id) raise + logo_path = await self.fetch_and_push_profile_photo( + chat_entity, current_logo_path=chat.logo_path + ) + chat = self.telegram_chat_service.update( chat=chat, entity=chat_entity, - logo_path=logo_path.name, + # If a new logo was downloaded - use it, + # otherwise fallback to the current one + logo_path=logo_path.name if logo_path else chat.logo_path, ) await self.index(chat_entity) logger.info(f"Chat {chat.id!r} refreshed successfully") diff --git a/backend/core/dtos/user.py b/backend/core/dtos/user.py index e98d54ae..40d569df 100644 --- a/backend/core/dtos/user.py +++ b/backend/core/dtos/user.py @@ -23,10 +23,10 @@ class UserInitDataPO(BaseModel): class TelegramUserDTO(BaseModel): id: int - first_name: str + first_name: str = "" last_name: str | None = None username: str | None = None - is_premium: bool + is_premium: bool = False language_code: str photo_url: str | None = None allow_write_to_pm: bool = True diff --git a/backend/core/services/supertelethon.py b/backend/core/services/supertelethon.py index 473ae63e..40482c23 100644 --- a/backend/core/services/supertelethon.py +++ b/backend/core/services/supertelethon.py @@ -1,4 +1,3 @@ -import datetime import logging from pathlib import Path from typing import AsyncGenerator @@ -94,23 +93,31 @@ async def revoke_chat_invite(self, chat_id: int, link: str) -> None: ) ) - async def download_profile_photo(self, entity: Channel) -> Path | None: + async def download_profile_photo( + self, + entity: Channel, + current_logo_path: str | None = None, + ) -> Path | None: if not entity.photo or isinstance(entity.photo, ChatPhotoEmpty): logger.debug(f"Chat {entity.id!r} does not have a logo. Skipping") return None - # Adding timestamp allows to bypass the cache of the image to reflect the change - logo_path = f"{entity.id}-{int(datetime.datetime.now().timestamp())}.jpg" + # Adding timestamp allows bypassing the cache of the image to reflect the change + new_file_name = f"{entity.photo.photo_id}.png" + + if current_logo_path and current_logo_path == new_file_name: + logger.debug(f"Logo for chat {entity.id} is up-to-date. Skipping download.") + return None - with open(CHAT_LOGO_PATH / logo_path, "wb") as f: + with open(CHAT_LOGO_PATH / new_file_name, "wb") as f: await self.client.download_profile_photo(entity, f) - # To avoid redundant files, we clean the old versions of the logo + # To avoid redundant files, we remove the previous file clean_old_versions( - path=CHAT_LOGO_PATH, prefix=f"{entity.id}-", current_file=logo_path + path=CHAT_LOGO_PATH, prefix=current_logo_path, current_file=new_file_name ) - return CHAT_LOGO_PATH / logo_path + return CHAT_LOGO_PATH / new_file_name async def promote_user( self, chat_id: int, telegram_user_id: int, custom_title: str diff --git a/backend/core/utils/custom_rules/telegram_usernames.py b/backend/core/utils/custom_rules/telegram_usernames.py index e5357ed7..196a9fdc 100644 --- a/backend/core/utils/custom_rules/telegram_usernames.py +++ b/backend/core/utils/custom_rules/telegram_usernames.py @@ -42,8 +42,6 @@ def _inner(nfts: list[NftItem]) -> list[NftItem]: telegram_username = TelegramUsername(nft.blockchain_metadata.name) - print(telegram_username.username, target_length, len(telegram_username)) - if len(telegram_username) <= target_length: valid_nfts.append(nft)