Blocks travel to destinations where the return time would overlap with OC initiation. Includes responsive toggle switch for manual control.
当前为
// ==UserScript==
// @name OC Timing: Travel Blocker
// @namespace zonure.scripts
// @version 1.0
// @description Blocks travel to destinations where the return time would overlap with OC initiation. Includes responsive toggle switch for manual control.
// @author Zonure [3787510]
// @match https://www.torn.com/page.php?sid=travel
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'oc_travel_block_enabled';
const style = document.createElement('style');
style.textContent = `
.script-disabled-button {
background-color: #a00 !important;
color: crimson !important;
font-weight: bold;
text-transform: uppercase;
}
#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);
let isEnabled = localStorage.getItem(STORAGE_KEY);
if (isEnabled === null) {
isEnabled = 'true';
localStorage.setItem(STORAGE_KEY, isEnabled);
}
isEnabled = isEnabled === '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';
disableButtonIfNeeded();
});
wrapper.prepend(container);
};
const disableButtonIfNeeded = () => {
const travelRoot = document.getElementById('travel-root');
if (!travelRoot || !isEnabled) return;
const modelDataRaw = travelRoot.getAttribute('data-model');
if (!modelDataRaw) return;
let parsed;
try {
parsed = JSON.parse(modelDataRaw);
} catch (e) {
return;
}
const destinations = parsed?.destinations;
let shouldDisable = false;
if (Array.isArray(destinations)) {
for (const dest of destinations) {
for (const key of ['standard', 'airstrip', 'private', 'business']) {
if (dest[key]?.ocReadyBeforeBack === true) {
shouldDisable = true;
break;
}
}
if (shouldDisable) break;
}
}
const buttons = document.querySelectorAll("a.torn-btn.btn-dark-bg, button.torn-btn.btn-dark-bg");
buttons.forEach((btn) => {
const alreadyBlocked = btn.classList.contains("script-disabled-button");
if (btn.textContent.trim() === "Continue") {
if (shouldDisable && !alreadyBlocked) {
btn.disabled = true;
btn.textContent = "DISABLED";
btn.title = "Disabled: OC not ready yet.";
btn.classList.add("script-disabled-button");
btn.onclick = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
console.log("Blocked: OC not ready.");
};
} else if (!shouldDisable && alreadyBlocked) {
btn.disabled = false;
btn.textContent = "Continue";
btn.title = "";
btn.classList.remove("script-disabled-button");
btn.onclick = null;
}
}
});
};
const init = () => {
injectToggle();
disableButtonIfNeeded();
};
const observer = new MutationObserver(() => {
injectToggle();
disableButtonIfNeeded();
});
observer.observe(document.body, { childList: true, subtree: true });
init();
})();