A modular, secure API key manager with native TornPDA support and dynamic user detection.
Ezt a szkriptet nem ajánlott közvetlenül telepíteni. Ez egy könyvtár más szkriptek számára, amik tartalmazzák a // @require https://update.greatest.deepsurf.us/scripts/575462/1808747/Rostoll%27s%20Secure%20Keymaster%20%28Profile%20Edition%29.js hivatkozást.
// ==UserScript==
// @name Rostoll's Keymaster Library
// @namespace https://greatest.deepsurf.us/en/users/1594626-rostoll-3936240
// @version 4.0
// @description Secure API key manager library for Rostoll's Torn scripts.
// @author Rostoll [3936240]
// @license MIT
// @match https://www.torn.com/*
// @grant none
// ==/UserScript==
/* jshint esversion: 11 */
class RostollKeymaster {
constructor(config) {
this.scriptName = config.scriptName || 'Unknown Script';
this.storageKey = config.storageKey || 'rst_default_api_key';
this.pdaToken = config.pdaToken || '';
this.isPDA = this.pdaToken.indexOf("#") !== 0 && this.pdaToken.length === 16;
this.init();
}
async getKey() {
if (this.isPDA) return this.pdaToken;
const key = await GM_getValue(this.storageKey, '');
return key.length === 16 ? key : null;
}
init() {
if (window.self !== window.top) return; // Anti-iframe lock
this.injectStyles();
this.checkGlobalKey();
this.injectProfileUI();
}
injectStyles() {
if (document.getElementById('rst-keymaster-styles')) return;
const style = document.createElement('style');
style.id = 'rst-keymaster-styles';
style.innerHTML = `
#rst-profile-settings { margin: 10px 0; background: #242424; border-radius: 5px; border: 1px solid #333; color: #ccc; font-family: Arial, sans-serif; overflow: hidden; box-shadow: 0px 2px 4px rgba(0,0,0,0.5); }
#rst-prof-header { background: #333; padding: 10px 15px; cursor: pointer; font-weight: bold; display: flex; align-items: center; color: #ddd; transition: background 0.2s; }
#rst-prof-header:hover { background: #3d3d3d; }
#rst-prof-icon { margin-right: 8px; font-size: 12px; transition: transform 0.2s; }
#rst-prof-body { display: none; padding: 15px; border-top: 1px solid #444; }
.rst-script-block { border-bottom: 1px dashed #444; padding-bottom: 15px; margin-bottom: 15px; }
.rst-script-block:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }
.rst-prof-row { display: flex; align-items: center; margin-bottom: 10px; gap: 15px; }
.rst-script-title { font-size: 14px; font-weight: bold; color: #4caf50; margin-bottom: 10px; }
.rst-prof-label { min-width: 80px; font-size: 13px; color: #aaa; }
.rst-api-input { flex: 1; max-width: 250px; background: #111; border: 1px solid #555; color: #4caf50; padding: 6px 10px; border-radius: 4px; font-family: monospace; font-size: 14px; letter-spacing: 1px; transition: filter 0.3s, border-color 0.3s; }
.rst-api-input:focus { outline: none; border-color: #4caf50; filter: blur(0px) !important; }
.rst-blur { filter: blur(4px); }
.rst-btn { padding: 6px 12px; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; font-size: 11px; transition: background 0.2s; }
.rst-btn-verify { background: #333; color: #fff; border: 1px solid #555; }
.rst-btn-verify:hover { background: #444; }
.rst-btn-clear { background: #5a1e1e; color: #fff; border: 1px solid #7a2828; }
.rst-btn-clear:hover { background: #7a2828; }
.rst-status-box { font-size: 11px; font-weight: bold; margin-top: 5px; }
.rst-status-bad { color: #ff6600; }
.rst-status-good { color: #4caf50; }
.rst-status-wait { color: #88beff; }
#rst-global-banner { position: relative; display: block; width: 100%; background: #2a0808; border-bottom: 2px solid #ff4444; color: #fff; text-align: center; padding: 10px 5px; font-family: sans-serif; font-size: 13px; z-index: 9999999; box-sizing: border-box; }
#rst-global-banner a { color: #ffaa00; font-weight: bold; text-decoration: none; margin-left: 5px; }
#rst-global-banner a:hover { text-decoration: underline; }
`;
document.head.appendChild(style);
}
checkGlobalKey() {
if (this.isPDA) return;
const key = GM_getValue(this.storageKey, '');
const isProfilePage = window.location.href.includes('profiles.php');
if (key.length !== 16 && !isProfilePage) {
if (!document.getElementById('rst-global-banner')) {
const banner = document.createElement('div');
banner.id = 'rst-global-banner';
banner.innerHTML = `⚠️ A Rostoll Script requires an API Key. <a href="/profiles.php?rstFocus=true">Tap here to set it up ➔</a>`;
document.body.prepend(banner);
}
}
}
injectProfileUI() {
if (!window.location.href.includes('profiles.php')) return;
const myIdMatch = document.cookie.match(/uid=(\d+)/);
const myId = myIdMatch ? myIdMatch[1] : null;
const urlParams = new URLSearchParams(window.location.search);
const pageXid = urlParams.get('XID');
const shouldFocus = urlParams.get('rstFocus') === 'true';
if (pageXid && pageXid !== myId) return;
let attempts = 0;
const waitInterval = setInterval(() => {
attempts++;
if (attempts >= 20) return clearInterval(waitInterval);
const allHeaders = Array.from(document.querySelectorAll('div[class*="title"]'));
const medalsHeader = allHeaders.find(h => h.textContent.trim() === 'Medals' || h.textContent.includes('Medals'));
let targetAnchor = medalsHeader ? medalsHeader.closest('div[class*="box-info"], div[class*="profile-wrapper"], div[class*="mt-"]') : null;
if (!targetAnchor) {
const basicInfoHeader = allHeaders.find(h => h.textContent.includes('Basic Information'));
targetAnchor = basicInfoHeader ? basicInfoHeader.closest('div[class*="box-info"], div[class*="profile-wrapper"], div[class*="mt-"]') : null;
}
if (!targetAnchor) return;
clearInterval(waitInterval);
this.buildAccordion(targetAnchor, shouldFocus);
}, 500);
}
buildAccordion(targetAnchor, shouldFocus) {
let accordion = document.getElementById('rst-profile-settings');
let body;
// Create the master accordion if it doesn't exist yet
if (!accordion) {
accordion = document.createElement('div');
accordion.id = 'rst-profile-settings';
accordion.innerHTML = `
<div id="rst-prof-header">
<span id="rst-prof-icon">▶</span>
<span>Rostoll's Script Settings</span>
</div>
<div id="rst-prof-body">
<p style="font-size: 11px; margin-bottom: 15px; color: #888; border-bottom: 1px solid #444; padding-bottom: 10px;">
Manage your API Keys below. Each script requires its own key for maximum security. Keys are stored locally and never leave your browser.
</p>
</div>
`;
targetAnchor.parentNode.insertBefore(accordion, targetAnchor);
const header = document.getElementById('rst-prof-header');
body = document.getElementById('rst-prof-body');
const icon = document.getElementById('rst-prof-icon');
header.addEventListener('click', () => {
const isOpen = body.style.display === 'block';
body.style.display = isOpen ? 'none' : 'block';
icon.innerHTML = isOpen ? '▶' : '▼';
});
} else {
body = document.getElementById('rst-prof-body');
}
// Check if this script's row already exists
if (document.getElementById(`rst-block-${this.storageKey}`)) return;
const savedKey = GM_getValue(this.storageKey, '');
const hasKey = savedKey.length === 16;
const block = document.createElement('div');
block.className = 'rst-script-block';
block.id = `rst-block-${this.storageKey}`;
let uiContent = this.isPDA ? `
<div class="rst-prof-row">
<span class="rst-prof-label">API Key:</span>
<span style="color: #888; font-family: monospace; font-size: 12px;">Managed securely by Torn PDA</span>
</div>
<div class="rst-status-box rst-status-good">🟢 Status: Active via Torn PDA</div>
` : `
<div class="rst-prof-row">
<span class="rst-prof-label">API Key:</span>
<input type="text" id="rst-api-input-${this.storageKey}" class="rst-api-input ${hasKey ? 'rst-blur' : ''}" placeholder="Paste 16-char key..." spellcheck="false" autocomplete="off" maxlength="16" value="${savedKey}">
</div>
<div class="rst-prof-row" style="margin-bottom: 5px;">
<span class="rst-prof-label"></span>
<button class="rst-btn rst-btn-verify" id="rst-btn-verify-${this.storageKey}">VERIFY & SAVE</button>
<button class="rst-btn rst-btn-clear" id="rst-btn-clear-${this.storageKey}">CLEAR</button>
</div>
<div id="rst-prof-status-${this.storageKey}" class="rst-status-box ${hasKey ? 'rst-status-good' : 'rst-status-bad'}">
${hasKey ? '🟢 Status: Key Verified & Active' : '🔴 Status: No Key Found'}
</div>
`;
block.innerHTML = `<div class="rst-script-title">⚙️ ${this.scriptName}</div>${uiContent}`;
body.appendChild(block);
// Auto-open and scroll if a key is missing or Focus was requested
if (shouldFocus || (!hasKey && !this.isPDA)) {
body.style.display = 'block';
document.getElementById('rst-prof-icon').innerHTML = '▼';
setTimeout(() => {
accordion.scrollIntoView({ behavior: 'smooth', block: 'center' });
block.style.background = 'rgba(255, 170, 0, 0.1)';
setTimeout(() => block.style.background = 'transparent', 2000);
}, 300);
}
// Bind events if not PDA
if (!this.isPDA) this.bindEvents();
}
bindEvents() {
const input = document.getElementById(`rst-api-input-${this.storageKey}`);
const verifyBtn = document.getElementById(`rst-btn-verify-${this.storageKey}`);
const clearBtn = document.getElementById(`rst-btn-clear-${this.storageKey}`);
const statusBox = document.getElementById(`rst-prof-status-${this.storageKey}`);
input.addEventListener('blur', () => {
if (input.value.length > 0) input.classList.add('rst-blur');
});
verifyBtn.addEventListener('click', () => {
const val = input.value.trim();
if (val.length === 16) {
statusBox.className = 'rst-status-box rst-status-wait';
statusBox.innerHTML = '⏳ Verifying with Torn API...';
verifyBtn.disabled = true;
GM_xmlhttpRequest({
method: "GET",
url: `https://api.torn.com/user/?selections=profile&key=${val}`,
onload: (response) => {
verifyBtn.disabled = false;
try {
const data = JSON.parse(response.responseText);
if (data.error) {
statusBox.className = 'rst-status-box rst-status-bad';
statusBox.innerHTML = `⚠️ Error: ${data.error.error}`;
} else {
GM_setValue(this.storageKey, val);
input.classList.add('rst-blur');
statusBox.className = 'rst-status-box rst-status-good';
statusBox.innerHTML = `🟢 Welcome, ${data.name}! Key Saved.`;
const banner = document.getElementById('rst-global-banner');
if (banner) banner.remove();
}
} catch (e) {
statusBox.className = 'rst-status-box rst-status-bad';
statusBox.innerHTML = '⚠️ Error parsing response.';
}
},
onerror: () => {
verifyBtn.disabled = false;
statusBox.className = 'rst-status-box rst-status-bad';
statusBox.innerHTML = '⚠️ Network error.';
}
});
} else {
statusBox.className = 'rst-status-box rst-status-bad';
statusBox.innerHTML = '⚠️ Key must be exactly 16 chars!';
}
});
clearBtn.addEventListener('click', () => {
GM_setValue(this.storageKey, '');
input.value = '';
input.classList.remove('rst-blur');
statusBox.className = 'rst-status-box rst-status-bad';
statusBox.innerHTML = '🔴 Key Cleared.';
});
}
}