Popmundo envanter paneli — çok dilli arayüz, otomatik tarama, arama, fiyat takibi, CSV ve çoklu karakter desteği
// ==UserScript==
// @name 📦 Depot
// @name:en 📦 Depot
// @name:pt-BR 📦 Depot
// @namespace popmundo.inventory
// @version 2.8
// @description Popmundo envanter paneli — çok dilli arayüz, otomatik tarama, arama, fiyat takibi, CSV ve çoklu karakter desteği
// @description:en Popmundo inventory panel — multilingual UI, auto-scan, search, price tracking, CSV export & multi-character support
// @description:pt-BR Painel de inventário Popmundo — multilíngue, varredura automática, busca, preços, CSV e suporte multi-personagem
// @author luke-james-gibson
// @license MIT
// @id dpv9q3
// @supportURL https://greasyfork.org/tr/scripts/568770-popmundo-envanter
// @match https://*.popmundo.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant unsafeWindow
// @grant GM_info
// ==/UserScript==
(function () {
'use strict';
// ─── POPCONTROL DISABLE CHECK ────────────────────────────────────────────────
try { const _ppc = JSON.parse(localStorage.getItem('ppc_enabled')||'{}'); if (_ppc['depot'] === false) return; } catch {}
// Language
const LANG = (() => { try { const c = document.cookie.match(/ppm_lang=([^;]+)/); return c ? c[1] : 'TR'; } catch { return 'TR'; } })();
const _D = (tr, en, pt) => ({ TR: tr, EN: en, 'PT-BR': pt }[LANG] || tr);
const S = {
// Scan modal
scanTitle: _D('🔄 Otomatik Tarama', '🔄 Auto Scan', '🔄 Varredura Automática'),
scanPersonal: _D('👤 Kişisel Eşyalar', '👤 Personal Items', '👤 Itens Pessoais'),
scanVehicles: _D('🚗 Kişisel Araçlar', '🚗 Personal Vehicles', '🚗 Veículos Pessoais'),
scanHousing: _D('🏠 Evler', '🏠 Housing', '🏠 Moradias'),
scanArtist: _D('🎵 Sanatçı / Turne Aracı', '🎵 Artist / Tour Vehicle', '🎵 Artista / Veículo de Turnê'),
scanWarn: _D('⚠️ Tarama sırasında sekmeler otomatik değişir.', '⚠️ Tabs will change automatically during scan.', '⚠️ As abas mudarão automaticamente durante a varredura.'),
scanCancel: _D('İptal', 'Cancel', 'Cancelar'),
scanStart: _D('▶ Taramayı Başlat', '▶ Start Scan', '▶ Iniciar Varredura'),
scanSelectOne: _D('En az bir kategori seçin.', 'Select at least one category.', 'Selecione pelo menos uma categoria.'),
// Scan report
scanDone: _D('✅ Tarama Tamamlandı', '✅ Scan Complete', '✅ Varredura Concluída'),
scanLocCount: _D('Taranan Lokasyon:', 'Scanned Locations:', 'Locais Varridos:'),
scanItemCount: _D('Toplam Eşya:', 'Total Items:', 'Total de Itens:'),
scanClose: _D('Kapat', 'Close', 'Fechar'),
// Scan prompts / logs
scanAskId: _D('Karakter ID girin:', 'Enter Character ID:', 'Digite o ID do Personagem:'),
scanBadId: _D('Geçerli ID girilmedi.', 'No valid ID entered.', 'Nenhum ID válido inserido.'),
scanVehList: _D('🔍 Araç listesi:', '🔍 Vehicle list:', '🔍 Lista de veículos:'),
scanVehUnit: _D('araç.', 'vehicles.', 'veículos.'),
scanHouseList: _D('🔍 Ev listesi:', '🔍 Housing list:', '🔍 Lista de moradias:'),
scanHouseUnit: _D('ev.', 'properties.', 'imóveis.'),
scanLogOk: _D('çeşit.', 'types.', 'tipos.'),
scanLogEmpty: _D('eşya yok.', 'no items.', 'sem itens.'),
scanStop: _D('🛑 DURDUR', '🛑 STOP', '🛑 PARAR'),
// Panel
panelTitle: '📦 Depot ' + GM_info.script.version,
dragHint: _D('⠿ Sürükle', '⠿ Drag', '⠿ Arrastar'),
btnSettings: _D('Panel Ayarları', 'Panel Settings', 'Configurações do Painel'),
btnBackup: _D('📤 Scripti Yedekle', '📤 Backup Script', '📤 Fazer Backup'),
btnRestore: _D('📥 Yedeği Yükle', '📥 Load Backup', '📥 Carregar Backup'),
catLabel: _D('Kategori CSV (Kategori,Eşya):', 'Category CSV (Category,Item):', 'CSV de Categorias (Categoria,Item):'),
catPlaceholder: _D('Kategori,Eşya Adı', 'Category,Item Name', 'Categoria,Nome do Item'),
btnCatSave: _D('💾 Kategorileri Güncelle', '💾 Update Categories', '💾 Atualizar Categorias'),
btnClearAll: _D('⚠️ Tüm Veriyi Sıfırla', '⚠️ Reset All Data', '⚠️ Redefinir Todos os Dados'),
btnSavePage: _D('📍 Sayfayı Kaydet', '📍 Save Page', '📍 Salvar Página'),
btnDelRecord: _D('🗑️ Kaydı Sil', '🗑️ Delete Record', '🗑️ Excluir Registro'),
btnInventory: _D('Envanter İşlemleri', 'Inventory Actions', 'Ações de Inventário'),
btnAutoScan: _D('🔄 OTOMATİK TARA', '🔄 AUTO SCAN', '🔄 VARREDURA AUTOMÁTICA'),
btnListManage: _D('📋 ENVANTERLERİ LİSTELE / YÖNET','📋 LIST / MANAGE INVENTORIES', '📋 LISTAR / GERENCIAR INVENTÁRIOS'),
btnDetailed: _D('📄 Detaylı CSV', '📄 Detailed CSV', '📄 CSV Detalhado'),
btnStockCSV: _D('📊 Stok & Fiyatlı CSV', '📊 Stock & Priced CSV', '📊 CSV de Estoque & Preços'),
btnImportCSV: _D('📥 CSV Yükle', '📥 Import CSV', '📥 Importar CSV'),
btnForumList: _D('💰 Fiyat Kontrolü', '💰 Price Check', '💰 Verificação de Preço'),
// CSV Editor strings
csvEditorTitle: _D('📊 CSV Görüntüleyici / Düzenleyici','📊 CSV Viewer / Editor', '📊 Visualizador / Editor CSV'),
csvFormat: _D('CSV Formatı:', 'CSV Format:', 'Formato CSV:'),
csvFmtDetailed: _D('Detaylı CSV (Tüm Bilgiler)', 'Detailed CSV (All Info)', 'CSV Detalhado (Todas as Informações)'),
csvFmtSummary: _D('Özet CSV (Stok & Fiyat)', 'Summary CSV (Stock & Price)', 'CSV Resumido (Estoque & Preço)'),
csvFmtForum: _D('Forum Formatı (Fiyat Listesi)', 'Forum Format (Price List)', 'Formato Fórum (Lista de Preços)'),
csvDataLabel: _D('CSV Verisi (düzenlenebilir):', 'CSV Data (editable):', 'Dados CSV (editável):'),
csvCopy: _D('📋 Kopyala', '📋 Copy', '📋 Copiar'),
csvDownload: _D('💾 İndir', '💾 Download', '💾 Baixar'),
csvApply: _D('✓ Değişiklikleri Uygula', '✓ Apply Changes', '✓ Aplicar Alterações'),
csvUpdated: _D('CSV verisi güncellendi.', 'CSV data updated.', 'Dados CSV atualizados.'),
csvCopied: _D('CSV panoya kopyalandı!', 'CSV copied to clipboard!', 'CSV copiado para a área de transferência!'),
csvNoPrice: _D('Güncellenecek fiyat bulunamadı.', 'No prices to update.', 'Nenhum preço para atualizar.'),
csvApplyUnsup: _D('Bu format için değişiklik uygulama desteklenmiyor.', 'Applying changes not supported for this format.', 'Aplicação não suportada para este formato.'),
csvErrPrefix: _D('Hata: ', 'Error: ', 'Erro: '),
// Fiyat Kontrolü strings
pcTitle: _D('💰 Fiyat Kontrolü', '💰 Price Check', '💰 Verificação de Preço'),
pcPasteLbl: _D('Forum listesi veya oyun içi mesajı buraya yapıştırın:', 'Paste forum list or in-game message here:', 'Cole a lista do fórum ou mensagem do jogo aqui:'),
pcPastePh: _D('Snowglobe|1|100m\nveya yapışık:\nPhony Bad Teeth x1Snowball x1', 'Snowglobe|1|100m\nor concatenated:\nPhony Bad Teeth x1Snowball x1', 'Snowglobe|1|100m\nou concatenado:\nPhony Bad Teeth x1Snowball x1'),
pcProcess: _D('İşle', 'Process', 'Processar'),
pcAIBtn: _D('🤖 AI ile Temizle', '🤖 Clean with AI', '🤖 Limpar com IA'),
pcAIHint: _D('Prompt kopyalandı! Gemini\'ye açıldı.', 'Prompt copied! Gemini opened.', 'Prompt copiado! Gemini aberto.'),
pcClose: _D('Kapat', 'Close', 'Fechar'),
pcFound: _D('Sende:', 'You have:', 'Você tem:'),
pcNotFound: _D('Envanterde yok', 'Not in inventory', 'Não está no inventário'),
pcForumPrice: _D('Girilen fiyat:', 'Listed price:', 'Preço listado:'),
pcLastPrice: _D('Son fiyat:', 'Last price:', 'Último preço:'),
pcAddPrice: _D('Fiyatı Ekle', 'Add Price', 'Adicionar Preço'),
pcAdded: _D('Eklendi ✓', 'Added ✓', 'Adicionado ✓'),
pcHistory: _D('Geçmiş', 'History', 'Histórico'),
pcNoHistory: _D('Fiyat geçmişi yok.', 'No price history.', 'Sem histórico de preços.'),
pcNoInput: _D('Lütfen bir liste girin.', 'Please enter a list.', 'Por favor, insira uma lista.'),
pcResults: _D('Sonuçlar:', 'Results:', 'Resultados:'),
pcApplyAll: _D('Tümünü Uygula', 'Apply All', 'Aplicar Todos'),
pcApplied: _D('fiyat uygulandı.', 'prices applied.', 'preços aplicados.'),
searchPH: _D('Envanterde ara (Örn: Haiku)...', 'Search inventory (e.g. Haiku)...', 'Buscar no inventário (ex: Haiku)...'),
// Alerts
alertNoItems: _D('Bu sayfada eşya bulunamadı.', 'No items found on this page.', 'Nenhum item encontrado nesta página.'),
alertDeleted: _D('Silindi.', 'Deleted.', 'Excluído.'),
alertNotFound: _D('Kayıt bulunamadı.', 'Record not found.', 'Registro não encontrado.'),
alertEnterData: _D('Veri girin.', 'Enter data.', 'Insira os dados.'),
alertCatFmt: _D("Format hatalı! 'Kategori,Eşya'", "Invalid format! 'Category,Item'", "Formato inválido! 'Categoria,Item'"),
alertClearConf: _D('Tüm DB sıfırlanacak!', 'All data will be reset!', 'Todos os dados serão redefinidos!'),
alertRestored: _D('Geri yüklendi.', 'Restored.', 'Restaurado.'),
alertInvalidFile:_D('Geçersiz dosya.', 'Invalid file.', 'Arquivo inválido.'),
// List
listEmpty: _D('Veritabanı boş.', 'Database empty.', 'Banco de dados vazio.'),
btnDelAll: _D('Tümünü Sil', 'Delete All', 'Excluir Tudo'),
btnDel: _D('SİL', 'DEL', 'EXC'),
confirmDel: _D('Bu kayıt silinsin mi?', 'Delete this record?', 'Excluir este registro?'),
// Search
noResults: _D('Sonuç bulunamadı.', 'No results found.', 'Nenhum resultado encontrado.'),
totalLabel: _D('Toplam', 'Total', 'Total'),
lastScanLabel: _D('Son tarama:', 'Last scan:', 'Última varredura:'),
btnCopy: _D('📋 Bu sonuçları kopyala', '📋 Copy these results', '📋 Copiar estes resultados'),
btnCopied: _D('✅ Kopyalandı', '✅ Copied', '✅ Copiado'),
noPriceLabel: _D('Teklif ediniz', 'Make an offer', 'Consultar preço'),
// clsLbl
clsPersonal: _D('Eşyalar', 'Items', 'Itens'),
clsVehicle: _D('Kişisel Araç', 'Personal Vehicle', 'Veículo Pessoal'),
clsHousing: _D('Ev', 'Housing', 'Moradia'),
clsArtist: _D('Sanatçı Aracı', 'Artist Vehicle', 'Veículo do Artista'),
minTooltip: _D('Paneli aç', 'Open panel', 'Abrir painel'),
btnMinimize: _D('Küçült', 'Minimize', 'Minimizar'),
btnClose: _D('Kapat', 'Close', 'Fechar'),
// Loc name fallbacks
locVehicleFb: _D('Kişisel Araç', 'Personal Vehicle', 'Veículo Pessoal'),
locLocaleFb: _D('Mekan', 'Locale', 'Local'),
locCharFb: _D('Karakter', 'Character', 'Personagem'),
locInvSuffix: _D('Envanteri', 'Inventory', 'Inventário'),
locArtistFb: _D('Grup Aracı', 'Artist Vehicle', 'Veículo do Artista'),
locUnknown: _D('Bilinmeyen', 'Unknown', 'Desconhecido'),
variantDefault: _D('Standart', 'Standard', 'Padrão'),
};
const _clDepot = (() => { try { const v = localStorage.getItem('ppc_lc_depot'); return v ? JSON.parse(v) : null; } catch { return null; } })();
const s = k => (_clDepot && _clDepot[k]) ? _clDepot[k] : (S[k] || k);
// GM Storage helpers
const gmGet = (k,def) => { try { const v=GM_getValue(k,null); return v?JSON.parse(v):def; } catch { return def; } };
const gmSet = (k,v) => GM_setValue(k, JSON.stringify(v));
const gmDel = (k) => GM_deleteValue(k);
// localStorage helpers (scan state only)
const lsGet = (k,def) => { try { const v=localStorage.getItem(k); return v?JSON.parse(v):def; } catch { return def; } };
const lsSet = (k,v) => localStorage.setItem(k, JSON.stringify(v));
const lsDel = (k) => localStorage.removeItem(k);
// Storage keys
const K = { db:'pop_inv_data', cat:'pop_cat_data', price:'pop_price_data', veh:'pop_vehicle_names', char:'pop_char_id', owner:'pop_owner_name', theme:'pop_theme', min:'pop_min', pw:'pop_panel_w', pos:'pop_panel_pos', posMin:'pop_panel_pos_min', scan:'pop_scan_state', artistView:'pop_artist_view_state', artistMerge:'pop_artist_merge_log' };
// DB cache
let _db=null, _prices=null;
const getDB = () => _db || (_db=gmGet(K.db,{}));
const saveDB = (v) => { _db=v; gmSet(K.db,v); };
const getPrices = () => _prices || (_prices=gmGet(K.price,{}));
const savePrices = (v) => { _prices=v; gmSet(K.price,v); };
// ── Ortak Fiyat Geçmişi (Helper + Depot paylaşır) ──────────────────────────
// Format: { "Painkiller|||": { prices:[{p:50000,d:"15.01.25"},...], last:50000, lastD:"15.01.25" } }
let _sp = null;
const getSharedPrices = () => _sp || (_sp = gmGet('pop_shared_prices', {}));
const saveSharedPrices = (v) => { _sp = v; gmSet('pop_shared_prices', v); };
const _spDate = () => { const d=new Date(); return `${String(d.getDate()).padStart(2,'0')}.${String(d.getMonth()+1).padStart(2,'0')}.${String(d.getFullYear()).slice(2)}`; };
const addSharedPrice = (name, variant, priceVal) => {
if (!priceVal || priceVal < 10000) return; // ücretsiz ve anlamsız düşük fiyatları yoksay
const sp = getSharedPrices();
const k = priceKey(name, variant);
const dStr = _spDate();
if (!sp[k]) sp[k] = { prices: [], last: priceVal, lastD: dStr };
// Aynı fiyat daha önce kaydedilmişse tekrar ekleme, sadece lastD güncelle
const existing = sp[k].prices.find(e => e.p === priceVal);
if (!existing) {
sp[k].prices.push({ p: priceVal, d: dStr });
if (sp[k].prices.length > 20) sp[k].prices.splice(0, sp[k].prices.length - 20);
}
sp[k].last = priceVal;
sp[k].lastD = dStr;
saveSharedPrices(sp);
};
const getLastSharedPrice = (name, variant) => {
const k = priceKey(name, variant);
// Önce shared_prices'a bak
const sp = getSharedPrices();
if (sp[k]) return sp[k];
// Fallback: pop_price_data (eski Depot fiyatları)
const rawPrice = getPrices()[k];
if (rawPrice) {
const numVal = parsePrice(rawPrice);
if (numVal >= 10000)
return { prices: [{ p: numVal, d: '—' }], last: numVal, lastD: '—' };
}
return null;
};
// Utilities
const today = () => { const d=new Date(); return `${String(d.getDate()).padStart(2,'0')}.${String(d.getMonth()+1).padStart(2,'0')}.${d.getFullYear()}`; };
let _dictCache = null;
const getDictData = () => {
if (_dictCache) return _dictCache;
const raw = gmGet(K.cat, { items: [] });
if (!Array.isArray(raw.items)) raw.items =[];
const lookup = {};
raw.items.forEach(it => {
if(it.en) lookup[it.en.toLowerCase()] = it;
if(it.it) lookup[it.it.toLowerCase()] = it;
if(it.es) lookup[it.es.toLowerCase()] = it;
if(it.pt_pt) lookup[it.pt_pt.toLowerCase()] = it;
if(it.pt_br) lookup[it.pt_br.toLowerCase()] = it;
if(it.tr) lookup[it.tr.toLowerCase()] = it;
});
_dictCache = { items: raw.items, lookup };
return _dictCache;
};
const saveDictData = (items) => { gmSet(K.cat, { items }); _dictCache = null; };
const getDict = (n) => {
if(!n) return { cat:'Diğer', en:n, it:'', es:'', pt_pt:'', pt_br:'', tr:n };
const d = getDictData().lookup[n.toLowerCase()];
return d ? d : { cat:'Diğer', en:n, it:'', es:'', pt_pt:'', pt_br:'', tr:n };
};
const getCat = (n) => getDict(n).cat;
const theme = () => GM_getValue(K.theme,'')||'dark';
const priceKey = (n,v) => { const d=getDict(n); const cleanV = v ? v.replace(/\.\s*$/,'').trim() : ''; return `${d.en||d.tr||n}|||${cleanV}`; };
const esc = (v) => String(v).replace(/&/g,'&').replace(/"/g,'"');
const normV = (v) => v.replace(/\.\s*$/,'').trim();
const dlCSV = (content,name) => { const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([content],{type:'text/csv;charset=utf-8;'})); a.download=name; a.click(); };
const randDelay = () => 2000 + Math.floor(Math.random()*1500);
const getTitle = () => document.title.replace(/^.*?Popmundo\s*[-–]\s*/i,'').trim();
// Scan state
const getScan = () => lsGet(K.scan, {active:false,queue:[],index:0,logs:[]});
const saveScan = (st) => lsSet(K.scan, st);
// Scan: sayfa ayrıştırma
const getLocInfo = (u) => {
if (u.includes('/Character/Vehicle/')) {
const veh=gmGet(K.veh,{}), rel=u.replace(/^https?:\/\/[^/]+/,'');
return { name:veh[u]||veh[rel]||getTitle()||s('locVehicleFb'), type:'Araç' };
}
if (u.includes('/Locale/ItemsEquipment/')) {
const h2=document.querySelector('.localebox h2')?.innerText.trim();
const city=document.querySelector('.localebox .right a')?.innerText.trim();
return { name:h2?(city?`${h2} (${city})`:h2):(getTitle()||s('locLocaleFb')), type:'Mekan' };
}
if (u.includes('/Character/Items/')) {
const n=document.querySelector('.charPresBox h2')?.innerText.trim()||getTitle();
return { name:`${n||s('locCharFb')} ${s('locInvSuffix')}`, type:'Kişisel' };
}
if (u.includes('/Artist/VehicleItems/')) return { name:getTitle()||s('locArtistFb'), type:'Grup Aracı' };
return { name:s('locUnknown'), type:'Genel' };
};
const getOwner = () => {
const el=document.querySelector('.charPresBox h2');
if (el) { const n=el.innerText.trim(); GM_setValue(K.owner,n); return n; }
return GM_getValue(K.owner,'')||s('locCharFb');
};
const extractVariant = (anchor) => {
const td=anchor.closest('td')||anchor.parentElement, clone=td.cloneNode(true);
clone.querySelector('[id*="lnkItem"]')?.remove();
clone.querySelectorAll('.tvip-item-id, .tvis-ia-wrap').forEach(el => el.remove());
const cEm=Array.from(clone.querySelectorAll('em')).find(e=>/^x\d+/i.test(e.textContent.trim()));
if (cEm) cEm.remove();
return clone.textContent.replace(/\s+/g,' ').trim();
};
const updatePage = () => {
const fullU=window.location.href, u=window.location.pathname+window.location.search, info=getLocInfo(fullU), owner=getOwner(), db=getDB(), map={};
document.querySelectorAll('tr.hoverable').forEach(row => {
const a=row.querySelector('a[id*="lnkItem"]'); if (!a) return;
const name=a.innerText.trim(), em=row.querySelector('em');
const count=em&&/^x\d+/i.test(em.textContent.trim())?(parseInt(em.textContent.replace(/[^\d]/g,''))||1):1;
const variant=extractVariant(a);
if (!map[name]) map[name]={variants:{}};
map[name].variants[variant]=(map[name].variants[variant]||0)+count;
});
const cnt=Object.keys(map).length;
if (cnt>0) {
if (!db[owner]) db[owner]={};
// Special handling for artist tool shared storage
if (info.type === 'Grup Aracı') {
handleArtistStorageUpdate(owner, u, info, map, db);
} else {
// Normal storage for other locations
db[owner][u]={locType:info.name,locClass:info.type,url:u,lastUpdate:today(),
items:Object.entries(map).map(([n,d])=>({name:n,variants:d.variants}))};
}
_db=db; saveDB(db);
}
return cnt;
};
// Artist tool shared storage handling
const handleArtistStorageUpdate = (owner, url, info, newMap, db) => {
const artistViewState = getArtistViewState();
const currentCharacterItems = Object.entries(newMap).map(([n,d])=>({name:n,variants:d.variants}));
// Check if this is the first time viewing or if there are differences
const lastViewKey = `${owner}_${url}`;
const lastView = artistViewState[lastViewKey];
if (!lastView || hasInventoryChanged(lastView.items, currentCharacterItems)) {
// Save the current view as the latest state
artistViewState[lastViewKey] = {
character: owner,
timestamp: Date.now(),
items: currentCharacterItems,
locType: info.name,
locClass: info.type,
url: url,
lastUpdate: today()
};
// Merge with other characters' data for the shared storage
const mergedData = mergeArtistInventories(artistViewState, url, info);
// Store the merged data under a special shared key
const sharedKey = 'SHARED_ARTIST_VEHICLE';
if (!db[sharedKey]) db[sharedKey] = {};
db[sharedKey][url] = mergedData;
saveArtistViewState(artistViewState);
// Log the merge for debugging
logArtistMerge(owner, 'view_updated', currentCharacterItems.length);
}
};
const getArtistViewState = () => {
return gmGet(K.artistView, {});
};
const saveArtistViewState = (state) => {
gmSet(K.artistView, state);
};
const hasInventoryChanged = (oldItems, newItems) => {
if (oldItems.length !== newItems.length) return true;
const oldMap = new Map();
oldItems.forEach(item => {
Object.entries(item.variants).forEach(([variant, count]) => {
oldMap.set(`${item.name}|${variant}`, count);
});
});
const newMap = new Map();
newItems.forEach(item => {
Object.entries(item.variants).forEach(([variant, count]) => {
newMap.set(`${item.name}|${variant}`, count);
});
});
if (oldMap.size !== newMap.size) return true;
for (let [key, count] of oldMap) {
if (newMap.get(key) !== count) return true;
}
return false;
};
const mergeArtistInventories = (viewState, url, info) => {
const mergedItems = new Map();
const contributingCharacters = new Set();
// Collect all items from all characters who have viewed this storage
Object.entries(viewState).forEach(([key, view]) => {
if (key.endsWith(url) && view.items) {
contributingCharacters.add(view.character);
view.items.forEach(item => {
Object.entries(item.variants).forEach(([variant, count]) => {
const itemKey = `${item.name}|${variant}`;
mergedItems.set(itemKey, {
name: item.name,
variant: variant,
count: (mergedItems.get(itemKey)?.count || 0) + count
});
});
});
}
});
// Convert back to the expected format
const itemsByName = new Map();
mergedItems.forEach(({name, variant, count}) => {
if (!itemsByName.has(name)) {
itemsByName.set(name, { name, variants: {} });
}
itemsByName.get(name).variants[variant] = count;
});
return {
locType: info.name + ` (${contributingCharacters.size} characters)`,
locClass: info.type,
url: url,
lastUpdate: today(),
items: Array.from(itemsByName.values()),
contributingCharacters: Array.from(contributingCharacters),
isShared: true
};
};
const logArtistMerge = (character, action, itemCount) => {
const mergeLog = gmGet(K.artistMerge, []);
mergeLog.push({
timestamp: Date.now(),
character,
action,
itemCount,
date: new Date().toISOString()
});
// Keep only last 50 entries
if (mergeLog.length > 50) {
mergeLog.splice(0, mergeLog.length - 50);
}
gmSet(K.artistMerge, mergeLog);
};
// Scan: modal & rapor
const ovStyle = 'position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:9999999;display:flex;align-items:center;justify-content:center;';
const modalWrap = (inner) => '<div style="background:#1a1a1a;border-radius:10px;padding:24px;max-width:420px;width:92%;color:#e0e0e0;font-family:Arial,sans-serif;">'+inner+'</div>';
const showScanModal = (onConfirm) => {
const ov=document.createElement('div'); ov.style.cssText=ovStyle;
ov.innerHTML=modalWrap(
'<div style="font-size:15px;font-weight:bold;color:#3498db;margin-bottom:12px;">'+s('scanTitle')+'</div>'
+'<div style="display:flex;flex-direction:column;gap:10px;margin-bottom:14px;">'
+'<label style="display:flex;align-items:center;gap:8px;font-size:12px;cursor:pointer;"><input type="checkbox" id="sc-p" checked> '+s('scanPersonal')+'</label>'
+'<label style="display:flex;align-items:center;gap:8px;font-size:12px;cursor:pointer;"><input type="checkbox" id="sc-v" checked> '+s('scanVehicles')+'</label>'
+'<label style="display:flex;align-items:center;gap:8px;font-size:12px;cursor:pointer;"><input type="checkbox" id="sc-h" checked> '+s('scanHousing')+'</label>'
+'<label style="display:flex;align-items:center;gap:8px;font-size:12px;cursor:pointer;"><input type="checkbox" id="sc-a" checked> '+s('scanArtist')+'</label>'
+'</div>'
+'<div style="font-size:10px;color:#aaa;background:#222;border-radius:4px;padding:8px;margin-bottom:14px;">'+s('scanWarn')+'</div>'
+'<div style="display:flex;gap:8px;">'
+'<button id="sc-cancel" style="flex:1;padding:10px;background:none;color:#dc3545;border:1px solid #dc3545;border-radius:5px;cursor:pointer;font-weight:bold;">'+s('scanCancel')+'</button>'
+'<button id="sc-start" style="flex:2;padding:10px;background:#0056b3;color:white;border:none;border-radius:5px;cursor:pointer;font-weight:bold;">'+s('scanStart')+'</button>'
+'</div>'
);
document.body.appendChild(ov);
document.getElementById('sc-cancel').onclick=()=>ov.remove();
document.getElementById('sc-start').onclick=()=>{
const opts={personal:document.getElementById('sc-p').checked,vehicles:document.getElementById('sc-v').checked,housing:document.getElementById('sc-h').checked,artist:document.getElementById('sc-a').checked};
if (!Object.values(opts).some(Boolean)){alert(s('scanSelectOne'));return;}
ov.remove(); onConfirm(opts);
};
};
const showScanReport = (state) => {
const db=getDB(); let totI=0,totL=0;
Object.values(db).forEach(od=>{totL+=Object.keys(od).length;Object.values(od).forEach(e=>e.items.forEach(it=>Object.values(it.variants).forEach(c=>{totI+=Number(c)||0;})));});
const ov=document.createElement('div'); ov.style.cssText=ovStyle.replace('9999999','9999998');
ov.innerHTML=modalWrap(
'<div style="font-size:16px;font-weight:bold;color:#2ecc71;margin-bottom:12px;">'+s('scanDone')+'</div>'
+'<div style="background:#222;border-radius:5px;padding:10px;margin-bottom:10px;font-size:13px;">'
+'<div>'+s('scanLocCount')+' <b style="color:#2ecc71">'+totL+'</b></div>'
+'<div>'+s('scanItemCount')+' <b style="color:#2ecc71">'+totI+'</b></div></div>'
+'<div style="background:#222;border-radius:5px;padding:8px;max-height:180px;overflow-y:auto;font-size:11px;font-family:monospace;line-height:1.6;">'
+state.logs.map(l=>'<div style="color:'+(l.startsWith('✅')?'#2ecc71':l.startsWith('⚠️')?'#f39c12':'#e74c3c')+'">'+l+'</div>').join('')
+'</div><button id="sc-done" style="width:100%;margin-top:14px;padding:10px;background:#2ecc71;color:#111;border:none;border-radius:5px;cursor:pointer;font-weight:bold;">'+s('scanClose')+'</button>'
);
document.body.appendChild(ov);
document.getElementById('sc-done').onclick=()=>{ov.remove();location.href=`${location.origin}/World/Popmundo.aspx/Character/${state.charId}`;};
};
// Scan: keşif & adım
const discover = () => {
const url=window.location.href, base=`${location.origin}/World/Popmundo.aspx`;
let id=(url.match(/\/Character\/(?:Details|Items|Vehicles|Housing)\/(\d+)/)||[])[1]
||document.querySelector('a[href*="/Character/Details/"]')?.href.match(/\/(\d+)$/)?.[1]
||document.querySelector('a[href*="/Character/Items/"]')?.href.match(/\/(\d+)$/)?.[1]
||GM_getValue(K.char,'');
if (!id){const inp=prompt(s('scanAskId'));if(inp&&/^\d+$/.test(inp.trim()))id=inp.trim();else{alert(s('scanBadId'));return;}}
GM_setValue(K.char,id);
showScanModal(opts=>{
const q=[];
if(opts.personal)q.push(`${base}/Character/Items/${id}`);
if(opts.vehicles)q.push(`${base}/Character/Vehicles/${id}`);
if(opts.housing) q.push(`${base}/Character/Housing/${id}`);
if(opts.artist) q.push(`${base}/Artist/VehicleItems/`);
saveScan({active:true,queue:q,index:0,logs:[],charId:id});
location.href=q[0];
});
};
const runStep = () => {
if (!window.location.href.includes('/World/Popmundo.aspx/')) return;
let st=getScan(); if (!st.active) return;
setTimeout(()=>{
const u=window.location.href; let found=[];
if (u.includes('Character/Vehicles/')) {
const anchors=document.querySelectorAll('#tablevehicles a[href*="/Character/Vehicle/"]');
const veh=gmGet(K.veh,{});
anchors.forEach(a=>{if(a.href&&a.innerText.trim())veh[a.href]=a.innerText.trim();});
gmSet(K.veh,veh); found=Array.from(anchors).map(a=>a.href);
st.logs.push(`${s('scanVehList')} ${found.length} ${s('scanVehUnit')}`);
} else if (u.includes('Character/Housing/')) {
found=Array.from(document.querySelectorAll('#tablelocales a[href*="/Locale/"]'))
.filter(a=>/\/Locale\/\d+$/.test(a.getAttribute('href')))
.map(a=>{const id=a.getAttribute('href').match(/\/Locale\/(\d+)$/)?.[1];return id?`${location.origin}/World/Popmundo.aspx/Locale/ItemsEquipment/${id}`:null;})
.filter(Boolean);
st.logs.push(`${s('scanHouseList')} ${found.length} ${s('scanHouseUnit')}`);
} else {
const cnt=updatePage(), info=getLocInfo(u);
st.logs.push(cnt>0?`✅ ${info.name} (${info.type}): ${cnt} ${s('scanLogOk')}`:`⚠️ ${info.name}: ${s('scanLogEmpty')}`);
}
if (found.length) st.queue.splice(st.index+1,0,...found);
st.index++;
if (st.index<st.queue.length){saveScan(st);location.href=st.queue[st.index];}
else{const fs={...st};lsDel(K.scan);GM_setValue('pop_last_scan',today());showScanReport(fs);}
},randDelay());
};
// Panel: temalar
const CD = { bg:'#111',text:'#e0e0e0',border:'#444',hBg:'#1a1a1a',hBorder:'#444',subBg:'#1a1a1a',inBg:'#222',inText:'#eee',inBorder:'#555',detBg:'#1a1a1a',detBorder:'#444',owBg:'#2a2a2a',owText:'#f0ad4e',loc:'#5bc0de',lbl:'#aaa',muted:'#777',togBg:'#222',togText:'#ccc',togBorder:'#555',taBg:'#222',resBorder:'#2a2a2a',gtBg:'#1e3a1e',gtBorder:'#2ecc71',gtText:'#2ecc71',vtBg:'#1a2535',vtBorder:'#3498db',vtText:'#aad4f5',rowText:'#e0e0e0',countText:'#2ecc71',owText2:'#f0ad4e',link:'#5bc0de',sep:'#555',sec:'#95a5a6',dragHint:'#666',priceBg:'#1a1a1a',priceBorder:'#444',priceText:'#ccc' };
const CL = { bg:'#f5f5f5',text:'#222',border:'#bbb',hBg:'#e0e0e0',hBorder:'#ccc',subBg:'#e8e8e8',inBg:'#fff',inText:'#222',inBorder:'#aaa',detBg:'#ececec',detBorder:'#ccc',owBg:'#ddd',owText:'#555',loc:'#1a6896',lbl:'#555',muted:'#888',togBg:'#ddd',togText:'#333',togBorder:'#bbb',taBg:'#fff',resBorder:'#ddd',gtBg:'#d4edda',gtBorder:'#28a745',gtText:'#155724',vtBg:'#cce5ff',vtBorder:'#004085',vtText:'#004085',rowText:'#333',countText:'#155724',owText2:'#7d4f00',link:'#1a6896',sep:'#aaa',sec:'#555',dragHint:'#999',priceBg:'#fff',priceBorder:'#aaa',priceText:'#222' };
const C = () => theme()==='light' ? CL : CD;
// Panel: CSV export / import
const exportDetailed = () => {
const db=getDB(), ds=today();
let csv='\uFEFFItem Category;Item Name;Variant;Qty;Owner;Class;Inventory Name;Date;URL;English;Italian;Spanish;Portuguese/Portugal;Portuguese/Brazil;Turkish\n';
Object.keys(db).forEach(owner=>Object.keys(db[owner]).forEach(url=>{
const e=db[owner][url], rel=url.replace(/^https?:\/\/[^/]+/,'');
e.items.forEach(item=>{
const d = getDict(item.name);
let ln = item.name;
if(LANG==='TR'&&d.tr) ln=d.tr; else if(LANG==='EN'&&d.en) ln=d.en; else if(LANG==='PT-BR'&&d.pt_br) ln=d.pt_br;
Object.entries(item.variants).forEach(([v,c])=>
csv+=`"${d.cat}";"${ln}";"${v}";${c};"${owner}";"${e.locClass||'Genel'}";"${e.locType}";"${e.lastUpdate||ds}";"${rel}";"${d.en||''}";"${d.it||''}";"${d.es||''}";"${d.pt_pt||''}";"${d.pt_br||''}";"${d.tr||''}"\n`
);
});
}));
dlCSV(csv,`Pop_depot_${ds}.csv`);
};
const exportSummary = () => {
const db=getDB(), ds=today(), prices=getPrices(), sum={};
Object.values(db).forEach(od=>Object.values(od).forEach(e=>e.items.forEach(item=> {
const d = getDict(item.name);
Object.entries(item.variants).forEach(([v,c])=>{
const cleanV = v ? v.replace(/\.\s*$/,'').trim() : '';
const k=priceKey(item.name,cleanV);
if (!sum[k]) sum[k]={name:item.name, variant:cleanV, count:0, dict:d};
sum[k].count+=c;
});
})));
let csv='\uFEFFItem Category;Item Name;Variant;Total Qty;Unit Price;English;Italian;Spanish;Portuguese/Portugal;Portuguese/Brazil;Turkish\n';
Object.values(sum).forEach(x=>{
const rawP=prices[priceKey(x.name,x.variant)]||'0';
let ln = x.name;
if(LANG==='TR'&&x.dict.tr) ln=x.dict.tr; else if(LANG==='EN'&&x.dict.en) ln=x.dict.en; else if(LANG==='PT-BR'&&x.dict.pt_br) ln=x.dict.pt_br;
csv+=`"${x.dict.cat}";"${ln}";"${x.variant}";${x.count};${rawP};"${x.dict.en||''}";"${x.dict.it||''}";"${x.dict.es||''}";"${x.dict.pt_pt||''}";"${x.dict.pt_br||''}";"${x.dict.tr||''}"\n`;
});
dlCSV(csv,`Pop_Sales_${ds}.csv`);
};
// Panel: CSV viewing/editing system
const showCSVEditor = () => {
const db = getDB();
const c = C();
const modal = document.createElement('div');
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:9999999;display:flex;align-items:center;justify-content:center;';
const content = document.createElement('div');
content.style.cssText = 'background:#1a1a1a;border-radius:10px;padding:20px;max-width:90vw;width:90vw;max-height:90vh;overflow-y:auto;color:#e0e0e0;font-family:Arial,sans-serif;';
content.innerHTML =
'<div style="font-size:16px;font-weight:bold;color:#f0ad4e;margin-bottom:15px;">'+s('csvEditorTitle')+'</div>'
+'<div style="margin-bottom:15px;">'
+'<label style="color:#aaa;font-size:12px;display:block;margin-bottom:5px;">'+s('csvFormat')+'</label>'
+'<select id="csv-format-select" style="width:100%;padding:6px;background:#222;color:#eee;border:1px solid #555;border-radius:4px;font-size:12px;margin-bottom:10px;">'
+'<option value="detailed">'+s('csvFmtDetailed')+'</option>'
+'<option value="summary">'+s('csvFmtSummary')+'</option>'
+'<option value="forum">'+s('csvFmtForum')+'</option>'
+'</select>'
+'<label style="color:#aaa;font-size:12px;display:block;margin-bottom:5px;">'+s('csvDataLabel')+'</label>'
+'<textarea id="csv-editor-textarea" style="width:100%;height:300px;font-size:11px;background:#222;color:#eee;border:1px solid #555;border-radius:4px;resize:vertical;box-sizing:border-box;font-family:monospace;white-space:pre;overflow-wrap:normal;"></textarea>'
+'</div>'
+'<div style="display:flex;gap:10px;margin-bottom:15px;">'
+'<button id="csv-copy-btn" style="flex:1;padding:8px;background:#17a2b8;color:white;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">'+s('csvCopy')+'</button>'
+'<button id="csv-download-btn" style="flex:1;padding:8px;background:#28a745;color:white;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">'+s('csvDownload')+'</button>'
+'<button id="csv-apply-btn" style="flex:1;padding:8px;background:#ffc107;color:#111;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">'+s('csvApply')+'</button>'
+'</div>'
+'<div id="csv-status" style="font-size:12px;color:#aaa;margin-bottom:10px;"></div>'
+'<button id="close-csv-btn" style="width:100%;padding:8px;background:#6c757d;color:white;border:none;border-radius:4px;cursor:pointer;">'+s('pcClose')+'</button>';
modal.appendChild(content);
document.body.appendChild(modal);
const textarea = document.getElementById('csv-editor-textarea');
const statusDiv = document.getElementById('csv-status');
const formatSelect = document.getElementById('csv-format-select');
const generateCSVData = () => {
const format = formatSelect ? formatSelect.value : 'detailed';
const ds = today();
if (format === 'detailed') {
let csv = '\uFEFFItem Category;Item Name;Variant;Qty;Owner;Class;Inventory Name;Date;URL;English;Italian;Spanish;Portuguese/Portugal;Portuguese/Brazil;Turkish\n';
Object.keys(db).forEach(owner=>Object.keys(db[owner]).forEach(url=>{
const e=db[owner][url], rel=url.replace(/^https?:\/\/[^/]+/,'');
e.items.forEach(item=>{
const d = getDict(item.name);
let ln = item.name;
if(LANG==='TR'&&d.tr) ln=d.tr; else if(LANG==='EN'&&d.en) ln=d.en; else if(LANG==='PT-BR'&&d.pt_br) ln=d.pt_br;
Object.entries(item.variants).forEach(([v,c])=>
csv+=`"${d.cat}";"${ln}";"${v}";${c};"${owner}";"${e.locClass||'Genel'}";"${e.locType}";"${e.lastUpdate||ds}";"${rel}";"${d.en||''}";"${d.it||''}";"${d.es||''}";"${d.pt_pt||''}";"${d.pt_br||''}";"${d.tr||''}"\n`
);
});
}));
return csv;
} else if (format === 'summary') {
const prices=getPrices(), sum={};
Object.values(db).forEach(od=>Object.values(od).forEach(e=>e.items.forEach(item=> {
const d = getDict(item.name);
Object.entries(item.variants).forEach(([v,c])=>{
const cleanV = v ? v.replace(/\.\s*$/,'').trim() : '';
const k=priceKey(item.name,cleanV);
if (!sum[k]) sum[k]={name:item.name, variant:cleanV, count:0, dict:d};
sum[k].count+=c;
});
})));
let csv = '\uFEFFItem Category;Item Name;Variant;Total Qty;Unit Price;English;Italian;Spanish;Portuguese/Portugal;Portuguese/Brazil;Turkish\n';
Object.values(sum).forEach(x=>{
const rawP=prices[priceKey(x.name,x.variant)]||'0';
let ln = x.name;
if(LANG==='TR'&&x.dict.tr) ln=x.dict.tr; else if(LANG==='EN'&&x.dict.en) ln=x.dict.en; else if(LANG==='PT-BR'&&x.dict.pt_br) ln=x.dict.pt_br;
csv+=`"${x.dict.cat}";"${ln}";"${x.variant}";${x.count};${rawP};"${x.dict.en||''}";"${x.dict.it||''}";"${x.dict.es||''}";"${x.dict.pt_pt||''}";"${x.dict.pt_br||''}";"${x.dict.tr||''}"\n`;
});
return csv;
} else if (format === 'forum') {
const prices=getPrices(), items=[];
Object.values(db).forEach(od=>Object.values(od).forEach(e=>e.items.forEach(item=> {
const d = getDict(item.name);
Object.entries(item.variants).forEach(([v,c])=>{
const cleanV = v ? v.replace(/\.\s*$/,'').trim() : '';
const k=priceKey(item.name,cleanV);
const price=prices[k]||'';
let ln = item.name;
if(LANG==='TR'&&d.tr) ln=d.tr; else if(LANG==='EN'&&d.en) ln=d.en; else if(LANG==='PT-BR'&&d.pt_br) ln=d.pt_br;
// Extract usage info from variant
let usageInfo = '1';
const usageMatch = cleanV.match(/(\d+\s*(?:uses?|kullanımlık|kullanım)(?:\s*left\.?)?)/i);
if (usageMatch) {
usageInfo = usageMatch[1];
}
items.push(`${ln}|${usageInfo}|${price}`);
});
})));
return items.join('\n');
}
};
const updateTextarea = () => {
if (textarea) {
textarea.value = generateCSVData();
updateStatus(s('csvUpdated'), '#2ecc71');
}
};
const updateStatus = (message, color = '#aaa') => {
if (statusDiv) {
statusDiv.textContent = message;
statusDiv.style.color = color;
}
};
// Event listeners
document.getElementById('csv-copy-btn').addEventListener('click', () => {
textarea.select();
document.execCommand('copy');
updateStatus(s('csvCopied'), '#2ecc71');
});
document.getElementById('csv-download-btn').addEventListener('click', () => {
const format = formatSelect.value;
const filename = `Pop_${format}_${today()}.csv`;
dlCSV(textarea.value, filename);
updateStatus(filename + ' ' + s('alertDeleted'), '#2ecc71');
});
document.getElementById('csv-apply-btn').addEventListener('click', () => {
const format = formatSelect.value;
if (format === 'detailed' || format === 'summary') {
updateStatus(s('csvApplyUnsup'), '#f39c12');
} else if (format === 'forum') {
try {
const lines = textarea.value.split('\n').filter(line => line.trim());
const prices = getPrices();
let updatedCount = 0;
lines.forEach(line => {
const parts = line.split('|').map(p => p.trim());
if (parts.length >= 3) {
const itemName = parts[0];
const usageInfo = parts[1];
const price = parts[2];
// Extract variant from usage info
let variant = '';
if (usageInfo !== '1') {
variant = usageInfo;
}
const k = priceKey(itemName, variant);
if (price && prices[k] !== price) {
prices[k] = price;
updatedCount++;
// Ortak fiyat geçmişine de yaz
const numVal = parsePrice(price);
if (numVal >= 10000) addSharedPrice(itemName, variant, numVal);
}
}
});
if (updatedCount > 0) {
savePrices(prices);
updateStatus(updatedCount + ' ' + _D('fiyat güncellendi!','prices updated!','preços atualizados!'), '#2ecc71');
} else {
updateStatus(s('csvNoPrice'), '#f39c12');
}
} catch (error) {
updateStatus(s('csvErrPrefix') + error.message, '#dc3545');
}
}
});
formatSelect.addEventListener('change', updateTextarea);
document.getElementById('close-csv-btn').addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
// Auto-resize textarea based on content
const resizeTextarea = () => {
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.max(300, Math.min(500, textarea.scrollHeight)) + 'px';
}
};
textarea.addEventListener('input', resizeTextarea);
// Initialize with data
updateTextarea();
setTimeout(resizeTextarea, 100);
};
// Panel: ayar yedek
const exportSettings = () => {
const out={}, gmKeys=[K.db,K.cat,K.price,K.veh,K.char,K.owner,K.theme];
gmKeys.forEach(k=>{const v=GM_getValue(k,null);if(v)out['gm_'+k]=v;});
const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([JSON.stringify(out,null,2)],{type:'application/json'})); a.download=`popmundo_yedek_${today()}.json`; a.click();
};
const importCSV = (txt) => {
try {
const lines=txt.split(/\r?\n/),delim=txt.includes(';')?';':',',newDB={};
const h=lines[0].split(delim).map(x=>x.replace(/^"|"$/g,'').trim());
const [iN,iV,iO,iT,iU]=['Item Name','Variant','Owner','Inventory Name','URL'].map(x=>h.indexOf(x));
const iC=h.indexOf(h.find(x=>x.includes('Qty')));
for (let i=1;i<lines.length;i++) {
if (!lines[i].trim()) continue;
const c=lines[i].split(delim).map(x=>x.replace(/^"|"$/g,'').trim());
const owner=c[iO]||s('locUnknown'),url=c[iU]||'';
if (!newDB[owner]) newDB[owner]={};
if (!newDB[owner][url]) newDB[owner][url]={locType:c[iT],url,items:[]};
let ex=newDB[owner][url].items.find(it=>it.name===c[iN]);
if (!ex){ex={name:c[iN],variants:{}};newDB[owner][url].items.push(ex);}
ex.variants[c[iV]]=(ex.variants[c[iV]]||0)+(parseInt(c[iC])||0);
}
saveDB(newDB); location.reload();
} catch(e){alert(_D('Hata: ','Error: ','Erro: ')+e.message);}
};
const parseForumList = (txt) => {
// ── Yapışık mesaj ön işleme: "Name x1Name x1" → "Name x1\nName x1" ──
// Açıklama cümlelerini temizle (I've prepared... gibi)
let processed = txt
.replace(/i['']ve prepared[^.!?\n]*/gi, '')
.replace(/i['']d like[^.!?\n]*/gi, '')
.replace(/might interest[^.!?\n]*/gi, '')
.replace(/starting price[^.!?\n]*/gi, '')
.trim();
// x\d+ ardından büyük harf geliyorsa satır sonu ekle (yapışık format)
processed = processed.replace(/x(\d+)\s*([A-Z])/g, 'x$1\n$2');
const lines = processed.split('\n').filter(line => line.trim());
const items = [];
lines.forEach(line => {
line = line.trim();
if (!line) return;
// Format 1: Eşya Adı|Kullanım Bilgisi|Fiyat (preferred format)
if (line.includes('|')) {
const parts = line.split('|').map(p => p.trim());
if (parts.length >= 3) {
const itemName = parts[0];
const usageInfo = parts[1];
const price = parts[2];
// Clean usage info for display
let cleanUsage = usageInfo;
if (usageInfo === '1' || usageInfo.toLowerCase() === 'single') {
cleanUsage = '';
} else {
cleanUsage = usageInfo.replace(/\s*uses?\s*left\.?$/i, ' uses left')
.replace(/\s*kullanımlık$/i, ' kullanımlık')
.replace(/\s*kullanım$/i, ' kullanım');
}
let displayName = itemName;
if (cleanUsage) {
displayName = `${itemName} (${cleanUsage})`;
}
items.push({
name: itemName,
displayName: displayName,
usage: usageInfo,
price: price,
priceValue: parsePrice(price),
format: 'structured'
});
return;
}
}
// Legacy formats - fallback for backward compatibility
// Fiyat tespiti - satırın sonunda fiyat var mı?
const priceMatch = line.match(/(\d+[.,]?\d*[kmb]?)\s*$/i);
let price = '';
let name = line;
if (priceMatch) {
price = priceMatch[1];
name = line.slice(0, line.length - price.length).trim();
}
// Format 2: x64 - Snowball (5 uses) - 4m
let match = name.match(/^([xX]\d+)\s*[-—>»]+\s*(.+?)\s*[-—>»]+\s*(.+?)$/);
if (match) {
let itemName = match[2].trim();
let usageInfo = '';
// Extract usage information
const usageMatch = match[3].match(/\((\d+\s*(?:uses?|kullanımlık|kullanım)(?:\s*left\.?)?)\)/i);
if (usageMatch) {
usageInfo = usageMatch[1];
} else if (match[3].match(/\d+\s*(?:uses?|kullanımlık|kullanım)(?:\s*left\.?)?/i)) {
usageInfo = match[3].match(/(\d+\s*(?:uses?|kullanımlık|kullanım)(?:\s*left\.?)?)/i)[1];
}
if (!price) price = match[3].trim();
// Clean item name
itemName = itemName
.replace(/\s*\d+\s*kullanımlık\s*$/i, '')
.replace(/\s*\d+\s*uses?\s*left\.?$/i, '')
.replace(/\s*\(\s*\d+\s*uses?\s*\)\s*$/i, '')
.replace(/\s*\d+\s*kullanım\s*$/i, '')
.trim();
items.push({
name: itemName,
displayName: usageInfo ? `${itemName} (${usageInfo})` : itemName,
usage: usageInfo || '1',
price: price,
priceValue: parsePrice(price),
format: 'legacy_x_format'
});
return;
}
// Format 3: Eşya adı >> fiyat
match = name.match(/^(.+?)\s*>>\s*(.+?)$/);
if (match) {
let itemName = match[1].trim();
if (!price) price = match[2].trim();
items.push({
name: itemName,
displayName: itemName,
usage: '1',
price: price,
priceValue: parsePrice(price),
format: 'legacy_arrow'
});
return;
}
// Format 4: Eşya adı - fiyat (farklı ayraçlar)
match = name.match(/^(.+?)\s*[-—>»]\s*(.+?)$/);
if (match) {
let itemName = match[1].trim();
if (!price) price = match[2].trim();
items.push({
name: itemName,
displayName: itemName,
usage: '1',
price: price,
priceValue: parsePrice(price),
format: 'legacy_dash'
});
return;
}
// Format 5: Satır sonunda fiyat var
if (price) {
let itemName = name
.replace(/\s*\d+\s*kullanımlık\s*$/i, '')
.replace(/\s*\d+\s*uses?\s*left\.?$/i, '')
.replace(/\s*\(\s*\d+\s*uses?\s*\)\s*$/i, '')
.replace(/\s*\d+\s*kullanım\s*$/i, '')
.trim();
items.push({
name: itemName,
displayName: itemName,
usage: '1',
price: price,
priceValue: parsePrice(price),
format: 'legacy_price_only'
});
return;
}
// Format 6: Sadece fiyat satırı (altındaki eşyalar için)
if (line.match(/^[0-9.,]+[kmb]?$/i)) {
const lastItem = items[items.length - 1];
if (lastItem && !lastItem.price) {
lastItem.price = line.trim();
lastItem.priceValue = parsePrice(line.trim());
}
return;
}
// Format 7: Sadece eşya adı (sonraki satırda fiyat olacak)
if (!line.includes('-') && !line.includes('>>') && !line.includes('x') && !line.match(/^[0-9.,]+[kmb]?$/i)) {
items.push({
name: line,
displayName: line,
usage: '1',
price: '',
priceValue: 0,
format: 'legacy_name_only'
});
return;
}
});
return items;
};
const showForumMatcher = () => showPriceCheck();
const showPriceCheck = () => {
const db = getDB();
const c = C();
// ── Envanter topla ──
const myItems = [];
Object.values(db).forEach(ownerData => Object.values(ownerData).forEach(location => {
location.items.forEach(item => {
Object.entries(item.variants).forEach(([variant, count]) => {
const d = getDict(item.name);
myItems.push({ name: item.name, variant, count,
en: d.en||'', tr: d.tr||'', pt_br: d.pt_br||'', it: d.it||'', es: d.es||'' });
});
});
}));
// ── Normalize & match helper ──
const norm = str => (str||'').toLowerCase()
.replace(/[çÇ]/g,'c').replace(/[şŞ]/g,'s').replace(/[ğĞ]/g,'g')
.replace(/[ıİ]/g,'i').replace(/[öÖ]/g,'o').replace(/[üÜ]/g,'u')
.replace(/[^a-z0-9\s]/g,' ').replace(/\s+/g,' ').trim();
const findInInventory = (searchName) => {
const sn = norm(searchName);
const results = [];
const seen = new Set();
myItems.forEach(item => {
const names = [item.name, item.en, item.tr, item.pt_br, item.it, item.es];
const match = names.some(n => {
if (!n) return false;
const nn = norm(n);
if (nn === sn) return true;
// Fuzzy: tüm kelimeler eşleşiyor mu?
const sWords = sn.split(' ').filter(w=>w.length>1);
const nWords = nn.split(' ').filter(w=>w.length>1);
if (sWords.length && nWords.length) {
const common = sWords.filter(sw => nWords.some(nw => nw.includes(sw)||sw.includes(nw)));
if (common.length / Math.max(sWords.length, nWords.length) >= 0.6) return true;
}
return false;
});
if (match) {
const key = item.name+'|'+item.variant;
if (!seen.has(key)) { seen.add(key); results.push(item); }
}
});
return results;
};
// ── Fiyat geçmişi popup ──
const showPriceHistory = (name, variant, anchorEl) => {
document.getElementById('pc-hist-popup')?.remove();
const sp = getSharedPrices();
const k = priceKey(name, variant);
const data = sp[k];
const popup = document.createElement('div');
popup.id = 'pc-hist-popup';
popup.style.cssText = 'position:fixed;z-index:99999999;background:#222;border:1px solid #f0ad4e;border-radius:6px;padding:10px;min-width:200px;box-shadow:0 4px 16px rgba(0,0,0,.6);font-size:11px;color:#eee;';
const rect = anchorEl.getBoundingClientRect();
popup.style.left = Math.min(rect.left, window.innerWidth-220)+'px';
popup.style.top = (rect.bottom+4)+'px';
if (!data || !data.prices.length) {
popup.innerHTML = '<div style="color:#aaa;font-style:italic;">'+s('pcNoHistory')+'</div>';
} else {
const rows = [...data.prices].reverse().map(e =>
`<div style="display:flex;justify-content:space-between;gap:12px;padding:3px 0;border-bottom:1px solid #333;">
<span style="color:#f0ad4e;font-weight:bold;">${e.p.toLocaleString('tr-TR')} M$</span>
<span style="color:#888;">${e.d}</span>
</div>`
).join('');
popup.innerHTML = `<div style="font-weight:bold;color:#f0ad4e;margin-bottom:6px;">📋 ${esc(name)}</div>${rows}`;
}
const closeB = document.createElement('button');
closeB.textContent = '✕'; closeB.style.cssText = 'margin-top:6px;width:100%;background:#444;color:#eee;border:none;border-radius:3px;cursor:pointer;padding:3px;font-size:10px;';
closeB.onclick = () => popup.remove();
popup.appendChild(closeB);
document.body.appendChild(popup);
setTimeout(() => document.addEventListener('click', function h(e){ if(!popup.contains(e.target)){popup.remove();document.removeEventListener('click',h);} }, {once:false}), 50);
};
// ── Modal ──
const modal = document.createElement('div');
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:9999999;display:flex;align-items:center;justify-content:center;';
const content = document.createElement('div');
content.style.cssText = 'background:#1a1a1a;border-radius:10px;padding:20px;max-width:680px;width:95%;max-height:88vh;display:flex;flex-direction:column;color:#e0e0e0;font-family:Arial,sans-serif;';
content.innerHTML =
'<div style="font-size:16px;font-weight:bold;color:#f0ad4e;margin-bottom:12px;flex-shrink:0;">'+s('pcTitle')+'</div>'
+'<div style="flex-shrink:0;margin-bottom:10px;">'
+'<label style="color:#aaa;font-size:11px;display:block;margin-bottom:5px;">'+s('pcPasteLbl')+'</label>'
+'<textarea id="pc-input" placeholder="'+s('pcPastePh')+'" style="width:100%;height:90px;font-size:11px;background:#222;color:#eee;border:1px solid #555;border-radius:4px;resize:vertical;box-sizing:border-box;padding:6px;"></textarea>'
+'</div>'
+'<div style="display:flex;gap:8px;margin-bottom:10px;flex-shrink:0;">'
+'<button id="pc-process" style="flex:2;padding:8px;background:#6f42c1;color:white;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">'+s('pcProcess')+'</button>'
+'<button id="pc-ai" style="flex:1;padding:8px;background:#17a2b8;color:white;border:none;border-radius:4px;cursor:pointer;font-size:11px;">'+s('pcAIBtn')+'</button>'
+'<button id="pc-close" style="flex:1;padding:8px;background:#6c757d;color:white;border:none;border-radius:4px;cursor:pointer;">'+s('pcClose')+'</button>'
+'</div>'
+'<div id="pc-results" style="overflow-y:auto;flex:1;min-height:0;"></div>';
modal.appendChild(content);
document.body.appendChild(modal);
// ── Render results ──
const renderResults = (items) => {
const res = document.getElementById('pc-results');
if (!items.length) { res.innerHTML = '<p style="color:#aaa;font-style:italic;font-size:12px;">'+s('pcNoInput')+'</p>'; return; }
let applyAllCount = 0;
const pendingPrices = []; // {name, variant, price} eklenecekler
let html = '<div style="font-size:11px;font-weight:bold;color:#aaa;margin-bottom:8px;">'+s('pcResults')+'</div>';
html += '<table style="width:100%;border-collapse:collapse;font-size:11px;">';
html += '<tr style="background:#2a2a2a;"><th style="padding:5px 6px;text-align:left;color:#888;">'+s('btnSavePage').replace(/[📍]/g,'').trim()+'</th><th style="text-align:right;padding:5px 6px;color:#888;">'+s('pcFound')+'</th><th style="text-align:right;padding:5px 6px;color:#888;">'+s('pcLastPrice')+'</th><th style="text-align:right;padding:5px 6px;color:#888;">'+s('pcForumPrice')+'</th><th style="padding:5px 6px;"></th></tr>';
items.forEach((item, idx) => {
const matches = findInInventory(item.name);
const totalCount = matches.reduce((s,m)=>s+m.count,0);
const inInv = matches.length > 0;
// Fiyat geçmişi — ilk match'in adını kullan
const spKey = inInv ? matches[0].name : item.name;
const spVariant = inInv ? (matches[0].variant||'') : '';
const spData = getLastSharedPrice(spKey, spVariant);
const lastPriceHtml = spData
? `<button class="pc-hist-btn" data-idx="${idx}" data-name="${esc(spKey)}" data-variant="${esc(spVariant)}" style="background:none;border:none;color:#f0ad4e;cursor:pointer;font-size:11px;padding:1px 4px;border-radius:3px;border:1px solid #f0ad4e44;">${spData.last.toLocaleString('tr-TR')} M$</button>`
: '<span style="color:#666;">—</span>';
const forumPriceHtml = item.price
? `<span style="color:#2ecc71;">${esc(item.price)}</span>`
: '<span style="color:#555;">—</span>';
const addBtnHtml = item.price && parsePrice(item.price) >= 10000
? `<button class="pc-add-btn" data-idx="${idx}" data-name="${esc(inInv?matches[0].name:item.name)}" data-variant="${esc(inInv?matches[0].variant:'')}${''}" data-price="${esc(item.price)}" style="padding:2px 6px;background:#28a745;color:white;border:none;border-radius:3px;cursor:pointer;font-size:10px;white-space:nowrap;">${s('pcAddPrice')}</button>`
: '';
if (addBtnHtml) { applyAllCount++; pendingPrices.push({name:inInv?matches[0].name:item.name, variant:inInv?matches[0].variant:'', price:item.price, idx}); }
const rowBg = inInv ? '#1a2a1a' : '#1a1a1a';
const nameColor = inInv ? '#2ecc71' : '#aaa';
html += `<tr style="background:${rowBg};border-bottom:1px solid #2a2a2a;">
<td style="padding:5px 6px;color:${nameColor};">${esc(item.displayName||item.name)}</td>
<td style="padding:5px 6px;text-align:right;color:${inInv?'#2ecc71':'#dc3545'};white-space:nowrap;">${inInv?totalCount+' ×':s('pcNotFound')}</td>
<td style="padding:5px 6px;text-align:right;">${lastPriceHtml}</td>
<td style="padding:5px 6px;text-align:right;">${forumPriceHtml}</td>
<td style="padding:5px 6px;text-align:right;">${addBtnHtml}</td>
</tr>`;
});
html += '</table>';
if (applyAllCount > 1) {
html += `<button id="pc-apply-all" style="width:100%;margin-top:8px;padding:7px;background:#28a745;color:white;border:none;border-radius:4px;cursor:pointer;font-weight:bold;font-size:12px;">✓ ${s('pcApplyAll')} (${applyAllCount})</button>`;
}
res.innerHTML = html;
// History popup buttons
res.querySelectorAll('.pc-hist-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
showPriceHistory(btn.dataset.name, btn.dataset.variant, btn);
});
});
// Add price buttons
res.querySelectorAll('.pc-add-btn').forEach(btn => {
btn.addEventListener('click', () => {
const numVal = parsePrice(btn.dataset.price);
if (numVal >= 10000) {
addSharedPrice(btn.dataset.name, btn.dataset.variant, numVal);
// Also write to pop_price_data
const p = getPrices(); p[priceKey(btn.dataset.name, btn.dataset.variant)] = btn.dataset.price;
savePrices(p);
btn.textContent = s('pcAdded'); btn.style.background='#17a2b8'; btn.disabled=true;
}
});
});
// Apply all
document.getElementById('pc-apply-all')?.addEventListener('click', () => {
const p = getPrices(); let cnt=0;
pendingPrices.forEach(({name,variant,price}) => {
const numVal = parsePrice(price);
if (numVal>=10000) { addSharedPrice(name,variant,numVal); p[priceKey(name,variant)]=price; cnt++; }
});
savePrices(p);
res.querySelectorAll('.pc-add-btn').forEach(b=>{ b.textContent=s('pcAdded'); b.style.background='#17a2b8'; b.disabled=true; });
document.getElementById('pc-apply-all').textContent = cnt+' '+s('pcApplied');
document.getElementById('pc-apply-all').disabled=true;
});
};
// ── Buton eventleri ──
document.getElementById('pc-process').addEventListener('click', () => {
const input = document.getElementById('pc-input').value;
if (!input.trim()) { document.getElementById('pc-results').innerHTML='<p style="color:#dc3545;">'+s('pcNoInput')+'</p>'; return; }
const items = parseForumList(input);
renderResults(items);
});
// AI fallback: rentry.org/DepotFiyat prompt kopyala + Gemini aç
document.getElementById('pc-ai').addEventListener('click', () => {
const promptUrl = 'https://rentry.org/DepotFiyat/raw';
fetch(promptUrl).then(r=>r.text()).then(promptText => {
const userInput = document.getElementById('pc-input').value;
const fullPrompt = promptText.trim() + '\n\n---\n\nGirdi:\n' + userInput;
navigator.clipboard.writeText(fullPrompt).then(() => {
window.open('https://gemini.google.com', '_blank');
alert(s('pcAIHint'));
});
}).catch(() => {
navigator.clipboard.writeText('https://rentry.org/DepotFiyat — promptu al ve uygulan');
window.open('https://gemini.google.com', '_blank');
});
});
document.getElementById('pc-close').addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
};
const importSettings = (txt) => {
try {
const d=JSON.parse(txt); if (typeof d!=='object') throw new Error();
Object.entries(d).forEach(([k,v])=>{ if (k.startsWith('gm_')) GM_setValue(k.slice(3),v); });
alert(s('alertRestored')); location.reload();
} catch { alert(s('alertInvalidFile')); }
};
// Panel: fiyat
const parsePrice = (str) => {
if (!str) return 0;
const v=String(str).trim().toLowerCase().replace(',','.');
const n=parseFloat(v); if (isNaN(n)) return 0;
if (v.endsWith('m')) return n*1000000;
if (v.endsWith('k')) return n*1000;
return n;
};
const doSavePrice = (inp) => {
const name=inp.dataset.name,variant=inp.dataset.variant; if (!name) return;
const cleanVariant = variant ? variant.replace(/\.\s*$/,'').trim() : '';
const raw=inp.value.trim(),p=getPrices(),k=priceKey(name,cleanVariant);
if (raw) p[k]=raw; else delete p[k]; savePrices(p);
const numVal=parsePrice(raw),count=parseInt(inp.dataset.count)||0;
// Ortak fiyat geçmişine de yaz
if (numVal >= 10000) addSharedPrice(name, cleanVariant, numVal);
const valEl=inp.closest('.pi-vt-row')&&inp.closest('.pi-vt-row').querySelector('.pi-val-span');
if (valEl) valEl.textContent=numVal>0?'= '+(numVal*count).toLocaleString('tr-TR'):'';
inp.style.borderColor='#2ecc71'; setTimeout(()=>{inp.style.borderColor='';},1000);
};
// Panel: liste & arama
let invListOpen=false;
const clsLbl=(c)=>({Kişisel:s('clsPersonal'),Araç:s('clsVehicle'),Mekan:s('clsHousing'),'Grup Aracı':s('clsArtist')}[c]||c||s('clsPersonal'));
// Multi-character selection state
let selectedCharacters = new Set();
let allCharacters = [];
const _showDepotPriceHistory = (name, variant, anchorEl) => {
document.getElementById('dp-hist-popup')?.remove();
const c = C();
const sp = getLastSharedPrice(name, variant);
const popup = document.createElement('div');
popup.id = 'dp-hist-popup';
popup.style.cssText = 'position:fixed;z-index:9999999;background:'+c.hBg+';border:1px solid #f0ad4e;border-radius:6px;padding:10px;min-width:200px;box-shadow:0 4px 16px rgba(0,0,0,.5);font-size:11px;color:'+c.text+';';
const rect = anchorEl.getBoundingClientRect();
popup.style.left = Math.min(rect.left, window.innerWidth-220)+'px';
popup.style.top = (rect.bottom+4)+'px';
const titleDiv = document.createElement('div');
titleDiv.style.cssText = 'font-weight:bold;color:#f0ad4e;margin-bottom:8px;font-size:12px;';
titleDiv.textContent = '💰 '+name+(variant?' ('+variant+')':'');
popup.appendChild(titleDiv);
if (!sp || !sp.prices.length) {
const noHist = document.createElement('p');
noHist.style.cssText = 'color:'+c.muted+';font-style:italic;font-size:11px;margin:0;';
noHist.textContent = s('pcNoHistory');
popup.appendChild(noHist);
} else {
[...sp.prices].reverse().forEach(e => {
const row = document.createElement('div');
row.style.cssText = 'display:flex;justify-content:space-between;gap:12px;padding:4px 0;border-bottom:1px solid '+c.sep+'44;cursor:pointer;';
row.title = _D('Tıkla: Fiyat inputuna gir','Click: Fill price input','Clique: Preencher preço');
const pEl = document.createElement('span'); pEl.style.cssText='color:#f0ad4e;font-weight:bold;'; pEl.textContent=e.p.toLocaleString('tr-TR')+' M$';
const dEl = document.createElement('span'); dEl.style.cssText='color:'+c.muted+';'; dEl.textContent=e.d;
row.append(pEl, dEl);
row.addEventListener('click', () => {
// Find the corresponding price input and fill it
const inp = document.querySelector('.pi-price-inp[data-name="'+CSS.escape(name)+'"][data-variant="'+CSS.escape(variant)+'"]');
if (inp) {
inp.value = e.p >= 1000000 ? (e.p/1000000)+'m' : e.p >= 1000 ? (e.p/1000)+'k' : String(e.p);
doSavePrice(inp);
}
popup.remove();
});
popup.appendChild(row);
});
}
const closeB = document.createElement('button');
closeB.textContent = '✕ '+s('btnClose');
closeB.style.cssText = 'margin-top:8px;width:100%;background:'+c.togBg+';color:'+c.togText+';border:1px solid '+c.togBorder+';border-radius:3px;cursor:pointer;padding:4px;font-size:10px;';
closeB.onclick = () => popup.remove();
popup.appendChild(closeB);
document.body.appendChild(popup);
setTimeout(() => {
const h = (ev) => { if(!popup.contains(ev.target)){popup.remove();document.removeEventListener('click',h);} };
document.addEventListener('click', h);
}, 50);
};
const bindResults = (ld) => {
ld.addEventListener('click',(e)=>{
// Fiyat geçmişi butonu
const histBtn = e.target.closest('.pi-hist-btn');
if (histBtn) {
e.stopPropagation();
_showDepotPriceHistory(histBtn.dataset.name, histBtn.dataset.variant, histBtn);
return;
}
const btn=e.target.closest('[data-del-owner]');
const btnAll=e.target.closest('[data-del-all-owner]');
if (btnAll&&confirm(_D(btnAll.dataset.delAllOwner+' tüm verisi silinsin mi?','Delete all data for '+btnAll.dataset.delAllOwner+'?','Excluir todos os dados de '+btnAll.dataset.delAllOwner+'?'))) {
const db=getDB(); delete db[btnAll.dataset.delAllOwner]; saveDB(db); _db=null; showList(); return;
}
if (btn&&confirm(s('confirmDel'))){
const owner=btn.dataset.delOwner,url=btn.dataset.delUrl,db=getDB();
delete db[owner][url];
if (!Object.keys(db[owner]).length) delete db[owner];
saveDB(db); _db=null; showList();
}
});
ld.addEventListener('keydown',(e)=>{
if (e.key==='Enter'&&e.target.classList.contains('pi-price-inp')){e.preventDefault();doSavePrice(e.target);}
});
};
const showList = () => {
const ld=document.getElementById('pi-results'); if (!ld) return;
const db=getDB(),c=C(),owners=Object.keys(db).sort();
if (!owners.length){ld.innerHTML='<p style="color:#e74c3c;text-align:center;font-size:11px;">'+s('listEmpty')+'</p>';return;}
// Character selection UI at the top
let characterSelectionHtml = '';
if (owners.length > 1) {
characterSelectionHtml = '<div style="background:'+c.detBg+';border:1px solid '+c.detBorder+';border-radius:4px;padding:8px;margin-bottom:10px;">'
+ '<div style="font-size:11px;font-weight:bold;color:'+c.text+';margin-bottom:6px;">👥 ' + _D('Karakter Seçimi', 'Character Selection', 'Seleção de Personagem') + '</div>'
+ '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px;">'
+ '<button id="select-all-chars-list" style="padding:3px 6px;font-size:9px;background:#28a745;color:white;border:none;border-radius:3px;cursor:pointer;">' + _D('Tümünü Seç', 'Select All', 'Selecionar Todos') + '</button>'
+ '<button id="select-none-chars-list" style="padding:3px 6px;font-size:9px;background:#dc3545;color:white;border:none;border-radius:3px;cursor:pointer;">' + _D('Hiçbiri', 'Select None', 'Selecionar Nenhum') + '</button>'
+ '<span id="selected-count-list" style="padding:3px 6px;font-size:9px;color:'+c.muted+';">' + selectedCharacters.size + '/' + owners.length + ' ' + _D('seçili', 'selected', 'selecionado') + '</span>'
+ '</div>'
+ '<div id="character-checkboxes-list" style="max-height:120px;overflow-y:auto;font-size:10px;">';
owners.forEach(owner => {
const isChecked = selectedCharacters.has(owner);
const totalCnt = Object.values(db[owner]).reduce((a,e)=>a+e.items.reduce((b,it)=>b+Object.values(it.variants).reduce((x,y)=>x+y,0),0),0);
characterSelectionHtml += '<label style="display:flex;align-items:center;gap:4px;padding:2px 0;cursor:pointer;">'
+ '<input type="checkbox" data-char="' + esc(owner) + '" ' + (isChecked ? 'checked' : '') + ' style="margin:0;">'
+ '<span style="flex:1;">' + esc(owner) + ' <span style="color:'+c.muted+';">[' + totalCnt + ']</span></span>'
+ '</label>';
});
characterSelectionHtml += '</div></div>';
}
let html = characterSelectionHtml;
// Filter owners based on selection
const filteredOwners = selectedCharacters.size > 0 ? owners.filter(owner => selectedCharacters.has(owner)) : owners;
filteredOwners.forEach(owner=>{
const totalCnt=Object.values(db[owner]).reduce((a,e)=>a+e.items.reduce((b,it)=>b+Object.values(it.variants).reduce((x,y)=>x+y,0),0),0);
html+='<div style="margin-bottom:6px;border:1px solid '+c.border+';background:'+c.subBg+';border-radius:4px;">'
+'<div style="background:'+c.owBg+';padding:4px 8px;color:'+c.owText+';font-size:11px;font-weight:bold;display:flex;justify-content:space-between;align-items:center;">'
+'<span>👤 '+esc(owner)+' <span style="font-weight:normal;opacity:.7;">['+totalCnt+']</span></span>'
+'<button data-del-all-owner="'+esc(owner)+'" style="background:none;color:#e74c3c;border:1px solid #e74c4c;padding:1px 6px;font-size:9px;cursor:pointer;border-radius:2px;">'+s('btnDelAll')+'</button></div>';
Object.keys(db[owner]).sort().forEach(url=>{
const e=db[owner][url],cnt=e.items.reduce((a,it)=>a+Object.values(it.variants).reduce((x,y)=>x+y,0),0);
html+='<div style="padding:4px 8px;border-bottom:1px solid '+c.resBorder+';display:flex;justify-content:space-between;align-items:center;">'
+'<a href="'+esc(url)+'" target="_blank" style="font-size:10px;color:'+c.loc+';text-decoration:none;">📍 '+esc(e.locType)+' <span style="color:'+c.muted+'">['+cnt+']</span></a>'
+'<button data-del-owner="'+esc(owner)+'" data-del-url="'+esc(url)+'" style="background:#a94442;color:white;border:none;padding:1px 5px;font-size:9px;cursor:pointer;border-radius:2px;">'+s('btnDel')+'</button></div>';
});
html+='</div>';
});
ld.innerHTML=html;
// Character selection event handlers for list view
const selectAllBtn = document.getElementById('select-all-chars-list');
const selectNoneBtn = document.getElementById('select-none-chars-list');
const selectedCountSpan = document.getElementById('selected-count-list');
if (selectAllBtn) {
selectAllBtn.addEventListener('click', () => {
selectedCharacters.clear();
owners.forEach(char => selectedCharacters.add(char));
updateCharacterCheckboxesList();
showList(); // Re-render list
});
}
if (selectNoneBtn) {
selectNoneBtn.addEventListener('click', () => {
selectedCharacters.clear();
updateCharacterCheckboxesList();
showList(); // Re-render list
});
}
// Individual checkbox handlers
const checkboxes = ld.querySelectorAll('[data-char]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
const charName = e.target.dataset.char;
if (e.target.checked) {
selectedCharacters.add(charName);
} else {
selectedCharacters.delete(charName);
}
updateSelectedCountList();
showList(); // Re-render list
});
});
function updateCharacterCheckboxesList() {
const checkboxes = ld.querySelectorAll('[data-char]');
checkboxes.forEach(checkbox => {
checkbox.checked = selectedCharacters.has(checkbox.dataset.char);
});
updateSelectedCountList();
}
function updateSelectedCountList() {
if (selectedCountSpan) {
selectedCountSpan.textContent = selectedCharacters.size + '/' + owners.length + ' ' + _D('seçili', 'selected', 'selecionado');
}
}
bindResults(ld);
};
const renderSearch = (query) => {
const ld=document.getElementById('pi-results'); if (!ld) return;
if (!query){invListOpen?showList():(ld.innerHTML='');return;}
const db=getDB(),c=C(),prices=getPrices(),rows=[],vt={};
let grand=0; const qLower=query.toLowerCase();
// Get all unique characters for filtering
allCharacters = Object.keys(db).sort();
// If no characters selected, select all by default
if (selectedCharacters.size === 0) {
allCharacters.forEach(char => selectedCharacters.add(char));
}
Object.keys(db).forEach(owner=>{
// Skip if owner not in selected characters
if (!selectedCharacters.has(owner)) return;
Object.keys(db[owner]).forEach(url=>{
const e=db[owner][url],displayOwner=e.locClass==='Araç'?e.locType:owner;
e.items.forEach(item=>{
const d = getDict(item.name);
const match =[item.name, d.cat, d.en, d.it, d.es, d.pt_pt, d.pt_br, d.tr].some(f => f && f.toLowerCase().includes(qLower));
if (!match) return;
let ln = item.name;
if(LANG==='TR'&&d.tr) ln=d.tr; else if(LANG==='EN'&&d.en) ln=d.en; else if(LANG==='PT-BR'&&d.pt_br) ln=d.pt_br;
Object.entries(item.variants).forEach(([v,cnt])=>{
const cleanV = v ? v.replace(/\.\s*$/,'').trim() : '';
const k=priceKey(item.name,cleanV);
if (!vt[k]) vt[k]={name:ln, variant:cleanV, count:0, cat:d.cat, en:d.en, orName:item.name};
vt[k].count+=cnt; grand+=cnt;
rows.push({name:ln,variant:cleanV,count:cnt,displayOwner,locClass:e.locClass,locType:e.locType,url,cat:d.cat});
});
});
});
});
if (!rows.length){ld.innerHTML='<p style="font-size:10px;color:'+c.muted+';padding:4px;">'+s('noResults')+'</p>';return;}
const lastScan=GM_getValue('pop_last_scan','')||'—';
let html='<div style="background:'+c.gtBg+';border:1px solid '+c.gtBorder+';border-radius:4px;padding:6px 10px;margin-bottom:6px;font-size:12px;color:'+c.gtText+';display:flex;justify-content:space-between;align-items:center;">'
+'<b>'+s('totalLabel')+' "'+esc(query)+'": '+grand+'</b>'
+'<span style="font-size:10px;font-weight:normal;opacity:.7;">'+s('lastScanLabel')+' '+lastScan+'</span></div>';
html+='<div style="background:'+c.vtBg+';border:1px solid '+c.vtBorder+';border-radius:4px;padding:6px 10px;margin-bottom:8px;font-size:11px;">';
const vtList=Object.values(vt).sort((a,b) => a.cat.localeCompare(b.cat) || a.name.localeCompare(b.name));
vtList.forEach(x=>{
const pk=priceKey(x.orName,x.variant),raw=prices[pk]||'';
const numVal=parsePrice(raw),valTxt=numVal>0?'= '+(numVal*x.count).toLocaleString('tr-TR'):s('noPriceLabel');
const spInfo = getLastSharedPrice(x.orName, x.variant);
const spBtnTxt = spInfo ? '💰 '+spInfo.last.toLocaleString('tr-TR') : '📋';
html+='<div class="pi-vt-row" style="display:flex;align-items:center;gap:6px;padding:3px 0;border-bottom:1px solid '+c.vtBorder+'44;color:'+c.vtText+';">'
+'<span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">'
+'<span style="font-size:9px;background:#004085;color:#fff;padding:1px 4px;border-radius:2px;margin-right:4px;">'+esc(x.cat)+'</span>'
+'<b>'+esc(x.name)+'</b> <span style="opacity:.7;font-size:10px;">'+(x.variant?esc(x.variant):'')+'</span> <span style="color:'+c.gtText+';font-weight:bold;">x'+x.count+'</span></span>'
+'<button class="pi-hist-btn" data-name="'+esc(x.orName)+'" data-variant="'+esc(x.variant)+'" style="font-size:9px;padding:1px 5px;background:'+(spInfo?'#2d1a00':'#333')+';color:'+(spInfo?'#f0ad4e':'#888')+';border:1px solid '+(spInfo?'#f0ad4e44':'#555')+';border-radius:3px;cursor:pointer;white-space:nowrap;flex-shrink:0;">'+spBtnTxt+'</button>'
+'<input type="text" placeholder="30k / 2m" value="'+esc(raw)+'" data-count="'+x.count+'" data-name="'+esc(x.orName)+'" data-variant="'+esc(x.variant)+'" class="pi-price-inp"'
+' style="width:76px;font-size:10px;padding:2px 4px;background:'+c.priceBg+';color:'+c.priceText+';border:1px solid '+c.priceBorder+';border-radius:3px;flex-shrink:0;">'
+'<span class="pi-val-span" style="font-size:10px;color:'+c.countText+';min-width:60px;flex-shrink:0;">'+valTxt+'</span></div>';
});
html+='<button id="pi-copy-btn" style="width:100%;margin-top:6px;padding:5px;background:#444;color:#ccc;border:1px solid #666;border-radius:3px;cursor:pointer;font-size:10px;">'+s('btnCopy')+'</button>';
html+='</div><div style="font-size:10px;">';
rows.forEach(r=>{
const charIdM = r.url.match(/\/Character\/(?:Items|ItemsEquipment)\/(\d+)/);
const charUrl = charIdM ? r.url.replace(/\/Character\/(?:Items|ItemsEquipment)\/\d+/, '/Character/Details/'+charIdM[1]) : null;
const ownerHtml = charUrl ? '<a href="'+esc(charUrl)+'" target="_blank" style="color:'+c.owText2+';text-decoration:none;font-weight:600;">'+esc(r.displayOwner)+'</a>' : '<span style="color:'+c.owText2+';font-weight:600;">'+esc(r.displayOwner)+'</span>';
html+='<div style="padding:5px 4px;border-bottom:1px solid '+c.resBorder+';display:flex;flex-direction:column;gap:2px;">'
+'<div style="display:flex;align-items:baseline;gap:4px;flex-wrap:wrap;">'
+'<span style="color:'+c.rowText+';font-size:10px;font-weight:600;word-break:break-word;">'+esc(r.name)+'</span>'
+'<span style="color:'+c.muted+';font-size:9px;">'+esc(r.variant)+'</span>'
+'<span style="color:'+c.countText+';font-weight:bold;font-size:10px;margin-left:auto;">x'+r.count+'</span>'
+'</div>'
+'<div style="display:flex;align-items:center;gap:4px;flex-wrap:wrap;font-size:9px;">'
+ownerHtml+'<span style="color:'+c.sep+';">→</span>'
+'<a href="'+esc(r.url)+'" target="_blank" style="color:'+c.link+';text-decoration:none;">'+esc(r.locType)+'</a>'
+'</div></div>';
});
ld.innerHTML=html+'</div>';
bindResults(ld);
// Character selection event handlers
const selectAllBtn = document.getElementById('select-all-chars');
const selectNoneBtn = document.getElementById('select-none-chars');
const selectedCountSpan = document.getElementById('selected-count');
if (selectAllBtn) {
selectAllBtn.addEventListener('click', () => {
selectedCharacters.clear();
allCharacters.forEach(char => selectedCharacters.add(char));
updateCharacterCheckboxes();
renderSearch(query); // Re-render with updated selection
});
}
if (selectNoneBtn) {
selectNoneBtn.addEventListener('click', () => {
selectedCharacters.clear();
updateCharacterCheckboxes();
renderSearch(query); // Re-render with updated selection
});
}
// Individual checkbox handlers
const checkboxes = ld.querySelectorAll('[data-char]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
const charName = e.target.dataset.char;
if (e.target.checked) {
selectedCharacters.add(charName);
} else {
selectedCharacters.delete(charName);
}
updateSelectedCount();
renderSearch(query); // Re-render with updated selection
});
});
function updateCharacterCheckboxes() {
const checkboxes = ld.querySelectorAll('[data-char]');
checkboxes.forEach(checkbox => {
checkbox.checked = selectedCharacters.has(checkbox.dataset.char);
});
updateSelectedCount();
}
function updateSelectedCount() {
if (selectedCountSpan) {
selectedCountSpan.textContent = selectedCharacters.size + '/' + allCharacters.length + ' ' + _D('seçili', 'selected', 'selecionado');
}
}
document.getElementById('pi-copy-btn')?.addEventListener('click',()=>{
let txt = '', curCat = '';
vtList.forEach(x => {
if (curCat !== x.cat) { curCat = x.cat; txt += `\n[b]${curCat}[/b]\n`; }
const raw = prices[priceKey(x.orName, x.variant)] || '';
const lTag = (x.en && x.en !== x.name) ? ` (${x.en})` : '';
const vTag = x.variant ? ` ${x.variant}` : '';
txt += `${x.name}${lTag}${vTag}: x${x.count}${raw ? ' - ' + raw : ' - ' + s('noPriceLabel')}\n`;
});
navigator.clipboard.writeText(txt.trim()).then(()=>{const b=document.getElementById('pi-copy-btn');if(b){b.textContent=s('btnCopied');setTimeout(()=>{b.textContent=s('btnCopy');},1500);}});
});
};
// Panel: UI
function _createDepotFAB() {
if (document.getElementById('pi-fab')) return;
if (GM_getValue('dp_pc_connected', false)) return; // PC bağlıysa FAB gösterme
// Position right of MissionAid float if present
const maFloat = document.getElementById('qrFloat');
const rightOffset = maFloat ? (window.innerWidth - maFloat.getBoundingClientRect().left + 8) : 14;
const fab = document.createElement('button');
fab.id = 'pi-fab'; fab.type = 'button';
fab.style.cssText = 'position:fixed;bottom:18px;right:'+rightOffset+'px;z-index:999990;width:40px;height:40px;border-radius:50%;background:#6f42c1;color:#fff;font-size:20px;border:2px solid #fff;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;font-family:inherit;line-height:1';
fab.title = _D('Depot','Depot','Depot');
fab.innerHTML = '<span style="pointer-events:none">📦</span>';
fab.onclick = () => { fab.remove(); createUI(); };
document.body.appendChild(fab);
}
const createUI = () => {
if (document.getElementById('pi-panel')) return;
const c=C(),w=Math.min(gmGet(K.pw,340),window.innerWidth-8);
const panel=document.createElement('div'); panel.id='pi-panel';
const defaultRight = () => ({ right: 10, top: Math.round(window.innerHeight / 2) - 20 });
const pos = gmGet(K.pos, null);
const posStyle = pos
? 'left:'+pos.left+'px;top:'+pos.top+'px;'
: 'right:10px;top:'+Math.round(window.innerHeight/2-200)+'px;';
const th=theme(), ts='background:'+c.togBg+';color:'+c.togText+';border:1px solid '+c.togBorder;
const thD=th==='dark'?'background:#333;color:#fff;border:2px solid #f0ad4e':ts;
const thL=th==='light'?'background:#fff;color:#222;border:2px solid #f0ad4e':ts;
const mkLangBtn=(lang,label)=>{
const active=LANG===lang;
return '<button data-lang="'+lang+'" style="flex:1;padding:6px 4px;font-size:11px;border-radius:4px;cursor:pointer;font-weight:'+(active?'bold':'normal')+';background:'+(active?'#f0ad4e':c.togBg)+';color:'+(active?'#111':c.togText)+';border:2px solid '+(active?'#f0ad4e':c.togBorder)+';transition:all .15s;">'+label+'</button>';
};
panel.style.cssText='position:fixed;'+posStyle+'width:'+w+'px;min-width:220px;max-width:calc(100vw - 8px);z-index:999999;background:'+c.bg+';color:'+c.text+';border:2px solid '+c.border+';border-radius:10px;padding:0;font-family:Arial,sans-serif;box-shadow:0 12px 40px rgba(0,0,0,.5);max-height:88vh;overflow-y:auto;overflow-x:hidden;word-break:break-word;';
panel.innerHTML=
// HEADER — title + drag hint + minimize
'<div id="pi-drag" style="background:'+c.hBg+';border-radius:8px 8px 0 0;padding:10px 14px;border-bottom:2px solid '+c.hBorder+';display:flex;justify-content:space-between;align-items:center;cursor:grab;user-select:none;">'
+'<span style="font-size:14px;font-weight:bold;color:#f0ad4e;">'+s('panelTitle')+'</span>'
+'<div style="display:flex;gap:6px;align-items:center;">'
+('ontouchstart' in window ? '' : '<span style="font-size:10px;color:'+c.dragHint+';border:1px dashed '+c.dragHint+';padding:2px 5px;border-radius:3px;">'+s('dragHint')+'</span>')
+'<button id="pi-min" style="'+ts+';border-radius:6px;padding:3px 8px;cursor:pointer;font-size:11px;display:flex;align-items:center;gap:4px;white-space:nowrap;flex-shrink:0;">'
+(GM_getValue('dp_pc_connected',false)?'✕ '+s('btnClose'):'📦 '+s('btnMinimize'))
+'</button>'
+'</div></div>'
+'<div style="padding:8px 12px 0;">'
// ACCORDION ROW — two buttons side by side
+'<div style="display:flex;gap:6px;margin-bottom:6px;">'
+'<button id="pi-tog-cfg" style="flex:1;padding:8px 6px;'+ts+';border-radius:5px;cursor:pointer;font-weight:bold;font-size:11px;text-align:center;">⚙️ '+s('btnSettings')+' ▾</button>'
+'<button id="pi-tog-det" style="flex:1;padding:8px 6px;'+ts+';border-radius:5px;cursor:pointer;font-weight:bold;font-size:11px;text-align:center;">📋 '+s('btnInventory')+' ▾</button>'
+'</div>'
// SETTINGS PANEL
+'<div id="pi-cfg" style="display:none;background:'+c.detBg+';padding:10px;border-radius:5px;border:1px solid '+c.detBorder+';margin-bottom:8px;">'
// Lang buttons — first row inside settings
+'<div id="pi-lang-btns" style="display:flex;gap:5px;margin-bottom:10px;">'
+mkLangBtn('TR','🇹🇷 Türkçe')+mkLangBtn('EN','🇬🇧 English')+mkLangBtn('PT-BR','🇧🇷 Português')
+'</div>'
+'<div style="display:flex;gap:6px;margin-bottom:10px;">'
+'<button id="pi-th-dark" style="flex:1;padding:7px;border-radius:4px;cursor:pointer;font-size:11px;font-weight:bold;'+thD+'">🌙 Dark</button>'
+'<button id="pi-th-light" style="flex:1;padding:7px;border-radius:4px;cursor:pointer;font-size:11px;font-weight:bold;'+thL+'">☀️ Light</button></div>'
+'<div style="display:flex;gap:5px;margin-bottom:6px;">'
+'<button id="pi-exp-set" style="flex:1;padding:8px;background:#17a2b8;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;">'+s('btnBackup')+'</button>'
+'<button id="pi-imp-set" style="flex:1;padding:8px;background:#6c757d;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;">'+s('btnRestore')+'</button></div>'
+'<div style="display:flex;gap:5px;margin-top:8px;">'
+'<a href="https://rentry.org/depotoku/" target="_blank" style="flex:1;padding:6px;background:#6c757d;color:white;text-align:center;text-decoration:none;font-size:11px;border-radius:3px;">📖 Beni Oku</a>'
+'<a href="https://rentry.org/depotkategori/" target="_blank" style="flex:1;padding:6px;background:#6c757d;color:white;text-align:center;text-decoration:none;font-size:11px;border-radius:3px;">📁 Kategori Güncelleme Rehberi</a>'
+'</div>'
+'<div style="margin-top:12px;padding-top:10px;border-top:1px solid '+c.detBorder+';">'
+'<label style="color:'+c.lbl+';font-size:11px;font-weight:bold;display:block;margin-bottom:6px;">'+s('catLabel')+'</label>'
+'<a href="https://docs.google.com/spreadsheets/d/1X7MXYBs_YBc6RD0KKxc6cxMMvz3mYhdKUEPxsOR_df8/edit?gid=577170584#gid=577170584" target="_blank" style="display:block;width:100%;padding:6px;background:#17a2b8;color:white;text-align:center;text-decoration:none;font-size:11px;border-radius:3px;margin-bottom:8px;">🌐 Çok Dilli Sözlük Tablosu</a>'
+'<label style="color:'+c.lbl+';font-size:10px;display:block;margin-bottom:4px;">Tablonun tümünü (Ctrl+A / Ctrl+C) buraya yapıştırıp kaydedin:</label>'
+'<textarea id="pi-cat-txt" placeholder="'+s('catPlaceholder')+'" style="width:100%;height:60px;font-size:10px;background:'+c.taBg+';color:'+c.text+';border:1px solid '+c.inBorder+';border-radius:3px;resize:vertical;margin-bottom:6px;box-sizing:border-box;"></textarea>'
+'<button id="pi-cat-save" style="width:100%;margin-bottom:8px;padding:8px;background:#28a745;color:white;border:none;border-radius:4px;cursor:pointer;font-size:11px;font-weight:bold;">'+s('btnCatSave')+'</button>'
+'<button id="pi-clear" style="width:100%;padding:8px;background:none;color:#dc3545;border:1px solid #dc3545;border-radius:4px;cursor:pointer;font-size:11px;">'+s('btnClearAll')+'</button>'
+'</div>'
+'</div>'
// INVENTORY PANEL
+'<div id="pi-det" style="display:none;background:'+c.detBg+';padding:10px;border-radius:5px;border:1px solid '+c.detBorder+';margin-bottom:8px;">'
+'<button id="pi-scan" style="width:100%;padding:10px;background:#0056b3;color:white;border:none;border-radius:5px;cursor:pointer;font-weight:bold;margin-bottom:8px;">'+s('btnAutoScan')+'</button>'
+'<button id="pi-list" style="width:100%;background:#17a2b8;color:white;border:none;padding:8px;font-weight:bold;border-radius:5px;cursor:pointer;margin-bottom:8px;">'+s('btnListManage')+'</button>'
+'<button id="pi-forum" style="width:100%;background:#6f42c1;color:white;border:none;padding:8px;font-weight:bold;border-radius:5px;cursor:pointer;margin-bottom:8px;">'+s('btnForumList')+'</button>'
+'<div style="display:flex;gap:5px;">'
+'<button id="pi-exp-det" style="flex:1;padding:8px;background:#6c757d;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;">'+s('btnDetailed')+'</button>'
+'<button id="pi-exp-sum" style="flex:1;padding:8px;background:#6c757d;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;">'+s('btnStockCSV')+'</button>'
+'<button id="pi-imp-csv" style="flex:1;padding:8px;background:#6c757d;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;">'+s('btnImportCSV')+'</button>'
+'<button id="pi-csv-editor" style="flex:1;padding:8px;background:#17a2b8;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;">📊 CSV Edit</button>'
+'</div></div>'
// SAVE / DELETE ROW
+'<div style="display:flex;gap:6px;margin-bottom:8px;">'
+'<button id="pi-save" style="flex:3;background:#218838;color:white;border:none;padding:10px;font-weight:bold;border-radius:5px;cursor:pointer;">'+s('btnSavePage')+'</button>'
+'<button id="pi-del" style="flex:1;background:none;color:#f39c12;border:1px solid #f39c12;border-radius:4px;cursor:pointer;font-size:10px;">'+s('btnDelRecord')+'</button>'
+'</div>'
// SEARCH
+'<input type="text" id="pi-search" placeholder="'+s('searchPH')+'" style="width:100%;padding:10px;background:'+c.inBg+';color:'+c.inText+';border:2px solid #3498db;border-radius:5px;box-sizing:border-box;outline:none;">'
+'<div id="pi-results" style="margin-top:10px;"></div>'
+'</div>';
document.body.appendChild(panel);
// Dil butonları — document delegation, pi-cfg içinde display:none sorununu aşar
document.addEventListener('click',(e)=>{
const btn=e.target.closest('#pi-lang-btns [data-lang]'); if(!btn)return;
document.cookie='ppm_lang='+btn.dataset.lang+';path=/;domain=.popmundo.com;max-age=31536000';
location.reload();
});
// Accordion: birine tıklayınca diğeri kapanır
const togMutual=(bA,dA,bB,dB)=>{
const btnA=document.getElementById(bA),divA=document.getElementById(dA);
const btnB=document.getElementById(bB),divB=document.getElementById(dB);
if(!btnA||!btnB) return;
const setArrow=(btn,open)=>{btn.textContent=btn.textContent.replace(open?'▴':'▾',open?'▾':'▴');};
btnA.addEventListener('click',()=>{
const openA=divA.style.display!=='none';
divA.style.display=openA?'none':'block'; setArrow(btnA,openA);
if(!openA){divB.style.display='none'; if(btnB.textContent.includes('▴'))setArrow(btnB,true);}
});
btnB.addEventListener('click',()=>{
const openB=divB.style.display!=='none';
divB.style.display=openB?'none':'block'; setArrow(btnB,openB);
if(!openB){divA.style.display='none'; if(btnA.textContent.includes('▴'))setArrow(btnA,true);}
});
};
togMutual('pi-tog-cfg','pi-cfg','pi-tog-det','pi-det');
// Sol kenardan resize
const rh=document.createElement('div');
rh.style.cssText='position:absolute;top:0;left:0;width:8px;height:100%;cursor:ew-resize;z-index:2;';
panel.appendChild(rh);
let resizing=false,startX=0,startW=0;
rh.addEventListener('mousedown',(e)=>{resizing=true;startX=e.clientX;startW=panel.offsetWidth;e.preventDefault();});
document.addEventListener('mousemove',(e)=>{if(!resizing)return;const nw=Math.max(220,Math.min(window.innerWidth-8,startW-(e.clientX-startX)));panel.style.width=nw+'px';});
document.addEventListener('mouseup',()=>{if(!resizing)return;resizing=false;gmSet(K.pw,panel.offsetWidth);});
// Sürükleme
let drag=false,ox=0,oy=0;
document.getElementById('pi-drag').addEventListener('mousedown',(e)=>{
if(e.target.closest('button'))return;
drag=true; panel.style.cursor='grabbing';
const rect=panel.getBoundingClientRect();
ox=rect.left-e.clientX; oy=rect.top-e.clientY;
});
document.addEventListener('mousemove',(e)=>{
if(!drag)return;
panel.style.left=Math.max(0,e.clientX+ox)+'px';
panel.style.top=Math.max(0,e.clientY+oy)+'px';
panel.style.right='auto'; panel.style.bottom='auto';
});
document.addEventListener('mouseup',()=>{
if(!drag)return; drag=false; panel.style.cursor='';
gmSet(K.pos,{left:panel.offsetLeft,top:panel.offsetTop});
});
const fileInput=(accept,cb)=>{const i=document.createElement('input');i.type='file';i.accept=accept;i.onchange=e=>{const r=new FileReader();r.onload=ev=>cb(ev.target.result);r.readAsText(e.target.files[0]);};i.click();};
const on=(id,fn)=>document.getElementById(id)&&document.getElementById(id).addEventListener('click',fn);
// Hata önleme: Liste elemanı yoksa işlem yapma
const safeAddEventListener = (id, event, fn) => {
const el = document.getElementById(id);
if (el) el.addEventListener(event, fn);
};
on('pi-min',()=>{
panel.remove();
if (!GM_getValue('dp_pc_connected', false)) _createDepotFAB();
// If connected: PopControl button brings it back — no FAB needed
});
on('pi-save',()=>{const cnt=updatePage();if(cnt>0){alert(_D(cnt+' çeşit eşya kaydedildi.',cnt+' item type(s) saved.',cnt+' tipo(s) de item salvos.'));location.reload();}else alert(s('alertNoItems'));});
on('pi-del',()=>{const u=window.location.pathname+window.location.search,fullU=location.href,db=getDB();let f=false;Object.keys(db).forEach(o=>{if(db[o]&&(db[o][u]||db[o][fullU])){delete db[o][u];delete db[o][fullU];f=true;}});if(f){saveDB(db);alert(s('alertDeleted'));location.reload();}else alert(s('alertNotFound'));});
on('pi-scan',()=>discover());
on('pi-list',()=>{invListOpen?((document.getElementById('pi-results').innerHTML=''),invListOpen=false):(showList(),invListOpen=true);});
on('pi-exp-det',exportDetailed);
on('pi-exp-sum',exportSummary);
on('pi-imp-csv',()=>fileInput('.csv',importCSV));
on('pi-csv-editor',showCSVEditor);
on('pi-exp-set',exportSettings);
on('pi-imp-set',()=>fileInput('.json',importSettings));
on('pi-forum',showForumMatcher);
on('pi-th-dark',()=>{GM_setValue(K.theme,'dark');panel.remove();createUI();});
on('pi-th-light',()=>{GM_setValue(K.theme,'light');panel.remove();createUI();});
document.getElementById('pi-search').addEventListener('input',(e)=>renderSearch(e.target.value.trim()));
on('pi-cat-save',()=>{
const raw=document.getElementById('pi-cat-txt').value; if(!raw.trim())return alert(s('alertEnterData'));
const lines = raw.split(/\r?\n/).map(x=>x.trim()).filter(Boolean);
let sIdx = -1;
for(let i=0;i<lines.length;i++){ if(lines[i].toLowerCase().includes('item category') && lines[i].toLowerCase().includes('english')){ sIdx=i; break; } }
if(sIdx === -1) sIdx = 0;
const nItems =[]; let cCount = 0;
const h = lines[sIdx].split('\t').map(x=>x.trim().toLowerCase());
if(h.includes('english') || h.includes('item category')){
const iC = h.findIndex(x=>x.includes('category')), iEn = h.findIndex(x=>x==='english'), iIt = h.findIndex(x=>x==='italian'), iEs = h.findIndex(x=>x==='spanish'), iPtPt = h.findIndex(x=>x.includes('portuguese/portugal')), iPtBr = h.findIndex(x=>x.includes('portuguese/brazil')), iTr = h.findIndex(x=>x==='turkish');
for(let i=sIdx+1;i<lines.length;i++){
const c = lines[i].split('\t').map(x=>x.trim());
if(c.length<2 || (!c[iEn] && !c[iTr])) continue;
nItems.push({ cat:iC>=0?c[iC]:'Diğer', en:iEn>=0?c[iEn]:'', it:iIt>=0?c[iIt]:'', es:iEs>=0?c[iEs]:'', pt_pt:iPtPt>=0?c[iPtPt]:'', pt_br:iPtBr>=0?c[iPtBr]:'', tr:iTr>=0?c[iTr]:'' });
cCount++;
}
} else {
lines.forEach(line=>{
const p = line.includes('\t')?line.split('\t'):line.split(',');
if(p.length>=2){ nItems.push({ cat:p[0].trim(), en:p[1].trim(), tr:p[1].trim() }); cCount++; }
});
}
if(cCount>0){
const ex = getDictData().items, m = new Map();
ex.forEach(x => m.set(x.en||x.tr, x));
nItems.forEach(x => m.set(x.en||x.tr, x));
saveDictData(Array.from(m.values()));
alert(_D(cCount+' eşya hafızaya alındı!', cCount+' items stored!', cCount+' itens salvos!'));
document.getElementById('pi-cat-txt').value = '';
} else alert(s('alertCatFmt'));
});
};
// Scan: oturum başlat
const sc=getScan();
if (sc.active && window.location.href.includes('/World/Popmundo.aspx/')) {
const btn=document.createElement('button');
const curName=sc.queue[sc.index]?sc.queue[sc.index].split('/').pop():'';
btn.textContent=`${s('scanStop')} (${sc.index}/${sc.queue.length}) — ${curName}`;
btn.style.cssText='position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:999998;padding:10px 20px;background:#dc3545;color:white;border:none;border-radius:6px;cursor:pointer;font-weight:bold;font-size:13px;';
btn.onclick=()=>{lsDel(K.scan);location.reload();};
document.body.appendChild(btn);
if (document.readyState==='complete') runStep();
else window.addEventListener('load',runStep);
}
// EN strings for PopControl export
window.__ppcStrDepot = {"scanTitle": "🔄 Auto Scan", "scanPersonal": "👤 Personal Items", "scanVehicles": "🚗 Personal Vehicles", "scanHousing": "🏠 Housing", "scanArtist": "🎵 Artist / Tour Vehicle", "scanWarn": "⚠️ Tabs will change automatically during scan.", "scanCancel": "Cancel", "scanStart": "▶ Start Scan", "scanSelectOne": "Select at least one category.", "scanDone": "✅ Scan Complete", "scanLocCount": "Scanned Locations:", "scanItemCount": "Total Items:", "scanClose": "Close", "scanAskId": "Enter Character ID:", "scanBadId": "No valid ID entered.", "scanVehList": "🔍 Vehicle list:", "scanVehUnit": "vehicles.", "scanHouseList": "🔍 Housing list:", "scanHouseUnit": "properties.", "scanLogOk": "types.", "scanLogEmpty": "no items.", "scanStop": "🛑 STOP", "panelTitle": "📦 Depot", "dragHint": "⠿ Drag", "btnSettings": "Panel Settings", "btnBackup": "📤 Backup Script", "btnRestore": "📥 Load Backup", "catLabel": "Category CSV (Category,Item):", "catPlaceholder": "Category,Item Name", "btnCatSave": "💾 Update Categories", "btnClearAll": "⚠️ Reset All Data", "btnSavePage": "📍 Save Page", "btnDelRecord": "🗑️ Delete Record", "btnInventory": "Inventory Actions", "btnAutoScan": "🔄 AUTO SCAN", "btnListManage": "📋 LIST / MANAGE INVENTORIES", "btnDetailed": "📄 Detailed CSV", "btnStockCSV": "📊 Stock & Priced CSV", "btnImportCSV": "📥 Import CSV", "searchPH": "Search inventory (e.g. Haiku)...", "alertNoItems": "No items found on this page.", "alertDeleted": "Deleted.", "alertNotFound": "Record not found.", "alertEnterData": "Enter data.", "alertClearConf": "All data will be reset!", "alertRestored": "Restored.", "alertInvalidFile": "Invalid file.", "listEmpty": "Database empty.", "btnDelAll": "Delete All", "btnDel": "DEL", "confirmDel": "Delete this record?", "noResults": "No results found.", "totalLabel": "Total", "lastScanLabel": "Last scan:", "btnCopy": "📋 Copy these results", "btnCopied": "✅ Copied", "noPriceLabel": "Make an offer", "clsPersonal": "Items", "clsVehicle": "Personal Vehicle", "clsHousing": "Housing", "clsArtist": "Artist Vehicle", "minTooltip": "Open panel", "btnMinimize": "Minimize", "btnClose": "Close", "locVehicleFb": "Personal Vehicle", "locLocaleFb": "Locale", "locCharFb": "Character", "locInvSuffix": "Inventory", "locArtistFb": "Artist Vehicle", "locUnknown": "Unknown", "variantDefault": "Standard"};
window.__ppcStrDepot.panelTitle = '📦 Depot ' + GM_info.script.version;
// Yeni string'ler PopControl locale cache için
Object.assign(window.__ppcStrDepot, {
"csvEditorTitle":"📊 CSV Viewer / Editor","csvFormat":"CSV Format:","csvFmtDetailed":"Detailed CSV (All Info)",
"csvFmtSummary":"Summary CSV (Stock & Price)","csvFmtForum":"Forum Format (Price List)",
"csvDataLabel":"CSV Data (editable):","csvCopy":"📋 Copy","csvDownload":"💾 Download",
"csvApply":"✓ Apply Changes","csvUpdated":"CSV data updated.","csvCopied":"CSV copied to clipboard!",
"csvNoPrice":"No prices to update.","csvApplyUnsup":"Applying changes not supported for this format.","csvErrPrefix":"Error: ",
"pcTitle":"💰 Price Check","pcPasteLbl":"Paste forum list or in-game message here:",
"pcProcess":"Process","pcAIBtn":"🤖 Clean with AI","pcAIHint":"Prompt copied! Gemini opened.",
"pcClose":"Close","pcFound":"You have:","pcNotFound":"Not in inventory",
"pcForumPrice":"Listed price:","pcLastPrice":"Last price:","pcAddPrice":"Add Price",
"pcAdded":"Added ✓","pcHistory":"History","pcNoHistory":"No price history.",
"pcNoInput":"Please enter a list.","pcResults":"Results:",
"pcApplyAll":"Apply All","pcApplied":"prices applied."
});
// ─── POPCONTROL BAĞLANTISI ────────────────────────────────────────────────────
function _registerDepot() {
if (window.PPC_Depot_Done) return;
const pc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.PopControl) || window.PopControl;
if (!pc?.register) return;
try {
pc.register({
id:'depot', icon:'📦', label:_D('Depot','Depot','Depot'),
strings: window.__ppcStrDepot || {},
buttons:[{icon:'📦', label:_D('Depot','Depot','Depot'), onClick:function(){
var p=document.getElementById('pi-panel');
if (p) { p.style.display = p.style.display==='none'?'':'none'; }
else createUI();
}}],
onUndo:function(){
window.PPC_Depot_Done = false;
document.getElementById('pi-panel')?.remove();
_createDepotFAB();
},
});
window.PPC_Depot_Done = true;
_injectDepotMenuItems();
console.log('[Depot] PopControl bağlantısı başarılı');
} catch(e) {
console.error('[Depot] PopControl bağlantı hatası:', e);
}
}
// ── PUBLIC API (diğer scriptler ve menü injection için) ──
window.__depot_api = {
togglePanel: () => {
const p = document.getElementById('pi-panel');
if (p) p.style.display = p.style.display === 'none' ? '' : 'none';
else createUI();
},
focusSearch: () => {
let p = document.getElementById('pi-panel');
if (!p) { createUI(); p = document.getElementById('pi-panel'); }
if (p) { p.style.display = ''; setTimeout(() => document.getElementById('pi-search')?.focus(), 80); }
},
openPriceCheck: () => typeof showPriceCheck === 'function' && showPriceCheck(),
startScan: () => typeof discover === 'function' && discover(),
};
// ── TOP VIP MENÜSÜ ENJEKSİYONU ──────────────────────────────────────────────
const _injectDepotMenuItems = () => {
if (!location.href.includes('/World/Popmundo.aspx/Character')) return;
if (document.getElementById('mnu-depot-search')) return;
const depotButtons = [
{ id: 'mnu-depot-search', label: _D('🔍 Eşya Ara', '🔍 Search Item', '🔍 Buscar Item'), fn: () => window.__depot_api?.focusSearch() },
{ id: 'mnu-depot-pricecheck', label: _D('💰 Fiyat Kontrolü', '💰 Price Check', '💰 Verificar Preço'), fn: () => window.__depot_api?.openPriceCheck() },
{ id: 'mnu-depot-scan', label: _D('🔄 Otomatik Tara', '🔄 Auto Scan', '🔄 Varredura Auto.'), fn: () => window.__depot_api?.startScan() },
];
// PopControl varsa tamamen devret — doğrudan config mutasyonu yok
const _pc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.PopControl) || window.PopControl;
if (_pc?.MenuManager) {
_pc.MenuManager.registerMenu({ id: 'top-vip', title: '⭐ TOP VIP ⭐', position: 'above-career', items: depotButtons, collapsible: true });
return;
}
// ── Standalone fallback (~18 satır, PopControl yoksa) ────────────────────
let ul = document.querySelector('#top-vip-menu ul');
if (!ul) {
const ref = [...document.querySelectorAll('.menu h3')]
.find(h => /Kariyer|Career|Carreira/.test(h.textContent))?.closest('.menu');
if (!ref) return;
const isCol = localStorage.getItem('top-vip-collapsed') === 'true';
const h3 = Object.assign(document.createElement('h3'), { textContent: '⭐ TOP VIP ⭐' });
h3.style.cssText = 'background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;text-align:center;padding:8px;margin:0;border-radius:6px 6px 0 0;cursor:pointer;box-shadow:0 2px 8px rgba(102,126,234,.3);user-select:none;';
ul = document.createElement('ul');
ul.style.cssText = `margin:0;padding:8px 0;background:#f8f9fa;border:1px solid #e9ecef;border-top:none;border-radius:0 0 6px 6px;${isCol ? 'display:none;' : ''}`;
h3.onclick = () => { const c = ul.style.display === 'none'; ul.style.display = c ? '' : 'none'; localStorage.setItem('top-vip-collapsed', !c); };
const menu = Object.assign(document.createElement('div'), { id: 'top-vip-menu', className: 'menu' });
menu.append(h3, ul); ref.before(menu);
menu.after(Object.assign(document.createElement('div'), { style: 'height:12px' }));
}
depotButtons.forEach(btn => {
if (document.getElementById(btn.id)) return;
const a = Object.assign(document.createElement('a'), { href: '#', textContent: btn.label });
a.style.cssText = 'color:#667eea;font-weight:600;text-decoration:none;display:block;padding:4px 12px;border-radius:4px;transition:all .2s;';
a.onmouseover = () => { a.style.background = '#667eea'; a.style.color = '#fff'; };
a.onmouseout = () => { a.style.background = ''; a.style.color = '#667eea'; };
a.onclick = e => { e.preventDefault(); btn.fn(); };
const li = Object.assign(document.createElement('li'), { id: btn.id }); li.style.margin = '2px 0';
li.appendChild(a); ul.appendChild(li);
});
};
_injectDepotMenuItems();
document.addEventListener('PopControlReady', () => setTimeout(_registerDepot, 50), { once: true });
(function _checkPC(n) {
if (window.PPC_Depot_Done) return;
const pc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.PopControl) || window.PopControl;
if (pc?.register) _registerDepot();
else if (n < 20) setTimeout(() => _checkPC(n + 1), 150);
else _createDepotFAB();
})(0);
})();