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