update_notifier.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
  2. # █▀█ █ █ █ █▀█ █▀▄ █
  3. # © Copyright 2022
  4. # https://t.me/hikariatama
  5. #
  6. # 🔒 Licensed under the GNU AGPLv3
  7. # 🌐 https://www.gnu.org/licenses/agpl-3.0.html
  8. import asyncio
  9. import contextlib
  10. import git
  11. from .. import loader, utils, version
  12. from ..inline.types import InlineCall
  13. @loader.tds
  14. class UpdateNotifierMod(loader.Module):
  15. """Tracks latest Hikka releases, and notifies you, if update is required"""
  16. strings = {
  17. "name": "UpdateNotifier",
  18. "update_required": (
  19. "🌘 <b>Hikka Update available!</b>\n\nNew Hikka version released.\n🔮"
  20. " <b>Hikka <s>{}</s> -> {}</b>\n\n{}"
  21. ),
  22. "more": "\n<i><b>🎥 And {} more...</b></i>",
  23. }
  24. strings_ru = {
  25. "update_required": (
  26. "🌘 <b>Доступно обновление Hikka!</b>\n\nОпубликована новая версия Hikka.\n🔮"
  27. " <b>Hikka <s>{}</s> -> {}</b>\n\n{}"
  28. ),
  29. "more": "\n<i><b>🎥 И еще {}...</b></i>",
  30. }
  31. _notified = None
  32. def __init__(self):
  33. self.config = loader.ModuleConfig(
  34. loader.ConfigValue(
  35. "disable_notifications",
  36. doc=lambda: "Disable update notifications",
  37. validator=loader.validators.Boolean(),
  38. )
  39. )
  40. def get_changelog(self) -> str:
  41. try:
  42. repo = git.Repo()
  43. for remote in repo.remotes:
  44. remote.fetch()
  45. if not (
  46. diff := repo.git.log([f"HEAD..origin/{version.branch}", "--oneline"])
  47. ):
  48. return False
  49. except Exception:
  50. return False
  51. res = "\n".join(
  52. f"<b>{commit.split()[0]}</b>:"
  53. f" <i>{utils.escape_html(' '.join(commit.split()[1:]))}</i>"
  54. for commit in diff.splitlines()[:10]
  55. )
  56. if diff.count("\n") >= 10:
  57. res += self.strings("more").format(len(diff.splitlines()) - 10)
  58. return res
  59. def get_latest(self) -> str:
  60. try:
  61. return list(
  62. git.Repo().iter_commits(f"origin/{version.branch}", max_count=1)
  63. )[0].hexsha
  64. except Exception:
  65. return ""
  66. async def client_ready(self):
  67. try:
  68. git.Repo()
  69. except Exception as e:
  70. raise loader.LoadError("Can't load due to repo init error") from e
  71. self._markup = self.inline.generate_markup(
  72. [
  73. {"text": "🔄 Update", "data": "hikka_update"},
  74. {"text": "🚫 Ignore", "data": "hikka_upd_ignore"},
  75. ]
  76. )
  77. self.poller.start()
  78. @loader.loop(interval=60)
  79. async def poller(self):
  80. if self.config["disable_notifications"] or not self.get_changelog():
  81. return
  82. self._pending = self.get_latest()
  83. if (
  84. self.get("ignore_permanent", False)
  85. and self.get("ignore_permanent") == self._pending
  86. ):
  87. await asyncio.sleep(60)
  88. return
  89. if self._pending not in [utils.get_git_hash(), self._notified]:
  90. m = await self.inline.bot.send_message(
  91. self.tg_id,
  92. self.strings("update_required").format(
  93. utils.get_git_hash()[:6],
  94. '<a href="https://github.com/hikariatama/Hikka/compare/{}...{}">{}</a>'
  95. .format(
  96. utils.get_git_hash()[:12],
  97. self.get_latest()[:12],
  98. self.get_latest()[:6],
  99. ),
  100. self.get_changelog(),
  101. ),
  102. disable_web_page_preview=True,
  103. reply_markup=self._markup,
  104. )
  105. self._notified = self._pending
  106. self.set("ignore_permanent", False)
  107. await self._delete_all_upd_messages()
  108. self.set("upd_msg", m.message_id)
  109. async def _delete_all_upd_messages(self):
  110. for client in self.allclients:
  111. with contextlib.suppress(Exception):
  112. await client.loader.inline.bot.delete_message(
  113. client.tg_id,
  114. client.loader._db.get("UpdateNotifierMod", "upd_msg"),
  115. )
  116. @loader.callback_handler()
  117. async def update(self, call: InlineCall):
  118. """Process update buttons clicks"""
  119. if call.data not in {"hikka_update", "hikka_upd_ignore"}:
  120. return
  121. if call.data == "hikka_upd_ignore":
  122. self.set("ignore_permanent", self.get_latest())
  123. await call.answer("Notifications about the latest have been suppressed")
  124. return
  125. await self._delete_all_upd_messages()
  126. with contextlib.suppress(Exception):
  127. await call.delete()
  128. await self.allmodules.commands["update"](
  129. await self._client.send_message(
  130. self.inline.bot_username,
  131. f"<code>{self.get_prefix()}update --force</code>",
  132. )
  133. )