├── src ├── views │ └── index.html ├── config │ └── whp.js ├── __tests__ │ ├── youtube-dl.js │ ├── stock.js │ └── taunts.js ├── middlewares │ ├── validateActiveToken.js │ ├── verifyActiveSession.js │ ├── sendHandler.js │ └── statusHandler.js ├── server.js ├── routes.js ├── controllers │ ├── whpServerStart.js │ ├── whpSend.js │ ├── whpHook.js │ └── whpCmd.js └── helpers │ └── index.js ├── .eslintignore ├── .prettierrc ├── .editorconfig ├── .env.example ├── .gitignore ├── .eslintrc.cjs ├── package.json └── readme.md /src/views/index.html: -------------------------------------------------------------------------------- 1 |
André Mácola
2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.php 2 | *.html 3 | *.min.js 4 | **/node_modules/** 5 | **/vendor/** 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "prettier.bracketSpacing": false, 5 | "prettier.printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 3 7 | insert_final_newline = true 8 | trim_trailing_whitespace = false 9 | -------------------------------------------------------------------------------- /src/config/whp.js: -------------------------------------------------------------------------------- 1 | const Whp = { 2 | port: process.env.PORT, 3 | sessionId: process.env.SESSION_TOKEN, 4 | client: null, 5 | webhook: process.env.WEBHOOK, 6 | isStarting: false, 7 | }; 8 | 9 | export default Whp; 10 | -------------------------------------------------------------------------------- /src/__tests__/youtube-dl.js: -------------------------------------------------------------------------------- 1 | import { getVideoFile } from '../helpers/index.js'; 2 | 3 | getVideoFile('https://www.youtube.com/watch?v=Ijo1NPtwq3g').then((response) => { 4 | console.log(response.split(' ')[1].replace(':', '')); 5 | }).catch((err) => { 6 | console.log(err); 7 | }); 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | SESSION_TOKEN=client_token 3 | ALLOW_TYPES=application/pdf,image/jpeg,image/png,image/gif,audio/ogg,audio/mpeg 4 | CDN_URL=http://localhost:3000/cdn 5 | WEBHOOK=https://webhook.site/ 6 | RAPIDAPI_KEY=xxxxxx 7 | BITLY_TOKEN=xxxxxx 8 | BITLY_GUID=xxxxxx 9 | MYGROUP=xyz@g.us 10 | MYNUMBER=xyz@c.us 11 | -------------------------------------------------------------------------------- /src/middlewares/validateActiveToken.js: -------------------------------------------------------------------------------- 1 | /* validate session token */ 2 | function validateActiveToken(req, res, next) { 3 | const { token } = req.params; 4 | if (token != process.env.SESSION_TOKEN) { 5 | return res.status(200).json({ 6 | status: `Invalid session token`, 7 | }); 8 | } 9 | return next(); 10 | } 11 | 12 | export default validateActiveToken; 13 | -------------------------------------------------------------------------------- /src/middlewares/verifyActiveSession.js: -------------------------------------------------------------------------------- 1 | import { whpClient } from '../helpers'; 2 | 3 | /* verify active instance session */ 4 | async function verifyActiveSession(req, res, next) { 5 | const client = await whpClient(); 6 | 7 | if (!client) { 8 | return res.status(200).json({ 9 | status: `Client offline`, 10 | }); 11 | } 12 | 13 | return next(); 14 | } 15 | 16 | export default verifyActiveSession; 17 | -------------------------------------------------------------------------------- /src/middlewares/sendHandler.js: -------------------------------------------------------------------------------- 1 | import whpSend from '../controllers/whpSend'; 2 | 3 | function sendHandler(req, res) { 4 | const { cmd } = req.body; 5 | const whp = new whpSend(req, res); 6 | 7 | switch (cmd) { 8 | case 'chat': 9 | return whp.text(); 10 | case 'link': 11 | return whp.link(); 12 | case 'media': 13 | return whp.media(); 14 | default: 15 | return whp.bad(); 16 | } 17 | } 18 | 19 | export default sendHandler; 20 | -------------------------------------------------------------------------------- /src/middlewares/statusHandler.js: -------------------------------------------------------------------------------- 1 | import { whpClient } from '../helpers'; 2 | 3 | async function statusHandler(req, res) { 4 | const client = await whpClient().getMe(); 5 | const status = { 6 | instance: process.env.SESSION_TOKEN, 7 | status: 'CONNECTED', 8 | phone: client.me.user, 9 | picture: client.profilePicThumb.eurl, 10 | battery: client.battery, 11 | platform: client.platform, 12 | webhook: process.env.WEBHOOK, 13 | qrCode: null, 14 | }; 15 | 16 | return res.json(status); 17 | } 18 | 19 | export default statusHandler; 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | *.Trashes 4 | *.DS_Store 5 | *[Tt]humbs.db 6 | Icon? 7 | ._* 8 | desktop.ini 9 | # development 10 | ###################### 11 | .idea 12 | .dropbox 13 | .codekit-cache 14 | *.env 15 | *.data.json 16 | node_modules/ 17 | test/ 18 | data/ 19 | # cdn 20 | ###################### 21 | cdn/ 22 | # ftp/sftp 23 | ###################### 24 | .ftpconfig 25 | sftp.json 26 | sftp-config.json 27 | 28 | # managed by open-wa 29 | **.data.json 30 | **.node-persist** 31 | **_IGNORE_** 32 | # end managed by open-wa 33 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | import express from 'express'; 3 | import routes from './routes'; 4 | import whpServerStart from './controllers/whpServerStart'; 5 | 6 | dotenv.config(); 7 | const app = express(); 8 | 9 | import * as url from 'url'; 10 | // const __filename = url.fileURLToPath(import.meta.url); 11 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 12 | 13 | app.use(express.json()); 14 | app.use(routes); 15 | app.use('/cdn', express.static(__dirname + '/../cdn')); 16 | 17 | app.listen(process.env.PORT, whpServerStart()); 18 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'plugin:@wordpress/eslint-plugin/esnext', 5 | env: { 6 | node: true, 7 | amd: true, 8 | browser: false, 9 | es6: true, 10 | }, 11 | globals: { 12 | }, 13 | parserOptions: { 14 | ecmaVersion: 2020, 15 | }, 16 | rules: { 17 | quotes: [ 18 | 'error', 19 | 'single', 20 | { allowTemplateLiterals: true }, 21 | ], 22 | 'no-var': 0, 23 | 'no-console': 0, 24 | eqeqeq: 0, 25 | 'space-unary-ops': 0, 26 | 'space-in-parens': [ 'warn', 'never' ], 27 | 'template-curly-spacing': [ 'warn', 'never' ], 28 | 'computed-property-spacing': [ 'warn', 'never' ], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whp-api", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "description": "whatsapp automation rest api", 6 | "main": "index.js", 7 | "scripts": { 8 | "start-server": "nodemon --experimental-specifier-resolution=node src/server.js" 9 | }, 10 | "nodemonConfig": { 11 | "ignore": ["**/__tests__/**", "*.data.json", "_IGNORE_*"], 12 | "delay": 100 13 | }, 14 | "author": "andremacola", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@open-wa/wa-automate": "^4.50.2", 18 | "@wordpress/eslint-plugin": "^6.1.0", 19 | "axios": "^0.19.2", 20 | "death": "^1.1.0", 21 | "dotenv": "^16.0.3", 22 | "express": "^4.18.2", 23 | "p-queue": "^7.3.0", 24 | "youtube-dl-exec": "^2.1.10" 25 | }, 26 | "devDependencies": { 27 | "eslint": "^7.2.0", 28 | "nodemon": "^2.0.20" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/__tests__/stock.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as dotenv from 'dotenv'; 3 | dotenv.config(); 4 | 5 | axios 6 | .get(`https://yh-finance.p.rapidapi.com/market/v2/get-quotes`, 7 | { 8 | params: { 9 | symbols: `PETR4.SA`, 10 | region: 'BR', 11 | }, 12 | headers: { 13 | 'x-rapidapi-host': 'yh-finance.p.rapidapi.com', 14 | 'x-rapidapi-key': process.env.RAPIDAPI_KEY, 15 | }, 16 | }, 17 | ) 18 | .then((res) => { 19 | const { longName, symbol, regularMarketPrice, regularMarketChange } = res.data.quoteResponse.result[0]; 20 | const icTitle = (regularMarketChange < 0) ? '📉' : '📈'; 21 | const icVar = (regularMarketChange < 0) ? '🔻' : '🔼'; 22 | const msg = `${icTitle} *${longName}* ${icTitle}\n*ID:* ${symbol}\n*Preço:* ${icVar} R$${regularMarketPrice}\n*Variação:* ${icVar} ${regularMarketChange}%`; 23 | 24 | console.log(msg); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/taunts.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | // import { getFile } from '../helpers'; 3 | import * as dotenv from 'dotenv'; 4 | import * as urll from 'url'; 5 | 6 | dotenv.config(); 7 | const __dirname = urll.fileURLToPath(new URL('.', import.meta.url)); 8 | 9 | async function taunts(body = '!!random') { 10 | const folder = __dirname + '../../cdn/taunts/'; 11 | const files = fs.readdirSync(folder); 12 | const tauntRef = body.replaceAll('!!', '').replace('title', '').trim(); 13 | 14 | let tauntFileName; 15 | if (tauntRef === 'random') { 16 | tauntFileName = files[Math.floor(Math.random() * files.length)]; 17 | } else { 18 | tauntFileName = files.find((file) => file.split(' ')[0] === tauntRef); 19 | if (!tauntFileName && !parseInt(tauntRef)) { 20 | tauntFileName = files.filter((file) => file.toLocaleLowerCase().includes(tauntRef)); 21 | tauntFileName = tauntFileName[Math.floor(Math.random() * tauntFileName.length)]; 22 | } 23 | } 24 | 25 | const tauntFilePath = folder + '/' + tauntFileName; 26 | const tauntExist = fs.existsSync(tauntFilePath); 27 | 28 | if (tauntExist) { 29 | console.log(tauntFileName, ' taunt existe'); 30 | // const url = `${process.env.CDN_URL}/taunts/${tauntFileName}.mp3`; 31 | // const media = await getFile(url); 32 | // console.log('media', media); 33 | } else { 34 | console.log(tauntFileName, ' taunt não existe'); 35 | } 36 | } 37 | 38 | taunts(); 39 | 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | **THIS IS A WORKING PROJECT.** 2 | 3 | Whatsapp REST API for [wa-automate-nodejs](https://github.com/open-wa/wa-automate-nodejs) 4 | 5 | **This is a basic example how to create a REST API for Whatsapp. Not for production use (yet).** 6 | 7 | ### HOW TO USE ### 8 | 9 | 1. Set ambient variables inside .env file (see .env.example for more information). *MYGROUP* and *MYNUMBER* is only for development purpose. 10 | 2. `yarn install` or `npm install` 11 | 3. `yarn start-server` 12 | 13 | ### ENDPOINTS ### 14 | 15 | |Command|Endpoint| 16 | |---|---| 17 | |Send **(POST):** | http://localhost:3000/SESSION_TOKEN/send| 18 | |Info **(GET):** | http://localhost:3000/SESSION_TOKEN/status| 19 | |Stop **(GET):** | http://localhost:3000/SESSION_TOKEN/stop| 20 | |Start **(GET):** | http://localhost:3000/SESSION_TOKEN/start| 21 | 22 | 23 | ### SEND EXAMPLES ### 24 | ```json 25 | // Text message (for groups use @g.us) 26 | 27 | { 28 | "cmd": "chat", 29 | "to": "number@c.us", 30 | "msg": "some message" 31 | } 32 | 33 | // Audios (audio/ogg, audio/mpeg) 34 | 35 | { 36 | "cmd": "media", 37 | "to": "number@c.us", 38 | "url": "https://domain.tld/audio.mp3" 39 | } 40 | 41 | // Images (image/jpeg, image/png, image/gif) 42 | 43 | { 44 | "cmd": "media", 45 | "to": "number@c.us", 46 | "url": "https://domain.tld/image.jpg", 47 | "msg": "some caption (optional)" 48 | } 49 | 50 | // Link or Youtube videos 51 | 52 | { 53 | "cmd": "link", 54 | "to": "number@c.us", 55 | "url": "https://github.com", 56 | "msg": "some message (optional)" 57 | } 58 | ``` 59 | 60 | 61 | ### WEBHOOKS ### 62 | 63 | Set webhook url on .env file. The api will send a post with every message or ack events 64 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { whpClient, sessionStop, getWhp, setWhp } from './helpers'; 3 | import whpServerStart from './controllers/whpServerStart'; 4 | import validateActiveToken from './middlewares/validateActiveToken'; 5 | import verifyActiveSession from './middlewares/verifyActiveSession'; 6 | import sendHandler from './middlewares/sendHandler'; 7 | import statusHandler from './middlewares/statusHandler'; 8 | import nodemon from 'nodemon'; 9 | 10 | const router = express.Router(); 11 | 12 | /* session debug */ 13 | router.get( 14 | '/debug/', 15 | async (req, res) => { 16 | nodemon.emit('restart'); 17 | return res.json(await whpClient().getMe()); 18 | }, 19 | ); 20 | 21 | /* session status/info */ 22 | router.get( 23 | '/:token/status', 24 | validateActiveToken, 25 | verifyActiveSession, 26 | statusHandler, 27 | ); 28 | 29 | /* session stop */ 30 | router.get( 31 | '/:token/stop', 32 | validateActiveToken, 33 | verifyActiveSession, 34 | async (req, res) => { 35 | sessionStop(); 36 | return res.status(200).json({ 37 | status: `Session Terminated!`, 38 | }); 39 | }, 40 | ); 41 | 42 | /* session start */ 43 | router.get( 44 | '/:token/start', 45 | validateActiveToken, 46 | async (req, res) => { 47 | let { isStarting, client } = getWhp(); 48 | if (!client && !isStarting) { 49 | await whpServerStart(); 50 | isStarting = setWhp('isStarting', true); 51 | } 52 | const status = (isStarting) ? 'Starting Session...' : 'Online'; 53 | return res.status(200).json({ 54 | status, 55 | }); 56 | }, 57 | ); 58 | 59 | /* send messages */ 60 | router.post( 61 | '/:token/send/', 62 | validateActiveToken, 63 | verifyActiveSession, 64 | sendHandler, 65 | ); 66 | 67 | export default router; 68 | -------------------------------------------------------------------------------- /src/controllers/whpServerStart.js: -------------------------------------------------------------------------------- 1 | import wa from '@open-wa/wa-automate'; 2 | import ON_DEATH from 'death'; 3 | import { setWhp } from '../helpers'; 4 | import whpHook from './whpHook'; 5 | import whpCmd from './whpCmd'; 6 | 7 | const whpServerStart = async function() { 8 | setWhp('isStarting', true); 9 | wa.create({ 10 | headless: true, 11 | sessionId: process.env.SESSION_TOKEN, 12 | }) 13 | .then(async (client) => await whpConfigureClient(client)) 14 | .catch((error) => { 15 | console.log('Error', error.message); 16 | }); 17 | }; 18 | 19 | const whpConfigureClient = async function(client) { 20 | setWhp('client', client); 21 | 22 | client.onAck((data) => whpHook.send('ack', data)); 23 | client.onAnyMessage((data) => whpHook.send('message', data)); 24 | client.onMessage((data) => whpCmd.handler(client, data)); 25 | 26 | // console.log(await client.getMessageById('false_559881199008-1591773499@g.us_3EB0E29D045D458FCC1E_559881199008@c.us')); 27 | 28 | // client.onPlugged(whpHook.post(await client.getMe())); 29 | // client.onMessage((data) => whpHook.send('message', data)); 30 | // client.onAnyMessage(webHook('any_message')); 31 | // client.onAddedToGroup(webHook('added_to_group')); 32 | // client.onBattery(webHook('battery')); 33 | // client.onContactAdded(webHook('contact_added')); 34 | // client.onIncomingCall(webHook('incoming_call')); 35 | // client.onPlugged(webHook('plugged')); 36 | // client.onStateChanged(webHook('state')); 37 | 38 | setWhp('isStarting', false); 39 | console.log(`\n⚡ Listening on http://localhost:${process.env.PORT}!`); 40 | 41 | ON_DEATH(async function(signal, err) { 42 | console.log(`\n❌ Killing session!`, signal, err); 43 | await client.kill(); 44 | process.exit(0); 45 | }); 46 | }; 47 | 48 | export default whpServerStart; 49 | -------------------------------------------------------------------------------- /src/controllers/whpSend.js: -------------------------------------------------------------------------------- 1 | import { whpClient, sendGetStatus, getFile } from './../helpers'; 2 | 3 | class whpSend { 4 | constructor(req, res) { 5 | this.req = req; 6 | this.res = res; 7 | this.client = whpClient(); 8 | } 9 | 10 | bad(msg = 'Bad Request') { 11 | return this.res.status(400).send(msg); 12 | } 13 | 14 | checkNumber(n) { 15 | // const number = await this.client.checkNumberStatus(recipient); 16 | // return (number.status == 200) ? number.id._serialized : false; 17 | if (n.charAt(4) == 9 && n.slice(0, 2) == 55) { 18 | return n.slice(0, 4) + n.slice(5); 19 | } 20 | return n; 21 | } 22 | 23 | async text() { 24 | const { cmd, to, msg } = this.req.body; 25 | const number = this.checkNumber(to); 26 | console.log(number); 27 | if (cmd == 'chat' && number && msg) { 28 | const send = sendGetStatus(await this.client.sendText(number, msg)); 29 | return this.res.status(200).json({ 30 | send, 31 | cmd, 32 | to, 33 | }); 34 | } 35 | this.bad(); 36 | } 37 | 38 | async link() { 39 | const { cmd, to, url, msg } = this.req.body; 40 | const number = this.checkNumber(to); 41 | console.log(number); 42 | if (cmd == 'link' && number && url) { 43 | const caption = (msg) ? msg : ''; 44 | 45 | let send = await this.client.sendLinkWithAutoPreview(to, url, caption); 46 | send = (typeof send == 'undefined') ? true : false; 47 | return this.res.status(200).json({ 48 | send, 49 | cmd, 50 | to, 51 | }); 52 | } 53 | this.bad(); 54 | } 55 | 56 | async media() { 57 | const { cmd, to, url, msg } = this.req.body; 58 | const number = this.checkNumber(to); 59 | console.log(number); 60 | if (cmd == 'media' && number && url) { 61 | const media = await getFile(url); 62 | const caption = (msg) ? msg : ''; 63 | 64 | if (!media) { 65 | return this.bad(); 66 | } 67 | 68 | const send = await this.client.sendFile(to, media.base64, media.name, caption); 69 | return this.res.status(200).json({ 70 | send, 71 | cmd, 72 | to, 73 | }); 74 | } 75 | this.bad(); 76 | } 77 | } 78 | 79 | export default whpSend; 80 | -------------------------------------------------------------------------------- /src/controllers/whpHook.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import queue from 'p-queue'; 3 | import { getMsgServer, getMessageID, getMsgServerFromNumber, formatPhoneNumber } from '../helpers'; 4 | 5 | class whpHook { 6 | static async post(data) { 7 | try { 8 | await axios.post(process.env.WEBHOOK, data); 9 | } catch (e) { 10 | console.log('Error posting a hook'); 11 | } 12 | } 13 | 14 | static async send(event, data) { 15 | try { 16 | await this.post(this.dataHandler(event, data)); 17 | } catch (e) { 18 | console.log('Error sending a hook'); 19 | } 20 | } 21 | 22 | static event(event) { 23 | return async function(data) { 24 | const ts = Date.now(); 25 | return await queue.add(() => this.post({ 26 | ts, 27 | event, 28 | data, 29 | })); 30 | }.bind(this); 31 | } 32 | 33 | static dataHandler(event, data) { 34 | let hook; 35 | 36 | if (data.type == 'sticker') { 37 | return false; 38 | } 39 | 40 | switch (event) { 41 | case 'message': 42 | hook = { 43 | event, 44 | type: data.type, 45 | token: process.env.SESSION_TOKEN, 46 | user: formatPhoneNumber(data.to), 47 | contact: { 48 | number: formatPhoneNumber(data.to), 49 | name: data.sender.name, 50 | server: getMsgServer(data.isGroupMsg), 51 | }, 52 | chat: { 53 | dtm: data.timestamp, 54 | id: data.id, 55 | mid: getMessageID(data.id), 56 | dir: (data.fromMe) ? 'o' : 'i', 57 | type: data.type, 58 | body: data.body, 59 | }, 60 | ack: data.ack, 61 | }; 62 | if (data.isGroupMsg) { 63 | hook.contact.groupName = data.chat.contact.name; 64 | hook.contact.groupNumber = data.chat.contact.id; 65 | } 66 | break; 67 | case 'ack': 68 | hook = { 69 | event, 70 | dir: (data.id.fromMe) ? 'o' : 'i', 71 | from: formatPhoneNumber(data.from), 72 | to: formatPhoneNumber(data.to), 73 | server: getMsgServerFromNumber(data.from), 74 | muid: false, 75 | id: data.id.id, 76 | ack: data.ack, 77 | }; 78 | if (data.author) { 79 | hook.from = formatPhoneNumber(data.author); 80 | hook.group = data.from; 81 | } 82 | break; 83 | default: 84 | break; 85 | } 86 | 87 | return hook; 88 | } 89 | } 90 | 91 | export default whpHook; 92 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | import * as urll from 'url'; 3 | import whp from '../config/whp'; 4 | import axios from 'axios'; 5 | import youtubedl from 'youtube-dl-exec'; 6 | 7 | const __dirname = urll.fileURLToPath(new URL('.', import.meta.url)); 8 | dotenv.config(); 9 | 10 | export const setWhp = function(option, value) { 11 | if (!option) { 12 | return false; 13 | } 14 | return whp[option] = (value) ? value : false; 15 | }; 16 | 17 | export const getWhp = function(option) { 18 | return (option) ? whp[option] : whp; 19 | }; 20 | 21 | export const whpClient = function() { 22 | return getWhp('client'); 23 | }; 24 | 25 | export const sessionStop = async function() { 26 | const client = await whpClient(); 27 | console.log('sessionStop', 'init'); 28 | if (client) { 29 | console.log('sessionStop', 'kill'); 30 | await client.kill(); 31 | setWhp('client', null); 32 | } 33 | }; 34 | 35 | export const sendGetStatus = function(send) { 36 | return send = (!send) ? false : true; 37 | }; 38 | 39 | export const getFileName = function(url) { 40 | return url.substring(url.lastIndexOf('/') + 1); 41 | }; 42 | 43 | export const getFile = async function(url) { 44 | return axios 45 | .get(url, { 46 | responseType: 'arraybuffer', 47 | }) 48 | .then((r) => { 49 | if (process.env.ALLOW_TYPES.split(',').includes(r.headers['content-type'])) { 50 | return { 51 | type: r.headers['content-type'], 52 | base64: 'data:' + r.headers['content-type'] + ';base64,' + Buffer.from(r.data, 'binary').toString('base64'), 53 | name: getFileName(url), 54 | }; 55 | } 56 | return false; 57 | }); 58 | }; 59 | 60 | export const getMsgServer = function(isGroupMsg) { 61 | return (isGroupMsg) ? '@g.us' : '@c.us'; 62 | }; 63 | 64 | export const getMsgServerFromNumber = function(number) { 65 | return number.slice(-4); 66 | }; 67 | 68 | export const getMessageID = function(id) { 69 | // return id = id.split('us_')[1].split('_')[0]; 70 | return id.slice(id.indexOf('us_') + 3).split('_')[0]; 71 | }; 72 | 73 | export const formatPhoneNumber = function(number) { 74 | return number.slice(0, -5); 75 | }; 76 | 77 | export const getShortLink = async function(url) { 78 | return axios 79 | .post('https://api-ssl.bitly.com/v4/shorten', 80 | { 81 | group_guid: process.env.BITLY_GUID, 82 | domain: 'bit.ly', 83 | long_url: url, 84 | }, 85 | { 86 | headers: { 87 | 'Content-Type': 'application/json', 88 | 'Access-Control-Allow-Origin': '*', 89 | Authorization: `Bearer ${process.env.BITLY_TOKEN}`, 90 | }, 91 | }) 92 | .then((r) => (r.data.link) ? r.data.link : false) 93 | .catch((err) => console.log(err)); 94 | }; 95 | 96 | export async function getVideoFile(url, quality = '(mp4)[height<480]') { 97 | return new Promise((res, rej) => { 98 | youtubedl(url, { 99 | format: quality, 100 | output: `${__dirname}../../cdn/videos/%(id)s.%(ext)s`, 101 | // dumpSingleJson: true, 102 | noCheckCertificates: true, 103 | noWarnings: true, 104 | addHeader: [ 105 | 'referer:youtube.com', 106 | 'user-agent:googlebot', 107 | ], 108 | }) 109 | .then((output) => res(output)) 110 | .catch((err) => rej(err)); 111 | }); 112 | } 113 | 114 | // export default { 115 | // setWhp, 116 | // getWhp, 117 | // whpClient, 118 | // sessionStop, 119 | // sendGetStatus, 120 | // getFileName, 121 | // getFile, 122 | // getMsgServer, 123 | // getMessageID, 124 | // getMsgServerFromNumber, 125 | // formatPhoneNumber, 126 | // getShortLink, 127 | // getVideoInfo, 128 | // getVideoFile, 129 | // }; 130 | 131 | -------------------------------------------------------------------------------- /src/controllers/whpCmd.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import * as urll from 'url'; 3 | import axios from 'axios'; 4 | import { getShortLink, getVideoFile } from './../helpers'; 5 | 6 | const __dirname = urll.fileURLToPath(new URL('.', import.meta.url)); 7 | 8 | class whpCmd { 9 | bad() { 10 | return false; 11 | } 12 | 13 | static handler(client, data) { 14 | const body = data.body; 15 | const number = data.from; 16 | const msgId = data.id; 17 | switch (true) { 18 | case (body == '@help'): 19 | this.help(client, number); 20 | break; 21 | case (body.startsWith('@vid ')): 22 | this.video(client, number, body, msgId); 23 | break; 24 | case (body.startsWith('@pay ')): 25 | this.outLine(client, number, body, msgId); 26 | break; 27 | case (body == '@pay'): 28 | const wall = data.quotedMsgObj.canonicalUrl; 29 | if (wall) { 30 | client.reply(number, `https://outline.com/${wall}`, msgId, true); 31 | } else { 32 | client.reply(number, 'Link inválido', msgId, true); 33 | } 34 | break; 35 | case (body == '@ping'): 36 | client.sendText(number, '@pong'); 37 | break; 38 | case (body == '@ei' || body.startsWith('@ei ')): 39 | this.frases(client, number, msgId); 40 | break; 41 | case (body.startsWith('@ac ')): 42 | this.stock(client, number, body); 43 | break; 44 | case (body == '@usd'): 45 | this.brl(client, number, 'USD'); 46 | break; 47 | case (body == '@eur'): 48 | this.brl(client, number, 'EUR'); 49 | break; 50 | case (body.startsWith('!!')): 51 | this.taunts(client, number, body, msgId); 52 | break; 53 | default: 54 | break; 55 | } 56 | } 57 | 58 | static async help(client, number) { 59 | const msg = `ℹ *Comandos Disponíveis* ℹ 60 | *@ping*: _@pong_ 61 | *@ei*: _uma frase aleatória_ 62 | *@pay endereço-da-noticia*: _Burlar paywall_ 63 | *@usd*: _Cotação atual do Dólar_ 64 | *@eur*: _Cotação atual do Euro_ 65 | *@ac sigla-do-ativo*: _Cotação do ativo na bolsa_ 66 | *@_vid_ endereço-do-video*: Baixar vídeo do Youtube/Twitter/Vimeo (em dev) 67 | *!!termo-ou-numero*: _Retorna um taunt (ex: !!89, !!mortal ou !!go go go)_`; 68 | return await client.sendText(number, msg); 69 | } 70 | 71 | static async frases(client, number, replyMsg) { 72 | const prons = [ 'Ei', 'Hnn', 'Rapá', 'Fala', 'Veish' ]; 73 | const frases = [ 'Már cumpôca eu vou aí', 'Tu tá parecendo um menino do buchão', 'É só tu arrudiar bem por alí', 'Essa piquena é pai D\'Égua', 'Esse bicho é todo desassuntado', 'Eu vou aí na boquinha da noite', 'Tu é todo migueloso', 'Cadê essa ôta?', 'Te dou-lhe um bogue', 'Te dou-lhe um murro', 'Te dou-lhe um cascudo', 'Paruano eu vou pro meu interior', 'Eu tô é tu', 'Esse bicho é todo galudo', 'Te sai de boca!', 'Ele é iscritinho o pai', 'Éguas vai cair um toró! São Pedro tá inspirado!', 'Lá vai ela com a calça no rendengue', 'Eu tô só a coíra', 'Merman, larga de ser esparrosa', 'Eu não sou teus pareceiros', 'Eu vou me banhar rapidão', 'Aquela piquena é amostrada', 'Alí só tem maroca', 'Merman, eu fiquei arriliada', 'Eu cheguei lá na caruda', 'Tu só quer ser', 'Bora binhalí merendar', 'Larga de ser canhenga', 'Daqui pra rua grande é uma pernada', 'Aquilo ali é qualira', 'Piqueno eu vou te dále', 'Éguas té doido', 'Bota o teu', 'Não te faz de doida que o pau de acha', 'Heinhein' ]; 74 | 75 | const pron = prons[Math.floor(Math.random() * prons.length)]; 76 | const frase = frases[Math.floor(Math.random() * frases.length)]; 77 | const msg = `${pron}, ${frase}`; 78 | 79 | return await client.reply(number, msg, replyMsg, true); 80 | } 81 | 82 | static async video(client, number, body, replyMsg) { 83 | const url = body.replace('@vid ', ''); 84 | await getVideoFile(url) 85 | .then((res) => { 86 | const file = res.split(' ')[1].replace(':', ''); 87 | const videoUrl = `${process.env.CDN_URL}/videos/${file}.mp4`; 88 | return client.reply(number, '😁 Yess! *Baixe o vídeo em:* ' + videoUrl, replyMsg, true); 89 | }) 90 | .catch((err) => { 91 | console.log(err); 92 | return client.reply(number, '😕 Haaaa! Não consegui baixar o vídeo. O Endereço está correto?', replyMsg, true); 93 | }); 94 | } 95 | 96 | static async taunts(client, number, body, replyMsg) { 97 | const folder = __dirname + '../../cdn/taunts/'; 98 | const files = fs.readdirSync(folder); 99 | const tauntRef = body.replaceAll('!!', '').replace('title', '').trim(); 100 | 101 | let tauntFileName; 102 | if (tauntRef === 'random') { 103 | tauntFileName = files[Math.floor(Math.random() * files.length)]; 104 | } else { 105 | tauntFileName = files.find((file) => file.split(' ')[0] === tauntRef); 106 | if (!tauntFileName && !parseInt(tauntRef)) { 107 | tauntFileName = files.filter((file) => file.toLocaleLowerCase().includes(tauntRef)); 108 | tauntFileName = tauntFileName[Math.floor(Math.random() * tauntFileName.length)]; 109 | } 110 | } 111 | 112 | const tauntFilePath = folder + '/' + tauntFileName; 113 | const tauntExist = fs.existsSync(tauntFilePath); 114 | 115 | if (tauntExist) { 116 | await client.sendFile(number, tauntFilePath, tauntFileName); 117 | if (body.includes('title')) { 118 | await client.sendText(number, tauntFileName.replace('.mp3', '')); 119 | } 120 | } else { 121 | await client.reply(number, '😕 Haaaa! Não consegui achar esse taunt. Tenta outro número!', replyMsg, true); 122 | } 123 | } 124 | 125 | static brl(client, number, coin = 'USD') { 126 | axios 127 | .get(`https://economia.awesomeapi.com.br/${coin}`) 128 | .then((res) => { 129 | const { name, bid, ask, pctChange } = res.data[0]; 130 | const icTitle = (pctChange[0] === '-') ? '📉' : '📈'; 131 | const icVar = (pctChange[0] === '-') ? '🔻' : '🔼'; 132 | const msg = `${icTitle} *${name}* ${icTitle}\n*Compra:* R$${bid}\n*Venda:* R$${ask}\n*Variação:* ${icVar} ${pctChange}%`; 133 | return client.sendText(number, msg); 134 | }); 135 | } 136 | 137 | static stock(client, number, stock) { 138 | const ac = stock.slice(4); 139 | axios 140 | .get(`https://yh-finance.p.rapidapi.com/market/v2/get-quotes`, 141 | { 142 | params: { 143 | symbols: `${ac}.SA`, 144 | region: 'BR', 145 | }, 146 | headers: { 147 | 'x-rapidapi-host': 'yh-finance.p.rapidapi.com', 148 | 'x-rapidapi-key': process.env.RAPIDAPI_KEY, 149 | }, 150 | }, 151 | ) 152 | .then((res) => { 153 | const { longName, symbol, regularMarketPrice, regularMarketChange } = res.data.quoteResponse.result[0]; 154 | const icTitle = (regularMarketChange < 0) ? '📉' : '📈'; 155 | const icVar = (regularMarketChange < 0) ? '🔻' : '🔼'; 156 | const msg = `${icTitle} *${longName}* ${icTitle}\n*ID:* ${symbol}\n*Preço:* ${icVar} R$${regularMarketPrice}\n*Variação:* ${icVar} ${regularMarketChange}%`; 157 | return client.sendText(number, msg); 158 | }); 159 | } 160 | 161 | static async outLine(client, number, body, replyMsg) { 162 | const sourceUrl = body.replace('@pay ', ''); 163 | 164 | const link = await getShortLink(`https://12ft.io/proxy?q=${sourceUrl}`); 165 | if (link) { 166 | return client.reply(number, `🗞️ *Leia*: ${link}`, replyMsg, true); 167 | } 168 | } 169 | } 170 | 171 | export default whpCmd; 172 | --------------------------------------------------------------------------------