├── .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 | 18 |
git clone https://github.com/renatoiub/whatsapp-hard-api-node
19 | 20 | 23 | 26 | 27 |
28 | 29 | 32 |
npm i 
33 | 34 | 37 | 40 | 41 |
42 | 43 | 50 | 51 | 54 |
npm start
55 | 56 | 59 | 62 | 63 |
64 | 65 |

Intalação

66 | 67 |

Docker install:

68 | 69 | 78 | 79 |

Manager da API

80 |

Contribua com o projeto e tenha acesso ao manager da api

81 |
82 | 83 | ![link About do site](https://github.com/renatoiub/whatsapp-hard-api-node/blob/v1.6.0/src/public/img/manager.jpg) 84 |
85 |

Teste a api com o Manager!

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 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
Conexão via qr_code
Conexção via código de emparelhamento
Envia texto
Send Buttons
Send Template
Arquivos: audio - video - image - document - gif

base64: true
Send Media URL
Send Media File
Convert audio and video to whatsapp format
Resposta de mensagem
Envia presença: Digitando.. Gravando audio..
Send Location
Send List (beta)
Send Link Preview
Send Contact
Send Reaction - emoji
Get contacts
Grupos: Cria, entra, sai, adiciona contatos, remove contatos e admins. Marcação fantasma (ghostMention) true
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 | --------------------------------------------------------------------------------