From 8f201b59e38254e3bc996880541476a3eeca10a4 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Sun, 19 Apr 2026 01:09:36 +0530 Subject: [PATCH] first commit --- .gitignore | 38 + README.md | 85 ++ auth.js | 46 + index.js | 1774 ++++++++++++++++++++++++++++++++ logs/app-2026-04-18.jsonl | 2 + package-lock.json | 1997 +++++++++++++++++++++++++++++++++++++ package.json | 18 + state copy.json | 50 + state.json | 20 + 9 files changed, 4030 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 auth.js create mode 100644 index.js create mode 100644 logs/app-2026-04-18.jsonl create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 state copy.json create mode 100644 state.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2772d8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Node modules +node_modules/ +.wwebjs_auth/ +.wwebjs_cache/ + +# Logs +*.log +app.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment variables +.env +.env.* + +# OS files +.DS_Store +Thumbs.db + +# Editor / IDE +.vscode/ +.idea/ + +# Temporary files +*.tmp +*.temp + +# Build / cache +dist/ +build/ +.cache/ + +# Misc +*.bak + +credentials.json +token.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f270b1 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# WhatsApp -> Google Drive batch uploader (Baileys, `mcb` commands) + +Uploads files from WhatsApp chats into structured Google Drive folders using batch commands. + +## Commands +- User-friendly (case-insensitive): `start`, `done`, `cancel`, `status`, `help` +- Also supported fallback: `mcb-*` and legacy `hi mcb` / `bye mcb` +- Batch management: + - `rename ` -> change active batch target folder + - `list` -> show queued file names and sizes + - `undo` -> remove last queued file + - `retry-failed` -> retry previously failed uploads +- Project/admin: + - `mcb-project ` -> set/save a project folder + - `admin-stats` -> today + lifetisw + me operational stats + - `admin-stats 7d` -> rolling summary for last N days (example: `7d`) + - `admin-stats YYYY-MM-DD` -> stats for a specific date + - `admin-health` -> live ops snapshot (heartbeat, queues, alerts) + +## Local setup (Windows/Linux) +1) Install dependencies: +```bash +npm install +``` + +2) Create `.env` (example values): +```env +GOOGLE_CREDENTIALS=./credentials.json +GOOGLE_TOKEN=./token.json +DEFAULT_ROOT_FOLDER=Whatsapp-Drive +WA_CLIENT_ID=MCB-bot +BAILEYS_AUTH_DIR=./baileys_auth +OWNER_NUMBER= +BATCH_TTL_MIN=30 +IDLE_RESTART_CHECK_MIN=60 +IDLE_RESTART_MIN=240 +IDLE_RESTART_MAX_PER_DAY=6 +DOWNLOAD_TIMEOUT_MS=900000 +DOWNLOAD_MAX_RETRIES=2 +DOWNLOAD_RETRY_DELAY_MS=5000 +MAX_MEDIA_MB=95 +WA_PROTOCOL_TIMEOUT=9000000 +WA_INIT_RETRIES=3 +WA_INIT_RETRY_DELAY_MS=5000 +WA_HEADLESS=true +LOG_DIR=./logs +HEARTBEAT_INTERVAL_SEC=60 +RETRY_MAX_ITEMS=200 +SMART_SUBFOLDERS=true +FAIL_ALERT_WINDOW=10 +FAIL_ALERT_THRESHOLD_PCT=20 +FAIL_ALERT_COOLDOWN_MIN=30 +# Optional on Linux VPS if Chromium is not auto-detected: +# WA_EXECUTABLE_PATH=/usr/bin/chromium-browser +``` + +3) Put Google OAuth client JSON at `credentials.json`. + +4) Generate Drive token: +```bash +npm run auth +``` + +5) Start bot: +```bash +npm start +``` + +## Linux VPS notes +- Install Chromium/Chrome and required libraries for Puppeteer. +- If Chromium path is custom, set `WA_EXECUTABLE_PATH`. +- Use a process manager (for example PM2/systemd) for auto-restart. + +## Runtime notes +- Temporary files are written under your OS temp directory (`MCB-batches`). +- Upload target folders are auto-created in Drive. +- Uploaded folder gets read-only public link (`anyone` with `reader` role). +- Duplicate protection is enabled per batch (same filename + size is skipped). +- Failed uploads are moved to retry queue and can be retried with `retry-failed`. +- Structured rotating logs are written to `logs/app-YYYY-MM-DD.jsonl`. +- Heartbeat logs are emitted periodically for VPS monitoring. +- Uploads are auto-organized into Drive subfolders by date + event type (`Invoices`, `Photos`, `Documents`). +- Active upload sessions are restored from disk after process restart (within batch TTL). +- Owner receives failure alerts when recent upload failures exceed configured threshold. diff --git a/auth.js b/auth.js new file mode 100644 index 0000000..5cd5f98 --- /dev/null +++ b/auth.js @@ -0,0 +1,46 @@ +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); +const { google } = require("googleapis"); +require("dotenv").config(); + +const CREDENTIALS_PATH = process.env.GOOGLE_CREDENTIALS || "./credentials.json"; +const TOKEN_PATH = process.env.GOOGLE_TOKEN || "./token.json"; + +function loadCredentials() { + const raw = fs.readFileSync(CREDENTIALS_PATH, "utf8"); + const json = JSON.parse(raw); + const cfg = json.installed || json.web; + if (!cfg) throw new Error("Invalid credentials.json. Expected installed/web client."); + return { + client_id: cfg.client_id, + client_secret: cfg.client_secret, + redirect_uris: cfg.redirect_uris, + }; +} + +async function main() { + const { client_id, client_secret, redirect_uris } = loadCredentials(); + const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]); + + const authUrl = oAuth2Client.generateAuthUrl({ + access_type: "offline", + scope: ["https://www.googleapis.com/auth/drive.file"], + prompt: "consent", + }); + + console.log("Authorize this app by visiting this url:\n", authUrl, "\n"); + + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + rl.question("Paste the code from the page here: ", async (code) => { + rl.close(); + const { tokens } = await oAuth2Client.getToken(code.trim()); + fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2)); + console.log("āœ… Token stored at:", path.resolve(TOKEN_PATH)); + }); +} + +main().catch((e) => { + console.error("Auth error:", e); + process.exit(1); +}); diff --git a/index.js b/index.js new file mode 100644 index 0000000..2556a3a --- /dev/null +++ b/index.js @@ -0,0 +1,1774 @@ +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const readline = require("readline"); +const crypto = require("crypto"); +const mime = require("mime-types"); +const qrcode = require("qrcode-terminal"); +const pino = require("pino"); +require("dotenv").config(); + +const { + default: makeWASocket, + useMultiFileAuthState, + fetchLatestBaileysVersion, + Browsers, + downloadMediaMessage, + DisconnectReason, +} = require("@whiskeysockets/baileys"); +const { google } = require("googleapis"); + +const CREDENTIALS_PATH = process.env.GOOGLE_CREDENTIALS || "./credentials.json"; +const TOKEN_PATH = process.env.GOOGLE_TOKEN || "./token.json"; +const DEFAULT_ROOT_FOLDER = process.env.DEFAULT_ROOT_FOLDER || "Whatsapp-Drive"; +const WA_CLIENT_ID = process.env.WA_CLIENT_ID || "MCB-bot"; +const BAILEYS_AUTH_DIR = process.env.BAILEYS_AUTH_DIR || "./baileys_auth"; +const BATCH_TTL_MIN = Number(process.env.BATCH_TTL_MIN || "30"); +const IDLE_RESTART_CHECK_MIN = Number(process.env.IDLE_RESTART_CHECK_MIN || "60"); +const IDLE_RESTART_MIN = Number(process.env.IDLE_RESTART_MIN || "240"); +const IDLE_RESTART_MAX_PER_DAY = Number(process.env.IDLE_RESTART_MAX_PER_DAY || "6"); +const DOWNLOAD_TIMEOUT_MS = Number(process.env.DOWNLOAD_TIMEOUT_MS || "900000"); +const DOWNLOAD_MAX_RETRIES = Number(process.env.DOWNLOAD_MAX_RETRIES || "2"); +const DOWNLOAD_RETRY_DELAY_MS = Number(process.env.DOWNLOAD_RETRY_DELAY_MS || "5000"); +const MAX_MEDIA_MB = Number(process.env.MAX_MEDIA_MB || "95"); +const WA_INIT_RETRIES = Number(process.env.WA_INIT_RETRIES || "3"); +const WA_INIT_RETRY_DELAY_MS = Number(process.env.WA_INIT_RETRY_DELAY_MS || "5000"); +const WA_HEADLESS = process.env.WA_HEADLESS !== "false"; +const WA_EXECUTABLE_PATH = process.env.WA_EXECUTABLE_PATH || ""; +const AUTO_AUTH_ON_MISSING_TOKEN = process.env.AUTO_AUTH_ON_MISSING_TOKEN !== "false"; +const CMD_PREFIX = "mcb"; +const LOG_DIR = process.env.LOG_DIR || "./logs"; +const HEARTBEAT_INTERVAL_SEC = Number(process.env.HEARTBEAT_INTERVAL_SEC || "60"); +const RETRY_MAX_ITEMS = Number(process.env.RETRY_MAX_ITEMS || "200"); +const SMART_SUBFOLDERS = process.env.SMART_SUBFOLDERS !== "false"; +const FAIL_ALERT_WINDOW = Number(process.env.FAIL_ALERT_WINDOW || "10"); +const FAIL_ALERT_THRESHOLD_PCT = Number(process.env.FAIL_ALERT_THRESHOLD_PCT || "20"); +const FAIL_ALERT_COOLDOWN_MIN = Number(process.env.FAIL_ALERT_COOLDOWN_MIN || "30"); +const BATCH_META_FILE = ".batch.json"; + +const STATE_FILE = "./state.json"; // stores per-user selected project folderId +const TMP_BASE = path.join(os.tmpdir(), "MCB-batches"); +const LOG_FILE = "./app.log"; +const OWNER_NUMBER = process.env.OWNER_NUMBER || ""; // e.g. 15551234567 (no +) +const ROOT_CACHE = { + rootFolderId: null, + chatFolderByChatId: new Map(), +}; +let DRIVE_CLIENT = null; +let sock = null; + +function getDateKey(ts = Date.now()) { + const d = new Date(ts); + return d.toISOString().slice(0, 10); +} + +function normalizeState(state) { + const base = state && typeof state === "object" ? state : {}; + if (!base.users || typeof base.users !== "object") base.users = {}; + if (!base.analytics || typeof base.analytics !== "object") base.analytics = {}; + if (!base.analytics.daily || typeof base.analytics.daily !== "object") base.analytics.daily = {}; + if (!base.analytics.lifetime || typeof base.analytics.lifetime !== "object") { + base.analytics.lifetime = { + batchesStarted: 0, + batchesCompleted: 0, + filesQueued: 0, + filesUploaded: 0, + filesFailed: 0, + uploadBytes: 0, + completedBatchFilesTotal: 0, + }; + } + if (!Array.isArray(base.analytics.uploadOutcomes)) base.analytics.uploadOutcomes = []; + if (!base.analytics.alerts || typeof base.analytics.alerts !== "object") { + base.analytics.alerts = { + lastFailureAlertAt: 0, + lastFailureRate: 0, + }; + } + return base; +} + +function ensureUserState(state, chatId) { + if (!state.users[chatId]) { + state.users[chatId] = { + projectName: null, + folderId: null, + lastBatchFolderName: null, + lastBatchFolderId: null, + retryQueue: [], + }; + } + if (!Array.isArray(state.users[chatId].retryQueue)) { + state.users[chatId].retryQueue = []; + } + return state.users[chatId]; +} + +function getDailyStats(state, dateKey = getDateKey()) { + if (!state.analytics.daily[dateKey] || typeof state.analytics.daily[dateKey] !== "object") { + state.analytics.daily[dateKey] = { + uniqueUsers: {}, + messages: 0, + batchesStarted: 0, + batchesCompleted: 0, + filesQueued: 0, + filesUploaded: 0, + filesFailed: 0, + uploadBytes: 0, + completedBatchFilesTotal: 0, + }; + } + return state.analytics.daily[dateKey]; +} + +function markUserActive(state, chatId) { + const today = getDailyStats(state); + today.uniqueUsers[chatId] = 1; +} + +function incrementStats(state, updates) { + const today = getDailyStats(state); + const lifetime = state.analytics.lifetime; + for (const [k, v] of Object.entries(updates || {})) { + if (typeof v !== "number") continue; + if (typeof today[k] === "number") today[k] += v; + if (typeof lifetime[k] === "number") lifetime[k] += v; + } +} + +function loadJson(file, fallback) { + try { + return JSON.parse(fs.readFileSync(file, "utf8")); + } catch { + return fallback; + } +} + +function saveJson(file, obj) { + fs.writeFileSync(file, JSON.stringify(obj, null, 2)); +} + +function logLine(level, message, meta) { + const ts = new Date().toISOString(); + const base = `[${ts}] [${level}] ${message}`; + const line = meta ? `${base} ${JSON.stringify(meta)}` : base; + const entry = { ts, level, message, ...(meta || {}) }; + console.log(line); + try { + fs.mkdirSync(LOG_DIR, { recursive: true }); + fs.appendFileSync(LOG_FILE, line + "\n"); + fs.appendFileSync(path.join(LOG_DIR, `app-${getDateKey()}.jsonl`), JSON.stringify(entry) + "\n"); + } catch { } +} + +function logInfo(message, meta) { + logLine("INFO", message, meta); +} + +function logWarn(message, meta) { + logLine("WARN", message, meta); +} + +function logError(message, meta) { + logLine("ERROR", message, meta); +} + +function getOwnerChatId() { + if (!OWNER_NUMBER) return null; + return `${OWNER_NUMBER}@s.whatsapp.net`; +} + +function loadCredentials() { + const raw = fs.readFileSync(CREDENTIALS_PATH, "utf8"); + const json = JSON.parse(raw); + const cfg = json.installed || json.web; + if (!cfg) throw new Error("Invalid credentials.json. Expected installed/web client."); + return cfg; +} + +function getDriveClient() { + if (DRIVE_CLIENT) return DRIVE_CLIENT; + const cfg = loadCredentials(); + const tokens = loadJson(TOKEN_PATH, null); + if (!tokens) throw new Error("Missing token.json. Run `node auth.js` first."); + + const oAuth2Client = new google.auth.OAuth2( + cfg.client_id, + cfg.client_secret, + (cfg.redirect_uris || [])[0] + ); + + oAuth2Client.setCredentials(tokens); + + oAuth2Client.on("tokens", (newTokens) => { + const current = loadJson(TOKEN_PATH, {}); + saveJson(TOKEN_PATH, { ...current, ...newTokens }); + }); + + DRIVE_CLIENT = google.drive({ version: "v3", auth: oAuth2Client }); + return DRIVE_CLIENT; +} + +function hasUsableToken() { + const tokens = loadJson(TOKEN_PATH, null); + return Boolean(tokens && (tokens.refresh_token || tokens.access_token)); +} + +function askInput(question) { + return new Promise((resolve) => { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + rl.question(question, (answer) => { + rl.close(); + resolve(String(answer || "").trim()); + }); + }); +} + +async function ensureGoogleToken() { + if (hasUsableToken()) return; + + if (!AUTO_AUTH_ON_MISSING_TOKEN) { + throw new Error("Missing token.json. Run `node auth.js` first."); + } + + if (!process.stdin.isTTY) { + throw new Error("Missing token.json and no interactive terminal. Run `node auth.js` manually."); + } + + const cfg = loadCredentials(); + const oAuth2Client = new google.auth.OAuth2( + cfg.client_id, + cfg.client_secret, + (cfg.redirect_uris || [])[0] + ); + + const authUrl = oAuth2Client.generateAuthUrl({ + access_type: "offline", + scope: ["https://www.googleapis.com/auth/drive.file"], + prompt: "consent", + }); + + logWarn("token.json not found. Starting interactive Google OAuth flow."); + console.log("\nAuthorize this app by visiting this URL:\n", authUrl, "\n"); + const code = await askInput("Paste the code from Google here: "); + if (!code) throw new Error("No authorization code entered."); + + const { tokens } = await oAuth2Client.getToken(code); + saveJson(TOKEN_PATH, tokens); + logInfo("Google token saved", { tokenPath: path.resolve(TOKEN_PATH) }); +} + +async function findOrCreateFolder(drive, folderName, parentId = null) { + const qParts = [ + `mimeType='application/vnd.google-apps.folder'`, + `name='${folderName.replace(/'/g, "\\'")}'`, + `trashed=false`, + ]; + if (parentId) qParts.push(`'${parentId}' in parents`); + + const res = await drive.files.list({ + q: qParts.join(" and "), + fields: "files(id, name)", + spaces: "drive", + }); + + if (res.data.files && res.data.files.length > 0) return res.data.files[0].id; + + const createRes = await drive.files.create({ + requestBody: { + name: folderName, + mimeType: "application/vnd.google-apps.folder", + parents: parentId ? [parentId] : undefined, + }, + fields: "id", + }); + + return createRes.data.id; +} + +async function getOrCreateRootFolderId(drive) { + if (ROOT_CACHE.rootFolderId) return ROOT_CACHE.rootFolderId; + ROOT_CACHE.rootFolderId = await findOrCreateFolder(drive, DEFAULT_ROOT_FOLDER); + return ROOT_CACHE.rootFolderId; +} + +async function getOrCreateClientFolder(drive, msg) { + const chatId = getChatIdFromMsg(msg); + if (ROOT_CACHE.chatFolderByChatId.has(chatId)) { + return ROOT_CACHE.chatFolderByChatId.get(chatId); + } + + const rootFolderId = await getOrCreateRootFolderId(drive); + const clientFolderName = await getClientFolderName(msg); + const clientFolderId = await findOrCreateFolder(drive, clientFolderName, rootFolderId); + ROOT_CACHE.chatFolderByChatId.set(chatId, clientFolderId); + return clientFolderId; +} + +async function getClientFolderName(msg) { + const pushName = String(msg?.pushName || "").trim(); + if (pushName) return pushName; + const chatId = getChatIdFromMsg(msg); + const number = String(chatId).split("@")[0]; + return `${number}`; +} + +async function setFolderPublicView(drive, folderId) { + await drive.permissions.create({ + fileId: folderId, + requestBody: { + type: "anyone", + role: "reader", + }, + }); +} + +async function uploadFileStreamToDrive(drive, { filePath, filename, parentFolderId, mimeType }) { + const res = await drive.files.create({ + requestBody: { + name: filename, + parents: [parentFolderId], + }, + media: { + mimeType: mimeType || "application/octet-stream", + body: fs.createReadStream(filePath), + }, + fields: "id, webViewLink, name", + }); + + return res.data; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function uploadWithRetry(drive, file, opts, maxRetries = 3) { + let attempt = 0; + while (true) { + try { + return await uploadFileStreamToDrive(drive, opts); + } catch (err) { + attempt += 1; + const isLast = attempt >= maxRetries; + logWarn("Upload failed", { attempt, maxRetries, error: err?.message, file: file?.filename }); + if (isLast) throw err; + await sleep(1000 * attempt); + } + } +} + +// ---------- Batch handling (disk-based) ---------- +const BATCH = new Map(); +const BATCH_TTL_MS = BATCH_TTL_MIN * 60 * 1000; + +function now() { + return Date.now(); +} + +function getBatch(chatId) { + const b = BATCH.get(chatId); + if (!b) return null; + + if (now() - b.lastActivityAt > BATCH_TTL_MS) { + clearBatch(chatId); + return null; + } + return b; +} + +function startBatch(chatId, folderId, folderName) { + clearBatch(chatId); + const dir = path.join(TMP_BASE, sanitizeId(chatId), sanitizeId(folderName)); + fs.mkdirSync(dir, { recursive: true }); + BATCH.set(chatId, { + startedAt: now(), + lastActivityAt: now(), + dir, + files: [], + fileSignatures: new Set(), + failedFiles: [], + activeDownloads: 0, + doneRequested: false, + uploadInProgress: false, + pendingDoneMsg: null, + pendingDoneTimer: null, + doneTriggerMsg: null, + folderId, + folderName, + }); + persistBatch(chatId); +} + +function touchBatch(chatId) { + const b = BATCH.get(chatId); + if (b) { + b.lastActivityAt = now(); + persistBatch(chatId); + } +} + +function clearBatch(chatId) { + const b = BATCH.get(chatId); + if (b?.pendingDoneTimer) { + clearInterval(b.pendingDoneTimer); + } + if (b && b.dir && fs.existsSync(b.dir)) { + fs.rmSync(b.dir, { recursive: true, force: true }); + } + BATCH.delete(chatId); +} + +function sanitizeId(id) { + return String(id).replace(/[^a-zA-Z0-9_-]/g, "_"); +} + +function sanitizeFileName(name) { + const safe = name.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").trim(); + return safe || `file_${Date.now()}`; +} + +function buildSafeFileName(media, index) { + const ts = new Date().toISOString().replace(/[:.]/g, ""); + const original = media.filename ? path.basename(media.filename) : ""; + const extFromMime = mime.extension(media.mimetype) || ""; + + if (original) { + const parsed = path.parse(original); + const base = sanitizeFileName(parsed.name); + const ext = parsed.ext || (extFromMime ? `.${extFromMime}` : ""); + return `${base}_${ts}${ext}`; + } + + const base = `file_${index}`; + const ext = extFromMime ? `.${extFromMime}` : ""; + return `${base}_${ts}${ext}`; +} + +function formatProgressBar(done, total, width = 12) { + const ratio = total ? done / total : 0; + const filled = Math.round(ratio * width); + const empty = Math.max(0, width - filled); + return `${"🟩".repeat(filled)}${"⬜".repeat(empty)}`; +} + +function bytesToMb(bytes) { + return (bytes / (1024 * 1024)).toFixed(2); +} + +function formatElapsedSec(ms) { + const totalSec = Math.floor(ms / 1000); + const min = Math.floor(totalSec / 60); + const sec = totalSec % 60; + return `${min}m ${sec}s`; +} + +function bytesToDisplay(bytes) { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${bytesToMb(bytes)} MB`; +} + +function buildFileSignature(sourceName, sizeBytes) { + return `${String(sourceName || "").toLowerCase()}|${Number(sizeBytes || 0)}`; +} + +function getSourceNameFromMedia(media) { + const original = media?.filename ? path.basename(media.filename) : ""; + if (original) return sanitizeFileName(original); + const ext = mime.extension(media?.mimetype || "") || "bin"; + return `unnamed.${ext}`; +} + +function getRetryDir(chatId) { + return path.join(TMP_BASE, "retry", sanitizeId(chatId)); +} + +function enqueueRetryFiles(chatId, folderName, failedFiles) { + if (!Array.isArray(failedFiles) || failedFiles.length === 0) return []; + const retryDir = getRetryDir(chatId); + fs.mkdirSync(retryDir, { recursive: true }); + const queued = []; + + for (const file of failedFiles) { + try { + const suffix = `${Date.now()}_${Math.floor(Math.random() * 10000)}`; + const destName = `${path.parse(file.filename).name}_${suffix}${path.extname(file.filename)}`; + const destPath = path.join(retryDir, sanitizeFileName(destName)); + fs.renameSync(file.filePath, destPath); + queued.push({ + filePath: destPath, + filename: file.filename, + mimeType: file.mimeType, + sizeBytes: Number(file.sizeBytes || 0), + folderId: file.parentFolderId, + folderName, + failedAt: new Date().toISOString(), + sourceName: file.sourceName || file.filename, + receivedAt: Number(file.receivedAt || Date.now()), + }); + } catch (err) { + logWarn("Failed to enqueue retry file", { chatId, file: file?.filename, error: err?.message }); + } + } + + return queued; +} + +function summarizeAdminStats(state) { + const today = getDailyStats(state); + const lifetime = state.analytics.lifetime; + const dailyActiveUsers = Object.keys(today.uniqueUsers || {}).length; + const avgBatchToday = today.batchesCompleted > 0 + ? (today.completedBatchFilesTotal / today.batchesCompleted).toFixed(2) + : "0.00"; + const avgBatchLifetime = lifetime.batchesCompleted > 0 + ? (lifetime.completedBatchFilesTotal / lifetime.batchesCompleted).toFixed(2) + : "0.00"; + + return [ + `šŸ“Š Admin stats (${getDateKey()})`, + `Daily active users: *${dailyActiveUsers}*`, + `Files uploaded today: *${today.filesUploaded}*`, + `Failures today: *${today.filesFailed}*`, + `Avg batch size today: *${avgBatchToday}* files`, + "", + `Lifetime uploaded: *${lifetime.filesUploaded}*`, + `Lifetime failures: *${lifetime.filesFailed}*`, + `Avg batch size lifetime: *${avgBatchLifetime}* files`, + ].join("\n"); +} + +function summarizeAdminStatsForDate(state, dateKey) { + const day = state?.analytics?.daily?.[dateKey]; + if (!day) return `šŸ“Š Admin stats (${dateKey})\nNo data found for this date.`; + const dailyActiveUsers = Object.keys(day.uniqueUsers || {}).length; + const avgBatch = day.batchesCompleted > 0 + ? (day.completedBatchFilesTotal / day.batchesCompleted).toFixed(2) + : "0.00"; + + return [ + `šŸ“Š Admin stats (${dateKey})`, + `Daily active users: *${dailyActiveUsers}*`, + `Files uploaded: *${day.filesUploaded || 0}*`, + `Failures: *${day.filesFailed || 0}*`, + `Avg batch size: *${avgBatch}* files`, + ].join("\n"); +} + +function summarizeAdminStatsForLastDays(state, days) { + const safeDays = Math.max(1, Math.min(365, Number(days || 1))); + let activeUsers = new Set(); + let filesUploaded = 0; + let filesFailed = 0; + let batchesCompleted = 0; + let completedBatchFilesTotal = 0; + const today = new Date(); + + for (let i = 0; i < safeDays; i++) { + const d = new Date(today); + d.setUTCDate(today.getUTCDate() - i); + const key = d.toISOString().slice(0, 10); + const day = state?.analytics?.daily?.[key]; + if (!day) continue; + Object.keys(day.uniqueUsers || {}).forEach((u) => activeUsers.add(u)); + filesUploaded += Number(day.filesUploaded || 0); + filesFailed += Number(day.filesFailed || 0); + batchesCompleted += Number(day.batchesCompleted || 0); + completedBatchFilesTotal += Number(day.completedBatchFilesTotal || 0); + } + + const avgBatch = batchesCompleted > 0 + ? (completedBatchFilesTotal / batchesCompleted).toFixed(2) + : "0.00"; + + return [ + `šŸ“Š Admin stats (last ${safeDays} day${safeDays > 1 ? "s" : ""})`, + `Active users: *${activeUsers.size}*`, + `Files uploaded: *${filesUploaded}*`, + `Failures: *${filesFailed}*`, + `Avg batch size: *${avgBatch}* files`, + ].join("\n"); +} + +function summarizeAdminHealth(state) { + const uptimeSec = Math.round(process.uptime()); + const lastMsgAgeSec = Math.round((Date.now() - lastMessageAt) / 1000); + const memRssMb = Math.round(process.memoryUsage().rss / (1024 * 1024)); + const activeBatches = BATCH.size; + + const users = state?.users || {}; + let retryQueueTotal = 0; + for (const user of Object.values(users)) { + retryQueueTotal += Array.isArray(user?.retryQueue) ? user.retryQueue.length : 0; + } + + let activeDownloads = 0; + let uploadsInProgress = 0; + for (const b of BATCH.values()) { + activeDownloads += Number(b?.activeDownloads || 0); + uploadsInProgress += b?.uploadInProgress ? 1 : 0; + } + + const outcomes = state?.analytics?.uploadOutcomes || []; + const windowSize = Math.max(1, FAIL_ALERT_WINDOW); + const recent = outcomes.slice(-windowSize); + const recentFails = recent.filter((x) => x === 0).length; + const recentFailPct = recent.length ? ((recentFails / recent.length) * 100).toFixed(1) : "0.0"; + const alerts = state?.analytics?.alerts || {}; + const lastAlertAt = alerts.lastFailureAlertAt + ? new Date(alerts.lastFailureAlertAt).toISOString() + : "never"; + + return [ + "🩺 Admin health", + `Uptime: *${uptimeSec}s*`, + `Last message age: *${lastMsgAgeSec}s*`, + `Memory RSS: *${memRssMb} MB*`, + `Active batches: *${activeBatches}*`, + `Active downloads: *${activeDownloads}*`, + `Uploads in progress: *${uploadsInProgress}*`, + `Retry queue total: *${retryQueueTotal}*`, + `Recent failure rate (${recent.length || 0}/${windowSize}): *${recentFailPct}%*`, + `Alert threshold: *${FAIL_ALERT_THRESHOLD_PCT}%*`, + `Last failure alert: *${lastAlertAt}*`, + ].join("\n"); +} + +function getBatchMetaPath(dir) { + return path.join(dir, BATCH_META_FILE); +} + +function randomId() { + return crypto.randomBytes(8).toString("hex"); +} + +function unwrapEphemeral(message) { + if (!message) return null; + if (message.ephemeralMessage?.message) return unwrapEphemeral(message.ephemeralMessage.message); + if (message.viewOnceMessage?.message) return unwrapEphemeral(message.viewOnceMessage.message); + if (message.viewOnceMessageV2?.message) return unwrapEphemeral(message.viewOnceMessageV2.message); + if (message.viewOnceMessageV2Extension?.message) return unwrapEphemeral(message.viewOnceMessageV2Extension.message); + return message; +} + +function getChatIdFromMsg(msg) { + return String(msg?.key?.remoteJid || msg?.from || ""); +} + +function getMessageText(msg) { + const m = unwrapEphemeral(msg?.message) || {}; + if (m.conversation) return m.conversation; + if (m.extendedTextMessage?.text) return m.extendedTextMessage.text; + if (m.imageMessage?.caption) return m.imageMessage.caption; + if (m.videoMessage?.caption) return m.videoMessage.caption; + if (m.documentMessage?.caption) return m.documentMessage.caption; + if (m.buttonsResponseMessage?.selectedButtonId) return m.buttonsResponseMessage.selectedButtonId; + if (m.listResponseMessage?.singleSelectReply?.selectedRowId) return m.listResponseMessage.singleSelectReply.selectedRowId; + if (m.templateButtonReplyMessage?.selectedId) return m.templateButtonReplyMessage.selectedId; + return ""; +} + +function getMediaNode(msg) { + const m = unwrapEphemeral(msg?.message) || {}; + if (m.documentMessage) return { type: "documentMessage", node: m.documentMessage }; + if (m.imageMessage) return { type: "imageMessage", node: m.imageMessage }; + if (m.videoMessage) return { type: "videoMessage", node: m.videoMessage }; + if (m.audioMessage) return { type: "audioMessage", node: m.audioMessage }; + if (m.stickerMessage) return { type: "stickerMessage", node: m.stickerMessage }; + return null; +} + +function getMediaSizeBytesFromMsg(msg) { + const media = getMediaNode(msg); + const len = media?.node?.fileLength; + if (typeof len === "bigint") return Number(len); + if (typeof len === "number") return len; + if (typeof len === "string") return Number(len); + return 0; +} + +function getMimeTypeFromMsg(msg) { + const media = getMediaNode(msg); + return media?.node?.mimetype || "application/octet-stream"; +} + +async function replyToMessage(msg, text) { + const chatId = getChatIdFromMsg(msg); + if (!sock || !chatId) return null; + try { + return await sock.sendMessage(chatId, { text }, { quoted: msg }); + } catch { + try { + return await sock.sendMessage(chatId, { text }); + } catch { + return null; + } + } +} + +async function reactToMessage(msg, emoji) { + const chatId = getChatIdFromMsg(msg); + if (!sock || !chatId || !msg?.key) return; + try { + await sock.sendMessage(chatId, { react: { text: emoji, key: msg.key } }); + } catch { } +} + +async function editOrSendMessage(chatId, previousMessageKey, text, quotedMsg = null) { + if (!sock || !chatId) return null; + if (previousMessageKey) { + try { + return await sock.sendMessage(chatId, { text, edit: previousMessageKey }); + } catch { } + } + try { + return await sock.sendMessage(chatId, { text }, quotedMsg ? { quoted: quotedMsg } : undefined); + } catch { + return null; + } +} + +function persistBatch(chatId) { + const batch = BATCH.get(chatId); + if (!batch || !batch.dir) return; + try { + const files = (batch.files || []).map((f) => ({ + filePath: f.filePath, + filename: f.filename, + mimeType: f.mimeType, + sizeBytes: Number(f.sizeBytes || 0), + sourceName: f.sourceName || "", + signature: f.signature || "", + receivedAt: Number(f.receivedAt || Date.now()), + })); + const meta = { + chatId, + startedAt: Number(batch.startedAt || Date.now()), + lastActivityAt: Number(batch.lastActivityAt || Date.now()), + dir: batch.dir, + folderId: batch.folderId, + folderName: batch.folderName, + files, + }; + fs.writeFileSync(getBatchMetaPath(batch.dir), JSON.stringify(meta, null, 2)); + } catch (err) { + logWarn("Failed to persist batch meta", { chatId, error: err?.message }); + } +} + +function classifyEventFolder(file) { + const name = String(file?.sourceName || file?.filename || "").toLowerCase(); + const mimeType = String(file?.mimeType || "").toLowerCase(); + if (/(invoice|receipt|bill|payment|po|estimate)/.test(name)) return "Invoices"; + if (mimeType.startsWith("image/") || mimeType.startsWith("video/")) return "Photos"; + return "Documents"; +} + +function getDateFolderForFile(file) { + const ts = Number(file?.receivedAt || Date.now()); + return getDateKey(ts); +} + +async function resolveUploadParentFolderId(drive, rootFolderId, file, folderCache) { + if (!SMART_SUBFOLDERS) return rootFolderId; + const dateFolder = getDateFolderForFile(file); + const eventFolder = classifyEventFolder(file); + const cacheKey = `${rootFolderId}|${dateFolder}|${eventFolder}`; + if (folderCache.has(cacheKey)) return folderCache.get(cacheKey); + + const dateFolderId = await findOrCreateFolder(drive, dateFolder, rootFolderId); + const eventFolderId = await findOrCreateFolder(drive, eventFolder, dateFolderId); + folderCache.set(cacheKey, eventFolderId); + return eventFolderId; +} + +function recordUploadOutcomes(state, successCount, failedCount) { + const outcomes = state.analytics.uploadOutcomes; + for (let i = 0; i < Number(successCount || 0); i++) outcomes.push(1); + for (let i = 0; i < Number(failedCount || 0); i++) outcomes.push(0); + const maxKeep = Math.max(FAIL_ALERT_WINDOW * 5, 100); + if (outcomes.length > maxKeep) { + state.analytics.uploadOutcomes = outcomes.slice(-maxKeep); + } +} + +async function maybeSendFailureAlert(state) { + const ownerChatId = getOwnerChatId(); + if (!ownerChatId) return; + + const outcomes = state.analytics.uploadOutcomes || []; + const windowSize = Math.max(1, FAIL_ALERT_WINDOW); + const recent = outcomes.slice(-windowSize); + if (recent.length < windowSize) return; + + const failCount = recent.filter((x) => x === 0).length; + const failRate = (failCount / recent.length) * 100; + if (failRate < FAIL_ALERT_THRESHOLD_PCT) return; + + const nowTs = Date.now(); + const cooldownMs = Math.max(1, FAIL_ALERT_COOLDOWN_MIN) * 60 * 1000; + const alerts = state.analytics.alerts || { lastFailureAlertAt: 0, lastFailureRate: 0 }; + if (alerts.lastFailureAlertAt && nowTs - alerts.lastFailureAlertAt < cooldownMs) return; + + alerts.lastFailureAlertAt = nowTs; + alerts.lastFailureRate = failRate; + state.analytics.alerts = alerts; + saveJson(STATE_FILE, state); + + try { + if (!sock) return; + await sock.sendMessage(ownerChatId, { + text: [ + "āš ļø Upload failure alert", + `Recent window: ${recent.length} files`, + `Failures: ${failCount} (${failRate.toFixed(1)}%)`, + `Threshold: ${FAIL_ALERT_THRESHOLD_PCT}%`, + ].join("\n"), + }); + } catch (err) { + logWarn("Failed to send owner failure alert", { error: err?.message }); + } +} + +function restoreBatchesFromDisk() { + if (!fs.existsSync(TMP_BASE)) return; + + let restored = 0; + const seenChat = new Set(); + const chatDirs = fs.readdirSync(TMP_BASE, { withFileTypes: true }) + .filter((d) => d.isDirectory() && d.name !== "retry"); + + for (const chatDir of chatDirs) { + const chatRoot = path.join(TMP_BASE, chatDir.name); + const subdirs = fs.readdirSync(chatRoot, { withFileTypes: true }).filter((d) => d.isDirectory()); + let best = null; + + for (const sd of subdirs) { + const dir = path.join(chatRoot, sd.name); + const metaPath = getBatchMetaPath(dir); + if (!fs.existsSync(metaPath)) continue; + const meta = loadJson(metaPath, null); + if (!meta || !meta.chatId || !meta.folderId || !meta.folderName) continue; + if (!best || Number(meta.lastActivityAt || 0) > Number(best.lastActivityAt || 0)) best = meta; + } + + if (!best) continue; + const chatId = best.chatId; + if (seenChat.has(chatId)) continue; + + const expired = now() - Number(best.lastActivityAt || 0) > BATCH_TTL_MS; + if (expired) { + try { + if (best.dir && fs.existsSync(best.dir)) fs.rmSync(best.dir, { recursive: true, force: true }); + } catch { } + continue; + } + + const files = Array.isArray(best.files) ? best.files.filter((f) => f.filePath && fs.existsSync(f.filePath)) : []; + const fileSignatures = new Set(files.map((f) => f.signature || buildFileSignature(f.sourceName || f.filename, f.sizeBytes || 0))); + BATCH.set(chatId, { + startedAt: Number(best.startedAt || now()), + lastActivityAt: Number(best.lastActivityAt || now()), + dir: best.dir, + files, + fileSignatures, + failedFiles: [], + activeDownloads: 0, + doneRequested: false, + uploadInProgress: false, + pendingDoneMsg: null, + pendingDoneTimer: null, + doneTriggerMsg: null, + folderId: best.folderId, + folderName: best.folderName, + }); + persistBatch(chatId); + seenChat.add(chatId); + restored += 1; + } + + if (restored > 0) { + logInfo("Restored batches from disk", { restored }); + } +} + +async function sendChatOrReply(chatId, text, triggerMsg = null) { + if (!chatId && triggerMsg) chatId = getChatIdFromMsg(triggerMsg); + return editOrSendMessage(chatId, null, text, triggerMsg || null); +} + +function clearPendingDoneStatus(batch) { + if (!batch) return; + if (batch.pendingDoneTimer) { + clearInterval(batch.pendingDoneTimer); + batch.pendingDoneTimer = null; + } + batch.pendingDoneMsg = null; +} + +async function startPendingDoneStatus(chatId, triggerMsg = null) { + const batch = getBatch(chatId); + if (!batch) return; + if (batch.pendingDoneTimer) return; + + const initial = [ + "ā³ Received done command.", + "Some files are still being received.", + "I will auto-start upload once receiving is complete.", + ].join("\n"); + batch.pendingDoneMsg = await sendChatOrReply(chatId, initial, triggerMsg); + + batch.pendingDoneTimer = setInterval(async () => { + const b = getBatch(chatId); + if (!b || !b.doneRequested || b.uploadInProgress) { + if (b) clearPendingDoneStatus(b); + return; + } + + const text = [ + "ā³ Waiting for incoming files...", + `Receiving now: *${b.activeDownloads || 0}*`, + `Queued: *${b.files.length}*`, + "Refresh: every 5s. Upload starts automatically.", + ].join("\n"); + + try { + const edited = await editOrSendMessage(chatId, b.pendingDoneMsg?.key || null, text, null); + if (edited?.key) b.pendingDoneMsg = edited; + } catch { } + }, 5000); +} + +async function runBatchUpload(chatId, triggerMsg = null) { + const batch = getBatch(chatId); + if (!batch) { + await sendChatOrReply(chatId, "No active batch or no files received. Type start.", triggerMsg); + return; + } + if (batch.uploadInProgress) { + await sendChatOrReply(chatId, "ā« Upload already in progress. I will update status shortly.", triggerMsg); + return; + } + + batch.uploadInProgress = true; + batch.doneRequested = false; + clearPendingDoneStatus(batch); + + try { + const drive = getDriveClient(); + const state = normalizeState(loadJson(STATE_FILE, { users: {} })); + const userState = ensureUserState(state, chatId); + const folderId = batch.folderId || userState.folderId; + const folderLabel = batch.folderName || userState.projectName || "Default"; + + if (!folderId) { + batch.uploadInProgress = false; + await sendChatOrReply(chatId, "āŒ Could not resolve upload folder. Type start to create a new batch.", triggerMsg); + return; + } + if (!batch.files.length) { + batch.uploadInProgress = false; + await sendChatOrReply(chatId, "No files queued yet.", triggerMsg); + return; + } + + const total = batch.files.length; + const startMs = Date.now(); + let uploadedBytes = 0; + let uploadedCount = 0; + let failedCount = 0; + const failedFiles = []; + const folderCache = new Map(); + + const totalBytes = batch.files.reduce((sum, f) => { + try { + return sum + fs.statSync(f.filePath).size; + } catch { + return sum; + } + }, 0); + + const progressMsg = await sendChatOrReply( + chatId, + `ā« Uploading *${total}* file(s) to *${folderLabel}*...\n${formatProgressBar(0, totalBytes)}\n0/${total} | Elapsed: 0s | ETA: --\nUploaded: 0/${bytesToMb(totalBytes)} MB | Speed: 0.00 MB/s`, + triggerMsg + ); + logInfo("Batch upload started", { chatId, count: total, folderLabel }); + + for (let i = 0; i < batch.files.length; i++) { + const f = batch.files[i]; + let parentFolderId = folderId; + try { + parentFolderId = await resolveUploadParentFolderId(drive, folderId, f, folderCache); + const uploaded = await uploadWithRetry( + drive, + f, + { + filePath: f.filePath, + filename: f.filename, + parentFolderId, + mimeType: f.mimeType, + }, + 3 + ); + logInfo("File uploaded", { chatId, name: uploaded.name, link: uploaded.webViewLink }); + uploadedCount += 1; + try { + const sz = fs.statSync(f.filePath).size; + uploadedBytes += sz; + } catch { } + } catch (err) { + failedCount += 1; + failedFiles.push({ ...f, parentFolderId }); + logError("File upload failed", { chatId, filename: f.filename, error: err?.message }); + } + + const done = i + 1; + const elapsedMs = Date.now() - startMs; + const elapsedSec = Math.round(elapsedMs / 1000); + const speedMbps = elapsedMs > 0 ? Number(bytesToMb(uploadedBytes)) / (elapsedMs / 1000) : 0; + const speedBytesPerSec = elapsedMs > 0 ? uploadedBytes / (elapsedMs / 1000) : 0; + const remainingBytes = Math.max(0, totalBytes - uploadedBytes); + const etaSec = speedBytesPerSec > 0 ? Math.round(remainingBytes / speedBytesPerSec) : 0; + try { + const edited = await editOrSendMessage( + chatId, + progressMsg?.key || null, + `ā« Uploading *${total}* file(s) to *${folderLabel}*...\n${formatProgressBar(uploadedBytes, totalBytes)}\n${done}/${total} | Elapsed: ${elapsedSec}s | ETA: ${etaSec}s\nUploaded: ${bytesToMb(uploadedBytes)}/${bytesToMb(totalBytes)} MB | Speed: ${speedMbps.toFixed(2)} MB/s` + ); + if (edited?.key) progressMsg.key = edited.key; + } catch { } + } + + let folderLink = "Unavailable"; + if (uploadedCount > 0) { + await setFolderPublicView(drive, folderId); + const folderMeta = await drive.files.get({ + fileId: folderId, + fields: "id, name, webViewLink", + }); + folderLink = folderMeta?.data?.webViewLink || folderLink; + } + + const retryItems = enqueueRetryFiles(chatId, folderLabel, failedFiles); + userState.retryQueue = [...(userState.retryQueue || []), ...retryItems].slice(-RETRY_MAX_ITEMS); + incrementStats(state, { + batchesCompleted: 1, + filesUploaded: uploadedCount, + filesFailed: failedCount, + uploadBytes: uploadedBytes, + completedBatchFilesTotal: total, + }); + recordUploadOutcomes(state, uploadedCount, failedCount); + saveJson(STATE_FILE, state); + await maybeSendFailureAlert(state); + + const elapsedMsFinal = Date.now() - startMs; + const elapsedSecFinal = Math.round(elapsedMsFinal / 1000); + const speedMbpsFinal = elapsedMsFinal > 0 + ? Number(bytesToMb(uploadedBytes)) / (elapsedMsFinal / 1000) + : 0; + const finalCombinedMessage = [ + "āœ… Upload complete", + `Batch: ${folderLabel}`, + `Total files: ${total}`, + `Uploaded: ${uploadedCount}`, + `Failed: ${failedCount}`, + `Retry queued: ${retryItems.length}`, + `Total size: ${bytesToMb(totalBytes)} MB`, + `šŸ“ Drive link: ${folderLink}`, + "", + `ā« Uploading ${total} file(s) to ${folderLabel}...`, + `${formatProgressBar(uploadedBytes, totalBytes)}`, + `${total}/${total} | Elapsed: ${elapsedSecFinal}s | ETA: 0s`, + `Uploaded: ${bytesToMb(uploadedBytes)}/${bytesToMb(totalBytes)} MB | Speed: ${speedMbpsFinal.toFixed(2)} MB/s`, + "", + "Next: send more files directly, or type start | list | done", + ].join("\n"); + + clearBatch(chatId); + try { + await editOrSendMessage(chatId, progressMsg?.key || null, finalCombinedMessage, null); + } catch { + await sendChatOrReply(chatId, finalCombinedMessage, null); + } + logInfo("Batch upload complete", { chatId, folderLabel, uploadedCount, failedCount, totalBytes }); + } catch (err) { + const b = BATCH.get(chatId); + if (b) { + b.uploadInProgress = false; + b.doneRequested = false; + clearPendingDoneStatus(b); + } + logError("runBatchUpload failed", { chatId, error: err?.message }); + await sendChatOrReply(chatId, "āŒ Upload failed unexpectedly. Please type done again."); + } +} + +async function tryAutoRunDone(chatId) { + const batch = getBatch(chatId); + if (!batch) return; + if (!batch.doneRequested || batch.uploadInProgress) return; + if ((batch.activeDownloads || 0) > 0) return; + await runBatchUpload(chatId, batch.doneTriggerMsg || null); +} + +async function downloadMediaWithTimeout(msg, timeoutMs) { + let timer; + const timeoutPromise = new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error("download timeout")), timeoutMs); + }); + + try { + return await Promise.race([ + downloadMediaMessage( + msg, + "buffer", + {}, + { + logger: pino({ level: "silent" }), + reuploadRequest: sock ? sock.updateMediaMessage : undefined, + } + ), + timeoutPromise, + ]); + } finally { + clearTimeout(timer); + } +} + +// ---------- Idle restart ---------- +let lastMessageAt = Date.now(); +let isRestarting = false; + +function getRestartStats(state) { + if (!state.restartStats || state.restartStats.date !== getDateKey()) { + state.restartStats = { date: getDateKey(), count: 0 }; + } + return state.restartStats; +} + +async function idleRestartCheck() { + if (isRestarting) return; + const idleMs = Date.now() - lastMessageAt; + const idleMin = idleMs / 60000; + if (idleMin < IDLE_RESTART_MIN) return; + + const state = loadJson(STATE_FILE, { users: {} }); + const stats = getRestartStats(state); + if (stats.count >= IDLE_RESTART_MAX_PER_DAY) return; + + stats.count += 1; + saveJson(STATE_FILE, state); + isRestarting = true; + + logWarn("Idle restart triggered", { idleMin: Math.round(idleMin), count: stats.count }); + const ownerChatId = getOwnerChatId(); + if (ownerChatId) { + try { + if (sock) { + await sock.sendMessage(ownerChatId, { + text: `ā™»ļø Auto-restart (idle ${Math.round(idleMin)} min). Restart #${stats.count} today.`, + }); + } + } catch (err) { + logWarn("Failed to send idle restart notice", { error: err?.message }); + } + } + + setTimeout(() => process.exit(0), 1000); +} + +function startIdleMonitor() { + const checkMs = IDLE_RESTART_CHECK_MIN * 60 * 1000; + setInterval(() => { + idleRestartCheck().catch((err) => + logWarn("Idle restart check failed", { error: err?.message }) + ); + }, checkMs); +} + +function startHealthMonitor() { + const intervalMs = Math.max(15, HEARTBEAT_INTERVAL_SEC) * 1000; + setInterval(() => { + const uptimeSec = Math.round(process.uptime()); + const lastMsgAgeSec = Math.round((Date.now() - lastMessageAt) / 1000); + logInfo("HEARTBEAT", { + uptimeSec, + lastMsgAgeSec, + activeBatches: BATCH.size, + memoryRssMb: Math.round(process.memoryUsage().rss / (1024 * 1024)), + }); + }, intervalMs); +} + +// ---------- Baileys ---------- +async function handleIncomingMessage(msg) { + try { + if (isStatusOrBroadcastMessage(msg)) return; + if (msg?.key?.fromMe) return; + + lastMessageAt = Date.now(); + const text = getMessageText(msg).trim(); + const chatId = getChatIdFromMsg(msg); + const hasMedia = Boolean(getMediaNode(msg)); + const cmd = parseCommand(text); + + if (!chatId) return; + + logInfo("Message received", { + from: chatId, + hasMedia, + textPreview: text.slice(0, 120), + }); + + const drive = getDriveClient(); + const state = normalizeState(loadJson(STATE_FILE, { users: {} })); + const userState = ensureUserState(state, chatId); + markUserActive(state, chatId); + incrementStats(state, { messages: 1 }); + saveJson(STATE_FILE, state); + + const clientFolderId = await getOrCreateClientFolder(drive, msg); + if (cmd) { + if (cmd.type === "help") { + await replyToMessage( + msg, + [ + "start -> begin collecting files", + "Send files now. When finished, type done.", + "Use list/status/undo/rename/cancel anytime.", + ].join("\n") + ); + return; + } + + if (cmd.type === "adminStats") { + const ownerChatId = getOwnerChatId(); + if (ownerChatId && chatId !== ownerChatId) { + await replyToMessage(msg, "āŒ admin-stats is restricted."); + return; + } + const arg = String(cmd.arg || "").toLowerCase(); + if (!arg) { + await replyToMessage(msg, summarizeAdminStats(state)); + return; + } + if (/^\d+d$/.test(arg)) { + const days = Number(arg.slice(0, -1)); + await replyToMessage(msg, summarizeAdminStatsForLastDays(state, days)); + return; + } + if (/^\d{4}-\d{2}-\d{2}$/.test(arg)) { + await replyToMessage(msg, summarizeAdminStatsForDate(state, arg)); + return; + } + await replyToMessage(msg, "Use: admin-stats | admin-stats 7d | admin-stats YYYY-MM-DD"); + return; + } + + if (cmd.type === "adminHealth") { + const ownerChatId = getOwnerChatId(); + if (ownerChatId && chatId !== ownerChatId) { + await replyToMessage(msg, "āŒ admin-health is restricted."); + return; + } + await replyToMessage(msg, summarizeAdminHealth(state)); + return; + } + + if (cmd.type === "project") { + if (!cmd.name) { + await replyToMessage(msg, "Send like: mcb-project MyProjectName"); + return; + } + const folderId = await findOrCreateFolder(drive, cmd.name, clientFolderId); + userState.projectName = cmd.name; + userState.folderId = folderId; + saveJson(STATE_FILE, state); + await replyToMessage(msg, `āœ… Project set: *${cmd.name}*`); + logInfo("Project set", { chatId, projectName: cmd.name, folderId }); + return; + } + + if (cmd.type === "rename") { + const batch = getBatch(chatId); + if (!batch) { + await replyToMessage(msg, "No active batch. Type start first."); + return; + } + if (!cmd.name) { + await replyToMessage(msg, "Use: rename MyFolderName"); + return; + } + const folderId = await findOrCreateFolder(drive, cmd.name, clientFolderId); + batch.folderId = folderId; + batch.folderName = cmd.name; + touchBatch(chatId); + + userState.lastBatchFolderName = cmd.name; + userState.lastBatchFolderId = folderId; + saveJson(STATE_FILE, state); + await replyToMessage(msg, `āœ… Batch folder renamed to *${cmd.name}*`); + logInfo("Batch folder renamed", { chatId, folderName: cmd.name, folderId }); + return; + } + + if (cmd.type === "start") { + const target = await resolveBatchTarget(drive, userState, clientFolderId, cmd.name); + startBatch(chatId, target.folderId, target.folderName); + userState.lastBatchFolderName = target.folderName; + userState.lastBatchFolderId = target.folderId; + incrementStats(state, { batchesStarted: 1 }); + saveJson(STATE_FILE, state); + await replyToMessage(msg, `āœ… Started upload session in *${target.folderName}*.\nSend more files, then type done.`); + logInfo("Batch started", { chatId, folderName: target.folderName, folderId: target.folderId }); + return; + } + + if (cmd.type === "list") { + const batch = getBatch(chatId); + if (!batch || batch.files.length === 0) { + await replyToMessage(msg, "No files queued yet."); + return; + } + const lines = batch.files.slice(0, 15).map((f, i) => `${i + 1}. ${f.filename} (${bytesToDisplay(Number(f.sizeBytes || 0))})`); + const extra = batch.files.length > 15 ? `\n...and ${batch.files.length - 15} more` : ""; + await replyToMessage(msg, `šŸ“‹ Queued files (${batch.files.length}):\n${lines.join("\n")}${extra}`); + return; + } + + if (cmd.type === "undo") { + const batch = getBatch(chatId); + if (!batch || batch.files.length === 0) { + await replyToMessage(msg, "Nothing to undo."); + return; + } + const lastFile = batch.files.pop(); + if (lastFile?.signature && batch.fileSignatures) batch.fileSignatures.delete(lastFile.signature); + try { + if (lastFile?.filePath && fs.existsSync(lastFile.filePath)) fs.rmSync(lastFile.filePath, { force: true }); + } catch (err) { + logWarn("Undo failed to remove local file", { chatId, file: lastFile?.filename, error: err?.message }); + } + touchBatch(chatId); + await replyToMessage(msg, `ā†©ļø Removed last queued file: ${lastFile?.filename || "unknown"}`); + return; + } + + if (cmd.type === "cancel") { + clearBatch(chatId); + await replyToMessage(msg, "šŸ—‘ļø Batch cancelled and cleared."); + logInfo("Batch cancelled", { chatId }); + return; + } + + if (cmd.type === "status") { + const batch = getBatch(chatId); + if (!batch) { + await replyToMessage(msg, "No active batch. Type start."); + return; + } + const msLeft = Math.max(0, BATCH_TTL_MS - (now() - batch.lastActivityAt)); + await replyToMessage(msg, `šŸ“¦ Batch files: *${batch.files.length}*\nā±ļø Time left: *${Math.ceil(msLeft / 60000)} min*`); + logInfo("Batch status", { chatId, count: batch.files.length, minLeft: Math.ceil(msLeft / 60000) }); + return; + } + + if (cmd.type === "retryFailed") { + const retryQueue = Array.isArray(userState.retryQueue) ? userState.retryQueue : []; + if (retryQueue.length === 0) { + await replyToMessage(msg, "No failed files waiting for retry."); + return; + } + + const total = retryQueue.length; + let uploadedCount = 0; + let failedCount = 0; + let uploadedBytes = 0; + const kept = []; + const folderLabel = retryQueue[0].folderName || "RetryFolder"; + let progressMsg = await replyToMessage(msg, `šŸ” Retrying ${total} failed file(s)...`); + + for (const item of retryQueue) { + try { + await uploadWithRetry( + drive, + item, + { + filePath: item.filePath, + filename: item.filename, + parentFolderId: item.folderId || userState.folderId || clientFolderId, + mimeType: item.mimeType, + }, + 3 + ); + uploadedCount += 1; + uploadedBytes += Number(item.sizeBytes || 0); + try { + if (item.filePath && fs.existsSync(item.filePath)) fs.rmSync(item.filePath, { force: true }); + } catch { } + } catch (err) { + failedCount += 1; + kept.push(item); + logWarn("Retry upload failed", { chatId, file: item.filename, error: err?.message }); + } + } + + userState.retryQueue = kept.slice(0, RETRY_MAX_ITEMS); + incrementStats(state, { filesUploaded: uploadedCount, filesFailed: failedCount, uploadBytes: uploadedBytes }); + recordUploadOutcomes(state, uploadedCount, failedCount); + saveJson(STATE_FILE, state); + await maybeSendFailureAlert(state); + try { + const edited = await editOrSendMessage(chatId, progressMsg?.key || null, `šŸ” Retry done for *${folderLabel}*\nUploaded: *${uploadedCount}*\nStill failed: *${failedCount}*`); + if (edited?.key) progressMsg = edited; + } catch { } + return; + } + + if (cmd.type === "done") { + const batch = getBatch(chatId); + if (!batch) { + await replyToMessage(msg, "No active batch or no files received. Type start."); + return; + } + if (batch.uploadInProgress) { + await replyToMessage(msg, "ā« Upload already in progress. I will update you once done."); + return; + } + if ((batch.activeDownloads || 0) > 0) { + batch.doneRequested = true; + batch.doneTriggerMsg = msg; + touchBatch(chatId); + await startPendingDoneStatus(chatId, msg); + await replyToMessage(msg, "ā³ Still receiving files. I will start upload automatically once receiving finishes."); + return; + } + if (batch.files.length === 0) { + await replyToMessage(msg, "No active batch or no files received. Type start."); + return; + } + await runBatchUpload(chatId, msg); + return; + } + + if (cmd.type === "unknown") { + await replyToMessage(msg, "Unknown command. Type help."); + logWarn("Unknown command", { chatId, text }); + return; + } + } + + let batch = getBatch(chatId); + if (!batch && hasMedia) { + const target = await resolveBatchTarget(drive, userState, clientFolderId, ""); + startBatch(chatId, target.folderId, target.folderName); + userState.lastBatchFolderName = target.folderName; + userState.lastBatchFolderId = target.folderId; + incrementStats(state, { batchesStarted: 1 }); + saveJson(STATE_FILE, state); + await replyToMessage(msg, `āœ… Started upload session in *${target.folderName}*.\nSend more files, then type done.`); + logInfo("Batch auto-started from first media", { chatId, folderName: target.folderName, folderId: target.folderId }); + batch = getBatch(chatId); + } + + if (!batch || !hasMedia) return; + + batch.activeDownloads = (batch.activeDownloads || 0) + 1; + touchBatch(chatId); + try { + const mediaSizeBytes = getMediaSizeBytesFromMsg(msg); + const maxBytes = MAX_MEDIA_MB > 0 ? MAX_MEDIA_MB * 1024 * 1024 : 0; + if (maxBytes && mediaSizeBytes > maxBytes) { + await replyToMessage( + msg, + `āŒ File too large for WhatsApp download (${bytesToMb(mediaSizeBytes)} MB). Limit set to ${MAX_MEDIA_MB.toFixed(0)} MB.\nPlease share a Drive link or split the file into smaller parts.` + ); + logWarn("Media download blocked by size limit", { chatId, sizeMb: bytesToMb(mediaSizeBytes), maxMb: MAX_MEDIA_MB.toFixed(0) }); + return; + } + + const statusStart = Date.now(); + logInfo("Media download started", { chatId }); + let mediaBuffer = null; + let lastErr = null; + for (let attempt = 1; attempt <= DOWNLOAD_MAX_RETRIES + 1; attempt += 1) { + try { + mediaBuffer = await downloadMediaWithTimeout(msg, DOWNLOAD_TIMEOUT_MS); + if (mediaBuffer) break; + lastErr = new Error("empty media data"); + } catch (err) { + lastErr = err; + } + logWarn("Media download retry", { chatId, attempt, error: lastErr?.message }); + if (attempt <= DOWNLOAD_MAX_RETRIES) await sleep(DOWNLOAD_RETRY_DELAY_MS); + } + + if (!mediaBuffer) { + await replyToMessage(msg, "āŒ Could not download the attachment."); + logWarn("Media download failed", { chatId, error: lastErr?.message }); + return; + } + + const mediaNode = getMediaNode(msg); + const derivedSourceName = mediaNode?.node?.fileName || `file_${batch.files.length + 1}.${mime.extension(getMimeTypeFromMsg(msg)) || "bin"}`; + const fauxMedia = { filename: derivedSourceName, mimetype: getMimeTypeFromMsg(msg) }; + const filename = buildSafeFileName(fauxMedia, batch.files.length + 1); + const filePath = path.join(batch.dir, filename); + fs.writeFileSync(filePath, mediaBuffer); + + const fileSizeBytes = fs.statSync(filePath).size; + const sourceName = sanitizeFileName(path.basename(derivedSourceName)); + const signature = buildFileSignature(sourceName, fileSizeBytes); + if (batch.fileSignatures && batch.fileSignatures.has(signature)) { + try { + fs.rmSync(filePath, { force: true }); + } catch { } + await replyToMessage(msg, "āš ļø Duplicate file skipped (same filename + size in this batch)."); + logInfo("Duplicate skipped", { chatId, sourceName, sizeBytes: fileSizeBytes }); + return; + } + + logInfo("Media download completed", { + chatId, + filename, + sizeMb: bytesToMb(fileSizeBytes), + elapsed: formatElapsedSec(Date.now() - statusStart), + }); + + batch.fileSignatures.add(signature); + batch.files.push({ + filePath, + filename, + mimeType: getMimeTypeFromMsg(msg), + sizeBytes: fileSizeBytes, + sourceName, + signature, + receivedAt: Date.now(), + }); + touchBatch(chatId); + incrementStats(state, { filesQueued: 1 }); + saveJson(STATE_FILE, state); + await reactToMessage(msg, "āœ…"); + logInfo("File queued", { chatId, filename, batchCount: batch.files.length }); + } finally { + const b = BATCH.get(chatId); + if (b) { + b.activeDownloads = Math.max(0, (b.activeDownloads || 0) - 1); + touchBatch(chatId); + if (b.doneRequested && !b.uploadInProgress && (b.activeDownloads || 0) === 0) { + tryAutoRunDone(chatId).catch((err) => logWarn("Auto-run done failed", { chatId, error: err?.message })); + } + } + } + } catch (err) { + logError("Message handler error", { error: err?.message }); + try { + await replyToMessage(msg, "āŒ Error while processing. Type help."); + } catch { } + } +} + +let monitorsStarted = false; +async function startBaileys() { + const { state, saveCreds } = await useMultiFileAuthState(BAILEYS_AUTH_DIR); + const { version } = await fetchLatestBaileysVersion(); + sock = makeWASocket({ + version, + auth: state, + printQRInTerminal: false, + browser: Browsers.ubuntu("WhatsDrive-Baileys"), + generateHighQualityLinkPreview: false, + markOnlineOnConnect: false, + syncFullHistory: false, + logger: pino({ level: "silent" }), + defaultQueryTimeoutMs: Number(process.env.WA_PROTOCOL_TIMEOUT || "9000000"), + }); + + sock.ev.on("creds.update", saveCreds); + + sock.ev.on("connection.update", async (update) => { + const { connection, lastDisconnect, qr } = update || {}; + if (qr) { + qrcode.generate(qr, { small: true }); + logInfo("QR received. Scan to login."); + } + + if (connection === "open") { + logInfo("Baileys bot is ready"); + if (!monitorsStarted) { + startIdleMonitor(); + startHealthMonitor(); + monitorsStarted = true; + } + const ownerChatId = getOwnerChatId(); + if (ownerChatId) { + try { + await sock.sendMessage(ownerChatId, { text: "āœ… Baileys bot started and ready. Logs are being written to app.log." }); + } catch (err) { + logWarn("Failed to send ready message to owner", { error: err?.message }); + } + } + return; + } + + if (connection === "close") { + const statusCode = lastDisconnect?.error?.output?.statusCode || lastDisconnect?.error?.statusCode; + const shouldReconnect = statusCode !== DisconnectReason.loggedOut; + logWarn("Baileys disconnected", { statusCode, shouldReconnect }); + if (shouldReconnect) { + await sleep(2000); + startBaileys().catch((err) => logError("Baileys reconnect failed", { error: err?.message })); + } + } + }); + + sock.ev.on("messages.upsert", async ({ messages, type }) => { + if (!Array.isArray(messages)) return; + if (type !== "notify" && type !== "append") return; + for (const msg of messages) { + await handleIncomingMessage(msg); + } + }); +} + +async function initializeClientWithRetry() { + for (let attempt = 1; attempt <= WA_INIT_RETRIES; attempt += 1) { + try { + await startBaileys(); + return; + } catch (err) { + logError("Baileys initialize failed", { + attempt, + maxRetries: WA_INIT_RETRIES, + error: err?.message, + }); + if (attempt >= WA_INIT_RETRIES) throw err; + await sleep(WA_INIT_RETRY_DELAY_MS); + } + } +} + +function parseCommand(rawText) { + const normalized = String(rawText || "").trim().replace(/\s+/g, " "); + const lower = normalized.toLowerCase(); + const adminMatch = normalized.match(/^(?:mcb-)?admin-stats(?:\s+(.+))?$/i); + if (adminMatch) { + return { type: "adminStats", arg: String(adminMatch[1] || "").trim() }; + } + if (/^(?:mcb-)?admin-health$/i.test(normalized)) { + return { type: "adminHealth" }; + } + + if (lower === "help" || lower === "mcb-help") return { type: "help" }; + if (lower === "list" || lower === "mcb-list") return { type: "list" }; + if (lower === "undo" || lower === "mcb-undo") return { type: "undo" }; + if (lower === "retry-failed" || lower === "mcb-retry-failed") return { type: "retryFailed" }; + if (lower === "start" || lower === "mcb-start" || lower === "hi mcb") { + return { type: "start", name: "" }; + } + if (lower.startsWith("hi mcb ")) { + return { type: "start", name: normalized.slice(7).trim() }; + } + if (lower.startsWith("rename ")) { + return { type: "rename", name: normalized.slice(7).trim() }; + } + if (lower.startsWith("mcb-rename ")) { + return { type: "rename", name: normalized.slice(11).trim() }; + } + if (lower === "done" || lower === "mcb-done" || lower === "bye mcb" || lower === "by mcb") { + return { type: "done" }; + } + if (lower === "cancel" || lower === "mcb-cancel") return { type: "cancel" }; + if (lower === "status" || lower === "mcb-status") return { type: "status" }; + + if (lower.startsWith("mcb-project")) { + const name = normalized.replace(/^mcb-project/i, "").trim(); + return { type: "project", name }; + } + + if (lower.startsWith("mcb-")) return { type: "unknown" }; + return null; +} + +function isStatusOrBroadcastMessage(msg) { + const remoteJid = String(msg?.key?.remoteJid || msg?.from || "").toLowerCase(); + const participant = String(msg?.key?.participant || "").toLowerCase(); + const from = remoteJid; + const to = participant; + if (from === "status@broadcast" || to === "status@broadcast") return true; + if (from.endsWith("@broadcast") || to.endsWith("@broadcast")) return true; + if (msg?.isStatus === true) return true; + return false; +} + +function buildAutoBatchFolderName() { + const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, ""); + return `Batch_${stamp}`; +} + +async function resolveBatchTarget(drive, userState, clientFolderId, requestedName = "") { + const explicitName = String(requestedName || "").trim(); + if (explicitName) { + const folderId = await findOrCreateFolder(drive, explicitName, clientFolderId); + return { folderId, folderName: explicitName }; + } + + if (userState.lastBatchFolderName && userState.lastBatchFolderId) { + return { + folderId: userState.lastBatchFolderId, + folderName: userState.lastBatchFolderName, + }; + } + + if (userState.projectName && userState.folderId) { + return { + folderId: userState.folderId, + folderName: userState.projectName, + }; + } + + const autoName = buildAutoBatchFolderName(); + const autoFolderId = await findOrCreateFolder(drive, autoName, clientFolderId); + return { folderId: autoFolderId, folderName: autoName }; +} + +function clearAllBatches() { + for (const chatId of BATCH.keys()) { + clearBatch(chatId); + } +} + +process.on("SIGINT", () => { + logWarn("SIGINT received, cleaning up."); + clearAllBatches(); + process.exit(0); +}); + +process.on("SIGTERM", () => { + logWarn("SIGTERM received, cleaning up."); + clearAllBatches(); + process.exit(0); +}); + +process.on("unhandledRejection", (reason) => { + logError("Unhandled promise rejection", { reason: String(reason) }); +}); + +process.on("uncaughtException", (err) => { + logError("Uncaught exception", { error: err?.message, stack: err?.stack }); +}); + +async function boot() { + restoreBatchesFromDisk(); + await ensureGoogleToken(); + getDriveClient(); + await initializeClientWithRetry(); +} + +boot().catch((err) => { + logError("Fatal startup error", { error: err?.message }); + process.exit(1); +}); diff --git a/logs/app-2026-04-18.jsonl b/logs/app-2026-04-18.jsonl new file mode 100644 index 0000000..362eae4 --- /dev/null +++ b/logs/app-2026-04-18.jsonl @@ -0,0 +1,2 @@ +{"ts":"2026-04-18T19:38:36.046Z","level":"INFO","message":"QR received. Scan to login."} +{"ts":"2026-04-18T19:38:41.454Z","level":"WARN","message":"SIGINT received, cleaning up."} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a50d139 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1997 @@ +{ + "name": "whatsdrive-baileys", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "whatsdrive-baileys", + "version": "1.0.0", + "dependencies": { + "@whiskeysockets/baileys": "^6.7.18", + "dotenv": "^16.4.5", + "googleapis": "^140.0.0", + "mime-types": "^2.1.35", + "pino": "^9.3.2", + "qrcode-terminal": "^0.12.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.8.tgz", + "integrity": "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==", + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.4.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/node-cache": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@cacheable/node-cache/-/node-cache-1.7.6.tgz", + "integrity": "sha512-6Omk2SgNnjtxB5f/E6bTIWIt5xhdpx39fGNRQgU9lojvRxU68v+qY+SXXLsp3ZGukqoPjsK21wZ6XABFr/Ge3A==", + "license": "MIT", + "dependencies": { + "cacheable": "^2.3.1", + "hookified": "^1.14.0", + "keyv": "^5.5.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "license": "MIT" + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@whiskeysockets/baileys": { + "version": "6.7.21", + "resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-6.7.21.tgz", + "integrity": "sha512-xx9OHd6jlPiu5yZVuUdwEgFNAOXiEG8sULHxC6XfzNwssnwxnA9Lp44pR05H621GQcKyCfsH33TGy+Na6ygX4w==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cacheable/node-cache": "^1.4.0", + "@hapi/boom": "^9.1.3", + "async-mutex": "^0.5.0", + "axios": "^1.6.0", + "libsignal": "git+https://github.com/whiskeysockets/libsignal-node.git", + "music-metadata": "^11.7.0", + "pino": "^9.6", + "protobufjs": "^7.2.4", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "audio-decode": "^2.1.3", + "jimp": "^1.6.0", + "link-preview-js": "^3.0.0", + "sharp": "*" + }, + "peerDependenciesMeta": { + "audio-decode": { + "optional": true + }, + "jimp": { + "optional": true + }, + "link-preview-js": { + "optional": true + } + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/cacheable": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.4.tgz", + "integrity": "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==", + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.0.8", + "@cacheable/utils": "^2.4.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.9.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/curve25519-js": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", + "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "140.0.1", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-140.0.1.tgz", + "integrity": "sha512-ZGvBX4mQcFXO9ACnVNg6Aqy3KtBPB5zTuue43YVLxwn8HSv8jB7w+uDKoIPSoWuxGROgnj2kbng6acXncOQRNA==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/libsignal": { + "name": "@whiskeysockets/libsignal-node", + "version": "2.0.1", + "resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67", + "license": "GPL-3.0", + "dependencies": { + "curve25519-js": "^0.0.4", + "protobufjs": "6.8.8" + } + }, + "node_modules/libsignal/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "license": "MIT" + }, + "node_modules/libsignal/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/libsignal/node_modules/protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/music-metadata": { + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.12.3.tgz", + "integrity": "sha512-n6hSTZkuD59qWgHh6IP5dtDlDZQXoxk/bcA85Jywg8Z1iFrlNgl2+GTFgjZyn52W5UgQpV42V4XqrQZZAMbZTQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.2", + "@tokenizer/token": "^0.3.0", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "file-type": "^21.3.1", + "media-typer": "^1.1.0", + "strtok3": "^10.3.4", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0", + "win-guid": "^0.2.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/qified": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.9.1.tgz", + "integrity": "sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg==", + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.1.1.tgz", + "integrity": "sha512-AHb76R16GB5EsPBE2J7Ko5kiEyXwviB9P5SMrAKcuAu4vJPZttViAbj9+tZeaQE5zjDme+1vcHP78Yj/WoAveA==", + "license": "MIT" + }, + "node_modules/qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a0a792c --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "whatsdrive-baileys", + "version": "1.0.0", + "description": "WhatsApp -> Google Drive batch uploader (Baileys)", + "main": "index.js", + "scripts": { + "start": "node index.js", + "auth": "node auth.js" + }, + "dependencies": { + "@whiskeysockets/baileys": "^6.7.18", + "dotenv": "^16.4.5", + "googleapis": "^140.0.0", + "mime-types": "^2.1.35", + "pino": "^9.3.2", + "qrcode-terminal": "^0.12.0" + } +} diff --git a/state copy.json b/state copy.json new file mode 100644 index 0000000..2131448 --- /dev/null +++ b/state copy.json @@ -0,0 +1,50 @@ +{ + "users": { + "15192773600@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "2 tier chocolate vegan Star Wars cake", + "lastBatchFolderId": "1oJufpdl2yuyZTOEejVv22rKMEf7o6LAV" + }, + "14374329515@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "social currency", + "lastBatchFolderId": "1iUdfbHOCg3Cambad7DhmDla9wCBPtk_0" + }, + "918220348218@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "mohan", + "lastBatchFolderId": "1MxGKRFjb60x2pcCySc6DBTC_c2qXzG0L" + }, + "16476796537@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "Manesh", + "lastBatchFolderId": "1VylDIP7pvKtNa2WV-lAK6f6gNaU9pZ0V" + }, + "919585967575@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "Vijay testing", + "lastBatchFolderId": "12zRnuoeIhudGLbKckMciYLDxd7zLJpVA" + }, + "16476229848@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "Clickstogarlands", + "lastBatchFolderId": "1fs-6-2FA6RkioHR2Ybo7Q1xGp6ftCU7B" + }, + "15198976975@c.us": { + "projectName": null, + "folderId": null, + "lastBatchFolderName": "TCA _sportsday", + "lastBatchFolderId": "1JVzN_9KHMIm0jDQZL0UthM_jCJDkV8Dz" + } + }, + "restartStats": { + "date": "2026-04-17", + "count": 1 + } +} \ No newline at end of file diff --git a/state.json b/state.json new file mode 100644 index 0000000..aafe57e --- /dev/null +++ b/state.json @@ -0,0 +1,20 @@ +{ + "users": {}, + "analytics": { + "daily": {}, + "lifetime": { + "batchesStarted": 0, + "batchesCompleted": 0, + "filesQueued": 0, + "filesUploaded": 0, + "filesFailed": 0, + "uploadBytes": 0, + "completedBatchFilesTotal": 0 + }, + "uploadOutcomes": [], + "alerts": { + "lastFailureAlertAt": 0, + "lastFailureRate": 0 + } + } +}