v14: /reply and /r command to quick-reply to last DM peer

- /reply <msg> or /r <msg> sends encrypted DM to last person
- lastDmPeer set when sending a DM or receiving one
- Shows error if no prior DM conversation exists

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-26 16:46:35 +04:00
parent 93be964d52
commit 811dd2c008

17
chat.py
View File

@@ -223,7 +223,7 @@ CHAT_HTML = r"""<!DOCTYPE html>
<div id="header">
<input id="name" placeholder="Name" value="" autocomplete="off">
<span id="group-tag"></span>
<span id="header-info"><code>/dm @user msg</code> <code>/users</code> <code>/setpass</code> <code>/color</code></span>
<span id="header-info"><code>/dm @user msg</code> <code>/r reply</code> <code>/users</code> <code>/setpass</code> <code>/color</code></span>
</div>
<div id="messages"></div>
<div id="bottom">
@@ -336,11 +336,23 @@ function send() {
// Local commands
const dmMatch = text.match(/^\/dm\s+@?(\S+)\s+([\s\S]+)/);
if (dmMatch) {
lastDmPeer = dmMatch[1];
encryptAndSendDM(dmMatch[1], dmMatch[2]);
$input.value = '';
$input.style.height = 'auto';
return;
}
const replyMatch = text.match(/^\/(?:reply|r)\s+([\s\S]+)/);
if (replyMatch) {
if (!lastDmPeer) {
addMsg({ts:Date.now()/1000, user:'***', text:'No one to reply to. Use /dm @user first.'});
} else {
encryptAndSendDM(lastDmPeer, replyMatch[1]);
}
$input.value = '';
$input.style.height = 'auto';
return;
}
if (text === '/users' || text === '/online') {
fetch('/keys').then(r => r.json()).then(users => {
addMsg({ts:Date.now()/1000, user:'***', text:'Users with keys: ' + users.join(', ')});
@@ -494,6 +506,7 @@ document.getElementById('pw-input').onkeydown = function(e) {
// ── E2E Encrypted DMs (ECDH + AES-256-GCM via Web Crypto) ──
let myKeyPair = null;
let lastDmPeer = null; // for /reply
let myPubJwk = null;
const derivedKeys = {}; // cache: username -> CryptoKey (AES)
@@ -595,6 +608,8 @@ async function handleEncryptedDM(data) {
// Only decrypt if we are sender or recipient
if (data.to !== myName && data.user !== myName) return;
const otherUser = data.user === myName ? data.to : data.user;
// Update reply target when someone DMs us
if (data.user !== myName) lastDmPeer = data.user;
try {
const aesKey = await getAESKey(otherUser);
if (!aesKey) throw new Error('no key');