Commonly used functions in my Torn scripts.
สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/392610/752829/DKK%20Torn%20Utilities%20DEVELOPMENT.js
// ==UserScript== // @name DKK Torn Utilities DEVELOPMENT // @description Commonly used functions in my Torn scripts. // @version 2.3.6 // @exclude * // @namespace https://openuserjs.org/users/DeKleineKobini // @homepageURL https://www.torn.com/forums.php#/p=threads&t=16110079 // ==/UserScript== /* Classes */ class DKKLog { constructor(prefix, level) { this.levels = { FATAL: 0, ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4, TRACE: 5, ALL: 6, OFF: 7 }, this.prefix = prefix; this._level = level || 2; } get level() { return this._level; } set level(val) { this._level = this.levels[val]; } logging(level, message, objs) { if (this._level< this.levels[level]) return; let msg = `${this.prefix}[${level}] ${message}`; if (objs && objs.length) console.log(msg, ...objs); else console.log(msg); } fatal(message, ...objs) { this.logging("FATAL", message, objs); } error(message, ...objs) { this.logging("ERROR", message, objs); } warn(message, ...objs) { this.logging("WARN", message, objs); } info(message, ...objs) { this.logging("INFO", message, objs); } debug(message, ...objs) { this.logging("DEBUG", message, objs); } trace(message, ...objs) { this.logging("TRACE", message, objs); } all(message, ...objs) { this.logging("ALL", message, objs); } } class TornAPI { constructor(callback, local) { if (!GM_xmlhttpRequest) dkk.fatal("GM_xmlhttpRequest was not found."); this.key = local || localStorage.getItem("dkkutils_apikey"); if (!this.isValid()) { dkklog.trace("Asking for the api key."); let selector; switch (location.pathname) { case "/christmas_town.php": selector = ".content-wrapper div[id*='root'] > div > div:eq(0)"; break; default: selector = ".content-title"; break; } let createPrompt = () => { if (!$("#dkkapi").length) { dkklog.trace("Creating prompt to ask for api key."); $(selector).after( "<div><article class='dkk-widget' id='dkkapi'><header class='dkk-widget_green dkk-round'><span class='dkk-widget_title'>API Promt</span>" + "<input type='text' id='dkkapi-promt' style='margin-right: 8px;'><a id='dkkapi-save' class='dkk-button-green' href='#'>Save</a>" + "</header></article></div><div class='clear'></div>" ); } $("#dkkapi-save").click(event => { event.preventDefault(); this.key = $("#dkkapi-promt").val(); if (this.isValid()) { dkklog.trace("Saving api key."); $("#dkkapi").remove(); localStorage.setItem("dkkutils_apikey", this.key); if (callback) callback(this); } else { dkklog.trace("Wrong api key inputted."); $("#dkkapi-promt").val(""); } }); } if ($(selector).length) createPrompt(); else observeMutations(document, selector, true, createPrompt, { childList: true, subtree: true }); } else { dkklog.trace("Succesfully retreived api key from localStorage."); if (callback) callback(this); } } isValid() { if (!this.key || this.key === undefined || this.key == "undefined" || this.key === null || this.key == "null" || this.key === "") return false; if (this.key.length != 16) return false; return true; } sendRequest(part, id, selections) { dkklog.debug(`Sending API request to ${part}/${selections} for id ${id}.`); return new Promise((resolve, reject) => { if (!GM_xmlhttpRequest) { dkk.fatal("GM_xmlhttpRequest was not found.") reject("GM_xmlhttpRequest was not found.") return; } GM_xmlhttpRequest({ method: "GET", url: `https://api.torn.com/${part}/${id}?selections=${selections}&key=${this.key}`, onreadystatechange: (res) => { if (res.readyState > 3 && res.status === 200) { dkklog.trace("API response received.", res) if (!isJsonString(res.responseText)) { reject("JSON Error", res.responseText); return; } let json = JSON.parse(res.responseText); if (json.error) { var code = json.error.code; if (code == 2) { this.key = null; localStorage.removeItem("dkkutils_apikey"); } dkklog.warn("A TornAPI error occured.", json.error); reject("API Error: " + code); } else { resolve(json); } } }, onerror: function(err) { dkklog.error("An XHR error occured.", err) reject('XHR error.'); } }) }); } } class CurrentUser { constructor() { if ($("#mainContainer").length) this.update(); else observeMutations(document, "#mainContainer", true, this.update, { childList: true, subtree: true }); } update() { let body = $("body") let contentWrapper = $("#mainContainer > .content-wrapper"); this.isJailed = body.hasClass("jail"); this.isHospitalized = body.hasClass("hospital"); // TODO - check based on css ??? this.isTravelling = contentWrapper.hasClass("travelling"); // TODO - test } } class Storage { constructor(key, type) { this.key = key; if (!type) type = "localStorage"; // - localStorage // - GM this.type = type; this.validate(); } validate() { switch (this.type) { case "GM": if (!GM_setValue) dkk.fatal("GM_setValue was not found."); if (!GM_getValue) dkk.fatal("GM_getValue was not found."); if (!GM_deleteValue) dkk.error("GM_deleteValue was not found."); break; } } get(defaultObject) { switch (this.type) { case "GM": return new Promise(async (resolve, reject) => { let val = await GM_getValue(this.key); if (!val) val = defaultObject; else if (isJsonString(val)) { val = JSON.parse(val); if (val.expire && val.expire != -1 && val.expire > Date.now()) { resolve(); return; } val = val.value; } resolve(val); }); break; case "localStorage": let val = localStorage.getItem(this.key); if (!val) val = defaultObject; else if (isJsonString(val)) { val = JSON.parse(val); if (val.expire && val.expire != -1 && val.expire > Date.now()) return null; val = val.value; } return val; default: return; } } set(value, time) { let type = typeof value; // if ((type != "string" && type != "number" && type != boolean)) let store = { value: value }; if (time) store.expire = Date.now() + time; store = JSON.stringify(store); switch (this.type) { case "GM": GM_setValue(this.key, store); break; case "localStorage": localStorage.setItem(this.key, store); break; } } remove() { switch (this.type) { case "GM": GM_deleteValue(this.key); break; case "localStorage": localStorage.removeItem(this.key); break; } } } /* Script Setup */ var dkklog = new DKKLog("[DKK]", 2); loadCSS(); addFunctions(); if (!unsafeWindow.scripts) unsafeWindow.scripts = { ids: [] }; function loadCSS() { addCSS("main", ".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; } " + ".dkk-button-green { background-color: rgba(255, 255, 255, 0.15); color: rgb(255, 255, 255); }" + ".dkk-widget { margin-top: 10px; }" + ".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; } " + ".dkk-widget_green { background-color: rgb(144, 176, 46); }" + ".dkk-widget_red { background-color: rgb(251, 0, 25); }" + ".dkk-widget_title { flex-grow: 1; box-sizing: border-box; }" + ".dkk-widget_body { display: flex; padding: 0px; line-height: 1.4; background-color: rgb(242, 242, 242); }" + ".dkk-round-bottom { border-radius: 0 0 10px 10px; }" + ".dkk-round { border-radius: 5px; }" + ".dkk-panel-left, .dkk-panel-right, .dkk-panel-middle { flex: 1 0 0px; max-height: 120px; overflow: auto; min-height: 60px; }" + ".dkk-panel-left { border-left: 1px solid transparent; }" + ".dkk-panel-middle { display: flex-direction: column; }" + ".dkk-panel-right { display: flex-direction: column; border-radius: 0 0 5px 5px; }" + ".dkk-data-table { width: 100%; height: 100%; border-collapse: separate; text-align: left; }" + ".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)); }" + ".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); }" ) } function addFunctions() { // formating for numbers Number.prototype.format = function(n, x) { var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\.' : '$') + ')'; return this.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$&,'); }; // string functions String.prototype.replaceAll = function(text, replace) { let str = this.toString(); while (str.includes(text)) { str = str.replace(text, replace); } return str; }; } function initScript(options) { // some easy name conversion if (options.name) { if (!options.id) options.id = options.name.replaceAll(" ", "").toLowerCase(); if (!options.abbr) options.abbr = options.name; } if (!options.logging) { options.logging = "WARN"; } unsafeWindow.scripts.ids.push(options.id); unsafeWindow.scripts[options.id] = { name: options.name, abbr: options.abbr }; dkklog.prefix = "[" + options.abbr + "]"; dkklog.level = options.logging; } /* Torn General*/ function getScriptUser() { let body = $("body") let contentWrapper = $("#mainContainer > .content-wrapper"); return { isJailed: body.hasClass("jail"), isHospitalized: body.hasClass("hospital"), isTravelling: contentWrapper.hasClass("travelling") } } /* Networking */ function xhrIntercept(callback) { let oldXHROpen = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function() { this.addEventListener('readystatechange', function() { if (this.readyState > 3 && this.status == 200) { var page = this.responseURL.substring(this.responseURL.indexOf("torn.com/") + "torn.com/".length, this.responseURL.indexOf(".php")); var json, uri; if (isJsonString(this.response)) json = JSON.parse(this.response); else uri = getUrlParams(this.responseURL); callback(page, json, uri, this); } }); return oldXHROpen.apply(this, arguments); } } function ajax(callback) { $(document).ajaxComplete((event, xhr, settings) => { if (xhr.readyState > 3 && xhr.status == 200) { if (settings.url.indexOf("torn.com/") < 0) settings.url = "torn.com" + (settings.url.startsWith("/") ? "" : "/") + settings.url; var page = settings.url.substring(settings.url.indexOf("torn.com/") + "torn.com/".length, settings.url.indexOf(".php")); var json, uri; if (isJsonString(xhr.responseText)) json = JSON.parse(xhr.responseText); else uri = getUrlParams(settings.url); callback(page, json, uri, xhr, settings); } }); } function interceptFetch(url, callback) { unsafeWindow.fetch = async (input, options) => { const response = await fetch(input, options) if (response.url.startsWith("https://www.torn.com/" + url)) { let res = response.clone(); Promise.resolve(res.json().then((json) => callback(json, res.url))); } return response; } } /* DOM */ function observeMutationsFull(root, callback, options) { if (!options) options = { childList: true }; new MutationObserver(callback).observe(root, options); } function observeMutations(root, selector, runOnce, callback, options, callbackRemoval) { var ran = false; observeMutationsFull(root, function(mutations, me) { var check = $(selector); if (check.length) { if (runOnce) me.disconnect(); ran = true; callback(mutations, me); } else if (ran) { ran = false; if (callbackRemoval) callbackRemoval(mutations, me); } }, options); } /* Caching - outdated */ function setCache(key, value, time, sub) { var end = time == -1 ? -1 : Date.now() + time; var obj = sub ? value : { value: value, end: Date.now() + time }; GM_setValue(key, JSON.stringify(obj)); } async function getCache(key, subbed) { let _obj = await GM_getValue(key, subbed ? "{}" : "{\"end\":0}"); let obj = JSON.parse(_obj); var end = obj.end; if (!end || end == -1 || end > Date.now()) return subbed ? obj : obj.value; return undefined; } function getSubCache(cache, id) { if (cache[id]) { var end = cache[id].end; if (end == -1 || end > Date.now()) return cache[id].value; } return undefined; } /* General Utilities */ function addCSS(id, css) { if ($("#dkkcss-" + id).length) return; if (!$("#dkkcss").length) $("head").append("<div id='dkkcss'></div>") $("#dkkcss").append(`<style id='dkkcss-${id}'>${css}</style`); } function removeCSS(idd) { $("#dkkcss-" + id).remove(); } function isJsonString(str) { if (!str || str == "") return false; try { JSON.parse(str); } catch (e) { return false; } return true; } /** * JavaScript Get URL Parameter (https://www.kevinleary.net/javascript-get-url-parameters/) * * @param String prop The specific URL parameter you want to retreive the value for * @return String|Object If prop is provided a string value is returned, otherwise an object of all properties is returned */ function getUrlParams(url, prop) { var params = {}; var search = decodeURIComponent(((url) ? url : window.location.href).slice(window.location.href.indexOf('?') + 1)); var definitions = search.split('&'); definitions.forEach(function(val, key) { var parts = val.split('=', 2); params[parts[0]] = parts[1]; }); return (prop && prop in params) ? params[prop] : params; } function getSpecialSearch() { let hash = window.location.hash; hash = hash.replace("#/", "?"); hash = hash.replace("#!", "?"); return hash; } function stripHtml(html) { var tmp = document.createElement("DIV"); tmp.innerHTML = html; var stripped = tmp.textContent || tmp.innerText || ""; stripped = stripped.replaceAll("\n", ""); stripped = stripped.replaceAll("\t", ""); stripped = stripped.replaceAll(" ", " "); return stripped; } function getNewDay() { let now = new Date(); let newDay = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate() - 1, 0, 0, 0)); if (Date.now() >= newDay.getTime()) newDay.setUTCDate(newDay.getUTCDate() + 1); if (Date.now() >= newDay.getTime()) newDay.setUTCDate(newDay.getUTCDate() + 1); return newDay; } function getMillisUntilNewDay() { return getNewDay().getTime() - Date.now(); } function _isMobile() { return navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i); } function runOnEvent(funct, event, runBefore) { if (runBefore) funct(); $(window).bind(event, function() { funct(); }); } function timeSince(timeStamp) { let now = new Date(); let secondsPast = (now.getTime() - timeStamp.getTime()) / 1000; if (secondsPast < 60) return parseInt(secondsPast) + 's'; else if (secondsPast < 3600) return parseInt(secondsPast / 60) + 'm'; else if (secondsPast <= 86400) return parseInt(secondsPast / 3600) + 'h'; else if (secondsPast > 86400) { let day = timeStamp.getDate(); let month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", ""); let year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear(); return day + " " + month + year; } }