Display weapon/armor quality and bonuses directly in Torn market-style pages.
// ==UserScript==
// @name Weapon Bonus View PC
// @namespace Weapon Bonus View PC
// @version 4.0.04
// @description Display weapon/armor quality and bonuses directly in Torn market-style pages.
// @author Maximate
// @match https://www.torn.com/*
// @grant GM_getValue
// @grant GM.getValue
// @grant GM_setValue
// @grant GM.setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @connect btrmmuuoofbonmuwrkzg.supabase.co
// @connect weav3r.dev
// ==/UserScript==
(function () {
"use strict";
if (window.WEAPON_BONUS_VIEW) return;
window.WEAPON_BONUS_VIEW = true;
const App = {
prefix: "[Weapon Bonus View]",
state: {
storage: {},
requestHistory: [],
cacheRecord: new Map(),
floatingNodes: [],
observers: [],
intervals: [],
bazaarCache: {},
lastBazaarOrder: "",
lastBazaarSearch: "",
bazaarUpdating: false,
interceptXMLInstalled: false,
interceptFetchInstalled: false,
context: {
inItemList: false,
inFactionArmoury: false,
inCabinet: false,
},
},
};
const log = (msg) => console.log(`${App.prefix} ${msg}`);
const GMX = {
async get(key, fallback) {
if (typeof GM !== "undefined" && GM.getValue) return GM.getValue(key, fallback);
if (typeof GM_getValue === "function") return Promise.resolve(GM_getValue(key, fallback));
return fallback;
},
async set(key, value) {
if (typeof GM !== "undefined" && GM.setValue) return GM.setValue(key, value);
if (typeof GM_setValue === "function") return Promise.resolve(GM_setValue(key, value));
},
style(css) {
if (typeof GM_addStyle === "function") return GM_addStyle(css);
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
},
};
const Data = {
weaponIds: new Set([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,63,76,98,99,100,108,109,110,111,146,147,170,173,174,175,177,189,217,218,219,223,224,225,227,228,230,231,232,233,234,235,236,237,238,240,241,243,244,245,247,248,249,250,251,252,253,254,255,289,290,291,292,346,359,360,382,387,388,391,393,395,397,398,399,400,401,402,438,439,440,483,484,485,486,487,488,489,490,539,545,546,547,548,549,599,600,604,605,612,613,614,615,632,790,792,805,830,831,832,837,838,839,844,845,846,850,871,874,1053,1055,1056,1152,1153,1154,1155,1156,1157,1158,1159,1231,1255,1257,1296]),
armorIds: new Set([32,33,34,49,50,176,178,332,333,334,348,538,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,848,1307,1308,1309,1310,1311,1355,1356,1357,1358,1359]),
filterTypes: new Set(["Melee", "Secondary", "Primary", "Armor"]),
itemNameToId: {"Hammer":1,"Baseball Bat":2,"Crowbar":3,"Knuckle Dusters":4,"Pen Knife":5,"Kitchen Knife":6,"Dagger":7,"Axe":8,"Scimitar":9,"Chainsaw":10,"Samurai Sword":11,"Glock 17":12,"Raven MP25":13,"Ruger 57":14,"Beretta M9":15,"USP":16,"Beretta 92FS":17,"Fiveseven":18,"Magnum":19,"Desert Eagle":20,"Dual 92G Berettas":21,"Sawed-Off Shotgun":22,"Benelli M1 Tactical":23,"MP5 Navy":24,"P90":25,"AK-47":26,"M4A1 Colt Carbine":27,"Benelli M4 Super":28,"M16 A2 Rifle":29,"Steyr AUG":30,"M249 SAW":31,"Leather Vest":32,"Police Vest":33,"Bulletproof Vest":34,"Full Body Armor":49,"Outer Tactical Vest":50,"Minigun":63,"Snow Cannon":76,"Neutrilux 2000":98,"Springfield 1911":99,"Egg Propelled Launcher":100,"9mm Uzi":108,"RPG Launcher":109,"Leather Bullwhip":110,"Ninja Claws":111,"Yasukuni Sword":146,"Rusty Sword":147,"Wand of Destruction":170,"Butterfly Knife":173,"XM8 Rifle":174,"Taser":175,"Chain Mail":176,"Cobra Derringer":177,"Flak Jacket":178,"S&W Revolver":189,"Claymore Sword":217,"Crossbow":218,"Enfield SA-80":219,"Jackhammer":223,"Swiss Army Knife":224,"Mag 7":225,"Spear":227,"Vektor CR-21":228,"Flare Gun":230,"Heckler & Koch SL8":231,"SIG 550":232,"BT MP9":233,"Chain Whip":234,"Wooden Nunchaku":235,"Kama":236,"Kodachi":237,"Sai":238,"Type 98 Anti Tank":240,"Bushmaster Carbon 15":241,"Taurus":243,"Blowgun":244,"Bo Staff":245,"Katana":247,"Qsz-92":248,"SKS Carbine":249,"Twin Tiger Hooks":250,"Wushu Double Axes":251,"Ithaca 37":252,"Lorcin 380":253,"S&W M29":254,"Flamethrower":255,"Dual Axes":289,"Dual Hammers":290,"Dual Scimitars":291,"Dual Samurai Swords":292,"Combat Vest":332,"Liquid Body Armor":333,"Flexible Body Armor":334,"Pair of High Heels":346,"Hazmat Suit":348,"Fine Chisel":359,"Ivory Walking Cane":360,"Gold Plated AK-47":382,"Handbag":387,"Pink Mac-10":388,"Macana":391,"Slingshot":393,"Metal Nunchaku":395,"Flail":397,"SIG 552":398,"ArmaLite M-15A4":399,"Guandao":400,"Lead Pipe":401,"Ice Pick":402,"Cricket Bat":438,"Frying Pan":439,"Pillow":440,"MP5k":483,"AK74U":484,"Skorpion":485,"TMP":486,"Thompson":487,"MP 40":488,"Luger":489,"Blunderbuss":490,"Medieval Helmet":538,"Blood Spattered Sickle":539,"Dual TMPs":545,"Dual Bushmasters":546,"Dual MP5s":547,"Dual P90s":548,"Dual Uzis":549,"Golden Broomstick":599,"Devil's Pitchfork":600,"Pair of Ice Skates":604,"Diamond Icicle":605,"Tavor TAR-21":612,"Harpoon":613,"Diamond Bladed Knife":614,"Naval Cutlass":615,"Petrified Humerus":632,"Kevlar Gloves":640,"WWII Helmet":641,"Motorcycle Helmet":642,"Construction Helmet":643,"Welding Helmet":644,"Safety Boots":645,"Hiking Boots":646,"Leather Helmet":647,"Leather Pants":648,"Leather Boots":649,"Leather Gloves":650,"Combat Helmet":651,"Combat Pants":652,"Combat Boots":653,"Combat Gloves":654,"Riot Helmet":655,"Riot Body":656,"Riot Pants":657,"Riot Boots":658,"Riot Gloves":659,"Dune Helmet":660,"Dune Vest":661,"Dune Pants":662,"Dune Boots":663,"Dune Gloves":664,"Assault Helmet":665,"Assault Body":666,"Assault Pants":667,"Assault Boots":668,"Assault Gloves":669,"Delta Gas Mask":670,"Delta Body":671,"Delta Pants":672,"Delta Boots":673,"Delta Gloves":674,"Marauder Face Mask":675,"Marauder Body":676,"Marauder Pants":677,"Marauder Boots":678,"Marauder Gloves":679,"EOD Helmet":680,"EOD Apron":681,"EOD Pants":682,"EOD Boots":683,"EOD Gloves":684,"Plastic Sword":790,"Penelope":792,"Duke's Hammer":805,"Nock Gun":830,"Beretta Pico":831,"Riding Crop":832,"Rheinmetall MG 3":837,"Homemade Pocket Shotgun":838,"Madball":839,"Tranquilizer Gun":844,"Bolt Gun":845,"Scalpel":846,"Kevlar Lab Coat":848,"Sledgehammer":850,"Bug Swatter":871,"Prototype":874,"Bread Knife":1053,"Poison Umbrella":1055,"Millwall Brick":1056,"SMAW Launcher":1152,"China Lake":1153,"Milkor MGL":1154,"PKM":1155,"Negev NG-5":1156,"Stoner 96":1157,"Meat Hook":1158,"Cleaver":1159,"Golf Club":1231,"Bone Saw":1255,"Cattle Prod":1257,"Ban Hammer":1296,"Sentinel Helmet":1307,"Sentinel Apron":1308,"Sentinel Pants":1309,"Sentinel Boots":1310,"Sentinel Gloves":1311,"Vanguard Respirator":1355,"Vanguard Body":1356,"Vanguard Pants":1357,"Vanguard Boots":1358,"Vanguard Gloves":1359},
bonusClassExceptions: {"poison":"poisoned","demoralize":"demoralized","freeze":"frozen","hazardous":"hazarfouse","impassable":"full-block","radiation protection":"item-radiation-bonus","immutable":"sentinel","invulnerable":"negative-status_mitigation","imperviable":"life-bonus","impenetrable":"bullets-protection","insurmountable":"quarter-life-damage-mitigation","impregnable":"melee-protection","burn":"burning","proficience":"experience","eviscerate":"evicerate","double-edged":"doubleedged"},
};
const Config = {
key: "weapon_bonus_view_config",
defaults: {
API: "",
ENABLE_BAZAAR_UPDATE: true,
LIMIT_PER_REFRESH: 20,
STORAGE_KEY: "weapon_bonus_view_cache",
COLOR_SET: {
defaultBlue: "#00A9F9",
defaultRed: "#E54C19",
defaultGreen: "#82c91e",
defaultPurple: "#b072ef",
defaultYellow: "#CA9800",
defaultOrange: "#E67700",
normal: { bonusBg: "#FFF", bonusFont: "#333" },
dark: { bonusBg: "#444", bonusFont: "#ddd" },
},
},
value: null,
load() {
let saved = {};
try {
const raw = localStorage.getItem(this.key);
if (raw) saved = JSON.parse(raw);
} catch {}
let api = localStorage.getItem("APIKey") || "";
const pdaKey = "###PDA-APIKEY###";
if (pdaKey.charAt(0) !== "#" && !api) api = pdaKey;
if (api && !localStorage.getItem("APIKey")) localStorage.setItem("APIKey", api);
this.value = { ...this.defaults, ...saved, API: api || saved.API || "" };
localStorage.setItem(this.key, JSON.stringify(this.value));
if (!this.value.API) log("No valid API, please insert a public API");
},
};
const Store = {
async init() { App.state.storage = await GMX.get(Config.value.STORAGE_KEY, {}); },
async save() { await GMX.set(Config.value.STORAGE_KEY, App.state.storage); },
get(id) { return App.state.storage[String(id)]; },
async set(id, value) { App.state.storage[String(id)] = value; await this.save(); },
};
const Util = {
isMobile() { return window.innerWidth <= 768; },
isSupportedItem(itemId) { return Data.weaponIds.has(itemId) || Data.armorIds.has(itemId); },
getItemType(itemId) { if (Data.weaponIds.has(itemId)) return "weapon"; if (Data.armorIds.has(itemId)) return "armor"; return null; },
clearFloatingNodes() { for (const node of App.state.floatingNodes) if (node?.remove) node.remove(); App.state.floatingNodes = []; },
setContext(partial) { App.state.context = { inItemList: false, inFactionArmoury: false, inCabinet: false, ...partial }; },
updateCacheRecord(armouryId, item, updateFn) { if (armouryId) App.state.cacheRecord.set(String(armouryId), { item, updateFn }); },
invokeCacheUpdate(armouryId) { const record = App.state.cacheRecord.get(String(armouryId)); if (record) record.updateFn(record.item); },
safeText(el) { return el && el.length ? el.text().trim() : ""; },
isLocked(item) { return item.attr("data-wbv-state") === "pending"; },
markPending(item) { item.attr("data-wbv-state", "pending"); },
markDone(item) { item.attr("data-wbv-state", "done"); },
clearMark(item) { item.removeAttr("data-wbv-state"); },
appendUnique(target, node, item) {
if (!target || !target.length || !node) {
Util.clearMark(item);
return false;
}
target.find("> div.item-detail").remove();
target.append(node);
Util.markDone(item);
return true;
},
placeFloatingNode(item, node) {
if (!item || !item.length || !node) return false;
const rowOffset = item.offset();
if (!rowOffset) return false;
node.css({
position: "absolute",
left: `${rowOffset.left + 120}px`,
top: `${rowOffset.top + 6}px`,
zIndex: 100002,
pointerEvents: "none",
});
$("body").append(node);
App.state.floatingNodes.push(node);
Util.markDone(item);
return true;
},
onHashChange() { window.addEventListener("hashchange", () => { App.state.cacheRecord.clear(); Util.clearFloatingNodes(); }); },
};
const Api = {
async getItemDetails(armouryId) {
const key = String(armouryId);
if (!key) return null;
const cached = Store.get(key);
if (cached) return cached;
if (App.state.requestHistory.includes(key)) return null;
if (App.state.requestHistory.length >= Config.value.LIMIT_PER_REFRESH && App.state.context.inItemList) return null;
if (!Config.value.API) return null;
App.state.requestHistory.push(key);
try {
const response = await $.ajax({ url: `https://api.torn.com/torn/${key}?selections=itemdetails&key=${Config.value.API}` });
const info = response?.itemdetails;
if (!info) return null;
await Store.set(key, info);
return info;
} catch { return null; }
},
async cacheInventoryResponse(resp) {
const { itemName, itemID, armoryID, extras } = resp || {};
if (!armoryID || !extras) return null;
let quality, accuracy, damage, armor, bonuses;
for (const key in extras) {
const extra = extras[key];
if (!extra) continue;
if (extra.title === "Quality") quality = extra.value.replace("%", "");
else if (extra.title === "Armor") armor = extra.value;
else if (extra.title === "Damage") damage = extra.value;
else if (extra.title === "Accuracy") accuracy = extra.value;
else if (extra.title === "Bonus") {
bonuses ||= {};
const description = extra.descTitle?.split("<br/>")?.[1] || "";
bonuses[extra.value] = { icon: extra.icon, description, bonus: extra.value, value: parseInt(String(extra.rawValue || "").replace("%", ""), 10) };
}
}
const info = { ID: itemID, UID: armoryID, name: itemName, quality };
if (armor) info.armor = parseFloat(armor);
if (damage) info.damage = parseFloat(damage);
if (accuracy) info.accuracy = parseFloat(accuracy);
if (bonuses) info.bonuses = bonuses;
await Store.set(armoryID, info);
return armoryID;
},
};
const UI = {
handlePercent(description, value) { const idx = description.indexOf(value); return idx >= 0 && description[idx + String(value).length] === "%" ? `${value}%` : value; },
getBonusIconClass(name) { const normalized = String(name || "").toLowerCase(); const mapped = Data.bonusClassExceptions[normalized] || normalized; return `bonus-attachment-${mapped}`; },
extractBonuses(bonuses) {
if (!bonuses) return null;
const out = [];
const seen = new Set();
for (const key in bonuses) {
const item = bonuses[key];
if (!item) continue;
const { bonus, value, description, icon } = item;
if (!description || description.indexOf(value) === -1) continue;
const displayValue = UI.handlePercent(description, value);
const dedupeKey = `${String(bonus).toLowerCase()}|${displayValue}|${description}`;
if (seen.has(dedupeKey)) continue;
seen.add(dedupeKey);
out.push({ bonus, value: displayValue, description, html: `<i class="${icon || UI.getBonusIconClass(bonus)}" title="${description}"></i><font color="${Config.value.COLOR_SET.defaultPurple}" style="margin-left:5px;">${displayValue}</font>` });
}
return out.length ? out : null;
},
getQualityColor(quality, rarity = 0) {
const colors = Config.value.COLOR_SET;
if (rarity === 1 || rarity === "Yellow") return colors.defaultYellow;
if (rarity === 2 || rarity === "Orange") return colors.defaultOrange;
if (rarity === 3 || rarity === "Red") return colors.defaultRed;
if (quality < 20) return colors.defaultRed;
if (quality < 50) return colors.defaultYellow;
return colors.defaultGreen;
},
createItemDetailsNode(itemDetails, isBazaar = false) {
const { damage, accuracy, quality, armor, bonuses, rarity } = itemDetails;
const q = parseFloat(quality || 0);
const qualityColor = UI.getQualityColor(q, rarity);
const qualityHtml = `<font color="${qualityColor}" style="margin-left:5px;" title="Quality: ${q}%">Q:${q.toFixed(2)}%</font>`;
const bonusDetails = UI.extractBonuses(bonuses);
if (bonusDetails) {
if (App.state.context.inFactionArmoury || App.state.context.inItemList || App.state.context.inCabinet) {
const wrapper = $(`<div class="item-detail" style="display:flex;align-items:center;"></div>`);
const bonus = bonusDetails[0];
const tag = $(`<div class="message icon-text border-round" title="${bonus.description}">${bonus.bonus} ${bonus.value}</div>`);
tag.css({ display: "flex", height: "20px", lineHeight: "20px", padding: "0 4px", marginLeft: "5px", fontWeight: "bold", textAlign: "center", backgroundColor: qualityColor, color: "#eee" });
wrapper.append(tag);
return wrapper;
}
if (Util.isMobile() || isBazaar) return $(`<div class="item-detail" style="display:flex;align-items:center;">${bonusDetails[0].html}</div>`);
return $(`<div class="item-detail" style="display:flex;align-items:center;">${bonusDetails[0].html}${qualityHtml}</div>`);
}
if ((Util.isMobile() && !App.state.context.inFactionArmoury) || isBazaar || App.state.context.inCabinet) return $(`<div class="item-detail" style="display:flex;align-items:center;">${qualityHtml}</div>`);
if (armor) return $(`<div class="item-detail" style="display:flex;align-items:center;"><i class="bonus-attachment-item-defence-bonus"></i><font color="${Config.value.COLOR_SET.defaultGreen}">${parseFloat(armor).toFixed(2)}</font>${qualityHtml}</div>`);
return $(`<div class="item-detail" style="display:flex;align-items:center;"><i class="bonus-attachment-item-damage-bonus"></i><font color="${Config.value.COLOR_SET.defaultRed}">${parseFloat(damage || 0).toFixed(2)}</font><i class="bonus-attachment-item-accuracy-bonus" style="margin-left:5px;"></i><font color="${Config.value.COLOR_SET.defaultBlue}">${parseFloat(accuracy || 0).toFixed(2)}</font>${qualityHtml}</div>`);
},
addStyles() {
if (document.getElementById("weapon-bonus-view-styles")) return;
GMX.style(`
.top-bonuses, .bottom-bonuses { position: absolute; left: 3px; display: flex; flex-flow: row; justify-content: space-between; width: 100px; }
.top-bonuses { top: -5px; }
.bottom-bonuses { bottom: -5px; }
body.dark-mode .bonus-attachment, body.dark-mode .armory-bg { background: ${Config.value.COLOR_SET.dark.bonusBg} !important; color: ${Config.value.COLOR_SET.dark.bonusFont} !important; }
.armory-bg { background: ${Config.value.COLOR_SET.normal.bonusBg}; color: ${Config.value.COLOR_SET.normal.bonusFont}; }
.bonus-attachment { background: ${Config.value.COLOR_SET.normal.bonusBg}; color: ${Config.value.COLOR_SET.normal.bonusFont}; box-shadow: 0 1px 0 #00000003; border-radius: 7px; padding: 2px; margin-right: 1px; display: flex; align-items: center; float: left; }
.icon-attachment { float: left; }
`);
const marker = document.createElement("div");
marker.id = "weapon-bonus-view-styles";
marker.style.display = "none";
document.body.appendChild(marker);
},
};
const Page = {
observe(target, callback) { if (!target) return; const obs = new MutationObserver(callback); obs.observe(target, { subtree: true, childList: true }); App.state.observers.push(obs); },
interceptXML() {
if (App.state.interceptXMLInstalled) return;
App.state.interceptXMLInstalled = true;
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (...args) {
this.addEventListener("readystatechange", function () {
if (this.readyState !== 4) return;
try {
const url = new URL(this.responseURL);
const params = new URLSearchParams(url.search);
if (url.pathname === "/page.php" && params.get("sid") === "inventory") {
Api.cacheInventoryResponse(JSON.parse(this.response)).then((armouryId) => { if (armouryId) Util.invokeCacheUpdate(armouryId); }).catch(() => {});
}
} catch {}
}, false);
return originalOpen.apply(this, args);
};
},
interceptFetch() {
if (App.state.interceptFetchInstalled) return;
App.state.interceptFetchInstalled = true;
const targetWindow = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
const originalFetch = targetWindow.fetch;
targetWindow.fetch = async (...args) => {
const response = await originalFetch(...args);
try {
const requestUrl = typeof args[0] === "string" ? args[0] : args[0]?.url;
if (!requestUrl) return response;
const url = new URL(requestUrl, location.origin);
const params = new URLSearchParams(url.search);
if (url.pathname === "/bazaar.php" && params.get("sid") === "bazaarData") {
const order = params.get("order") || "";
const searchName = params.get("searchname") || "";
if (order !== App.state.lastBazaarOrder) { App.state.lastBazaarOrder = order; App.state.bazaarCache = {}; }
if (searchName !== App.state.lastBazaarSearch) { App.state.lastBazaarSearch = searchName; App.state.bazaarCache = {}; }
const cloned = response.clone();
const json = await cloned.json();
Modules.userBazaar.handleResponse(params, json);
}
} catch {}
return response;
};
},
};
const PriceChecker = {
config: {
supabaseUrl: "https://btrmmuuoofbonmuwrkzg.supabase.co",
supabaseAnonKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ0cm1tdXVvb2Zib25tdXdya3pnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg4NTEzMTgsImV4cCI6MjA4NDQyNzMxOH0.E-s0k46BORXLICAvxtEpqoM3Qmh4-TRLaJAwXO6wJTY",
marketApi: "https://weav3r.dev/api/ranked-weapons",
cacheKey: "wbv_price_checker_cache",
cacheTtl: 5 * 60 * 1000,
maxCacheEntries: 50,
settingsKey: "wbv_price_checker_settings",
},
state: {
open: false,
eventsBound: false,
activeTab: "history",
loading: false,
marketLoading: false,
error: "",
marketError: "",
results: [],
marketResults: [],
total: 0,
marketTotal: 0,
offset: 0,
itemType: "weapon",
filters: { itemName: "", bonus1Id: "", bonus1Min: "", bonus1Max: "", bonus2Id: "", bonus2Min: "", bonus2Max: "", qualityMin: "", qualityMax: "" },
},
bonusData: [
{id:50,title:"Achilles"},{id:72,title:"Assassinate"},{id:52,title:"Backstab"},{id:54,title:"Berserk"},{id:57,title:"Bleed"},{id:33,title:"Blindfire"},{id:51,title:"Blindside"},{id:85,title:"Bloodlust"},{id:67,title:"Comeback"},{id:55,title:"Conserve"},{id:45,title:"Cripple"},{id:49,title:"Crusher"},{id:47,title:"Cupid"},{id:63,title:"Deadeye"},{id:62,title:"Deadly"},{id:36,title:"Demoralize"},{id:86,title:"Disarm"},{id:105,title:"Double Tap"},{id:74,title:"Double-edged"},{id:87,title:"Empower"},{id:56,title:"Eviscerate"},{id:75,title:"Execute"},{id:1,title:"Expose"},{id:82,title:"Finale"},{id:79,title:"Focus"},{id:38,title:"Freeze"},{id:80,title:"Frenzy"},{id:64,title:"Fury"},{id:53,title:"Grace"},{id:34,title:"Hazardous"},{id:83,title:"Home run"},{id:115,title:"Immutable"},{id:26,title:"Impassable"},{id:17,title:"Impenetrable"},{id:22,title:"Imperviable"},{id:15,title:"Impregnable"},{id:92,title:"Insurmountable"},{id:91,title:"Invulnerable"},{id:102,title:"Irradiate"},{id:121,title:"Irrepressible"},{id:112,title:"Kinetokinesis"},{id:89,title:"Lacerate"},{id:61,title:"Motivation"},{id:59,title:"Paralyze"},{id:84,title:"Parry"},{id:101,title:"Penetrate"},{id:21,title:"Plunder"},{id:68,title:"Powerful"},{id:14,title:"Proficience"},{id:66,title:"Puncture"},{id:88,title:"Quicken"},{id:90,title:"Radiation Protection"},{id:65,title:"Rage"},{id:41,title:"Revitalize"},{id:43,title:"Roshambo"},{id:120,title:"Shock"},{id:44,title:"Slow"},{id:104,title:"Smash"},{id:73,title:"Smurf"},{id:71,title:"Specialist"},{id:35,title:"Spray"},{id:37,title:"Storage"},{id:20,title:"Stricken"},{id:58,title:"Stun"},{id:60,title:"Suppress"},{id:78,title:"Sure Shot"},{id:48,title:"Throttle"},{id:103,title:"Toxin"},{id:81,title:"Warlord"},{id:46,title:"Weaken"},{id:76,title:"Wind-up"},{id:42,title:"Wither"},
],
bonusMap: {},
defaults: { bonusTolerance: 10, qualityTolerance: 10, disableBonusValueFilter: false, disableQualityFilter: false },
escape(value) {
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
},
getSettings() {
try { return { ...this.defaults, ...(JSON.parse(localStorage.getItem(this.config.settingsKey) || "{}")) }; } catch { return { ...this.defaults }; }
},
saveSettings(next) { localStorage.setItem(this.config.settingsKey, JSON.stringify({ ...this.getSettings(), ...next })); },
getCacheStore() {
try { return JSON.parse(localStorage.getItem(this.config.cacheKey) || "{}"); } catch { return {}; }
},
setCacheStore(store) { try { localStorage.setItem(this.config.cacheKey, JSON.stringify(store)); } catch {} },
getCached(key) {
const store = this.getCacheStore();
const entry = store[key];
if (entry && Date.now() - entry.ts < this.config.cacheTtl) return entry.data;
if (entry) { delete store[key]; this.setCacheStore(store); }
return null;
},
setCached(key, data) {
const store = this.getCacheStore();
const keys = Object.keys(store);
if (keys.length >= this.config.maxCacheEntries) keys.sort((a, b) => store[a].ts - store[b].ts).slice(0, 10).forEach((k) => delete store[k]);
store[key] = { ts: Date.now(), data };
this.setCacheStore(store);
},
async request(options) {
const useGM = typeof GM_xmlhttpRequest === "function" ? GM_xmlhttpRequest : (typeof GM !== "undefined" && typeof GM.xmlHttpRequest === "function" ? GM.xmlHttpRequest.bind(GM) : null);
if (useGM) {
return new Promise((resolve, reject) => {
useGM({
method: options.method || "GET",
url: options.url,
headers: options.headers,
data: options.body,
timeout: 15000,
onload: (response) => response.status >= 200 && response.status < 300 ? resolve(response.responseText) : reject(new Error(`API error: ${response.status}`)),
onerror: () => reject(new Error("Network error")),
ontimeout: () => reject(new Error("Request timeout")),
});
});
}
const response = await fetch(options.url, { method: options.method || "GET", headers: options.headers, body: options.body });
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.text();
},
async requestJson(options) {
const text = await this.request(options);
try { return JSON.parse(text); } catch { throw new Error("Parse error"); }
},
formatPrice(p) {
const value = Number(p || 0);
if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;
if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;
if (value >= 1e3) return `$${(value / 1e3).toFixed(1)}K`;
return `$${value.toLocaleString()}`;
},
formatDate(ts) {
const d = new Date(Number(ts) * 1000);
if (Number.isNaN(d.getTime())) return "?";
const diff = Math.floor((Date.now() - d.getTime()) / 86400000);
if (diff === 0) return "Today";
if (diff === 1) return "Yesterday";
if (diff < 7) return `${diff}d ago`;
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
},
getBonusId(name) {
if (!name) return null;
const normalized = String(name).toLowerCase().replace(/[\s-]/g, "");
return this.bonusMap[normalized] || this.bonusMap[String(name).toLowerCase()] || null;
},
getBonusName(id) { return this.bonusMap[id] || `Bonus #${id}`; },
extractBonuses(itemDetail) {
const bonuses = itemDetail?.bonuses;
if (!bonuses) return [];
const out = [];
for (const key in bonuses) {
const current = bonuses[key];
if (!current?.bonus) continue;
const id = this.getBonusId(current.bonus);
if (!id) continue;
const rawValue = current.value != null ? Number(current.value) : null;
out.push({ id, value: Number.isFinite(rawValue) ? rawValue : null });
}
return out.slice(0, 2);
},
calcRange(value, tolerance) {
if (value == null || value === "") return { min: "", max: "" };
const numeric = Number(value);
if (!Number.isFinite(numeric)) return { min: "", max: "" };
if (tolerance === 0) return { min: String(numeric), max: String(numeric) };
const factor = tolerance / 100;
return { min: String(Math.floor(numeric * (1 - factor))), max: String(Math.ceil(numeric * (1 + factor))) };
},
prepareFilters(itemDetail, itemType) {
const bonuses = this.extractBonuses(itemDetail);
const settings = this.getSettings();
this.state.results = [];
this.state.marketResults = [];
this.state.total = 0;
this.state.marketTotal = 0;
this.state.error = "";
this.state.marketError = "";
this.state.filters.itemName = itemDetail?.name || "";
this.state.itemType = itemType || "weapon";
this.state.offset = 0;
this.state.filters.bonus1Id = bonuses[0]?.id ? String(bonuses[0].id) : "";
Object.assign(this.state.filters, this.calcBonusFields("bonus1", bonuses[0]?.value, settings.bonusTolerance));
this.state.filters.bonus2Id = bonuses[1]?.id ? String(bonuses[1].id) : "";
Object.assign(this.state.filters, this.calcBonusFields("bonus2", bonuses[1]?.value, settings.bonusTolerance));
const qualityRange = this.calcRange(itemDetail?.quality, settings.qualityTolerance);
this.state.filters.qualityMin = qualityRange.min;
this.state.filters.qualityMax = qualityRange.max;
},
calcBonusFields(prefix, value, tolerance) {
const range = this.calcRange(value, tolerance);
return { [`${prefix}Min`]: range.min, [`${prefix}Max`]: range.max };
},
ensureModal() {
if (document.getElementById("wbv-price-modal")) return;
const styleId = "wbv-price-checker-styles";
if (!document.getElementById(styleId)) {
GMX.style(`
#wbv-price-overlay{position:fixed;inset:0;z-index:100099;background:rgba(0,0,0,.62);backdrop-filter:blur(2px);display:none;}
#wbv-price-overlay.open{display:block;}
#wbv-price-modal{position:fixed;inset:5vh auto auto 50%;transform:translateX(-50%);width:min(860px,94vw);max-height:90vh;z-index:100100;background:#1e1f22;color:#dbdee1;border:1px solid #111214;border-radius:14px;display:none;flex-direction:column;box-shadow:0 24px 60px rgba(0,0,0,.55);overflow:hidden;font-family:Whitney,"Helvetica Neue",Helvetica,Arial,sans-serif;}
#wbv-price-modal.open{display:flex;} #wbv-price-modal .wbv-pc-head{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;background:#1a1b1e;border-bottom:1px solid rgba(255,255,255,.04);}
#wbv-price-modal .wbv-pc-title{display:flex;flex-direction:column;gap:2px;} #wbv-price-modal .wbv-pc-title strong{font-size:17px;color:#f2f3f5;} #wbv-price-modal .wbv-pc-sub{font-size:12px;color:#949ba4;}
#wbv-price-modal .wbv-pc-tabs{display:flex;gap:8px;padding:10px 12px 0;background:#1e1f22;} #wbv-price-modal .wbv-pc-tab{flex:1;padding:9px 10px;background:#2b2d31;border:none;border-radius:8px;color:#b5bac1;font-weight:700;cursor:pointer;}
#wbv-price-modal .wbv-pc-tab.active{color:#fff;background:#404249;} #wbv-price-modal .wbv-pc-body{padding:12px;overflow:auto;} #wbv-price-modal .wbv-pc-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;padding:10px;border-radius:12px;background:#232428;border:1px solid rgba(255,255,255,.04);}
#wbv-price-modal .wbv-pc-grid input,#wbv-price-modal .wbv-pc-grid select{width:100%;box-sizing:border-box;padding:9px 10px;border:1px solid #111214;border-radius:8px;background:#111214;color:#f2f3f5;}
#wbv-price-modal .wbv-pc-actions{display:flex;gap:8px;align-items:center;margin-top:10px;flex-wrap:wrap;} #wbv-price-modal .wbv-pc-btn,#wbv-price-modal .wbv-pc-close,#wbv-price-modal .wbv-pc-small{padding:8px 11px;border:none;border-radius:8px;background:#5865f2;color:#fff;font-weight:700;cursor:pointer;}
#wbv-price-modal .wbv-pc-close{background:#2b2d31;color:#dbdee1;} #wbv-price-modal .wbv-pc-small{background:#2b2d31;padding:7px 10px;font-size:12px;} #wbv-price-modal .wbv-pc-tog{display:flex;gap:12px;flex-wrap:wrap;font-size:12px;color:#b5bac1;}
#wbv-price-modal .wbv-pc-stream{display:flex;flex-direction:column;gap:2px;margin-top:12px;} #wbv-price-modal .wbv-pc-item{display:grid;grid-template-columns:40px 1fr;gap:10px;padding:10px 8px;border-radius:10px;} #wbv-price-modal .wbv-pc-item:hover{background:rgba(255,255,255,.03);}
#wbv-price-modal .wbv-pc-avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#5865f2,#3ba55d);display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px;font-weight:700;} #wbv-price-modal .wbv-pc-content{min-width:0;}
#wbv-price-modal .wbv-pc-line{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;} #wbv-price-modal .wbv-pc-name{font-size:14px;font-weight:700;color:#f2f3f5;} #wbv-price-modal .wbv-pc-time{font-size:12px;color:#949ba4;}
#wbv-price-modal .wbv-pc-meta{font-size:13px;color:#b5bac1;margin-top:3px;line-height:1.45;} #wbv-price-modal .wbv-pc-badges{display:flex;gap:6px;flex-wrap:wrap;margin-top:7px;}
#wbv-price-modal .wbv-pc-bonus{display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:#2b2d31;font-size:11px;color:#f2f3f5;} #wbv-price-modal .wbv-pc-price{margin-top:7px;font-size:13px;color:#949cf7;font-weight:700;}
#wbv-price-modal .wbv-pc-foot{display:flex;justify-content:space-between;align-items:center;padding:11px 12px;border-top:1px solid rgba(255,255,255,.04);background:#1a1b1e;} #wbv-price-modal .wbv-pc-empty,#wbv-price-modal .wbv-pc-loading{padding:14px 6px;color:#949ba4;font-size:13px;} #wbv-price-modal .wbv-pc-error{color:#ff8787;padding:10px 2px;} .wbv-price-check{margin-left:6px;padding:4px 9px;border:none;border-radius:999px;background:#5865f2;color:#fff;font-size:11px;line-height:1.2;cursor:pointer;font-weight:700;}
`);
const marker = document.createElement("div");
marker.id = styleId;
marker.style.display = "none";
document.body.appendChild(marker);
}
const overlay = document.createElement("div");
overlay.id = "wbv-price-overlay";
document.body.appendChild(overlay);
const modal = document.createElement("div");
modal.id = "wbv-price-modal";
document.body.appendChild(modal);
this.bindEvents();
},
bindEvents() {
if (this.state.eventsBound) return;
this.state.eventsBound = true;
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && this.state.open) this.close();
});
document.addEventListener("click", (event) => {
if (event.target && event.target.id === "wbv-price-overlay" && this.state.open) this.close();
});
},
attachButton(target, itemDetail, itemType) {
if (!target || !target.length || !itemDetail?.name) return;
if (target.find(".wbv-price-check").length) return;
const button = $('<button class="wbv-price-check" type="button">Price</button>');
button.on("click", (event) => {
event.preventDefault();
event.stopPropagation();
this.prepareFilters(itemDetail, itemType);
this.open();
});
target.append(button);
},
open() { this.ensureModal(); this.state.open = true; this.state.activeTab = "history"; this.render(); this.searchHistory(); },
close() {
this.state.open = false;
const modal = document.getElementById("wbv-price-modal");
const overlay = document.getElementById("wbv-price-overlay");
if (modal) modal.classList.remove("open");
if (overlay) overlay.classList.remove("open");
},
render() {
this.ensureModal();
const modal = document.getElementById("wbv-price-modal");
if (!modal) return;
const overlay = document.getElementById("wbv-price-overlay");
if (!this.state.open) {
modal.classList.remove("open");
if (overlay) overlay.classList.remove("open");
return;
}
modal.classList.add("open");
if (overlay) overlay.classList.add("open");
const settings = this.getSettings();
const options = this.bonusData.slice().sort((a, b) => a.title.localeCompare(b.title)).map((bonus) => `<option value="${bonus.id}">${this.escape(bonus.title)}</option>`).join("");
const results = this.state.activeTab === "history" ? this.renderHistoryResults() : this.renderMarketResults();
modal.innerHTML = `<div class="wbv-pc-head"><div class="wbv-pc-title"><strong>Price Check</strong><span class="wbv-pc-sub">Search history and live listings in a cleaner feed view</span></div><button class="wbv-pc-close" type="button" id="wbv-pc-close">Close</button></div><div class="wbv-pc-tabs"><button class="wbv-pc-tab ${this.state.activeTab === "history" ? "active" : ""}" data-tab="history" type="button">History</button><button class="wbv-pc-tab ${this.state.activeTab === "market" ? "active" : ""}" data-tab="market" type="button">Market</button></div><div class="wbv-pc-body"><div class="wbv-pc-grid"><input id="wbv-pc-item-name" placeholder="Item name" value="${this.escape(this.state.filters.itemName)}"><select id="wbv-pc-bonus1"><option value="">Any Bonus 1</option>${options}</select><input id="wbv-pc-bonus1-min" placeholder="Bonus 1 min" value="${this.escape(this.state.filters.bonus1Min)}" ${settings.disableBonusValueFilter ? "disabled" : ""}><input id="wbv-pc-bonus1-max" placeholder="Bonus 1 max" value="${this.escape(this.state.filters.bonus1Max)}" ${settings.disableBonusValueFilter ? "disabled" : ""}><select id="wbv-pc-bonus2"><option value="">Any Bonus 2</option>${options}</select><input id="wbv-pc-bonus2-min" placeholder="Bonus 2 min" value="${this.escape(this.state.filters.bonus2Min)}" ${settings.disableBonusValueFilter ? "disabled" : ""}><input id="wbv-pc-bonus2-max" placeholder="Bonus 2 max" value="${this.escape(this.state.filters.bonus2Max)}" ${settings.disableBonusValueFilter ? "disabled" : ""}><input id="wbv-pc-quality-min" placeholder="Quality min" value="${this.escape(this.state.filters.qualityMin)}" ${settings.disableQualityFilter ? "disabled" : ""}><input id="wbv-pc-quality-max" placeholder="Quality max" value="${this.escape(this.state.filters.qualityMax)}" ${settings.disableQualityFilter ? "disabled" : ""}></div><div class="wbv-pc-actions"><button class="wbv-pc-btn" id="wbv-pc-search" type="button">${this.state.activeTab === "history" ? "Search History" : "Search Market"}</button><div class="wbv-pc-tog"><label><input type="checkbox" id="wbv-pc-disable-bonus" ${settings.disableBonusValueFilter ? "checked" : ""}> Disable bonus values</label><label><input type="checkbox" id="wbv-pc-disable-quality" ${settings.disableQualityFilter ? "checked" : ""}> Disable quality</label></div></div><div class="wbv-pc-stream">${results}</div></div><div class="wbv-pc-foot"><span>${this.state.activeTab === "history" ? `${Number(this.state.total || 0).toLocaleString()} results` : `${Number(this.state.marketTotal || 0).toLocaleString()} listings`}</span>${this.state.activeTab === "history" ? `<div><button class="wbv-pc-small" id="wbv-pc-prev" type="button" ${this.state.offset === 0 ? "disabled" : ""}>Prev</button> <button class="wbv-pc-small" id="wbv-pc-next" type="button" ${(this.state.offset + 20) >= this.state.total ? "disabled" : ""}>Next</button></div>` : `<a href="https://weav3r.dev/" target="_blank" rel="noopener noreferrer" style="color:#adb5bd;">TornW3B</a>`}</div>`;
$("#wbv-pc-bonus1").val(this.state.filters.bonus1Id);
$("#wbv-pc-bonus2").val(this.state.filters.bonus2Id);
modal.querySelector("#wbv-pc-close").onclick = () => this.close();
modal.querySelectorAll(".wbv-pc-tab").forEach((tab) => {
tab.onclick = () => { this.state.activeTab = tab.dataset.tab; this.render(); if (this.state.activeTab === "market" && !this.state.marketResults.length && !this.state.marketLoading) this.searchMarket(); };
});
modal.querySelector("#wbv-pc-search").onclick = () => { this.syncFiltersFromDom(); if (this.state.activeTab === "history") { this.state.offset = 0; this.searchHistory(); } else { this.searchMarket(); } };
modal.querySelector("#wbv-pc-disable-bonus").onchange = (event) => { this.saveSettings({ disableBonusValueFilter: event.target.checked }); this.render(); };
modal.querySelector("#wbv-pc-disable-quality").onchange = (event) => { this.saveSettings({ disableQualityFilter: event.target.checked }); this.render(); };
const prev = modal.querySelector("#wbv-pc-prev");
const next = modal.querySelector("#wbv-pc-next");
if (prev) prev.onclick = () => { this.state.offset = Math.max(0, this.state.offset - 20); this.searchHistory(); };
if (next) next.onclick = () => { this.state.offset += 20; this.searchHistory(); };
},
syncFiltersFromDom() {
this.state.filters.itemName = $("#wbv-pc-item-name").val().trim();
this.state.filters.bonus1Id = $("#wbv-pc-bonus1").val();
this.state.filters.bonus1Min = $("#wbv-pc-bonus1-min").val();
this.state.filters.bonus1Max = $("#wbv-pc-bonus1-max").val();
this.state.filters.bonus2Id = $("#wbv-pc-bonus2").val();
this.state.filters.bonus2Min = $("#wbv-pc-bonus2-min").val();
this.state.filters.bonus2Max = $("#wbv-pc-bonus2-max").val();
this.state.filters.qualityMin = $("#wbv-pc-quality-min").val();
this.state.filters.qualityMax = $("#wbv-pc-quality-max").val();
},
buildHistoryBody() {
const body = { limit: 20, offset: this.state.offset, sort_by: "timestamp", sort_order: "desc" };
const settings = this.getSettings();
const filters = this.state.filters;
if (filters.itemName) body.item_name = filters.itemName;
if (filters.bonus1Id) {
body.bonus1_id = parseInt(filters.bonus1Id, 10);
if (!settings.disableBonusValueFilter) {
if (filters.bonus1Min) body.bonus1_value_min = parseFloat(filters.bonus1Min);
if (filters.bonus1Max) body.bonus1_value_max = parseFloat(filters.bonus1Max);
}
}
if (filters.bonus2Id) {
body.bonus2_id = parseInt(filters.bonus2Id, 10);
if (!settings.disableBonusValueFilter) {
if (filters.bonus2Min) body.bonus2_value_min = parseFloat(filters.bonus2Min);
if (filters.bonus2Max) body.bonus2_value_max = parseFloat(filters.bonus2Max);
}
}
if (!settings.disableQualityFilter) {
if (filters.qualityMin) body.quality_min = parseFloat(filters.qualityMin);
if (filters.qualityMax) body.quality_max = parseFloat(filters.qualityMax);
}
return body;
},
async searchHistory() {
this.state.loading = true;
this.state.error = "";
this.render();
try {
const body = this.buildHistoryBody();
const cacheKey = JSON.stringify(body);
const cached = this.getCached(cacheKey);
const data = cached || await this.requestJson({ method: "POST", url: `${this.config.supabaseUrl}/functions/v1/search-auctions`, headers: { "Content-Type": "application/json", apikey: this.config.supabaseAnonKey, Authorization: `Bearer ${this.config.supabaseAnonKey}` }, body: JSON.stringify(body) });
if (!cached) this.setCached(cacheKey, data);
this.state.results = data.auctions || [];
this.state.total = data.total || 0;
} catch (error) {
this.state.error = error.message || "Search failed";
this.state.results = [];
this.state.total = 0;
}
this.state.loading = false;
this.render();
},
buildMarketParams() {
const params = new URLSearchParams();
const filters = this.state.filters;
const settings = this.getSettings();
if (filters.itemName) params.set(this.state.itemType === "armor" ? "armorPiece" : "weaponName", filters.itemName);
if (filters.bonus1Id) {
params.set("bonus1", this.getBonusName(filters.bonus1Id));
if (!settings.disableBonusValueFilter) {
if (filters.bonus1Min) params.set("minBonus1Value", filters.bonus1Min);
if (filters.bonus1Max) params.set("maxBonus1Value", filters.bonus1Max);
}
}
if (filters.bonus2Id) {
params.set("bonus2", this.getBonusName(filters.bonus2Id));
if (!settings.disableBonusValueFilter) {
if (filters.bonus2Min) params.set("minBonus2Value", filters.bonus2Min);
if (filters.bonus2Max) params.set("maxBonus2Value", filters.bonus2Max);
}
}
if (!settings.disableQualityFilter) {
if (filters.qualityMin) params.set("minQuality", filters.qualityMin);
if (filters.qualityMax) params.set("maxQuality", filters.qualityMax);
}
params.set("sortField", "price");
params.set("sortDirection", "asc");
params.set("tab", this.state.itemType === "armor" ? "armor" : "weapons");
return params;
},
async searchMarket() {
this.state.marketLoading = true;
this.state.marketError = "";
this.render();
try {
const data = await this.requestJson({ method: "GET", url: `${this.config.marketApi}?${this.buildMarketParams().toString()}` });
this.state.marketResults = data.weapons || data.armor || data.items || [];
this.state.marketTotal = data.total_count || this.state.marketResults.length || 0;
} catch (error) {
this.state.marketError = error.message || "Market search failed";
this.state.marketResults = [];
this.state.marketTotal = 0;
}
this.state.marketLoading = false;
this.render();
},
renderHistoryResults() {
if (this.state.loading) return "<div>Searching history...</div>";
if (this.state.error) return `<div class="wbv-pc-error">${this.escape(this.state.error)}</div>`;
if (!this.state.results.length) return "<div>No matching sales found.</div>";
return this.state.results.map((item) => {
const bonuses = (item.bonus_values || []).map((bonus) => `<span class="wbv-pc-bonus">${bonus.bonus_value != null ? `${this.escape(bonus.bonus_value)}% ` : ""}${this.escape(this.getBonusName(bonus.bonus_id))}</span>`).join("");
return `<div class="wbv-pc-item"><div><strong>${this.escape(item.item_name)}</strong></div><div class="wbv-pc-meta">Quality: ${this.escape(Number(item.stat_quality || 0).toFixed(1))}%${item.stat_damage != null ? ` · DMG: ${this.escape(Number(item.stat_damage).toFixed(1))}` : ""}${item.stat_accuracy != null ? ` · ACC: ${this.escape(Number(item.stat_accuracy).toFixed(1))}` : ""}${item.stat_armor != null ? ` · Armor: ${this.escape(Number(item.stat_armor).toFixed(1))}` : ""}</div>${bonuses ? `<div>${bonuses}</div>` : ""}<div class="wbv-pc-meta">${this.formatPrice(item.price)} · ${this.formatDate(item.timestamp)}</div></div>`;
}).join("");
},
renderMarketResults() {
if (this.state.marketLoading) return "<div>Searching market...</div>";
if (this.state.marketError) return `<div class="wbv-pc-error">${this.escape(this.state.marketError)}</div>`;
if (!this.state.marketResults.length) return "<div>No similar items found on the market.</div>";
return this.state.marketResults.map((item) => {
const entries = item.bonuses ? Object.values(item.bonuses) : [];
const bonuses = entries.map((bonus) => `<span class="wbv-pc-bonus">${bonus.value ? `${this.escape(bonus.value)}% ` : ""}${this.escape(bonus.bonus)}</span>`).join("");
return `<div class="wbv-pc-item"><div><strong>${this.escape(item.itemName)}</strong></div><div class="wbv-pc-meta">Quality: ${this.escape(Number(item.quality || 0).toFixed(1))}%${item.damage != null ? ` · DMG: ${this.escape(Number(item.damage).toFixed(1))}` : ""}${item.accuracy != null ? ` · ACC: ${this.escape(Number(item.accuracy).toFixed(1))}` : ""}${item.rarity ? ` · ${this.escape(item.rarity)}` : ""}</div>${bonuses ? `<div>${bonuses}</div>` : ""}<div class="wbv-pc-meta">${this.formatPrice(item.price)}</div></div>`;
}).join("");
},
getAvatarText(name) {
const text = String(name || "?").trim();
const words = text.split(/\s+/).filter(Boolean);
if (words.length >= 2) return this.escape(`${words[0][0]}${words[1][0]}`.toUpperCase());
return this.escape(text.slice(0, 2).toUpperCase() || "?");
},
renderHistoryResults() {
if (this.state.loading) return '<div class="wbv-pc-loading">Searching history...</div>';
if (this.state.error) return `<div class="wbv-pc-error">${this.escape(this.state.error)}</div>`;
if (!this.state.results.length) return '<div class="wbv-pc-empty">No matching sales found.</div>';
return this.state.results.map((item) => {
const bonuses = (item.bonus_values || []).map((bonus) => `<span class="wbv-pc-bonus">${bonus.bonus_value != null ? `${this.escape(bonus.bonus_value)}% ` : ""}${this.escape(this.getBonusName(bonus.bonus_id))}</span>`).join("");
return `<div class="wbv-pc-item"><div class="wbv-pc-avatar">${this.getAvatarText(item.item_name)}</div><div class="wbv-pc-content"><div class="wbv-pc-line"><span class="wbv-pc-name">${this.escape(item.item_name)}</span><span class="wbv-pc-time">${this.escape(this.formatDate(item.timestamp))}</span></div><div class="wbv-pc-meta">Quality ${this.escape(Number(item.stat_quality || 0).toFixed(1))}%${item.stat_damage != null ? ` | DMG ${this.escape(Number(item.stat_damage).toFixed(1))}` : ""}${item.stat_accuracy != null ? ` | ACC ${this.escape(Number(item.stat_accuracy).toFixed(1))}` : ""}${item.stat_armor != null ? ` | Armor ${this.escape(Number(item.stat_armor).toFixed(1))}` : ""}</div>${bonuses ? `<div class="wbv-pc-badges">${bonuses}</div>` : ""}<div class="wbv-pc-price">${this.formatPrice(item.price)}</div></div></div>`;
}).join("");
},
renderMarketResults() {
if (this.state.marketLoading) return '<div class="wbv-pc-loading">Searching market...</div>';
if (this.state.marketError) return `<div class="wbv-pc-error">${this.escape(this.state.marketError)}</div>`;
if (!this.state.marketResults.length) return '<div class="wbv-pc-empty">No similar items found on the market.</div>';
return this.state.marketResults.map((item) => {
const entries = item.bonuses ? Object.values(item.bonuses) : [];
const bonuses = entries.map((bonus) => `<span class="wbv-pc-bonus">${bonus.value ? `${this.escape(bonus.value)}% ` : ""}${this.escape(bonus.bonus)}</span>`).join("");
const marketMeta = item.playerName ? `Bazaar | ${this.escape(item.playerName)}` : "Item Market";
return `<div class="wbv-pc-item"><div class="wbv-pc-avatar">${this.getAvatarText(item.itemName)}</div><div class="wbv-pc-content"><div class="wbv-pc-line"><span class="wbv-pc-name">${this.escape(item.itemName)}</span><span class="wbv-pc-time">${marketMeta}</span></div><div class="wbv-pc-meta">Quality ${this.escape(Number(item.quality || 0).toFixed(1))}%${item.damage != null ? ` | DMG ${this.escape(Number(item.damage).toFixed(1))}` : ""}${item.accuracy != null ? ` | ACC ${this.escape(Number(item.accuracy).toFixed(1))}` : ""}${item.rarity ? ` | ${this.escape(item.rarity)}` : ""}</div>${bonuses ? `<div class="wbv-pc-badges">${bonuses}</div>` : ""}<div class="wbv-pc-price">${this.formatPrice(item.price)}</div></div></div>`;
}).join("");
},
init() {
this.bonusData.forEach((bonus) => {
this.bonusMap[bonus.id] = bonus.title;
this.bonusMap[bonus.title.toLowerCase()] = bonus.id;
this.bonusMap[bonus.title.toLowerCase().replace(/[\s-]/g, "")] = bonus.id;
});
this.ensureModal();
},
};
const Modules = {
async injectCommon(item, armouryId, updater, opts = {}) {
if (!armouryId || item.find("div.item-detail").length || Util.isLocked(item)) return null;
Util.markPending(item);
Util.updateCacheRecord(armouryId, item, updater);
const itemDetail = await Api.getItemDetails(armouryId);
if (!itemDetail || item.find("div.item-detail").length) {
Util.clearMark(item);
return null;
}
return UI.createItemDetailsNode(itemDetail, opts.isBazaar || false);
},
imarket: {
async updateSearch(item) {
Util.setContext({ inItemList: false });
if (item.find("div.item-detail").length) return;
const detail = item.find("li.item-t");
const itemId = parseInt(detail.attr("itemid"), 10);
if (!itemId || !Util.isSupportedItem(itemId)) return;
const armouryId = detail.attr("data-armoury");
const node = await Modules.injectCommon(item, armouryId, Modules.imarket.updateSearch);
if (!node) return;
let appendTarget;
if (Util.isMobile()) {
appendTarget = item.find("li.cost");
if (!appendTarget.length) return;
node.css({ marginLeft: "30px" });
} else {
appendTarget = item.find("li.item-t span.t-hide");
if (!appendTarget.length) return;
const setPosition = () => node.css({ marginLeft: "60px", position: "absolute", left: `${item.offset().left + item.width() - 60}px`, background: "#F2F2F2", width: "180px" });
setPosition();
window.addEventListener("resize", setPosition);
}
appendTarget.css({ display: "flex", alignItems: "center" });
Util.appendUnique(appendTarget, node, item);
PriceChecker.attachButton(appendTarget, Store.get(armouryId), Util.getItemType(itemId) || "weapon");
},
async updateDirect(item) {
Util.setContext({ inItemList: false });
if (item.find("div.item-detail").length) return;
const link = item.find("a.view-link");
const itemId = parseInt(link.attr("itemid"), 10);
if (!itemId || !Util.isSupportedItem(itemId)) return;
const armouryId = link.attr("data-armoury");
const isBazaar = item.parent().attr("class") === "private-bazaar" && Util.isMobile();
const node = await Modules.injectCommon(item, armouryId, Modules.imarket.updateDirect, { isBazaar });
if (!node) return;
if (Util.isMobile()) {
const appendTarget = item.find("li.cost");
if (!appendTarget.length) return;
node.css({ marginLeft: "3px" });
appendTarget.css({ display: "flex", alignItems: "center" });
Util.appendUnique(appendTarget, node, item);
PriceChecker.attachButton(appendTarget, Store.get(armouryId), Util.getItemType(itemId) || "weapon");
return;
}
const offsetLeft = item.offset().left + item.width();
if (offsetLeft <= 0) return;
const setPosition = () => node.css({ marginLeft: "10px", position: "absolute", left: `${offsetLeft - 5}px`, background: "#FFF", width: "180px", zIndex: 100001, height: `${item.height()}px`, top: `${item.offset().top}px`, fontSize: "12px", boxSizing: "border-box", paddingLeft: "15px" });
setPosition();
window.addEventListener("resize", setPosition);
App.state.floatingNodes.push(node);
$("body").append(node);
Util.markDone(item);
PriceChecker.attachButton(item.find("a.view-link").parent(), Store.get(armouryId), Util.getItemType(itemId) || "weapon");
},
init() {
const root = $("div#item-market-main-wrap")[0] || document.querySelector(".shop-market-page") || document.querySelector("[class*='item-market']") || document.querySelector("div.content-wrapper");
if (!root) return;
const scan = (scope) => {
$(scope).find("ul.items ul.item, ul.items > li > ul.item").each(function () { Modules.imarket.updateDirect($(this)); });
$(scope).find(".shop-market-page .show-item-info, .show-item-info").each(function () { Modules.imarket.updateSearch($(this)); });
};
scan(root);
const warmScan = setInterval(() => {
if (!document.body.contains(root)) {
clearInterval(warmScan);
return;
}
scan(root);
}, 1200);
App.state.intervals.push(warmScan);
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) {
if (node.tagName === "UL" && $(node).hasClass("items")) {
Util.clearFloatingNodes();
$(node).find("ul.item").each(function () { Modules.imarket.updateDirect($(this)); });
} else if ($(node).hasClass("shop-market-page")) {
$(node).find(".show-item-info").each(function () { Modules.imarket.updateSearch($(this)); });
} else {
scan(node);
}
}
});
},
},
item: {
async update(item) {
Util.setContext({ inItemList: true });
if (item.find("div.item-detail").length) return;
const itemId = parseInt(item.attr("data-item"), 10);
const armouryId = item.attr("data-armoryid") || item.attr("data-id");
if (!itemId || !Util.isSupportedItem(itemId)) return;
const node = await Modules.injectCommon(item, armouryId, Modules.item.update);
if (!node) return;
let appendTarget = item.find("span.name-wrap span.name");
if (!appendTarget.length) appendTarget = item.find("li.desc");
if (!appendTarget.length) return;
appendTarget.css({ display: "flex", alignItems: "center" });
Util.appendUnique(appendTarget, node, item);
PriceChecker.attachButton(appendTarget, Store.get(armouryId), Util.getItemType(itemId) || "weapon");
},
init() {
const root = $("div.items-wrap")[0];
if (!root) return;
$(root).find("li[data-armoryid], li[data-id][data-item]").each(function () { Modules.item.update($(this)); });
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) if (node.tagName === "LI" && $(node).attr("data-armoryid")) Modules.item.update($(node));
});
},
},
factionArmoury: {
isMatch() { return location.href.includes("factions.php") && location.href.includes("armoury"); },
findFirstNumber(value) {
if (!value) return null;
const match = String(value).match(/(\d{3,})/);
return match ? match[1] : null;
},
isSupportedRow(item) {
const rowText = item.text().toLowerCase();
return rowText.includes("primary") || rowText.includes("secondary") || rowText.includes("melee") || rowText.includes("armor");
},
getRefs(item) {
const itemSource = item.find("[data-itemid], [data-itemId], [itemid], [data-item], div.img-wrap").first();
const itemId = parseInt(
(itemSource.length
? (itemSource.attr("data-itemid") || itemSource.attr("data-itemId") || itemSource.attr("itemid") || itemSource.attr("data-item"))
: null),
10
);
let armouryId =
item.attr("data-armoryid") ||
item.attr("data-armoryId") ||
item.attr("data-armouryid") ||
item.attr("armoryid") ||
item.attr("armouryid") ||
null;
if (!armouryId) {
item.find("*").each(function () {
if (armouryId) return false;
const el = $(this);
armouryId =
el.attr("data-armoryid") ||
el.attr("data-armoryId") ||
el.attr("data-armouryid") ||
el.attr("armoryid") ||
el.attr("armouryid") ||
el.attr("data-id") ||
el.attr("data-key") ||
el.attr("data-reactid") ||
Modules.factionArmoury.findFirstNumber(el.attr("href")) ||
Modules.factionArmoury.findFirstNumber(el.attr("onclick")) ||
null;
});
}
if (!itemId || !armouryId) return null;
return { itemId, armouryId };
},
async update(item) {
Util.setContext({ inFactionArmoury: true });
if (item.attr("data-wbv-state") === "pending") return;
const refs = Modules.factionArmoury.getRefs(item);
if (!refs) return;
const { itemId, armouryId } = refs;
const supported = itemId ? Util.isSupportedItem(itemId) : Modules.factionArmoury.isSupportedRow(item);
if (!supported || !armouryId) return;
const node = await Modules.injectCommon(item, armouryId, Modules.factionArmoury.update);
if (!node) return;
const itemDetail = Store.get(armouryId);
if (!Util.isMobile()) {
node.css({ marginLeft: "0" });
if (!itemDetail?.bonuses) node.addClass("armory-bg");
Util.placeFloatingNode(item, node);
PriceChecker.attachButton(item, itemDetail, itemDetail?.armor !== undefined && itemDetail?.armor !== null ? "armor" : "weapon");
return;
}
let appendTarget = item.find("td").eq(1);
if (!appendTarget.length) appendTarget = item.find("div.name").first();
if (!appendTarget.length) appendTarget = item.find("td").eq(0);
if (!appendTarget.length) appendTarget = item.find("a").first().parent();
if (!appendTarget.length) {
Util.clearMark(item);
return;
}
appendTarget.css({ display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" });
Util.appendUnique(appendTarget, node, item);
PriceChecker.attachButton(appendTarget, itemDetail, itemDetail?.armor !== undefined && itemDetail?.armor !== null ? "armor" : "weapon");
},
scan(root) { $(root).find("ul.item-list li, table tr, tbody tr, div[class*='row']").each(function () { Modules.factionArmoury.update($(this)); }); },
heal(root) {
$(root).find("ul.item-list li, table tr, tbody tr, div[class*='row']").each(function () {
const row = $(this);
if (Util.isMobile()) {
if (!row.find("div.item-detail").length && row.attr("data-wbv-state") === "done") {
Util.clearMark(row);
}
} else if (row.attr("data-wbv-state") === "done") {
Util.clearMark(row);
}
Modules.factionArmoury.update(row);
});
},
init() {
UI.addStyles();
const attach = (root) => {
if (!root || root.dataset.wbvObserved === "1") return;
root.dataset.wbvObserved = "1";
Modules.factionArmoury.scan(root);
const healInterval = setInterval(() => {
if (!document.body.contains(root)) {
clearInterval(healInterval);
return;
}
Modules.factionArmoury.heal(root);
}, 700);
App.state.intervals.push(healInterval);
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) {
const $node = $(node);
if ($node.is("ul.item-list")) {
$node.find("li").each(function () { Modules.factionArmoury.update($(this)); });
} else if ($node.is("tr")) {
Modules.factionArmoury.update($node);
} else if ($node.is("li")) {
Modules.factionArmoury.update($node);
} else {
const parentRow = $node.closest("tr, li");
if (parentRow.length) {
Modules.factionArmoury.update(parentRow);
}
$node.find("ul.item-list li, table tr, tbody tr, div[class*='row']").each(function () { Modules.factionArmoury.update($(this)); });
}
}
});
};
const tryAttach = () => {
const root = $("div#faction-armoury")[0] || document.querySelector("[id='faction-armoury']") || document.querySelector("div.content-wrapper");
if (root) { attach(root); return true; }
return false;
};
if (tryAttach()) return;
const wait = setInterval(() => { if (tryAttach()) clearInterval(wait); }, 500);
},
},
bigAlGunShop: {
init() {
const root = $("div.sell-list-wrap")[0];
if (!root) return;
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) if (node.tagName === "LI" && $(node).attr("data-id")) Modules.item.update($(node));
});
$("div.sell-list-wrap li[data-item]:not([data-id=''])").each(function () { Modules.item.update($(this)); });
},
},
bazaarManage: {
async update(item) {
Util.setContext({ inItemList: true });
if (item.find("div.item-detail").length) return;
const itemName = Util.safeText(item.find("span.t-overflow"));
const itemId = Data.itemNameToId[itemName];
if (!itemId || !Util.isSupportedItem(itemId)) return;
const reactId = item.attr("data-reactid") || "";
const match = /(\$[\d]+)/.exec(reactId);
if (!match) return;
const armouryId = match[0].replace("$", "");
const node = await Modules.injectCommon(item, armouryId, Modules.bazaarManage.update);
if (!node) return;
const appendTarget = item.find("div.name-wrap");
if (!appendTarget.length) return;
appendTarget.css({ display: "flex", alignItems: "center" });
Util.appendUnique(appendTarget, node, item);
PriceChecker.attachButton(appendTarget, Store.get(armouryId), Util.getItemType(itemId) || "weapon");
},
init(rootSelector) {
const root = $(rootSelector)[0];
if (!root) return;
$(root).find("li.clearfix[data-reactid]").each(function () { Modules.bazaarManage.update($(this)); });
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) if (node.tagName === "LI" && $(node).hasClass("clearfix") && $(node).attr("data-reactid")) Modules.bazaarManage.update($(node));
});
},
},
displayCase: {
async update(item) {
Util.setContext({ inCabinet: true });
if (item.find(".item-detail").length || item.find(".top-bonuses").length) return;
const hover = item.find("div.item-hover");
const itemId = parseInt(hover.attr("itemid"), 10);
if (!itemId || !Util.getItemType(itemId)) return;
const armouryId = hover.attr("armouryid");
if (!armouryId || armouryId === "0") return;
const node = await Modules.injectCommon(item, armouryId, Modules.displayCase.update);
if (!node) return;
node.css({ position: "absolute", left: "0", top: "35px" });
node.addClass("armory-bg");
node.find("div.message").css({ height: "15px", lineHeight: "15px", padding: "0 5px" });
item.append(node);
PriceChecker.attachButton(item, Store.get(armouryId), Util.getItemType(itemId) || "weapon");
},
init() {
UI.addStyles();
const root = $("div.content-wrapper")[0];
if (!root) return;
$(root).find("ul.display-cabinet li").each(function () { Modules.displayCase.update($(this)); });
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) if ($(node).hasClass("display-main-page")) $(node).find("ul.display-cabinet li").each(function () { Modules.displayCase.update($(this)); });
});
},
},
userBazaar: {
handleBonus(title) { const match = /<b>(.*?)<\/b>.*?(\d+)(%)?/.exec(title || ""); return match ? [match[1], `${match[2]}${match[3] || ""}`] : null; },
updateItem(row, itemIdx) {
if (!row.length) return;
const item = App.state.bazaarCache[String(itemIdx)];
if (!item) return;
const target = row.find(`div[class^="imgBar"]`);
if (!target.length || row.find("div.top-bonuses").length) return;
const { damage, accuracy, quality, arm, coverage, bonuses, category, rarity } = item;
if (!Data.filterTypes.has(category)) return;
const top = $(`<div class="top-bonuses"></div>`);
const bottom = $(`<div class="bottom-bonuses"></div>`);
if (bonuses && bonuses[0] && bonuses[0].title) {
for (const bonus of bonuses) {
if (!bonus.title) continue;
const info = Modules.userBazaar.handleBonus(bonus.title);
if (!info) continue;
const box = $(`<div class="bonus-attachment"></div>`);
const inner = $(`<div class="message icon-text border-round" title="${bonus.title}"><i class="${bonus.class}" title="${bonus.title}"></i> ${info[1]}</div>`);
inner.css({ color: UI.getQualityColor(quality, rarity) });
box.append(inner);
top.append(box);
}
} else {
const box = $(`<div class="bonus-attachment"></div>`);
const val = $(`<font class="label-value t-overflow">Q:${quality}%</font>`);
val.css({ color: UI.getQualityColor(quality, rarity) });
box.append(val);
top.append(box);
}
if (arm !== "0") {
const armorBox = $(`<div class="bonus-attachment"></div>`);
armorBox.append($(`<i class="bonus-attachment-item-defence-bonus"></i>`), $(`<span class="label-value t-overflow">${arm}</span>`));
bottom.append(armorBox);
const coverageBox = $(`<div class="bonus-attachment"></div>`);
let coverageData = {};
try { coverageData = JSON.parse(coverage); } catch {}
let coverageHtml = "";
for (const bodyPart in coverageData) coverageHtml += `<b>${bodyPart}:</b> ${coverageData[bodyPart]}%<br>`;
coverageBox.append($(`<i class="bonus-attachment-item-coverage-bonus"></i>`), $(`<span class="label-value t-overflow" title='${coverageHtml}'>${coverageData["Full Body Coverage"] || ""}%</span>`));
bottom.append(coverageBox);
} else {
const damageBox = $(`<div class="bonus-attachment"></div>`);
damageBox.append($(`<i class="bonus-attachment-item-damage-bonus"></i>`), $(`<span class="label-value t-overflow">${damage}</span>`));
const accBox = $(`<div class="bonus-attachment"></div>`);
accBox.append($(`<i class="bonus-attachment-item-accuracy-bonus"></i>`), $(`<span class="label-value t-overflow">${accuracy}</span>`));
bottom.append(damageBox, accBox);
}
target.append(top, bottom);
row.attr("data-idx", itemIdx);
},
updateVisibleRows() {
if (App.state.bazaarUpdating) return;
App.state.bazaarUpdating = true;
try {
const children = $("[class^='rowItems___']").children();
if (!children.length) return;
const firstUpdated = children.filter("[data-idx]").eq(0);
const lastUpdated = children.filter("[data-idx]").last();
let first = firstUpdated.attr("data-idx");
let last = lastUpdated.attr("data-idx");
let current;
let currentIdx = 0;
if (!first && !last) {
const countLimit = 6;
while (true) {
let mismatch = false;
for (let i = 0; i < countLimit; i++) {
const cached = App.state.bazaarCache[String(currentIdx + i)];
current = children.eq(i);
if (!current.length || !cached) break;
const currentPrice = current.find("p[class*=price]").text().split(" ")[0].replaceAll(",", "").replace("$", "");
const currentName = current.find("p[class*=name]").text().trim();
if (String(currentPrice) !== String(cached.price) || String(currentName) !== String(cached.name)) { mismatch = true; break; }
}
if (!mismatch) break;
currentIdx += 1;
if (currentIdx > 50) break;
}
children.each(function () { Modules.userBazaar.updateItem($(this), currentIdx++); });
} else {
if (first) {
currentIdx = children.index(firstUpdated);
first = parseInt(first, 10);
while (first && --first >= 0 && --currentIdx >= 0) Modules.userBazaar.updateItem(children.eq(currentIdx), first);
}
if (last) {
currentIdx = children.index(lastUpdated);
last = parseInt(last, 10);
while (last && ++last < Object.keys(App.state.bazaarCache).length && ++currentIdx < children.length) Modules.userBazaar.updateItem(children.eq(currentIdx), last);
}
}
} finally { App.state.bazaarUpdating = false; }
},
handleResponse(params, resp) {
if (!resp?.list) return;
const start = parseInt(params.get("start") || "0", 10);
const list = resp.list;
for (let i = 0; i < list.length; i++) App.state.bazaarCache[String(start + i)] = list[i];
const interval = setInterval(() => {
if ($(`div[class^="imgBar"]`).length) {
clearInterval(interval);
setTimeout(() => Modules.userBazaar.updateVisibleRows(), 0);
}
}, 300);
},
init() {
UI.addStyles();
Page.interceptFetch();
const waitForGrid = setInterval(() => {
if ($(`div[class^="imgBar"]`).length) {
clearInterval(waitForGrid);
const root = $("div.ReactVirtualized__Grid__innerScrollContainer")[0];
if (!root) return;
Page.observe(root, (mutations) => {
for (const mut of mutations) for (const node of mut.addedNodes) if (node.classList && node.classList.contains("row___LkdFI")) Modules.userBazaar.updateVisibleRows();
});
}
}, 300);
},
},
};
const Router = {
init() {
const href = location.href;
const isItemMarket = href.includes("imarket.php") || href.includes("page.php?sid=ItemMarket");
let needXML = false;
if (href === "https://www.torn.com/bazaar.php#/add") { needXML = true; Modules.bazaarManage.init("div#bazaarRoot"); }
else if (href === "https://www.torn.com/imarket.php#/p=addl") { needXML = true; Modules.bazaarManage.init("div#item-market-main-wrap"); }
else if (isItemMarket) { needXML = true; Modules.imarket.init(); }
else if (href.includes("item.php")) { needXML = true; Modules.item.init(); }
else if (Modules.factionArmoury.isMatch()) { needXML = true; Modules.factionArmoury.init(); }
else if (href.includes("bigalgunshop.php")) { needXML = true; Modules.bigAlGunShop.init(); }
else if (href.includes("displaycase.php")) { needXML = true; Modules.displayCase.init(); }
else if (href.startsWith("https://www.torn.com/bazaar.php?userId=") && Config.value.ENABLE_BAZAAR_UPDATE) { needXML = true; Modules.userBazaar.init(); }
if (needXML) Page.interceptXML();
},
};
async function init() {
Config.load();
await Store.init();
PriceChecker.init();
Util.onHashChange();
Router.init();
console.log("%cWeapon%c Bonus %cView starts.", "font-size:30px;font-weight:600;color:#00A9F9;", "font-size:30px;font-weight:600;color:#000;", "font-size:30px;");
}
init();
})();