summaryrefslogtreecommitdiffstats
path: root/index.php
diff options
context:
space:
mode:
authorGab <24553253+gabrix73@users.noreply.github.com>2026-06-29 00:13:23 +0200
committerGab <24553253+gabrix73@users.noreply.github.com>2026-06-29 00:13:23 +0200
commit1d1d06c025be8b16a3535ecceffc26fe303977db (patch)
treecc814b265bcaa2711f092f53349cc66068a6256a /index.php
parent322a17dba0d44200f6c241b18a58761220050aba (diff)
downloadm2usenet-and-mail2news-1d1d06c025be8b16a3535ecceffc26fe303977db.tar.gz
m2usenet-and-mail2news-1d1d06c025be8b16a3535ecceffc26fe303977db.tar.xz
m2usenet-and-mail2news-1d1d06c025be8b16a3535ecceffc26fe303977db.zip
Update m2usenet UI and backend; add identicon renderer and nacl libs
- index.php, send.php: updated client UI - m2usenet.go: backend changes - identicon.php: new identicon renderer - nacl.min.js, nacl-util.min.js: vendored client crypto libs
Diffstat (limited to 'index.php')
-rw-r--r--index.php720
1 files changed, 555 insertions, 165 deletions
diff --git a/index.php b/index.php
index 62497e9..4cec619 100644
--- a/index.php
+++ b/index.php
@@ -1,16 +1,15 @@
<?php
// /var/www/m2usenet/index.php
-// m2usenet v2.1.0 - Hardened Gateway Interface
+// m2usenet v2.6.0 - Face Header + Identity Hash v2
-// Start session for CSRF token
if (session_status() === PHP_SESSION_NONE) {
ini_set('session.cookie_httponly', 1);
- ini_set('session.cookie_secure', 1);
+ ini_set('session.cookie_secure', 0); // .onion non ha TLS
ini_set('session.use_strict_mode', 1);
+ ini_set('session.cookie_samesite', 'Lax'); // Strict può bloccare su .onion
session_start();
}
-// Generate CSRF token
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
@@ -21,7 +20,7 @@ $csrfToken = $_SESSION['csrf_token'];
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>m2usenet Gateway v2.1</title>
+ <title>m2usenet Gateway v2.6</title>
<style>
:root {
--background: #f9f9f9;
@@ -140,6 +139,12 @@ $csrfToken = $_SESSION['csrf_token'];
to { transform: translateX(0); opacity: 1; }
}
+ @keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 rgba(156, 39, 176, 0.7); }
+ 50% { box-shadow: 0 0 0 15px rgba(156, 39, 176, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(156, 39, 176, 0); }
+ }
+
.notification-icon { font-size: 1.5em; }
.notification-close { margin-left: auto; cursor: pointer; font-size: 1.2em; opacity: 0.8; }
.notification-close:hover { opacity: 1; }
@@ -195,7 +200,7 @@ $csrfToken = $_SESSION['csrf_token'];
}
.tab-content {
- display: none;
+ display: none !important;
padding: 20px;
background: var(--card-bg);
border: 1px solid var(--border);
@@ -205,7 +210,7 @@ $csrfToken = $_SESSION['csrf_token'];
}
.tab-content.active {
- display: block;
+ display: block !important;
}
label {
@@ -266,6 +271,11 @@ $csrfToken = $_SESSION['csrf_token'];
button:hover:not(:disabled) { background: var(--primary-hover); }
button:disabled { opacity: 0.6; cursor: not-allowed; }
+ .btn-secondary {
+ background: #6c757d;
+ }
+ .btn-secondary:hover:not(:disabled) { background: #5a6268; }
+
.progress-bar {
width: 100%;
background: var(--progress-bg);
@@ -329,9 +339,6 @@ $csrfToken = $_SESSION['csrf_token'];
text-align: center;
}
- .gateway-priority.secondary { background: #6c757d; }
- .gateway-priority.fallback { background: #ffc107; color: #333; }
-
.gateway-address {
font-family: monospace;
background: var(--input-readonly);
@@ -357,21 +364,123 @@ $csrfToken = $_SESSION['csrf_token'];
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
}
- .prefill-banner-icon {
- font-size: 1.5em;
+ .prefill-banner-icon { font-size: 1.5em; }
+ .prefill-banner-text { flex: 1; }
+ .prefill-banner-text strong { display: block; margin-bottom: 2px; }
+ .prefill-banner-text small { opacity: 0.9; }
+
+ .keypair-actions {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ margin-top: 15px;
}
- .prefill-banner-text {
+ .keypair-actions button {
flex: 1;
+ min-width: 150px;
}
- .prefill-banner-text strong {
- display: block;
- margin-bottom: 2px;
+ .identity-loaded {
+ background: #d4edda;
+ border: 1px solid #c3e6cb;
+ padding: 10px 15px;
+ border-radius: 4px;
+ margin-top: 15px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ .dark-theme .identity-loaded {
+ background: #1e3d1e;
+ border-color: #2e5d2e;
+ }
+
+ .identity-loaded-icon { font-size: 1.5em; }
+ .identity-loaded-text { flex: 1; }
+
+ .identicon-preview {
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 10px;
+ }
+
+ .identicon-preview img {
+ width: 32px;
+ height: 32px;
+ image-rendering: pixelated;
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ }
+
+ .identicon-section {
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-left: 4px solid #9c27b0;
+ padding: 15px;
+ margin: 15px 0;
+ border-radius: 0 4px 4px 0;
+ text-align: center;
+ }
+
+ .identicon-section h4 {
+ margin: 0 0 15px 0;
+ color: #9c27b0;
+ }
+
+ .identicon-large-preview {
+ margin: 15px 0;
+ }
+
+ .identicon-hash {
+ font-size: 0.8em;
+ color: #666;
+ word-break: break-all;
+ margin: 10px 0;
+ }
+
+ .identicon-hash code {
+ background: var(--input-readonly);
+ padding: 4px 8px;
+ border-radius: 4px;
+ display: inline-block;
+ max-width: 100%;
+ overflow-x: auto;
+ }
+
+ .identicon-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin: 15px 0;
+ }
+
+ .identicon-actions button {
+ margin: 0;
+ padding: 8px 15px;
+ font-size: 0.9em;
+ }
+
+ .identicon-tip {
+ background: #f3e5f5;
+ padding: 10px;
+ border-radius: 4px;
+ margin-top: 10px;
}
- .prefill-banner-text small {
- opacity: 0.9;
+ .dark-theme .identicon-tip {
+ background: #2d1f30;
+ }
+
+ .dark-theme #saveIdentityWarning {
+ background: linear-gradient(135deg, #3d3510 0%, #4a4012 100%);
+ border-color: #856404;
+ }
+
+ .dark-theme #saveIdentityWarning p {
+ color: #ffc107;
}
footer {
@@ -408,15 +517,17 @@ $csrfToken = $_SESSION['csrf_token'];
.gateway-item { flex-direction: column; align-items: flex-start; gap: 5px; }
.gateway-address { width: 100%; }
.notification { right: 10px; left: 10px; max-width: none; }
+ .keypair-actions { flex-direction: column; }
+ .keypair-actions button { min-width: 100%; }
}
</style>
- <script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/tweetnacl-util@0.15.1/nacl-util.min.js"></script>
+ <script src="nacl.min.js"></script>
+ <script src="nacl-util.min.js"></script>
</head>
<body>
<div class="container">
<header>
- <h1>m2usenet Gateway v2.1</h1>
+ <h1>m2usenet Gateway v2.6</h1>
<div class="theme-toggle">
<span>🌞</span>
<input type="checkbox" id="themeToggle">
@@ -425,7 +536,6 @@ $csrfToken = $_SESSION['csrf_token'];
</div>
</header>
- <!-- Prefill banner - shown when coming from onion-newsreader -->
<div id="prefillBanner" class="prefill-banner" style="display: none;">
<span class="prefill-banner-icon">📝</span>
<div class="prefill-banner-text">
@@ -439,7 +549,7 @@ $csrfToken = $_SESSION['csrf_token'];
<span>1. Generate Hashcash Token</span>
</button>
<button id="tabBtn2" onclick="showTab('sign')">
- <span>2. Sign Message</span>
+ <span>2. Sign Message (Identity)</span>
</button>
<button id="tabBtn3" onclick="showTab('send')">
<span>3. Send Message</span>
@@ -447,13 +557,33 @@ $csrfToken = $_SESSION['csrf_token'];
</div>
<div id="pow" class="tab-content active">
- <h2>Proof-of-Work Token</h2>
+ <h2>1. Load Identity or Start Fresh</h2>
+
+ <div class="identity-loader" style="background: var(--card-bg); border: 2px dashed var(--primary); border-radius: 8px; padding: 20px; margin-bottom: 20px; text-align: center;">
+ <p style="margin: 0 0 15px 0;"><strong>🔑 Have a saved identity?</strong> Load it to auto-fill all fields.</p>
+ <button id="loadIdentityBtn" class="btn-primary" style="font-size: 1.1em; padding: 12px 30px;">📂 Load Identity File</button>
+ <input type="file" id="identityFileInput" accept=".json" style="display:none;">
+ <p style="margin: 15px 0 0 0; font-size: 0.9em; color: var(--text); opacity: 0.7;">Or fill the fields below to create a new identity.</p>
+ </div>
+
+ <div id="identityLoadedPow" class="identity-loaded" style="display:none; margin-bottom: 20px;">
+ <span class="identity-loaded-icon">✅</span>
+ <div class="identity-loaded-text">
+ <strong id="loadedIdentityName">Identity Loaded</strong>
+ <small id="loadedIdentityEmail">email@example.com</small>
+ </div>
+ <div class="identicon-preview">
+ <img id="identiconPreviewPow" alt="Your identicon" style="width: 48px; height: 48px; image-rendering: pixelated; border-radius: 4px;">
+ </div>
+ </div>
+
<div class="section-info">
- <p><strong>About m2usenet:</strong> This application is a privacy-focused gateway that sends your messages to Usenet newsgroups via our mail2news gateways on the onion network. No access logs are collected, and your messages are routed through secure gateways.</p>
- <p><strong>What is this?</strong> This step generates a "proof-of-work" token (hashcash) that prevents spam by requiring your computer to perform some calculations. This is similar to how cryptocurrencies work - you need to "mine" a valid token before sending a message.</p>
- <p><strong>How to use:</strong> Enter your email address, select the difficulty level (higher bits = longer processing time), and click "Generate Token". Your browser will mine a valid token that will be required in the next steps.</p>
+ <p><strong>About m2usenet:</strong> A privacy-focused gateway that posts to Usenet newsgroups over Tor hidden services. No access logs are kept. To post you must first create &mdash; or load &mdash; a <strong>VFACE pseudonymous identity</strong>: an Ed25519 keypair whose public key deterministically generates your identicon. Same key &rarr; same identicon &rarr; same person over time.</p>
+ <p><strong>What is Proof-of-Work?</strong> This step generates a hashcash token that prevents spam by requiring your computer to perform some calculations.</p>
</div>
- <label>Email (resource): <input type="email" id="hcEmail" placeholder="your@email.com" required></label>
+
+ <label>Username (your pseudonym): <input type="text" id="fromName" placeholder="YourPseudonym" required></label>
+ <label>Email (for identity hash): <input type="email" id="hcEmail" placeholder="your@email.onion" required></label>
<label>Difficulty (bits):
<select id="hcBits">
<option value="16">16 bits (very fast, ~instant - recommended for Tor Browser)</option>
@@ -469,30 +599,65 @@ $csrfToken = $_SESSION['csrf_token'];
</div>
<div id="sign" class="tab-content">
- <h2>Ed25519 Digital Signature</h2>
+ <h2>2. Sign Your Message</h2>
+
+ <div id="identiconSection" class="identicon-section" style="display:none;">
+ <h4>🎨 Your Visual Identity</h4>
+ <div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap;">
+ <div class="identicon-large-preview">
+ <img id="identiconPreviewLarge" alt="Your identicon" style="width:96px;height:96px;image-rendering:pixelated;border:2px solid var(--border);border-radius:8px;">
+ </div>
+ <div style="flex: 1; min-width: 200px;">
+ <p style="margin: 0 0 5px 0;"><strong id="identityUsername">Username</strong></p>
+ <p style="margin: 0 0 10px 0; font-size: 0.9em; color: var(--text); opacity: 0.8;" id="identityEmail">email@example.com</p>
+ <p class="identicon-hash" style="margin: 0;">Hash: <code id="identityHashDisplay"></code></p>
+ </div>
+ </div>
+ <div class="identicon-actions" style="margin-top: 15px;">
+ <button type="button" id="downloadIdenticonBtn" class="btn-secondary">📥 Identicon</button>
+ <button type="button" id="saveIdentityBtn" class="btn-secondary">💾 Save Identity</button>
+ </div>
+ <div id="saveIdentityWarning" style="margin-top: 15px; padding: 12px; background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%); border: 1px solid #ffc107; border-radius: 6px; text-align: center;">
+ <p style="margin: 0; color: #856404; font-weight: bold;">⚠️ You must save your identity to continue!</p>
+ <p style="margin: 5px 0 0 0; color: #856404; font-size: 0.9em;">Click "Save Identity" above to download your identity file. This is required before signing and sending messages.</p>
+ </div>
+ <p style="margin-top: 15px; text-align: center; font-size: 0.9em;">
+ <a href="https://identicons.virebent.art" target="_blank" rel="noopener noreferrer" style="color: #9c27b0; text-decoration: none;">🔐 VFACE Verifier - Verify an identity</a>
+ </p>
+ </div>
+
<div class="section-info">
- <p><strong>What is this?</strong> This step creates a digital signature for your message using the Ed25519 cryptographic algorithm. This signature helps verify that the message was sent by you and hasn't been tampered with.</p>
- <p><strong>How to use:</strong> First, generate a key pair (this creates a public and private key). Then, write your message and click "Sign Message". This will create a digital signature that will be attached to your post.</p>
+ <p><strong>What is this?</strong> Your keypair is generated and your message is signed <strong>locally in your browser</strong> with the self-hosted TweetNaCl (<code>nacl</code>) library &mdash; your Ed25519 secret key never leaves your device. Only the public identity (username, email, public key) is sent, to render the identicon. The signature proves you authored this post.</p>
+ <p><strong>VFACE / Face Header:</strong> Your identicon is produced by the original <code>identicons-cli</code> engine &mdash; the same backend as <code>identicons.virebent.art</code> &mdash; so it is fully deterministic: the same key always yields the same identicon. It is embedded as a folded <code>Face:</code> header (RFC&nbsp;4021/2822) alongside <code>X-Ed25519-Pub</code> and <code>X-Ed25519-Sig</code>, visible in newsreaders like Newsgrouper and re-verifiable by any reader from your public key.</p>
</div>
- <label>Email used for PoW:</label>
+
+ <label>Email (from PoW):</label>
<input type="text" id="readonlyEmailSign" readonly>
+
<label>Message to Sign:</label>
<textarea id="messageToSign" rows="6" placeholder="Write your message here..." required></textarea>
- <button id="genKeyBtn">Generate Key Pair</button>
- <button id="signMsgBtn" disabled>Sign Message</button>
- <label>Generated Public Key:</label>
+
+ <div class="keypair-actions">
+ <button id="genKeyBtn">🔑 Generate New Keypair</button>
+ </div>
+ <input type="file" id="keyFileInput" accept=".json" style="display:none;">
+
+ <button id="signMsgBtn" disabled>✍️ Sign Message</button>
+
+ <label>Public Key:</label>
<div id="pubKeyOutput" class="output-field empty">Public key will appear here</div>
- <label>Generated Signature:</label>
+ <label>Signature:</label>
<div id="signatureOutput" class="output-field empty">Signature will appear here</div>
</div>
<div id="send" class="tab-content">
<h2>Send Message</h2>
<div class="section-info">
- <p><strong>What is this?</strong> This final step sends your signed message to the Usenet network via mail2news gateways. m2usenet v2.1.0 uses Tor for all connections with automatic fallback.</p>
+ <p><strong>What is this?</strong> This final step sends your signed message to the Usenet network via Tor hidden services.</p>
<div class="gateway-info">
<h4>🔐 Full Onion Network Path</h4>
<p style="margin-bottom: 15px; font-size: 0.95em;">Your message travels entirely within the Tor network:</p>
+
<div class="gateway-item">
<span class="gateway-priority">Step 1</span>
<div style="flex: 1;">
@@ -503,18 +668,20 @@ $csrfToken = $_SESSION['csrf_token'];
<details style="margin-top: 8px; font-size: 0.8em;">
<summary style="cursor: pointer; color: #007bff;">Show relay nodes</summary>
<ul style="margin: 8px 0; padding-left: 20px; font-family: monospace; color: #555;">
- <li>4uwpi53u524xdphjw2dv5kywsxmyjxtk4facb76jgl3sc3nda3sz4fqd.onion:25</li>
- <li>xilb7y4kj6u6qfo45o3yk2kilfv54ffukzei3puonuqlncy7cn2afwyd.onion:25</li>
+ <li><strong>PRIMARY:</strong> 4uwpi53u524xdphjw2dv5kywsxmyjxtk4facb76jgl3sc3nda3sz4fqd.onion:25</li>
+ <li><strong>FALLBACK:</strong> xilb7y4kj6u6qfo45o3yk2kilfv54ffukzei3puonuqlncy7cn2afwyd.onion:25</li>
</ul>
</details>
+
<div class="gateway-item">
<span class="gateway-priority">Step 2</span>
<div style="flex: 1;">
<strong>Mail2News Gateway</strong>
<div class="gateway-address" style="margin-top: 5px;">mail2news@xilb7y4kj6u6qfo45o3yk2kilfv54ffukzei3puonuqlncy7cn2afwyd.onion</div>
- <p style="font-size: 0.85em; margin: 5px 0 0 0; color: #666;">Converts email to Usenet posts while maintaining full anonymity within the onion network.</p>
+ <p style="font-size: 0.85em; margin: 5px 0 0 0; color: #666;">Converts email to Usenet posts while maintaining full anonymity.</p>
</div>
</div>
+
<div class="gateway-item">
<span class="gateway-priority">Step 3</span>
<div style="flex: 1;">
@@ -523,17 +690,19 @@ $csrfToken = $_SESSION['csrf_token'];
<p style="font-size: 0.85em; margin: 5px 0 0 0; color: #666;">Final destination for Usenet Tor hidden service.</p>
</div>
</div>
+
<p style="margin-top: 15px; padding: 10px; background: rgba(0,123,255,0.1); border-radius: 4px; font-size: 0.9em;">
- <strong>🔒 Privacy Guarantee:</strong> Your message never leaves the Tor network until it reaches the NNTP server. All three components (Pluto2 → Mail2News → NNTP) operate as Tor hidden services, providing end-to-end anonymity with protection against traffic analysis, timing attacks, and metadata correlation.
+ <strong>🔒 Privacy Guarantee:</strong> Your message never leaves the Tor network until it reaches the NNTP server. All three components (SMTP → Mail2News → NNTP) operate as Tor hidden services, providing end-to-end anonymity.
</p>
</div>
- <p><strong>How to use:</strong> Complete the form below with your name, newsgroups (max 3), subject, and verify that your message and authentication details are correct. Then click "Send" to post your message via the gateway system.</p>
</div>
+
<label>Email used for PoW:</label>
<input type="text" id="readonlyEmailSend" readonly>
<form id="sendForm" method="POST" action="send.php">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>">
- <label>From (Name): <input type="text" id="fromName" required placeholder="Your Name"></label>
+ <label>From (Name):</label>
+ <input type="text" id="readonlyFromName" readonly>
<input type="hidden" name="from" id="fromFull">
<label>Newsgroups (max 3, comma separated):
<input type="text" name="newsgroups" id="newsgroups" required placeholder="e.g. alt.privacy, comp.security">
@@ -545,12 +714,12 @@ $csrfToken = $_SESSION['csrf_token'];
<textarea name="message" id="messageContent" rows="8" required placeholder="Your message will appear here after signing..."></textarea>
<input type="hidden" name="x-ed25519-pub" id="x-ed25519-pub">
<input type="hidden" name="x-ed25519-sig" id="x-ed25519-sig">
- <button type="submit" id="sendBtn">Send via m2usenet Gateway</button>
+ <button type="submit" id="sendBtn">Send Message</button>
</form>
</div>
<footer>
- <div>m2usenet Gateway v2.1.0 © 2025 - Privacy-focused Usenet posting tool</div>
+ <div>m2usenet Gateway v2.6.0 © 2025 - Privacy-focused Usenet posting via Tor</div>
<div class="footer-links">
<a href="https://yamn.virebent.art">Home</a>
<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%69%6E%66%6F%40%76%69%72%65%62%65%6E%74%2E%61%72%74">Contact</a>
@@ -561,10 +730,15 @@ $csrfToken = $_SESSION['csrf_token'];
<script>
// ============================================================================
-// DEFINIZIONI FUNZIONI GLOBALI - DEVONO ESSERE PRIMA DI TUTTO
+// GLOBAL FUNCTIONS - MUST BE DEFINED FIRST
// ============================================================================
function showTab(id) {
+ // Block access to send tab if identity not saved
+ if (id === 'send' && !appState.identitySaved) {
+ showNotification('⚠ Please save your identity first before sending!', 'warning');
+ return;
+ }
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.getElementById(id).classList.add('active');
}
@@ -594,6 +768,7 @@ const appState = {
step1Complete: false,
step2Complete: false,
step3Complete: false,
+ identitySaved: false,
prefillMode: null
};
@@ -604,16 +779,62 @@ function updateTabIndicators() {
}
let keyPair = null;
+
+// --- VFACE identity persistence (session-scoped: secret lives in RAM/sessionStorage,
+// auto-cleared when the tab closes; the .json stays the portable on-disk form).
+// Lets reply->/compose reuse the active identity without re-loading the file. ---
+function persistIdentitySession() {
+ try {
+ if (!keyPair) return;
+ sessionStorage.setItem('m2usenet_identity', JSON.stringify({
+ username: document.getElementById('fromName').value || 'Anonymous',
+ email: document.getElementById('hcEmail').value || '',
+ publicKey: nacl.util.encodeBase64(keyPair.publicKey),
+ secretKey: nacl.util.encodeBase64(keyPair.secretKey)
+ }));
+ } catch (e) {}
+}
+function forgetIdentitySession() {
+ try { sessionStorage.removeItem('m2usenet_identity'); } catch (e) {}
+}
+async function restoreIdentityFromSession() {
+ let sd;
+ try { sd = JSON.parse(sessionStorage.getItem('m2usenet_identity') || 'null'); } catch (e) { return; }
+ if (!sd || !sd.publicKey || !sd.secretKey || keyPair) return;
+ try {
+ const pubKey = nacl.util.decodeBase64(sd.publicKey);
+ const secKey = nacl.util.decodeBase64(sd.secretKey);
+ if (pubKey.length !== 32 || secKey.length !== 64) return;
+ keyPair = { publicKey: pubKey, secretKey: secKey };
+ const uname = sd.username || 'Anonymous';
+ const mail = sd.email || '';
+ const set = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; };
+ set('fromName', uname); set('hcEmail', mail);
+ const pubOutput = document.getElementById('pubKeyOutput');
+ if (pubOutput) { pubOutput.innerText = sd.publicKey; pubOutput.classList.remove('empty'); }
+ set('x-ed25519-pub', sd.publicKey);
+ const signBtn = document.getElementById('signMsgBtn'); if (signBtn) signBtn.disabled = false;
+ set('readonlyFromName', uname);
+ if (mail) { set('fromFull', uname + ' <' + mail + '>'); set('readonlyEmailSign', mail); set('readonlyEmailSend', mail); }
+ const il = document.getElementById('identityLoadedPow'); if (il) il.style.display = 'flex';
+ const ln = document.getElementById('loadedIdentityName'); if (ln) ln.textContent = uname;
+ const le = document.getElementById('loadedIdentityEmail'); if (le) le.textContent = mail;
+ await updateIdenticonPreview();
+ const gk = document.getElementById('genKeyBtn'); if (gk) { gk.disabled = true; gk.textContent = '✓ Keypair Loaded'; }
+ appState.identitySaved = true;
+ const sb = document.getElementById('saveIdentityBtn'); if (sb) { sb.textContent = '✓ Identity (session)'; sb.style.background = '#28a745'; }
+ const warning = document.getElementById('saveIdentityWarning'); if (warning) warning.style.display = 'none';
+ showNotification('🔓 Identity restored for this session', 'info', 4000);
+ } catch (e) { console.error('session restore failed', e); }
+}
let workersSupported = true;
try {
if (typeof Worker === 'undefined') {
workersSupported = false;
- console.warn('[WARN] Web Workers not supported');
}
} catch (e) {
workersSupported = false;
- console.warn('[WARN] Web Workers check failed:', e);
}
// ============================================================================
@@ -626,6 +847,86 @@ document.getElementById('themeToggle').addEventListener('change', function() {
});
// ============================================================================
+// IDENTICON GENERATION (client-side)
+// ============================================================================
+
+let currentIdenticonData = null;
+
+async function generateIdenticon(username, email, pubkeyBase64) {
+ // Generate the identicon with the ORIGINAL identicons-cli engine (server-side),
+ // the same backend used by identicons.virebent.art (Ch1ffr3punk algorithm).
+ // Only PUBLIC data leaves the browser (username|email|ed25519-pubkey).
+ const body = new URLSearchParams({ username: username, email: email, pubkey: pubkeyBase64 });
+ const resp = await fetch('identicon.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: body.toString()
+ });
+ if (!resp.ok) throw new Error('identicon engine error ' + resp.status);
+ const r = await resp.json();
+ if (!r.face48 || !r.preview) throw new Error('identicon engine returned no data');
+
+ currentIdenticonData = {
+ dataUrl: r.preview, // 256x256 PNG dataurl (display/download)
+ hash: r.hash, // sha256(username|email|pubkey)
+ face48: r.face48, // raw base64 PNG (48x48) -> Face: header
+ faceHeader: r.faceHeader, // folded "Face: ..." header (RFC 2822)
+ username: username,
+ email: email,
+ pubkey: pubkeyBase64,
+ claim: JSON.stringify({
+ version: 2,
+ type: 'm2usenet-identity',
+ username: username,
+ email: email,
+ pubkey: pubkeyBase64,
+ identityHash: r.hash,
+ created: new Date().toISOString(),
+ algorithm: 'identicons-cli sha256(username|email|pubkey) [VFACE]'
+ }, null, 2)
+ };
+ return r.preview;
+}
+
+async function updateIdenticonPreview() {
+ if (!keyPair) return;
+
+ const pubB64 = nacl.util.encodeBase64(keyPair.publicKey);
+ const username = document.getElementById('fromName').value || 'Anonymous';
+ const email = document.getElementById('hcEmail').value || 'anonymous@m2usenet.local';
+
+ try {
+ const identiconUrl = await generateIdenticon(username, email, pubB64);
+
+ document.getElementById('identiconPreviewPow').src = identiconUrl;
+ document.getElementById('identiconPreviewLarge').src = identiconUrl;
+ document.getElementById('identityHashDisplay').textContent = currentIdenticonData.hash;
+ document.getElementById('identityUsername').textContent = username;
+ document.getElementById('identityEmail').textContent = email;
+ document.getElementById('identiconSection').style.display = 'block';
+
+ } catch (e) {
+ console.error('Identicon generation failed:', e);
+ }
+}
+
+document.getElementById('downloadIdenticonBtn').onclick = function() {
+ if (!currentIdenticonData) {
+ showNotification('⚠ Generate a keypair first!', 'warning');
+ return;
+ }
+
+ const a = document.createElement('a');
+ a.href = currentIdenticonData.dataUrl;
+ a.download = 'identicon-' + currentIdenticonData.hash.substring(0, 8) + '.png';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ showNotification('✓ Identicon downloaded!', 'success', 3000);
+};
+
+// ============================================================================
// INITIALIZATION
// ============================================================================
@@ -655,8 +956,9 @@ document.addEventListener('DOMContentLoaded', function() {
};
}
- showNotification('m2usenet Gateway v2.1 ready', 'success', 3000);
+ restoreIdentityFromSession();
showTab('pow');
+ showNotification('m2usenet Gateway v2.6.0 ready', 'success', 3000);
});
// ============================================================================
@@ -665,57 +967,32 @@ document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
- const submitButton = form.querySelector('button[type="submit"], input[type="submit"]');
+ const submitButton = form.querySelector('button[type="submit"]');
- if (!submitButton) {
- console.error('Submit button not found');
- return;
- }
+ if (!submitButton) return;
let isSubmitting = false;
form.addEventListener('submit', function(e) {
if (isSubmitting) {
e.preventDefault();
- console.log('Form already submitting, preventing duplicate');
return false;
}
isSubmitting = true;
submitButton.disabled = true;
- const originalText = submitButton.textContent || submitButton.value;
submitButton.textContent = 'Sending...';
- submitButton.value = 'Sending...';
submitButton.style.opacity = '0.6';
submitButton.style.cursor = 'not-allowed';
const progressDiv = document.createElement('div');
progressDiv.id = 'sending-progress';
progressDiv.style.cssText = 'margin-top: 10px; padding: 10px; background: #fff3cd; border-left: 4px solid #ffc107; color: #856404;';
- progressDiv.innerHTML = '<strong>⏳ Sending message...</strong><br>This may take 30-60 seconds. Please wait.';
+ progressDiv.innerHTML = '<strong>⏳ Sending message...</strong><br>This may take up to 60 seconds via Tor. Please wait.';
submitButton.parentNode.insertBefore(progressDiv, submitButton.nextSibling);
return true;
});
-
- form.addEventListener('keypress', function(e) {
- if (e.key === 'Enter' && isSubmitting) {
- e.preventDefault();
- return false;
- }
- });
-
- window.addEventListener('pageshow', function(event) {
- if (event.persisted) {
- isSubmitting = false;
- submitButton.disabled = false;
- submitButton.textContent = originalText;
- submitButton.style.opacity = '1';
- submitButton.style.cursor = 'pointer';
- const progressDiv = document.getElementById('sending-progress');
- if (progressDiv) progressDiv.remove();
- }
- });
});
// ============================================================================
@@ -794,7 +1071,6 @@ function sha1(str) {
}
function mineSingleThread(prefix, targetZeros, progressCallback, foundCallback) {
- console.log('[INFO] Using single-threaded mining');
const target = '0'.repeat(targetZeros);
let nonce = 0, checked = 0;
const batchSize = 100;
@@ -883,6 +1159,7 @@ document.getElementById('genTokenBtn').onclick = () => {
const fromName = document.getElementById('fromName').value || 'Anonymous';
document.getElementById('fromFull').value = `${fromName} <${email}>`;
+ document.getElementById('readonlyFromName').value = fromName;
document.getElementById('readonlyEmailSign').value = email;
document.getElementById('readonlyEmailSend').value = email;
@@ -896,17 +1173,13 @@ document.getElementById('genTokenBtn').onclick = () => {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
showNotification(`✓ Token generated in ${elapsed}s!`, 'success', 8000);
- console.log(`[INFO] Token mined: ${totalHashes} hashes in ${elapsed}s`);
setTimeout(() => showTab('sign'), 2000);
}
if (workersSupported) {
- console.log('[INFO] Attempting multi-threaded mining');
try {
const cores = navigator.hardwareConcurrency || 2;
- console.log(`[INFO] Using ${cores} workers`);
-
let found = false;
let coreChecked = Array(cores).fill(0);
const workers = [];
@@ -935,9 +1208,7 @@ document.getElementById('genTokenBtn').onclick = () => {
};
w.onerror = (err) => {
- console.error(`[ERROR] Worker ${i} error:`, err);
if (i === 0) {
- console.warn('[WARN] Falling back to single-thread');
workers.forEach(x => x.terminate());
mineSingleThread(prefix, zeros, updateProgress, onFound);
}
@@ -945,7 +1216,6 @@ document.getElementById('genTokenBtn').onclick = () => {
workers.push(w);
} catch (err) {
- console.error(`[ERROR] Failed to create worker ${i}:`, err);
if (i === 0) {
mineSingleThread(prefix, zeros, updateProgress, onFound);
return;
@@ -953,16 +1223,7 @@ document.getElementById('genTokenBtn').onclick = () => {
}
}
- setTimeout(() => {
- if (!found) {
- console.warn('[WARN] Mining timeout');
- showNotification('⚠ Mining is taking longer than expected.', 'warning', 10000);
- }
- }, 600000);
-
} catch (err) {
- console.error('[ERROR] Worker mining failed:', err);
- showNotification('⚠ Multi-threaded mining failed, using fallback', 'warning');
mineSingleThread(prefix, zeros, updateProgress, onFound);
}
} else {
@@ -971,22 +1232,15 @@ document.getElementById('genTokenBtn').onclick = () => {
};
// ============================================================================
-// STEP 2: ED25519 SIGNATURE
+// STEP 2: ED25519 SIGNATURE + KEYPAIR MANAGEMENT
// ============================================================================
-document.getElementById('genKeyBtn').onclick = function() {
+document.getElementById('genKeyBtn').onclick = async function() {
try {
- if (typeof nacl === 'undefined' || typeof nacl.sign === 'undefined') {
- throw new Error("Cryptography library not loaded");
- }
-
showNotification('🔐 Generating Ed25519 key pair...', 'info');
keyPair = nacl.sign.keyPair();
- const pubKey = keyPair.publicKey;
- const pubB64 = typeof nacl.util.encodeBase64 === 'function'
- ? nacl.util.encodeBase64(pubKey)
- : btoa(Array.from(new Uint8Array(pubKey)).map(byte => String.fromCharCode(byte)).join(''));
+ const pubB64 = nacl.util.encodeBase64(keyPair.publicKey);
const pubOutput = document.getElementById('pubKeyOutput');
pubOutput.innerText = pubB64;
@@ -994,17 +1248,168 @@ document.getElementById('genKeyBtn').onclick = function() {
document.getElementById('x-ed25519-pub').value = pubB64;
document.getElementById('signMsgBtn').disabled = false;
- showNotification('✓ Key pair generated!', 'success', 6000);
+ const username = document.getElementById('fromName').value || 'Anonymous';
+ const email = document.getElementById('hcEmail').value || '';
+ document.getElementById('identityLoadedPow').style.display = 'flex';
+ document.getElementById('loadedIdentityName').textContent = username;
+ document.getElementById('loadedIdentityEmail').textContent = email || 'New keypair - save identity!';
+
+ await updateIdenticonPreview();
+
+ // Auto-open the save dialog at the end of identity generation:
+ // saving the .json is mandatory to keep (and later reload) this pseudonym.
+ document.getElementById('saveIdentityBtn').click();
+
+ showNotification('✓ Identity generated & saving — keep the .json to reuse this pseudonym. Now write and sign your message.', 'success', 7000);
} catch (error) {
- console.error("Error generating key pair:", error);
showNotification('✗ Error: ' + error.message, 'error');
}
};
+document.getElementById('loadIdentityBtn').onclick = function() {
+ document.getElementById('identityFileInput').click();
+};
+
+document.getElementById('identityFileInput').onchange = async function(e) {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ try {
+ const text = await file.text();
+ const data = JSON.parse(text);
+
+ if (!data.publicKey || !data.secretKey) {
+ throw new Error('Invalid identity file format');
+ }
+
+ const pubKey = nacl.util.decodeBase64(data.publicKey);
+ const secKey = nacl.util.decodeBase64(data.secretKey);
+
+ if (pubKey.length !== 32 || secKey.length !== 64) {
+ throw new Error('Invalid key lengths');
+ }
+
+ keyPair = { publicKey: pubKey, secretKey: secKey };
+
+ const pubB64 = data.publicKey;
+ const pubOutput = document.getElementById('pubKeyOutput');
+ pubOutput.innerText = pubB64;
+ pubOutput.classList.remove('empty');
+ document.getElementById('x-ed25519-pub').value = pubB64;
+ document.getElementById('signMsgBtn').disabled = false;
+
+ if (data.username) {
+ document.getElementById('fromName').value = data.username;
+ document.getElementById('readonlyFromName').value = data.username;
+ }
+ if (data.email) {
+ document.getElementById('hcEmail').value = data.email;
+ const fromName = document.getElementById('fromName').value || 'Anonymous';
+ document.getElementById('fromFull').value = `${fromName} <${data.email}>`;
+ document.getElementById('readonlyEmailSign').value = data.email;
+ document.getElementById('readonlyEmailSend').value = data.email;
+ }
+
+ const username = data.username || 'Anonymous';
+ const email = data.email || '';
+ document.getElementById('identityLoadedPow').style.display = 'flex';
+ document.getElementById('loadedIdentityName').textContent = username;
+ document.getElementById('loadedIdentityEmail').textContent = email;
+
+ await updateIdenticonPreview();
+
+ if (currentIdenticonData && currentIdenticonData.dataUrl) {
+ document.getElementById('identiconPreviewPow').src = currentIdenticonData.dataUrl;
+ }
+
+ document.getElementById('genKeyBtn').disabled = true;
+ document.getElementById('genKeyBtn').textContent = '✓ Keypair Loaded';
+
+ // Identity already saved (loaded from file) - allow access to send tab
+ appState.identitySaved = true;
+ document.getElementById('saveIdentityBtn').textContent = '✓ Identity Loaded';
+ document.getElementById('saveIdentityBtn').style.background = '#28a745';
+
+ // Hide the warning message
+ const warning = document.getElementById('saveIdentityWarning');
+ if (warning) warning.style.display = 'none';
+
+ persistIdentitySession();
+ showNotification('✓ Identity loaded! Username and email populated. Now generate PoW token.', 'success', 6000);
+ } catch (error) {
+ showNotification('✗ Failed to load identity: ' + error.message, 'error');
+ }
+
+ e.target.value = '';
+};
+
+document.getElementById('saveIdentityBtn').onclick = async function() {
+ if (!keyPair) {
+ showNotification('⚠ Generate a keypair first!', 'warning');
+ return;
+ }
+
+ const username = document.getElementById('fromName').value || 'Anonymous';
+ const email = document.getElementById('hcEmail').value || 'anonymous@m2usenet.local';
+ const pubB64 = nacl.util.encodeBase64(keyPair.publicKey);
+ const secB64 = nacl.util.encodeBase64(keyPair.secretKey);
+
+ const identityString = username + '|' + email + '|' + pubB64;
+ const encoder = new TextEncoder();
+ const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(identityString));
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ const identityHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
+
+ const data = {
+ version: 2,
+ type: 'm2usenet-identity',
+ username: username,
+ email: email,
+ publicKey: pubB64,
+ secretKey: secB64,
+ identityHash: identityHash,
+ vface: (currentIdenticonData ? currentIdenticonData.faceHeader : ''),
+ face: (currentIdenticonData ? currentIdenticonData.face48 : ''),
+ algorithm: 'identicons-cli sha256(username|email|pubkey) [VFACE]',
+ created: new Date().toISOString(),
+ warning: 'Keep this file secure! Anyone with this file can sign messages as you.'
+ };
+
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'm2usenet-identity-' + username + '.json';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ // Mark identity as saved - allows access to send tab
+ appState.identitySaved = true;
+ persistIdentitySession();
+
+ // Update button to show saved state
+ this.textContent = '✓ Identity Saved';
+ this.style.background = '#28a745';
+
+ // Hide the warning message
+ const warning = document.getElementById('saveIdentityWarning');
+ if (warning) warning.style.display = 'none';
+
+ // If message already signed, go to send tab
+ if (appState.step2Complete) {
+ showNotification('✓ Identity saved! Proceeding to send...', 'success', 4000);
+ setTimeout(() => showTab('send'), 1500);
+ } else {
+ showNotification('✓ Identity saved! Now sign your message to continue.', 'success', 6000);
+ }
+};
+
document.getElementById('signMsgBtn').onclick = function() {
try {
if (!keyPair) {
- showNotification('⚠ Please generate a key pair first!', 'warning');
+ showNotification('⚠ Please generate or load a key pair first!', 'warning');
return;
}
@@ -1024,15 +1429,9 @@ document.getElementById('signMsgBtn').onclick = function() {
document.getElementById('messageToSign').classList.remove('error');
showNotification('✍ Signing message...', 'info');
- const msgBytes = typeof nacl.util.decodeUTF8 === 'function'
- ? nacl.util.decodeUTF8(msg)
- : new TextEncoder().encode(msg);
-
+ const msgBytes = nacl.util.decodeUTF8(msg);
const sig = nacl.sign.detached(msgBytes, keyPair.secretKey);
-
- const sigB64 = typeof nacl.util.encodeBase64 === 'function'
- ? nacl.util.encodeBase64(sig)
- : btoa(Array.from(new Uint8Array(sig)).map(byte => String.fromCharCode(byte)).join(''));
+ const sigB64 = nacl.util.encodeBase64(sig);
const sigOutput = document.getElementById('signatureOutput');
sigOutput.innerText = sigB64;
@@ -1044,21 +1443,45 @@ document.getElementById('signMsgBtn').onclick = function() {
appState.step2Complete = true;
updateTabIndicators();
- showNotification('✓ Message signed!', 'success', 8000);
- setTimeout(() => showTab('send'), 2000);
+ // After signing, check if identity is saved
+ if (!appState.identitySaved) {
+ // Scroll to identicon section and prompt to save
+ showNotification('✓ Message signed! Now save your identity to continue.', 'warning', 10000);
+ setTimeout(() => {
+ const identiconSection = document.getElementById('identiconSection');
+ if (identiconSection) {
+ identiconSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ identiconSection.style.animation = 'pulse 1s ease-in-out 3';
+ }
+ }, 500);
+ } else {
+ // Identity already saved, proceed to send
+ showNotification('✓ Message signed with your identity!', 'success', 8000);
+ setTimeout(() => showTab('send'), 2000);
+ }
} catch (error) {
- console.error("Error signing:", error);
showNotification('✗ Error: ' + error.message, 'error');
}
};
+document.getElementById('fromName').addEventListener('input', function() {
+ const email = document.getElementById('hcEmail').value;
+ document.getElementById('readonlyFromName').value = this.value;
+ if (email) {
+ document.getElementById('fromFull').value = `${this.value} <${email}>`;
+ }
+ if (keyPair) {
+ updateIdenticonPreview();
+ }
+});
+
// ============================================================================
// STEP 3: SEND FORM
// ============================================================================
document.getElementById('sendForm').addEventListener('submit', function(e) {
const requiredFields = [
- {id: 'fromName', name: 'Name'},
+ {id: 'fromFull', name: 'From'},
{id: 'newsgroups', name: 'Newsgroups'},
{id: 'subject', name: 'Subject'},
{id: 'hcToken', name: 'Hashcash Token'},
@@ -1104,59 +1527,45 @@ document.getElementById('sendForm').addEventListener('submit', function(e) {
return false;
}
- const confirmMsg = `Send to Usenet?\n\nNewsgroups: ${newsgroups.join(', ')}\nSubject: ${document.getElementById('subject').value}\nLength: ${messageLen} chars\n\nThis cannot be undone.`;
+ const confirmMsg = `Send message?\n\nNewsgroups: ${newsgroups.join(', ')}\nSubject: ${document.getElementById('subject').value}\nLength: ${messageLen} chars\n\nDelivery via Tor may take up to 60 seconds.`;
if (!confirm(confirmMsg)) {
e.preventDefault();
return false;
}
- showNotification('📤 Sending...', 'info', 0);
+ showNotification('📤 Sending message...', 'info', 0);
document.getElementById('sendBtn').disabled = true;
document.getElementById('sendBtn').textContent = 'Sending...';
return true;
});
-document.getElementById('fromName').addEventListener('input', function() {
- const email = document.getElementById('hcEmail').value;
- if (email) {
- document.getElementById('fromFull').value = `${this.value} <${email}>`;
- }
-});
-
// ============================================================================
-// ONION-NEWSREADER INTEGRATION (localStorage + URL params) - FIXED v2
+// ONION-NEWSREADER INTEGRATION
// ============================================================================
(function() {
- // Helper function to decode URL-encoded values
function decode(s) {
if (!s) return '';
try {
- // Replace + with space, then decode URI components
return decodeURIComponent(s.replace(/\+/g, ' '));
} catch(e) {
- console.error('[PREFILL] Decode error:', e);
return s;
}
}
- // Helper function to fill and optionally style a field
function fillField(id, value, highlight) {
highlight = highlight !== false;
const field = document.getElementById(id);
if (field && value) {
field.value = value;
- if (highlight) {
- field.classList.add('prefilled');
- }
+ if (highlight) field.classList.add('prefilled');
return true;
}
return false;
}
- // Show the prefill banner
function showPrefillBanner(isReply, details) {
const banner = document.getElementById('prefillBanner');
const title = document.getElementById('prefillTitle');
@@ -1177,78 +1586,62 @@ document.getElementById('fromName').addEventListener('input', function() {
let prefillData = null;
let prefillSource = null;
- // 1. Try localStorage first (from onion-newsreader)
try {
const storedData = localStorage.getItem('m2usenet_prefill');
if (storedData) {
prefillData = JSON.parse(storedData);
prefillSource = 'localStorage';
- // Clear after reading
localStorage.removeItem('m2usenet_prefill');
- console.log('[PREFILL] Raw data from localStorage:', prefillData);
- // Decode values in case they were stored encoded
if (prefillData.newsgroups) prefillData.newsgroups = decode(prefillData.newsgroups);
if (prefillData.subject) prefillData.subject = decode(prefillData.subject);
if (prefillData.references) prefillData.references = decode(prefillData.references);
-
- console.log('[PREFILL] Decoded data from localStorage:', prefillData);
}
} catch (e) {
- console.error('[PREFILL] localStorage error:', e);
localStorage.removeItem('m2usenet_prefill');
}
- // 2. Fallback to URL query parameters
if (!prefillData) {
const params = new URLSearchParams(window.location.search);
const action = params.get('action');
if (action === 'new' || action === 'reply' || params.has('newsgroups')) {
- // Get raw values and decode them explicitly
- const rawNG = params.get('newsgroups') || '';
- const rawSubj = params.get('subject') || '';
- const rawRefs = params.get('references') || '';
-
- console.log('[PREFILL] Raw URL params:', { rawNG, rawSubj, rawRefs });
-
prefillData = {
- newsgroups: decode(rawNG),
- subject: decode(rawSubj),
- references: decode(rawRefs)
+ newsgroups: decode(params.get('newsgroups') || ''),
+ subject: decode(params.get('subject') || ''),
+ references: decode(params.get('references') || ''),
+ body: decode(params.get('body') || '')
};
prefillSource = 'URL';
- console.log('[PREFILL] Decoded data from URL:', prefillData);
}
}
- // 3. Apply prefill data if available
if (prefillData) {
let filledFields = [];
- // Fill newsgroups
if (prefillData.newsgroups) {
fillField('newsgroups', prefillData.newsgroups);
filledFields.push('Newsgroups');
}
- // Fill subject
if (prefillData.subject) {
fillField('subject', prefillData.subject);
filledFields.push('Subject');
}
- // Fill references (for replies)
if (prefillData.references) {
fillField('references', prefillData.references);
filledFields.push('References');
}
+
+ if (prefillData.body) {
+ fillField('messageToSign', prefillData.body);
+ filledFields.push('Quoted message');
+ }
- // Determine if this is a reply or new post
const isReply = !!(prefillData.references ||
(prefillData.subject && prefillData.subject.toLowerCase().startsWith('re:')));
- // Show banner and notification
if (filledFields.length > 0) {
const details = `Pre-filled: ${filledFields.join(', ')} (from ${prefillSource})`;
showPrefillBanner(isReply, details);
@@ -1258,11 +1651,8 @@ document.getElementById('fromName').addEventListener('input', function() {
} else {
showNotification(`📝 New post to: ${prefillData.newsgroups}`, 'success', 5000);
}
-
- console.log(`[PREFILL] Applied ${filledFields.length} fields from ${prefillSource}`);
}
- // Start at first tab (PoW generation)
setTimeout(() => showTab('pow'), 300);
}
})();