Flying OC Alert

Blocks you from flying if your OC is about to start.

当前为 2025-05-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Flying OC Alert
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0.7
  5. // @description Blocks you from flying if your OC is about to start.
  6. // @author NichtGersti [3380912]
  7. // @license MIT
  8. // @match https://www.torn.com/page.php?sid=travel
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
  10.  
  11. // ==/UserScript==
  12.  
  13.  
  14. (function() {
  15. 'use strict';
  16. window.addEventListener('load', () => {
  17. let potentialError = document.querySelector("#skip-to-content")?.textContent?.trim()
  18. if (potentialError == "Error") {
  19. console.warn("[Flying OC Alert] You're probably racing. Aborting.");
  20. return;
  21. }
  22.  
  23. let apiKey = localStorage.getItem("nichtgersti-flying-oc-alert-api-key") ?? '###PDA-APIKEY###'; //Minimal access or above!
  24. let threshold = localStorage.getItem("nichtgersti-flying-oc-alert-threshold") ?? 10;
  25. let confirmMethod = localStorage.getItem("nichtgersti-flying-oc-alert-method") ?? "default";
  26. let confirmPrompt = localStorage.getItem("nichtgersti-flying-oc-alert-prompt") ?? "continue";
  27.  
  28. let ocUrl = "https://api.torn.com/v2/user/?selections=organizedcrime&key={apiKey}".replace("{apiKey}", apiKey);
  29.  
  30.  
  31. let solution = Math.floor(Math.random() * 100);
  32. let addend1 = Math.floor(Math.random() * (solution - 1));
  33. let addend2 = Math.floor(solution - addend1);
  34.  
  35. let confirmTask = undefined;
  36. let confirmData = undefined;
  37. if (confirmMethod == "prompt") {
  38. confirmTask = confirmPrompt;
  39. confirmData = confirmPrompt;
  40. }
  41. else if (confirmMethod == "addition") {
  42. confirmTask = `${addend1}+${addend2}`;
  43. confirmData = solution.toString();
  44. }
  45.  
  46. let alertDiv = document.createElement("div");
  47. alertDiv.id = "flying-oc-alert";
  48. alertDiv.classList.add("info-msg-cont", "border-round", "m-top10", "green");
  49. alertDiv.innerHTML = `
  50. <div class="info-msg border-round messageWrap___phpSP">
  51. <i class="infoIcon___GLFcq"></i>
  52. <div class="delimiter">
  53. <div class="msg right-round messageContent___LhCmx">
  54. <div style="display:flex;justify-content: space-between;align-items: center;">
  55. <span style="display:inline-block;vertical-align:middle">Time until your Organized Crime is ready: <span id="flying-oc-alert-timer">Check your API key.</span></span>
  56. <div id="flying-oc-settings-button" style="display:inline-block;float:right">
  57. <i class="fa fa-cog" aria-hidden="true"></i>
  58. </div>
  59. </div>
  60. <div id="flying-oc-confirm-div" hidden>
  61. ${buildConfirm(confirmMethod, confirmTask, confirmData)}
  62. </div>
  63. <div id="flying-oc-settings" hidden>
  64. ${buildSettings()}
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. `;
  70. let wrapper = document.querySelector("#travel-root .wrapper");
  71. wrapper.insertBefore(alertDiv, wrapper.lastChild);
  72. let confirmButton = document.querySelector("#flying-oc-confirm-button");
  73. confirmButton.addEventListener("click", () => pressConfirm());
  74. let settingsButton = document.querySelector("#flying-oc-settings-button");
  75. settingsButton.addEventListener("click", () => openSettings());
  76. let settingsSaveButton = document.querySelector("#flying-oc-settings-save-button");
  77. settingsSaveButton.addEventListener("click", () => saveSettings());
  78.  
  79. fetch(ocUrl).then( response => {
  80. if (response.ok) {
  81. return response.json();
  82. }
  83. throw new Error('Something went wrong');
  84. })
  85. .then( result => {
  86. if (result.error) {
  87. switch (result.error.code){
  88. case 2:
  89. apiKey = null;
  90. localStorage.setItem("nichtgersti-flying-oc-alert-api", null);
  91. console.error("[Flying OC Alert] Incorrect Api Key:", result);
  92. return;
  93. case 9:
  94. console.warn("[Flying OC Alert] The API is temporarily disabled, please try again later");
  95. return;
  96. default:
  97. console.error("[Flying OC Alert] Error:", result.error.error);
  98. return;
  99. }
  100. }
  101.  
  102. let infoOc = result.organizedCrime;
  103. let timeString;
  104. if (!infoOc || infoOc.status != "Planning") {
  105. timeString = "You're currently not planning an Organized Crime.";
  106. document.querySelector("#flying-oc-alert-timer").textContent = timeString;
  107. return;
  108. }
  109.  
  110.  
  111. let totalSeconds = infoOc.ready_at - Math.floor(Date.now() / 1000);
  112. if ((totalSeconds / 3600) < threshold) {
  113. document.querySelector("#flying-oc-alert").classList.add("red");
  114. document.querySelector("#flying-oc-alert").classList.remove("green");
  115. document.querySelector("#flying-oc-confirm-div").hidden = false;
  116. setHideFlying(true);
  117. }
  118.  
  119. timeString = createTimerString(totalSeconds);
  120. document.querySelector("#flying-oc-alert-timer").textContent = createTimerString(totalSeconds);
  121.  
  122. setInterval((function() {
  123. let seconds = infoOc.ready_at - Math.floor(Date.now() / 1000);
  124. document.querySelector("#flying-oc-alert-timer").textContent = createTimerString(seconds);
  125. }), 10000);
  126.  
  127. })
  128. .catch(error => console.error("[Flying OC Alert] Error:", error));
  129.  
  130. function setHideFlying(hide = true) {
  131. let travelTypeSelector = document.querySelector(".travelTypeSelector___zK5N4") || undefined;
  132. if (travelTypeSelector && hide) travelTypeSelector.style.display = "none";
  133. else if (travelTypeSelector) travelTypeSelector.style.removeProperty("display");
  134.  
  135. let worldMap = document.querySelector(".worldMap___SvXMZ") || undefined;
  136. if (worldMap && hide) worldMap.style.display = "none";
  137. else if (worldMap) worldMap.style.removeProperty("display");
  138.  
  139. let destinationPanel = document.querySelector(".destinationPanel___LsJ4v") || undefined;
  140. if (destinationPanel && hide) destinationPanel.style.display = "none";
  141. else if (destinationPanel) destinationPanel.style.removeProperty("display");
  142.  
  143. let destinationList = document.querySelector(".destinationList___fx7Gb") || undefined;
  144. if (destinationList && hide) destinationList.style.display = "none";
  145. else if (destinationList) destinationList.style.removeProperty("display");
  146. }
  147.  
  148. function createTimerString(totalSeconds) {
  149. if (totalSeconds < 0) {
  150. return `Your OC is being delayed by someone else!`;
  151. }
  152.  
  153. if (totalSeconds < 60) {
  154. return `Less than 1 minute!`;
  155. }
  156.  
  157. let days = Math.floor(totalSeconds / 86400);
  158. let hours = Math.floor(totalSeconds / 3600) % 24;
  159. let minutes = Math.floor(totalSeconds / 60) % 60;
  160. let seconds = totalSeconds % 60;
  161.  
  162. let timerString = "";
  163. if (totalSeconds > 86400) timerString += `${days}d `;
  164. if (totalSeconds > 3600) timerString += `${hours}h `;
  165. if (totalSeconds > 60) timerString += `${minutes}m`;
  166. return timerString;
  167. }
  168.  
  169. function buildConfirm(method = confirmMethod, task = undefined, data = undefined) {
  170. let confirmText = "Confirm to unlock traveling.";
  171.  
  172. if (method == "prompt") confirmText = `Write "${task}" to confirm.`;
  173. else if (method == "addition") confirmText = `Solve "${task}" to confirm.`;
  174.  
  175. return `
  176. <hr style="margin-top:10px;margin-bottom:10px">
  177. <div style="display:flex;justify-content: space-between;align-items: center;">
  178. <span style="display:inline-block;vertical-align:middle">${confirmText}</span>
  179. <div style="display:flex;justify-content: space-between;">
  180. <input type="text" id="flying-oc-confirm-input" name="flying-oc-confirm-input"${method == "default" ? " hidden" : ""} data="${data}">
  181. <div id="flying-oc-confirm-button" class="btn torn-btn btn-action-tab btn-dark-bg">
  182. Confirm
  183. </div>
  184. </div>
  185. </div>
  186. `;
  187.  
  188. }
  189.  
  190. function buildSettings() {
  191. return `
  192. <hr style="margin-top:10px;margin-bottom:10px">
  193. <div style="display:flex;justify-content: space-between;align-items: center;">
  194. <div style="display:inline-block;vertical-align:middle">
  195. <label for="flying-oc-api-key">API Key (Minimal Access):</label>
  196. <input type="text" id="flying-oc-api-key" name="flying-oc-api-key" value="${apiKey}"><br><br>
  197. <label for="flying-oc-threshold">Threshold (in hours):</label>
  198. <input type="text" id="flying-oc-threshold" name="flying-oc-threshold" value="${threshold}"><br><br>
  199. <label for="flying-oc-method">Confirmation Method:</label>
  200. <select id="flying-oc-method" name="flying-oc-method">
  201. <option value="default"${confirmMethod == "default" ? " selected" : ""}>Default</option>
  202. <option value="prompt"${confirmMethod == "prompt" ? " selected" : ""}>Prompt</option>
  203. <option value="addition"${confirmMethod == "addition" ? " selected" : ""}>Addition</option>
  204. </select><br><br>
  205. <label for="flying-oc-prompt">Confirmation Prompt (only if "Prompt" is selected):</label>
  206. <input type="text" id="flying-oc-prompt" name="flying-oc-confirm-prompt" value="${confirmPrompt}">
  207. </div>
  208. <div id="flying-oc-settings-save-button" class="btn torn-btn btn-action-tab btn-dark-bg" style="display:inline-block;float:right">
  209. Save
  210. </div>
  211. </div>
  212. `
  213. };
  214.  
  215. function openSettings(open = !document.querySelector("#flying-oc-settings").hidden) {
  216. document.querySelector("#flying-oc-settings").hidden = open;
  217. }
  218.  
  219. function saveSettings() {
  220. let newApiKey = document.querySelector("#flying-oc-api-key").value;
  221. if (newApiKey.length == 16) {
  222. apiKey = newApiKey;
  223. localStorage.setItem("nichtgersti-flying-oc-alert-api-key", apiKey);
  224. } else document.querySelector("#flying-oc-api-key").value = apiKey;
  225.  
  226. let newThreshold = Number.parseInt(document.querySelector("#flying-oc-threshold").value);
  227. if (newThreshold) {
  228. threshold = newThreshold;
  229. localStorage.setItem("nichtgersti-flying-oc-alert-threshold", threshold);
  230. } else document.querySelector("#flying-oc-threshold").value = threshold;
  231.  
  232. confirmMethod = document.querySelector("#flying-oc-method").selectedOptions[0].value;
  233. localStorage.setItem("nichtgersti-flying-oc-alert-method", confirmMethod);
  234.  
  235. confirmPrompt = document.querySelector("#flying-oc-prompt").value;
  236. localStorage.setItem("nichtgersti-flying-oc-alert-prompt", confirmPrompt);
  237.  
  238. console.log("[Flying OC Alert] Settings saved.");
  239. }
  240.  
  241. function pressConfirm() {
  242. let confirmInput = document.querySelector("#flying-oc-confirm-input");
  243. if (confirmInput.getAttribute("data") != "undefined" && confirmInput.value != confirmInput.getAttribute("data")) return;
  244. setHideFlying(false);
  245. document.querySelector("#flying-oc-confirm-div").hidden = true;
  246. }
  247. })
  248. })();