├── .env.example
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── Dockerfile
├── Procfile
├── README.md
├── db
├── init_start.txt
└── sessions.json
├── nodemon.json
├── package.json
├── src
├── api
│ ├── class
│ │ ├── instance.js
│ │ └── session.js
│ ├── controllers
│ │ ├── group.controller.js
│ │ ├── instance.controller.js
│ │ ├── message.controller.js
│ │ └── misc.controller.js
│ ├── errors
│ │ ├── api.error.js
│ │ └── extendable.error.js
│ ├── helper
│ │ ├── downloadMsg.js
│ │ ├── genVc.js
│ │ ├── processbtn.js
│ │ └── sleep.js
│ ├── middlewares
│ │ ├── adminToken.js
│ │ ├── error.js
│ │ ├── keyCheck.js
│ │ ├── loginCheck.js
│ │ ├── managerLogin.js
│ │ └── tokenCheck.js
│ ├── models
│ │ └── chat.model.js
│ ├── routes
│ │ ├── group.route.js
│ │ ├── index.js
│ │ ├── instance.route.js
│ │ ├── message.route.js
│ │ └── misc.route.js
│ └── views
│ │ └── qrcode.ejs
├── config
│ ├── config.js
│ └── express.js
├── public
│ ├── css
│ │ ├── styles.css
│ │ ├── toastr.css
│ │ └── toastr.min.css
│ ├── img
│ │ ├── favicon.png
│ │ ├── logo_api.png
│ │ ├── manager.jpg
│ │ └── noimage.jpg
│ ├── js
│ │ └── toastr.min.js
│ └── uploads
│ │ └── logo_api.png
└── server.js
├── temp
└── init_start.txt
└── tests
└── status.route.test.js
/.env.example:
--------------------------------------------------------------------------------
1 | # ==================================
2 | # SECURITY CONFIGURATION
3 | # ==================================
4 | TOKEN=YOUR_TOKEN
5 | PROTECT_ROUTES=true
6 | ADMINTOKEN=da71b564a1ed7e998204ca0d7cae38e791ca2154
7 | MAX_INSTANCES=50
8 |
9 | # ==================================
10 | # MANANGER CONFIGURATION
11 | # For manager contact on whatsapp: 556492134371
12 | # ==================================
13 |
14 | USER=admin
15 | PASSWORD=adminpass
16 | NODE_ENV=development
17 | SESSION_SECRET=8e46213be6df58e0702d3e8d4b5cf9ba48a610c3
18 | COOKIE_SECRET=da71b564a1ed7e998204ca0d7cae38e791ca2154
19 |
20 |
21 | # ==================================
22 | # APPLICATION CONFIGURATION
23 | # ==================================
24 | PORT=3333
25 | RESTORE_SESSIONS_ON_START_UP=true
26 | APP_URL=http://localhost:3333
27 | LOG_LEVEL=silent
28 |
29 |
30 | # ==================================
31 | # FILES CONFIGURATION
32 | # do not put spaces between mimetypes
33 | # IMPORTANT:STANDARD VIDEO FORMAT THAT WILL NOT BE CONVERTED: MP4 AND AUDIO: OGG
34 | # ==================================
35 |
36 | videoMimeTypes =video/mp4,video/avi,video/mkv,video/quicktime,video/x-msvideo,video/x-matroska
37 | audioMimeTypes =audio/mp3,audio/wav,audio/ogg,audio/mpeg
38 | documentMimeTypes =application/pdf,application/msword,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/x-rar-compressed
39 | imageMimeTypes = image/jpeg,image/png,image/gif,image/jpg
40 |
41 | # ==================================
42 | # AUDIO OUTUPT CONFIG
43 | # IMPORTANT: MP3 WILL GO THROUGH CONVERSION
44 | # MP3/OGG
45 | # ==================================
46 |
47 | DEFAULT_AUDIO_OUTPUT=OGG
48 |
49 | # ==================================
50 | # END CONFIGURATION
51 | # ==================================
52 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parserOptions": {
4 | "ecmaVersion": "latest",
5 | "sourceType": "module"
6 | },
7 | "extends": ["eslint:recommended", "prettier"],
8 | "env": {
9 | "es2021": true,
10 | "node": true
11 | },
12 | "rules": {
13 | "no-console": "warn",
14 | "no-undefined": "warn",
15 | "no-unused-vars": "warn"
16 | },
17 | "globals": {
18 | "WhatsAppInstances": true,
19 | "describe": true,
20 | "it": true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # NODE
2 | node_modules
3 | package-lock.json
4 | yarn.lock
5 |
6 | # APPLICATION
7 | .env
8 | sessiondata
9 |
10 | # IDEs
11 | .vscode/settings.json
12 | .idea
13 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | whatsapp-api-nodejs.postman_collection.json
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 4,
4 | "semi": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node
2 | WORKDIR /
3 | RUN git config --global credential.helper store && \
4 | git clone https://github.com/renatoiub/whatsapp-hard-api-node.git && \
5 | cd whatsapp-hard-api-node && \
6 | npm install
7 |
8 | ENV AUTO_UPDATE=true
9 | ENV PROTECT_ROUTES=true
10 | ENV TOKEN=RANDOM_STRING_HERE
11 | ENV PORT=3333
12 | ENV RESTORE_SESSIONS_ON_START_UP=true
13 | ENV APP_URL=http://localhost:3333
14 | ENV LOG_LEVEL=silent
15 | ENV videoMimeTypes=video/mp4,video/avi,video/mkv,video/quicktime,video/x-msvideo,video/x-matroska
16 | ENV audioMimeTypes=audio/mp3,audio/wav,audio/ogg,audio/mpeg
17 | ENV documentMimeTypes=application/pdf,application/msword,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/x-rar-compressed
18 | ENV imageMimeTypes=image/jpeg,image/png,image/gif,image/jpg
19 | ENV MAX_INSTANCES=50
20 | ENV ADMINTOKEN=da71b564a1ed7e998204ca0d7cae38e791ca2154
21 | ENV DEFAULT_AUDIO_OUTPUT=OGG
22 | ENV USER=admin
23 | ENV PASSWORD=adminpass
24 | ENV NODE_ENV=development
25 | ENV SESSION_SECRET=8e46213be6df58e0702d3e8d4b5cf9ba48a610c3
26 | ENV COOKIE_SECRET=da71b564a1ed7e998204ca0d7cae38e791ca2154
27 |
28 |
29 |
30 | EXPOSE $PORT
31 |
32 | CMD cd /whatsapp-hard-api-node/ && \
33 | if [ "$AUTO_UPDATE" = true ]; then git pull ; fi && \
34 | npm start
35 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run start
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
WhatsApp-hard - Api - NodeJs MultiDevice
2 | Esta api é uma implementação do WhiskeySockets Baileys , como um serviço RestFull Api, que controla funções do WhatsApp.
3 | Este código tem como base principal o projeto Whatsapp-api-nodejs que foi atualizado e melhorado com o tempo.
4 | Com este código, você pode criar chats de multiserviço, bots de serviço ou qualquer outro sistema que utilize o WhatsApp. Com este código, você não precisa conhecer JavaScript para Node.js, basta iniciar o servidor e fazer as solicitações na linguagem com a qual você se sentir mais confortável.
5 |
6 | Intalação:
7 |
8 | NPM install:
9 |
10 |
11 | Faça o download e a intalação do nodejs.
12 | https://nodejs.org/en/download
13 | Faça o download ou o clone deste repositório. É recomendado ter o git instalado para futuras atualizações:
14 | https://git-scm.com/downloads
15 | Comando para clonar este reposítorio:
16 |
17 |
18 | git clone https://github.com/renatoiub/whatsapp-hard-api-node
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Instalação de dependências
31 |
32 | npm i
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Renomear o arquivo env e configurar:
45 | Renomear o arquivo .env.example para .env
46 | Colocar no env a porta da aplicação, e os mimetypes que você deseja enviar via API. Caso opite por proteger as rotas, terá que enviar o token Bearer token (Authorization: Bearer RANDOM_STRING_HERE) nas requisições.
47 |
48 |
49 |
50 |
51 |
52 | Start da aplicação:
53 |
54 | npm start
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Intalação
66 |
67 | Docker install:
68 |
69 |
70 | Crie uma imagem apartir do Dockerfile.
71 | Edite o arquivo conforme a sua necessidade.
72 | Comando para iniciar a imagem:
73 | docker build -t hard-api-whatsapp .
74 | Certifique-se de estar na pasta onde o Dockerfile está
75 |
76 |
77 |
78 |
79 | Manager da API
80 | Contribua com o projeto e tenha acesso ao manager da api
81 |
82 |
83 | 
84 |
85 |
86 |
87 |
88 | Eventos do WEbhook:
89 |
90 | connection.update
91 | qrCode.update
92 | presence.update
93 | contacts.upsert
94 | chats.upsert
95 | chats.delete
96 | messages.update
97 | messages.upsert
98 | call.events
99 | groups.upsert
100 | groups.update
101 | group-participants.update
102 |
103 | Documentação:
104 |
105 |
109 |
110 |
111 |
112 | Envios e Recursos:
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Conexão via qr_code
123 | ✔
124 |
125 |
126 |
127 | Conexção via código de emparelhamento
128 | ✔
129 |
130 |
131 |
132 | Envia texto
133 | ✔
134 |
135 |
136 | Send Buttons
137 | ❌
138 |
139 |
140 | Send Template
141 | ❌
142 |
143 |
144 | Arquivos: audio - video - image - document - gif base64: true
145 | ✔
146 |
147 |
148 | Send Media URL
149 | ✔
150 |
151 |
152 | Send Media File
153 | ✔
154 |
155 |
156 | Convert audio and video to whatsapp format
157 | ✔
158 |
159 |
160 | Resposta de mensagem
161 | ✔
162 |
163 |
164 | Envia presença: Digitando.. Gravando audio..
165 | ✔
166 |
167 |
168 | Send Location
169 | ✔
170 |
171 |
172 | Send List (beta)
173 | ✔
174 |
175 |
176 | Send Link Preview
177 | ✔
178 |
179 |
180 | Send Contact
181 | ✔
182 |
183 |
184 | Send Reaction - emoji
185 | ✔
186 |
187 |
188 | Get contacts
189 | ✔
190 |
191 |
192 | Grupos: Cria, entra, sai, adiciona contatos, remove contatos e admins. Marcação fantasma (ghostMention) true
193 | ✔
194 |
195 |
196 |
197 |
198 | Informações adicionais:
199 | A api não usa nenhum banco de dados.
200 | A api é multidevices e aceita vários números conectados
201 | O consumo médio de memória varia de quantidade de instâncias e é extremamente leve
202 |
203 |
204 |
205 | Contribua com o projeto e receba atualizaçoes:
206 | Contato:
207 | Developer: https://github.com/renatoiub/
208 | Email: renatoiub@live.com
209 | Instagram: @renatoiub.
210 | Contribua com o projeto e receba atualizações:
211 | Pix: empresa@estoqueintegrado.com
212 |
213 |
--------------------------------------------------------------------------------
/db/init_start.txt:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/db/sessions.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src", ".env"],
3 | "ignore": [],
4 | "ext": "js",
5 | "exec": "node src/server.js"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whatsapp-hard-api-nodejs",
3 | "version": "1.0",
4 | "description": "Whatsapp api rest multidevices baseada em baileys",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node src/server.js",
8 | "dev": "nodemon",
9 | "format:check": "prettier --check .",
10 | "format:write": "prettier --write .",
11 | "lint:check": "eslint .",
12 | "lint:fix": "eslint --fix .",
13 | "test": "mocha tests/*.test.js --exit",
14 | "configure-husky": "npx husky install && npx husky add .husky/pre-commit \"npx --no-install lint-staged\""
15 | },
16 | "husky": {
17 | "hooks": {
18 | "pre-commit": "lint-staged"
19 | }
20 | },
21 | "lint-staged": {
22 | "*.{js,jsx}": [
23 | "prettier --write",
24 | "git add"
25 | ],
26 | "*.{html,css,less,ejs}": [
27 | "prettier --write",
28 | "git add"
29 | ]
30 | },
31 | "repository": "",
32 | "author": "Renato Freitas",
33 | "license": "MIT",
34 | "dependencies": {
35 | "@adiwajshing/keyed-db": "^0.2.4",
36 | "@ffmpeg-installer/ffmpeg": "^1.1.0",
37 | "@whiskeysockets/baileys": "github:renatoiub/baileys",
38 | "axios": "^1.1.3",
39 | "baileys": "github:renatoiub/baileys",
40 | "connect-flash": "^0.1.1",
41 | "cookie-parser": "^1.4.6",
42 | "dotenv": "^16.0.3",
43 | "ejs": "^3.1.7",
44 | "express": "^4.18.2",
45 | "express-exception-handler": "^1.3.23",
46 | "express-session": "^1.18.0",
47 | "jsonwebtoken": "^9.0.2",
48 | "link-preview-js": "^3.0.0",
49 | "multer": "^1.4.5-lts.1",
50 | "node-cache": "^5.1.2",
51 | "node-fetch": "^2.7.0",
52 | "node-mime-types": "^1.1.0",
53 | "node-schedule": "^2.1.1",
54 | "pino": "^8.7.0",
55 | "qrcode": "^1.5.1",
56 | "sharp": "^0.32.2",
57 | "uuid": "^9.0.0"
58 | },
59 | "devDependencies": {
60 | "eslint": "^8.28.0",
61 | "eslint-config-prettier": "^8.5.0",
62 | "husky": "^8.0.2",
63 | "lint-staged": "^13.0.4",
64 | "mocha": "^10.1.0",
65 | "nodemon": "^2.0.20",
66 | "prettier": "^2.8.0",
67 | "supertest": "^6.3.1"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/api/class/instance.js:
--------------------------------------------------------------------------------
1 | const ffmpegPath = require('@ffmpeg-installer/ffmpeg');
2 | const { exec } = require('child_process');
3 | const fetch = require('node-fetch');
4 | const QRCode = require('qrcode');
5 | const pino = require('pino');
6 | const { promisify } = require('util');
7 | const NodeCache = require('node-cache');
8 | const cache = new NodeCache({ stdTTL: 86400 });
9 | const GroupsCache = new NodeCache({ stdTTL: 20 });
10 | const GroupsMetaDados = new NodeCache({ stdTTL: 3600 });
11 | const schedule = require('node-schedule');
12 | const async = require('async');
13 |
14 |
15 | let intervalStore = [];
16 |
17 | const {
18 | makeWASocket,
19 | DisconnectReason,
20 | isJidUser,
21 | isJidGroup,
22 | jidDecode,
23 | jidEncode,
24 | jid,
25 | isLid,
26 | isJidBroadcast,
27 | makeInMemoryStore,
28 | proto,
29 | delay,
30 | useMultiFileAuthState,
31 | fetchLatestBaileysVersion,
32 | makeCacheableSignalKeyStore,
33 | getDevice,
34 | GroupMetadata,
35 | MessageUpsertType,
36 | ParticipantAction,
37 | generateWAMessageFromContent,
38 | getUSyncDevices,
39 | WASocket
40 | } = require('@whiskeysockets/baileys');
41 |
42 | const { unlinkSync } = require('fs');
43 | const { v4: uuidv4 } = require('uuid');
44 | const path = require('path');
45 | const processButton = require('../helper/processbtn');
46 | const generateVC = require('../helper/genVc');
47 | const axios = require('axios');
48 | const config = require('../../config/config');
49 | const downloadMessage = require('../helper/downloadMsg');
50 | const dados = makeInMemoryStore({ pino });
51 | const fs = require('fs').promises;
52 | const getMIMEType = require('mime-types');
53 | const readFileAsync = promisify(fs.readFile);
54 | const util = require('util');
55 | const url = require('url');
56 | const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
57 |
58 |
59 |
60 |
61 |
62 | async function clear() {
63 | const mainDirectoryPath = 'db/';
64 | const filesToExclude = ['creds.json', 'contacts.json', 'groups.json'];
65 | //console.log('Evento iniciado as 14:00')
66 | try {
67 | const folders = await fs.readdir(mainDirectoryPath);
68 | if (folders.length === 0) {
69 |
70 | return;
71 | }
72 |
73 |
74 | for (const folder of folders) {
75 | const folderPath = path.join(mainDirectoryPath, folder);
76 |
77 |
78 | try {
79 | const stats = await fs.stat(folderPath);
80 | if (stats.isDirectory()) {
81 |
82 | const files = await fs.readdir(folderPath);
83 |
84 |
85 |
86 | for (const file of files) {
87 | if (!filesToExclude.includes(file)) {
88 | const filePath = path.join(folderPath, file);
89 |
90 | try {
91 | await fs.unlink(filePath);
92 |
93 | } catch (err) {
94 |
95 | }
96 | } else {
97 |
98 | }
99 | }
100 | } else {
101 |
102 | }
103 | } catch (err) {
104 |
105 | }
106 | }
107 | } catch (err) {
108 |
109 | }
110 | }
111 |
112 |
113 | const job = schedule.scheduleJob('0 3 * * *', clear);
114 |
115 |
116 |
117 |
118 | class WhatsAppInstance {
119 | socketConfig = {
120 | defaultQueryTimeoutMs: undefined,
121 | printQRInTerminal: false,
122 | logger: pino({
123 | level: config.log.level,
124 | }),
125 |
126 |
127 | // markOnlineOnConnect: false
128 | msgRetryCounterCache: cache,
129 | forceGroupsPrekeys : false,
130 | getMessage: (key) => {
131 | return (dados.loadMessage(key.remoteJid, key.id))?.message || undefined;
132 | },
133 | patchMessageBeforeSending: (msg) => {
134 | if (msg.deviceSentMessage?.message?.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) {
135 | msg = JSON.parse(JSON.stringify(msg));
136 | msg.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT;
137 | }
138 |
139 | if (msg.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) {
140 | msg = JSON.parse(JSON.stringify(msg));
141 | msg.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT;
142 | }
143 |
144 | const requiresPatch = !!(msg.buttonsMessage || msg.listMessage || msg.templateMessage);
145 | if (requiresPatch) {
146 | msg = {
147 | viewOnceMessageV2: {
148 | message: {
149 | messageContextInfo: {
150 | deviceListMetadataVersion: 2,
151 | deviceListMetadata: {},
152 | },
153 | ...msg,
154 | },
155 | },
156 | };
157 | }
158 |
159 | return msg;
160 | },
161 | };
162 |
163 | key = '';
164 | authState;
165 | allowWebhook = undefined;
166 | webhook = undefined;
167 |
168 | instance = {
169 | key: this.key,
170 | chats: [],
171 | contacts: [],
172 | qr: '',
173 | messages: [],
174 | qrRetry: 0,
175 | customWebhook: '',
176 | WAPresence: [],
177 | deleted: false,
178 | };
179 |
180 | axiosInstance = axios.create({
181 | baseURL: config.webhookUrl,
182 | });
183 |
184 | constructor(key, allowWebhook, webhook,cacheDuration = 24 * 60 * 60 * 1000) {
185 | this.key = key ? key : uuidv4();
186 | this.instance.customWebhook = this.webhook ? this.webhook : webhook;
187 | this.allowWebhook = config.webhookEnabled ? config.webhookEnabled : allowWebhook;
188 | this.queue = this.createQueue(257);
189 |
190 | if (this.allowWebhook && this.instance.customWebhook !== null) {
191 | this.allowWebhook = true;
192 | this.instance.customWebhook = webhook;
193 | this.axiosInstance = axios.create({
194 | baseURL: webhook,
195 | });
196 | }
197 |
198 | }
199 |
200 |
201 | createQueue() {
202 | return async.queue(async (task, callback) => {
203 | try {
204 |
205 | await this.assertSession(task.lid); // Chama o método assertSession com o contexto da classe
206 | //callback(); // Indica que a tarefa foi concluída
207 | } catch (error) {
208 | console.error(`Erro ao processar ${task.lid}:`, error);
209 | //callback(error); // Passa o erro para o callback
210 | }
211 | }, 1);
212 | }
213 |
214 | async geraThumb(videoPath) {
215 | const name = uuidv4();
216 | const tempDir = 'temp';
217 | const thumbPath = 'temp/' + name + 'thumb.png';
218 |
219 | const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
220 | const base64 = base64Regex.test(videoPath);
221 |
222 | try {
223 | let videoBuffer;
224 | let videoTempPath;
225 |
226 | if (videoPath.startsWith('http')) {
227 | const response = await axios.get(videoPath, { responseType: 'arraybuffer' });
228 | videoTempPath = path.join(tempDir, name + '.mp4');
229 | videoBuffer = Buffer.from(response.data);
230 | await fs.writeFile(videoTempPath, videoBuffer);
231 | } else if (base64 === true) {
232 | videoTempPath = path.join(tempDir, 'temp/' + name + '.mp4');
233 | const buffer = Buffer.from(videoPath, 'base64');
234 | await fs.writeFile(videoTempPath, buffer);
235 | } else {
236 | videoTempPath = videoPath;
237 | }
238 |
239 | const command = `${ffmpegPath.path} -i ${videoTempPath} -ss 00:00:01 -vframes 1 ${thumbPath}`;
240 | await new Promise((resolve, reject) => {
241 | exec(command, (error, stdout, stderr) => {
242 | if (error) {
243 | reject(error);
244 | } else {
245 | resolve();
246 | }
247 | });
248 | });
249 |
250 | const thumbContent = await fs.readFile(thumbPath, { encoding: 'base64' });
251 |
252 | await Promise.all([fs.unlink(videoTempPath), fs.unlink(thumbPath)]);
253 |
254 | return thumbContent;
255 | } catch (error) {
256 | console.log(error);
257 | }
258 | }
259 |
260 | async thumbURL(url) {
261 | const videoUrl = url;
262 | try {
263 | const thumbContentFromUrl = await this.geraThumb(videoUrl);
264 | return thumbContentFromUrl;
265 | } catch (error) {
266 | console.log(error);
267 | }
268 | }
269 |
270 | async thumbBUFFER(buffer) {
271 | try {
272 | const thumbContentFromBuffer = await this.geraThumb(buffer);
273 | return thumbContentFromBuffer;
274 | } catch (error) {
275 | console.log(error);
276 | }
277 | }
278 |
279 | async thumbBase64(buffer) {
280 | try {
281 | const thumbContentFromBuffer = await this.geraThumb(buffer);
282 | return thumbContentFromBuffer;
283 | } catch (error) {
284 | console.log(error);
285 | }
286 | }
287 |
288 | async convertMP3(audioSource) {
289 | try {
290 | const return_mp3 = await this.mp3(audioSource);
291 | return return_mp3;
292 | } catch (error) {
293 | console.log(error);
294 | }
295 | }
296 |
297 | async mp3(audioSource) {
298 | const name = uuidv4();
299 | try {
300 | const mp3_temp = 'temp/' + name + '.mp3';
301 | const command = `${ffmpegPath.path} -i ${audioSource} -acodec libmp3lame -ab 128k ${mp3_temp}`;
302 | await new Promise((resolve, reject) => {
303 | exec(command, (error, stdout, stderr) => {
304 | if (error) {
305 | reject(error);
306 | } else {
307 | resolve();
308 | }
309 | });
310 | });
311 |
312 | const audioContent = await fs.readFile(mp3_temp, { encoding: 'base64' });
313 |
314 | await Promise.all([fs.unlink(mp3_temp), fs.unlink(audioSource)]);
315 |
316 | return audioContent;
317 | } catch (error) {
318 | console.log(error);
319 | }
320 | }
321 |
322 |
323 | async convertToMP4(audioSource) {
324 | const name = uuidv4();
325 | try {
326 | let audioBuffer;
327 | if (Buffer.isBuffer(audioSource)) {
328 | audioBuffer = audioSource;
329 | } else if (audioSource.startsWith('http')) {
330 | const response = await fetch(audioSource);
331 | audioBuffer = await response.buffer();
332 | } else if (audioSource.startsWith('data:audio')) {
333 | const base64DataIndex = audioSource.indexOf(',');
334 | if (base64DataIndex !== -1) {
335 | const base64Data = audioSource.slice(base64DataIndex + 1);
336 | audioBuffer = Buffer.from(base64Data, 'base64');
337 | }
338 | } else {
339 | audioBuffer = audioSource;
340 | }
341 |
342 | const tempOutputFile = `temp/temp_output_${name}.opus`;
343 | const mp3_temp = 'temp/' + name + '.mp3';
344 |
345 | const ffmpegCommand = `${ffmpegPath.path} -i "${mp3_temp}" -c:a libopus -b:a 128k -ac 1 "${tempOutputFile}"`;
346 |
347 | await fs.writeFile(mp3_temp, Buffer.from(audioBuffer));
348 |
349 | await new Promise((resolve, reject) => {
350 | exec(ffmpegCommand, (error, stdout, stderr) => {
351 | if (error) {
352 | reject(error);
353 | } else {
354 | resolve();
355 | }
356 | });
357 | });
358 |
359 | fs.unlink(mp3_temp);
360 |
361 | return tempOutputFile;
362 | } catch (error) {
363 | throw error;
364 | }
365 | }
366 |
367 | async convertTovideoMP4(videoSource) {
368 | const name = uuidv4();
369 | try {
370 | let videoBuffer;
371 |
372 | if (Buffer.isBuffer(videoSource)) {
373 | videoBuffer = videoSource;
374 | } else if (videoSource.startsWith('http')) {
375 | const response = await fetch(videoSource);
376 | videoBuffer = await response.buffer();
377 | } else if (videoSource.startsWith('data:video')) {
378 | const base64DataIndex = videoSource.indexOf(',');
379 | if (base64DataIndex !== -1) {
380 | const base64Data = videoSource.slice(base64DataIndex + 1);
381 | videoBuffer = Buffer.from(base64Data, 'base64');
382 | }
383 | } else {
384 | videoBuffer = videoSource;
385 | }
386 |
387 | const tempOutputFile = `temp/temp_output_${name}.mp4`;
388 | const mp4 = 'temp/' + name + '.mp4';
389 |
390 | const ffmpegCommand = `${ffmpegPath.path} -i "${mp4}" -c:v libx264 -c:a aac -strict experimental -b:a 192k -movflags faststart -f mp4 "${tempOutputFile}"`;
391 |
392 | await fs.writeFile(mp4, Buffer.from(videoBuffer));
393 |
394 | await new Promise((resolve, reject) => {
395 | exec(ffmpegCommand, (error, stdout, stderr) => {
396 | if (error) {
397 | reject(error);
398 | } else {
399 | resolve();
400 | }
401 | });
402 | });
403 |
404 | fs.unlink(mp4);
405 |
406 | return tempOutputFile;
407 | } catch (error) {
408 | throw error;
409 | }
410 | }
411 |
412 | async getMimeTypeFromBase64(base64String) {
413 | return new Promise((resolve, reject) => {
414 | try {
415 | const header = base64String.substring(0, base64String.indexOf(','));
416 | const match = header.match(/^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
417 |
418 | if (match && match[1]) {
419 | resolve(match[1]);
420 | } else {
421 | reject(new Error('Tipo MIME não pôde ser determinado.'));
422 | }
423 | } catch (error) {
424 | reject(error);
425 | }
426 | });
427 | }
428 |
429 | async getBufferFromMP4File(filePath) {
430 | return new Promise((resolve, reject) => {
431 | fs.readFile(filePath, (err, data) => {
432 | if (err) {
433 | reject(err);
434 | } else {
435 | resolve(data);
436 | }
437 | });
438 | });
439 | }
440 |
441 | async getFileNameFromUrl(url) {
442 | try {
443 | const pathArray = new URL(url).pathname.split('/');
444 | const fileName = pathArray[pathArray.length - 1];
445 | return fileName;
446 | } catch (error) {
447 | throw error;
448 | }
449 | }
450 |
451 |
452 |
453 | async dataBase() {
454 | try {
455 | return await useMultiFileAuthState('db/' + this.key);
456 | } catch (error) {
457 | console.log('Falha ao atualizar a base de dados');
458 | }
459 | }
460 |
461 | async SendWebhook(type, hook, body, key) {
462 | if (this.instance.webhok === false) {
463 | return;
464 | } else {
465 | const webhook_url = this.instance.webhook_url;
466 | const events = this.instance.webhook_events;
467 |
468 | const hasMessagesSet = events.includes(hook);
469 |
470 | if (hasMessagesSet === true) {
471 | this.web = axios.create({
472 | baseURL: this.instance.webhook_url,
473 | });
474 | this.web
475 | .post('', {
476 | type,
477 | body,
478 | instanceKey: key,
479 | })
480 | .catch((e) => {});
481 | }
482 | }
483 | }
484 |
485 | async instanceFind(key) {
486 | const filePath = path.join('db/sessions.json');
487 |
488 | const data = await fs.readFile(filePath, 'utf-8');
489 | if (data) {
490 | const sessions = JSON.parse(data);
491 | const existingSession = sessions.find((session) => session.key === this.key);
492 | if (!existingSession) {
493 | const data = {
494 | "key": false,
495 | "browser": false,
496 | "webhook": false,
497 | "base64": false,
498 | "webhookUrl": false,
499 | "webhookEvents": false,
500 | "messagesRead": false,
501 | };
502 | return data;
503 | } else {
504 | return existingSession;
505 | }
506 | } else {
507 | const data = {
508 | "key": false,
509 | "browser": false,
510 | "webhook": false,
511 | "base64": false,
512 | "webhookUrl": false,
513 | "webhookEvents": false,
514 | "messagesRead": false,
515 | };
516 | return data;
517 | }
518 | }
519 |
520 | async init() {
521 | const ver = await fetchLatestBaileysVersion();
522 | const filePath = path.join('db/sessions.json');
523 |
524 | const data = await fs.readFile(filePath, 'utf-8');
525 | if (!data) {
526 | return;
527 | }
528 | const sessions = JSON.parse(data);
529 |
530 | const existingSession = sessions.find((session) => session.key === this.key);
531 | if (!existingSession) {
532 | return;
533 | }
534 |
535 | const { state, saveCreds, keys } = await this.dataBase();
536 | this.authState = {
537 | state: state,
538 | saveCreds: saveCreds,
539 | keys: makeCacheableSignalKeyStore(keys, this.logger),
540 | };
541 |
542 | let b;
543 | let ignoreGroup;
544 |
545 | if (existingSession) {
546 | b = {
547 | browser: {
548 | platform: existingSession.browser,
549 | browser: 'Chrome',
550 | version: '20.0.04',
551 | },
552 | };
553 | ignoreGroup = existingSession.ignoreGroups;
554 | this.instance.mark = existingSession.messagesRead;
555 | this.instance.webhook = existingSession.webhook;
556 | this.instance.webhook_url = existingSession.webhookUrl;
557 | this.instance.webhook_events = existingSession.webhookEvents;
558 | this.instance.base64 = existingSession.base64;
559 | this.instance.ignoreGroups = ignoreGroup;
560 | } else {
561 | b = {
562 | browser: {
563 | platform: 'Chrome (Linux)',
564 | browser: 'chrome',
565 | version: '22.5.0',
566 | },
567 | };
568 | ignoreGroup = false;
569 | this.instance.mark = false;
570 | this.instance.webhook = false;
571 | this.instance.webhook_url = false;
572 | this.instance.webhook_events = false;
573 | this.instance.base64 = false;
574 | this.instance.ignoreGroups = ignoreGroup;
575 | }
576 |
577 | this.socketConfig.auth = this.authState.state;
578 | if (ignoreGroup === true) {
579 | this.socketConfig.shouldIgnoreJid = (jid) => {
580 | const isGroupJid = isJidGroup(jid);
581 | const isBroadcast = isJidBroadcast(jid);
582 | const isNewsletter = jid.includes('newsletter');
583 | return isGroupJid || isBroadcast || isNewsletter ;
584 | }
585 | } else {
586 |
587 | this.socketConfig.shouldIgnoreJid = (jid) => {
588 | const isNewsletter = jid.includes('newsletter');
589 | const isBroadcast = isJidBroadcast(jid);
590 | return isBroadcast || isNewsletter ;
591 |
592 | }
593 |
594 | }
595 | this.socketConfig.version = [2, 3000, 1021580394];
596 | this.socketConfig.browser = Object.values(b.browser);
597 | this.socketConfig.emitOwnEvents = true;
598 | this.instance.sock = makeWASocket(this.socketConfig);
599 | const dados = makeInMemoryStore({ pino });
600 | this.setHandler();
601 | dados?.bind(this.instance.sock?.ev);
602 | return this;
603 | }
604 |
605 | setHandler() {
606 | const sock = this.instance.sock;
607 |
608 | sock?.ev.on('creds.update', this.authState.saveCreds);
609 |
610 | sock?.ev.on('connection.update', async (update) => {
611 | const { connection, lastDisconnect, qr } = update;
612 | const status = lastDisconnect?.error?.output?.statusCode;
613 |
614 | if (connection === 'connecting') return;
615 |
616 | if (connection === 'close') {
617 | if (status === DisconnectReason.loggedOut || status === 405 || status === 402 || status === 403) {
618 | await this.deleteFolder('db/' + this.key);
619 | await delay(1000);
620 | this.instance.online = false;
621 | await this.init();
622 | } else if (status === 440) {
623 | return;
624 | } else {
625 | await this.init();
626 | }
627 |
628 | await this.SendWebhook('connection', 'connection.update', {
629 | connection: connection,
630 | connection_code: lastDisconnect?.error?.output?.statusCode
631 | }, this.key);
632 | } else if (connection === 'open') {
633 | this.instance.online = true;
634 | await this.SendWebhook('connection', 'connection.update', {
635 | connection: connection,
636 | }, this.key);
637 |
638 | //this.assertAll();
639 |
640 | }
641 |
642 | if (qr) {
643 | QRCode.toDataURL(qr).then((url) => {
644 | this.instance.qr = url;
645 | });
646 | await this.SendWebhook('qrCode', 'qrCode.update', {
647 | qr: qr,
648 | }, this.key);
649 | }
650 | });
651 |
652 |
653 |
654 | sock?.ev.on('presence.update', async (json) => {
655 | await this.SendWebhook('presence', 'presence.update', json, this.key);
656 | });
657 |
658 | sock?.ev.on('contacts.upsert', async (contacts) => {
659 | let folderPath;
660 | let filePath;
661 | try {
662 | const folderPath = 'db/' + this.key;
663 |
664 | const filePath = path.join(folderPath, 'contacts.json');
665 | await fs.access(folderPath);
666 |
667 | const currentContent = await fs.readFile(filePath, 'utf-8');
668 | const existingContacts = JSON.parse(currentContent);
669 |
670 | contacts.forEach((contact) => {
671 | const existingContactIndex = existingContacts.findIndex(
672 | (c) => c.id === contact.id
673 | );
674 |
675 | if (existingContactIndex !== -1) {
676 | existingContacts[existingContactIndex] = contact;
677 | } else {
678 | existingContacts.push(contact);
679 | }
680 | });
681 |
682 | await fs.writeFile(filePath, JSON.stringify(existingContacts, null, 2), 'utf-8');
683 |
684 | await this.SendWebhook('contacts', 'contacts.upsert', contacts, this.key);
685 | } catch (error) {
686 | const folderPath = 'db/' + this.key;
687 |
688 | const filePath = path.join(folderPath, 'contacts.json');
689 | await fs.mkdir(folderPath, { recursive: true });
690 | await fs.writeFile(filePath, JSON.stringify(contacts, null, 2), 'utf-8');
691 | }
692 | });
693 |
694 | sock?.ev.on('chats.upsert', async (newChat) => {
695 | try {
696 | await this.SendWebhook('chats', 'chats.upsert', newChat, this.key);
697 | } catch (e) {
698 | return;
699 | }
700 | });
701 |
702 | sock?.ev.on('chats.delete', async (deletedChats) => {
703 | try {
704 | await this.SendWebhook('chats', 'chats.delete', deletedChats, this.key);
705 | } catch (e) {
706 | return;
707 | }
708 | });
709 |
710 | sock?.ev.on('messages.update', async (m) => {
711 | try {
712 | await this.SendWebhook('updateMessage', 'messages.update', m, this.key);
713 | } catch (e) {
714 | return;
715 | }
716 | });
717 |
718 | // on new mssage
719 | sock?.ev.on('messages.upsert', async (m) => {
720 |
721 | if (m.type === 'prepend') this.instance.messages.unshift(...m.messages);
722 | if (m.type !== 'notify') return;
723 |
724 | this.instance.messages.unshift(...m.messages);
725 |
726 |
727 | m.messages.map(async (msg) => {
728 |
729 |
730 | if (!msg.message) return;
731 |
732 | if (this.instance.mark === true) {
733 | try
734 | {
735 |
736 | await this.lerMensagem(msg.key.id, msg.key.remoteJid);
737 | }
738 | catch(e)
739 | {
740 | //console.log(e)
741 | }
742 | }
743 |
744 | const messageType = Object.keys(msg.message)[0];
745 | if (['protocolMessage', 'senderKeyDistributionMessage'].includes(messageType))
746 | return;
747 |
748 |
749 | if (this.instance.webhook === true) {
750 | try{
751 |
752 | const webhookData = {
753 | key: this.key,
754 | ...msg,
755 | };
756 |
757 | if (messageType === 'conversation') {
758 | webhookData['text'] = m;
759 | }
760 |
761 | if (this.instance.base64 === true) {
762 |
763 |
764 | switch (messageType) {
765 | case 'imageMessage':
766 | webhookData['msgContent'] = await downloadMessage(
767 | msg.message.imageMessage,
768 | 'image'
769 | );
770 | break;
771 | case 'videoMessage':
772 | webhookData['msgContent'] = await downloadMessage(
773 | msg.message.videoMessage,
774 | 'video'
775 | );
776 |
777 | //webhookData['msgContent'] = await fs.readFile(arquivo_video, {
778 | //encoding: 'base64',
779 | //});
780 |
781 | //webhookData['thumb'] = await this.thumbBase64(arquivo_video);
782 |
783 | break;
784 | case 'audioMessage':
785 |
786 | if (process.env.DEFAULT_AUDIO_OUTPUT && process.env.DEFAULT_AUDIO_OUTPUT === 'MP3')
787 | {
788 |
789 |
790 |
791 |
792 | const arquivo_audio = await downloadMessage(msg.message.audioMessage,'audio');
793 | const buffer = Buffer.from(arquivo_audio, 'base64');
794 | const name = 'temp/'+uuidv4()+'.ogg';
795 | await fs.writeFile(name, buffer);
796 |
797 |
798 |
799 |
800 | const convert = await this.mp3(name);
801 |
802 | webhookData['msgContent'] = convert;
803 | }
804 | else
805 | {
806 | webhookData['msgContent'] = await downloadMessage(
807 | msg.message.audioMessage,
808 | 'audio'
809 | );
810 |
811 | }
812 | break;
813 | case 'documentMessage':
814 | webhookData['msgContent'] = await downloadMessage(
815 | msg.message.documentMessage,
816 | 'document'
817 | );
818 | break;
819 | default:
820 | webhookData['msgContent'] = '';
821 | break;
822 | }
823 |
824 | }
825 |
826 | await this.SendWebhook('message', 'messages.upsert', webhookData, this.key);
827 | }
828 | catch(e)
829 | {
830 | console.log('Error webhook send');
831 | }
832 | }
833 |
834 |
835 |
836 |
837 |
838 | });
839 | });
840 |
841 | sock?.ws.on('CB:call', async (data) => {
842 | try {
843 | if (data.content) {
844 | if (data.content.find((e) => e.tag === 'offer')) {
845 | const content = data.content.find((e) => e.tag === 'offer');
846 |
847 | await this.SendWebhook('call_offer', 'call.events', {
848 | id: content.attrs['call-id'],
849 | timestamp: parseInt(data.attrs.t),
850 | user: {
851 | id: data.attrs.from,
852 | platform: data.attrs.platform,
853 | platform_version: data.attrs.version,
854 | },
855 | }, this.key);
856 | } else if (data.content.find((e) => e.tag === 'terminate')) {
857 | const content = data.content.find((e) => e.tag === 'terminate');
858 |
859 | await this.SendWebhook('call', 'call.events', {
860 | id: content.attrs['call-id'],
861 | user: {
862 | id: data.attrs.from,
863 | },
864 | timestamp: parseInt(data.attrs.t),
865 | reason: data.content[0].attrs.reason,
866 | }, this.key);
867 | }
868 | }
869 | } catch (e) {
870 | return;
871 | }
872 | });
873 |
874 | sock?.ev.on('groups.upsert', async (groupUpsert) => {
875 |
876 | try {
877 | await this.SendWebhook('updateGroups', 'groups.upsert', {
878 | data: groupUpsert,
879 | }, this.key);
880 | await this.updateGroupData()
881 | GroupsMetaDados.flushAll();
882 | } catch (e) {
883 | return;
884 | }
885 | });
886 |
887 | sock?.ev.on('groups.update', async (groupUpdate) => {
888 |
889 | try {
890 | await this.SendWebhook('updateGroups', 'groups.update', {
891 | data: groupUpdate,
892 | }, this.key);
893 | await this.updateGroupData()
894 | GroupsMetaDados.flushAll();
895 | } catch (e) {
896 | return;
897 | }
898 | });
899 |
900 | sock?.ev.on('group-participants.update', async (groupParticipants) => {
901 |
902 | try {
903 | await this.SendWebhook('group-participants', 'group-participants.update', {
904 | data: groupParticipants,
905 | }, this.key);
906 | await this.updateGroupData()
907 | GroupsMetaDados.flushAll();
908 | } catch (e) {
909 | return;
910 | }
911 | });
912 | }
913 |
914 | async deleteInstance(key) {
915 | const filePath = path.join('db/sessions.json');
916 |
917 | let data = await fs.readFile(filePath, 'utf-8');
918 | let sessions = JSON.parse(data);
919 | let existingSession = sessions.find(session => session.key === key);
920 |
921 | if (existingSession) {
922 | let updatedSessions = sessions.filter(session => session.key !== key);
923 |
924 | try {
925 | let salvar = await fs.writeFile(filePath, JSON.stringify(updatedSessions, null, 2), 'utf-8');
926 | } catch (error) {
927 | console.log('erro ao salvar');
928 | }
929 |
930 | if (this.instance.online == true) {
931 | this.instance.deleted = true;
932 | await this.instance.sock?.logout();
933 | } else {
934 | await this.deleteFolder('db/' + this.key);
935 | }
936 | } else {
937 | return {
938 | error: true,
939 | message: 'Sessão não localizada',
940 | };
941 | }
942 | }
943 |
944 | async getInstanceDetail(key) {
945 | let connect = this.instance?.online;
946 |
947 | if (connect !== true) {
948 | connect = false;
949 | }
950 | const sessionData = await this.instanceFind(key);
951 | return {
952 | instance_key: key,
953 | phone_connected: connect,
954 | browser: sessionData.browser,
955 | webhook: sessionData.webhook,
956 | base64: sessionData.base64,
957 | webhookUrl: sessionData.webhookUrl,
958 | webhookEvents: sessionData.webhookEvents,
959 | messagesRead: sessionData.messagesRead,
960 | ignoreGroups: sessionData.ignoreGroups,
961 | user: this.instance?.online ? this.instance.sock?.user : {},
962 | };
963 | }
964 | getWhatsappCode(id)
965 | {
966 | if (id.startsWith('55')) {
967 | const numero = id.slice(2);
968 | const ddd = numero.slice(0, 2);
969 | let n;
970 |
971 | const indice = numero.indexOf('@');
972 |
973 | if (indice >= 1) {
974 | n = numero.slice(0, indice);
975 | } else {
976 | n = numero;
977 | }
978 |
979 | const comprimentoSemDDD = n.slice(2).length;
980 |
981 | if (comprimentoSemDDD < 8) {
982 | throw new Error('no account exists!');
983 | } else if (comprimentoSemDDD > 9) {
984 | throw new Error('no account exists.');
985 | } else if (parseInt(ddd) <= 27 && comprimentoSemDDD < 9) {
986 | let novoNumero = n.substring(0, 2) + '9' + n.substring(2);
987 | id = '55' + novoNumero;
988 | } else if (parseInt(ddd) > 27 && comprimentoSemDDD > 8) {
989 | let novoNumero = n.substring(0, 2) + n.substring(3);
990 | id = '55' + novoNumero;
991 | }
992 |
993 | return id;
994 | }
995 | else
996 | {
997 | return id;
998 | }
999 | }
1000 | getWhatsAppId(id) {
1001 | id = id.replace(/\D/g, "");
1002 | if (id.includes('@g.us') || id.includes('@s.whatsapp.net')) return id;
1003 | return id.includes('-') ? `${id}@g.us` : `${id}@s.whatsapp.net`;
1004 | }
1005 |
1006 | getGroupId(id) {
1007 | if (id.includes('@g.us') || id.includes('@g.us')) return id;
1008 | return id.includes('-') ? `${id}@g.us` : `${id}@g.us`;
1009 | }
1010 |
1011 | async deleteFolder(folder) {
1012 | try {
1013 | const folderPath = await path.join(folder);
1014 |
1015 | const folderExists = await fs.access(folderPath).then(() => true).catch(() => false);
1016 |
1017 | if (folderExists) {
1018 | const files = await fs.readdir(folderPath);
1019 |
1020 | for (const file of files) {
1021 | const filePath = await path.join(folderPath, file);
1022 | await fs.unlink(filePath);
1023 | }
1024 |
1025 | await fs.rmdir(folderPath);
1026 | return;
1027 | }
1028 | } catch (e) {
1029 | return;
1030 | }
1031 | }
1032 |
1033 | async lerMensagem(idMessage, to) {
1034 | try {
1035 | const msg = await this.getMessage(idMessage, to);
1036 | if (msg) {
1037 | await this.instance.sock?.readMessages([msg.key]);
1038 | }
1039 | } catch (e) {
1040 | //console.log(e)
1041 | }
1042 | }
1043 |
1044 | async verifyId(id) {
1045 | const cachedResult = await this.verifyCache(id);
1046 | if (cachedResult) {
1047 |
1048 | return cachedResult.jid;
1049 |
1050 | } else {
1051 | try {
1052 | const [result] = await this.instance.sock?.onWhatsApp(id);
1053 |
1054 | if (result.exists) {
1055 | await this.salvaCache(id, result);
1056 | return result.jid;
1057 | } else {
1058 |
1059 | throw new Error('O número:'+id+' não é um Whatsapp Valido');
1060 | }
1061 | } catch (error) {
1062 | throw new Error('O número:'+id+' não é um Whatsapp Valido');
1063 | }
1064 | }
1065 | }
1066 |
1067 | async verifyCache(id) {
1068 | const cachedItem = cache.get(id);
1069 |
1070 | if (cachedItem) {
1071 |
1072 | return cachedItem;
1073 | } else {
1074 |
1075 | return null;
1076 | }
1077 | }
1078 |
1079 | async salvaCache(id, result) {
1080 | cache.set(id, result);
1081 |
1082 | }
1083 |
1084 |
1085 |
1086 |
1087 | async sendTextMessage(data) {
1088 | //await this.assertAll();
1089 | let to = data.id;
1090 |
1091 | if (data.typeId === 'user') {
1092 | to = await this.verifyId(to);
1093 |
1094 | } else {
1095 |
1096 | await this.verifyGroup(to);
1097 |
1098 | }
1099 | if (data.options && data.options.delay && data.options.delay > 0) {
1100 | await this.setStatus('composing', to, data.typeId, data.options.delay);
1101 | }
1102 |
1103 | let mentions = false;
1104 |
1105 | if (data.typeId === 'group' && data.groupOptions && data.groupOptions.markUser) {
1106 | if (data.groupOptions.markUser === 'ghostMention') {
1107 | const metadata = await this.groupidinfo(to);
1108 | mentions = metadata.participants.map((participant) => participant.id);
1109 | } else {
1110 | mentions = this.parseParticipants(groupOptions.markUser);
1111 | }
1112 | }
1113 |
1114 | let quoted = { quoted: null };
1115 | let cache = {useCachedGroupMetadata:false};
1116 | if (data.typeId === 'group')
1117 | {
1118 | const metadados = await this.groupidinfo(to);
1119 | const meta = metadados.participants.map((participant) => participant.id);
1120 | cache = {useCachedGroupMetadata:meta};
1121 | //await this.assertSessions(to);
1122 |
1123 |
1124 | }
1125 |
1126 | if (data.options && data.options.replyFrom) {
1127 | const msg = await this.getMessage(data.options.replyFrom, to);
1128 |
1129 | if (msg) {
1130 | quoted = { quoted: msg };
1131 | }
1132 | }
1133 |
1134 | const send = await this.instance.sock?.sendMessage(
1135 | to, {
1136 | text: data.message,
1137 | mentions
1138 | },
1139 | quoted,
1140 | cache
1141 | );
1142 | return send;
1143 | }
1144 |
1145 | async assertSessions(group)
1146 | {
1147 | console.log('Processamento de grupo '+group+' Iniciado')
1148 | if(GroupsMetaDados.get('assert'+group+this.key))
1149 | {
1150 | return
1151 | }
1152 | else
1153 | {
1154 |
1155 | ////this.queue.push({ group }, (err) => {
1156 | //if (err) {
1157 | // console.error(`Erro ao processar ${group}:`, err);
1158 | //} else {
1159 | //GroupsMetaDados.set('assert'+group+this.key, true);
1160 | //}
1161 | //});
1162 | //}
1163 | const metadados = await this.groupidinfo(group);
1164 | const phoneNumbers = metadados.participants.map((participant) => participant.id);
1165 | for (let i = phoneNumbers.length - 1; i >= 0; i--) {
1166 | const lid = phoneNumbers[i];
1167 | this.queue.push({ lid }, (err) => {
1168 | if (err) {
1169 | //console.error(`Erro ao processar ${lid}:`, err);
1170 | } else {
1171 | //console.log(`Processamento de ${lid} concluído.`);
1172 | }
1173 | });
1174 | //}
1175 | }
1176 | GroupsMetaDados.set('assert'+group+this.key, true);
1177 |
1178 | }
1179 | }
1180 |
1181 | async assertAll()
1182 | {
1183 | try
1184 | {
1185 | const result = await this.groupFetchAllParticipating();
1186 | for (const key in result ) {
1187 | if (result[key].size > 300) {
1188 | this.assertSessions(result[key].id)
1189 | }
1190 | }
1191 |
1192 |
1193 | }
1194 | catch(e)
1195 | {
1196 | console.log(e);
1197 | }
1198 |
1199 | }
1200 |
1201 | async assertSession(lid) {
1202 |
1203 | try {
1204 | //const metadados = await this.groupidinfo(group);
1205 | //const phoneNumbers = metadados.participants.map((participant) => participant.id);
1206 |
1207 | const devices = [];
1208 | const additionalDevices = await this.instance.sock?.getUSyncDevices([lid], false, false);
1209 | devices.push(...additionalDevices);
1210 |
1211 | const senderKeyJids = [];
1212 | for (const { user, device } of devices) {
1213 | const jid = jidEncode(user, isLid ? 'lid' : 's.whatsapp.net', device)
1214 | senderKeyJids.push(jid);
1215 | }
1216 |
1217 | const assert = await this.instance.sock?.assertSessions(senderKeyJids);
1218 | //console.log(`Sessão confirmada para ${lid}`);
1219 | } catch (error) {
1220 | //console.log(error)
1221 | }
1222 | }
1223 |
1224 | async getMessage(idMessage, to) {
1225 | try {
1226 | const user_instance = this.instance.sock?.user.id;
1227 | const user = this.getWhatsAppId(user_instance.split(':')[0]);
1228 | const msg = await dados.loadMessage(to, idMessage);
1229 | return msg;
1230 | } catch (error) {
1231 | return false;
1232 | }
1233 | }
1234 |
1235 | async sendMediaFile(data, origem) {
1236 | let to = data.id;
1237 |
1238 | if (data.typeId === 'user') {
1239 | to = await this.verifyId(to);
1240 |
1241 | } else {
1242 |
1243 | await this.verifyGroup(to);
1244 | }
1245 |
1246 | let caption = '';
1247 | if (data.options && data.options.caption) {
1248 | caption = data.options.caption;
1249 | }
1250 |
1251 | let mentions = false;
1252 |
1253 | if (data.typeId === 'group' && data.groupOptions && data.groupOptions.markUser) {
1254 | if (data.groupOptions.markUser === 'ghostMention') {
1255 | const metadata = await this.groupidinfo(to);
1256 | mentions = metadata.participants.map((participant) => participant.id);
1257 | } else {
1258 | mentions = this.parseParticipants(groupOptions.markUser);
1259 | }
1260 | }
1261 |
1262 | let quoted = { quoted: null };
1263 | let cache = {useCachedGroupMetadata:false};
1264 | if (data.typeId === 'group')
1265 | {
1266 | const metadados = await this.groupidinfo(to);
1267 | const meta = metadados.participants.map((participant) => participant.id);
1268 | cache = {useCachedGroupMetadata:meta};
1269 | }
1270 |
1271 | if (data.options && data.options.replyFrom) {
1272 | const msg = await this.getMessage(data.options.replyFrom, to);
1273 |
1274 | if (msg) {
1275 | quoted = { quoted: msg };
1276 | }
1277 | }
1278 |
1279 | const acepty = ['audio', 'document', 'video', 'image'];
1280 |
1281 | if (!acepty.includes(data.type)) {
1282 | throw new Error('Arquivo invalido');
1283 | }
1284 |
1285 | const origin = ['url', 'base64', 'file'];
1286 | if (!origin.includes(origem)) {
1287 | throw new Error('Metodo de envio invalido');
1288 | }
1289 |
1290 | let type = false;
1291 | let mimetype = false;
1292 | let filename = false;
1293 | let file = false;
1294 | let audio = false;
1295 | let document = false;
1296 | let video = false;
1297 | let image = false;
1298 | let thumb = false;
1299 | let send;
1300 |
1301 | let myArray;
1302 | if (data.type === 'image') {
1303 | myArray = config.imageMimeTypes;
1304 | } else if (data.type === 'video') {
1305 | myArray = config.videoMimeTypes;
1306 | } else if (data.type === 'audio') {
1307 | myArray = config.audioMimeTypes;
1308 | } else {
1309 | myArray = config.documentMimeTypes;
1310 | }
1311 |
1312 | if (origem === 'url') {
1313 | const parsedUrl = url.parse(data.url);
1314 | if (parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:') {
1315 | mimetype = await this.GetFileMime(data.url);
1316 |
1317 | if (!myArray.includes(mimetype.trim())) {
1318 | throw new Error('Arquivo ' + mimetype + ' não é permitido para ' + data.type);
1319 | }
1320 |
1321 | origem = data.url;
1322 | }
1323 | } else if (origem === 'base64') {
1324 | if(!data.filename || data.filename ==='')
1325 | {throw new Error('Nome do arquivo é obrigatorio');}
1326 |
1327 | mimetype = getMIMEType.lookup(data.filename);
1328 |
1329 | if (!myArray.includes(mimetype.trim())) {
1330 | throw new Error('Arquivo ' + mimetype + ' não é permitido para ' + data.type);
1331 | }
1332 |
1333 |
1334 | }
1335 |
1336 | if (data.type === 'audio') {
1337 | if (mimetype === 'audio/ogg') {
1338 | if (data.options && data.options.delay) {
1339 | if (data.options.delay > 0) {
1340 | await this.instance.sock?.sendPresenceUpdate('recording', to);
1341 | await delay(data.options.delay * 1000);
1342 | }
1343 | }
1344 |
1345 | type = {
1346 | url: data.url,
1347 | };
1348 | mimetype = 'audio/mp4';
1349 | filename = await this.getFileNameFromUrl(data.url);
1350 | } else {
1351 | audio = await this.convertToMP4(origem);
1352 | mimetype = 'audio/mp4';
1353 | type = await fs.readFile(audio);
1354 | if (data.options && data.options.delay) {
1355 | if (data.options.delay > 0) {
1356 | await this.instance.sock?.sendPresenceUpdate('recording', to);
1357 | await delay(data.options.delay * 1000);
1358 | }
1359 | }
1360 | }
1361 | } else if (data.type === 'video') {
1362 | if (mimetype === 'video/mp4') {
1363 | type = {
1364 | url: data.url,
1365 | };
1366 | thumb = await this.thumbURL(data.url);
1367 | filename = await this.getFileNameFromUrl(data.url);
1368 | } else {
1369 | video = await this.convertTovideoMP4(origem);
1370 | mimetype = 'video/mp4';
1371 | type = await fs.readFile(video);
1372 | thumb = await this.thumbBUFFER(video);
1373 | }
1374 | } else {
1375 |
1376 | if(!data.base64string)
1377 | {
1378 | type = {
1379 | url: data.url,
1380 | };
1381 |
1382 | filename = await this.getFileNameFromUrl(data.url);
1383 | }
1384 | else
1385 | {
1386 |
1387 |
1388 | const buffer = Buffer.from(data.base64string, 'base64');
1389 |
1390 | filename = data.filename;
1391 | const file = path.join('temp/', filename);
1392 |
1393 | const join = await fs.writeFile(file, buffer);
1394 | type = await fs.readFile('temp/'+filename);
1395 |
1396 | }
1397 | }
1398 |
1399 | send = await this.instance.sock?.sendMessage(
1400 | to, {
1401 | mimetype: mimetype,
1402 | [data.type]: type,
1403 | caption: caption,
1404 | ptt: data.type === 'audio' ? true : false,
1405 | fileName: filename ? filename : file.originalname,
1406 | mentions
1407 | }, quoted, cache
1408 | );
1409 |
1410 | if (data.type === 'audio' || data.type === 'video' || data.type=='document') {
1411 | if (data.type === 'video') {
1412 | const ms = JSON.parse(JSON.stringify(send));
1413 | ms.message.videoMessage.thumb = thumb;
1414 | send = ms;
1415 | }
1416 |
1417 | const tempDirectory = 'temp/';
1418 | const files = await fs.readdir(tempDirectory);
1419 |
1420 | await Promise.all(files.map(async (file) => {
1421 | const filePath = path.join(tempDirectory, file);
1422 | await fs.unlink(filePath);
1423 | }));
1424 | }
1425 |
1426 | return send;
1427 | }
1428 |
1429 | async GetFileMime(arquivo) {
1430 | try {
1431 | const file = await await axios.head(arquivo);
1432 | return file.headers['content-type'];
1433 | return file;
1434 | } catch (error) {
1435 | throw new Error(
1436 | 'Arquivo invalido'
1437 | );
1438 | }
1439 | }
1440 |
1441 | async sendMedia(to, userType, file, type, caption = '', replyFrom = false, d = false) {
1442 | if (userType === 'user') {
1443 | to = await this.verifyId(to);
1444 |
1445 | } else {
1446 |
1447 | await this.verifyGroup(to);
1448 | }
1449 |
1450 | const acepty = ['audio', 'document', 'video', 'image'];
1451 |
1452 | let myArray;
1453 | if (type === 'image') {
1454 | myArray = config.imageMimeTypes;
1455 | } else if (type === 'video') {
1456 | myArray = config.videoMimeTypes;
1457 | } else if (type === 'audio') {
1458 | myArray = config.audioMimeTypes;
1459 | } else {
1460 | myArray = config.documentMimeTypes;
1461 | }
1462 |
1463 | const mime = file.mimetype;
1464 |
1465 | if (!myArray.includes(mime.trim())) {
1466 | throw new Error('Arquivo ' + mime + ' não é permitido para ' + type);
1467 | }
1468 |
1469 | if (!acepty.includes(type)) {
1470 | throw new Error('Type not valid');
1471 | }
1472 |
1473 | let mimetype = false;
1474 | let filename = false;
1475 | let buferFile = false;
1476 | if (type === 'audio') {
1477 | if (d > 0) {
1478 | await this.instance.sock?.sendPresenceUpdate('recording', to);
1479 | await delay(d * 1000);
1480 | }
1481 |
1482 | if (mime === 'audio/ogg') {
1483 | const filePath = file.originalname;
1484 | const extension = path.extname(filePath);
1485 |
1486 | mimetype = 'audio/mp4';
1487 | filename = file.originalname;
1488 | buferFile = file.buffer;
1489 | } else {
1490 | filename = uuidv4() + '.mp4';
1491 |
1492 | const audio = await this.convertToMP4(file.buffer);
1493 | mimetype = 'audio/mp4';
1494 | buferFile = await fs.readFile(audio);
1495 | }
1496 | } else if (type === 'video') {
1497 | if (mime === 'video/mp4') {
1498 | const filePath = file.originalname;
1499 | const extension = path.extname(filePath);
1500 |
1501 | mimetype = 'video/mp4';
1502 | filename = file.originalname;
1503 | buferFile = file.buffer;
1504 | } else {
1505 | filename = uuidv4() + '.mp4';
1506 |
1507 | const video = await this.convertTovideoMP4(file.buffer);
1508 | mimetype = 'video/mp4';
1509 | buferFile = await fs.readFile(video);
1510 | }
1511 | } else {
1512 | const filePath = file.originalname;
1513 | const extension = path.extname(filePath);
1514 |
1515 | const mimetype = getMIMEType.lookup(extension);
1516 | filename = file.originalname;
1517 | buferFile = file.buffer;
1518 | }
1519 |
1520 | let quoted = { quoted: null };
1521 | if (replyFrom) {
1522 | const msg = await this.getMessage(replyFrom, to);
1523 |
1524 | if (msg) {
1525 | quoted = { quoted: msg };
1526 | }
1527 | }
1528 | let cache = {useCachedGroupMetadata:false};
1529 | if (userType === 'group')
1530 | {
1531 | const metadados = await this.groupidinfo(to);
1532 | const meta = metadados.participants.map((participant) => participant.id);
1533 | cache = {useCachedGroupMetadata:meta};
1534 | }
1535 |
1536 |
1537 | const data = await this.instance.sock?.sendMessage(
1538 | to, {
1539 | [type]: buferFile,
1540 | caption: caption,
1541 | mimetype: mimetype,
1542 | ptt: type === 'audio' ? true : false,
1543 | fileName: filename
1544 | }, quoted, cache
1545 | );
1546 |
1547 | if (type === 'audio' || type === 'video') {
1548 | const tempDirectory = 'temp/';
1549 | const files = await fs.readdir(tempDirectory);
1550 |
1551 | await Promise.all(files.map(async (file) => {
1552 | const filePath = path.join(tempDirectory, file);
1553 | await fs.unlink(filePath);
1554 | }));
1555 | }
1556 |
1557 | return data;
1558 | }
1559 |
1560 | async newbuffer(mp4) {
1561 | try {
1562 | const filePath = path.join('temp', mp4);
1563 | const buffer = await fs.readFile(filePath);
1564 | return buffer;
1565 | } catch (error) {
1566 | throw new Error('Falha ao ler o arquivo mp4');
1567 | }
1568 | }
1569 |
1570 | async criaFile(tipo, origem) {
1571 | try {
1572 | if (tipo == 'file') {
1573 | const randomName = uuidv4();
1574 | const fileExtension = path.extname(origem.originalname);
1575 | const newFileName = `${randomName}${fileExtension}`;
1576 |
1577 | await fs.writeFile('temp/' + newFileName, origem.buffer);
1578 | return 'temp/' + newFileName;
1579 | }
1580 | } catch (error) {
1581 | throw new Error('Falha ao converter o arquivo MP4');
1582 | }
1583 | }
1584 |
1585 | async convertemp4(file, retorno) {
1586 | try {
1587 | const tempAudioPath = file;
1588 | const output = 'temp/' + retorno;
1589 | const ffmpegCommand = `${ffmpegPath.path} -i "${tempAudioPath}" -vn -ab 128k -ar 44100 -f ipod "${output}" -y`;
1590 |
1591 | exec(ffmpegCommand, (error, stdout, stderr) => {
1592 | if (error) {
1593 | reject({
1594 | error: true,
1595 | message: 'Falha ao converter o áudio.',
1596 | });
1597 | } else {
1598 | return retorno;
1599 | }
1600 | });
1601 | } catch (error) {
1602 | throw new Error('Falha ao converter o arquivo MP4');
1603 | }
1604 | }
1605 |
1606 | async DownloadProfile(of,group=false) {
1607 | try{
1608 | if(!group)
1609 | {
1610 |
1611 | of = await this.verifyId(of);
1612 | }
1613 | else
1614 | {
1615 | await this.verifyGroup(of);
1616 | }
1617 |
1618 | const ppUrl = await this.instance.sock?.profilePictureUrl(of,
1619 | 'image'
1620 | );
1621 | return ppUrl;
1622 | }
1623 | catch(e)
1624 | {
1625 | return process.env.APP_URL+'/img/noimage.jpg'
1626 | }
1627 | }
1628 |
1629 | async getUserStatus(of) {
1630 | of = await this.verifyId(of);
1631 | const status = await this.instance.sock?.fetchStatus(of)
1632 | return status;
1633 | }
1634 |
1635 | async contacts() {
1636 | const folderPath = 'db/' + this.key;
1637 | const filePath = path.join(folderPath, 'contacts.json');
1638 | try {
1639 | await fs.access(folderPath);
1640 |
1641 | const currentContent = await fs.readFile(filePath, 'utf-8');
1642 | const existingContacts = JSON.parse(currentContent);
1643 | return {
1644 | error: false,
1645 | contacts: existingContacts,
1646 | };
1647 | } catch (error) {
1648 | return {
1649 | error: true,
1650 | message: 'Os contatos ainda não foram carregados.',
1651 | };
1652 | }
1653 | }
1654 |
1655 |
1656 | async blockUnblock(to, data) {
1657 | try {
1658 | if (!data === 'block') {
1659 | data = 'unblock';
1660 | }
1661 |
1662 | to = await this.verifyId(to);
1663 | const status = await this.instance.sock?.updateBlockStatus(to, data);
1664 | return status;
1665 | } catch (e) {
1666 | return {
1667 | error: true,
1668 | message: 'Falha ao bloquear/desbloquear',
1669 | };
1670 | }
1671 | }
1672 |
1673 | async sendButtonMessage(to, data) {
1674 | to = await this.verifyId(to);
1675 | const result = await this.instance.sock?.sendMessage(to,
1676 | {
1677 | templateButtons: processButton(data.buttons),
1678 | text: data.text ?? '',
1679 | footer: data.footerText ?? '',
1680 | viewOnce: true
1681 | }
1682 | );
1683 | return result;
1684 | }
1685 |
1686 | async sendContactMessage(to, data) {
1687 | to = await this.verifyId(to);
1688 | const vcard = generateVC(data);
1689 | const result = await this.instance.sock?.sendMessage(to,
1690 | {
1691 | contacts: {
1692 | displayName: data.fullName,
1693 | contacts: [{
1694 | displayName: data.fullName,
1695 | vcard
1696 | }, ],
1697 | },
1698 | }
1699 | );
1700 | return result;
1701 | }
1702 |
1703 | async sendListMessage(to, type, options, groupOptions, data) {
1704 | if (type === 'user') {
1705 | to = await this.verifyId(to);
1706 |
1707 | } else {
1708 |
1709 | await this.verifyGroup(to);
1710 | }
1711 | if (options && options.delay && options.delay > 0) {
1712 | await this.setStatus('composing', to, type, options.delay);
1713 | }
1714 |
1715 | let mentions = false;
1716 |
1717 | if (type === 'group' && groupOptions && groupOptions.markUser) {
1718 | if (groupOptions.markUser === 'ghostMention') {
1719 | const metadata = await this.instance.sock?.groupMetadata(this.getGroupId(to));
1720 | mentions = metadata.participants.map((participant) => participant.id);
1721 | } else {
1722 | mentions = this.parseParticipants(groupOptions.markUser);
1723 | }
1724 | }
1725 |
1726 | let quoted = {
1727 | quoted: null
1728 | };
1729 |
1730 | if (options && options.replyFrom) {
1731 | const msg = await this.getMessage(options.replyFrom, to);
1732 |
1733 | if (msg) {
1734 | quoted = {
1735 | quoted: msg
1736 | };
1737 | }
1738 | }
1739 |
1740 | const msgList = {
1741 | text: data.title,
1742 | title: data.title,
1743 | description: data.description,
1744 | buttonText: data.buttonText,
1745 | footerText: data.footerText,
1746 | sections: data.sections,
1747 | listType: 2,
1748 | };
1749 |
1750 | let idlogado = await this.idLogado();
1751 | const msgRes = generateWAMessageFromContent(to, {
1752 | listMessage: msgList,
1753 | mentions
1754 | }, quoted, {
1755 | idlogado
1756 | });
1757 |
1758 | const result = await this.instance.sock?.relayMessage(to, msgRes.message, msgRes.key.id);
1759 |
1760 | return msgRes;
1761 | }
1762 |
1763 | async sendMediaButtonMessage(to, data) {
1764 | to = await this.verifyId(to);
1765 |
1766 | const result = await this.instance.sock?.sendMessage(
1767 | this.getWhatsAppId(to), {
1768 | [data.mediaType]: {
1769 | url: data.image,
1770 | },
1771 | footer: data.footerText ?? '',
1772 | caption: data.text,
1773 | templateButtons: processButton(data.buttons),
1774 | mimetype: data.mimeType,
1775 | viewOnce: true
1776 | }
1777 | );
1778 | return result;
1779 | }
1780 |
1781 | async createJid(number) {
1782 | if (!isNaN(number)) {
1783 | const jid = `${number}@s.whatsapp.net`;
1784 | return jid;
1785 | } else {
1786 | return number;
1787 | }
1788 | }
1789 |
1790 | async setStatus(status, to, type, pause = false) {
1791 |
1792 | try{
1793 | if (type === 'user') {
1794 | to = await this.verifyId(to);
1795 |
1796 | } else {
1797 |
1798 | await this.verifyGroup(to);
1799 | }
1800 |
1801 | const result = await this.instance.sock?.sendPresenceUpdate(status, to);
1802 | if (pause > 0) {
1803 | await delay(pause * 1000);
1804 | await this.instance.sock?.sendPresenceUpdate('paused', to);
1805 | }
1806 | return result;
1807 | }
1808 | catch(e)
1809 | {
1810 | throw new Error('Falha ao enviar a presença, verifique o id e tente novamente')
1811 | }
1812 | }
1813 |
1814 | async updateProfilePicture(to, url, type) {
1815 |
1816 | try {
1817 |
1818 | if (type === 'user') {
1819 | to = await this.verifyId(this.getWhatsAppId(to));
1820 |
1821 | } else {
1822 |
1823 | await this.verifyGroup(to);
1824 | }
1825 |
1826 | const img = await axios.get(url, {
1827 | responseType: 'arraybuffer'
1828 | });
1829 | const res = await this.instance.sock?.updateProfilePicture(to, img.data);
1830 | return {
1831 | error: false,
1832 | message: 'Foto alterada com sucesso!',
1833 | };
1834 | } catch (e) {
1835 | console.log(e)
1836 | return {
1837 | error: true,
1838 | message: 'Unable to update profile picture',
1839 | };
1840 | }
1841 | }
1842 |
1843 | async mystatus(status)
1844 | {
1845 | try
1846 | {
1847 | const result = await this.instance.sock?.sendPresenceUpdate(status)
1848 | return {
1849 | error: false,
1850 | message:
1851 | 'Status alterado para '+status,
1852 | }
1853 |
1854 | }
1855 | catch (e)
1856 | {
1857 | return {
1858 | error: true,
1859 | message:
1860 | 'Não foi possível alterar para o status '+status,
1861 | }
1862 |
1863 | }
1864 |
1865 | }
1866 |
1867 | // get user or group object from db by id
1868 | async getUserOrGroupById(id) {
1869 | try {
1870 | let Chats = await this.getChat()
1871 | const group = Chats.find((c) => c.id === this.getWhatsAppId(id))
1872 | if (!group)
1873 | throw new Error(
1874 | 'unable to get group, check if the group exists'
1875 | )
1876 | return group
1877 | } catch (e) {
1878 | logger.error(e)
1879 | logger.error('Error get group failed')
1880 | }
1881 | }
1882 |
1883 | // Group Methods
1884 | parseParticipants(users) {
1885 | return users.map((users) => this.getWhatsAppId(users))
1886 | }
1887 |
1888 | async updateDbGroupsParticipants() {
1889 | try {
1890 | let groups = await this.groupFetchAllParticipating()
1891 | let Chats = await this.getChat()
1892 | if (groups && Chats) {
1893 | for (const [key, value] of Object.entries(groups)) {
1894 | let group = Chats.find((c) => c.id === value.id)
1895 | if (group) {
1896 | let participants = []
1897 | for (const [
1898 | key_participant,
1899 | participant,
1900 | ] of Object.entries(value.participants)) {
1901 | participants.push(participant)
1902 | }
1903 | group.participant = participants
1904 | if (value.creation) {
1905 | group.creation = value.creation
1906 | }
1907 | if (value.subjectOwner) {
1908 | group.subjectOwner = value.subjectOwner
1909 | }
1910 | Chats.filter((c) => c.id === value.id)[0] = group
1911 | }
1912 | }
1913 | await this.updateDb(Chats)
1914 | }
1915 | } catch (e) {
1916 | logger.error(e)
1917 | logger.error('Error updating groups failed')
1918 | }
1919 | }
1920 |
1921 | async createNewGroup(name, users) {
1922 |
1923 | try {
1924 | const group = await this.instance.sock?.groupCreate(
1925 | name,
1926 | users.map(this.getWhatsAppId)
1927 | )
1928 | return group
1929 | } catch (e) {
1930 | return {
1931 | error: true,
1932 | message:
1933 | 'Erro ao criar o grupo',
1934 | }
1935 | }
1936 | }
1937 |
1938 | async groupFetchAllParticipating() {
1939 |
1940 | const cacheDir = 'db/' + this.key;
1941 | const cacheFile = path.join(cacheDir, 'groups.json');
1942 |
1943 |
1944 | try {
1945 |
1946 | await fs.access(cacheFile);
1947 | const data = await fs.readFile(cacheFile, 'utf-8');
1948 | return JSON.parse(data);
1949 | } catch (e) {
1950 |
1951 | try {
1952 | await fs.access(cacheDir);
1953 | } catch (e) {
1954 | await fs.mkdir(cacheDir, { recursive: true });
1955 | }
1956 | const checkEvent = await GroupsCache.get(this.key);
1957 |
1958 | if (!checkEvent) {
1959 |
1960 | await GroupsCache.set(this.key, true);
1961 | }
1962 |
1963 | const result = await this.instance.sock?.groupFetchAllParticipating();
1964 |
1965 | if (result && Object.keys(result).length > 0) {
1966 | await fs.writeFile(cacheFile, JSON.stringify(result), 'utf-8');
1967 |
1968 | return result;
1969 | } else {
1970 |
1971 | }
1972 |
1973 | }
1974 | }
1975 |
1976 | async updateGroupData() {
1977 | await delay(1000);
1978 |
1979 | if (!this.key) {
1980 | return;
1981 | }
1982 |
1983 |
1984 |
1985 | const cacheDir = 'db/' + this.key;
1986 | const cacheFile = path.join(cacheDir, 'groups.json');
1987 |
1988 | let result;
1989 |
1990 | try {
1991 |
1992 | const checkEvent = GroupsCache.get(this.key);
1993 |
1994 |
1995 | if (checkEvent) {
1996 |
1997 | return false;
1998 | }
1999 |
2000 |
2001 | result = await this.instance.sock?.groupFetchAllParticipating();
2002 |
2003 |
2004 | try {
2005 | await fs.access(cacheDir);
2006 | } catch (e) {
2007 | await fs.mkdir(cacheDir, { recursive: true });
2008 | }
2009 |
2010 |
2011 | if (result && Object.keys(result).length > 0) {
2012 | await fs.writeFile(cacheFile, JSON.stringify(result), 'utf-8');
2013 |
2014 | } else {
2015 |
2016 | }
2017 | } catch (e) {
2018 |
2019 | } finally {
2020 |
2021 | GroupsCache.set(this.key, true);
2022 | }
2023 |
2024 | return result;
2025 | }
2026 |
2027 | async verifyGroup(id) {
2028 |
2029 | try {
2030 |
2031 | if(GroupsMetaDados.get(id+this.key))
2032 | {
2033 | return true
2034 | }
2035 | const result = await this.groupFetchAllParticipating()
2036 | if(result.hasOwnProperty(id))
2037 | {
2038 |
2039 | GroupsMetaDados.set(id+this.key, true);
2040 | return true;
2041 | }
2042 | else
2043 | {
2044 | throw new Error('Grupo não existe');
2045 | }
2046 | } catch (error) {
2047 | console.log(error)
2048 |
2049 | throw new Error('Grupo não existe');
2050 | }
2051 | }
2052 |
2053 |
2054 |
2055 |
2056 | async addNewParticipant(id, users) {
2057 |
2058 | try {
2059 | await this.verifyGroup(id);
2060 |
2061 | const res = await this.instance.sock?.groupParticipantsUpdate(
2062 | this.getGroupId(id),
2063 | users.map(this.getWhatsAppId),
2064 | "add"
2065 |
2066 | )
2067 | return res
2068 | }
2069 | catch {
2070 | return {
2071 | error: true,
2072 | message:
2073 | 'Unable to add participant, you must be an admin in this group',
2074 | }
2075 | }
2076 | }
2077 |
2078 | async makeAdmin(id, users) {
2079 |
2080 | try {
2081 | await this.verifyGroup(id);
2082 | const res = await this.instance.sock?.groupParticipantsUpdate(
2083 | this.getGroupId(id),
2084 | users.map(this.getWhatsAppId),
2085 | "promote"
2086 |
2087 | )
2088 | return res
2089 |
2090 | } catch {
2091 | return {
2092 | error: true,
2093 | message:
2094 | 'Unable to add participant, you must be an admin in this group',
2095 | }
2096 | }
2097 | }
2098 |
2099 | async removeuser(id, users) {
2100 |
2101 | try {
2102 | await this.verifyGroup(id);
2103 |
2104 | const res = await this.instance.sock?.groupParticipantsUpdate(
2105 | this.getGroupId(id),
2106 | users.map(this.getWhatsAppId),
2107 | "remove"
2108 |
2109 | )
2110 | return res
2111 |
2112 |
2113 | } catch {
2114 | return {
2115 | error: true,
2116 | message:
2117 | 'Unable to add participant, you must be an admin in this group',
2118 | }
2119 | }
2120 | }
2121 |
2122 | async demoteAdmin(id, users) {
2123 |
2124 | try {
2125 | await this.verifyGroup(id);
2126 |
2127 | const res = await this.instance.sock?.groupParticipantsUpdate(
2128 | this.getGroupId(id),
2129 | users.map(this.getWhatsAppId),
2130 | "demote"
2131 |
2132 | )
2133 |
2134 | } catch {
2135 | return {
2136 | error: true,
2137 | message:
2138 | 'Unable to add participant, you must be an admin in this group',
2139 | }
2140 | }
2141 | }
2142 |
2143 |
2144 |
2145 | async idLogado()
2146 | {
2147 | const user_instance = this.instance.sock?.user.id;
2148 | const user = this.getWhatsAppId(user_instance.split(':')[0]);
2149 | return user;
2150 | }
2151 | async joinURL(url)
2152 | {
2153 | try
2154 | {
2155 | const partesDaURL = url.split('/');
2156 | const codigoDoGrupo = partesDaURL[partesDaURL.length - 1];
2157 |
2158 | const entrar = await this.instance.sock?.groupAcceptInvite(codigoDoGrupo);
2159 | await this.updateGroupData()
2160 | GroupsMetaDados.flushAll();
2161 |
2162 | return entrar
2163 | //voltarnoGrupo
2164 |
2165 | }
2166 | catch(e)
2167 | {
2168 | return {
2169 | error: true,
2170 | message:'Erro ao entrar via URL, verifique se a url ainda é valida ou se o grupo é um grupo aberto.',
2171 | }
2172 |
2173 | }
2174 |
2175 | }
2176 |
2177 | async leaveGroup(id) {
2178 |
2179 |
2180 | try {
2181 | await this.verifyGroup(id);
2182 | await this.instance.sock?.groupLeave(id)
2183 |
2184 | return {
2185 | error: false,
2186 | message:
2187 | 'Saiu do grupo.',
2188 | }
2189 |
2190 | } catch (e) {
2191 | return {
2192 | error: true,
2193 | message:
2194 | 'Erro ao sair do grupo, verifique se o grupo ainda existe.',
2195 | }
2196 | }
2197 | }
2198 |
2199 |
2200 | async getInviteCodeGroup(id) {
2201 |
2202 | try {
2203 | await this.verifyGroup(id);
2204 | const convite = await this.instance.sock?.groupInviteCode(id)
2205 | const url ='https://chat.whatsapp.com/'+convite
2206 | return url;
2207 |
2208 | } catch (e) {
2209 |
2210 | return {
2211 | error: true,
2212 | message:
2213 | 'Erro ao verificar o grupo, verifique se o grupo ainda existe ou se você é administrador.',
2214 | }
2215 | }
2216 | }
2217 |
2218 | async getInstanceInviteCodeGroup(id) {
2219 | try {
2220 | await this.verifyGroup(id);
2221 | return await this.instance.sock?.groupInviteCode(id)
2222 | } catch (e) {
2223 | logger.error(e)
2224 | logger.error('Error get invite group failed')
2225 | }
2226 | }
2227 |
2228 | async groupSettingUpdate(id, action) {
2229 | try {
2230 | await this.verifyGroup(id);
2231 | const res = await this.instance.sock?.groupSettingUpdate(
2232 | id,
2233 | action
2234 | )
2235 | return {
2236 | error: false,
2237 | message:
2238 | 'Alteração referente a ' + action + ' Concluida',
2239 | }
2240 | } catch (e) {
2241 | //console.log(e)
2242 | return {
2243 | error: true,
2244 | message:
2245 | 'Erro ao alterar' + action + ' Verifique se você tem permissão ou se o grupo existe',
2246 | }
2247 | }
2248 | }
2249 |
2250 | async groupUpdateSubject(id, subject) {
2251 |
2252 | try {
2253 | await this.verifyGroup(id);
2254 | const res = await this.instance.sock?.groupUpdateSubject(
2255 | this.getWhatsAppId(id),
2256 | subject
2257 | )
2258 | return {
2259 | error: false,
2260 | message:
2261 | 'Nome do grupo alterado para '+subject
2262 | }
2263 | } catch (e) {
2264 | //console.log(e)
2265 | return {
2266 | error: true,
2267 | message:
2268 | 'Erro ao alterar o grupo, verifique se você é administrador ou se o grupo existe',
2269 | }
2270 | }
2271 | }
2272 |
2273 | async groupUpdateDescription(id, description) {
2274 |
2275 | try {
2276 | await this.verifyGroup(id);
2277 | const res = await this.instance.sock?.groupUpdateDescription(
2278 | id,
2279 | description
2280 | )
2281 | //console.log(res)
2282 | return {
2283 | error: false,
2284 | message:
2285 | 'Descrição do grupo alterada para '+description
2286 | }
2287 | } catch (e) {
2288 |
2289 | return {
2290 | error: true,
2291 | message:
2292 | 'Falha ao alterar a descrição do grupo, verifique se você é um administrador ou se o grupo existe',
2293 | }
2294 | }
2295 | }
2296 |
2297 | async groupGetInviteInfo(url) {
2298 | try {
2299 | await this.verifyGroup(id);
2300 | const codeurl = url.split('/');
2301 |
2302 |
2303 | const code = codeurl[codeurl.length - 1];
2304 |
2305 |
2306 | const res = await this.instance.sock?.groupGetInviteInfo(code)
2307 |
2308 | return res
2309 | } catch (e) {
2310 | //console.log(e)
2311 | return {
2312 | error: true,
2313 | message:
2314 | 'Falha ao obter o verificar o grupo. Verifique o codigo da url ou se o grupo ainda existe..',
2315 | }
2316 | }
2317 | }
2318 |
2319 |
2320 | async groupidinfo(id) {
2321 |
2322 |
2323 | try {
2324 | await this.verifyGroup(id);
2325 |
2326 | const result = await this.groupFetchAllParticipating()
2327 | if(result.hasOwnProperty(id))
2328 | {
2329 |
2330 | return result[id];
2331 |
2332 | }
2333 | else
2334 | {
2335 | return {
2336 | error: true,
2337 | message: 'Grupo não existe!',
2338 | };
2339 |
2340 | }
2341 | } catch (e) {
2342 |
2343 | return {
2344 | error: true,
2345 | message: 'Grupo não existe',
2346 | };
2347 | }
2348 | }
2349 |
2350 |
2351 | async groupAcceptInvite(id) {
2352 | try {
2353 | const res = await this.instance.sock?.groupAcceptInvite(id)
2354 | return res
2355 | } catch (e) {
2356 | //console.log(e)
2357 | return {
2358 | error: true,
2359 | message:
2360 | 'Falha ao obter o verificar o grupo. Verifique o codigo da url ou se o grupo ainda existe..',
2361 | }
2362 | }
2363 | }
2364 |
2365 | // update db document -> chat
2366 | async updateDb(object) {
2367 | try {
2368 | await Chat.updateOne({ key: this.key }, { chat: object })
2369 | } catch (e) {
2370 | logger.error('Error updating document failed')
2371 |
2372 | }
2373 | }
2374 |
2375 | async readMessage(msgObj) {
2376 | try {
2377 | const key = {
2378 | remoteJid: msgObj.remoteJid,
2379 | id: msgObj.id,
2380 | participant: msgObj?.participant // required when reading a msg from group
2381 | }
2382 | const res = await this.instance.sock?.readMessages([key])
2383 | return res
2384 | } catch (e) {
2385 | logger.error('Error read message failed')
2386 | }
2387 | }
2388 |
2389 | async reactMessage(id, key, emoji) {
2390 | try {
2391 | const reactionMessage = {
2392 | react: {
2393 | text: emoji, // use an empty string to remove the reaction
2394 | key: key
2395 | }
2396 | }
2397 | const res = await this.instance.sock?.sendMessage(
2398 | this.getWhatsAppId(id),
2399 | reactionMessage
2400 | )
2401 | return res
2402 | } catch (e) {
2403 | logger.error('Error react message failed')
2404 | }
2405 | }
2406 | }
2407 |
2408 | exports.WhatsAppInstance = WhatsAppInstance
2409 |
--------------------------------------------------------------------------------
/src/api/class/session.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unsafe-optional-chaining */
2 | const { WhatsAppInstance } = require('../class/instance')
3 | const logger = require('pino')()
4 | const config = require('../../config/config')
5 | const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
6 |
7 | const fs = require('fs').promises;
8 |
9 | class Session {
10 | async restoreSessions() {
11 | let restoredSessions = new Array();
12 | let allSessions = [];
13 |
14 | try {
15 | const filePath = 'db/sessions.json';
16 |
17 |
18 | const fileExists = await fs.access(filePath).then(() => true).catch(() => false);
19 |
20 | if (fileExists) {
21 |
22 | const data = await fs.readFile(filePath, 'utf-8');
23 | allSessions = JSON.parse(data);
24 | } else {
25 |
26 | await fs.writeFile(filePath, '[]', 'utf-8');
27 | }
28 | const data = await fs.readFile('db/sessions.json', 'utf-8');
29 | allSessions = JSON.parse(data);
30 |
31 |
32 |
33 |
34 | allSessions.forEach(async (sessionData) => {
35 |
36 | const { key, webhook, webhookUrl } = sessionData;
37 | const instance = new WhatsAppInstance(key, webhook, webhookUrl);
38 | await instance.init();
39 | WhatsAppInstances[key] = instance;
40 | restoredSessions.push(key);
41 | await sleep(150);
42 | });
43 | } catch (e) {
44 | logger.error('Error restoring sessions');
45 | logger.error(e);
46 | }
47 |
48 | return restoredSessions;
49 | }
50 | }
51 |
52 | exports.Session = Session;
53 |
--------------------------------------------------------------------------------
/src/api/controllers/group.controller.js:
--------------------------------------------------------------------------------
1 | exports.create = async (req, res) => {
2 |
3 | const data = await WhatsAppInstances[req.query.key].createNewGroup(
4 | req.body.name,
5 | req.body.users
6 | )
7 | return res.status(201).json({ error: false, data: data })
8 | }
9 |
10 | exports.addNewParticipant = async (req, res) => {
11 | const data = await WhatsAppInstances[req.query.key].addNewParticipant(
12 | req.body.id,
13 | req.body.users
14 | )
15 | return res.status(201).json({ error: false, data: data })
16 | }
17 | exports.removeuser = async (req, res) => {
18 | const data = await WhatsAppInstances[req.query.key].removeuser(
19 | req.body.id,
20 | req.body.users
21 | )
22 | return res.status(201).json({ error: false, data: data })
23 | }
24 |
25 | exports.makeAdmin = async (req, res) => {
26 | const data = await WhatsAppInstances[req.query.key].makeAdmin(
27 | req.body.id,
28 | req.body.users
29 | )
30 | return res.status(201).json({ error: false, data: data })
31 | }
32 |
33 | exports.demoteAdmin = async (req, res) => {
34 | const data = await WhatsAppInstances[req.query.key].demoteAdmin(
35 | req.body.id,
36 | req.body.users
37 | )
38 | return res.status(201).json({ error: false, data: data })
39 | }
40 |
41 | exports.listAll = async (req, res) => {
42 | const data = await WhatsAppInstances[req.query.key].getAllGroups(
43 | req.query.key
44 | )
45 | return res.status(201).json({ error: false, data: data })
46 | }
47 |
48 | exports.leaveGroup = async (req, res) => {
49 | const data = await WhatsAppInstances[req.query.key].leaveGroup(req.body.id)
50 | return res.status(201).json({ error: false, data: data })
51 | }
52 |
53 | exports.join = async (req, res) => {
54 | const data = await WhatsAppInstances[req.query.key].joinURL(req.body.url)
55 | return res.status(201).json({ error: false, data: data })
56 | }
57 |
58 | exports.getInviteCodeGroup = async (req, res) => {
59 | const data = await WhatsAppInstances[req.query.key].getInviteCodeGroup(
60 | req.body.id
61 | )
62 | return res
63 | .status(201)
64 | .json({ error: false, data})
65 | }
66 |
67 | exports.getInstanceInviteCodeGroup = async (req, res) => {
68 | const data = await WhatsAppInstances[
69 | req.query.key
70 | ].getInstanceInviteCodeGroup(req.body.id)
71 | return res
72 | .status(201)
73 | .json({ error: false, link: 'https://chat.whatsapp.com/' + data })
74 | }
75 |
76 | exports.getAllGroups = async (req, res) => {
77 | const instance = WhatsAppInstances[req.query.key]
78 | let data
79 | try {
80 | data = await instance.groupFetchAllParticipating()
81 | } catch (error) {
82 | //console.log(error)
83 | data = {}
84 | }
85 | return res.json({
86 | error: false,
87 | message: 'Grupos encontrados',
88 | data,
89 | })
90 | }
91 |
92 | exports.groupParticipantsUpdate = async (req, res) => {
93 | const data = await WhatsAppInstances[req.query.key].groupParticipantsUpdate(
94 | req.body.id,
95 | req.body.users,
96 | req.body.action
97 | )
98 | return res.status(201).json({ error: false, data: data })
99 | }
100 |
101 | exports.groupSettingUpdate = async (req, res) => {
102 | const data = await WhatsAppInstances[req.query.key].groupSettingUpdate(
103 | req.body.id,
104 | req.body.action
105 | )
106 | return res.status(201).json({ data })
107 | }
108 |
109 | exports.groupUpdateSubject = async (req, res) => {
110 |
111 | const data = await WhatsAppInstances[req.query.key].groupUpdateSubject(
112 | req.body.id,
113 | req.body.subject
114 | )
115 | return res.status(201).json({ data })
116 | }
117 |
118 | exports.groupUpdateDescription = async (req, res) => {
119 | const data = await WhatsAppInstances[req.query.key].groupUpdateDescription(
120 | req.body.id,
121 | req.body.description
122 | )
123 | return res.status(201).json({ data })
124 | }
125 |
126 | exports.groupInviteInfo = async (req, res) => {
127 | const data = await WhatsAppInstances[req.query.key].groupGetInviteInfo(
128 | req.body.url
129 | )
130 | return res.status(201).json({data: data })
131 | }
132 | exports.groupidinfo = async (req, res) => {
133 | const data = await WhatsAppInstances[req.query.key].groupidinfo(
134 | req.body.id
135 | )
136 | return res.status(201).json({data: data })
137 | }
138 |
139 | exports.groupJoin = async (req, res) => {
140 | const data = await WhatsAppInstances[req.query.key].groupAcceptInvite(
141 | req.body.code
142 | )
143 | return res.status(201).json({ error: false, data: data })
144 | }
145 |
--------------------------------------------------------------------------------
/src/api/controllers/instance.controller.js:
--------------------------------------------------------------------------------
1 | const { WhatsAppInstance } = require('../class/instance');
2 | const fs = require('fs').promises;
3 | const path = require('path');
4 | const config = require('../../config/config');
5 | const { Session } = require('../class/session');
6 | const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
7 |
8 | exports.init = async (req, res) => {
9 | let webhook = req.body.webhook || false;
10 | let webhookUrl = req.body.webhookUrl || false;
11 | let browser = req.body.browser || 'Minha Api';
12 | let ignoreGroups = req.body.ignoreGroups || false;
13 | let webhookEvents = req.body.webhookEvents || [];
14 | let messagesRead = req.body.messagesRead || false;
15 | let base64 = req.body.base64 || false;
16 |
17 | const key = req.body.key;
18 | const filePath = path.join('db/sessions.json');
19 |
20 | const data = await fs.readFile(filePath, 'utf-8');
21 | const sessions = JSON.parse(data);
22 | const sessionCount = sessions.length;
23 |
24 | if (process.env.MAX_INSTANCES) {
25 | const maxInstances = parseInt(process.env.MAX_INSTANCES, 10);
26 | if (maxInstances <= sessionCount) {
27 | return res.json({
28 | error: true,
29 | message: 'Limite de sessions criadas já foi atingido'
30 | });
31 | }
32 | }
33 | const valida = sessions.find(session => session.key === key);
34 |
35 | if (valida) {
36 | return res.json({
37 | error: true,
38 | message: 'Sessão já foi iniciada.'
39 | });
40 | } else {
41 | const appUrl = config.appUrl || req.protocol + '://' + req.headers.host;
42 |
43 | const filePath = path.join('db/sessions.json');
44 | const dataSession = await fs.readFile(filePath, 'utf-8');
45 | const sessions = JSON.parse(dataSession);
46 |
47 | sessions.push({ key: key, ignoreGroups: ignoreGroups, webhook: webhook, base64: base64, webhookUrl: webhookUrl, browser: browser, webhookEvents: webhookEvents, messagesRead: messagesRead });
48 |
49 | await fs.writeFile(filePath, JSON.stringify(sessions, null, 2), 'utf-8');
50 |
51 | const instance = new WhatsAppInstance(key, webhook, webhookUrl);
52 | const data = await instance.init();
53 | WhatsAppInstances[data.key] = instance;
54 | res.json({
55 | error: false,
56 | message: 'Instancia iniciada',
57 | key: data.key,
58 | webhook: {
59 | enabled: webhook,
60 | webhookUrl: webhookUrl,
61 | webhookEvents: webhookEvents
62 | },
63 | qrcode: {
64 | url: appUrl + '/instance/qr?key=' + data.key,
65 | },
66 | browser: browser,
67 | messagesRead: messagesRead,
68 | ignoreGroups: ignoreGroups,
69 | });
70 | }
71 | };
72 |
73 | exports.editar = async (req, res) => {
74 | let webhook = req.body.webhook || false;
75 | let webhookUrl = req.body.webhookUrl || false;
76 | let browser = req.body.browser || 'Minha Api';
77 | let ignoreGroups = req.body.ignoreGroups || false;
78 | let webhookEvents = req.body.webhookEvents || [];
79 | let messagesRead = req.body.messagesRead || false;
80 | let base64 = req.body.base64 || false;
81 |
82 | const key = req.body.key;
83 | const filePath = path.join('db/sessions.json');
84 | const data = await fs.readFile(filePath, 'utf-8');
85 | const sessions = JSON.parse(data);
86 | const index = sessions.findIndex(session => session.key === key);
87 |
88 | if (index !== -1) {
89 | sessions[index] = { key, ignoreGroups, webhook, base64, webhookUrl, browser, webhookEvents, messagesRead, ignoreGroups };
90 | await fs.writeFile(filePath, JSON.stringify(sessions, null, 2), 'utf-8');
91 |
92 | const instance = WhatsAppInstances[key];
93 | const data = await instance.init();
94 | res.json({
95 | error: false,
96 | message: 'Instancia editada',
97 | key: key,
98 | webhook: {
99 | enabled: webhook,
100 | webhookUrl: webhookUrl,
101 | webhookEvents: webhookEvents
102 | },
103 | browser: browser,
104 | messagesRead: messagesRead,
105 | ignoreGroups: ignoreGroups,
106 | });
107 | } else {
108 | return res.json({
109 | error: true,
110 | message: 'Sessão não localizada.'
111 | });
112 | }
113 | };
114 |
115 | exports.getcode = async (req, res) => {
116 | try {
117 | if (!req.body.number) {
118 | return res.json({
119 | error: true,
120 | message: 'Numero de telefone inválido'
121 | });
122 | } else {
123 | const instance = WhatsAppInstances[req.query.key];
124 | data = await instance.getInstanceDetail(req.body.key);
125 |
126 | if (data.phone_connected === true) {
127 | return res.json({
128 | error: true,
129 | message: 'Telefone já conectado'
130 | });
131 | } else {
132 | const number = await WhatsAppInstances[req.query.key].getWhatsappCode(req.body.number);
133 | const code = await WhatsAppInstances[req.query.key].instance?.sock?.requestPairingCode(number);
134 | return res.json({
135 | error: false,
136 | code: code
137 | });
138 | }
139 | }
140 | } catch (e) {
141 | return res.json({
142 | error: true,
143 | message: 'instância não localizada'
144 | });
145 | }
146 | };
147 |
148 | exports.ativas = async (req, res) => {
149 | if (req.query.active) {
150 | let instance = [];
151 | const db = mongoClient.db('whatsapp-api');
152 | const result = await db.listCollections().toArray();
153 | result.forEach((collection) => {
154 | instance.push(collection.name);
155 | });
156 |
157 | return res.json({
158 | data: instance
159 | });
160 | }
161 |
162 | let instance = Object.keys(WhatsAppInstances).map(async (key) =>
163 | WhatsAppInstances[key].getInstanceDetail(key)
164 | );
165 | let data = await Promise.all(instance);
166 |
167 | return {
168 | data: data
169 | };
170 | };
171 |
172 | exports.qr = async (req, res) => {
173 | const verifica = await exports.validar(req, res);
174 | if (verifica == true) {
175 | const instance = WhatsAppInstances[req.query.key];
176 | let data;
177 | try {
178 | data = await instance.getInstanceDetail(req.query.key);
179 | } catch (error) {
180 | data = {};
181 | }
182 | if (data.phone_connected === true) {
183 | return res.json({
184 | error: true,
185 | message: 'Telefone já conectado'
186 | });
187 | } else {
188 | try {
189 | const qrcode = await WhatsAppInstances[req.query.key]?.instance.qr;
190 | res.render('qrcode', {
191 | qrcode: qrcode,
192 | });
193 | } catch {
194 | res.json({
195 | qrcode: '',
196 | });
197 | }
198 | }
199 | } else {
200 | return res.json({
201 | error: true,
202 | message: 'Instâcncia não existente'
203 | });
204 | }
205 | };
206 |
207 | exports.qrbase64 = async (req, res) => {
208 | const verifica = await exports.validar(req, res);
209 | if (verifica == true) {
210 | const instance = WhatsAppInstances[req.query.key];
211 | let data;
212 | try {
213 | data = await instance.getInstanceDetail(req.query.key);
214 | } catch (error) {
215 | data = {};
216 | }
217 | if (data.phone_connected === true) {
218 | return res.json({
219 | error: true,
220 | message: 'Telefone já conectado'
221 | });
222 | } else {
223 | try {
224 | const qrcode = await WhatsAppInstances[req.query.key]?.instance.qr;
225 | res.json({
226 | error: false,
227 | message: 'QR Base64 fetched successfully',
228 | qrcode: qrcode,
229 | });
230 | } catch {
231 | res.json({
232 | qrcode: '',
233 | });
234 | }
235 | }
236 | } else {
237 | return res.json({
238 | error: true,
239 | message: 'Instâcncia não existente'
240 | });
241 | }
242 | };
243 |
244 | exports.validar = async (req, res) => {
245 | const verifica = await exports.ativas(req, res);
246 | const existe = verifica.data.some(item => item.instance_key === req.query.key);
247 | if (existe) {
248 | return true;
249 | } else {
250 | return false;
251 | }
252 | };
253 |
254 | exports.info = async (req, res) => {
255 | const verifica = await exports.validar(req, res);
256 | if (verifica == true) {
257 | const instance = WhatsAppInstances[req.query.key];
258 | let data;
259 | try {
260 | data = await instance.getInstanceDetail(req.query.key);
261 | } catch (error) {
262 | data = {};
263 | }
264 | return res.json({
265 | error: false,
266 | message: 'Instance fetched successfully',
267 | instance_data: data,
268 | });
269 | } else {
270 | return res.json({
271 | error: true,
272 | message: 'Instâcncia não existente'
273 | });
274 | }
275 | };
276 |
277 | exports.infoManager = async (key) => {
278 | try {
279 | const instance = WhatsAppInstances[key];
280 | const data = await instance.getInstanceDetail(key);
281 | return data;
282 | } catch (error) {
283 | return {error:true, message:'erro ao encontrar a instância, tente ovamente'}
284 | }
285 |
286 | };
287 |
288 |
289 | exports.restore = async (req, res, next) => {
290 | try {
291 | let instance = Object.keys(WhatsAppInstances).map(async (key) =>
292 | WhatsAppInstances[key].getInstanceDetail(key)
293 | );
294 | let data = await Promise.all(instance);
295 |
296 | if (data.length > 0) {
297 | return res.json({
298 | error: false,
299 | message: 'All instances restored',
300 | data: data,
301 | });
302 | } else {
303 | const session = new Session();
304 | let restoredSessions = await session.restoreSessions();
305 |
306 | return res.json({
307 | error: false,
308 | message: 'All instances restored',
309 | data: restoredSessions,
310 | });
311 | }
312 | } catch (error) {
313 | next(error);
314 | }
315 | };
316 |
317 | exports.logout = async (req, res) => {
318 | const instance = WhatsAppInstances[req.query.key];
319 | let errormsg;
320 | try {
321 | await WhatsAppInstances[req.query.key].instance?.sock?.logout();
322 | await instance.deleteFolder('db/' + req.query.key);
323 | await instance.init();
324 | } catch (error) {
325 |
326 | errormsg = error;
327 | }
328 | return res.json({
329 | error: false,
330 | message: 'logout successfull',
331 | errormsg: errormsg ? errormsg : null,
332 | });
333 | };
334 |
335 | exports.delete = async (req, res) => {
336 | let errormsg;
337 | const verifica = await exports.validar(req, res);
338 | if (verifica == true) {
339 | try {
340 | await WhatsAppInstances[req.query.key].deleteInstance(req.query.key);
341 | delete WhatsAppInstances[req.query.key];
342 | } catch (error) {
343 | errormsg = error;
344 | }
345 | return res.json({
346 | error: false,
347 | message: 'Instance deleted successfully',
348 | data: errormsg ? errormsg : null,
349 | });
350 | } else {
351 | return res.json({
352 | error: false,
353 | message: 'Instance deleted successfully',
354 | data: errormsg ? errormsg : null,
355 | });
356 | }
357 | };
358 |
359 | exports.list = async (req, res) => {
360 | let instance = Object.keys(WhatsAppInstances).map(async (key) =>
361 | WhatsAppInstances[key].getInstanceDetail(key)
362 | );
363 | let data = await Promise.all(instance);
364 | return res.json({
365 | error: false,
366 | message: 'All instance listed',
367 | data: data,
368 | });
369 | };
370 |
371 | exports.deleteInactives = async (req, res) => {
372 | let instance = Object.keys(WhatsAppInstances).map(async (key) =>
373 | WhatsAppInstances[key].getInstanceDetail(key)
374 | );
375 | let data = await Promise.all(instance);
376 | const deletePromises = [];
377 | for (const instance of data) {
378 | if (instance.phone_connected === undefined || instance.phone_connected === false) {
379 | const deletePromise = WhatsAppInstances[instance.instance_key].deleteInstance(instance.instance_key);
380 | delete WhatsAppInstances[instance.instance_key];
381 | deletePromises.push(deletePromise);
382 | }
383 | await sleep(150);
384 | }
385 | await Promise.all(deletePromises);
386 | return res.json({
387 | error: false,
388 | message: 'All inactive sessions deleted',
389 | });
390 | };
391 |
392 | exports.deleteAll = async (req, res) => {
393 | let instance = Object.keys(WhatsAppInstances).map(async (key) =>
394 | WhatsAppInstances[key].getInstanceDetail(key)
395 | );
396 | let data = await Promise.all(instance);
397 | const deletePromises = [];
398 | for (const instance of data) {
399 | const deletePromise = WhatsAppInstances[instance.instance_key].deleteInstance(instance.instance_key);
400 | delete WhatsAppInstances[instance.instance_key];
401 | deletePromises.push(deletePromise);
402 | await sleep(150);
403 | }
404 | await Promise.all(deletePromises);
405 | return res.json({
406 | error: false,
407 | message: 'All sessions deleted',
408 | });
409 | };
410 |
--------------------------------------------------------------------------------
/src/api/controllers/message.controller.js:
--------------------------------------------------------------------------------
1 | exports.Text = async (req, res) => {
2 | const data = await WhatsAppInstances[req.query.key].sendTextMessage(
3 | req.body
4 | )
5 | return res.status(201).json({ error: false, data: data })
6 | }
7 | exports.TextManager = async (instanceKey, message) => {
8 | const data = await WhatsAppInstances[instanceKey].sendTextMessage(
9 | message
10 | )
11 | return { error: false, data: data }
12 | }
13 |
14 | exports.Image = async (req, res) => {
15 | const data = await WhatsAppInstances[req.query.key].sendMediaFile(
16 | req.body.id,
17 | req.file,
18 | 'image',
19 | req.mimetype,
20 | req.body?.caption
21 | )
22 | return res.status(201).json({ error: false, data: data })
23 | }
24 | exports.sendurlfile = async (req, res) => {
25 | const data = await WhatsAppInstances[req.query.key].sendMediaFile(
26 | req.body,
27 | 'url'
28 | )
29 | return res.status(201).json({ error: false, data: data })
30 | }
31 | exports.sendbase64file = async (req, res) => {
32 | const data = await WhatsAppInstances[req.query.key].sendMediaFile(
33 | req.body,
34 | 'base64'
35 | )
36 | return res.status(201).json({ error: false, data: data })
37 | }
38 | exports.imageFile = async (req, res) => {
39 | const data = await WhatsAppInstances[req.query.key].sendMedia(
40 | req.body.id,
41 | req.body.userType,
42 | req.file,
43 | 'image',
44 | req.body?.caption,
45 | req.body?.replyFrom,
46 | req.body?.caption,
47 | req.body?.replyFrom,
48 | req.body?.delay
49 | )
50 | return res.status(201).json({ error: false, data: data })
51 | }
52 | exports.audioFile = async (req, res) => {
53 |
54 | const data = await WhatsAppInstances[req.query.key].sendMedia(
55 | req.body.id,
56 | req.body.userType,
57 | req.file,
58 | 'audio',
59 | req.body?.caption,
60 | req.body?.replyFrom,
61 | req.body?.delay
62 | )
63 | return res.status(201).json({ error: false, data: data })
64 | }
65 |
66 | exports.Video = async (req, res) => {
67 | const data = await WhatsAppInstances[req.query.key].sendMedia(
68 | req.body.id,
69 | req.body.userType,
70 | req.file,
71 | 'video',
72 | req.body?.caption,
73 | req.body?.replyFrom,
74 | req.body?.delay
75 | )
76 | return res.status(201).json({ error: false, data: data })
77 | }
78 |
79 |
80 | exports.Audio = async (req, res) => {
81 | const data = await WhatsAppInstances[req.query.key].sendMediaFile(
82 | req.body.id,
83 | req.file,
84 | req.mimetype,
85 | 'audio'
86 | )
87 | return res.status(201).json({ error: false, data: data })
88 | }
89 |
90 | exports.Document = async (req, res) => {
91 | const data = await WhatsAppInstances[req.query.key].sendMedia(
92 | req.body.id,
93 | req.body.userType,
94 | req.file,
95 | 'document',
96 | req.body?.caption,
97 | req.body?.replyFrom,
98 | req.body?.delay
99 | )
100 | return res.status(201).json({ error: false, data: data })
101 | }
102 |
103 | exports.Mediaurl = async (req, res) => {
104 | const data = await WhatsAppInstances[req.query.key].sendUrlMediaFile(
105 | req.body.id,
106 | req.body.url,
107 | req.body.type, // Types are [image, video, audio, document]
108 | req.body.mimetype, // mimeType of mediaFile / Check Common mimetypes in `https://mzl.la/3si3and`
109 | req.body.caption
110 | )
111 | return res.status(201).json({ error: false, data: data })
112 | }
113 |
114 | exports.Button = async (req, res) => {
115 | // console.log(res.body)
116 | const data = await WhatsAppInstances[req.query.key].sendButtonMessage(
117 | req.body.id,
118 | req.body.btndata
119 | )
120 | return res.status(201).json({ error: false, data: data })
121 | }
122 |
123 | exports.Contact = async (req, res) => {
124 | const data = await WhatsAppInstances[req.query.key].sendContactMessage(
125 | req.body.id,
126 | req.body.vcard
127 | )
128 | return res.status(201).json({ error: false, data: data })
129 | }
130 |
131 | exports.List = async (req, res) => {
132 | const data = await WhatsAppInstances[req.query.key].sendListMessage(
133 | req.body.id,
134 | req.body.type,
135 | req.body.options,
136 | req.body.groupOptions,
137 | req.body.msgdata
138 | )
139 | return res.status(201).json({ error: false, data: data })
140 | }
141 |
142 | exports.MediaButton = async (req, res) => {
143 | const data = await WhatsAppInstances[req.query.key].sendMediaButtonMessage(
144 | req.body.id,
145 | req.body.btndata
146 | )
147 | return res.status(201).json({ error: false, data: data })
148 | }
149 |
150 | exports.SetStatus = async (req, res) => {
151 | const presenceList = [
152 | 'unavailable',
153 | 'available',
154 | 'composing',
155 | 'recording',
156 | 'paused',
157 | ]
158 | if (presenceList.indexOf(req.body.status) === -1) {
159 | return res.status(400).json({
160 | error: true,
161 | message:
162 | 'status parameter must be one of ' + presenceList.join(', '),
163 | })
164 | }
165 |
166 | const data = await WhatsAppInstances[req.query.key]?.setStatus(
167 | req.body.status,
168 | req.body.id,
169 | req.body.type,
170 | req.body.delay
171 |
172 | )
173 | return res.status(201).json({ error: false, data: data })
174 | }
175 |
176 | exports.Read = async (req, res) => {
177 | const data = await WhatsAppInstances[req.query.key].readMessage(req.body.msg)
178 | return res.status(201).json({ error: false, data: data })
179 | }
180 |
181 | exports.React = async (req, res) => {
182 | const data = await WhatsAppInstances[req.query.key].reactMessage(req.body.id, req.body.key, req.body.emoji)
183 | return res.status(201).json({ error: false, data: data })
184 | }
185 |
--------------------------------------------------------------------------------
/src/api/controllers/misc.controller.js:
--------------------------------------------------------------------------------
1 | exports.onWhatsapp = async (req, res) => {
2 | // eslint-disable-next-line no-unsafe-optional-chaining
3 | const data = await WhatsAppInstances[req.query.key]?.verifyId(req.body.id)
4 | return res.status(201).json({ error: false, data: data })
5 | }
6 |
7 | exports.downProfile = async (req, res) => {
8 | const data = await WhatsAppInstances[req.query.key]?.DownloadProfile(
9 | req.body.id,
10 | req?.body?.group
11 | )
12 | return res.status(201).json({ error: false, data: data })
13 | }
14 |
15 | exports.getStatus = async (req, res) => {
16 | const data = await WhatsAppInstances[req.query.key]?.getUserStatus(
17 | req.body.id
18 | )
19 | return res.status(201).json({ error: false, data: data })
20 | }
21 | exports.contacts = async (req, res) => {
22 |
23 | const data = await WhatsAppInstances[req.query.key]?.contacts(
24 | req.query.key
25 | )
26 | return res.status(201).json({data})
27 | }
28 | exports.mystatus = async (req, res) => {
29 | const data = await WhatsAppInstances[req.query.key]?.mystatus(
30 | req.body.status
31 | )
32 | return res.status(201).json({data})
33 | }
34 | exports.chats = async (req, res) => {
35 | const data = await WhatsAppInstances[req.query.key]?.chats(
36 | req.body.id
37 | )
38 | return res.status(201).json({ error: false, data: data })
39 | }
40 |
41 | exports.blockUser = async (req, res) => {
42 | const data = await WhatsAppInstances[req.query.key]?.blockUnblock(
43 | req.body.id,
44 | req.body.block_status
45 | )
46 | if (req.query.block_status == 'block') {
47 | return res
48 | .status(201)
49 | .json({ error: false, message: 'Contact Blocked' })
50 | } else
51 | return res
52 | .status(201)
53 | .json({ error: false, message: 'Contact Unblocked' })
54 | }
55 |
56 | exports.updateProfilePicture = async (req, res) => {
57 | const data = await WhatsAppInstances[req.query.key].updateProfilePicture(
58 | req.body.id,
59 | req.body.url,
60 | req.body.type
61 | )
62 | return res.status(201).json({ error: false, data: data })
63 | }
64 |
65 | exports.updateProfilePictureManager = async (instanceKey, id, url, type) => {
66 | try {
67 | const data = await WhatsAppInstances[instanceKey].updateProfilePicture(id, url, type);
68 | return data
69 | } catch (error) {
70 | return { error: true, data: error };
71 | }
72 | };
73 |
74 |
75 | exports.getUserOrGroupById = async (req, res) => {
76 | const data = await WhatsAppInstances[req.query.key].getUserOrGroupById(
77 | req.body.id
78 | )
79 | return res.status(201).json({ error: false, data: data })
80 | }
81 |
--------------------------------------------------------------------------------
/src/api/errors/api.error.js:
--------------------------------------------------------------------------------
1 | const ExtendableError = require('../errors/extendable.error')
2 |
3 | class APIError extends ExtendableError {
4 | constructor({ message, errors, status = 500 }) {
5 | super({
6 | message,
7 | errors,
8 | status,
9 | })
10 | }
11 | }
12 |
13 | module.exports = APIError
14 |
--------------------------------------------------------------------------------
/src/api/errors/extendable.error.js:
--------------------------------------------------------------------------------
1 | class ExtendableError extends Error {
2 | constructor({ message, errors, status }) {
3 | super(message)
4 | this.name = this.constructor.name
5 | this.message = message
6 | this.errors = errors
7 | this.status = status
8 | }
9 | }
10 |
11 | module.exports = ExtendableError
12 |
--------------------------------------------------------------------------------
/src/api/helper/downloadMsg.js:
--------------------------------------------------------------------------------
1 | const { downloadContentFromMessage } = require('@whiskeysockets/baileys')
2 | const fs = require('fs').promises;
3 | const { v4: uuidv4 } = require('uuid')
4 |
5 | module.exports = async function downloadMessage(msg, msgType) {
6 | let buffer = Buffer.from([])
7 | try {
8 | const stream = await downloadContentFromMessage(msg, msgType)
9 | for await (const chunk of stream) {
10 | buffer = Buffer.concat([buffer, chunk])
11 | }
12 | } catch {
13 | return console.log('error downloading file-message')
14 | }
15 |
16 | return buffer.toString('base64')
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/api/helper/genVc.js:
--------------------------------------------------------------------------------
1 | module.exports = function generateVC(data) {
2 | const result =
3 | 'BEGIN:VCARD\n' +
4 | 'VERSION:3.0\n' +
5 | `FN:${data.fullName}\n` +
6 | `ORG:${data.organization};\n` +
7 | `TEL;type=CELL;type=VOICE;waid=${data.phoneNumber}:${data.phoneNumber}\n` +
8 | 'END:VCARD'
9 |
10 | return result
11 | }
12 |
--------------------------------------------------------------------------------
/src/api/helper/processbtn.js:
--------------------------------------------------------------------------------
1 | module.exports = function processButton(buttons) {
2 | const preparedButtons = []
3 |
4 | buttons.map((button) => {
5 | if (button.type == 'replyButton') {
6 | preparedButtons.push({
7 | quickReplyButton: {
8 | displayText: button.title ?? '',
9 | },
10 | })
11 | }
12 |
13 | if (button.type == 'callButton') {
14 | preparedButtons.push({
15 | callButton: {
16 | displayText: button.title ?? '',
17 | phoneNumber: button.payload ?? '',
18 | },
19 | })
20 | }
21 | if (button.type == 'urlButton') {
22 | preparedButtons.push({
23 | urlButton: {
24 | displayText: button.title ?? '',
25 | url: button.payload ?? '',
26 | },
27 | })
28 | }
29 | })
30 | return preparedButtons
31 | }
32 |
--------------------------------------------------------------------------------
/src/api/helper/sleep.js:
--------------------------------------------------------------------------------
1 | module.exports = function sleep(ms) {
2 | return new Promise((resolve) => {
3 | setTimeout(resolve, ms)
4 | })
5 | }
6 |
--------------------------------------------------------------------------------
/src/api/middlewares/adminToken.js:
--------------------------------------------------------------------------------
1 | const config = require('../../config/config')
2 |
3 | function adminToken(req, res, next) {
4 | const adminToken = req.query.admintoken;
5 |
6 | const token = adminToken;
7 |
8 |
9 | if (!token) {
10 | return res.status(403).send({
11 | error: true,
12 | message: 'admintoken Nao fornecido via get ',
13 | })
14 | }
15 |
16 | if (config.adminToken !== token) {
17 | return res
18 | .status(403)
19 | .send({ error: true, message: 'Token de Admin inválido' })
20 | }
21 | next()
22 | }
23 |
24 | module.exports = adminToken
25 |
--------------------------------------------------------------------------------
/src/api/middlewares/error.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | const APIError = require('../../api/errors/api.error')
3 |
4 | const handler = (err, req, res, next) => {
5 | const statusCode = err.statusCode ? err.statusCode : 500
6 |
7 | res.setHeader('Content-Type', 'application/json')
8 | res.status(statusCode)
9 | res.json({
10 | error: true,
11 | code: statusCode,
12 | message: err.message,
13 | })
14 | }
15 |
16 | exports.handler = handler
17 |
18 | exports.notFound = (req, res, next) => {
19 | const err = new APIError({
20 | message: 'Not found',
21 | status: 404,
22 | })
23 | return handler(err, req, res)
24 | }
25 |
--------------------------------------------------------------------------------
/src/api/middlewares/keyCheck.js:
--------------------------------------------------------------------------------
1 | function keyVerification(req, res, next) {
2 | const key = req.query['key']?.toString()
3 | if (!key) {
4 | return res
5 | .status(403)
6 | .send({ error: true, message: 'no key query was present' })
7 | }
8 | const instance = WhatsAppInstances[key]
9 | if (!instance) {
10 | return res
11 | .status(403)
12 | .send({ error: true, message: 'invalid key supplied' })
13 | }
14 | next()
15 | }
16 |
17 | module.exports = keyVerification
18 |
--------------------------------------------------------------------------------
/src/api/middlewares/loginCheck.js:
--------------------------------------------------------------------------------
1 | function loginVerification(req, res, next) {
2 | const key = req.query['key']?.toString()
3 | if (!key) {
4 | return res
5 | .status(403)
6 | .send({ error: true, message: 'no key query was present' })
7 | }
8 | const instance = WhatsAppInstances[key]
9 | if (!instance.instance?.online) {
10 | return res
11 | .status(401)
12 | .send({ error: true, message: "phone isn't connected" })
13 | }
14 | next()
15 | }
16 |
17 | module.exports = loginVerification
18 |
--------------------------------------------------------------------------------
/src/api/middlewares/managerLogin.js:
--------------------------------------------------------------------------------
1 | const config = require('../../config/config')
2 |
3 | function tokenVerification(req, res, next) {
4 | const bearer = req.headers.authorization
5 | const token = bearer?.slice(7)?.toString()
6 |
7 | if (!token) {
8 | return res.status(403).send({
9 | error: true,
10 | message: 'no bearer token header was present',
11 | })
12 | }
13 |
14 | if (config.token !== token) {
15 | return res
16 | .status(403)
17 | .send({ error: true, message: 'invalid bearer token supplied' })
18 | }
19 | next()
20 | }
21 |
22 | module.exports = tokenVerification
23 |
--------------------------------------------------------------------------------
/src/api/middlewares/tokenCheck.js:
--------------------------------------------------------------------------------
1 | const config = require('../../config/config')
2 |
3 | function tokenVerification(req, res, next) {
4 | const bearer = req.headers.authorization
5 | const token = bearer?.slice(7)?.toString()
6 |
7 | if (!token) {
8 | return res.status(403).send({
9 | error: true,
10 | message: 'no bearer token header was present',
11 | })
12 | }
13 |
14 | if (config.token !== token) {
15 | return res
16 | .status(403)
17 | .send({ error: true, message: 'invalid bearer token supplied' })
18 | }
19 | next()
20 | }
21 |
22 | module.exports = tokenVerification
23 |
--------------------------------------------------------------------------------
/src/api/models/chat.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const chatSchema = new mongoose.Schema({
4 | key: {
5 | type: String,
6 | required: [true, 'key is missing'],
7 | unique: true,
8 | },
9 | chat: {
10 | type: Array,
11 | },
12 | })
13 |
14 | const Chat = mongoose.model('Chat', chatSchema)
15 |
16 | module.exports = Chat
17 |
--------------------------------------------------------------------------------
/src/api/routes/group.route.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const controller = require('../controllers/group.controller')
3 | const keyVerify = require('../middlewares/keyCheck')
4 | const loginVerify = require('../middlewares/loginCheck')
5 | const { protectRoutes } = require('../../config/config');
6 | const tokenCheck = require('../middlewares/tokenCheck');
7 | const router = express.Router()
8 | if (protectRoutes) {
9 | router.use(tokenCheck);
10 | }
11 |
12 |
13 |
14 | router.route('/create').post(keyVerify, loginVerify, controller.create)
15 | router.route('/listall').post(keyVerify, loginVerify, controller.listAll)
16 | router.route('/leave').post(keyVerify, loginVerify, controller.leaveGroup)
17 |
18 | router
19 | .route('/inviteuser')
20 | .post(keyVerify, loginVerify, controller.addNewParticipant)
21 | router
22 | .route('/removeuser')
23 | .post(keyVerify, loginVerify, controller.removeuser)
24 | router.route('/makeadmin').post(keyVerify, loginVerify, controller.makeAdmin)
25 | router
26 | .route('/demoteadmin')
27 | .post(keyVerify, loginVerify, controller.demoteAdmin)
28 | router
29 | .route('/getinvitecode')
30 | .post(keyVerify, loginVerify, controller.getInviteCodeGroup)
31 | router
32 | router
33 | .route('/join')
34 | .post(keyVerify, loginVerify, controller.join)
35 | router
36 | .route('/getinstanceinvitecode')
37 | .post(keyVerify, loginVerify, controller.getInstanceInviteCodeGroup)
38 | router
39 | .route('/getallgroups')
40 | .get(keyVerify, loginVerify, controller.getAllGroups)
41 | router
42 | .route('/participantsupdate')
43 | .post(keyVerify, loginVerify, controller.groupParticipantsUpdate)
44 | router
45 | .route('/settingsupdate')
46 | .post(keyVerify, loginVerify, controller.groupSettingUpdate)
47 | router
48 | .route('/updatesubject')
49 | .post(keyVerify, loginVerify, controller.groupUpdateSubject)
50 | router
51 | .route('/updatedescription')
52 | .post(keyVerify, loginVerify, controller.groupUpdateDescription)
53 | router
54 | .route('/groupurlinfo')
55 | .post(keyVerify, loginVerify, controller.groupInviteInfo)
56 | router
57 | .route('/groupidinfo')
58 | .post(keyVerify, loginVerify, controller.groupidinfo)
59 | router.route('/groupjoin').post(keyVerify, loginVerify, controller.groupJoin)
60 | module.exports = router
61 |
--------------------------------------------------------------------------------
/src/api/routes/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const instanceRoutes = require('./instance.route');
4 | const messageRoutes = require('./message.route');
5 | const miscRoutes = require('./misc.route');
6 | const groupRoutes = require('./group.route');
7 | //const managerRoutes = require('./manager.route');
8 |
9 | router.get('/status', (req, res) => res.send('OK'));
10 | router.use('/instance', instanceRoutes);
11 | router.use('/message', messageRoutes);
12 | router.use('/group', groupRoutes);
13 | router.use('/misc', miscRoutes);
14 | //router.get('/', (req, res) => res.redirect('/manager/login'));
15 | //router.use('/manager', managerRoutes); // Adiciona as rotas de gerenciamento aqui
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/src/api/routes/instance.route.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const controller = require('../controllers/instance.controller')
3 | const keyVerify = require('../middlewares/keyCheck')
4 | const loginVerify = require('../middlewares/loginCheck')
5 | const { protectRoutes } = require('../../config/config');
6 | const tokenCheck = require('../middlewares/tokenCheck');
7 | const adminToken = require('../middlewares/adminToken');
8 |
9 |
10 |
11 | const router = express.Router()
12 | if (protectRoutes) {
13 | router.use(tokenCheck);
14 | }
15 | router.route('/init').post(adminToken,controller.init)
16 | router.route('/editar').post(controller.editar)
17 | router.route('/qr').get(keyVerify, controller.qr)
18 | router.route('/qrbase64').get(keyVerify, controller.qrbase64)
19 | router.route('/info').get(keyVerify, controller.info)
20 | router.route('/restore').get(adminToken,controller.restore)
21 | router.route('/logout').get(keyVerify, loginVerify, controller.logout)
22 | router.route('/delete').get(keyVerify, controller.delete)
23 | router.route('/list').get(adminToken,controller.list)
24 | router.route('/deleteInactives').get(adminToken,controller.deleteInactives)
25 | router.route('/deleteAll').get(adminToken,controller.deleteAll)
26 | router.route('/getcode').post(keyVerify,controller.getcode)
27 |
28 | module.exports = router
29 |
--------------------------------------------------------------------------------
/src/api/routes/message.route.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const controller = require('../controllers/message.controller')
3 | const keyVerify = require('../middlewares/keyCheck')
4 | const loginVerify = require('../middlewares/loginCheck')
5 | const multer = require('multer')
6 |
7 |
8 | const router = express.Router()
9 | const storage = multer.memoryStorage()
10 | const upload = multer({ storage: storage, inMemory: true }).single('file')
11 | const { protectRoutes } = require('../../config/config');
12 | const tokenCheck = require('../middlewares/tokenCheck');
13 |
14 | if (protectRoutes) {
15 | router.use(tokenCheck);
16 | }
17 |
18 | router.route('/text').post(keyVerify, loginVerify, controller.Text)
19 | router.route('/image').post(keyVerify, loginVerify, upload, controller.Image)
20 | router.route('/sendurlfile').post(keyVerify, loginVerify, upload, controller.sendurlfile)
21 | router.route('/sendbase64file').post(keyVerify, loginVerify, upload, controller.sendbase64file)
22 | router.route('/imagefile').post(keyVerify, loginVerify, upload, controller.imageFile)
23 | router.route('/audiofile').post(keyVerify, loginVerify, upload, controller.audioFile)
24 | router.route('/video').post(keyVerify, loginVerify, upload, controller.Video)
25 | router.route('/audio').post(keyVerify, loginVerify, upload, controller.Audio)
26 | router.route('/doc').post(keyVerify, loginVerify, upload, controller.Document)
27 | router.route('/mediaurl').post(keyVerify, loginVerify, controller.Mediaurl)
28 | router.route('/button').post(keyVerify, loginVerify, controller.Button)
29 | router.route('/contact').post(keyVerify, loginVerify, controller.Contact)
30 | router.route('/list').post(keyVerify, loginVerify, controller.List)
31 | router.route('/setstatus').post(keyVerify, loginVerify, controller.SetStatus)
32 | router
33 | .route('/mediabutton')
34 | .post(keyVerify, loginVerify, controller.MediaButton)
35 | router.route("/read").post(keyVerify, loginVerify, controller.Read)
36 | router.route("/react").post(keyVerify, loginVerify, controller.React)
37 |
38 | module.exports = router
39 |
--------------------------------------------------------------------------------
/src/api/routes/misc.route.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const controller = require('../controllers/misc.controller')
3 | const keyVerify = require('../middlewares/keyCheck')
4 | const loginVerify = require('../middlewares/loginCheck')
5 | const { protectRoutes } = require('../../config/config');
6 | const tokenCheck = require('../middlewares/tokenCheck');
7 | const router = express.Router()
8 | if (protectRoutes) {
9 | router.use(tokenCheck);
10 | }
11 |
12 | router.route('/onwhatsapp').post(keyVerify, loginVerify, controller.onWhatsapp)
13 | router.route('/downProfile').post(keyVerify, loginVerify, controller.downProfile)
14 | router.route('/getStatus').post(keyVerify, loginVerify, controller.getStatus)
15 | router.route('/blockUser').post(keyVerify, loginVerify, controller.blockUser)
16 | router.route('/contacts').get(keyVerify, loginVerify, controller.contacts)
17 | router.route('/chats').post(keyVerify, loginVerify, controller.chats)
18 | router.route('/mystatus').post(keyVerify, loginVerify, controller.mystatus)
19 | router
20 | .route('/updateProfilePicture')
21 | .post(keyVerify, loginVerify, controller.updateProfilePicture)
22 | router
23 | .route('/getuserorgroupbyid')
24 | .get(keyVerify, loginVerify, controller.getUserOrGroupById)
25 | module.exports = router
26 |
--------------------------------------------------------------------------------
/src/api/views/qrcode.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WhatsApp QrCode
8 |
15 |
16 |
17 | Escanei o qr_code para usar o whatsapp:
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/config/config.js:
--------------------------------------------------------------------------------
1 | // Port number
2 | const PORT = process.env.PORT || '3333'
3 | const TOKEN = process.env.TOKEN || ''
4 | const ADMINTOKEN = process.env.ADMINTOKEN || ''
5 | const PROTECT_ROUTES = !!(
6 | process.env.PROTECT_ROUTES && process.env.PROTECT_ROUTES === 'true'
7 | )
8 |
9 | const RESTORE_SESSIONS_ON_START_UP = !!(
10 | process.env.RESTORE_SESSIONS_ON_START_UP &&
11 | process.env.RESTORE_SESSIONS_ON_START_UP === 'true'
12 | )
13 |
14 | const videoMimeTypes = process.env.videoMimeTypes?.split(',')
15 | const audioMimeTypes = process.env.audioMimeTypes?.split(',')
16 | const documentMimeTypes = process.env.documentMimeTypes?.split(',')
17 | const imageMimeTypes = process.env.imageMimeTypes?.split(',')
18 | const IGNORE_GROUPS = process.env.IGNORE_GROUPS
19 | const APP_URL = process.env.APP_URL || false
20 |
21 | const LOG_LEVEL = process.env.LOG_LEVEL
22 |
23 | const INSTANCE_MAX_RETRY_QR = process.env.INSTANCE_MAX_RETRY_QR || 2
24 |
25 | const CLIENT_PLATFORM = process.env.CLIENT_PLATFORM || 'Whatsapp MD'
26 | const CLIENT_BROWSER = process.env.CLIENT_BROWSER || 'Chrome'
27 | const CLIENT_VERSION = process.env.CLIENT_VERSION || '4.0.0'
28 |
29 | // Enable or disable mongodb
30 | const MONGODB_ENABLED = !!(
31 | process.env.MONGODB_ENABLED && process.env.MONGODB_ENABLED === 'true'
32 | )
33 | // URL of the Mongo DB
34 | const MONGODB_URL =
35 | process.env.MONGODB_URL || 'mongodb://127.0.0.1:27017/WhatsAppInstance'
36 | // Enable or disable webhook globally on project
37 | const WEBHOOK_ENABLED = !!(
38 | process.env.WEBHOOK_ENABLED && process.env.WEBHOOK_ENABLED === 'true'
39 | )
40 | // Webhook URL
41 | const WEBHOOK_URL = process.env.WEBHOOK_URL
42 | // Receive message content in webhook (Base64 format)
43 | const WEBHOOK_BASE64 = !!(
44 | process.env.WEBHOOK_BASE64 && process.env.WEBHOOK_BASE64 === 'true'
45 | )
46 | // allowed events which should be sent to webhook
47 | const WEBHOOK_ALLOWED_EVENTS = process.env.WEBHOOK_ALLOWED_EVENTS?.split(',') || ['all']
48 | // Mark messages as seen
49 | const MARK_MESSAGES_READ = !!(
50 | process.env.MARK_MESSAGES_READ && process.env.MARK_MESSAGES_READ === 'true'
51 | )
52 |
53 | module.exports = {
54 | port: PORT,
55 | token: TOKEN,
56 | adminToken: ADMINTOKEN,
57 | restoreSessionsOnStartup: RESTORE_SESSIONS_ON_START_UP,
58 | appUrl: APP_URL,
59 | log: {
60 | level: LOG_LEVEL,
61 | },
62 | instance: {
63 | maxRetryQr: INSTANCE_MAX_RETRY_QR,
64 | },
65 | mongoose: {
66 | enabled: MONGODB_ENABLED,
67 | url: MONGODB_URL,
68 | options: {
69 | // useCreateIndex: true,
70 | useNewUrlParser: true,
71 | useUnifiedTopology: true,
72 | },
73 | },
74 | browser: {
75 | platform: CLIENT_PLATFORM,
76 | browser: CLIENT_BROWSER,
77 | version: CLIENT_VERSION,
78 | },
79 | webhookEnabled: WEBHOOK_ENABLED,
80 | webhookUrl: WEBHOOK_URL,
81 | webhookBase64: WEBHOOK_BASE64,
82 | protectRoutes: PROTECT_ROUTES,
83 | markMessagesRead: MARK_MESSAGES_READ,
84 | webhookAllowedEvents: WEBHOOK_ALLOWED_EVENTS,
85 | IGNORE_GROUPS: IGNORE_GROUPS,
86 | videoMimeTypes: videoMimeTypes,
87 | audioMimeTypes: audioMimeTypes,
88 | documentMimeTypes: documentMimeTypes,
89 | imageMimeTypes: imageMimeTypes
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/config/express.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const cookieParser = require('cookie-parser');
4 | const session = require('express-session'); // Adicione esta linha
5 | const flash = require('connect-flash');
6 | const exceptionHandler = require('express-exception-handler');
7 | const error = require('../api/middlewares/error');
8 | const tokenCheck = require('../api/middlewares/tokenCheck');
9 | const { protectRoutes } = require('./config');
10 |
11 | exceptionHandler.handle();
12 |
13 | const app = express();
14 |
15 | app.use(express.json({ limit: '50mb' }));
16 | app.use(express.urlencoded({ extended: true }));
17 | app.use(cookieParser(process.env.COOKIE_SECRET));
18 |
19 | // Configuração do middleware de sessão
20 | app.use(session({
21 | secret: process.env.SESSION_SECRET,
22 | resave: false,
23 | saveUninitialized: true,
24 | cookie: { secure: process.env.NODE_ENV === 'production' }
25 | }));
26 |
27 | app.use(flash());
28 |
29 | app.set('view engine', 'ejs');
30 | app.set('views', path.join(__dirname, '../api/views'));
31 | app.use(express.static(path.join(__dirname, '../public')));
32 |
33 | global.WhatsAppInstances = {};
34 |
35 | // Middleware existente
36 | const routes = require('../api/routes/');
37 |
38 | app.use('/', routes);
39 |
40 | app.use(error.handler);
41 |
42 | module.exports = app;
43 |
--------------------------------------------------------------------------------
/src/public/css/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | display: flex;
3 | flex-direction: column;
4 | height: 100vh;
5 | }
6 |
7 | #wrapper {
8 | display: flex;
9 | flex: 1;
10 | }
11 |
12 | #sidebar {
13 | width: 250px;
14 | flex-shrink: 0;
15 | height: 100%;
16 | }
17 |
18 | #content {
19 | flex: 1;
20 | overflow-y: auto;
21 | padding: 20px;
22 | }
23 | .btn-outline-dark {
24 | color: black;
25 | border-color: black;
26 | margin-left:5px;
27 |
28 | }
29 |
30 | .btn-outline-dark:hover {
31 | background-color: #C9C9C9;
32 | color: white;
33 | }
34 |
35 | .btn-outline-dark i {
36 | color: black;
37 | }
38 |
39 | .footer {
40 | width: 100%;
41 | }
42 | .profile-wrapper {
43 | position: relative;
44 | display: inline-block;
45 | }
46 |
47 | .profile-img {
48 | width: 50px;
49 | height: 50px;
50 | }
51 |
52 | .edit-profile-img {
53 | position: absolute;
54 | bottom: 0;
55 | right: 0;
56 | padding: 2px 6px;
57 | font-size: 12px;
58 | }
59 |
60 | /* Para dispositivos móveis */
61 | @media (max-width: 768px) {
62 | #sidebar {
63 | display: none;
64 | }
65 |
66 | .navbar-toggler {
67 | display: block;
68 | }
69 |
70 | .navbar-collapse {
71 | position: fixed;
72 | top: 0;
73 | left: 0;
74 | width: 250px;
75 | height: 100%;
76 | background-color: #343a40 !important;
77 | z-index: 1000;
78 | overflow-y: auto;
79 | }
80 |
81 | #wrapper {
82 | flex-direction: column;
83 | }
84 | }
85 |
86 | /* Para desktops */
87 | @media (min-width: 769px) {
88 | .navbar-toggler {
89 | display: none;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/public/css/toastr.css:
--------------------------------------------------------------------------------
1 | .toast-title {
2 | font-weight: bold;
3 | }
4 | .toast-message {
5 | -ms-word-wrap: break-word;
6 | word-wrap: break-word;
7 | }
8 | .toast-message a,
9 | .toast-message label {
10 | color: #FFFFFF;
11 | }
12 | .toast-message a:hover {
13 | color: #CCCCCC;
14 | text-decoration: none;
15 | }
16 | .toast-close-button {
17 | position: relative;
18 | right: -0.3em;
19 | top: -0.3em;
20 | float: right;
21 | font-size: 20px;
22 | font-weight: bold;
23 | color: #FFFFFF;
24 | -webkit-text-shadow: 0 1px 0 #ffffff;
25 | text-shadow: 0 1px 0 #ffffff;
26 | opacity: 0.8;
27 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
28 | filter: alpha(opacity=80);
29 | line-height: 1;
30 | }
31 | .toast-close-button:hover,
32 | .toast-close-button:focus {
33 | color: #000000;
34 | text-decoration: none;
35 | cursor: pointer;
36 | opacity: 0.4;
37 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
38 | filter: alpha(opacity=40);
39 | }
40 | .rtl .toast-close-button {
41 | left: -0.3em;
42 | float: left;
43 | right: 0.3em;
44 | }
45 | /*Additional properties for button version
46 | iOS requires the button element instead of an anchor tag.
47 | If you want the anchor version, it requires `href="#"`.*/
48 | button.toast-close-button {
49 | padding: 0;
50 | cursor: pointer;
51 | background: transparent;
52 | border: 0;
53 | -webkit-appearance: none;
54 | }
55 | .toast-top-center {
56 | top: 0;
57 | right: 0;
58 | width: 100%;
59 | }
60 | .toast-bottom-center {
61 | bottom: 0;
62 | right: 0;
63 | width: 100%;
64 | }
65 | .toast-top-full-width {
66 | top: 0;
67 | right: 0;
68 | width: 100%;
69 | }
70 | .toast-bottom-full-width {
71 | bottom: 0;
72 | right: 0;
73 | width: 100%;
74 | }
75 | .toast-top-left {
76 | top: 12px;
77 | left: 12px;
78 | }
79 | .toast-top-right {
80 | top: 12px;
81 | right: 12px;
82 | }
83 | .toast-bottom-right {
84 | right: 12px;
85 | bottom: 12px;
86 | }
87 | .toast-bottom-left {
88 | bottom: 12px;
89 | left: 12px;
90 | }
91 | #toast-container {
92 | position: fixed;
93 | z-index: 999999;
94 | pointer-events: none;
95 | /*overrides*/
96 | }
97 | #toast-container * {
98 | -moz-box-sizing: border-box;
99 | -webkit-box-sizing: border-box;
100 | box-sizing: border-box;
101 | }
102 | #toast-container > div {
103 | position: relative;
104 | pointer-events: auto;
105 | overflow: hidden;
106 | margin: 0 0 6px;
107 | padding: 15px 15px 15px 50px;
108 | width: 300px;
109 | -moz-border-radius: 3px 3px 3px 3px;
110 | -webkit-border-radius: 3px 3px 3px 3px;
111 | border-radius: 3px 3px 3px 3px;
112 | background-position: 15px center;
113 | background-repeat: no-repeat;
114 | -moz-box-shadow: 0 0 12px #999999;
115 | -webkit-box-shadow: 0 0 12px #999999;
116 | box-shadow: 0 0 12px #999999;
117 | color: #FFFFFF;
118 | opacity: 0.8;
119 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
120 | filter: alpha(opacity=80);
121 | }
122 | #toast-container > div.rtl {
123 | direction: rtl;
124 | padding: 15px 50px 15px 15px;
125 | background-position: right 15px center;
126 | }
127 | #toast-container > div:hover {
128 | -moz-box-shadow: 0 0 12px #000000;
129 | -webkit-box-shadow: 0 0 12px #000000;
130 | box-shadow: 0 0 12px #000000;
131 | opacity: 1;
132 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
133 | filter: alpha(opacity=100);
134 | cursor: pointer;
135 | }
136 | #toast-container > .toast-info {
137 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
138 | }
139 | #toast-container > .toast-error {
140 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
141 | }
142 | #toast-container > .toast-success {
143 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
144 | }
145 | #toast-container > .toast-warning {
146 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
147 | }
148 | #toast-container.toast-top-center > div,
149 | #toast-container.toast-bottom-center > div {
150 | width: 300px;
151 | margin-left: auto;
152 | margin-right: auto;
153 | }
154 | #toast-container.toast-top-full-width > div,
155 | #toast-container.toast-bottom-full-width > div {
156 | width: 96%;
157 | margin-left: auto;
158 | margin-right: auto;
159 | }
160 | .toast {
161 | background-color: #030303;
162 | }
163 | .toast-success {
164 | background-color: #51A351;
165 | }
166 | .toast-error {
167 | background-color: #BD362F;
168 | }
169 | .toast-info {
170 | background-color: #2F96B4;
171 | }
172 | .toast-warning {
173 | background-color: #F89406;
174 | }
175 | .toast-progress {
176 | position: absolute;
177 | left: 0;
178 | bottom: 0;
179 | height: 4px;
180 | background-color: #000000;
181 | opacity: 0.4;
182 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
183 | filter: alpha(opacity=40);
184 | }
185 | /*Responsive Design*/
186 | @media all and (max-width: 240px) {
187 | #toast-container > div {
188 | padding: 8px 8px 8px 50px;
189 | width: 11em;
190 | }
191 | #toast-container > div.rtl {
192 | padding: 8px 50px 8px 8px;
193 | }
194 | #toast-container .toast-close-button {
195 | right: -0.2em;
196 | top: -0.2em;
197 | }
198 | #toast-container .rtl .toast-close-button {
199 | left: -0.2em;
200 | right: 0.2em;
201 | }
202 | }
203 | @media all and (min-width: 241px) and (max-width: 480px) {
204 | #toast-container > div {
205 | padding: 8px 8px 8px 50px;
206 | width: 18em;
207 | }
208 | #toast-container > div.rtl {
209 | padding: 8px 50px 8px 8px;
210 | }
211 | #toast-container .toast-close-button {
212 | right: -0.2em;
213 | top: -0.2em;
214 | }
215 | #toast-container .rtl .toast-close-button {
216 | left: -0.2em;
217 | right: 0.2em;
218 | }
219 | }
220 | @media all and (min-width: 481px) and (max-width: 768px) {
221 | #toast-container > div {
222 | padding: 15px 15px 15px 50px;
223 | width: 25em;
224 | }
225 | #toast-container > div.rtl {
226 | padding: 15px 50px 15px 15px;
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/public/css/toastr.min.css:
--------------------------------------------------------------------------------
1 | .toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}
--------------------------------------------------------------------------------
/src/public/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatoiub/whatsapp-hard-api-node/41de699c99612595299efc910429b8d4498c38b4/src/public/img/favicon.png
--------------------------------------------------------------------------------
/src/public/img/logo_api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatoiub/whatsapp-hard-api-node/41de699c99612595299efc910429b8d4498c38b4/src/public/img/logo_api.png
--------------------------------------------------------------------------------
/src/public/img/manager.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatoiub/whatsapp-hard-api-node/41de699c99612595299efc910429b8d4498c38b4/src/public/img/manager.jpg
--------------------------------------------------------------------------------
/src/public/img/noimage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatoiub/whatsapp-hard-api-node/41de699c99612595299efc910429b8d4498c38b4/src/public/img/noimage.jpg
--------------------------------------------------------------------------------
/src/public/js/toastr.min.js:
--------------------------------------------------------------------------------
1 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("
").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'× ',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("
"),M=e("
"),B=e("
"),q=e("
"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.4",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)});
2 | //# sourceMappingURL=toastr.js.map
3 |
--------------------------------------------------------------------------------
/src/public/uploads/logo_api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatoiub/whatsapp-hard-api-node/41de699c99612595299efc910429b8d4498c38b4/src/public/uploads/logo_api.png
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv')
2 | //const mongoose = require('mongoose')
3 | const logger = require('pino')()
4 | dotenv.config()
5 |
6 | const app = require('./config/express')
7 | const config = require('./config/config')
8 |
9 | const { Session } = require('./api/class/session')
10 |
11 |
12 | let server
13 |
14 |
15 | server = app.listen(config.port, async () => {
16 | logger.info(`Listening on port ${config.port}`)
17 |
18 | if (config.restoreSessionsOnStartup) {
19 | logger.info(`Restaurando sessions`)
20 | const session = new Session()
21 | let restoreSessions = await session.restoreSessions()
22 | logger.info(`Sessions restauradas`)
23 | }
24 | })
25 |
26 | const exitHandler = () => {
27 | if (server) {
28 | server.close(() => {
29 | logger.info('Server closed')
30 | process.exit(1)
31 | })
32 | } else {
33 | process.exit(1)
34 | }
35 | }
36 |
37 | const unexpectedErrorHandler = (error) => {
38 | logger.error(error)
39 | exitHandler()
40 | }
41 |
42 | process.on('uncaughtException', unexpectedErrorHandler)
43 | process.on('unhandledRejection', unexpectedErrorHandler)
44 |
45 | process.on('SIGTERM', () => {
46 | logger.info('SIGTERM received')
47 | if (server) {
48 | server.close()
49 | }
50 | })
51 |
52 | module.exports = server
53 |
--------------------------------------------------------------------------------
/temp/init_start.txt:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/tests/status.route.test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest')
2 | const assert = require('assert')
3 | const app = require('../src/server')
4 | const { protectRoutes } = require('../src/config/config')
5 |
6 | if (protectRoutes) {
7 | describe('instance endpoints', () => {
8 | it('should fail with no bearer token is present', (done) => {
9 | request(app)
10 | .get('/status')
11 | .expect(403)
12 | .then((res) => {
13 | assert(res.body.message, 'Initializing successfully')
14 | done()
15 | })
16 | .catch((err) => done(err))
17 | })
18 |
19 | it('should fail with bearer token is mismatch', (done) => {
20 | request(app)
21 | .get('/status')
22 | .set('Authorization', `Bearer ${process.env.TOKEN}wrong`)
23 | .expect(403)
24 | .then((res) => {
25 | assert(res.body.message, 'invalid bearer token supplied')
26 | done()
27 | })
28 | .catch((err) => done(err))
29 | })
30 |
31 | it('should successfully when bearer token is present and matched', (done) => {
32 | request(app)
33 | .get('/status')
34 | .set('Authorization', `Bearer ${process.env.TOKEN}`)
35 | .expect(200)
36 | .then((res) => {
37 | assert(res.body, 'OK')
38 | done()
39 | })
40 | .catch((err) => done(err))
41 | })
42 | })
43 | }
44 |
--------------------------------------------------------------------------------