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": "Я погоджуюсь і хочу продовжити",