Greasy Fork is available in English.

GitHub Notifier

Notifies the user whenever they have new unread notifications on GitHub.

  1. // ==UserScript==
  2. // @name GitHub Notifier
  3. // @namespace https://rafaelgssa.gitlab.io/monkey-scripts
  4. // @version 5.0.0
  5. // @author rafaelgssa
  6. // @description Notifies the user whenever they have new unread notifications on GitHub.
  7. // @match https://github.com/*
  8. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  9. // @require https://greatest.deepsurf.us/scripts/405813-monkey-utils/code/Monkey%20Utils.js?version=821710
  10. // @require https://greatest.deepsurf.us/scripts/405802-monkey-dom/code/Monkey%20DOM.js?version=821769
  11. // @require https://greatest.deepsurf.us/scripts/405935-iwc/code/IWC.js?version=819599
  12. // @run-at document-end
  13. // @grant GM.info
  14. // @grant GM_info
  15. // @noframes
  16. // ==/UserScript==
  17.  
  18. /* global DOM, SJ */
  19.  
  20. (() => {
  21. 'use strict';
  22.  
  23. const scriptId = 'ghn';
  24. const scriptName = GM.info.script.name;
  25.  
  26. const unreadIndicator = '(New) ';
  27.  
  28. let doShowBrowserNotifications = false;
  29.  
  30. let hasChecked = false;
  31.  
  32. let hasUnread = false;
  33.  
  34. /**
  35. * Loads the script.
  36. */
  37. const load = () => {
  38. const indicatorEl = document.querySelector('.mail-status');
  39. if (!indicatorEl) {
  40. // User is not logged in.
  41. return;
  42. }
  43. SJ.lock(`${scriptId}_lock_browserNotifications`, onBrowserNotificationsLock); // Locks browser notifications so that only one tab shows them.
  44. check(indicatorEl);
  45. DOM.observeNode(indicatorEl, { attributes: true }, /** @type {NodeCallback} */ (check));
  46. };
  47.  
  48. /**
  49. * Triggered when browser notifications are locked.
  50. */
  51. const onBrowserNotificationsLock = () => {
  52. console.log(`[${scriptName}] Locked browser notifications!`);
  53. doShowBrowserNotifications = true;
  54. };
  55.  
  56. /**
  57. * Checks for unread notifications and notifies the user.
  58. * @param {Element} indicatorEl The element that indicates the notification status.
  59. */
  60. const check = (indicatorEl) => {
  61. const newValue = indicatorEl.classList.contains('unread');
  62. if (hasUnread !== newValue) {
  63. hasUnread = newValue;
  64. notifyUser();
  65. }
  66. hasChecked = true;
  67. };
  68.  
  69. /**
  70. * Notifies the user.
  71. */
  72. const notifyUser = () => {
  73. if (hasUnread) {
  74. if (!document.title.startsWith(unreadIndicator)) {
  75. document.title = `${unreadIndicator}${document.title}`;
  76. }
  77. } else if (document.title.startsWith(unreadIndicator)) {
  78. document.title = document.title.slice(unreadIndicator.length);
  79. }
  80. if (doShowBrowserNotifications && hasChecked && hasUnread && document.hidden) {
  81. // Only show a browser notification for subsequent unread notifications if the user is away from the tab.
  82. showBrowserNotification('You have new unread notifications.');
  83. }
  84. };
  85.  
  86. /**
  87. * Shows a browser notification.
  88. * @param {string} body The message to show.
  89. * @return {Promise<void>}
  90. */
  91. const showBrowserNotification = async (body) => {
  92. if (Notification.permission !== 'granted') {
  93. await Notification.requestPermission();
  94. }
  95. if (Notification.permission === 'granted') {
  96. new Notification(scriptName, { body });
  97. }
  98. };
  99.  
  100. try {
  101. load();
  102. } catch (err) {
  103. console.log(`Failed to load ${scriptName}: `, err);
  104. }
  105. })();