🏠 Home 

DKK Torn Utilities DEVELOPMENT

Commonly used functions in my Torn scripts.

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/392610/752829/DKK%20Torn%20Utilities%20DEVELOPMENT.js

  1. // ==UserScript==
  2. // @name DKK Torn Utilities DEVELOPMENT
  3. // @description Commonly used functions in my Torn scripts.
  4. // @version 2.3.6
  5. // @exclude *
  6. // @namespace https://openuserjs.org/users/DeKleineKobini
  7. // @homepageURL https://www.torn.com/forums.php#/p=threads&t=16110079
  8. // ==/UserScript==
  9. /* Classes */
  10. class DKKLog {
  11. constructor(prefix, level) {
  12. this.levels = {
  13. FATAL: 0,
  14. ERROR: 1,
  15. WARN: 2,
  16. INFO: 3,
  17. DEBUG: 4,
  18. TRACE: 5,
  19. ALL: 6,
  20. OFF: 7
  21. },
  22. this.prefix = prefix;
  23. this._level = level || 2;
  24. }
  25. get level() {
  26. return this._level;
  27. }
  28. set level(val) {
  29. this._level = this.levels[val];
  30. }
  31. logging(level, message, objs) {
  32. if (this._level< this.levels[level]) return;
  33. let msg = `${this.prefix}[${level}] ${message}`;
  34. if (objs && objs.length) console.log(msg, ...objs);
  35. else console.log(msg);
  36. }
  37. fatal(message, ...objs) { this.logging("FATAL", message, objs); }
  38. error(message, ...objs) { this.logging("ERROR", message, objs); }
  39. warn(message, ...objs) { this.logging("WARN", message, objs); }
  40. info(message, ...objs) { this.logging("INFO", message, objs); }
  41. debug(message, ...objs) { this.logging("DEBUG", message, objs); }
  42. trace(message, ...objs) { this.logging("TRACE", message, objs); }
  43. all(message, ...objs) { this.logging("ALL", message, objs); }
  44. }
  45. class TornAPI {
  46. constructor(callback, local) {
  47. if (!GM_xmlhttpRequest) dkk.fatal("GM_xmlhttpRequest was not found.");
  48. this.key = local || localStorage.getItem("dkkutils_apikey");
  49. if (!this.isValid()) {
  50. dkklog.trace("Asking for the api key.");
  51. let selector;
  52. switch (location.pathname) {
  53. case "/christmas_town.php":
  54. selector = ".content-wrapper div[id*='root'] > div > div:eq(0)";
  55. break;
  56. default:
  57. selector = ".content-title";
  58. break;
  59. }
  60. let createPrompt = () => {
  61. if (!$("#dkkapi").length) {
  62. dkklog.trace("Creating prompt to ask for api key.");
  63. $(selector).after(
  64. "<div><article class='dkk-widget' id='dkkapi'><header class='dkk-widget_green dkk-round'><span class='dkk-widget_title'>API Promt</span>"
  65. + "<input type='text' id='dkkapi-promt' style='margin-right: 8px;'><a id='dkkapi-save' class='dkk-button-green' href='#'>Save</a>"
  66. + "</header></article></div><div class='clear'></div>"
  67. );
  68. }
  69. $("#dkkapi-save").click(event => {
  70. event.preventDefault();
  71. this.key = $("#dkkapi-promt").val();
  72. if (this.isValid()) {
  73. dkklog.trace("Saving api key.");
  74. $("#dkkapi").remove();
  75. localStorage.setItem("dkkutils_apikey", this.key);
  76. if (callback) callback(this);
  77. } else {
  78. dkklog.trace("Wrong api key inputted.");
  79. $("#dkkapi-promt").val("");
  80. }
  81. });
  82. }
  83. if ($(selector).length) createPrompt();
  84. else observeMutations(document, selector, true, createPrompt, { childList: true, subtree: true });
  85. } else {
  86. dkklog.trace("Succesfully retreived api key from localStorage.");
  87. if (callback) callback(this);
  88. }
  89. }
  90. isValid() {
  91. if (!this.key || this.key === undefined || this.key == "undefined" || this.key === null || this.key == "null" || this.key === "") return false;
  92. if (this.key.length != 16) return false;
  93. return true;
  94. }
  95. sendRequest(part, id, selections) {
  96. dkklog.debug(`Sending API request to ${part}/${selections} for id ${id}.`);
  97. return new Promise((resolve, reject) => {
  98. if (!GM_xmlhttpRequest) {
  99. dkk.fatal("GM_xmlhttpRequest was not found.")
  100. reject("GM_xmlhttpRequest was not found.")
  101. return;
  102. }
  103. GM_xmlhttpRequest({
  104. method: "GET",
  105. url: `https://api.torn.com/${part}/${id}?selections=${selections}&key=${this.key}`,
  106. onreadystatechange: (res) => {
  107. if (res.readyState > 3 && res.status === 200) {
  108. dkklog.trace("API response received.", res)
  109. if (!isJsonString(res.responseText)) {
  110. reject("JSON Error", res.responseText);
  111. return;
  112. }
  113. let json = JSON.parse(res.responseText);
  114. if (json.error) {
  115. var code = json.error.code;
  116. if (code == 2) {
  117. this.key = null;
  118. localStorage.removeItem("dkkutils_apikey");
  119. }
  120. dkklog.warn("A TornAPI error occured.", json.error);
  121. reject("API Error: " + code);
  122. } else {
  123. resolve(json);
  124. }
  125. }
  126. },
  127. onerror: function(err) {
  128. dkklog.error("An XHR error occured.", err)
  129. reject('XHR error.');
  130. }
  131. })
  132. });
  133. }
  134. }
  135. class CurrentUser {
  136. constructor() {
  137. if ($("#mainContainer").length) this.update();
  138. else observeMutations(document, "#mainContainer", true, this.update, { childList: true, subtree: true });
  139. }
  140. update() {
  141. let body = $("body")
  142. let contentWrapper = $("#mainContainer > .content-wrapper");
  143. this.isJailed = body.hasClass("jail");
  144. this.isHospitalized = body.hasClass("hospital"); // TODO - check based on css ???
  145. this.isTravelling = contentWrapper.hasClass("travelling"); // TODO - test
  146. }
  147. }
  148. class Storage {
  149. constructor(key, type) {
  150. this.key = key;
  151. if (!type) type = "localStorage";
  152. // - localStorage
  153. // - GM
  154. this.type = type;
  155. this.validate();
  156. }
  157. validate() {
  158. switch (this.type) {
  159. case "GM":
  160. if (!GM_setValue) dkk.fatal("GM_setValue was not found.");
  161. if (!GM_getValue) dkk.fatal("GM_getValue was not found.");
  162. if (!GM_deleteValue) dkk.error("GM_deleteValue was not found.");
  163. break;
  164. }
  165. }
  166. get(defaultObject) {
  167. switch (this.type) {
  168. case "GM":
  169. return new Promise(async (resolve, reject) => {
  170. let val = await GM_getValue(this.key);
  171. if (!val) val = defaultObject;
  172. else if (isJsonString(val)) {
  173. val = JSON.parse(val);
  174. if (val.expire && val.expire != -1 && val.expire > Date.now()) {
  175. resolve();
  176. return;
  177. }
  178. val = val.value;
  179. }
  180. resolve(val);
  181. });
  182. break;
  183. case "localStorage":
  184. let val = localStorage.getItem(this.key);
  185. if (!val) val = defaultObject;
  186. else if (isJsonString(val)) {
  187. val = JSON.parse(val);
  188. if (val.expire && val.expire != -1 && val.expire > Date.now()) return null;
  189. val = val.value;
  190. }
  191. return val;
  192. default:
  193. return;
  194. }
  195. }
  196. set(value, time) {
  197. let type = typeof value;
  198. // if ((type != "string" && type != "number" && type != boolean))
  199. let store = { value: value };
  200. if (time) store.expire = Date.now() + time;
  201. store = JSON.stringify(store);
  202. switch (this.type) {
  203. case "GM":
  204. GM_setValue(this.key, store);
  205. break;
  206. case "localStorage":
  207. localStorage.setItem(this.key, store);
  208. break;
  209. }
  210. }
  211. remove() {
  212. switch (this.type) {
  213. case "GM":
  214. GM_deleteValue(this.key);
  215. break;
  216. case "localStorage":
  217. localStorage.removeItem(this.key);
  218. break;
  219. }
  220. }
  221. }
  222. /* Script Setup */
  223. var dkklog = new DKKLog("[DKK]", 2);
  224. loadCSS();
  225. addFunctions();
  226. if (!unsafeWindow.scripts) unsafeWindow.scripts = { ids: [] };
  227. function loadCSS() {
  228. addCSS("main",
  229. ".dkk-button, .dkk-button-green { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; ext-shadow: rgba(0, 0, 0, 0.05) 1px 1px 2px; cursor: pointer; font-weight: 400; text-transform: none; position: relative; text-align: center; line-height: 1.2; box-shadow: rgba(255, 255, 255, 0.5) 0px 1px 1px 0px inset, rgba(0, 0, 0, 0.25) 0px 1px 1px 1px; border-width: initial; border-style: none; border-color: initial; border-image: initial; padding: 2px 10px; border-radius: 4px; } "
  230. + ".dkk-button-green { background-color: rgba(255, 255, 255, 0.15); color: rgb(255, 255, 255); }"
  231. + ".dkk-widget { margin-top: 10px; }"
  232. + ".dkk-widget_red, .dkk-widget_green { background-image: linear-gradient(90deg, transparent 50%, rgba(0, 0, 0, 0.07) 0px); background-size: 4px; display: flex; align-items: center; color: rgb(255, 255, 255); font-size: 13px; letter-spacing: 1px; text-shadow: rgba(0, 0, 0, 0.65) 1px 1px 2px; padding: 6px 10px; border-radius: 5px 5px 0 0; } "
  233. + ".dkk-widget_green { background-color: rgb(144, 176, 46); }"
  234. + ".dkk-widget_red { background-color: rgb(251, 0, 25); }"
  235. + ".dkk-widget_title { flex-grow: 1; box-sizing: border-box; }"
  236. + ".dkk-widget_body { display: flex; padding: 0px; line-height: 1.4; background-color: rgb(242, 242, 242); }"
  237. + ".dkk-round-bottom { border-radius: 0 0 10px 10px; }"
  238. + ".dkk-round { border-radius: 5px; }"
  239. + ".dkk-panel-left, .dkk-panel-right, .dkk-panel-middle { flex: 1 0 0px; max-height: 120px; overflow: auto; min-height: 60px; }"
  240. + ".dkk-panel-left { border-left: 1px solid transparent; }"
  241. + ".dkk-panel-middle { display: flex-direction: column; }"
  242. + ".dkk-panel-right { display: flex-direction: column; border-radius: 0 0 5px 5px; }"
  243. + ".dkk-data-table { width: 100%; height: 100%; border-collapse: separate; text-align: left; }"
  244. + ".dkk-data-table > tbody > tr > th { height: 16px; white-space: nowrap; text-overflow: ellipsis; font-weight: 700; padding: 2px 10px; border-top: 1px solid rgb(255, 255, 255); border-bottom: 1px solid rgb(204, 204, 204); background: linear-gradient(rgb(255, 255, 255), rgb(215, 205, 220)); }"
  245. + ".dkk-data-table > tbody > tr > td { padding: 2px 10px; border-top: 1px solid rgb(255, 255, 255); border-right: 1px solid rgb(204, 204, 204); border-bottom: 1px solid rgb(204, 204, 204); }"
  246. )
  247. }
  248. function addFunctions() {
  249. // formating for numbers
  250. Number.prototype.format = function(n, x) {
  251. var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\.' : '$') + ')';
  252. return this.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$&,');
  253. };
  254. // string functions
  255. String.prototype.replaceAll = function(text, replace) {
  256. let str = this.toString();
  257. while (str.includes(text)) { str = str.replace(text, replace); }
  258. return str;
  259. };
  260. }
  261. function initScript(options) {
  262. // some easy name conversion
  263. if (options.name) {
  264. if (!options.id) options.id = options.name.replaceAll(" ", "").toLowerCase();
  265. if (!options.abbr) options.abbr = options.name;
  266. }
  267. if (!options.logging) {
  268. options.logging = "WARN";
  269. }
  270. unsafeWindow.scripts.ids.push(options.id);
  271. unsafeWindow.scripts[options.id] = {
  272. name: options.name,
  273. abbr: options.abbr
  274. };
  275. dkklog.prefix = "[" + options.abbr + "]";
  276. dkklog.level = options.logging;
  277. }
  278. /* Torn General*/
  279. function getScriptUser() {
  280. let body = $("body")
  281. let contentWrapper = $("#mainContainer > .content-wrapper");
  282. return {
  283. isJailed: body.hasClass("jail"),
  284. isHospitalized: body.hasClass("hospital"),
  285. isTravelling: contentWrapper.hasClass("travelling")
  286. }
  287. }
  288. /* Networking */
  289. function xhrIntercept(callback) {
  290. let oldXHROpen = window.XMLHttpRequest.prototype.open;
  291. window.XMLHttpRequest.prototype.open = function() {
  292. this.addEventListener('readystatechange', function() {
  293. if (this.readyState > 3 && this.status == 200) {
  294. var page = this.responseURL.substring(this.responseURL.indexOf("torn.com/") + "torn.com/".length, this.responseURL.indexOf(".php"));
  295. var json, uri;
  296. if (isJsonString(this.response)) json = JSON.parse(this.response);
  297. else uri = getUrlParams(this.responseURL);
  298. callback(page, json, uri, this);
  299. }
  300. });
  301. return oldXHROpen.apply(this, arguments);
  302. }
  303. }
  304. function ajax(callback) {
  305. $(document).ajaxComplete((event, xhr, settings) => {
  306. if (xhr.readyState > 3 && xhr.status == 200) {
  307. if (settings.url.indexOf("torn.com/") < 0) settings.url = "torn.com" + (settings.url.startsWith("/") ? "" : "/") + settings.url;
  308. var page = settings.url.substring(settings.url.indexOf("torn.com/") + "torn.com/".length, settings.url.indexOf(".php"));
  309. var json, uri;
  310. if (isJsonString(xhr.responseText)) json = JSON.parse(xhr.responseText);
  311. else uri = getUrlParams(settings.url);
  312. callback(page, json, uri, xhr, settings);
  313. }
  314. });
  315. }
  316. function interceptFetch(url, callback) {
  317. unsafeWindow.fetch = async (input, options) => {
  318. const response = await fetch(input, options)
  319. if (response.url.startsWith("https://www.torn.com/" + url)) {
  320. let res = response.clone();
  321. Promise.resolve(res.json().then((json) => callback(json, res.url)));
  322. }
  323. return response;
  324. }
  325. }
  326. /* DOM */
  327. function observeMutationsFull(root, callback, options) {
  328. if (!options) options = {
  329. childList: true
  330. };
  331. new MutationObserver(callback).observe(root, options);
  332. }
  333. function observeMutations(root, selector, runOnce, callback, options, callbackRemoval) {
  334. var ran = false;
  335. observeMutationsFull(root, function(mutations, me) {
  336. var check = $(selector);
  337. if (check.length) {
  338. if (runOnce) me.disconnect();
  339. ran = true;
  340. callback(mutations, me);
  341. } else if (ran) {
  342. ran = false;
  343. if (callbackRemoval) callbackRemoval(mutations, me);
  344. }
  345. }, options);
  346. }
  347. /* Caching - outdated */
  348. function setCache(key, value, time, sub) {
  349. var end = time == -1 ? -1 : Date.now() + time;
  350. var obj = sub ? value : {
  351. value: value,
  352. end: Date.now() + time
  353. };
  354. GM_setValue(key, JSON.stringify(obj));
  355. }
  356. async function getCache(key, subbed) {
  357. let _obj = await GM_getValue(key, subbed ? "{}" : "{\"end\":0}");
  358. let obj = JSON.parse(_obj);
  359. var end = obj.end;
  360. if (!end || end == -1 || end > Date.now())
  361. return subbed ? obj : obj.value;
  362. return undefined;
  363. }
  364. function getSubCache(cache, id) {
  365. if (cache[id]) {
  366. var end = cache[id].end;
  367. if (end == -1 || end > Date.now())
  368. return cache[id].value;
  369. }
  370. return undefined;
  371. }
  372. /* General Utilities */
  373. function addCSS(id, css) {
  374. if ($("#dkkcss-" + id).length) return;
  375. if (!$("#dkkcss").length) $("head").append("<div id='dkkcss'></div>")
  376. $("#dkkcss").append(`<style id='dkkcss-${id}'>${css}</style`);
  377. }
  378. function removeCSS(idd) {
  379. $("#dkkcss-" + id).remove();
  380. }
  381. function isJsonString(str) {
  382. if (!str || str == "") return false;
  383. try {
  384. JSON.parse(str);
  385. } catch (e) {
  386. return false;
  387. }
  388. return true;
  389. }
  390. /**
  391. * JavaScript Get URL Parameter (https://www.kevinleary.net/javascript-get-url-parameters/)
  392. *
  393. * @param String prop The specific URL parameter you want to retreive the value for
  394. * @return String|Object If prop is provided a string value is returned, otherwise an object of all properties is returned
  395. */
  396. function getUrlParams(url, prop) {
  397. var params = {};
  398. var search = decodeURIComponent(((url) ? url : window.location.href).slice(window.location.href.indexOf('?') + 1));
  399. var definitions = search.split('&');
  400. definitions.forEach(function(val, key) {
  401. var parts = val.split('=', 2);
  402. params[parts[0]] = parts[1];
  403. });
  404. return (prop && prop in params) ? params[prop] : params;
  405. }
  406. function getSpecialSearch() {
  407. let hash = window.location.hash;
  408. hash = hash.replace("#/", "?");
  409. hash = hash.replace("#!", "?");
  410. return hash;
  411. }
  412. function stripHtml(html) {
  413. var tmp = document.createElement("DIV");
  414. tmp.innerHTML = html;
  415. var stripped = tmp.textContent || tmp.innerText || "";
  416. stripped = stripped.replaceAll("\n", "");
  417. stripped = stripped.replaceAll("\t", "");
  418. stripped = stripped.replaceAll(" ", " ");
  419. return stripped;
  420. }
  421. function getNewDay() {
  422. let now = new Date();
  423. let newDay = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate() - 1, 0, 0, 0));
  424. if (Date.now() >= newDay.getTime()) newDay.setUTCDate(newDay.getUTCDate() + 1);
  425. if (Date.now() >= newDay.getTime()) newDay.setUTCDate(newDay.getUTCDate() + 1);
  426. return newDay;
  427. }
  428. function getMillisUntilNewDay() {
  429. return getNewDay().getTime() - Date.now();
  430. }
  431. function _isMobile() {
  432. return navigator.userAgent.match(/Android/i) ||
  433. navigator.userAgent.match(/webOS/i) ||
  434. navigator.userAgent.match(/iPhone/i) ||
  435. navigator.userAgent.match(/iPad/i) ||
  436. navigator.userAgent.match(/iPod/i) ||
  437. navigator.userAgent.match(/BlackBerry/i) ||
  438. navigator.userAgent.match(/Windows Phone/i);
  439. }
  440. function runOnEvent(funct, event, runBefore) {
  441. if (runBefore) funct();
  442. $(window).bind(event, function() {
  443. funct();
  444. });
  445. }
  446. function timeSince(timeStamp) {
  447. let now = new Date();
  448. let secondsPast = (now.getTime() - timeStamp.getTime()) / 1000;
  449. if (secondsPast < 60) return parseInt(secondsPast) + 's';
  450. else if (secondsPast < 3600) return parseInt(secondsPast / 60) + 'm';
  451. else if (secondsPast <= 86400) return parseInt(secondsPast / 3600) + 'h';
  452. else if (secondsPast > 86400) {
  453. let day = timeStamp.getDate();
  454. let month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", "");
  455. let year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear();
  456. return day + " " + month + year;
  457. }
  458. }