Optimized: Points-style PDA panel showing Torn display + abroad stock with flight selection and restock timer. Collapsible. Abroad stock color-coded. Safe polling, debounced layout.
As of
// ==UserScript==
// @name 💫 Points Maker (Optimized + Abroad Color + Flight & Restock)
// @namespace http://tampermonkey.net/
// @version 1.2.6.a
// @description Optimized: Points-style PDA panel showing Torn display + abroad stock with flight selection and restock timer. Collapsible. Abroad stock color-coded. Safe polling, debounced layout.
// @match https://www.torn.com/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const PANEL_ID = 'points_maker_pda';
const POLL_INTERVAL_MS = 45 * 1000;
const YATA_URL = 'https://yata.yt/api/v1/travel/export/';
const PROM_URL = 'https://api.prombot.co.uk/api/travel';
const SPECIAL_DRUG = 'Xanax';
const MAX_ABROAD = { flowers: 50, plushies: 50, drugs: 50 };
const RESTOCK_TIMERS = {
Camel: 22, Panda: 18, Peony: 112, Tribulus: 107, Lion: 19, Violet: 98,
Jaguar: 27, Cherry: 121, Monkey: 23, Heather: 129, Ceibo: 129, 'Red Fox': 28,
Nessie: 23, Stingray: 22, Banana: 95, Orchid: 118, Crocus: 114, Chaimos: 21,
Dahlia: 113, Edelweiss: 100
};
const FLIGHT_TIMES = {
Standard: { 'Mexico: Ciudad Juárez': 26, 'Cayman Islands: George Town': 35, 'Canada: Toronto': 41,
'Hawaii: Honolulu': 134, 'United Kingdom: London': 159, 'Argentina: Buenos Aires': 167,
'Switzerland: Zurich': 175, 'Japan: Tokyo': 225, 'China: Beijing': 242, 'United Arab Emirates: Dubai': 271,
'South Africa: Johannesburg': 297 },
Airstrip: { 'Mexico: Ciudad Juárez': 18, 'Cayman Islands: George Town': 25, 'Canada: Toronto': 29,
'Hawaii: Honolulu': 94, 'United Kingdom: London': 111, 'Argentina: Buenos Aires': 117,
'Switzerland: Zurich': 123, 'Japan: Tokyo': 158, 'China: Beijing': 169, 'United Arab Emirates: Dubai': 190,
'South Africa: Johannesburg': 208 },
WLT: { 'Mexico: Ciudad Juárez': 13, 'Cayman Islands: George Town': 18, 'Canada: Toronto': 20,
'Hawaii: Honolulu': 67, 'United Kingdom: London': 80, 'Argentina: Buenos Aires': 83,
'Switzerland: Zurich': 88, 'Japan: Tokyo': 113, 'China: Beijing': 121, 'United Arab Emirates: Dubai': 135,
'South Africa: Johannesburg': 149 },
Business: { 'Mexico: Ciudad Juárez': 8, 'Cayman Islands: George Town': 11, 'Canada: Toronto': 12,
'Hawaii: Honolulu': 40, 'United Kingdom: London': 48, 'Argentina: Buenos Aires': 50,
'Switzerland: Zurich': 53, 'Japan: Tokyo': 68, 'China: Beijing': 72, 'United Arab Emirates: Dubai': 81,
'South Africa: Johannesburg': 89 }
};
const FLOWERS = {
"Dahlia": { short: "Dahlia", loc: "MX 🇲🇽", country: "Mexico" },
"Orchid": { short: "Orchid", loc: "HW 🏝️", country: "Hawaii" },
"African Violet": { short: "Violet", loc: "SA 🇿🇦", country: "South Africa" },
"Cherry Blossom": { short: "Cherry", loc: "JP 🇯🇵", country: "Japan" },
"Peony": { short: "Peony", loc: "CN 🇨🇳", country: "China" },
"Ceibo Flower": { short: "Ceibo", loc: "AR 🇦🇷", country: "Argentina" },
"Edelweiss": { short: "Edelweiss", loc: "CH 🇨🇭", country: "Switzerland" },
"Crocus": { short: "Crocus", loc: "CA 🇨🇦", country: "Canada" },
"Heather": { short: "Heather", loc: "UK 🇬🇧", country: "United Kingdom" },
"Tribulus Omanense": { short: "Tribulus", loc: "AE 🇦🇪", country: "UAE" },
"Banana Orchid": { short: "Banana", loc: "KY 🇰🇾", country: "Cayman Islands" }
};
const PLUSHIES = {
"Sheep Plushie": { short: "Sheep", loc: "B.B 🏪", country: "Torn City" },
"Teddy Bear Plushie": { short: "Teddy", loc: "B.B 🏪", country: "Torn City" },
"Kitten Plushie": { short: "Kitten", loc: "B.B 🏪", country: "Torn City" },
"Jaguar Plushie": { short: "Jaguar", loc: "MX 🇲🇽", country: "Mexico" },
"Wolverine Plushie": { short: "Wolverine", loc: "CA 🇨🇦", country: "Canada" },
"Nessie Plushie": { short: "Nessie", loc: "UK 🇬🇧", country: "United Kingdom" },
"Red Fox Plushie": { short: "Fox", loc: "UK 🇬🇧", country: "United Kingdom" },
"Monkey Plushie": { short: "Monkey", loc: "AR 🇦🇷", country: "Argentina" },
"Chamois Plushie": { short: "Chamois", loc: "CH 🇨🇭", country: "Switzerland" },
"Panda Plushie": { short: "Panda", loc: "CN 🇨🇳", country: "China" },
"Lion Plushie": { short: "Lion", loc: "SA 🇿🇦", country: "South Africa" },
"Camel Plushie": { short: "Camel", loc: "AE 🇦🇪", country: "UAE" },
"Stingray Plushie": { short: "Stingray", loc: "KY 🇰🇾", country: "Cayman Islands" }
};
const COUNTRY_NAME_TO_CODE = {
'JAPAN': 'JAP', 'MEXICO': 'MEX', 'CANADA': 'CAN', 'CHINA': 'CHI', 'UNITED KINGDOM': 'UNI',
'ARGENTINA': 'ARG', 'SWITZERLAND': 'SWI', 'HAWAII': 'HAW', 'UAE': 'UAE', 'CAYMAN ISLANDS': 'CAY',
'SOUTH AFRICA': 'SOU', 'S.A': 'SOU', 'SA': 'SOU', 'TORN': 'BB', 'B.B': 'BB'
};
// Utility functions
function escapeHtml(s) { if (s == null) return ''; return String(s).replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m])); }
function colorForPercent(value, max) {
if (!max || max === 0) return '#bdbdbd';
const pct = (value / max) * 100;
if (pct >= 75) return '#00c853';
if (pct >= 40) return '#3399ff';
return '#ff1744';
}
// UI Build
let statusEl, summaryEl, contentEl, flightSelectEl;
function buildUI() {
if(document.getElementById(PANEL_ID)) return;
const root = document.createElement('div');
root.id = PANEL_ID;
root.innerHTML = `
<div id="tc_header" class="header">▶ 💫 Points Maker</div>
<div id="tc_content_wrapper">
<div id="tc_controls" class="controls">
<button id="tc_refresh">Refresh</button>
<button id="tc_setkey">Set API Key</button>
<button id="tc_resetkey">Reset Key</button>
<select id="tc_flight_select">
<option value="Standard">Standard</option>
<option value="Airstrip">Airstrip</option>
<option value="WLT">WLT</option>
<option value="Business">Business</option>
</select>
</div>
<div id="tc_status">Waiting for API key...</div>
<div id="tc_summary"></div>
<div id="tc_content"></div>
</div>
`;
document.body.appendChild(root);
statusEl = root.querySelector('#tc_status');
summaryEl = root.querySelector('#tc_summary');
contentEl = root.querySelector('#tc_content');
flightSelectEl = root.querySelector('#tc_flight_select');
// Collapse
const headerEl = root.querySelector('#tc_header');
const contentWrapper = root.querySelector('#tc_content_wrapper');
let collapsed = GM_getValue(`${PANEL_ID}-collapsed`, false);
function updateCollapse() { headerEl.textContent = (collapsed ? '▶ ' : '▼ ') + '💫 Points Maker'; contentWrapper.style.display = collapsed ? 'none' : 'block'; }
updateCollapse();
headerEl.addEventListener('click', () => { collapsed = !collapsed; GM_setValue(`${PANEL_ID}-collapsed`, collapsed); updateCollapse(); });
root.querySelector('#tc_refresh').addEventListener('click', () => refreshAll(true));
root.querySelector('#tc_setkey').addEventListener('click', () => askKey(true));
root.querySelector('#tc_resetkey').addEventListener('click', () => {
GM_setValue('tornAPIKey', null); apiKey = null;
statusEl.textContent = 'Key cleared. Click Set API Key.';
summaryEl.innerHTML = '';
contentEl.innerHTML = '';
stopPolling();
});
}
// API key input
let apiKey = GM_getValue('tornAPIKey', null);
function askKey(force=false) {
if(!force && apiKey) return;
const key = window.prompt('Enter your Torn API Key:', apiKey||'');
if(key && key.trim().length>0){ apiKey = key.trim(); GM_setValue('tornAPIKey', apiKey); if(statusEl) statusEl.textContent='API key saved. Ready.'; refreshAll(true); }
else if(statusEl) statusEl.textContent='No key entered.';
}
buildUI();
// Polling
let pollTimer=null, isRefreshing=false;
function startPolling(){ if(pollTimer) return; pollTimer=setTimeout(()=>refreshAll(true),200);}
function stopPolling(){ if(!pollTimer)return; clearTimeout(pollTimer); pollTimer=null;}
async function refreshAll(force=false){
if(isRefreshing && !force) return;
isRefreshing=true;
if(statusEl) statusEl.textContent='Fetching...';
try{
const tornPromise = fetchTornDisplayInventory().catch(()=>null);
const yataPromise = gmGetJson(YATA_URL).catch(()=>null);
const promPromise = gmGetJson(PROM_URL).catch(()=>null);
const [itemsAgg, yataRaw, promRaw] = await Promise.all([tornPromise, yataPromise, promPromise]);
renderUI(itemsAgg||{}, yataRaw, promRaw);
if(statusEl) statusEl.textContent=`Updated: ${new Date().toLocaleTimeString()}`;
} catch(e){ console.warn(e); if(statusEl) statusEl.textContent='Update failed'; }
finally{ isRefreshing=false; if(pollTimer!==null) pollTimer=setTimeout(()=>refreshAll(false),POLL_INTERVAL_MS);}
}
// --- Insert here all the previous renderUI, parseYata, parseProm, countsForReq, calcSetsAndRemainderFromCounts, findLowest etc ---
// Keep your previous renderUI logic, but now consider:
// 1. Flight type from flightSelectEl.value
// 2. Restock timers to recommend next available item
// 3. Depletion rate checks from YATA/Prom
if(apiKey){ startPolling(); refreshAll(true); }
})();