Sedition AIO script
// ==UserScript==
// @name SheepieAIO SPVersion
// @author SheepPrincess [2679129]
// @namespace SneakyBeakySheepieChaining Cell
// @version SP-v2.3.3
// @description Sedition AIO script
// @license MIT
// @match https://www.torn.com/*
// @grant unsafeWindow
// @match https://www.torn.com/factions.php?step=your*
// @icon https://sheepie.ca/img/SheepieWorld64.png
// @connect tornprobability.com
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect script.google.com
// @grant GM_registerMenuCommand
// @connect script.googleusercontent.com
// @connect api.torn.com
// @require https://www.torn.com/js/script/lib/jquery-1.8.2.js
// @connect https://sheepie.ca/
// @grant GM_xmlhttpRequest
// @connect sheepie.ca
// @connect sheepie.ca*
// @connect https://sheepie.ca/*
// ==/UserScript==
// Forked and borrowed work from
//-Chain Timer Enhancer
//authors Omanpx [1906686] & Flav [2499383]
//- EHeasley's Egg Navigator 1.5.2
//Traverse every page in Torn in search for eggs
//author Heasleys4hemp [1468764]
//- Fast Slots 0.3
//Makes slots stop instantly. Works for every spin except first.
//author Silmaril [2665762]
//- Crime Morale 1.4.13
//Scamming helper
//author tobytorn [1617955]
//- OC Role Display - Ravage Edition 3.1.2
//Color Coding OC positions, Weight % added
//author NotIbbyz (Darrellia [2802767]) & Allenone [2033011]
//original work
//- Torn Pickpocketing Colors 0.4
//Color codes crimes based on difficulty
//author Korbrm [2931507]
//- Torn Market Filler 0.6.3
//On "Fill" click autofills market item price with lowest market price minus $1 (customizable), fills max quantity, marks checkboxes for guns.
//author Silmaril [2665762]
//- Torn: Refill Blood Bag Reminder 4.3.0
//Show blood-bag icon when ready to fill blood bags. Configurable 1-3 bags with dynamic life/cooldown thresholds.
//author ButtChew [3840391]
//- Torn: Attack Better 1.7.1
//(toggle) Move Torn "Start Fight" button on top of your weapon of choice and remove certain elements to help with load times.
//author smokey_ [2492729]
//- Torn: Fast Packs 0.4
//Removes animations that play when opening supply packs
//author Hemicopter [2780600]
//- Don't forget your Xanax 2024-06-26
//Reminds you to take your xanax when you have no drug cooldown
//author Shade
(function SheepieAIO() {
'use strict';
// ===== Sheepie localStorage Performance Proxy =====
const originalGetItem = localStorage.getItem.bind(localStorage);
const originalSetItem = localStorage.setItem.bind(localStorage);
const SheepieStorageCache = {};
localStorage.getItem = function(key) {
if (key in SheepieStorageCache) {
return SheepieStorageCache[key];
}
const value = originalGetItem(key);
SheepieStorageCache[key] = value;
return value;
};
localStorage.setItem = function(key, value) {
SheepieStorageCache[key] = value;
return originalSetItem(key, value);
};
// ===== Sheepie Smart Interval Controller =====
const SheepieIntervals = [];
function sheepieSetInterval(fn, delay) {
const wrapped = function() {
if (document.hidden) return; // pause when tab not visible
fn();
};
const id = setInterval(wrapped, delay);
SheepieIntervals.push(id);
return id;
}
function isTornPDA() {
return /TornPDA/i.test(navigator.userAgent);
}
if (isTornPDA()) {
let sheeptall = 20;
localStorage.setItem("sheeptall", sheeptall);
let sheepwide = 50;
localStorage.setItem("sheepwide", sheepwide);
let sheeptall2 = 20;
localStorage.setItem("sheeptall2", sheeptall2);
let sheepwide2 = 30;
localStorage.setItem("sheepwide2", sheepwide2);
} else {
let sheeptall = 30;
localStorage.setItem("sheeptall", sheeptall);
let sheepwide = 90;
localStorage.setItem("sheepwide", sheepwide);
let sheeptall2 = 20;
localStorage.setItem("sheeptall2", sheeptall2);
let sheepwide2 = 40;
localStorage.setItem("sheepwide2", sheepwide2);}
let sheepwide = Number(localStorage.getItem("sheepwide")) || 120;
let sheeptall = Number(localStorage.getItem("sheeptall")) || 50;
let sheepwide2 = Number(localStorage.getItem("sheepwide2")) || 80;
let sheeptall2 = Number(localStorage.getItem("sheeptall2")) || 30;
(function() {
'use strict';
function saveSidebarUsername() {
const sidebar = document.querySelector("#sidebarroot");
if (!sidebar) return setTimeout(saveSidebarUsername, 500); // retry in 500ms
// Find the username link (first <a> in sidebar that is not Messages/Logout)
const usernameLink = Array.from(sidebar.querySelectorAll("a"))
.find(a => a.textContent.trim().length > 0 &&
!a.href.includes("messages") &&
!a.href.includes("logout"));
if (usernameLink) {
const username = usernameLink.textContent.trim();
// Save to localStorage only if it hasn't been saved yet or changed
if (localStorage.getItem("username") !== username) {
localStorage.setItem("username", username);
}
} else {
// Retry in 500ms if username not found yet
setTimeout(saveSidebarUsername, 500);
}
}
saveSidebarUsername();
})();
const username = localStorage.getItem("username");
if (username === "SheepPrincess" || username === "Baws") {
if (localStorage.getItem("sheepcolour") === null) {
localStorage.setItem("sheepcolour", "#FFB5C0");}
} else if (username === "Extrovert") {
if (localStorage.getItem("sheepcolour") === null) {
localStorage.setItem("sheepcolour", "red");}
} else {
if (localStorage.getItem("sheepcolour") === null) {
localStorage.setItem("sheepcolour", "#734180");}
const sheepColour = localStorage.getItem("sheepcolour");
}
let sheepColour = localStorage.getItem("sheepcolour");
requestIdleCallback(() => {
(function () {
'use strict';
const TICKER_ID = "sheepie-news-ticker";
const TICKER_URL = "https://sheepie.ca/code.html";
const REFRESH_INTERVAL = 5 * 60 * 1000;
const NEWS_CHECK_INTERVAL = 5 * 60 * 1000;
// 🚫 Prevent duplicate tickers
if (document.getElementById(TICKER_ID)) {
return;
}
// Create ticker container
const ticker = document.createElement("div");
ticker.id = TICKER_ID; // 👈 important
ticker.style.position = "relative";
ticker.style.width = "100%";
ticker.style.background = sheepColour;
ticker.style.color = "black";
ticker.style.padding = "6px 0";
ticker.style.overflow = "hidden";
ticker.style.fontFamily = "Arial";
ticker.style.fontWeight = "bold";
ticker.style.zIndex = "99999";
const content = document.createElement("div");
content.style.display = "inline-block";
content.style.whiteSpace = "nowrap";
ticker.appendChild(content);
document.body.prepend(ticker);
// Push Torn content down
const tickerHeight = ticker.offsetHeight;
document.body.style.paddingTop = 0 + "px";
// Function to fetch news
function fetchNews() {
const lastCheck = parseInt(localStorage.getItem("news_timestamp") || 0);
const now = Date.now();
if (now - lastCheck < NEWS_CHECK_INTERVAL) {
// Less than 2 minutes, skip fetching
return;
}
GM_xmlhttpRequest({
method: "GET",
url: TICKER_URL + "?t=" + now,
onload: function (response) {
const html = response.responseText;
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const items = Array.from(doc.querySelectorAll("#ticker-items li"))
.map(li => li.textContent.trim())
.filter(line => line.length > 0);
// Save news and timestamp to localStorage
localStorage.setItem("news_items", JSON.stringify(items));
localStorage.setItem("news_timestamp", now);
// Check first line for SheepieAIO
if (items.length > 0 && items[0].includes("SheepieAIO")) {
localStorage.setItem("warmode", "true");
} else {
localStorage.setItem("warmode", "false");
}
// Update ticker
updateTicker(items);
},
onerror: function (err) {
console.error("Failed to fetch news", err);
}
});
}
// Function to update ticker content
function updateTicker(items) {
if (items.length === 0) {
content.innerHTML = "<span style='margin-right:60px;'>No ticker items found</span>";
return;
}
// Duplicate items for seamless scrolling
const allItems = items.concat(items);
// Add sheep emote between each item
content.innerHTML = allItems
.map((line, index) => {
const emote = (index < allItems.length - 1) ? " 🐑 " : "";
return `<span style="margin-right:60px;">${line}${emote}</span>`;
})
.join("");
// Remove previous animation
content.style.animation = "";
// Smooth scroll speed
const scrollWidth = content.scrollWidth / 2; // half because duplicated
const pixelsPerSecond = 40; // adjust speed
const duration = scrollWidth / pixelsPerSecond;
content.style.display = "inline-block";
content.style.paddingLeft = "0";
content.style.animation = `scrollLeft ${duration}s linear infinite`;
}
// Initial ticker load from stored news (if any)
const storedNews = JSON.parse(localStorage.getItem("news_items") || "[]");
if (storedNews.length > 0) updateTicker(storedNews);
// Load ticker every REFRESH_INTERVAL (content scroll refresh)
sheepieSetInterval(() => {
const news = JSON.parse(localStorage.getItem("news_items") || "[]");
if (news.length > 0) updateTicker(news);
}, REFRESH_INTERVAL);
// Fetch news every 2 minutes (or first time)
fetchNews();
sheepieSetInterval(fetchNews, NEWS_CHECK_INTERVAL);
// Add CSS for smooth scroll and hover pause
const style = document.createElement("style");
style.textContent = `
@keyframes scrollLeft {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
div:hover {
animation-play-state: paused !important;
}
`;
document.head.appendChild(style);
})();
});
(function () {
'use strict';
const STORAGE_KEY = "mySimpleToggleState";
// Create button
const button = document.createElement("button");
button.style.position = "fixed";
button.style.bottom = "40px";
button.style.left = "50px";
button.style.fontSize = "14px";
button.style.borderRadius = "8px";
button.style.border = "1px solid #888";
button.style.cursor = "pointer";
button.style.zIndex = "9999998";
// Load saved state
let isOn = localStorage.getItem(STORAGE_KEY) === "true";
function updateButton() {
button.textContent = isOn ? "ON" : "OFF";
button.style.backgroundColor = isOn ? "#4CAF50" : "#ccc";
button.style.color = isOn ? "#fff" : "#000";
}
// Toggle logic
button.addEventListener("click", function () {
isOn = !isOn;
localStorage.setItem(STORAGE_KEY, isOn);
updateButton();
location.reload();
});
updateButton();
document.body.appendChild(button);
})();
const isOn = localStorage.getItem("mySimpleToggleState") === "true";
if (isOn) {
sessionStorage.removeItem("floatingTimerRemaining");
function createButtonAtPositionToDo(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = '<h1>Timer Info</h1><p>hold middle click and drag to move timer<br><br><br>295s darkgreen<br>240s green<br>210s lightgreen<br>160s yellow<br>120s orange<br>95s red<br>90s orange<br>75s alternate red-orange<br>35s darkred </p><h2>ToDo</h2><p>bug testing - smooth out sidebar a bit thin the icons out mayhaps<br>add harambe memorial<br> see how difficult making the chain save buttons wiggle would be <br>add editable notes tab, use localstorage to store notes, call notes on open of tab, save notes on exit and button <br>Add a credits tab<br> rgb easteregg button <br> racing reminder toggle <br> xanax toggle <br> toggle dropdown menu?</p>';
customButton.style.width = "450px";
customButton.style.height = "800px";
customButton.style.position = 'fixed';
customButton.style.bottom = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.borderRadius = '5px'
customButton.addEventListener('click', function() {
customButton.style.display = 'none';
});
document.body.appendChild(customButton);
}
(function () {
'use strict';
const STORAGE_KEY = "SheepieWatch";
// Initialize value if it doesn't exist
if (localStorage.getItem(STORAGE_KEY) === null) {
localStorage.setItem(STORAGE_KEY, "false");
}
// Create button
const btn = document.createElement("button");
btn.style.position = "fixed";
btn.style.bottom = "40px";
btn.style.left = "55px";
btn.style.borderRadius = "8px";
btn.style.border = "2px solid #444";
btn.style.cursor = "pointer";
btn.style.zIndex = "9999";
btn.style.width = "120px";
btn.style.height = "50px";
function isTornPDA() {
return /TornPDA/i.test(navigator.userAgent);
}
if (isTornPDA()) {
btn.style.bottom = '333990px';
btn.style.left = '3339990px';}
function updateButton() {
const value = localStorage.getItem(STORAGE_KEY) === "true";
btn.textContent = `SheepieWatch: ${value ? "ON 🟢" : "OFF 🔴"}`;
btn.style.background = value ? "#b5f5c0" : "#f5b5b5";
}
btn.addEventListener("click", function () {
const current = localStorage.getItem(STORAGE_KEY) === "true";
localStorage.setItem(STORAGE_KEY, (!current).toString());
updateButton();
window.location.href = window.location.href;
});
updateButton();
(function() {
'use strict';
// Create button
const infoButton = document.createElement('button');
infoButton.textContent = 'Scripts';
Object.assign(infoButton.style, {
position: 'fixed',
top: '30px',
left: '170px',
zIndex: 999999,
color: 'black',
background: sheepColour,
color: 'black',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
width: '4%',
fontSize: '74%',
});
// Create info window
const infoWindow = document.createElement('div');
infoWindow.innerHTML = `
<div id="infoHeader" style="font-weight:bold; cursor:move; margin-bottom:10px;">
Info <span id="closeInfo" style="float:right; cursor:pointer; color:red;">✖</span>
</div>
<div id="infoContent" style="font-size:0.9em; line-height:1.2em; max-width:600px;">
<h1>please click the lil arrow for dropdown info! </h1><h2>close the window with the lil X in the top right!! its hard to see --></h2>
<p><strong>Stolen/forked & heavily edited by SheepPrincess [2679129]</strong> with help from evil AI ;-;<br>
Not a real coder — just simplifying life for friends & factionmates ❤️</p>
<details>
<summary><strong>"Dumb" Chain Timer</strong></summary>
<ul>
<li>"Dumb" chain timer: sets remaining chaintime on page focus loss, ignores hits when page not focused</li>
</ul>
</details>
<details>
<summary><strong>Video Player</strong></summary>
<ul>
<li>Embedded small basic video player in a button</li>
</ul>
</details>
<details>
<summary><strong>Chain Timer Enhancer</strong> (heavily modded, disabled when page out of focus)</summary>
<ul>
<li>Resizable/moveable timer with improved color coding</li>
<li>Click to go to target attack page, middle-mouse drag to move</li>
<li>Low chain: creates multiple "SaveChain" buttons (persist 10s, random targets, enables BetterAttack script; disabled on attack screens)</li>
</ul>
</details>
<details>
<summary><strong>EHeasley's Egg Navigator 1.5.2</strong></summary>
<p>Traverse Torn pages searching for eggs.</p>
</details>
<details>
<summary><strong>Egg Finder 1.1.0</strong></summary>
<ul>
<li>Notifies if there is an egg on page</li>
<li>Centers egg & makes it float over everything</li>
<li>Enlarges egg & clickable area ~400%</li>
<li>Outlines hitbox in red</li>
</ul>
</details>
<details>
<summary><strong>Fast Slots 0.3</strong></summary>
<p>Stops slots instantly (except first spin)</p>
</details>
<details>
<summary><strong>Crime Morale 1.4.13</strong></summary>
<p>Scamming helper</p>
</details>
<details>
<summary><strong>OC Role Display - Ravage Edition 3.1.2</strong></summary>
<p>Color codes OC positions & weight %</p>
</details>
<details>
<summary><strong>Torn Pickpocketing Colors 0.4</strong></summary>
<p>Color codes crimes by difficulty</p>
</details>
<details>
<summary><strong>Torn Market Filler 0.6.3</strong></summary>
<p>Autofills lowest market price - $1, max quantity, gun checkboxes</p>
</details>
<details>
<summary><strong>Torn: Refill Blood Bag Reminder 4.3.0</strong></summary>
<p>Shows icon when ready; configurable 1–3 bags</p>
</details>
<details>
<summary><strong>Torn: Attack Better 1.7.1</strong></summary>
<p>Moves "Start Fight" button atop chosen weapon; removes some elements for faster load</p>
</details>
<details>
<summary><strong>Torn: Fast Packs 0.4</strong></summary>
<p>Removes supply pack animations</p>
</details>
<details>
<summary><strong>Don't forget your Xanax 2024-06-26</strong></summary>
<p>Reminds you to take Xanax when no drug cooldown</p>
</details>
</div>
`;
Object.assign(infoWindow.style, {
display: 'none',
position: 'fixed',
bottom: '100px',
left: '100px',
width: '800px',
maxWidth: '80%',
background: sheepColour,
color: 'black',
border: '2px solid #4CAF50',
borderRadius: '8px',
boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
padding: '1px',
zIndex: 99999999,
cursor: 'move',
resize: 'both',
overflow: 'auto'
});
document.body.appendChild(infoWindow);
const closeBtn = infoWindow.querySelector('#closeInfo');
const header = infoWindow.querySelector('#infoHeader');
// Toggle window
infoButton.addEventListener('click', () => {
infoWindow.style.display = infoWindow.style.display === 'block' ? 'none' : 'block';
});
// Close button
closeBtn.addEventListener('click', () => {
infoWindow.style.display = 'none';
});
function createButtonAtPositionShow(text, topPx, LeftPx) {
var customButton = document.createElement("button");
customButton.style.width = "120px";
customButton.style.height = "80px";
customButton.innerHTML = 'V2.3.1 ~SeditionPublic~ Ɛ>~Version~<3 Click To Show Sidebar';
customButton.style.position = 'absolute';
customButton.style.top = topPx + 'px';
customButton.style.left = LeftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = "1px solid purple";
customButton.style.cursor = 'pointer';
customButton.style.zIndex = '999999';
function isTornPDA() {
return /TornPDA/i.test(navigator.userAgent);
}
if (isTornPDA()) {
customButton.style.position = 'absolute';
customButton.style.top = '333990px';
customButton.style.left = '3339990px';
customButton.style.width = '0px';
// PDA-specific code here
}
customButton.addEventListener('click', function() {
createButtonAtPosition2("Sheepie", 50, 25);
createButtonAtPosition("ResetSize", 80, 25);
createButtonAtPosition4("TargetList", 110, 25);
createButtonAtPosition5("FFScouter", 140, 25);
createButtonAtPosition6("FacDrugs", 170, 25);
createButtonAtPosition7("FacBoosters", 200, 25);
createButtonAtPosition8("FacMeds", 230, 25);
createButtonAtPosition9("Chain", 260, 25);
createButtonAtPosition3("CustomLink", 290, 25);
createButtonAtPosition11("Bugs", 40, 20);
createButtonAtPosition10("ToDo", 40, 60);
createButtonAtPosition12("Sheepiepage", 40, 100);
document.body.appendChild(cornerGif2);
document.body.appendChild(cornerGif2);
document.body.appendChild(cornerGif4);
customButton.style.display = 'none';
document.body.appendChild(btn);
document.body.appendChild(infoButton);
});
requestIdleCallback(() => {
document.body.appendChild(customButton);});
}
createButtonAtPositionShow("ShowSidebar", 50, 25);
const cornerGif = document.createElement('img');
cornerGif.src = 'https://factionimages.torn.com/85dbb882-5766-4e03-a985-71af719051bd-14432.gif';
cornerGif.style.position = 'fixed';
cornerGif.style.top = '10px';
cornerGif.style.left = '10px';
cornerGif.style.width = '150px';
cornerGif.style.zIndex = '999999';
cornerGif.style.border = "1px solid purple";
cornerGif.addEventListener('click', function() {
function isTornPDA() {
return /TornPDA/i.test(navigator.userAgent);
}
if (isTornPDA()) {
createButtonAtPosition2("Sheepie", 50, 25);
createButtonAtPosition("ResetSize", 70, 25);
createButtonAtPosition4("TargetList", 90, 25);
createButtonAtPosition5("FFScouter", 110, 25);
createButtonAtPosition6("FacDrugs", 130, 25);
createButtonAtPosition7("FacBoosters", 150, 25);
createButtonAtPosition8("FacMeds", 170, 25);
createButtonAtPosition9("Chain", 190, 25);
createButtonAtPosition3("CustomLink", 210, 25);
createButtonAtPosition11("Bugs", 40, 20);
createButtonAtPosition10("ToDo", 40, 50);
createButtonAtPosition12("Sheepiepage", 40, 80);
document.body.appendChild(cornerGif2);
document.body.appendChild(cornerGif2);
document.body.appendChild(cornerGif4);
document.body.appendChild(btn);
document.body.appendChild(infoButton);
} else {
window.open('https://www.torn.com/factions.php?step=profile&ID=14432').focus();}
});
requestIdleCallback(() => {
document.body.appendChild(cornerGif);
});
})()
})();
if (isToggleEnabled()) {}
(function () {
'use strict';
if (window.tmYTInjected) return;
window.tmYTInjected = true;
const STORAGE_KEY = "tmYTState";
const VISIBILITY_KEY = "tmYTVisible";
const POSITION_KEY = "tmYTPosition"; // original unminimized position/size
const AUTO_SAVE_MS = 1000;
let accumulatedTime = 0;
let sessionStart = null;
let autoSaveInterval = null;
let isMinimized = false;
let hasInteracted = false;
/* ---------- Buttons ---------- */
const openBtn = document.createElement("button");
openBtn.textContent = "YT";
openBtn.style.cssText = `
position: fixed; bottom: 40px; left: 0px; z-index: 10000001;
background: red; color: white; font-size: 24px; border: 1px solid purple;
border-radius: 4px; padding: 6px 10px; cursor: pointer;
`
if (isTornPDA()) {
openBtn.style.position = 'absolute';
openBtn.style.top = '333990px';
openBtn.style.left = '3339990px';
openBtn.style.width = '0px';
// PDA-specific code here
};
const closeBtn = document.createElement("button");
closeBtn.textContent = "YT";
closeBtn.style.cssText = `
position: fixed; bottom: 40px; left: 0px; z-index: 9999999;
background: red; color: white; font-size: 24px; border: 1px solid purple;
border-radius: 4px; padding: 6px 10px; cursor: pointer; display: none;
}`
;
requestIdleCallback(() => {
document.body.appendChild(openBtn);
document.body.appendChild(closeBtn);
});
/* ---------- Player Wrapper ---------- */
const wrapper = document.createElement("div");
wrapper.style.cssText = `
position: fixed; bottom: 60px; right: 40px; width: 420px; height: 340px;
background: #0f0f0f; border: 1px solid #333; border-radius: 8px;
z-index: 9999998; display: none; flex-direction: column; font-family: Arial;
box-shadow: 0 8px 20px rgba(0,0,0,0.6); overflow: hidden; cursor: default;
`;
wrapper.inert = true;
wrapper.innerHTML = `
<div id="tm-header" style="background:#1b1b1b;padding:6px;color:#ccc;display:flex;justify-content:space-between;cursor:move">
<span id="tm-title">YT 00:00</span>
<button id="tm-minimize" style="background:none;border:none;color:#ccc;cursor:pointer">–</button>
</div>
<div id="tm-content" style="display:flex;flex-direction:column;flex:1;overflow:hidden;">
<div style="display:flex;">
<input id="tm-input" placeholder="Paste YouTube link"
style="flex:1;border:none;padding:6px;font-size:12px;outline:none;color:#ccc;background:#151515">
<button id="tm-go" style="padding:6px 8px;font-size:12px;background:#333;color:#ccc;border:none;cursor:pointer">Go</button>
</div>
<iframe id="tm-frame" style="flex:1;border:none"
allow="autoplay; encrypted-media" allowfullscreen></iframe>
<div style="display:flex;justify-content:flex-end;background:#1b1b1b;padding:4px;gap:4px">
<button id="tm-save" style="padding:4px 6px;font-size:12px;background:#333;color:#ccc;border:none;border-radius:4px;cursor:pointer">Save Position</button>
<button id="tm-clear" style="padding:4px 6px;font-size:12px;background:#444;color:#ccc;border:none;border-radius:4px;cursor:pointer">Clear Saved Video</button>
</div>
</div>
<div id="tm-resize"
style="width:12px;height:12px;background:#444;position:absolute;bottom:2px;right:2px;cursor:se-resize">
</div>
`;
document.body.appendChild(wrapper);
const frame = wrapper.querySelector("#tm-frame");
const input = wrapper.querySelector("#tm-input");
const saveBtn = wrapper.querySelector("#tm-save");
const goBtn = wrapper.querySelector("#tm-go");
const clearBtn = wrapper.querySelector("#tm-clear");
const minimizeBtn = wrapper.querySelector("#tm-minimize");
const contentDiv = wrapper.querySelector("#tm-content");
const header = wrapper.querySelector("#tm-header");
const titleSpan = wrapper.querySelector("#tm-title");
/* ---------- Storage Helpers ---------- */
function saveState(videoId, playlistId, time) {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ videoId, playlistId, time }));
}
function loadState() {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
}
function clearState() {
localStorage.removeItem(STORAGE_KEY);
accumulatedTime = 0;
sessionStart = null;
}
function saveVisibility(visible) {
localStorage.setItem(VISIBILITY_KEY, visible ? "1" : "0");
}
function loadVisibility() {
return localStorage.getItem(VISIBILITY_KEY) === "1";
}
function saveWrapperPosition(pos) {
localStorage.setItem(POSITION_KEY, JSON.stringify(pos));
}
function loadWrapperPosition() {
return JSON.parse(localStorage.getItem(POSITION_KEY) || "{}");
}
/* ---------- Video Load ---------- */
function extractIds(url) {
const videoMatch = url.match(/(?:v=|youtu\.be\/)([\w-]+)/);
const playlistMatch = url.match(/[?&]list=([\w-]+)/);
return { videoId: videoMatch ? videoMatch[1] : null, playlistId: playlistMatch ? playlistMatch[1] : null };
}
function buildEmbedURL(url, startTime = 0, muted = true) {
const { videoId, playlistId } = extractIds(url);
const muteParam = muted ? "&mute=1" : "";
if (playlistId && !videoId) return `https://www.youtube.com/embed/videoseries?list=${playlistId}&autoplay=1&start=${startTime}${muteParam}`;
if (videoId) {
if (playlistId) return `https://www.youtube.com/embed/${videoId}?list=${playlistId}&autoplay=1&start=${startTime}${muteParam}`;
return `https://www.youtube.com/embed/${videoId}?autoplay=1&start=${startTime}${muteParam}`;
}
return null;
}
function loadVideo(url) {
const state = loadState();
const { videoId, playlistId } = extractIds(url);
const embedURL = buildEmbedURL(url, state.time || 0, !hasInteracted);
if (!embedURL) return;
if (!frame.src || !frame.src.includes(videoId || playlistId)) frame.src = embedURL;
saveState(videoId, playlistId, state.time || 0);
}
/* ---------- Timer ---------- */
function formatTime(s) {
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
return `${h>0?h+":" : ""}${m.toString().padStart(2,"0")}:${sec.toString().padStart(2,"0")}`;
}
function updateTitleTimestamp() {
const elapsed = sessionStart ? Math.floor((Date.now() - sessionStart)/1000) : 0;
titleSpan.textContent = `YT ${formatTime(accumulatedTime + elapsed)}`;
}
function startAutoSave() {
if (autoSaveInterval) return;
sessionStart = Date.now();
autoSaveInterval = setInterval(() => {
const elapsed = Math.floor((Date.now() - sessionStart)/1000);
const totalTime = accumulatedTime + elapsed;
const state = loadState();
saveState(state.videoId, state.playlistId, totalTime);
updateTitleTimestamp();
}, AUTO_SAVE_MS);
}
function stopSessionTimer() {
if (!sessionStart) return;
const elapsed = Math.floor((Date.now() - sessionStart)/1000);
accumulatedTime += elapsed;
sessionStart = null;
if (autoSaveInterval) {
clearInterval(autoSaveInterval);
autoSaveInterval = null;
}
}
/* ---------- Restore State ---------- */
const lastState = loadState();
accumulatedTime = lastState.time || 0;
let savedPos = loadWrapperPosition();
const defaultWidth = 420;
const defaultHeight = 340;
wrapper.style.left = savedPos.left != null ? Math.min(savedPos.left, window.innerWidth - (savedPos.width || defaultWidth)) + "px" : "40px";
wrapper.style.top = savedPos.top != null ? Math.min(savedPos.top, window.innerHeight - (savedPos.height || defaultHeight)) + "px" : "60px";
wrapper.style.width = (savedPos.width || defaultWidth) + "px";
wrapper.style.height = (savedPos.height || defaultHeight) + "px";
if (!frame.src && (lastState.videoId || lastState.playlistId)) {
const url = lastState.videoId ? `https://www.youtube.com/watch?v=${lastState.videoId}` : `https://www.youtube.com/playlist?list=${lastState.playlistId}`;
loadVideo(url);
}
/* ---------- Show/Hide ---------- */
function updateButtons() {
if (wrapper.style.display === "none") {
openBtn.style.display = "block";
closeBtn.style.display = "none";
} else {
openBtn.style.display = "none";
closeBtn.style.display = "block";
}
}
function minimizePlayer() {
isMinimized = true;
contentDiv.style.display = "none";
wrapper.style.height = "30px";
}
function restorePlayer() {
isMinimized = false;
contentDiv.style.display = "flex";
wrapper.style.width = (savedPos.width || defaultWidth) + "px";
wrapper.style.height = (savedPos.height || defaultHeight) + "px";
}
function showPlayer(visible) {
wrapper.style.display = visible ? "flex" : "none";
wrapper.inert = !visible;
saveVisibility(visible);
if (visible) {
restorePlayer();
startAutoSave();
} else {
stopSessionTimer();
}
updateButtons();
}
openBtn.addEventListener("click", () => showPlayer(true));
closeBtn.addEventListener("click", () => {
showPlayer(false);
clearState();
});
minimizeBtn.addEventListener("click", () => {
if (contentDiv.style.display === "none") restorePlayer();
else minimizePlayer();
});
/* ---------- Input ---------- */
function handleInput() {
const url = input.value.trim();
if (!url) return;
loadVideo(url);
input.value = "";
hasInteracted = true;
}
input.addEventListener("keypress", e => { if (e.key === "Enter") handleInput(); });
goBtn.addEventListener("click", handleInput);
/* ---------- Save / Clear ---------- */
saveBtn.addEventListener("click", () => {
const elapsed = sessionStart ? Math.floor((Date.now() - sessionStart)/1000) : 0;
const totalTime = accumulatedTime + elapsed;
const state = loadState();
saveState(state.videoId, state.playlistId, totalTime);
alert(`Position saved at ~${totalTime} seconds`);
});
clearBtn.addEventListener("click", () => {
clearState();
frame.src = "";
alert("Saved video data cleared.");
});
/* ---------- Drag ---------- */
let dragging = false, offsetX, offsetY;
header.addEventListener("mousedown", (e) => { dragging = true; offsetX = e.clientX - wrapper.offsetLeft; offsetY = e.clientY - wrapper.offsetTop; });
document.addEventListener("mousemove", (e) => {
if (!dragging) return;
wrapper.style.left = e.clientX - offsetX + "px";
wrapper.style.top = e.clientY - offsetY + "px";
wrapper.style.right = "auto";
wrapper.style.bottom = "auto";
});
document.addEventListener("mouseup", () => {
if (dragging) {
dragging = false;
if (!isMinimized) {
savedPos.left = parseInt(wrapper.style.left,10);
savedPos.top = parseInt(wrapper.style.top,10);
savedPos.width = wrapper.offsetWidth;
savedPos.height = wrapper.offsetHeight;
saveWrapperPosition(savedPos);
}
}
});
/* ---------- Resize ---------- */
const resize = wrapper.querySelector("#tm-resize");
let resizing = false, startW, startH, startX, startY;
resize.addEventListener("mousedown", (e) => { resizing = true; startW = wrapper.offsetWidth; startH = wrapper.offsetHeight; startX = e.clientX; startY = e.clientY; e.preventDefault(); });
document.addEventListener("mousemove", (e) => {
if (!resizing) return;
wrapper.style.width = startW + (e.clientX - startX) + "px";
wrapper.style.height = startH + (e.clientY - startY) + "px";
});
document.addEventListener("mouseup", () => {
if (resizing) {
resizing = false;
if (!isMinimized) {
savedPos.width = wrapper.offsetWidth;
savedPos.height = wrapper.offsetHeight;
savedPos.left = parseInt(wrapper.style.left,10);
savedPos.top = parseInt(wrapper.style.top,10);
saveWrapperPosition(savedPos);
}
}
});
/* ---------- INITIAL ---------- */
showPlayer(loadVisibility());
updateTitleTimestamp();
})();
}
if (isToggleEnabled()) {
(function () {
'use strict';
const bankers = [
{ id: "2493877", name: "Banker 1" },
{ id: "2202288", name: "Banker 2" },
{ id: "1316121", name: "Banker 3" },
{ id: "2112766", name: "Banker 4" },
{ id: "2161442", name: "Banker 5" },
{ id: "2124788", name: "Banker 6" },
{ id: "1322101", name: "Banker 7" },
{ id: "2261496", name: "Banker 8" },
{ id: "2535362", name: "Banker 9" },
{ id: "2649361", name: "Banker 10" }
];
// Create Toggle Button
const button = document.createElement("button");
button.textContent = "Bankers";
button.style.position = "fixed";
button.style.bottom = "80px";
button.style.left = "0px";
if (isTornPDA()) { button.style.bottom = "40px";
button.style.left = "0px";}
button.style.zIndex = "9999";
button.style.background = sheepColour;
button.style.color = "black";
button.style.border = "1px solid #444";
button.style.borderRadius = "6px";
button.style.cursor = "pointer";
button.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)";
button.style.fontSize = '80%';
requestIdleCallback(() => {
document.body.appendChild(button);
});
// Create Popup Panel
const panel = document.createElement("div");
panel.style.position = "fixed";
panel.style.top = "50%";
panel.style.left = "50%";
panel.style.transform = "translate(-50%, -50%)";
panel.style.background = sheepColour;
panel.style.border = "2px solid #333";
panel.style.borderRadius = "10px";
panel.style.padding = "15px";
panel.style.zIndex = "10000";
panel.style.display = "none";
panel.style.maxHeight = "80vh";
panel.style.overflowY = "auto";
panel.style.boxShadow = "0 0 25px rgba(0,0,0,0.8)";
// Close Button
const closeBtn = document.createElement("div");
closeBtn.textContent = "✕";
closeBtn.style.textAlign = "right";
closeBtn.style.cursor = "pointer";
closeBtn.style.color = "#aaa";
closeBtn.style.marginBottom = "10px";
closeBtn.onclick = () => panel.style.display = "none";
panel.appendChild(closeBtn);
// Add Banker Images
bankers.forEach(banker => {
const link = document.createElement("a");
link.href = `https://www.torn.com/profiles.php?XID=${banker.id}`;
const img = document.createElement("img");
img.src = `/sigs/19_${banker.id}.png`;
img.alt = banker.name;
img.style.display = "block";
img.style.margin = "8px auto";
img.style.width = "100%";
img.style.borderRadius = "6px";
img.style.transition = "transform 0.2s ease";
img.onmouseenter = () => img.style.transform = "scale(1.03)";
img.onmouseleave = () => img.style.transform = "scale(1)";
link.appendChild(img);
panel.appendChild(link);
});
document.body.appendChild(panel);
// Toggle Logic
button.onclick = () => {
panel.style.display = panel.style.display === "none" ? "block" : "none";
};
})();
function combineToSeconds(minutes, seconds) {
return parseInt(minutes) * 60 + parseInt(seconds);
}
// ==================== FLOATING DRAGGABLE TIMER ====================
const sheeppenvar = localStorage.getItem("sheeppen");
const minID = 3900000;
const maxID = 3964119;
function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
if (sheeppenvar === null) {function isTornPDA() {
return /TornPDA/i.test(navigator.userAgent);
}
if (isTornPDA()) { const sheep = 0
localStorage.setItem("sheeppen", sheep);
} else {
const sheep = prompt("Please enter size from 50 to 250 (80 is default), refresh the page after entering desired sheepcounter");
localStorage.setItem("sheeppen", sheep);
window.location.reload(); }
}
// Create the floating timer element
const floatingTimer = document.createElement('div');
floatingTimer.id = 'sheepieFloatingTimer';
floatingTimer.style.position = 'fixed';
floatingTimer.style.zIndex = '9999999';
floatingTimer.style.color = 'black';
floatingTimer.style.cursor = 'move';
floatingTimer.style.fontSize = `${sheeppenvar}px`;
floatingTimer.style.padding = '8px 12px';
floatingTimer.style.backgroundColor = '#00000000';
floatingTimer.style.boxShadow = '0 4px 12px rgba(0,0,0,0.4)';
floatingTimer.style.textAlign = 'center';
floatingTimer.style.minWidth = '0px';
floatingTimer.style.userSelect = 'none';
floatingTimer.style.top = '40px';
floatingTimer.style.right = '260px';
// Convert any bottom/right positioning to top/left so dragging always works
document.body.appendChild(floatingTimer);
const rect = floatingTimer.getBoundingClientRect();
floatingTimer.style.top = rect.top + 'px';
floatingTimer.style.left = rect.left + 'px';
floatingTimer.style.bottom = 'auto';
floatingTimer.style.right = 'auto';
let toggleEnabled = localStorage.getItem('sheepTimerToggle') === 'true';
if (toggleEnabled) {floatingTimer.style.top = '3330px';
floatingTimer.style.right = '3330px';}
// ==================== MAKE IT DRAGGABLE ====================
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
floatingTimer.onmousedown = function (e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
};
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
floatingTimer.style.top = (floatingTimer.offsetTop - pos2) + "px";
floatingTimer.style.left = (floatingTimer.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
// Click = random attack (click still works, dragging does not trigger it)
floatingTimer.addEventListener('click', function () {
let randID = getRandomNumber(minID, maxID);
let profileLink = `https://www.torn.com/loader.php?sid=attack&user2ID=${randID}`;
window.location.href = profileLink;
});
// ==================== OBSERVER + COLOUR LOGIC ====================
const element = document.querySelector('.bar-timeleft___B9RGV');
if (element) {
// Keep the original timer large (optional: uncomment next line to hide it)
// element.style.display = 'none';
const sizeRule = `
.bar-timeleft___B9RGV {
font-size: ${sheeppenvar}px !important;
cursor: pointer;
}
`;
const styleEl = document.createElement('style');
styleEl.textContent = sizeRule;
document.head.appendChild(styleEl);
let observer = null;
function startObserver() {
if (observer) return;
const config = { characterData: true, attributes: false, childList: false, subtree: true };
observer = new MutationObserver((list) => {
if (!document.hasFocus()) return;
if (!list[0] || !list[0].target) return;
const timeText = list[0].target.textContent.trim();
floatingTimer.textContent = timeText;
const timeArray = timeText.split(":");
if (timeArray.length < 2) return;
const totalSeconds = combineToSeconds(timeArray[0], timeArray[1]);
let remainingTime = (totalSeconds) * 1000; // 5 minutes
sessionStorage.setItem('floatingTimerRemaining', remainingTime)
const SheepieWatch = localStorage.getItem("SheepieWatch") === "true";
let bg = "green";
if ([300, 299, 298, 297, 296, 295, 294, 293, 292, 291, 290, 289, 288, 287, 286, 285, 284, 283, 282, 281, 280, 279, 278, 277, 276, 275, 274, 273, 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241].includes(totalSeconds)) { bg = "darkgreen";}
else if ([240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, 214, 213, 212, 211].includes(totalSeconds)) bg = "green";
else if ([210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161].includes(totalSeconds)) bg = "lightgreen";
else if ([160, 159, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121].includes(totalSeconds)) bg = "yellow";
else if ([120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96].includes(totalSeconds)) { bg = "orange";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);}}
else if ([95, 94, 93, 92, 91].includes(totalSeconds)) { bg = "red";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber1);
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);
let sheepnumber3 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber2, sheepnumber3);
let sheepnumber4 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber3, sheepnumber4);
let sheepnumber5 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber4, sheepnumber5);}}
else if ([90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76].includes(totalSeconds)) { bg = "orange";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);}}
else if ([75, 74, 73, 72, 71].includes(totalSeconds)) { bg = "red";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber1);
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);
let sheepnumber3 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber2, sheepnumber3);
let sheepnumber4 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber3, sheepnumber4);
let sheepnumber5 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber4, sheepnumber5);}}
else if ([70, 69, 68, 67, 66].includes(totalSeconds)) { bg = "orange";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);}}
else if ([65, 64, 63, 62, 61].includes(totalSeconds)) { bg = "red";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber1);
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);
let sheepnumber3 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber2, sheepnumber3);
let sheepnumber4 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber3, sheepnumber4);
let sheepnumber5 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber4, sheepnumber5);}}
else if ([60, 59, 58, 58, 57, 56].includes(totalSeconds)){ bg = "orange";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);}}
else if ([55, 54, 53, 52, 51].includes(totalSeconds)) { bg = "red";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber1);
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);
let sheepnumber3 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber2, sheepnumber3);
let sheepnumber4 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber3, sheepnumber4);
let sheepnumber5 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber4, sheepnumber5);}}
else if ([50,49,48,47,46].includes(totalSeconds)) { bg = "orange";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);}}
else if ([45, 44, 43, 42, 41].includes(totalSeconds)) { bg = "red";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber1);
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);
let sheepnumber3 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber2, sheepnumber3);
let sheepnumber4 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber3, sheepnumber4);
let sheepnumber5 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber4, sheepnumber5);}}
else if ([40, 39, 38, 37, 36].includes(totalSeconds)) { bg = "orange";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);}}
else if ([35, 34, 33, 32, 31].includes(totalSeconds)) { bg = "red";
if (SheepieWatch) {
let sheepnumber1 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber1);
let sheepnumber2 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber1, sheepnumber2);
let sheepnumber3 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber2, sheepnumber3);
let sheepnumber4 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber3, sheepnumber4);
let sheepnumber5 = Math.floor(Math.random() * 788) + 150;
createButtonAtPosition17("SaveChain", sheepnumber4, sheepnumber5);}}
else if ([30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1].includes(totalSeconds)) { bg = "darkred";}
else if ([310,0].includes(totalSeconds)) { bg = "darkred";
sessionStorage.removeItem("floatingTimerRemaining");}
floatingTimer.style.backgroundColor = bg;
});
observer.observe(element, config);
}
function stopObserver() {
if (observer) {
observer.disconnect();
observer = null;
}
}
// Page visibility handling
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
startObserver();
} else {
stopObserver();
}
});
// Start if already focused
if (document.visibilityState === "visible") {
startObserver();
}
}
}
const cornerGif2 = document.createElement('img');
cornerGif2.src = 'https://factionimages.torn.com/85dbb882-5766-4e03-a985-71af719051bd-14432.gif';
cornerGif2.style.position = 'fixed';
cornerGif2.style.top = '10px';
cornerGif2.style.left = '10px';
cornerGif2.style.width = '150px';
cornerGif2.style.zIndex = '9999';
cornerGif2.style.border = "1px solid purple";
cornerGif2.addEventListener('click', function() {
window.open('https://www.torn.com/factions.php?step=profile&ID=14432').focus();
});
requestIdleCallback(() => {
document.body.appendChild(cornerGif2)
});
const cornerGif3 = document.createElement('img');
cornerGif3.src = 'https://sheepie.ca/img/d4kWYmJ.gif';
cornerGif3.style.position = 'absolute';
cornerGif3.style.top = '30px';
cornerGif3.style.right = '0px';
cornerGif3.style.width = '150px';
if (isTornPDA()) {
cornerGif3.style.position = 'absolute';
cornerGif3.style.top = '3330px';
cornerGif3.style.right = '3330px';
cornerGif3.style.width = '0px';
// PDA-specific code here
}
cornerGif3.style.zIndex = '9999';
cornerGif3.style.border = "1px solid purple";
cornerGif3.addEventListener('click', function() {
const minID = 3900000;
const maxID = 3964119;
let randID = getRandomNumber(minID, maxID);
window.open(`https://www.torn.com/loader.php?sid=attack&user2ID=${randID}`).focus();
// window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ').focus();
});
function isToggleEnabled() {
return localStorage.getItem("mySimpleToggleState") === "true";
}
const cornerGif4 = document.createElement('img');
cornerGif4.src = 'https://sheepie.ca/img/7v3an3v.gif';
cornerGif4.style.position = 'absolute';
cornerGif4.style.bottom = '20px';
cornerGif4.style.left = '100px';
cornerGif4.style.zIndex = '999999';
cornerGif4.style.border = "1px solid purple";
if (isTornPDA()) {
cornerGif4.style.position = 'absolute';
cornerGif4.style.top = '3330px';
cornerGif4.style.right = '3330px';
cornerGif4.style.width = '0px';
// PDA-specific code here
}
cornerGif4.addEventListener('click', function() {
window.open('https://www.torn.com/profiles.php?XID=2679129').focus();
});
requestIdleCallback(() => {
if (isToggleEnabled()) {document.body.appendChild(cornerGif3);}
});
function createButtonAtPosition(text, topPx, leftPx) {
var customButton1 = document.createElement("button");
customButton1.innerHTML = 'ResetScript';
customButton1.style.width = sheepwide + "px";
customButton1.style.height = sheeptall + "px";
customButton1.style.position = 'fixed';
customButton1.style.top = topPx + 'px';
customButton1.style.left = leftPx + 'px';
customButton1.style.backgroundColor = sheepColour;
customButton1.style.color = 'black';
customButton1.style.border = 'none';
customButton1.style.cursor = 'pointer';
customButton1.style.border = "1px solid purple";
customButton1.style.zIndex = '999999';
customButton1.style.fontSize = (sheeptall * 0.4) + "px";
customButton1.addEventListener('click', function() {
localStorage.removeItem("sheeppen");
localStorage.removeItem("sheeplink");
localStorage.removeItem("sheepAPI");
localStorage.removeItem("sheepcolour")
localStorage.removeItem("floatingTimerPosition")
localStorage.removeItem("floatingTimerSize")
localStorage.removeItem("SheepieWatch")
localStorage.removeItem("mySimpleToggleState")
localStorage.removeItem("sheepattackAPI");
window.location.reload();
});
document.body.appendChild(customButton1);
}
function createButtonAtPosition2(text, topPx, leftPx) {
var customButton2 = document.createElement("button");
customButton2.style.width = sheepwide + "px";
customButton2.style.height = sheeptall + "px";
customButton2.innerHTML = 'Send Me Sheep Plushies';
customButton2.style.position = 'fixed';
customButton2.style.top = topPx + 'px';
customButton2.style.left = leftPx + 'px';
customButton2.style.backgroundColor = '#FFD1DC';
customButton2.style.color = 'black';
customButton2.style.border = 'none';
customButton2.style.cursor = 'pointer';
customButton2.style.border = "1px solid purple";
customButton2.style.zIndex = '999999';
customButton2.style.fontSize = (sheeptall * 0.4) + "px";
customButton2.addEventListener('click', function() {
window.open('https://www.torn.com/profiles.php?XID=2679129', 'Sheepie').focus();
});
document.body.appendChild(customButton2);
}
function createButtonAtPosition3(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.innerHTML = 'CustomLink';
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
const sheeplink = localStorage.getItem("sheeplink");
if (sheeplink === null) {
const sheeplink = prompt("Please enter link, be SURE to start it with 'HTTPS://www.' ");
localStorage.setItem("sheeplink", sheeplink);
window.location.reload();
} else {
window.open(sheeplink).focus();
}
});
document.body.appendChild(customButton);
}
function createButtonAtPosition4(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'TargetList';
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://www.torn.com/page.php?sid=list&type=targets', 'TargetList').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition5(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'FFScouter';
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://ffscouter.com/target-finder?preset=custom&liveUpdates=0&minLevel=1&maxLevel=100&minFF=2&maxFF=2.3&limit=20&inactiveOnly=1&factionless=1', 'FFSCouter').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition6(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'FacDrugs';
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://www.torn.com/factions.php?step=your&type=1#/faction-armoury=undefined&start=0&sub=drugs', 'Drugs').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition7(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'FacBoosters';
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://www.torn.com/factions.php?step=your&type=1#/faction-armoury=undefined&start=0&sub=boosters', 'Boosters').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition8(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'Fac Meds';
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://www.torn.com/factions.php?step=your&type=1#/faction-armoury=undefined&start=0&sub=medical', 'Meds').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition9(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'Chain';
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://www.torn.com/factions.php?step=your&type=1#/war/chain', 'Chain').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition10(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'Info';
customButton.style.width = sheepwide2 + "px";
customButton.style.height = sheeptall2 + "px";
customButton.style.position = 'fixed';
customButton.style.bottom = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
customButton.style.fontSize = (sheeptall2 * 0.4) + "px";
customButton.addEventListener('click', function() {
createButtonAtPositionToDo("ToDo", 40, 25);
});
document.body.appendChild(customButton);
}
function createButtonAtPosition11(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'Bugs';
customButton.style.width = sheepwide2 + "px";
customButton.style.height = sheeptall2 + "px";
customButton.style.position = 'fixed';
customButton.style.bottom = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
customButton.style.fontSize = (sheeptall2 * 0.4) + "px";
customButton.addEventListener('click', function() {
createButtonAtPositionReporting("ToDo", 40, 25);
});
document.body.appendChild(customButton);
}
function createButtonAtPositionReporting(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'please be sure to be as detailed as possible, attach images or upload them to imgur.com and link me them, you can also reachout to me over discord #Aleshkii -click here to send me a message in torn-';
customButton.style.width = "150px";
customButton.style.height = "200px";
customButton.style.position = 'fixed';
customButton.style.bottom = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
setTimeout(function() {
customButton.style.display = 'none';
}, 15000);
customButton.addEventListener('click', function() {
window.open('https://www.torn.com/messages.php#/p=compose&XID=2679129', 'reporting').focus();
customButton.style.display = 'none';
});
document.body.appendChild(customButton);
}
function createButtonAtPosition12(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'URL';
customButton.style.width = sheepwide2 + "px";
customButton.style.height = sheeptall2 + "px";
customButton.style.position = 'fixed';
customButton.style.bottom = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
customButton.style.fontSize = (sheeptall2 * 0.4) + "px";
customButton.addEventListener('click', function() {
window.open('https://sheepie.ca/index.html', 'SheepieAIO').focus();
});
document.body.appendChild(customButton);
}
function createButtonAtPosition17(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.innerHTML = 'SaveChain';
customButton.style.width = "120px";
customButton.style.height = "50px";
customButton.style.position = 'fixed';
customButton.style.bottom = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '9999';
if (window.location.href === "https://www.torn.com/loader.php?sid=attack*") {customButton.style.display = 'none';}
let sheepnum2 = Math.floor(Math.random() * 25000) + 15000;
function getRandomColor() {
const red = Math.floor(Math.random() * 256);
const green = Math.floor(Math.random() * 256);
const blue = Math.floor(Math.random() * 256);
return `rgb(${red}, ${green}, ${blue})`;
}
let sheepcolour = getRandomColor();
customButton.style.backgroundColor = sheepcolour;
setTimeout(function() {
customButton.style.display = 'none';
}, sheepnum2);
customButton.addEventListener('click', function() {
const sheepattackAPI = localStorage.getItem("sheepattackAPI");
if (sheepattackAPI === null) {
const sheepAttack = ("yes");
localStorage.setItem("sheepattackAPI", sheepAttack);}
const minID = 3900000;
const maxID = 3964119;
let randID = getRandomNumber(minID, maxID);
let profileLink = `https://www.torn.com/loader.php?sid=attack&user2ID=${randID}`;
window.location.href = profileLink;
});
document.body.appendChild(customButton);
}
// OC Roles
// author tobytorn [1617955]
(function() {
'use strict';
const API_URL = "https://tornprobability.com:3000/api/GetRoleWeights";
const CACHE_KEY = "oc_role_weights_cache";
const CACHE_TIME_KEY = "oc_role_weights_cache_time";
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
let roleWeightsAPI = null;
/* -----------------------------
STYLE (NO ANIMATION)
------------------------------*/
const style = document.createElement('style');
style.textContent = `
.oc-solid-red {
outline: 4px solid red !important;
outline-offset: 0px !important;
}
.oc-has-contrib {
position: relative !important;
padding-bottom: 22px !important;
}
.oc-contrib-linewrap {
position: absolute !important;
left: 8px !important;
right: 8px !important;
bottom: 6px !important;
display: flex !important;
align-items: center !important;
gap: 8px !important;
pointer-events: none !important;
}
.oc-contrib-linewrap::before,
.oc-contrib-linewrap::after {
content: "";
flex: 1;
height: 1px;
background: rgba(115,65,128,0.95);
}
.oc-contrib-linetext {
font-weight: 900;
font-size: 12px;
color: 'sheepColour';
}`;
document.head.appendChild(style);
/* -----------------------------
YOUR ORIGINAL OC DATA
(UNCHANGED)
------------------------------*/
const defaultLevel6 = 67;
const defaultLevel5 = 65;
const defaultLevel4 = 65;
const defaultLevel3 = 65;
const defaultLevel2 = 65;
const defaultDecline = 700;
const ocRoles = [
{
OCName: "Blast From The Past",
Positions: {
"MUSCLE": 70,
"ENGINEER": 70,
"BOMBER": 69,
"PICKLOCK #1": 69,
"HACKER": 69,
"PICKLOCK #2": 60
}
},{
OCName: "Stacking the Deck",
Positions: {
"HACKER": 60,
"IMITATOR": 64,
"CAT BURGLAR": 60,
"DRIVER": 50
}
},
{
OCName: "Ace in the Hole",
Positions: {
"HACKER": 65,
"DRIVER": 55,
"MUSCLE #1": 62,
"IMITATOR": 65,
"MUSCLE #2": 63
}
},
{
OCName: "Break the Bank",
Positions: {
"MUSCLE #1": 64,
"MUSCLE #2": 60,
"MUSCLE #3": 64,
"THIEF #1": 60,
"THIEF #2": 67,
"ROBBER": 64
}
},
{
OCName: "Bidding War",
Positions: {
"ROBBER #1": 60,
"ROBBER #2": 69,
"ROBBER #3": 70,
"BOMBER #1": 60,
"BOMBER #2": 67,
"DRIVER": 63
}
},
{
OCName: "Clinical Precision",
Positions: {
"IMITATOR": 68,
"CLEANER": 67,
"CAT BURGLAR": 67,
"ASSASSIN": 67
}
},
{
OCName: "Honey Trap",
Positions: {
"MUSCLE #1": 67,
"MUSCLE #2": 67,
"ENFORCER": 67}
},
{
OCName: "Sneaky Git Grab",
Positions: {
"PICKPOCKET": 70,
"IMITATOR": 65,
"TECHIE": 65,
"HACKER": 66}
},
{
OCName: "Leave No Trace",
Positions: {
"IMITATOR": 66,
"NEGOTIATOR": 65,
"TECHIE": 65
}
},
{
OCName: "Counter Offer",
Positions: {
"ROBBER": 66,
"ENGINEER": 65,
"PICKLOCK": 65,
"HACKER": 65,
"LOOTER": 65}
},
{
OCName: "Stage Fright",
Positions: {
"SNIPER": 70,
"MUSCLE #1": 67,
"ENFORCER": 67,
"MUSCLE #3": 67,
"LOOKOUT": 62,
"MUSCLE #2": 1
}
}, {
OCName: "Snow Blind",
Positions: {
"HUSTLER": 70,
"IMITATOR": 67,
"MUSCLE #1": 60,
"MUSCLE #2": 60
}
},
{
OCName: "Gaslight The Way",
Positions: {
"IMITATOR #1": 64,
"IMITATOR #2": 66,
"IMITATOR #3": 70,
"LOOTER #1": 60,
"LOOTER #2": 50,
"LOOTER #3": 66}
},
{
OCName: "Pet Project",
Positions: `default_${defaultLevel2}`
},
{
OCName: "Cash Me If You Can",
Positions: `default_${defaultLevel2}`
},
{
OCName: "Smoke and Wing Mirrors",
Positions: {
"CAR THIEF": 70,
"IMITATOR": 65,
"HUSTLER #2": 65,
"HUSTLER #1": 62
}
},
{
OCName: "Best of the Lot",
Positions: `default_${defaultLevel2}`
},
{
OCName: "Market Forces",
Positions: {
"ENFORCER": 67,
"NEGOTIATOR": 67,
"MUSCLE": 65,
"LOOKOUT": 65,
"ARSONIST": 50
}
},
{
OCName: "Guardian Ángels",
Positions: {
"HUSTLER": 67,
"ENGINEER": 65,
"ENFORCER": 65
}
},
{
OCName: "No Reserve",
Positions: {
"TECHIE": 68,
"ENGINEER": 68,
"CAR THIEF": 68
}
},
{
OCName: "Manifest Cruelty",
Positions: `default_${defaultDecline}`
}
];
/* -----------------------------
PROCESS PANEL ONCE
------------------------------*/
function processScenario(panel) {
if (panel.dataset.ocProcessed === "true") return;
panel.dataset.ocProcessed = "true";
const ocName = panel.querySelector('.panelTitle___aoGuV')?.innerText.trim();
if (!ocName) return;
const slots = panel.querySelectorAll('.wrapper___Lpz_D');
slots.forEach(slot => {
if (slot.dataset.ocProcessed === "true") return;
slot.dataset.ocProcessed = "true";
const roleElem = slot.querySelector('.title___UqFNy');
const chanceElem = slot.querySelector('.successChance___ddHsR');
if (!roleElem || !chanceElem) return;
const rawRole = roleElem.innerText.trim();
const successChance = parseInt(chanceElem.textContent.trim(), 10) || 0;
const joinBtn = slot.querySelector("button[class^='torn-btn joinButton']");
/* -----------------------------
DISPLAY ROLE WEIGHT (FROM CACHE)
------------------------------*/
if (roleWeightsAPI) {
const cleanOCName = ocName.split("\n")[0].trim();
const ocKey = cleanOCName.toLowerCase().replace(/[^a-z0-9\u00C0-\u017F]/gi, "");
const roleKey = rawRole.toLowerCase().replace(/[^a-z0-9\u00C0-\u017F]/gi, "");
let pct;
if (roleWeightsAPI) {
const cleanOCName = ocName.split("\n")[0].trim();
const ocKeyCompact = cleanOCName
.toLowerCase()
.replace(/[^a-z0-9\u00C0-\u017F]/gi, "");
const roleKeyCompact = rawRole
.toLowerCase()
.replace(/[^a-z0-9\u00C0-\u017F]/gi, "");
// find matching OC
const ocEntry = Object.entries(roleWeightsAPI).find(([k]) =>
k.toLowerCase() === ocKeyCompact
);
if (ocEntry) {
const roleEntry = Object.entries(ocEntry[1] || {}).find(([k]) =>
k.toLowerCase() === roleKeyCompact
);
if (roleEntry && typeof roleEntry[1] === "number") {
pct = Math.round(roleEntry[1]);
}
}
if (typeof pct === "number") {
slot.classList.add("oc-has-contrib");
if (!slot.querySelector(".oc-contrib-linewrap")) {
const wrap = document.createElement("div");
wrap.className = "oc-contrib-linewrap";
const text = document.createElement("span");
text.className = "oc-contrib-linetext";
text.textContent = pct + "%";
wrap.appendChild(text);
slot.appendChild(wrap);
}
}
}
if (typeof pctRaw === "number") {
slot.classList.add("oc-has-contrib");
if (!slot.querySelector(".oc-contrib-linewrap")) {
const wrap = document.createElement("div");
wrap.className = "oc-contrib-linewrap";
const text = document.createElement("span");
text.className = "oc-contrib-linetext";
text.textContent = Math.round(pctRaw) + "%";
wrap.appendChild(text);
slot.appendChild(wrap);
}
}
}
/* -----------------------------
YOUR ORIGINAL MIN/MAX LOGIC
(UNCHANGED)
------------------------------*/
const ocData = ocRoles.find(o => o.OCName.toLowerCase() === ocName.toLowerCase());
if (!ocData) return;
let required = null;
if (typeof ocData.Positions === 'string' && ocData.Positions.startsWith('default_')) {
required = parseInt(ocData.Positions.split('_')[1], 10);
} else if (typeof ocData.Positions === 'object') {
required = ocData.Positions[rawRole];
}
if (required === undefined || required === null) return;
let maxAllowed = required + 10;
if (ocName.toLowerCase() === "stage fright" && rawRole === "MUSCLE #2") {
maxAllowed = 70;
}
const honorTexts = slot.querySelectorAll('.honor-text');
const userName = honorTexts.length > 1 ? honorTexts[1].textContent.trim() : null;
if (!userName) {
if (successChance < required) {
slot.style.backgroundColor = '#ff000061';
} else if (successChance > maxAllowed) {
slot.style.backgroundColor = '#F7b500';
} else {
slot.style.backgroundColor = '#21a61c61';
}
if (joinBtn) {
if (successChance < required || successChance > maxAllowed) {
joinBtn.setAttribute('disabled', '');
} else {
joinBtn.removeAttribute('disabled');
}
}
} else if (successChance < required) {
slot.classList.add('oc-solid-red');
}
});
}
/* -----------------------------
OBSERVER (LIGHTWEIGHT)
------------------------------*/
let lastRun = 0;
const MIN_INTERVAL = 1000; // adjust (150–300ms ideal)
const observer = new MutationObserver(() => {
const now = Date.now();
if (now - lastRun < MIN_INTERVAL) {
return; // too soon, skip
}
lastRun = now;
document.querySelectorAll('.wrapper___U2Ap7')
.forEach(processScenario);
});
observer.observe(document.body, { childList: true, subtree: true });
/* -----------------------------
24HR CACHE SYSTEM
------------------------------*/
function loadWeights() {
const cached = localStorage.getItem(CACHE_KEY);
const cacheTime = localStorage.getItem(CACHE_TIME_KEY);
if (cached && cacheTime && (Date.now() - cacheTime < CACHE_DURATION)) {
roleWeightsAPI = JSON.parse(cached);
return;
}
GM_xmlhttpRequest({
method: "GET",
url: API_URL,
onload: (res) => {
try {
roleWeightsAPI = JSON.parse(res.responseText);
localStorage.setItem(CACHE_KEY, JSON.stringify(roleWeightsAPI));
localStorage.setItem(CACHE_TIME_KEY, Date.now());
;
} catch (e) {
console.warn("Failed parsing weights", e);
}
}
});
}
loadWeights();
})();
// Crime Morale
// author tobytorn [1617955]
(function () {
'use strict';
// Avoid duplicate injection in TornPDA
if (window.CRIME_MORALE_INJECTED) {
return;
}
window.CRIME_MORALE_INJECTED = true;
const LOCAL_STORAGE_PREFIX = 'CRIME_MORALE_';
const STORAGE_MORALE = 'morale';
const STYLE_ELEMENT_ID = 'CRIME-MORALE-STYLE';
function getLocalStorage(key, defaultValue) {
const value = window.localStorage.getItem(LOCAL_STORAGE_PREFIX + key);
try {
return JSON.parse(value) ?? defaultValue;
} catch (err) {
return defaultValue;
}
}
function setLocalStorage(key, value) {
window.localStorage.setItem(LOCAL_STORAGE_PREFIX + key, JSON.stringify(value));
}
const isPda = window.GM_info?.scriptHandler?.toLowerCase().includes('tornpda');
const [getValue, setValue] =
isPda || typeof window.GM_getValue !== 'function' || typeof window.GM_setValue !== 'function'
? [getLocalStorage, setLocalStorage]
: [window.GM_getValue, window.GM_setValue];
function addStyle(css) {
const style =
document.getElementById(STYLE_ELEMENT_ID) ??
(function () {
const style = document.createElement('style');
style.id = STYLE_ELEMENT_ID;
document.head.appendChild(style);
return style;
})();
style.appendChild(document.createTextNode(css));
}
function formatLifetime(seconds) {
const hours = Math.floor(seconds / 3600);
const text =
hours >= 72
? `${Math.floor(hours / 24)}d`
: hours > 0
? `${hours}h`
: seconds >= 0
? `${Math.floor(seconds / 60)}m`
: '';
const color = hours >= 24 ? 't-gray-c' : hours >= 12 ? 't-yellow' : hours >= 0 ? 't-red' : '';
return { seconds, hours, text, color };
}
async function checkDemoralization(data) {
const demMod = (data.DB || {}).demMod;
if (typeof demMod !== 'number') {
return;
}
const morale = 100 - demMod;
updateMorale(morale);
await setValue(STORAGE_MORALE, morale);
}
class BurglaryObserver {
constructor() {
this.data = getValue('burglary', {});
this.data.favorite = this.data.favorite ?? [];
this.properties = null;
this.crimeOptions = null;
this.observer = new MutationObserver((mutations) => {
const isAdd = mutations.some((mutation) => {
for (const added of mutation.addedNodes) {
if (added instanceof HTMLElement) {
return true;
}
}
return false;
});
if (!isAdd) {
return;
}
for (const element of this.crimeOptions) {
if (!element.classList.contains('cm-bg-seen')) {
element.classList.add('cm-bg-seen');
this._refreshCrimeOption(element);
}
}
});
}
start() {
if (this.crimeOptions) {
return;
}
this.crimeOptions = document.body.getElementsByClassName('crime-option');
this.observer.observe($('.burglary-root')[0], { subtree: true, childList: true });
}
stop() {
this.crimeOptions = null;
this.observer.disconnect();
}
onNewData(data) {
this.start();
this.properties = data.DB?.crimesByType?.properties;
this._refreshCrimeOptions();
}
_refreshCrimeOptions() {
for (const element of this.crimeOptions) {
this._refreshCrimeOption(element);
}
}
_refreshCrimeOption(element) {
if (!this.properties) {
return;
}
const $element = $(element);
const $title = $element.find('[class*=crimeOptionSection___]').first();
$title.find('.cm-bg-lifetime').remove();
const guessedProperty = this._guessCrimeOptionData($element);
const property = this._checkCrimeOptionData($element, guessedProperty);
if (!property) {
$element.removeAttr('data-cm-id');
return;
}
$element.attr('data-cm-id', property.subID);
const now = Math.floor(Date.now() / 1000);
const lifetime = formatLifetime(property.expire - now);
if (lifetime.hours >= 0) {
$title.css('position', 'relative');
$title.append(`<div class="cm-bg-lifetime ${lifetime.color}">${lifetime.text}</div>`);
}
$element.find('.cm-bg-favor').remove();
const $favor = $('<div class="cm-bg-favor"></div>');
$favor.toggleClass('cm-bg-active', this.data.favorite.includes(property.title));
$element.find('.crime-image').append($favor);
$favor.on('click', () => {
this._toggleFavorite(property.title);
this._refreshCrimeOptions();
});
}
_guessCrimeOptionData($crimeOption) {
const savedId = $crimeOption.attr('data-cm-id');
if (savedId) {
return this.properties.find((x) => x.subID === savedId);
}
const $item = $crimeOption.closest('.virtual-item');
if ($item.prev().hasClass('lastOfGroup___YNUeQ')) {
return this.properties[0];
}
let prevId = undefined;
$item.prevAll().each(function () {
prevId = $(this).find('.crime-option[data-cm-id]').attr('data-cm-id');
if (prevId) {
return false; // break the loop
}
});
const prevIndex = this.properties.findIndex((x) => prevId && x.subID === prevId);
if (prevIndex >= 0) {
// Since we always scan crime options in document order,
// $prevItemWithId and $item should correspond to adjacent data entries.
return this.properties[prevIndex + 1];
}
if ($item.index() === 0) {
const $nextOptionWithId = $item.nextAll().find('.crime-option[data-cm-id]').first();
const nextId = $nextOptionWithId.attr('data-cm-id');
const nextIndex = this.properties.findIndex((x) => x.subID && x.subID === nextId);
const nextPos = $nextOptionWithId.closest('.virtual-item').index();
if (nextIndex >= 0 && nextPos >= 0) {
return this.properties[nextIndex - nextPos];
}
}
return undefined;
}
_checkCrimeOptionData($crimeOption, property) {
if (property === undefined) {
return undefined;
}
const { title, titleType } = this._getCrimeOptionTitle($crimeOption);
return titleType && property[titleType] === title ? property : undefined;
}
_getCrimeOptionTitle($crimeOption) {
const mobileTitle = $crimeOption.find('.title___kOWyb').text();
if (mobileTitle !== '') {
return { title: mobileTitle, titleType: 'mobileTitle' };
}
const textNode = $crimeOption.find('.crimeOptionSection___hslpu')[0]?.firstChild;
if (textNode?.nodeType === Node.TEXT_NODE) {
return { title: textNode.textContent, titleType: 'title' };
}
return { title: null, titleType: null };
}
_toggleFavorite(title) {
const index = this.data.favorite.indexOf(title);
if (index >= 0) {
this.data.favorite.splice(index, 1);
} else {
this.data.favorite.push(title);
}
setValue('burglary', this.data);
}
}
const burglaryObserver = new BurglaryObserver();
async function checkBurglary(crimeType, data) {
if (crimeType !== '7') {
burglaryObserver.stop();
return;
}
burglaryObserver.onNewData(data);
}
const PP_CYCLING = 0;
const PP_DISTRACTED = 34; // eslint-disable-line no-unused-vars
const PP_MUSIC = 102;
const PP_LOITERING = 136;
const PP_PHONE = 170;
const PP_RUNNING = 204;
const PP_SOLICITING = 238; // eslint-disable-line no-unused-vars
const PP_STUMBLING = 272;
const PP_WALKING = 306;
const PP_BEGGING = 340;
const PP_SKINNY = 'Skinny';
const PP_AVERAGE = 'Average';
const PP_ATHLETIC = 'Athletic';
const PP_MUSCULAR = 'Muscular';
const PP_HEAVYSET = 'Heavyset';
const PP_ANY_BUILD = [PP_SKINNY, PP_AVERAGE, PP_ATHLETIC, PP_MUSCULAR, PP_HEAVYSET];
const PP_MARKS = {
'Drunk Man': { level: 1, status: [PP_STUMBLING], build: PP_ANY_BUILD },
'Drunk Woman': { level: 1, status: [PP_STUMBLING], build: PP_ANY_BUILD },
'Homeless Person': { level: 1, status: [PP_BEGGING], build: [PP_AVERAGE] },
Junkie: { level: 1, status: [PP_STUMBLING], build: PP_ANY_BUILD },
'Elderly Man': { level: 1, status: [PP_WALKING], build: [PP_SKINNY, PP_AVERAGE, PP_ATHLETIC, PP_HEAVYSET] },
'Elderly Woman': { level: 1, status: [PP_WALKING], build: [PP_SKINNY, PP_AVERAGE, PP_ATHLETIC, PP_HEAVYSET] },
'Young Man': { level: 2, status: [PP_MUSIC], build: [PP_SKINNY, PP_AVERAGE, PP_ATHLETIC] },
'Young Woman': { level: 2, status: [PP_PHONE], build: [PP_SKINNY, PP_AVERAGE, PP_HEAVYSET] },
Student: { level: 2, status: [PP_PHONE], build: [PP_SKINNY, PP_AVERAGE] },
'Classy Lady': {
level: 2,
status: [PP_PHONE, PP_WALKING],
build: [PP_SKINNY, PP_HEAVYSET],
bestBuild: [PP_HEAVYSET],
},
Laborer: { level: 2, status: [PP_PHONE], build: PP_ANY_BUILD },
'Postal Worker': { level: 2, status: [PP_WALKING], build: [PP_AVERAGE] },
'Rich Kid': {
level: 3,
status: [PP_WALKING, PP_PHONE],
build: [PP_SKINNY, PP_ATHLETIC, PP_HEAVYSET],
bestBuild: [PP_ATHLETIC],
},
'Sex Worker': { level: 3, status: [PP_PHONE], build: [PP_SKINNY, PP_AVERAGE], bestBuild: [PP_AVERAGE] },
Thug: { level: 3, status: [PP_RUNNING], build: [PP_SKINNY, PP_AVERAGE, PP_ATHLETIC], bestBuild: [PP_SKINNY] },
Businessman: {
level: 4,
status: [PP_PHONE],
build: [PP_AVERAGE, PP_MUSCULAR, PP_HEAVYSET],
bestBuild: [PP_MUSCULAR, PP_HEAVYSET],
},
Businesswoman: {
level: 4,
status: [PP_PHONE],
build: [PP_SKINNY, PP_AVERAGE, PP_ATHLETIC],
bestBuild: [PP_ATHLETIC],
},
'Gang Member': {
level: 4,
status: [PP_LOITERING],
build: [PP_AVERAGE, PP_ATHLETIC, PP_MUSCULAR],
bestBuild: [PP_AVERAGE],
},
Jogger: { level: 4, status: [PP_WALKING], build: [PP_ATHLETIC, PP_MUSCULAR], bestBuild: [PP_MUSCULAR] },
Mobster: { level: 4, status: [PP_WALKING], build: [PP_SKINNY] },
Cyclist: { level: 5, status: [PP_CYCLING], build: ['1.52 m', `5'0"`, '1.62 m', `5'4"`] },
'Police Officer': {
level: 6,
status: [PP_RUNNING],
build: PP_ANY_BUILD,
bestBuild: [PP_SKINNY, '1.52 m', `5'0"`, '1.62 m', `5'4"`],
},
};
let pickpocketingOb = null;
let pickpocketingExitOb = null;
let pickpocketingInterval = 0;
async function checkPickpocketing(crimeType) {
if (crimeType !== '5') {
stopPickpocketing();
return;
}
const $wrapper = $('.pickpocketing-root');
if ($wrapper.length === 0) {
if (pickpocketingInterval === 0) {
// This is the first fetch.
pickpocketingInterval = setInterval(() => {
const $wrapperInInterval = $('.pickpocketing-root');
if ($wrapperInInterval.length === 0) {
return;
}
clearInterval(pickpocketingInterval);
pickpocketingInterval = 0;
startPickpocketing($wrapperInInterval);
}, 1000);
}
} else {
startPickpocketing($wrapper);
}
}
function refreshPickpocketing() {
const $wrapper = $('.pickpocketing-root');
const now = Date.now();
// Releasing reference to removed elements to avoid memory leak
pickpocketingExitOb.disconnect();
let isBelowExiting = false;
$wrapper.find('.crime-option').each(function () {
const $this = $(this);
const top = Math.floor($this.position().top);
const oldTop = parseInt($this.attr('data-cm-top'));
if (top !== oldTop) {
$this.attr('data-cm-top', top.toString());
$this.attr('data-cm-timestamp', now.toString());
}
const timestamp = parseInt($this.attr('data-cm-timestamp')) || now;
const isLocked = $this.is('[class*=locked___]');
const isExiting = $this.is('[class*=exitActive___]');
const isRecentlyMoved = now - timestamp <= 1000;
$this
.find('[class*=commitButtonSection___]')
.toggleClass('cm-overlay', !isLocked && (isBelowExiting || isRecentlyMoved))
.toggleClass('cm-overlay-fade', !isLocked && !isBelowExiting && isRecentlyMoved);
isBelowExiting = isBelowExiting || isExiting;
if (!$this.is('[class*=cm-pp-level-]')) {
const markAndTime = $this.find('[class*=titleAndProps___] > *:first-child').text().trim().toLowerCase();
const iconPosStr = $this.find('[class*=timerCircle___] [class*=icon___]').css('background-position-y');
const iconPosMatch = iconPosStr?.match(/(-?\d+)px/);
const iconPos = -parseInt(iconPosMatch?.[1] ?? '');
const build = $this.find('[class*=physicalProps___]').text().trim().toLowerCase();
for (const [mark, markInfo] of Object.entries(PP_MARKS)) {
if (markAndTime.startsWith(mark.toLowerCase())) {
if (markInfo.status.includes(iconPos) && markInfo.build.some((b) => build.includes(b.toLowerCase()))) {
$this.addClass(`cm-pp-level-${markInfo.level}`);
if (markInfo.bestBuild?.some((b) => build.includes(b.toLowerCase()))) {
$this.addClass(`cm-pp-best-build`);
}
}
break;
}
}
}
pickpocketingExitOb.observe(this, { attributes: true, attributeFilter: ['class'], attributeOldValue: true });
});
}
function startPickpocketing($wrapper) {
if (!pickpocketingOb) {
pickpocketingOb = new MutationObserver(refreshPickpocketing);
pickpocketingExitOb = new MutationObserver(function (mutations) {
for (const mutation of mutations) {
if (
mutation.oldValue.indexOf('exitActive___') < 0 &&
mutation.target.className.indexOf('exitActive___') >= 0
) {
refreshPickpocketing();
return;
}
}
});
}
pickpocketingOb.observe($wrapper[0], {
childList: true,
characterData: true,
subtree: true,
});
}
function stopPickpocketing() {
if (!pickpocketingOb) {
return;
}
pickpocketingOb.disconnect();
pickpocketingOb = null;
pickpocketingExitOb.disconnect();
pickpocketingExitOb = null;
}
// Maximize extra exp (capitalization exp - total cost)
class ScammingSolver {
get BASE_ACTION_COST() {
return this.algo === 'meritGrift' ? 0.001 : 0.02;
}
get FAILURE_COST_MAP() {
return this.algo === 'merit' || this.algo === 'meritGrift'
? {
1: 0,
20: 0,
40: 0,
60: 0,
80: 0,
}
: {
1: 1,
20: 1,
40: 1,
60: 0.5,
80: 0.33,
};
}
get CONCERN_SUCCESS_RATE_MAP() {
return {
'young adult': 0.55,
'middle-aged': 0.5,
senior: 0.45,
professional: 0.4,
affluent: 0.35,
'': 0.5,
};
}
get CELL_VALUE_MAP() {
return this.algo === 'merit'
? {
low: 2,
medium: 2,
high: 2,
fail: -20,
}
: this.algo === 'meritGrift'
? {
low: 0,
medium: 1,
high: 1,
fail: 0,
}
: {
low: 0.5,
medium: 1.5,
high: 2.5,
fail: -20, // The penalty should be -10. I add a bit to it for demoralization and chain bonus lost.
};
}
get SAFE_CELL_SET() {
return new Set(['neutral', 'low', 'medium', 'high', 'temptation']);
}
get DISPLACEMENT() {
// prettier-ignore
return {
1: {
strong: [[10, 19], [15, 29], [18, 35], [21, 39], [22, 42], [23, 44]],
soft: [[3, 7], [5, 11], [6, 13], [6, 14], [7, 15], [7, 16]],
back: [[-4, -2], [-6, -3], [-7, -4], [-8, -4], [-9, -4], [-9, -5]],
},
20: {
strong: [[8, 15], [12, 23], [15, 28], [16, 31], [18, 33], [18, 35]],
soft: [[3, 7], [5, 11], [6, 13], [6, 14], [7, 15], [7, 16]],
back: [[-4, -2], [-6, -3], [-7, -4], [-8, -4], [-9, -4], [-9, -5]],
},
40: {
strong: [[7, 13], [11, 20], [13, 24], [14, 27], [15, 29], [16, 30]],
soft: [[3, 6], [5, 9], [6, 11], [6, 12], [7, 13], [7, 14]],
back: [[-4, -2], [-6, -3], [-7, -4], [-8, -4], [-9, -4], [-9, -5]],
},
60: {
strong: [[6, 11], [9, 17], [11, 20], [12, 23], [13, 24], [14, 25]],
soft: [[2, 4], [3, 6], [4, 7], [4, 8], [4, 9], [5, 9]],
back: [[-4, -2], [-6, -3], [-7, -4], [-8, -4], [-9, -4], [-9, -5]],
},
80: {
strong: [[5, 9], [8, 14], [9, 17], [10, 19], [11, 20], [12, 21]],
soft: [[2, 3], [3, 5], [4, 6], [4, 6], [4, 7], [5, 7]],
back: [[-3, -2], [-5, -3], [-6, -4], [-6, -4], [-7, -4], [-7, -5]],
},
};
}
get MERIT_MASK_MAP() {
return {
temptation: 1n << 50n,
sensitivity: 1n << 51n,
hesitation: 1n << 52n,
concern: 1n << 53n,
};
}
get MERIT_REQUIREMENT_MASK() {
return 0xfn << 50n;
}
/**
* @param {'exp' | 'merit' | 'meritGrift'} algo
* @param {('neutral' | 'low' | 'medium' | 'high' | 'temptation' | 'sensitivity' | 'hesitation' | 'concern' | 'fail')[]} bar
* @param {1 | 20 | 40 | 60 | 80} targetLevel
* @param {number} round
* @param {number} suspicion
* @param {'young adult' | 'middle-aged' | 'senior' | 'professional' | 'affluent' | ''} mark
*/
constructor(algo, bar, targetLevel, round, suspicion, mark) {
this.algo = algo;
this.bar = bar;
this.targetLevel = targetLevel;
this.failureCost = this.FAILURE_COST_MAP[this.targetLevel];
this.initialRound = round;
this.initialSuspicion = suspicion;
this.mark = mark;
this.driftArrayMap = new Map(); // (resolvingBitmap) => number[50]
this.dp = new Map(); // (resolvingBitmap | round) => {value: number, action: string, multi: number}[50]
this.resolvingMasks = new Array(50);
for (let pip = 0; pip < 50; pip++) {
if (this.resolvingMasks[pip]) {
continue;
}
if (this.bar[pip] !== 'hesitation' && this.bar[pip] !== 'concern') {
this.resolvingMasks[pip] = 0n;
continue;
}
let mask = this.algo === 'merit' ? this.MERIT_MASK_MAP[this.bar[pip]] : 0n;
for (let endPip = pip; endPip < 50 && this.bar[endPip] === this.bar[pip]; endPip++) {
mask += 1n << BigInt(endPip);
}
for (let endPip = pip; endPip < 50 && this.bar[endPip] === this.bar[pip]; endPip++) {
this.resolvingMasks[endPip] = mask;
}
}
}
/**
* @param {number} driftBitmap 1 for temptation triggered, 2 for sensitivity triggered
*/
solve(round, pip, resolvingBitmap, multiplierUsed, driftBitmap) {
if (this.algo === 'merit') {
for (let pip = 0; pip < 50; pip++) {
if (this._isResolved(pip, resolvingBitmap)) {
resolvingBitmap |= this.MERIT_MASK_MAP[this.bar[pip]] ?? 0n;
}
}
resolvingBitmap |= BigInt(driftBitmap) << 50n;
}
const result = this._visit(round - multiplierUsed, resolvingBitmap, multiplierUsed, pip);
return result[pip];
}
/**
* @param {number} round
* @param {bigint} resolvingBitmap
* @param {number} minMulti
* @param {number | undefined} singlePip
*/
_visit(round, resolvingBitmap, minMulti, singlePip = undefined) {
const dpKey = BigInt(round) | (resolvingBitmap << 6n);
// Cached solutions do not respect `minMulti`.
if (minMulti === 0) {
const visited = this.dp.get(dpKey);
if (visited) {
return visited;
}
}
const result = new Array(50);
this.dp.set(dpKey, result);
if (this._estimateSuspicion(round) >= 50) {
for (let pip = 0; pip < 50; pip++) {
result[pip] = this._getCellResult(pip, resolvingBitmap);
}
return result;
}
const driftArray = this._getDriftArray(resolvingBitmap);
const [pipBegin, pipEnd] = singlePip !== undefined ? [singlePip, singlePip + 1] : [0, 50];
for (let pip = pipBegin; pip < pipEnd; pip++) {
const best = this._getCellResult(pip, resolvingBitmap);
if (this.bar[pip] === 'fail') {
result[pip] = best;
continue;
}
if (!this._isResolved(pip, resolvingBitmap)) {
if (this.bar[pip] === 'hesitation') {
const resolvedResult = this._visit(round, resolvingBitmap | this.resolvingMasks[pip], 0);
result[pip] = resolvedResult[pip];
continue;
}
if (this.bar[pip] === 'concern') {
const resolvedResult = this._visit(round + 1, resolvingBitmap | this.resolvingMasks[pip], 0);
const unresolvedResult = this._visit(round + 1, resolvingBitmap, 0);
const concernSuccessRate = this.CONCERN_SUCCESS_RATE_MAP[this.mark] ?? this.CONCERN_SUCCESS_RATE_MAP[''];
const value =
resolvedResult[pip].value * concernSuccessRate +
(unresolvedResult[pip].value - this.failureCost) * (1 - concernSuccessRate) -
this.BASE_ACTION_COST;
result[pip] = {
value: Math.max(0, value),
action: value > 0 ? 'resolve' : 'abandon',
multi: 0,
};
continue;
}
}
for (let multi = minMulti; multi <= 5; multi++) {
const suspicionAfterMulti = this._estimateSuspicion(round + multi);
const nextRoundResult = this._visit(round + multi + 1, resolvingBitmap, 0);
const feasibleActions = pip > 0 ? ['strong', 'soft', 'back'] : ['strong', 'soft'];
for (const action of feasibleActions) {
const displacementArray = this.DISPLACEMENT[this.targetLevel.toString()]?.[action]?.[multi];
if (!displacementArray) {
continue;
}
const [minDisplacement, maxDisplacement] = displacementArray;
let totalValue = 0;
for (let disp = minDisplacement; disp <= maxDisplacement; disp++) {
const landingPip = Math.max(Math.min(pip + disp, 49), 0);
const newPip = driftArray[landingPip];
if (landingPip < suspicionAfterMulti || newPip < suspicionAfterMulti) {
totalValue += this.CELL_VALUE_MAP.fail;
} else {
if (!this.SAFE_CELL_SET.has(this.bar[landingPip]) && !this._isResolved(landingPip, resolvingBitmap)) {
totalValue -= this.failureCost;
}
totalValue -= this.BASE_ACTION_COST;
const landingResult =
this.algo === 'merit' && newPip !== landingPip
? this._visit(round + multi + 1, resolvingBitmap | this.MERIT_MASK_MAP[this.bar[landingPip]], 0)
: nextRoundResult;
totalValue += landingResult[newPip].value;
}
}
const avgValue = totalValue / (maxDisplacement - minDisplacement + 1) - this.BASE_ACTION_COST * multi;
if (avgValue > best.value) {
best.value = avgValue;
best.action = action;
best.multi = multi;
}
}
}
result[pip] = best;
}
return result;
}
_getDriftArray(resolvingBitmap) {
const cached = this.driftArrayMap.get(resolvingBitmap);
if (cached) {
return cached;
}
const driftArray = new Array(50);
this.driftArrayMap.set(resolvingBitmap, driftArray);
for (let pip = 0; pip < 50; pip++) {
let newPip = pip;
switch (this.bar[pip]) {
case 'temptation':
while (
newPip + 1 < 50 &&
(!this.SAFE_CELL_SET.has(this.bar[newPip]) || this.bar[newPip] === 'temptation') &&
!this._isResolved(newPip, resolvingBitmap)
) {
newPip++;
}
break;
case 'sensitivity':
while (newPip > 0 && this.bar[newPip] !== 'neutral' && !this._isResolved(newPip, resolvingBitmap)) {
newPip--;
}
break;
}
driftArray[pip] = newPip;
}
return driftArray;
}
_getCellResult(pip, resolvingBitmap) {
let value = this.CELL_VALUE_MAP[this.bar[pip]] ?? 0;
if (this.algo === 'merit' && (resolvingBitmap & this.MERIT_REQUIREMENT_MASK) !== this.MERIT_REQUIREMENT_MASK) {
value = Math.min(value, 0);
}
const action = this.bar[pip] === 'fail' ? 'fail' : value > 0 ? 'capitalize' : 'abandon';
return { value, action, multi: 0 };
}
_estimateSuspicion(round) {
if (round <= this.initialRound) {
return this.initialSuspicion;
}
const predefined = [0, 0, 0, 0, 2, 5, 8, 11, 16, 23, 34, 50][round] ?? 50;
const current = Math.floor(this.initialSuspicion * 1.5 ** (round - this.initialRound));
return Math.max(predefined, current);
}
_isResolved(pip, resolvingBitmap) {
return ((1n << BigInt(pip)) & resolvingBitmap) !== 0n;
}
}
class ScammingStore {
get TARGET_LEVEL_MAP() {
return {
'delivery scam': 1,
'family scam': 1,
'prize scam': 1,
'charity scam': 20,
'tech support scam': 20,
'vacation scam': 40,
'tax scam': 40,
'advance-fee scam': 60,
'job scam': 60,
'romance scam': 80,
'investment scam': 80,
};
}
get SPAM_ID_MAP() {
return {
295: 'delivery',
293: 'family',
291: 'prize',
297: 'charity',
299: 'tech support',
301: 'vacation',
303: 'tax',
305: 'advance-fee',
307: 'job',
309: 'romance',
311: 'investment',
};
}
constructor() {
this.data = getValue('scamming', {});
this.data.targets = this.data.targets ?? {};
this.data.farms = this.data.farms ?? {};
this.data.spams = this.data.spams ?? {};
this.data.defaultAlgo = this.data.defaultAlgo ?? 'exp';
this.data.algoNotice = this.data.algoNotice ?? {};
this.unsyncedSet = new Set(Object.keys(this.data.targets));
this.solvers = {};
this.lastSolutions = {};
this.cash = undefined;
}
update(data) {
this._updateTargets(data.DB?.crimesByType?.targets);
this._updateFarms(data.DB?.additionalInfo?.currentOngoing);
this._updateSpams(data.DB?.currentUserStats?.crimesByIDAttempts, data.DB?.crimesByType?.methods);
this.cash = data.DB?.user?.money;
this._save();
}
setDefaultAlgo(algo) {
this.data.defaultAlgo = algo;
this._save();
}
changeAlgo(target) {
target.algos.push(target.algos.shift());
target.solution = null;
this._solve(target);
this._save();
}
setAlgoNoticeRead(algo) {
this.data.algoNotice[algo] = true;
this._save();
}
_save() {
setValue('scamming', this.data);
}
_updateTargets(targets) {
if (!targets) {
return;
}
for (const target of targets) {
const stored = this.data.targets[target.subID];
if (stored && !target.new && target.bar) {
stored.driftBitmap = stored.driftBitmap ?? 0; // data migration for v1.4.6
stored.turns = stored.turns ?? target.turns ?? 0; // data migration for v1.4.10
stored.mark = (target.target ?? '').toLowerCase();
let updated = false;
if (
stored.multiplierUsed !== target.multiplierUsed ||
stored.pip !== target.pip ||
stored.turns !== (target.turns ?? 0)
) {
stored.multiplierUsed = target.multiplierUsed;
stored.pip = target.pip;
stored.turns = target.turns ?? 0;
stored.expire = target.expire;
updated = true;
}
if (updated && this.unsyncedSet.has(stored.id)) {
stored.unsynced = true; // replied on another device
}
this.unsyncedSet.delete(stored.id);
if (stored.bar) {
for (let pip = 0; pip < 50; pip++) {
if (target.bar[pip] === stored.bar[pip]) {
continue;
}
if (target.bar[pip] === 'fail' && stored.suspicion <= pip) {
stored.suspicion = pip + 1;
updated = true;
}
if (target.bar[pip] === 'neutral' && (BigInt(stored.resolvingBitmap) & (1n << BigInt(pip))) === 0n) {
stored.resolvingBitmap = (BigInt(stored.resolvingBitmap) | (1n << BigInt(pip))).toString();
updated = true;
}
}
if (target.firstPip) {
if (stored.bar[target.firstPip] === 'temptation') {
stored.driftBitmap |= 1;
}
if (stored.bar[target.firstPip] === 'sensitivity') {
stored.driftBitmap |= 2;
}
}
}
if (updated) {
// Round is not accurate for concern and hesitation.
stored.round = stored.unsynced ? this._estimateRound(target) : stored.round + 1;
}
if (!stored.bar) {
stored.bar = target.bar;
updated = true;
}
if (updated || !stored.solution) {
this._solve(stored);
}
} else {
const multiplierUsed = target.multiplierUsed ?? 0;
const pip = target.pip ?? 0;
const round = multiplierUsed === 0 && pip === 0 ? 0 : Math.max(1, multiplierUsed);
const stored = {
id: target.subID,
email: target.email,
level: this.TARGET_LEVEL_MAP[target.scamMethod.toLowerCase()] ?? 999,
mark: '',
round,
turns: target.turns ?? 0,
multiplierUsed,
pip,
expire: target.expire,
bar: target.bar ?? null,
suspicion: 0,
resolvingBitmap: '0',
driftBitmap: 0,
algos: null,
solution: null,
unsynced: round > 0,
};
this.data.targets[target.subID] = stored;
this._solve(stored);
}
}
const now = Math.floor(Date.now() / 1000);
for (const target of Object.values(this.data.targets)) {
if (target.expire < now) {
delete this.data.targets[target.id];
}
}
}
_updateFarms(currentOngoing) {
if (typeof currentOngoing !== 'object' || !(currentOngoing.length > 0)) {
return;
}
for (const item of currentOngoing) {
if (!item.type) {
continue;
}
this.data.farms[item.type] = { expire: item.timeEnded };
}
}
_updateSpams(crimesByIDAttempts, methods) {
if (!crimesByIDAttempts || !methods) {
return;
}
const now = Math.floor(Date.now() / 1000);
for (const [id, count] of Object.entries(crimesByIDAttempts)) {
const type = this.SPAM_ID_MAP[id];
const method = methods.find((x) => String(x.crimeID) === id);
if (!type || !method) {
continue;
}
const stored = this.data.spams[id];
if (stored) {
if (count !== stored.count) {
stored.count = count;
stored.accurate = now - stored.ts < 3600;
stored.since = now;
}
stored.ts = now;
stored.depreciation = method.depreciation;
} else {
this.data.spams[id] = {
count,
accurate: false,
since: null,
ts: now,
depreciation: method.depreciation,
};
}
}
}
_solve(target) {
if (!target.bar) {
return;
}
this.lastSolutions[target.id] = target.solution;
let solver = this.solvers[target.id];
if (!solver || solver.algo !== target.algos?.[0] || target.suspicion > 0) {
if (!target.algos) {
target.algos = ['exp'];
if (this._isDecepticonFeasible(target)) {
target.algos.push('merit');
}
if (this._isGriftHorseFeasible(target)) {
target.algos.push('meritGrift');
}
const defaultIndex = target.algos.indexOf(this.data.defaultAlgo);
if (defaultIndex > 0) {
target.algos = [...target.algos.slice(defaultIndex), ...target.algos.slice(0, defaultIndex)];
}
}
solver = new ScammingSolver(
target.algos[0],
target.bar,
target.level,
target.round,
target.suspicion,
target.mark,
);
this.solvers[target.id] = solver;
}
target.solution = solver.solve(
target.round,
target.pip,
BigInt(target.resolvingBitmap),
target.multiplierUsed,
target.driftBitmap,
);
}
_estimateRound(target) {
// This "turns" from the server gets +2 from temptation and sensitivity (round +1 in these cases) and
// gets +1 from hesitation (round +2 in this case).
// The "*Attempt" fields from the server are at most 1 even with multiple attempts.
return Math.max(
0,
(target.turns ?? 0) -
(target.temptationAttempt ?? 0) -
(target.sensitivityAttempt ?? 0) +
(target.hesitationAttempt ?? 0),
);
}
_isDecepticonFeasible(target) {
const cells = new Set(target.bar);
return cells.has('temptation') && cells.has('sensitivity') && cells.has('hesitation') && cells.has('concern');
}
_isGriftHorseFeasible(target) {
return target.mark === 'affluent';
}
}
class ScammingObserver {
constructor() {
this.store = new ScammingStore();
this.crimeOptions = null;
this.farmIcons = null;
this.spamOptions = null;
this.virtualLists = null;
this.observer = new MutationObserver((mutations) => {
const isAdd = mutations.some((mutation) => {
for (const added of mutation.addedNodes) {
if (added instanceof HTMLElement) {
return true;
}
}
return false;
});
if (!isAdd) {
return;
}
for (const element of this.crimeOptions) {
if (!element.classList.contains('cm-sc-seen')) {
element.classList.add('cm-sc-seen');
this._refreshCrimeOption(element);
}
}
for (const element of this.farmIcons) {
if (!element.classList.contains('cm-sc-seen')) {
element.classList.add('cm-sc-seen');
this._refreshFarm(element);
}
}
for (const element of this.spamOptions) {
if (!element.classList.contains('cm-sc-seen')) {
element.classList.add('cm-sc-seen');
this._refreshSpam(element);
}
}
for (const element of this.virtualLists) {
if (!element.classList.contains('cm-sc-seen')) {
element.classList.add('cm-sc-seen');
this._refreshSettings(element);
}
}
});
}
start() {
if (this.crimeOptions) {
return;
}
this.crimeOptions = document.body.getElementsByClassName('crime-option');
this.farmIcons = document.body.getElementsByClassName('scraperPhisher___oy1Wn');
this.spamOptions = document.body.getElementsByClassName('optionWithLevelRequirement___cHH35');
this.virtualLists = document.body.getElementsByClassName('virtualList___noLef');
this.observer.observe($('.scamming-root')[0], { subtree: true, childList: true });
}
stop() {
this.crimeOptions = null;
this.observer.disconnect();
}
onNewData() {
this.start();
for (const element of this.crimeOptions) {
this._refreshCrimeOption(element);
}
for (const element of this.farmIcons) {
this._refreshFarm(element);
}
for (const element of this.spamOptions) {
this._refreshSpam(element);
}
}
_buildHintHtml(target, solution, lastSolution, showGriftNotice) {
const actionText =
{
strong: 'Fast Fwd',
soft: 'Soft Fwd',
back: 'Back',
capitalize: '$$$',
abandon: 'Abandon',
resolve: 'Resolve',
}[solution.action] ?? 'N/A';
const algo = target.algos?.[0];
const algoText =
{
exp: 'Exp',
merit: 'Decep',
meritGrift: 'Grift',
}[algo] ?? 'Score';
const score = Math.floor(solution.value * 100);
const scoreText = `${score}${algo === 'meritGrift' ? '%' : ''}`;
let scoreColor = '';
if (algo === 'meritGrift') {
scoreColor = score < 30 ? 't-red' : score < 60 ? 't-yellow' : 't-green';
} else {
scoreColor = score < 30 ? 't-red' : score < 100 ? 't-yellow' : 't-green';
}
const scoreDiff = lastSolution ? score - Math.floor(lastSolution.value * 100) : 0;
const scoreDiffColor = scoreDiff > 0 ? 't-green' : 't-red';
const scoreDiffText = scoreDiff !== 0 ? `(${scoreDiff > 0 ? '+' : ''}${scoreDiff})` : '';
let rspText = solution.multi > target.multiplierUsed ? 'Accel' : actionText;
let rspColor = '';
let fullRspText = solution.multi > 0 ? `(${target.multiplierUsed}/${solution.multi} + ${actionText})` : '';
if (target.unsynced) {
rspText = 'Unsynced';
rspColor = 't-gray-c';
fullRspText = fullRspText !== '' ? fullRspText : `(${actionText})`;
}
const $wrapper = $('<span class="cm-sc-info cm-sc-hint cm-sc-hint-content"></span>');
if (showGriftNotice) {
$wrapper.append(`<span><span class="cm-sc-algo">${algoText}</span></span>`);
$wrapper.append('<span class="cm-sc-notice t-blue">Click to read about this strategy</span>');
$wrapper.children('.cm-sc-notice').on('click', () => {
const msg =
'Warning: The "Grift Horse" strategy is highly aggressive and does NOT avoid critical failures. ' +
'You may lose a significant amount of crime experience.\n\n' +
'Click OK to proceed with this risky strategy, or Cancel to choose a safer alternative.';
if (confirm(msg)) {
this.store.setAlgoNoticeRead(algo);
location.reload();
}
});
} else {
$wrapper.append(
`<span><span class="cm-sc-algo">${algoText}</span>: <span class="${scoreColor}">${scoreText}</span><span class="${scoreDiffColor}">${scoreDiffText}</span></span>`,
);
$wrapper.append(
`<span class="cm-sc-hint-action"><span class="${rspColor}">${rspText}</span> <span class="t-gray-c">${fullRspText}</span></span>`,
);
}
$wrapper.append(`<span class="cm-sc-hint-button t-blue">Lv${target.level}</span>`);
return $wrapper;
}
_refreshCrimeOption(element) {
this._refreshTarget(element);
this._refreshFarmButton(element);
}
_refreshTarget(element) {
const $crimeOption = $(element);
const $email = $crimeOption.find('span.email___gVRXx');
const email = $email.text();
const target = Object.values(this.store.data.targets).find((x) => x.email === email);
if (!target) {
return;
}
// clear old info elements
const hasHint = $crimeOption.find('.cm-sc-hint-content').length > 0;
$crimeOption.find('.cm-sc-info').remove();
$email.parent().addClass('cm-sc-info-wrapper');
$email.parent().children().addClass('cm-sc-orig-info');
// hint
const solution = target.solution;
if (solution) {
if (!hasHint) {
$email.parent().removeClass('cm-sc-hint-hidden');
}
const algo = target.algos?.[0];
const showGriftNotice = algo === 'meritGrift' && !this.store.data.algoNotice[algo];
const actionAttr = showGriftNotice
? ''
: solution.multi > target.multiplierUsed
? 'accelerate'
: solution.action;
$crimeOption.attr('data-cm-action', actionAttr);
$crimeOption.toggleClass('cm-sc-unsynced', !showGriftNotice && (target.unsynced ?? false));
const lastSolution = this.store.lastSolutions[target.id];
$email.parent().append(this._buildHintHtml(target, solution, lastSolution, showGriftNotice));
$email.parent().append(`<span class="cm-sc-info cm-sc-orig-info cm-sc-hint-button t-blue">Hint</div>`);
$crimeOption.find('.cm-sc-hint-button').on('click', () => {
$email.parent().toggleClass('cm-sc-hint-hidden');
});
if (target.algos?.length > 1) {
const $algo = $crimeOption.find('.cm-sc-algo');
$algo.addClass('t-blue');
$algo.addClass('cm-sc-active');
$algo.on('click', () => {
this.store.changeAlgo(target);
this._refreshTarget(element);
});
}
} else {
$email.parent().addClass('cm-sc-hint-hidden');
}
// lifetime
const now = Math.floor(Date.now() / 1000);
const lifetime = formatLifetime(target.expire - now);
$email.before(`<span class="cm-sc-info ${lifetime.color}">${lifetime.text}</div>`);
// scale
const $cells = $crimeOption.find('.cell___AfwZm');
if ($cells.length >= 50) {
$cells.find('.cm-sc-scale').remove();
// Ignore cells after the first 50, which are faded out soon
for (let i = 0; i < 50; i++) {
const dist = i - target.pip;
const label = dist % 5 !== 0 || dist === 0 || dist < -5 ? '' : dist % 10 === 0 ? (dist / 10).toString() : "'";
let $scale = $cells.eq(i).children('.cm-sc-scale');
if ($scale.length === 0) {
$scale = $('<div class="cm-sc-scale"></div>');
$cells.eq(i).append($scale);
}
$scale.text(label);
}
}
// multiplier
const $accButton = $crimeOption.find('.response-type-button').eq(3);
$accButton.find('.cm-sc-multiplier').remove();
if (target.multiplierUsed > 0) {
$accButton.append(`<div class="cm-sc-multiplier">${target.multiplierUsed}</div>`);
}
}
_refreshFarmButton(element) {
const $element = $(element);
if ($element.find('.emailAddresses___ky_qG').length === 0) {
return;
}
$element.find('.commitButtonSection___wJfnI button').toggleClass('cm-sc-low-cash', this.store.cash < 10000);
}
_refreshFarm(element) {
const $element = $(element);
const label = $element.attr('aria-label') ?? '';
const farm = Object.entries(this.store.data.farms).find(([type]) => label.toLowerCase().includes(type))?.[1];
if (!farm) {
return;
}
const now = Math.floor(Date.now() / 1000);
const lifetime = formatLifetime(farm.expire - now);
$element.find('.cm-sc-farm-lifetime').remove();
$element.append(`<div class="cm-sc-farm-lifetime ${lifetime.color}">${lifetime.text}</div>`);
}
_refreshSpam(element) {
const $spamOption = $(element);
if ($spamOption.closest('.dropdownList').length === 0) {
return;
}
const label = $spamOption
.contents()
.filter((_, x) => x.nodeType === Node.TEXT_NODE)
.text();
const spam = Object.entries(this.store.data.spams).find(([id]) =>
label.toLowerCase().includes(this.store.SPAM_ID_MAP[id]),
)?.[1];
$spamOption.addClass('cm-sc-spam-option');
$spamOption.find('.cm-sc-spam-elapsed').remove();
if (!spam || !spam.since || spam.depreciation) {
return;
}
const now = Math.floor(Date.now() / 1000);
const elapsed = formatLifetime(now - spam.since);
if (!spam.accurate) {
elapsed.text = '> ' + elapsed.text;
}
if (elapsed.hours >= 24 * 8) {
elapsed.text = '> 7d';
}
if (elapsed.hours >= 24 && elapsed.hours < 72) {
elapsed.color = 't-green';
}
$spamOption.append(`<div class="cm-sc-spam-elapsed ${elapsed.color}">${elapsed.text}</div>`);
}
_refreshSettings(element) {
const store = this.store;
const defaultAlgo = store.data.defaultAlgo;
const $settings = $(`<div class="cm-sc-settings">
<span>Default Strategy:</span>
<span class="cm-sc-algo-option t-blue" data-cm-value="exp">Exp</span>
<span class="cm-sc-algo-option t-blue" data-cm-value="merit">Decepticon</span>
<span class="cm-sc-algo-option t-blue" data-cm-value="meritGrift">Grift Horse</span>
</div>`);
$settings.children(`[data-cm-value="${defaultAlgo}"]`).addClass('cm-sc-active');
$settings.children('.cm-sc-algo-option').on('click', function () {
const $this = $(this);
store.setDefaultAlgo($this.attr('data-cm-value'));
$this.siblings().removeClass('cm-sc-active');
$this.addClass('cm-sc-active');
});
$settings.insertBefore(element);
}
}
const scammingObserver = new ScammingObserver();
async function checkScamming(crimeType, data) {
if (crimeType !== '12') {
scammingObserver.stop();
return;
}
scammingObserver.store.update(data);
scammingObserver.onNewData();
}
async function onCrimeData(crimeType, data) {
await checkDemoralization(data);
await checkBurglary(crimeType, data);
await checkPickpocketing(crimeType);
await checkScamming(crimeType, data);
}
function interceptFetch() {
const targetWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const origFetch = targetWindow.fetch;
targetWindow.fetch = async (...args) => {
const rsp = await origFetch(...args);
try {
const url = new URL(args[0], location.origin);
const params = new URLSearchParams(url.search);
const reqBody = args[1]?.body;
const crimeType = params.get('typeID') ?? reqBody?.get('typeID');
if (url.pathname === '/page.php' && params.get('sid') === 'crimesData' && crimeType) {
const clonedRsp = rsp.clone();
await onCrimeData(crimeType, await clonedRsp.json());
}
} catch {
// ignore
}
return rsp;
};
}
function renderMorale() {
const interval = setInterval(async function () {
if (!$) {
return; // JQuery is not loaded in TornPDA yet
}
const $container = $('.crimes-app-header');
if ($container.length === 0) {
return;
}
clearInterval(interval);
$container.append(`<span>Morale: <span id="crime-morale-value">-</span>%</span>`);
const morale = parseInt(await getValue(STORAGE_MORALE));
if (!isNaN(morale)) {
updateMorale(morale);
}
// Show hidden debug button on double-click
let lastClick = 0; // dblclick event doesn't work well on mobile
$('#crime-morale-value')
.parent()
.on('click', function () {
if (Date.now() - lastClick > 1000) {
lastClick = Date.now();
return;
}
const data = {
morale: getValue(STORAGE_MORALE),
burglary: getValue('burglary'),
scamming: getValue('scamming'),
};
const export_uri = `data:application/json;charset=utf-8,${encodeURIComponent(JSON.stringify(data))}`;
$(this).replaceWith(`<a download="crime-morale-debug.json" href="${export_uri}"
class="torn-btn" style="display:inline-block;">Export Debug Data</a>`);
});
}, 500);
}
function updateMorale(morale) {
$('#crime-morale-value').text(morale.toString());
}
function renderStyle() {
addStyle(`
.cm-bg-lifetime {
position: absolute;
top: 0;
right: 0;
padding: 2px;
background: var(--default-bg-panel-color);
border: 1px solid darkgray;
}
.cm-bg-favor {
position: absolute;
right: 0;
bottom: 0;
background: #fffc;
height: 20px;
width: 20px;
font-size: 20px;
line-height: 1;
cursor: pointer;
pointer-events: auto !important;
}
.cm-bg-favor:after {
content: '\u2606';
display: block;
width: 100%;
height: 100%;
text-align: center;
}
.cm-bg-favor.cm-bg-active:after {
content: '\u2605';
color: orange;
}
:root {
--cm-pp-level-1: #37b24d;
--cm-pp-level-2: #95af14;
--cm-pp-level-3: #f4cc00;
--cm-pp-level-4: #fa9201;
--cm-pp-level-5: #e01111;
--cm-pp-level-6: #a016eb;
--cm-pp-filter-level-1: brightness(0) saturate(100%) invert(61%) sepia(11%) saturate(2432%) hue-rotate(79deg) brightness(91%) contrast(96%);
--cm-pp-filter-level-2: brightness(0) saturate(100%) invert(62%) sepia(80%) saturate(2102%) hue-rotate(32deg) brightness(99%) contrast(84%);
--cm-pp-filter-level-3: brightness(0) saturate(100%) invert(71%) sepia(53%) saturate(1820%) hue-rotate(9deg) brightness(107%) contrast(102%);
--cm-pp-filter-level-4: brightness(0) saturate(100%) invert(61%) sepia(62%) saturate(1582%) hue-rotate(356deg) brightness(94%) contrast(108%);
--cm-pp-filter-level-5: brightness(0) saturate(100%) invert(12%) sepia(72%) saturate(5597%) hue-rotate(354deg) brightness(105%) contrast(101%);
--cm-pp-filter-level-6: brightness(0) saturate(100%) invert(26%) sepia(84%) saturate(4389%) hue-rotate(271deg) brightness(86%) contrast(119%);
}
@keyframes cm-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
visibility: hidden;
}
}
.cm-overlay {
position: relative;
}
.cm-overlay:after {
content: '';
position: absolute;
background: repeating-linear-gradient(135deg, #2223, #2223 70px, #0003 70px, #0003 80px);
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 900000;
}
.cm-overlay-fade:after {
animation-name: cm-fade-out;
animation-duration: 0.2s;
animation-timing-function: ease-in;
animation-fill-mode: forwards;
animation-delay: 0.4s
}
.cm-pp-level-1 {
color: var(--cm-pp-level-1);
}
.cm-pp-level-2 {
color: var(--cm-pp-level-2);
}
.cm-pp-level-3 {
color: var(--cm-pp-level-3);
}
.cm-pp-level-4 {
color: var(--cm-pp-level-4);
}
.cm-pp-level-5 {
color: var(--cm-pp-level-5);
}
.cm-pp-level-6 {
color: var(--cm-pp-level-6);
}
.cm-pp-level-1 [class*=timerCircle___] [class*=icon___] {
filter: var(--cm-pp-filter-level-1);
}
.cm-pp-level-2 [class*=timerCircle___] [class*=icon___] {
filter: var(--cm-pp-filter-level-2);
}
.cm-pp-level-3 [class*=timerCircle___] [class*=icon___] {
filter: var(--cm-pp-filter-level-3);
}
.cm-pp-level-4 [class*=timerCircle___] [class*=icon___] {
filter: var(--cm-pp-filter-level-4);
}
.cm-pp-level-5 [class*=timerCircle___] [class*=icon___] {
filter: var(--cm-pp-filter-level-5);
}
.cm-pp-level-6 [class*=timerCircle___] [class*=icon___] {
filter: var(--cm-pp-filter-level-6);
}
.cm-pp-level-1 [class*=timerCircle___] .CircularProgressbar-path {
stroke: var(--cm-pp-level-1) !important;
}
.cm-pp-level-2 [class*=timerCircle___] .CircularProgressbar-path {
stroke: var(--cm-pp-level-2) !important;
}
.cm-pp-level-3 [class*=timerCircle___] .CircularProgressbar-path {
stroke: var(--cm-pp-level-3) !important;
}
.cm-pp-level-4 [class*=timerCircle___] .CircularProgressbar-path {
stroke: var(--cm-pp-level-4) !important;
}
.cm-pp-level-5 [class*=timerCircle___] .CircularProgressbar-path {
stroke: var(--cm-pp-level-5) !important;
}
.cm-pp-level-6 [class*=timerCircle___] .CircularProgressbar-path {
stroke: var(--cm-pp-level-6) !important;
}
.cm-pp-level-1 [class*=commitButton___] {
border: 2px solid var(--cm-pp-level-1);
}
.cm-pp-level-2 [class*=commitButton___] {
border: 2px solid var(--cm-pp-level-2);
}
.cm-pp-level-3 [class*=commitButton___] {
border: 2px solid var(--cm-pp-level-3);
}
.cm-pp-level-4 [class*=commitButton___] {
border: 2px solid var(--cm-pp-level-4);
}
.cm-pp-level-5 [class*=commitButton___] {
border: 2px solid var(--cm-pp-level-5);
}
.cm-pp-best-build:not(.crime-option-locked) [class*=physicalProps___]:before {
content: '\u2713 ';
font-weight: bold;
color: var(--cm-pp-level-2);
}
.cm-sc-info {
transform: translateY(1px);
}
.cm-sc-notice,
.cm-sc-hint-button {
cursor: pointer;
}
.cm-sc-info-wrapper.cm-sc-hint-hidden > .cm-sc-hint,
.cm-sc-info-wrapper:not(.cm-sc-hint-hidden) > .cm-sc-orig-info {
display: none;
}
.cm-sc-hint-content {
display: flex;
justify-content: space-between;
flex-grow: 1;
gap: 5px;
white-space: nowrap;
overflow: hidden;
}
.cm-sc-notice,
.cm-sc-hint-action {
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.cm-sc-seen[data-cm-action=strong] .response-type-button:nth-child(1):after,
.cm-sc-seen[data-cm-action=soft] .response-type-button:nth-child(2):after,
.cm-sc-seen[data-cm-action=back] .response-type-button:nth-child(3):after,
.cm-sc-seen[data-cm-action=accelerate] .response-type-button:nth-child(4):after,
.cm-sc-seen[data-cm-action=capitalize] .response-type-button:nth-child(5):after {
content: '\u2713';
color: var(--crimes-green-color);
position: absolute;
top: 0;
right: 0;
font-size: 12px;
font-weight: bolder;
line-height: 1;
z-index: 999;
}
.cm-sc-seen.cm-sc-unsynced[data-cm-action=strong] .response-type-button:nth-child(1):after,
.cm-sc-seen.cm-sc-unsynced[data-cm-action=soft] .response-type-button:nth-child(2):after,
.cm-sc-seen.cm-sc-unsynced[data-cm-action=back] .response-type-button:nth-child(3):after,
.cm-sc-seen.cm-sc-unsynced[data-cm-action=accelerate] .response-type-button:nth-child(4):after,
.cm-sc-seen.cm-sc-unsynced[data-cm-action=capitalize] .response-type-button:nth-child(5):after {
content: '?';
}
.cm-sc-seen[data-cm-action=abandon] .response-type-button:after {
content: '\u2715';
color: var(--crimes-stats-criticalFails-color);
position: absolute;
top: 0;
right: 0;
font-size: 12px;
font-weight: bolder;
line-height: 1;
z-index: 999;
}
.cm-sc-scale {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100% + 10px);
line-height: 1;
font-size: 8px;
display: flex;
align-items: flex-end;
justify-content: center;
}
.cm-sc-multiplier {
position: absolute;
bottom: 0;
right: 0;
color: var(--crimes-baseText-color);
text-align: right;
font-size: 10px;
line-height: 1;
}
.cm-sc-farm-lifetime {
padding-top: 2px;
text-align: center;
}
.cm-sc-spam-option .levelLabel___LNbg8,
.cm-sc-spam-option .separator___C2skk {
display: none;
}
.cm-sc-spam-elapsed {
position: absolute;
right: -5px;
}
.cm-sc-settings {
height: 40px;
width: 100%;
background: var(--default-bg-panel-color););
border-bottom: 1px solid var(--crimes-crimeOption-borderBottomColor);
padding-left: 10px;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 20px;
}
.cm-sc-algo-option {
cursor: pointer;
line-height: 1.5;
border-top: 2px solid #0000;
border-bottom: 2px solid #0000;
}
.cm-sc-algo-option.cm-sc-active {
border-bottom-color: var(--default-blue-color);
}
.cm-sc-algo.cm-sc-active {
cursor: pointer;
}
.cm-sc-algo.cm-sc-active:before {
content: '\u21bb ';
}
.cm-sc-low-cash:after {
content: 'Low Cash';
color: var(--default-red-color);
position: absolute;
width: 100%;
left: 0;
top: calc(100% - 4px);
line-height: 1;
font-size: 12px;
}
`);
}
interceptFetch();
renderMorale();
if (document.readyState === 'loading') {
document.addEventListener('readystatechange', () => {
if (document.readyState === 'interactive') {
renderStyle();
}
});
} else {
renderStyle();
}
})();
// Fast Slots
// author Silmaril [2665762]
(function() {
'use strict';
const originalAjax = $.ajax;
$.ajax = function (options) {
if (options.data != null && options.data.sid == 'slotsData' && options.data.step == 'play') {
const originalSuccess = options.success;
options.success = function (data, textStatus, jqXHR) {
if (data.error) delete data.error;
if (data.errorMsg) delete data.errorMsg;
data.barrelsAnimationSpeed = 0;
if (originalSuccess) {
originalSuccess(data, textStatus, jqXHR);
}
};
}
return originalAjax(options);
}
function enableBetButtons() {
document.querySelectorAll(".slots-btn-list .betbtn").forEach(btn => {
btn.classList.remove("disabled");
});
}
function disableBetButtons() {
document.querySelectorAll(".slots-btn-list .betbtn").forEach(btn => {
btn.classList.add("disabled");
});
}
function watchBarrelsSpinAndStop(delay = 60) {
const barrels = document.querySelectorAll("#barrel0, #barrel1, #barrel2");
let timers = new Map();
let stopped = new Map();
barrels.forEach(barrel => stopped.set(barrel, true));
barrels.forEach(barrel => {
const observer = new MutationObserver(() => {
disableBetButtons();
stopped.set(barrel, false);
clearTimeout(timers.get(barrel));
timers.set(barrel, setTimeout(() => {
stopped.set(barrel, true);
if ([...stopped.values()].every(Boolean)) {
enableBetButtons();
}
}, delay));
});
observer.observe(barrel, {
attributes: true,
attributeFilter: ["style"]
});
});
}
var o = setInterval(() => {
if($('#barrels').length == 1){
clearInterval(o)
watchBarrelsSpinAndStop();
}
}, 100);
})();
// Pickpocketing colors
// author Korbrm [2931507]
(function() {
'use strict';
const categoryColorMap = {
"Safe": "#37b24d",
"Moderately Unsafe": "#74b816",
"Unsafe": "#f59f00",
"Risky": "#f76707",
"Dangerous": "#f03e3e",
"Very Dangerous": "#7048e8",
};
var sideColorMap = {
"Safe": "#37b24d",
"Moderately Unsafe": "#74b816",
"Unsafe": "#f59f00",
"Risky": "#f76707",
"Dangerous": "#f03e3e",
"Very Dangerous": "#7048e8",
}
const tier1 = {
"Safe": "#37b24d",
"Moderately Unsafe": "#f76707",
"Unsafe": "#f03e3e",
"Risky": "#f03e3e",
"Dangerous": "#f03e3e",
"Very Dangerous": "#7048e8",
}
const tier2 = {
"Safe": "#37b24d",
"Moderately Unsafe": "#37b24d",
"Unsafe": "#f76707",
"Risky": "#f03e3e",
"Dangerous": "#f03e3e",
"Very Dangerous": "#7048e8",
}
const tier3 = {
"Safe": "#37b24d",
"Moderately Unsafe": "#37b24d",
"Unsafe": "#37b24d",
"Risky": "#f76707",
"Dangerous": "#f03e3e",
"Very Dangerous": "#7048e8",
}
const tier4 = {
"Safe": "#37b24d",
"Moderately Unsafe": "#37b24d",
"Unsafe": "#37b24d",
"Risky": "#37b24d",
"Dangerous": "#f76707",
"Very Dangerous": "#7048e8",
}
const tier5 = {
"Safe": "#37b24d",
"Moderately Unsafe": "#37b24d",
"Unsafe": "#37b24d",
"Risky": "#37b24d",
"Dangerous": "#37b24d",
"Very Dangerous": "#7048e8",
}
const markGroups = {
"Safe": ["Drunk man", "Drunk woman", "Homeless person", "Junkie", "Elderly man", "Elderly woman"],
"Moderately Unsafe": ["Classy lady", "Laborer", "Postal worker", "Young man", "Young woman", "Student"],
"Unsafe": ["Rich kid", "Sex worker", "Thug"],
"Risky": ["Jogger", "Businessman", "Businesswoman", "Gang member", "Mobster"],
"Dangerous": ["Cyclist"],
"Very Dangerous": ["Police officer"],
};
function updateDivColors() {
var spanElement = document.querySelector('.value___FdkAT.copyTrigger___fsdzI');
const url = window.location.href;
if (!url.includes("#/pickpocketing")){
return;
}
if (spanElement) {
var pickpocketSkill = spanElement.textContent;
}
if (pickpocketSkill < 10) { sideColorMap = tier1; } else
if (pickpocketSkill < 35) { sideColorMap = tier2; } else
if (pickpocketSkill < 65) { sideColorMap = tier3; } else
if (pickpocketSkill < 80) { sideColorMap = tier4; } else
{ sideColorMap = tier5; }
const divElements = document.querySelectorAll('.titleAndProps___DdeVu:not(.processed)');
divElements.forEach(divElement => {
const divContent = divElement.querySelector('div').textContent.trim();
const additionalData = divElement.querySelector('button.physicalPropsButton___xWW45');
if (additionalData) {
const additionalText = additionalData.textContent.trim();
const text = divContent + ' ' + additionalText;
for (const category in markGroups) {
if (markGroups[category].some(group => text.includes(group))) {
divElement.querySelector('div').style.color = categoryColorMap[category];
if (window.innerWidth > 386) {
divElement.querySelector('div').textContent = `${divContent} (${category})`;
}
divElement.classList.add('processed');
let parentElement = divElement;
for (let i = 0; i < 3; i++) {
parentElement = parentElement.parentElement;
}
if (!parentElement.classList.contains('processed')) {
parentElement.style.borderLeft = `3px solid ${sideColorMap[category]}`;
parentElement.classList.add('processed');
}
}
}
}
});
}
updateDivColors();
setInterval(updateDivColors, 2000);
})();
// name Torn Market Filler
// author Silmaril [2665762]
(async function() {
'use strict';
const itemUrl = "https://api.torn.com/torn/{itemId}?selections=items&key={apiKey}&comment=MarketFiller";
const marketUrl = "https://api.torn.com/v2/market/{itemId}?selections=itemMarket&key={apiKey}&comment=MarketFiller";
const marketUrlV2 = "https://api.torn.com/v2/market?id={itemId}&selections=itemMarket&key={apiKey}&comment=MarketFiller";
let showPricesPopup = localStorage.getItem("silmaril-torn-market-filler-show-prices-popup") ?? '1';
showPricesPopup = Boolean(parseInt(showPricesPopup));
let priceDeltaRaw = localStorage.getItem("silmaril-torn-market-filler-price-delta") ?? localStorage.getItem("silmaril-torn-bazaar-filler-price-delta") ?? '-1[0]';
let apiKey = localStorage.getItem("sheepAPI") ?? '###PDA-APIKEY###';
let togglePricesPopupMenuId, setPriceDeltaMenuId, setApiKeyMenuId;
try {
togglePricesPopupMenuId = GM_registerMenuCommand(`Toggle Prices Popup (${showPricesPopup ? 'ON' : 'OFF'})`, togglePricesPopupVisibility);
setPriceDeltaMenuId = GM_registerMenuCommand(`Set Price Delta: ${priceDeltaRaw}`, setPriceDelta);
setApiKeyMenuId = GM_registerMenuCommand(`Set Api Key: ${apiKey}`, function() { checkApiKey(false); });
} catch (error) {
console.warn('[TornMarketFiller] Tampermonkey not detected!');
}
let GM_addStyle = function (s) {
let style = document.createElement("style");
style.type = "text/css";
style.innerHTML = s;
document.head.appendChild(style);
};
GM_addStyle(`#item-market-root [class^=addListingWrapper___] [class^=panels___] [class^=priceInputWrapper___]>.input-money-group>.input-money,#item-market-root [class^=viewListingWrapper___] [class^=priceInputWrapper___]>.input-money-group>.input-money{font-size:smaller!important;border-bottom-left-radius:0!important;border-top-left-radius:0!important}.silmaril-market-filler-popup{background:var(--tooltip-bg-color);padding:12px 18px;border-radius:8px;border:1px solid #888;box-shadow:0 4px 18px 0 #0009;color:var(--info-msg-font-color);z-index:99999;position:absolute;font-size:1em!important;line-height:1.5;pointer-events:auto}.silmaril-market-filler-popup-close{position:absolute;top:4px;right:7px;font-size:1em;color:#aaa;cursor:pointer}.silmaril-market-filler-popup-draggable{user-select:none;cursor:move}.silmaril-torn-market-filler-popup-price{cursor:pointer}`);
const pages = { "AddItems": 10, "ViewItems": 20, "Other": 0};
let recentFilledInput = null;
let popupOffsetX = localStorage.getItem("silmaril-torn-market-filler-popup-offset-x") ?? 0, popupOffsetY = 0, isDragging = false;
const marketTaxFactor = 1 - getCurrentMarketTax();
let currentPage = pages.Other;
let holdTimer;
const LOADING_THE_PRICES = 'Loading the prices...';
const isMobileView = window.innerWidth <= 784;
const observerTarget = document.querySelector("#item-market-root");
const observerConfig = { attributes: false, childList: true, characterData: false, subtree: true };
const observer = new MutationObserver(function(mutations) {
mutations.forEach(mutationRaw => {
let mutation = mutationRaw.target;
currentPage = getCurrentPage();
if (currentPage == pages.AddItems){
if (mutation.id && mutation.id.startsWith('headlessui-tabs-panel-')) {
mutation.querySelectorAll('[class*=itemRowWrapper___]:not(.silmaril-market-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]').forEach(x => AddFillButton(x));
}
if (String(mutation.className).indexOf('priceInputWrapper___') > -1){
AddFillButton(mutation);
}
} else if (currentPage == pages.ViewItems){
if (mutation.className && mutation.className.startsWith('viewListingWrapper___')) {
mutation.querySelectorAll('[class*=itemRowWrapper___]:not(.silmaril-market-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]').forEach(x => AddFillButton(x));
}
}
});
});
observer.observe(observerTarget, observerConfig);
addCustomFillPopup();
function AddFillButton(itemPriceElement){
if (itemPriceElement.querySelector('.silmaril-market-filler-button') != null){
return;
}
const wrapperParent = findParentByCondition(itemPriceElement, (el) => String(el.className).indexOf('itemRowWrapper___') > -1);
wrapperParent.classList.add('silmaril-market-filler-processed');
let itemIdString = wrapperParent.querySelector('[class^=itemRow___] [type=button][class^=viewInfoButton___]').getAttribute('aria-controls');
let itemImage = wrapperParent.querySelector('[class*=viewInfoButton] img');
let itemId = currentPage == pages.AddItems ? getItemIdFromString(itemIdString) : getItemIdFromImage(itemImage);
const span = document.createElement('span');
span.className = 'silmaril-market-filler-button input-money-symbol';
span.style.position = "relative";
span.setAttribute('data-action-flag', 'fill');
span.addEventListener('click', async function(e) { await handleFillClick(e, itemId) });
span.addEventListener('mousedown', startHold);
span.addEventListener('touchstart', startHold);
span.addEventListener('mouseup', cancelHold);
span.addEventListener('mouseleave', cancelHold);
span.addEventListener('touchend', cancelHold);
span.addEventListener('touchcancel', cancelHold);
const input = document.createElement('input');
input.type = 'button';
input.className = 'wai-btn';
span.appendChild(input);
itemPriceElement.querySelector('.input-money-group').prepend(span);
}
async function GetPrices(itemId){
let requestUrl = priceDeltaRaw.indexOf('[market]') != -1 ? itemUrl : marketUrlV2;
requestUrl = requestUrl
.replace("{itemId}", itemId)
.replace("{apiKey}", apiKey);
return fetch(requestUrl)
.then(response => response.json())
.then(data => {
if (data.error != null){
switch (data.error.code){
case 2:
apiKey = null;
localStorage.setItem("silmaril-torn-bazaar-filler-apikey", null);
console.error("[TornMarketFiller] Incorrect Api Key:", data);
return {"price": 'Wrong API key!', "amount": 0};
case 9:
console.warn("[TornMarketFiller] The API is temporarily disabled, please try again later");
return {"price": 'API is OFF!', "amount": 0};
default:
console.error("[TornMarketFiller] Error:", data.error.error);
return {"price": data.error.error, "amount": 0};
}
}
if (priceDeltaRaw.indexOf('[market]') != -1){
return {"price": data.items[itemId].market_value, "amount": 1};
} else {
if (data.itemmarket.listings[0].price == null){
console.warn("[TornMarketFiller] The API is temporarily disabled, please try again later");
return {"price": 'API is OFF!', "amount": 0};
}
// temporary hotfix to avoid wrong prices
if (data.itemmarket.item.id != itemId){
return {"price": 'API is BROKEN!', "amount": 0};
}
return data.itemmarket.listings;
}
})
.catch(error => {
console.error("[TornMarketFiller] Error fetching data:", error);
return 'Failed!';
});
}
function GetPrice(prices){
if (prices == null){
return 'No prices loaded';
}
if (prices.amount == 0){
return prices.price;
}
if (priceDeltaRaw.indexOf('[market]') != -1) {
prices = Array(prices);
let priceDelta = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
return Math.round(performOperation(prices[0].price, priceDelta));
} else if (priceDeltaRaw.indexOf('[median]') != -1) {
let priceDelta = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
return Math.round(performOperation(getMedianPrice(prices), priceDelta));
} else {
let marketSlotOffset = priceDeltaRaw.indexOf('[') == -1 ? 0 : parseInt(priceDeltaRaw.substring(priceDeltaRaw.indexOf('[') + 1, priceDeltaRaw.indexOf(']')));
let priceDeltaWithoutMarketOffset = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
return Math.round(performOperation(prices[Math.min(marketSlotOffset, prices.length - 1)].price, priceDeltaWithoutMarketOffset));
}
}
async function handleFillClick(event, itemId){
let target = event.currentTarget || event.target;
let priceInputs = target.parentNode.querySelectorAll('input.input-money');
recentFilledInput = priceInputs;
const popup = document.querySelector('.silmaril-market-filler-popup');
if (popup) {
const rect = target.getBoundingClientRect();
if (popupOffsetX == 0){
popupOffsetX = window.scrollX + rect.left - 300;
localStorage.setItem("silmaril-torn-market-filler-popup-offset-x", popupOffsetX);
}
popupOffsetY = window.scrollY + rect.top + 4;
let left = popupOffsetX;
let top = popupOffsetY;
popup.style.display = showPricesPopup ? 'block' : 'none';
popup.style.visibility = 'hidden';
popup.style.left = `${left}px`;
popup.style.top = `${top}px`;
popup.querySelector('.silmaril-market-filler-popup-body').innerHTML = LOADING_THE_PRICES;
const popupRect = popup.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
if (popupRect.right > viewportWidth) {
left = Math.max(0, viewportWidth - popupRect.width - 10 + scrollX);
}
if (popupRect.left < 0) {
left = 10 + scrollX;
}
if (popupRect.bottom > viewportHeight) {
top = Math.max(0, viewportHeight - popupRect.height - 10 + scrollY);
}
if (popupRect.top < 0) {
top = 10 + scrollY;
}
popup.style.left = `${left}px`;
popup.style.top = `${top}px`;
popup.style.visibility = 'visible';
}
let action = target.getAttribute('data-action-flag');
let prices = await GetPrices(itemId);
const breakdown = GetPricesBreakdown(prices);
// Thanks to Rosti [2840742] for the help with the prices popup component
showCustomFillPopup(target, breakdown);
let price = action == 'fill' ? GetPrice(prices) : '';
switchActionFlag(target);
let parentRow = findParentByCondition(target, (el) => String(el.className).indexOf('info___') > -1);
let quantityInputs = parentRow.querySelectorAll('[class^=amountInputWrapper___] .input-money-group > .input-money');
if (quantityInputs.length > 0){
if (quantityInputs[0].value.length === 0 || parseInt(quantityInputs[0].value) < 1){
quantityInputs[0].value = action == 'fill' ? Number.MAX_SAFE_INTEGER : 0;
quantityInputs[1].value = action == 'fill' ? Number.MAX_SAFE_INTEGER : 0;
} else {
quantityInputs[0].value = action == 'clear' ? '' : quantityInputs[0].value;
quantityInputs[1].value = action == 'clear' ? '' : quantityInputs[1].value;
}
quantityInputs[0].dispatchEvent(new Event("input", {bubbles: true}));
} else {
let checkbox = parentRow.querySelector('[class^=checkboxWrapper___] > [class^=checkboxContainer___] [type=checkbox]');
if (checkbox && ((action == 'fill' && !checkbox.checked) || (action == 'clear' && checkbox.checked))){
checkbox.click();
}
}
priceInputs.forEach(x => {x.value = price});
priceInputs[0].dispatchEvent(new Event("input", {bubbles: true}));
}
function hideAllFillPopups() {
document.querySelector('.silmaril-market-filler-popup').style.display = 'none';
}
function showCustomFillPopup(targetElem, contentHTML) {
const popup = document.querySelector('.silmaril-market-filler-popup');
popup.querySelector('.silmaril-market-filler-popup-body').innerHTML = contentHTML;
popup.querySelectorAll('.silmaril-torn-market-filler-popup-price').forEach(row => {
row.addEventListener('click', (e) => {
recentFilledInput.forEach(x => {x.value = parseInt(e.target.getAttribute('data-price')) - 1});
recentFilledInput[0].dispatchEvent(new Event("input", {bubbles: true}));
});
});
}
function addCustomFillPopup() {
const popup = document.createElement('div');
popup.className = 'silmaril-market-filler-popup';
popup.style.display = 'none';
popup.style.left = popupOffsetX + 'px';
popup.style.top = '0px';
popup.innerHTML = '<div class="silmaril-market-filler-popup-close" title="Close">×</div><b class="silmaril-market-filler-popup-draggable">Drag from here</b><br><div class="silmaril-market-filler-popup-body"></div>';
popup.querySelector('.silmaril-market-filler-popup-close').onclick = function(){ popup.style.display = 'none'; };
document.body.appendChild(popup);
const dragHandle = popup.querySelector('.silmaril-market-filler-popup-draggable');
dragHandle.addEventListener("mousedown", (e) => {
isDragging = true;
popupOffsetX = e.clientX - popup.offsetLeft;
popupOffsetY = e.clientY - popup.offsetTop;
});
document.addEventListener("mousemove", (e) => {
if (isDragging) {
popup.style.left = (e.clientX - popupOffsetX) + "px";
popup.style.top = (e.clientY - popupOffsetY) + "px";
}
});
document.addEventListener("mouseup", (e) => {
if (isDragging) {
popupOffsetX = e.clientX - popupOffsetX;
popupOffsetY = e.clientY - popupOffsetY;
localStorage.setItem("silmaril-torn-market-filler-popup-offset-x", popupOffsetX);
}
isDragging = false;
});
// Touch events (mobile)
dragHandle.addEventListener("touchstart", (e) => {
isDragging = true;
const touch = e.touches[0];
popupOffsetX = touch.clientX - popup.offsetLeft;
popupOffsetY = touch.clientY - popup.offsetTop;
e.preventDefault();
}, { passive: false });
document.addEventListener("touchmove", (e) => {
if (isDragging) {
const touch = e.touches[0];
popup.style.left = (touch.clientX - popupOffsetX) + "px";
popup.style.top = (touch.clientY - popupOffsetY) + "px";
}
}, { passive: false });
document.addEventListener("touchend", () => {
if (isDragging) {
popupOffsetX = popup.offsetLeft;
popupOffsetY = popup.offsetTop;
localStorage.setItem("silmaril-torn-market-filler-popup-offset-x", popupOffsetX);
}
isDragging = false;
});
}
function getItemIdFromString(string){
const match = string.match(/-(\d+)-/);
if (match) {
const number = match[1];
return number;
} else {
console.error("[TornMarketFiller] ItemId not found!");
return -1;
}
}
function getItemIdFromImage(image){
let numberPattern = /\/(\d+)\//;
let match = image.src.match(numberPattern);
if (match) {
return parseInt(match[1], 10);
} else {
console.error("[TornMarketFiller] ItemId not found!");
return -1;
}
}
function switchActionFlag(target){
switch (target.getAttribute('data-action-flag')){
case 'fill':
target.setAttribute('data-action-flag', 'clear');
break;
case 'clear':
default:
target.setAttribute('data-action-flag', 'fill');
break;
}
}
function findParentByCondition(element, conditionFn){
let currentElement = element;
while (currentElement !== null) {
if (conditionFn(currentElement)) {
return currentElement;
}
currentElement = currentElement.parentElement;
}
return null;
}
function setPriceDelta() {
let userInput = prompt('Enter price delta formula (default: -1[0]):', priceDeltaRaw);
if (userInput !== null) {
priceDeltaRaw = userInput;
localStorage.setItem("silmaril-torn-market-filler-price-delta", userInput);
} else {
console.error("[TornMarketFiller] User cancelled the Price Delta input.");
}
}
function GetPricesBreakdown(prices){
if (prices == null) return "No prices loaded";
if (prices[0] === undefined){
prices = Array(prices);
}
const sb = new StringBuilder();
for (let i = 0; i < Math.min(prices.length, 5); i++){
if(typeof prices[i] !== "object" || prices[i].amount === undefined || prices[i].price === undefined) continue;
sb.append(`<span class="silmaril-torn-market-filler-popup-price" data-price=${prices[i].price}>${prices[i].amount} x ${formatNumberWithCommas(prices[i].price)} (${formatNumberWithCommas(Math.round(prices[i].price * marketTaxFactor))})</span>`);
if (i < Math.min(prices.length, 5)-1){
sb.append('<br>');
}
}
return sb.toString();
}
function performOperation(number, operation) {
const match = operation.match(/^([-+]?)(\d+(?:\.\d+)?)(%)?$/);
if (!match) {
throw new Error('Invalid operation string');
}
const [, operator, operand, isPercentage] = match;
const operandValue = parseFloat(operand);
const adjustedOperand = isPercentage ? (number * operandValue) / 100 : operandValue;
switch (operator) {
case '':
case '+':
return number + adjustedOperand;
case '-':
return number - adjustedOperand;
default:
throw new Error('Invalid operator');
}
}
function formatNumberWithCommas(number) {
return new Intl.NumberFormat('en-US').format(number);
}
function checkApiKey(checkExisting = true) {
if (!checkExisting || apiKey === null || apiKey.indexOf('PDA-APIKEY') > -1 || apiKey.length != 16){
let userInput = prompt("Please enter a PUBLIC Api Key, it will be used to get current bazaar prices:", apiKey ?? '');
if (userInput !== null && userInput.length == 16) {
apiKey = userInput;
localStorage.setItem("silmaril-torn-bazaar-filler-apikey", userInput);
} else {
console.error("[TornMarketFiller] User cancelled the Api Key input.");
}
}
}
function askForPricesPopupFlag() {
let dsf = null;
let userInput = prompt("Please choose to show or hide the lowest 5 prices popup, enter 1 to SHOW or 0 to HIDE:", showPricesPopup ? '1' : '0');
if (userInput !== null && userInput.length == 1) {
if (userInput != '1' && userInput != '0'){
console.error("[TornMarketFiller] User entered invalid value for the Prices Popup input.");
return;
}
showPricesPopup = Boolean(parseInt(userInput));
localStorage.setItem('silmaril-torn-market-filler-show-prices-popup', showPricesPopup ? '1' : '0');
} else {
console.error("[TornMarketFiller] User cancelled the Prices Popup input.");
}
}
function togglePricesPopupVisibility() {
showPricesPopup = !showPricesPopup;
localStorage.setItem('silmaril-torn-market-filler-show-prices-popup', showPricesPopup ? '1' : '0');
}
function getMedianPrice(items) {
const prices = items.flatMap(item => Array(item.amount).fill(item.price));
prices.sort((a, b) => a - b);
const mid = Math.floor(prices.length / 2);
if (prices.length % 2 === 0) {
return (prices[mid - 1] + prices[mid]) / 2;
} else {
return prices[mid];
}
}
function getCurrentPage(){
if (window.location.href.indexOf('#/addListing') > -1){
return pages.AddItems;
} else if (window.location.href.indexOf('#/viewListing') > -1){
return pages.ViewItems;
} else {
return pages.Other;
}
}
function getCurrentMarketTax() {
return 0.05;
}
// function getTornToday() {
// const now = document.querySelector('span.server-date-time').textContent.split(' ');
// return now[now.length - 1];
// }
function parseDate(str) {
const [dd, mm, yy] = str.split('/').map(Number);
const fullYear = yy < 50 ? 2000 + yy : 1900 + yy;
return new Date(fullYear, mm - 1, dd);
}
const startHold = () => {
holdTimer = setTimeout(() => {
askForPricesPopupFlag();
setPriceDelta();
checkApiKey(false);
}, 2000);
};
const cancelHold = () => {
clearTimeout(holdTimer);
};
class StringBuilder {
constructor() {
this.parts = [];
}
append(str) {
this.parts.push(str);
return this;
}
toString() {
return this.parts.join('');
}
}
})();
const sheepAPI = localStorage.getItem("sheepAPI");
if (sheepAPI === null) {
if (window.location.href === "https://www.torn.com/preferences.php#tab=api") {createButtonAtPositionAPI("API", 0, 550);} else {
createButtonAtPositionAPI2("API", 0, 550);}}
function createButtonAtPositionAPI(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = "100px";
customButton.style.height = "50px";
customButton.innerHTML = 'Click Here To Enter Public API';
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999999999999';
customButton.addEventListener('click', function() {
let sheepAPI = localStorage.getItem("sheepAPI");
if (sheepAPI === null) {
sheepAPI = prompt("Please enter your Public API key");
// Check if input is 16 characters and only letters/numbers
if (/^[a-zA-Z0-9]{16}$/.test(sheepAPI)) {
localStorage.setItem("sheepAPI", sheepAPI);
window.location.reload();
} else {
alert("Invalid API key. It must be exactly 16 letters and numbers.");
// Optionally, you can prompt again
}
}
});
document.body.appendChild(customButton);
}
function createButtonAtPositionAPI2(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = "100px";
customButton.style.height = "50px";
customButton.innerHTML = 'Click Here To Get Public API key';
customButton.style.position = 'fixed';
customButton.style.top = topPx + 'px';
customButton.style.left = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999999999999';
customButton.addEventListener('click', function() {
const sheepAPI = localStorage.getItem("sheepAPI");
if (sheepAPI === null) {
window.open('https://www.torn.com/preferences.php#tab=api').focus();
} else {
}
});
document.body.appendChild(customButton);
}
// name Torn Market Filler
// author Silmaril [2665762]
(async function() {
'use strict';
const itemUrl = "https://api.torn.com/torn/{itemId}?selections=items&key={apiKey}&comment=MarketFiller";
const marketUrl = "https://api.torn.com/v2/market/{itemId}?selections=itemMarket&key={apiKey}&comment=MarketFiller";
const marketUrlV2 = "https://api.torn.com/v2/market?id={itemId}&selections=itemMarket&key={apiKey}&comment=MarketFiller";
let showPricesPopup = localStorage.getItem("silmaril-torn-market-filler-show-prices-popup") ?? '1';
showPricesPopup = Boolean(parseInt(showPricesPopup));
let priceDeltaRaw = localStorage.getItem("silmaril-torn-market-filler-price-delta") ?? localStorage.getItem("silmaril-torn-bazaar-filler-price-delta") ?? '-1[0]';
let apiKey = localStorage.getItem("sheepAPI") ?? '###PDA-APIKEY###';
let togglePricesPopupMenuId, setPriceDeltaMenuId, setApiKeyMenuId;
try {
togglePricesPopupMenuId = GM_registerMenuCommand(`Toggle Prices Popup (${showPricesPopup ? 'ON' : 'OFF'})`, togglePricesPopupVisibility);
setPriceDeltaMenuId = GM_registerMenuCommand(`Set Price Delta: ${priceDeltaRaw}`, setPriceDelta);
setApiKeyMenuId = GM_registerMenuCommand(`Set Api Key: ${apiKey}`, function() { checkApiKey(false); });
} catch (error) {
console.warn('[TornMarketFiller] Tampermonkey not detected!');
}
let GM_addStyle = function (s) {
let style = document.createElement("style");
style.type = "text/css";
style.innerHTML = s;
document.head.appendChild(style);
};
GM_addStyle(`#item-market-root [class^=addListingWrapper___] [class^=panels___] [class^=priceInputWrapper___]>.input-money-group>.input-money,#item-market-root [class^=viewListingWrapper___] [class^=priceInputWrapper___]>.input-money-group>.input-money{font-size:smaller!important;border-bottom-left-radius:0!important;border-top-left-radius:0!important}.silmaril-market-filler-popup{background:var(--tooltip-bg-color);padding:12px 18px;border-radius:8px;border:1px solid #888;box-shadow:0 4px 18px 0 #0009;color:var(--info-msg-font-color);z-index:99999;position:absolute;font-size:1em!important;line-height:1.5;pointer-events:auto}.silmaril-market-filler-popup-close{position:absolute;top:4px;right:7px;font-size:1em;color:#aaa;cursor:pointer}.silmaril-market-filler-popup-draggable{user-select:none;cursor:move}.silmaril-torn-market-filler-popup-price{cursor:pointer}`);
const pages = { "AddItems": 10, "ViewItems": 20, "Other": 0};
let recentFilledInput = null;
let popupOffsetX = localStorage.getItem("silmaril-torn-market-filler-popup-offset-x") ?? 0, popupOffsetY = 0, isDragging = false;
const marketTaxFactor = 1 - getCurrentMarketTax();
let currentPage = pages.Other;
let holdTimer;
const LOADING_THE_PRICES = 'Loading the prices...';
const isMobileView = window.innerWidth <= 784;
const observerTarget = document.querySelector("#item-market-root");
const observerConfig = { attributes: false, childList: true, characterData: false, subtree: true };
const observer = new MutationObserver(function(mutations) {
mutations.forEach(mutationRaw => {
let mutation = mutationRaw.target;
currentPage = getCurrentPage();
if (currentPage == pages.AddItems){
if (mutation.id && mutation.id.startsWith('headlessui-tabs-panel-')) {
mutation.querySelectorAll('[class*=itemRowWrapper___]:not(.silmaril-market-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]').forEach(x => AddFillButton(x));
}
if (String(mutation.className).indexOf('priceInputWrapper___') > -1){
AddFillButton(mutation);
}
} else if (currentPage == pages.ViewItems){
if (mutation.className && mutation.className.startsWith('viewListingWrapper___')) {
mutation.querySelectorAll('[class*=itemRowWrapper___]:not(.silmaril-market-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]').forEach(x => AddFillButton(x));
}
}
});
});
observer.observe(observerTarget, observerConfig);
addCustomFillPopup();
function AddFillButton(itemPriceElement){
if (itemPriceElement.querySelector('.silmaril-market-filler-button') != null){
return;
}
const wrapperParent = findParentByCondition(itemPriceElement, (el) => String(el.className).indexOf('itemRowWrapper___') > -1);
wrapperParent.classList.add('silmaril-market-filler-processed');
let itemIdString = wrapperParent.querySelector('[class^=itemRow___] [type=button][class^=viewInfoButton___]').getAttribute('aria-controls');
let itemImage = wrapperParent.querySelector('[class*=viewInfoButton] img');
let itemId = currentPage == pages.AddItems ? getItemIdFromString(itemIdString) : getItemIdFromImage(itemImage);
const span = document.createElement('span');
span.className = 'silmaril-market-filler-button input-money-symbol';
span.style.position = "relative";
span.setAttribute('data-action-flag', 'fill');
span.addEventListener('click', async function(e) { await handleFillClick(e, itemId) });
span.addEventListener('mousedown', startHold);
span.addEventListener('touchstart', startHold);
span.addEventListener('mouseup', cancelHold);
span.addEventListener('mouseleave', cancelHold);
span.addEventListener('touchend', cancelHold);
span.addEventListener('touchcancel', cancelHold);
const input = document.createElement('input');
input.type = 'button';
input.className = 'wai-btn';
span.appendChild(input);
itemPriceElement.querySelector('.input-money-group').prepend(span);
}
async function GetPrices(itemId){
let requestUrl = priceDeltaRaw.indexOf('[market]') != -1 ? itemUrl : marketUrlV2;
requestUrl = requestUrl
.replace("{itemId}", itemId)
.replace("{apiKey}", apiKey);
return fetch(requestUrl)
.then(response => response.json())
.then(data => {
if (data.error != null){
switch (data.error.code){
case 2:
apiKey = null;
localStorage.setItem("silmaril-torn-bazaar-filler-apikey", null);
console.error("[TornMarketFiller] Incorrect Api Key:", data);
return {"price": 'Wrong API key!', "amount": 0};
case 9:
console.warn("[TornMarketFiller] The API is temporarily disabled, please try again later");
return {"price": 'API is OFF!', "amount": 0};
default:
console.error("[TornMarketFiller] Error:", data.error.error);
return {"price": data.error.error, "amount": 0};
}
}
if (priceDeltaRaw.indexOf('[market]') != -1){
return {"price": data.items[itemId].market_value, "amount": 1};
} else {
if (data.itemmarket.listings[0].price == null){
console.warn("[TornMarketFiller] The API is temporarily disabled, please try again later");
return {"price": 'API is OFF!', "amount": 0};
}
// temporary hotfix to avoid wrong prices
if (data.itemmarket.item.id != itemId){
return {"price": 'API is BROKEN!', "amount": 0};
}
return data.itemmarket.listings;
}
})
.catch(error => {
console.error("[TornMarketFiller] Error fetching data:", error);
return 'Failed!';
});
}
function GetPrice(prices){
if (prices == null){
return 'No prices loaded';
}
if (prices.amount == 0){
return prices.price;
}
if (priceDeltaRaw.indexOf('[market]') != -1) {
prices = Array(prices);
let priceDelta = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
return Math.round(performOperation(prices[0].price, priceDelta));
} else if (priceDeltaRaw.indexOf('[median]') != -1) {
let priceDelta = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
return Math.round(performOperation(getMedianPrice(prices), priceDelta));
} else {
let marketSlotOffset = priceDeltaRaw.indexOf('[') == -1 ? 0 : parseInt(priceDeltaRaw.substring(priceDeltaRaw.indexOf('[') + 1, priceDeltaRaw.indexOf(']')));
let priceDeltaWithoutMarketOffset = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
return Math.round(performOperation(prices[Math.min(marketSlotOffset, prices.length - 1)].price, priceDeltaWithoutMarketOffset));
}
}
async function handleFillClick(event, itemId){
let target = event.currentTarget || event.target;
let priceInputs = target.parentNode.querySelectorAll('input.input-money');
recentFilledInput = priceInputs;
const popup = document.querySelector('.silmaril-market-filler-popup');
if (popup) {
const rect = target.getBoundingClientRect();
if (popupOffsetX == 0){
popupOffsetX = window.scrollX + rect.left - 300;
localStorage.setItem("silmaril-torn-market-filler-popup-offset-x", popupOffsetX);
}
popupOffsetY = window.scrollY + rect.top + 4;
let left = popupOffsetX;
let top = popupOffsetY;
popup.style.display = showPricesPopup ? 'block' : 'none';
popup.style.visibility = 'hidden';
popup.style.left = `${left}px`;
popup.style.top = `${top}px`;
popup.querySelector('.silmaril-market-filler-popup-body').innerHTML = LOADING_THE_PRICES;
const popupRect = popup.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
if (popupRect.right > viewportWidth) {
left = Math.max(0, viewportWidth - popupRect.width - 10 + scrollX);
}
if (popupRect.left < 0) {
left = 10 + scrollX;
}
if (popupRect.bottom > viewportHeight) {
top = Math.max(0, viewportHeight - popupRect.height - 10 + scrollY);
}
if (popupRect.top < 0) {
top = 10 + scrollY;
}
popup.style.left = `${left}px`;
popup.style.top = `${top}px`;
popup.style.visibility = 'visible';
}
let action = target.getAttribute('data-action-flag');
let prices = await GetPrices(itemId);
const breakdown = GetPricesBreakdown(prices);
// Thanks to Rosti [2840742] for the help with the prices popup component
showCustomFillPopup(target, breakdown);
let price = action == 'fill' ? GetPrice(prices) : '';
switchActionFlag(target);
let parentRow = findParentByCondition(target, (el) => String(el.className).indexOf('info___') > -1);
let quantityInputs = parentRow.querySelectorAll('[class^=amountInputWrapper___] .input-money-group > .input-money');
if (quantityInputs.length > 0){
if (quantityInputs[0].value.length === 0 || parseInt(quantityInputs[0].value) < 1){
quantityInputs[0].value = action == 'fill' ? Number.MAX_SAFE_INTEGER : 0;
quantityInputs[1].value = action == 'fill' ? Number.MAX_SAFE_INTEGER : 0;
} else {
quantityInputs[0].value = action == 'clear' ? '' : quantityInputs[0].value;
quantityInputs[1].value = action == 'clear' ? '' : quantityInputs[1].value;
}
quantityInputs[0].dispatchEvent(new Event("input", {bubbles: true}));
} else {
let checkbox = parentRow.querySelector('[class^=checkboxWrapper___] > [class^=checkboxContainer___] [type=checkbox]');
if (checkbox && ((action == 'fill' && !checkbox.checked) || (action == 'clear' && checkbox.checked))){
checkbox.click();
}
}
priceInputs.forEach(x => {x.value = price});
priceInputs[0].dispatchEvent(new Event("input", {bubbles: true}));
}
function hideAllFillPopups() {
document.querySelector('.silmaril-market-filler-popup').style.display = 'none';
}
function showCustomFillPopup(targetElem, contentHTML) {
const popup = document.querySelector('.silmaril-market-filler-popup');
popup.querySelector('.silmaril-market-filler-popup-body').innerHTML = contentHTML;
popup.querySelectorAll('.silmaril-torn-market-filler-popup-price').forEach(row => {
row.addEventListener('click', (e) => {
recentFilledInput.forEach(x => {x.value = parseInt(e.target.getAttribute('data-price')) - 1});
recentFilledInput[0].dispatchEvent(new Event("input", {bubbles: true}));
});
});
}
function addCustomFillPopup() {
const popup = document.createElement('div');
popup.className = 'silmaril-market-filler-popup';
popup.style.display = 'none';
popup.style.left = popupOffsetX + 'px';
popup.style.top = '0px';
popup.innerHTML = '<div class="silmaril-market-filler-popup-close" title="Close">×</div><b class="silmaril-market-filler-popup-draggable">Drag from here</b><br><div class="silmaril-market-filler-popup-body"></div>';
popup.querySelector('.silmaril-market-filler-popup-close').onclick = function(){ popup.style.display = 'none'; };
document.body.appendChild(popup);
const dragHandle = popup.querySelector('.silmaril-market-filler-popup-draggable');
dragHandle.addEventListener("mousedown", (e) => {
isDragging = true;
popupOffsetX = e.clientX - popup.offsetLeft;
popupOffsetY = e.clientY - popup.offsetTop;
});
document.addEventListener("mousemove", (e) => {
if (isDragging) {
popup.style.left = (e.clientX - popupOffsetX) + "px";
popup.style.top = (e.clientY - popupOffsetY) + "px";
}
});
document.addEventListener("mouseup", (e) => {
if (isDragging) {
popupOffsetX = e.clientX - popupOffsetX;
popupOffsetY = e.clientY - popupOffsetY;
localStorage.setItem("silmaril-torn-market-filler-popup-offset-x", popupOffsetX);
}
isDragging = false;
});
// Touch events (mobile)
dragHandle.addEventListener("touchstart", (e) => {
isDragging = true;
const touch = e.touches[0];
popupOffsetX = touch.clientX - popup.offsetLeft;
popupOffsetY = touch.clientY - popup.offsetTop;
e.preventDefault();
}, { passive: false });
document.addEventListener("touchmove", (e) => {
if (isDragging) {
const touch = e.touches[0];
popup.style.left = (touch.clientX - popupOffsetX) + "px";
popup.style.top = (touch.clientY - popupOffsetY) + "px";
}
}, { passive: false });
document.addEventListener("touchend", () => {
if (isDragging) {
popupOffsetX = popup.offsetLeft;
popupOffsetY = popup.offsetTop;
localStorage.setItem("silmaril-torn-market-filler-popup-offset-x", popupOffsetX);
}
isDragging = false;
});
}
function getItemIdFromString(string){
const match = string.match(/-(\d+)-/);
if (match) {
const number = match[1];
return number;
} else {
console.error("[TornMarketFiller] ItemId not found!");
return -1;
}
}
function getItemIdFromImage(image){
let numberPattern = /\/(\d+)\//;
let match = image.src.match(numberPattern);
if (match) {
return parseInt(match[1], 10);
} else {
console.error("[TornMarketFiller] ItemId not found!");
return -1;
}
}
function switchActionFlag(target){
switch (target.getAttribute('data-action-flag')){
case 'fill':
target.setAttribute('data-action-flag', 'clear');
break;
case 'clear':
default:
target.setAttribute('data-action-flag', 'fill');
break;
}
}
function findParentByCondition(element, conditionFn){
let currentElement = element;
while (currentElement !== null) {
if (conditionFn(currentElement)) {
return currentElement;
}
currentElement = currentElement.parentElement;
}
return null;
}
function setPriceDelta() {
let userInput = prompt('Enter price delta formula (default: -1[0]):', priceDeltaRaw);
if (userInput !== null) {
priceDeltaRaw = userInput;
localStorage.setItem("silmaril-torn-market-filler-price-delta", userInput);
} else {
console.error("[TornMarketFiller] User cancelled the Price Delta input.");
}
}
function GetPricesBreakdown(prices){
if (prices == null) return "No prices loaded";
if (prices[0] === undefined){
prices = Array(prices);
}
const sb = new StringBuilder();
for (let i = 0; i < Math.min(prices.length, 5); i++){
if(typeof prices[i] !== "object" || prices[i].amount === undefined || prices[i].price === undefined) continue;
sb.append(`<span class="silmaril-torn-market-filler-popup-price" data-price=${prices[i].price}>${prices[i].amount} x ${formatNumberWithCommas(prices[i].price)} (${formatNumberWithCommas(Math.round(prices[i].price * marketTaxFactor))})</span>`);
if (i < Math.min(prices.length, 5)-1){
sb.append('<br>');
}
}
return sb.toString();
}
function performOperation(number, operation) {
const match = operation.match(/^([-+]?)(\d+(?:\.\d+)?)(%)?$/);
if (!match) {
throw new Error('Invalid operation string');
}
const [, operator, operand, isPercentage] = match;
const operandValue = parseFloat(operand);
const adjustedOperand = isPercentage ? (number * operandValue) / 100 : operandValue;
switch (operator) {
case '':
case '+':
return number + adjustedOperand;
case '-':
return number - adjustedOperand;
default:
throw new Error('Invalid operator');
}
}
function formatNumberWithCommas(number) {
return new Intl.NumberFormat('en-US').format(number);
}
function checkApiKey(checkExisting = true) {
if (!checkExisting || apiKey === null || apiKey.indexOf('PDA-APIKEY') > -1 || apiKey.length != 16){
let userInput = prompt("Please enter a PUBLIC Api Key, it will be used to get current bazaar prices:", apiKey ?? '');
if (userInput !== null && userInput.length == 16) {
apiKey = userInput;
localStorage.setItem("silmaril-torn-bazaar-filler-apikey", userInput);
} else {
console.error("[TornMarketFiller] User cancelled the Api Key input.");
}
}
}
function askForPricesPopupFlag() {
let dsf = null;
let userInput = prompt("Please choose to show or hide the lowest 5 prices popup, enter 1 to SHOW or 0 to HIDE:", showPricesPopup ? '1' : '0');
if (userInput !== null && userInput.length == 1) {
if (userInput != '1' && userInput != '0'){
console.error("[TornMarketFiller] User entered invalid value for the Prices Popup input.");
return;
}
showPricesPopup = Boolean(parseInt(userInput));
localStorage.setItem('silmaril-torn-market-filler-show-prices-popup', showPricesPopup ? '1' : '0');
} else {
console.error("[TornMarketFiller] User cancelled the Prices Popup input.");
}
}
function togglePricesPopupVisibility() {
showPricesPopup = !showPricesPopup;
localStorage.setItem('silmaril-torn-market-filler-show-prices-popup', showPricesPopup ? '1' : '0');
}
function getMedianPrice(items) {
const prices = items.flatMap(item => Array(item.amount).fill(item.price));
prices.sort((a, b) => a - b);
const mid = Math.floor(prices.length / 2);
if (prices.length % 2 === 0) {
return (prices[mid - 1] + prices[mid]) / 2;
} else {
return prices[mid];
}
}
function getCurrentPage(){
if (window.location.href.indexOf('#/addListing') > -1){
return pages.AddItems;
} else if (window.location.href.indexOf('#/viewListing') > -1){
return pages.ViewItems;
} else {
return pages.Other;
}
}
function getCurrentMarketTax() {
return 0.05;
}
// function getTornToday() {
// const now = document.querySelector('span.server-date-time').textContent.split(' ');
// return now[now.length - 1];
// }
function parseDate(str) {
const [dd, mm, yy] = str.split('/').map(Number);
const fullYear = yy < 50 ? 2000 + yy : 1900 + yy;
return new Date(fullYear, mm - 1, dd);
}
const startHold = () => {
holdTimer = setTimeout(() => {
askForPricesPopupFlag();
setPriceDelta();
checkApiKey(false);
}, 2000);
};
const cancelHold = () => {
clearTimeout(holdTimer);
};
class StringBuilder {
constructor() {
this.parts = [];
}
append(str) {
this.parts.push(str);
return this;
}
toString() {
return this.parts.join('');
}
}
})();
// name Torn: Refill Blood Bag Reminder
// author ButtChew [3840391]
(function () {
'use strict';
const CONFIG = {
// Icon settings
fullLifeIconId: 'tm-full-life-bloodbag',
bloodBagPng: 'https://i.postimg.cc/mkZ1T68H/blood-bag-2.png',
// Destination URLs
destinations: {
factionArmoury: 'https://www.torn.com/factions.php?step=your&type=1#/tab=armoury&start=0&sub=medical',
personalInventory: 'https://www.torn.com/item.php#medical-items',
},
// Blood bag mechanics
lifePerBag: 30, // Each bag uses 30% life
cooldownPerBagMs: 60 * 60 * 1000, // Each bag adds 1 hour cooldown
// Status icons selector (for inserting our icon)
statusIconsSelector: 'ul[class*="status-icons"]',
// Poll interval
pollMs: 2000,
};
(function () {
'use strict';
const BANNER_ID = "sheepieBloodBagBanner";
const LINK = "https://www.torn.com/factions.php?step=your&type=1#/tab=armoury&start=0&sub=medical";
function checkForBloodBag() {
const icon = document.querySelector('img[src*="blood-bag-2.png"]');
if (!icon) return;
if (document.getElementById(BANNER_ID)) return;
const banner = document.createElement("div");
banner.id = BANNER_ID;
banner.textContent = "please fill blood bags";
banner.style.position = "fixed";
banner.style.top = "150000000000000px";
banner.style.left = "333333333px";
if (localStorage.getItem("warmode") === "true") {
// do warmode stuff
banner.style.top = "15px";
banner.style.left = "50%";
}
banner.style.transform = "translateX(-50%)";
banner.style.background = "#8b0000";
banner.style.color = "white";
banner.style.padding = "10px 20px";
banner.style.fontSize = "16px";
banner.style.fontWeight = "bold";
banner.style.borderRadius = "8px";
banner.style.boxShadow = "0 4px 12px rgba(0,0,0,0.4)";
banner.style.zIndex = "999999";
banner.style.cursor = "pointer";
banner.style.transition = "opacity 0.5s ease";
banner.addEventListener("click", function () {
window.location.href = LINK;
});
document.body.appendChild(banner);
setTimeout(() => {
banner.style.opacity = "0";
setTimeout(() => banner.remove(), 500);
}, 3000);
}
// Because Torn loads dynamically, poll briefly
const interval = setInterval(() => {
checkForBloodBag();
}, 2500);
// Stop checking after 10 seconds
setTimeout(() => clearInterval(interval), 10000);
})();
// ===== GM_* COMPATIBILITY (TornPDA support) =====
const safeGM = {
getValue: (key, defaultVal) => {
try {
return typeof GM_getValue === 'function' ? GM_getValue(key, defaultVal) : defaultVal;
} catch { return defaultVal; }
},
setValue: (key, val) => {
try {
if (typeof GM_setValue === 'function') GM_setValue(key, val);
} catch { /* ignore */ }
},
registerMenuCommand: (name, fn) => {
try {
if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand(name, fn);
} catch { /* ignore */ }
}
};
// ===== SESSIONSTORAGE DATA EXTRACTION =====
function getSidebarData() {
try {
const key = Object.keys(sessionStorage).find(k => /sidebarData\d+/.test(k));
if (!key) return null;
return JSON.parse(sessionStorage.getItem(key));
} catch {
return null;
}
}
function getLifeFromStorage() {
const data = getSidebarData();
if (!data) return null;
// Life data is at data.bars.life with amount/max properties
const life = data?.bars?.life;
if (life && typeof life.amount === 'number' && typeof life.max === 'number') {
const pct = life.max > 0 ? Math.round((life.amount / life.max) * 100) : 0;
return { current: life.amount, max: life.max, pct };
}
return null;
}
function hmsToMs(hms) {
if (!hms) return 0;
const parts = hms.split(':').map(Number);
if (parts.length === 3) {
const [h, m, s] = parts;
return ((h * 60 + m) * 60 + s) * 1000;
}
return 0;
}
function getMedicalCooldownInfo() {
const data = getSidebarData();
if (!data) return null;
const med = data?.statusIcons?.icons?.medical_cooldown;
if (!med) return null;
const nowSec = Date.now() / 1000;
const remainingMs = Math.max(0, (med.timerExpiresAt - nowSec) * 1000);
const maxMs = hmsToMs(med.factionUpgrade);
return {
remainingMs,
maxMs,
freeMs: Math.max(0, maxMs - remainingMs)
};
}
// ===== SETTINGS =====
function getDestinationURL() {
const destination = safeGM.getValue('bloodBagDestination', 'factionArmoury');
return CONFIG.destinations[destination] || CONFIG.destinations.factionArmoury;
}
function getBagsToFill() {
const bags = safeGM.getValue('bloodBagCount', 3);
return Math.max(1, Math.min(3, bags)); // Clamp to 1-3
}
function getThresholds() {
const bags = getBagsToFill();
return {
lifePercent: bags * CONFIG.lifePerBag, // 30%, 60%, or 90%
cooldownBufferMs: (bags - 1) * CONFIG.cooldownPerBagMs // 0, 1hr, or 2hr buffer
};
}
function getOpenInNewTab() {
return safeGM.getValue('bloodBagNewTab', true); // Default: open in new tab
}
function openSettingsModal() {
// Remove existing modal if present
const existingModal = document.getElementById('bloodbag-settings-modal');
if (existingModal) existingModal.remove();
const currentDestination = safeGM.getValue('bloodBagDestination', 'factionArmoury');
const currentBags = getBagsToFill();
const currentNewTab = getOpenInNewTab();
const thresholds = getThresholds();
const settingsModal = document.createElement('div');
settingsModal.id = 'bloodbag-settings-modal';
settingsModal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 100000;
display: flex;
align-items: center;
justify-content: center;
`;
const getCooldownText = (bags) => {
if (bags === 1) return 'any room available';
return `${bags - 1}hr buffer available`;
};
settingsModal.innerHTML = `
<div style="
background: #2e2e2e;
border-radius: 10px;
width: 450px;
max-width: 90%;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
">
<div style="
background: linear-gradient(to bottom, #1a1a1a, #2a2a2a);
padding: 15px 20px;
border-radius: 10px 10px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
">
<h2 style="margin: 0; color: #fff; font-size: 18px;">Blood Bag Settings</h2>
<button id="close-bloodbag-settings" style="
background: none;
border: none;
color: #aaa;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
">x</button>
</div>
<div style="padding: 20px; color: #ccc;">
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 5px; color: #aaa; font-size: 14px;">
Bags to Fill:
</label>
<select id="bloodbag-count" style="
width: 100%;
padding: 10px;
background: #1a1a1a;
border: 1px solid #444;
border-radius: 5px;
color: #fff;
font-size: 14px;
box-sizing: border-box;
">
<option value="1" ${currentBags === 1 ? 'selected' : ''}>1 bag (requires >30% life)</option>
<option value="2" ${currentBags === 2 ? 'selected' : ''}>2 bags (requires >60% life)</option>
<option value="3" ${currentBags === 3 ? 'selected' : ''}>3 bags (requires >90% life)</option>
</select>
<p style="font-size: 12px; color: #888; margin-top: 5px;">
How many blood bags do you want to fill at once?<br>
Each bag uses 30% life and adds 1hr medical cooldown.
</p>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 5px; color: #aaa; font-size: 14px;">
Destination Page:
</label>
<select id="bloodbag-destination" style="
width: 100%;
padding: 10px;
background: #1a1a1a;
border: 1px solid #444;
border-radius: 5px;
color: #fff;
font-size: 14px;
box-sizing: border-box;
">
<option value="factionArmoury" ${currentDestination === 'factionArmoury' ? 'selected' : ''}>Faction Armoury (Medical)</option>
<option value="personalInventory" ${currentDestination === 'personalInventory' ? 'selected' : ''}>Personal Inventory (Medical)</option>
</select>
<p style="font-size: 12px; color: #888; margin-top: 5px;">
Where clicking the blood bag icon takes you.<br>
<em>Tip: Long-press the icon to open this settings panel.</em>
</p>
</div>
<div style="margin-bottom: 20px;">
<label style="display: flex; align-items: center; cursor: pointer; color: #ccc; font-size: 14px;">
<input type="checkbox" id="bloodbag-newtab" ${currentNewTab ? 'checked' : ''} style="
width: 18px;
height: 18px;
margin-right: 10px;
cursor: pointer;
">
Open in new tab
</label>
<p style="font-size: 12px; color: #888; margin-top: 5px; margin-left: 28px;">
When disabled, clicking the icon navigates in the same tab.
</p>
</div>
<div id="trigger-conditions" style="
background: rgba(255,255,255,0.05);
padding: 12px;
border-radius: 5px;
margin-bottom: 20px;
font-size: 12px;
color: #aaa;
">
<strong style="color: #ccc;">Current Trigger Conditions:</strong><br>
- Life above ${thresholds.lifePercent}%<br>
- Medical cooldown: ${getCooldownText(currentBags)}
</div>
<div style="text-align: right;">
<button id="cancel-bloodbag-settings" style="
background: linear-gradient(to bottom, #555, #777);
border: none;
color: white;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
font-size: 14px;
">Cancel</button>
<button id="save-bloodbag-settings" style="
background: linear-gradient(to bottom, #799427, #a3c248);
border: none;
color: white;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
">Save</button>
</div>
</div>
</div>
`;
// Update trigger conditions when bags selection changes
const updateTriggerDisplay = () => {
const bags = parseInt(document.getElementById('bloodbag-count').value, 10);
const lifeReq = bags * CONFIG.lifePerBag;
const conditionsDiv = document.getElementById('trigger-conditions');
if (conditionsDiv) {
conditionsDiv.innerHTML = `
<strong style="color: #ccc;">Current Trigger Conditions:</strong><br>
- Life above ${lifeReq}%<br>
- Medical cooldown: ${getCooldownText(bags)}
`;
}
};
document.body.appendChild(settingsModal);
// Event listeners
document.getElementById('bloodbag-count').addEventListener('change', updateTriggerDisplay);
document.getElementById('close-bloodbag-settings').addEventListener('click', () => settingsModal.remove());
document.getElementById('cancel-bloodbag-settings').addEventListener('click', () => settingsModal.remove());
document.getElementById('save-bloodbag-settings').addEventListener('click', () => {
const bags = parseInt(document.getElementById('bloodbag-count').value, 10);
const destination = document.getElementById('bloodbag-destination').value;
const newTab = document.getElementById('bloodbag-newtab').checked;
safeGM.setValue('bloodBagCount', bags);
safeGM.setValue('bloodBagDestination', destination);
safeGM.setValue('bloodBagNewTab', newTab);
settingsModal.remove();
updateIcon();
});
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) settingsModal.remove();
});
}
// Register settings menu command
safeGM.registerMenuCommand('Blood Bag Settings', openSettingsModal);
// ===== ICON MANAGEMENT =====
function updateIcon() {
const statusUl = document.querySelector(CONFIG.statusIconsSelector);
if (!statusUl) return;
const existing = document.getElementById(CONFIG.fullLifeIconId);
const life = getLifeFromStorage();
const med = getMedicalCooldownInfo();
const thresholds = getThresholds();
// Check conditions
// Life must be above threshold (30%, 60%, or 90% based on bags setting)
const lifeOk = life && life.pct > thresholds.lifePercent;
// Cooldown check: current < max - buffer
// For 1 bag: just need current < max (any room)
// For 2 bags: need current < max - 1hr
// For 3 bags: need current < max - 2hr
let cooldownOk = true;
if (med && med.maxMs > 0) {
// We have medical cooldown info
// Check if: remainingMs < maxMs - bufferMs
// Which means: we have enough room for all bags
cooldownOk = med.remainingMs < (med.maxMs - thresholds.cooldownBufferMs);
}
// If no medical cooldown icon exists, cooldownOk stays true (no cooldown = can use)
const shouldShow = lifeOk && cooldownOk;
if (shouldShow) {
// Build tooltip text
let label = `Life: ${formatNum(life.current)} / ${formatNum(life.max)} (${life.pct}%)`;
if (med && med.maxMs > 0) {
const remainHrs = Math.floor(med.remainingMs / 3600000);
const remainMin = Math.floor((med.remainingMs % 3600000) / 60000);
const maxHrs = Math.floor(med.maxMs / 3600000);
label += ` - CD: ${remainHrs}h${remainMin}m / ${maxHrs}h`;
} else {
label += ` - No medical cooldown`;
}
if (existing) {
updateIconTooltip(existing, label);
return;
}
const li = buildBloodBagIcon(label);
statusUl.appendChild(li);
} else if (existing) {
existing.remove();
}
}
function buildBloodBagIcon(tooltipText) {
const li = document.createElement('li');
li.id = CONFIG.fullLifeIconId;
li.style.background = 'none';
li.style.animation = 'tmPulse 900ms ease-out 1';
const a = document.createElement('a');
a.href = getDestinationURL();
if (getOpenInNewTab()) {
a.target = '_blank';
a.rel = 'noopener noreferrer';
}
a.setAttribute('aria-label', tooltipText);
a.tabIndex = 0;
a.setAttribute('data-is-tooltip-opened', 'false');
const img = document.createElement('img');
img.src = CONFIG.bloodBagPng;
img.alt = 'Blood Bag';
img.width = 17;
img.height = 17;
img.style.display = 'block';
a.appendChild(img);
li.appendChild(a);
// Long-press to open settings
setupLongPress(a, 500, openSettingsModal);
// Native-style tooltip
enableNativeLikeTooltip(a);
// Add pulse animation style if not present
if (!document.getElementById('tm-pulse-style')) {
const style = document.createElement('style');
style.id = 'tm-pulse-style';
style.textContent = `
@keyframes tmPulse {
0% { transform: scale(0.9); }
60% { transform: scale(1.1); }
100% { transform: scale(1.0); }
}
`;
document.head.appendChild(style);
}
return li;
}
function setupLongPress(element, duration, callback) {
let timer = null;
let didLongPress = false;
function startPress() {
didLongPress = false;
timer = setTimeout(() => {
didLongPress = true;
callback();
}, duration);
}
function cancelPress() {
clearTimeout(timer);
timer = null;
}
function endPress(e) {
clearTimeout(timer);
if (didLongPress) {
e.preventDefault();
e.stopPropagation();
didLongPress = false;
}
}
// Touch events (mobile/TornPDA)
element.addEventListener('touchstart', startPress, { passive: true });
element.addEventListener('touchend', endPress);
element.addEventListener('touchmove', cancelPress, { passive: true });
element.addEventListener('touchcancel', cancelPress);
// Mouse events (desktop)
element.addEventListener('mousedown', startPress);
element.addEventListener('mouseup', endPress);
element.addEventListener('mouseleave', cancelPress);
// Prevent click if long-press occurred
element.addEventListener('click', (e) => {
if (didLongPress) {
e.preventDefault();
e.stopPropagation();
didLongPress = false;
}
});
}
function updateIconTooltip(li, text) {
const a = li.querySelector('a');
if (!a) return;
a.href = getDestinationURL();
if (getOpenInNewTab()) {
a.target = '_blank';
a.rel = 'noopener noreferrer';
} else {
a.removeAttribute('target');
a.removeAttribute('rel');
}
a.setAttribute('aria-label', text);
if (typeof a.__tmUpdateTipText === 'function') a.__tmUpdateTipText(text);
}
// ===== TOOLTIP =====
function enableNativeLikeTooltip(anchor) {
let tipEl = null;
let hideTimer = null;
const CLS = {
tip: 'tooltip___aWICR tooltipCustomClass___gbI4V',
arrowWrap: 'arrow___yUDKb top___klE_Y',
arrowIcon: 'arrowIcon___KHyjw',
};
function buildTooltip(text) {
const el = document.createElement('div');
el.className = CLS.tip;
el.setAttribute('role', 'tooltip');
el.setAttribute('tabindex', '-1');
el.style.position = 'absolute';
el.style.transitionProperty = 'opacity';
el.style.transitionDuration = '200ms';
el.style.opacity = '0';
const [title, subtitle] = parseTwoLines(text);
const b = document.createElement('b');
b.textContent = title;
el.appendChild(b);
if (subtitle) {
const div = document.createElement('div');
div.textContent = subtitle;
el.appendChild(div);
}
const arrowWrap = document.createElement('div');
arrowWrap.className = CLS.arrowWrap;
const arrowIcon = document.createElement('div');
arrowIcon.className = CLS.arrowIcon;
arrowWrap.appendChild(arrowIcon);
el.appendChild(arrowWrap);
return el;
}
function setText(text) {
if (!tipEl) return;
const [title, subtitle] = parseTwoLines(text);
const b = tipEl.querySelector('b');
if (b) b.textContent = title;
let sub = b?.nextElementSibling;
if (subtitle) {
if (!sub || sub.tagName !== 'DIV') {
sub = document.createElement('div');
b.after(sub);
}
sub.textContent = subtitle;
} else if (sub) {
sub.remove();
}
}
function parseTwoLines(text) {
const parts = text.split(' - ');
if (parts.length >= 2) {
return [parts[0].trim(), parts[1].trim()];
}
return [text.trim(), ''];
}
function positionTooltip() {
if (!tipEl) return;
const r = anchor.getBoundingClientRect();
const ew = tipEl.offsetWidth;
const eh = tipEl.offsetHeight;
let left = Math.round(r.left + (r.width - ew) / 2);
let top = Math.round(r.top - eh - 14);
left = Math.max(8, Math.min(left, window.innerWidth - ew - 8));
if (top < 8) {
top = Math.round(r.bottom + 10);
}
tipEl.style.left = `${left}px`;
tipEl.style.top = `${top}px`;
const arrow = tipEl.querySelector(`.${CLS.arrowWrap.split(' ')[0]}`);
if (arrow) {
const iconCenter = r.left + r.width / 2;
const arrowLeft = Math.round(iconCenter - left - 6 + 14);
arrow.style.left = `${arrowLeft}px`;
}
}
function showTip() {
clearTimeout(hideTimer);
const text = anchor.getAttribute('aria-label');
if (!text) return;
if (!tipEl) {
tipEl = buildTooltip(text);
document.body.appendChild(tipEl);
anchor.__tmTipEl = tipEl;
} else {
setText(text);
}
anchor.setAttribute('data-is-tooltip-opened', 'true');
tipEl.style.opacity = '0';
tipEl.style.left = '-9999px';
tipEl.style.top = '-9999px';
requestAnimationFrame(() => {
positionTooltip();
requestAnimationFrame(() => {
if (tipEl) tipEl.style.opacity = '1';
});
});
}
function hideTip(immediate = false) {
if (!tipEl) return;
anchor.setAttribute('data-is-tooltip-opened', 'false');
if (immediate) {
tipEl.remove();
anchor.__tmTipEl = null;
tipEl = null;
return;
}
tipEl.style.opacity = '0';
hideTimer = setTimeout(() => {
tipEl?.remove();
anchor.__tmTipEl = null;
tipEl = null;
}, 210);
}
anchor.__tmUpdateTipText = (text) => setText(text);
anchor.addEventListener('mouseenter', showTip);
anchor.addEventListener('mouseleave', () => hideTip(false));
anchor.addEventListener('focus', showTip);
anchor.addEventListener('blur', () => hideTip(true));
window.addEventListener('scroll', () => hideTip(true), { passive: true });
}
// ===== CSS GUARDS =====
function ensureStyles() {
if (document.getElementById('tm-bloodbag-styles')) return;
const s = document.createElement('style');
s.id = 'tm-bloodbag-styles';
s.textContent = `
#${CONFIG.fullLifeIconId},
#${CONFIG.fullLifeIconId} a,
#${CONFIG.fullLifeIconId} img {
background: none !important;
background-image: none !important;
-webkit-mask: none !important;
mask: none !important;
box-shadow: none !important;
border: none !important;
}
#${CONFIG.fullLifeIconId}::before,
#${CONFIG.fullLifeIconId}::after,
#${CONFIG.fullLifeIconId} a::before,
#${CONFIG.fullLifeIconId} a::after {
content: none !important;
}
ul[class*="status-icons"] {
height: auto !important;
overflow: visible !important;
}
`;
document.head.appendChild(s);
}
// ===== UTILITIES =====
function formatNum(n) {
try {
return n.toLocaleString();
} catch {
return String(n);
}
}
// ===== INITIALIZATION =====
// Declare before anything can call scheduleCheck (fixes TDZ crash)
let checkScheduled = false;
function scheduleCheck() {
if (checkScheduled) return;
checkScheduled = true;
requestAnimationFrame(() => {
checkScheduled = false;
updateIcon();
});
}
// One-time CSS injection (run immediately like v3.4)
ensureStyles();
// Observe DOM changes (SPA) and poll (like v3.4)
const mo = new MutationObserver(() => {
scheduleCheck();
});
mo.observe(document.documentElement, { childList: true, subtree: true });
setInterval(scheduleCheck, CONFIG.pollMs);
// Initial check
scheduleCheck();
})();
(function () {
'use strict';
const now = new Date();
const year = now.getFullYear();
// Months are 0-based in JS (April = 3)
const aprilFirst = new Date(year, 3, 1, 0, 0, 0);
const aprilNinth = new Date(year, 3, 9, 23, 59, 59);
if (now >= aprilFirst && now <= aprilNinth) {
// ✅ EXECUTE YOUR CODE HERE
console.log("April 1–9 detected, running Easter scripts!");
// name Heasley's Egg Navigator
// author Heasleys4hemp [1468764]
'use strict';
var ButtonFloat = parseInt(localStorage.getItem('eeh-float')) || 0;
var ButtonFloatPos = parseInt(localStorage.getItem('eeh-float-pos')) || 0; //0 = bottom-left ; 1 = top-left; 2 = bottom-right; 3 = top-right
var linkIndex = localStorage.getItem('eeh-index') || 0;
var eeh_pressTimer, eeh_anim_pressTimer;
var eeh_reset_time = 9800;
var eeh_fade_in = 200;
var eeh_is_disabled = false;
var eeh_holding = false;
if (typeof GM == 'undefined') {
window.GM = {};
}
if (typeof GM.addStyle == "undefined") { //Add GM.addStyle for browsers that do not support it (e.g. TornPDA, Firefox+Greasemonkey)
GM.addStyle = function (aCss) {
'use strict';
let head = document.getElementsByTagName('head')[0];
if (head) {
let style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.textContent = aCss;
head.appendChild(style);
return style;
}
return null;
};
}
if (typeof GM.registerMenuCommand != "undefined") {
GM.registerMenuCommand('Toggle Floating Button', toggleFloatButton,
{
autoClose: false
}
);
GM.registerMenuCommand('Toggle Float Position', toggleFloatPosition,
{
autoClose: false
}
);
}
const obs_ops = {attributes: false, childList: true, characterData: false, subtree:true};
const easteregg_svg = `<svg xmlns="http://www.w3.org/2000/svg" fill="#AFC372" stroke="transparent" stroke-width="0" width="13" height="17" viewBox="0 0 14 18"><path d="M1.68,16a5.6,5.6,0,0,0,.43.41A5.72,5.72,0,0,0,3,17a4.73,4.73,0,0,0,.74.39,5.08,5.08,0,0,0,.8.3,5.35,5.35,0,0,0,.69.17,8.62,8.62,0,0,0,.87.11h.84a8.46,8.46,0,0,0,.88-.11l.69-.17a7.14,7.14,0,0,0,.81-.31q.38-.18.72-.39a6.57,6.57,0,0,0,.9-.67,5.14,5.14,0,0,0,.41-.4A6.3,6.3,0,0,0,13,11.67a8.86,8.86,0,0,0-.09-1.21c0-.31-.1-.64-.17-1s-.2-.85-.33-1.29-.3-.93-.48-1.39-.33-.81-.51-1.2c-.1-.2-.19-.39-.29-.58L11,4.72c-.18-.33-.4-.69-.64-1s-.4-.55-.62-.82A4.41,4.41,0,0,0,6.5,1,4.41,4.41,0,0,0,3.29,2.86a9.15,9.15,0,0,0-.61.82c-.24.34-.44.68-.62,1L1.87,5l-.33.66c-.16.36-.32.72-.46,1.09S.74,7.7.61,8.16a13.14,13.14,0,0,0-.34,1.3,10,10,0,0,0-.18,1A8.47,8.47,0,0,0,0,11.67a6.29,6.29,0,0,0,.89,3.25A6.63,6.63,0,0,0,1.68,16ZM1.27,14.8a.7.7,0,0,1,.4.38,1.4,1.4,0,0,1,.09.29A6.38,6.38,0,0,1,1.27,14.8Zm1,1.15c.17-.14.46,0,.66.32a1.41,1.41,0,0,1,.14.31A5.55,5.55,0,0,1,2.22,16Zm1.41,1a.44.44,0,0,1,.2-.39c.22-.11.52.1.67.46a1.28,1.28,0,0,1,.09.32A6.22,6.22,0,0,1,3.63,16.94Zm1.58.55a.47.47,0,0,1,.27-.4c.22-.06.46.16.57.51A7.4,7.4,0,0,1,5.21,17.49ZM7,17.6c.11-.35.35-.57.57-.51a.49.49,0,0,1,.27.39A5.66,5.66,0,0,1,7,17.6Zm1.46-.28A1.18,1.18,0,0,1,8.52,17c.16-.36.46-.57.67-.46a.43.43,0,0,1,.2.38A7.27,7.27,0,0,1,8.44,17.32ZM10,16.56a.84.84,0,0,1,.13-.29c.19-.31.47-.44.65-.33A7.57,7.57,0,0,1,10,16.56Zm1.26-1.14a.75.75,0,0,1,.08-.24.72.72,0,0,1,.36-.37A6.76,6.76,0,0,1,11.28,15.42Zm1.06-6q.11.51.18,1a.73.73,0,0,1-.37-.4A.44.44,0,0,1,12.34,9.45ZM10.49,4.67l.3.54c.11.2.21.41.31.63a.85.85,0,0,1-.65-.4C10.24,5.12,10.26,4.78,10.49,4.67Zm-.41,2.2c-.25.09-.58-.12-.74-.46s-.09-.68.16-.76a.69.69,0,0,1,.74.46C10.4,6.45,10.33,6.79,10.08,6.87ZM7.22,1.49a3.3,3.3,0,0,1,1,.51.5.5,0,0,1-.14.59.68.68,0,0,1-.86-.28A.61.61,0,0,1,7.22,1.49Zm-2.39.45a3.34,3.34,0,0,1,1-.46.6.6,0,0,1,0,.83A.66.66,0,0,1,5,2.59.53.53,0,0,1,4.83,1.94ZM3.58,3.12a4.75,4.75,0,0,0,2.91.93A4.7,4.7,0,0,0,9.42,3.1c.24.3.47.62.68.92A4.5,4.5,0,0,1,6.49,5.39,4.46,4.46,0,0,1,2.9,4,9.35,9.35,0,0,1,3.58,3.12ZM7.93,7.54c-.29,0-.57-.25-.64-.64a.59.59,0,0,1,.38-.76c.29,0,.57.25.64.63S8.21,7.5,7.93,7.54Zm-2-.64c-.07.39-.36.67-.65.64s-.45-.38-.38-.77.36-.67.64-.63A.6.6,0,0,1,5.9,6.9Zm-3-.79a.69.69,0,0,1,.74-.46c.25.08.32.42.16.76s-.49.55-.74.46S2.78,6.45,2.94,6.11Zm-.73-.9c.08-.16.18-.33.28-.51.17.14.17.45,0,.74a.89.89,0,0,1-.57.39C2,5.62,2.1,5.41,2.21,5.21ZM1.38,7.08A7.89,7.89,0,0,0,6.52,8.7a7.91,7.91,0,0,0,5.11-1.6c.19.5.36,1,.5,1.52-1,1.2-3.11,2-5.61,2S1.83,9.8.88,8.58C1,8.09,1.19,7.58,1.38,7.08ZM11.55,11.5A.59.59,0,0,1,11,11a.46.46,0,0,1,.4-.57.59.59,0,0,1,.56.52A.47.47,0,0,1,11.55,11.5Zm-1.68.85a.6.6,0,0,1-.59-.5.45.45,0,0,1,.36-.59.62.62,0,0,1,.59.51A.45.45,0,0,1,9.87,12.35Zm-1.77,0a.56.56,0,0,1-.53.57.57.57,0,0,1-.51-.6.52.52,0,1,1,1,0Zm-2,0a.56.56,0,0,1-.5.6.59.59,0,0,1,0-1.17A.55.55,0,0,1,6.06,12.27Zm-2.21-.42a.61.61,0,0,1-.59.5.45.45,0,0,1-.36-.58.6.6,0,0,1,.59-.51A.46.46,0,0,1,3.85,11.85ZM2.13,11a.58.58,0,0,1-.56.52.46.46,0,0,1-.39-.57.59.59,0,0,1,.56-.52A.46.46,0,0,1,2.13,11ZM.65,9.48A.46.46,0,0,1,.78,10a.69.69,0,0,1-.29.36C.53,10.11.59,9.8.65,9.48ZM.38,11.67a4.84,4.84,0,0,1,0-.53c.74,1.68,3.19,3,6.1,3s5.33-1.32,6.09-3c0,.17,0,.35,0,.51a5.86,5.86,0,0,1-.39,2.11C11.21,15.09,9,16,6.51,16S1.75,15.06.75,13.73A5.84,5.84,0,0,1,.38,11.67Z"></path></svg>`;
const EVERY_LINK = ["", "index.php","forums.php#/p=threads&f=67&t=16326854&b=0&a=0","city.php","jobs.php","gym.php","properties.php","page.php?sid=education",
"crimes.php","loader.php?sid=missions","newspaper.php","jailview.php","hospitalview.php",
"casino.php","page.php?sid=hof","factions.php","competition.php","page.php?sid=list&type=friends",
"page.php?sid=list&type=enemies", "page.php?sid=list&type=targets","messages.php","page.php?sid=events","awards.php","points.php","rules.php",
"staff.php","credits.php","citystats.php","committee.php","bank.php","donator.php","item.php",
"page.php?sid=stocks","fans.php","museum.php","loader.php?sid=racing","church.php",
"dump.php","loan.php","page.php?sid=travel","amarket.php","bigalgunshop.php","shops.php?step=bitsnbobs",
"shops.php?step=cyberforce","shops.php?step=docks","shops.php?step=jewelry",
"shops.php?step=nikeh","shops.php?step=pawnshop","shops.php?step=pharmacy","pmarket.php",
"shops.php?step=postoffice","shops.php?step=super","shops.php?step=candy",
"shops.php?step=clothes","shops.php?step=recyclingcenter","shops.php?step=printstore","page.php?sid=ItemMarket","estateagents.php","bazaar.php?userId=1",
"calendar.php","token_shop.php","freebies.php","bringafriend.php","comics.php","archives.php","joblist.php",
"newspaper_class.php","personals.php","newspaper.php#/archive",
"profiles.php?XID=1",
"bounties.php","usersonline.php","joblist.php?step=search#!p=corpinfo&ID=79286","page.php?sid=log","page.php?sid=ammo","playerreport.php",
"loader.php?sid=itemsMods","displaycase.php","trade.php",
"crimes.php?step=criminalrecords","page.php?sid=factionWarfare#/dirty-bombs",
"index.php?page=fortune","page.php?sid=bunker","church.php?step=proposals",
"messageinc.php","preferences.php","messageinc2.php#!p=main","page.php?sid=gallery&XID=1","personalstats.php?ID=1",
"properties.php?step=rentalmarket","properties.php?step=sellingmarket","forums.php",
"page.php?sid=slots",
"page.php?sid=roulette","page.php?sid=highlow","page.php?sid=keno","page.php?sid=craps",
"page.php?sid=bookie","page.php?sid=lottery","page.php?sid=blackjack",
"page.php?sid=holdem","page.php?sid=russianRoulette","page.php?sid=spinTheWheel",
"page.php?sid=spinTheWheelLastSpins","page.php?sid=slotsStats",
"page.php?sid=slotsLastRolls","page.php?sid=rouletteStatistics","page.php?sid=rouletteLastSpins",
"page.php?sid=highlowStats","page.php?sid=highlowLastGames",
"page.php?sid=kenoStatistics","page.php?sid=kenoLastGames","page.php?sid=crapsStats",
"page.php?sid=crapsLastRolls","page.php?sid=bookie#/stats/","page.php?sid=lotteryTicketsBought",
"page.php?sid=lotteryPreviousWinners","page.php?sid=blackjackStatistics",
"page.php?sid=blackjackLastGames","page.php?sid=holdemStats",
"loader.php?sid=viewRussianRouletteLastGames","loader.php?sid=viewRussianRouletteStats",
"messageinc2.php#!p=viewall","bazaar.php#/add",
"bazaar.php#/personalize","factions.php?step=your#/tab=crimes",
"factions.php?step=your#/tab=rank","page.php?sid=events#onlySaved=true",
"factions.php?step=your#/tab=controls","factions.php?step=your#/tab=info","messages.php#/p=ignorelist",
"messages.php#/p=outbox","factions.php?step=your#/tab=upgrades",
"messages.php#/p=saved","messages.php#/p=compose","displaycase.php#add","displaycase.php#manage",
"factions.php?step=your#/tab=armoury","bazaar.php#/manage","companies.php",
"itemuseparcel.php","index.php?page=rehab","index.php?page=people","christmas_town.php",
"christmas_town.php#/mymaps","christmas_town.php#/parametereditor","christmas_town.php#/npceditor",
"page.php?sid=UserList","index.php?page=hunting","old_forums.php","donatordone.php","revive.php","pc.php",
"loader.php?sid=attackLog","loader.php?sid=attack&user2ID=1","loader.php?sid=crimes","loader.php?sid=crimes#/searchforcash",
"loader.php?sid=crimes#/bootlegging","loader.php?sid=crimes#/graffiti","loader.php?sid=crimes#/shoplifting",
"loader.php?sid=crimes#/pickpocketing","loader.php?sid=crimes#/cardskimming","loader.php?sid=crimes#/burglary","loader.php?sid=crimes#/hustling",
"loader.php?sid=crimes#/disposal","loader.php?sid=crimes#/cracking","loader.php?sid=crimes#/forgery","loader.php?sid=crimes#/scamming",
"/war.php?step=rankreport&rankID=69","/war.php?step=warreport&warID=420","/war.php?step=raidreport&raidID=69",
"/war.php?step=chainreport&chainID=69420", "page.php?sid=keepsakes",
"page.php?sid=crimes2","authenticate.php"];
const eeeh_options_observer = new MutationObserver(function(mutations) {
const url = window.location.href;
if (url.includes("forums.php")) {
if (url.includes("f=67&t=16326854") && $('li.parent-post[data-id="23383506"]').length) {
if (!document.getElementsByClassName("eeh-options").length) {
insertOptions();
}
eeeh_options_observer.disconnect();
}
} else {
eeeh_options_observer.disconnect();
}
});
const eeeh_observer = new MutationObserver(function(mutations) {
if (document.getElementById("eggTraverse")) {
eeeh_observer.disconnect();
return;
}
if (ButtonFloat) {
//insert floating button
if (document.getElementsByTagName('body')[0]) {
insertFloat();
eeeh_observer.disconnect();
return;
}
} else {
// Insert into sidebar
if (document.querySelector('#sidebar > div:first-of-type')) {
insertNormal(); // Insert normal sidebar version
eeeh_observer.disconnect();
return;
}
}
});
window.addEventListener(
"hashchange",
() => {
hashChanged();
},
false,
);
eeeh_observer.observe(document, obs_ops);
eeeh_options_observer.observe(document, obs_ops);
function hashChanged() {
const url = window.location.href;
if (url.includes("forums.php")) {
eeeh_options_observer.observe(document, obs_ops);
}
if (eeh_is_disabled) {
setTimeout(() => {
eeh_is_disabled = false;
}, "1000");
}
}
function getEggLabel(eggButtonType) {
let eggLabel = `Egg Navigator (${linkIndex})`;
if (eggButtonType == "float") {
eggLabel = `#${linkIndex}`;
}
return eggLabel;
}
function setEggTraverseClickEvent(eggButtonType) {
var eggTraverse = $('#eggTraverse');
var egg_icon = eggTraverse.find('.eeh-icon');
eggTraverse.on('mousedown touchstart', function(e) {
eeh_anim_pressTimer = window.setTimeout(function() {
eeh_holding = true;
egg_icon.fadeOut(eeh_reset_time);
eeh_pressTimer = window.setTimeout(function() {
if (eeh_holding) {
linkIndex = 0;
egg_icon.fadeIn(eeh_fade_in);
localStorage.setItem("eeh-index", linkIndex);
eggTraverse.attr('href', EVERY_LINK[0]);
eggTraverse.find('.eeh-name').text(getEggLabel(eggButtonType));
}
}, eeh_reset_time);
}, eeh_fade_in);
}).on('mouseup touchend mouseleave', function(e){
clearTimeout(eeh_anim_pressTimer);
if (eeh_holding) {
clearTimeout(eeh_pressTimer);
eeh_holding = false;
egg_icon.stop(true, true).fadeIn(eeh_fade_in);
}
}).contextmenu(function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
}).on('click', function(e) {
if (eeh_holding) {
eeh_holding = false;
egg_icon.stop(true, true).fadeIn(eeh_fade_in);
}
if (window.event.ctrlKey) {
//ctrl was held down during the click
incrementEggTraverse(eggButtonType);
} else {
//normal click
if (!eeh_is_disabled) {
eeh_is_disabled = true;
incrementEggTraverse(eggButtonType);
} else {
e.preventDefault();
}
}
});
}
function incrementEggTraverse(eggButtonType) {
var eggTraverse = $('#eggTraverse');
linkIndex++;
if (linkIndex >= EVERY_LINK.length) linkIndex = 0;
localStorage.setItem("eeh-index", linkIndex);
eggTraverse.attr('href', EVERY_LINK[linkIndex]);
eggTraverse.find('.eeh-name').text(getEggLabel(eggButtonType));
}
function insertNormal() {
if (!document.getElementById("eggTraverse")) {
let href = EVERY_LINK[linkIndex];
let easterspans = `<div class="eeh-link">
<a href="${href}" id="eggTraverse">
<span class="eeh-icon">${easteregg_svg}</span>
<span class="eeh-name">Egg Navigator (${linkIndex})</span>
</a>
</div>`;
const sidebar = document.getElementById('sidebar');
if (sidebar.firstChild) {
// Insert the easterspans HTML string after the first child element of sidebar
$('#sidebar > *').first().after(easterspans);
setEggTraverseClickEvent("sidebar");
}
insertStyle();
}
}
function insertFloat() {
if (!document.getElementById("eggTraverse")) {
let href = EVERY_LINK[linkIndex];
const eeh_float = `<a href="${href}" id="eggTraverse" class="eeh-float">
<span class="eeh-icon">${easteregg_svg}</span>
<span class="eeh-name"> #${linkIndex}</span>
</a>`;
$('body').append(eeh_float);
setFloatPosition();
setEggTraverseClickEvent("float");
insertStyle();
}
}
function insertOptions() {
if (!document.getElementsByClassName("eeh-options").length) {
const post = $('li.parent-post[data-id="23383506"]').find('div.post-container div.post');
let enabled_float = ButtonFloat ? "enabled" : "disabled";
let enabledClass_float = ButtonFloat ? "eeh-green" : "eeh-red";
let enabled_float_pos;
switch(ButtonFloatPos) {
case 0:
enabled_float_pos = "bottom left";
break;
case 1:
enabled_float_pos = "top left";
break;
case 2:
enabled_float_pos = "bottom right";
break;
case 3:
enabled_float_pos = "top right";
break;
}
post.before(`
<div class="eeh-options"><button id="eeh-float-toggle">Toggle floating button</button>
<p>Floating button: <span id="eeh-float-toggle-label" class="${enabledClass_float}">${enabled_float}</span></p>
</div>
<div class="eeh-options"><button id="eeh-float-pos-toggle">Toggle float position</button>
<p>Float position: <span id="eeh-float-pos-toggle-label">${enabled_float_pos}</span></p>
</div>
`);
$('#eeh-float-toggle').click(function() {
let label = $('#eeh-float-toggle-label');
if (toggleFloatButton()) {
label.text("enabled");
} else {
label.text("disabled");
}
label.toggleClass('eeh-green eeh-red');
});
$('#eeh-float-pos-toggle').click(function() {
let label = $('#eeh-float-pos-toggle-label');
switch(toggleFloatPosition()) {
case 0:
label.text("bottom left");
break;
case 1:
label.text("top left");
break;
case 2:
label.text("bottom right");
break;
case 3:
label.text("top right");
break;
default:
label.text("Float button is not enabled");
}
});
}
}
function insertStyle() {
GM.addStyle(`
.eeh-link {
background-color: var(--default-bg-panel-color);
cursor: pointer;
overflow: hidden;
vertical-align: top;
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
margin-top: 2px;
height: 23px;
margin-bottom: 2px;
}
.eeh-link:hover {
background-color: var(--default-bg-panel-active-color);
}
.eeh-link a {
display: flex;
-ms-align-items: center;
align-items: center;
color: var(--default-color);
text-decoration: none;
height: 100%;
}
.eeh-link a .eeh-icon {
float: left;
width: 34px;
height: 23px;
display: flex;
-ms-align-items: center;
align-items: center;
justify-content: center;
margin-left: 0;
}
.eeh-link a .eeh-icon {
stroke: transparent;
stroke-width: 0;
}
.eeh-link a .eeh-name {
line-height: 22px;
padding-top: 1px;
overflow: hidden;
max-width: 134px;
}
.eeh-float.eeh-float-right .eeh-icon {
order: 1;
}
.eeh-float.eeh-float-left .eeh-icon {
order: 2;
}
.eeh-float.eeh-float-right .eeh-name {
margin-left: 5px;
order: 2;
}
.eeh-float.eeh-float-left .eeh-name {
margin-right: 5px;
order: 1;
}
.eeh-float .eeh-icon svg {
width: 20px !important;
height: 26px !important;
}
#eggTraverse.eeh-float {
z-index: 999999;
height: 40px;
width: 80px;
cursor: pointer;
padding: 10px 15px 10px 15px;
box-sizing: border-box;
border: 1px solid var(--default-panel-divider-outer-side-color);
position: fixed;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
display: flex;
align-items: center;
text-shadow: var(--default-tabs-text-shadow);
background: var(--info-msg-bg-gradient);
box-shadow: var(--default-tabs-box-shadow);
border-radius: 5px;
overflow: hidden;
font-size: 15px;
font-weight: 700;
line-height: 18px;
font-family: arial;
color: var(--default-color);
text-decoration: none;
}
#eggTraverse.eeh-float.eeh-float-top {
top: 80px;
}
#eggTraverse.eeh-float.eeh-float-bottom {
bottom: 80px;
}
#eggTraverse.eeh-float.eeh-float-left {
left: -10px;
padding-right: 5px;
justify-content: right;
}
#eggTraverse.eeh-float.eeh-float-right {
right: -10px;
padding-left: 5px;
justify-content: left;
}
[class*='topSection_'] .eeh-icon-svg-wrap {
position: absolute;
-ms-transform: translate(-120%, 10%);
transform: translate(-120%, 10%);
}
.content-wrapper > #easterrandom .eeh-icon-svg-wrap {
position: absolute;
-ms-transform: translate(-140%, 10%);
transform: translate(-140%, 10%);
}
.eeh-options {
margin: 20px;
margin-left: 0px;
}
.eeh-options p {
margin-top: 5px;
margin-left: 2px;
font-size: 15px;
font-weight: 700;
line-height: 18px;
font-family: arial;
}
.eeh-options button {
background: transparent linear-gradient(180deg ,#CCCCCC 0%,#999999 60%,#666666 100%) 0 0 no-repeat;
border-radius: 5px;
font-family: Arial,sans-serif;
font-size: 14px;
font-weight: 700;
text-align: center;
letter-spacing: 0;
color: #333;
text-shadow: 0 1px 0 #ffffff66;
text-decoration: none;
text-transform: uppercase;
margin: 0;
border: none;
outline: none;
overflow: visible;
box-sizing: border-box;
line-height: 16px;
padding: 4px 8px;
height: auto;
white-space: nowrap;
cursor: pointer;
margin-right: 5px;
}
.eeh-green {
color: var(--user-status-green-color);
}
.eeh-red {
color: var(--user-status-red-color);
}
@media screen and (max-width: 1000px) {
html:not(.html-manual-desktop) [class*='topSection_'] #easterrandom span.eeh-text, .content-wrapper > #easterrandom span.eeh-text {
display: none;
}
[class*='topSection_'] .eeh-icon-svg-wrap {
-ms-transform: translate(-140%, -110%);
transform: translate(-140%, -110%);
}
html:not(.html-manual-desktop) #eggTraverse.eeh-float.eeh-float-top {
top: 170px !important;
}
}
/* SVG Colors */
.eeh-link svg, .eeh-icon-svg svg {
filter: drop-shadow(0px 0.7px 0.1px #fff);
width: 13px !important;
height: 17px !important;
}
.eeh-icon-svg svg path {
fill: #AFC372 !important;
}
body.dark-mode .eeh-icon svg, body.dark-mode .eeh-icon-svg svg {
filter: drop-shadow(0px 0px 1.3px #000);
}
/* Torn Edits */
.members-cont>.member-item>a[href="profiles.php?XID=1468764"]>.member>.member-header {
color: #E0CE00 !important;
}
.members-cont>.member-item>a[href="profiles.php?XID=1468764"]>.member>.member-cont>span::after {
content: "👑 " url("https://profileimages.torn.com/ad324318-744c-c686-1468764.gif?v=1940629196397");
}
`);
}
function killButton() {
let eeh_button = document.getElementById("eggTraverse");
if (eeh_button) {
let parent = eeh_button.closest(`.eeh-link`);
if (parent) {
parent.remove();
} else {
eeh_button.remove();
}
}
}
function toggleFloatButton() {
killButton();
if (ButtonFloat) {
ButtonFloat = 0;
insertNormal();
} else {
ButtonFloat = 1;
insertFloat();
}
localStorage.setItem("eeh-float", ButtonFloat);
return ButtonFloat;
}
function toggleFloatPosition() {
let float_button = document.querySelector("#eggTraverse.eeh-float");
if (!float_button) return;
ButtonFloatPos++;
if (ButtonFloatPos >= 4) ButtonFloatPos = 0; //cycle back to 0=bottom-left
setFloatPosition();
return ButtonFloatPos;
}
function setFloatPosition() {
let float_button = document.querySelector("#eggTraverse.eeh-float");
if (!float_button) return;
float_button.classList.remove("eeh-float-bottom", "eeh-float-top", "eeh-float-left", "eeh-float-right");
switch(ButtonFloatPos) {
case 0:
float_button.classList.add("eeh-float-bottom", "eeh-float-left");
break;
case 1:
float_button.classList.add("eeh-float-top", "eeh-float-left");
break;
case 2:
float_button.classList.add("eeh-float-bottom", "eeh-float-right");
break;
case 3:
float_button.classList.add("eeh-float-top", "eeh-float-right");
break;
default:
float_button.classList.add("eeh-float-bottom", "eeh-float-left");
}
localStorage.setItem("eeh-float-pos", ButtonFloatPos);
}
} else {
// optional: do nothing or log
console.log("Outside April 1–9, easter scripts not running.");
}
})();
// name Fast Packs
// author Hemicopter [2780600]
(function() {
'use strict';
//Removes all images, leaving only the result text and buttons.
//This should prevent the "Use Another" button moving most of the time
let removeVisuals = true;
// cc Manuito
let GM_addStyle = function(s) {
let style = document.createElement("style");
style.type = "text/css";
style.innerHTML = s;
document.head.appendChild(style);
};
GM_addStyle(`
.d .pack-open-content.disabled-link .pack-open-msg a.open-another-cache {
pointer-events: auto !important;
cursor: default !important;
color: var(--default-blue-color) !important;
}
.d .pack-open-msg {
animation-duration: 0s !important;
animation-delay: 0s !important;
}
.d .animated-fadeIn {
opacity: 1 !important;
}
.d .animated {
animation-duration: 0s !important;
}
.d .pack-open-result .cache-item:nth-child(2) {
animation-delay: 0s !important;
}
.d .pack-open-result .item-amount {
animation: none !important;
animation-delay: 0s !important;
opacity: 1 !important;
}
.r .pack-open-result {
animation-name: none !important;
animation-duration: 0s !important;
}
.d .pack-open-result-divider.visible {
transition: none !important;
}
`);
if(removeVisuals) {
GM_addStyle(`
.pack-open-result {
visibility: hidden !important;
height: 0px !important;
}
.d .cache_wrapper,
.d .pack-open-result-divider {
display: none !important
}
`);
}
})();
const sheepattack = localStorage.getItem("sheepattackAPI");
if (isToggleEnabled()) {
if (sheepattack === null) {createButtonAtPositionsheepattack("API", 0, 0);}
else {
(function () {
'use strict';
const STORAGE_KEY = "SheepAttackSetting";
const OPTIONS = ["Primary", "Secondary", "Melee", "Temp"];
// Initialize storage if missing
if (!localStorage.getItem(STORAGE_KEY)) {
localStorage.setItem(STORAGE_KEY, OPTIONS[0]);
}
// Get current value
let currentValue = localStorage.getItem(STORAGE_KEY);
// Create button
const btn = document.createElement("button");
btn.textContent = currentValue;
btn.style.position = "absolute";
btn.style.top = "150px";
btn.style.right = "40px";
btn.style.padding = "8px 14px";
btn.style.borderRadius = "8px";
btn.style.border = "1px solid #888";
btn.style.cursor = "pointer";
btn.style.zIndex = "9999";
btn.style.background = "#222";
btn.style.backgroundColor = sheepColour;
btn.addEventListener("click", function () {
let currentIndex = OPTIONS.indexOf(localStorage.getItem(STORAGE_KEY));
let nextIndex = (currentIndex + 1) % OPTIONS.length;
localStorage.setItem(STORAGE_KEY, OPTIONS[nextIndex]);
location.reload(); // Reload page
});
document.body.appendChild(btn);
})();
createButtonAtPositionsheepattackoff("API", 0, 0);}}
function createButtonAtPositionsheepattackoff(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = sheepwide2 + "px";
customButton.style.height = sheeptall2 + "px";
customButton.innerHTML = 'Disable FastAttack';
customButton.style.position = 'absolute';
customButton.style.top = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall2 * 0.4) + "px";
customButton.addEventListener('click', function() {
localStorage.removeItem("sheepattackAPI");
customButton.style.display = 'none';
window.location.reload();;
});
requestIdleCallback(() => {document.body.appendChild(customButton); });
}
function createButtonAtPositionsheepattack(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = sheepwide2 + "px";
customButton.style.height = sheeptall2 + "px";
customButton.innerHTML = 'Enable FastAttack';
customButton.style.position = 'absolute';
customButton.style.top = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '999999';
customButton.style.fontSize = (sheeptall2 * 0.4) + "px";
customButton.addEventListener('click', function() {
const sheepattackAPI = localStorage.getItem("sheepattackAPI");
if (sheepattackAPI === null) {
const sheepAttack = ("yes");
localStorage.setItem("sheepattackAPI", sheepAttack);}
window.location.reload();;
});
requestIdleCallback(() => {document.body.appendChild(customButton); });
}
// Attack Better
// Auther smokey_ [2492729]
(function () {
'use strict';
const attackSetting = localStorage.getItem("SheepAttackSetting") || "Primary";
const BUTTON_LOCATION = attackSetting; // Change this value to 'Temp' to move the button to the Temp (HEG, Tear, Smoke) or Primary to move it ontop of the Primary Weapon element or Secondary or Melee
function moveStartFightButton() {
let startFightButton, weaponImage, weaponWrapper;
if (BUTTON_LOCATION === 'Primary') {
startFightButton = document.querySelector('.torn-btn.btn___RxE8_.silver'); // start fight button
weaponImage = document.querySelector('.weaponImage___tUzwP img'); // equipped weapon image
weaponWrapper = document.querySelector('.weaponWrapper___h3buK'); // common parent element
} else if (BUTTON_LOCATION === 'Secondary') {
startFightButton = document.querySelector('.torn-btn.btn___RxE8_.silver');
weaponImage = document.querySelector('#weapon_second .weaponImage___tUzwP img');
weaponWrapper = document.querySelector('#weapon_second');
} else if (BUTTON_LOCATION === 'Melee') {
startFightButton = document.querySelector('.torn-btn.btn___RxE8_.silver');
weaponImage = document.querySelector('#weapon_melee .weaponImage___tUzwP img');
weaponWrapper = document.querySelector('#weapon_melee');
} else if (BUTTON_LOCATION === 'Temp') {
startFightButton = document.querySelector('.torn-btn.btn___RxE8_.silver');
weaponImage = document.querySelector('#weapon_temp .weaponImage___tUzwP img');
weaponWrapper = document.querySelector('#weapon_temp');
}
if (startFightButton && weaponImage && weaponWrapper) {
const buttonWrapper = document.createElement('div'); // create new div element
buttonWrapper.classList.add('button-wrapper');
buttonWrapper.appendChild(startFightButton); // append start fight button to new div element
weaponWrapper.insertBefore(buttonWrapper, weaponImage.nextSibling); // insert new div element after equipped weapon image
// Position the button wrapper over the weapon image
buttonWrapper.style.position = 'absolute';
buttonWrapper.style.top = weaponImage.offsetTop + 'px';
buttonWrapper.style.left = '+15px'; // set left position to move it to the left
startFightButton.addEventListener('click', function() {
buttonWrapper.remove();
});
}
}
let loopCount = 0;
const buttonIntervalId = setInterval(function () {
loopCount++;
if (loopCount > 5) { // stop the loop after 5s (20 loops * 250ms per loop = 5s)
clearInterval(buttonIntervalId);
return;
}
const sheepattackAPI = localStorage.getItem("sheepattackAPI");
if (sheepattackAPI === 'yes') {
moveStartFightButton();
if (document.querySelector('.button-wrapper')) { // check if the button has been moved
clearInterval(buttonIntervalId);
}}
}, 250);
// Wait for page to load before executing this part of the script
window.addEventListener('load', function () {
//
// Element Stripping
//
// get the custom-bg-desktop sidebar-off element
const sidebarElement = document.querySelector('.custom-bg-desktop.sidebar-off');
// if the element exists, remove it from the DOM to prevent it from being downloaded or loaded
if (sidebarElement) {
sidebarElement.remove();
}
})
// Defender Model
var startTimeDefender = Date.now();
var intervalIdDefender = setInterval(function() {
if (Date.now() - startTimeDefender > 5000) {
clearInterval(intervalIdDefender);
return;
}
var defenderModel = document.querySelectorAll("#defender > div.playerArea___oG4xu > div.playerWindow___FvmHZ > div > div.modelLayers___FdSU_.center___An_7Z > div.modelWrap___j3kfA *");
for (const element of defenderModel) {
element.remove();
}
}, 100);
// Attacker Model
var startTimeAttacker = Date.now();
var intervalIdAttacker = setInterval(function() {
if (Date.now() - startTimeAttacker > 5000) {
clearInterval(intervalIdAttacker);
return;
}
var attackerModel = document.querySelectorAll("#attacker > div.playerArea___oG4xu > div.playerWindow___FvmHZ > div.allLayers___cXY5i > div.modelLayers___FdSU_.center___An_7Z > div.modelWrap___j3kfA *");
for (const element of attackerModel) {
element.remove();
}
}, 100);
})();
//
//
// Dont forget your xanax
// author Shade
if (localStorage.getItem("warmode") === "true") {
// do warmode stuff
if (window.location.href !== "https://www.torn.com/loader.php?sid=attack*") {
(function() {
'use strict';
$(document).ready(function() {
setTimeout(function() {
var drugcd = document.querySelector("[aria-label^='Drug Cooldown:']");
if (drugcd == null) {
const BANNER_ID = "xanaxBanner";
const banner = document.createElement("div");
banner.id = BANNER_ID;
banner.textContent = "Take your fwickin Xanax";
// Styling
banner.style.position = "fixed";
banner.style.top = "20px";
banner.style.left = "50%";
banner.style.transform = "translateX(-50%)";
banner.style.background = "#8b0000"; // original red
banner.style.color = "white";
banner.style.padding = "10px 20px";
banner.style.fontSize = "16px";
banner.style.fontWeight = "bold";
banner.style.borderRadius = "8px";
banner.style.boxShadow = "0 4px 12px rgba(0,0,0,0.4)";
banner.style.zIndex = "999999";
banner.style.cursor = "pointer";
banner.style.transition = "opacity 0.5s ease";
// Append to body
document.body.appendChild(banner);
// Optional: fade out after 3 seconds
setTimeout(() => {
banner.style.opacity = "0";
setTimeout(() => banner.remove(), 500);
}, 30000);
}
}, 3000);
});
})();
}
}
// sheepchain
const sheepchain = localStorage.getItem("sheepchain");
if (sheepchain === null) {createButtonAtPositionsheepchain("API", 120, 10);}
else {createButtonAtPositionsheepchainoff("API", 120, 10);
createButtonAtPositionsheepchainactive("API", 10, 10);}
function createButtonAtPositionsheepchainoff(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = sheepwide + "0px";
customButton.style.height = sheeptall + "px";
customButton.innerHTML = 'Disable SheepChain';
customButton.style.position = 'absolute';
customButton.style.top = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
customButton.style.display = 'none';
customButton.addEventListener('click', function() {
localStorage.removeItem("sheepchain");
customButton.style.display = 'none';
window.location.reload();;
});
document.body.appendChild(customButton);
}
function createButtonAtPositionsheepchain(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.innerHTML = 'Enable SheepChain';
customButton.style.position = 'absolute';
customButton.style.top = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
customButton.style.display = 'none';
customButton.addEventListener('click', function() {
const sheepchain = localStorage.getItem("sheepchain");
if (sheepchain === null) {
const sheepchain = ("yes");
localStorage.setItem("sheepchain", sheepchain);}
window.location.reload();;
});
document.body.appendChild(customButton);
}
function createButtonAtPositionsheepchainactive(text, topPx, leftPx) {
var customButton = document.createElement("button");
customButton.style.width = sheepwide + "px";
customButton.style.height = sheeptall + "px";
customButton.innerHTML = 'SheepChain Active';
customButton.style.position = 'absolute';
customButton.style.top = topPx + 'px';
customButton.style.right = leftPx + 'px';
customButton.style.backgroundColor = sheepColour;
customButton.style.color = 'black';
customButton.style.border = 'none';
customButton.style.cursor = 'pointer';
customButton.style.border = "1px solid purple";
customButton.style.zIndex = '99999';
customButton.style.display = 'none';
customButton.addEventListener('load', function() {
const sheepchain = localStorage.getItem("sheepchain");
if (sheepchain === 'yes') {
customButton.style.display = 'none';
}
});
document.body.appendChild(customButton);
}
//Torn Territory War Time Left
//Ramin Quluzade, Silmaril [2665762]
(function() {
'use strict';
const targetElementSelector = '.f-war-list.war-new';
const observerOptions = { childList: true, subtree: true };
const observerCallback = async function(mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
const targetElement = document.querySelector(targetElementSelector);
if (targetElement) {
let territoryWars = mutation.target.querySelectorAll(".f-war-list.war-new div[class^='status-wrap territoryBox']");
if (territoryWars.length > 0) {
territoryWars.forEach(war => {
war.querySelector('.info .faction-progress-wrap').style.paddingTop = '0px';
let timeLeftElement = document.createElement('div');
timeLeftElement.classList.add('time-left', 'timer');
let timeLeftBestElement = document.createElement('div');
timeLeftBestElement.classList.add('time-left-best', 'timer');
war.querySelector('.info .faction-progress-wrap').append(timeLeftElement, timeLeftBestElement);
});
territoryWars.forEach(war => {
let enemyCountDiv = war.querySelector('.info .member-count.enemy-count .count');
let allyCountDiv = war.querySelector('.info .member-count.your-count .count');
renderTimeLeft(war);
// Set up a MutationObserver on the added child element
const childObserver = new MutationObserver(function(childMutations) {
childMutations.forEach(function(childMutation) {
if (childMutation.type === 'characterData') {
let territoryWar = childMutation.target.parentNode.parentNode.parentNode.parentNode;
renderTimeLeft(territoryWar);
}
});
});
setInterval(renderTimeLeft, 1000 + Math.floor(Math.random() * 10) + 1, war);
childObserver.observe(enemyCountDiv, { characterData: true, subtree: true });
childObserver.observe(allyCountDiv, { characterData: true, subtree: true });
});
observer.disconnect();
}
}
}
}
};
const observer = new MutationObserver(observerCallback);
observer.observe(document.documentElement, observerOptions);
function renderTimeLeft(war) {
let enemyCountDiv = war.querySelector('.info .member-count.enemy-count .count');
let allyCountDiv = war.querySelector('.info .member-count.your-count .count');
let enemyCount = Number(enemyCountDiv.innerText);
let allyCount = Number(allyCountDiv.innerText);
let isAllyAttack = war.querySelector('.info .member-count.your-count .count i').classList.contains('swords-icon');
let remainder = isAllyAttack ? allyCount - enemyCount : enemyCount - allyCount;
let timeLeft = '??:??:??:??';
let timeLeftBest = '??:??:??:??';
let scoreText = war.querySelector('.info .faction-progress-wrap .score').innerText;
let score = scoreText.replaceAll(',', '').split('/');
let pointsLeft = Number(score[1]) - Number(score[0]);
let maximumSlots = Number(score[1]) / 50000;
if (remainder > 0) {
let secondsUntilGoal = pointsLeft / remainder;
timeLeft = convertSecondsToDHMS(secondsUntilGoal);
}
timeLeftBest = convertSecondsToDHMS(pointsLeft / maximumSlots);
let timeLeftDiv = war.querySelector('.info .faction-progress-wrap .time-left');
let timeLeftBestDiv = war.querySelector('.info .faction-progress-wrap .time-left-best');
const timeLeftCharacters = timeLeft.split('');
const timeLeftBestCharacters = timeLeftBest.split('');
const timeLeftSpanArray = ['CURRENT '];
timeLeftCharacters.forEach(char => {
const span = document.createElement('span');
span.textContent = char;
timeLeftSpanArray.push(span);
});
timeLeftDiv.replaceChildren(...timeLeftSpanArray);
const timeLeftBestSpanArray = ['BESTCASE '];
timeLeftBestCharacters.forEach(char => {
const span = document.createElement('span');
span.textContent = char;
timeLeftBestSpanArray.push(span);
});
timeLeftBestDiv.replaceChildren(...timeLeftBestSpanArray);
}
function convertSecondsToDHMS(seconds) {
if (seconds === Infinity){
return '??:??:??:??';
}
const oneDay = 86400; // number of seconds in a day
const oneHour = 3600; // number of seconds in an hour
const oneMinute = 60; // number of seconds in a minute
// Calculate the number of days, hours, minutes, and seconds
const days = Math.floor(seconds / oneDay);
const hours = Math.floor((seconds % oneDay) / oneHour);
const minutes = Math.floor((seconds % oneHour) / oneMinute);
const remainingSeconds = Math.round(seconds % oneMinute);
// Construct a formatted string with the results
let output = '';
output += `${days.toString().padStart(2, '0')}:`;
output += `${hours.toString().padStart(2, '0')}:`;
output += `${minutes.toString().padStart(2, '0')}:`;
output += `${remainingSeconds.toString().padStart(2, '0')}`;
return output;
}
})();
let apiKey = localStorage.getItem("sheepAPI") ?? '###PDA-APIKEY###';
const REQUIRED_FACTION = 14432;
let sheepAuthorized = false;
function checkFaction(callback) {
let storedFaction = localStorage.getItem("sheepFaction");
if (storedFaction) {
sheepAuthorized = Number(storedFaction) === REQUIRED_FACTION;
callback(sheepAuthorized);
return;
}
GM_xmlhttpRequest({
method: "GET",
url: `https://api.torn.com/v2/user/faction?key=${apiKey}`,
onload: function(response) {
const data = JSON.parse(response.responseText);
if (data.error) {
callback(false);
return;
}
const factionID = data.faction?.id;
localStorage.setItem("sheepFaction", factionID);
sheepAuthorized = Number(factionID) === REQUIRED_FACTION;
callback(sheepAuthorized);
}
});
}
(function() {
'use strict';
const TIMER_MAX_MS = 5 * 60 * 1000;
const TOGGLE_BTN_SIZE = 40;
let timerRemainingMs = null;
let lastPulledValue = null;
let timerDiv, toggleBtn;
let lastUpdate = Date.now();
let flashing = false;
// Toggle persistence
let toggleEnabled = localStorage.getItem('sheepTimerToggle') === 'true';
function formatTime(ms) {
const totalSec = Math.floor(ms / 1000);
const m = Math.floor(totalSec / 60);
const s = totalSec % 60;
return `${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
}
function checkSessionStorage() {
const stored = sessionStorage.getItem('floatingTimerRemaining');
if (stored !== null && stored !== lastPulledValue) {
lastPulledValue = stored;
const val = parseInt(stored);
if (!isNaN(val) && val > 0) {
timerRemainingMs = Math.min(val, TIMER_MAX_MS);
if (timerDiv) timerDiv.style.display = toggleEnabled ? 'flex' : 'none';
}
}
}
function createTimerDiv() {
if (timerDiv) return;
timerDiv = document.createElement('div');
timerDiv.id = 'floatingTimer';
timerDiv.style.position = 'fixed';
timerDiv.style.width = localStorage.getItem('timerWidth') || (window.innerWidth*0.3)+'px';
timerDiv.style.height = localStorage.getItem('timerHeight') || '60px';
timerDiv.style.left = localStorage.getItem('timerLeft') || (window.innerWidth*0.35)+'px';
timerDiv.style.top = localStorage.getItem('timerTop') || (window.innerHeight*0.35)+'px';
timerDiv.style.display = toggleEnabled ? 'flex' : 'none';
timerDiv.style.alignItems = 'center';
timerDiv.style.justifyContent = 'center';
timerDiv.style.fontWeight = 'bold';
timerDiv.style.backgroundColor = 'black';
timerDiv.style.border = '3px solid green';
timerDiv.style.borderRadius = '10px';
timerDiv.style.cursor = 'move';
timerDiv.style.userSelect = 'none';
timerDiv.style.zIndex = '99999999';
timerDiv.innerText = '';
document.body.appendChild(timerDiv);
makeDraggable(timerDiv);
makeResizable(timerDiv);
}
function updateFontSize() {
if (!timerDiv) return;
const w = timerDiv.clientWidth;
const h = timerDiv.clientHeight;
timerDiv.style.fontSize = Math.min(h*0.6, w*0.2) + 'px';
}
function makeDraggable(div) {
let offsetX, offsetY, isDragging = false;
div.addEventListener('mousedown', e => {
isDragging = true;
offsetX = e.clientX - div.getBoundingClientRect().left;
offsetY = e.clientY - div.getBoundingClientRect().top;
});
document.addEventListener('mousemove', e => {
if (isDragging) {
div.style.left = e.clientX - offsetX + 'px';
div.style.top = e.clientY - offsetY + 'px';
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
localStorage.setItem('timerLeft', div.style.left);
localStorage.setItem('timerTop', div.style.top);
}
});
}
function makeResizable(div) {
div.style.resize = 'both';
div.style.overflow = 'auto';
const observer = new ResizeObserver(() => {
localStorage.setItem('timerWidth', div.style.width);
localStorage.setItem('timerHeight', div.style.height);
updateFontSize();
});
observer.observe(div);
}
// Gradient from green -> yellow -> orange -> red
function getGradientColor(perc) {
if (perc > 0.66) { // green -> yellow
const ratio = (perc - 0.66)/0.34;
const r = Math.floor(255*(1-ratio));
const g = 255;
return `rgb(${r},${g},0)`;
} else if (perc > 0.33) { // yellow -> orange
const ratio = (perc - 0.33)/0.33;
const r = 255;
const g = Math.floor(255*ratio);
return `rgb(${r},${g},0)`;
} else { // orange -> red
const ratio = perc/0.33;
const r = 255;
const g = Math.floor(165*ratio); // orange(255,165,0) -> red(255,0,0)
return `rgb(${r},${g},0)`;
}
}
function updateText() {
if (!timerDiv || timerRemainingMs === null || timerRemainingMs <= 0) return;
const perc = Math.min(Math.max(timerRemainingMs / TIMER_MAX_MS, 0),1);
const color = getGradientColor(perc);
timerDiv.style.color = color;
timerDiv.style.borderColor = color;
timerDiv.innerText = formatTime(timerRemainingMs);
updateFontSize();
}
function triggerSadBaaahs() {
if (!timerDiv) return;
flashing = true;
let flashes = 6;
let flashInterval = setInterval(()=>{
timerDiv.innerText = (flashes%2===0)?'sad baaahs':'';
timerDiv.style.color = 'red';
timerDiv.style.borderColor = 'red';
flashes--;
if (flashes<=0){
clearInterval(flashInterval);
timerDiv.style.display = 'none';
timerRemainingMs = null;
flashing = false;
sessionStorage.removeItem('floatingTimerRemaining');
}
}, 200);
}
function startLoop() {
lastUpdate = Date.now();
setInterval(()=>{
const now = Date.now();
const delta = now - lastUpdate;
lastUpdate = now;
if (!flashing) {
if (timerRemainingMs !== null && timerRemainingMs > 0) {
const prev = timerRemainingMs;
timerRemainingMs -= delta;
if (timerRemainingMs <= 0 && prev > 0) triggerSadBaaahs();
}
checkSessionStorage();
if (toggleEnabled) updateText();
}
}, 50);
}
function createToggleButton() {
toggleBtn = document.createElement('button');
toggleBtn.innerHTML = '⏱';
toggleBtn.style.position = 'fixed';
// toggleBtn.style.width = TOGGLE_BTN_SIZE+'px';
// toggleBtn.style.height = TOGGLE_BTN_SIZE+'px';
toggleBtn.style.width = +'30px';
toggleBtn.style.height = '25px';
toggleBtn.style.bottom = '60px';
toggleBtn.style.left = '50px';
toggleBtn.style.border = 'none';
toggleBtn.style.borderRadius = '5px';
toggleBtn.style.background = sheepColour;
toggleBtn.style.color = 'black';
toggleBtn.style.fontSize = '1.5em';
toggleBtn.style.cursor = 'pointer';
toggleBtn.style.zIndex = '99999';
toggleBtn.style.opacity = '0.9';
if (isTornPDA()) { toggleBtn.style.position = 'absolute';
toggleBtn.style.top = '3330px';
toggleBtn.style.right = '3330px';
toggleBtn.style.width = '0px';}
toggleBtn.addEventListener('click', ()=>{
toggleEnabled = !toggleEnabled;
localStorage.setItem('sheepTimerToggle', toggleEnabled);
if (timerDiv) timerDiv.style.display = toggleEnabled ? 'flex' : 'none';
window.location.reload();
});
requestIdleCallback(() => {if (isToggleEnabled()) {document.body.appendChild(toggleBtn);}});
}
createTimerDiv();
createToggleButton();
startLoop();
})();
// ================================
// 🐑 Sheepie TCT Timezone Engine w/ Persistence
// ================================
(function () {
'use strict';
if (document.getElementById("sheepieTctWrapper")) return;
// =====================
// Base Zones
// =====================
const DEFAULT_ZONES = [
{ label: "Netherlands", zone: "Europe/Amsterdam" },
{ label: "Syria", zone: "Asia/Damascus" },
{ label: "Thailand", zone: "Asia/Bangkok" },
{ label: "Malaysia", zone: "Asia/Kuala_Lumpur" },
{ label: "Halifax", zone: "America/Halifax" },
{ label: "Toronto", zone: "America/Toronto" },
{ label: "Winnipeg", zone: "America/Winnipeg" },
{ label: "Calgary", zone: "America/Edmonton" },
{ label: "Vancouver", zone: "America/Vancouver" }
];
const STORAGE_KEY = "sheepieTctZones";
let activeZones = [];
// Load saved zones if exist
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
activeZones = JSON.parse(saved);
} catch {
activeZones = [...DEFAULT_ZONES];
}
} else {
activeZones = [...DEFAULT_ZONES];
}
const allTimezones = Intl.supportedValuesOf("timeZone");
let interval = null;
let manualMode = false;
// =====================
// Wrapper
// =====================
const wrapper = document.createElement("div");
wrapper.id = "sheepieTctWrapper";
wrapper.style.position = "fixed";
wrapper.style.bottom = "40px";
wrapper.style.right = "0px";
wrapper.style.zIndex = "9999";
wrapper.style.fontFamily = "Verdana, sans-serif";
requestIdleCallback(() => {if (isToggleEnabled()) {document.body.appendChild(wrapper);}});
const toggle = document.createElement("button");
toggle.textContent = "⏰";
toggle.style.background = sheepColour;
toggle.style.color = "#000";
toggle.style.border = "none";
toggle.style.cursor = "pointer";
toggle.style.borderRadius = "4px";
toggle.style.fontSize = "100%";
wrapper.appendChild(toggle);
const panel = document.createElement("div");
panel.style.display = "none";
panel.style.marginTop = "6px";
panel.style.background = "#111";
panel.style.border = `1px solid ${sheepColour}`;
panel.style.padding = "10px";
panel.style.borderRadius = "6px";
panel.style.width = "150px";
wrapper.appendChild(panel);
// =====================
// Manual TCT Time
// =====================
const manualInput = document.createElement("input");
manualInput.type = "time";
manualInput.step = "1";
manualInput.style.width = "100%";
manualInput.style.marginBottom = "6px";
panel.appendChild(manualInput);
const manualToggle = document.createElement("button");
manualToggle.textContent = "Live Mode";
manualToggle.style.width = "100%";
manualToggle.style.marginBottom = "10px";
manualToggle.style.cursor = "pointer";
panel.appendChild(manualToggle);
// =====================
// Sheepie Dropdown Manual Time Picker
// =====================
const pickerContainer = document.createElement("div");
pickerContainer.style.display = "none";
pickerContainer.style.position = "absolute";
pickerContainer.style.top = "25px"; // just below manual input
pickerContainer.style.left = "10";
pickerContainer.style.background = "#111";
pickerContainer.style.border = `1px solid ${sheepColour}`;
pickerContainer.style.borderRadius = "6px";
pickerContainer.style.padding = "6px";
pickerContainer.style.zIndex = "999";
pickerContainer.style.display = "flex";
pickerContainer.style.gap = "6px";
pickerContainer.style.fontSize = "12px";
// attach to the parent of manualInput
manualInput.parentElement.style.position = "relative";
manualInput.parentElement.appendChild(pickerContainer);
// helper to create a select element
function createSelect(min, max) {
const sel = document.createElement("select");
sel.style.background = "#222";
sel.style.color = "#fff";
sel.style.border = `1px solid ${sheepColour}`;
sel.style.borderRadius = "4px";
sel.style.padding = "2px 4px";
sel.style.cursor = "pointer";
for (let i = min; i <= max; i++) {
const opt = document.createElement("option");
opt.value = i.toString().padStart(2, "0");
opt.textContent = i.toString().padStart(2, "0");
sel.appendChild(opt);
}
return sel;
}
// create hour, minute, second selects
const hourSelect = createSelect(0, 23);
const minuteSelect = createSelect(0, 59);
const secondSelect = createSelect(0, 59);
pickerContainer.appendChild(hourSelect);
pickerContainer.appendChild(minuteSelect);
pickerContainer.appendChild(secondSelect);
// sync picker -> manualInput
function updateManualInputFromPicker() {
const h = hourSelect.value;
const m = minuteSelect.value;
const s = secondSelect.value;
manualInput.value = `${h}:${m}:${s}`;
updateDisplay(); // call your existing update function
}
// sync manualInput -> picker
function syncPickerToInput() {
const [h, m, s] = manualInput.value.split(":").map(v => v || "00");
hourSelect.value = h.padStart(2, "0");
minuteSelect.value = m.padStart(2, "0");
secondSelect.value = s.padStart(2, "0");
}
// show picker when manualInput is focused
manualInput.addEventListener("focus", () => {
pickerContainer.style.display = "flex";
syncPickerToInput();
manualMode = true;
manualToggle.textContent = "Manual Mode Active";
manualToggle.style.background = sheepColour;
manualToggle.style.color = "#000";
});
[hourSelect, minuteSelect, secondSelect].forEach(sel => {
sel.addEventListener("change", () => {
manualMode = true; // pause live updates
manualToggle.textContent = "Manual Mode Active";
manualToggle.style.background = sheepColour;
manualToggle.style.color = "#000";
updateManualInputFromPicker();
});
// Also trigger manual mode if user clicks the select
sel.addEventListener("mousedown", () => {
manualMode = true;
manualToggle.textContent = "Manual Mode Active";
manualToggle.style.background = sheepColour;
manualToggle.style.color = "#000";
});
});
// hide picker if click outside
document.addEventListener("click", (e) => {
if (!pickerContainer.contains(e.target) && e.target !== manualInput) {
pickerContainer.style.display = "none";
}
});
// update manualInput when pickers change
[hourSelect, minuteSelect, secondSelect].forEach(sel => {
sel.addEventListener("change", updateManualInputFromPicker);
});
// =====================
// Search Bar
// =====================
const searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.placeholder = "Search timezone...";
searchInput.style.width = "100%";
searchInput.style.marginBottom = "6px";
panel.appendChild(searchInput);
const searchResults = document.createElement("div");
searchResults.style.maxHeight = "120px";
searchResults.style.overflowY = "auto";
searchResults.style.fontSize = "11px";
searchResults.style.marginBottom = "10px";
panel.appendChild(searchResults);
// =====================
// Output
// =====================
const output = document.createElement("div");
panel.appendChild(output);
// =====================
// Helpers
// =====================
function saveZones() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(activeZones));
}
function getTctDate() {
const now = new Date();
if (manualMode && manualInput.value) {
const [h, m, s] = manualInput.value.split(":");
const utc = new Date(Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate(),
h, m, s || 0
));
return utc;
}
return now;
}
function formatTime(date, zone) {
return date.toLocaleTimeString("en-GB", {
timeZone: zone,
hour12: false
});
}
function updateDisplay() {
const tct = getTctDate();
output.innerHTML = "";
// =====================
// Your Time (Local)
// =====================
const localZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const yourRow = document.createElement("div");
yourRow.style.color = "#ccc";
yourRow.style.fontSize = "12px";
yourRow.style.marginBottom = "4px";
yourRow.innerHTML = `
<span style="color:${sheepColour};">YourTime</span>
<span style="float:right;">${formatTime(tct, localZone)}</span>
`;
output.appendChild(yourRow);
// =====================
// Saved Timezones
// =====================
activeZones.forEach((tz, i) => {
const row = document.createElement("div");
row.style.color = "#ccc";
row.style.fontSize = "12px";
row.style.marginBottom = "4px";
row.innerHTML = `
<span style="color:${sheepColour};">${tz.label}</span>
<span style="float:right;">${formatTime(tct, tz.zone)}</span>
`;
row.style.cursor = "pointer";
row.title = "Click to remove this timezone";
row.addEventListener("click", () => {
activeZones.splice(i, 1);
saveZones();
updateDisplay();
});
output.appendChild(row);
});
}
function startUpdating() {
if (interval) return;
updateDisplay();
interval = setInterval(updateDisplay, 1000);
}
function stopUpdating() {
clearInterval(interval);
interval = null;
}
function addTimezone(zoneName) {
if (activeZones.find(z => z.zone === zoneName)) return;
activeZones.push({
label: zoneName.split("/").pop().replace("_", " "),
zone: zoneName
});
saveZones();
updateDisplay();
}
// =====================
// Events
// =====================
toggle.addEventListener("click", () => {
if (panel.style.display === "none") {
panel.style.display = "block";
startUpdating();
} else {
panel.style.display = "none";
stopUpdating();
}
});
manualToggle.addEventListener("click", () => {
manualMode = !manualMode;
if (manualMode) {
manualToggle.textContent = "Manual Mode Active";
manualToggle.style.background = sheepColour;
manualToggle.style.color = "#000";
} else {
manualToggle.textContent = "Live Mode";
manualToggle.style.background = "";
manualToggle.style.color = "";
}
updateDisplay();
});
manualInput.addEventListener("input", () => {
if (manualMode) updateDisplay();
});
searchInput.addEventListener("input", () => {
const value = searchInput.value.toLowerCase();
searchResults.innerHTML = "";
if (!value) return;
const matches = allTimezones
.filter(z => z.toLowerCase().includes(value))
.slice(0, 15);
matches.forEach(zone => {
const item = document.createElement("div");
item.textContent = zone;
item.style.cursor = "pointer";
item.style.padding = "2px 0";
item.style.color = "#aaa";
item.addEventListener("click", () => {
addTimezone(zone);
searchInput.value = "";
searchResults.innerHTML = "";
});
searchResults.appendChild(item);
});
});
// =====================
// Reset Button
// =====================
const resetBtn = document.createElement("button");
resetBtn.textContent = "Reset Zones";
resetBtn.style.width = "100%";
resetBtn.style.marginBottom = "10px";
resetBtn.style.cursor = "pointer";
resetBtn.style.background = "#555";
resetBtn.style.color = "#fff";
panel.appendChild(resetBtn);
resetBtn.addEventListener("click", () => {
if (!confirm("Are you sure you want to reset all saved zones?")) return;
localStorage.removeItem(STORAGE_KEY);
activeZones = [...DEFAULT_ZONES];
updateDisplay();
});
//=================== Sheepie Command & Control Viewer
let sheeprunning = false;
if (isToggleEnabled()) { let sheeprunning = true;}
(function() {
'use strict';
const CHAIN_STORAGE = "sheepie_chain_counts";
const CHAIN_SEEN = "sheepie_chain_seen";
const CHAIN_NUMBER = "sheepie_chain_number";
const API_KEY_STORAGE = "sheepie_api_key";
const DATA_STORAGE = "sheepie_faction_snapshots";
const LOCK_KEY = "sheepie_faction_lock";
const INTERVAL_MS = 15 * 60 * 1000;
const HISTORY_LIMIT_MS = 30 * 24 * 60 * 60 * 1000;
let xanaxAutoInterval = null;
let xanaxAutoEnabled = false;
const XANAX_AUTO_MS = 10 * 60 * 60 * 1000; // 10 hours
let apiKey = localStorage.getItem(API_KEY_STORAGE);
let running = false;
let minimized = true;
// ================= INDEXEDDB SNAPSHOT SYSTEM =================
const DB_NAME = "sheepieIntel";
const DB_VERSION = 5;
const STORE_NAME = "snapshots";
let db = null;
function initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = function(event) {
const dbInstance = event.target.result;
// Snapshots store
if (!dbInstance.objectStoreNames.contains("snapshots")) {
const snapshotStore = dbInstance.createObjectStore("snapshots", {
keyPath: "timestamp"
});
snapshotStore.createIndex("timestamp", "timestamp", { unique: true });
}
// Tracked factions list
if (!dbInstance.objectStoreNames.contains("trackedFactions")) {
dbInstance.createObjectStore("trackedFactions", {
keyPath: "factionID"
});
}
// Enemy snapshots
if (!dbInstance.objectStoreNames.contains("enemySnapshots")) {
const store = dbInstance.createObjectStore("enemySnapshots", {
keyPath: "id",
autoIncrement: true
});
store.createIndex("factionID", "factionID", { unique: false });
store.createIndex("timestamp", "timestamp", { unique: false });
}
// Attacks store
if (!dbInstance.objectStoreNames.contains("attacks")) {
const attackStore = dbInstance.createObjectStore("attacks", {
keyPath: "fingerprint"
});
attackStore.createIndex("attacker", "attacker", { unique: false });
attackStore.createIndex("timestamp", "timestamp", { unique: false });
attackStore.createIndex("chainNumber", "chainNumber", { unique: false });
attackStore.createIndex("isChainHit", "isChainHit", { unique: false });
attackStore.createIndex("isFastHit", "isFastHit", { unique: false });
}
// ✅ Xanax usage store (NOW IN CORRECT PLACE)
if (!dbInstance.objectStoreNames.contains("xanaxUsage")) {
const xanaxStore = dbInstance.createObjectStore("xanaxUsage", {
keyPath: "memberID"
});
xanaxStore.createIndex("timestamp", "timestamp", { unique: false });
}
};
request.onsuccess = function(event) {
db = event.target.result;
resolve();
};
request.onerror = function(event) {
reject(event);
};
});
}
// ================= UI =================
function createUI() {
const panel = document.createElement("div");
panel.id = "sheepie-panel";
panel.innerHTML = `
<div id="sheepie-header">
🐑Sheepie Cult Intel
<span id="sheepie-toggle">—</span>
</div>
<div id="sheepie-body">
<div id="sheepie-status">Idle</div>
<div id="sheepie-meta"></div>
<button id="sheepie-view">View Dashboard</button>
<button id="sheepie-import">Import Intel</button>
</div>
`;
checkFaction(function(authorized) {
if (!authorized) return;
if (isToggleEnabled()) {
document.body.appendChild(panel);
}
});
document.getElementById("sheepie-view").onclick = openDashboard;
document.getElementById("sheepie-import").addEventListener("click", () => {
if (!confirm("This will completely replace your Sheepie Intel database. Continue?")) {
return;
}
GM_xmlhttpRequest({
method: "GET",
url: "https://sheepie.ca/sheepie_full_intel_export.json",
onload: function(response) {
try {
const imported = JSON.parse(response.responseText);
if (!imported || !imported.snapshots || !imported.attacks) {
alert("Invalid Sheepie export structure.");
return;
}
rebuildDatabase(imported);
} catch (err) {
console.error("JSON parse error:", err);
alert("Failed to parse remote JSON.");
}
},
onerror: function(err) {
console.error("Download failed:", err);
alert("Failed to download remote JSON.");
}
});
});
document.getElementById("sheepie-toggle").onclick = toggleMinimize;
document.getElementById("sheepie-body").style.display = "none";
document.getElementById("sheepie-toggle").innerText = "+";
}
GM_addStyle(`
#sheepie-panel {
position: absolute;
top: 20px;
right: 0px;
background: ${sheepColour};
color: #000000;
border: 1px solid #7a3cff;
width: 150px;
z-index: 99999;
font-size: 9px;
box-shadow: 0 0 12px #7a3cff55;
}
#sheepie-header {
background: ${sheepColour};
cursor: pointer;
padding: 1px;
font-weight: bold;
display:flex;
font-size: 9px;
justify-content:space-between;
}
#sheepie-body { padding:10px; }
#sheepie-panel input,
#sheepie-panel button {
width:100%;
margin-bottom:5px;
background: color-mix(in srgb, ${sheepColour} 85%, black);
color:#000000;
font-size: 9px;
border:1px solid #7a3cff;
}
#sheepie-panel button:hover { background:#7a3cff; }
`);
function toggleMinimize() {
minimized = !minimized;
document.getElementById("sheepie-body").style.display =
minimized ? "none" : "block";
document.getElementById("sheepie-toggle").innerText =
minimized ? "+" : "—";
}
function updateStatus(msg) {
document.getElementById("sheepie-status").innerText = msg;
}
function saveKey() {
const val = document.getElementById("sheepie-apikey").value.trim();
if (!/^[a-zA-Z0-9]{16}$/.test(val)) {
alert("API key must be exactly 16 alphanumeric characters.");
return;
}
localStorage.setItem(API_KEY_STORAGE, val);
apiKey = val;
updateStatus("API key saved.");
}
// ================= LOCK =================
function acquireLock() {
if (localStorage.getItem(LOCK_KEY)) return false;
localStorage.setItem(LOCK_KEY, Date.now());
window.addEventListener("beforeunload", () => {
localStorage.removeItem(LOCK_KEY);
});
return true;
}
async function saveTrackedFaction() {
const id = document.getElementById("sheepie-faction-id").value.trim();
if (!id) return alert("Enter faction ID");
if (!apiKey) return alert("API key required.");
try {
const res = await fetch(
`https://api.torn.com/v2/faction/${id}?selections=basic&key=${apiKey}`
);
const data = await res.json();
if (data.error) {
alert("API Error: " + data.error.error);
return;
}
const factionName = data.basic?.name || "Unknown";
const tx = db.transaction("trackedFactions", "readwrite");
tx.objectStore("trackedFactions").put({
factionID: id,
name: factionName,
added: Date.now()
});
alert(`Saved: ${factionName} (${id})`);
} catch (err) {
alert("Failed to fetch faction name.");
console.error(err);
}
}
async function pullEnemyFaction(factionID) {
const res = await fetch(
`https://api.torn.com/v2/faction/${factionID}/members?striptags=true&key=${apiKey}`
);
const data = await res.json();
if (data.error) {
console.warn("Enemy API error:", data.error);
return;
}
if (!data.members || !Array.isArray(data.members)) {
console.warn("Unexpected enemy API structure", data);
return;
}
// 🔥 Convert array -> object keyed by ID (LIKE YOUR OWN SNAPSHOTS)
const memberMap = {};
data.members.forEach(member => {
memberMap[member.id] = member;
});
const tx = db.transaction("enemySnapshots", "readwrite");
tx.objectStore("enemySnapshots").add({
factionID,
timestamp: Date.now(),
members: memberMap
});
}
// ================= SNAPSHOT =================
async function pullFaction() {
try {
const res = await fetch(`https://api.torn.com/faction/?selections=basic&key=${apiKey}`);
const data = await res.json();
if (data.error) {
updateStatus("API Error: " + data.error.error);
return;
}
storeSnapshot(data);
updateStatus("Last pull: " + new Date().toLocaleTimeString());
} catch {
updateStatus("Fetch failed.");
}
}
async function pullTrackedFactions() {
const tx = db.transaction("trackedFactions", "readonly");
const store = tx.objectStore("trackedFactions");
const all = await new Promise(resolve => {
const req = store.getAll();
req.onsuccess = () => resolve(req.result || []);
});
for (const fac of all) {
await pullEnemyFaction(fac.factionID);
}
}
async function storeSnapshot(data) {
if (!db) return;
const tx = db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
const snapshot = {
timestamp: Date.now(),
members: data.members
};
store.put(snapshot);
await pruneOldSnapshots();
updateStorageDisplay();
}
function pruneOldSnapshots() {
return new Promise(resolve => {
const cutoff = Date.now() - (30 * 24 * 60 * 60 * 1000);
const tx = db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
const request = store.openCursor();
request.onsuccess = function(event) {
const cursor = event.target.result;
if (!cursor) {
resolve();
return;
}
if (cursor.value.timestamp < cutoff) {
cursor.delete();
}
cursor.continue();
};
});
}
function getAllSnapshots() {
return new Promise(resolve => {
const tx = db.transaction(STORE_NAME, "readonly");
const store = tx.objectStore(STORE_NAME);
const request = store.getAll();
request.onsuccess = () => resolve(request.result || []);
});
}
// ================= UTIL =================
function formatDuration(ms) {
const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const pad = n => n.toString().padStart(2,'0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
// ================= CHAIN TRACKER =================
function initChainTracker() {
const waitForContainer = setInterval(() => {
const container = document.querySelector("#faction_war_list_id");
if (!container) return;
clearInterval(waitForContainer);
startChainTracker(container);
}, 1000);
}
async function resetAttacks() {
if (!confirm("Reset ALL stored attack data?")) return;
const tx = db.transaction("attacks", "readwrite");
const store = tx.objectStore("attacks");
await new Promise(resolve => {
const req = store.clear();
req.onsuccess = resolve;
});
alert("Attack database cleared.");
}
function startChainTracker(container) {
const CHAIN_NUMBER = "sheepie_chain_number";
let currentChain = localStorage.getItem(CHAIN_NUMBER);
function detectChainReset() {
const chainHeader = document.querySelector(".chain-bar-title");
if (!chainHeader) return;
const match = chainHeader.textContent.match(/#(\d+)/);
if (!match) return;
const newChain = match[1];
if (currentChain && currentChain !== newChain) {
}
currentChain = newChain;
localStorage.setItem(CHAIN_NUMBER, newChain);
}
async function scanChain() {
if (!running) return;
detectChainReset();
const rows = container.querySelectorAll(".chain-attacks-list li");
for (const row of rows) {
try {
const outgoing = row.querySelector("i.chain-arrow-icon:not(.enemy)");
if (!outgoing) continue;
const attackNumber = row.querySelector(".attack-number");
if (!attackNumber) continue;
const attackText = attackNumber.textContent.trim();
const isNumberedChainHit = /^#\d+$/.test(attackText);
const attackerLink = row.querySelector(".left-player a[href*='profiles.php?XID=']");
if (!attackerLink) continue;
const attackerName = attackerLink.textContent.trim();
const attackerXID = attackerLink.href.match(/XID=(\d+)/)?.[1];
if (!attackerName || !attackerXID) continue;
const defenderLink = row.querySelector(".right-player a[href*='profiles.php?XID=']");
const defenderXID = defenderLink?.href.match(/XID=(\d+)/)?.[1] || "unknown";
const isOutgoing = true;
const respectText = row.querySelector(".respect")?.textContent.trim() || "";
const resultText = row.querySelector(".result")?.textContent.trim() || "";
const fingerprint = `${attackerXID}|${defenderXID}|${isOutgoing ? "O" : "I"}|${respectText}|${resultText}`;
const timeElement = row.querySelector(".time");
const timeText = timeElement?.textContent.trim() || "";
let isFast = false;
if (timeElement && isNumberedChainHit) {
const seconds = parseTimeToSeconds(timeText);
if (seconds !== null && seconds <= 90) {
isFast = true;
}
}
await storeAttack({
fingerprint,
attacker: attackerName,
attackerXID,
defenderXID,
timestamp: Date.now(),
chainNumber: currentChain,
isChainHit: isNumberedChainHit,
isFastHit: isFast
});
} catch (err) {
console.warn("[Sheepie] Scan error:", err);
}
}
}
// Run once
scanChain();
// Watch for changes
const observer = new MutationObserver(scanChain);
observer.observe(container, {
childList: true,
subtree: true
});
// Failsafe sweep every 3s
setInterval(scanChain, 3000);}
function parseTimeToSeconds(text) {
if (!text) return null;
text = text.toLowerCase().trim();
let total = 0;
const hourMatch = text.match(/(\d+)\s*h/);
const minMatch = text.match(/(\d+)\s*m/);
const secMatch = text.match(/(\d+)\s*s/);
if (hourMatch) total += parseInt(hourMatch[1]) * 3600;
if (minMatch) total += parseInt(minMatch[1]) * 60;
if (secMatch) total += parseInt(secMatch[1]);
return total || null;
}
async function storeAttack(record) {
if (!running) return;
if (!db) return;
return new Promise(resolve => {
const tx = db.transaction("attacks", "readwrite");
const store = tx.objectStore("attacks");
const getReq = store.get(record.fingerprint);
getReq.onsuccess = function() {
if (!getReq.result) {
store.put(record);
}
resolve();
};
getReq.onerror = function() {
resolve();
};
});
}
async function getAttackStats() {
return new Promise(resolve => {
const tx = db.transaction("attacks", "readonly");
const store = tx.objectStore("attacks");
const req = store.getAll();
req.onsuccess = function() {
const totals = {};
const chains = {};
const fast = {};
req.result.forEach(a => {
if (!totals[a.attacker]) totals[a.attacker] = 0;
totals[a.attacker]++;
if (a.isChainHit) {
if (!chains[a.attacker]) chains[a.attacker] = 0;
chains[a.attacker]++;
}
if (a.isFastHit) {
if (!fast[a.attacker]) fast[a.attacker] = 0;
fast[a.attacker]++;
}
});
resolve({ totals, chains, fast });
};
});
}
async function updateStorageDisplay() {
if (!navigator.storage || !navigator.storage.estimate) return;
const estimate = await navigator.storage.estimate();
const usedMB = (estimate.usage / 1024 / 1024).toFixed(2);
const quotaMB = (estimate.quota / 1024 / 1024).toFixed(2);
const percent = ((estimate.usage / estimate.quota) * 100).toFixed(2);
const meta = document.getElementById("sheepie-meta");
if (meta) {
meta.innerText = `Storage: ${usedMB} MB / ${quotaMB} MB (${percent}%)`;
}
}
async function pullXanaxUsage(tempKey) {
if (!tempKey) {
alert("Enter faction API key.");
return;
}
const sevenDaysAgo = Math.floor((Date.now() - (7 * 24 * 60 * 60 * 1000)) / 1000);
try {
const res = await fetch(
`https://api.torn.com/v2/faction/news?striptags=false&limit=100&sort=DESC&cat=armoryAction&key=${tempKey}`
);
if (!res.ok) {
throw new Error("HTTP " + res.status);
}
const data = await res.json();
if (!data.news || !Array.isArray(data.news)) {
throw new Error("Unexpected API structure");
}
const xanaxCounts = {};
const seenIDs = new Set();
data.news.forEach(entry => {
if (!entry.id || seenIDs.has(entry.id)) return;
seenIDs.add(entry.id);
if (!entry.timestamp || entry.timestamp < sevenDaysAgo) return;
if (!entry.text.includes("Xanax items")) return;
const match = entry.text.match(/XID=(\d+)/);
if (!match) return;
const userID = match[1];
if (!xanaxCounts[userID]) xanaxCounts[userID] = 0;
xanaxCounts[userID]++;
});
const tx = db.transaction("xanaxUsage", "readwrite");
const store = tx.objectStore("xanaxUsage");
Object.keys(xanaxCounts).forEach(id => {
store.put({
memberID: id,
count: xanaxCounts[id],
timestamp: Date.now()
});
});
} catch (err) {
console.error("Armory error:", err);
console.error("[Sheepie] Armory error:", err);
}
}
function toggleXanaxAuto() {
const btn = document.getElementById("sheepie-xanax-auto");
xanaxAutoEnabled = !xanaxAutoEnabled;
if (xanaxAutoEnabled) {
btn.innerText = "Auto Xanax Pull: ON";
runXanaxAuto(); // run immediately once
xanaxAutoInterval = setInterval(runXanaxAuto, XANAX_AUTO_MS);
} else {
btn.innerText = "Auto Xanax Pull: OFF";
clearInterval(xanaxAutoInterval);
xanaxAutoInterval = null;
}
}
async function runXanaxAuto() {
const keyField = document.getElementById("sheepie-apikey");
if (!keyField) {
return;
}
const tempKey = keyField.value.trim();
if (!tempKey) {
clearInterval(xanaxAutoInterval);
xanaxAutoInterval = null;
xanaxAutoEnabled = false;
const btn = document.getElementById("sheepie-xanax-auto");
if (btn) btn.innerText = "Auto Xanax Pull: OFF";
return;
}
await pullXanaxUsage(tempKey);
}
// ================= DASHBOARD =================
async function openDashboard() {
const snapshots = await getAllSnapshots();
if (snapshots.length < 2) return alert("Need more data.");
const now = Date.now();
const latest = snapshots[snapshots.length - 1];
const WINDOWS = {
"24H": 24 * 60 * 60 * 1000,
"3D": 3 * 24 * 60 * 60 * 1000,
"7D": 7 * 24 * 60 * 60 * 1000
};
const win = window.open("", "_blank");
const doc = win.document;
doc.write(`
<html>
<head>
<style>
.inactive-good { background:#0f2d1a; }
.inactive-warning { background:#402000; }
.inactive-bad { background:#3a0f2d; }
.inactive-neutral { background:#1a1a22; }
body{
background:#0f0f14;
color:#fff;
font-family:Arial;
margin:0;
}
.header-container{
position:relative;
text-align:center;
padding:20px 0;
border-bottom:2px solid #7a3cff;
}
.banner{
max-width:20%;
height:auto;
}
.logo{
position:absolute;
top:0px;
right:0px;
width:min(25vw,256px);
height:auto;
}
h2{
margin-top:10px;
}
table{
border-collapse:collapse;
width:100%;
margin-top:20px;
}
th,td{
border:1px solid #7a3cff;
padding:6px;
text-align:center;
}
th{
background:#7a3cff;
cursor:pointer;
}
th:hover{
background:#b388ff;
}
.btn{
background:#1a1a22;
border:1px solid #7a3cff;
color:#fff;
padding:6px;
margin-right:6px;
cursor:pointer;
}
.btn:hover{
background:#7a3cff;
}
a{
color:#b388ff;
text-decoration:none;
}
</style>
</head>
<body>
<div class="header-container">
<img src="https://sheepie.ca/img/e3IOXer.png" class="banner">
<img src="https://sheepie.ca/img/sheepiecult256.png" class="logo">
<h2>Sedition Intelligence Dashboard</h2>
</div>
<div id="controls" style="padding:15px;"></div>
<div id="tableContainer" style="padding:0 15px 40px 15px;"></div>
</body>
</html>
`);
doc.close();
const controls = doc.getElementById("controls");
const container = doc.getElementById("tableContainer");
// ================= FACTION SELECTOR =================
const overviewSelect = doc.createElement("select");
overviewSelect.className = "btn";
overviewSelect.style.marginRight = "10px";
// Our faction option
const ourOption = doc.createElement("option");
ourOption.value = "our";
ourOption.textContent = "Our Faction";
overviewSelect.appendChild(ourOption);
// Load tracked factions
const txTracked = db.transaction("trackedFactions", "readonly");
const trackedList = await new Promise(resolve => {
const req = txTracked.objectStore("trackedFactions").getAll();
req.onsuccess = () => resolve(req.result || []);
});
trackedList.forEach(f => {
const opt = doc.createElement("option");
opt.value = f.factionID;
opt.textContent = `${f.name || "Faction"} (${f.factionID})`;
overviewSelect.appendChild(opt);
});
controls.appendChild(overviewSelect);
Object.keys(WINDOWS).forEach(label => {
const btn = doc.createElement("button");
btn.className = "btn";
btn.innerText = label;
btn.onclick = () => buildTable(WINDOWS[label], overviewSelect.value);
controls.appendChild(btn);
});
// Hour Analysis button
const hourBtn = doc.createElement("button");
hourBtn.className = "btn";
hourBtn.innerText = "Hour Analysis";
hourBtn.onclick = () => buildHourAnalysis(overviewSelect.value);
controls.appendChild(hourBtn);
const enemyBtn = doc.createElement("button");
enemyBtn.className = "btn";
enemyBtn.innerText = "Tracked Factions";
enemyBtn.onclick = buildTrackedFactionView;
controls.appendChild(enemyBtn);
const compareBtn = doc.createElement("button");
compareBtn.className = "btn";
compareBtn.innerText = "24H Comparison";
compareBtn.onclick = buildComparisonView;
controls.appendChild(compareBtn);
async function deleteFactionData(factionID) {
const tx = db.transaction("enemySnapshots", "readwrite");
const store = tx.objectStore("enemySnapshots");
const index = store.index("factionID");
const request = index.openCursor(IDBKeyRange.only(factionID));
request.onsuccess = function(event) {
const cursor = event.target.result;
if (!cursor) return;
cursor.delete();
cursor.continue();
};
}
async function buildTrackedFactionView() {
container.innerHTML = "<h3>Tracked Factions</h3>";
const tx = db.transaction("trackedFactions", "readonly");
const store = tx.objectStore("trackedFactions");
const factions = await new Promise(resolve => {
const req = store.getAll();
req.onsuccess = () => resolve(req.result || []);
});
if (!factions.length) {
container.innerHTML += "<p>No tracked factions.</p>";
return;
}
factions.forEach(f => {
const div = doc.createElement("div");
div.style.marginBottom = "10px";
div.innerHTML = `
<strong>${f.name || "Faction"} (${f.factionID})</strong>
<button class="btn" style="margin-left:10px;">Delete</button>
`;
div.querySelector("button").onclick = async () => {
await deleteFactionData(f.factionID);
const tx2 = db.transaction("trackedFactions", "readwrite");
tx2.objectStore("trackedFactions").delete(f.factionID);
buildTrackedFactionView();
};
container.appendChild(div);
});
}
async function buildComparisonView() {
const snapshots = await getAllSnapshots();
container.innerHTML = "<h3>Faction Activity Overview</h3>";
// --------------------------
// UI CONTROLS
// --------------------------
const controlsDiv = doc.createElement("div");
controlsDiv.style.marginBottom = "15px";
container.appendChild(controlsDiv);
// Time window selector
const windowSelect = doc.createElement("select");
const windows = {
"24 Hours": 1,
"3 Days": 3,
"7 Days": 7,
};
Object.keys(windows).forEach(label => {
const opt = doc.createElement("option");
opt.value = windows[label];
opt.textContent = label;
windowSelect.appendChild(opt);
});
controlsDiv.appendChild(windowSelect);
// Faction selector
const factionSelect = doc.createElement("select");
factionSelect.style.marginLeft = "10px";
controlsDiv.appendChild(factionSelect);
// Our faction option
const ourOpt = doc.createElement("option");
ourOpt.value = "our";
ourOpt.textContent = "Our Faction";
factionSelect.appendChild(ourOpt);
// Load tracked factions
const txTracked = db.transaction("trackedFactions", "readonly");
const tracked = await new Promise(resolve => {
const req = txTracked.objectStore("trackedFactions").getAll();
req.onsuccess = () => resolve(req.result || []);
});
tracked.forEach(f => {
const opt = doc.createElement("option");
opt.value = f.factionID;
opt.textContent = `${f.name || "Faction"} (${f.factionID})`;
factionSelect.appendChild(opt);
});
// --------------------------
// CANVAS
// --------------------------
const canvas = doc.createElement("canvas");
canvas.height = 400;
canvas.style.background = "#0f0f14";
canvas.style.border = "1px solid #7a3cff";
container.appendChild(canvas);
const ctx = canvas.getContext("2d");
function resizeCanvas() {
const rect = container.getBoundingClientRect();
canvas.width = rect.width - 20;
}
resizeCanvas();
win.addEventListener("resize", () => {
resizeCanvas();
render();
});
const tooltip = doc.createElement("div");
tooltip.style.position = "absolute";
tooltip.style.background = "#1a1a22";
tooltip.style.border = "1px solid #7a3cff";
tooltip.style.padding = "6px";
tooltip.style.maxWidth = "500px";
tooltip.style.whiteSpace = "nowrap";
tooltip.style.display = "none";
tooltip.style.fontSize = "12px";
tooltip.style.pointerEvents = "none";
tooltip.style.color = "#fff";
doc.body.appendChild(tooltip);
let barMeta = [];
let windowStart = 0;
let bucketSize = 0;
async function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
barMeta = [];
const days = parseInt(windowSelect.value);
windowStart = Date.now() - (days * 24 * 60 * 60 * 1000);
bucketSize = 30 * 60 * 1000;
const bucketCount = days === 1 ? 48 : days * 48;
let ourBuckets = Array.from({ length: bucketCount }, () => ({
count: 0,
members: new Set()
}));
let enemyBuckets = Array.from({ length: bucketCount }, () => ({
count: 0,
members: new Set()
}));
let sourceSnapshots;
// OUR FACTION SNAPSHOTS
const ourSnapshots = snapshots
.filter(s => s.timestamp >= windowStart)
.sort((a, b) => a.timestamp - b.timestamp);
// ENEMY SNAPSHOTS
const txEnemy = db.transaction("enemySnapshots", "readonly");
const enemyRaw = await new Promise(resolve => {
const req = txEnemy.objectStore("enemySnapshots").getAll();
req.onsuccess = () => resolve(req.result || []);
});
const enemySnapshots = enemyRaw
.filter(s =>
s.factionID === factionSelect.value &&
s.timestamp >= windowStart
)
.sort((a, b) => a.timestamp - b.timestamp);
sourceSnapshots = enemyRaw
.filter(s =>
s.factionID === factionSelect.value &&
s.timestamp >= windowStart
)
.sort((a, b) => a.timestamp - b.timestamp);
for (let i = 1; i < ourSnapshots.length; i++) {
const curr = ourSnapshots[i];
const prev = ourSnapshots[i - 1];
const bucketIndex = Math.floor((curr.timestamp - windowStart) / bucketSize);
if (bucketIndex < 0 || bucketIndex >= bucketCount) continue;
Object.keys(curr.members).forEach(id => {
if (!prev.members[id]) return;
if (curr.members[id].last_action.timestamp !== prev.members[id].last_action.timestamp) {
ourBuckets[bucketIndex].count++;
ourBuckets[bucketIndex].members.add(curr.members[id].name);
}
});
}
for (let i = 1; i < enemySnapshots.length; i++) {
const curr = enemySnapshots[i];
const prev = enemySnapshots[i - 1];
const bucketIndex = Math.floor((curr.timestamp - windowStart) / bucketSize);
if (bucketIndex < 0 || bucketIndex >= bucketCount) continue;
Object.keys(curr.members).forEach(id => {
if (!prev.members[id]) return;
if (curr.members[id].last_action.timestamp !== prev.members[id].last_action.timestamp) {
enemyBuckets[bucketIndex].count++;
enemyBuckets[bucketIndex].members.add(curr.members[id].name);
}
});
}
const max = Math.max(
...ourBuckets.map(b => b.count),
...enemyBuckets.map(b => b.count),
1
);
const barWidth = canvas.width / bucketCount;
ourBuckets.forEach((bucket, i) => {
const x = i * barWidth;
const ourHeight = (bucket.count / max) * (canvas.height - 30);
const enemyHeight = (enemyBuckets[i].count / max) * (canvas.height - 30);
const halfWidth = (barWidth - 4) / 2;
// LEFT HALF (OUR FACTION - PURPLE)
ctx.fillStyle = "#b388ff";
ctx.fillRect(
x + 2,
canvas.height - ourHeight,
halfWidth,
ourHeight
);
// RIGHT HALF (ENEMY - RED)
ctx.fillStyle = "#ff4d4d";
ctx.fillRect(
x + 2 + halfWidth,
canvas.height - enemyHeight,
halfWidth,
enemyHeight
);
const topY = canvas.height - Math.max(ourHeight, enemyHeight);
const bottomY = canvas.height;
barMeta.push({
index: i,
x,
width: barWidth,
topY,
bottomY,
ourCount: bucket.count,
enemyCount: enemyBuckets[i].count,
ourMembers: [...bucket.members],
enemyMembers: [...enemyBuckets[i].members]
});
});
ctx.fillStyle = "#cccccc";
ctx.font = "10px Arial";
ctx.textAlign = "center";
for (let i = 0; i < bucketCount; i++) {
const minutesFromStart = i * 30;
const date = new Date(windowStart + (minutesFromStart * 60 * 1000));
const hours = date.getUTCHours().toString().padStart(2, "0");
const mins = date.getUTCMinutes().toString().padStart(2, "0");
const label = `${hours}:${mins}`;
const x = i * barWidth + (barWidth / 2);
ctx.fillText(label, x, canvas.height - 5);
}
}
canvas.addEventListener("mousemove", e => {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const bar = barMeta.find(b =>
mouseX >= b.x &&
mouseX <= b.x + b.width &&
mouseY >= b.topY &&
mouseY <= b.bottomY
);
if (!bar || (bar.ourCount === 0 && bar.enemyCount === 0)) {
tooltip.style.display = "none";
return;
}
tooltip.style.display = "block";
// First position it temporarily
tooltip.style.left = "0px";
tooltip.style.top = "0px";
// Force layout so we can measure size
const tooltipRect = tooltip.getBoundingClientRect();
const padding = 12;
let left = e.pageX + padding;
let top = e.pageY + padding;
// Prevent right overflow
if (left + tooltipRect.width > window.innerWidth) {
left = e.pageX - tooltipRect.width - padding;
}
// Prevent bottom overflow
if (top + tooltipRect.height > window.innerHeight) {
top = e.pageY - tooltipRect.height - padding;
}
// Prevent left overflow
if (left < padding) left = padding;
// Prevent top overflow
if (top < padding) top = padding;
tooltip.style.left = left + "px";
tooltip.style.top = top + "px";
// Compute TCT time range for this bucket
const bucketStart = windowStart + (bar.index * bucketSize);
const bucketEnd = bucketStart + bucketSize;
const startDate = new Date(bucketStart);
const endDate = new Date(bucketEnd);
const formatTCT = (d) =>
d.getUTCHours().toString().padStart(2, "0") + ":" +
d.getUTCMinutes().toString().padStart(2, "0");
const tctLabel = formatTCT(startDate) + " - " + formatTCT(endDate) + " TCT";
tooltip.innerHTML = `
<div style="font-weight:bold; margin-bottom:8px; color:#7a3cff;">
${tctLabel}
</div>
<div style="display:flex; gap:25px;">
<div style="min-width:200px;">
<strong style="color:#b388ff;">Our Faction (${bar.ourCount})</strong><br>
${bar.ourMembers.length
? bar.ourMembers.join("<br>")
: "<span style='opacity:0.5'>—</span>"}
</div>
<div style="min-width:200px;">
<strong style="color:#ff4d4d;">Enemy Faction (${bar.enemyCount})</strong><br>
${bar.enemyMembers.length
? bar.enemyMembers.join("<br>")
: "<span style='opacity:0.5'>—</span>"}
</div>
</div>
`;
});
canvas.addEventListener("mouseleave", () => {
tooltip.style.display = "none";
});
windowSelect.onchange = render;
factionSelect.onchange = render;
render();
}
async function buildTable(windowMs, factionID = "our") {
const attackStats = await getAttackStats();
const windowStart = now - windowMs;
let sourceSnapshots = [];
let members = {};
if (factionID === "our") {
sourceSnapshots = snapshots;
members = latest.members;
} else {
const txEnemy = db.transaction("enemySnapshots", "readonly");
const enemyRaw = await new Promise(resolve => {
const req = txEnemy.objectStore("enemySnapshots").getAll();
req.onsuccess = () => resolve(req.result || []);
});
const enemySnaps = enemyRaw
.filter(s => s.factionID === factionID)
.sort((a, b) => a.timestamp - b.timestamp);
if (!enemySnaps.length) {
container.innerHTML = "<p>No data for that faction yet.</p>";
return;
}
sourceSnapshots = enemySnaps;
members = enemySnaps[enemySnaps.length - 1].members;
}
const windowSnaps = sourceSnapshots.filter(s => s.timestamp >= windowStart);
container.innerHTML = `
<table id="intel">
<thead>
<tr>
<th data-type="text">Name</th>
<th data-type="number">Lvl</th>
<th data-type="number">Inactive (hrs)</th>
<th data-type="number">Streak (hrs)</th>
<th data-type="number">Activity</th>
<th data-type="number">Hosp Events</th>
<th data-type="text">Hosp Time</th>
<th data-type="number">Flights</th>
<th data-type="text">Flight Time</th>
<th data-type="number">Total Hits</th>
<th data-type="number">Chain Attacks</th>
<th data-type="number">Chain Saves</th>
<th data-type="number">Score</th>
${factionID === "our" ? '<th data-type="number">Xanax Used</th>' : ''}
</tr>
</thead>
<tbody></tbody>
</table>
`;
const tbody = container.querySelector("tbody");
async function getXanaxCount(memberID) {
const tx = db.transaction("xanaxUsage", "readonly");
const store = tx.objectStore("xanaxUsage");
return new Promise(resolve => {
const req = store.getAll();
req.onsuccess = () => {
const events = req.result || [];
const total = events.filter(e => e.memberID == memberID).length;
resolve(total);
};
req.onerror = () => resolve(0);
});
}
for (const id of Object.keys(members)) {
const member = members[id];
const latestAction = member.last_action.timestamp;
const inactiveHours = ((now/1000 - latestAction) / 3600);
const totalHits = attackStats.totals[member.name] || 0;
const chainAttacks = attackStats.chains[member.name] || 0;
const fastHits = attackStats.fast[member.name] || 0;
let streakStart = latest.timestamp;
for (let i = sourceSnapshots.length - 1; i >= 0; i--) {
const snap = sourceSnapshots[i];
if (!snap.members[id]) break;
if (snap.members[id].last_action.timestamp === latestAction){
streakStart = snap.timestamp;}
else break;
}
const streakHours = ((now - streakStart) / 3600000);
let activity = 0;
let hospEvents = 0;
let hospTime = 0;
let flightEvents = 0;
let flightTime = 0;
let hospStart = null;
let flightStart = null;
for (let i = 1; i < windowSnaps.length; i++) {
const prev = windowSnaps[i-1].members[id];
const curr = windowSnaps[i].members[id];
if (!prev || !curr) continue;
if (prev.last_action.timestamp !== curr.last_action.timestamp){
activity++;}
// Hospital transitions
if (prev.status.state !== "Hospital" && curr.status.state === "Hospital"){
hospStart = windowSnaps[i].timestamp;}
if (prev.status.state === "Hospital" && curr.status.state !== "Hospital") {
if (hospStart !== null) {
hospEvents++;
hospTime += (windowSnaps[i].timestamp - hospStart);
hospStart = null;
}
}
// Travel transitions
if (prev.status.state !== "Traveling" && curr.status.state === "Traveling"){
flightStart = windowSnaps[i].timestamp;}
if (prev.status.state === "Traveling" && curr.status.state !== "Traveling") {
if (flightStart !== null) {
flightEvents++;
flightTime += (windowSnaps[i].timestamp - flightStart);
flightStart = null;
}
}
}
// Clamp if still in state at window start
const firstSnap = windowSnaps[0]?.members[id];
if (firstSnap?.status.state === "Hospital" && hospStart === null){
hospStart = windowSnaps[0].timestamp;}
if (firstSnap?.status.state === "Traveling" && flightStart === null){
flightStart = windowSnaps[0].timestamp;}
if (hospStart !== null){
hospTime += (now - hospStart);}
if (flightStart !== null){
flightTime += (now - flightStart);}
let score =
(activity * 5)
- (inactiveHours * 1.5)
- (hospEvents * 2)
- (flightTime / 3600000);
if (score < 0) score = 0;
const row = doc.createElement("tr");
// Inactivity colour coding
if (inactiveHours <= 2) {
row.classList.add("inactive-good");
} else if (inactiveHours <= 8) {
row.classList.add("inactive-neutral");
} else if (inactiveHours <= 24) {
row.classList.add("inactive-warning");
} else {
row.classList.add("inactive-bad");
}
row.innerHTML = `
<td><a href="https://www.torn.com/profiles.php?XID=${id}" target="_blank">${member.name}</a></td>
<td>${member.level}</td>
<td>${inactiveHours.toFixed(1)}</td>
<td>${streakHours.toFixed(1)}</td>
<td>${activity}</td>
<td>${hospEvents}</td>
<td>${formatDuration(hospTime)}</td>
<td>${flightEvents}</td>
<td>${formatDuration(flightTime)}</td>
<td>${totalHits}</td>
<td>${chainAttacks}</td>
<td>${fastHits}</td>
<td>${score.toFixed(1)}</td>
${factionID === "our" ? `<td>${await getXanaxCount(id)}</td>` : ''}
`;
tbody.appendChild(row);
}
const headers = container.querySelectorAll("th");
headers.forEach((header, index) => {
let asc = true;
header.onclick = () => {
const rows = Array.from(tbody.querySelectorAll("tr"));
const type = header.dataset.type;
rows.sort((a, b) => {
let A = a.children[index].innerText;
let B = b.children[index].innerText;
if (type === "number") {
A = parseFloat(A); B = parseFloat(B);
return asc ? A - B : B - A;
}
return asc ? A.localeCompare(B) : B.localeCompare(A);
});
asc = !asc;
rows.forEach(r => tbody.appendChild(r));
};
});
}
async function buildHourAnalysis(factionID = "our") {
let useTCT = true; // Default to Torn City Time (UTC)
container.innerHTML = "";
const now = Date.now();
const hourSelect = doc.createElement("select");
hourSelect.style.marginRight = "10px";
for (let h = 0; h < 24; h++) {
const opt = doc.createElement("option");
opt.value = h;
opt.textContent = `${h.toString().padStart(2,'0')}:00`;
hourSelect.appendChild(opt);
}
const timeModeSelect = doc.createElement("select");
timeModeSelect.style.marginRight = "10px";
const tctOption = doc.createElement("option");
tctOption.value = "tct";
tctOption.textContent = "TCT (UTC)";
timeModeSelect.appendChild(tctOption);
const localOption = doc.createElement("option");
localOption.value = "local";
localOption.textContent = "Local";
timeModeSelect.appendChild(localOption);
// Default to TCT
timeModeSelect.value = "tct";
timeModeSelect.onchange = () => {
useTCT = timeModeSelect.value === "tct";
render();
};
const windowSelect = doc.createElement("select");
const windowOptions = {
"24 Hours": 1,
"3 Days": 3,
"7 Days": 7,
};
Object.keys(windowOptions).forEach(label => {
const opt = doc.createElement("option");
opt.value = windowOptions[label];
opt.textContent = label;
windowSelect.appendChild(opt);
});
container.appendChild(timeModeSelect);
container.appendChild(hourSelect);
container.appendChild(windowSelect);
const tableDiv = doc.createElement("div");
tableDiv.style.marginTop = "20px";
container.appendChild(tableDiv);
async function render() {
const selectedHour = parseInt(hourSelect.value);
const days = parseInt(windowSelect.value);
const windowStart = now - (days * 24 * 60 * 60 * 1000);
let sourceSnapshots = [];
if (factionID === "our") {
sourceSnapshots = snapshots;
} else {
const txEnemy = db.transaction("enemySnapshots", "readonly");
const enemyRaw = await new Promise(resolve => {
const req = txEnemy.objectStore("enemySnapshots").getAll();
req.onsuccess = () => resolve(req.result || []);
});
sourceSnapshots = enemyRaw
.filter(s => s.factionID === factionID)
.sort((a, b) => a.timestamp - b.timestamp);
}
const filtered = sourceSnapshots.filter(s => s.timestamp >= windowStart);
if (filtered.length < 2) {
tableDiv.innerHTML = "<p>Not enough data for this faction.</p>";
return;
}
const counts = {};
for (let i = 1; i < filtered.length; i++) {
const currSnap = filtered[i];
const prevSnap = filtered[i-1];
const dateObj = new Date(currSnap.timestamp);
const snapHour = useTCT ? dateObj.getUTCHours() : dateObj.getHours();
if (snapHour !== selectedHour) continue;
Object.keys(currSnap.members).forEach(id => {
const curr = currSnap.members[id];
const prev = prevSnap.members[id];
if (!curr || !prev) return;
if (curr.last_action.timestamp !== prev.last_action.timestamp) {
if (!counts[id]) counts[id] = 0;
counts[id]++;
}
});
}
tableDiv.innerHTML = `
<table>
<thead>
<tr>
<th data-type="text">Name</th>
<th data-type="number">Activity Count</th>
<th data-type="number">Avg Per Day</th>
</tr>
</thead>
<tbody></tbody>
</table>
`;
const tbody = tableDiv.querySelector("tbody");
Object.keys(counts)
.sort((a, b) => counts[b] - counts[a])
.forEach(id => {
const latestSnap = sourceSnapshots[sourceSnapshots.length - 1];
const member = latestSnap?.members[id];
if (!member) return;
const total = counts[id];
const avg = (total / days).toFixed(2);
const row = doc.createElement("tr");
row.innerHTML = `
<td><a href="https://www.torn.com/profiles.php?XID=${id}" target="_blank">${member.name}</a></td>
<td>${total}</td>
<td>${avg}</td>
`;
tbody.appendChild(row);
});
// Enable sorting
const headers = tableDiv.querySelectorAll("th");
headers.forEach((header, index) => {
let asc = true;
header.onclick = () => {
const rows = Array.from(tbody.querySelectorAll("tr"));
const type = header.dataset.type;
rows.sort((a, b) => {
let A = a.children[index].innerText;
let B = b.children[index].innerText;
if (type === "number") {
A = parseFloat(A);
B = parseFloat(B);
return asc ? A - B : B - A;
}
return asc ? A.localeCompare(B) : B.localeCompare(A);
});
asc = !asc;
rows.forEach(r => tbody.appendChild(r));
};
});
}
hourSelect.onchange = render;
windowSelect.onchange = render;
render();
}
buildTable(WINDOWS["24H"]);
}
// ================= IMPORT / EXPORT =================
async function exportIntel() {
if (!db) {
alert("Database not ready.");
return;
}
const exportAll = (storeName) => {
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readonly");
const store = tx.objectStore(storeName);
const request = store.getAll();
request.onsuccess = () => resolve(request.result || []);
request.onerror = reject;
});
};
try {
const [
snapshots,
enemySnapshots,
attacks,
xanaxUsage,
trackedFactions
] = await Promise.all([
exportAll("snapshots"),
exportAll("enemySnapshots"),
exportAll("attacks"),
exportAll("xanaxUsage"),
exportAll("trackedFactions")
]);
const fullExport = {
exportedAt: Date.now(),
version: "1.5.0",
snapshots,
enemySnapshots,
attacks,
xanaxUsage,
trackedFactions
};
if (
!snapshots.length &&
!enemySnapshots.length &&
!attacks.length &&
!xanaxUsage.length
) {
alert("No intel data to export.");
return;
}
const blob = new Blob(
[JSON.stringify(fullExport, null, 2)],
{ type: "application/json" }
);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "sheepie_full_intel_export.json";
a.click();
URL.revokeObjectURL(url);
alert("Full intel export complete.");
} catch (err) {
console.error("Export failed:", err);
alert("Export failed. Check console.");
}
}
function rebuildDatabase(data) {
// Close existing DB connection if open
if (db) {
db.close();
}
const deleteRequest = indexedDB.deleteDatabase("sheepieIntel");
deleteRequest.onblocked = function() {
console.warn("Delete blocked — another tab or connection still open.");
};
deleteRequest.onerror = function() {
console.error("Delete failed.");
alert("Failed to delete old database.");
};
deleteRequest.onsuccess = function() {
createFreshDatabase(data);
};
}
function createFreshDatabase(data) {
const openRequest = indexedDB.open("sheepieIntel", 1);
openRequest.onupgradeneeded = function(event) {
const db = event.target.result;
// attacks
const attacksStore = db.createObjectStore("attacks", { keyPath: "fingerprint" });
attacksStore.createIndex("attacker", "attacker", { unique: false });
attacksStore.createIndex("chainNumber", "chainNumber", { unique: false });
// enemySnapshots
const enemyStore = db.createObjectStore("enemySnapshots", {
keyPath: "id",
autoIncrement: true
});
enemyStore.createIndex("factionID", "factionID", { unique: false });
enemyStore.createIndex("timestamp", "timestamp", { unique: false });
// snapshots
const snapshotStore = db.createObjectStore("snapshots", {
keyPath: "timestamp"
});
snapshotStore.createIndex("timestamp", "timestamp", { unique: true });
// trackedFactions
db.createObjectStore("trackedFactions", {
keyPath: "factionID"
});
// xanaxUsage
db.createObjectStore("xanaxUsage", {
keyPath: "timestamp",
autoIncrement: true
});
};
openRequest.onsuccess = function(event) {
const db = event.target.result;
const tx = db.transaction(
["attacks", "enemySnapshots", "snapshots", "trackedFactions", "xanaxUsage"],
"readwrite"
);
const attacksStore = tx.objectStore("attacks");
const enemyStore = tx.objectStore("enemySnapshots");
const snapshotStore = tx.objectStore("snapshots");
const trackedStore = tx.objectStore("trackedFactions");
const xanaxStore = tx.objectStore("xanaxUsage");
data.snapshots.forEach(item => snapshotStore.put(item));
data.enemySnapshots.forEach(item => enemyStore.put(item));
data.attacks.forEach(item => attacksStore.put(item));
data.xanaxUsage.forEach(item => xanaxStore.put(item));
data.trackedFactions.forEach(item => trackedStore.put(item));
tx.oncomplete = function() {
alert("Sheepie Intel import complete.");
location.reload();
};
tx.onerror = function(e) {
console.error("Transaction error:", e);
alert("Database rebuild failed.");
};
};
openRequest.onerror = function() {
alert("Failed to open new database.");
};
}
// ================= LOOP =================
function schedule() {
if (!running) return;
setTimeout(async () => {
if (!running) return;
await pullFaction();
try {
await pullTrackedFactions();
} catch (e) {
console.warn("Enemy pull failed", e);
}
schedule();
}, INTERVAL_MS);
}
function start() {
if (!apiKey) return alert("Set API key.");
if (!acquireLock()) return updateStatus("Another tab running.");
running = true;
updateStatus("Running (15m snapshots)");
pullFaction();
pullTrackedFactions();
schedule();
}
function stop() {
running = false;
localStorage.removeItem(LOCK_KEY);
updateStatus("Stopped.");
}
(async () => {
await initDB();
createUI();
updateStorageDisplay();
initChainTracker();
})();
})();
})();
})();