├── .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
2 |
3 | Another WhatsApp bot with super clean code design. Written in TypeScript.
4 |
5 | > Warning: non-english
6 | 
7 | > English below
8 | >
9 | >Download and send messages
10 | 
11 | >Create new commands
12 | 
13 | 
14 | > Aaaaand many more
15 | 
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 [](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* AW M {"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 |
--------------------------------------------------------------------------------