Display the value of the supply pack you just opened.
// ==UserScript==
// @name TORN: TornTools - Opened Supply Pack Value
// @namespace torntools.opened-supply-pack-value
// @version 1.0.5
// @author DeKleineKobini [2114440] and the TornTools team
// @description Display the value of the supply pack you just opened.
// @license GPL-3.0-or-later
// @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @supportURL https://github.com/Mephiles/torntools_extension/issues
// @match https://*.torn.com/item.php*
// @connect torntools.tornplayground.eu
// @grant GM.getValue
// @grant GM.info
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant GM_addStyle
// @grant unsafeWindow
// @run-at document-end
// @contributionURL https://buymeacoffee.com/dekleinekobini
// ==/UserScript==
(function() {
"use strict";
var s = new Set();
var _css = async (t) => {
if (s.has(t)) return;
s.add(t);
((c) => {
if (typeof GM_addStyle === "function") GM_addStyle(c);
else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c);
})(t);
};
var FEATURE_MANAGER;
var ttStorage;
var SCRIPT_INJECTOR;
var RUNTIME_INFORMATION;
var RUNTIME_STORAGE;
var OFFLOAD_SERVICE;
var DATA_FETCHER;
var ITEM_RESOLVER;
var EVENT_HANDLER;
function setFeatureManager(featureManager) {
FEATURE_MANAGER = featureManager;
}
function setTTStorage(storage) {
ttStorage = storage;
}
function setScriptInjector(scriptInjector) {
SCRIPT_INJECTOR = scriptInjector;
}
function setRuntimeInformation(runtimeInformation) {
RUNTIME_INFORMATION = runtimeInformation;
}
function setRuntimeStorage(runtimeStorage) {
RUNTIME_STORAGE = runtimeStorage;
}
function setOffloadService(offloadService) {
OFFLOAD_SERVICE = offloadService;
}
function setDataFetcher(dataFetcher) {
DATA_FETCHER = dataFetcher;
}
function setStaticItemResolver(staticItemResolver) {
ITEM_RESOLVER = staticItemResolver;
}
function setEventHandler(eventHandler) {
EVENT_HANDLER = eventHandler;
}
_css(".tt-opened-supply-pack-value-text{color:#82c91e;padding-top:.4em;display:block}");
_css(".tt-loading-placeholder{content:var(--default-preloader-url,url(https://www.torn.com/images/v2/main/ajax-loader.gif));margin:0 auto;padding:10px;display:none}.tt-loading-placeholder.active{display:block}");
var SCRIPT_TYPE = (() => {
if (typeof window === "undefined" || window.location.href.endsWith("/_generated_background_page.html")) return "BACKGROUND";
else if (typeof browser === "object" && browser.action) return "POPUP";
else if (typeof location !== "undefined" && location.protocol?.includes("extension")) return "INTERNAL_CONTENT";
else return "CONTENT";
})();
function sleep(millis) {
return new Promise((resolve) => setTimeout(resolve, millis));
}
var TO_MILLIS = {
SECONDS: 1e3,
MINUTES: 6e4,
HOURS: 36e5,
DAYS: 864e5
};
function isIntNumber(number) {
if (number === null) return false;
if (number.match(/[a-zA-Z]/)) return false;
const _number = parseFloat(number.toString());
return !Number.isNaN(_number) && Number.isFinite(_number) && _number % 1 === 0;
}
function getUUID() {
return `_${Math.random().toString(36).slice(2, 11)}`;
}
function getCookie(cname) {
const name = `${cname}=`;
for (let cookie of decodeURIComponent(document.cookie).split(";")) {
cookie = cookie.trimStart();
if (cookie.includes(name)) return cookie.substring(name.length);
}
return "";
}
function toNumericVersion(version) {
return parseInt(version.split(".").map((part) => part.padStart(3, "0")).join("").padEnd(9, "9"));
}
function isTabFocused() {
return document.hasFocus();
}
function checkListener(listener, entry) {
const element = listener.parent.querySelector(listener.selector);
if (!(listener.invert ? !element : !!element)) return false;
if (listener.timeoutId) clearTimeout(listener.timeoutId);
entry.listeners.delete(listener);
listener.resolve(listener.invert ? true : element);
cleanupEntryIfEmpty(entry);
return true;
}
function cleanupEntryIfEmpty(entry) {
if (entry.listeners.size > 0) return;
entry.observer.disconnect();
observerRegistry.delete(entry.parent);
}
function removeListenerFromRegistry(listener) {
const entry = observerRegistry.get(listener.parent);
if (!entry) return;
entry.listeners.delete(listener);
cleanupEntryIfEmpty(entry);
}
function requireElement(selector, attributes = {}) {
const options = {
invert: false,
parent: document,
timeout: TO_MILLIS.SECONDS * 5,
observerOptions: {
childList: true,
subtree: true
},
...attributes
};
const error = new Error("Maximum cycles reached.");
return new Promise((resolve, reject) => {
const element = options.parent.querySelector(selector);
if (options.invert && !element) {
resolve(true);
return;
} else if (!options.invert && element) {
resolve(element);
return;
}
const timeoutId = options.timeout > 0 ? window.setTimeout(() => {
removeListenerFromRegistry(listener);
reject(error);
}, options.timeout) : null;
const listener = {
selector,
invert: options.invert,
parent: options.parent,
resolve,
reject,
timeoutId
};
getOrCreateObserverEntry(options.parent).listeners.add(listener);
});
}
var observerRegistry = new Map();
function getOrCreateObserverEntry(parent) {
const existing = observerRegistry.get(parent);
if (existing) return existing;
const observer = new MutationObserver(() => {
const entry = observerRegistry.get(parent);
if (!entry) return;
entry.listeners.forEach((listener) => checkListener(listener, entry));
});
const entry = {
parent,
observer,
listeners: new Set()
};
observerRegistry.set(parent, entry);
observer.observe(parent, {
childList: true,
subtree: true
});
return entry;
}
function requireDOMInteractive() {
return new Promise((resolve) => {
if (document.readyState === "loading") document.addEventListener("readystatechange", () => resolve(), { once: true });
else resolve();
});
}
var mobile;
var tablet;
var hasSidebar;
var tabletHorizontal;
var tabletVertical;
function elementBuilder(options) {
if (typeof options === "string") return document.createElement(options);
else if (typeof options === "object") {
options = {
id: void 0,
class: void 0,
text: void 0,
html: void 0,
value: void 0,
href: void 0,
children: [],
attributes: {},
events: {},
style: {},
dataset: {},
...options
};
const newElement = document.createElement(options.type);
if (options.id) newElement.id = options.id;
if (options.class) newElement.className = Array.isArray(options.class) ? options.class.filter((name) => !!name).join(" ") : options.class.trim();
if (options.text !== void 0) newElement.textContent = options.text.toString();
if (options.html) newElement.innerHTML = options.html;
if (options.value && "value" in newElement) {
if (typeof options.value === "function") newElement.value = options.value();
else newElement.value = options.value;
}
if (options.href && "href" in newElement) newElement.href = options.href;
for (const child of options.children?.filter((child) => !!child) || []) if (typeof child === "string") newElement.appendChild(document.createTextNode(child));
else newElement.appendChild(child);
if (options.attributes) {
let attributes = options.attributes;
if (typeof attributes === "function") attributes = attributes();
for (const attribute in attributes) newElement.setAttribute(attribute, attributes[attribute].toString());
}
for (const event in options.events) newElement.addEventListener(event, options.events[event]);
for (const key in options.style) newElement.style[key] = options.style[key];
for (const key in options.dataset) if (typeof options.dataset[key] === "object") newElement.dataset[key] = JSON.stringify(options.dataset[key]);
else newElement.dataset[key] = options.dataset[key].toString();
return newElement;
} else throw new Error("Invalid options provided to newElement.");
}
async function checkDevice() {
await requireDOMInteractive();
const innerWidth = window.innerWidth;
mobile = innerWidth <= 600;
tablet = innerWidth <= 1e3 && innerWidth >= 600;
hasSidebar = innerWidth > 1e3;
tabletHorizontal = tablet && innerWidth >= 784;
tabletVertical = tablet && !tabletHorizontal;
return {
mobile,
tablet,
tabletHorizontal,
tabletVertical,
hasSidebar
};
}
function isCustomEvent(event) {
return event instanceof CustomEvent;
}
var EVENT_CHANNELS = function(EVENT_CHANNELS) {
EVENT_CHANNELS["CHAT_MESSAGE"] = "chat-message";
EVENT_CHANNELS["CHAT_NEW"] = "chat-box-new";
EVENT_CHANNELS["CHAT_OPENED"] = "chat-box-opened";
EVENT_CHANNELS["CHAT_PEOPLE_MENU_OPENED"] = "chat-people-menu-opened";
EVENT_CHANNELS["CHAT_SETTINGS_MENU_OPENED"] = "chat-settings-menu-opened";
EVENT_CHANNELS["CHAT_REFRESHED"] = "chat-refreshed";
EVENT_CHANNELS["CHAT_RECONNECTED"] = "chat-reconnected";
EVENT_CHANNELS["CHAT_CLOSED"] = "chat-closed";
EVENT_CHANNELS["COMPANY_EMPLOYEES_PAGE"] = "company-employees-page";
EVENT_CHANNELS["COMPANY_STOCK_PAGE"] = "company-stock-page";
EVENT_CHANNELS["FACTION_ARMORY_TAB"] = "faction-armory-tab";
EVENT_CHANNELS["FACTION_CRIMES"] = "faction-crimes";
EVENT_CHANNELS["FACTION_CRIMES2"] = "faction-crimes2";
EVENT_CHANNELS["FACTION_CRIMES2_TAB"] = "faction-crimes2-tab";
EVENT_CHANNELS["FACTION_CRIMES2_REFRESH"] = "faction-crimes2-refresh";
EVENT_CHANNELS["FACTION_GIVE_TO_USER"] = "faction-give-to-user";
EVENT_CHANNELS["FACTION_UPGRADE_INFO"] = "faction-upgrade-info";
EVENT_CHANNELS["FACTION_INFO"] = "faction-info";
EVENT_CHANNELS["FACTION_MAIN"] = "faction-main";
EVENT_CHANNELS["FACTION_NATIVE_FILTER"] = "faction-filter_native";
EVENT_CHANNELS["FACTION_NATIVE_SORT"] = "faction-sort_native";
EVENT_CHANNELS["FACTION_NATIVE_ICON_UPDATE"] = "faction-icon_update_native";
EVENT_CHANNELS["FF_SCOUTER_GAUGE"] = "ff-scouter-gauge";
EVENT_CHANNELS["ITEM_AMOUNT"] = "item-amount";
EVENT_CHANNELS["ITEM_EQUIPPED"] = "item-equipped";
EVENT_CHANNELS["ITEM_ITEMS_LOADED"] = "item-items-loaded";
EVENT_CHANNELS["ITEM_SWITCH_TAB"] = "item-switch-tab";
EVENT_CHANNELS["HOSPITAL_SWITCH_PAGE"] = "hospital-switch-page";
EVENT_CHANNELS["JAIL_SWITCH_PAGE"] = "jail-switch-page";
EVENT_CHANNELS["USERLIST_SWITCH_PAGE"] = "userlist-switch-page";
EVENT_CHANNELS["TRAVEL_SELECT_TYPE"] = "travel-select-type";
EVENT_CHANNELS["TRAVEL_SELECT_COUNTRY"] = "travel-select-country";
EVENT_CHANNELS["TRAVEL_DESTINATION_UPDATE"] = "travel-destination-update";
EVENT_CHANNELS["TRAVEL_ABROAD__SHOP_LOAD"] = "TRAVEL_ABROAD__SHOP_LOAD";
EVENT_CHANNELS["TRAVEL_ABROAD__SHOP_REFRESH"] = "TRAVEL_ABROAD__SHOP_REFRESH";
EVENT_CHANNELS["FEATURE_ENABLED"] = "feature-enabled";
EVENT_CHANNELS["FEATURE_DISABLED"] = "feature-disabled";
EVENT_CHANNELS["STATE_CHANGED"] = "state-changed";
EVENT_CHANNELS["SHOP__LOAD"] = "SHOP__LOAD";
EVENT_CHANNELS["GYM_LOAD"] = "gym-load";
EVENT_CHANNELS["GYM_TRAIN"] = "gym-train";
EVENT_CHANNELS["CRIMES_LOADED"] = "crimes-loaded";
EVENT_CHANNELS["CRIMES_CRIME"] = "crimes-crime";
EVENT_CHANNELS["CRIMES2_HOME_LOADED"] = "crimes2-home-loaded";
EVENT_CHANNELS["CRIMES2_BURGLARY_LOADED"] = "crimes2-burglary-loaded";
EVENT_CHANNELS["CRIMES2_CRIME_LOADED"] = "crimes2-crime-loaded";
EVENT_CHANNELS["MISSION_LOAD"] = "mission-load";
EVENT_CHANNELS["MISSION_REWARDS"] = "mission-rewards";
EVENT_CHANNELS["TRADE"] = "trade";
EVENT_CHANNELS["PROFILE_FETCHED"] = "profile-fetched";
EVENT_CHANNELS["FILTER_APPLIED"] = "filter-applied";
EVENT_CHANNELS["STATS_ESTIMATED"] = "stats-estimated";
EVENT_CHANNELS["SWITCH_PAGE"] = "switch-page";
EVENT_CHANNELS["AUCTION_SWITCH_TYPE"] = "auction-switch-type";
EVENT_CHANNELS["ITEMMARKET_CATEGORY_ITEMS"] = "itemmarket-category-items";
EVENT_CHANNELS["ITEMMARKET_CATEGORY_ITEMS_UPDATE"] = "itemmarket-category-items-update";
EVENT_CHANNELS["ITEMMARKET_ITEMS"] = "itemmarket-items";
EVENT_CHANNELS["ITEMMARKET_ITEMS_UPDATE"] = "itemmarket-items-update";
EVENT_CHANNELS["ITEMMARKET_ITEM_DETAILS"] = "itemmarket-item-details";
EVENT_CHANNELS["WINDOW__FOCUS"] = "WINDOW__FOCUS";
EVENT_CHANNELS["PROPERTIES__ROUTE"] = "PROPERTIES__ROUTE";
EVENT_CHANNELS["PROPERTIES__ROUTE_PAGE"] = "PROPERTIES__ROUTE_PAGE";
EVENT_CHANNELS["EFFICIENT_REHAB"] = "EFFICIENT_REHAB";
EVENT_CHANNELS["EFFICIENT_REHAB__INJECTED"] = "EFFICIENT_REHAB__INJECTED";
EVENT_CHANNELS["CITY_ITEMS_MAP__SET_ITEMS"] = "CITY_ITEMS_MAP__SET_ITEMS";
EVENT_CHANNELS["CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS"] = "CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS";
EVENT_CHANNELS["CITY_ITEMS_MAP__MODEL_ITEMS"] = "CITY_ITEMS_MAP__MODEL_ITEMS";
EVENT_CHANNELS["CITY_ITEMS_MAP__CLEAR"] = "CITY_ITEMS_MAP__CLEAR";
return EVENT_CHANNELS;
}({});
var ANTI_SCRAPE_EVENTS = [
"TRAVEL_ABROAD__SHOP_LOAD",
"chat-message",
"chat-box-opened",
"chat-closed",
"chat-refreshed",
"chat-reconnected",
"itemmarket-category-items",
"itemmarket-category-items-update",
"itemmarket-items",
"itemmarket-items-update"
];
function triggerCustomListener(channel, payload) {
if (ANTI_SCRAPE_EVENTS.includes(channel) && !isTabFocused()) return;
EVENT_HANDLER.triggerEvent(channel, payload);
}
var EVENT_CHANNEL_XHR = "tt-xhr";
function hasEventDetail(event) {
return typeof event.detail !== "undefined";
}
function addXHRListener(callback) {
SCRIPT_INJECTOR.injectXHR();
window.addEventListener(EVENT_CHANNEL_XHR, (event) => {
if (!hasEventDetail(event)) return;
callback(event);
});
}
var TornToolsCache = class {
_cache;
persistTimer = null;
constructor() {
this._cache = {};
}
set cache(value) {
this._cache = value || {};
}
get cache() {
return this._cache;
}
get(section, key) {
return this.getCacheValue(section, key)?.value;
}
remove(section, key) {
const actualKey = key ?? section;
const actualSection = key ? section : null;
if (actualSection && !this.hasValue(actualSection, actualKey) || !actualSection && !this.hasValue(actualKey.toString())) return;
if (actualSection) delete this.cache[actualSection][actualKey];
else delete this.cache[actualKey];
this.schedulePersist();
}
hasValue(section, key) {
return this.getCacheValue(section, key) !== null;
}
getCacheValue(section, key) {
const actualKey = key ?? section;
const actualSection = key ? section : null;
let value = null;
if (actualSection) {
if (section in this.cache && actualKey in this.cache[actualSection]) value = this.cache[actualSection][actualKey];
} else if (actualKey in this.cache) value = this.cache[actualKey];
if (value === null || !("value" in value)) return null;
if ("indefinite" in value) return value;
else return value.timeout > Date.now() ? value : null;
}
set(object, ttl, section) {
return this._set(object, ttl, section);
}
setIndefinite(object, section) {
return this._set(object, null, section);
}
_set(object, ttl, section) {
const timeout = ttl === null ? null : Date.now() + ttl;
if (section) {
if (!(section in this.cache)) this.cache[section] = {};
for (const [key, value] of Object.entries(object)) this.cache[section][key] = this.createCacheValue(value, timeout);
} else for (const [key, value] of Object.entries(object)) this.cache[key] = this.createCacheValue(value, timeout);
this.schedulePersist();
}
createCacheValue(value, timeout) {
if (timeout === null) return {
value,
indefinite: true
};
else return {
value,
timeout
};
}
async clear(section) {
if (section) {
delete this.cache[section];
this.schedulePersist();
} else {
this.cache = {};
if (this.persistTimer) clearTimeout(this.persistTimer);
this.persistTimer = null;
await ttStorage.set({ cache: {} });
}
}
async refresh() {
let hasChanged = false;
const now = Date.now();
refreshObject(this.cache);
for (const section in this.cache) if (!Object.keys(this.cache[section]).length) delete this.cache[section];
if (hasChanged) this.schedulePersist();
function refreshObject(object) {
for (const key in object) {
const value = object[key];
if ("value" in value) {
const cacheValue = value;
if ("indefinite" in cacheValue || cacheValue.timeout > now) continue;
hasChanged = true;
delete object[key];
} else refreshObject(value);
}
}
}
schedulePersist() {
if (this.persistTimer) clearTimeout(this.persistTimer);
this.persistTimer = setTimeout(() => {
this.persistTimer = null;
ttStorage.set({ cache: this.cache }).catch((err) => console.error("Failed to persist cache.", err));
}, 500);
}
};
var ttCache = new TornToolsCache();
var DefaultSetting = class {
type;
defaultValue;
constructor(type, defaultValue) {
this.type = type;
this.defaultValue = defaultValue ?? null;
}
};
var DEFAULT_STORAGE = {
version: {
current: new DefaultSetting("string", () => RUNTIME_INFORMATION.getVersion()),
initial: new DefaultSetting("string", () => RUNTIME_INFORMATION.getVersion()),
oldVersion: new DefaultSetting("string"),
showNotice: new DefaultSetting("boolean", true)
},
api: {
torn: {
key: new DefaultSetting("string"),
online: new DefaultSetting("boolean", true),
error: new DefaultSetting("string"),
owner: new DefaultSetting("number")
},
tornstats: { key: new DefaultSetting("string") },
yata: { key: new DefaultSetting("string") },
ffScouter: { key: new DefaultSetting("string") }
},
settings: {
updateNotice: new DefaultSetting("boolean", true),
featureDisplay: new DefaultSetting("boolean", true),
featureDisplayPosition: new DefaultSetting("string", "bottom-left"),
featureDisplayOnlyFailed: new DefaultSetting("boolean", false),
featureDisplayHideDisabled: new DefaultSetting("boolean", false),
featureDisplayHideEmpty: new DefaultSetting("boolean", true),
developer: new DefaultSetting("boolean", false),
formatting: {
tct: new DefaultSetting("boolean", false),
date: new DefaultSetting("string", "eu"),
time: new DefaultSetting("string", "eu")
},
sorting: { abroad: {
column: new DefaultSetting("string", ""),
order: new DefaultSetting("string", "none")
} },
notifications: {
sound: new DefaultSetting("string", "default"),
soundCustom: new DefaultSetting("string", ""),
tts: new DefaultSetting("boolean", false),
ttsVoice: new DefaultSetting("string", "default"),
ttsRate: new DefaultSetting("number", 1),
link: new DefaultSetting("boolean", true),
volume: new DefaultSetting("number", 100),
requireInteraction: new DefaultSetting("boolean", false),
types: {
global: new DefaultSetting("boolean", () => typeof Notification !== "undefined" && Notification.permission === "granted"),
events: new DefaultSetting("boolean", true),
messages: new DefaultSetting("boolean", true),
status: new DefaultSetting("boolean", true),
traveling: new DefaultSetting("boolean", true),
cooldowns: new DefaultSetting("boolean", true),
education: new DefaultSetting("boolean", true),
newDay: new DefaultSetting("boolean", true),
energy: new DefaultSetting("array", ["100%"]),
nerve: new DefaultSetting("array", ["100%"]),
happy: new DefaultSetting("array", ["100%"]),
life: new DefaultSetting("array", ["100%"]),
offline: new DefaultSetting("array", []),
chainTimerEnabled: new DefaultSetting("boolean", true),
chainBonusEnabled: new DefaultSetting("boolean", true),
leavingHospitalEnabled: new DefaultSetting("boolean", true),
landingEnabled: new DefaultSetting("boolean", true),
cooldownDrugEnabled: new DefaultSetting("boolean", true),
cooldownBoosterEnabled: new DefaultSetting("boolean", true),
cooldownMedicalEnabled: new DefaultSetting("boolean", true),
chainTimer: new DefaultSetting("array", []),
chainBonus: new DefaultSetting("array", []),
leavingHospital: new DefaultSetting("array", []),
landing: new DefaultSetting("array", []),
cooldownDrug: new DefaultSetting("array", []),
cooldownBooster: new DefaultSetting("array", []),
cooldownMedical: new DefaultSetting("array", []),
stocks: new DefaultSetting("object", {}),
missionsLimitEnabled: new DefaultSetting("boolean", false),
missionsLimit: new DefaultSetting("string", ""),
missionsExpireEnabled: new DefaultSetting("boolean", false),
missionsExpire: new DefaultSetting("array", []),
npcsGlobal: new DefaultSetting("boolean", true),
npcs: new DefaultSetting("array", []),
npcPlannedEnabled: new DefaultSetting("boolean", true),
npcPlanned: new DefaultSetting("array", []),
refillEnergyEnabled: new DefaultSetting("boolean", true),
refillEnergy: new DefaultSetting("string", ""),
refillNerveEnabled: new DefaultSetting("boolean", true),
refillNerve: new DefaultSetting("string", "")
}
},
apiUsage: {
comment: new DefaultSetting("string", "TornTools"),
delayEssential: new DefaultSetting("number", 30),
delayBasic: new DefaultSetting("number", 120),
delayPassive: new DefaultSetting("number", 3600),
delayStakeouts: new DefaultSetting("number", 30),
user: {
bars: new DefaultSetting("boolean", true),
cooldowns: new DefaultSetting("boolean", true),
travel: new DefaultSetting("boolean", true),
newevents: new DefaultSetting("boolean", true),
newmessages: new DefaultSetting("boolean", true),
refills: new DefaultSetting("boolean", true),
stocks: new DefaultSetting("boolean", true),
education: new DefaultSetting("boolean", true),
networth: new DefaultSetting("boolean", true),
inventory: new DefaultSetting("boolean", true),
jobpoints: new DefaultSetting("boolean", true),
merits: new DefaultSetting("boolean", true),
perks: new DefaultSetting("boolean", true),
icons: new DefaultSetting("boolean", true),
ammo: new DefaultSetting("boolean", true),
battlestats: new DefaultSetting("boolean", true),
crimes: new DefaultSetting("boolean", true),
workstats: new DefaultSetting("boolean", true),
skills: new DefaultSetting("boolean", true),
weaponexp: new DefaultSetting("boolean", true),
properties: new DefaultSetting("boolean", true),
calendar: new DefaultSetting("boolean", true),
organizedcrime: new DefaultSetting("boolean", true),
missions: new DefaultSetting("boolean", true),
personalstats: new DefaultSetting("boolean", true),
attacks: new DefaultSetting("boolean", true),
money: new DefaultSetting("boolean", true),
honors: new DefaultSetting("boolean", true),
medals: new DefaultSetting("boolean", true),
virus: new DefaultSetting("boolean", true)
}
},
themes: {
pages: new DefaultSetting("string", "default"),
containers: new DefaultSetting("string", "default")
},
hideIcons: new DefaultSetting("array", []),
hideCasinoGames: new DefaultSetting("array", []),
hideStocks: new DefaultSetting("array", []),
alliedFactions: new DefaultSetting("array", []),
customLinks: new DefaultSetting("array", []),
employeeInactivityWarning: new DefaultSetting("array", []),
factionInactivityWarning: new DefaultSetting("array", []),
userAlias: new DefaultSetting("array", []),
csvDelimiter: new DefaultSetting("string", ";"),
pages: {
global: {
alignLeft: new DefaultSetting("boolean", false),
hideLevelUpgrade: new DefaultSetting("boolean", false),
hideQuitButtons: new DefaultSetting("boolean", false),
hideTutorials: new DefaultSetting("boolean", false),
keepAttackHistory: new DefaultSetting("boolean", true),
miniProfileLastAction: new DefaultSetting("boolean", true),
reviveProvider: new DefaultSetting("string", ""),
pageTitles: new DefaultSetting("boolean", true),
stackingMode: new DefaultSetting("boolean", false),
noOutsideLinkAlert: new DefaultSetting("boolean", false),
urlFill: new DefaultSetting("boolean", true)
},
profile: {
avgpersonalstats: new DefaultSetting("boolean", false),
statusIndicator: new DefaultSetting("boolean", true),
idBesideProfileName: new DefaultSetting("boolean", true),
notes: new DefaultSetting("boolean", true),
showAllyWarning: new DefaultSetting("boolean", true),
ageToWords: new DefaultSetting("boolean", true),
disableAllyAttacks: new DefaultSetting("boolean", true),
box: new DefaultSetting("boolean", true),
boxStats: new DefaultSetting("boolean", true),
boxSpy: new DefaultSetting("boolean", true),
boxStakeout: new DefaultSetting("boolean", true),
boxAttackHistory: new DefaultSetting("boolean", true),
boxFetch: new DefaultSetting("boolean", true)
},
chat: {
fontSize: new DefaultSetting("number", 12),
searchChat: new DefaultSetting("boolean", true),
completeUsernames: new DefaultSetting("boolean", true),
highlights: new DefaultSetting("array", [{
name: "$player",
color: "#7ca900"
}]),
titleHighlights: new DefaultSetting("array", []),
tradeTimer: new DefaultSetting("boolean", true),
resizable: new DefaultSetting("boolean", true),
hideChatButton: new DefaultSetting("boolean", true),
hideChat: new DefaultSetting("boolean", false)
},
sidebar: {
notes: new DefaultSetting("boolean", true),
highlightEnergy: new DefaultSetting("boolean", true),
highlightNerve: new DefaultSetting("boolean", false),
ocTimer: new DefaultSetting("boolean", true),
oc2Timer: new DefaultSetting("boolean", true),
oc2TimerPosition: new DefaultSetting("boolean", false),
oc2TimerLevel: new DefaultSetting("boolean", true),
factionOCTimer: new DefaultSetting("boolean", false),
collapseAreas: new DefaultSetting("boolean", true),
settingsLink: new DefaultSetting("boolean", true),
hideGymHighlight: new DefaultSetting("boolean", false),
hideNewspaperHighlight: new DefaultSetting("boolean", false),
upkeepPropHighlight: new DefaultSetting("number", 0),
barLinks: new DefaultSetting("boolean", true),
pointsValue: new DefaultSetting("boolean", true),
npcLootTimes: new DefaultSetting("boolean", true),
npcLootTimesService: new DefaultSetting("string", "tornstats"),
cooldownEndTimes: new DefaultSetting("boolean", true),
companyAddictionLevel: new DefaultSetting("boolean", true),
showJobPointsToolTip: new DefaultSetting("boolean", true),
rwTimer: new DefaultSetting("boolean", true),
virusTimer: new DefaultSetting("boolean", false)
},
popup: {
dashboard: new DefaultSetting("boolean", true),
marketSearch: new DefaultSetting("boolean", true),
bazaarUsingExternal: new DefaultSetting("boolean", true),
calculator: new DefaultSetting("boolean", true),
stocksOverview: new DefaultSetting("boolean", true),
notifications: new DefaultSetting("boolean", true),
defaultTab: new DefaultSetting("string", "dashboard"),
showStakeouts: new DefaultSetting("boolean", true),
showIcons: new DefaultSetting("boolean", true),
fullBarTime: new DefaultSetting("boolean", false)
},
icon: {
global: new DefaultSetting("boolean", true),
energy: new DefaultSetting("boolean", true),
nerve: new DefaultSetting("boolean", true),
happy: new DefaultSetting("boolean", true),
life: new DefaultSetting("boolean", true),
chain: new DefaultSetting("boolean", true),
travel: new DefaultSetting("boolean", true)
},
education: {
greyOut: new DefaultSetting("boolean", true),
finishTime: new DefaultSetting("boolean", true)
},
jail: { filter: new DefaultSetting("boolean", true) },
bank: {
investmentInfo: new DefaultSetting("boolean", true),
investmentDueTime: new DefaultSetting("boolean", true)
},
home: {
networthDetails: new DefaultSetting("boolean", true),
effectiveStats: new DefaultSetting("boolean", true)
},
items: {
quickItems: new DefaultSetting("boolean", true),
values: new DefaultSetting("boolean", true),
drugDetails: new DefaultSetting("boolean", true),
marketLinks: new DefaultSetting("boolean", false),
highlightBloodBags: new DefaultSetting("string", "none"),
missingFlowers: new DefaultSetting("boolean", false),
missingPlushies: new DefaultSetting("boolean", false),
missingBooks: new DefaultSetting("boolean", false),
bookEffects: new DefaultSetting("boolean", true),
canGains: new DefaultSetting("boolean", true),
nerveGains: new DefaultSetting("boolean", true),
candyHappyGains: new DefaultSetting("boolean", true),
energyWarning: new DefaultSetting("boolean", true),
medicalLife: new DefaultSetting("boolean", true),
openedSupplyPackValue: new DefaultSetting("boolean", true),
hideRecycleMessage: new DefaultSetting("boolean", false),
hideTooManyItemsWarning: new DefaultSetting("boolean", false)
},
crimes: { quickCrimes: new DefaultSetting("boolean", true) },
companies: {
idBesideCompanyName: new DefaultSetting("boolean", false),
specials: new DefaultSetting("boolean", true),
autoStockFill: new DefaultSetting("boolean", true),
employeeEffectiveness: new DefaultSetting("number", 18)
},
travel: {
computer: new DefaultSetting("boolean", true),
table: new DefaultSetting("boolean", true),
cleanFlight: new DefaultSetting("boolean", false),
tabTitleTimer: new DefaultSetting("boolean", false),
travelProfits: new DefaultSetting("boolean", true),
fillMax: new DefaultSetting("boolean", true),
peopleFilter: new DefaultSetting("boolean", true),
landingTime: new DefaultSetting("boolean", true),
flyingTime: new DefaultSetting("boolean", true),
itemFilter: new DefaultSetting("boolean", true),
energyWarning: new DefaultSetting("boolean", true),
cooldownWarnings: new DefaultSetting("boolean", true),
autoTravelTableCountry: new DefaultSetting("boolean", false),
autoFillMax: new DefaultSetting("boolean", true),
efficientRehab: new DefaultSetting("boolean", true),
efficientRehabSelect: new DefaultSetting("boolean", false),
hideInventoryButton: new DefaultSetting("boolean", true),
fastHunting: new DefaultSetting("boolean", true)
},
stocks: {
filter: new DefaultSetting("boolean", true),
acronyms: new DefaultSetting("boolean", true),
valueAndProfit: new DefaultSetting("boolean", true),
moneyInput: new DefaultSetting("boolean", true)
},
competitions: {
easterEggs: new DefaultSetting("boolean", false),
easterEggsAlert: new DefaultSetting("boolean", true)
},
events: { worth: new DefaultSetting("boolean", true) },
hospital: { filter: new DefaultSetting("boolean", true) },
auction: {
filter: new DefaultSetting("boolean", true),
movePagination: new DefaultSetting("boolean", false)
},
api: {
autoFillKey: new DefaultSetting("boolean", true),
autoDemo: new DefaultSetting("boolean", false),
autoPretty: new DefaultSetting("boolean", true),
clickableSelections: new DefaultSetting("boolean", true)
},
forums: {
menu: new DefaultSetting("boolean", true),
hidePosts: new DefaultSetting("object", {}),
hideThreads: new DefaultSetting("object", {}),
highlightPosts: new DefaultSetting("object", {}),
highlightThreads: new DefaultSetting("object", {}),
ignoredThreads: new DefaultSetting("object", {}),
debugInfoBtn: new DefaultSetting("boolean", true),
onlyNewFeedButton: new DefaultSetting("boolean", true)
},
bazaar: {
itemsCost: new DefaultSetting("boolean", true),
worth: new DefaultSetting("boolean", true),
fillMax: new DefaultSetting("boolean", true),
maxBuyIgnoreCash: new DefaultSetting("boolean", false),
highlightSubVendorItems: new DefaultSetting("boolean", false)
},
trade: {
itemValues: new DefaultSetting("boolean", true),
openChat: new DefaultSetting("boolean", true)
},
displayCase: { worth: new DefaultSetting("boolean", true) },
shops: {
fillMax: new DefaultSetting("boolean", true),
maxBuyIgnoreCash: new DefaultSetting("boolean", false),
profit: new DefaultSetting("boolean", true),
filters: new DefaultSetting("boolean", true),
values: new DefaultSetting("boolean", true)
},
casino: {
netTotal: new DefaultSetting("boolean", true),
blackjack: new DefaultSetting("boolean", true),
highlow: new DefaultSetting("boolean", false),
highlowMovement: new DefaultSetting("boolean", true)
},
racing: {
winPercentage: new DefaultSetting("boolean", true),
upgrades: new DefaultSetting("boolean", true),
filter: new DefaultSetting("boolean", true)
},
faction: {
idBesideFactionName: new DefaultSetting("boolean", false),
csvRaidReport: new DefaultSetting("boolean", true),
csvRankedWarReport: new DefaultSetting("boolean", true),
csvWarReport: new DefaultSetting("boolean", true),
csvChainReport: new DefaultSetting("boolean", true),
csvChallengeContributions: new DefaultSetting("boolean", true),
openOc: new DefaultSetting("boolean", true),
highlightOwn: new DefaultSetting("boolean", true),
availablePlayers: new DefaultSetting("boolean", true),
recommendedNnb: new DefaultSetting("boolean", true),
ocNnb: new DefaultSetting("boolean", true),
ocTimes: new DefaultSetting("boolean", true),
ocLastAction: new DefaultSetting("boolean", true),
banker: new DefaultSetting("boolean", true),
showFullInfobox: new DefaultSetting("boolean", true),
foldableInfobox: new DefaultSetting("boolean", true),
numberMembers: new DefaultSetting("boolean", true),
warFinishTimes: new DefaultSetting("boolean", false),
memberFilter: new DefaultSetting("boolean", true),
memberFilterRevivable: new DefaultSetting("boolean", false),
armoryFilter: new DefaultSetting("boolean", true),
armoryWorth: new DefaultSetting("boolean", true),
upgradeRequiredRespect: new DefaultSetting("boolean", true),
memberInfo: new DefaultSetting("boolean", false),
rankedWarFilter: new DefaultSetting("boolean", true),
quickItems: new DefaultSetting("boolean", true),
stakeout: new DefaultSetting("boolean", true),
showFactionSpy: new DefaultSetting("boolean", true),
oc2Filter: new DefaultSetting("boolean", true),
warnCrime: new DefaultSetting("boolean", false),
rankedWarValue: new DefaultSetting("boolean", true),
totalChallengeContributions: new DefaultSetting("boolean", true),
memberRevives: new DefaultSetting("boolean", true),
warReportHighlight: new DefaultSetting("boolean", true)
},
property: {
filter: new DefaultSetting("boolean", true),
value: new DefaultSetting("boolean", true),
happy: new DefaultSetting("boolean", true)
},
gym: {
specialist: new DefaultSetting("boolean", true),
disableStats: new DefaultSetting("boolean", true),
graph: new DefaultSetting("boolean", true),
steadfast: new DefaultSetting("boolean", true),
progress: new DefaultSetting("boolean", true)
},
missions: {
hints: new DefaultSetting("boolean", true),
rewards: new DefaultSetting("boolean", true)
},
attack: {
bonusInformation: new DefaultSetting("boolean", true),
timeoutWarning: new DefaultSetting("boolean", true),
fairAttack: new DefaultSetting("boolean", true),
weaponExperience: new DefaultSetting("boolean", true),
hideAttackButtons: new DefaultSetting("array", [])
},
city: {
items: new DefaultSetting("boolean", true),
combineDuplicates: new DefaultSetting("boolean", true),
groupByPeriod: new DefaultSetting("boolean", false),
groupByPeriodUnit: new DefaultSetting("string", "day")
},
joblist: { specials: new DefaultSetting("boolean", true) },
bounties: { filter: new DefaultSetting("boolean", true) },
userlist: { filter: new DefaultSetting("boolean", true) },
itemmarket: {
highlightCheapItems: new DefaultSetting("number|empty", ""),
highlightCheapItemsSound: new DefaultSetting("boolean", false),
leftBar: new DefaultSetting("boolean", false),
fillMax: new DefaultSetting("boolean", true),
bazaars: new DefaultSetting("boolean", true)
},
competition: { filter: new DefaultSetting("boolean", true) },
museum: { autoFill: new DefaultSetting("boolean", true) },
enemies: { filter: new DefaultSetting("boolean", true) },
friends: { filter: new DefaultSetting("boolean", true) },
targets: { filter: new DefaultSetting("boolean", true) },
crimes2: { value: new DefaultSetting("boolean", true) }
},
scripts: {
noConfirm: {
itemEquip: new DefaultSetting("boolean", true),
tradeAccept: new DefaultSetting("boolean", false),
pointsMarketRemove: new DefaultSetting("boolean", false),
pointsMarketBuy: new DefaultSetting("boolean", false),
abroadItemBuy: new DefaultSetting("boolean", true),
propertiesSell: new DefaultSetting("boolean", false)
},
achievements: {
show: new DefaultSetting("boolean", true),
completed: new DefaultSetting("boolean", false)
},
reminders: {
finished: new DefaultSetting("boolean", false),
show: new DefaultSetting("boolean", true),
types: {
energyRefill: new DefaultSetting("boolean", true),
nerveRefill: new DefaultSetting("boolean", true),
medicalCooldown: new DefaultSetting("boolean", true),
boosterCooldown: new DefaultSetting("boolean", true),
drugCooldown: new DefaultSetting("boolean", true),
bankInvestment: new DefaultSetting("boolean", true),
virusCoding: new DefaultSetting("boolean", true),
missionReward: new DefaultSetting("boolean", true),
oc: new DefaultSetting("boolean", true),
ocItem: new DefaultSetting("boolean", true),
race: new DefaultSetting("boolean", true),
education: new DefaultSetting("boolean", true)
}
},
lastAction: {
factionMember: new DefaultSetting("boolean", false),
companyOwn: new DefaultSetting("boolean", false),
companyOther: new DefaultSetting("boolean", false)
},
statsEstimate: {
global: new DefaultSetting("boolean", true),
delay: new DefaultSetting("number", 1500),
cachedOnly: new DefaultSetting("boolean", true),
displayNoResult: new DefaultSetting("boolean", false),
maxLevel: new DefaultSetting("number", 100),
profiles: new DefaultSetting("boolean", true),
enemies: new DefaultSetting("boolean", true),
hof: new DefaultSetting("boolean", true),
attacks: new DefaultSetting("boolean", true),
userlist: new DefaultSetting("boolean", true),
bounties: new DefaultSetting("boolean", true),
factions: new DefaultSetting("boolean", true),
wars: new DefaultSetting("boolean", true),
abroad: new DefaultSetting("boolean", true),
competition: new DefaultSetting("boolean", true),
rankedWars: new DefaultSetting("boolean", true),
targets: new DefaultSetting("boolean", true)
},
ffScouter: {
miniProfile: new DefaultSetting("boolean", true),
profile: new DefaultSetting("boolean", true),
attack: new DefaultSetting("boolean", true),
factionList: new DefaultSetting("boolean", true),
gauge: new DefaultSetting("boolean", true)
}
},
external: {
tornstats: new DefaultSetting("boolean", false),
yata: new DefaultSetting("boolean", false),
prometheus: new DefaultSetting("boolean", false),
lzpt: new DefaultSetting("boolean", false),
tornw3b: new DefaultSetting("boolean", false),
ffScouter: new DefaultSetting("boolean", false),
tornintel: new DefaultSetting("boolean", false),
playgroundTorntools: new DefaultSetting("boolean", false)
},
servicePreferences: {
spies: {
tornstats: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 1)
},
yata: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 2)
}
},
factionSpies: {
tornstats: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 1)
},
yata: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 2)
}
},
travelData: {
prometheus: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 1)
},
tornintel: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 1)
},
yata: {
enabled: new DefaultSetting("boolean", true),
priority: new DefaultSetting("number", 2)
}
}
},
reporting: {}
},
filters: {
hospital: {
enabled: new DefaultSetting("boolean", true),
timeStart: new DefaultSetting("number", 0),
timeEnd: new DefaultSetting("number", 100),
levelStart: new DefaultSetting("number", 0),
levelEnd: new DefaultSetting("number", 100),
faction: new DefaultSetting("string", ""),
activity: new DefaultSetting("array", []),
revivesOn: new DefaultSetting("boolean", false)
},
jail: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
faction: new DefaultSetting("string", "All"),
timeStart: new DefaultSetting("number", 0),
timeEnd: new DefaultSetting("number", 100),
levelStart: new DefaultSetting("number", 1),
levelEnd: new DefaultSetting("number", 100),
scoreStart: new DefaultSetting("number", 0),
scoreEnd: new DefaultSetting("number", 5e3),
bailCost: new DefaultSetting("number", -1)
},
racing: {
enabled: new DefaultSetting("boolean", true),
hideRaces: new DefaultSetting("array", []),
timeStart: new DefaultSetting("number", 0),
timeEnd: new DefaultSetting("number", 48),
driversMin: new DefaultSetting("number", 2),
driversMax: new DefaultSetting("number", 100),
lapsMin: new DefaultSetting("number", 1),
lapsMax: new DefaultSetting("number", 100),
track: new DefaultSetting("array", []),
name: new DefaultSetting("string", ""),
exemptions: new DefaultSetting("array", [])
},
containers: new DefaultSetting("object", {}),
travel: {
open: new DefaultSetting("boolean", false),
type: new DefaultSetting("string", "basic"),
categories: new DefaultSetting("array", []),
countries: new DefaultSetting("array", []),
hideOutOfStock: new DefaultSetting("boolean", false),
applySalesTax: new DefaultSetting("boolean", false),
sellAnonymously: new DefaultSetting("boolean", false)
},
abroadPeople: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
status: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 0),
levelEnd: new DefaultSetting("number", 100),
faction: new DefaultSetting("string", ""),
special: {
newPlayer: new DefaultSetting("string", "both"),
inCompany: new DefaultSetting("string", "both"),
inFaction: new DefaultSetting("string", "both"),
isDonator: new DefaultSetting("string", "both"),
hasBounties: new DefaultSetting("string", "both"),
bazaarOpen: new DefaultSetting("string", "both")
},
estimates: new DefaultSetting("array", []),
ffScoreMax: new DefaultSetting("number", null),
ffScoreMin: new DefaultSetting("number", null)
},
abroadItems: {
enabled: new DefaultSetting("boolean", true),
profitOnly: new DefaultSetting("boolean", false),
outOfStock: new DefaultSetting("boolean", false),
categories: new DefaultSetting("array", []),
taxes: new DefaultSetting("array", [])
},
trade: { hideValues: new DefaultSetting("boolean", false) },
gym: {
specialist1: new DefaultSetting("string", "none"),
specialist2: new DefaultSetting("string", "none"),
strength: new DefaultSetting("boolean", false),
speed: new DefaultSetting("boolean", false),
defense: new DefaultSetting("boolean", false),
dexterity: new DefaultSetting("boolean", false)
},
city: { highlightItems: new DefaultSetting("boolean", true) },
bounties: {
maxLevel: new DefaultSetting("number", 100),
hideUnavailable: new DefaultSetting("boolean", false)
},
properties: {
enabled: new DefaultSetting("boolean", true),
daysOnLeaseLow: new DefaultSetting("number", 1),
daysOnLeaseHigh: new DefaultSetting("number", 100),
status: new DefaultSetting("string", "all"),
types: new DefaultSetting("array", [])
},
userlist: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 0),
levelEnd: new DefaultSetting("number", 100),
special: {
fedded: new DefaultSetting("string", "both"),
fallen: new DefaultSetting("string", "both"),
traveling: new DefaultSetting("string", "both"),
newPlayer: new DefaultSetting("string", "both"),
onWall: new DefaultSetting("string", "both"),
inCompany: new DefaultSetting("string", "both"),
inFaction: new DefaultSetting("string", "both"),
isDonator: new DefaultSetting("string", "both"),
inHospital: new DefaultSetting("string", "both"),
inJail: new DefaultSetting("string", "both"),
earlyDischarge: new DefaultSetting("string", "both"),
hasBounties: new DefaultSetting("string", "both"),
bazaarOpen: new DefaultSetting("string", "both")
},
hospReason: {
attackedBy: new DefaultSetting("string", "both"),
muggedBy: new DefaultSetting("string", "both"),
hospitalizedBy: new DefaultSetting("string", "both"),
other: new DefaultSetting("string", "both")
},
estimates: new DefaultSetting("array", []),
ffScoreMax: new DefaultSetting("number", null),
ffScoreMin: new DefaultSetting("number", null)
},
stocks: {
enabled: new DefaultSetting("boolean", true),
name: new DefaultSetting("string", ""),
investment: {
owned: new DefaultSetting("string", "both"),
benefit: new DefaultSetting("string", "both"),
passive: new DefaultSetting("string", "both"),
collectionReady: new DefaultSetting("string", "both")
},
price: {
price: new DefaultSetting("string", "both"),
profit: new DefaultSetting("string", "both")
}
},
faction: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 1),
levelEnd: new DefaultSetting("number", 100),
lastActionStart: new DefaultSetting("number", 0),
lastActionEnd: new DefaultSetting("number", -1),
status: new DefaultSetting("array", []),
position: new DefaultSetting("string", ""),
special: {
fedded: new DefaultSetting("string", "both"),
fallen: new DefaultSetting("string", "both"),
newPlayer: new DefaultSetting("string", "both"),
inCompany: new DefaultSetting("string", "both"),
isDonator: new DefaultSetting("string", "both"),
isRecruit: new DefaultSetting("string", "both")
},
ffScoreMax: new DefaultSetting("number", null),
ffScoreMin: new DefaultSetting("number", null),
revivable: new DefaultSetting("array", [])
},
factionArmory: {
enabled: new DefaultSetting("boolean", true),
hideUnavailable: new DefaultSetting("boolean", false),
weapons: {
name: new DefaultSetting("string", ""),
category: new DefaultSetting("string", ""),
rarity: new DefaultSetting("string", ""),
weaponType: new DefaultSetting("string", ""),
damage: new DefaultSetting("string", ""),
accuracy: new DefaultSetting("string", ""),
weaponBonus: new DefaultSetting("array", [])
},
armor: {
name: new DefaultSetting("string", ""),
rarity: new DefaultSetting("string", ""),
defence: new DefaultSetting("string", ""),
set: new DefaultSetting("string", ""),
armorBonus: new DefaultSetting("string", "")
},
temporary: { name: new DefaultSetting("string", "") }
},
factionRankedWar: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
status: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 1),
levelEnd: new DefaultSetting("number", 100),
estimates: new DefaultSetting("array", []),
ffScoreMax: new DefaultSetting("number", null),
ffScoreMin: new DefaultSetting("number", null)
},
profile: {
relative: new DefaultSetting("boolean", false),
stats: new DefaultSetting("array", [])
},
competition: {
levelStart: new DefaultSetting("number", 1),
levelEnd: new DefaultSetting("number", 100),
estimates: new DefaultSetting("array", [])
},
shops: {
hideLoss: new DefaultSetting("boolean", false),
hideUnder100: new DefaultSetting("boolean", false)
},
auction: {
enabled: new DefaultSetting("boolean", true),
weapons: {
name: new DefaultSetting("string", ""),
category: new DefaultSetting("string", ""),
rarity: new DefaultSetting("string", ""),
weaponType: new DefaultSetting("string", ""),
damage: new DefaultSetting("string", ""),
accuracy: new DefaultSetting("string", ""),
weaponBonus: new DefaultSetting("array", []),
quality: new DefaultSetting("string", "")
},
armor: {
name: new DefaultSetting("string", ""),
rarity: new DefaultSetting("string", ""),
defence: new DefaultSetting("string", ""),
set: new DefaultSetting("string", ""),
armorBonus: new DefaultSetting("string", "")
},
items: {
name: new DefaultSetting("string", ""),
category: new DefaultSetting("string", ""),
rarity: new DefaultSetting("string", "")
}
},
enemies: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 0),
levelEnd: new DefaultSetting("number", 100),
estimates: new DefaultSetting("array", []),
ffScoreMax: new DefaultSetting("number", null),
ffScoreMin: new DefaultSetting("number", null)
},
friends: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 0),
levelEnd: new DefaultSetting("number", 100)
},
targets: {
enabled: new DefaultSetting("boolean", true),
activity: new DefaultSetting("array", []),
levelStart: new DefaultSetting("number", 0),
levelEnd: new DefaultSetting("number", 100),
estimates: new DefaultSetting("array", []),
ffScoreMax: new DefaultSetting("number", null),
ffScoreMin: new DefaultSetting("number", null)
},
oc2: {
enabled: new DefaultSetting("boolean", true),
difficulty: new DefaultSetting("array", []),
status: new DefaultSetting("array", [])
}
},
userdata: new DefaultSetting("object", { date: -1 }),
torndata: new DefaultSetting("object", { date: -2 }),
stockdata: {
date: new DefaultSetting("number", 0),
stocks: new DefaultSetting("array", [])
},
factiondata: new DefaultSetting("object", {}),
localdata: {
tradeMessage: new DefaultSetting("number", 0),
popup: { calculatorItems: new DefaultSetting("array", []) },
vault: {
initialized: new DefaultSetting("boolean", false),
lastTransaction: new DefaultSetting("string", ""),
total: new DefaultSetting("number", 0),
user: {
initial: new DefaultSetting("number", 0),
current: new DefaultSetting("number", 0)
},
partner: {
initial: new DefaultSetting("number", 0),
current: new DefaultSetting("number", 0)
}
},
chatResize: new DefaultSetting("object", {}),
feedHidden: new DefaultSetting("object", {}),
threadsHiddenInFeed: new DefaultSetting("array", []),
hiddenTravelInventory: new DefaultSetting("boolean", false)
},
stakeouts: new DefaultSetting("object", {
list: [],
date: 0
}),
factionStakeouts: new DefaultSetting("object", {
list: [],
date: 0
}),
attackHistory: {
fetchData: new DefaultSetting("boolean", true),
lastAttack: new DefaultSetting("number", 0),
history: new DefaultSetting("object", {})
},
notes: {
sidebar: {
text: new DefaultSetting("string", ""),
height: new DefaultSetting("string", "22px")
},
profile: new DefaultSetting("object", {})
},
quick: {
items: new DefaultSetting("array", []),
factionItems: new DefaultSetting("array", []),
crimes: new DefaultSetting("array", []),
jail: new DefaultSetting("array", [])
},
cache: new DefaultSetting("object", {}),
npcs: new DefaultSetting("object", {}),
notificationHistory: new DefaultSetting("array", []),
notifications: {
events: new DefaultSetting("object", {}),
messages: new DefaultSetting("object", {}),
newDay: new DefaultSetting("object", {}),
energy: new DefaultSetting("object", {}),
happy: new DefaultSetting("object", {}),
nerve: new DefaultSetting("object", {}),
life: new DefaultSetting("object", {}),
travel: new DefaultSetting("object", {}),
drugs: new DefaultSetting("object", {}),
boosters: new DefaultSetting("object", {}),
medical: new DefaultSetting("object", {}),
hospital: new DefaultSetting("object", {}),
chain: new DefaultSetting("object", {}),
chainCount: new DefaultSetting("object", {}),
stakeouts: new DefaultSetting("object", {}),
npcs: new DefaultSetting("object", {}),
offline: new DefaultSetting("object", {}),
missionsLimit: new DefaultSetting("object", {}),
missionsExpire: new DefaultSetting("object", {}),
refillEnergy: new DefaultSetting("object", {}),
refillNerve: new DefaultSetting("object", {})
},
migrations: new DefaultSetting("array", [])
};
function getDefaultStorage(defaultStorage) {
const newStorage = {};
for (const key in defaultStorage) if (typeof defaultStorage[key] === "object") {
const setting = defaultStorage[key];
if (setting instanceof DefaultSetting && "defaultValue" in setting) switch (typeof setting.defaultValue) {
case "function":
newStorage[key] = setting.defaultValue();
break;
case "boolean":
case "number":
case "string":
case "object":
newStorage[key] = setting.defaultValue;
break;
default: newStorage[key] = setting.defaultValue;
}
else newStorage[key] = getDefaultStorage(defaultStorage[key]);
} else newStorage[key] = defaultStorage[key];
return newStorage;
}
var MIGRATIONS = [
{
id: "43fae1f2-5568-4ae5-b12f-f3625e1e58c6",
version: "9.0.0",
execute(database, _flags, _oldStorage) {
database.cache["personal-stats"] = {};
}
},
{
id: "b194a6d5-4230-4b03-8a8b-bebd7c431cc9",
version: "9.0.0",
execute(database, _flags, _oldStorage) {
database.settings.pages.api.autoDemo = false;
}
},
{
id: "b0f539ba-41f8-4eed-93e2-e8523f7c49a5",
version: "9.0.1",
execute(database, _flags, oldStorage) {
const oldCustomLinks = oldStorage?.settings?.customLinks ?? [];
database.settings.customLinks = oldCustomLinks.map((link) => {
return link.preset && link.preset !== "custom" ? {
newTab: link.newTab,
location: link.location,
name: link.name,
preset: link.preset
} : {
newTab: link.newTab,
location: link.location,
name: link.name,
href: link.href
};
});
}
},
{
id: "360b1f70-c78b-44c1-b217-24bd6b398bac",
version: "9.0.5",
execute(database, _flags, oldStorage) {
if (!oldStorage?.settings?.userAlias || Array.isArray(oldStorage.settings.userAlias)) return;
const oldUserAliases = oldStorage.settings.userAlias;
database.settings.userAlias = Object.entries(oldUserAliases).map(([id, { alias, name }]) => {
const idMatch = id.match(/^(\d+)$/);
return idMatch ? {
userId: parseInt(idMatch[0]),
userName: name,
alias
} : {
userId: -1,
userName: name,
alias,
incorrectId: id
};
});
}
},
{
id: "95c020eb-2c75-4bbe-8fe9-64f96f108f48",
version: "9.0.5",
execute(database, _flags, oldStorage) {
if (!oldStorage?.settings?.pages?.popup?.defaultTab) return;
if (oldStorage.settings.pages.popup.defaultTab === "stocks") database.settings.pages.popup.defaultTab = "stocksOverview";
else if (oldStorage.settings.pages.popup.defaultTab === "market") database.settings.pages.popup.defaultTab = "marketSearch";
}
},
{
id: "96356911-fecd-4b79-9825-ee5ad422c8fe",
version: "9.0.5",
execute(database, _flags, oldStorage) {
if (typeof oldStorage?.settings?.pages?.popup.hoverBarTime !== "boolean") return;
database.settings.pages.popup.fullBarTime = oldStorage.settings.pages.popup.hoverBarTime;
}
},
{
id: "7396191c-35a9-4d92-905a-0e411f9a6823",
version: "9.0.5",
execute(_database, _flags, _oldStorage) {
ttStorage.remove("usage");
}
},
{
id: "d3e6e03a-698d-4df4-9062-4d3c9ce9d479",
version: "9.0.5",
execute(database, _flags, oldStorage) {
if (!oldStorage?.filters?.travel?.categories?.includes("other")) return;
database.filters.travel.categories = [...oldStorage.filters.travel.categories, "defensive"];
}
},
{
id: "700848e9-ee48-42ce-b8b1-893cb471cfe4",
version: "9.0.6",
execute(_database, flags, _oldStorage) {
flags.clearCache = true;
}
},
{
id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
version: "9.0.6",
execute(database, _flags, oldStorage) {
const oldStakeouts = oldStorage?.stakeouts;
if (!oldStakeouts || typeof oldStakeouts !== "object") return;
const reservedKeys = new Set([
"order",
"date",
"list"
]);
const oldOrder = oldStakeouts.order ?? [];
const list = [];
Object.entries(oldStakeouts).filter((entry) => !reservedKeys.has(entry[0])).forEach(([id, data]) => {
const orderIndex = oldOrder.indexOf(id);
list.push({
...data,
id: parseInt(id),
order: orderIndex !== -1 ? orderIndex : Date.now()
});
});
database.stakeouts.list = list;
}
},
{
id: "b2c3d4e5-f6a7-8901-bcde-f12345678901",
version: "9.0.6",
execute(database, _flags, oldStorage) {
const oldFactionStakeouts = oldStorage?.factionStakeouts;
if (!oldFactionStakeouts || typeof oldFactionStakeouts !== "object") return;
const reservedKeys = new Set(["date", "list"]);
const list = [];
Object.entries(oldFactionStakeouts).filter((entry) => !reservedKeys.has(entry[0])).forEach(([id, data]) => {
list.push({
...data,
id: parseInt(id),
order: Date.now()
});
});
database.factionStakeouts.list = list;
}
},
{
id: "16d7de5c-e9ad-4060-966e-49b4252301c5",
version: "9.0.7",
execute(_database, _flags, _oldStorage) {
OFFLOAD_SERVICE.reinitializeTimers().catch(() => {});
}
},
{
id: "a1b8db49-f255-43fc-b3b8-dc82b8c072b1",
version: "9.0.9",
execute(database, _flags, oldStorage) {
const owner = oldStorage.userdata?.profile?.id;
if (!owner) return;
database.api.torn.owner = owner;
}
},
{
id: "8a88db28-d02c-4b08-a672-bb73394b5ae4",
version: "9.0.12",
execute(_database, _flags, _oldStorage) {
OFFLOAD_SERVICE.reinitializeTimers().catch(() => {});
}
},
{
id: "19384047-faaa-4894-a0bb-1695b964a125",
version: "9.0.14",
execute(_database, flags, _oldStorage) {
flags.updateStockdata = true;
}
},
{
id: "b2102994-0920-4586-8259-0e5beedc7f13",
version: "9.1.0",
execute(database, _flags, oldStorage) {
if (oldStorage.api.torn.owner) return;
const owner = oldStorage.userdata?.profile?.id;
if (!owner) return;
database.api.torn.owner = owner;
}
},
{
id: "8f883a44-fa45-407b-bdc7-18c6982ab108",
version: "9.1.0",
execute(database, _flags, _oldStorage) {
database.cache["stats-estimate"] = {};
}
},
{
id: "4b9a96e4-ce68-46c1-b5ab-b7dfbc699db0",
version: "9.1.2",
execute(_database, flags, _oldStorage) {
flags.updateUserdata = true;
}
}
];
async function executeMigrationScripts(storage, oldStorage) {
if (RUNTIME_INFORMATION.isUserscript()) return;
const migrations = MIGRATIONS.filter(({ version }) => toNumericVersion(version) >= toNumericVersion(storage.version.initial)).filter(({ id }) => !storage.migrations.map(({ id }) => id).includes(id));
const flags = {
updateUserdata: false,
updateFactiondata: false,
updateStockdata: false,
updateTorndata: false,
clearCache: false
};
migrations.reverse().forEach((migration) => {
migration.execute(storage, flags, oldStorage);
storage.migrations.push({ id: migration.id });
});
if (flags.updateUserdata) storage.userdata.date = 0;
if (flags.updateFactiondata) storage.factiondata.date = 0;
if (flags.updateStockdata) storage.stockdata.date = 0;
if (flags.updateTorndata) storage.torndata.date = 0;
if (flags.clearCache) storage.cache = {};
}
var settings;
var filters;
var version;
var api;
var userdata;
var torndata;
var stakeouts;
var attackHistory;
var notes;
var factiondata;
var quick;
var localdata;
var npcs;
var notificationHistory;
var stockdata;
var factionStakeouts;
var notifications;
var migrations;
var storageListeners = {
settings: [],
filters: [],
version: [],
userdata: [],
torndata: [],
attackHistory: [],
stakeouts: [],
factionStakeouts: [],
notes: [],
factiondata: [],
localdata: [],
cache: [],
api: [],
npcs: [],
stockdata: [],
notificationHistory: [],
notifications: [],
quick: [],
migrations: []
};
var databaseLoaded = false;
var databaseLoadPromise = null;
async function loadDatabase(force = false) {
if (databaseLoaded && !force) return {
settings,
filters,
version,
userdata,
stakeouts,
factionStakeouts,
notes,
factiondata,
localdata,
cache: ttCache.cache,
api,
npcs,
torndata,
notificationHistory,
attackHistory,
quick,
stockdata,
notifications,
migrations
};
if (databaseLoadPromise) return await databaseLoadPromise;
databaseLoadPromise = (async () => {
const database = await ttStorage.get();
populateDatabaseVariables(database);
console.debug("TT - Database loaded.");
return database;
})();
try {
const result = await databaseLoadPromise;
databaseLoaded = true;
databaseLoadPromise = null;
return result;
} catch (error) {
databaseLoadPromise = null;
throw error;
}
}
async function migrateDatabase(force = false) {
try {
const loadedStorage = await ttStorage.get();
if (!loadedStorage || !Object.keys(loadedStorage).length) {
console.log("TT - Fresh installation detected, setting up default storage.");
await ttStorage.reset();
await loadDatabase();
return;
}
const storedVersion = loadedStorage?.version?.current || "5.0.0";
const currentVersion = RUNTIME_INFORMATION.getVersion();
console.log(`TT - Migration check: ${storedVersion} -> ${currentVersion}`);
const migratedStorage = convertStorage(loadedStorage, DEFAULT_STORAGE);
await executeMigrationScripts(migratedStorage, loadedStorage);
migratedStorage.version.current = currentVersion;
await ttStorage.set(migratedStorage);
populateDatabaseVariables(migratedStorage);
console.debug("TT - Database migration completed successfully.");
} catch (error) {
console.error("TT - Database migration failed:", error);
await loadDatabase();
}
}
function convertStorage(oldStorage, defaultStorage) {
const newStorage = {};
for (const key in defaultStorage) {
if (!oldStorage) oldStorage = {};
const defaultValue = defaultStorage[key];
const oldValue = key in oldStorage ? oldStorage[key] : void 0;
if (typeof defaultValue === "object" && defaultValue !== null) {
if (defaultValue instanceof DefaultSetting) newStorage[key] = migrateDefaultSetting(oldValue ?? {}, defaultValue);
else newStorage[key] = convertStorage(oldValue ?? {}, defaultValue);
} else newStorage[key] = oldValue ?? defaultValue;
}
return newStorage;
}
function migrateDefaultSetting(oldValue, setting) {
if (isValidSettingValue(oldValue, setting)) return oldValue;
if (setting.defaultValue !== null) return typeof setting.defaultValue === "function" ? setting.defaultValue() : setting.defaultValue;
return null;
}
function isValidSettingValue(value, setting) {
if (setting.type === "array") return Array.isArray(value);
return setting.type.split("|").some((type) => type === "empty" && value === "" || typeof value === type);
}
function populateDatabaseVariables(database) {
settings = database.settings;
filters = database.filters;
version = database.version;
api = database.api;
userdata = database.userdata;
torndata = database.torndata;
localdata = database.localdata;
stakeouts = database.stakeouts;
attackHistory = database.attackHistory;
notes = database.notes;
factiondata = database.factiondata;
quick = database.quick;
npcs = database.npcs;
stockdata = database.stockdata;
factionStakeouts = database.factionStakeouts;
notificationHistory = database.notificationHistory;
notifications = database.notifications;
migrations = database.migrations;
ttCache.cache = database.cache;
}
var initializedDatabaseListeners = false;
function initializeDatabaseListener() {
if (initializedDatabaseListeners) return;
RUNTIME_STORAGE.addChangeListener((changes, area) => {
if (area === "local") for (const key in changes) {
switch (key) {
case "settings":
settings = changes.settings.newValue;
break;
case "filters":
filters = changes.filters.newValue;
break;
case "version":
version = changes.version.newValue;
break;
case "userdata":
userdata = changes.userdata.newValue;
break;
case "api":
api = changes.api.newValue;
break;
case "torndata":
torndata = changes.torndata.newValue;
break;
case "stakeouts":
stakeouts = changes.stakeouts.newValue;
break;
case "attackHistory":
attackHistory = changes.attackHistory.newValue;
break;
case "notes":
notes = changes.notes.newValue;
break;
case "factiondata":
factiondata = changes.factiondata.newValue;
break;
case "quick":
quick = changes.quick.newValue;
break;
case "localdata":
localdata = changes.localdata.newValue;
break;
case "cache":
ttCache.cache = changes.cache.newValue;
break;
case "npcs":
npcs = changes.npcs.newValue;
break;
case "stockdata":
stockdata = changes.stockdata.newValue;
break;
case "notificationHistory":
notificationHistory = changes.notificationHistory.newValue;
break;
case "notifications":
notifications = changes.notifications.newValue;
break;
case "factionStakeouts": factionStakeouts = changes.factionStakeouts.newValue;
}
if (storageListeners[key]) storageListeners[key].forEach((listener) => listener(changes[key].oldValue, changes[key].newValue));
}
});
initializedDatabaseListeners = true;
}
function setLocaldata(data) {
localdata = data;
}
function setFilters(data) {
filters = data;
}
var REGEXES = {
convertToNumber: /-?[\d,]+(\.\d+)?/,
formatNumber: /\B(?=(\d{3})+(?!\d))/g
};
function convertToNumber(string) {
if (!string) return NaN;
const match = string.match(REGEXES.convertToNumber);
return match ? Number(match[0].replaceAll(",", "")) : NaN;
}
function formatNumber(number, partialOptions = {}) {
const options = {
shorten: false,
formatter: void 0,
decimals: 0,
currency: false,
forceOperation: false,
roman: false,
...partialOptions
};
if (typeof number !== "number") {
if (Number.isNaN(parseInt(number))) return number;
else number = parseFloat(number);
}
if (number === Number.POSITIVE_INFINITY) return "∞";
if (options.decimals !== void 0) number = parseFloat(number.toFixed(options.decimals));
if (options.formatter) return options.formatter.format(number);
if (options.roman) {
if (number === 0) return "";
else if (number < 0) throw "Roman numbers can only be positive!";
const ROMAN = [
[1e3, "M"],
[900, "CM"],
[500, "D"],
[400, "CD"],
[100, "C"],
[90, "XC"],
[50, "L"],
[40, "XL"],
[10, "X"],
[9, "IX"],
[5, "V"],
[4, "IV"],
[1, "I"]
];
return toRoman(number);
function toRoman(number) {
if (number === 0) return "";
for (const [value, character] of ROMAN) {
if (number < value) continue;
return character + toRoman(number - value);
}
return "N/A";
}
}
const abstract = Math.abs(number);
const operation = number < 0 ? "-" : options.forceOperation ? "+" : "";
let text;
if (options.shorten) {
const version = options.shorten === true ? 1 : options.shorten;
const decimals = options.decimals !== -1 ? options.decimals : 3;
const words = (() => {
switch (version) {
case 1: return {
thousand: "k",
million: "mil",
billion: "bill"
};
case 2:
case 3: return {
thousand: "k",
million: "m",
billion: "b"
};
}
})();
if (version === 1 || version === 2) {
if (abstract >= 1e9) {
if (abstract % 1e9 === 0) text = (abstract / 1e9).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + words.billion;
else text = (abstract / 1e9).toFixed(3) + words.billion;
} else if (abstract >= 1e6) {
if (abstract % 1e6 === 0) text = abstract / 1e6 + words.million;
else text = (abstract / 1e6).toFixed(3) + words.million;
} else if (abstract >= 1e3) {
if (abstract % 1e3 === 0) text = abstract / 1e3 + words.thousand;
}
} else if (abstract >= 1e9) {
if (abstract % 1e9 === 0) text = abstract / 1e9 + words.billion;
else text = parseFloat((abstract / 1e9).toFixed(decimals)) + words.billion;
} else if (abstract >= 1e6) {
if (abstract % 1e6 === 0) text = abstract / 1e6 + words.million;
else text = parseFloat((abstract / 1e6).toFixed(decimals)) + words.million;
} else if (abstract >= 1e3) {
if (abstract % 1e3 === 0) text = abstract / 1e3 + words.thousand;
else if (abstract % 100 === 0) text = abstract / 1e3 + words.thousand;
}
}
if (!text) text = abstract.toString().replace(REGEXES.formatNumber, ",");
return `${operation}${options.currency ? "$" : ""}${text}`;
}
function capitalizeText(text, partialOptions = {}) {
if (!{
everyWord: false,
...partialOptions
}.everyWord) return text[0].toUpperCase() + text.slice(1);
return text.trim().split(" ").map((word) => capitalizeText(word)).join(" ").trim();
}
var LINKS = {
auction: "https://www.torn.com/amarket.php",
bank: "https://www.torn.com/bank.php",
bazaar: "https://www.torn.com/bazaar.php",
bounties: "https://www.torn.com/bounties.php#!p=main",
chain: "https://www.torn.com/factions.php?step=your#/war/chain",
church: "https://www.torn.com/church.php",
committee: "https://www.torn.com/committee.php",
companies: "https://www.torn.com/companies.php",
companyEmployees: "https://www.torn.com/companies.php#/option=employees",
crimes: "https://www.torn.com/crimes.php",
donator: "https://www.torn.com/donator.php",
education: "https://www.torn.com/page.php?sid=education",
events: "https://www.torn.com/events.php#/step=all",
faction: "https://www.torn.com/factions.php",
faction__ranked_war: "https://www.torn.com/factions.php?step=your&type=1#/war/rank",
faction_oc: "https://www.torn.com/factions.php?step=your#/tab=crimes",
gym: "https://www.torn.com/gym.php",
home: "https://www.torn.com/index.php",
homepage: "https://www.torn.com/index.php",
hospital: "https://www.torn.com/hospitalview.php",
itemmarket: "https://www.torn.com/page.php?sid=ItemMarket",
items: "https://www.torn.com/item.php",
items_booster: "https://www.torn.com/item.php#boosters-items",
items_candy: "https://www.torn.com/item.php#candy-items",
items_drug: "https://www.torn.com/item.php#drugs-items",
items_medical: "https://www.torn.com/item.php#medical-items",
jailview: "https://www.torn.com/jailview.php",
jobs: "https://www.torn.com/companies.php",
loan: "https://www.torn.com/loan.php",
messages: "https://www.torn.com/messages.php",
missions: "https://www.torn.com/page.php?sid=missions",
organizedCrimes: "https://www.torn.com/factions.php?step=your#/tab=crimes",
pc: "https://www.torn.com/pc.php",
points: "https://www.torn.com/page.php?sid=points",
pointsmarket: "https://www.torn.com/pmarket.php",
properties: "https://www.torn.com/properties.php",
property_upkeep: "https://www.torn.com/properties.php#/p=options&tab=upkeep",
property_vault: "https://www.torn.com/properties.php#/p=options&tab=vault",
raceway: "https://www.torn.com/page.php?sid=racing",
staff: "https://www.torn.com/staff.php",
stocks: "https://www.torn.com/page.php?sid=stocks",
trade: "https://www.torn.com/trade.php",
travelagency: "https://www.torn.com/page.php?sid=travel"
};
LINKS.donator, LINKS.donator, LINKS.staff, LINKS.committee, LINKS.church, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.companies, LINKS.companies, LINKS.companies, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.education, LINKS.education, LINKS.bank, LINKS.bank, LINKS.travelagency, LINKS.property_vault, LINKS.loan, LINKS.auction, LINKS.bazaar, LINKS.itemmarket, LINKS.pointsmarket, LINKS.stocks, LINKS.stocks, LINKS.trade, LINKS.homepage, LINKS.raceway, LINKS.raceway, LINKS.faction_oc, LINKS.faction_oc, LINKS.faction_oc, LINKS.faction_oc, LINKS.bounties, LINKS.bank, LINKS.auction, LINKS.auction, LINKS.hospital, LINKS.hospital, LINKS.hospital, LINKS.jailview, LINKS.hospital, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.property_upkeep, LINKS.property_upkeep, LINKS.property_upkeep;
[
{
id: 1,
reason: "Admin"
},
{
id: 4,
reason: "NPC"
},
{
id: 7,
reason: "NPC"
},
{
id: 9,
reason: "NPC"
},
{
id: 10,
reason: "NPC"
},
{
id: 15,
reason: "NPC"
},
{
id: 17,
reason: "NPC"
},
{
id: 19,
reason: "NPC"
},
{
id: 20,
reason: "NPC"
},
{
id: 21,
reason: "NPC"
}
].map(({ id }) => id);
function getRFC() {
const rfc = getCookie("rfc_v");
if (!rfc) for (const cookie of document.cookie.split("; ")) {
const parts = cookie.split("=");
if (parts[0] === "rfc_v") return parts[1];
}
return rfc;
}
function millisToNewDay() {
const now = Date.now();
const newDate = new Date();
newDate.setUTCHours(0, 0, 0);
newDate.setUTCDate(newDate.getUTCDate() + 1);
return newDate.getTime() - now;
}
function isUseItem(step, _json) {
return step === "useItem";
}
var Feature = class {
name;
scope;
executionTiming;
constructor(name, scope, executionTiming = "CONTENT_LOADED") {
this.name = name;
this.scope = scope;
this.executionTiming = executionTiming;
}
precondition() {
return true;
}
initialise() {}
execute(liveReload) {}
cleanup() {}
storageKeys() {
return [];
}
requirements() {
return true;
}
shouldTriggerEvents() {
return false;
}
shouldLiveReload() {
return false;
}
requiresScreenInformation() {
return true;
}
};
var SUPPLY_PACK_ITEMS = [
364,
365,
370,
588,
815,
817,
818,
1035,
1057,
1078,
1079,
1080,
1081,
1082,
1083,
1112,
1113,
1114,
1115,
1116,
1117,
1118,
1119,
1120,
1121,
1122,
1293,
1298
];
function addListener() {
let reqXID;
let itemID;
addXHRListener(async ({ detail: { xhr, json } }) => {
if (!FEATURE_MANAGER.isEnabled(OpenedSupplyPackValueFeature)) return;
const params = new URLSearchParams(xhr.requestBody);
if (!isUseItem(params.get("step"), json) || !json.success) return;
if (json?.itemID) itemID = parseInt(json.itemID);
else if (params.has("id")) itemID = convertToNumber(params.get("id"));
else if (params.has("itemID")) itemID = convertToNumber(params.get("itemID"));
if (shouldDisplayOpenedValue(itemID)) reqXID = (await requireElement(`[data-item="${itemID}"] .pack-open-msg input[type="hidden"]`)).value;
if ((params.get("XID") === reqXID || isDrugPackUseRequest(params) || SUPPLY_PACK_ITEMS.includes(itemID)) && json.items?.itemAppear) await showTotalValue(calculateValueFromResponse(json), itemID);
});
}
function calculateValueFromResponse(response) {
if (!response.items?.itemAppear) return null;
return response.items.itemAppear.map((item) => "isMoney" in item ? convertToNumber(item.moneyGain.substring(1)) : ITEM_RESOLVER.getFullItem(parseInt(item.ID)).value.market_price * parseInt(item.qty)).reduce((totalValue, value) => totalValue + value, 0);
}
async function showTotalValue(totalOpenedValue, itemID) {
await sleep(.5 * TO_MILLIS.SECONDS);
const greenMsg = await requireElement(`[data-item="${itemID}"] .cont-wrap form p`);
removeTotalValueElement();
const openedValueTextElement = elementBuilder({
id: "ttOpenedValueText",
type: "strong",
class: "tt-opened-supply-pack-value-text",
text: `TornTools total value: ${formatNumber(totalOpenedValue, { currency: true })}`
});
greenMsg.insertAdjacentElement("beforeend", openedValueTextElement);
}
function shouldDisplayOpenedValue(itemID) {
return SUPPLY_PACK_ITEMS.includes(itemID) && !isDrugPack(itemID);
}
function isDrugPack(itemID) {
return itemID === 370;
}
function isDrugPackUseRequest(params) {
return convertToNumber(params.get("item")) === 370 || convertToNumber(params.get("itemID")) === 370;
}
function removeTotalValueElement() {
document.getElementById("ttOpenedValueText")?.remove();
}
var OpenedSupplyPackValueFeature = class extends Feature {
constructor() {
super("Opened Supply Pack Value", "items");
}
isEnabled() {
return settings.pages.items.openedSupplyPackValue;
}
initialise() {
addListener();
}
cleanup() {
removeTotalValueElement();
}
storageKeys() {
return ["settings.pages.items.openedSupplyPackValue"];
}
async requirements() {
if (!ITEM_RESOLVER.hasFullItems()) return "No API access.";
return true;
}
};
_css(".tt-hidden{display:none!important}.tt-black-overlay{z-index:100;background-color:#00000059;width:100%;height:100%;position:fixed;top:0;left:0}.no-margin{margin:0}.tt-delimiter{border-top:#ccc;border-left:none;border-right:none;border-top:1px solid var(--sidebar-horizontal-divider-bg-color);border-bottom:#fff;border-bottom:1px solid var(--sidebar-horizontal-divider-shadow-color);height:0;margin-bottom:5px;overflow:hidden}.tt-overlay{z-index:1000000;background-color:#00000059;width:100%;height:100%;position:fixed;top:0;left:0}.tt-overlay-item,.tt-overlay-item-notbroken{z-index:999999999;position:relative}.tt-overlay-item .tt-overlay-ignore{z-index:0;pointer-events:none}.tt-overlay-item .tt-overlay-ignore:before{content:\"\";z-index:1000000;background-color:#00000059;width:100%;height:100%;position:absolute;top:0;left:0}.relative{position:relative}.flex-break{border:0;height:0;margin:0;flex-basis:100%!important}.mt10{margin-top:10px}.mb10{margin-bottom:10px}.t-flex{display:flex}[class*=torn-icon-]{vertical-align:middle;background:url(https://www.torn.com/images/v2/city/location_icons_34x34px.svg) no-repeat;width:34px;height:34px;display:inline-block}.torn-icon-item-market{background-position:-68px -34px}.tt-sidebar-area{margin-top:2px;overflow:hidden}.tt-sidebar-area>div{cursor:pointer;vertical-align:top;background-color:var(--default-bg-panel-color);border-top-right-radius:5px;border-bottom-right-radius:5px;position:relative;overflow:hidden}.tt-sidebar-area a{color:var(--default-content-font-color);justify-content:flex-start;align-items:center;height:100%;text-decoration:none;display:flex;overflow:hidden}.tt-sidebar-area a span{float:none;vertical-align:middle;margin-left:10px;display:inline-block}.tt-button-link{cursor:pointer;color:var(--default-blue-color)}.tt-btn{background-color:var(--tt-color-light-green);color:#000;border-radius:6px;width:fit-content}.tt-btn:not([disabled]){cursor:pointer}.tt-btn[disabled]{cursor:not-allowed;opacity:.4}.tt-msg-box{background:var(--info-msg-grey-gradient);box-shadow:var(--info-msg-box-shadow);color:var(--info-msg-font-color);border-radius:5px;margin-top:10px;font-size:0;line-height:16px}.tt-msg-box .tt-msg-div{background:var(--info-msg-horizontal-gradient);border-radius:5px;justify-content:flex-start;display:flex}.tt-msg-box .tt-msg{vertical-align:middle;background-color:var(--default-bg-panel-active-color);background:var(--info-msg-delimiter-gradient);border-radius:0 5px 5px 0;flex-grow:1;width:1px;height:auto}.tt-msg-box .tt-content{vertical-align:middle;color:var(--info-msg-font-color);background-color:var(--default-bg-panel-active-color);background:var(--info-msg-bg-gradient);border-radius:0 5px 5px 0;padding:10px;font-size:13px;position:relative}.tt-message-box{color:var(--info-msg-font-color);box-shadow:var(--info-msg-box-shadow);border-radius:5px;margin-top:10px;font-size:13px;display:flex}.tt-message-box .tt-message-icon-wrap{background:var(--info-msg-grey-gradient);border-radius:5px 0 0 5px;width:34px}.tt-message-box .tt-message-icon{background:var(--info-msg-horizontal-gradient);border-radius:5px 0 0 5px;justify-content:center;width:34px;height:100%;display:flex}.tt-message-box .tt-svg{width:34px;height:34px}.tt-message-box .tt-message-wrap{background-color:var(--default-bg-panel-active-color);background:var(--info-msg-bg-gradient);border-radius:0 5px 5px 0;flex-grow:1;align-items:center;padding:10px;display:flex}.tt-message-box .tt-message{flex-grow:1}.tt-svg{width:128px;height:128px}.tt-svg .tt-svg-upper{stroke:#000;fill:#000}.tt-svg .tt-svg-lower{stroke:#568725;fill:#568725}#sidebarroot .pill{cursor:pointer;background-color:var(--default-bg-panel-color);min-height:22px;color:var(--default-font-color);border-top-right-radius:5px;border-bottom-right-radius:5px;align-items:center;margin-top:2px;text-decoration:none;display:flex;overflow:hidden}#sidebarroot .pill:not([icon]){box-sizing:border-box;padding-top:5px;padding-bottom:5px}#sidebarroot .pill:not([icon]),#sidebarroot .pill[icon] span{height:100%;color:var(--default-font-color);justify-content:flex-start;align-items:center;padding-left:8px;text-decoration:none;display:flex;overflow:hidden}body.tt-tablet #sidebarroot .pill{min-height:34px}body[data-layout=hospital] #sidebarroot .pill{margin-top:0;margin-bottom:1px}#sidebarroot .pill:hover{background-color:var(--default-bg-panel-active-color)!important}.tt-sidebar-information{flex-direction:column;display:flex}.tt-sidebar-information .title{color:inherit;margin:inherit;font-weight:700;text-decoration:none}.tt-sidebar-information .countdown.short{color:var(--tt-color-red)}.tt-sidebar-information .countdown.medium{color:var(--tt-color-orange)}.tt-top-icons{gap:10px;display:flex}");
_css(":root{--tt-color-green:#00a500;--tt-color-light-green:#acea00;--tt-color-red:#d83500;--tt-color-green--20:#00a50033;--tt-color-green--30:#00a5004d;--tt-color-green--40:#00a50066;--tt-background-torn-gray:repeating-linear-gradient(90deg, #627e0d, #627e0d 2px, #6e8820 0, #6e8820 4px);--tt-background-green:repeating-linear-gradient(90deg, #627e0d, #627e0d 2px, #6e8820 0, #6e8820 4px);--tt-background-alternative:repeating-linear-gradient(90deg, #242424, #242424 2px, #2e2e2e 0, #2e2e2e 4px)}body:not(.dark-mode){--tt-color-blue:blue;--tt-color-orange:orange;--tt-color-item-text:#678c00;--tt-color-item-quantity:black;--tt-background-popup:#f1f1f1;--tt-shadow-popup:unset}body.dark-mode{--tt-color-blue:#058cff;--tt-color-orange:gold;--tt-color-item-text:#9c0;--tt-color-item-quantity:#ddd;--tt-background-popup:#444;--tt-shadow-popup:0 0 10px black}.tt-color-green{color:var(--tt-color-green)}.tt-color-red{color:var(--tt-color-red)}");
function handleDeviceSizeClasses() {
checkDevice().then(({ mobile, tablet }) => {
if (mobile) document.body.classList.add("tt-mobile");
else document.body.classList.remove("tt-mobile");
if (tablet) document.body.classList.add("tt-tablet");
else document.body.classList.remove("tt-tablet");
});
}
var ScriptFeatureManager = class {
constructor() {
this.getScriptState();
}
createPopup() {}
isEnabled(featureConstructor) {
return this.getScriptState().enabled[new featureConstructor().name];
}
registerFeature(feature) {
if (feature.requiresScreenInformation()) handleDeviceSizeClasses();
feature.initialise();
feature.execute();
this.getScriptState().enabled[feature.name] = true;
if (feature.shouldTriggerEvents()) triggerCustomListener(EVENT_CHANNELS.FEATURE_ENABLED, { name: feature.name });
}
getScriptState() {
const win = RUNTIME_INFORMATION.getWindow();
if (!win.ttScriptState) {
const newState = { enabled: {} };
win.ttScriptState = newState;
return newState;
}
return win.ttScriptState;
}
};
function isPDA() {
return typeof PDA_evaluateJavascript === "function";
}
function registerCoreUserscriptContext() {
setRuntimeInformation(UserscriptRuntimeInformation);
setFeatureManager(new ScriptFeatureManager());
setEventHandler(ScriptEventHandler);
initializeScriptTheme();
}
function initializeScriptTheme() {
document.documentElement.style.setProperty("--tt-theme-color", "#fff");
document.documentElement.style.setProperty("--tt-theme-background", "var(--tt-background-green)");
}
var UserscriptRuntimeInformation = {
getWindow() {
return unsafeWindow;
},
getVersion() {
return GM.info.version;
},
isUserscript() {
return true;
},
reloadWindow(force) {
if (isPDA()) window.flutter_inappwebview.callHandler("reloadPage");
else location.reload(force);
}
};
var ScriptEventHandler = {
triggerEvent(channel, payload) {
document.dispatchEvent(new CustomEvent(`TT_${channel}`, { detail: payload }));
},
registerListener(channel, listener) {
document.addEventListener(`TT_${channel}`, (event) => {
if (!isCustomEvent(event)) return;
listener(event.detail);
});
},
get eventRoot() {
return document;
},
triggerEventCrossWorld(target, channel, payload) {
target.dispatchEvent(new CustomEvent(`TT_${channel}`, { detail: payload }));
},
registerListenerCrossWorld(target, channel, listener) {
target.addEventListener(`TT_${channel}`, (event) => {
if (!isCustomEvent(event)) return;
listener(event.detail);
});
}
};
var TornToolsStorage = class {
async update(key, fn) {
const database = await this.get(key);
fn(database);
await this.set({ [key]: database });
}
async change(object) {
const keys = Object.keys(object);
for (const key of keys) {
const data = this.recursive(await this.get(key), object[key]);
await this.set({ [key]: data });
}
}
recursive(parent, toChange) {
for (const key in toChange) if (parent && typeof parent === "object" && !Array.isArray(parent[key]) && key in parent && typeof toChange[key] === "object" && !Array.isArray(toChange[key]) && toChange[key] !== null) parent[key] = this.recursive(parent[key], toChange[key]);
else if (parent && typeof parent === "object") {
const value = toChange[key];
parent[key] = Array.isArray(value) ? Array.from(value) : value;
} else parent = { [key]: toChange[key] };
return parent;
}
};
var TTScriptStorage = class extends TornToolsStorage {
prefix;
constructor(prefix) {
super();
this.prefix = prefix;
}
storageKey(key) {
return key === "cache" ? key : `${this.prefix}_${key}`;
}
async get(key) {
if (Array.isArray(key)) return await Promise.all(key.map((k) => this.storageKey(k)).map((k) => GM.getValue(k)));
else if (key) return await GM.getValue(this.storageKey(key));
else {
const storageKeys = Object.keys(DEFAULT_STORAGE);
const storageValues = await this.get(storageKeys);
return storageKeys.reduce((total, k, i) => {
total[k] = storageValues[i];
return total;
}, {});
}
}
async set(object) {
await Promise.all(Object.entries(object).map(([key, value]) => {
UserscriptRuntimeStorage.callback({ [key]: {
newValue: value,
oldValue: null
} }, "local");
return GM.setValue(this.storageKey(key), value);
}));
}
remove(_key) {
throw new Error("Method not implemented.");
}
clear() {
throw new Error("Method not implemented.");
}
reset(_key) {
throw new Error("Method not implemented.");
}
getSize() {
throw new Error("Method not implemented.");
}
};
var PDAScriptStorage = class extends TornToolsStorage {
async get(key) {
if (Array.isArray(key)) return await Promise.all(key.map(this.get.bind(this)));
else if (key) {
if (key === "cache") return await GM.getValue(key);
else return await PDA_storage.get(key);
} else {
const storageKeys = Object.keys(DEFAULT_STORAGE);
const storageValues = await this.get(storageKeys);
return storageKeys.reduce((total, k, i) => {
total[k] = storageValues[i];
return total;
}, {});
}
}
async set(object) {
await Promise.all(Object.entries(object).map(([key, value]) => {
UserscriptRuntimeStorage.callback({ [key]: {
newValue: value,
oldValue: null
} }, "local");
if (key === "cache") return GM.setValue(key, value);
else return PDA_storage.set(key, value);
}));
}
async remove(key) {
if (typeof key === "string") await PDA_storage.delete(key);
else await Promise.all(key.map(PDA_storage.delete));
}
async clear() {
await Promise.all((await PDA_storage.list()).map(PDA_storage.delete));
}
reset(_key) {
throw new Error("Method not implemented.");
}
async getSize() {
return (await PDA_storage.usage()).used;
}
};
async function registerDatabaseUserscriptContext(storagePrefix) {
setTTStorage(isPDA() ? new PDAScriptStorage() : new TTScriptStorage(storagePrefix));
setRuntimeStorage(UserscriptRuntimeStorage);
await migrateDatabase(true);
initializeDatabaseListener();
const [localdata, filters, cache] = await ttStorage.get([
"localdata",
"filters",
"cache"
]);
setLocaldata(localdata ? localdata : getDefaultStorage(DEFAULT_STORAGE.localdata));
setFilters(filters ? filters : getDefaultStorage(DEFAULT_STORAGE.filters));
ttCache.cache = cache ? cache : getDefaultStorage(DEFAULT_STORAGE.cache);
}
var UserscriptRuntimeStorage = {
callback: () => {},
addChangeListener(callback) {
this.callback = callback;
}
};
var RequestListenerInjector = class {
injectListeners;
id;
constructor(injectListeners) {
this.injectListeners = injectListeners;
this.id = capitalizeText(injectListeners.name);
}
inject() {
if (this.isInjected()) return;
this.injectListeners();
this.setInjected();
}
isInjected() {
return document.documentElement.dataset[`tt${this.id}`] === "true";
}
setInjected() {
document.documentElement.dataset[`tt${this.id}`] = "true";
}
};
function injectFetchListeners() {
const oldFetch = RUNTIME_INFORMATION.getWindow().fetch;
RUNTIME_INFORMATION.getWindow().fetch = (input, init) => new Promise((resolve, reject) => {
oldFetch(input, init).then(async (response) => {
const page = response.url.substring(response.url.indexOf("torn.com/") + 9, response.url.indexOf(".php"));
let json = {};
try {
json = await response.clone().json();
} catch {}
let body = null;
if (init) {
if (typeof init.body === "object" && init.body?.constructor?.name === "FormData") {
const newBody = {};
for (const [key, value] of [...init.body]) if (isIntNumber(value)) newBody[key] = parseFloat(value);
else newBody[key] = value;
body = newBody;
}
}
const url = response.url || input;
const detail = {
page,
json,
text: await response.clone().text(),
fetch: {
url,
body,
status: response.status
}
};
window.dispatchEvent(new CustomEvent("tt-fetch", { detail }));
resolve(response);
}).catch((error) => {
reject(error);
});
});
}
function injectXhrListeners() {
const oldXHROpen = window.XMLHttpRequest.prototype.open;
const oldXHRSend = window.XMLHttpRequest.prototype.send;
window.XMLHttpRequest.prototype.open = function(method, url) {
this["method"] = method;
this["url"] = url;
this["params"] = this["params"] ?? {};
this.addEventListener("readystatechange", function() {
if (this.readyState > 3 && this.status === 200) {
const page = this.responseURL.substring(this.responseURL.indexOf("torn.com/") + 9, this.responseURL.indexOf(".php"));
let json, uri;
if (isJsonString(this.response)) json = JSON.parse(this.response);
else uri = getUrlParams(this.responseURL);
let text;
if (this.responseType === "" || this.responseType === "text") text = this.responseText;
window.dispatchEvent(new CustomEvent("tt-xhr", { detail: {
page,
json,
uri,
xhr: {
requestBody: this["requestBody"],
response: this.response,
responseType: this.responseType,
responseText: text,
responseURL: this.responseURL
}
} }));
}
});
arguments[0] = method;
arguments[1] = url;
return oldXHROpen.apply(this, arguments);
};
window.XMLHttpRequest.prototype.send = function(body) {
this["params"] = this["params"] ?? {};
if ("xhrSendAdjustments" in window && typeof window.xhrSendAdjustments === "object") for (const key in window.xhrSendAdjustments) {
if (typeof window.xhrSendAdjustments[key] !== "function") continue;
body = window.xhrSendAdjustments[key]({ ...this }, body);
}
this["requestBody"] = body;
arguments[0] = body;
return oldXHRSend.apply(this, arguments);
};
}
function getUrlParams(url, prop) {
if (!url) url = location.href;
const definitions = decodeURIComponent(url.slice(url.indexOf("?") + 1)).split("&");
const params = {};
definitions.forEach((val) => {
const parts = val.split("=", 2);
params[parts[0]] = parts[1];
});
return prop && prop in params ? params[prop] : params;
}
function isJsonString(str) {
if (!str || str === "") return false;
try {
JSON.parse(str);
} catch {
return false;
}
return true;
}
var BASE_HIGHLIGHT_SIZE = 38;
var SYNC_ATTEMPT_INTERVAL = 250;
var SYNC_ATTEMPT_LIMIT = 80;
function injectCityItemsMapListeners(pageWindow = window) {
if (pageWindow.__ttCityItemsMap?.injected) return;
const state = {
injected: true,
entries: [],
overlays: new Map()
};
pageWindow.__ttCityItemsMap = state;
EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__SET_ITEMS, ({ entries }) => {
state.entries = Array.isArray(entries) ? entries.filter(isCityItemsMapEntry) : [];
scheduleSync();
});
EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS, () => {
const items = getModelItems();
EVENT_HANDLER.triggerEventCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__MODEL_ITEMS, { items });
});
EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__CLEAR, clearOverlays);
function scheduleSync() {
let attempts = 0;
syncOverlays();
if (state.syncTimer) return;
state.syncTimer = pageWindow.setInterval(() => {
attempts++;
if (syncOverlays() || attempts >= SYNC_ATTEMPT_LIMIT || !state.entries.length) {
if (state.syncTimer) pageWindow.clearInterval(state.syncTimer);
state.syncTimer = void 0;
}
}, SYNC_ATTEMPT_INTERVAL);
}
function syncOverlays() {
const map = getMap();
const leaflet = pageWindow.L;
if (!map || !isLeafletOverlayRuntime(leaflet)) return false;
const activeEntryIds = new Set(state.entries.map((entry) => entry.entryId));
for (const [entryId, record] of state.overlays) if (!activeEntryIds.has(entryId)) {
removeOverlay(record);
state.overlays.delete(entryId);
}
for (const entry of state.entries) {
let record = state.overlays.get(entry.entryId);
const latLng = getLatLngForEntry(entry);
if (!latLng) continue;
if (!record) {
record = {
entry,
marker: null,
latLng
};
state.overlays.set(entry.entryId, record);
} else {
record.entry = entry;
record.latLng = latLng;
}
ensureOverlay(record, map, leaflet);
}
return state.entries.every((entry) => !!state.overlays.get(entry.entryId)?.marker);
}
function ensureOverlay(record, map, leaflet) {
const latLng = record.latLng;
if (!latLng) return;
try {
if (record.marker?._map && record.marker._map !== map) removeOverlay(record);
if (record.marker) {
record.marker.setLatLng?.(latLng);
updateOverlayElement(record);
return;
}
const icon = leaflet.divIcon({
className: "tt-city-item-overlay city-item",
html: `<span class="tt-city-item-overlay-content"><img src="${getItemImageUrl(record.entry.itemId)}" alt=""></span>`,
iconSize: [BASE_HIGHLIGHT_SIZE, BASE_HIGHLIGHT_SIZE],
iconAnchor: [BASE_HIGHLIGHT_SIZE / 2, BASE_HIGHLIGHT_SIZE / 2]
});
const marker = leaflet.marker(latLng, {
icon,
interactive: true,
keyboard: false,
zIndexOffset: 1e3
});
if (typeof marker.addTo !== "function") return;
marker.addTo(map);
record.marker = marker;
updateOverlayElement(record);
} catch {
record.marker = null;
}
}
function updateOverlayElement(record) {
const element = record.marker?.getElement?.();
if (!element) return;
element.classList.add("tt-city-item-overlay", "city-item");
element.dataset.id = record.entry.itemId.toString();
element.dataset.itemId = record.entry.itemId.toString();
element.dataset.entryId = record.entry.entryId;
element.dataset.td = record.entry.td;
element.removeAttribute("title");
}
function clearOverlays() {
state.entries = [];
if (state.syncTimer) {
pageWindow.clearInterval(state.syncTimer);
state.syncTimer = void 0;
}
for (const record of state.overlays.values()) removeOverlay(record);
state.overlays.clear();
}
function removeOverlay(record) {
if (!record.marker) return;
try {
if (record.marker.remove) record.marker.remove();
else record.marker.removeFrom?.(getMap());
} catch {
try {
record.marker._map?.removeLayer?.(record.marker);
} catch {}
}
record.marker = null;
}
function getMap() {
const mapElement = pageWindow.document.querySelector("#map");
const map = getTornRuntime()?.map?.lmap ?? mapElement?._leaflet_map;
return isLeafletMap(map) ? map : null;
}
function getLatLngForEntry(entry) {
if (!Number.isFinite(entry.x) || !Number.isFinite(entry.y)) return null;
const tornMap = getTornRuntime()?.map;
const leaflet = pageWindow.L;
try {
if (tornMap?.getLPoint && leaflet?.CRS?.EPSG3857?.pointToLatLng) {
const point = [entry.x / 2, entry.y / 2];
const leafletPoint = tornMap.getLPoint(point);
return normalizeLatLng(leaflet.CRS.EPSG3857.pointToLatLng(leafletPoint, tornMap.minZoom));
}
} catch {}
return null;
}
function getModelItems() {
const model = getTornRuntime()?.model;
if (!model) return [];
try {
const fullModel = model.get();
if (Array.isArray(fullModel?.territoryUserItems)) return fullModel.territoryUserItems;
} catch {}
try {
const userItems = model.get("territoryUserItems");
if (Array.isArray(userItems)) return userItems;
} catch {}
return [];
}
function getTornRuntime() {
const torn = pageWindow.torn;
return isTornRuntime(torn) ? torn : null;
}
}
function isCityItemsMapEntry(value) {
return isRecord(value) && typeof value.entryId === "string" && typeof value.itemId === "number" && Number.isFinite(value.itemId) && typeof value.name === "string" && typeof value.td === "string" && typeof value.x === "number" && Number.isFinite(value.x) && typeof value.y === "number" && Number.isFinite(value.y);
}
function isRecord(value) {
return typeof value === "object" && value !== null;
}
function isTornRuntime(value) {
return isRecord(value) && (!("map" in value) || value.map == null || isTornMapRuntime(value.map)) && (!("model" in value) || value.model == null || isTornModelRuntime(value.model));
}
function isTornMapRuntime(value) {
return isRecord(value) && (!("lmap" in value) || value.lmap == null || isLeafletMap(value.lmap)) && (!("minZoom" in value) || value.minZoom == null || typeof value.minZoom === "number") && (!("getLPoint" in value) || value.getLPoint == null || typeof value.getLPoint === "function");
}
function isTornModelRuntime(value) {
return isRecord(value) && typeof value.get === "function";
}
function isLeafletMap(value) {
return isRecord(value) && typeof value.addLayer === "function";
}
function isLeafletOverlayRuntime(value) {
return isRecord(value) && typeof value.divIcon === "function" && typeof value.marker === "function";
}
function normalizeLatLng(latLng) {
if (!latLng) return null;
if (Array.isArray(latLng)) {
const [lat, lng] = latLng;
return Number.isFinite(lat) && Number.isFinite(lng) ? latLng : null;
}
return Number.isFinite(latLng.lat) && Number.isFinite(latLng.lng) ? latLng : null;
}
function getItemImageUrl(itemId) {
return `https://www.torn.com/images/items/${itemId}/small.png`;
}
function injectEfficientRehabListeners(pageWindow = window) {
EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.EFFICIENT_REHAB, ({ ticks }) => {
const $slider = $("#rehub-progress .ui-slider");
const rehabPercentages = JSON.parse($slider.attr("data-percentages")) || [];
if (!(ticks in rehabPercentages)) {
console.warn("TornTools - Failed to update the rehab amount due to it being an invalid amount of ticks");
return;
}
const percentage = rehabPercentages[ticks];
$slider.slider("value", percentage).slider("option", "slide")({}, { value: $slider.slider("value") });
});
EVENT_HANDLER.triggerEventCrossWorld(pageWindow, EVENT_CHANNELS.EFFICIENT_REHAB__INJECTED);
}
function registerInjectorUserscriptContext() {
setScriptInjector(UserscriptScriptInjector);
}
function injectUserscriptCityItemsMapListeners() {
injectCityItemsMapListeners(unsafeWindow);
}
function injectUserscriptEfficientRehabListeners() {
injectEfficientRehabListeners(unsafeWindow);
}
var fetchListenerInjector = new RequestListenerInjector(injectFetchListeners);
var xhrListenerInjector = new RequestListenerInjector(injectXhrListeners);
var cityItemsMapListenerInjector = new RequestListenerInjector(injectUserscriptCityItemsMapListeners);
var efficientRehabInjector = new RequestListenerInjector(injectUserscriptEfficientRehabListeners);
var UserscriptScriptInjector = {
injectFetch() {
fetchListenerInjector.inject();
},
injectXHR() {
xhrListenerInjector.inject();
},
injectCityItemsMap() {
cityItemsMapListenerInjector.inject();
},
injectEfficientRehab() {
efficientRehabInjector.inject();
}
};
var BADGE_TYPES = {
default: {
text: "",
color: null
},
error: {
text: "error",
color: "#FF0000"
},
count: {
text: async (options) => {
if (options.events && options.messages) return `${options.events}/${options.messages}`;
else if (options.events) return options.events.toString();
else if (options.messages) return options.messages.toString();
else return await getBadgeText() === "error" ? "error" : null;
},
color: async (options) => {
if (options.events && options.messages) return "#1ed2ac";
else if (options.events) return "#009eda";
else if (options.messages) return "#84af03";
else return await getBadgeText() === "error" ? "error" : null;
}
}
};
async function setBadge(type, partialOptions = {}) {
if (SCRIPT_TYPE !== "BACKGROUND") return false;
const options = {
events: 0,
messages: 0,
...partialOptions
};
const badge = { ...BADGE_TYPES[type] };
if (typeof badge.text === "function") badge.text = await badge.text(options);
if (typeof badge.color === "function") badge.color = await badge.color(options);
if (!badge.text) badge.text = "";
browser.action.setBadgeText({ text: badge.text || "" });
if (badge.color) browser.action.setBadgeBackgroundColor({ color: badge.color });
return true;
}
function getBadgeText() {
if (SCRIPT_TYPE !== "BACKGROUND") return Promise.resolve(null);
return browser.action.getBadgeText({});
}
var CUSTOM_API_ERROR = {
NO_NETWORK: "tt-no_network",
NO_PERMISSION: "tt-no_permission",
CANCELLED: "tt-cancelled"
};
var FETCH_PLATFORMS = {
tornv2: "https://api.torn.com/v2/",
torn_direct: "https://www.torn.com/",
yata: "https://yata.yt/",
tornstats: "https://www.tornstats.com/",
torntools: "https://torntools.gregork.com/",
nukefamily: "https://nuke.family/",
uhc: "https://tornuhc.eu/",
stig: "https://api.no1irishstig.co.uk/",
prometheus: "https://prombot.co.uk:8443/",
lzpt: "https://api.lzpt.io/",
wtf: "https://what-the-f.de/",
tornw3b: "https://weav3r.dev/",
ffscouter: "https://ffscouter.com/",
laekna: "https://laekna-revive-bot.onrender.com/",
tornintel: "https://torn-intel.com/",
playground_torntools: "https://torntools.tornplayground.eu/"
};
var TORN_API_PLATFORMS = ["tornv2"];
var TEXT_RESPONSE_PLATFORMS = ["torn_direct", "laekna"];
async function fetchData(location, partialOptions = {}) {
const options = mergeOptions(partialOptions);
if (options.relay && SCRIPT_TYPE !== "BACKGROUND" && !RUNTIME_INFORMATION.isUserscript()) return relayToBackground(location, options);
const request = buildFetchRequest(location, options);
let result;
try {
result = parseFetchResponse(await DATA_FETCHER.fetch(request.url, {
method: request.method,
...request.method === "POST" ? { body: request.body } : {},
headers: request.headers,
timeout: decideTimeoutTimer(location)
}), location);
} catch (error) {
return await handleError(location, options, error);
}
if (!result.success) return await handleError(location, options, result);
else if (isApiErrorResponse(result.data)) return await handleError(location, options, result.data);
await handleTornApiState(location, options);
return result.data;
}
function mergeOptions(partial) {
return {
section: "",
id: void 0,
selections: [],
legacySelections: [],
key: void 0,
action: void 0,
method: "GET",
body: void 0,
silent: false,
includeKey: false,
relay: false,
params: {},
...partial
};
}
async function relayToBackground(location, options) {
return OFFLOAD_SERVICE.fetchRelay(location, {
...options,
relay: false
});
}
function decideTimeoutTimer(location) {
switch (location) {
case "yata": return 30 * TO_MILLIS.SECONDS;
default: return 10 * TO_MILLIS.SECONDS;
}
}
function buildFetchRequest(location, options) {
const url = buildUrl(location, options);
const headers = buildHeaders(location, options);
if (options.method === "POST") return {
url,
method: options.method,
body: buildBody(options),
headers
};
else return {
url,
method: options.method,
headers
};
}
function buildUrl(location, options) {
let path, pathSections;
let key;
const params = new URLSearchParams();
switch (location) {
case "tornv2":
path = `${options.section}/${options.id || ""}`;
params.append("selections", [...options.selections, ...options.legacySelections].join(","));
params.append("legacy", options.legacySelections.join(","));
if (settings.apiUsage.comment) params.append("comment", settings.apiUsage.comment);
break;
case "torn_direct":
path = options.action;
params.set("rfcv", getRFC());
break;
case "tornstats":
pathSections = [
"api",
"v2",
options.key || api.tornstats.key || api.torn.key || ""
];
if (options.section) pathSections.push(options.section);
if (options.id) pathSections.push(options.id);
path = pathSections.join("/");
break;
case "yata":
pathSections = [
"api",
"v1",
options.section
];
if (options.id) pathSections.push(options.id, "");
if (options.includeKey) key = api.yata.key;
path = pathSections.join("/");
break;
case "prometheus":
path = ["api", options.section].join("/");
break;
case "tornw3b":
path = ["api", options.section].join("/");
break;
case "ffscouter":
path = [
"api",
"v1",
options.section
].join("/");
key = api.ffScouter.key;
break;
case "tornintel":
path = ["api", options.section].join("/");
break;
case "playground_torntools":
path = ["api", options.section].join("/");
break;
default: path = options.section;
}
if (options.includeKey) params.append("key", options.key || key || api.torn.key || "");
if (options.params) for (const [key, value] of Object.entries(options.params)) params.append(key, value.toString());
return `${FETCH_PLATFORMS[location]}${path}${params.toString() ? `?${params}` : ""}`;
}
function buildHeaders(location, options) {
const headers = {};
if (location === "tornv2") headers["Authorization"] = `ApiKey ${options.key || api.torn.key}`;
if (options.method === "POST") {
if (!(options.body instanceof URLSearchParams)) headers["content-type"] = "application/json";
if (location === "torn_direct") headers["x-requested-with"] = "XMLHttpRequest";
}
return headers;
}
function buildBody(options) {
if (options.method !== "POST") return null;
return options.body instanceof URLSearchParams ? options.body : JSON.stringify(options.body);
}
function parseFetchResponse(response, location) {
try {
return {
data: JSON.parse(response.text),
success: true
};
} catch {
if (TEXT_RESPONSE_PLATFORMS.includes(location)) return {
data: response.text,
success: true
};
if (response.ok) return { success: true };
return {
success: false,
error: new HTTPException(response.status)
};
}
}
async function handleError(location, options, result) {
if (result instanceof DOMException) return handleTimeoutError(location, options);
if (result instanceof TypeError) return handleNetworkError(location, options, result.message);
return handleApiError(location, options, result);
}
async function handleTimeoutError(location, options) {
const error = "Request cancelled because it took too long.";
await handleTornApiState(location, options, error);
throw {
error,
isLocal: false,
code: CUSTOM_API_ERROR.CANCELLED
};
}
async function handleTornApiState(location, options, error, online = false) {
if (!TORN_API_PLATFORMS.includes(location) || options.silent || SCRIPT_TYPE !== "BACKGROUND") return;
if (error) {
await ttStorage.change({ api: { torn: {
online,
error
} } });
await setBadge("error");
} else {
await getBadgeText().then((value) => {
if (value === "error") return setBadge("default");
}).catch(() => console.error("TT - Couldn't get the badge text."));
await ttStorage.change({ api: { torn: {
online: true,
error: ""
} } });
}
}
async function handleNetworkError(location, options, message) {
let error = message;
let isLocal = false;
let code;
if (error === "Failed to fetch") {
isLocal = true;
if (!RUNTIME_INFORMATION.isUserscript() && SCRIPT_TYPE === "BACKGROUND" && !await hasOrigins(FETCH_PLATFORMS[location])) {
error = "Permission issues";
code = CUSTOM_API_ERROR.NO_PERMISSION;
} else {
error = "Network issues";
code = CUSTOM_API_ERROR.NO_NETWORK;
}
}
await handleTornApiState(location, options, error);
throw {
error,
isLocal,
code
};
}
async function hasOrigins(...origins) {
return browser.permissions.contains({ origins });
}
async function handleApiError(location, options, result) {
if (TORN_API_PLATFORMS.includes(location)) {
let error, online;
if (result.error instanceof HTTPException) {
error = result.error.toString();
online = false;
} else {
error = result.error.error;
online = result.error.code !== 9 && !(result instanceof HTTPException);
}
await handleTornApiState(location, options, error, online);
throw result.error instanceof HTTPException ? result.error.asObject() : result.error;
}
throw { error: result.error };
}
function isApiErrorResponse(data) {
return !!data && typeof data === "object" && "error" in data;
}
var HTTPException = class HTTPException {
code;
constructor(code) {
this.code = code;
}
get message() {
return this.code in HTTPException.codes ? HTTPException.codes[this.code] : `Unknown code (${this.code})`;
}
asObject() {
return {
code: this.code,
message: this.message,
http: true
};
}
toString() {
return `HTTP ${this.code}: ${this.message}`;
}
static get codes() {
return {
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Unused",
307: "Temporary Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Required",
413: "Request Entry Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Requested Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a teapot",
429: "Too Many Requests",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported"
};
}
};
var ScriptItemResolver = {
items: [],
itemsMap: {},
loadItem(id) {
return this.getFullItem(id) ?? this.getStaticItem(id);
},
findItem(matcher) {
return this.getAllFullItems().find(matcher) ?? null;
},
getStaticItem(id) {
return this.getFullItem(id);
},
hasFullItems: () => true,
getFullItem(id) {
if (!Object.keys(this.itemsMap).length) throw new Error("no items loaded");
return id in this.itemsMap ? this.itemsMap[id] : null;
},
async loadItems() {
if (ttCache.hasValue("static-data", "items-map")) {
const map = ttCache.get("static-data", "items-map");
this.items = Object.values(map);
this.itemsMap = map;
return;
}
const itemsMap = (await fetchData("playground_torntools", { section: "static-items" })).items.reduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {});
this.items = Object.values(itemsMap);
this.itemsMap = itemsMap;
ttCache.set({ "static-data": { "items-map": itemsMap } }, millisToNewDay());
},
getAllFullItems() {
return this.items;
},
getAllStaticItems() {
return this.getAllFullItems();
}
};
function registerNetworkUserscriptContext() {
setOffloadService(ScriptOffloadService);
setDataFetcher(ScriptDataFetcher);
setStaticItemResolver(ScriptItemResolver);
}
var ScriptOffloadService = {
fetchRelay(_location, _options) {
return Promise.reject(new Error("OffloadService is not available in script context. Use DataFetcher instead."));
},
initialize() {
return Promise.resolve({ success: true });
},
reinitializeTimers() {
return Promise.resolve();
}
};
var ScriptDataFetcher = { fetch(url, options) {
if (url.startsWith(FETCH_PLATFORMS.torn_direct)) return fetchOnPage(url, options);
return new Promise((resolve, reject) => {
try {
const u = new URL(url);
u.searchParams.append("pda-cache-busting", getUUID());
url = u.toString();
} catch {}
GM.xmlHttpRequest({
method: options?.method || "GET",
url,
headers: options?.headers,
data: options?.method === "POST" ? typeof options.body === "string" ? options.body : JSON.stringify(options.body) : void 0,
timeout: options?.timeout,
onload: (response) => {
if (!response) {
reject(new Error("Request has no actual response. Likely something went wrong in the fetch implementation."));
return;
}
resolve({
text: response.responseText,
status: response.status,
ok: response.status >= 200 && response.status < 300
});
},
onerror: (error) => reject(error),
ontimeout: () => reject(new DOMException("Request cancelled because it took too long.", "AbortError"))
});
});
} };
async function fetchOnPage(url, options) {
const controller = new AbortController();
const timeoutId = options?.timeout ? setTimeout(() => controller.abort(), options.timeout) : void 0;
try {
const response = await fetch(url, {
method: options?.method || "GET",
...options?.method === "POST" ? { body: options.body } : {},
headers: options?.headers,
signal: controller.signal
});
return {
text: await response.text(),
status: response.status,
ok: response.ok
};
} finally {
if (timeoutId) clearTimeout(timeoutId);
}
}
(async () => {
registerCoreUserscriptContext();
await registerDatabaseUserscriptContext("tt_ospv");
registerInjectorUserscriptContext();
registerNetworkUserscriptContext();
await ScriptItemResolver.loadItems();
FEATURE_MANAGER.registerFeature(new OpenedSupplyPackValueFeature());
})();
})();