Hide Crime Outcome

Hides the crime outcome panel for quick clicking. Quick and dirty script

当前为 2024-06-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Hide Crime Outcome
  3. // @namespace dev.kwack.torn.hide-crime-results
  4. // @version 2.2.8
  5. // @description Hides the crime outcome panel for quick clicking. Quick and dirty script
  6. // @author Kwack [2190604]
  7. // @match https://www.torn.com/loader.php?sid=crimes*
  8. // @grant GM_addStyle
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant unsafeWindow
  12. // @run-at document-end
  13. // ==/UserScript==
  14.  
  15. // I hope you like spaghetti 🍝
  16. // Special shoutout to Spec [3118077] for the request, the minimal mode, and for some good testing.
  17.  
  18. (() => {
  19. const SVG_SETTINGS = `<svg xmlns="http://www.w3.org/2000/svg" fill="currentcolor" stroke="transparent" stroke-width="0" width="15" height="15" viewBox="0 0 23 23"><path d="M24 13.616v-3.232c-1.651-.587-2.694-.752-3.219-2.019v-.001c-.527-1.271.1-2.134.847-3.707l-2.285-2.285c-1.561.742-2.433 1.375-3.707.847h-.001c-1.269-.526-1.435-1.576-2.019-3.219h-3.232c-.582 1.635-.749 2.692-2.019 3.219h-.001c-1.271.528-2.132-.098-3.707-.847l-2.285 2.285c.745 1.568 1.375 2.434.847 3.707-.527 1.271-1.584 1.438-3.219 2.02v3.232c1.632.58 2.692.749 3.219 2.019.53 1.282-.114 2.166-.847 3.707l2.285 2.286c1.562-.743 2.434-1.375 3.707-.847h.001c1.27.526 1.436 1.579 2.019 3.219h3.232c.582-1.636.75-2.69 2.027-3.222h.001c1.262-.524 2.12.101 3.698.851l2.285-2.286c-.744-1.563-1.375-2.433-.848-3.706.527-1.271 1.588-1.44 3.221-2.021zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"></path></svg>`;
  20. const SVG_ARROW = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="44" viewBox="0 0 18 44"><path d="M0,44,15,22h3L3,44ZM15,22,0,0H3L18,22Z"></path></svg>`;
  21. const MODES = [
  22. {
  23. name: "Disabled",
  24. img: "https://raw.githubusercontent.com/Kwack-Kwack/scripts/main/static/images/hide-player-filters_mode-disabled.gif",
  25. description: "Disables the script, showing the full crime outcome as normal.",
  26. color: "#777",
  27. },
  28. {
  29. name: "Hidden",
  30. img: "https://raw.githubusercontent.com/Kwack-Kwack/scripts/main/static/images/hide-player-filters_mode-hidden.gif",
  31. description: "Hides the crime outcome content completely, ideal for quickly spamming crimes.",
  32. color: "red",
  33. },
  34. {
  35. name: "Minimal",
  36. img: "https://raw.githubusercontent.com/Kwack-Kwack/scripts/main/static/images/hide-player-filters_mode-minimal.gif",
  37. description: "Hides only the story text, but keeps the important information.",
  38. color: "orange",
  39. },
  40. {
  41. name: "Toast",
  42. img: "https://raw.githubusercontent.com/Kwack-Kwack/scripts/main/static/images/hide-player-filters_mode-toast.gif",
  43. description: "Shows a small toast notification in the bottom right corner with the outcome",
  44. color: "green",
  45. },
  46. // More coming soon...
  47. ];
  48.  
  49. const mutationCallback = () => {
  50. const header = $("div.crimes-app > div[class*=appHeader_]");
  51. if (header && !header?.find?.("a#kw--crimes-settings-btn")[0]) addSettingsIcon(header);
  52.  
  53. if (!$("#kw--crimes-settings")[0]) addSettingsElement();
  54. // debugger;
  55. };
  56. new MutationObserver(mutationCallback).observe($("div#react-root")[0], { childList: true, subtree: true });
  57.  
  58. const addSettingsIcon = (header) => {
  59. if (!header || !(header instanceof $)) return;
  60. const existing = header.find("a");
  61. header
  62. .children()
  63. .first()
  64. .after(
  65. $("<a/>", {
  66. class: existing.attr("class"),
  67. id: "kw--crimes-settings-btn",
  68. style: "color: var(--kw--icon-color)",
  69. })
  70. .append(SVG_SETTINGS)
  71. .append(new Text("Hide Outcome"))
  72. .on("click", () => $("#kw--crimes-settings").removeClass("kw-hide"))
  73. );
  74. };
  75.  
  76. const addSettingsElement = () => {
  77. let modeIndex = getSetting("mode") ?? 0;
  78. setMode(modeIndex); // Will trigger the class change
  79.  
  80. const changeModeIndex = (increase) => {
  81. if (increase) {
  82. modeIndex === MODES.length - 1 ? (modeIndex = 0) : modeIndex++;
  83. } else {
  84. modeIndex === 0 ? (modeIndex = MODES.length - 1) : modeIndex--;
  85. }
  86.  
  87. $("#kw--crimes-slider").css("transform", `translateX(-${modeIndex * 100}%)`);
  88. };
  89. $("body").append(
  90. $("<div/>", { id: "kw--crimes-settings", class: "kw-hide" })
  91. .append(
  92. $("<div/>")
  93. .append(
  94. $("<h1/>", { text: "Hide Crime Outcome" }),
  95. $("<div/>", {
  96. id: "kw--crimes-slider",
  97. style: `transform: translateX(-${modeIndex * 100}%)`,
  98. }).append(...MODES.map((mode) => generateSliderPage(mode, changeModeIndex))),
  99. $("<button/>", { id: "kw--crimes-settings-save" })
  100. .append("Save")
  101. .on("click", () => {
  102. $("#kw--crimes-settings").addClass("kw-hide");
  103. setMode(modeIndex);
  104. })
  105. )
  106. .on("click", (e) => e.stopPropagation())
  107. )
  108. .on("click", () => $("#kw--crimes-settings").addClass("kw-hide"))
  109. );
  110. };
  111.  
  112. const getSetting = (key) => GM_getValue(`kw.hide-outcome.settings.${key}`) ?? 0;
  113. const setSetting = (key, value) => GM_setValue(`kw.hide-outcome.settings.${key}`, value);
  114.  
  115. const setMode = (modeIndex) => {
  116. if (typeof modeIndex !== "number") {
  117. const parsed = parseInt(modeIndex);
  118. if (isNaN(parsed)) {
  119. console.error(`Invalid modeIndex ${modeIndex}, defaulting to 0`);
  120. modeIndex = 0;
  121. } else modeIndex = parsed;
  122. }
  123. if (modeIndex < 0 || modeIndex >= MODES.length) {
  124. console.error(`Out of bounds modeIndex ${modeIndex}, defaulting to 0`);
  125. modeIndex = 0;
  126. }
  127. setSetting("mode", modeIndex);
  128. $("body").data("kw--crimes-mode", modeIndex);
  129. $("body").removeClass((_, c) =>
  130. c
  131. .split(" ")
  132. .filter((c) => c.startsWith("kw--crimes-mode-"))
  133. .join(" ")
  134. );
  135. $("body").addClass("kw--crimes-mode-" + MODES[modeIndex].name.toLowerCase());
  136. };
  137.  
  138. const generateSliderPage = ({ img, name, color }, changeModeIndex) =>
  139. $(`<div/>`, { class: "kw--crimes-slider-page" }).append(
  140. $("<button/>", { style: "transform: scaleX(-1)" })
  141. .on("click", () => changeModeIndex(true))
  142. .append($(SVG_ARROW)),
  143. $("<div/>").append(
  144. $("<h2/>", { text: name, style: `color: ${color}` }),
  145. $("<img/>", { src: img, alt: name })
  146. ),
  147. $("<button/>")
  148. .on("click", () => changeModeIndex(false))
  149. .append($(SVG_ARROW))
  150. );
  151.  
  152. const addToastContainer = () => $("body").append($("<div/>", { id: "kw--crimes-toast-container" }));
  153. const showToast = (type, msg) => {
  154. console.log({ type, msg });
  155. let el;
  156. $("#kw--crimes-toast-container").append(
  157. (el = $("<div/>", {
  158. class: `kw--crimes-toast kw--crimes-toast-${type}`,
  159. text: `${type?.toUpperCase()} - ${msg}`,
  160. }))
  161. );
  162. setTimeout(el.remove.bind(el), 5000);
  163. };
  164.  
  165. const fetchInjection = (oldFetch) => {
  166. // Yes this seems like a stupid way of doing it, but it's consistent on iOS devices, unlike typeof unsafeWindow.
  167. let win;
  168. try {
  169. win = unsafeWindow || window;
  170. } catch {} // do nothing
  171. (win || window).fetch = (...args) =>
  172. new Promise((resolve, reject) => {
  173. oldFetch
  174. .apply(this, args)
  175. .then((r) => {
  176. try {
  177. const url = new URL(r.url);
  178. if ($(document.body).data("kw--crimes-mode") !== 3) return resolve(r);
  179. if (
  180. url.pathname === "/loader.php" &&
  181. url.searchParams.get("sid") === "crimesData" &&
  182. url.searchParams.get("step") === "attempt"
  183. ) {
  184. r.clone()
  185. .json()
  186. .then((data) => {
  187. const outcome = data?.DB?.outcome;
  188. showToast(
  189. outcome.result?.replaceAll(" ", ""),
  190. outcome?.rewards?.map((r) => stringifyReward(r)).join(", ") ||
  191. "No reward detected"
  192. );
  193. });
  194. }
  195. resolve(r);
  196. } catch (e) {
  197. console.error(`kw--hide-crime-outcome fetchInject error ${e.toString()}`);
  198. resolve(r); // Resolve the original response - this error is an error in the intercept, not the request
  199. }
  200. })
  201. .catch(reject); // Reject with original error
  202. });
  203.  
  204. function stringifyReward(reward) {
  205. switch (reward.type.toLowerCase()) {
  206. // MISSING: Losing an item critfail
  207. case "money":
  208. return reward.value ? `$${reward.value}` : "Issue parsing money amount";
  209. case "jail":
  210. case "hospital":
  211. return reward.value
  212. ? `${reward.type === "jail" ? "Jailed" : "Hosped"} until ${new Date(
  213. reward.value * 1000
  214. ).toLocaleTimeString()}`
  215. : `Issue parsing ${reward.type === "jail" ? "Jailed" : "Hosped"} time`;
  216. case "items":
  217. return Array.isArray(reward.value)
  218. ? reward.value.map(({ name, amount }) => `${amount}x ${name}`).join(", ")
  219. : `Issue parsing reward "${reward.type}"`;
  220. case "ammo":
  221. return reward.value.name && reward.value.amount
  222. ? `${reward.value.amount}x ${reward.value.name}`
  223. : `Issue parsing ammo reward`;
  224. case "other":
  225. return reward.textTablet || reward.text || "Unknown other reward text";
  226. case "injury": return reward.value ? `Lost ${reward.value} life` : "Issue parsing injury amount";
  227. default:
  228. console.warn(`Unexpected reward type ${reward.type} for reward ${JSON.stringify(reward)}`);
  229. return `Unknown reward type ${reward.type}`;
  230. }
  231. }
  232. };
  233.  
  234. const addStyle = (modes) => {
  235. const modeStyles = modes
  236. .map(
  237. (m) => `body.kw--crimes-mode-${m.name.toLowerCase()} {
  238. --kw--icon-color: ${m.color};
  239. }`
  240. )
  241. .join("\n\n");
  242. GM_addStyle(modeStyles);
  243. GM_addStyle(`
  244. #kw--crimes-settings {
  245. position: fixed;
  246. top: 0;
  247. right: 0;
  248. left: 0;
  249. bottom: 0;
  250. z-index: 99998;
  251. background: rgba(0, 0, 0, 0.3);
  252. }
  253.  
  254. /* All buttons except the arrow buttons */
  255. #kw--crimes-settings button {
  256. color: var(--btn-color);
  257. background: var(--btn-background);
  258. cursor: pointer;
  259. padding: 0.5em;
  260. }
  261.  
  262. #kw--crimes-settings button:hover {
  263. color: var(--btn-hover-color);
  264. background: var(--btn-hover-background);
  265. }
  266.  
  267. #kw--crimes-settings > div {
  268. margin: 14vh auto 0;
  269. width: 90vw;
  270. max-width: 600px;
  271. background: var(--chat-box-bg);
  272. z-index: 99999;
  273. display: flex;
  274. flex-direction: column;
  275. gap: 1rem;
  276. border-radius: 1rem;
  277. overflow: clip;
  278. }
  279.  
  280. /* Reset weird TORN css */
  281. #kw--crimes-settings h1, #kw--crimes-settings h2 {
  282. margin: 0;
  283. padding: 0.5em;
  284. text-align: center;
  285. }
  286.  
  287. #kw--crimes-settings h1 {
  288. font-size: 2rem;
  289. border-bottom: 3px solid var(--panel-border-bottom-color);
  290. }
  291.  
  292. #kw--crimes-settings #kw--crimes-slider {
  293. margin: 0;
  294. width: 100%;
  295. display: flex;
  296. /* overflow-x: clip; */
  297. }
  298.  
  299. #kw--crimes-settings #kw--crimes-slider > div.kw--crimes-slider-page {
  300. width: 100%;
  301. flex-shrink: 0;
  302. display: flex;
  303. justify-content: space-between;
  304. gap: 1rem;
  305. }
  306.  
  307. #kw--crimes-settings #kw--crimes-slider > div.kw--crimes-slider-page > button {
  308. background: transparent;
  309. }
  310.  
  311. #kw--crimes-settings #kw--crimes-slider img {
  312. width: 100%;
  313. height: auto;
  314. }
  315.  
  316. #kw--crimes-settings #kw--crimes-settings-save {
  317. width: 100%;
  318. padding: 1em;
  319. }
  320.  
  321. .kw-hide {
  322. display: none !important;
  323. }
  324. body.kw--crimes-mode-hidden {
  325. --kw--icon-color: red;
  326. }
  327.  
  328. body.kw--crimes-mode-minimal {
  329. --kw--icon-color: orange;
  330. }
  331.  
  332. body.kw--crimes-mode-toast {
  333. --kw--icon-color: green;
  334. }
  335.  
  336. body.kw--crimes-mode-disabled {
  337. --kw--icon-color: #777;
  338. }
  339.  
  340. body.kw--crimes-mode-hidden [class*=outcomePanel_], body.kw--crimes-mode-toast [class*=outcomePanel_],
  341. body.kw--crimes-mode-hidden [class*=outcomeWrapper_], body.kw--crimes-mode-toast [class*=outcomeWrapper_] {
  342. display: none;
  343. }
  344.  
  345. body.kw--crimes-mode-minimal [class*=outcomePanel_] [class*=story_], body.kw--crimes-mode-minimal [class*=outcomeWrapper_] [class*=story_] {
  346. display: none;
  347. }
  348.  
  349. #kw--crimes-toast-container {
  350. display: none;
  351. }
  352. /* Only show the toast container when in toast mode */
  353. body.kw--crimes-mode-toast #kw--crimes-toast-container {
  354. display: flex;
  355. flex-direction: column-reverse;
  356. position: fixed;
  357. bottom: 0;
  358. right: 0;
  359. gap: 1px;
  360. z-index: 9999999999;
  361. }
  362.  
  363. #kw--crimes-toast-container .kw--crimes-toast {
  364. --toast-bg: darkslategray;
  365. --toast-color: white;
  366. background: var(--toast-bg);
  367. color: var(--toast-color);
  368. padding: 1em;
  369. border-radius: 0.5em;
  370. margin: 0.5em;
  371. transition: all 0.5s;
  372. font-size: 1rem;
  373. min-width: 10vw;
  374. }
  375. /* Only show the last 3 toasts */
  376. #kw--crimes-toast-container .kw--crimes-toast:not(:nth-last-child(-n+3)) {
  377. display: none;
  378. }
  379.  
  380. #kw--crimes-toast-container .kw--crimes-toast.kw--crimes-toast-criticalfailure {
  381. --toast-bg: red;
  382. --toast-color: white;
  383. }
  384.  
  385. #kw--crimes-toast-container .kw--crimes-toast.kw--crimes-toast-failure {
  386. --toast-bg: darkorange;
  387. --toast-color: white;
  388. }
  389.  
  390. #kw--crimes-toast-container .kw--crimes-toast.kw--crimes-toast-success {
  391. --toast-bg: green;
  392. --toast-color: white;
  393. }
  394. `);
  395. };
  396.  
  397. addStyle(MODES);
  398. addToastContainer();
  399. fetchInjection(window.fetch);
  400. mutationCallback();
  401. })();