SGTools Notifier

Notifies the user when a SGTools rules check is complete and optionally redirects to the giveaway.

  1. // ==UserScript==
  2. // @name SGTools Notifier
  3. // @namespace https://rafaelgssa.gitlab.io/monkey-scripts
  4. // @version 5.0.1
  5. // @author rafaelgssa
  6. // @description Notifies the user when a SGTools rules check is complete and optionally redirects to the giveaway.
  7. // @match https://www.sgtools.info/*
  8. // @match https://www.steamgifts.com/giveaway/*
  9. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  10. // @require https://greasyfork.org/scripts/405813-monkey-utils/code/Monkey%20Utils.js?version=821710
  11. // @require https://greasyfork.org/scripts/405802-monkey-dom/code/Monkey%20DOM.js?version=821769
  12. // @require https://greasyfork.org/scripts/405831-monkey-storage/code/Monkey%20Storage.js?version=821709
  13. // @require https://greasyfork.org/scripts/405840-monkey-wizard/code/Monkey%20Wizard.js?version=821711
  14. // @run-at document-idle
  15. // @grant GM.info
  16. // @grant GM.setValue
  17. // @grant GM.getValue
  18. // @grant GM.deleteValue
  19. // @grant GM_info
  20. // @grant GM_setValue
  21. // @grant GM_getValue
  22. // @grant GM_deleteValue
  23. // @noframes
  24. // ==/UserScript==
  25.  
  26. /* global DOM, PersistentStorage, SettingsWizard */
  27.  
  28. (async () => {
  29. 'use strict';
  30.  
  31. const scriptId = 'sgtn';
  32. const scriptName = GM.info.script.name;
  33.  
  34. const schemas = /** @type {WizardSchema[]} */ ([
  35. {
  36. type: 'multi',
  37. id: 'doRedirect',
  38. message: 'Do you want to be redirected to the giveaway when the check is complete?',
  39. defaultValue: false,
  40. choices: [
  41. {
  42. id: 'y',
  43. template: "'%' for yes",
  44. value: true,
  45. },
  46. {
  47. id: 'n',
  48. template: "'%' for no",
  49. value: false,
  50. },
  51. ],
  52. },
  53. ]);
  54.  
  55. const defaultValues = /** @type {StorageValues} */ ({
  56. settings: Object.fromEntries(schemas.map((schema) => [schema.id, schema.defaultValue])),
  57. });
  58.  
  59. const isInSgTools = window.location.hostname === 'www.sgtools.info';
  60.  
  61. let doRedirect = false;
  62.  
  63. /** @type {HTMLElement | null} */
  64. let checkButton;
  65.  
  66. /**
  67. * Loads the script.
  68. * @returns {Promise<void>}
  69. */
  70. const load = async () => {
  71. if (!isInSgTools) {
  72. return removePageUrlFragment();
  73. }
  74. checkButton = document.querySelector('#check');
  75. if (!checkButton) {
  76. return;
  77. }
  78. doRedirect = /** @type {boolean} */ (await PersistentStorage.getSetting('doRedirect'));
  79. checkButton.addEventListener('click', waitForRulesCheck);
  80. };
  81.  
  82. /**
  83. * Removes the fragment from the page URL and notifies the user if exists.
  84. */
  85. const removePageUrlFragment = () => {
  86. if (window.location.hash !== `#${scriptId}`) {
  87. return;
  88. }
  89. window.history.replaceState(
  90. '',
  91. document.title,
  92. `${window.location.origin}${window.location.pathname}${window.location.search}`
  93. );
  94. notifyUser(true);
  95. };
  96.  
  97. /**
  98. * Waits until the check is complete and notifies the user.
  99. * @returns {Promise<void>}
  100. */
  101. const waitForRulesCheck = async () => {
  102. if (!checkButton) {
  103. return;
  104. }
  105. const element = await DOM.dynamicQuerySelector('#getlink, #error_alert:not(.hidden)', 60 * 30);
  106. if (!element) {
  107. // Rules have not been checked after 30 minutes.
  108. return;
  109. }
  110. if (!element.matches('#getlink')) {
  111. // User failed to pass the rules.
  112. return notifyUser(false);
  113. }
  114. if (!doRedirect) {
  115. return notifyUser(true);
  116. }
  117. checkButton.removeEventListener('click', waitForRulesCheck);
  118. checkButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
  119. return waitForGiveawayLink();
  120. };
  121.  
  122. /**
  123. * Waits for the giveaway link, redirects to the giveaway and notifies the user.
  124. * @returns {Promise<void>}
  125. */
  126. const waitForGiveawayLink = async () => {
  127. const link = /** @type {HTMLAnchorElement | undefined} */ (await DOM.dynamicQuerySelector(
  128. '#gaurl a'
  129. ));
  130. if (link) {
  131. window.location.href = `${link.href}#${scriptId}`;
  132. } else {
  133. notifyUser(true);
  134. }
  135. };
  136.  
  137. /**
  138. * Notifies the user.
  139. * @param {boolean} isSuccess Whether the user passed the rules or not.
  140. */
  141. const notifyUser = (isSuccess) => {
  142. const [emoji, message] = isSuccess
  143. ? ['✔️', 'You passed the rules!']
  144. : ['❌', 'You failed to pass the rules.'];
  145. if (isInSgTools) {
  146. document.title = `${emoji} ${document.title}`;
  147. }
  148. if (document.hidden) {
  149. // Only show a browser notification if the user is away from the tab.
  150. showBrowserNotification(`${emoji} ${message}`);
  151. }
  152. };
  153.  
  154. /**
  155. * Shows a browser notification.
  156. * @param {string} body The message to show.
  157. * @return {Promise<void>}
  158. */
  159. const showBrowserNotification = async (body) => {
  160. if (Notification.permission !== 'granted') {
  161. await Notification.requestPermission();
  162. }
  163. if (Notification.permission === 'granted') {
  164. new Notification(scriptName, { body });
  165. }
  166. };
  167.  
  168. try {
  169. await PersistentStorage.init(scriptId, defaultValues);
  170. await SettingsWizard.init(scriptId, scriptName, schemas);
  171. await load();
  172. } catch (err) {
  173. console.log(`Failed to load ${scriptName}: `, err);
  174. }
  175. })();