A smart OC timer that works on both Tampermonkey and Torn PDA, auto-detecting the environment.
当前为
// ==UserScript==
// @name Torn Travel OC Warner PDA Compatable
// @namespace http://tampermonkey.net/
// @version 5.2
// @description A smart OC timer that works on both Tampermonkey and Torn PDA, auto-detecting the environment.
// @author BazookaJoe
// @match https://www.torn.com/page.php?sid=travel*
// @require https://code.jquery.com/jquery-3.7.1.min.js
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM.xmlHttpRequest
// @connect api.torn.com
// ==/UserScript==
/* globals jQuery, $, GM_addStyle, GM_setValue, GM_getValue */
(function() {
'use strict';
// --- 1. ENVIRONMENT DETECTION & CONFIGURATION ---
const isStandardEnv = (typeof GM_getValue !== 'undefined' && typeof GM !== 'undefined');
console.log(`Torn OC Warner (v5.2): Running in ${isStandardEnv ? 'Standard (Tampermonkey)' : 'PDA'} mode.`);
const CONFIG = { /* --- UNCHANGED --- */ };
let autoDetectedOcTime = 0;
let travelObserver = null;
// --- 2. DATA STORAGE & API WRAPPERS ---
function saveData(key, value) { isStandardEnv ? GM_setValue(key, value) : localStorage.setItem(key, value); }
function loadData(key, defaultValue) { const value = isStandardEnv ? GM_getValue(key) : localStorage.getItem(key); return value === null || typeof value === 'undefined' ? defaultValue : value; }
function runApiCall(callback) { /* --- UNCHANGED --- */ }
// --- 3. ENVIRONMENT-SPECIFIC LOGIC ---
function getFormattedTimeString(totalSeconds) { /* --- UNCHANGED --- */ }
function runCountdown(ocTimeInSeconds, finalStatus = null) { /* --- UNCHANGED --- */ }
function checkTravelAndHighlight() { /* --- UNCHANGED --- */ }
function initializePdaClickListener() { /* --- UNCHANGED --- */ }
function showFullScreenWarning(type, message, onProceed) { /* --- UNCHANGED --- */ }
function getCalculationValues(pdaClickedButton = null) { /* --- UNCHANGED --- */ }
// --- 4. UI & INITIALIZATION ---
function addUniversalStyles() {
// Only this function is changed.
const styles = `
#oc-universal-panel { position: fixed; top: 130px; right: -300px; width: 300px; height: auto; max-height: calc(100vh - 150px); background-color: #f0f0f0; border: 2px solid #F57C00; border-right: none; box-shadow: 0 4px 12px rgba(0,0,0,0.4); z-index: 99998; border-top-left-radius: 8px; border-bottom-left-radius: 8px; transition: right 0.3s ease-in-out; display: flex; flex-direction: column; color: #333; }
#oc-universal-panel.expanded { right: 0; }
#oc-universal-panel h4 { margin: 0; padding: 10px; font-size: 16px; color: #111; background-color: #e0e0e0; border-bottom: 1px solid #ccc; border-top-left-radius: 6px; flex-shrink: 0; }
#oc-universal-panel-content { padding: 10px; flex-grow: 1; overflow-y: auto; font-size: 12px; }
#oc-universal-toggle { position: fixed; top: 130px; right: 0; width: 35px; height: 50px; background-color: #F57C00; color: white; border: 2px solid #F57C00; border-right: none; border-top-left-radius: 8px; border-bottom-left-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; box-shadow: -2px 2px 8px rgba(0,0,0,0.3); z-index: 99999; }
#oc-countdown-timer { font-family: monospace; font-weight: bold; color: #D35400; text-align: center; font-size: 14px; background: #fff; padding: 5px; border-radius: 4px; margin-bottom: 10px; border: 1px solid #ddd; }
.oc-debug-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; font-size: 11px; }
.oc-debug-input { background-color: #fff; border: 1px solid #ccc; color: #333; padding: 4px; width: 60px; box-sizing: border-box; text-align: right; }
.oc-button { background-color: #e0e0e0; color: #333; border: 1px solid #999; padding: 4px 8px; border-radius: 3px; cursor: pointer; display: block; width: 100%; text-align: center; margin-top: 10px; }
/* --- CORRECTED CSS LOGIC --- */
.oc-adjustment-row, .oc-manual-buffer-row, #hide-adjust-btn {
display: none; /* Hide adjustment rows AND the hide button by default */
}
.oc-adjustment-row.visible, .oc-manual-buffer-row.visible {
display: flex; /* Show rows when they have the .visible class */
}
#hide-adjust-btn.visible {
display: block; /* Show the button when it has the .visible class */
}
/* --- Styles for Tampermonkey Warning --- */
.travel-oc-warning { background: linear-gradient(to bottom, #ffb75e, #ed8f03) !important; color: #000 !important; border: 1px solid #c77600 !important; }
.travel-oc-critical-warning { background: linear-gradient(to bottom, #ff6b6b, #ee1f1f) !important; color: #fff !important; border: 1px solid #c50f0f !important; }
#oc-travel-warning-message { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; font-size: 28px; font-weight: bold; color: white; text-shadow: 2px 2px 5px rgba(0,0,0,0.8); pointer-events: none; }
#oc-travel-warning-message.warning-overlay { background: rgba(255, 152, 0, 0.5); }
#oc-travel-warning-message.critical-overlay { background: rgba(238, 31, 31, 0.5); }
/* --- Styles for PDA Warning --- */
#pda-fullscreen-alert { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 99999; display: flex; justify-content: center; align-items: center; flex-direction: column; color: white; text-align: center; }
#pda-fullscreen-alert.warning { background-color: rgba(255, 152, 0, 0.9); }
#pda-fullscreen-alert.critical { background-color: rgba(238, 31, 31, 0.9); }
.pda-alert-button { padding: 10px 20px; font-size: 1.2em; border: 2px solid white; border-radius: 5px; background-color: rgba(0,0,0,0.3); }
`;
isStandardEnv ? GM_addStyle(styles) : document.head.insertAdjacentHTML('beforeend', `<style>${styles}</style>`);
}
function createUniversalPanel() {
if (document.getElementById('oc-universal-panel')) return;
const panelHTML = ` <div id="oc-universal-panel"> <h4>Travel OC Tools</h4> <div id="oc-universal-panel-content"> <div>OC Countdown:</div> <div id="oc-countdown-timer">Loading...</div> <hr style="border-color: #ccc; margin: 10px 0;"> <div class="oc-debug-row"> <label for="buffer-toggle">Enable Warning Buffer</label> <input type="checkbox" id="buffer-toggle"> </div> <div class="oc-debug-row oc-manual-buffer-row"> <label for="manual-buffer-hours">Buffer (hours)</label> <input type="number" id="manual-buffer-hours" class="oc-debug-input" step="0.1" min="0"> </div> <hr style="border-color: #ccc; margin: 10px 0;"> <div class="oc-debug-row"> <label>Auto OC Time:</label> <span id="debug-oc-time">N/A</span> </div> <div class="oc-debug-row oc-adjustment-row"> <label for="override-oc-time">Adj. OC (h)</label> <input type="number" id="override-oc-time" class="oc-debug-input" step="0.1" placeholder="+/-"> </div> <div class="oc-debug-row"> <label>Est. Flight Time:</label> <span id="debug-flight-time">N/A</span> </div> <div class="oc-debug-row oc-adjustment-row"> <label for="override-flight-time">Adj. Flight (h)</label> <input type="number" id="override-flight-time" class="oc-debug-input" step="0.1" placeholder="+/-"> </div> <button id="hide-adjust-btn" class="oc-button">Hide Adjustments</button> <hr style="border-color: #ccc; margin: 10px 0;"> <input type="password" id="debug-unlock-input" class="oc-debug-input" placeholder="Unlock Adjustments..." style="width:100%; text-align:center; margin-bottom: 5px;"> <button id="debug-unlock-btn" class="oc-button">Unlock</button> <button id="set-api-key-final" class="oc-button">Set API Key</button> </div> </div>`;
const toggleButtonHTML = `<div id="oc-universal-toggle">OCTW</div>`;
$('body').append(panelHTML).append(toggleButtonHTML);
// Event Listeners (Unchanged)
$('#oc-universal-toggle').on('click', () => { const $panel = $('#oc-universal-panel'); $panel.toggleClass('expanded'); $('#oc-universal-toggle').text($panel.hasClass('expanded') ? '»' : 'OCTW'); });
$('#set-api-key-final').on('click', () => { const newKey = prompt("Please enter your Torn API Key:", loadData(CONFIG.STORAGE_KEYS.API_KEY, "")); if (newKey !== null) { saveData(CONFIG.STORAGE_KEYS.API_KEY, newKey.trim()); runApiCall(initializeListeners); } });
$('#debug-unlock-btn').on('click', () => { if ($('#debug-unlock-input').val().toLowerCase() === 'test') { $('.oc-adjustment-row, #hide-adjust-btn').addClass('visible'); saveData(CONFIG.STORAGE_KEYS.SHOW_ADJUST, 'true'); $('#debug-unlock-input').val(''); } });
$('#hide-adjust-btn').on('click', () => { $('#override-oc-time, #override-flight-time').val(''); $('.oc-adjustment-row, #hide-adjust-btn').removeClass('visible'); saveData(CONFIG.STORAGE_KEYS.SHOW_ADJUST, 'false'); if(isStandardEnv) checkTravelAndHighlight(); });
$('#buffer-toggle').on('change', function() { const isChecked = $(this).is(':checked'); saveData(CONFIG.STORAGE_KEYS.USE_BUFFER, String(isChecked)); $('.oc-manual-buffer-row').toggleClass('visible', isChecked); if(isStandardEnv) checkTravelAndHighlight(); });
$('#manual-buffer-hours').on('input', () => { saveData(CONFIG.STORAGE_KEYS.MANUAL_BUFFER, $('#manual-buffer-hours').val()); if(isStandardEnv) checkTravelAndHighlight(); });
if(isStandardEnv) { $('#override-oc-time, #override-flight-time').on('input', () => checkTravelAndHighlight()); }
}
function initializeListeners() { /* --- UNCHANGED --- */ }
function main() { /* --- UNCHANGED --- */ }
// Unchanged full script logic
CONFIG.STORAGE_KEYS = { API_KEY: isStandardEnv ? 'oc_warner_tm_apiKey' : 'oc_warner_pda_apiKey', SHOW_ADJUST: isStandardEnv ? 'oc_warner_tm_showAdjust' : 'oc_warner_pda_showAdjust', USE_BUFFER: isStandardEnv ? 'oc_warner_tm_useBuffer' : 'oc_warner_pda_useBuffer', MANUAL_BUFFER: isStandardEnv ? 'oc_warner_tm_manualBuffer' : 'oc_warner_pda_manualBuffer' };
runApiCall = function(callback) { const apiKey = loadData(CONFIG.STORAGE_KEYS.API_KEY, ""); autoDetectedOcTime = 0; runCountdown(0, "Fetching..."); if (!apiKey) { runCountdown(0, "Set API Key"); if (callback) callback(); return; } const handleSuccess = (result) => { let finalTime = 0; try { if (result.error) throw new Error(`API Error ${result.error.code}: ${result.error.error}`); const infoOc = result.organizedCrime; if (!infoOc || infoOc.status !== "Planning") throw new Error("No OC Planned"); const totalSeconds = infoOc.ready_at - Math.floor(Date.now() / 1000); if (totalSeconds > 0) { finalTime = totalSeconds; runCountdown(finalTime); } else { throw new Error("OC Ready (Delayed)"); } } catch (e) { runCountdown(0, e.message); } finally { autoDetectedOcTime = finalTime; $('#debug-oc-time').text(getFormattedTimeString(autoDetectedOcTime)); if (callback) callback(); } }; const handleError = () => { runCountdown(0, "Network Error"); if (callback) callback(); }; if (isStandardEnv) { GM.xmlHttpRequest({ method: "GET", url: `https://api.torn.com/v2/user/?selections=organizedcrime&key=${apiKey}`, onload: response => handleSuccess(JSON.parse(response.responseText)), onerror: handleError }); } else { fetch(`https://api.torn.com/v2/user/?selections=organizedcrime&key=${apiKey}`).then(response => response.json()).then(handleSuccess).catch(handleError); } };
initializeListeners = function() { if (isStandardEnv) { if (travelObserver) travelObserver.disconnect(); const destinationPanel = document.querySelector('div[class*="destinationPanel___"]'); if (destinationPanel) { travelObserver = new MutationObserver(() => checkTravelAndHighlight()); travelObserver.observe(destinationPanel, { childList: true, subtree: true }); checkTravelAndHighlight(); } } else { initializePdaClickListener(); } };
main = function() { addUniversalStyles(); createUniversalPanel(); if (loadData(CONFIG.STORAGE_KEYS.SHOW_ADJUST, 'false') === 'true') { $('.oc-adjustment-row, #hide-adjust-btn').addClass('visible'); } const useBuffer = loadData(CONFIG.STORAGE_KEYS.USE_BUFFER, 'true') === 'true'; $('#buffer-toggle').prop('checked', useBuffer).trigger('change'); $('#manual-buffer-hours').val(loadData(CONFIG.STORAGE_KEYS.MANUAL_BUFFER, CONFIG.DEFAULT_OC_BUFFER_HOURS)); runApiCall(initializeListeners); };
$(document).ready(main);
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址