DKK Torn Utilities DEVELOPMENT

Commonly used functions in my Torn scripts.

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

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/392610/750742/DKK%20Torn%20Utilities%20DEVELOPMENT.js

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