├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package.json ├── res ├── default.exif └── readme │ ├── command.gif │ ├── context.gif │ ├── message.gif │ ├── metadata.gif │ └── wleowleo.png ├── src ├── apply-backup.ts ├── commands │ ├── about.ts │ ├── batchsticker.ts │ ├── batchunsticker.ts │ ├── core │ │ ├── Ban.ts │ │ ├── Sleep.ts │ │ ├── addpremium.ts │ │ ├── adminonly.ts │ │ ├── databaseBackup.ts │ │ ├── delete.ts │ │ ├── eval.ts │ │ ├── feedback.ts │ │ ├── help.ts │ │ ├── language.ts │ │ └── menu.ts │ ├── echo.ts │ ├── premium.ts │ ├── stats.ts │ ├── sticker.ts │ └── unsticker.ts ├── index.ts └── utils │ ├── commands.ts │ ├── context.ts │ ├── index.ts │ ├── requestArguments.ts │ ├── requestMessage.ts │ ├── saveFile.ts │ └── typings │ ├── kao.moji.d.ts │ └── types.d.ts ├── tsconfig.json └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /js 3 | /data 4 | /tmp 5 | .env 6 | yarn-error.log 7 | test.js 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": false, 4 | "printWidth": 170, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rioze 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # miki-whatsapp-bot Hoshii Miki 2 | 3 | Another WhatsApp bot with super clean code design. Written in TypeScript. 4 | 5 | > Warning: non-english 6 | ![how to create a new command, annotated using handwriting](res/readme/wleowleo.png) 7 | > English below 8 | > 9 | >Download and send messages 10 | ![Download and send messages](res/readme/message.gif) 11 | >Create new commands 12 | ![Create new commands](res/readme/command.gif) 13 | ![Create new commands](res/readme/metadata.gif) 14 | > Aaaaand many more 15 | ![Aaaaand many more](res/readme/context.gif) 16 | 17 | ## How to Contribute 18 | Please fork the repository and make changes as you'd like. 19 | Steps to contribute: 20 | 1. Fork this repository 21 | 2. Create your feature branch (git checkout -b feature/newFeature) 22 | 3. Commit your changes (git commit -am 'Add some feature ...') 23 | 4. Push to the branch (git push origin feature/newFeature) 24 | 5. Create a new Pull Request 25 | 26 | 27 | ## Requirements (Prerequisites) 28 | 29 | - Node.JS 30 | - Imagemagick 31 | - FFMpeg 32 | - Libwebp (for running webpmux and dwebp) 33 | 34 | ## Cloning 35 | ```sh 36 | git clone https://github.com/riozee/miki-whatsapp-bot 37 | cd miki-whatsapp-bot 38 | ``` 39 | 40 | ## `.env` Configurations 41 | Before running, make sure to create `.env` file in the bot folder and paste this configurations. 42 | 43 | ```sh 44 | # the bot number itself (NECESSARY) 45 | BOT_NUMBER="1234567890" 46 | # your number (NECESSARY) 47 | OWNER_NUMBER="1234567890" 48 | # the number people will contact to buy Premium 49 | CUSTOMER_SERVICE_NUMBER="1234567890" 50 | # the number to receive the feedback when a user sent a feedback using /feedback command 51 | # (can be a group) (ends it with @g.us if it's a group) 52 | FEEDBACK_NUMBER="1234567890" 53 | # bot will send a backup copy of the database every minute. This is the number to send to 54 | # (can be a group) (ends it with @g.us if it's a group) 55 | DATABASE_BACKUP_NUMBER="1234567890" 56 | # everytime there is an error in command, bot will send a report to this number 57 | # (can be a group) (ends it with @g.us if it's a group) 58 | ERROR_REPORT_NUMBER="1234567890" 59 | # prefix of the bot 60 | PREFIX="!@#$%^&*" 61 | ``` 62 | 63 | ## Run 64 | 65 | ```sh 66 | npm install 67 | npm run build 68 | npm start 69 | ``` 70 | 71 | ## Features 72 | 73 | | No | Name | Permission | Status | 74 | | --- | ----------------------- | ------------- | ------ | 75 | | 1 | about | all | ✔ | 76 | | 2 | addpremium | all-owner | ✔ | 77 | | 3 | echo/say | all | ✔ | 78 | | 4 | adminonly | group-admin | ✔ | 79 | | 5 | premium | all | ✔ | 80 | | 6 | ban | all-owner | ✔ | 81 | | 7 | stats | all | ✔ | 82 | | 8 | getbackup | all-owner | ✔ | 83 | | 9 | ev | all-owner | ✔ | 84 | | 10 | feedback | all | ✔ | 85 | | 11 | help | all | ✔ | 86 | | 12 | language | private-admin | ✔ | 87 | | 13 | menu | all | ✔ | 88 | | 14 | premium | all-owner | ✔ | 89 | | 15 | sleep | group-admin | ✔ | 90 | | 16 | sticker/sticker/s | all | ✔ | 91 | | 17 | unsticker/unsticker/uns | all | ✔ | 92 | | 18 | batchsticker | all (premium) | ✔ | 93 | | 19 | batchunsticker | all (premium) | ✔ | 94 | 95 | ## Authors 96 | Rioze [![telegram](https://img.shields.io/badge/Telegram-2CA5E0)](https://t.me/riozee) 97 | 98 | ## License 99 | ``` 100 | MIT License 101 | 102 | Copyright (c) 2022-2023 Rioze 103 | 104 | Permission is hereby granted, free of charge, to any person obtaining a copy 105 | of this software and associated documentation files (the "Software"), to deal 106 | in the Software without restriction, including without limitation the rights 107 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 108 | copies of the Software, and to permit persons to whom the Software is 109 | furnished to do so, subject to the following conditions: 110 | 111 | The above copyright notice and this permission notice shall be included in all 112 | copies or substantial portions of the Software. 113 | 114 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 115 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 116 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 117 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 118 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 119 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 120 | SOFTWARE. 121 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "js/index.js", 3 | "scripts": { 4 | "build": "tsc", 5 | "start": "node js/index", 6 | "apply-backup": "node js/apply-backup" 7 | }, 8 | "dependencies": { 9 | "@whiskeysockets/baileys": "^6.4.0", 10 | "dotenv": "^16.0.0", 11 | "kao.moji": "^0.1.3", 12 | "link-preview-js": "^2.1.13", 13 | "lodash": "^4.17.21", 14 | "promptees": "^2.0.0", 15 | "qrcode-terminal": "^0.12.0" 16 | }, 17 | "devDependencies": { 18 | "@types/lodash": "^4.14.182", 19 | "@types/ws": "^8.5.3", 20 | "typescript": "^4.7.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /res/default.exif: -------------------------------------------------------------------------------- 1 | II*AWM{"sticker-pack-name":"Miki Bot","sticker-pack-publisher":"github.com/riozec"} -------------------------------------------------------------------------------- /res/readme/command.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riozee/miki-whatsapp-bot/3f500941c5d35a9c72ff5194487380c3bebab605/res/readme/command.gif -------------------------------------------------------------------------------- /res/readme/context.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riozee/miki-whatsapp-bot/3f500941c5d35a9c72ff5194487380c3bebab605/res/readme/context.gif -------------------------------------------------------------------------------- /res/readme/message.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riozee/miki-whatsapp-bot/3f500941c5d35a9c72ff5194487380c3bebab605/res/readme/message.gif -------------------------------------------------------------------------------- /res/readme/metadata.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riozee/miki-whatsapp-bot/3f500941c5d35a9c72ff5194487380c3bebab605/res/readme/metadata.gif -------------------------------------------------------------------------------- /res/readme/wleowleo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riozee/miki-whatsapp-bot/3f500941c5d35a9c72ff5194487380c3bebab605/res/readme/wleowleo.png -------------------------------------------------------------------------------- /src/apply-backup.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import makeWASocket, * as Baileys from "@whiskeysockets/baileys"; 3 | import { createMessageContext } from "./utils"; 4 | import Promptees from "promptees"; 5 | import axios from "axios"; 6 | import * as fs from "fs"; 7 | 8 | import type * as Types from "./utils/typings/types"; 9 | 10 | (async () => { 11 | const PIN = String(Math.floor(Math.random() * 999999)); 12 | console.log("Your PIN: " + PIN); 13 | const { state, saveCreds } = await Baileys.useMultiFileAuthState("./data/session"); 14 | 15 | const bot = makeWASocket({ 16 | printQRInTerminal: true, 17 | browser: Baileys.Browsers.appropriate("Miki"), 18 | version: (await Baileys.fetchLatestBaileysVersion()).version, 19 | auth: state, 20 | }); 21 | 22 | bot.ev.on("connection.update", (update) => { 23 | const { connection, lastDisconnect } = update; 24 | if (connection === "close") { 25 | console.error("Disconnected from WhatsApp."); 26 | } else if (connection === "connecting") { 27 | console.log("Connecting to WhatsApp..."); 28 | } else if (connection === "open") { 29 | console.log("Connected to WhatsApp."); 30 | console.log("Waiting for /applybackup command from owner..."); 31 | } 32 | }); 33 | 34 | bot.ev.on("creds.update", () => saveCreds()); 35 | 36 | const promptees = new Promptees(); 37 | const Context = createMessageContext({}, bot, promptees); 38 | 39 | bot.ev.on("messages.upsert", async (update) => { 40 | if (update.type !== "notify") return; 41 | const m = update.messages[0]; 42 | if (!m.message) return; 43 | if (m.key?.fromMe) return; 44 | if (m.key?.id?.length === 16 && m.key.id.startsWith("BAE5")) return; 45 | if (m.key?.remoteJid === "status@broadcast") return; 46 | if (m.message.protocolMessage) return; 47 | if (m.message.reactionMessage) return; 48 | if (m.message.pollUpdateMessage) return; 49 | 50 | const context = new Context(m); 51 | const id = context.userId().out + context.chatId().out; 52 | if (promptees.isPrompting(id)) return promptees.returnPrompt(id, context); 53 | console.log(context.userId().out); 54 | if (context.userId().out === process.env.OWNER_NUMBER + "@s.whatsapp.net") { 55 | console.log(context.command().out); 56 | if (context.command().out === "applybackup") { 57 | const content = context.quotedMsgContent().out; 58 | console.log(content); 59 | if ( 60 | context.quotedMsgType().out === "documentMessage" && 61 | content && 62 | typeof content !== "string" && 63 | "fileName" in content && 64 | /^MIKIBACKUP_\d+\.json$/.test(content.fileName || "") 65 | ) { 66 | while (true) { 67 | const _context = await context.reply({ text: "Enter PIN:" }).waitInput().out; 68 | /*this will never happen ->*/ if (_context === "timeout") return; 69 | console.log(_context.text().out); 70 | if (_context.text().out === PIN) { 71 | try { 72 | _context.react("✅"); 73 | context.reply({ text: "Loading..." }); 74 | console.log("Downloading file..."); 75 | const media = (await context.quotedMedia("buffer").out)!; 76 | console.log("Loading file..."); 77 | const DB = JSON.parse(media.toString()) as Types.LOCALDB; 78 | const info = `Backup file info:\n Name: ${content.fileName}\n Time: ${new Date( 79 | (DB.system as Types.SYSTEMDB).backupTime 80 | ).toLocaleString()}`; 81 | console.log(info); 82 | console.log("Saving file..."); 83 | if (!fs.existsSync("./data/")) fs.mkdirSync("./data/"); 84 | fs.writeFileSync("./data/db.json", media!); 85 | console.log("Done! Now you can start the bot."); 86 | await context.react("✅").reply({ text: "Done! Now you can start the bot.\n\n" + info }).out; 87 | return process.exit(0); 88 | } catch (e) { 89 | context.reply({ text: String(e) }); 90 | continue; 91 | } 92 | } else { 93 | _context.react("❌"); 94 | continue; 95 | } 96 | } 97 | } else { 98 | return context.react("❌").reply({ text: "Please reply to a backup file." }); 99 | } 100 | } 101 | } 102 | }); 103 | })(); 104 | -------------------------------------------------------------------------------- /src/commands/about.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../utils"; 2 | 3 | command.new({ 4 | onCommand: (context) => { 5 | const ums = process.uptime() * 1000; 6 | const f = Math.floor; 7 | context.reply({ 8 | text: `\n\n*Miki Bot* 🍙\n\nUptime: *${f(ums / 3_600_000)}:${f((ums / 60000) % 60)}:${f((ums / 1000) % 60)}.${f(ums % 1000)}*\n\nhttps://github.com/riozee/miki-whatsapp-bot`, 9 | }); 10 | }, 11 | metadata: { 12 | __filename, 13 | category: "botsettings", 14 | command: ["about"], 15 | permission: "all", 16 | locale: { 17 | description: { 18 | id: "[TODO] about description", 19 | en: "[TODO] about description", 20 | }, 21 | name: { 22 | id: "tentang", 23 | }, 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /src/commands/batchsticker.ts: -------------------------------------------------------------------------------- 1 | import { basicTexts, command, randomFileName, saveFile } from "../utils"; 2 | import { exec } from "child_process"; 3 | import * as fs from "fs"; 4 | 5 | const supportedMimeTypes: string[][] = [ 6 | ["image/jpeg", "jpg"], 7 | ["image/png", "png"], 8 | ["image/gif", "gif"], 9 | ["video/mp4", "mp4"], 10 | ["image/webp", "webp"], 11 | ["video/mpeg", "mpeg"], 12 | ["video/x-msvideo", "avi"], 13 | ["video/ogg", "ogv"], 14 | ["video/webm", "webm"], 15 | ["video/3gpp", "3gp"], 16 | ]; 17 | const supportedList = supportedMimeTypes.map((v) => v[1].toUpperCase()).join("/"); 18 | 19 | const TEXTS = { 20 | id: { 21 | NOTPREMIUM: () => "Mengatur kedua nama paket dan pembuat stiker hanya dapat dilakukan oleh pengguna Premium.", 22 | PROCESSING: (number: number) => "Memproses " + number + " media...", 23 | FAILED: (number: number) => "Gagal memproses " + number + " media.", 24 | DONE: () => "Selesai", 25 | ASK: () => "Silahkan kirim atau balas ke beberapa pesan gambar, video atau dokumen. *Maksimal 20*\n\nType *done* if done or *cancel*.", 26 | SUPPORTED: (supported: string) => "Format yang didukung: " + supported + " (maksimal 1,5 MB)", 27 | }, 28 | en: { 29 | NOTPREMIUM: () => "Setting both the pack and author name of the sticker can only be done by Premium users.", 30 | PROCESSING: (number: number) => "Processing " + number + " media...", 31 | FAILED: (number: number) => "Failed processing " + number + " media.", 32 | DONE: () => "Done", 33 | ASK: () => "Please send or reply to one or more image, video or document messages. *Max 20*\n\nType *done* if done or *cancel*.", 34 | SUPPORTED: (supported: string) => "Supported formats: " + supported + " (max 1.5 MB)", 35 | }, 36 | }; 37 | 38 | command.new({ 39 | onCommand: async (context, bot) => { 40 | const language = context.language().out; 41 | context.reply({ 42 | text: TEXTS[language].ASK() + "\n\n" + TEXTS[language].SUPPORTED(supportedList), 43 | }); 44 | const media: typeof context[] = []; 45 | const quotedMedia: typeof context[] = []; 46 | 47 | while (true) { 48 | const response = await context.waitInput().out; 49 | if (response === "timeout" || response.text().out?.toLowerCase() === basicTexts[language].CANCEL().toLowerCase()) 50 | return context.reply({ text: basicTexts[language].CANCELED() }); 51 | if (response.text().out?.toLowerCase() === TEXTS[language].DONE().toLowerCase()) break; 52 | 53 | const content = response.msgContent().out; 54 | const quotedContent = response.quotedMsgContent().out; 55 | const isValid = (_content: typeof content | typeof quotedContent) => { 56 | if (typeof _content !== "string" && _content && "mimetype" in _content && supportedMimeTypes.some((v) => v[0] === _content.mimetype)) { 57 | if (Number(_content.fileLength) < 1500000) return true; 58 | } 59 | return false; 60 | }; 61 | 62 | if (isValid(content)) { 63 | response.react("✅"); 64 | media.push(response); 65 | } else if (isValid(quotedContent)) { 66 | response.react("✅"); 67 | quotedMedia.push(response); 68 | } else { 69 | response.react("❌").reply({ text: TEXTS[language].SUPPORTED(supportedList) }); 70 | } 71 | if (media.length + quotedMedia.length === 20) break; 72 | } 73 | 74 | context.reply({ text: TEXTS[language].PROCESSING(media.length + quotedMedia.length) }); 75 | let [pack, author] = (context.arguments().out || "").split(/\s*\|\s*/); 76 | if (author && !context.isPremiumUser().out) { 77 | context.reply({ text: TEXTS[language].NOTPREMIUM() }); 78 | author = ""; 79 | } 80 | let exifFile: string; 81 | if (!pack && !author) exifFile = "./res/default.exif"; 82 | else exifFile = generateExif(pack, author || "Miki Bot by Rioze"); 83 | 84 | const tasks: Promise[] = []; 85 | for (const _media of media) { 86 | tasks.push( 87 | new Promise((resolve, reject) => { 88 | _media 89 | .media("stream") 90 | .out!.then((stream) => saveFile(stream!)) 91 | .then((file) => processSticker(file)) 92 | .then(resolve) 93 | .catch(reject); 94 | }) 95 | ); 96 | } 97 | for (const _media of quotedMedia) { 98 | tasks.push( 99 | new Promise((resolve, reject) => { 100 | _media 101 | .quotedMedia("stream") 102 | .out!.then((stream) => saveFile(stream!)) 103 | .then((file) => processSticker(file)) 104 | .then(resolve) 105 | .catch(reject); 106 | }) 107 | ); 108 | } 109 | return await Promise.allSettled(tasks).then((results) => { 110 | const rejected = results.filter(({ status }) => status === "rejected"); 111 | if (rejected.length) context.reply({ text: TEXTS[language].FAILED(rejected.length) }); 112 | }); 113 | 114 | function processSticker(file: string) { 115 | return new Promise((resolve, reject) => { 116 | const output = randomFileName(); 117 | exec( 118 | `ffmpeg -i ${file} -vcodec libwebp -compression_level 6 -q:v 25 -b:v 200k -vf "scale='if(gt(a,1),520,-1)':'if(gt(a,1),-1,520)':flags=lanczos:force_original_aspect_ratio=decrease,format=bgra,pad=520:520:-1:-1:color=#00000000,setsar=1" -f webp ${output}`, 119 | (e) => { 120 | if (e) return reject(e); 121 | exec(`webpmux -set exif ${exifFile} ${output} -o ${output}`, (e) => { 122 | if (e) console.error(e); 123 | resolve(context.reply({ sticker: { url: output } }, { quoted: undefined })); 124 | }); 125 | } 126 | ); 127 | }); 128 | } 129 | }, 130 | metadata: { 131 | __filename, 132 | category: "mediatools", 133 | command: ["batchsticker"], 134 | permission: "all", 135 | premium: true, 136 | locale: { 137 | description: { 138 | id: "[TODO] batchsticker description", 139 | en: "[TODO] batchsticker description", 140 | }, 141 | name: { 142 | id: "stiker sekaligus", 143 | }, 144 | }, 145 | }, 146 | }); 147 | 148 | function generateExif(pack: string = "", author: string = "") { 149 | const json = JSON.stringify({ 150 | "sticker-pack-name": pack, 151 | "sticker-pack-publisher": author, 152 | }); 153 | // @ts-ignore 154 | let len: number | string = new TextEncoder("utf-8").encode(json).length; 155 | const f = Buffer.from([0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00]); 156 | const code = [0x00, 0x00, 0x16, 0x00, 0x00, 0x00]; 157 | len > 256 ? ((len = len - 256), code.unshift(0x01)) : code.unshift(0x00); 158 | len < 16 ? (len = "0" + len) : (len = len.toString(16)); 159 | const buffer = Buffer.concat([f, Buffer.from(len, "hex"), Buffer.from(code), Buffer.from(json, "utf-8")]); 160 | const filename = randomFileName(); 161 | return fs.writeFileSync(filename, buffer), filename; 162 | } 163 | -------------------------------------------------------------------------------- /src/commands/batchunsticker.ts: -------------------------------------------------------------------------------- 1 | import { command, basicTexts, saveFile, randomFileName } from "../utils"; 2 | import { exec } from "child_process"; 3 | 4 | const TEXTS = { 5 | id: { 6 | PROCESSING: (number: number) => "Memproses " + number + " stiker...", 7 | FAILED: (number: number) => "Gagal memproses " + number + " stiker.", 8 | DONE: () => "Selesai", 9 | ASK: () => "Silahkan kirim atau balas ke beberapa stiker. *Maksimal 20*\n\nKetik *selesai* jika selesai atau *batal*.", 10 | NOTSTICKER: () => "Itu bukan stiker.", 11 | }, 12 | en: { 13 | PROCESSING: (number: number) => "Processing " + number + " sticker" + (number === 1 ? "" : "s") + "...", 14 | FAILED: (number: number) => "Failed processing " + number + " sticker" + (number === 1 ? "" : "s") + ".", 15 | DONE: () => "Done", 16 | ASK: () => "Please send or reply to one or more stickers. *Max 20*\n\nType *done* if done or *cancel*.", 17 | NOTSTICKER: () => "That's not a sticker.", 18 | }, 19 | }; 20 | 21 | command.new({ 22 | onCommand: async (context, bot) => { 23 | const language = context.language().out; 24 | context.reply({ 25 | text: TEXTS[language].ASK(), 26 | }); 27 | const media: typeof context[] = []; 28 | const quotedMedia: typeof context[] = []; 29 | 30 | while (true) { 31 | const response = await context.waitInput().out; 32 | if (response === "timeout" || response.text().out?.toLowerCase() === basicTexts[language].CANCEL().toLowerCase()) 33 | return context.reply({ text: basicTexts[language].CANCELED() }); 34 | if (response.text().out?.toLowerCase() === TEXTS[language].DONE().toLowerCase()) break; 35 | 36 | const content = response.msgContent().out; 37 | const quotedContent = response.quotedMsgContent().out; 38 | const isValid = (_content: typeof content | typeof quotedContent) => { 39 | if (typeof _content !== "string" && _content && "mimetype" in _content && _content.mimetype === "image/webp") return true; 40 | return false; 41 | }; 42 | 43 | if (isValid(content)) { 44 | response.react("✅"); 45 | media.push(response); 46 | } else if (isValid(quotedContent)) { 47 | response.react("✅"); 48 | quotedMedia.push(response); 49 | } else { 50 | response.react("❌").reply({ text: TEXTS[language].NOTSTICKER() }); 51 | } 52 | if (media.length + quotedMedia.length === 20) break; 53 | } 54 | 55 | context.reply({ text: TEXTS[language].PROCESSING(media.length + quotedMedia.length) }); 56 | 57 | const tasks: Promise[] = []; 58 | for (const _media of media) { 59 | tasks.push( 60 | new Promise((resolve, reject) => { 61 | _media 62 | .media("stream") 63 | .out!.then((stream) => saveFile(stream!)) 64 | .then((file) => { 65 | let isAnimated = false; 66 | const content = _media.msgContent().out; 67 | if (typeof content !== "string" && "isAnimated" in content && content.isAnimated) isAnimated = true; 68 | return processSticker(file, isAnimated); 69 | }) 70 | .then(resolve) 71 | .catch(reject); 72 | }) 73 | ); 74 | } 75 | for (const _media of quotedMedia) { 76 | tasks.push( 77 | new Promise((resolve, reject) => { 78 | _media 79 | .quotedMedia("stream") 80 | .out!.then((stream) => saveFile(stream!)) 81 | .then((file) => { 82 | let isAnimated = false; 83 | const content = _media.msgContent().out; 84 | if (typeof content !== "string" && "isAnimated" in content && content.isAnimated) isAnimated = true; 85 | return processSticker(file, isAnimated); 86 | }) 87 | .then(resolve) 88 | .catch(reject); 89 | }) 90 | ); 91 | } 92 | return await Promise.allSettled(tasks).then((results) => { 93 | const rejected = results.filter(({ status }) => status === "rejected"); 94 | if (rejected.length) context.reply({ text: TEXTS[language].FAILED(rejected.length) }); 95 | }); 96 | 97 | function processSticker(file: string, isAnimated: boolean) { 98 | return new Promise((resolve, reject) => { 99 | if (isAnimated) { 100 | const outputGif = randomFileName(); 101 | const outputMp4 = randomFileName(); 102 | exec(`convert WEBP:${file} GIF:${outputGif}`, (e) => { 103 | if (e) return reject(e); 104 | exec( 105 | `ffmpeg -f gif -i ${outputGif} -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -b:v 0 -crf 25 -f mp4 -vcodec libx264 -pix_fmt yuv420p ${outputMp4}`, 106 | (e) => { 107 | if (e) return reject(e); 108 | resolve(context.reply({ video: { url: outputMp4 }, gifPlayback: true }, { quoted: undefined })); 109 | } 110 | ); 111 | }); 112 | } else { 113 | const outputPng = randomFileName(); 114 | exec(`dwebp ${file} -o ${outputPng}`, (e) => { 115 | if (e) return reject(e); 116 | resolve(context.reply({ image: { url: outputPng } }, { quoted: undefined })); 117 | }); 118 | } 119 | }); 120 | } 121 | }, 122 | metadata: { 123 | __filename, 124 | category: "mediatools", 125 | command: ["batchunsticker"], 126 | permission: "all", 127 | premium: true, 128 | locale: { 129 | description: { 130 | id: "[TODO] batchunsticker description", 131 | en: "[TODO] batchunsticker description", 132 | }, 133 | name: { 134 | id: "unstiker sekaligus", 135 | }, 136 | }, 137 | }, 138 | }); 139 | -------------------------------------------------------------------------------- /src/commands/core/Ban.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | CHAT_BANNED: (id: string) => 6 | `*Chat ini telah di-ban oleh admin bot.*\n\nID Chat: ${id}\n\nHarap simpan ID Chat karena akan digunakan pada proses unban. Terima kasih.`, 7 | CHAT_UNBANNED: () => `Chat ini telah di-unban oleh admin bot. Terima kasih.`, 8 | }, 9 | en: { 10 | CHAT_BANNED: (id: string) => 11 | `*This chat has been banned by the bot admin.*\n\nChat ID: ${id}\n\nPlease save the Chat ID as it will be used in the unban process. Thank you.`, 12 | CHAT_UNBANNED: () => `This chat has been unbanned by the bot admin. Thank you.`, 13 | }, 14 | }; 15 | 16 | command.new({ 17 | onCommand: async (context, bot) => { 18 | const lang = context.language().out; 19 | const arg = context.arguments().out; 20 | let id = arg; 21 | if (!id) id = context.chatId().out; 22 | const systemData = context.systemData().out; 23 | systemData.banneds ||= []; 24 | const idx = systemData.banneds.findIndex((v) => v === id); 25 | if (idx !== -1) { 26 | systemData.banneds.splice(idx, 1); 27 | const text = { text: TEXTS[lang].CHAT_UNBANNED() }; 28 | context.reply(text); 29 | if (context.chatId().out !== id) 30 | bot.sendMessage(id, text, { 31 | ephemeralExpiration: 86400 * 7, 32 | }).catch(() => {}); 33 | } else { 34 | systemData.banneds.push(id); 35 | const text = { text: TEXTS[lang].CHAT_BANNED(id) }; 36 | context.reply(text); 37 | if (context.chatId().out !== id) 38 | bot.sendMessage(id, text, { 39 | ephemeralExpiration: 86400 * 7, 40 | }).catch(() => {}); 41 | } 42 | }, 43 | metadata: { 44 | __filename, 45 | command: ["ban"], 46 | permission: "all-owner", 47 | category: "owner", 48 | locale: { 49 | name: { 50 | id: "blokir", 51 | }, 52 | description: { 53 | id: "[TODO] ban description", 54 | en: "[TODO] ban description", 55 | }, 56 | }, 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /src/commands/core/Sleep.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | SLEEP: () => 6 | `zzzz... Miki sudah tidur.\n\nMiki akan berhenti menerima dan merespon ke pesan di grup ini.\n\nKamu bisa gunakan perintah */sleep* lagi untuk membangunkan Miki.`, 7 | WAKEUP: () => 8 | `Ah! Miki sudah bangun.\n\nMiki akan mulai menerima dan merespon ke pesan di grup ini.\n\nKamu bisa gunakan perintah */sleep* lagi untuk menidurkan Miki.`, 9 | }, 10 | en: { 11 | SLEEP: () => 12 | `zzzz... Miki has fallen asleep.\n\nMiki will stop receiving and responding to messages in this group.\n\nYou can use the */sleep* command again to wake up Miki.`, 13 | WAKEUP: () => 14 | `Ah! Miki has woken up.\n\nMiki will start receiving and responding to messages in this group.\n\nYou can use the */sleep* command again to let Miki sleep.`, 15 | }, 16 | }; 17 | 18 | command.new({ 19 | onCommand: (context) => { 20 | const lang = context.language().out; 21 | const chatData = context.chatData().out; 22 | if (chatData.muted) { 23 | delete chatData.muted; 24 | context.reply({ text: TEXTS[lang].WAKEUP() }); 25 | } else { 26 | chatData.muted = true; 27 | context.reply({ text: TEXTS[lang].SLEEP() }); 28 | } 29 | }, 30 | metadata: { 31 | __filename, 32 | command: ["sleep"], 33 | permission: "group-admin", 34 | category: "grouptools", 35 | locale: { 36 | name: { 37 | id: "tidur", 38 | }, 39 | description: { 40 | id: "[TODO] sleep description", 41 | en: "[TODO] sleep description", 42 | }, 43 | }, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/commands/core/addpremium.ts: -------------------------------------------------------------------------------- 1 | import { command, requestArguments } from "../../utils"; 2 | import type * as Types from "../../utils/typings/types"; 3 | 4 | const TEXTS = { 5 | id: { 6 | SUCCESS: (days: number, untilDate: string, status: boolean) => 7 | `Berhasil menambahkan ${days} hari ke premium.\n\nBerlaku hingga: ${untilDate}\nStatus premium: ${status ? "AKTIF" : "TIDAK AKTIF"}`, 8 | }, 9 | en: { 10 | SUCCESS: (days: number, untilDate: string, status: boolean) => 11 | `Added ${days} day${days === 1 ? "" : "s"} to premium.\n\nActive until: ${untilDate}\nPremium status: ${status ? "ACTIVE" : "INACTIVE"}`, 12 | }, 13 | }; 14 | 15 | let LOCALDB: Types.LOCALDB; 16 | const wnet = "@s.whatsapp.net"; 17 | 18 | command.new({ 19 | onStart: (DB) => (LOCALDB = DB), 20 | onCommand: async (context, bot) => { 21 | const lang = context.language().out; 22 | const [dur, num] = await requestArguments(context, { 23 | arguments: [ 24 | ["Duration", (x) => !isNaN(+x), { onWrong: "Duration is not a number." }], 25 | ["Number", (x) => LOCALDB[x.replace(/\D+g/, "") + wnet]?.type === "user", { onWrong: "User not found." }], 26 | ], 27 | }); 28 | if (dur === null) return; 29 | const now = Date.now(); 30 | const duration = parseInt(dur) * (24 * 60 * 60 * 1000); 31 | const number = num.replace(/\D+g/, "") + wnet; 32 | (LOCALDB[number] as Types.USERDB).premiumUntil = now + duration; 33 | const days = duration / (24 * 60 * 60 * 1000); 34 | context.reply({ text: `Added ${days} day(s) to ${number}` }); 35 | bot.sendMessage( 36 | number, 37 | { text: TEXTS[lang].SUCCESS(days, new Date(now + duration).toLocaleString(), now + duration > Date.now()) }, 38 | { 39 | ephemeralExpiration: 86400 * 7, 40 | } 41 | ); 42 | }, 43 | metadata: { 44 | __filename, 45 | command: ["addpremium"], 46 | permission: "all-owner", 47 | category: "owner", 48 | locale: { 49 | description: { 50 | id: "[TODO] addpremium description", 51 | en: "[TODO] addpremium description", 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/commands/core/adminonly.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | ON: () => "Sekarang hanya admin grup yang dapat menggunakan Miki.", 6 | OFF: () => "Sekarang semua anggota grup dapat menggunakan Miki.", 7 | }, 8 | en: { 9 | ON: () => "Only admins can use Miki now.", 10 | OFF: () => "All members can use Miki now.", 11 | }, 12 | }; 13 | 14 | command.new({ 15 | onCommand: (context) => { 16 | const language = context.language().out; 17 | const chatData = context.chatData().out; 18 | if (chatData.adminonly) { 19 | delete chatData.adminonly; 20 | return context.reply({ text: TEXTS[language].OFF() }); 21 | } else { 22 | chatData.adminonly = true; 23 | return context.reply({ text: TEXTS[language].ON() }); 24 | } 25 | }, 26 | metadata: { 27 | __filename, 28 | command: ["adminonly"], 29 | permission: "group-admin", 30 | category: "botsettings", 31 | locale: { 32 | description: { 33 | id: "[TODO] adminonly description", 34 | en: "[TODO] adminonly description", 35 | }, 36 | name: { 37 | id: "hanya admin", 38 | }, 39 | }, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /src/commands/core/databaseBackup.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { command } from "../../utils"; 3 | import { LOCALDB } from "../../utils/typings/types"; 4 | 5 | let LOCALDB: LOCALDB; 6 | let intervalId: NodeJS.Timeout; 7 | 8 | command.new({ 9 | onStart: (DB) => (LOCALDB = DB), 10 | onConnected: (bot) => { 11 | if (!process.env.DATABASE_BACKUP_NUMBER) return console.warn("WARNING: no database backup number."); 12 | intervalId = setInterval(() => { 13 | bot.sendMessage( 14 | process.env.DATABASE_BACKUP_NUMBER?.endsWith("@g.us") ? process.env.DATABASE_BACKUP_NUMBER : process.env.DATABASE_BACKUP_NUMBER + "@s.whatsapp.net", 15 | { 16 | document: fs.readFileSync("./data/db.json"), 17 | mimetype: "application/json", 18 | fileName: `MIKIBACKUP_${Date.now()}.json`, 19 | }, 20 | { 21 | ephemeralExpiration: 86400 * 7, 22 | } 23 | ); 24 | }, 60000); 25 | }, 26 | onDisconnected: (bot) => { 27 | clearInterval(intervalId); 28 | }, 29 | onCommand: (context) => { 30 | context.reply({ 31 | document: fs.readFileSync("./data/db.json"), 32 | mimetype: "application/json", 33 | fileName: `MIKIBACKUP_${Date.now()}.json`, 34 | }); 35 | }, 36 | metadata: { 37 | __filename, 38 | category: "owner", 39 | command: ["getbackup"], 40 | permission: "all-owner", 41 | locale: { 42 | description: { 43 | id: "[TODO] getbackup description", 44 | en: "[TODO] getbackup description", 45 | }, 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/commands/core/delete.ts: -------------------------------------------------------------------------------- 1 | import { command, requestMessage } from "../../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | NOTFROMME: () => "Miki hanya dapat menghapus pesan Miki sendiri.", 6 | NOMSG: () => "Silahkan balas ke pesan yang dikirim Miki biar Miki hapus.", 7 | }, 8 | en: { 9 | NOTFROMME: () => "Miki cannot delete other people's message.", 10 | NOMSG: () => "Please reply to the message Miki sent. Miki will delete it ASAP.", 11 | }, 12 | }; 13 | 14 | command.new({ 15 | onCommand: async (context, bot) => { 16 | const botNumber = process.env.BOT_NUMBER + "@s.whatsapp.net"; 17 | if (context.quotedUserId().out) { 18 | if (context.quotedUserId().out !== botNumber) return context.reply({ text: TEXTS[context.language().out].NOTFROMME() }); 19 | } else { 20 | const [messageContext] = await requestMessage(context, { 21 | messages: [ 22 | [ 23 | "", 24 | (c) => c.quotedUserId().out === botNumber, 25 | { 26 | onMissing: TEXTS[context.language().out].NOMSG(), 27 | onWrong: TEXTS[context.language().out].NOTFROMME() + "\n\n" + TEXTS[context.language().out].NOMSG(), 28 | }, 29 | ], 30 | ], 31 | }); 32 | if (messageContext === null) return; 33 | context = messageContext; 34 | } 35 | 36 | const content = context.msgContent().out!; 37 | if (/*always true*/ typeof content !== "string" && "contextInfo" in content) { 38 | return bot.sendMessage(context.chatId().out, { 39 | delete: { 40 | remoteJid: context.chatId().out, 41 | fromMe: true, 42 | participant: botNumber, 43 | id: content.contextInfo!.stanzaId, 44 | }, 45 | }); 46 | } 47 | }, 48 | metadata: { 49 | __filename, 50 | category: "botsettings", 51 | command: ["delete", "del"], 52 | permission: "all", 53 | locale: { 54 | description: { 55 | id: "[TODO] delete description", 56 | en: "[TODO] delete description", 57 | }, 58 | name: { 59 | id: "hapus pesan", 60 | }, 61 | }, 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /src/commands/core/eval.ts: -------------------------------------------------------------------------------- 1 | import * as utils from "../../utils"; 2 | import { format } from "util"; 3 | import type * as Types from "../../utils/typings/types"; 4 | 5 | let LOCALDB: Types.LOCALDB; 6 | 7 | utils.command.new({ 8 | onStart: (DB) => (LOCALDB = DB), 9 | onCommand: async (context, bot) => { 10 | try { 11 | const [arg] = await utils.requestArguments(context, { 12 | arguments: [["Script"]], 13 | customInput: context.arguments().out || context.quotedText().out, 14 | separator: (v) => [v], 15 | }); 16 | if (arg === null) return; 17 | return context.reply({ text: format(await eval(arg)) }); 18 | } catch (error) { 19 | return context.react("😕").reply({ text: String(error) }); 20 | } 21 | }, 22 | metadata: { 23 | __filename, 24 | command: ["ev"], 25 | permission: "all-owner", 26 | category: "owner", 27 | locale: { 28 | description: { 29 | id: "[TODO] ev description", 30 | en: "[TODO] ev description", 31 | }, 32 | }, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /src/commands/core/feedback.ts: -------------------------------------------------------------------------------- 1 | import { command, basicTexts } from "../../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | NOARG: () => "Silahkan kirimkan pesan teks atau media yang ingin diteruskan ke developer Miki Bot...\n\nKetik *batal* untuk membatalkan.", 6 | DONE: () => "Pesan telah diteruskan. Terima kasih atas partisipasinya!", 7 | }, 8 | en: { 9 | NOARG: () => "Please send a text or media message to be forwarded to developer of Miki Bot...\n\nType *cancel* to cancel.", 10 | DONE: () => "Message has been forwarded. Thank you for your participation!", 11 | }, 12 | }; 13 | 14 | command.new({ 15 | onCommand: async (context, bot) => { 16 | const language = context.language().out; 17 | const arg = context.arguments().out; 18 | const _dest = process.env.FEEDBACK_NUMBER || process.env.OWNER_NUMBER; 19 | const dest = _dest?.endsWith("@g.us") ? _dest : _dest + "@s.whatsapp.net"; 20 | if (!arg) { 21 | const _context = await context.reply({ text: TEXTS[language].NOARG() }).waitInput().out; 22 | if (_context === "timeout" || _context.text().out?.toLowerCase() === basicTexts[language].CANCEL().toLowerCase()) { 23 | return context.reply({ text: basicTexts[language].CANCELED() }); 24 | } 25 | await bot.sendMessage(dest, { forward: _context.update }, { quoted: _context.update }); 26 | return _context.reply({ text: TEXTS[language].DONE() }); 27 | } else { 28 | await bot.sendMessage(dest, { text: arg }, { quoted: context.update }); 29 | return context.reply({ text: TEXTS[language].DONE() }); 30 | } 31 | }, 32 | metadata: { 33 | __filename, 34 | category: "botsettings", 35 | command: ["feedback"], 36 | permission: "all", 37 | locale: { 38 | description: { 39 | id: "[TODO] feedback description", 40 | en: "[TODO] feedback description", 41 | }, 42 | name: { 43 | id: "umpan balik", 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/commands/core/help.ts: -------------------------------------------------------------------------------- 1 | import { command, requestArguments } from "../../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | NAME: () => "Nama perintah", 6 | TITLE: (name: string) => `*Penjelasan Tentang Perintah "${name}"*`, 7 | NOT_FOUND: (name: string) => `Perintah "${name}" tidak ditemukan.\n\nGunakan perintah */menu* untuk melihat daftar perintah yang tersedia.`, 8 | }, 9 | en: { 10 | NAME: () => "Command name", 11 | TITLE: (name: string) => `*Information About "${name}" Command*`, 12 | NOT_FOUND: (name: string) => `The "${name}" command was not found.\n\nUse */menu* command to see list of all commands.`, 13 | }, 14 | }; 15 | 16 | command.new({ 17 | onCommand: async (context) => { 18 | const language = context.language().out; 19 | const [_arg] = await requestArguments(context, { 20 | arguments: [[TEXTS[language].NAME()]], 21 | }); 22 | if (_arg === null) return; 23 | const arg = _arg.toLowerCase(); 24 | const cmd = command.listOfCommands.find(({ metadata }) => metadata.command.includes(arg)); 25 | if (!cmd) return context.reply({ text: TEXTS[language].NOT_FOUND(arg) }); 26 | return context.reply({ text: TEXTS[language].TITLE(arg) + "\n\n" + cmd.metadata.locale.description[language] }); 27 | }, 28 | metadata: { 29 | __filename, 30 | command: ["help"], 31 | permission: "all", 32 | category: "botsettings", 33 | locale: { 34 | name: { 35 | id: "bantuan", 36 | }, 37 | description: { 38 | id: "[TODO] help description", 39 | en: "[TODO] help description", 40 | }, 41 | }, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /src/commands/core/language.ts: -------------------------------------------------------------------------------- 1 | import { command, requestArguments } from "../../utils"; 2 | 3 | const LANGUAGES = { 4 | ID: "Bahasa Indonesia", 5 | EN: "English", 6 | }; 7 | 8 | const TEXTS = { 9 | id: { 10 | NOARG: () => "Silahkan ketik nomor urut, kode bahasa atau nama bahasa dari daftar bahasa yang tersedia di bawah.", 11 | DONE: () => "Bahasa berhasil diganti ke Bahasa Indonesia.", 12 | }, 13 | en: { 14 | NOARG: () => "Please write the number, language code, or language name from the list below:", 15 | DONE: () => "Language has been changed to English.", 16 | }, 17 | }; 18 | 19 | command.new({ 20 | onCommand: async (context, bot) => { 21 | const language = context.language().out; 22 | const text = 23 | TEXTS[language].NOARG() + 24 | "\n\n" + 25 | Object.entries(LANGUAGES) 26 | .map((v, i) => `${i + 1}. *${v[0]}* » ${v[1]}`) 27 | .join("\n"); 28 | const findLanguage = (x: string) => { 29 | if (isNaN(parseInt(x))) { 30 | const str = x.toUpperCase() as "ID" | "EN"; 31 | if (LANGUAGES[str]) return str.toLowerCase(); 32 | else 33 | return Object.entries(LANGUAGES) 34 | .find((x) => x[1].toUpperCase() === str)?.[0] 35 | ?.toLowerCase(); 36 | } else return Object.entries(LANGUAGES)[+x - 1]?.[0]?.toLowerCase(); 37 | }; 38 | const [arg] = await requestArguments(context, { 39 | arguments: [ 40 | [ 41 | "language", 42 | (x) => Boolean(findLanguage(x)), 43 | { 44 | onWrong: text, 45 | onMissing: text, 46 | }, 47 | ], 48 | ], 49 | separator: (x) => [x], 50 | }); 51 | if (arg === null) return; 52 | const lang = findLanguage(arg) as "id" | "en"; 53 | if (context.isGroupChat().out) { 54 | context.chatData().out.language = lang; 55 | } else { 56 | context.userData().out.language = lang; 57 | } 58 | return context.reply({ 59 | text: TEXTS[lang].DONE(), 60 | }); 61 | }, 62 | metadata: { 63 | __filename, 64 | command: ["language"], 65 | permission: "private-admin", 66 | category: "botsettings", 67 | locale: { 68 | description: { 69 | id: "[TODO] language description", 70 | en: "[TODO] language description", 71 | }, 72 | name: { 73 | id: "bahasa", 74 | }, 75 | }, 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /src/commands/core/menu.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../../utils"; 2 | import type { commandConfigurations } from "../../utils/typings/types"; 3 | 4 | const TEXTS: { 5 | [l in "id" | "en"]: { 6 | [k in Uppercase]: Function; 7 | }; 8 | } = { 9 | id: { 10 | BOTSETTINGS: () => "BOT 👾", 11 | GAMES: () => "PERMAINAN 🧩", 12 | GROUPTOOLS: () => "MANAJEMEN GRUP 👥", 13 | MEDIATOOLS: () => "ALAT MEDIA 🖼️", 14 | OTHERTOOLS: () => "PERALATAN LAINNYA ⚙️", 15 | OWNER: () => "PEMILIK BOT 😎", 16 | RANDOMFUN: () => "HIBURAN ACAK 🎲", 17 | }, 18 | en: { 19 | BOTSETTINGS: () => "BOT 👾", 20 | GAMES: () => "GAMES 🧩", 21 | GROUPTOOLS: () => "GROUP MANAGEMENT 👥", 22 | MEDIATOOLS: () => "MEDIA TOOLS 🖼️", 23 | OTHERTOOLS: () => "OTHER TOOLS ⚙️", 24 | OWNER: () => "BOT OWNER 😎", 25 | RANDOMFUN: () => "RANDOM FUN 🎲", 26 | }, 27 | }; 28 | 29 | command.new({ 30 | onCommand: async (context) => { 31 | const hits = context.userData().out.stats.hits; 32 | const isOwner = context.isBotOwner().out; 33 | const language = context.language().out; 34 | const categories: { [k: string]: commandConfigurations["metadata"][] } = {}; 35 | for (const cmd of command.listOfCommands) { 36 | if (cmd.metadata.permission.includes("owner") && !isOwner) continue; 37 | categories[cmd.metadata.category] ??= []; 38 | categories[cmd.metadata.category].push(cmd.metadata); 39 | } 40 | let str = "*Miki Bot Commands List* 🍙"; 41 | for (const category in categories) { 42 | str += "\n\n▓ *" + TEXTS[language][category.toUpperCase() as Uppercase]() + "*\n┊"; 43 | for (const metadata of categories[category]) { 44 | const names = metadata.command; 45 | const otherName = metadata.locale.name?.[language]; 46 | const premium = metadata.premium; 47 | for (const [idx, name] of Object.entries(names)) { 48 | if (idx === "0") { 49 | str += "\n" + (hits[name] ? "┊" : "┃") + ` */${name}*` + (otherName ? ` » ${otherName}` : "") + (premium ? " $" : ""); 50 | } else { 51 | str += "\n" + (hits[name] ? "┊" : "┃") + ` /${name}`; 52 | } 53 | } 54 | } 55 | str += "\n┊\n┗━━●────────────"; 56 | } 57 | return context.reply({ 58 | text: str, 59 | }); 60 | }, 61 | metadata: { 62 | __filename, 63 | command: ["menu"], 64 | permission: "all", 65 | category: "botsettings", 66 | locale: { 67 | description: { 68 | id: "[TODO] menu description", 69 | en: "[TODO] menu description", 70 | }, 71 | }, 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /src/commands/echo.ts: -------------------------------------------------------------------------------- 1 | import { command, requestArguments } from "../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | TEXT: () => "Teks", 6 | }, 7 | en: { 8 | TEXT: () => "Text", 9 | }, 10 | }; 11 | 12 | command.new({ 13 | onCommand: async (context) => { 14 | const [arg] = await requestArguments(context, { 15 | arguments: [[TEXTS[context.language().out].TEXT()]], 16 | customInput: context.arguments().out || context.quotedText().out, 17 | separator: (v) => [v], 18 | }); 19 | if (arg === null) return; 20 | return context.reply({ text: arg }); 21 | }, 22 | metadata: { 23 | __filename, 24 | command: ["echo", "say"], 25 | permission: "all", 26 | category: "othertools", 27 | locale: { 28 | name: { 29 | id: "katakan", 30 | }, 31 | description: { 32 | id: "[TODO] echo description", 33 | en: "[TODO] echo description", 34 | }, 35 | }, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /src/commands/premium.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | PREMSTATS: () => "Status Premium:", 6 | ACTIVE: () => "*AKTIF*", 7 | INACTIVE: () => "TIDAK AKTIF", 8 | UNTIL: () => "s/d", 9 | INACTIVE_FOOTER: (cust_number: string) => 10 | "Ingin berlangganan Premium? Silahkan hubungi wa.me/" + cust_number, 11 | BENEFITS: () => 12 | "Keuntungan Premium:\n1. Akses tanpa batas ke perintah-perintah Premium.\n2. Akses tanpa batas ke fitur-fitur menarik di dalam perintah\n3. Waktu jeda lebih sedikit.\n4. Memungkinkan Miki Bot tetap hidup dan terus berkembang.", 13 | }, 14 | en: { 15 | PREMSTATS: () => "Premium Status:", 16 | ACTIVE: () => "*ACTIVE*", 17 | INACTIVE: () => "INACTIVE", 18 | UNTIL: () => "until", 19 | INACTIVE_FOOTER: (cust_number: string) => 20 | "Want to subscribe to Premium? Please contact Miki Bot Customer Service at wa.me/" + cust_number, 21 | BENEFITS: () => 22 | "Premium Benefits:\n1. Unlimited access to Premium commands.\n2. Unlimited access to interesting features in commands\n3. Lower delay time.\n4. Allows Miki Bot to stay alive and growing.", 23 | }, 24 | }; 25 | 26 | command.new({ 27 | onCommand: (context) => { 28 | const T = TEXTS[context.language().out]; 29 | if (context.isPremiumUser().out) { 30 | const premiumUntil = context.userData().out.premiumUntil; 31 | context.reply({ 32 | text: `${T.PREMSTATS()} ${T.ACTIVE()} ${T.UNTIL()} ${new Date(premiumUntil!).toLocaleString()}\n\n${T.BENEFITS()}`, 33 | }); 34 | } else { 35 | context.reply({ 36 | text: `${T.PREMSTATS()} ${T.INACTIVE()}\n\n${T.INACTIVE_FOOTER(process.env.CUSTOMER_SERVICE_NUMBER!)}\n\n${T.BENEFITS()}`, 37 | }); 38 | } 39 | }, 40 | metadata: { 41 | __filename, 42 | category: "botsettings", 43 | command: ["premium"], 44 | permission: "all", 45 | locale: { 46 | description: { 47 | id: "[TODO] premium description", 48 | en: "[TODO] premium description", 49 | }, 50 | }, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /src/commands/stats.ts: -------------------------------------------------------------------------------- 1 | import { command } from "../utils"; 2 | 3 | const TEXTS = { 4 | id: { 5 | NONE: () => "Kamu belum menggunakan perintah apapun.", 6 | }, 7 | en: { 8 | NONE: () => "You have not used any command.", 9 | }, 10 | }; 11 | 12 | command.new({ 13 | onCommand: (context) => { 14 | const hits = Object.entries(context.userData().out.stats.hits); 15 | if (!hits.length) return context.reply({ text: TEXTS[context.language().out].NONE() }); 16 | return context.reply({ 17 | text: hits 18 | .sort((a, b) => b[1] - a[1]) 19 | .map((v, i) => `${i + 1}. */${v[0]}* » ${v[1]}×`) 20 | .join("\n"), 21 | }); 22 | }, 23 | metadata: { 24 | __filename, 25 | category: "botsettings", 26 | command: ["stats"], 27 | permission: "all", 28 | locale: { 29 | description: { 30 | id: "[TODO] stats description", 31 | en: "[TODO] stats description", 32 | }, 33 | name: { 34 | id: "statistik", 35 | }, 36 | }, 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /src/commands/sticker.ts: -------------------------------------------------------------------------------- 1 | import { command, MessageContext, randomFileName, requestMessage, saveFile } from "../utils"; 2 | import * as fs from "fs"; 3 | import { exec } from "child_process"; 4 | 5 | const supportedMimeTypes: string[][] = [ 6 | ["image/jpeg", "jpg"], 7 | ["image/png", "png"], 8 | ["image/gif", "gif"], 9 | ["video/mp4", "mp4"], 10 | ["image/webp", "webp"], 11 | ["video/mpeg", "mpeg"], 12 | ["video/x-msvideo", "avi"], 13 | ["video/ogg", "ogv"], 14 | ["video/webm", "webm"], 15 | ["video/3gpp", "3gp"], 16 | ]; 17 | const supportedList = supportedMimeTypes.map((v) => v[1].toUpperCase()).join("/"); 18 | 19 | const TEXTS = { 20 | id: { 21 | NOTPREMIUM: () => "Mengatur kedua nama paket dan pembuat stiker hanya dapat dilakukan oleh pengguna Premium.", 22 | WRONG: () => "Format tidak didukung.", 23 | MISSING: () => "Silahkan kirim atau balas ke pesan gambar, video atau dokumen.", 24 | SUPPORTED: (supported: string) => "Format yang didukung: " + supported + " (maksimal 1,5 MB)", 25 | }, 26 | en: { 27 | NOTPREMIUM: () => "Setting both the pack and author name of the sticker can only be done by Premium users.", 28 | WRONG: () => "Unsupported format.", 29 | MISSING: () => "Please send or reply to an image, video or document.", 30 | SUPPORTED: (supported: string) => "Supported formats: " + supported + " (max 1.5 MB)", 31 | }, 32 | }; 33 | 34 | command.new({ 35 | onCommand: async (context) => { 36 | const language = context.language().out; 37 | const content = context.msgContent().out; 38 | const quotedContent = context.quotedMsgContent().out; 39 | function isValid(_content: typeof content | typeof quotedContent) { 40 | if (typeof _content !== "string" && _content && "mimetype" in _content && supportedMimeTypes.some((v) => v[0] === _content.mimetype)) { 41 | if (Number(_content.fileLength) < 1500000) return true; 42 | } 43 | return false; 44 | } 45 | 46 | let file: string; 47 | if (/image|video|document/.test(context.msgType().out || "")) { 48 | if (isValid(content)) { 49 | file = await saveFile((await context.media("stream").out)!); 50 | } else { 51 | return context.reply({ text: `${TEXTS[language].WRONG()}\n\n${TEXTS[language].SUPPORTED(supportedList)}` }); 52 | } 53 | } else if (/image|video|document/.test(context.quotedMsgType().out || "")) { 54 | if (isValid(quotedContent)) { 55 | file = await saveFile((await context.quotedMedia("stream").out)!); 56 | } else { 57 | return context.reply({ text: `${TEXTS[language].WRONG()}\n\n${TEXTS[language].SUPPORTED(supportedList)}` }); 58 | } 59 | } else { 60 | const [mediaContext] = await requestMessage(context, { 61 | messages: [ 62 | [ 63 | "", 64 | (c) => isValid(c.msgContent().out) || isValid(c.quotedMsgContent().out), 65 | { 66 | onMissing: `${TEXTS[language].MISSING()}\n\n${TEXTS[language].SUPPORTED(supportedList)}`, 67 | onWrong: `${TEXTS[language].WRONG()}\n\n${TEXTS[language].MISSING()}\n\n${TEXTS[language].SUPPORTED(supportedList)}`, 68 | }, 69 | ], 70 | ], 71 | }); 72 | if (mediaContext === null) return; 73 | if (isValid(mediaContext.msgContent().out)) { 74 | file = await saveFile((await mediaContext.media("stream").out)!); 75 | } else { 76 | file = await saveFile((await mediaContext.quotedMedia("stream").out)!); 77 | } 78 | } 79 | 80 | return await new Promise((resolve, reject) => { 81 | const output = randomFileName(); 82 | exec( 83 | `ffmpeg -i ${file} -vcodec libwebp -compression_level 6 -q:v 25 -b:v 200k -vf "scale='if(gt(a,1),520,-1)':'if(gt(a,1),-1,520)':flags=lanczos:force_original_aspect_ratio=decrease,format=bgra,pad=520:520:-1:-1:color=#00000000,setsar=1" -f webp ${output}`, 84 | (e) => { 85 | if (e) return reject(e); 86 | let [pack, author] = (context.arguments().out || "").split(/\s*\|\s*/); 87 | if (author && !context.isPremiumUser().out) { 88 | context.reply({ text: TEXTS[language].NOTPREMIUM() }); 89 | author = ""; 90 | } 91 | let exifFile: string; 92 | if (!pack && !author) exifFile = "./res/default.exif"; 93 | else exifFile = generateExif(pack, author || "Miki Bot by Rioze"); 94 | exec(`webpmux -set exif ${exifFile} ${output} -o ${output}`, (e) => { 95 | if (e) console.error(e); 96 | resolve(context.reply({ sticker: { url: output } })); 97 | }); 98 | } 99 | ); 100 | }); 101 | }, 102 | metadata: { 103 | __filename, 104 | category: "mediatools", 105 | command: ["sticker", "stiker", "s"], 106 | permission: "all", 107 | locale: { 108 | description: { 109 | id: "[TODO] sticker description", 110 | en: "[TODO] sticker description", 111 | }, 112 | }, 113 | }, 114 | }); 115 | 116 | function generateExif(pack: string = "", author: string = "") { 117 | const json = JSON.stringify({ 118 | "sticker-pack-name": pack, 119 | "sticker-pack-publisher": author, 120 | }); 121 | // @ts-ignore 122 | let len: number | string = new TextEncoder("utf-8").encode(json).length; 123 | const f = Buffer.from([0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00]); 124 | const code = [0x00, 0x00, 0x16, 0x00, 0x00, 0x00]; 125 | len > 256 ? ((len = len - 256), code.unshift(0x01)) : code.unshift(0x00); 126 | len < 16 ? (len = "0" + len) : (len = len.toString(16)); 127 | const buffer = Buffer.concat([f, Buffer.from(len, "hex"), Buffer.from(code), Buffer.from(json, "utf-8")]); 128 | const filename = randomFileName(); 129 | return fs.writeFileSync(filename, buffer), filename; 130 | } 131 | -------------------------------------------------------------------------------- /src/commands/unsticker.ts: -------------------------------------------------------------------------------- 1 | import { command, randomFileName, requestMessage, saveFile } from "../utils"; 2 | import { exec } from "child_process"; 3 | 4 | const TEXTS = { 5 | id: { 6 | STICKER: () => "Stiker", 7 | }, 8 | en: { 9 | STICKER: () => "Sticker", 10 | }, 11 | }; 12 | 13 | command.new({ 14 | onCommand: async (context) => { 15 | const language = context.language().out; 16 | const content = context.msgContent().out; 17 | const quotedContent = context.quotedMsgContent().out; 18 | function isValid(_content: typeof content | typeof quotedContent) { 19 | if (typeof _content !== "string" && _content && "mimetype" in _content && _content.mimetype === "image/webp") { 20 | return true; 21 | } 22 | return false; 23 | } 24 | 25 | let file: string; 26 | let isAnimated = false; 27 | const qContent = context.quotedMsgContent().out; 28 | if (isValid(qContent)) { 29 | file = await saveFile((await context.quotedMedia("stream").out)!); 30 | if (typeof qContent !== "string" && "isAnimated" in qContent! && qContent.isAnimated) isAnimated = true; 31 | } else { 32 | const [stickerContext] = await requestMessage(context, { 33 | messages: [[TEXTS[language].STICKER(), (c) => isValid(c.msgContent().out) || isValid(c.quotedMsgContent().out)]], 34 | }); 35 | if (stickerContext === null) return; 36 | if (isValid(stickerContext.msgContent().out)) { 37 | file = await saveFile((await stickerContext.media("stream").out)!); 38 | const content = stickerContext.msgContent().out; 39 | if (typeof content !== "string" && "isAnimated" in content && content.isAnimated) isAnimated = true; 40 | } else { 41 | file = await saveFile((await stickerContext.quotedMedia("stream").out)!); 42 | const qContent = stickerContext.quotedMsgContent().out!; 43 | if (typeof qContent !== "string" && "isAnimated" in qContent && qContent.isAnimated) isAnimated = true; 44 | } 45 | } 46 | 47 | if (isAnimated) { 48 | const outputGif = randomFileName(); 49 | const outputMp4 = randomFileName(); 50 | return await new Promise((resolve, reject) => { 51 | exec(`convert WEBP:${file} GIF:${outputGif}`, (e) => { 52 | if (e) return reject(e); 53 | exec( 54 | `ffmpeg -f gif -i ${outputGif} -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -b:v 0 -crf 25 -f mp4 -vcodec libx264 -pix_fmt yuv420p ${outputMp4}`, 55 | (e) => { 56 | if (e) return reject(e); 57 | resolve(context.reply({ video: { url: outputMp4 }, gifPlayback: true })); 58 | } 59 | ); 60 | }); 61 | }); 62 | } else { 63 | const outputPng = randomFileName(); 64 | return await new Promise((resolve, reject) => { 65 | exec(`dwebp ${file} -o ${outputPng}`, (e) => { 66 | if (e) return reject(e); 67 | resolve(context.reply({ image: { url: outputPng } })); 68 | }); 69 | }); 70 | } 71 | }, 72 | metadata: { 73 | __filename, 74 | category: "mediatools", 75 | command: ["unsticker", "unstiker", "uns"], 76 | permission: "all", 77 | locale: { 78 | description: { 79 | id: "[TODO] unstiker description", 80 | en: "[TODO] unstiker description", 81 | }, 82 | }, 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import path from "path"; 3 | import * as fs from "fs"; 4 | import Promptees from "promptees"; 5 | import * as fsp from "fs/promises"; 6 | import makeWASocket, * as Baileys from '@whiskeysockets/baileys'; 7 | import { MessageContext, createMessageContext, createGroupParticipantsUpdateContext } from "./utils"; 8 | 9 | import type * as Types from "./utils/typings/types"; 10 | 11 | (async () => { 12 | // create a file to store data 13 | if (!fs.existsSync("./data/")) fs.mkdirSync("./data/"); 14 | if (!fs.existsSync("./tmp/")) fs.mkdirSync("./tmp/"); 15 | if (!fs.existsSync("./res/")) fs.mkdirSync("./res/"); 16 | if (!fs.existsSync("./data/db.json")) fs.writeFileSync("./data/db.json", "{}"); 17 | const LOCALDB: Types.LOCALDB = JSON.parse(fs.readFileSync("./data/db.json").toString()); 18 | // save every minute 19 | setInterval(() => fsp.writeFile("./data/db.json", JSON.stringify(((LOCALDB.system.backupTime = Date.now()), LOCALDB))), 60000); 20 | 21 | if (!LOCALDB.system) LOCALDB.system = { type: "system", backupTime: Date.now() }; 22 | 23 | // load all commands and intents 24 | const { command, basicTexts } = await import("./utils"); 25 | async function recursiveImport(dir: string) { 26 | for (const file of fs.readdirSync(dir)) { 27 | const _path = path.join(dir, file); 28 | if (_path.endsWith(".js")) await import(_path); 29 | else if (fs.statSync(_path).isDirectory()) recursiveImport(_path); 30 | } 31 | } 32 | await recursiveImport(path.join(__dirname, "./commands")); 33 | for (const { onStart } of command.listOfCommands) { 34 | onStart?.(LOCALDB, command.listOfCommands); 35 | } 36 | console.log(command.listOfCommands.length, "commands loaded."); 37 | 38 | // my lib: https://npmjs.com/promptees 39 | const promptees = new Promptees({ 40 | // Sets the default command wait time to 15 minutes. 41 | timeout: 1000 * 60 * 15, 42 | onTimeout: () => "timeout", 43 | }); 44 | 45 | // WhatsApp connection 46 | (async function connectToWhatsApp() { 47 | const { state, saveCreds } = await Baileys.useMultiFileAuthState("./data/session"); 48 | const messageStore: { [key: string]: Baileys.WAProto.IMessage } = {}; 49 | const messageQueue: (() => Promise)[] = []; 50 | 51 | // send up to 10 messages per second (avoid ban) 52 | let queueIntervalId: NodeJS.Timeout; 53 | (function sendQueue() { 54 | if (messageQueue.length) { 55 | const msgToBeSent = messageQueue.splice(0, 2); 56 | for (const msg of msgToBeSent) { 57 | msg(); 58 | } 59 | } 60 | const interval = messageQueue.length ? Math.floor(Math.random() * 100) + 100 : 1000; 61 | queueIntervalId = setTimeout(sendQueue, interval); 62 | })(); 63 | 64 | // initialize WA connection 65 | const bot = makeWASocket({ 66 | printQRInTerminal: true, 67 | browser: Baileys.Browsers.appropriate("Miki"), 68 | version: (await Baileys.fetchLatestBaileysVersion()).version, 69 | auth: state, 70 | getMessage: async (key) => (key.id ? messageStore[key.id] : undefined), 71 | patchMessageBeforeSending: (message) => { 72 | if (message.buttonsMessage || message.templateMessage || message.listMessage) { 73 | message = { 74 | viewOnceMessage: { 75 | message: { 76 | messageContextInfo: { 77 | deviceListMetadataVersion: 2, 78 | deviceListMetadata: {}, 79 | }, 80 | ...message, 81 | }, 82 | }, 83 | }; 84 | } 85 | return message; 86 | }, 87 | }); 88 | 89 | // overwrite sendMessage so it can save and push messages 90 | const oldSendMessage = bot.sendMessage; 91 | bot.sendMessage = async function (...args) { 92 | return await new Promise((resolve, reject) => { 93 | // queue message to be sent 94 | messageQueue.push(async () => { 95 | try { 96 | const sent = await oldSendMessage.call(this, ...args); 97 | if (!sent) return resolve(sent); 98 | // save message temporarily 99 | messageStore[sent.key.id!] = sent.message!; 100 | // delete after 15 seconds 101 | setTimeout((id: string) => delete messageStore[id], 15000, `${sent.key.id}`); 102 | return resolve(sent); 103 | } catch (e) { 104 | return reject(e); 105 | } 106 | }); 107 | }); 108 | }; 109 | 110 | // connection handler 111 | bot.ev.on("connection.update", (update) => { 112 | const { connection, lastDisconnect } = update; 113 | if (connection === "close") { 114 | console.error("Disconnected from WhatsApp"); 115 | for (const { onDisconnected } of command.listOfCommands) { 116 | onDisconnected?.(bot); 117 | } 118 | // @ts-ignore 119 | if (lastDisconnect?.error?.output?.statusCode !== Baileys.DisconnectReason.loggedOut) { 120 | clearTimeout(queueIntervalId); 121 | connectToWhatsApp(); //reconnect 122 | } else { 123 | console.error("Logged out from WhatsApp"); 124 | console.error("Removing old session"); 125 | fs.rmSync("./data/session", { recursive: true, force: true }); 126 | connectToWhatsApp(); //reconnect 127 | } 128 | } else if (connection === "connecting") { 129 | console.log("Connecting to WhatsApp"); 130 | } else if (connection === "open") { 131 | console.log("Connected to WhatsApp"); 132 | for (const { onConnected } of command.listOfCommands) { 133 | onConnected?.(bot); 134 | } 135 | } 136 | }); 137 | 138 | // session credentials handler 139 | bot.ev.on("creds.update", () => saveCreds()); 140 | 141 | const MessageContext = createMessageContext(LOCALDB, bot, promptees); 142 | 143 | // message handler 144 | bot.ev.on("messages.upsert", async (update) => { 145 | try { 146 | if (update.type !== "notify") return; 147 | const m = update.messages[0]; 148 | if (!m.message) return; 149 | if (m.key?.fromMe) return; 150 | if (m.key?.id?.length === 16 && m.key.id.startsWith("BAE5")) return; // message from other Baileys bot 151 | if (m.key?.remoteJid === "status@broadcast") return; 152 | if (m.message.protocolMessage) return; 153 | if (m.message.reactionMessage) return; 154 | if (m.message.pollUpdateMessage) return; 155 | if (m.message.ephemeralMessage) { 156 | m.message = m.message.ephemeralMessage.message!; 157 | } else if (m.message.viewOnceMessage) { 158 | m.message = m.message.viewOnceMessage.message!; 159 | } 160 | 161 | const now = Date.now(); 162 | const context = new MessageContext(m); 163 | const user_id = context.userId().out; 164 | const chat_id = context.chatId().out; 165 | 166 | // check if either user or chat is listed in banned list, and return immediately if do exists 167 | if (LOCALDB.system.banneds?.some((id: string) => id === user_id || id === chat_id)) return; 168 | 169 | if (!LOCALDB[user_id]) { 170 | LOCALDB[user_id] = { 171 | type: "user", 172 | language: "id", 173 | stats: { 174 | joined: now, 175 | lastSeen: now, 176 | hits: {}, 177 | }, 178 | }; 179 | } 180 | if (chat_id !== user_id) { 181 | if (!LOCALDB[chat_id]) { 182 | LOCALDB[chat_id] = { 183 | type: "group", 184 | language: "id", 185 | }; 186 | } 187 | } 188 | 189 | const id = user_id + chat_id; 190 | const cmdName = context.command().out; 191 | const language = context.language().out; 192 | const user_data = context.userData().out; 193 | const chat_data = context.chatData().out; 194 | const isBotOwner = context.isBotOwner().out; 195 | const isGroupChat = context.isGroupChat().out; 196 | const isPrompting = promptees.isPrompting(id); 197 | const isPremiumUser = context.isPremiumUser().out; 198 | const TEXTS = basicTexts[language]; 199 | let _isGroupChatAdmin: boolean | null = null; 200 | const isGroupChatAdmin = async () => _isGroupChatAdmin ?? (_isGroupChatAdmin = await context.isGroupChatAdmin().out); 201 | 202 | user_data.stats.lastSeen = now; 203 | 204 | console.log( 205 | [ 206 | ">", 207 | new Date(now).toLocaleTimeString(), 208 | "~" + user_id.replace(/\D+/g, ""), 209 | isGroupChat ? "@" + chat_id : "", 210 | context.msgType().out, 211 | context.text().out?.slice(0, 250) || "-", 212 | ] 213 | .filter(Boolean) 214 | .join(" ") 215 | ); 216 | console.dir(context.update, { depth: null }); 217 | 218 | if (!chat_data.muted) { 219 | for (const { onMessage } of command.listOfCommands) { 220 | onMessage?.(context, bot); 221 | } 222 | } 223 | 224 | // if the bot is waiting for a response from the user, return it 225 | if (isPrompting) return promptees.returnPrompt(id, context); 226 | 227 | // if the user sends a command 228 | if (cmdName) { 229 | // if the chat is muted, don't run commands except 'sleep' command. 230 | if (chat_data.muted && cmdName !== "sleep") return; 231 | // if the group is set to admin only who can run commands 232 | if (isGroupChat && chat_data.adminonly && !(await isGroupChatAdmin())) return; 233 | // find command 234 | const theCommand = command.listOfCommands.find((v) => v.metadata.command.includes(cmdName)); 235 | if (!theCommand) { 236 | if (isGroupChat) return; 237 | else { 238 | return context.react("🤷‍♀️").reply({ 239 | text: TEXTS.CMD_NOT_FOUND(), 240 | }); 241 | } 242 | } 243 | // anti spam 244 | if (user_data.stats.lastHit) { 245 | const cooldownTime = (user_data.premiumUntil || 0) > now ? 250 : 2000; 246 | if (now - user_data.stats.lastHit < cooldownTime) { 247 | return context.react("✋").reply({ text: TEXTS.SPAM(cooldownTime === 250 ? 0.25 : 2) }); 248 | } 249 | } 250 | user_data.stats.lastHit = now; 251 | // permission 252 | switch (theCommand.metadata.permission) { 253 | case "all": 254 | break; 255 | case "all-owner": 256 | if (!isBotOwner) return context.react("❌").reply({ text: TEXTS.NOT_OWNER() }); 257 | break; 258 | case "group-admin": 259 | if (!isGroupChat) return context.react("❌").reply({ text: TEXTS.NOT_GROUP() }); 260 | if (!(await isGroupChatAdmin())) return context.react("❌").reply({ text: TEXTS.NOT_ADMIN() }); 261 | break; 262 | case "group-all": 263 | if (!isGroupChat) return context.react("❌").reply({ text: TEXTS.NOT_GROUP() }); 264 | break; 265 | case "group-owner": 266 | if (!isBotOwner) return context.react("❌").reply({ text: TEXTS.NOT_OWNER() }); 267 | if (!isGroupChat) return context.react("❌").reply({ text: TEXTS.NOT_GROUP() }); 268 | break; 269 | case "private-all": 270 | if (isGroupChat) return context.react("❌").reply({ text: TEXTS.NOT_PRIVATE() }); 271 | break; 272 | case "private-owner": 273 | if (!isBotOwner) return context.react("❌").reply({ text: TEXTS.NOT_OWNER() }); 274 | if (isGroupChat) return context.react("❌").reply({ text: TEXTS.NOT_PRIVATE() }); 275 | break; 276 | case "private-admin": 277 | if (isGroupChat && !(await isGroupChatAdmin())) return context.react("❌").reply({ text: TEXTS.NOT_PRIVATE_ADMIN() }); 278 | } 279 | // limit premium command to a non-premium user for once every 24h 280 | const lastPremCmdDiff = now - (user_data.lastPremCmd || 0); 281 | if (theCommand.metadata.premium && !isPremiumUser && lastPremCmdDiff < 86_400_000) { 282 | const time = (a: number, b: number) => Math.floor((now - lastPremCmdDiff) / a) % b; 283 | return context 284 | .react("💲") 285 | .reply({ text: TEXTS.PREMIUM_LIMIT(`${time(3_600_000, 1)}:${time(60_000, 60)}:${time(1_000, 60)}`) }); 286 | } 287 | // send description if first time 288 | if (!user_data.stats.hits[cmdName]) { 289 | context.reply({ 290 | text: TEXTS.FIRSTTIME_CMD_DESC(cmdName, theCommand.metadata.locale.description[language], theCommand.metadata.premium || false), 291 | }); 292 | } 293 | // finally run the command 294 | try { 295 | await theCommand.onCommand(context, bot); 296 | user_data.stats.hits[cmdName] ??= 0; 297 | user_data.stats.hits[cmdName] += 1; 298 | if (theCommand.metadata.premium) user_data.lastPremCmd = now; 299 | return context.react("✅"); 300 | } catch (e) { 301 | console.error(e); 302 | context.react("❌").reply({ text: TEXTS.ERROR() }); 303 | const dest = process.env.ERROR_REPORT_NUMBER || process.env.OWNER_NUMBER; 304 | bot.sendMessage( 305 | dest?.endsWith("@g.us") ? dest : dest + "@s.whatsapp.net", 306 | { 307 | text: `Error at *${cmdName}*:\n\t${(e as Error)?.stack || e}`, 308 | }, 309 | { quoted: context.update, ephemeralExpiration: 86400 } 310 | ); 311 | } 312 | } 313 | } catch (error) { 314 | console.error(error); 315 | } 316 | }); 317 | 318 | const GroupParticipantsUpdateContext = createGroupParticipantsUpdateContext(LOCALDB, bot); 319 | 320 | // group participants update handler 321 | bot.ev.on("group-participants.update", (update) => { 322 | const groupParticipantsUpdateContext = new GroupParticipantsUpdateContext(update); 323 | 324 | if (!groupParticipantsUpdateContext.chatData().out.muted) { 325 | for (const { onGroupParticipantsUpdate } of command.listOfCommands) { 326 | onGroupParticipantsUpdate?.(groupParticipantsUpdateContext, bot); 327 | } 328 | } 329 | }); 330 | })(); 331 | })().catch((e) => { 332 | console.error(e); 333 | process.exit(1); 334 | }); 335 | -------------------------------------------------------------------------------- /src/utils/commands.ts: -------------------------------------------------------------------------------- 1 | import type * as Types from "./typings/types"; 2 | 3 | export const command = new (class Commands { 4 | listOfCommands: Types.commandConfigurations[] = []; 5 | 6 | new(configurations: Types.commandConfigurations) { 7 | // if there is a command having the same file name, you want it to update the existing one, not adding and duplicated it. 8 | const index = this.listOfCommands.findIndex(({ metadata }) => metadata.__filename === configurations.metadata.__filename); 9 | if (index !== -1) { 10 | this.listOfCommands.splice(index, 1, configurations); 11 | } else { 12 | this.listOfCommands.push(configurations); 13 | } 14 | } 15 | })(); 16 | 17 | export const basicTexts = { 18 | id: { 19 | CMD_NOT_FOUND: () => 20 | "Maaf, Miki gak bisa mengenali perintah tersebut.\n\nBeritahu Miki menggunakan perintah */feedback* jika kamu ingin fitur tersebut ditambahkan. Miki akan memberitahukannya pada sang owner!", 21 | INT_NOT_FOUND: () => "Maaf, Miki gak bisa memahami tulisanmu.\n\nAjarin miki dengan menggunakan perintah */feedback*, yuk!", 22 | NOT_OWNER: () => "Cuma sang owner yang bisa kasih perintah ini ke Miki.", 23 | NOT_ADMIN: () => "Cuma sang admin grup yang bisa kasih perintah ini ke Miki.", 24 | NOT_GROUP: () => "Kamu harus berada di dalam grup untuk dapat memberikan perintah ini ke Miki.", 25 | NOT_PRIVATE: () => "Kamu harus berada di chat pribadi untuk dapat memberikan perintah ini ke Miki.", 26 | NOT_PREMIUM: () => "Perintah ini khusus untuk member Premium. Kamu harus menjadi member Premium untuk menggunakan perintah ini.", 27 | NOT_PRIVATE_ADMIN: () => "Perintah ini hanya bisa dijalankan di chat pribadi atau admin grup jika di dalam grup.", 28 | SPAM: (s: number) => `Harap beri jeda selama ${s} detik sebelum memulai perintah baru.`, 29 | FIRSTTIME_CMD_DESC: (cmd: string, desc: string, prem: boolean) => 30 | `*Pengenalan Perintah _/${cmd}_*\n\n${ 31 | prem ? `_Perintah ini adalah perintah Premium._\n\n` : "" 32 | }${desc}\n\nKamu bisa melihat pesan bantuan ini lagi dengan perintah:\n*/help (spasi) nama perintah*`, 33 | CANCELED: () => `Operasi dibatalkan.`, 34 | CANCEL: () => "Batal", 35 | PREMIUM_LIMIT: (time: string) => 36 | `Kamu hanya dapat menggunakan perintah Premium sekali sehari. Berlangganan Premium sekarang untuk mendapatkan akses tanpa batas. Ketik */premium* untuk selengkapnya.\n\nTunggu ${time} lagi untuk menggunakan perintah Premium.`, 37 | ERROR: () => 38 | "Terjadi kesalahan pada sistem Miki. Mohon maaf atas ketidaknyamanannya. Silahkan coba beberapa saat lagi atau laporkan dengan perintah */feedback*.", 39 | }, 40 | en: { 41 | CMD_NOT_FOUND: () => 42 | "Sorry, Miki can't recognize the command.\n\nTell Miki using */feedback* command if you want the feature to be added. Miki will help u tell my owner!", 43 | INT_NOT_FOUND: () => "Sorry, Miki can't understand.\n\nCould you please teach Miki using the */feedback* command...?", 44 | NOT_OWNER: () => "Only my owner can give Miki this command.", 45 | NOT_ADMIN: () => "Only the group admin can give Miki this command.", 46 | NOT_GROUP: () => "You must be in a group to be able to give Miki this command.", 47 | NOT_PRIVATE: () => "You must be in a private chat to be able to give Miki this command.", 48 | NOT_PREMIUM: () => "This command is only for Premium members. You must be a Premium member to use this command.", 49 | NOT_PRIVATE_ADMIN: () => "This command can only be used inside a private chat or admins if inside a group chat.", 50 | SPAM: (s: number) => `Please give a delay for ${s} seconds before starting a new command.`, 51 | FIRSTTIME_CMD_DESC: (cmd: string, desc: string, prem: boolean) => 52 | `*Introduction to _/${cmd}_*\n\n${ 53 | prem ? `_Perintah ini adalah perintah Premium._\n\n` : "" 54 | }${desc}\n\nYou can see this help message again by using the command:\n*/help (space) command name*`, 55 | CANCELED: () => `Operation canceled.`, 56 | CANCEL: () => "Cancel", 57 | PREMIUM_LIMIT: (time: string) => 58 | `You can only use Premium commands once per day. Subscribe to Premium for unlimited access.\n\nWait for ${time} to use Premium commands.`, 59 | ERROR: () => "There was an error in Miki's system. Sorry for the inconvenience. Please try again at a later time or report it using the */feedback* command.", 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /src/utils/context.ts: -------------------------------------------------------------------------------- 1 | import type * as Types from "./typings/types"; 2 | import makeWASocket, * as Baileys from "@whiskeysockets/baileys"; 3 | import Promptees, { PrompteesOpts } from "promptees"; 4 | import { escapeRegExp } from "lodash"; 5 | const PREFIXES = process.env.PREFIX?.split("") || ["/"]; 6 | 7 | export function createMessageContext(LOCALDB: Types.LOCALDB, bot: ReturnType, promptees: Promptees) { 8 | return class Context { 9 | private _out: any = undefined; 10 | 11 | constructor( 12 | /** 13 | * Incoming message 14 | */ 15 | public update: Baileys.WAProto.IWebMessageInfo 16 | ) {} 17 | 18 | get out(): O { 19 | return this._out; 20 | } 21 | 22 | /** 23 | * It basically a method to modify the `context.out` value. 24 | * 25 | * With it you can create a different design pattern, e.g: 26 | * ```js 27 | * ctx.text().and(o => o.toUpperCase()).and((o, ctx) => ctx.reply({ 28 | * text: `You said "${o}" in upper case.` 29 | * })); 30 | * ``` 31 | */ 32 | and<_O>(fn: (out: O, thisContext: this) => _O) { 33 | this._out = fn(this._out, this); 34 | return this as unknown as Context<_O>; 35 | } 36 | 37 | /** 38 | * Also known as the sender id. If in a private chat, the id will be the same as the chat id. 39 | * 40 | * ```js 41 | * ctx.userId().out; 42 | * ``` 43 | */ 44 | userId() { 45 | this._out = (this.update.key.participant || this.update.participant || this.update.key.remoteJid)!; 46 | return this as unknown as Context; 47 | } 48 | 49 | /** 50 | * The chat id. If in a private chat, the id will be the same as the user id. 51 | * 52 | * ```js 53 | * ctx.chatId().out; 54 | * ``` 55 | */ 56 | chatId() { 57 | this._out = this.update.key.remoteJid!; 58 | return this as unknown as Context; 59 | } 60 | 61 | /** 62 | * Data from the bot system. Can be accessed from any chat. 63 | * 64 | * ```js 65 | * ctx.systemData().out; 66 | * ``` 67 | */ 68 | systemData() { 69 | this._out = LOCALDB.system; 70 | return this as unknown as Context; 71 | } 72 | 73 | /** 74 | * Chat data. If in a private chat, `userData().out` and `chatData().out` are the same object. 75 | * 76 | * ```js 77 | * ctx.chatData().out; 78 | * ``` 79 | */ 80 | chatData() { 81 | this._out = LOCALDB[this.chatId().out]; 82 | return this as unknown as Context; 83 | } 84 | 85 | /** 86 | * User data. If in a private chat, `userData().out` and `chatData().out` are the same object. 87 | * 88 | * ```js 89 | * ctx.userData().out; 90 | * ``` 91 | */ 92 | userData() { 93 | this._out = LOCALDB[this.userId().out]; 94 | return this as unknown as Context; 95 | } 96 | 97 | /** 98 | * Language used in the chat (`"id"` or `"en"`). Defaults to `"id"`. 99 | * 100 | * ```js 101 | * ctx.language().out; 102 | * ``` 103 | */ 104 | language() { 105 | this._out = this.chatData().out.language || "id"; 106 | return this as unknown as Context<"id" | "en">; 107 | } 108 | 109 | /** 110 | * This message type. 111 | * 112 | * Examples: `'imageMessage', 'extendedTextMessage', 'stickerMessage', ...` 113 | * 114 | * ```js 115 | * ctx.msgType().out; 116 | * ``` 117 | */ 118 | msgType() { 119 | this._out = Object.keys(this.update.message!).filter((v) => { 120 | return v !== "messageContextInfo" && v !== "senderKeyDistributionMessage"; 121 | })[0] as keyof Baileys.WAProto.IMessage; 122 | return this as unknown as Context; 123 | } 124 | 125 | /** 126 | * This message content. 127 | * 128 | * ```js 129 | * ctx.msgContent().out; 130 | * ``` 131 | */ 132 | msgContent() { 133 | this._out = this.update.message![this.msgType().out]!; 134 | return this as unknown as Context>; 135 | } 136 | 137 | /** 138 | * Downloads the media on this message. Resolves to undefined if the message type is not media. 139 | * 140 | * ```js 141 | * await ctx.media(...).out; 142 | * ``` 143 | */ 144 | media(type: "buffer", options?: Baileys.MediaDownloadOptions): Context>; 145 | media(type: "stream", options?: Baileys.MediaDownloadOptions): Context>; 146 | media(type: "buffer" | "stream", options: Baileys.MediaDownloadOptions = {}) { 147 | const content = this.msgContent().out; 148 | if (typeof content === "object" && "url" in content) { 149 | this._out = Baileys.downloadMediaMessage(this.update, type, options); 150 | } else { 151 | this._out = undefined; 152 | } 153 | return this as unknown as Context>; 154 | } 155 | 156 | /** 157 | * Downloads the media of quoted message. Resolves to undefined if the message type is not media. 158 | * 159 | * ```js 160 | * await ctx.media(...).out; 161 | * ``` 162 | */ 163 | quotedMedia(type: "buffer", options?: Baileys.MediaDownloadOptions): Context>; 164 | quotedMedia(type: "stream", options?: Baileys.MediaDownloadOptions): Context>; 165 | quotedMedia(type: "buffer" | "stream", options: Baileys.MediaDownloadOptions = {}) { 166 | const content = this.quotedMsgContent().out; 167 | if (content && typeof content === "object" && "url" in content) { 168 | this._out = Baileys.downloadMediaMessage( 169 | { key: {}, message: { [this.quotedMsgType().out!]: content } } as Baileys.WAProto.IWebMessageInfo, 170 | type, 171 | options 172 | ); 173 | } else { 174 | this._out = undefined; 175 | } 176 | return this as unknown as Context>; 177 | } 178 | 179 | /** 180 | * This quoted message type. `undefined` if no message is quoted. 181 | * 182 | * Examples: `'imageMessage', 'extendedTextMessage', 'stickerMessage', ...` 183 | * 184 | * ```js 185 | * ctx.quotedMsgType().out; 186 | * ``` 187 | */ 188 | quotedMsgType() { 189 | const content = this.msgContent().out; 190 | if ( 191 | typeof content !== "string" && 192 | "contextInfo" in content && 193 | content.contextInfo && 194 | "quotedMessage" in content.contextInfo && 195 | content.contextInfo.quotedMessage 196 | ) { 197 | this._out = Object.keys(content.contextInfo.quotedMessage)[0]; 198 | } else { 199 | this._out = undefined; 200 | } 201 | return this as unknown as Context; 202 | } 203 | 204 | /** 205 | * This quoted message content. `undefined` if no message is quoted. 206 | * 207 | * ```js 208 | * ctx.quotedMsgContent().out; 209 | * ``` 210 | */ 211 | quotedMsgContent() { 212 | const content = this.msgContent().out; 213 | if ( 214 | typeof content !== "string" && 215 | "contextInfo" in content && 216 | content.contextInfo && 217 | "quotedMessage" in content.contextInfo && 218 | content.contextInfo.quotedMessage 219 | ) { 220 | this._out = content.contextInfo.quotedMessage![this.quotedMsgType().out!]; 221 | } else { 222 | this._out = undefined; 223 | } 224 | return this as unknown as Context; 225 | } 226 | 227 | /** 228 | * Text of this message. `undefined` if the message contains no text. 229 | * 230 | * ```js 231 | * ctx.text().out; 232 | * ``` 233 | */ 234 | text() { 235 | const content = this.msgContent().out; 236 | if (typeof content === "string") this._out = content; 237 | else if ("text" in content) this._out = content.text!; 238 | else if ("caption" in content) this._out = content.caption!; 239 | else if ("singleSelectReply" in content) this._out = content.title!; 240 | else if ("selectedDisplayText" in content) this._out = content.selectedDisplayText!; 241 | else if ("contentText" in content) this._out = content.contentText + "\n\n" + content.footerText; 242 | else this._out = undefined; 243 | return this as unknown as Context; 244 | } 245 | 246 | /** 247 | * Text of the quoted message. `undefined` if not quoting any message or the message contains no text. 248 | * 249 | * ```js 250 | * ctx.quotedText().out; 251 | * ``` 252 | */ 253 | quotedText() { 254 | const content = this.quotedMsgContent().out; 255 | if (content) { 256 | if (typeof content === "string") this._out = content; 257 | else if ("text" in content) this._out = content.text!; 258 | else if ("caption" in content) this._out = content.caption!; 259 | else if ("singleSelectReply" in content) this._out = content.title!; 260 | else if ("selectedDisplayText" in content) this._out = content.selectedDisplayText!; 261 | else if ("contentText" in content) this._out = content.contentText + "\n\n" + content.footerText; 262 | else this._out = undefined; 263 | } else { 264 | this._out = undefined; 265 | } 266 | return this as unknown as Context; 267 | } 268 | 269 | /** 270 | * The sender id of quoted message. 271 | * 272 | * ```js 273 | * ctx.quotedUserId().out; 274 | * ``` 275 | */ 276 | quotedUserId() { 277 | const content = this.msgContent().out; 278 | if (typeof content !== "string" && "contextInfo" in content) { 279 | this._out = content.contextInfo?.participant!; 280 | } else { 281 | this._out = undefined; 282 | } 283 | return this as unknown as Context; 284 | } 285 | 286 | /** 287 | * The chat id of quoted message. The id can be different from the current chat, for example when user replies privately to the bot message in another group. 288 | * 289 | * ```js 290 | * ctx.quotedChatId().out; 291 | * ``` 292 | */ 293 | quotedChatId() { 294 | const content = this.msgContent().out; 295 | if (typeof content !== "string" && "contextInfo" in content) { 296 | this._out = content.contextInfo?.remoteJid!; 297 | } else { 298 | this._out = undefined; 299 | } 300 | return this as unknown as Context; 301 | } 302 | 303 | /** 304 | * Gets an array containing the id of mentioned users. Undefined if message isn't mentioning anyone. 305 | */ 306 | mentions() { 307 | const content = this.msgContent().out; 308 | if (typeof content !== "string" && "contextInfo" in content) { 309 | const mentions = content.contextInfo?.mentionedJid || []; 310 | if (mentions.length) this._out = mentions; 311 | else this._out = undefined; 312 | } else { 313 | this._out = undefined; 314 | } 315 | return this as unknown as Context; 316 | } 317 | 318 | /** 319 | * Reply to this message (and quote the message). 320 | * 321 | * ```js 322 | * ctx.reply(...); 323 | * ctx.reply(...).out; //for obtaining the message update 324 | * (new MessageContext(await ctx.reply(...).out)).reply(...); // replying to someone's message and then reply to the sent message itself, hehe 325 | * ``` 326 | */ 327 | reply(content: Baileys.AnyMessageContent, options: Baileys.MiscMessageGenerationOptions = {}) { 328 | const msgContent = this.msgContent().out; 329 | this._out = bot.sendMessage(this.chatId().out, content, { 330 | quoted: this.update, 331 | ephemeralExpiration: typeof msgContent !== "string" && "contextInfo" in msgContent ? msgContent.contextInfo?.expiration! : undefined, 332 | ...options, 333 | }); 334 | return this as unknown as Context>; 335 | } 336 | 337 | /** 338 | * React to this message. Just one emoji is allowed. 339 | * 340 | * ```js 341 | * ctx.react('❤'); 342 | * ``` 343 | */ 344 | react(emoji: string) { 345 | const msg = Baileys.proto.Message.fromObject({ 346 | reactionMessage: { 347 | key: { 348 | fromMe: this.update.key.fromMe || false, 349 | id: this.update.key.id, 350 | remoteJid: this.chatId().out, 351 | participant: this.userId().out, 352 | }, 353 | text: emoji, 354 | senderTimestampMs: Date.now().toString(), 355 | }, 356 | }); 357 | this._out = bot.relayMessage(this.chatId().out, msg, { 358 | messageId: Baileys.generateMessageID(), 359 | }); 360 | return this as unknown as Context; 361 | } 362 | 363 | /** 364 | * Wait until the user sends another message in the same chat. Returns a new `Context` object from the new message. Returns the string `"timeout"` after 15 minutes without response. 365 | * 366 | * @see - https://npmjs.com/promptees 367 | * 368 | * ```js 369 | * await ctx.waitInput().out; 370 | * ``` 371 | */ 372 | waitInput(opts?: PrompteesOpts, OnTimeout>) { 373 | this._out = promptees.waitForResponse(this.userId().out + this.chatId().out, opts); 374 | return this as unknown as Context>>; 375 | } 376 | 377 | /** 378 | * Returns the argument of the command if the message begins with one of prefixes (`process.env.PREFIX`), replying or mentioning the bot. 379 | * 380 | * Example: `/echo This is the argument` 381 | * 382 | * ```js 383 | * ctx.arguments().out; 384 | * ``` 385 | */ 386 | arguments() { 387 | const text = this.text().out?.trimStart(); 388 | if (text) { 389 | const idx = PREFIXES.findIndex((p) => text.startsWith(p)); 390 | if (idx !== -1) { 391 | this._out = text.replace(new RegExp(`^${escapeRegExp(PREFIXES[idx])}\\s*\\S*\\s*`), ""); 392 | } else if (text.startsWith("@" + process.env.BOT_NUMBER)) { 393 | this._out = text.replace(new RegExp(`^@${process.env.BOT_NUMBER}\\s*\\S*\\s*`), ""); 394 | } else if (this.quotedUserId().out === process.env.BOT_NUMBER + "@s.whatsapp.net" || !this.isGroupChat().out) { 395 | this._out = text.replace(/^\S*\s*/, ""); 396 | } else { 397 | this._out = undefined; 398 | } 399 | } else { 400 | this._out = undefined; 401 | } 402 | return this as unknown as Context; 403 | } 404 | 405 | /** 406 | * Returns the name of current command if the message begins with one of prefixes (`process.env.PREFIX`), replying or mentioning the bot. 407 | * 408 | * Example: `/echo This is the argument` 409 | * 410 | * The command name will be: `"echo"` 411 | * 412 | * ```js 413 | * ctx.command().out; 414 | * ``` 415 | */ 416 | command() { 417 | const text = this.text().out?.trimStart(); 418 | if (text) { 419 | const idx = PREFIXES.findIndex((p) => text.startsWith(p)); 420 | if (idx !== -1) { 421 | this._out = new RegExp(`^${escapeRegExp(PREFIXES[idx])}\\s*(\\S+)\\s*`).exec(text)?.[1]?.toLowerCase(); 422 | } else if (text.startsWith("@" + process.env.BOT_NUMBER)) { 423 | this._out = text.slice(`@${process.env.BOT_NUMBER}`.length).trimStart().split(/\s+/, 1)[0].toLowerCase() || undefined; 424 | } else if (this.quotedUserId().out === process.env.BOT_NUMBER + "@s.whatsapp.net" || !this.isGroupChat().out) { 425 | this._out = text.split(/\s+/, 1)[0].toLowerCase(); 426 | } else { 427 | this._out = undefined; 428 | } 429 | } else { 430 | this._out = undefined; 431 | } 432 | return this as unknown as Context; 433 | } 434 | 435 | /** 436 | * Returns `true` if the current chat is a group chat. 437 | * 438 | * ```js 439 | * ctx.isGroupChat().out; 440 | * ``` 441 | */ 442 | isGroupChat() { 443 | this._out = this.chatId().out.endsWith("@g.us"); 444 | return this as unknown as Context; 445 | } 446 | 447 | /** 448 | * Resolves to `true` if the user who sent the message is currently the admin of the current group chat. 449 | * 450 | * ```js 451 | * ctx.isGroupChatAdmin().out; 452 | * ``` 453 | */ 454 | isGroupChatAdmin() { 455 | this._out = (async () => { 456 | if (!this.isGroupChat().out) return false; 457 | const sender = this.userId().out; 458 | const participants = (await bot.groupMetadata(this.chatId().out)).participants; 459 | const participant = participants.find((v) => v.id === sender); 460 | return Boolean(participant?.admin); 461 | })(); 462 | return this as unknown as Context>; 463 | } 464 | 465 | /** 466 | * Returns true if the user is the owner of bot. 467 | * 468 | * ```js 469 | * ctx.isBotOwner().out; 470 | * ``` 471 | */ 472 | isBotOwner() { 473 | this._out = this.userId().out === process.env.OWNER_NUMBER + "@s.whatsapp.net"; 474 | return this as unknown as Context; 475 | } 476 | 477 | /** 478 | * Returns true if the user is in premium subscription. 479 | * 480 | * ```js 481 | * ctx.isPremiumUser().out; 482 | * ``` 483 | */ 484 | isPremiumUser() { 485 | this._out = (this.userData().out.premiumUntil || 0) > Date.now(); 486 | return this as unknown as Context; 487 | } 488 | }; 489 | } 490 | export type MessageContext = ReturnType["prototype"]; 491 | 492 | export function createGroupParticipantsUpdateContext(LOCALDB: Types.LOCALDB, bot: ReturnType) { 493 | return class Context { 494 | private _out: any = undefined; 495 | 496 | constructor( 497 | /** 498 | * Group participants update content. 499 | */ 500 | public update: { 501 | id: string; 502 | participants: string[]; 503 | action: Baileys.ParticipantAction; 504 | } 505 | ) {} 506 | 507 | get out(): O { 508 | return this._out; 509 | } 510 | 511 | /** 512 | * It basically a method to modify the `context.out` value. 513 | * 514 | * With it you can create a different design pattern, e.g: 515 | * ```js 516 | * ctx.text().and(o => o.toUpperCase()).and((o, ctx) => ctx.reply({ 517 | * text: `You said "${o}" in upper case.` 518 | * })); 519 | * ``` 520 | */ 521 | and<_O>(fn: (out: O, thisContext: this) => _O) { 522 | this._out = fn(this._out, this); 523 | return this as unknown as Context<_O>; 524 | } 525 | 526 | /** 527 | * The chat id. 528 | * 529 | * ```js 530 | * ctx.chatId().out; 531 | * ``` 532 | */ 533 | chatId() { 534 | this._out = this.update.id!; 535 | return this as unknown as Context; 536 | } 537 | 538 | /** 539 | * Data from the bot system. Can be accessed from any chat. 540 | * 541 | * ```js 542 | * ctx.systemData().out; 543 | * ``` 544 | */ 545 | systemData() { 546 | this._out = LOCALDB.system; 547 | return this as unknown as Context; 548 | } 549 | 550 | /** 551 | * Chat data. 552 | * 553 | * ```js 554 | * ctx.chatData().out; 555 | * ``` 556 | */ 557 | chatData() { 558 | this._out = LOCALDB[this.chatId().out]; 559 | return this as unknown as Context; 560 | } 561 | 562 | /** 563 | * The data from each user included in the update. 564 | */ 565 | userData() { 566 | const _userData: { [k: string]: any } = {}; 567 | for (const id in LOCALDB) { 568 | if (LOCALDB[id].type !== "user") continue; 569 | if (this.update.participants.includes(id)) _userData[id] = LOCALDB[id]; 570 | } 571 | this._out = _userData; 572 | return this as unknown as Context<{ [userId: string]: Types.USERDB }>; 573 | } 574 | 575 | /** 576 | * Language used in the chat (`"id"` or `"en"`). Defaults to `"id"`. 577 | * 578 | * ```js 579 | * ctx.language().out; 580 | * ``` 581 | */ 582 | language() { 583 | this._out = this.chatData().out.language || "id"; 584 | return this as unknown as Context<"id" | "en">; 585 | } 586 | 587 | /** 588 | * Respond to group participants update. 589 | * 590 | * ```js 591 | * ctx.reply(...); 592 | * ctx.reply(...).out; //for obtaining the message update 593 | * ``` 594 | */ 595 | reply(content: Baileys.AnyMessageContent, options: Baileys.MiscMessageGenerationOptions = {}) { 596 | this._out = bot.sendMessage(this.chatId().out, content, { 597 | ephemeralExpiration: 86400 * 7, 598 | ...options, 599 | }); 600 | return this as unknown as Context>; 601 | } 602 | }; 603 | } 604 | export type GroupParticipantsUpdateContext = ReturnType["prototype"]; 605 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | // I expanded these exported members so you can easily find them. 2 | 3 | export { requestArguments } from "./requestArguments"; 4 | export { requestMessage } from "./requestMessage"; 5 | export { createMessageContext, createGroupParticipantsUpdateContext, MessageContext, GroupParticipantsUpdateContext } from "./context"; 6 | export { command, basicTexts } from "./commands"; 7 | export { saveFile, randomFileName } from "./saveFile"; 8 | -------------------------------------------------------------------------------- /src/utils/requestArguments.ts: -------------------------------------------------------------------------------- 1 | import { MessageContext, basicTexts } from "./"; 2 | 3 | type argType = [ 4 | string, 5 | ("string" | RegExp | ((x: string) => boolean))?, 6 | { 7 | required?: boolean; 8 | onMissing?: string | ((context: MessageContext) => string); 9 | onWrong?: string | ((context: MessageContext) => string); 10 | }? 11 | ]; 12 | type argsShape = { 13 | arguments: argType[]; 14 | separator?: string | RegExp | ((x: string) => string[]); 15 | onCancel?: string | ((context: MessageContext | "timeout") => string); 16 | customInput?: string; 17 | }; 18 | 19 | const TEXTS = { 20 | id: { 21 | ARG_MISSING: (name: string) => `Silahkan kirim ${name}...\n\nKetik *batal* untuk membatalkan.`, 22 | ARG_WRONG: (name: string) => `Data yang kamu masukkan tidak valid.\n\nSilahkan kirim ulang ${name}...\n\nKetik *batal* untuk membatalkan.`, 23 | CANCEL: () => "Batal", 24 | CANCELED: () => basicTexts.id.CANCELED(), 25 | }, 26 | en: { 27 | ARG_MISSING: (name: string) => `Please enter the ${name}...\n\nType *cancel* to cancel`, 28 | ARG_WRONG: (name: string) => `The data you've sent is not valid.\n\nPlease reenter the ${name}...\n\nType *cancel* to cancel`, 29 | CANCEL: () => "Cancel", 30 | CANCELED: () => basicTexts.en.CANCELED(), 31 | }, 32 | }; 33 | 34 | /** 35 | * Use it like this: 36 | * ```ts 37 | * const [name, age, email] = await requestArguments(context, { 38 | * separator: /\s+/g, 39 | * arguments: [ 40 | * ['name'], 41 | * ['age', 'number'] 42 | * ['email', /[a-z\d]+@[a-z]+\.[a-z]+/i, { 43 | * required: false, 44 | * onWrong: context => `${context.text().out} is not a valid email address` 45 | * }] 46 | * ] 47 | * }); 48 | * if (name === null) return; // canceled 49 | * ``` 50 | * 51 | * `separator` is default to whitespace `/\s+/g`. 52 | * 53 | * The `arguments` shape consists of an array of the following: 54 | * ```ts 55 | * [name, validator, { 56 | * required?: boolean, 57 | * onWrong?: string | (context => string), 58 | * onMissing?: string | (context => string) 59 | * }?] 60 | * ``` 61 | */ 62 | export async function requestArguments(context: MessageContext, shape: argsShape): Promise<[string | null, ...string[]]> { 63 | const lang = context.language().out; 64 | const text = shape.customInput ?? context.arguments().out; 65 | const args: string[] = text ? (typeof shape.separator === "function" ? shape.separator(text) : text.split(shape.separator ?? /\s+/g)) : []; 66 | function isCanceled(response: MessageContext | "timeout"): response is "timeout" { 67 | if (response === "timeout") return true; 68 | if (new RegExp(`^${TEXTS[lang].CANCEL()}$`, "i").test(response.text().out || "")) return true; 69 | return false; 70 | } 71 | 72 | for (const [_idx, argShape] of Object.entries(shape.arguments)) { 73 | const idx = +_idx; 74 | 75 | while (true) { 76 | if (typeof args[idx] === "undefined") { 77 | const response = await context 78 | .reply({ 79 | text: 80 | typeof argShape[2]?.onMissing === "string" 81 | ? argShape[2].onMissing 82 | : typeof argShape[2]?.onMissing === "function" 83 | ? argShape[2].onMissing(context) 84 | : TEXTS[lang].ARG_MISSING(argShape[0]), 85 | }) 86 | .waitInput().out; 87 | if (isCanceled(response)) { 88 | context.reply({ text: typeof shape.onCancel === "function" ? shape.onCancel(response) : TEXTS[lang].CANCELED() }); 89 | return [null]; 90 | } 91 | context = response; 92 | const text = context.text().out; 93 | if (text) args[idx] = text; 94 | continue; 95 | } else { 96 | if (typeof argShape[1] === "undefined" || argShape[1] === "string") { 97 | break; 98 | } else if (argShape[1] instanceof RegExp && argShape[1].test(args[idx])) { 99 | break; 100 | } else if (typeof argShape[1] === "function" && argShape[1](args[idx])) { 101 | break; 102 | } else { 103 | const response = await context 104 | .reply({ 105 | text: 106 | typeof argShape[2]?.onWrong === "string" 107 | ? argShape[2].onWrong 108 | : typeof argShape[2]?.onWrong === "function" 109 | ? argShape[2].onWrong(context) 110 | : TEXTS[lang].ARG_WRONG(argShape[0]) 111 | }) 112 | .waitInput().out; 113 | if (isCanceled(response)) { 114 | context.reply({ text: typeof shape.onCancel === "function" ? shape.onCancel(response) : TEXTS[lang].CANCELED() }); 115 | return [null]; 116 | } 117 | context = response; 118 | const text = context.text().out; 119 | if (text) args[idx] = text; 120 | continue; 121 | } 122 | } 123 | } 124 | } 125 | 126 | return [...args] as [string | null, ...string[]]; 127 | } 128 | -------------------------------------------------------------------------------- /src/utils/requestMessage.ts: -------------------------------------------------------------------------------- 1 | import { MessageContext, basicTexts } from "./"; 2 | import * as Baileys from "@whiskeysockets/baileys"; 3 | 4 | type msgType = [ 5 | string, 6 | (Baileys.MessageType | ((x: MessageContext) => boolean))?, 7 | { 8 | onWrong?: string | ((context: MessageContext) => string); 9 | onMissing?: string | ((context: MessageContext) => string); 10 | }? 11 | ]; 12 | type msgShape = { 13 | messages: msgType[]; 14 | onCancel?: string | ((context: MessageContext | "timeout") => string); 15 | customInput?: MessageContext; 16 | }; 17 | 18 | const TEXTS = { 19 | id: { 20 | MSG_MISSING: (name: string) => `Silahkan kirim ${name}...\n\nKetik *batal* untuk membatalkan.`, 21 | MSG_WRONG: (name: string) => `Pesan bukan ${name}.\n\nSilahkan ulangi...\n\nKetik *batal* untuk membatalkan.`, 22 | CANCEL: () => "Batal", 23 | CANCELED: () => basicTexts.id.CANCELED(), 24 | }, 25 | en: { 26 | MSG_MISSING: (name: string) => `Please send a(n) ${name}...\n\nType *cancel* to cancel`, 27 | MSG_WRONG: (name: string) => `The message is not a(n) ${name}.\n\nPlease resend...\n\nType *cancel* to cancel`, 28 | CANCEL: () => "Cancel", 29 | CANCELED: () => basicTexts.en.CANCELED(), 30 | }, 31 | }; 32 | 33 | /** 34 | * Use it like this: 35 | * ```ts 36 | * const [imageMessageContext] = await requestMessage(context, { 37 | * messages: [ 38 | * ['image', 'imageMessage'], 39 | * ] 40 | * }); 41 | * if (imageMessageContext === null) return; // canceled 42 | * ``` 43 | */ 44 | export async function requestMessage(context: MessageContext, shape: msgShape): Promise<[MessageContext | null, ...MessageContext[]]> { 45 | const lang = context.language().out; 46 | function isCanceled(response: MessageContext | "timeout"): response is "timeout" { 47 | if (response === "timeout") return true; 48 | if (new RegExp(`^${TEXTS[lang].CANCEL()}$`, "i").test(response.text().out || "")) return true; 49 | return false; 50 | } 51 | function getCustomInput() { 52 | if (!shape.customInput) return; 53 | const customInput = shape.customInput; 54 | delete shape.customInput; 55 | return customInput; 56 | } 57 | const messages: MessageContext[] = []; 58 | for (const [_idx, msgShape] of Object.entries(shape.messages)) { 59 | let msg = { 60 | text: 61 | typeof msgShape[2]?.onMissing === "string" 62 | ? msgShape[2].onMissing 63 | : typeof msgShape[2]?.onMissing === "function" 64 | ? msgShape[2].onMissing(context) 65 | : TEXTS[lang].MSG_MISSING(msgShape[0]) 66 | }; 67 | while (true) { 68 | let isCustomInput: any; 69 | const response = (isCustomInput = getCustomInput()) || (await context.reply(msg).waitInput().out); 70 | if (isCanceled(response)) { 71 | context.reply({ text: typeof shape.onCancel === "function" ? shape.onCancel(response) : TEXTS[lang].CANCELED() }); 72 | return [null]; 73 | } 74 | if (typeof msgShape[1] === "function" ? msgShape[1](response) : response.msgType().out === msgShape[1]) { 75 | messages.push(response); 76 | break; 77 | } else { 78 | if (!isCustomInput) { 79 | msg = { 80 | text: 81 | typeof msgShape[2]?.onWrong === "string" 82 | ? msgShape[2].onWrong 83 | : typeof msgShape[2]?.onWrong === "function" 84 | ? msgShape[2].onWrong(response) 85 | : TEXTS[lang].MSG_WRONG(msgShape[0]) 86 | }; 87 | } 88 | continue; 89 | } 90 | } 91 | } 92 | 93 | return [...messages] as [MessageContext | null, ...MessageContext[]]; 94 | } 95 | -------------------------------------------------------------------------------- /src/utils/saveFile.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | 3 | export async function saveFile(data: string | Buffer | import("stream").Transform): Promise { 4 | const random = randomFileName(); 5 | if (typeof data === "string" || data instanceof Buffer) { 6 | fs.writeFileSync(random, data); 7 | return random; 8 | } else { 9 | const stream = fs.createWriteStream(random); 10 | data.pipe(stream); 11 | return await new Promise((resolve, reject) => { 12 | data.on("close", () => resolve(random)); 13 | data.on("error", reject); 14 | stream.on("error", reject); 15 | }); 16 | } 17 | } 18 | 19 | export function randomFileName() { 20 | return "./tmp/" + Date.now() + "-" + Math.random().toString(36).slice(2).toUpperCase(); 21 | } 22 | 23 | setInterval(() => { 24 | const now = Date.now(); 25 | for (const file of fs.readdirSync("./tmp/")) { 26 | const dateCreated = +file.split("-")[0]; 27 | if (now - dateCreated > 900000) { 28 | fs.unlinkSync("./tmp/" + file); 29 | } 30 | } 31 | }, 60000); 32 | -------------------------------------------------------------------------------- /src/utils/typings/kao.moji.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for kao.moji ^0.1.3 2 | // Definitions by: riozec https://github.com/riozec 3 | 4 | // not a complete one tho, just make it to fit in this project 5 | 6 | declare module "kao.moji" { 7 | class kao { 8 | static moji: { 9 | angry: () => string; 10 | mad: () => string; 11 | bear: () => string; 12 | beg: () => string; 13 | blush: () => string; 14 | bow: () => string; 15 | bunny: () => string; 16 | rabbit: () => string; 17 | bye: () => string; 18 | hi: () => string; 19 | hello: () => string; 20 | cat: () => string; 21 | confused: () => string; 22 | crying: () => string; 23 | cute: () => string; 24 | kawaii: () => string; 25 | dancing: () => string; 26 | depressed: () => string; 27 | determined: () => string; 28 | devil: () => string; 29 | disappointed: () => string; 30 | eating: () => string; 31 | drinking: () => string; 32 | evil: () => string; 33 | excited: () => string; 34 | fall: () => string; 35 | feminine: () => string; 36 | flower: () => string; 37 | funny: () => string; 38 | glasses: () => string; 39 | sunglasses: () => string; 40 | grin: () => string; 41 | gross: () => string; 42 | happy: () => string; 43 | heart: () => string; 44 | helpless: () => string; 45 | hide: () => string; 46 | hit: () => string; 47 | hug: () => string; 48 | hurry: () => string; 49 | kiss: () => string; 50 | laughing: () => string; 51 | lennyface: () => string; 52 | love: () => string; 53 | magic: () => string; 54 | middlefinger: () => string; 55 | monkey: () => string; 56 | music: () => string; 57 | nervous: () => string; 58 | peacesign: () => string; 59 | poop: () => string; 60 | roger: () => string; 61 | roll: () => string; 62 | running: () => string; 63 | sad: () => string; 64 | saliva: () => string; 65 | salute: () => string; 66 | scared: () => string; 67 | shake: () => string; 68 | sheep: () => string; 69 | shocked: () => string; 70 | shrug: () => string; 71 | shy: () => string; 72 | embarrassed: () => string; 73 | sleep: () => string; 74 | smiling: () => string; 75 | smug: () => string; 76 | sparkles: () => string; 77 | stars: () => string; 78 | spin: () => string; 79 | sulk: () => string; 80 | surprised: () => string; 81 | sweat: () => string; 82 | tableflip: () => string; 83 | thatsit: () => string; 84 | thumbsup: () => string; 85 | tired: () => string; 86 | trymybest: () => string; 87 | unicode: () => string; 88 | vomit: () => string; 89 | weird: () => string; 90 | wink: () => string; 91 | available: () => Array; 92 | }; 93 | } 94 | export = kao; 95 | } 96 | -------------------------------------------------------------------------------- /src/utils/typings/types.d.ts: -------------------------------------------------------------------------------- 1 | import type * as Baileys from "@whiskeysockets/baileys"; 2 | import type { MessageContext, GroupParticipantsUpdateContext } from ".."; 3 | 4 | export type LOCALDB = { [key: string]: GROUPDB | USERDB | SYSTEMDB }; 5 | 6 | export type GROUPDB = { 7 | type: "group"; 8 | language: "id" | "en"; 9 | adminonly?: boolean; 10 | muted?: boolean; 11 | [key: string]: any; 12 | }; 13 | 14 | export type USERDB = { 15 | type: "user"; 16 | language: "id" | "en"; 17 | premiumUntil?: number; 18 | lastPremCmd?: number; 19 | stats: { 20 | joined: number; 21 | lastSeen: number; 22 | hits: { 23 | [command: string]: number; 24 | }; 25 | lastHit?: number; 26 | }; 27 | [key: string]: any; 28 | }; 29 | 30 | export type SYSTEMDB = { 31 | type: "system"; 32 | backupTime: number; 33 | banneds?: string[]; 34 | }; 35 | 36 | export type commandConfigurations = { 37 | /** 38 | * Function that is executed immediately and only once when the command is loaded. Useful for preparing the database because there is access to the database. 39 | */ 40 | onStart?: (DB: LOCALDB, listOfCommands: commandConfigurations[]) => any; 41 | /** 42 | * Executed when connected to WhatsApp. Indicated when console is showing `"Connected to WhatsApp"`. 43 | */ 44 | onConnected?: (bot: Baileys.WASocket) => any; 45 | /** 46 | * Executed when disconnected from WhatsApp. Indicated when console is showing `"Disconnected from WhatsApp"`. 47 | */ 48 | onDisconnected?: (bot: Baileys.WASocket) => any; 49 | /** 50 | * Function that is executed every time a message is received. 51 | */ 52 | onMessage?: (context: Omit, bot: Baileys.WASocket) => any; 53 | /** 54 | * Function that is executed when the user gives this command. 55 | */ 56 | onCommand: (context: MessageContext, bot: Baileys.WASocket) => any; 57 | /** 58 | * Function that is executed when an update occurs to group members, such as add, kick, promote or demote. 59 | */ 60 | onGroupParticipantsUpdate?: (context: GroupParticipantsUpdateContext, bot: Baileys.WASocket) => any; 61 | /** 62 | * Object contains the configuration and information of this command. 63 | */ 64 | metadata: { 65 | /** 66 | * Necessary for reloading. 67 | */ 68 | __filename: string; 69 | /** 70 | * Contains the names of the commands that the user can type to run this command. Case-insensitive. 71 | */ 72 | command: string[]; 73 | /** 74 | * Specifies where and who can execute this command. 75 | * 76 | * Options: 77 | * - `"all"` - Anyone in anywhere. 78 | * - `"all-owner"` - Anywhere but only the owner. 79 | * - `"private-all"` - Anyone but only in private chat. 80 | * - `"private-owner"` - Only owner and only in private chat. 81 | * - `"private-admin"` - If in group chat then only admin else anyone. 82 | * - `"group-all"` - Anyone but only in group chat. 83 | * - `"group-owner"` - Only owner and only in group chat. 84 | * - `"group-admin"` - Only the admin of group chat. 85 | */ 86 | permission: "all" | "all-owner" | `private-${"all" | "owner" | "admin"}` | `group-${"all" | "owner" | "admin"}`; 87 | /** 88 | * Specifies if this command is only for premium users. 89 | */ 90 | premium?: true; 91 | /** 92 | * Command category. 93 | */ 94 | category: "mediatools" | "othertools" | "games" | "randomfun" | "grouptools" | "botsettings" | "owner"; 95 | /** 96 | * Command description. 97 | */ 98 | locale: { 99 | /** 100 | * Long description. Shown when first time using the command. 101 | */ 102 | description: { 103 | /** 104 | * Bahasa Indonesia 105 | */ 106 | id: string; 107 | /** 108 | * English 109 | */ 110 | en: string; 111 | }; 112 | /** 113 | * Short name. Shown in menu list. 114 | */ 115 | name?: { 116 | /** 117 | * Bahasa Indonesia 118 | */ 119 | id?: string; 120 | /** 121 | * English 122 | */ 123 | en?: string; 124 | }; 125 | }; 126 | }; 127 | }; 128 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2019"], 4 | "target": "es5", 5 | "module": "commonjs", 6 | "outDir": "./js/", 7 | "rootDir": "./src/", 8 | "strict": true, 9 | "esModuleInterop": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@adiwajshing/keyed-db@^0.2.4": 6 | version "0.2.4" 7 | resolved "https://registry.yarnpkg.com/@adiwajshing/keyed-db/-/keyed-db-0.2.4.tgz#2a09e88fce20b2672deb60a7750c5fe3ab0dfd99" 8 | integrity sha512-yprSnAtj80/VKuDqRcFFLDYltoNV8tChNwFfIgcf6PGD4sjzWIBgs08pRuTqGH5mk5wgL6PBRSsMCZqtZwzFEw== 9 | 10 | "@eshaz/web-worker@1.2.1": 11 | version "1.2.1" 12 | resolved "https://registry.yarnpkg.com/@eshaz/web-worker/-/web-worker-1.2.1.tgz#834385830529582589e9790350beb24bf3ac8018" 13 | integrity sha512-v5AKAVtM0toVD2rDCGjzhySWlXG/sG5HVialdzrxFKTAnFZNCjQelX0n2tPK0tE86jf4s3hpWlpRtOh8OObktg== 14 | 15 | "@hapi/boom@^9.1.3": 16 | version "9.1.4" 17 | resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" 18 | integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== 19 | dependencies: 20 | "@hapi/hoek" "9.x.x" 21 | 22 | "@hapi/hoek@9.x.x": 23 | version "9.3.0" 24 | resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" 25 | integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== 26 | 27 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": 28 | version "1.1.2" 29 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" 30 | integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== 31 | 32 | "@protobufjs/base64@^1.1.2": 33 | version "1.1.2" 34 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" 35 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== 36 | 37 | "@protobufjs/codegen@^2.0.4": 38 | version "2.0.4" 39 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" 40 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== 41 | 42 | "@protobufjs/eventemitter@^1.1.0": 43 | version "1.1.0" 44 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" 45 | integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== 46 | 47 | "@protobufjs/fetch@^1.1.0": 48 | version "1.1.0" 49 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" 50 | integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== 51 | dependencies: 52 | "@protobufjs/aspromise" "^1.1.1" 53 | "@protobufjs/inquire" "^1.1.0" 54 | 55 | "@protobufjs/float@^1.0.2": 56 | version "1.0.2" 57 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" 58 | integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== 59 | 60 | "@protobufjs/inquire@^1.1.0": 61 | version "1.1.0" 62 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" 63 | integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== 64 | 65 | "@protobufjs/path@^1.1.2": 66 | version "1.1.2" 67 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" 68 | integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== 69 | 70 | "@protobufjs/pool@^1.1.0": 71 | version "1.1.0" 72 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" 73 | integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== 74 | 75 | "@protobufjs/utf8@^1.1.0": 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" 78 | integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== 79 | 80 | "@thi.ng/bitstream@^2.2.12": 81 | version "2.2.22" 82 | resolved "https://registry.yarnpkg.com/@thi.ng/bitstream/-/bitstream-2.2.22.tgz#e0442480f13bf3741637715006acedfe215c4f10" 83 | integrity sha512-phKOd7gfDcZfiu5qHr2etak9D1jczY8tW5vojaiAwEwCA09ZkYTuiTlrBJdTGnqjc3//1Kb6O3EQo7two5GlbQ== 84 | dependencies: 85 | "@thi.ng/errors" "^2.2.17" 86 | 87 | "@thi.ng/errors@^2.2.17": 88 | version "2.2.17" 89 | resolved "https://registry.yarnpkg.com/@thi.ng/errors/-/errors-2.2.17.tgz#0a4cc59e4a2a3c607db65052f71be62d805dff9a" 90 | integrity sha512-gL1KOHVQpSX9e5BDbKzxJcXj+tM2T6dJOxXTJz3nHNSXT6VdEsXCVeLKGUFBkLkA5QsH/nzmgQq7a5vS7cdGTA== 91 | 92 | "@tokenizer/token@^0.3.0": 93 | version "0.3.0" 94 | resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" 95 | integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== 96 | 97 | "@types/lodash@^4.14.182": 98 | version "4.14.196" 99 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.196.tgz#a7c3d6fc52d8d71328b764e28e080b4169ec7a95" 100 | integrity sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ== 101 | 102 | "@types/long@^4.0.0", "@types/long@^4.0.1": 103 | version "4.0.2" 104 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" 105 | integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== 106 | 107 | "@types/node@*", "@types/node@>=13.7.0": 108 | version "20.4.5" 109 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.5.tgz#9dc0a5cb1ccce4f7a731660935ab70b9c00a5d69" 110 | integrity sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg== 111 | 112 | "@types/node@^10.1.0": 113 | version "10.17.60" 114 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" 115 | integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== 116 | 117 | "@types/ws@^8.5.3": 118 | version "8.5.5" 119 | resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" 120 | integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg== 121 | dependencies: 122 | "@types/node" "*" 123 | 124 | "@wasm-audio-decoders/common@9.0.1": 125 | version "9.0.1" 126 | resolved "https://registry.yarnpkg.com/@wasm-audio-decoders/common/-/common-9.0.1.tgz#985f35d7f216da1b0f691132c3875793cc516022" 127 | integrity sha512-s4KxPsxhD6e+EtjmHPzTmKJSJTEXHGYlBzUGGLN/plV01x3RecybW73nIMtdLLXL8k/zQ8HYFcA6d9lqCJcDjQ== 128 | dependencies: 129 | "@eshaz/web-worker" "1.2.1" 130 | 131 | "@wasm-audio-decoders/flac@^0.1.12": 132 | version "0.1.12" 133 | resolved "https://registry.yarnpkg.com/@wasm-audio-decoders/flac/-/flac-0.1.12.tgz#5e35512e8c0d4079c0926341cc37ad3a88e94e83" 134 | integrity sha512-k4PELVzBu4p4j0YmA2K8VF0GxoWWYqkqa3C29nOikOss1SZLnX2CczfiJCklL9AA/9tlZrvBJz3/nDxXDqR35A== 135 | dependencies: 136 | "@wasm-audio-decoders/common" "9.0.1" 137 | codec-parser "2.4.2" 138 | 139 | "@wasm-audio-decoders/ogg-vorbis@^0.1.7": 140 | version "0.1.7" 141 | resolved "https://registry.yarnpkg.com/@wasm-audio-decoders/ogg-vorbis/-/ogg-vorbis-0.1.7.tgz#a79e039db0cb56f6ff44f7d694fba71506186dee" 142 | integrity sha512-MftO1fJeBRUuDAZfa9Yp/gf+786PWt/JyiFXj+Ntn0mxlKDyPr3pTXx8PvhmO2/s/nabHDhAhiZi4aXzaBEUpg== 143 | dependencies: 144 | "@wasm-audio-decoders/common" "9.0.1" 145 | codec-parser "2.4.2" 146 | 147 | "@whiskeysockets/baileys@^6.4.0": 148 | version "6.4.0" 149 | resolved "https://registry.yarnpkg.com/@whiskeysockets/baileys/-/baileys-6.4.0.tgz#7effbec2d0d7529ce4ee338024fb65fb28b87089" 150 | integrity sha512-SdCoVpILWX7z2WEe40BwNjMqpI6Guf0gbG80NNfsJBcRcDQIS9pkmazIhTvNKytWZm7BFZR+QNKcBUxdPqdLsQ== 151 | dependencies: 152 | "@adiwajshing/keyed-db" "^0.2.4" 153 | "@hapi/boom" "^9.1.3" 154 | audio-decode "^2.1.3" 155 | axios "^1.3.3" 156 | cache-manager "^5.2.2" 157 | futoin-hkdf "^1.5.1" 158 | libphonenumber-js "^1.10.20" 159 | libsignal "github:adiwajshing/libsignal-node" 160 | music-metadata "^7.12.3" 161 | node-cache "^5.1.2" 162 | pino "^7.0.0" 163 | protobufjs "^6.11.3" 164 | uuid "^9.0.0" 165 | ws "^8.13.0" 166 | 167 | abort-controller@^3.0.0: 168 | version "3.0.0" 169 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" 170 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== 171 | dependencies: 172 | event-target-shim "^5.0.0" 173 | 174 | asynckit@^0.4.0: 175 | version "0.4.0" 176 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 177 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 178 | 179 | atomic-sleep@^1.0.0: 180 | version "1.0.0" 181 | resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" 182 | integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== 183 | 184 | audio-buffer@^5.0.0: 185 | version "5.0.0" 186 | resolved "https://registry.yarnpkg.com/audio-buffer/-/audio-buffer-5.0.0.tgz#92129984ebdbb8ad1c4ec6fd81525a0e0a29e99e" 187 | integrity sha512-gsDyj1wwUp8u7NBB+eW6yhLb9ICf+0eBmDX8NGaAS00w8/fLqFdxUlL5Ge/U8kB64DlQhdonxYC59dXy1J7H/w== 188 | 189 | audio-decode@^2.1.3: 190 | version "2.1.4" 191 | resolved "https://registry.yarnpkg.com/audio-decode/-/audio-decode-2.1.4.tgz#86e7eb2ce62661037949ce19989e5464ac1e7a7d" 192 | integrity sha512-i1KTHqBXcU3vVCPXfp4zZCaXsLYyO4CNbt6JhvV1so/PpOpBnIXNFgHEq0bWNsroUV/J6i2/Yck0QhoaCzGh+A== 193 | dependencies: 194 | "@wasm-audio-decoders/flac" "^0.1.12" 195 | "@wasm-audio-decoders/ogg-vorbis" "^0.1.7" 196 | audio-buffer "^5.0.0" 197 | audio-type "^2.2.0" 198 | mpg123-decoder "^0.4.8" 199 | node-wav "^0.0.2" 200 | ogg-opus-decoder "^1.6.4" 201 | qoa-format "^1.0.0" 202 | 203 | audio-type@^2.2.0: 204 | version "2.2.1" 205 | resolved "https://registry.yarnpkg.com/audio-type/-/audio-type-2.2.1.tgz#5ceae0b0b3cf3b1e117edabb9e5332eaf3bd4f25" 206 | integrity sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA== 207 | 208 | axios@^1.3.3: 209 | version "1.4.0" 210 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" 211 | integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== 212 | dependencies: 213 | follow-redirects "^1.15.0" 214 | form-data "^4.0.0" 215 | proxy-from-env "^1.1.0" 216 | 217 | boolbase@^1.0.0: 218 | version "1.0.0" 219 | resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 220 | integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== 221 | 222 | cache-manager@^5.2.2: 223 | version "5.2.3" 224 | resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-5.2.3.tgz#b6a8b4469c57fdfdae1deed7f81ea9e057c7eade" 225 | integrity sha512-9OErI8fksFkxAMJ8Mco0aiZSdphyd90HcKiOMJQncSlU1yq/9lHHxrT8PDayxrmr9IIIZPOAEfXuGSD7g29uog== 226 | dependencies: 227 | lodash.clonedeep "^4.5.0" 228 | lru-cache "^9.1.2" 229 | 230 | cheerio-select@^2.1.0: 231 | version "2.1.0" 232 | resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" 233 | integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== 234 | dependencies: 235 | boolbase "^1.0.0" 236 | css-select "^5.1.0" 237 | css-what "^6.1.0" 238 | domelementtype "^2.3.0" 239 | domhandler "^5.0.3" 240 | domutils "^3.0.1" 241 | 242 | cheerio@1.0.0-rc.11: 243 | version "1.0.0-rc.11" 244 | resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.11.tgz#1be84be1a126958366bcc57a11648cd9b30a60c2" 245 | integrity sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag== 246 | dependencies: 247 | cheerio-select "^2.1.0" 248 | dom-serializer "^2.0.0" 249 | domhandler "^5.0.3" 250 | domutils "^3.0.1" 251 | htmlparser2 "^8.0.1" 252 | parse5 "^7.0.0" 253 | parse5-htmlparser2-tree-adapter "^7.0.0" 254 | tslib "^2.4.0" 255 | 256 | clone@2.x: 257 | version "2.1.2" 258 | resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" 259 | integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== 260 | 261 | codec-parser@2.4.2: 262 | version "2.4.2" 263 | resolved "https://registry.yarnpkg.com/codec-parser/-/codec-parser-2.4.2.tgz#32a3271c9a79711cd61effeeea4b5b4d8b52efb8" 264 | integrity sha512-RN6gT8aGdDq4vx//Ln+MRDIKPXA3SpJKsCcQSZ4poF2bZCO4/G0C2Ko/MVNpbJ7Y9ewpzeuLlBx2zH8BpcM4ew== 265 | 266 | combined-stream@^1.0.8: 267 | version "1.0.8" 268 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 269 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 270 | dependencies: 271 | delayed-stream "~1.0.0" 272 | 273 | content-type@^1.0.5: 274 | version "1.0.5" 275 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" 276 | integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== 277 | 278 | cross-fetch@3.1.5: 279 | version "3.1.5" 280 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" 281 | integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== 282 | dependencies: 283 | node-fetch "2.6.7" 284 | 285 | css-select@^5.1.0: 286 | version "5.1.0" 287 | resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" 288 | integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== 289 | dependencies: 290 | boolbase "^1.0.0" 291 | css-what "^6.1.0" 292 | domhandler "^5.0.2" 293 | domutils "^3.0.1" 294 | nth-check "^2.0.1" 295 | 296 | css-what@^6.1.0: 297 | version "6.1.0" 298 | resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" 299 | integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== 300 | 301 | curve25519-js@^0.0.4: 302 | version "0.0.4" 303 | resolved "https://registry.yarnpkg.com/curve25519-js/-/curve25519-js-0.0.4.tgz#e6ad967e8cd284590d657bbfc90d8b50e49ba060" 304 | integrity sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w== 305 | 306 | debug@^4.3.4: 307 | version "4.3.4" 308 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 309 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 310 | dependencies: 311 | ms "2.1.2" 312 | 313 | delayed-stream@~1.0.0: 314 | version "1.0.0" 315 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 316 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 317 | 318 | dom-serializer@^2.0.0: 319 | version "2.0.0" 320 | resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" 321 | integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== 322 | dependencies: 323 | domelementtype "^2.3.0" 324 | domhandler "^5.0.2" 325 | entities "^4.2.0" 326 | 327 | domelementtype@^2.3.0: 328 | version "2.3.0" 329 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" 330 | integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== 331 | 332 | domhandler@^5.0.2, domhandler@^5.0.3: 333 | version "5.0.3" 334 | resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" 335 | integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== 336 | dependencies: 337 | domelementtype "^2.3.0" 338 | 339 | domutils@^3.0.1: 340 | version "3.1.0" 341 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" 342 | integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== 343 | dependencies: 344 | dom-serializer "^2.0.0" 345 | domelementtype "^2.3.0" 346 | domhandler "^5.0.3" 347 | 348 | dotenv@^16.0.0: 349 | version "16.3.1" 350 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" 351 | integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== 352 | 353 | duplexify@^4.1.2: 354 | version "4.1.2" 355 | resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" 356 | integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== 357 | dependencies: 358 | end-of-stream "^1.4.1" 359 | inherits "^2.0.3" 360 | readable-stream "^3.1.1" 361 | stream-shift "^1.0.0" 362 | 363 | end-of-stream@^1.4.1: 364 | version "1.4.4" 365 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 366 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 367 | dependencies: 368 | once "^1.4.0" 369 | 370 | entities@^4.2.0, entities@^4.4.0: 371 | version "4.5.0" 372 | resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" 373 | integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== 374 | 375 | event-target-shim@^5.0.0: 376 | version "5.0.1" 377 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 378 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 379 | 380 | fast-redact@^3.0.0: 381 | version "3.3.0" 382 | resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" 383 | integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== 384 | 385 | file-type@^16.5.4: 386 | version "16.5.4" 387 | resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" 388 | integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== 389 | dependencies: 390 | readable-web-to-node-stream "^3.0.0" 391 | strtok3 "^6.2.4" 392 | token-types "^4.1.1" 393 | 394 | follow-redirects@^1.15.0: 395 | version "1.15.2" 396 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" 397 | integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== 398 | 399 | form-data@^4.0.0: 400 | version "4.0.0" 401 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" 402 | integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== 403 | dependencies: 404 | asynckit "^0.4.0" 405 | combined-stream "^1.0.8" 406 | mime-types "^2.1.12" 407 | 408 | futoin-hkdf@^1.5.1: 409 | version "1.5.2" 410 | resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.5.2.tgz#d316623d29f45fe5e6f136f435eccd74096bf676" 411 | integrity sha512-Bnytx8kQJQoEAPGgTZw3kVPy8e/n9CDftPzc0okgaujmbdF1x7w8wg+u2xS0CML233HgruNk6VQW28CzuUFMKw== 412 | 413 | htmlparser2@^8.0.1: 414 | version "8.0.2" 415 | resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" 416 | integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== 417 | dependencies: 418 | domelementtype "^2.3.0" 419 | domhandler "^5.0.3" 420 | domutils "^3.0.1" 421 | entities "^4.4.0" 422 | 423 | ieee754@^1.2.1: 424 | version "1.2.1" 425 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 426 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 427 | 428 | inherits@^2.0.3: 429 | version "2.0.4" 430 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 431 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 432 | 433 | kao.moji@^0.1.3: 434 | version "0.1.3" 435 | resolved "https://registry.yarnpkg.com/kao.moji/-/kao.moji-0.1.3.tgz#bf58e963f7aa9e401f0b57dda84604553c061ea4" 436 | integrity sha512-j7Bp4vFFa4bcAbWWR59tGVVKKa7ilVxjzVHROsXn2y5jKdp0W1c77beJwAeSNLX/UUK51DfVdrD8eQOI1S473A== 437 | 438 | libphonenumber-js@^1.10.20: 439 | version "1.10.38" 440 | resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.38.tgz#edb9c1f13796ab17a8484180ef937184784628e9" 441 | integrity sha512-4NjVXVUmpZ9Zsqq6FXa2+MKI+KAI3tOqA0pxXgXGluhpj4ge5didmbWJpMBqGB3AVGv1SnEtKdGTbxjSEG1kCQ== 442 | 443 | "libsignal@github:adiwajshing/libsignal-node": 444 | version "2.0.1" 445 | resolved "https://codeload.github.com/adiwajshing/libsignal-node/tar.gz/11dbd962ea108187c79a7c46fe4d6f790e23da97" 446 | dependencies: 447 | curve25519-js "^0.0.4" 448 | protobufjs "6.8.8" 449 | 450 | link-preview-js@^2.1.13: 451 | version "2.1.19" 452 | resolved "https://registry.yarnpkg.com/link-preview-js/-/link-preview-js-2.1.19.tgz#22c3d9574942de672acb7ec53e98ad03e88fadcc" 453 | integrity sha512-ZXiacDuzpNLY/Xx7R3njpI07K2VWZqe/kANwc4RdRPTc+uAkBHW3hGHTeN7upnyrwV7hfwR79CjRXo0fvfGH5A== 454 | dependencies: 455 | abort-controller "^3.0.0" 456 | cheerio "1.0.0-rc.11" 457 | cross-fetch "3.1.5" 458 | url "0.11.0" 459 | 460 | lodash.clonedeep@^4.5.0: 461 | version "4.5.0" 462 | resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" 463 | integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== 464 | 465 | lodash@^4.17.21: 466 | version "4.17.21" 467 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 468 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 469 | 470 | long@^4.0.0: 471 | version "4.0.0" 472 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" 473 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== 474 | 475 | lru-cache@^9.1.2: 476 | version "9.1.2" 477 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835" 478 | integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ== 479 | 480 | media-typer@^1.1.0: 481 | version "1.1.0" 482 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" 483 | integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== 484 | 485 | mime-db@1.52.0: 486 | version "1.52.0" 487 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 488 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 489 | 490 | mime-types@^2.1.12: 491 | version "2.1.35" 492 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 493 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 494 | dependencies: 495 | mime-db "1.52.0" 496 | 497 | mpg123-decoder@^0.4.8: 498 | version "0.4.8" 499 | resolved "https://registry.yarnpkg.com/mpg123-decoder/-/mpg123-decoder-0.4.8.tgz#a2b5e77f491c7b7bf8b896e4764d71dcaec56e59" 500 | integrity sha512-HXs8vbPjiFM0NOZ45T3C5i7mpYGEYhjH37SnFA907lOb9c93DQL40cDjerxj65IMwAYyPFq1aUjtDOjyR7O0gQ== 501 | dependencies: 502 | "@wasm-audio-decoders/common" "9.0.1" 503 | 504 | ms@2.1.2: 505 | version "2.1.2" 506 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 507 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 508 | 509 | music-metadata@^7.12.3: 510 | version "7.13.4" 511 | resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-7.13.4.tgz#17d2d3d894fdee9899cc5b08110f790788758f7e" 512 | integrity sha512-eRRoEMhhYdth2Ws24FmkvIqrtkIBE9sqjHbrRNpkg2Iux3zc37PQKRv2/r/mTtELb7XlB1uWC2UcKKX7BzNMGA== 513 | dependencies: 514 | "@tokenizer/token" "^0.3.0" 515 | content-type "^1.0.5" 516 | debug "^4.3.4" 517 | file-type "^16.5.4" 518 | media-typer "^1.1.0" 519 | strtok3 "^6.3.0" 520 | token-types "^4.2.1" 521 | 522 | node-cache@^5.1.2: 523 | version "5.1.2" 524 | resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" 525 | integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== 526 | dependencies: 527 | clone "2.x" 528 | 529 | node-fetch@2.6.7: 530 | version "2.6.7" 531 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 532 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 533 | dependencies: 534 | whatwg-url "^5.0.0" 535 | 536 | node-wav@^0.0.2: 537 | version "0.0.2" 538 | resolved "https://registry.yarnpkg.com/node-wav/-/node-wav-0.0.2.tgz#89cb63cf8cd66ec8ab455f5ba4864e5fcb4605e8" 539 | integrity sha512-M6Rm/bbG6De/gKGxOpeOobx/dnGuP0dz40adqx38boqHhlWssBJZgLCPBNtb9NkrmnKYiV04xELq+R6PFOnoLA== 540 | 541 | nth-check@^2.0.1: 542 | version "2.1.1" 543 | resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" 544 | integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== 545 | dependencies: 546 | boolbase "^1.0.0" 547 | 548 | ogg-opus-decoder@^1.6.4: 549 | version "1.6.4" 550 | resolved "https://registry.yarnpkg.com/ogg-opus-decoder/-/ogg-opus-decoder-1.6.4.tgz#1f184c041994ca5e22da6ade05e2ccbaf8f22014" 551 | integrity sha512-MYqiJyCZWjIKKRJMXRVi69D3VGbaR/FNCNcINNI5Ec5ZUm5pLyDT6f0cYksU0Hb3ZHXXQIp62VJChYqwk60EMg== 552 | dependencies: 553 | "@wasm-audio-decoders/common" "9.0.1" 554 | codec-parser "2.4.2" 555 | opus-decoder "0.7.1" 556 | 557 | on-exit-leak-free@^0.2.0: 558 | version "0.2.0" 559 | resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" 560 | integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== 561 | 562 | once@^1.4.0: 563 | version "1.4.0" 564 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 565 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 566 | dependencies: 567 | wrappy "1" 568 | 569 | opus-decoder@0.7.1: 570 | version "0.7.1" 571 | resolved "https://registry.yarnpkg.com/opus-decoder/-/opus-decoder-0.7.1.tgz#48c50f1feb0b49fa078ade93f08caeff9142ffae" 572 | integrity sha512-AOFCMKLn7LJm8pOkksY5TsW/6+XmNyh1OQS9gxmdOGHLNYoOBrjSfc0nPNcmUMGEzOrTqZtPi8VJ/ABs2Hndvg== 573 | dependencies: 574 | "@wasm-audio-decoders/common" "9.0.1" 575 | 576 | parse5-htmlparser2-tree-adapter@^7.0.0: 577 | version "7.0.0" 578 | resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" 579 | integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== 580 | dependencies: 581 | domhandler "^5.0.2" 582 | parse5 "^7.0.0" 583 | 584 | parse5@^7.0.0: 585 | version "7.1.2" 586 | resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" 587 | integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== 588 | dependencies: 589 | entities "^4.4.0" 590 | 591 | peek-readable@^4.1.0: 592 | version "4.1.0" 593 | resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" 594 | integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== 595 | 596 | pino-abstract-transport@v0.5.0: 597 | version "0.5.0" 598 | resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" 599 | integrity sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ== 600 | dependencies: 601 | duplexify "^4.1.2" 602 | split2 "^4.0.0" 603 | 604 | pino-std-serializers@^4.0.0: 605 | version "4.0.0" 606 | resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1791ccd2539c091ae49ce9993205e2cd5dbba1e2" 607 | integrity sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q== 608 | 609 | pino@^7.0.0: 610 | version "7.11.0" 611 | resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6" 612 | integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg== 613 | dependencies: 614 | atomic-sleep "^1.0.0" 615 | fast-redact "^3.0.0" 616 | on-exit-leak-free "^0.2.0" 617 | pino-abstract-transport v0.5.0 618 | pino-std-serializers "^4.0.0" 619 | process-warning "^1.0.0" 620 | quick-format-unescaped "^4.0.3" 621 | real-require "^0.1.0" 622 | safe-stable-stringify "^2.1.0" 623 | sonic-boom "^2.2.1" 624 | thread-stream "^0.15.1" 625 | 626 | process-warning@^1.0.0: 627 | version "1.0.0" 628 | resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" 629 | integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== 630 | 631 | promptees@^2.0.0: 632 | version "2.0.0" 633 | resolved "https://registry.yarnpkg.com/promptees/-/promptees-2.0.0.tgz#bc54040861a17e9298d0598a5c84c022c307b779" 634 | integrity sha512-SxY5t6XdfqGHlMaYtXTO58td5Rc0BiLKp4k6ipn223OPgiiD5M09FE0WGq+X0rt0UpSbp4J0O8hVK3R+mQGSig== 635 | 636 | protobufjs@6.8.8: 637 | version "6.8.8" 638 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" 639 | integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== 640 | dependencies: 641 | "@protobufjs/aspromise" "^1.1.2" 642 | "@protobufjs/base64" "^1.1.2" 643 | "@protobufjs/codegen" "^2.0.4" 644 | "@protobufjs/eventemitter" "^1.1.0" 645 | "@protobufjs/fetch" "^1.1.0" 646 | "@protobufjs/float" "^1.0.2" 647 | "@protobufjs/inquire" "^1.1.0" 648 | "@protobufjs/path" "^1.1.2" 649 | "@protobufjs/pool" "^1.1.0" 650 | "@protobufjs/utf8" "^1.1.0" 651 | "@types/long" "^4.0.0" 652 | "@types/node" "^10.1.0" 653 | long "^4.0.0" 654 | 655 | protobufjs@^6.11.3: 656 | version "6.11.3" 657 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" 658 | integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== 659 | dependencies: 660 | "@protobufjs/aspromise" "^1.1.2" 661 | "@protobufjs/base64" "^1.1.2" 662 | "@protobufjs/codegen" "^2.0.4" 663 | "@protobufjs/eventemitter" "^1.1.0" 664 | "@protobufjs/fetch" "^1.1.0" 665 | "@protobufjs/float" "^1.0.2" 666 | "@protobufjs/inquire" "^1.1.0" 667 | "@protobufjs/path" "^1.1.2" 668 | "@protobufjs/pool" "^1.1.0" 669 | "@protobufjs/utf8" "^1.1.0" 670 | "@types/long" "^4.0.1" 671 | "@types/node" ">=13.7.0" 672 | long "^4.0.0" 673 | 674 | proxy-from-env@^1.1.0: 675 | version "1.1.0" 676 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 677 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 678 | 679 | punycode@1.3.2: 680 | version "1.3.2" 681 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" 682 | integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== 683 | 684 | qoa-format@^1.0.0: 685 | version "1.0.0" 686 | resolved "https://registry.yarnpkg.com/qoa-format/-/qoa-format-1.0.0.tgz#1638adb119ed3a32cf65efdb2c7e278f6d64b2ef" 687 | integrity sha512-Vjp2aV2x06tHbZesCi2UtISaFdlLzy47Vbt0rNwwdihKFvGtUeFfytdnA8XZYADqWtRbK19+XXeRkv1Stg4qSQ== 688 | dependencies: 689 | "@thi.ng/bitstream" "^2.2.12" 690 | 691 | qrcode-terminal@^0.12.0: 692 | version "0.12.0" 693 | resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" 694 | integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== 695 | 696 | querystring@0.2.0: 697 | version "0.2.0" 698 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" 699 | integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== 700 | 701 | quick-format-unescaped@^4.0.3: 702 | version "4.0.4" 703 | resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" 704 | integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== 705 | 706 | readable-stream@^3.1.1, readable-stream@^3.6.0: 707 | version "3.6.2" 708 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" 709 | integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== 710 | dependencies: 711 | inherits "^2.0.3" 712 | string_decoder "^1.1.1" 713 | util-deprecate "^1.0.1" 714 | 715 | readable-web-to-node-stream@^3.0.0: 716 | version "3.0.2" 717 | resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" 718 | integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== 719 | dependencies: 720 | readable-stream "^3.6.0" 721 | 722 | real-require@^0.1.0: 723 | version "0.1.0" 724 | resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" 725 | integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== 726 | 727 | safe-buffer@~5.2.0: 728 | version "5.2.1" 729 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 730 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 731 | 732 | safe-stable-stringify@^2.1.0: 733 | version "2.4.3" 734 | resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" 735 | integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== 736 | 737 | sonic-boom@^2.2.1: 738 | version "2.8.0" 739 | resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" 740 | integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== 741 | dependencies: 742 | atomic-sleep "^1.0.0" 743 | 744 | split2@^4.0.0: 745 | version "4.2.0" 746 | resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" 747 | integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== 748 | 749 | stream-shift@^1.0.0: 750 | version "1.0.1" 751 | resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" 752 | integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== 753 | 754 | string_decoder@^1.1.1: 755 | version "1.3.0" 756 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 757 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 758 | dependencies: 759 | safe-buffer "~5.2.0" 760 | 761 | strtok3@^6.2.4, strtok3@^6.3.0: 762 | version "6.3.0" 763 | resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" 764 | integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== 765 | dependencies: 766 | "@tokenizer/token" "^0.3.0" 767 | peek-readable "^4.1.0" 768 | 769 | thread-stream@^0.15.1: 770 | version "0.15.2" 771 | resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" 772 | integrity sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA== 773 | dependencies: 774 | real-require "^0.1.0" 775 | 776 | token-types@^4.1.1, token-types@^4.2.1: 777 | version "4.2.1" 778 | resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" 779 | integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== 780 | dependencies: 781 | "@tokenizer/token" "^0.3.0" 782 | ieee754 "^1.2.1" 783 | 784 | tr46@~0.0.3: 785 | version "0.0.3" 786 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 787 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 788 | 789 | tslib@^2.4.0: 790 | version "2.6.1" 791 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" 792 | integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== 793 | 794 | typescript@^4.7.3: 795 | version "4.9.5" 796 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" 797 | integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== 798 | 799 | url@0.11.0: 800 | version "0.11.0" 801 | resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" 802 | integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== 803 | dependencies: 804 | punycode "1.3.2" 805 | querystring "0.2.0" 806 | 807 | util-deprecate@^1.0.1: 808 | version "1.0.2" 809 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 810 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 811 | 812 | uuid@^9.0.0: 813 | version "9.0.0" 814 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" 815 | integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== 816 | 817 | webidl-conversions@^3.0.0: 818 | version "3.0.1" 819 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 820 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 821 | 822 | whatwg-url@^5.0.0: 823 | version "5.0.0" 824 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 825 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 826 | dependencies: 827 | tr46 "~0.0.3" 828 | webidl-conversions "^3.0.0" 829 | 830 | wrappy@1: 831 | version "1.0.2" 832 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 833 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 834 | 835 | ws@^8.13.0: 836 | version "8.13.0" 837 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" 838 | integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== 839 | --------------------------------------------------------------------------------