DKK Torn Utilities DEVELOPMENT

Commonly used functions in my Torn scripts.

当前为 2019-11-19 提交的版本,查看 最新版本

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