From b7779fffd00fca5f1d3ae27761fa3b077d83d86d Mon Sep 17 00:00:00 2001 From: Renovate Date: Fri, 24 May 2024 22:42:27 +0300 Subject: [PATCH 01/18] Update dependency libbot to v3.1.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3258caf..4cd668b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ ujson>=5.0.0 uvloop==0.19.0 --extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple async_pymongo==0.1.4 -libbot[speed,pyrogram]==3.0.0 +libbot[speed,pyrogram]==3.1.0 pykeyboard==0.1.7 \ No newline at end of file -- 2.39.2 From b2f09339ee855bfa8ca66333389fee04b5ed21d0 Mon Sep 17 00:00:00 2001 From: Renovate Date: Sun, 26 May 2024 19:01:48 +0300 Subject: [PATCH 02/18] Update dependency libbot to v3.2.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4cd668b..df8be36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ ujson>=5.0.0 uvloop==0.19.0 --extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple async_pymongo==0.1.4 -libbot[speed,pyrogram]==3.1.0 +libbot[speed,pyrogram]==3.2.1 pykeyboard==0.1.7 \ No newline at end of file -- 2.39.2 From bebd6b4e4f304cd58694f9976ca00e46361079b6 Mon Sep 17 00:00:00 2001 From: Renovate Date: Sun, 26 May 2024 23:12:56 +0300 Subject: [PATCH 03/18] Update dependency libbot to v3.2.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index df8be36..26c241b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ ujson>=5.0.0 uvloop==0.19.0 --extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple async_pymongo==0.1.4 -libbot[speed,pyrogram]==3.2.1 +libbot[speed,pyrogram]==3.2.2 pykeyboard==0.1.7 \ No newline at end of file -- 2.39.2 From 2404ee909515ee3554c707640319ecb771f4c034 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 26 May 2024 22:56:11 +0200 Subject: [PATCH 04/18] Closes #48 --- classes/pyroclient.py | 44 ++++++++++++++++++++++++++++++++++++++++++- classes/updater.py | 44 +++++++++++++++++++++++++++++++++++++++++++ config_example.json | 6 ++++-- locale/de.json | 3 ++- locale/en.json | 3 ++- locale/ru.json | 3 ++- locale/uk.json | 3 ++- 7 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 classes/updater.py diff --git a/classes/pyroclient.py b/classes/pyroclient.py index adbcf21..85f5248 100644 --- a/classes/pyroclient.py +++ b/classes/pyroclient.py @@ -1,5 +1,8 @@ +import logging +from datetime import datetime, timedelta from typing import List, Union +from aiohttp import ClientSession from apscheduler.triggers.cron import CronTrigger from libbot.pyrogram.classes import PyroClient as LibPyroClient from pymongo import ASCENDING, GEOSPHERE, TEXT @@ -7,18 +10,32 @@ from pyrogram.types import User from classes.location import Location from classes.pyrouser import PyroUser +from classes.updater import Updater from modules.database_api import col_locations from modules.reminder import remind +logger = logging.getLogger(__name__) + class PyroClient(LibPyroClient): def __init__(self, **kwargs): + self.__version__ = (0, 1, 2) + super().__init__(**kwargs) + + self.updater = Updater(ClientSession()) + self.contexts = [] + if self.scheduler is not None: self.scheduler.add_job( remind, CronTrigger.from_crontab("* * * * *"), args=(self,) ) - self.contexts = [] + if self.config["update_checker"]: + self.scheduler.add_job( + self.check_updates, + CronTrigger.from_crontab("0 12 */3 * *"), + next_run_time=datetime.now() + timedelta(seconds=10), + ) async def start(self, **kwargs): await col_locations.create_index( @@ -31,6 +48,10 @@ class PyroClient(LibPyroClient): await col_locations.create_index([("name", TEXT)], name="location_name") return await super().start(**kwargs) + async def stop(self, **kwargs): + await self.updater.client_session.close() + await super().stop(**kwargs) + async def find_user(self, user: Union[int, User]) -> PyroUser: """Find User by it's ID or User object. @@ -68,3 +89,24 @@ class PyroClient(LibPyroClient): return [ await Location.get(record["id"]) async for record in col_locations.find({}) ] + + async def check_updates(self) -> None: + if await self.updater.check_updates( + self.__version__, self.config["strings"]["url_updater"] + ): + try: + release = await self.updater.get_latest_release( + self.config["strings"]["url_updater"] + ) + except Exception as exc: + logger.error("Could not fetch the latest version: %s", exc) + return + + await self.send_message( + self.owner, + self._("update_available", "messages").format( + version_current=f"v{'.'.join(str(subversion) for subversion in self.__version__)}", + version_new=release["tag_name"], + release_url=release["html_url"], + ), + ) diff --git a/classes/updater.py b/classes/updater.py new file mode 100644 index 0000000..0f06c59 --- /dev/null +++ b/classes/updater.py @@ -0,0 +1,44 @@ +import logging +from typing import Any, Dict, Tuple + +from aiohttp import ClientSession + +logger = logging.getLogger(__name__) + + +class Updater: + def __init__(self, client_session: ClientSession) -> None: + self.client_session: ClientSession = client_session + + async def check_updates( + self, version_current: Tuple[int, int, int], api_url: str + ) -> bool: + response = await self.client_session.get(api_url) + + if response.status != 200: + return False + + try: + version_latest = (await response.json())["tag_name"][1:].split(".") + except Exception as exc: + logger.error("Error parsing latest version: %s", exc) + return False + + return any( + version_current[index] < int(subversion) + for index, subversion in enumerate(version_latest) + ) + + async def get_latest_release(self, api_url: str) -> Dict[str, Any]: + response = await self.client_session.get(api_url) + + if response.status != 200: + raise RuntimeError(f"Could not fetch latest release: {response.status}") + + try: + return await response.json() + except Exception as exc: + logger.error("Error parsing latest release: %s", exc) + raise RuntimeError( + f"Error parsing latest release: {response.status}" + ) from exc diff --git a/config_example.json b/config_example.json index 3302d10..d13c4f9 100644 --- a/config_example.json +++ b/config_example.json @@ -32,6 +32,8 @@ "disabled_plugins": [], "strings": { "url_repo": "https://git.end-play.xyz/GarbageReminder/TelegramBot", - "url_contact": "https://git.end-play.xyz/GarbageReminder/TelegramBot/issues" - } + "url_contact": "https://git.end-play.xyz/GarbageReminder/TelegramBot/issues", + "url_updater": "https://git.end-play.xyz/api/v1/repos/GarbageReminder/TelegramBot/releases/latest" + }, + "update_checker": true } \ No newline at end of file diff --git a/locale/de.json b/locale/de.json index 51037a6..05115c7 100644 --- a/locale/de.json +++ b/locale/de.json @@ -78,7 +78,8 @@ "toggle_enabled": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time}. Verwenden Sie /setup, um Ihren Standort auszuwählen.", "toggle": "Führen Sie /toggle aus, um Benachrichtigungen zu aktivieren.", "upcoming_empty": "Keine Müllabfuhr-Einträge für die nächsten 30 Tage bei **{name}** gefunden", - "upcoming": "Bevorstehende Müllabfuhr:\n\n{entries}" + "upcoming": "Bevorstehende Müllabfuhr:\n\n{entries}", + "update_available": "Es gibt eine neue Version von GarbageBot!\n\nVersion: `{version_current}` -> `{version_new}`\n\n[Release-Seite]({release_url}) | [Update-Anleitung](https://garbagebot.eu/bot_telegram/upgrading)" }, "force_replies": { "import": "JSON mit Abfalltermine", diff --git a/locale/en.json b/locale/en.json index 3728b7d..f4df4b6 100644 --- a/locale/en.json +++ b/locale/en.json @@ -78,7 +78,8 @@ "toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.", "toggle": "Execute /toggle to enable notifications.", "upcoming_empty": "No garbage collection entries found for the next 30 days at **{name}**", - "upcoming": "Upcoming garbage collection:\n\n{entries}" + "upcoming": "Upcoming garbage collection:\n\n{entries}", + "update_available": "There is a new version of GarbageBot available!\n\nVersion: `{version_current}` -> `{version_new}`\n\n[Release page]({release_url}) | [Update instructions](https://garbagebot.eu/bot_telegram/upgrading)" }, "buttons": { "delete_confirm": "I agree and want to proceed", diff --git a/locale/ru.json b/locale/ru.json index dff6e2a..2d14cff 100644 --- a/locale/ru.json +++ b/locale/ru.json @@ -78,7 +78,8 @@ "toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.", "toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.", "upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**", - "upcoming": "Найближчі вивози сміття:\n\n{entries}" + "upcoming": "Найближчі вивози сміття:\n\n{entries}", + "update_available": "Доступна нова версія GarbageBot!\n\nВерсія: `{version_current}` -> `{version_new}`\n\n[Сторінка релізу]({release_url}) | [Інструкція з оновлення](https://garbagebot.eu/bot_telegram/upgrading)" }, "buttons": { "delete_confirm": "Я погоджуюсь і хочу продовжити", diff --git a/locale/uk.json b/locale/uk.json index 49c067e..513e748 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -78,7 +78,8 @@ "toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.", "toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.", "upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**", - "upcoming": "Найближчі вивози сміття:\n\n{entries}" + "upcoming": "Найближчі вивози сміття:\n\n{entries}", + "update_available": "Доступна нова версія GarbageBot!\n\nВерсія: `{version_current}` -> `{version_new}`\n\n[Сторінка релізу]({release_url}) | [Інструкція з оновлення](https://garbagebot.eu/bot_telegram/upgrading)" }, "buttons": { "delete_confirm": "Я погоджуюсь і хочу продовжити", -- 2.39.2 From e1a7b6309eb9c79f5aacb47d0cf70e0197fa8c67 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 26 May 2024 22:56:30 +0200 Subject: [PATCH 05/18] Closes #49 --- main.py | 8 +++++--- migrations/202405261500.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 migrations/202405261500.py diff --git a/main.py b/main.py index 6aa78c8..2cb0e19 100644 --- a/main.py +++ b/main.py @@ -35,14 +35,16 @@ with contextlib.suppress(ImportError): def main(): + if args.migrate: + migrate_database() + logger.info("Migration finished. Exiting...") + exit() + client = PyroClient( scheduler=scheduler, commands_source=sync.json_read(Path("commands.json")) ) Conversation(client) - if args.migrate: - migrate_database() - try: client.run() except KeyboardInterrupt: diff --git a/migrations/202405261500.py b/migrations/202405261500.py new file mode 100644 index 0000000..82521e2 --- /dev/null +++ b/migrations/202405261500.py @@ -0,0 +1,16 @@ +from libbot import sync +from mongodb_migrations.base import BaseMigration + + +class Migration(BaseMigration): + def upgrade(self): + sync.config_set("update_checker", True) + sync.config_set( + "url_updater", + "https://git.end-play.xyz/api/v1/repos/GarbageReminder/TelegramBot/releases/latest", + "strings", + ) + + def downgrade(self): + sync.config_delete("update_checker", missing_ok=True) + sync.config_delete("url_updater", "strings") -- 2.39.2 From f6731d5734c9c79a3f18c560e4bcc37b0b0811fc Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 12:51:47 +0200 Subject: [PATCH 06/18] Fixed downgrade for strings.url_updater config key --- migrations/202405261500.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/202405261500.py b/migrations/202405261500.py index 82521e2..b63e1d9 100644 --- a/migrations/202405261500.py +++ b/migrations/202405261500.py @@ -13,4 +13,4 @@ class Migration(BaseMigration): def downgrade(self): sync.config_delete("update_checker", missing_ok=True) - sync.config_delete("url_updater", "strings") + sync.config_delete("url_updater", "strings", missing_ok=True) -- 2.39.2 From b7fc1715fd389789b78660f83cc5347259e84a03 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 17:41:41 +0200 Subject: [PATCH 07/18] Added pytz as a dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 26c241b..93119a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ aiohttp~=3.9.5 apscheduler~=3.10.4 convopyro==0.5 mongodb-migrations==1.3.1 +pytz<=2023.2 tgcrypto==1.2.5 ujson>=5.0.0 uvloop==0.19.0 -- 2.39.2 From 94e229949d561c54a82648fab29564c2d659eb70 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 17:43:45 +0200 Subject: [PATCH 08/18] Added get_reminder_date() and get_reminder_time() methods; update_* methods now also change the attributes of the object --- classes/pyrouser.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/classes/pyrouser.py b/classes/pyrouser.py index abb4a50..38b3764 100644 --- a/classes/pyrouser.py +++ b/classes/pyrouser.py @@ -1,5 +1,6 @@ import logging from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union from bson import ObjectId @@ -88,26 +89,32 @@ class PyroUser: """ logger.debug("%s's locale has been set to %s", self.id, locale) await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}}) + self.locale = locale async def update_state(self, enabled: bool = False) -> None: logger.debug("%s's state has been set to %s", self.id, enabled) await col_users.update_one({"_id": self._id}, {"$set": {"enabled": enabled}}) + self.enabled = enabled async def update_location(self, location_id: int = 0) -> None: logger.debug("%s's location has been set to %s", self.id, location_id) await col_users.update_one( {"_id": self._id}, {"$set": {"location": location_id}} ) + self.location = await Location.get(location_id) async def update_offset(self, offset: int = 1) -> None: logger.debug("%s's offset has been set to %s", self.id, offset) await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}}) + self.offset = offset async def update_time(self, hour: int = 18, minute: int = 0) -> None: logger.debug("%s's time has been set to %s h. %s m.", self.id, hour, minute) await col_users.update_one( {"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}} ) + self.time_hour = hour + self.time_minute = minute async def delete(self) -> None: logger.debug("%s's data has been deleted", self.id) @@ -125,3 +132,60 @@ class PyroUser: del db_entry["_id"] # type: ignore return db_entry + + def get_reminder_date(self) -> datetime: + """Get next reminder date (year, month and day) + + ### Raises: + * `AttributeError`: Some attribute(s) are missing + + ### Returns: + * `datetime`: Datetime object of the next reminder date + """ + if self.location is None: + logger.warning( + "Could not get the reminder date for %s: User does not have some attribute(s) set", + self.id, + ) + raise AttributeError( + f"Could not get the reminder date for {self.id}: User does not have some attribute(s) set" + ) + + if not self.location.timezone: + logger.warning("Location %s does not have a timezone set", self.location.id) + + return ( + datetime.now(self.location.timezone or timezone.utc) + timedelta(days=1) + ).replace(hour=0, minute=0, second=0, microsecond=0) + + def get_reminder_time(self) -> datetime: + """Get reminder time (hour and minute) + + ### Raises: + * `AttributeError`: Some attribute(s) are missing + + ### Returns: + * `datetime`: Datetime object of the next reminder date + """ + if self.time_hour is None or self.time_minute is None or self.location is None: + logger.warning( + "Could not get the reminder time for %s: User does not have some attribute(s) set", + self.id, + ) + raise AttributeError( + f"Could not get the reminder time for {self.id}: User does not have some attribute(s) set" + ) + + if not self.location.timezone: + logger.warning("Location %s does not have a timezone set", self.location.id) + + return ( + datetime.now(timezone.utc) + .replace( + hour=self.time_hour, + minute=self.time_minute, + second=0, + microsecond=0, + ) + .astimezone(self.location.timezone or timezone.utc) + ) -- 2.39.2 From de483cd450dfa6c23593feb2a6899112f5f9d1c2 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 17:46:58 +0200 Subject: [PATCH 09/18] Default time is now 16:00 --- classes/pyrouser.py | 4 ++-- validation/examples/user.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/pyrouser.py b/classes/pyrouser.py index 38b3764..9353e6e 100644 --- a/classes/pyrouser.py +++ b/classes/pyrouser.py @@ -43,7 +43,7 @@ class PyroUser: enabled: bool = True, location_id: int = 0, offset: int = 1, - time_hour: int = 18, + time_hour: int = 16, time_minute: int = 0, ): db_entry = await col_users.find_one({"id": id}) @@ -108,7 +108,7 @@ class PyroUser: await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}}) self.offset = offset - async def update_time(self, hour: int = 18, minute: int = 0) -> None: + async def update_time(self, hour: int = 16, minute: int = 0) -> None: logger.debug("%s's time has been set to %s h. %s m.", self.id, hour, minute) await col_users.update_one( {"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}} diff --git a/validation/examples/user.json b/validation/examples/user.json index b4765c9..db15f1a 100644 --- a/validation/examples/user.json +++ b/validation/examples/user.json @@ -4,6 +4,6 @@ "enabled": true, "location": 1, "offset": 1, - "time_hour": 18, + "time_hour": 16, "time_minute": 0 } \ No newline at end of file -- 2.39.2 From b5bfbcd375d67fb37a4df72fb70cfae43623f0b4 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 17:47:23 +0200 Subject: [PATCH 10/18] Attempted a fix for #8 --- modules/reminder.py | 17 ++++++++++------- modules/utils.py | 8 ++++++-- plugins/commands/set_offset.py | 20 ++++++++++---------- plugins/commands/set_time.py | 23 ++++++++++++++--------- plugins/commands/setup.py | 12 ++++++------ plugins/commands/start.py | 18 ++++++++++-------- plugins/commands/toggle.py | 13 ++++++------- 7 files changed, 62 insertions(+), 49 deletions(-) diff --git a/modules/reminder.py b/modules/reminder.py index e2151e0..bd6b9aa 100644 --- a/modules/reminder.py +++ b/modules/reminder.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime, timedelta +from datetime import datetime, timezone from bson import json_util from libbot.pyrogram.classes import PyroClient @@ -9,13 +9,12 @@ from classes.location import Location from classes.pyrouser import PyroUser from modules.database import col_users from modules.database_api import col_entries -from modules.utils import from_utc logger = logging.getLogger(__name__) async def remind(app: PyroClient) -> None: - utcnow = datetime.utcnow() + utcnow = datetime.now(timezone.utc) logger.debug("Performing reminder lookup for %s (UTCNOW)", utcnow) @@ -40,12 +39,16 @@ async def remind(app: PyroClient) -> None: try: location: Location = await app.get_location(user.location.id) # type: ignore except ValueError: + logger.warning("Skipping reminder for %s due to invalid location", user.id) continue - user_date = from_utc( - datetime.utcnow() + timedelta(days=user.offset), - user.location.timezone.zone, - ).replace(hour=0, minute=0, second=0, microsecond=0) + try: + user_date = user.get_reminder_date() + except AttributeError: + logger.warning( + "Skipping reminder for %s due to missing attributes", user.id + ) + continue entries = await col_entries.find( { diff --git a/modules/utils.py b/modules/utils.py index aaece4b..13e1c96 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -6,7 +6,9 @@ from pytz import timezone as pytz_timezone def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: - """Move timezone unaware datetime object to UTC timezone and return it. + """*DEPRECATED AND WILL BE REMOVED IN FUTURE RELEASES* + + Move timezone unaware datetime object to UTC timezone and return it. ### Args: * date (`datetime`): Datetime to be converted. @@ -20,7 +22,9 @@ def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: - """Move timezone unaware datetime object to the timezone specified and return it. + """*DEPRECATED AND WILL BE REMOVED IN FUTURE RELEASES* + + Move timezone unaware datetime object to the timezone specified and return it. ### Args: * date (`datetime`): Datetime to be converted. diff --git a/plugins/commands/set_offset.py b/plugins/commands/set_offset.py index 30fce12..697c7ea 100644 --- a/plugins/commands/set_offset.py +++ b/plugins/commands/set_offset.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime +from datetime import datetime, timezone from convopyro import listen_message from pyrogram import filters @@ -7,7 +7,6 @@ from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove from classes.pyroclient import PyroClient from modules import custom_filters -from modules.utils import from_utc logger = logging.getLogger(__name__) @@ -65,18 +64,19 @@ async def command_set_offset(app: PyroClient, message: Message): logger.info("User %s has set offset to %s", user.id, offset) - garbage_time = from_utc( - datetime(1970, 1, 1, user.time_hour, user.time_minute), - None if user.location is None else user.location.timezone.zone, - ).strftime(app._("time", "formats")) + garbage_time = ( + datetime.now(timezone.utc) + .replace(hour=user.time_hour, minute=user.time_minute) + .astimezone(user.location.timezone or timezone.utc) + ) await answer.reply_text( app._("set_offset_finished", "messages", locale=user.locale).format( offset=offset, - time=garbage_time, - toggle_notice="" - if user.enabled - else app._("toggle", "messages", locale=user.locale), + time=garbage_time.strftime(app._("time", "formats", locale=user.locale)), + toggle_notice=( + "" if user.enabled else app._("toggle", "messages", locale=user.locale) + ), ), reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/set_time.py b/plugins/commands/set_time.py index 166b384..99c5fd7 100644 --- a/plugins/commands/set_time.py +++ b/plugins/commands/set_time.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime +from datetime import datetime, timezone from convopyro import listen_message from pyrogram import filters @@ -7,7 +7,6 @@ from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove from classes.pyroclient import PyroClient from modules import custom_filters -from modules.utils import to_utc logger = logging.getLogger(__name__) @@ -58,28 +57,34 @@ async def command_set_time(app: PyroClient, message: Message): now = datetime.now() parsed_time = datetime.strptime(answer.text, "%H:%M").replace( - year=now.year, month=now.month, day=now.day, second=0, microsecond=0 + year=now.year, + month=now.month, + day=now.day, + second=0, + microsecond=0, + tzinfo=timezone.utc, ) - user_time = to_utc(parsed_time, user.location.timezone.zone) + user_time = parsed_time.astimezone(user.location.timezone or timezone.utc) await user.update_time(hour=user_time.hour, minute=user_time.minute) logger.info( - "User %s has selected notification time of %s", + "User %s has selected notification time of %s (%s UTC)", user.id, + parsed_time.strftime("%H:%M"), user_time.strftime("%H:%M"), ) - garbage_time = parsed_time.strftime(app._("time", "formats")) + garbage_time = parsed_time.strftime(app._("time", "formats", locale=user.locale)) await answer.reply_text( app._("set_time_finished", "messages", locale=user.locale).format( offset=user.offset, time=garbage_time, - toggle_notice="" - if user.enabled - else app._("toggle", "messages", locale=user.locale), + toggle_notice=( + "" if user.enabled else app._("toggle", "messages", locale=user.locale) + ), ), reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/setup.py b/plugins/commands/setup.py index 3790831..174ec52 100644 --- a/plugins/commands/setup.py +++ b/plugins/commands/setup.py @@ -1,5 +1,4 @@ import logging -from datetime import datetime from convopyro import listen_message from libbot import i18n @@ -11,7 +10,6 @@ from classes.pyroclient import PyroClient from modules import custom_filters from modules.search_name import search_name from modules.search_nearby import search_nearby -from modules.utils import from_utc logger = logging.getLogger(__name__) @@ -74,10 +72,12 @@ async def command_setup(app: PyroClient, message: Message): await user.update_location(location.id) - user_time = from_utc( - datetime(1970, 1, 1, user.time_hour, user.time_minute), - None if user.location is None else user.location.timezone.zone, - ).strftime(app._("time", "formats", locale=user.locale)) + try: + user_time = user.get_reminder_time().strftime( + app._("time", "formats", locale=user.locale) + ) + except AttributeError: + user_time = "N/A" await message.reply_text( app._("setup_finished", "messages", locale=user.locale).format( diff --git a/plugins/commands/start.py b/plugins/commands/start.py index 12d2c6c..d77325d 100644 --- a/plugins/commands/start.py +++ b/plugins/commands/start.py @@ -1,5 +1,3 @@ -from datetime import datetime - from convopyro import listen_message from pyrogram import filters from pyrogram.types import ( @@ -11,7 +9,6 @@ from pyrogram.types import ( from classes.pyroclient import PyroClient from modules import custom_filters -from modules.utils import from_utc @PyroClient.on_message( @@ -91,13 +88,18 @@ async def command_start(app: PyroClient, message: Message): await user.update_location(location.id) - user_time = from_utc( - datetime(1970, 1, 1, user.time_hour, user.time_minute), - None if user.location is None else user.location.timezone.zone, - ).strftime(app._("time", "formats", locale=user.locale)) + try: + user_time = user.get_reminder_time().strftime( + app._("time", "formats", locale=user.locale) + ) + except AttributeError: + user_time = "N/A" + await answer.reply_text( app._("start_selection_yes", "messages", locale=user.locale).format( - name=location.name, offset=user.offset, time=user_time + name=location.name, + offset=user.offset, + time=user_time, ), reply_markup=ReplyKeyboardRemove(), ) diff --git a/plugins/commands/toggle.py b/plugins/commands/toggle.py index 1b67107..c19e232 100644 --- a/plugins/commands/toggle.py +++ b/plugins/commands/toggle.py @@ -1,11 +1,8 @@ -from datetime import datetime - from pyrogram import filters from pyrogram.types import Message from classes.pyroclient import PyroClient from modules import custom_filters -from modules.utils import from_utc @PyroClient.on_message( @@ -22,10 +19,12 @@ async def command_toggle(app: PyroClient, message: Message): ) return - user_time = from_utc( - datetime(1970, 1, 1, user.time_hour, user.time_minute), - None if user.location is None else user.location.timezone.zone, - ).strftime(app._("time", "formats")) + try: + user_time = user.get_reminder_time().strftime( + app._("time", "formats", locale=user.locale) + ) + except AttributeError: + user_time = "N/A" if user.location is None: await message.reply_text( -- 2.39.2 From 7293cafd2e2d11605e85c3e1b564240c6d205889 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:17:58 +0200 Subject: [PATCH 11/18] Removed unnecessary checks --- modules/reminder.py | 13 ++++--------- plugins/commands/setup.py | 9 +++------ plugins/commands/start.py | 9 +++------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/modules/reminder.py b/modules/reminder.py index bd6b9aa..aca992d 100644 --- a/modules/reminder.py +++ b/modules/reminder.py @@ -1,6 +1,7 @@ import logging -from datetime import datetime, timezone +from datetime import datetime +import pytz from bson import json_util from libbot.pyrogram.classes import PyroClient @@ -14,7 +15,7 @@ logger = logging.getLogger(__name__) async def remind(app: PyroClient) -> None: - utcnow = datetime.now(timezone.utc) + utcnow = datetime.now(pytz.utc) logger.debug("Performing reminder lookup for %s (UTCNOW)", utcnow) @@ -42,13 +43,7 @@ async def remind(app: PyroClient) -> None: logger.warning("Skipping reminder for %s due to invalid location", user.id) continue - try: - user_date = user.get_reminder_date() - except AttributeError: - logger.warning( - "Skipping reminder for %s due to missing attributes", user.id - ) - continue + user_date = user.get_reminder_date().replace(tzinfo=None) entries = await col_entries.find( { diff --git a/plugins/commands/setup.py b/plugins/commands/setup.py index 174ec52..3f60ef9 100644 --- a/plugins/commands/setup.py +++ b/plugins/commands/setup.py @@ -72,12 +72,9 @@ async def command_setup(app: PyroClient, message: Message): await user.update_location(location.id) - try: - user_time = user.get_reminder_time().strftime( - app._("time", "formats", locale=user.locale) - ) - except AttributeError: - user_time = "N/A" + user_time = user.get_reminder_time().strftime( + app._("time", "formats", locale=user.locale) + ) await message.reply_text( app._("setup_finished", "messages", locale=user.locale).format( diff --git a/plugins/commands/start.py b/plugins/commands/start.py index d77325d..af11347 100644 --- a/plugins/commands/start.py +++ b/plugins/commands/start.py @@ -88,12 +88,9 @@ async def command_start(app: PyroClient, message: Message): await user.update_location(location.id) - try: - user_time = user.get_reminder_time().strftime( - app._("time", "formats", locale=user.locale) - ) - except AttributeError: - user_time = "N/A" + user_time = user.get_reminder_time().strftime( + app._("time", "formats", locale=user.locale) + ) await answer.reply_text( app._("start_selection_yes", "messages", locale=user.locale).format( -- 2.39.2 From 0562521f0d6e019ebec97e86f493ed212e302298 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:18:33 +0200 Subject: [PATCH 12/18] Improved typing --- classes/callbacks.py | 2 +- classes/garbage_entry.py | 4 ++-- classes/location.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/classes/callbacks.py b/classes/callbacks.py index 0093c9b..4b111be 100644 --- a/classes/callbacks.py +++ b/classes/callbacks.py @@ -10,7 +10,7 @@ class CallbackLanguage: language: str @classmethod - def from_callback(cls, callback: CallbackQuery): + def from_callback(cls, callback: CallbackQuery) -> "CallbackLanguage": """Parse callback query and extract language data from it. ### Args: diff --git a/classes/garbage_entry.py b/classes/garbage_entry.py index 7c97b60..644c305 100644 --- a/classes/garbage_entry.py +++ b/classes/garbage_entry.py @@ -23,7 +23,7 @@ class GarbageEntry: date: datetime @classmethod - async def from_dict(cls, data: Mapping[str, Any]): + async def from_dict(cls, data: Mapping[str, Any]) -> "GarbageEntry": """Generate GarbageEntry object from the mapping provided ### Args: @@ -60,7 +60,7 @@ class GarbageEntry: ) @classmethod - async def from_record(cls, data: Mapping[str, Any]): + async def from_record(cls, data: Mapping[str, Any]) -> "GarbageEntry": locations = [ await Location.get(location_id) for location_id in data["locations"] ] diff --git a/classes/location.py b/classes/location.py index 5a29557..57497a3 100644 --- a/classes/location.py +++ b/classes/location.py @@ -28,7 +28,7 @@ class Location: timezone: Union[BaseTzInfo, DstTzInfo] @classmethod - async def get(cls, id: int): + async def get(cls, id: int) -> "Location": db_entry = await col_locations.find_one({"id": id}) if db_entry is None: @@ -40,7 +40,7 @@ class Location: return cls(**db_entry) @classmethod - async def find(cls, name: str): + async def find(cls, name: str) -> "Location": db_entry = await col_locations.find_one({"name": {"$regex": name}}) if db_entry is None: @@ -52,7 +52,7 @@ class Location: return cls(**db_entry) @classmethod - async def nearby(cls, lat: float, lon: float): + async def nearby(cls, lat: float, lon: float) -> "Location": db_entry = await col_locations.find_one({"location": {"$near": [lon, lat]}}) if db_entry is None: -- 2.39.2 From e307d60e8e96ddeedd47ae41a37526c4126ee0ee Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:18:58 +0200 Subject: [PATCH 13/18] pytz.utc instead of datetime.timezone.utc --- plugins/commands/set_offset.py | 7 ++++--- plugins/commands/upcoming.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/commands/set_offset.py b/plugins/commands/set_offset.py index 697c7ea..4fd2557 100644 --- a/plugins/commands/set_offset.py +++ b/plugins/commands/set_offset.py @@ -1,6 +1,7 @@ import logging -from datetime import datetime, timezone +from datetime import datetime +import pytz from convopyro import listen_message from pyrogram import filters from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove @@ -65,9 +66,9 @@ async def command_set_offset(app: PyroClient, message: Message): logger.info("User %s has set offset to %s", user.id, offset) garbage_time = ( - datetime.now(timezone.utc) + datetime.now(pytz.utc) .replace(hour=user.time_hour, minute=user.time_minute) - .astimezone(user.location.timezone or timezone.utc) + .astimezone(user.location.timezone or pytz.utc) ) await answer.reply_text( diff --git a/plugins/commands/upcoming.py b/plugins/commands/upcoming.py index 881958d..e34e79c 100644 --- a/plugins/commands/upcoming.py +++ b/plugins/commands/upcoming.py @@ -1,5 +1,6 @@ -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta +import pytz from pyrogram import filters from pyrogram.types import Message @@ -23,11 +24,11 @@ async def command_upcoming(app: PyroClient, message: Message): date_min = ( datetime.now(user.location.timezone).replace(second=0, microsecond=0) - ).replace(tzinfo=timezone.utc) + ).replace(tzinfo=pytz.utc) date_max = ( datetime.now(user.location.timezone).replace(second=0, microsecond=0) + timedelta(days=30) - ).replace(tzinfo=timezone.utc) + ).replace(tzinfo=pytz.utc) entries = [ await GarbageEntry.from_record(entry) -- 2.39.2 From 1c76c8d91169401cbc752a4c614940d4df1d1bb4 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:22:29 +0200 Subject: [PATCH 14/18] Added automatic timezone update on location change; Update methods now return changed values --- classes/pyrouser.py | 68 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/classes/pyrouser.py b/classes/pyrouser.py index 9353e6e..2a91a8a 100644 --- a/classes/pyrouser.py +++ b/classes/pyrouser.py @@ -1,8 +1,9 @@ import logging from dataclasses import dataclass -from datetime import datetime, timedelta, timezone -from typing import Any, Union +from datetime import datetime, timedelta +from typing import Any, Mapping, Tuple, Union +import pytz from bson import ObjectId from classes.location import Location @@ -45,7 +46,7 @@ class PyroUser: offset: int = 1, time_hour: int = 16, time_minute: int = 0, - ): + ) -> "PyroUser": db_entry = await col_users.find_one({"id": id}) if db_entry is None: @@ -73,7 +74,7 @@ class PyroUser: return cls(**db_entry) @classmethod - async def from_dict(cls, **kwargs): + async def from_dict(cls, **kwargs) -> "PyroUser": if "location" in kwargs: try: kwargs["location"] = await Location.get(kwargs["location"]) # type: ignore @@ -81,46 +82,85 @@ class PyroUser: kwargs["location"] = None # type: ignore return cls(**kwargs) - async def update_locale(self, locale: Union[str, None]) -> None: + async def update_locale(self, locale: Union[str, None]) -> Union[str, None]: """Change user's locale stored in the database. ### Args: * locale (`Union[str, None]`): New locale to be set. """ + logger.debug("%s's locale has been set to %s", self.id, locale) + await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}}) + self.locale = locale - async def update_state(self, enabled: bool = False) -> None: + return self.locale + + async def update_state(self, enabled: bool = False) -> bool: logger.debug("%s's state has been set to %s", self.id, enabled) + await col_users.update_one({"_id": self._id}, {"$set": {"enabled": enabled}}) + self.enabled = enabled - async def update_location(self, location_id: int = 0) -> None: + return self.enabled + + async def update_location(self, location_id: int = 0) -> Location: logger.debug("%s's location has been set to %s", self.id, location_id) + await col_users.update_one( {"_id": self._id}, {"$set": {"location": location_id}} ) - self.location = await Location.get(location_id) - async def update_offset(self, offset: int = 1) -> None: + location = await Location.get(location_id) + + # Execute if timezones of old and new locations are different + if self.location and (self.location.timezone.zone != location.timezone.zone): + # Get UTC time for selected reminder time + now_utc = datetime.now(pytz.utc).replace( + hour=self.time_hour, minute=self.time_minute, second=0, microsecond=0 + ) + + # Get the time for the reminder time of old and new location + local_old = now_utc.astimezone(self.location.timezone) + local_new = ( + location.timezone.localize(local_old.replace(tzinfo=None)) + ).astimezone(pytz.utc) + + # Update the time to match the new timezone + await self.update_time(hour=local_new.hour, minute=local_new.minute) + + self.location = location + + return self.location + + async def update_offset(self, offset: int = 1) -> int: logger.debug("%s's offset has been set to %s", self.id, offset) + await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}}) + self.offset = offset - async def update_time(self, hour: int = 16, minute: int = 0) -> None: + return offset + + async def update_time(self, hour: int = 16, minute: int = 0) -> Tuple[int, int]: logger.debug("%s's time has been set to %s h. %s m.", self.id, hour, minute) + await col_users.update_one( {"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}} ) + self.time_hour = hour self.time_minute = minute + return self.time_hour, self.time_minute + async def delete(self) -> None: logger.debug("%s's data has been deleted", self.id) await col_users.delete_one({"_id": self._id}) - async def checkout(self) -> Any: + async def checkout(self) -> Mapping[str, Any]: logger.debug("%s's data has been checked out", self.id) db_entry = await col_users.find_one({"_id": self._id}) @@ -155,7 +195,7 @@ class PyroUser: logger.warning("Location %s does not have a timezone set", self.location.id) return ( - datetime.now(self.location.timezone or timezone.utc) + timedelta(days=1) + datetime.now(self.location.timezone or pytz.utc) + timedelta(days=1) ).replace(hour=0, minute=0, second=0, microsecond=0) def get_reminder_time(self) -> datetime: @@ -180,12 +220,12 @@ class PyroUser: logger.warning("Location %s does not have a timezone set", self.location.id) return ( - datetime.now(timezone.utc) + datetime.now(pytz.utc) .replace( hour=self.time_hour, minute=self.time_minute, second=0, microsecond=0, ) - .astimezone(self.location.timezone or timezone.utc) + .astimezone(self.location.timezone or pytz.utc) ) -- 2.39.2 From 65e9e830c18e60a046aac492a267cb1ac190643e Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:23:07 +0200 Subject: [PATCH 15/18] Fixed possible bugs and renamed pytz imports --- modules/utils.py | 12 +++++------- plugins/commands/set_time.py | 27 ++++++++++++--------------- plugins/commands/upcoming.py | 8 ++++---- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/modules/utils.py b/modules/utils.py index 13e1c96..19b0586 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,8 +1,7 @@ from datetime import datetime from typing import Union -from pytz import UTC -from pytz import timezone as pytz_timezone +import pytz def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: @@ -18,7 +17,9 @@ def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: * `datetime`: Timezone unaware datetime in UTC with timezone's offset applied to it. """ timezone = "UTC" if timezone is None else timezone - return pytz_timezone(timezone).localize(date).astimezone(UTC).replace(tzinfo=None) + return ( + pytz.timezone(timezone).localize(date).astimezone(pytz.utc).replace(tzinfo=None) + ) def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: @@ -35,8 +36,5 @@ def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: """ timezone = "UTC" if timezone is None else timezone return ( - pytz_timezone("UTC") - .localize(date) - .astimezone(pytz_timezone(timezone)) - .replace(tzinfo=None) + pytz.utc.localize(date).astimezone(pytz.timezone(timezone)).replace(tzinfo=None) ) diff --git a/plugins/commands/set_time.py b/plugins/commands/set_time.py index 99c5fd7..3d190e6 100644 --- a/plugins/commands/set_time.py +++ b/plugins/commands/set_time.py @@ -1,6 +1,7 @@ import logging -from datetime import datetime, timezone +from datetime import datetime +import pytz from convopyro import listen_message from pyrogram import filters from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove @@ -54,34 +55,30 @@ async def command_set_time(app: PyroClient, message: Message): break - now = datetime.now() + # Time we got from the user + parsed_time = datetime.strptime(answer.text, "%H:%M") - parsed_time = datetime.strptime(answer.text, "%H:%M").replace( - year=now.year, - month=now.month, - day=now.day, - second=0, - microsecond=0, - tzinfo=timezone.utc, + # Datetime user means in their timezone + user_time = datetime.now(user.location.timezone).replace( + hour=parsed_time.hour, minute=parsed_time.minute, second=0, microsecond=0 ) - user_time = parsed_time.astimezone(user.location.timezone or timezone.utc) + # Datetime in user's timezone moved to UTC timezone + utc_time = user_time.astimezone(pytz.utc) - await user.update_time(hour=user_time.hour, minute=user_time.minute) + await user.update_time(hour=utc_time.hour, minute=utc_time.minute) logger.info( "User %s has selected notification time of %s (%s UTC)", user.id, - parsed_time.strftime("%H:%M"), user_time.strftime("%H:%M"), + utc_time.strftime("%H:%M"), ) - garbage_time = parsed_time.strftime(app._("time", "formats", locale=user.locale)) - await answer.reply_text( app._("set_time_finished", "messages", locale=user.locale).format( offset=user.offset, - time=garbage_time, + time=user_time.strftime(app._("time", "formats", locale=user.locale)), toggle_notice=( "" if user.enabled else app._("toggle", "messages", locale=user.locale) ), diff --git a/plugins/commands/upcoming.py b/plugins/commands/upcoming.py index e34e79c..9ede51a 100644 --- a/plugins/commands/upcoming.py +++ b/plugins/commands/upcoming.py @@ -22,13 +22,13 @@ async def command_upcoming(app: PyroClient, message: Message): ) return - date_min = ( + date_min = pytz.utc.localize( datetime.now(user.location.timezone).replace(second=0, microsecond=0) - ).replace(tzinfo=pytz.utc) - date_max = ( + ) + date_max = pytz.utc.localize( datetime.now(user.location.timezone).replace(second=0, microsecond=0) + timedelta(days=30) - ).replace(tzinfo=pytz.utc) + ) entries = [ await GarbageEntry.from_record(entry) -- 2.39.2 From 3f20fdb46a4aa3feabea746dc1fe294c429cd4b7 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:24:12 +0200 Subject: [PATCH 16/18] Removed wrong localize --- plugins/commands/upcoming.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/commands/upcoming.py b/plugins/commands/upcoming.py index 9ede51a..e34e79c 100644 --- a/plugins/commands/upcoming.py +++ b/plugins/commands/upcoming.py @@ -22,13 +22,13 @@ async def command_upcoming(app: PyroClient, message: Message): ) return - date_min = pytz.utc.localize( + date_min = ( datetime.now(user.location.timezone).replace(second=0, microsecond=0) - ) - date_max = pytz.utc.localize( + ).replace(tzinfo=pytz.utc) + date_max = ( datetime.now(user.location.timezone).replace(second=0, microsecond=0) + timedelta(days=30) - ) + ).replace(tzinfo=pytz.utc) entries = [ await GarbageEntry.from_record(entry) -- 2.39.2 From 99d621d90f581d71e28c9b7ab022a8b7b587e9c7 Mon Sep 17 00:00:00 2001 From: Renovate Date: Fri, 31 May 2024 00:28:46 +0300 Subject: [PATCH 17/18] Update dependency pytz to v2024 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 93119a6..0facec4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ aiohttp~=3.9.5 apscheduler~=3.10.4 convopyro==0.5 mongodb-migrations==1.3.1 -pytz<=2023.2 +pytz<=2024.1 tgcrypto==1.2.5 ujson>=5.0.0 uvloop==0.19.0 -- 2.39.2 From 04ee8e9c608e7efbe6a47ebe142f9fa744f52e3d Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 30 May 2024 23:41:52 +0200 Subject: [PATCH 18/18] Improved docstrings and set some logging events to INFO level instead of DEBUG --- classes/pyroclient.py | 1 + classes/pyrouser.py | 63 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/classes/pyroclient.py b/classes/pyroclient.py index 85f5248..118654a 100644 --- a/classes/pyroclient.py +++ b/classes/pyroclient.py @@ -91,6 +91,7 @@ class PyroClient(LibPyroClient): ] async def check_updates(self) -> None: + """Check for updates and send a message to the owner if newer version was found""" if await self.updater.check_updates( self.__version__, self.config["strings"]["url_updater"] ): diff --git a/classes/pyrouser.py b/classes/pyrouser.py index 2a91a8a..6ad38c3 100644 --- a/classes/pyrouser.py +++ b/classes/pyrouser.py @@ -88,8 +88,7 @@ class PyroUser: ### Args: * locale (`Union[str, None]`): New locale to be set. """ - - logger.debug("%s's locale has been set to %s", self.id, locale) + logger.info("%s's locale has been set to %s", self.id, locale) await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}}) @@ -98,7 +97,15 @@ class PyroUser: return self.locale async def update_state(self, enabled: bool = False) -> bool: - logger.debug("%s's state has been set to %s", self.id, enabled) + """Update user's state (enabled/disabled) + + ### Args: + * enabled (`bool`, *optional*): Whether the user is enabled. Defaults to `False`. + + ### Returns: + * `bool`: User's current state + """ + logger.info("%s's state has been set to %s", self.id, enabled) await col_users.update_one({"_id": self._id}, {"$set": {"enabled": enabled}}) @@ -106,8 +113,16 @@ class PyroUser: return self.enabled - async def update_location(self, location_id: int = 0) -> Location: - logger.debug("%s's location has been set to %s", self.id, location_id) + async def update_location(self, location_id: int) -> Location: + """Update user's location and move their time to the new timezone (if the user had a location set previously) + + ### Args: + * location_id (`int`): ID of the location + + ### Returns: + `Location`: New location + """ + logger.info("%s's location has been set to %s", self.id, location_id) await col_users.update_one( {"_id": self._id}, {"$set": {"location": location_id}} @@ -136,7 +151,15 @@ class PyroUser: return self.location async def update_offset(self, offset: int = 1) -> int: - logger.debug("%s's offset has been set to %s", self.id, offset) + """Update the offset of the reminder (in days) + + ### Args: + * offset (`int`, *optional*): Offset in days. Defaults to `1`. + + ### Returns: + * `int`: Offset in days + """ + logger.info("%s's offset has been set to %s", self.id, offset) await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}}) @@ -145,7 +168,16 @@ class PyroUser: return offset async def update_time(self, hour: int = 16, minute: int = 0) -> Tuple[int, int]: - logger.debug("%s's time has been set to %s h. %s m.", self.id, hour, minute) + """Update the time of the reminder (hour and minute, for UTC timezone) + + ### Args: + * hour (`int`, *optional*): Hour of the reminder. Defaults to `16`. + * minute (`int`, *optional*): Minute of the reminder. Defaults to `0`. + + ### Returns: + * `Tuple[int, int]`: Hour and minute of the reminder + """ + logger.info("%s's time has been set to %s h. %s m.", self.id, hour, minute) await col_users.update_one( {"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}} @@ -157,16 +189,27 @@ class PyroUser: return self.time_hour, self.time_minute async def delete(self) -> None: - logger.debug("%s's data has been deleted", self.id) + """Delete the database record of the user""" + logger.info("%s's data has been deleted", self.id) + await col_users.delete_one({"_id": self._id}) async def checkout(self) -> Mapping[str, Any]: - logger.debug("%s's data has been checked out", self.id) + """Checkout the user's database record + + ### Raises: + * `KeyError`: Database record of the user was not found + + ### Returns: + * `Mapping[str, Any]`: Database record + """ + logger.info("%s's data has been checked out", self.id) + db_entry = await col_users.find_one({"_id": self._id}) if db_entry is None: raise KeyError( - f"DB record with id {self._id} of user {self.id} is not found" + f"DB record with id {self._id} of user {self.id} was not found" ) del db_entry["_id"] # type: ignore -- 2.39.2