Add autonomous webhooks and dynamic WebView discovery

This commit is contained in:
Ops Agent committed 2026-10-11 08:43:39 +02:00
1 parent ab69f287b6
commit 97f874d183
29 files changed
+3044 -1489

No files matched your search

Binary file not shown.
Binary file not shown.
+63
View File
@@ -0,0 +1,63 @@
(() => {
const config=__CONFIG__, key='__whatsappWebhookV1';
const existing=window[key];
if(existing&&!existing.stopped){
if(existing.listener_id!==config.listener_id)throw Error('Another listener is installed; stop it first');
return existing.status();
}
const collection=require('WAWebMsgCollection').MsgCollection;
const prefs=require('WAWebUserPrefsMeUser');
if(typeof collection.on!=='function'||typeof collection.off!=='function')throw Error('Message events unavailable');
const seen=new Set(), pending=new Map(), timers=new Set();
const state={listener_id:config.listener_id,queue:[],stopped:false,delivered:0,
dropped:0,decode_errors:0,last_error:null,observed:0,sequence:0,
installed_at:Date.now(),self_only:config.self_only};
const identity=m=>m?.id?.toString?.();
function later(fn,delay){const timer=setTimeout(()=>{timers.delete(timer);if(!state.stopped)fn();},delay);timers.add(timer);}
async function capture(model,attempt=0){
const id=identity(model);if(!id||seen.has(id)||state.stopped)return;
if(config.self_only&&!prefs.isMeAccount(model.id.remote))return;
if(!Number.isFinite(model.t)||model.t*1000<config.since_ms-2000)return;
pending.set(id,model);
try{
const row=await require('WAWebSchemaMessage').getMessageTable().get(id);
if(state.stopped||seen.has(id))return;
if(!row){if(attempt<8)later(()=>capture(model,attempt+1),250);else{state.decode_errors++;pending.delete(id);}return;}
const decoded=require('WAWebDBMessageSerialization').messageFromDbRow(row);
if(!decoded)throw Error('Message decoder returned no row');
if(state.queue.length>=1000){state.dropped++;pending.delete(id);return;}
const event={seq:++state.sequence,event_id:id,type:'message',message:{
id,chat_id:model.id.remote.toString(),timestamp:decoded.t??model.t,
from_me:!!model.id.fromMe,message_type:decoded.type??model.type??null,
body:typeof decoded.body==='string'?decoded.body.slice(0,12000):null,
caption:typeof decoded.caption==='string'?decoded.caption.slice(0,4000):null,
text_truncated:(decoded.body?.length??0)>12000||(decoded.caption?.length??0)>4000,
body_length:typeof decoded.body==='string'?decoded.body.length:0,
caption_length:typeof decoded.caption==='string'?decoded.caption.length:0}};
state.queue.push(event);seen.add(id);pending.delete(id);
if(seen.size>10000)seen.delete(seen.values().next().value);
}catch(error){
state.last_error=String(error).slice(0,300);
if(attempt<8)later(()=>capture(model,attempt+1),250);else{state.decode_errors++;pending.delete(id);}
}
}
function onMessage(model){
if(!model?.id||state.stopped)return;
state.observed++;
if(config.self_only&&!prefs.isMeAccount(model.id.remote))return;
if(!Number.isFinite(model.t)||model.t*1000<config.since_ms-2000)return;
const id=identity(model);if(!seen.has(id)&&!pending.has(id)){pending.set(id,model);later(()=>capture(model),50);}
}
const events=['add','change:body','change:caption'];
state.status=()=>({installed:!state.stopped,listener_id:state.listener_id,
queued:state.queue.length,pending:pending.size,delivered:state.delivered,
dropped:state.dropped,decode_errors:state.decode_errors,last_error:state.last_error,
observed:state.observed,installed_at:state.installed_at,self_only:state.self_only});
state.stop=()=>{state.stopped=true;for(const event of events)collection.off(event,onMessage);
for(const timer of timers)clearTimeout(timer);timers.clear();pending.clear();state.queue.length=0;return state.status();};
window[key]=state;
for(const event of events)collection.on(event,onMessage);
// Reconcile recently loaded models on a reinstall; the receiver deduplicates IDs.
if(config.reconcile)for(const model of collection.getModelsArray())onMessage(model);
return state.status();
})()
Binary file not shown.