Lib DEV

Lib for reference

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/408013/832805/Lib%20DEV.js

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