From 8172f9c1cc2bce8bbe341975cec262b27d11bfea Mon Sep 17 00:00:00 2001 From: Erik Vroon Date: Sun, 30 Jan 2022 13:57:03 +0100 Subject: [PATCH] Add `GithubNotificationsControl` --- devdeck/assets/font-awesome/github.png | Bin 0 -> 13164 bytes .../controls/github_notifications_control.py | 77 ++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 devdeck/assets/font-awesome/github.png create mode 100644 devdeck/controls/github_notifications_control.py diff --git a/devdeck/assets/font-awesome/github.png b/devdeck/assets/font-awesome/github.png new file mode 100644 index 0000000000000000000000000000000000000000..9870e2f21b6db782022ae28883e406fed4315b27 GIT binary patch literal 13164 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 literal 0 HcmV?d00001 diff --git a/devdeck/controls/github_notifications_control.py b/devdeck/controls/github_notifications_control.py new file mode 100644 index 0000000..2e3f57b --- /dev/null +++ b/devdeck/controls/github_notifications_control.py @@ -0,0 +1,77 @@ +import os +import threading +from subprocess import Popen, DEVNULL +from time import sleep +from typing import Any, Dict + +import requests as requests +from devdeck_core.controls.deck_control import DeckControl + + +class GithubNotificationsControl(DeckControl): + def __init__(self, key_no: int, **kwargs) -> None: + super().__init__(key_no, **kwargs) + self.thread = None + self.running = False + self.last_url = None + + def initialize(self) -> None: + self.thread = threading.Thread(target=self._update_loop) + self.running = True + self.thread.start() + + def pressed(self) -> None: + if self.last_url is None: + self._update_display() + return + + browser = self.settings.get('browser') or 'firefox' + Popen([browser, self.last_url], stdout=DEVNULL, stderr=DEVNULL) + + # Wait 5 seconds for the browser to load the page before refreshing the display. + sleep(5) + self._update_display() + + def get_notifications(self) -> Dict[str, Any]: + assert self.settings['token'], 'Please specify your Github API token in `settings.yml`' + headers = { + 'Authorization': f"token {self.settings['token']}", + 'User-Agent': 'devdeck', + } + return requests.get('https://api.github.com/notifications', headers=headers).json() + + def _update_display(self) -> None: + notifications = self.get_notifications() + count = len(notifications) + alert_color = self.settings.get('color') or '#ff2b2b' + color = alert_color if count > 0 else '#ffffff' + + self.last_url = notifications[0]['subject']['url'] \ + .replace('api.', '').replace('repos/', '').replace('pulls/', 'pull/') \ + if count > 0 else None + + with self.deck_context() as context: + with context.renderer() as r: + r.image(os.path.join(os.path.dirname(__file__), "../assets/font-awesome", 'github.png')) \ + .width(240) \ + .height(240) \ + .center_horizontally() \ + .y(225) \ + .end() + + r.text(str(count)) \ + .center_horizontally() \ + .center_vertically(-175) \ + .font_size(150) \ + .color(color) \ + .end() + + def _update_loop(self) -> None: + while self.running is True: + self._update_display() + sleep(self.settings.get('refresh_seconds') or 60) + + def dispose(self) -> None: + self.running = False + if self.thread: + self.thread.join()