Files
script/OTP NTFY Console (Token).user.js
2026-05-12 21:05:35 +02:00

169 lines
6.5 KiB
JavaScript

// ==UserScript==
// @name OTP NTFY Console (Token)
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Pannello OTP sicuro con Bearer Token e monitoraggio live
// @author Gemini + Gatto
// @match *://www.ilportaledellautomobilista.it/*
// @grant GM_notification
// @grant GM_setClipboard
// ==/UserScript==
(function() {
'use strict';
// --- CONFIGURAZIONE SICURA ---
const BASE_URL = "ntfy.dati-web.it";
// <--- INCOLLA QUI IL TUO TOKEN tk_...
const NTFY_TOKEN = "tk_mh1ox5scgkorten10a7baelpehh6p";
let currentTopic = localStorage.getItem('otp_topic') || "OTP-Gatto";
let mode = localStorage.getItem('otp_gatto_mode') || "notify";
const OTP_REGEX = /(?:^|\s)(\S{8})(?=\s|$)/;
function injectInterface() {
const logo = document.getElementById('logo-portale');
if (!logo) {
setTimeout(injectInterface, 500);
return;
}
const parentLink = logo.closest('a');
const anchorPoint = parentLink ? parentLink : logo;
if (document.getElementById('otp-embedded-panel')) return;
const navPanel = document.createElement('div');
navPanel.id = "otp-embedded-panel";
// Stile del pannello
navPanel.style = `
display: inline-flex;
align-items: center;
gap: 12px;
margin-left: 300px;
margin-top: 16px;
padding: 0 15px;
background: #f8f9fa;
border: 2px solid #0056b3;
border-radius: 8px;
height: 60px;
min-width: 320px;
font-family: 'Segoe UI', Arial, sans-serif;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 9999;
`;
navPanel.innerHTML = `
<div style="display: flex; flex-direction: column; gap: 4px;">
<input type="text" id="topicInput" value="${currentTopic}" placeholder="Topic" style="width: 100px; height: 20px; font-size: 11px; border: 1px solid #ccc; border-radius: 3px; padding: 2px 5px;">
<button id="saveConfig" style="width: 110px; height: 22px; font-size: 10px; background: #0056b3; color: white; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;">AGG. TOPIC</button>
</div>
<div style="border-left: 1px solid #ccc; height: 40px;"></div>
<div style="display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: #333;">
<label style="cursor:pointer; display: flex; align-items: center; gap: 5px;">
<input type="radio" name="otpMode" value="notify" ${mode==='notify'?'checked':''}> Notifica
</label>
<label style="cursor:pointer; display: flex; align-items: center; gap: 5px;">
<input type="radio" name="otpMode" value="type" ${mode==='type'?'checked':''}> Digita
</label>
</div>
<div style="border-left: 1px solid #ccc; height: 40px;"></div>
<div style="text-align: center; flex-grow: 1;">
<div id="lastCodeLabel" style="font-size: 18px; font-weight: bold; color: #d9534f; letter-spacing: 1px;">---</div>
<div id="statusLabel" style="font-size: 9px; color: #28a745; font-weight: bold;">STABILE</div>
</div>
`;
anchorPoint.parentNode.insertBefore(navPanel, anchorPoint.nextSibling);
// Salvataggio impostazioni
document.getElementById('saveConfig').addEventListener('click', () => {
localStorage.setItem('otp_topic', document.getElementById('topicInput').value.trim());
localStorage.setItem('otp_gatto_mode', document.querySelector('input[name="otpMode"]:checked').value);
location.reload();
});
startListening();
}
async function startListening() {
const statusLabel = document.getElementById('statusLabel');
try {
const response = await fetch(`https://${BASE_URL}/${currentTopic}/json`, {
headers: {
"Authorization": `Bearer ${NTFY_TOKEN}`
}
});
if (response.status === 401 || response.status === 403) {
statusLabel.innerText = "ERRORE TOKEN";
statusLabel.style.color = "red";
return;
}
if (!response.ok) throw new Error("Errore connessione");
statusLabel.innerText = "ONLINE";
statusLabel.style.color = "#28a745";
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const data = JSON.parse(line);
if (data.event === 'message' && data.message) {
const match = data.message.match(OTP_REGEX);
if (match) handleOTP(match[1]);
}
} catch (e) { console.error("Errore parsing JSON", e); }
}
}
} catch (err) {
statusLabel.innerText = "RE-CONNECTING...";
statusLabel.style.color = "orange";
setTimeout(startListening, 5000);
}
}
function handleOTP(newOtp) {
document.getElementById('lastCodeLabel').innerText = newOtp;
GM_setClipboard(newOtp);
// Notifica di sistema
GM_notification({
title: "OTP Infocert",
text: `Codice: ${newOtp} (Copiato)`,
timeout: 5000
});
// Se la modalità è "Digita", prova a inserirlo nel primo campo input vuoto trovato
if (document.querySelector('input[name="otpMode"]:checked').value === 'type') {
const otpInput = document.querySelector('input[type="text"], input[maxlength="8"]');
if (otpInput) {
otpInput.value = newOtp;
otpInput.dispatchEvent(new Event('input', { bubbles: true }));
}
}
}
if (document.readyState === "complete" || document.readyState === "interactive") {
injectInterface();
} else {
document.addEventListener("DOMContentLoaded", injectInterface);
}
})();