DKK Torn Utilities DEVELOPMENT

Commonly used functions in my Torn scripts.

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

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