Track targets, sync assists, faction import, advanced stats formatting
目前為
// ==UserScript==
// @name BW WAR Tracker (REST API v2.0)
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Track targets, sync assists, faction import, advanced stats formatting
// @author Tu
// @match https://www.torn.com/*
// @icon https://www.torn.com/favicon.ico
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @connect api.torn.com
// @connect www.tornstats.com
// @connect bw-war-tracker-default-rtdb.europe-west1.firebasedatabase.app
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
let currentUser = null;
let factionMembers = [];
// myTargets is now synced via Firebase 'targets' node, local storage removed for sync
let targetsList = [];
let assists = [];
let isPolling = false;
// --- CONFIGURARE ---
const ADMIN_ID = 3946434;
const DB_URL = "https://bw-war-tracker-default-rtdb.europe-west1.firebasedatabase.app";
function initScript() {
if (document.body) {
setupScript();
} else {
setTimeout(initScript, 100);
}
}
function setupScript() {
GM_addStyle(`
/* UI GENERAL - MONOCHROME THEME */
#bw-war-button { position: fixed !important; bottom: 20px !important; left: 20px !important; background-color: #000 !important; color: #fff !important; border: 2px solid #fff !important; padding: 12px 24px !important; font-size: 14px !important; font-weight: bold; cursor: pointer; border-radius: 5px; z-index: 999999 !important; box-shadow: 0 2px 10px rgba(0,0,0,0.3) !important; transition: all 0.3s !important; }
#bw-war-button:hover { background-color: #333 !important; transform: translateY(-2px) !important; }
#bw-war-modal, #bw-settings-modal { display: none !important; position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 85% !important; max-width: 1000px !important; height: 75% !important; background-color: rgba(26, 26, 26, 0.98) !important; border: 2px solid #333 !important; border-radius: 10px !important; z-index: 999999 !important; box-shadow: 0 5px 30px rgba(0,0,0,0.8) !important; }
#bw-war-modal.active, #bw-settings-modal.active { display: flex !important; flex-direction: column !important; overflow: hidden !important; }
#bw-war-overlay { display: none !important; position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background-color: rgba(0,0,0,0.8) !important; z-index: 999998 !important; }
#bw-war-overlay.active { display: block !important; }
/* HEADER & BUTTONS */
.bw-modal-header { background: #000 !important; color: #fff !important; padding: 15px !important; border-radius: 8px 8px 0 0 !important; display: flex !important; flex-direction: column !important; align-items: center !important; border-bottom: 2px solid #333 !important; position: relative !important; }
.bw-modal-title { font-size: 22px !important; font-weight: 900 !important; letter-spacing: 1px; }
.bw-header-buttons { position: absolute !important; top: 15px !important; right: 15px !important; display: flex !important; gap: 8px !important; }
.bw-settings-button, .bw-close-button { background: none !important; border: 1px solid #fff !important; color: #fff !important; font-size: 16px !important; cursor: pointer !important; padding: 5px !important; border-radius: 4px !important; width: 30px !important; height: 30px !important; display: flex !important; justify-content: center !important; align-items: center !important; }
.bw-settings-button:hover, .bw-close-button:hover { background: #333 !important; }
/* TABS */
.bw-tabs { display: flex !important; background-color: #1a1a1a !important; border-bottom: 1px solid #333 !important; }
.bw-tab { flex: 1 !important; padding: 15px !important; background-color: #1a1a1a !important; color: #888 !important; border: none !important; cursor: pointer !important; font-weight: bold !important; transition: 0.2s; }
.bw-tab.active { background-color: #000 !important; color: #fff !important; border-bottom: 3px solid #fff !important; }
.bw-tab-content { display: none !important; padding: 20px !important; overflow-y: auto !important; flex: 1 !important; color: #fff !important; }
.bw-tab-content.active { display: block !important; }
/* LIST ITEMS */
.bw-member-item, .bw-target-item, .bw-assist-item { background-color: #111 !important; border: 1px solid #333 !important; padding: 12px 15px !important; border-radius: 5px !important; margin-bottom: 8px !important; display: flex !important; justify-content: space-between !important; align-items: center !important; }
.bw-member-name, .bw-target-name { color: #fff !important; cursor: pointer !important; font-weight: bold !important; text-decoration: none !important; font-size: 14px !important; }
.bw-member-name:hover, .bw-target-name:hover { color: #ccc !important; text-decoration: underline !important; }
.bw-rank-badge { background-color: #fff !important; color: #000 !important; padding: 2px 8px !important; border-radius: 10px !important; font-size: 10px !important; font-weight: bold !important; text-transform: uppercase; }
/* INPUTS & BUTTONS */
.bw-input-section { display: flex !important; gap: 10px !important; margin-bottom: 20px !important; }
.bw-input-field { flex: 1 !important; padding: 10px !important; background-color: #000 !important; border: 1px solid #444 !important; color: #fff !important; border-radius: 4px !important; }
/* Standard Buttons (Add/Attack) */
.bw-add-button { background-color: #000 !important; color: #fff !important; border: 1px solid #fff !important; padding: 8px 20px !important; border-radius: 4px !important; cursor: pointer !important; font-weight: bold !important; transition: 0.2s; }
.bw-add-button:hover { background-color: #fff !important; color: #000 !important; }
/* Remove Button */
.bw-remove-button { background-color: #000 !important; color: #fff !important; border: 1px solid #444 !important; padding: 5px 12px !important; border-radius: 3px !important; cursor: pointer !important; font-size: 11px !important; }
.bw-remove-button:hover { background-color: #333 !important; border-color: #fff !important; }
/* Slot Selector */
.bw-slot-selector { display: flex !important; gap: 5px !important; margin: 0 10px !important; }
.bw-slot-btn { background-color: #000 !important; color: #fff !important; border: 1px solid #444 !important; padding: 5px 10px !important; border-radius: 3px !important; cursor: pointer !important; font-size: 11px !important; }
.bw-slot-btn.active { background-color: #000 !important; border-color: #fff !important; box-shadow: 0 0 5px rgba(255,255,255,0.5) !important; }
/* "I'm in" / "Leave" Button */
.bw-im-in-btn { background-color: #000 !important; color: #fff !important; border: 1px solid #fff !important; padding: 6px 15px !important; border-radius: 15px !important; cursor: pointer !important; font-size: 12px !important; font-weight: bold !important; transition: all 0.2s !important; }
/* Joined State: White Background, Black Text */
.bw-im-in-btn.joined { background-color: #fff !important; color: #000 !important; border: 1px solid #fff !important; }
.bw-participants { display: flex !important; flex-wrap: wrap !important; gap: 5px !important; margin-top: 10px !important; }
.bw-participant-tag { background-color: #000 !important; color: #fff !important; padding: 4px 10px !important; border-radius: 12px !important; font-size: 10px !important; border: 1px solid #444 !important; }
.bw-loading { text-align: center !important; padding: 20px !important; color: #666 !important; font-style: italic; }
/* STATS DISPLAY & COLORS */
.bw-stats-input { background: #000 !important; border: 1px solid #333 !important; color: #fff !important; width: 60px !important; font-size: 10px !important; padding: 2px 5px !important; border-radius: 3px !important; margin-top: 5px !important; }
.bw-stats-display { font-size: 11px !important; font-weight: bold !important; margin-top: 5px !important; display: block !important; }
/* Color Classes for Stats */
.stat-red { color: #ff4444 !important; } /* > 20% disadvantage */
.stat-orange { color: #ffbb33 !important; } /* 0-20% disadvantage */
.stat-yellow { color: #ffeb3b !important; } /* 0-5% advantage */
.stat-green { color: #00c851 !important; } /* 5-10% advantage */
.stat-lightgreen { color: #69f0ae !important; }/* 10-30% advantage */
.stat-blue { color: #33b5e5 !important; } /* > 30% advantage (safe) */
.stat-white { color: #fff !important; } /* default/unknown */
`);
// CREATE ELEMENTS
const button = document.createElement('button');
button.id = 'bw-war-button';
button.textContent = 'BW WAR';
document.body.appendChild(button);
const overlay = document.createElement('div');
overlay.id = 'bw-war-overlay';
document.body.appendChild(overlay);
const modal = document.createElement('div');
modal.id = 'bw-war-modal';
modal.innerHTML = `
<div class="bw-modal-header">
<div class="bw-header-buttons"><button class="bw-settings-button">⚙️</button><button class="bw-close-button">×</button></div>
<img src="https://factionimages.torn.com/08099e78-0a69-4424-8ccd-5219338250ad-40039.png" class="bw-modal-logo" style="max-width: 200px; height: 50px; margin-bottom: 10px;">
<div class="bw-modal-title">Black & White War Manager</div>
</div>
<div class="bw-tabs">
<button class="bw-tab active" data-tab="info">Info</button>
<button class="bw-tab" data-tab="team">Team</button>
<button class="bw-tab" data-tab="targets">Targets</button>
<button class="bw-tab" data-tab="assists">Assists</button>
</div>
<div class="bw-tab-content active" id="info-content"><div class="bw-loading">Loading your information...</div></div>
<div class="bw-tab-content" id="team-content"><div class="bw-loading">Loading faction members...</div></div>
<div class="bw-tab-content" id="targets-content">
<div class="bw-input-section" id="admin-import-section" style="display:none;">
<input type="text" id="faction-import-input" class="bw-input-field" placeholder="Faction ID to Import (Admin Only)">
<button id="import-faction-btn" class="bw-add-button">Load Faction</button>
</div>
<div id="targets-list"></div>
</div>
<div class="bw-tab-content" id="assists-content">
<div class="bw-input-section">
<input type="text" id="assist-input" class="bw-input-field" placeholder="Enter User ID or Name" style="flex: 2;">
<div class="bw-slot-selector">
<button class="bw-slot-btn active" data-slots="1">+1</button><button class="bw-slot-btn" data-slots="2">+2</button><button class="bw-slot-btn" data-slots="3">+3</button><button class="bw-slot-btn" data-slots="4">+4</button>
</div>
<button id="add-assist-btn" class="bw-add-button">Add</button>
</div>
<div id="assists-list"></div>
</div>
`;
document.body.appendChild(modal);
const settingsModal = document.createElement('div');
settingsModal.id = 'bw-settings-modal';
settingsModal.innerHTML = `
<div class="bw-modal-header">
<div class="bw-header-buttons"><button class="bw-close-button settings-close">×</button></div>
<img src="https://factionimages.torn.com/08099e78-0a69-4424-8ccd-5219338250ad-40039.png" class="bw-modal-logo" style="max-width: 200px; height: 50px; margin-bottom: 10px;">
<div class="bw-modal-title">Settings</div>
</div>
<div style="padding: 30px; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60%;">
<div style="width: 100%; max-width: 400px;">
<label style="display: block; margin-bottom: 10px; font-weight: bold; color: #fff; text-align: center;">Torn API Key</label>
<div style="display: flex; gap: 10px;">
<input type="text" id="api-key-input" class="bw-input-field" placeholder="Enter key" style="text-align: center;">
<button id="save-api-key" class="bw-add-button">Save</button>
</div>
</div>
<div style="margin-top: 30px; padding: 15px; background-color: #111; border: 1px solid #333; border-radius: 5px; font-size: 12px; color: #888; text-align: center; width: 100%; max-width: 400px;">
Requires Full Access API key for faction data.<br>
<a href="https://www.torn.com/preferences.php#tab=api" target="_blank" style="color: #fff; text-decoration: underline;">Get Key Here</a>
</div>
</div>
`;
document.body.appendChild(settingsModal);
// HELPERS
function getApiKey() { return GM_getValue('torn_api_key', ''); }
function tornApiCall(endpoint, apiKey = null) {
return new Promise((resolve, reject) => {
const key = apiKey || getApiKey();
if (!key) { reject('No API key set'); return; }
GM_xmlhttpRequest({
method: 'GET',
url: `https://api.torn.com/${endpoint}&key=${key}`,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data.error) { reject(data.error.error); } else { resolve(data); }
} catch (e) { reject('Failed to parse response'); }
},
onerror: function() { reject('Network error'); }
});
});
}
function formatBigNumber(num) {
if (!num) return '0';
if (num >= 1e9) return parseFloat((num / 1e9).toFixed(2)) + 'B';
if (num >= 1e6) return parseFloat((num / 1e6).toFixed(2)) + 'M';
if (num >= 1e3) return parseFloat((num / 1e3).toFixed(2)) + 'K';
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function getColorForStats(targetStats, userTotalStats) {
if (!targetStats || !userTotalStats) return "stat-white";
const tStats = parseFloat(targetStats) * 1000; // Convert K to actual
const uStats = parseFloat(userTotalStats);
// Logic:
// t > u (20%+) -> Red
if (tStats > uStats * 1.2) return "stat-red";
// t > u (0-20%) -> Orange
if (tStats > uStats) return "stat-orange";
// u > t (0-5% adv) -> Yellow
if (tStats >= uStats * 0.95) return "stat-yellow";
// u > t (5-10% adv) -> Green
if (tStats >= uStats * 0.90) return "stat-green";
// u > t (10-30% adv) -> Light Green
if (tStats >= uStats * 0.70) return "stat-lightgreen";
// u > t (>50% adv - safe) -> Blue
return "stat-blue";
}
// --- DB FUNCTIONS (REST API) ---
function fetchData() {
if (isPolling) return;
isPolling = true;
// Fetch Assists
GM_xmlhttpRequest({
method: "GET",
url: `${DB_URL}/assists.json`,
onload: function(res) {
if(res.status === 200) {
const data = JSON.parse(res.responseText);
assists = data ? Object.keys(data).map(key => ({ ...data[key], firebaseId: key })) : [];
renderAssists();
}
// Fetch Targets
GM_xmlhttpRequest({
method: "GET",
url: `${DB_URL}/targets.json`,
onload: function(res2) {
isPolling = false;
if(res2.status === 200) {
const data2 = JSON.parse(res2.responseText);
targetsList = data2 ? Object.keys(data2).map(key => ({ ...data2[key], firebaseId: key })) : [];
renderTargets();
}
},
onerror: function() { isPolling = false; }
});
},
onerror: function() { isPolling = false; }
});
}
function startDBPolling() {
fetchData();
const poll = () => { setTimeout(() => { fetchData(); poll(); }, 4000); };
poll();
}
function addItemToDB(node, item) {
return new Promise((resolve, reject) => {
if (!currentUser) { alert("User info missing!"); reject(); return; }
item.addedBy = currentUser.name;
item.timestamp = Date.now();
item.customStats = "";
GM_xmlhttpRequest({
method: "POST",
url: `${DB_URL}/${node}.json`,
data: JSON.stringify(item),
headers: { "Content-Type": "application/json" },
onload: function(res) {
if(res.status === 200) { fetchData(); resolve(true); }
else { alert("DB Error"); reject(res.statusText); }
}
});
});
}
function updateItemInDB(node, firebaseId, updates) {
// Optimistic UI update handled in render
GM_xmlhttpRequest({
method: "PATCH",
url: `${DB_URL}/${node}/${firebaseId}.json`,
data: JSON.stringify(updates),
onload: function() { fetchData(); }
});
}
function removeItemFromDB(node, firebaseId) {
GM_xmlhttpRequest({
method: "DELETE",
url: `${DB_URL}/${node}/${firebaseId}.json`,
onload: function() { fetchData(); }
});
}
function clearTargetsDB() {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "DELETE",
url: `${DB_URL}/targets.json`,
onload: function() { resolve(); }
});
});
}
// --- RENDER FUNCTIONS ---
async function loadUserInfo() {
const container = document.querySelector('#info-content');
try {
const data = await tornApiCall('user/?selections=profile,battlestats,personalstats');
currentUser = data;
// Show/Hide Admin Import Input
const adminSection = document.getElementById('admin-import-section');
if (adminSection) {
adminSection.style.display = (currentUser.player_id === ADMIN_ID) ? 'flex' : 'none';
}
const bsTotal = (data.strength || 0) + (data.defense || 0) + (data.speed || 0) + (data.dexterity || 0);
currentUser.totalStats = bsTotal; // Save for comparison
container.innerHTML = `
<div style="max-width: 600px; margin: 0 auto;">
<div style="background-color: #111; border: 1px solid #333; border-radius: 8px; padding: 20px; margin-bottom: 20px;">
<a href="https://www.torn.com/profiles.php?XID=${data.player_id}" target="_blank" style="color: #fff; font-size: 20px; font-weight: bold; text-decoration: none;">${data.name} [${data.player_id}]</a>
<span style="color: #888; margin-left: 10px;">Level ${data.level}</span>
<div style="margin-top: 20px;">
<div style="color: #fff; font-weight: bold; margin-bottom: 10px; border-bottom: 1px solid #333; padding-bottom: 5px;">Battle Stats</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<div style="background: #000; padding: 10px; border-radius: 5px; border: 1px solid #222;"><div style="color: #888; font-size: 10px;">Strength</div><div style="color: #fff; font-weight: bold;">${formatBigNumber(data.strength)}</div></div>
<div style="background: #000; padding: 10px; border-radius: 5px; border: 1px solid #222;"><div style="color: #888; font-size: 10px;">Defense</div><div style="color: #fff; font-weight: bold;">${formatBigNumber(data.defense)}</div></div>
<div style="background: #000; padding: 10px; border-radius: 5px; border: 1px solid #222;"><div style="color: #888; font-size: 10px;">Speed</div><div style="color: #fff; font-weight: bold;">${formatBigNumber(data.speed)}</div></div>
<div style="background: #000; padding: 10px; border-radius: 5px; border: 1px solid #222;"><div style="color: #888; font-size: 10px;">Dexterity</div><div style="color: #fff; font-weight: bold;">${formatBigNumber(data.dexterity)}</div></div>
</div>
<div style="background: #000; padding: 10px; border-radius: 5px; margin-top: 10px; border: 1px solid #fff; text-align: center;">
<div style="color: #ccc; font-size: 12px;">Total Stats</div>
<div style="color: #fff; font-weight: bold; font-size: 18px;">${formatBigNumber(bsTotal)}</div>
</div>
</div>
</div>
</div>`;
} catch (error) {
container.innerHTML = `<div style="text-align: center; padding: 40px; color: #fff;">Error: ${error}<br>Check Settings.</div>`;
}
}
async function loadFactionMembers() {
const container = document.querySelector('#team-content');
try {
const data = await tornApiCall('faction/?selections=basic');
if (!data.members) throw new Error('No data');
factionMembers = Object.keys(data.members).map(id => {
const member = data.members[id];
// Try to guess rank priority (Leader > Co > ... > Recruit) - simplified string sort usually works ok-ish or mapping
return { id: id, name: member.name, position: member.position, status: member.status.state };
});
// Sort by Position (Simplified Alphabetical Sort for now, helps group ranks)
factionMembers.sort((a, b) => a.position.localeCompare(b.position));
container.innerHTML = `<div style="color: #fff; font-weight: bold; margin-bottom: 15px; font-size: 18px;">Faction Members (${factionMembers.length})</div>
${factionMembers.map(member => `<div class="bw-member-item">
<a href="https://www.torn.com/profiles.php?XID=${member.id}" target="_blank" class="bw-member-name">${member.name} [${member.id}]</a>
<span class="bw-rank-badge">${member.position}</span>
</div>`).join('')}`;
} catch (error) {
container.innerHTML = `<div style="text-align: center; padding: 40px; color: #888;">Error loading faction. Check API Key.</div>`;
}
}
function renderList(nodeType, listData, containerId) {
const list = document.getElementById(containerId);
if (listData.length === 0) { list.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">Empty list</div>'; return; }
const isAdmin = currentUser && currentUser.player_id === ADMIN_ID;
const userTotal = currentUser ? currentUser.totalStats : 0;
list.innerHTML = listData.map((item, index) => {
const isJoined = currentUser && item.participants && Object.values(item.participants).includes(currentUser.name);
const btnClass = isJoined ? "bw-im-in-btn joined" : "bw-im-in-btn";
const btnText = isJoined ? "Leave" : "I'm in";
const participantsList = item.participants ? Object.values(item.participants) : [];
// Stats Logic
let statsHtml = '';
if (isAdmin) {
statsHtml = `<input type="text" class="bw-stats-input" data-node="${nodeType}" data-id="${item.firebaseId}" value="${item.customStats || ''}" placeholder="Stats(k)">`;
} else if (item.customStats) {
const colorClass = getColorForStats(item.customStats, userTotal);
statsHtml = `<span class="bw-stats-display ${colorClass}">${item.customStats}k Stats</span>`;
}
// Slots display (only for assists)
const slotHtml = nodeType === 'assists' ? `<span style="color: #666; margin-left: 10px; font-size: 12px;">(${item.slots} slots)</span>` : '';
// Toggle Button (only for assists, Targets just has Attack)
let actionButtons = '';
if (nodeType === 'assists') {
actionButtons = `<button class="${btnClass}" data-index="${index}">${btnText}</button>`;
}
return `
<div class="${nodeType === 'assists' ? 'bw-assist-item' : 'bw-target-item'}" style="flex-direction: column; align-items: flex-start;">
<div style="width: 100%; display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
<div>
<a href="https://www.torn.com/profiles.php?XID=${item.id}" target="_blank" class="${nodeType === 'assists' ? 'bw-target-name' : 'bw-member-name'}">
${item.name} [${item.id}] <span style="color:#444; font-size:10px;">Lvl ${item.level}</span>
</a>
${slotHtml}
${statsHtml}
</div>
<div>
${actionButtons}
<button class="bw-add-button" data-id="${item.id}" style="margin: 0 5px; font-size: 11px; padding: 5px 10px;">Attack</button>
<button class="bw-remove-button" data-node="${nodeType}" data-id="${item.firebaseId}">Remove</button>
</div>
</div>
${nodeType === 'assists' && participantsList.length > 0 ? `<div class="bw-participants">${participantsList.map(p => `<span class="bw-participant-tag">${p}</span>`).join('')}</div>` : ''}
</div>`;
}).join('');
// Listeners
list.querySelectorAll('.bw-add-button').forEach(btn => btn.addEventListener('click', (e) => window.open(`https://www.torn.com/loader.php?sid=attack&user2ID=${e.target.dataset.id}`, '_blank')));
list.querySelectorAll('.bw-remove-button').forEach(btn => btn.addEventListener('click', (e) => {
if(confirm('Remove this item?')) removeItemFromDB(e.target.dataset.node, e.target.dataset.id);
}));
// Stats Input Listener (Admin)
if (isAdmin) {
list.querySelectorAll('.bw-stats-input').forEach(input => {
input.addEventListener('change', (e) => {
updateItemInDB(e.target.dataset.node, e.target.dataset.id, { customStats: e.target.value });
});
});
}
// Toggle Join Listener (Assists Only)
if (nodeType === 'assists') {
list.querySelectorAll('.bw-im-in-btn').forEach(btn => btn.addEventListener('click', async (e) => {
const index = parseInt(e.target.dataset.index);
const item = listData[index];
if (!currentUser) return;
let parts = item.participants ? Object.values(item.participants) : [];
if (parts.includes(currentUser.name)) { parts = parts.filter(p => p !== currentUser.name); }
else {
if (parts.length >= item.slots) { alert('Full!'); return; }
parts.push(currentUser.name);
}
updateItemInDB('assists', item.firebaseId, { participants: parts });
}));
}
}
function renderAssists() { renderList('assists', assists, 'assists-list'); }
function renderTargets() { renderList('targets', targetsList, 'targets-list'); }
// BUTTON ACTIONS
document.getElementById('add-assist-btn').addEventListener('click', async () => {
const val = document.getElementById('assist-input').value.trim();
if (!val) return;
try {
let data = await tornApiCall(`user/${val}?selections=profile`);
if (!data || !data.player_id) { alert('User not found'); return; }
let slots = parseInt(document.querySelector('.bw-slot-btn.active').dataset.slots);
await addItemToDB('assists', { id: data.player_id, name: data.name, level: data.level, slots: slots, participants: [] });
document.getElementById('assist-input').value = '';
} catch (e) { alert('Error: ' + e); }
});
document.getElementById('add-mytarget-btn').addEventListener('click', async () => {
const val = document.getElementById('mytarget-input').value.trim();
if (!val) return;
try {
let data = await tornApiCall(`user/${val}?selections=profile`);
if (!data || !data.player_id) { alert('User not found'); return; }
await addItemToDB('targets', { id: data.player_id, name: data.name, level: data.level });
document.getElementById('mytarget-input').value = '';
} catch (e) { alert('Error: ' + e); }
});
// ADMIN FACTION IMPORT
const importBtn = document.getElementById('import-faction-btn');
if (importBtn) {
importBtn.addEventListener('click', async () => {
const fid = document.getElementById('faction-import-input').value.trim();
if (!fid) return;
if (!confirm(`This will REPLACE the current target list with all members of Faction ${fid}. Continue?`)) return;
try {
const data = await tornApiCall(`faction/${fid}?selections=basic`);
if (!data.members) throw "No members found";
await clearTargetsDB(); // Wipe current list
// Add all members
const members = Object.values(data.members);
alert(`Importing ${members.length} members... please wait.`);
for (let m of members) {
// Small delay to not hammer Firebase too hard sequentially, though it handles it
await addItemToDB('targets', { id: m.player_id, name: m.name, level: m.level });
}
alert("Import Complete!");
} catch (e) { alert("Import Failed: " + e); }
});
}
// SLOT SELECTOR LOGIC
document.querySelectorAll('.bw-slot-btn').forEach(btn => btn.addEventListener('click', (e) => {
document.querySelectorAll('.bw-slot-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
}));
// MODAL LOGIC
function openModal() {
document.getElementById('bw-war-modal').classList.add('active');
document.getElementById('bw-war-overlay').classList.add('active');
document.getElementById('bw-settings-modal').classList.remove('active');
loadUserInfo(); loadFactionMembers(); startDBPolling();
}
function closeModals() {
document.getElementById('bw-war-modal').classList.remove('active');
document.getElementById('bw-settings-modal').classList.remove('active');
document.getElementById('bw-war-overlay').classList.remove('active');
}
document.getElementById('bw-war-button').addEventListener('click', openModal);
document.querySelector('.bw-settings-button').addEventListener('click', () => {
document.getElementById('bw-war-modal').classList.remove('active');
document.getElementById('bw-settings-modal').classList.add('active');
document.getElementById('api-key-input').value = getApiKey();
});
document.getElementById('bw-war-overlay').addEventListener('click', closeModals);
document.querySelectorAll('.bw-close-button').forEach(b => b.addEventListener('click', closeModals));
document.getElementById('save-api-key').addEventListener('click', () => {
const key = document.getElementById('api-key-input').value.trim();
if(key) { GM_setValue('torn_api_key', key); alert('Saved'); closeModals(); }
});
// TABS LOGIC
const tabs = document.querySelectorAll('.bw-tab');
const contents = document.querySelectorAll('.bw-tab-content');
tabs.forEach(tab => tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
contents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(`${tab.dataset.tab}-content`).classList.add('active');
}));
}
initScript();
})();