Injects a GUIDE button on each crime page linking to the relevant forum guide
// ==UserScript==
// @name Torn Crimes Guide Button
// @namespace https://www.torn.com/
// @version 1.1.0
// @description Injects a GUIDE button on each crime page linking to the relevant forum guide
// @author True
// @match https://www.torn.com/page.php?sid=crimes*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const guideLinks = {
'': 'https://www.torn.com/forums.php#p=threads&f=61&t=16385729', // hub
'searchforcash': 'https://www.torn.com/forums.php#p=threads&f=61&t=16343473',
'bootlegging': 'https://www.torn.com/forums.php#p=threads&f=61&t=16341811',
'graffiti': 'https://www.torn.com/forums.php#p=threads&f=61&t=16344567',
'shoplifting': 'https://www.torn.com/forums.php#p=threads&f=61&t=16346491',
'pickpocketing': 'https://www.torn.com/forums.php#p=threads&f=61&t=16358739',
'cardskimming': 'https://www.torn.com/forums.php#p=threads&f=61&t=16350490',
'burglary': 'https://www.torn.com/forums.php#p=threads&f=61&t=16353303',
'hustling': 'https://www.torn.com/forums.php#p=threads&f=61&t=16363421',
'disposal': 'https://www.torn.com/forums.php#p=threads&f=61&t=16367936',
'cracking': 'https://www.torn.com/forums.php#p=threads&f=61&t=16373016',
'forgery': 'https://www.torn.com/forums.php#p=threads&f=61&t=16388086',
'scamming': 'https://www.torn.com/forums.php#p=threads&f=61&t=16418415',
'arson': 'https://www.torn.com/forums.php#p=threads&f=61&t=16510947',
};
const BUTTON_ID = 'torn-guide-btn';
function getCrimeKey() {
// Hash is like #/scamming or #/ (hub) — strip the leading #/
const hash = window.location.hash.replace(/^#\/?/, '');
return hash.toLowerCase();
}
function injectButton(url) {
// Remove any existing button first
const existing = document.getElementById(BUTTON_ID);
if (existing) existing.remove();
// Target: h4[class*="heading___"] inside .appHeader — the crime title element.
// Insert the button as a sibling immediately after the h4.
const titleEl = document.querySelector('.crimes-app [class*="heading___"]');
if (!titleEl) {
console.warn('[GuideBtn] Could not find crime title element (h4[class*="heading___"]). Check selector.');
return;
}
const btn = document.createElement('a');
btn.id = BUTTON_ID;
btn.href = url;
btn.target = '_blank';
btn.rel = 'noopener noreferrer';
btn.textContent = 'GUIDE';
btn.title = 'Open guide for this crime';
Object.assign(btn.style, {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2px 9px',
marginLeft: '10px',
background: '#2a6496',
color: '#fff',
fontSize: '12px',
fontWeight: '700',
fontFamily: 'inherit',
letterSpacing: '0.06em',
borderRadius: '3px',
textDecoration: 'none',
border: '1px solid #1a4a70',
cursor: 'pointer',
verticalAlign: 'middle',
lineHeight: '1.8',
transition: 'background 0.15s',
});
btn.addEventListener('mouseenter', () => { btn.style.background = '#1a4a70'; });
btn.addEventListener('mouseleave', () => { btn.style.background = '#2a6496'; });
// Insert right after the h4 title, before the "Back to Hub" link
titleEl.insertAdjacentElement('afterend', btn);
}
function update() {
const key = getCrimeKey();
const url = guideLinks[key];
if (url) {
// Small delay to let Torn's dynamic content finish rendering
setTimeout(() => injectButton(url), 400);
} else {
const existing = document.getElementById(BUTTON_ID);
if (existing) existing.remove();
}
}
// ── Initial run ───────────────────────────────────────────────────────────
update();
// ── Re-run on hash change (Torn's SPA routing) ────────────────────────────
window.addEventListener('hashchange', update);
// ── MutationObserver: safety net for re-renders wiping the button ─────────
let lastHash = window.location.hash;
const observer = new MutationObserver(() => {
// Catch hash changes that don't fire the hashchange event
if (window.location.hash !== lastHash) {
lastHash = window.location.hash;
update();
return;
}
// Re-inject if Torn re-rendered the header and wiped our button
const key = getCrimeKey();
if (guideLinks[key] && !document.getElementById(BUTTON_ID)) {
injectButton(guideLinks[key]);
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();