Blocks travel to destinations where the return time would overlap with OC initiation. Works on both mobile and desktop. Toggle in UI, disables Continue after selecting a destination.
// ==UserScript==
// @name OC Timing: Travel Blocker (Modular)
// @namespace zonure.scripts
// @version 1.2.0
// @description Blocks travel to destinations where the return time would overlap with OC initiation. Works on both mobile and desktop. Toggle in UI, disables Continue after selecting a destination.
// @author Zonure [3787510]
// @match https://www.torn.com/page.php?sid=travel
// @grant none
// @license MIT
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const DEBUG = false; // Toggle to true for detailed logs
const STORAGE_KEY = 'oc_travel_block_enabled';
let isEnabled = localStorage.getItem(STORAGE_KEY);
if (isEnabled === null) {
isEnabled = 'true';
localStorage.setItem(STORAGE_KEY, isEnabled);
}
isEnabled = isEnabled === 'true';
const style = document.createElement('style');
style.textContent = `
.script-disabled-button {
background-color: #a00 !important;
color: crimson !important;
font-weight: bold;
text-transform: uppercase;
cursor: not-allowed !important;
pointer-events: none;
}
#oc-toggle-container {
margin: 10px 0;
padding: 6px 10px;
background: #222;
color: #fff;
border-radius: 5px;
font-size: 13px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.switch {
position: relative;
display: inline-block;
width: 38px;
height: 20px;
flex-shrink: 0;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.3s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #4caf50;
}
input:checked + .slider:before {
transform: translateX(18px);
}
@media (max-width: 600px) {
#oc-toggle-container {
font-size: 12px;
padding: 4px 8px;
gap: 6px;
}
.switch {
width: 32px;
height: 16px;
}
.slider:before {
height: 10px;
width: 10px;
left: 3px;
bottom: 3px;
}
input:checked + .slider:before {
transform: translateX(14px);
}
}
`;
document.head.appendChild(style);
const getDataModel = () => {
const travelRoot = document.getElementById('travel-root');
if (!travelRoot) return null;
try {
return JSON.parse(travelRoot.getAttribute('data-model'));
} catch (e) {
console.error("❌ Failed to parse data-model:", e);
return null;
}
};
const disableContinueButton = (country, method) => {
const buttons = document.querySelectorAll("a.torn-btn.btn-dark-bg, button.torn-btn.btn-dark-bg");
buttons.forEach((btn) => {
if (btn.textContent.trim() === "Continue") {
console.warn(`🔒 Blocking travel to ${country} via ${method}`);
btn.disabled = true;
btn.textContent = "DISABLED";
btn.title = "Blocked: OC not ready before return.";
btn.classList.add("script-disabled-button");
const prevent = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
};
btn.onclick = prevent;
btn.onmousedown = prevent;
btn.addEventListener("click", prevent, true);
btn.addEventListener("mousedown", prevent, true);
}
});
};
const injectToggle = () => {
const wrapper = document.querySelector('div.content-wrapper.summer');
if (!wrapper || wrapper.querySelector('#oc-toggle-container')) return;
const container = document.createElement('div');
container.id = 'oc-toggle-container';
container.innerHTML = `
<span>Travel Blocker:</span>
<label class="switch">
<input type="checkbox" id="oc-toggle" ${isEnabled ? 'checked' : ''}>
<span class="slider"></span>
</label>
<span id="oc-status">${isEnabled ? 'Enabled' : 'Disabled'}</span>
`;
const input = container.querySelector('#oc-toggle');
const status = container.querySelector('#oc-status');
input.addEventListener('change', () => {
isEnabled = input.checked;
localStorage.setItem(STORAGE_KEY, isEnabled);
status.textContent = isEnabled ? 'Enabled' : 'Disabled';
});
wrapper.prepend(container);
};
const handleDesktopClick = (event) => {
const button = event.target.closest("button.torn-btn.btn-dark-bg, a.torn-btn.btn-dark-bg");
if (!button) return;
const flightGrid = button.closest(".flightDetailsGrid___uAttX");
const countrySpan = flightGrid?.querySelector(".country___wBPip");
const countryName = countrySpan?.textContent.trim();
if (!countryName) return;
if (DEBUG) console.log(`[DESKTOP] Clicked travel button for: ${countryName}`);
const parsed = getDataModel();
const match = parsed?.destinations?.find(dest =>
dest.country.toLowerCase() === countryName.toLowerCase()
);
if (!match) {
console.warn(`⚠️ No destination found in data-model for ${countryName}`);
return;
}
const methodInput = document.querySelector('fieldset.travelTypeSelector___zK5N4 input[name="travelType"]:checked');
const selectedMethod = methodInput?.value;
if (!selectedMethod) {
console.warn("⚠️ Could not detect selected travel method.");
return;
}
if (DEBUG) console.log(`📦 Selected travel method: ${selectedMethod}`);
const ocReady = match[selectedMethod]?.ocReadyBeforeBack;
if (DEBUG) console.log(`🚦 OC Ready Before Back: ${ocReady} for method: ${selectedMethod}`);
if (ocReady === true) {
disableContinueButton(countryName, selectedMethod);
} else {
if (DEBUG) console.log(`✅ Travel allowed to ${countryName} via ${selectedMethod}`);
}
};
const handleMobileClick = (event) => {
const button = event.target.closest("button");
if (!button) return;
const spans = button.querySelectorAll("span");
const countryName = [...spans].map(s => s.textContent.trim())
.find(t => /^[A-Za-z\s]+$/.test(t));
if (!countryName) return;
if (DEBUG) console.log(`[MOBILE] Clicked destination: ${countryName}`);
const parsed = getDataModel();
const match = parsed?.destinations?.find(dest =>
dest.country.toLowerCase() === countryName.toLowerCase()
);
if (!match) {
console.warn(`⚠️ No match found in data-model for ${countryName}`);
return;
}
for (const key of ['standard', 'airstrip', 'private', 'business']) {
const method = match[key];
const ocReady = method?.ocReadyBeforeBack;
if (DEBUG) console.log(`🔍 Checking ${key} => ocReadyBeforeBack: ${ocReady}`);
if (ocReady === true) {
disableContinueButton(countryName, key);
return;
}
}
if (DEBUG) console.log(`✅ Travel allowed to ${countryName} via all methods`);
};
const setupClickInterceptor = () => {
const isMobile = window.matchMedia("(max-width: 600px)").matches;
document.body.addEventListener("click", (e) => {
if (!isEnabled) return;
if (isMobile) {
handleMobileClick(e);
} else {
handleDesktopClick(e);
}
});
};
const init = () => {
console.log("🚀 OC Travel Blocker initialized.");
injectToggle();
setupClickInterceptor();
};
const observer = new MutationObserver(() => injectToggle());
observer.observe(document.body, { childList: true, subtree: true });
init();
})();