All-in-one Torn assistant — Gym coach, Crime helper, Bazaar tracker, Shop analyzer, Item Locker & Break-Even calculator.
// ==UserScript==
// @name Torn Toolbox
// @namespace torn.toolbox.aio
// @version 1.0.0
// @description All-in-one Torn assistant — Gym coach, Crime helper, Bazaar tracker, Shop analyzer, Item Locker & Break-Even calculator.
// @author RnVjayBPZmYh [4233331]
// @match https://www.torn.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM.xmlHttpRequest
// @grant GM_xmlhttpRequest
// @connect weav3r.dev
// @run-at document-end
// @license MIT
// @homepageURL https://www.torn.com/profiles.php?XID=4233331
// ==/UserScript==
(function() {
'use strict';
/* ──────────────────────────────────────────────
* CORE — Storage, API, language helpers
* ────────────────────────────────────────────── */
const AIO = {
get: function(key, fallback = null) { try { let v = GM_getValue("TornAIO_" + key); return v !== undefined && v !== null ? v : fallback; } catch(e) { return fallback; } },
set: function(key, value) { try { GM_setValue("TornAIO_" + key, value); } catch(e) {} },
getApiKey: function() { return this.get('apiKey', ''); },
getLang: function() { return this.get('lang', 'tr'); }
};
/* ──────────────────────────────────────────────
* LANG — Türkçe / English dil paketleri
* ────────────────────────────────────────────── */
const LANG = {
tr: {
appTitle: "Torn Toolbox",
apiKey: "API Anahtarı (Limited Access):",
langSel: "Dil / Language:",
save: "Kaydet", close: "Kapat", test: "Test Et",
noApiWarning: "🚨 Toolbox özellikleri için API gerekli. (Sol alttaki ⚙ butonuna basın)",
aboutTitle: "Hakkında",
aboutDev: "Geliştirici:",
aboutProfile: "Profil",
aboutDonate: "Bağış Yap",
aboutDonateDesc: "Beğendiyseniz küçük bir bağış yapabilirsiniz 🙏",
aboutVersion: "Versiyon:",
gym: { close: "✅ Statlarınız dengeli", train: "geliştirilmeli", prog: "🏋️ Program:", save: "Kaydet", err100: "Hata: Toplam 100 olmalı!", missing: "eksik", cName: "Custom (Özel)" },
crimes: { req: "Alet Gerekli:", buy: "Satın Al", guide: "Suç Rehberi" },
bazaar: {
loading: "Bazaar verileri yükleniyor...",
header: "Bazaar Listesi:",
marketVal: "Market Değeri:",
sortBy: "Sırala:",
price: "Fiyat",
quantity: "Miktar",
updated: "Son Güncelleme",
asc: "Artan",
desc: "Azalan",
player: "Oyuncu:",
qty: "Miktar:",
store: "Dükkana Git",
ago: "önce",
seconds: "sn",
minutes: "dk",
hours: "sa",
days: "gün",
noData: "Bu eşya için bazaar verisi bulunamadı.",
apiErr: "API Hatası – Daha sonra tekrar deneyin.",
showing: "Gösterilen:",
of: "/",
bazaars: "bazaar",
items: "ürün",
total: "toplam",
poweredBy: "Weav3r Veritabanı"
},
locker: { qtyPlaceholder: "adet", lockedFull: "🔒 Kilitli", lockedPartial: "🔒 kilitli" },
breakeven: { title: "💰 Kâr Hesap", cost: "Maliyet:", anon: "Anonim (+%10)", fee: "Vergi:", minSell: "Min. Satış:", formula: "Formül:" },
shop: { profit: "Kâr", loss: "Zarar", loading: "Fiyatlar yükleniyor...", noData: "Fiyat verisi yok", cheapest: "En ucuz:", vs: "vs NPC:" }
},
en: {
appTitle: "Torn Toolbox",
apiKey: "API Key (Limited Access):",
langSel: "Language / Dil:",
save: "Save", close: "Close", test: "Test Key",
noApiWarning: "🚨 API key required for Toolbox. (Click ⚙ in bottom left)",
aboutTitle: "About",
aboutDev: "Developer:",
aboutProfile: "Profile",
aboutDonate: "Donate",
aboutDonateDesc: "If you like this script, consider a small donation 🙏",
aboutVersion: "Version:",
gym: { close: "✅ Stats balanced", train: "train required", prog: "🏋️ Program:", save: "Save", err100: "Error: Must total 100!", missing: "missing", cName: "Custom" },
crimes: { req: "Tool Required:", buy: "Buy", guide: "Crime Guide" },
bazaar: {
loading: "Loading bazaar listings...",
header: "Bazaar Listings:",
marketVal: "Market Value:",
sortBy: "Sort by:",
price: "Price",
quantity: "Quantity",
updated: "Last Updated",
asc: "Asc",
desc: "Desc",
player: "Player:",
qty: "Qty:",
store: "Go to Store",
ago: "ago",
seconds: "s",
minutes: "m",
hours: "h",
days: "d",
noData: "No bazaar listings found for this item.",
apiErr: "API Error – Please try again later.",
showing: "Showing:",
of: "of",
bazaars: "bazaars",
items: "items",
total: "total",
poweredBy: "Weav3r Database"
},
locker: { qtyPlaceholder: "qty", lockedFull: "🔒 Locked", lockedPartial: "🔒 locked" },
breakeven: { title: "💰 Break-Even", cost: "Cost:", anon: "Anonymous (+10%)", fee: "Fee:", minSell: "Min. Sell:", formula: "Formula:" },
shop: { profit: "Profit", loss: "Loss", loading: "Loading prices...", noData: "No price data", cheapest: "Cheapest:", vs: "vs NPC:" }
}
};
AIO.L = function() { return LANG[this.getLang()]; };
/* ──────────────────────────────────────────────
* API WARNING — API anahtarı yoksa uyarı göster
* ────────────────────────────────────────────── */
AIO.showWarning = function(el) {
if(!el || el.querySelector('.aio-warn')) return;
const d = document.createElement('div');
d.className = 'aio-warn';
d.style.cssText = 'background:linear-gradient(135deg,#8b0000,#4a0000);color:#fff;padding:12px 16px;font-weight:bold;text-align:center;margin:10px 0;border-radius:8px;border:1px solid #ff4444;font-size:13px;';
d.innerText = this.L().noApiWarning;
el.prepend(d);
};
/* ──────────────────────────────────────────────
* MARKET CACHE — Torn API item cache
* ────────────────────────────────────────────── */
AIO.getMarket = async function() {
const cache = this.get('marketCache');
if (cache && Date.now() - cache.time < 3600000) return cache.data;
const k = this.getApiKey();
if(!k) return null;
try {
const r = await fetch(`https://api.torn.com/torn/?key=${k}&selections=items&comment=TornToolbox`);
const d = await r.json();
if(d && d.items) {
this.set('marketCache', { data: d.items, time: Date.now() });
return d.items;
}
} catch(e){}
return null;
};
/* ──────────────────────────────────────────────
* UI — FAB butonu + Ayarlar modali + Bağış/Profil
* ────────────────────────────────────────────── */
AIO.initUI = function() {
if (document.getElementById('aio-fab')) return;
GM_addStyle(`
#aio-fab{position:fixed;bottom:20px;left:20px;width:46px;height:46px;border-radius:50%;background:linear-gradient(135deg,#e53935,#b71c1c);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:999999;box-shadow:0 2px 12px rgba(229,57,53,0.4);font-size:22px;border:2px solid rgba(255,255,255,0.3);transition:all 0.25s ease;}
#aio-fab:hover{transform:scale(1.12) rotate(30deg);box-shadow:0 4px 20px rgba(229,57,53,0.6);}
#aio-modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.75);backdrop-filter:blur(4px);z-index:9999999;display:flex;justify-content:center;align-items:center;font-family:'Segoe UI',Arial,sans-serif;animation:aio-fade-in 0.2s ease;}
@keyframes aio-fade-in{from{opacity:0}to{opacity:1}}
@keyframes aio-slide-up{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}
.aio-modal-box{background:linear-gradient(180deg,#1e1e2e,#161625);color:#e0e0e0;padding:0;border-radius:14px;width:380px;max-width:92vw;border:1px solid #333;box-shadow:0 8px 40px rgba(0,0,0,0.6);animation:aio-slide-up 0.25s ease;overflow:hidden;}
.aio-modal-header{padding:18px 22px 14px;border-bottom:1px solid #2a2a3e;background:linear-gradient(135deg,#252540,#1a1a2e);}
.aio-modal-header h2{margin:0;font-size:18px;font-weight:700;color:#fff;display:flex;align-items:center;gap:8px;}
.aio-modal-header h2 .aio-ver{font-size:11px;font-weight:400;color:#666;margin-left:auto;}
.aio-modal-body{padding:18px 22px;}
.aio-modal-body label{display:block;font-size:11px;font-weight:600;color:#888;margin-bottom:5px;text-transform:uppercase;letter-spacing:0.5px;}
.aio-modal-body input,.aio-modal-body select{width:100%;padding:9px 12px;background:#111;color:#fff;border:1px solid #333;border-radius:6px;font-size:13px;box-sizing:border-box;transition:border-color 0.2s;}
.aio-modal-body input:focus,.aio-modal-body select:focus{outline:none;border-color:#e53935;}
.aio-modal-body .aio-field{margin-bottom:16px;}
.aio-modal-footer{padding:14px 22px;border-top:1px solid #2a2a3e;display:flex;gap:8px;justify-content:flex-end;}
.aio-modal-footer button{padding:8px 16px;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:all 0.2s;}
.aio-btn-test{background:#2e7d32;color:#fff;}.aio-btn-test:hover{background:#388e3c;}
.aio-btn-save{background:#1565c0;color:#fff;}.aio-btn-save:hover{background:#1976d2;}
.aio-btn-close{background:#333;color:#aaa;}.aio-btn-close:hover{background:#444;color:#fff;}
.aio-about-section{margin-top:4px;padding:14px 16px;background:#111;border-radius:8px;border:1px solid #2a2a3e;}
.aio-about-section .aio-about-row{display:flex;justify-content:space-between;align-items:center;font-size:12px;margin-bottom:6px;color:#888;}
.aio-about-section .aio-about-row:last-child{margin-bottom:0;}
.aio-about-section a{color:#e53935;text-decoration:none;font-weight:600;transition:color 0.2s;}
.aio-about-section a:hover{color:#ff6659;}
.aio-donate-btn{display:inline-block;padding:6px 14px;background:linear-gradient(135deg,#ff9800,#f57c00);color:#fff;border-radius:5px;font-size:11px;font-weight:700;text-decoration:none!important;transition:all 0.2s;border:none;}
.aio-donate-btn:hover{transform:translateY(-1px);box-shadow:0 2px 8px rgba(255,152,0,0.4);}
`);
const btn = document.createElement('div'); btn.id = 'aio-fab'; btn.innerText = '⚙';
btn.onclick = () => {
if(document.getElementById('aio-modal-overlay')) return;
const l = AIO.L();
const mod = document.createElement('div');
mod.id = 'aio-modal-overlay';
mod.innerHTML = `
<div class="aio-modal-box">
<div class="aio-modal-header">
<h2>🔧 ${l.appTitle} <span class="aio-ver">v1.0.0</span></h2>
</div>
<div class="aio-modal-body">
<div class="aio-field">
<label>${l.apiKey}</label>
<input id="aio-api-inp" type="password" value="${AIO.getApiKey()}" spellcheck="false" autocomplete="off">
<div id="aio-stat" style="font-size:11px;margin-top:5px;font-weight:600;min-height:16px;"></div>
</div>
<div class="aio-field">
<label>${l.langSel}</label>
<select id="aio-lang-inp">
<option value="tr" ${AIO.getLang()==='tr'?'selected':''}>Türkçe</option>
<option value="en" ${AIO.getLang()==='en'?'selected':''}>English</option>
</select>
</div>
<div class="aio-about-section">
<div class="aio-about-row"><span>${l.aboutDev}</span><a href="https://www.torn.com/profiles.php?XID=4233331" target="_blank">RnVjayBPZmYh [4233331] ↗</a></div>
<div class="aio-about-row"><span>${l.aboutVersion}</span><span style="color:#e0e0e0;">1.0.0</span></div>
<div class="aio-about-row" style="margin-top:8px;flex-direction:column;gap:6px;align-items:flex-start;">
<span style="font-size:11px;color:#666;">${l.aboutDonateDesc}</span>
<a class="aio-donate-btn" href="https://www.torn.com/trade.php#step=start&userID=4233331" target="_blank">💸 ${l.aboutDonate}</a>
</div>
</div>
</div>
<div class="aio-modal-footer">
<button id="aio-test" class="aio-btn-test">${l.test}</button>
<button id="aio-save" class="aio-btn-save">${l.save}</button>
<button id="aio-close" class="aio-btn-close">${l.close}</button>
</div>
</div>
`;
document.body.appendChild(mod);
mod.addEventListener('click', (e) => { if(e.target === mod) mod.remove(); });
document.getElementById('aio-close').onclick = () => mod.remove();
document.getElementById('aio-test').onclick = async () => {
const s = document.getElementById('aio-stat');
s.innerText = 'Testing...'; s.style.color='#888';
try {
const k = document.getElementById('aio-api-inp').value;
const r = await fetch('https://api.torn.com/user/?selections=basic&key='+k);
const d = await r.json();
if(d.error) { s.innerText = '✘ Error: '+d.error.error; s.style.color='#ef5350'; }
else { s.innerText = '✔ OK: '+d.name+' ['+d.player_id+']'; s.style.color='#66bb6a'; }
} catch(e) { s.innerText='✘ Connection failed.'; s.style.color='#ef5350'; }
};
document.getElementById('aio-save').onclick = () => {
AIO.set('apiKey', document.getElementById('aio-api-inp').value);
AIO.set('lang', document.getElementById('aio-lang-inp').value);
try{ GM_setValue('torn_api_key', document.getElementById('aio-api-inp').value); }catch(e){}
location.reload();
};
};
document.body.appendChild(btn);
};
const MODULES = {};
/* ──────────────────────────────────────────────
* MODULE: Gym — Antrenman dengeleme ve yönlendirme
* Statlarınızı hedef programa göre karşılaştırır,
* eksik olanları renk koduyla vurgular ve ilgili
* antrenman butonlarına glow efekti ekler.
* ────────────────────────────────────────────── */
MODULES.Gym = {
PROGRAMS: [
{ name: 'Dengeli 25/25/25/25', str: 25, def: 25, spd: 25, dex: 25 },
{ name: 'Güç Odaklı 40/20/20/20', str: 40, def: 20, spd: 20, dex: 20 },
{ name: 'Savunma Odaklı 20/40/20/20', str: 20, def: 40, spd: 20, dex: 20 },
{ name: 'Hız Odaklı 20/20/40/20', str: 20, def: 20, spd: 40, dex: 20 },
{ name: 'Çeviklik Odaklı 20/20/20/40', str: 20, def: 20, spd: 20, dex: 40 },
{ name: 'My custom 35/10/40/15', str: 35, def: 10, spd: 40, dex: 15 },
{ name: 'Custom (Özel)', isCustom: true }
],
run: function() {
const root = document.getElementById('gymroot');
if(!root) return;
if(!AIO.getApiKey()) { AIO.showWarning(root); return; }
if(document.getElementById('aio-gym')) return;
const L = AIO.L().gym;
const d = document.createElement('div');
d.id = 'aio-gym';
d.style.cssText = 'margin:10px 0;padding:15px;background:linear-gradient(135deg,#1a1a2e,#16213e);color:#eee;border:1px solid #0f3460;border-radius:10px;';
const cP = AIO.get('gym_prog', 'Dengeli 25/25/25/25');
const cust = AIO.get('gym_custom', {str:25, def:25, spd:25, dex:25});
let sl = `<select id="aio-gym-sel" style="background:#111;color:#fff;padding:6px 10px;border:1px solid #333;border-radius:6px;margin-left:10px;font-size:12px;">`;
this.PROGRAMS.forEach(p => {
const n = p.isCustom ? L.cName : p.name;
sl += `<option value="${p.name}" ${p.name===cP?'selected':''}>${n}</option>`;
});
sl += `</select>`;
let customHtml = '';
if(this.PROGRAMS.find(p=>p.name===cP)?.isCustom) {
customHtml = `
<div id="aio-gym-cust-grp" style="margin-top:10px;display:flex;gap:10px;align-items:center;background:#111;padding:10px;border-radius:8px;border:1px solid #333;">
<span style="font-size:12px;">STR: <input type="number" id="aio-c-str" value="${cust.str}" style="width:42px;background:#1a1a2e;color:#fff;border:1px solid #444;border-radius:4px;padding:3px;text-align:center;"></span>
<span style="font-size:12px;">DEF: <input type="number" id="aio-c-def" value="${cust.def}" style="width:42px;background:#1a1a2e;color:#fff;border:1px solid #444;border-radius:4px;padding:3px;text-align:center;"></span>
<span style="font-size:12px;">SPD: <input type="number" id="aio-c-spd" value="${cust.spd}" style="width:42px;background:#1a1a2e;color:#fff;border:1px solid #444;border-radius:4px;padding:3px;text-align:center;"></span>
<span style="font-size:12px;">DEX: <input type="number" id="aio-c-dex" value="${cust.dex}" style="width:42px;background:#1a1a2e;color:#fff;border:1px solid #444;border-radius:4px;padding:3px;text-align:center;"></span>
<button id="aio-c-save" style="background:#2e7d32;color:#fff;border:none;padding:5px 12px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:600;">${L.save}</button>
<span id="aio-c-err" style="color:#ef5350;font-size:11px;font-weight:bold;margin-left:10px;"></span>
</div>
`;
}
d.innerHTML = `<div style="font-weight:bold;margin-bottom:10px;font-size:13px;">${L.prog} ${sl}</div>${customHtml}<div id="aio-gym-res" style="margin-top:10px;"></div>`;
root.prepend(d);
document.getElementById('aio-gym-sel').onchange = (e) => {
AIO.set('gym_prog', e.target.value);
setTimeout(() => location.reload(), 100);
};
const btnSave = document.getElementById('aio-c-save');
if(btnSave) {
btnSave.onclick = () => {
const str = parseFloat(document.getElementById('aio-c-str').value)||0;
const def = parseFloat(document.getElementById('aio-c-def').value)||0;
const spd = parseFloat(document.getElementById('aio-c-spd').value)||0;
const dex = parseFloat(document.getElementById('aio-c-dex').value)||0;
if(str+def+spd+dex !== 100) {
document.getElementById('aio-c-err').innerText = L.err100;
return;
}
AIO.set('gym_custom', {str, def, spd, dex});
setTimeout(() => location.reload(), 100);
};
}
const val = (c) => { const e=document.querySelector(`li[class*="${c}"] span[class*="propertyValue"]`); return e?parseFloat(e.innerText.replace(/,/g,'')):0; };
const u = { str:val('strength'), def:val('defense'), spd:val('speed'), dex:val('dexterity') };
const tot = u.str+u.def+u.spd+u.dex;
if(tot>0) {
const isCustomAct = this.PROGRAMS.find(p=>p.name===cP)?.isCustom;
const wp = isCustomAct ? cust : this.PROGRAMS.find(p=>p.name===cP);
const wt = wp.str+wp.def+wp.spd+wp.dex;
const dff = [
{n:'STR', c:'strength', d: (wp.str/wt*100) - (u.str/tot*100)},
{n:'DEF', c:'defense', d: (wp.def/wt*100) - (u.def/tot*100)},
{n:'SPD', c:'speed', d: (wp.spd/wt*100) - (u.spd/tot*100)},
{n:'DEX', c:'dexterity', d: (wp.dex/wt*100) - (u.dex/tot*100)}
].filter(x=>x.d>0.5).sort((a,b)=>b.d-a.d);
document.querySelectorAll('button[aria-label^="Train"]').forEach(b=>{b.style.boxShadow='';});
if(dff.length===0) {
document.getElementById('aio-gym-res').innerHTML = `<span style="color:#66bb6a;font-weight:600;">${L.close}</span>`;
} else {
let h='';
dff.forEach(x => {
const cl = x.d>5?'#ef5350':'#ff9800';
h += `<div style="color:${cl};font-weight:600;font-size:13px;margin-bottom:4px;">👉 ${x.n} ${L.train} (%${x.d.toFixed(1)} ${L.missing})</div>`;
document.querySelectorAll('button[aria-label^="Train"]').forEach(b=>{
if(b.getAttribute('aria-label').toLowerCase().includes(x.c)) {
b.style.boxShadow=`0 0 12px ${cl}`;
b.style.border=`2px solid ${cl}`;
}
});
});
document.getElementById('aio-gym-res').innerHTML = h;
}
}
document.querySelectorAll('button[aria-label^="Train"]').forEach(b => {
if(b.dataset.aioHooked) return;
b.dataset.aioHooked = 'true';
b.addEventListener('click', () => {
setTimeout(() => location.reload(), 1500);
});
});
}
};
/* ──────────────────────────────────────────────
* MODULE: Crimes — Suç sayfası yardımcısı
* Hangi suçun hangi aleti gerektirdiğini gösterir,
* markette doğrudan satın alma linki ve wiki
* rehber bağlantısı sunar.
* ────────────────────────────────────────────── */
MODULES.Crimes = {
DB: {
'searchforcash': { n: 'Glasses', id: 564, g:'https://wiki.torn.com/wiki/Search_For_Cash' },
'bootlegging': { n: 'High-Speed Drive', id: 565, g:'https://wiki.torn.com/wiki/Bootlegging' },
'graffiti': { n: 'Paint Mask', id: 979, g:'https://wiki.torn.com/wiki/Graffiti' },
'shoplifting': { n: 'Mountain Bike', id: 566, g:'https://wiki.torn.com/wiki/Shoplifting' },
'pickpocketing': { n: 'Cut-Throat Razor', id: 567, g:'https://wiki.torn.com/wiki/Pickpocketing' },
'cardskimming': { n: 'Duct Tape', id: 578, g:'https://wiki.torn.com/wiki/Card_Skimming' },
'burglary': { n: 'Flashlight', id: 1351, g:'https://wiki.torn.com/wiki/Burglary' },
'hustling': { n: 'Megaphone', id: 1353, g:'https://wiki.torn.com/wiki/Hustling' },
'disposal': { n: 'Latex Gloves', id: 633, g:'https://wiki.torn.com/wiki/Disposal' },
'cracking': { n: 'Office Chair', id: 1354, g:'https://wiki.torn.com/wiki/Cracking' },
'forgery': { n: 'Magnifying Glass', id: 1346, g:'https://wiki.torn.com/wiki/Forgery' },
'scamming': { n: 'Ergonomic Keyboard', id: 571, g:'https://wiki.torn.com/wiki/Scamming' },
'arson': { n: 'Windproof Lighter', id: 544, g:'https://wiki.torn.com/wiki/Arson' }
},
run: function() {
const root = document.querySelector('.crimes-app [class*="title__"]');
if(!root) return;
const h = location.hash.replace('#/', '').split('?')[0];
const c = this.DB[h];
let d = document.getElementById('aio-cr');
if(!c) { if(d) d.remove(); return; }
const L = AIO.L().crimes;
if(!d) {
d = document.createElement('div');
d.id = 'aio-cr';
d.style.cssText = 'background:linear-gradient(135deg,#1a1a2e,#16213e);padding:12px 16px;border-radius:8px;border:1px solid #0f3460;margin:10px 0;display:flex;justify-content:space-between;align-items:center;';
root.parentNode.insertBefore(d, root.nextSibling);
}
d.innerHTML = `
<div style="font-size:12px;color:#eee;">
<b>${L.req}</b>
<a href="https://www.torn.com/page.php?sid=ItemMarket#/market/view=search&itemID=${c.id}" target="_blank" style="color:#42a5f5;font-weight:bold;text-decoration:none;transition:color 0.2s;">🛍️ ${c.n} ${L.buy} (ID: ${c.id})</a>
</div>
<a href="${c.g}" target="_blank" style="font-size:12px;color:#ff9800;font-weight:bold;text-decoration:none;transition:color 0.2s;">📚 ${L.guide}</a>
`;
}
};
/* ──────────────────────────────────────────────
* CSS — Bazaar kart sistemi stilleri
* ────────────────────────────────────────────── */
GM_addStyle(`
.aio-bz-container{font-size:13px;border-radius:8px;margin:8px 0;padding:12px;display:flex;flex-direction:column;gap:8px;background:linear-gradient(180deg,#1a1a1a,#111);color:#ccc;border:1px solid #333;box-sizing:border-box;width:100%;overflow:hidden;font-family:'Segoe UI',Arial,sans-serif;}
.aio-bz-header{font-size:15px;font-weight:bold;color:#fff;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;padding-bottom:6px;border-bottom:1px solid #2a2a2a;}
.aio-bz-header .aio-bz-mv{font-size:12px;font-weight:normal;color:#888;}
.aio-bz-controls{display:flex;align-items:center;gap:6px;font-size:12px;padding:6px 10px;background:#0a0a0a;border-radius:6px;border:1px solid #2a2a2a;flex-wrap:wrap;}
.aio-bz-controls select,.aio-bz-controls button{padding:4px 10px;border:1px solid #333;border-radius:4px;background:#1a1a1a;color:#fff;cursor:pointer;font-size:12px;transition:border-color 0.2s;}
.aio-bz-controls select:focus,.aio-bz-controls button:focus{outline:none;border-color:#e53935;}
.aio-bz-scroll-wrap{position:relative;display:flex;align-items:stretch;width:100%;}
.aio-bz-scroll{flex:1;overflow-x:auto;overflow-y:hidden;height:140px;white-space:nowrap;padding:4px 0;border-radius:6px;border:1px solid #2a2a2a;position:relative;}
.aio-bz-scroll::-webkit-scrollbar{height:6px;}
.aio-bz-scroll::-webkit-scrollbar-track{background:#111;border-radius:3px;}
.aio-bz-scroll::-webkit-scrollbar-thumb{background:#444;border-radius:3px;}
.aio-bz-scroll::-webkit-scrollbar-thumb:hover{background:#666;}
.aio-bz-arrow{display:flex;align-items:center;justify-content:center;width:24px;flex-shrink:0;cursor:pointer;background:rgba(255,255,255,0.03);border:1px solid #2a2a2a;border-radius:4px;opacity:0.5;transition:all 0.2s;color:#888;font-size:20px;padding:0;margin:0 2px;}
.aio-bz-arrow:hover{opacity:1;background:rgba(255,255,255,0.08);color:#fff;}
.aio-bz-track{position:relative;height:100%;display:flex;align-items:center;}
.aio-bz-card{position:absolute;width:180px;display:flex;flex-direction:column;justify-content:space-between;border-radius:8px;padding:10px;font-size:12px;box-sizing:border-box;background:#151515;color:#fff;border:1px solid #333;top:50%;transform:translateY(-50%);transition:left 0.4s ease,opacity 0.4s ease;height:120px;}
.aio-bz-card a{color:#42a5f5;text-decoration:underline;font-weight:bold;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;max-width:160px;}
.aio-bz-card a:hover{color:#90caf9;}
.aio-bz-card .aio-bz-price{color:#66bb6a;font-weight:bold;font-size:16px;margin-bottom:2px;}
.aio-bz-card .aio-bz-meta{color:#888;font-size:11px;margin-bottom:2px;}
.aio-bz-card .aio-bz-time{color:#555;font-size:10px;text-align:right;margin-top:auto;}
.aio-bz-card .aio-bz-visit{display:block;text-align:center;background:linear-gradient(135deg,#1565c0,#0d47a1);color:#fff !important;padding:5px 10px;border-radius:5px;text-decoration:none !important;font-size:11px;font-weight:bold;margin-top:4px;transition:all 0.2s;}
.aio-bz-card .aio-bz-visit:hover{box-shadow:0 2px 8px rgba(21,101,192,0.4);}
.aio-bz-footer{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#555;margin-top:4px;padding-top:6px;border-top:1px solid #222;}
.aio-bz-msg{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center;width:100%;height:100px;color:#666;font-size:13px;}
`);
/* ──────────────────────────────────────────────
* CSS — Item Locker + Break-Even stilleri
* ────────────────────────────────────────────── */
GM_addStyle(`
.aio-lock-wrap{display:inline-flex;align-items:center;gap:3px;margin-left:6px;vertical-align:middle;}
.aio-lock-qty{width:40px;padding:2px 4px;background:#1a1a2e;color:#fff;border:1px solid #333;border-radius:4px;font-size:11px;text-align:center;transition:border-color 0.2s;}
.aio-lock-qty:focus{outline:none;border-color:#e53935;}
.aio-lock-btn{cursor:pointer;font-size:15px;padding:2px 4px;user-select:none;border-radius:4px;transition:all 0.2s;}
.aio-lock-btn:hover{background:rgba(255,255,255,0.08);}
.aio-lock-status{font-size:10px;color:#ef5350;font-weight:bold;margin-left:2px;}
.aio-row-full-lock [class*="amountInputWrapper"]{pointer-events:none!important;opacity:0.3!important;}
.aio-row-full-lock [class*="priceInputWrapper"]{pointer-events:none!important;opacity:0.3!important;}
#aio-be{position:fixed;bottom:80px;right:20px;width:240px;background:linear-gradient(180deg,#1a1a2e,#121225);color:#eee;border:1px solid #0f3460;border-radius:12px;z-index:999998;font-family:'Segoe UI',Arial,sans-serif;font-size:13px;box-shadow:0 4px 24px rgba(0,0,0,0.5);}
#aio-be.minimized{width:auto;}
#aio-be-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;cursor:move;border-bottom:1px solid #0f3460;border-radius:12px 12px 0 0;background:linear-gradient(135deg,#0f3460,#1a1a2e);user-select:none;}
#aio-be.minimized #aio-be-header{border-radius:12px;border-bottom:none;}
#aio-be-header span{font-weight:bold;font-size:13px;}
#aio-be-toggle{cursor:pointer;background:none;border:1px solid #333;color:#888;border-radius:4px;padding:2px 8px;font-size:11px;transition:all 0.2s;}
#aio-be-toggle:hover{border-color:#e53935;color:#fff;}
#aio-be-body{padding:12px;}
#aio-be.minimized #aio-be-body{display:none;}
.aio-be-row{margin-bottom:8px;}
.aio-be-row label{display:block;font-size:11px;color:#888;margin-bottom:3px;}
.aio-be-row input[type="text"]{width:100%;padding:6px 8px;background:#0a0a18;color:#fff;border:1px solid #2a2a3e;border-radius:5px;font-size:13px;box-sizing:border-box;transition:border-color 0.2s;}
.aio-be-row input[type="text"]:focus{outline:none;border-color:#e53935;}
.aio-be-check{display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;padding:4px 0;}
.aio-be-check input[type="checkbox"]{cursor:pointer;accent-color:#ff9800;}
.aio-be-result{background:#0a0a18;border:1px solid #2a2a3e;border-radius:8px;padding:10px;margin-top:8px;text-align:center;}
.aio-be-big{font-size:18px;font-weight:bold;color:#66bb6a;}
.aio-be-info{font-size:10px;color:#555;margin-top:4px;}
`);
const CARD_W = 180;
const CACHE_TTL = 60000;
/* ──────────────────────────────────────────────
* MODULE: Market — Bazaar listeleme ve karşılaştırma
* Item Market sayfasında ürüne tıkladığınızda,
* Weav3r API üzerinden en ucuz bazaar satıcılarını
* yatay kaydırmalı kart sistemiyle gösterir.
* Sıralama ve filtreleme özellikleri içerir.
* ────────────────────────────────────────────── */
MODULES.Market = {
_cache: {},
_currentItemId: null,
_sortKey: 'price',
_sortOrder: 'asc',
_minQty: 0,
_rawListings: [],
_allListings: [],
_running: false,
fetchWeav3r: function(itemId) {
return new Promise((resolve) => {
const doRequest = (method) => {
method({
method: "GET",
url: `https://weav3r.dev/api/marketplace/${itemId}`,
timeout: 10000,
onload: function(r) {
try {
if(r.status >= 200 && r.status < 300) {
resolve(JSON.parse(r.responseText));
} else { resolve(null); }
} catch(e) { resolve(null); }
},
onerror: function() { resolve(null); },
ontimeout: function() { resolve(null); }
});
};
if(typeof GM_xmlhttpRequest !== "undefined") { doRequest(GM_xmlhttpRequest); }
else if(typeof GM !== "undefined" && GM.xmlHttpRequest) { doRequest(GM.xmlHttpRequest); }
else { fetch(`https://weav3r.dev/api/marketplace/${itemId}`).then(r=>r.json()).then(d=>resolve(d)).catch(()=>resolve(null)); }
});
},
getCache: function(itemId) {
const c = this._cache[itemId];
if(c && Date.now() - c.ts < CACHE_TTL) return c.data;
return null;
},
setCache: function(itemId, data) {
this._cache[itemId] = { ts: Date.now(), data: data };
},
relativeTime: function(unixTs) {
const L = AIO.L().bazaar;
const diff = Math.floor((Date.now() - unixTs * 1000) / 1000);
if(diff < 60) return diff + L.seconds + ' ' + L.ago;
if(diff < 3600) return Math.floor(diff / 60) + L.minutes + ' ' + L.ago;
if(diff < 86400) return Math.floor(diff / 3600) + L.hours + ' ' + L.ago;
return Math.floor(diff / 86400) + L.days + ' ' + L.ago;
},
/* Apply sort + min-qty filter on the raw (unfiltered) listing set */
applyFilters: function() {
const key = this._sortKey;
const order = this._sortOrder;
const minQ = Math.max(0, this._minQty || 0);
return this._rawListings
.filter(x => x.quantity >= minQ)
.sort((a, b) => {
let diff;
if(key === 'price') diff = a.price - b.price;
else if(key === 'quantity') diff = a.quantity - b.quantity;
else diff = a.updated - b.updated;
return order === 'asc' ? diff : -diff;
});
},
createCard: function(listing, index, L) {
const card = document.createElement('div');
card.className = 'aio-bz-card';
card.style.left = (index * CARD_W) + 'px';
card.style.width = CARD_W + 'px';
card.dataset.index = index;
const displayName = listing.player_name || ('ID: ' + listing.player_id);
const bazaarUrl = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`;
card.innerHTML =
`<div class="aio-bz-price">$${listing.price.toLocaleString()}</div>` +
`<div class="aio-bz-meta">${L.qty} ${listing.quantity.toLocaleString()}</div>` +
`<a href="${bazaarUrl}" target="_blank" rel="noopener" title="${displayName}">${displayName}</a>` +
`<a href="${bazaarUrl}" target="_blank" rel="noopener" class="aio-bz-visit">${L.store} ↗</a>` +
`<div class="aio-bz-time">${this.relativeTime(listing.updated)}</div>`;
return card;
},
renderCards: function(container) {
const track = container.querySelector('.aio-bz-track');
const scroll = container.querySelector('.aio-bz-scroll');
if(!track || !scroll) return;
const L = AIO.L().bazaar;
const listings = this._allListings;
if(listings.length === 0) {
track.innerHTML = '';
track.style.width = '';
track.innerHTML = `<div class="aio-bz-msg">${L.noData}</div>`;
const counter = container.querySelector('.aio-bz-count');
if(counter) counter.textContent = L.noData;
return;
}
track.style.width = (listings.length * CARD_W) + 'px';
const scrollLeft = scroll.scrollLeft;
const viewW = scroll.clientWidth;
const buffer = 3;
const startIdx = Math.max(0, Math.floor(scrollLeft / CARD_W) - buffer);
const endIdx = Math.min(listings.length, Math.ceil((scrollLeft + viewW) / CARD_W) + buffer);
const needed = {};
for(let i = startIdx; i < endIdx; i++) {
needed[i] = true;
}
Array.from(track.querySelectorAll('.aio-bz-card')).forEach(card => {
const idx = parseInt(card.dataset.index);
if(needed[idx]) {
card.style.left = (idx * CARD_W) + 'px';
delete needed[idx];
} else {
card.remove();
}
});
const frag = document.createDocumentFragment();
for(const idx in needed) {
const i = parseInt(idx);
frag.appendChild(this.createCard(listings[i], i, L));
}
if(frag.childElementCount > 0) track.appendChild(frag);
const totalQty = listings.reduce((s, x) => s + x.quantity, 0);
const counter = container.querySelector('.aio-bz-count');
if(counter) {
counter.textContent = `${L.showing} ${startIdx+1}-${endIdx} ${L.of} ${listings.length} ${L.bazaars} (${totalQty.toLocaleString()} ${L.items} ${L.total})`;
}
},
createContainer: function(itemName, itemId) {
const L = AIO.L().bazaar;
const c = document.createElement('div');
c.className = 'aio-bz-container';
c.dataset.itemid = itemId;
const header = document.createElement('div');
header.className = 'aio-bz-header';
header.innerHTML = `<span>${L.header} ${itemName} (ID: ${itemId})</span><span class="aio-bz-mv"></span>`;
c.appendChild(header);
const controls = document.createElement('div');
controls.className = 'aio-bz-controls';
controls.innerHTML =
`<span>${L.sortBy}</span>` +
`<select class="aio-bz-sort-sel">` +
`<option value="price" ${this._sortKey==='price'?'selected':''}>${L.price}</option>` +
`<option value="quantity" ${this._sortKey==='quantity'?'selected':''}>${L.quantity}</option>` +
`<option value="updated" ${this._sortKey==='updated'?'selected':''}>${L.updated}</option>` +
`</select>` +
`<button class="aio-bz-order-btn">${this._sortOrder==='asc'?L.asc:L.desc}</button>` +
`<span style="margin-left:10px;color:#888;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.4px;">` +
`${L.qty.replace(':','')} ≥</span>` +
`<input type="number" class="aio-bz-min-qty" min="0" placeholder="0" value="0"
style="width:52px;padding:4px 6px;border:1px solid #333;background:#111;
color:#fff;border-radius:4px;font-size:12px;transition:border-color 0.2s;
-moz-appearance:textfield;" title="${L.qty.replace(':','')} filtresi">`;
c.appendChild(controls);
const scrollWrap = document.createElement('div');
scrollWrap.className = 'aio-bz-scroll-wrap';
const leftArrow = document.createElement('div');
leftArrow.className = 'aio-bz-arrow';
leftArrow.innerHTML = '‹';
leftArrow.onclick = () => { scrollEl.scrollBy({left: -200, behavior: 'smooth'}); };
const scrollEl = document.createElement('div');
scrollEl.className = 'aio-bz-scroll';
const track = document.createElement('div');
track.className = 'aio-bz-track';
track.innerHTML = `<div class="aio-bz-msg">${L.loading}</div>`;
scrollEl.appendChild(track);
const rightArrow = document.createElement('div');
rightArrow.className = 'aio-bz-arrow';
rightArrow.innerHTML = '›';
rightArrow.onclick = () => { scrollEl.scrollBy({left: 200, behavior: 'smooth'}); };
scrollWrap.appendChild(leftArrow);
scrollWrap.appendChild(scrollEl);
scrollWrap.appendChild(rightArrow);
c.appendChild(scrollWrap);
let scrollTicking = false;
scrollEl.addEventListener('scroll', () => {
if(!scrollTicking) {
scrollTicking = true;
requestAnimationFrame(() => {
this.renderCards(c);
scrollTicking = false;
});
}
});
const sortSel = controls.querySelector('.aio-bz-sort-sel');
const orderBtn = controls.querySelector('.aio-bz-order-btn');
const minQtyInp = controls.querySelector('.aio-bz-min-qty');
/* Always re-derive from the raw unfiltered dataset stored on the container */
const refreshListings = () => {
this._allListings = this.applyFilters();
track.innerHTML = '';
this.renderCards(c);
};
/* Focus glow matching existing design system */
minQtyInp.addEventListener('focus', () => { minQtyInp.style.borderColor = '#e53935'; });
minQtyInp.addEventListener('blur', () => { minQtyInp.style.borderColor = '#333'; });
sortSel.addEventListener('change', () => {
this._sortKey = sortSel.value;
refreshListings();
});
orderBtn.addEventListener('click', () => {
this._sortOrder = this._sortOrder === 'asc' ? 'desc' : 'asc';
orderBtn.textContent = this._sortOrder === 'asc' ? L.asc : L.desc;
refreshListings();
});
/* Debounce input so rapid typing doesn't thrash the renderer */
let _minQtyTimer = null;
minQtyInp.addEventListener('input', () => {
clearTimeout(_minQtyTimer);
_minQtyTimer = setTimeout(() => {
this._minQty = parseInt(minQtyInp.value) || 0;
refreshListings();
}, 300);
});
const footer = document.createElement('div');
footer.className = 'aio-bz-footer';
footer.innerHTML = `<span class="aio-bz-count">${L.loading}</span><span>${L.poweredBy}</span>`;
c.appendChild(footer);
return c;
},
loadListings: async function(container, itemId) {
const L = AIO.L().bazaar;
const track = container.querySelector('.aio-bz-track');
/* Reset min-qty filter when switching to a new item */
this._minQty = 0;
const minInp = container.querySelector('.aio-bz-min-qty');
if(minInp) minInp.value = '';
const cached = this.getCache(itemId);
if(cached) {
this._rawListings = cached;
this._allListings = this.applyFilters();
track.innerHTML = '';
this.renderCards(container);
return;
}
const data = await this.fetchWeav3r(itemId);
if(!data || !data.listings) {
track.innerHTML = `<div class="aio-bz-msg">${L.apiErr}</div>`;
const counter = container.querySelector('.aio-bz-count');
if(counter) counter.textContent = L.apiErr;
return;
}
if(data.market_price) {
const mvEl = container.querySelector('.aio-bz-mv');
if(mvEl) mvEl.textContent = `${L.marketVal} $${Number(data.market_price).toLocaleString()}`;
}
const listings = data.listings.map(x => ({
item_id: x.item_id,
player_id: x.player_id,
player_name: x.player_name,
quantity: x.quantity,
price: x.price,
updated: x.last_checked
}));
this.setCache(itemId, listings);
if(listings.length === 0) {
track.innerHTML = `<div class="aio-bz-msg">${L.noData}</div>`;
const counter = container.querySelector('.aio-bz-count');
if(counter) counter.textContent = L.noData;
return;
}
this._rawListings = listings;
this._allListings = this.applyFilters();
track.innerHTML = '';
this.renderCards(container);
},
findItemInfo: function(wrapper) {
const itemTile = wrapper.previousElementSibling;
if(!itemTile) return null;
const nameEl = itemTile.querySelector('.name___ukdHN, [class*="name___"]');
const btn = itemTile.querySelector('button[aria-controls^="wai-itemInfo-"]');
if(!nameEl || !btn) return null;
const itemName = nameEl.textContent.trim();
const idParts = btn.getAttribute('aria-controls').split('-');
const itemId = idParts[idParts.length - 1];
return { itemId, itemName };
},
processSellerWrapper: function(wrapper) {
if(!wrapper) return;
if(wrapper.classList.contains('aio-bz-container')) return;
if(wrapper.hasAttribute('data-aio-bz-done')) return;
if(wrapper.querySelector(':scope > .aio-bz-container')) return;
const info = this.findItemInfo(wrapper);
if(!info) return;
wrapper.setAttribute('data-aio-bz-done', 'true');
const container = this.createContainer(info.itemName, info.itemId);
wrapper.insertBefore(container, wrapper.firstChild);
this.loadListings(container, info.itemId);
},
processAllWrappers: function() {
const wrappers = document.querySelectorAll('[class*="sellerListWrapper"]');
wrappers.forEach(w => this.processSellerWrapper(w));
},
run: function() {
if(!location.href.includes('sid=ItemMarket')) return;
if(this._running) return;
this._running = true;
const self = this;
self.processAllWrappers();
let debounce = null;
let isProcessing = false;
const obs = new MutationObserver((mutations) => {
if(isProcessing) return;
let dominated = false;
for(const m of mutations) {
for(const n of m.addedNodes) {
if(n.nodeType === 1 && n.classList && n.classList.contains('aio-bz-container')) {
dominated = true; break;
}
}
if(dominated) break;
}
if(dominated) return;
if(debounce) clearTimeout(debounce);
debounce = setTimeout(() => {
try {
isProcessing = true;
self.processAllWrappers();
} finally {
isProcessing = false;
}
}, 150);
});
const root = document.querySelector('#mainContainer') || document.querySelector('#root') || document.body;
obs.observe(root, { childList: true, subtree: true });
}
};
/* ──────────────────────────────────────────────
* MODULE: Shop — NPC Dükkan kâr/zarar analizi
* NPC dükkanlarındaki her ürünün yanına Item Market
* en ucuz fiyatını ekler. Yeşil = kârlı, Kırmızı =
* zararlı. TornTools profit etiketlerini gizler.
* ────────────────────────────────────────────── */
MODULES.Shop = {
_running: false,
_processing: false,
_itemMarketCache: {},
VALID_SHOPS: ['bitsnbobs','cyberforce','docks','jewelry','pharmacy','clothes','postoffice','printstore','recyclingcenter','super','candy'],
isShopPage: function() {
if(!location.pathname.includes('shops.php')) return false;
const step = new URLSearchParams(location.search).get('step');
return step && this.VALID_SHOPS.includes(step);
},
fetchItemMarketPrice: function(itemId, apiKey) {
return new Promise((resolve) => {
const cached = this._itemMarketCache[itemId];
if(cached && Date.now() - cached.time < 300000) { resolve(cached.price); return; }
const url = `https://api.torn.com/v2/market/${itemId}/itemmarket?limit=1&key=${apiKey}&comment=TornToolbox`;
fetch(url).then(r => r.json()).then(data => {
if(data && data.itemmarket && data.itemmarket.listings && data.itemmarket.listings.length > 0) {
const cheapest = data.itemmarket.listings[0].price;
this._itemMarketCache[itemId] = { price: cheapest, time: Date.now() };
resolve(cheapest);
} else {
this._itemMarketCache[itemId] = { price: null, time: Date.now() };
resolve(null);
}
}).catch(() => resolve(null));
});
},
hideTornToolsProfit: function() {
document.querySelectorAll('.item-desc .price .tt-profit').forEach(el => {
el.style.display = 'none';
});
},
processItems: async function(apiKey) {
if(this._processing) return;
this._processing = true;
try {
this.hideTornToolsProfit();
const L = AIO.L().shop;
const items = document.querySelectorAll('.item-desc');
const toFetch = [];
items.forEach(itemDesc => {
const priceEl = itemDesc.querySelector('.price');
if(!priceEl) return;
if(priceEl.querySelector('.aio-shop-im')) return;
const itemEl = itemDesc.querySelector('.item[itemid]');
if(!itemEl) return;
const itemId = parseInt(itemEl.getAttribute('itemid'));
if(!itemId) return;
const npcPriceText = priceEl.childNodes[0];
if(!npcPriceText) return;
const npcPrice = parseInt(npcPriceText.textContent.replace(/[$,]/g, ''));
if(!npcPrice || isNaN(npcPrice)) return;
toFetch.push({ itemId, npcPrice, priceEl });
});
if(toFetch.length === 0) return;
const self = this;
const delay = (ms) => new Promise(r => setTimeout(r, ms));
for(let i = 0; i < toFetch.length; i++) {
const item = toFetch[i];
if(item.priceEl.querySelector('.aio-shop-im')) continue;
if(i > 0) await delay(300);
const cheapest = await self.fetchItemMarketPrice(item.itemId, apiKey);
if(cheapest === null) continue;
if(item.priceEl.querySelector('.aio-shop-im')) continue;
const profitable = cheapest > item.npcPrice;
const color = profitable ? '#66bb6a' : (cheapest < item.npcPrice ? '#ef5350' : '#666');
const tag = document.createElement('span');
tag.className = 'aio-shop-im';
tag.title = `Item Market ${L.cheapest} $${cheapest.toLocaleString()} ${L.vs} $${item.npcPrice.toLocaleString()}`;
tag.style.cssText = `color:${color};font-weight:bold;margin-left:6px;font-size:12px;white-space:nowrap;`;
tag.textContent = `$${cheapest.toLocaleString()}`;
self.hideTornToolsProfit();
item.priceEl.appendChild(tag);
}
} finally {
this._processing = false;
}
},
run: async function() {
if(!this.isShopPage()) return;
if(this._running) return;
this._running = true;
const apiKey = AIO.getApiKey();
if(!apiKey) return;
const self = this;
let debounce = null;
const waitForItems = () => {
const check = setInterval(() => {
const items = document.querySelectorAll('.item-desc');
if(items.length > 0) {
clearInterval(check);
self.hideTornToolsProfit();
self.processItems(apiKey);
new MutationObserver((mutations) => {
let dominated = false;
for(const m of mutations) {
for(const n of m.addedNodes) {
if(n.nodeType === 1 && n.classList && (n.classList.contains('aio-shop-im'))) {
dominated = true; break;
}
}
if(dominated) break;
}
if(dominated) return;
self.hideTornToolsProfit();
if(debounce) clearTimeout(debounce);
debounce = setTimeout(() => self.processItems(apiKey), 500);
}).observe(document.querySelector('.shop-wrap, #mainContainer') || document.body, { childList: true, subtree: true });
}
}, 500);
};
waitForItems();
}
};
/* ──────────────────────────────────────────────
* MODULE: ItemLocker — Market ürün kilitleme
* addListing sayfasında ürünleri tamamen veya
* adet bazlı kilitler. "Select All" yapıldığında
* kilitli ürünlerin miktarı otomatik azaltılır,
* tam kilitli ürünlerin fiyat/miktarı sıfırlanır.
* Kilitler oturumlar arası kalıcıdır.
* ────────────────────────────────────────────── */
MODULES.ItemLocker = {
_running: false,
_locks: {},
_adjusting: false,
init: function() {
this._locks = AIO.get('itemLocks', {});
},
saveLocks: function() {
AIO.set('itemLocks', this._locks);
},
getItemName: function(row) {
const el = row.querySelector('[class*="name___"]');
return el ? el.textContent.trim() : '';
},
simulateInput: function(input, value) {
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
setter.call(input, String(value));
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
},
setRowLockState: function(row, locked) {
const itemName = this.getItemName(row);
const lock = this._locks[itemName];
row.classList.remove('aio-row-full-lock');
if(locked && lock && lock.full) {
row.classList.add('aio-row-full-lock');
const qw = row.querySelector('[class*="amountInputWrapper"]');
if(qw) { const inp = qw.querySelector('input.input-money:not([type="hidden"])'); if(inp && inp.value !== '0') this.simulateInput(inp, '0'); }
const pw = row.querySelector('[class*="priceInputWrapper"]');
if(pw) { const inp = pw.querySelector('input.input-money:not([type="hidden"])'); if(inp && inp.value !== '0') this.simulateInput(inp, '0'); }
}
},
processRow: function(row) {
if(row.querySelector('.aio-lock-wrap')) return;
const titleEl = row.querySelector('[class*="title___"]');
if(!titleEl) return;
const itemName = this.getItemName(row);
if(!itemName) return;
const L = AIO.L().locker;
const lock = this._locks[itemName];
const isLocked = !!lock;
const self = this;
const wrap = document.createElement('span');
wrap.className = 'aio-lock-wrap';
const qtyInput = document.createElement('input');
qtyInput.type = 'number';
qtyInput.min = '0';
qtyInput.placeholder = L.qtyPlaceholder;
qtyInput.className = 'aio-lock-qty';
if(isLocked && !lock.full) qtyInput.value = lock.qty;
qtyInput.addEventListener('click', (e) => e.stopPropagation(), true);
qtyInput.addEventListener('mousedown', (e) => e.stopPropagation(), true);
const lockBtn = document.createElement('span');
lockBtn.className = 'aio-lock-btn' + (isLocked ? ' locked' : '');
lockBtn.innerHTML = isLocked ? '🔒' : '🔓';
const status = document.createElement('span');
status.className = 'aio-lock-status';
if(isLocked) status.textContent = lock.full ? L.lockedFull : lock.qty + ' ' + L.lockedPartial;
lockBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if(lockBtn.classList.contains('locked')) {
lockBtn.innerHTML = '🔓';
lockBtn.classList.remove('locked');
status.textContent = '';
delete self._locks[itemName];
row.classList.remove('aio-row-full-lock');
} else {
const val = parseInt(qtyInput.value) || 0;
lockBtn.innerHTML = '🔒';
lockBtn.classList.add('locked');
if(val > 0) {
self._locks[itemName] = { qty: val, full: false };
status.textContent = val + ' ' + L.lockedPartial;
} else {
self._locks[itemName] = { qty: 0, full: true };
status.textContent = L.lockedFull;
}
self.setRowLockState(row, true);
}
self.saveLocks();
}, true);
wrap.appendChild(qtyInput);
wrap.appendChild(lockBtn);
wrap.appendChild(status);
titleEl.appendChild(wrap);
if(isLocked) this.setRowLockState(row, true);
},
startEnforcer: function() {
const self = this;
setInterval(() => {
if(self._adjusting) return;
self._adjusting = true;
try {
document.querySelectorAll('.aio-lock-btn.locked').forEach(btn => {
const row = btn.closest('[class*="itemRow___"]');
if(!row) return;
const itemName = self.getItemName(row);
const lock = self._locks[itemName];
if(!lock || !lock.full) return;
const qw = row.querySelector('[class*="amountInputWrapper"]');
if(qw) { const inp = qw.querySelector('input.input-money:not([type="hidden"])'); if(inp && inp.value !== '0' && inp.value !== '') self.simulateInput(inp, '0'); }
const pw = row.querySelector('[class*="priceInputWrapper"]');
if(pw) { const inp = pw.querySelector('input.input-money:not([type="hidden"])'); if(inp && inp.value !== '0' && inp.value !== '') self.simulateInput(inp, '0'); }
});
} finally { self._adjusting = false; }
}, 150);
},
applyPartialLocks: function() {
const self = this;
document.querySelectorAll('.aio-lock-btn.locked').forEach(btn => {
const row = btn.closest('[class*="itemRow___"]');
if(!row) return;
const itemName = self.getItemName(row);
const lock = self._locks[itemName];
if(!lock || lock.full) return;
const qw = row.querySelector('[class*="amountInputWrapper"]');
if(!qw) return;
const inp = qw.querySelector('input.input-money:not([type="hidden"])');
if(!inp) return;
const val = parseInt(inp.value.replace(/,/g, '')) || 0;
if(val > 0) {
const reduced = Math.max(0, val - lock.qty);
self.simulateInput(inp, String(reduced));
if(reduced === 0) {
const pw = row.querySelector('[class*="priceInputWrapper"]');
if(pw) { const pi = pw.querySelector('input.input-money:not([type="hidden"])'); if(pi) self.simulateInput(pi, '0'); }
}
}
});
},
interceptSelectAll: function() {
const self = this;
const hook = () => {
const cb = document.querySelector('[class*="selectAllCheckbox"] input[type="checkbox"]');
if(cb && !cb.dataset.aioLockHooked) {
cb.dataset.aioLockHooked = 'true';
cb.addEventListener('change', () => {
setTimeout(() => self.applyPartialLocks(), 500);
});
}
};
hook();
new MutationObserver(hook).observe(document.body, { childList: true, subtree: true });
},
processAll: function() {
document.querySelectorAll('[class*="itemRow___"]').forEach(row => this.processRow(row));
},
run: function() {
if(!location.href.includes('sid=ItemMarket')) return;
if(!location.hash.includes('addListing')) return;
if(!this._running) {
this._running = true;
this.init();
this.startEnforcer();
this.interceptSelectAll();
const self = this;
new MutationObserver(() => {
if(location.hash.includes('addListing')) self.processAll();
}).observe(document.querySelector('#mainContainer') || document.body, { childList: true, subtree: true });
}
this.processAll();
}
};
/* ──────────────────────────────────────────────
* MODULE: BreakEven — Kâr/zarar hesaplayıcı
* Her sayfada görünen sürüklenebilir mini panel.
* Maliyet girişi yapın, anonim satış seçeneğini
* işaretleyin → zarar etmeme noktasını anında
* hesaplar. Konum ve küçültme durumu kalıcıdır.
* Formül: satış = maliyet / (1 − vergi oranı)
* ────────────────────────────────────────────── */
MODULES.BreakEven = {
_created: false,
createPanel: function() {
if(this._created || document.getElementById('aio-be')) return;
this._created = true;
const L = AIO.L().breakeven;
const pos = AIO.get('be_pos', null);
const minimized = AIO.get('be_min', false);
const panel = document.createElement('div');
panel.id = 'aio-be';
if(minimized) panel.classList.add('minimized');
if(pos) { panel.style.left = pos.left; panel.style.top = pos.top; panel.style.right = 'auto'; panel.style.bottom = 'auto'; }
const header = document.createElement('div');
header.id = 'aio-be-header';
const titleSpan = document.createElement('span');
titleSpan.textContent = L.title;
header.appendChild(titleSpan);
const toggleBtn = document.createElement('button');
toggleBtn.id = 'aio-be-toggle';
toggleBtn.textContent = minimized ? '▢' : '—';
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
const isMin = panel.classList.toggle('minimized');
toggleBtn.textContent = isMin ? '▢' : '—';
AIO.set('be_min', isMin);
});
header.appendChild(toggleBtn);
const body = document.createElement('div');
body.id = 'aio-be-body';
body.innerHTML =
'<div class="aio-be-row"><label>' + L.cost + '</label><input type="text" id="aio-be-cost" placeholder="1,350,000"></div>' +
'<div class="aio-be-row"><label class="aio-be-check"><input type="checkbox" id="aio-be-anon"> ' + L.anon + '</label></div>' +
'<div class="aio-be-row"><label>' + L.fee + ' <span id="aio-be-fee-val" style="color:#ff9800;font-weight:bold;">%5</span></label></div>' +
'<div class="aio-be-result"><div class="aio-be-big" id="aio-be-result">\u2014</div><div class="aio-be-info" id="aio-be-formula"></div></div>';
panel.appendChild(header);
panel.appendChild(body);
document.body.appendChild(panel);
const costInput = document.getElementById('aio-be-cost');
const anonCheck = document.getElementById('aio-be-anon');
const feeVal = document.getElementById('aio-be-fee-val');
const resultEl = document.getElementById('aio-be-result');
const formulaEl = document.getElementById('aio-be-formula');
const calculate = () => {
const raw = costInput.value.replace(/[^0-9]/g, '');
const cost = parseInt(raw) || 0;
const isAnon = anonCheck.checked;
const feeRate = isAnon ? 0.15 : 0.05;
feeVal.textContent = isAnon ? '%15' : '%5';
if(cost <= 0) { resultEl.textContent = '\u2014'; resultEl.style.color = '#66bb6a'; formulaEl.textContent = ''; return; }
const breakEven = Math.ceil(cost / (1 - feeRate));
resultEl.textContent = '$' + breakEven.toLocaleString();
resultEl.style.color = '#66bb6a';
formulaEl.textContent = L.formula + ' $' + cost.toLocaleString() + ' / ' + (1 - feeRate).toFixed(2) + ' = $' + breakEven.toLocaleString();
};
costInput.addEventListener('input', () => {
const raw = costInput.value.replace(/[^0-9]/g, '');
if(raw) costInput.value = parseInt(raw).toLocaleString();
calculate();
});
anonCheck.addEventListener('change', calculate);
header.addEventListener('mousedown', (e) => {
if(e.target === toggleBtn) return;
e.preventDefault();
const mx = e.clientX, my = e.clientY;
const rect = panel.getBoundingClientRect();
const ox = rect.left, oy = rect.top;
const move = (ev) => {
panel.style.left = (ox + ev.clientX - mx) + 'px';
panel.style.top = (oy + ev.clientY - my) + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
};
const up = () => {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
AIO.set('be_pos', { left: panel.style.left, top: panel.style.top });
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
});
},
run: function() {
this.createPanel();
}
};
/* ──────────────────────────────────────────────
* ROUTER — URL'e göre doğru modülü çalıştırır
* ────────────────────────────────────────────── */
const Router = {
scan: function() {
const p = location.pathname;
const h = location.href;
if(p.includes('gym.php')) MODULES.Gym.run();
if(h.includes('crimes')) MODULES.Crimes.run();
if(p.includes('bazaar.php') || h.includes('sid=ItemMarket')) MODULES.Market.run();
if(h.includes('sid=ItemMarket')) MODULES.ItemLocker.run();
if(p.includes('shops.php')) MODULES.Shop.run();
MODULES.BreakEven.run();
}
};
/* ──────────────────────────────────────────────
* INIT — UI başlat, Router'ı tetikle, gözlemle
* ────────────────────────────────────────────── */
AIO.initUI();
setTimeout(()=>Router.scan(), 1000);
setTimeout(()=>Router.scan(), 3000);
window.addEventListener('hashchange', () => Router.scan());
let _tmr=null;
new MutationObserver(() => {
clearTimeout(_tmr);
_tmr = setTimeout(()=>Router.scan(), 500);
}).observe(document.body, {childList:true, subtree:true});
})();