diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7269e52 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +MOBIZON_URL_SRV=https://api.mobizon.com.br +MOBIZON_API_KEY= diff --git a/.eslintrc.js b/.eslintrc.js index a82ee2d..a3a15ba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,8 @@ module.exports = { parser: '@typescript-eslint/parser', extends: [ - 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', - 'prettier/@typescript-eslint', + 'prettier', 'plugin:prettier/recommended', ], settings: { @@ -16,7 +15,7 @@ module.exports = { node: true, es6: true, }, - plugins: ['@typescript-eslint', 'react', 'prettier'], + plugins: ['@typescript-eslint', 'prettier'], parserOptions: { ecmaFeatures: { jsx: true, @@ -25,7 +24,6 @@ module.exports = { sourceType: 'module', }, rules: { - 'react/prop-types': 'off', 'prettier/prettier': 'error', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/explicit-function-return-type': 'off', diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..789dc21 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +name: Lint + +on: + push: + pull_request: + +jobs: + eslint: + name: eslint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: install node v12 + uses: actions/setup-node@v2 + with: + node-version: 12 + - name: npm install + run: npm install + - name: eslint + uses: icrawl/action-eslint@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + job-name: eslint diff --git a/.gitignore b/.gitignore index 2cad4e5..09dec44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.env + dist node_modules @@ -9,4 +11,4 @@ public/images/* src/data/session.json !src/data/.gitkeep -src/config/integrantes.json \ No newline at end of file +src/config/integrantes.json diff --git a/.prettierrc b/.prettierrc index 92beac4..27dd8af 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,5 @@ "semi": true, "singleQuote": true, "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file + "trailingComma": "all" +} diff --git a/ambient.d.ts b/ambient.d.ts index 0552251..b4de401 100644 --- a/ambient.d.ts +++ b/ambient.d.ts @@ -1,2 +1 @@ -// declare module 'whatsapp-web.js'; declare module 'qrcode-terminal'; diff --git a/package.json b/package.json index 8b5ff37..fd579a5 100644 --- a/package.json +++ b/package.json @@ -46,25 +46,26 @@ "dev": "ts-node-dev --respawn --transpile-only --ignore-watch node_modules --ignore-watch src/data -r dotenv/config src/index.ts" }, "dependencies": { + "@types/node": "^15.0.1", "axios": "^0.21.1", "express": "^4.17.1", + "mobizon-node": "^0.3.1", "node-base64-image": "^2.0.1", "puppeteer": "^8.0.0", "qr-image": "^3.2.0", "qrcode-terminal": "^0.12.0", - "whatsapp-web.js": "^1.12.4" + "whatsapp-web.js": "^1.12.6" }, "devDependencies": { "@types/axios": "0.14.0", "@types/puppeteer": "5.4.3", - "@typescript-eslint/eslint-plugin": "4.19.0", - "@typescript-eslint/parser": "4.19.0", + "@typescript-eslint/eslint-plugin": "4.22.1", + "@typescript-eslint/parser": "4.22.1", "dotenv": "8.2.0", - "eslint": "7.24.0", - "eslint-config-prettier": "8.1.0", + "eslint": "7.25.0", + "eslint-config-prettier": "8.3.0", "eslint-loader": "4.0.2", - "eslint-plugin-prettier": "3.3.1", - "eslint-plugin-react": "7.22.0", + "eslint-plugin-prettier": "3.4.0", "prettier": "2.2.1", "ts-node-dev": "1.1.6", "typescript": "4.2.4" diff --git a/src/app/commands/CepCommand.ts b/src/app/commands/CepCommand.ts index 686d9d7..59e389b 100644 --- a/src/app/commands/CepCommand.ts +++ b/src/app/commands/CepCommand.ts @@ -17,11 +17,11 @@ export default class EconomyCommand { try { const { data }: IResponse = await axios.get( - `https://brasilapi.com.br/api/cep/v1/${setCep}` + `https://brasilapi.com.br/api/cep/v1/${setCep}`, ); return msg.reply( - `*CEP*: ${data.cep}\n*Logradouro*: ${data.street}\n*Cidade*: ${data.city}\n*Bairro*: ${data.neighborhood}\n*UF*: ${data.state}` + `*CEP*: ${data.cep}\n*Logradouro*: ${data.street}\n*Cidade*: ${data.city}\n*Bairro*: ${data.neighborhood}\n*UF*: ${data.state}`, ); } catch (error) { return msg.reply(`CEP não localizado!`); diff --git a/src/app/commands/EconomyCommand.ts b/src/app/commands/EconomyCommand.ts index 1c9e26a..c72e632 100644 --- a/src/app/commands/EconomyCommand.ts +++ b/src/app/commands/EconomyCommand.ts @@ -9,7 +9,7 @@ export default class EconomyCommand { chat.sendStateTyping(); const { data } = await axios.get( - 'https://economia.awesomeapi.com.br/all/USD-BRL,BTC-BRL,EUR-BRL' + 'https://economia.awesomeapi.com.br/all/USD-BRL,BTC-BRL,EUR-BRL', ); const type = (currency: ICurrency) => { @@ -18,8 +18,8 @@ export default class EconomyCommand { return msg.reply( `Cotação atual: 💎💰🤑💹 \n${type(data.USD)} ${type(data.EUR)} ${type( - data.BTC - )}` + data.BTC, + )}`, ); } } diff --git a/src/app/commands/ProfileCommand.ts b/src/app/commands/ProfileCommand.ts index 375cd9c..b340543 100644 --- a/src/app/commands/ProfileCommand.ts +++ b/src/app/commands/ProfileCommand.ts @@ -1,4 +1,4 @@ -import { client, MessageMedia } from '../../server'; +import { client, MessageMedia } from '../../services/whatsapp'; import { encode } from 'node-base64-image'; import type { Message } from 'whatsapp-web.js'; @@ -19,10 +19,10 @@ export default class ProfileCommand { return msg.reply('Comando apenas para grupos!'); } - msg.reply('Stalkeando este contato...'); - if (!contact) return msg.reply('Contato não localizado.'); + msg.reply('Stalkeando este contato...'); + const url_i = await client.getProfilePicUrl(contact.number); if (!url_i) return msg.reply('Imagem não foi localizada.'); diff --git a/src/app/commands/QuoteCommand.ts b/src/app/commands/QuoteCommand.ts index b4ebbd3..859d741 100644 --- a/src/app/commands/QuoteCommand.ts +++ b/src/app/commands/QuoteCommand.ts @@ -1,4 +1,4 @@ -import { client } from '../../server'; +import { client } from '../../services/whatsapp'; import { company } from '../../config/integrantes.json'; import type { Message, GroupChat } from 'whatsapp-web.js'; @@ -19,7 +19,7 @@ export default class QuoteCommand { if (valores.numero == contato) { if (!valores.admin) { return msg.reply( - 'Ops, você não tem permissão para executar este comando!' + 'Ops, você não tem permissão para executar este comando!', ); } @@ -28,7 +28,7 @@ export default class QuoteCommand { for (const participant of chat.participants) { const contact = await client.getContactById( - participant.id._serialized + participant.id._serialized, ); mentions.push(contact); diff --git a/src/app/commands/SmsCommand.ts b/src/app/commands/SmsCommand.ts new file mode 100644 index 0000000..6f15f6a --- /dev/null +++ b/src/app/commands/SmsCommand.ts @@ -0,0 +1,35 @@ +import type { Message } from 'whatsapp-web.js'; +import mobizon from '../../services/mobizon'; + +export default class ProfileCommand { + mention: string; + constructor(mention: string) { + this.mention = mention; + } + + async execute(msg: Message) { + const chat = await msg.getChat(); + + const [contact] = await msg.getMentions(); + + chat.sendStateTyping(); + + if (!chat.isGroup) { + return msg.reply('Comando apenas para grupos!'); + } + + if (!contact) return msg.reply('Contato não localizado.'); + + const sendSms = await mobizon.sendSms({ + recipient: contact.number, + from: '', + text: 'Sms enviado via BOT.', + }); + + if (sendSms.code !== 0) { + return msg.reply('Oops, houve um erro ao enviar SMS, tente novamente.'); + } + + return msg.reply('SMS enviado com sucesso!'); + } +} diff --git a/src/app/commands/index.ts b/src/app/commands/index.ts index ca99f95..777e29b 100644 --- a/src/app/commands/index.ts +++ b/src/app/commands/index.ts @@ -2,3 +2,4 @@ export { default as EconomyCommand } from './EconomyCommand'; export { default as QuoteCommand } from './QuoteCommand'; export { default as CepCommand } from './CepCommand'; export { default as ProfileCommand } from './ProfileCommand'; +export { default as SmsCommand } from './SmsCommand'; diff --git a/src/app/interfaces/Cep.ts b/src/app/interfaces/Cep.ts index 82911a9..ffec829 100644 --- a/src/app/interfaces/Cep.ts +++ b/src/app/interfaces/Cep.ts @@ -1,13 +1,11 @@ -interface IResponse { +export interface IResponse { data: IServerData; } -interface IServerData { +export interface IServerData { cep: string; state: string; city: string; neighborhood: string; street: string; } - -export { IResponse, IServerData }; diff --git a/src/app/interfaces/Currency.ts b/src/app/interfaces/Currency.ts index e7206e8..0937bef 100644 --- a/src/app/interfaces/Currency.ts +++ b/src/app/interfaces/Currency.ts @@ -1,4 +1,4 @@ -interface ICurrency { +export interface ICurrency { code: string; codein: string; name: string; @@ -11,5 +11,3 @@ interface ICurrency { timestamp: string; create_date: string; } - -export { ICurrency }; diff --git a/src/app/utils/CommandDispatcher.ts b/src/app/utils/CommandDispatcher.ts index d270aad..98d0a3f 100644 --- a/src/app/utils/CommandDispatcher.ts +++ b/src/app/utils/CommandDispatcher.ts @@ -1,6 +1,6 @@ import Command from './Command'; -export default class CommandDispatcher { +class CommandDispatcher { private commandsHandlers: Map> = new Map(); async register(name: string, command: Command) { @@ -20,3 +20,5 @@ export default class CommandDispatcher { } } } + +export const commandDispatcher = new CommandDispatcher(); diff --git a/src/index.ts b/src/index.ts index 0ff1e7f..fb9cd70 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,22 +1,28 @@ -import { client } from './server'; +import { client } from './services/whatsapp'; import { EconomyCommand, QuoteCommand, CepCommand, ProfileCommand, + SmsCommand, } from './app/commands'; -import CommandDispatcher from './app/utils/CommandDispatcher'; +import { commandDispatcher } from './app/utils/CommandDispatcher'; import type { Message } from 'whatsapp-web.js'; -const dispatcher = new CommandDispatcher(); +client.on('message', (message: Message) => { + const economyCommand = new EconomyCommand(); + const quoteCommand = new QuoteCommand(); + const cepCommand = new CepCommand(message.body); + const profileCommand = new ProfileCommand(message.body); + const smsCommand = new SmsCommand(message.body); -client.on('message', async (message: Message) => { - dispatcher.register('cotacao', new EconomyCommand()); - dispatcher.register('mencionar', new QuoteCommand()); - dispatcher.register('cep', new CepCommand(message.body)); - dispatcher.register('perfil', new ProfileCommand(message.body)); + commandDispatcher.register('cotacao', economyCommand); + commandDispatcher.register('mencionar', quoteCommand); + commandDispatcher.register('cep', cepCommand); + commandDispatcher.register('perfil', profileCommand); + commandDispatcher.register('sms', smsCommand); if (message.body.startsWith('!')) { - dispatcher.dispatch(message.body.slice(1), message); + commandDispatcher.dispatch(message.body.slice(1), message); } }); diff --git a/src/services/mobizon.ts b/src/services/mobizon.ts new file mode 100644 index 0000000..b1a23bf --- /dev/null +++ b/src/services/mobizon.ts @@ -0,0 +1,9 @@ +import { mobizon } from 'mobizon-node'; + +mobizon.setConfig({ + apiServer: process.env.MOBIZON_URL_SRV, + apiKey: process.env.MOBIZON_API_KEY, + format: 'json', +}); + +export default mobizon; diff --git a/src/server.ts b/src/services/whatsapp.ts similarity index 64% rename from src/server.ts rename to src/services/whatsapp.ts index b840515..a3f9940 100644 --- a/src/server.ts +++ b/src/services/whatsapp.ts @@ -1,16 +1,16 @@ import { Client, MessageMedia } from 'whatsapp-web.js'; -import qrcode from 'qrcode-terminal'; -import fs from 'fs'; -import dir from 'path'; +import { generate } from 'qrcode-terminal'; +import { existsSync, writeFile, unlink } from 'fs'; +import { resolve } from 'path'; interface INotification { reply: (args: string) => void; recipientIds: any[]; } -const sessionFile = dir.resolve('src', 'data', 'session.json'); +const sessionFile = resolve('src', 'data', 'session.json'); -const session = fs.existsSync(sessionFile) ? require(sessionFile) : null; +const session = existsSync(sessionFile) ? require(sessionFile) : null; const client = new Client({ puppeteer: { @@ -18,14 +18,14 @@ const client = new Client({ args: ['--no-sandbox'], }, session, -}); +} as any); client.on('qr', (qr: string) => { - qrcode.generate(qr, { small: true }); + generate(qr, { small: true }); }); client.on('authenticated', (session: any) => { - fs.writeFile(sessionFile, JSON.stringify(session), (err) => { + writeFile(sessionFile, JSON.stringify(session), (err) => { if (err) console.log(err); }); }); @@ -35,19 +35,22 @@ client.on('ready', async () => { const { pushname } = client.info; - client.sendMessage('5511987454933@c.us', `[${pushname}] - WhatsApp Online`); + client.sendMessage( + '5511963928063@c.us', + `[${pushname}] - WhatsApp Online\n\n[x] Star on project: https://github.com/caioagiani/whatsapp-bot`, + ); }); client.on('auth_failure', () => { - fs.unlink(sessionFile, () => { + unlink(sessionFile, () => { console.log('Autenticação falhou, tente novamente.'); process.exit(1); }); }); client.on('disconnected', () => { - fs.unlink(sessionFile, () => - console.log('Sessão WhatsApp perdeu a conexão, tente novamente.') + unlink(sessionFile, () => + console.log('Sessão WhatsApp perdeu a conexão, tente novamente.'), ); }); diff --git a/tsconfig.json b/tsconfig.json index b7914b2..25189c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,17 @@ { "compilerOptions": { - "outDir": "dist", - "target": "ES6", - "module": "CommonJS", + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "es2017", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, "resolveJsonModule": true, - "esModuleInterop": true + "allowJs": true } }