v0.0.27: TG-compatible bots — plaintext send, numeric IDs, webhooks, BotFather
Bot compatibility: - Clients send plaintext bot_message to bot aliases (no E2E encryption) - Numeric chat_id: fp_to_numeric_id() deterministic hash, accept string/number - Webhook delivery: POST updates to bot's webhook URL (async, fire-and-forget) - getUpdates timeout raised to 50s (was 30, TG uses 50) - parse_mode HTML rendered in web client - E2E bot registration: optional seed + bundle for encrypted bot sessions BotFather + instance control: - --enable-bots CLI flag (default: disabled) - BotFather auto-created on first start (@botfather alias) - Bot ownership: owner fingerprint stored in bot_info - All bot endpoints return 403 when disabled Bot Bridge: - tools/bot-bridge.py: TG-compatible proxy for unmodified TG bots - Translates chat_id int↔string, proxies getUpdates/sendMessage - README with python-telegram-bot and Telegraf examples Test fixes: - Updated tests for ETH address display in header/messages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse {
|
||||
|
||||
async fn service_worker() -> impl IntoResponse {
|
||||
([(header::CONTENT_TYPE, "application/javascript")], r##"
|
||||
const CACHE = 'wz-v7';
|
||||
const CACHE = 'wz-v9';
|
||||
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
|
||||
|
||||
self.addEventListener('install', e => {
|
||||
@@ -241,7 +241,7 @@ let pollTimer = null;
|
||||
let ws = null; // WebSocket connection
|
||||
let wasmReady = false;
|
||||
|
||||
const VERSION = '0.0.25';
|
||||
const VERSION = '0.0.27';
|
||||
let DEBUG = true; // toggle with /debug command
|
||||
|
||||
// ── Receipt tracking ──
|
||||
@@ -547,7 +547,8 @@ function connectWebSocket() {
|
||||
msgText += '\\n';
|
||||
}
|
||||
}
|
||||
addMsg('@' + botName, msgText, false);
|
||||
const useHtml = json.parse_mode === 'HTML';
|
||||
addMsg('@' + botName, msgText, false, null, useHtml);
|
||||
lastDmPeer = json.from ? normFP(json.from) : '';
|
||||
return;
|
||||
}
|
||||
@@ -693,7 +694,8 @@ async function handleIncomingMessage(bytes) {
|
||||
msgText += '\\n';
|
||||
}
|
||||
}
|
||||
addMsg('@' + botName, msgText, false);
|
||||
const useHtml = json.parse_mode === 'HTML';
|
||||
addMsg('@' + botName, msgText, false, null, useHtml);
|
||||
lastDmPeer = json.from ? normFP(json.from) : '';
|
||||
return;
|
||||
}
|
||||
@@ -801,7 +803,7 @@ function formatSize(n) {
|
||||
return (n/1048576).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
function addMsg(from, text, isSelf, messageId) {
|
||||
function addMsg(from, text, isSelf, messageId, rawHtml) {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'msg';
|
||||
const color = isSelf ? '#4ade80' : peerColor(from);
|
||||
@@ -811,7 +813,8 @@ function addMsg(from, text, isSelf, messageId) {
|
||||
const status = (sentMsgReceipts[messageId] && sentMsgReceipts[messageId].status) || 'sent';
|
||||
receiptHtml = ' <span class="receipt" style="color:' + receiptColor(status) + '"> ' + receiptIndicator(status) + '</span>';
|
||||
}
|
||||
d.innerHTML = '<span class="ts">' + ts() + '</span> ' + lock + '<span style="color:' + color + ';font-weight:bold">' + makeAddressClickable(esc(from)) + '</span>: ' + makeAddressClickable(esc(text)) + receiptHtml;
|
||||
const bodyHtml = rawHtml ? text : makeAddressClickable(esc(text));
|
||||
d.innerHTML = '<span class="ts">' + ts() + '</span> ' + lock + '<span style="color:' + color + ';font-weight:bold">' + makeAddressClickable(esc(from)) + '</span>: ' + bodyHtml + receiptHtml;
|
||||
// Attach click handler for .addr spans
|
||||
d.querySelectorAll('.addr').forEach(el => {
|
||||
el.addEventListener('click', () => handleAddrClick(el.dataset.addr));
|
||||
@@ -1202,6 +1205,22 @@ async function doSend() {
|
||||
|
||||
localStorage.setItem('wz-peer', $peerInput.value.trim());
|
||||
|
||||
// Check if peer is a bot — send plaintext instead of E2E
|
||||
let isBotPeer = false;
|
||||
try {
|
||||
const wr = await fetch(SERVER + '/v1/alias/whois/' + normFP(peer));
|
||||
const wd = await wr.json();
|
||||
if (wd.alias && (wd.alias.endsWith('bot') || wd.alias.endsWith('Bot') || wd.alias.endsWith('_bot'))) isBotPeer = true;
|
||||
} catch(e) {}
|
||||
|
||||
if (isBotPeer) {
|
||||
const msgId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString();
|
||||
const botMsg = {type:'bot_message',id:msgId,from:normFP(myFingerprint),from_name:myEthAddress||myFingerprint.slice(0,19),text:text,timestamp:Math.floor(Date.now()/1000)};
|
||||
await fetch(SERVER+'/v1/messages/send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({to:normFP(peer),from:normFP(myFingerprint),message:Array.from(new TextEncoder().encode(JSON.stringify(botMsg)))})});
|
||||
addMsg((myEthAddress ? myEthAddress.slice(0,12)+'...' : myFingerprint.slice(0,19)), text, true, msgId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const msgId = await sendEncrypted(peer, text);
|
||||
sentMsgReceipts[msgId] = { status: 'sent', el: null };
|
||||
|
||||
Reference in New Issue
Block a user