update_notifier.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # ©️ Dan Gazizullin, 2021-2023
  2. # This file is a part of Hikka Userbot
  3. # 🌐 https://github.com/hikariatama/Hikka
  4. # You can redistribute it and/or modify it under the terms of the GNU AGPLv3
  5. # 🔑 https://www.gnu.org/licenses/agpl-3.0.html
  6. import asyncio
  7. import contextlib
  8. import git
  9. from .. import loader, utils, version
  10. from ..inline.types import InlineCall
  11. @loader.tds
  12. class UpdateNotifier(loader.Module):
  13. """Tracks latest Hikka releases, and notifies you, if update is required"""
  14. strings = {"name": "UpdateNotifier"}
  15. def __init__(self):
  16. self._notified = None
  17. self.config = loader.ModuleConfig(
  18. loader.ConfigValue(
  19. "disable_notifications",
  20. doc=lambda: self.strings("_cfg_doc_disable_notifications"),
  21. validator=loader.validators.Boolean(),
  22. )
  23. )
  24. def get_changelog(self) -> str:
  25. try:
  26. repo = git.Repo()
  27. for remote in repo.remotes:
  28. remote.fetch()
  29. if not (
  30. diff := repo.git.log([f"HEAD..origin/{version.branch}", "--oneline"])
  31. ):
  32. return False
  33. except Exception:
  34. return False
  35. res = "\n".join(
  36. f"<b>{commit.split()[0]}</b>:"
  37. f" <i>{utils.escape_html(' '.join(commit.split()[1:]))}</i>"
  38. for commit in diff.splitlines()[:10]
  39. )
  40. if diff.count("\n") >= 10:
  41. res += self.strings("more").format(len(diff.splitlines()) - 10)
  42. return res
  43. def get_latest(self) -> str:
  44. try:
  45. return next(
  46. git.Repo().iter_commits(f"origin/{version.branch}", max_count=1)
  47. ).hexsha
  48. except Exception:
  49. return ""
  50. async def client_ready(self):
  51. try:
  52. git.Repo()
  53. except Exception as e:
  54. raise loader.LoadError("Can't load due to repo init error") from e
  55. self._markup = lambda: self.inline.generate_markup(
  56. [
  57. {"text": self.strings("update"), "data": "hikka/update"},
  58. {"text": self.strings("ignore"), "data": "hikka/ignore_upd"},
  59. ]
  60. )
  61. @loader.loop(interval=60, autostart=True)
  62. async def poller(self):
  63. if self.config["disable_notifications"] or not self.get_changelog():
  64. return
  65. self._pending = self.get_latest()
  66. if (
  67. self.get("ignore_permanent", False)
  68. and self.get("ignore_permanent") == self._pending
  69. ):
  70. await asyncio.sleep(60)
  71. return
  72. if self._pending not in {utils.get_git_hash(), self._notified}:
  73. m = await self.inline.bot.send_animation(
  74. self.tg_id,
  75. "https://t.me/hikari_assets/71",
  76. caption=self.strings("update_required").format(
  77. utils.get_git_hash()[:6],
  78. '<a href="https://github.com/hikariatama/Hikka/compare/{}...{}">{}</a>'
  79. .format(
  80. utils.get_git_hash()[:12],
  81. self.get_latest()[:12],
  82. self.get_latest()[:6],
  83. ),
  84. self.get_changelog(),
  85. ),
  86. reply_markup=self._markup(),
  87. )
  88. self._notified = self._pending
  89. self.set("ignore_permanent", False)
  90. await self._delete_all_upd_messages()
  91. self.set("upd_msg", m.message_id)
  92. async def _delete_all_upd_messages(self):
  93. for client in self.allclients:
  94. with contextlib.suppress(Exception):
  95. await client.loader.inline.bot.delete_message(
  96. client.tg_id,
  97. client.loader.db.get("UpdateNotifier", "upd_msg"),
  98. )
  99. @loader.callback_handler()
  100. async def update(self, call: InlineCall):
  101. """Process update buttons clicks"""
  102. if call.data not in {"hikka/update", "hikka/ignore_upd"}:
  103. return
  104. if call.data == "hikka/ignore_upd":
  105. self.set("ignore_permanent", self.get_latest())
  106. await call.answer(self.strings("latest_disabled"))
  107. return
  108. await self._delete_all_upd_messages()
  109. with contextlib.suppress(Exception):
  110. await call.delete()
  111. await self.invoke("update", "-f", peer=self.inline.bot_username)