UNPKG

@faouzkk/baileys

Version:
457 lines (456 loc) 18.1 kB
"use strict"; /** * @file SQLite Store Management for Baileys * @license MIT * * Use and modify this code freely under the MIT license. If you use this in your projects, attribution would be appreciated. * * Author: Zaid (GitHub: hacxk) * * This module provides a SQLite-based storage solution for managing Baileys data, including message history, * contacts, groups, and connection state. It uses SQLite to persist data and LRU cache for efficient data * retrieval. It supports CRUD operations for managing messages, contacts, group metadata, and more. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeInSQLiteStore = void 0; const Utils_1 = require("../Utils"); const sqlite3_1 = __importDefault(require("sqlite3")); const sqlite_1 = require("sqlite"); const lru_cache_1 = require("lru-cache"); const pino_1 = __importDefault(require("pino")); const path_1 = __importDefault(require("path")); const fs_1 = require("fs"); const promises_1 = require("fs/promises"); async function makeInSQLiteStore(instance_id, dbPath, logger) { const state = null; const cache = new lru_cache_1.LRUCache({ max: 10000 }); const writeQueue = []; let isWriting = false; const dbDirectory = path_1.default.dirname(dbPath); if (!(0, fs_1.existsSync)(dbDirectory)) { await (0, promises_1.mkdir)(dbDirectory, { recursive: true }); logger === null || logger === void 0 ? void 0 : logger.info(`Created database directory: ${dbDirectory}`); } // Initialize SQLite database const db = await (0, sqlite_1.open)({ filename: dbPath, driver: sqlite3_1.default.Database, }); if (!db) throw new Error("No SQLite database connection established"); const log = logger || (0, pino_1.default)({ level: "info" }); const createTablesIfNotExist = async () => { const queries = [ `CREATE TABLE IF NOT EXISTS baileys_store ( instance_id TEXT NOT NULL, key TEXT NOT NULL, value TEXT, PRIMARY KEY (instance_id, key) );`, `CREATE TABLE IF NOT EXISTS message_status ( instance_id TEXT NOT NULL, jid TEXT NOT NULL, message_id TEXT NOT NULL, from_me INTEGER DEFAULT 0, is_read INTEGER DEFAULT 0, PRIMARY KEY (instance_id, jid, message_id) );`, ]; for (const query of queries) { try { await db.exec(query); log.info(`Table created or already exists: ${query.split(" ")[5]}`); } catch (error) { log.error({ error, query }, "Failed to create table"); } } }; await createTablesIfNotExist(); const processWriteQueue = async () => { if (isWriting || writeQueue.length === 0) return; isWriting = true; while (writeQueue.length > 0) { const writeOp = writeQueue.shift(); if (writeOp) { try { await writeOp(); } catch (error) { log.error({ error }, "Error processing write queue"); } } } isWriting = false; }; const saveToSQLite = async (key, data) => { const writeOp = async () => { try { await db.run("INSERT OR REPLACE INTO baileys_store (instance_id, key, value) VALUES (?, ?, ?)", [instance_id, key, JSON.stringify(data)]); } catch (error) { log.error({ error, key }, "Failed to save data to SQLite"); } }; writeQueue.push(writeOp); if (!isWriting) { processWriteQueue(); } }; const getData = async (key) => { if (cache.has(key)) return cache.get(key); try { const row = await db.get("SELECT value FROM baileys_store WHERE instance_id = ? AND key = ?", [instance_id, key]); if (row) { try { const data = JSON.parse(row.value); cache.set(key, data); return data; } catch (parseError) { log.error({ error: parseError, key }, "Failed to parse data from SQLite"); return null; } } } catch (error) { log.error({ error, key }, "Failed to get data from SQLite"); } return null; }; const setData = async (key, data) => { cache.set(key, data); await saveToSQLite(key, data); }; const bind = async (ev, sock) => { ev.on("connection.update", async (update) => { Object.assign(state || {}, update); await setData(`connection.update`, update); if (update.connection === "open") { await (0, Utils_1.delay)(2000); const groups = await sock.groupFetchAllParticipating(); for (const groupId in groups) { const groupMetadata = groups[groupId]; try { await setData(`group-${groupId}`, groupMetadata); } catch (error) { log.error({ error }, "Failed to save group participant"); } } } }); ev.on("messaging-history.set", async (data) => { const { chats, contacts, messages, isLatest } = data; await setData(`chats`, chats); await setData(`contacts`, contacts); for (const message of messages) { if (message.key && message.key.remoteJid && message.key.id) { await setData(`messages-${message.key.remoteJid}-${message.key.id}`, message); } } if (isLatest !== undefined) { await setData(`messaging-history-is-latest`, isLatest); } }); ev.on("chats.upsert", async (chats) => { await setData(`chats`, chats); }); ev.on("chats.update", async (updates) => { for (const update of updates) { const currentUnixTime = Math.floor(Date.now() / 1000); if (update.id) { const chat = (await getData(`chat-${update.id}-${currentUnixTime}`)) || {}; Object.assign(chat, update); await setData(`chat-${update.id}-${currentUnixTime}`, chat); } } }); ev.on("presence.update", async (update) => { await setData(`presence-${update.id}`, update.presences); }); ev.on("contacts.upsert", async (contacts) => { const existingContacts = (await getData(`contacts`)) || []; const updatedContacts = [...existingContacts, ...contacts]; await setData(`contacts`, updatedContacts); }); ev.on("messages.upsert", async (update) => { for (const message of update.messages) { if (message.key && message.key.remoteJid && message.key.id) { await setData(`messages-${message.key.remoteJid}-${message.key.id}`, message); await updateMessageStatus(message.key.remoteJid, message.key.id, message.key.fromMe ? "sent" : "delivered"); } } }); ev.on("message-receipt.update", async (updates) => { for (const update of updates) { if (update.key.remoteJid && update.key.id) { await updateMessageStatus(update.key.remoteJid, update.key.id, "read"); } } }); ev.on("groups.update", async (updates) => { for (const update of updates) { if (update.id) { const group = (await getData(`group-${update.id}`)) || {}; Object.assign(group, update); await setData(`group-${update.id}`, group); log.info({ groupId: update.id }, "Group updated"); } } }); ev.on("groups.upsert", async (groupMetadata) => { for (const metadata of groupMetadata) { await setData(`group-${metadata.id}`, metadata); log.info({ groupId: metadata.id }, "Group upserted"); } }); ev.on("contacts.update", async (updates) => { for (const update of updates) { if (update.id) { const contact = (await getData(`contact-${update.id}`)) || {}; Object.assign(contact, update); await setData(`contact-${update.id}`, contact); log.info({ contactId: update.id }, "Contact updated"); } } }); ev.on("chats.delete", async (deletions) => { for (const chatId of deletions) { await setData(`chat-${chatId}`, null); log.info({ chatId }, "Chat deleted"); } }); ev.on("messages.delete", async (item) => { if ("all" in item) { const messages = (await getData(`messages-${item.jid}`)) || {}; for (const id in messages) { await setData(`messages-${item.jid}-${id}`, null); } log.info({ jid: item.jid }, "All messages deleted"); } else { for (const key of item.keys) { if (key.remoteJid && key.id) { await setData(`messages-${key.remoteJid}-${key.id}`, null); log.info({ messageId: key.id, jid: key.remoteJid }, "Message deleted"); } } } }); ev.on("labels.edit", async (label) => { await setData(`label-${label.id}`, label); }); ev.on("labels.association", async (data) => { const { association, type } = data; const key = `label-association-${association.chatId}-${association.labelId}`; if (type === "add") { await setData(key, association); } else if (type === "remove") { await setData(key, null); } }); }; const getMessageLabels = async (messageId) => { const rows = await db.all("SELECT value FROM baileys_store WHERE instance_id = ? AND key LIKE ?", [instance_id, `label-association-%-${messageId}`]); return rows .map((row) => { try { const association = JSON.parse(row.value); return association.labelId; } catch (parseError) { log.error({ error: parseError }, "Failed to parse label association"); return null; } }) .filter(Boolean); }; const mostRecentMessage = async (jid) => { const row = await db.get("SELECT value FROM baileys_store WHERE instance_id = ? AND key LIKE ? ORDER BY key DESC LIMIT 1", [instance_id, `messages-${jid}-%`]); if (row) { try { return JSON.parse(row.value); } catch (parseError) { log.error({ error: parseError }, "Failed to parse most recent message"); throw parseError; } } throw new Error("No messages found for the given JID"); }; const loadMessages = async (jid, count) => { const messages = []; const rows = await db.all("SELECT value FROM baileys_store WHERE instance_id = ? AND key LIKE ? ORDER BY key DESC LIMIT ?", [instance_id, `messages-${jid}-%`, count]); for (const row of rows) { try { const message = JSON.parse(row.value); messages.push(message); } catch (parseError) { log.error({ error: parseError }, "Failed to parse message data"); } } return messages; }; const updateMessageStatus = async (jid, id, status) => { try { const fromMe = status === "sent" ? 1 : 0; const isRead = status === "read" ? 1 : 0; await db.run("INSERT OR REPLACE INTO message_status (instance_id, jid, message_id, from_me, is_read) VALUES (?, ?, ?, ?, ?)", [instance_id, jid, id, fromMe, isRead]); } catch (error) { log.error({ error, jid, id, status }, "Failed to update message status"); } }; const removeAllData = async () => { try { await db.run("DELETE FROM baileys_store WHERE instance_id = ?", [ instance_id, ]); await db.run("DELETE FROM message_status WHERE instance_id = ?", [ instance_id, ]); cache.clear(); log.info({ instance_id }, "All data removed for instance"); } catch (error) { log.error({ error, instance_id }, "Failed to remove all data"); throw error; } }; const loadMessage = async (jid, id) => { return await getData(`messages-${jid}-${id}`); }; const loadAllGroupMetadata = async () => { try { const rows = await db.all("SELECT value FROM baileys_store WHERE instance_id = ? AND key LIKE ?", [instance_id, "group-%"]); return rows .map((row) => { try { console.log(row.value); return JSON.parse(row.value); } catch (parseError) { log.error({ error: parseError }, "Failed to parse group metadata"); return null; } }) .filter(Boolean); } catch (error) { log.error({ error }, "Failed to load all group metadata"); return []; } }; const loadGroupMetadataByJid = async (jid) => { return await getData(`group-${jid}`); }; const customQuery = async (query, params) => { try { return await db.all(query, params); } catch (error) { log.error({ error, query }, "Failed to execute custom query"); throw error; } }; const getAllContacts = async () => { try { const rows = await db.all("SELECT value FROM baileys_store WHERE instance_id = ? AND key LIKE ?", [instance_id, "contact-%"]); return rows .map((row) => { try { return JSON.parse(row.value); } catch (parseError) { log.error({ error: parseError }, "Failed to parse contact data"); return null; } }) .filter(Boolean); } catch (error) { log.error({ error }, "Failed to get all contacts"); return []; } }; const getGroupByJid = async (jid) => { return await getData(`group-${jid}`); }; const fetchImageUrl = async (jid, sock) => { if (!sock) return undefined; try { const profilePictureUrl = await sock.profilePictureUrl(jid); return profilePictureUrl; } catch (error) { log.error({ error, jid }, "Failed to fetch image URL"); return null; } }; const fetchGroupMetadata = async (jid, sock) => { if (!sock) throw new Error("WASocket is undefined"); try { const metadata = await sock.groupMetadata(jid); await setData(`group-${jid}`, metadata); return metadata; } catch (error) { log.error({ error, jid }, "Failed to fetch group metadata"); throw error; } }; const fetchMessageReceipts = async ({ remoteJid, id, }) => { const message = await loadMessage(remoteJid, id); return message === null || message === void 0 ? void 0 : message.userReceipt; }; const toJSON = () => { return { chats: getData("chats"), contacts: getData("contacts"), messages: getData("messages"), labels: getData("labels"), labelAssociations: getData("labelAssociations"), }; }; const fromJSON = (json) => { const { chats, contacts, messages, labels, labelAssociations } = json; setData("chats", chats); setData("contacts", contacts); setData("messages", messages); setData("labels", labels); setData("labelAssociations", labelAssociations); }; return { state, bind, getData, setData, loadMessages, loadMessage, loadAllGroupMetadata, loadGroupMetadataByJid, customQuery, getAllContacts, getGroupByJid, updateMessageStatus, removeAllData, getMessageLabels, mostRecentMessage, fetchImageUrl, fetchGroupMetadata, fetchMessageReceipts, toJSON, fromJSON, }; } exports.makeInSQLiteStore = makeInSQLiteStore;