import {readFileSync, writeFileSync, existsSync} from 'fs'; /** * @type {import('@whiskeysockets/baileys')} */ const {initAuthCreds, BufferJSON, proto} = (await import('@whiskeysockets/baileys')).default; /** * @param {import('@whiskeysockets/baileys').WASocket | import('@whiskeysockets/baileys').WALegacySocket} */ function bind(conn) { if (!conn.chats) conn.chats = {}; /** * * @param {import('@whiskeysockets/baileys').Contact[]|{contacts:import('@whiskeysockets/baileys').Contact[]}} contacts * @returns */ function updateNameToDb(contacts) { if (!contacts) return; try { contacts = contacts.contacts || contacts; for (const contact of contacts) { const id = conn.decodeJid(contact.id); if (!id || id === 'status@broadcast') continue; let chats = conn.chats[id]; if (!chats) chats = conn.chats[id] = {...contact, id}; conn.chats[id] = { ...chats, ...({ ...contact, id, ...(id.endsWith('@g.us') ? {subject: contact.subject || contact.name || chats.subject || ''} : {name: contact.notify || contact.name || chats.name || chats.notify || ''}), } || {}), }; } } catch (e) { // console.error(e); } } conn.ev.on('contacts.upsert', updateNameToDb); conn.ev.on('groups.update', updateNameToDb); conn.ev.on('contacts.set', updateNameToDb); conn.ev.on('chats.set', async ({chats}) => { try { for (let {id, name, readOnly} of chats) { id = conn.decodeJid(id); if (!id || id === 'status@broadcast') continue; const isGroup = id.endsWith('@g.us'); let chats = conn.chats[id]; if (!chats) chats = conn.chats[id] = {id}; chats.isChats = !readOnly; if (name) chats[isGroup ? 'subject' : 'name'] = name; if (isGroup) { const metadata = await conn.groupMetadata(id).catch((_) => null); if (name || metadata?.subject) chats.subject = name || metadata.subject; if (!metadata) continue; chats.metadata = metadata; } } } catch (e) { // console.error(e); } }); conn.ev.on('group-participants.update', async function updateParticipantsToDb({id, participants, action}) { if (!id) return; id = conn.decodeJid(id); if (id === 'status@broadcast') return; if (!(id in conn.chats)) conn.chats[id] = {id}; const chats = conn.chats[id]; chats.isChats = true; const groupMetadata = await conn.groupMetadata(id).catch((_) => null); if (!groupMetadata) return; chats.subject = groupMetadata.subject; chats.metadata = groupMetadata; }); conn.ev.on('groups.update', async function groupUpdatePushToDb(groupsUpdates) { try { for (const update of groupsUpdates) { const id = conn.decodeJid(update.id); if (!id || id === 'status@broadcast') continue; const isGroup = id.endsWith('@g.us'); if (!isGroup) continue; let chats = conn.chats[id]; if (!chats) chats = conn.chats[id] = {id}; chats.isChats = true; const metadata = await conn.groupMetadata(id).catch((_) => null); if (metadata) chats.metadata = metadata; if (update.subject || metadata?.subject) chats.subject = update.subject || metadata.subject; } } catch (e) { // console.error(e); } }); conn.ev.on('chats.upsert', function chatsUpsertPushToDb(chatsUpsert) { try { const {id, name} = chatsUpsert; if (!id || id === 'status@broadcast') return; conn.chats[id] = {...(conn.chats[id] || {}), ...chatsUpsert, isChats: true}; const isGroup = id.endsWith('@g.us'); if (isGroup) conn.insertAllGroup().catch((_) => null); } catch (e) { // console.error(e); } }); conn.ev.on('presence.update', async function presenceUpdatePushToDb({id, presences}) { try { const sender = Object.keys(presences)[0] || id; const _sender = conn.decodeJid(sender); const presence = presences[sender]['lastKnownPresence'] || 'composing'; let chats = conn.chats[_sender]; if (!chats) chats = conn.chats[_sender] = {id: sender}; chats.presences = presence; if (id.endsWith('@g.us')) { let chats = conn.chats[id]; if (!chats) chats = conn.chats[id] = {id}; } } catch (e) { // console.error(e); } }); } const KEY_MAP = { 'pre-key': 'preKeys', 'session': 'sessions', 'sender-key': 'senderKeys', 'app-state-sync-key': 'appStateSyncKeys', 'app-state-sync-version': 'appStateVersions', 'sender-key-memory': 'senderKeyMemory', }; /** * * @param {String} filename * @param {import('pino').Logger} logger * @returns */ function useSingleFileAuthState(filename, logger) { let creds; let keys = {}; let saveCount = 0; // save the authentication state to a file const saveState = (forceSave) => { logger?.trace('saving auth state'); saveCount++; if (forceSave || saveCount > 5) { writeFileSync( filename, // BufferJSON replacer utility saves buffers nicely JSON.stringify({creds, keys}, BufferJSON.replacer, 2), ); saveCount = 0; } }; if (existsSync(filename)) { const result = JSON.parse( readFileSync(filename, {encoding: 'utf-8'}), BufferJSON.reviver, ); creds = result.creds; keys = result.keys; } else { creds = initAuthCreds(); keys = {}; } return { state: { creds, keys: { get: (type, ids) => { const key = KEY_MAP[type]; return ids.reduce( (dict, id) => { let value = keys[key]?.[id]; if (value) { if (type === 'app-state-sync-key') { value = proto.AppStateSyncKeyData.fromObject(value); } dict[id] = value; } return dict; }, {}, ); }, set: (data) => { for (const _key in data) { const key = KEY_MAP[_key]; keys[key] = keys[key] || {}; Object.assign(keys[key], data[_key]); } saveState(); }, }, }, saveState, }; } function loadMessage(jid, id = null) { let message = null; // If only 1 param, first param is assumed to be id not jid if (jid && !id) { id = jid; /** @type {(m: import('@whiskeysockets/baileys').proto.WebMessageInfo) => Boolean} */ const filter = (m) => m.key?.id == id; const messages = {}; const messageFind = Object.entries(messages) .find(([, msgs]) => { return msgs.find(filter); }); message = messageFind?.[1]?.find(filter); } else { // @ts-ignore jid = jid?.decodeJid?.(); const messages = {}; if (!(jid in messages)) return null; message = messages[jid].find((m) => m.key.id == id); } return message ? message : null; } export default { bind, useSingleFileAuthState, loadMessage, };