Compare commits

...

71 Commits

Author SHA1 Message Date
Leifer Mendez
eda8a67718 refactor(provider): twilio + hook 2022-12-07 18:06:27 +01:00
Leifer Mendez
73caf090ba fix(bot): added delay promises 2022-12-07 16:20:56 +01:00
Leifer Mendez
8dd3be909b feat(provider): added twilio provider 2022-12-06 21:28:52 +01:00
Leifer Mendez
24ac9fbf48 fix(bot): fix sensitive case 2022-12-06 19:35:21 +01:00
Leifer Mendez
4350dff22a feat(provider): 🔥 add twilii (weoking) 2022-12-05 20:45:05 +01:00
github-actions[bot]
30e3d443bb docs(contributor): contrib-readme-action has updated readme 2022-12-05 15:47:15 +00:00
Leifer Mendez
f5ea7fe2c4 Merge branch 'dev' of github.com:leifermendez/bot-whatsapp into dev 2022-12-05 16:46:47 +01:00
Leifer Mendez
94d139e484 ci(contributors): add username avatar 2022-12-05 16:46:29 +01:00
github-actions[bot]
8049241f3f docs(contributor): contrib-readme-action has updated readme 2022-12-05 15:45:01 +00:00
Leifer Mendez
28d308ed4b ci(contributors): add username avatar 2022-12-05 16:44:36 +01:00
Leifer Mendez
e0862053d0 Merge branch 'feature/fallback' into dev 2022-12-05 16:35:05 +01:00
Leifer Mendez
e0e76d3a56 ci(contributors): add username avatar 2022-12-05 16:33:22 +01:00
Leifer Mendez
242e44315b Merge pull request #139 from leifermendez/feature/fallback
Feature/fallback
2022-12-05 16:32:02 +01:00
Leifer Mendez
6b53ed13e2 docs(contributors): add username avatar 2022-12-05 16:15:21 +01:00
Leifer Mendez
fafccbcecc Merge commit 'f2533f1ed5c0078be59938720f2ecf96c616b843' into feature/fallback 2022-12-05 16:05:49 +01:00
Leifer Mendez
49698bfda9 fix(bot): update 2022-12-05 16:02:23 +01:00
Leifer Mendez
4154cc2230 feat(bot): 🔥 improvement provider handler 2022-12-05 14:49:23 +01:00
Leifer Mendez
ce8a96b958 feat(bot): add send image function 2022-12-05 13:01:32 +01:00
Leifer Mendez
f373a3abc7 chore(bot): fallback done 2022-12-05 09:59:40 +01:00
Leifer Mendez
371ee0a780 chore(bot): update fallback function 2022-12-04 16:01:52 +01:00
Leifer Mendez
327cf5730b Merge pull request #137 from vicente1992/feat-mysql-adapter
feat(adapter): added adapter mysql
2022-12-03 12:06:03 +01:00
vicente1992
717a7dc95f feat(adapter): added adapter mysql 2022-12-02 09:41:57 -05:00
Leifer Mendez
aa2417af12 chore(bot): added new fallback option 2022-12-02 15:22:13 +01:00
Leifer Mendez
f2533f1ed5 Merge pull request #136 from leifermendez/feature/monorepo
docs(contributing):  update
2022-12-01 21:34:54 +01:00
Leifer Mendez
7d41699207 docs(contributing): update 2022-12-01 21:33:50 +01:00
Leifer Mendez
f9ccfef8e0 Merge pull request #135 from leifermendez/feature/monorepo
chore(cli): 🔥 fix
2022-12-01 21:07:48 +01:00
Leifer Mendez
468a2ba251 chore(cli): 🔥 fix 2022-12-01 21:07:00 +01:00
Leifer Mendez
08dbdcf4ae Merge pull request #134 from leifermendez/feature/monorepo
chore(cli): 🎨 remove uneccesary steps
2022-12-01 20:59:57 +01:00
Leifer Mendez
50d73f7bc8 chore(cli): 🎨 remove uneccesary steps 2022-12-01 20:58:58 +01:00
Leifer Mendez
d7ed9ff592 Merge pull request #133 from leifermendez/feature/monorepo
chore(cli):  added new function
2022-12-01 20:45:34 +01:00
Leifer Mendez
05c6fd4528 chore(cli): added new function 2022-12-01 20:44:12 +01:00
Leifer Mendez
a99f424901 Merge pull request #132 from leifermendez/feature/monorepo
Feature/monorepo
2022-12-01 18:43:03 +01:00
Leifer Mendez
648354500b fix(fix): fix 2022-12-01 18:42:18 +01:00
Leifer Mendez
28c0480b8b fix(fix): fix 2022-12-01 18:39:49 +01:00
Leifer Mendez
f29ed6e29b Merge pull request #131 from leifermendez/feature/monorepo
Merge pull request #130 from leifermendez/dev
2022-12-01 18:38:03 +01:00
Leifer Mendez
903b4d79ac docs(contributing): 📝 added more example 2022-12-01 18:17:19 +01:00
Leifer Mendez
026c189901 Merge pull request #130 from leifermendez/dev
Dev
2022-12-01 12:52:46 +01:00
Leifer
f2b30ee349 docs: 📝 update CONTRIBUTING 2022-12-01 12:46:49 +01:00
Leifer
5c02a9325a chore(release): 0.2.0-alpha.0 2022-12-01 12:38:17 +01:00
Leifer
7645c8642f test 2022-12-01 12:33:18 +01:00
Leifer
c5ebbe319f refactor(hook): added new improvement 2022-12-01 12:32:45 +01:00
Leifer
6f36eb1690 test 2022-12-01 10:35:41 +01:00
Leifer
18f9e006a3 test 2022-12-01 10:34:30 +01:00
Leifer
a7e334ebe9 add banner 2022-12-01 10:27:18 +01:00
Leifer
a5e15d9d84 edit hook 2022-12-01 10:17:19 +01:00
Leifer
b3173517b4 add pretty-quick 2022-12-01 10:16:06 +01:00
Leifer Mendez
06d2963163 Merge pull request #129 from leifermendez/feature/monorepo
Feature/monorepo
2022-12-01 09:53:28 +01:00
Leifer Mendez
df8282015d docs(example-app): add cli crate app 2022-11-30 21:39:30 +01:00
Leifer Mendez
81b0aab850 refactor(io): added new method addChild 2022-11-30 21:06:26 +01:00
Leifer Mendez
a8705c5b44 refactor(io): added new method addChild 2022-11-30 21:05:32 +01:00
Leifer Mendez
efe739f9fc refactor(io): added new method addChild 2022-11-30 21:04:45 +01:00
Leifer Mendez
2e83a0508a docs(contribuiting): update 2022-11-30 17:14:50 +01:00
Leifer Mendez
d66adb2a1f Merge pull request #126 from leifermendez/feature/monorepo
Feature/monorepo
2022-11-29 22:14:56 +01:00
Leifer
e5cecdee03 docs(contributin): added more info 2022-11-29 22:13:58 +01:00
Leifer
9351af16b7 docs(contributing): added more info 2022-11-29 22:04:46 +01:00
Leifer
ad8831a75a f 2022-11-29 22:00:05 +01:00
Leifer
c63018ff08 f 2022-11-29 21:55:39 +01:00
Leifer
131bce3898 . 2022-11-29 21:45:44 +01:00
Leifer
fff9316030 . 2022-11-29 21:44:04 +01:00
Leifer
2b3148dc3c update documenta 2022-11-29 21:43:11 +01:00
Leifer Mendez
13a4202f08 docs(contributing): added main readme 2022-11-29 11:33:33 +01:00
Leifer Mendez
f0df143aaf Merge pull request #125 from leifermendez/feature/monorepo
Feature/monorepo
2022-11-29 11:23:07 +01:00
Leifer Mendez
70a94ab2c6 fix(linter): update linter and commitlint 2022-11-29 10:47:12 +01:00
Leifer Mendez
e6d18d1a72 chore(release): 0.1.1 2022-11-29 10:36:23 +01:00
Leifer Mendez
46cd57fb36 chore(release): 0.1.0 2022-11-29 10:33:25 +01:00
Leifer Mendez
4ae389846d feat: (🎸) add onClick prop to component 2022-11-29 10:22:37 +01:00
Leifer Mendez
37d04e9e89 test: (💍) Is justa test!
nothing

BREAKING CHANGE: 🧨 NO
2022-11-29 10:17:32 +01:00
Leifer Mendez
39d141ca67 refactor: (💡) change emoji 2022-11-28 21:20:48 +01:00
Leifer Mendez
befcc169e0 refactor: 💡 pkgmanager
Upgrade Yarn v3
2022-11-28 21:19:00 +01:00
Leifer Mendez
6bbb9c1b81 docs(main documentation): es una mejora 2022-11-28 19:44:20 +01:00
Leifer Mendez
bb77afc4d2 test 2022-11-28 19:40:53 +01:00
65 changed files with 19848 additions and 5233 deletions

View File

@@ -4,5 +4,5 @@
"reporter": ["html"],
"report-dir": "./coverage",
"check-coverage": true,
"lines": 95
"lines": 90
}

View File

@@ -2,7 +2,7 @@ name: Test / Coverage
on:
push:
branches: [feature/monorepo]
branches: [dev]
pull_request:
branches: [main]

20
.github/workflows/contributors.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Add contributors
on:
schedule:
- cron: '20 20 * * *'
push:
branches: [dev]
pull_request:
branches: [main, dev]
jobs:
contrib-readme-job:
runs-on: ubuntu-latest
name: A job to automate contrib in readme
steps:
- name: Contribute List
uses: akhilmhdh/contributors-readme-action@v2.3.6
with:
image_size: 50
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

11
.gitignore vendored
View File

@@ -14,12 +14,19 @@ mediaSend/*
.wwebjs_auth
packages/cli/config.json
config.json
.yarnrc.yml
coverage/
*.lcov
log
log/*
*.log
lib
tmp/
.yarn/*
!.yarn/releases
!.yarn/plugins/@yarnpkg/plugin-postinstall.cjs
.fleet/
example-app/
example-app*/
qr.svg
package-lock.json
package-lock.json
yarn-error.log

4
.husky/commit-msg Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit

2
.husky/pre-commit Normal file → Executable file
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn run format:check && yarn run format:write && git add .
yarn run fmt.staged

0
.husky/pre-push Normal file → Executable file
View File

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"conventionalCommits.scopes": [
"hook",
"contributing",
"cli",
"bot",
"provider"
]
}

View File

@@ -0,0 +1,8 @@
/* eslint-disable */
module.exports = {
name: "@yarnpkg/plugin-postinstall",
factory: function (require) {
var plugin;(()=>{"use strict";var e={d:(t,n)=>{for(var o in n)e.o(n,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:n[o]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{default:()=>s});const n=require("@yarnpkg/core"),o=require("clipanion"),a={postinstall:{description:"Postinstall hook that will always run in Yarn v2",type:n.SettingsType.STRING,default:""}},r=require("@yarnpkg/shell"),l=async e=>{if(e){console.log("Running postinstall command...");const t=await r.execute(e);if(0!==t)throw new Error("postinstall command failed with exit code "+t)}};var i=function(e,t,n,o){var a,r=arguments.length,l=r<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,n):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,n,o);else for(var i=e.length-1;i>=0;i--)(a=e[i])&&(l=(r<3?a(l):r>3?a(t,n,l):a(t,n))||l);return r>3&&l&&Object.defineProperty(t,n,l),l};class c extends o.Command{async execute(){const e=(await n.Configuration.find(this.context.cwd,this.context.plugins)).get("postinstall");await l(e)}}i([o.Command.Path("postinstall")],c.prototype,"execute",null);const s={configuration:a,commands:[c],hooks:{afterAllInstalled:async e=>{const t=e.configuration.get("postinstall");await l(t)}}};plugin=t})();
return plugin;
}
};

807
.yarn/releases/yarn-3.3.0.cjs vendored Normal file

File diff suppressed because one or more lines are too long

10
.yarnrc.yml Normal file
View File

@@ -0,0 +1,10 @@
nodeLinker: node-modules
npmPublishRegistry: 'https://registry.npmjs.org'
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-postinstall.cjs
spec: 'https://raw.githubusercontent.com/gravitywelluk/yarn-plugin-postinstall/master/bundles/%40yarnpkg/plugin-postinstall.js'
yarnPath: .yarn/releases/yarn-3.3.0.cjs
postinstall: npx husky install

View File

@@ -1,3 +1,48 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## 0.2.0-alpha.0 (2022-12-01)
### ⚠ BREAKING CHANGES
* 🧨 NO
### Features
* (🎸) add onClick prop to component ([4ae3898](https://github.com/leifermendez/bot-whatsapp/commit/4ae389846d38c133f6bb2129ae373eed39d9d08d))
### Bug Fixes
* **ci:** ci ([f55cfae](https://github.com/leifermendez/bot-whatsapp/commit/f55cfae6e4ccc1df949212999406680020d27f9c))
* **ci:** ci ([671c5b3](https://github.com/leifermendez/bot-whatsapp/commit/671c5b37f33360e8cb754625b8dd6e83bce9014d))
* **linter:** update linter and commitlint ([70a94ab](https://github.com/leifermendez/bot-whatsapp/commit/70a94ab2c6f8e4122780c77bc3a621944883e621))
* (💍) Is justa test! ([37d04e9](https://github.com/leifermendez/bot-whatsapp/commit/37d04e9e89d3f01fdc367654ba60fb11ab2614c4))
## 0.1.0 (2022-11-29)
### ⚠ BREAKING CHANGES
* 🧨 NO
### Features
* (🎸) add onClick prop to component ([4ae3898](https://github.com/leifermendez/bot-whatsapp/commit/4ae389846d38c133f6bb2129ae373eed39d9d08d))
### Bug Fixes
* **ci:** ci ([f55cfae](https://github.com/leifermendez/bot-whatsapp/commit/f55cfae6e4ccc1df949212999406680020d27f9c))
* **ci:** ci ([671c5b3](https://github.com/leifermendez/bot-whatsapp/commit/671c5b37f33360e8cb754625b8dd6e83bce9014d))
* (💍) Is justa test! ([37d04e9](https://github.com/leifermendez/bot-whatsapp/commit/37d04e9e89d3f01fdc367654ba60fb11ab2614c4))
#### Actualización 14 Ene 2022
- npm update

59
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,59 @@
# CONTRIBUTING
![](https://i.giphy.com/media/ntMt6TvalpstTIx7Ak/giphy.webp)
__Requerimientos:__
- Node v16 o superior __[descargar node](https://nodejs.org/es/download/)__
- __[Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#windows-stable)__ como gestor de paquetes. En el link conseguiras las intrucciones para instalar yarn.
- __[VSCode](https://code.visualstudio.com/download)__ (recomendado): Editor de codigo con plugins
- __[Conventional Commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits&ssr=false#overview)__ (plugin-vscode) este plugin te ayudara a crear commit semantico.
- Se usara la rama __dev__ *(https://github.com/leifermendez/bot-whatsapp/tree/dev)* como rama principal hasta que se haga oficialmente el lanzamiento de la V2
### 🚀 Iniciando
__Clonar repo rama dev__
```
git clone --branch dev https://github.com/leifermendez/bot-whatsapp
```
__Instalar dependencias__
```
cd bot-whatsapp
yarn install
```
__Compilar (build)__
Para compilar la aplicación es necesario ejecutar, eso te genera dentro de packages del monorepo un directorio `lib`
```
yarn build
```
__Example-app__
Se ejecuta el CLI (Command Line Interface) para ayudarte a crear un app-bot de ejemplo
```
yarn run cli
```
Abrir carpeta __example-app-base__ y ejecutar
```
cd example-app-base
npm i
npm run pre-copy
npm start
```
__Commit y Push__
El proyecto tiene implementado __[husky](https://typicode.github.io/husky/#/)__ es una herramienta que dispara unas acciones al momento de hacer commit y hacer push
__commit:__ Los commit son semanticos esto quiere decir que deben cumplir un standar al momento de escribirlos ejemplo ` feat(adapter): new adapter myqsl ` puede ver más info sobre esto __[aquí](https://github.com/conventional-changelog/commitlint/#what-is-commitlint)__
__push:__ Cada push ejecutar `yarn run test` el cual ejecuta los test internos que tienen que cumplir con __95% de cobertura__.
> Documento en constaten actualización....
------
- [Discord](https://link.codigoencasa.com/DISCORD)
- [Twitter](https://twitter.com/leifermendez)
- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR)
- [Telegram](https://t.me/leifermendez)

View File

@@ -1,3 +1,4 @@
```js
const {

2
GLOSSARY.md Normal file
View File

@@ -0,0 +1,2 @@
CTX: Es el objeto que representa un mensaje, con opciones, id, ref
messageInComming: Objeto entrante del provider {body, from,...}

View File

@@ -1,6 +1,6 @@
[![Test / Coverage](https://github.com/leifermendez/bot-whatsapp/actions/workflows/ci.yml/badge.svg)](https://github.com/leifermendez/bot-whatsapp/actions/workflows/ci.yml)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
--------
🦊 Documentación: [https://bot-whatsapp.pages.dev/](https://bot-whatsapp.pages.dev/)
Video como hacer PR: https://youtu.be/Lxt8Acob6aU
@@ -8,13 +8,79 @@ Video como hacer PR: https://youtu.be/Lxt8Acob6aU
**Comunidad**
<!-- readme: collaborators,contributors -start -->
<table>
<tr>
<td align="center">
<a href="https://github.com/leifermendez">
<img src="https://avatars.githubusercontent.com/u/15802366?v=4" width="50;" alt="leifermendez"/>
<br />
<sub><b>Leifer Mendez</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/aurik3">
<img src="https://avatars.githubusercontent.com/u/37228512?v=4" width="50;" alt="aurik3"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vicente1992">
<img src="https://avatars.githubusercontent.com/u/57806030?v=4" width="50;" alt="vicente1992"/>
<br />
<sub><b>Manuel Vicente Ortiz</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/leifermendezfroged">
<img src="https://avatars.githubusercontent.com/u/97020486?v=4" width="50;" alt="leifermendezfroged"/>
<br />
<sub><b>Leifer Mendez</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Gonzalito87">
<img src="https://avatars.githubusercontent.com/u/100331586?v=4" width="50;" alt="Gonzalito87"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/tonyvazgar">
<img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/>
<br />
<sub><b>Luis Antonio Vázquez García</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/ulisesvina">
<img src="https://avatars.githubusercontent.com/u/20508563?v=4" width="50;" alt="ulisesvina"/>
<br />
<sub><b>Ulises Viña</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rrruuuyyy">
<img src="https://avatars.githubusercontent.com/u/33061671?v=4" width="50;" alt="rrruuuyyy"/>
<br />
<sub><b>Rodrigo Mendoza Cabrera</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/yond1994">
<img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/>
<br />
<sub><b>Yonathan Suarez</b></sub>
</a>
</td></tr>
</table>
<!-- readme: collaborators,contributors -end -->
> Forma parte de este proyecto.
- [Discord](https://link.codigoencasa.com/DISCORD)
- [Twitter](https://twitter.com/leifermendez)
- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR)
- [Telegram](https://t.me/leifermendez)
- [Telegram](https://t.me/leifermendez)

32
TODO.md
View File

@@ -2,30 +2,50 @@
- [X] __(doc)__ Video de como colaborar PR
- [ ] __(doc)__ Video implementación de test y cobertura
- [ ] __(doc)__ Video explicacion de github action
- [ ] Crear packages list externas
### @bot-whatsapp/bot
- [ ] agregar export package
- [X] agregar export package
- [X] Posibilidad de en el capture meter todo un nuevo CTX de FLOW .addAnswer('Marca la opcion',{capture:true, join:CTX})
- [X] .addKeyword('1') no funciona con 1 caracter
- [X] sensitivy viene activado por defecto
- [ ] fallback respuesta en hijo: Se puede colocar en option el ref de la answer fallback
- [X] fallback respuesta en hijo: Se puede colocar en option el ref de la answer fallback
- [X] Cuando Envian Sticket devuelve mensaje raro
- [x] addAnswer agregar delay
- [ ] colocar mensaje esperando conectando whatsapp (provider)
- [ ] Cuando Envian Sticket devuelve mensaje raro
- [ ] createDatabase validar implementacion de funciones
- [ ] limitar caracteres de mensajes 4000
- [X] cuando envias numeros (5 o 1) se dispara el flujo
### @bot-whatsapp/database
- [X] agregar export package
- [X] __(doc):__ Video para explicar como implementar nuevos database
- [X] Mongo adapter
- [ ] MySQL adapter
- [X] MySQL adapter
- [ ] JsonFile adapter
### @bot-whatsapp/provider
- [X] agregar export package
- [ ] __(doc):__ Video para explicar como implementar nuevos providers
- [ ] WhatsappWeb provider enviar imagenes
- [ ] WhatsappWeb provider enviar audio
- [X] WhatsappWeb provider enviar imagenes
- [X] WhatsappWeb provider enviar audio
- [X] WhatsappWeb botones (Tiene truco) github:leifermendez/whatsapp-web.js
- [ ] Twilio adapter
- [ ] Meta adapter
### @bot-whatsapp/cli
- [X] Hacer comando para crear `example-app`
### @bot-whatsapp/create-bot
- [ ]
### Starters
- [X] Base
- [X] Basico
- [ ] Enviando Imagen
- [ ] Enviando Botones
- [ ] Mezclando flujos hijos
### Extra
- [X] Crear CI mantener fork update https://stackoverflow.com/questions/23793062/can-forks-be-synced-automatically-in-github

91
changelog.config.js Normal file
View File

@@ -0,0 +1,91 @@
module.exports = {
disableEmoji: false,
format: '{type}{scope}: {emoji}{subject}',
list: [
'test',
'feat',
'fix',
'chore',
'docs',
'refactor',
'style',
'ci',
'perf',
],
maxMessageLength: 64,
minMessageLength: 3,
questions: [
'type',
'scope',
'subject',
'body',
'breaking',
'issues',
'lerna',
],
scopes: [],
types: {
chore: {
description: 'Build process or auxiliary tool changes',
emoji: '(🤖)',
value: 'chore',
},
ci: {
description: 'CI related changes',
emoji: '(🎡)',
value: 'ci',
},
docs: {
description: 'Documentation only changes',
emoji: '(✏️)',
value: 'docs',
},
feat: {
description: 'A new feature',
emoji: '(🎸)',
value: 'feat',
},
fix: {
description: 'A bug fix',
emoji: '(🐛)',
value: 'fix',
},
perf: {
description: 'A code change that improves performance',
emoji: '(⚡️)',
value: 'perf',
},
refactor: {
description:
'A code change that neither fixes a bug or adds a feature',
emoji: '(💡)',
value: 'refactor',
},
release: {
description: 'Create a release commit',
emoji: '(🏹)',
value: 'release',
},
style: {
description:
'Markup, white-space, formatting, missing semi-colons...',
emoji: '(💄)',
value: 'style',
},
test: {
description: 'Adding missing tests',
emoji: '(💍)',
value: 'test',
},
messages: {
type: "Select the type of change that you're committing:",
customScope: 'Select the scope this component affects:',
subject:
'Write a short, imperative mood description of the change:\n',
body: 'Provide a longer description of the change:\n ',
breaking: 'List any breaking changes:\n',
footer: 'Issues this commit closes, e.g #123:',
confirmCommit: 'The packages that this commit has affected\n',
},
},
}

1
commitlint.config.js Normal file
View File

@@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] }

View File

@@ -0,0 +1,8 @@
{
"banner.output": [
"/** \n",
"* NO TOCAR ESTE ARCHIVO: Es generado automaticamente, si sabes lo que haces adelante ;)\n",
"* de lo contrario mejor ir a la documentacion o al servidor de discord link.codigoencasa.com/DISCORD\n",
"*/"
]
}

View File

@@ -1,31 +1,37 @@
{
"name": "@bot-whatsapp/root",
"version": "0.0.1",
"version": "0.2.0-alpha.0",
"description": "Bot de wahtsapp open source para MVP o pequeños negocios",
"main": "app.js",
"private": true,
"scripts": {
"commit": "git-cz",
"cli:rollup": "rollup --config ./packages/cli/rollup-cli.config.js ",
"create-bot:rollup": "rollup --config ./packages/create-bot-whatsapp/rollup-create.config.js ",
"bot:rollup": "rollup --config ./packages/bot/rollup-bot.config.js",
"provider:rollup": "rollup --config ./packages/provider/rollup-provider.config.js ",
"database:rollup": "rollup --config ./packages/database/rollup-database.config.js",
"format:check": "prettier --check ./packages",
"format:write": "prettier --write ./packages",
"fmt.staged": "pretty-quick --staged",
"lint:check": "eslint ./packages",
"lint:fix": "eslint --fix ./packages",
"build": "yarn run cli:rollup && yarn run bot:rollup && yarn run provider:rollup && yarn run database:rollup",
"link.dist": "cd packages/bot && npm link && cd ../provider && npm link && cd ../cli && npm link",
"copy.lib": "node ./scripts/move.js",
"test.unit": "node ./node_modules/uvu/bin.js packages test",
"test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit",
"test": "npm run test.coverage",
"cli": "node ./packages/cli/bin/cli.js",
"create": "node ./packages/create-bot-whatsapp/bin/create.js",
"dev:debug": "node --inspect ./example-app/app.js",
"dev": "node ./example-app/app.js",
"prepare": "npx husky install",
"preinstall": "npx only-allow yarn"
"preinstall": "npx only-allow yarn",
"postinstall": "npx prettier --write .",
"release": "standard-version"
},
"workspaces": [
"packages/create-bot-whatsapp",
"packages/bot",
"packages/cli",
"packages/database",
@@ -52,36 +58,36 @@
"repository": "https://github.com/leifermendez/bot-whatsapp",
"license": "ISC",
"devDependencies": {
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0",
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.1",
"c8": "^7.12.0",
"commitizen": "^4.2.5",
"conventional-changelog": "^3.1.25",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"fs-extra": "^11.1.0",
"git-cz": "^4.9.0",
"husky": "^8.0.2",
"only-allow": "^1.1.1",
"prettier": "^2.7.1",
"prettier": "^2.8.0",
"pretty-quick": "^3.1.3",
"prompts": "^2.4.2",
"rimraf": "^3.0.2",
"rollup": "^3.2.3",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-copy": "^3.4.0",
"standard-version": "^9.5.0",
"uvu": "^0.5.6"
},
"packageManager": "yarn@1.22.19",
"packageManager": "yarn@3.3.0",
"engines": {
"node": ">=16",
"npm": "please-use-yarn",
"yarn": ">=1"
"yarn": ">=3"
},
"author": "Leifer Mendez <leifer33@gmail.com>",
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
"author": "Leifer Mendez <leifer33@gmail.com>"
}

View File

@@ -1,6 +1,13 @@
const { toCtx } = require('../io/methods')
const { printer } = require('../utils/interactive')
const { delay } = require('../utils/delay')
const Queue = require('../utils/queue')
const { Console } = require('console')
const { createWriteStream } = require('fs')
const logger = new Console({
stdout: createWriteStream(`${process.cwd()}/core.class.log`),
})
/**
* [ ] Escuchar eventos del provider asegurarte que los provider emitan eventos
* [ ] Guardar historial en db
@@ -21,7 +28,14 @@ class CoreClass {
}
}
/**
* Manejador de eventos
*/
listenerBusEvents = () => [
{
event: 'preinit',
func: () => printer('Iniciando provider espere...'),
},
{
event: 'require_action',
func: ({ instructions, title = '⚡⚡ ACCION REQUERIDA ⚡⚡' }) =>
@@ -44,16 +58,19 @@ class CoreClass {
]
/**
* @private
* @param {*} ctxMessage
*
* @param {*} messageInComming
* @returns
*/
handleMsg = async (messageInComming) => {
logger.log(`[handleMsg]: `, messageInComming)
const { body, from } = messageInComming
let msgToSend = []
let fallBackFlag = false
if (!body.length) return
//Consultamos mensaje previo en DB
const prevMsg = await this.databaseClass.getPrevByNumber(from)
//Consultamos for refSerializada en el flow actual
const refToContinue = this.flowClass.findBySerialize(
prevMsg?.refSerialize
)
@@ -67,14 +84,24 @@ class CoreClass {
this.databaseClass.save(ctxByNumber)
}
//Si se tiene un callback se ejecuta
if (refToContinue && prevMsg?.options?.callback) {
const indexFlow = this.flowClass.findIndexByRef(refToContinue?.ref)
this.flowClass.allCallbacks[indexFlow].callback(messageInComming)
// 📄 [options: fallback]: esta funcion se encarga de repetir el ultimo mensaje
const fallBack = () => {
fallBackFlag = true
msgToSend = this.flowClass.find(refToContinue?.keyword, true) || []
this.sendFlow(msgToSend, from)
return refToContinue
}
//Si se tiene anidaciones de flows, si tienes anidados obligatoriamente capture:true
if (prevMsg?.options?.nested?.length) {
// 📄 [options: callback]: Si se tiene un callback se ejecuta
if (!fallBackFlag && refToContinue && prevMsg?.options?.callback) {
const indexFlow = this.flowClass.findIndexByRef(refToContinue?.ref)
this.flowClass.allCallbacks[indexFlow].callback(messageInComming, {
fallBack,
})
}
// 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa
if (!fallBackFlag && prevMsg?.options?.nested?.length) {
const nestedRef = prevMsg.options.nested
const flowStandalone = nestedRef.map((f) => ({
...nestedRef.find((r) => r.refSerialize === f.refSerialize),
@@ -85,28 +112,44 @@ class CoreClass {
return
}
//Consultamos si se espera respuesta por parte de cliente "Ejemplo: Dime tu nombre"
if (!prevMsg?.options?.nested?.length && prevMsg?.options?.capture) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
} else {
msgToSend = this.flowClass.find(body) || []
// 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean
if (!fallBackFlag && !prevMsg?.options?.nested?.length) {
const typeCapture = typeof prevMsg?.options?.capture
const valueCapture = prevMsg?.options?.capture
if (['string', 'boolean'].includes(typeCapture) && valueCapture) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
this.sendFlow(msgToSend, from)
return
}
}
msgToSend = this.flowClass.find(body) || []
this.sendFlow(msgToSend, from)
}
/**
* Enviar mensaje con contexto atraves del proveedor de whatsapp
* @param {*} numberOrId
* @param {*} ctxMessage ver más en GLOSSARY.md
* @returns
*/
sendProviderAndSave = (numberOrId, ctxMessage) => {
const { answer } = ctxMessage
return Promise.all([
this.providerClass.sendMessage(numberOrId, answer),
this.providerClass.sendMessage(numberOrId, answer, ctxMessage),
this.databaseClass.save({ ...ctxMessage, from: numberOrId }),
])
}
sendFlow = (messageToSend, numberOrId) => {
sendFlow = async (messageToSend, numberOrId) => {
const queue = []
for (const ctxMessage of messageToSend) {
queue.push(this.sendProviderAndSave(numberOrId, ctxMessage))
const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs)
Queue.enqueue(() =>
this.sendProviderAndSave(numberOrId, ctxMessage)
)
}
return Promise.all(queue)
}

View File

@@ -1,7 +1,7 @@
const CoreClass = require('./core/core.class')
const ProviderClass = require('./provider/provider.class')
const FlowClass = require('./io/flow.class')
const { addKeyword, addAnswer, toSerialize } = require('./io/methods')
const { addKeyword, addAnswer, addChild, toSerialize } = require('./io/methods')
/**
* Crear instancia de clase Bot
@@ -22,11 +22,13 @@ const createFlow = (args) => {
/**
* Crear instancia de clase Provider
* Depdendiendo del Provider puedes pasar argumentos
* Ver Documentacion
* @param {*} args
* @returns
*/
const createProvider = (providerClass = class {}) => {
const providerInstance = new providerClass()
const createProvider = (providerClass = class {}, args = null) => {
const providerInstance = new providerClass(args)
if (!providerClass.prototype instanceof ProviderClass)
throw new Error('El provider no implementa ProviderClass')
return providerInstance
@@ -38,6 +40,7 @@ module.exports = {
createProvider,
addKeyword,
addAnswer,
addChild,
toSerialize,
ProviderClass,
CoreClass,

View File

@@ -21,29 +21,25 @@ class FlowClass {
}
find = (keyOrWord, symbol = false, overFlow = null) => {
keyOrWord = `${keyOrWord}`
let capture = false
let messages = []
let refSymbol = null
overFlow = overFlow ?? this.flowSerialize
const mapSensitiveString = (str, flag = false) => {
if (!flag && Array.isArray(str)) {
return str.map((c) => c.toLowerCase())
/** Retornar expresion regular para buscar coincidencia */
const mapSensitive = (str, flag = false) => {
const regexSensitive = flag ? 'g' : 'i'
if (Array.isArray(str)) {
return new RegExp(str.join('|'), regexSensitive)
}
if (!flag && typeof str === 'string') {
return str.toLowerCase()
}
return str
return new RegExp(str, regexSensitive)
}
const findIn = (keyOrWord, symbol = false, flow = overFlow) => {
const sensitive = refSymbol?.options?.sensitive || false
capture = refSymbol?.options?.capture || false
keyOrWord = mapSensitiveString(keyOrWord, sensitive)
if (capture) return messages
if (symbol) {
@@ -51,9 +47,9 @@ class FlowClass {
if (refSymbol?.answer) messages.push(refSymbol)
if (refSymbol?.ref) findIn(refSymbol.ref, true)
} else {
refSymbol = flow.find((c) =>
mapSensitiveString(c.keyword, sensitive).includes(keyOrWord)
)
refSymbol = flow.find((c) => {
return mapSensitive(c.keyword, sensitive).test(keyOrWord)
})
if (refSymbol?.ref) findIn(refSymbol.ref, true)
return messages
}

View File

@@ -1,15 +1,15 @@
const { generateRef } = require('../../utils/hash')
const { toJson } = require('./toJson')
const { toSerialize } = require('./toSerialize')
/**
*
* @param answer string
* @param options {media:string, buttons:[], capture:true default false}
* @param options {media:string, buttons:[{"body":"😎 Cursos"}], delay:ms, capture:true default false}
* @returns
*/
const addAnswer =
(inCtx) =>
(answer, options, cb = null, nested = []) => {
answer = Array.isArray(answer) ? answer.join('\n') : answer
/**
* Todas las opciones referentes a el mensaje en concreto options:{}
* @returns
@@ -24,6 +24,7 @@ const addAnswer =
: false,
child:
typeof options?.child === 'string' ? `${options?.child}` : null,
delay: typeof options?.delay === 'number' ? options?.delay : 0,
})
const getNested = () => ({
@@ -78,6 +79,7 @@ const addAnswer =
}
}
/// Retornar contexto no colocar nada más abajo de esto
const ctx = ctxAnswer()
return {

View File

@@ -0,0 +1,15 @@
const { toSerialize } = require('./toSerialize')
/**
* @deprecate
* @param answer string
* @param options {media:string, buttons:[], capture:true default false}
* @returns
*/
const addChild = (flowIn = null) => {
if (!flowIn?.toJson) {
throw new Error('DEBE SER UN FLOW CON toJSON()')
}
return toSerialize(flowIn.toJson())
}
module.exports = { addChild }

View File

@@ -1,7 +1,8 @@
const { addAnswer } = require('./addAnswer')
const { addKeyword } = require('./addKeyword')
const { addChild } = require('./addChild')
const { toSerialize } = require('./toSerialize')
const { toCtx } = require('./toCtx')
const { toJson } = require('./toJson')
module.exports = { addAnswer, addKeyword, toCtx, toJson, toSerialize }
module.exports = { addAnswer, addKeyword, addChild, toCtx, toJson, toSerialize }

View File

@@ -19,7 +19,7 @@ class ProviderClass extends EventEmitter {
*
*/
sendMessage = async (userId, message) => {
sendMessage = async (userId, message, sendMessage) => {
if (NODE_ENV !== 'production')
console.log('[sendMessage]', { userId, message })
return message

View File

@@ -1,3 +1,4 @@
const banner = require('../../config/banner.rollup.json')
const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { join } = require('path')
@@ -7,6 +8,7 @@ const PATH = join(__dirname, 'lib', 'bundle.bot.cjs')
module.exports = {
input: join(__dirname, 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: PATH,
format: 'cjs',
},

View File

@@ -22,6 +22,15 @@ test('Debere probar las propeidades array', () => {
assert.is(MAIN_CTX.ctx.keyword, ARRANGE.keyword)
})
test('Debere probar las propeidades array en answer', () => {
const ARRANGE = {
keyword: ['hola!', 'ole'],
}
const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer(['hola', 'chao'])
assert.is(MAIN_CTX.ctx.keyword, ARRANGE.keyword)
})
test('Debere probar toSerialize', () => {
const ARRANGE = {
keyword: ['hola!', 'ole'],

View File

@@ -0,0 +1,4 @@
const delay = (miliseconds) =>
new Promise((res) => setTimeout(res, miliseconds))
module.exports = { delay }

View File

@@ -0,0 +1,46 @@
class Queue {
static queue = []
static pendingPromise = false
static enqueue(promise) {
return new Promise((resolve, reject) => {
this.queue.push({
promise,
resolve,
reject,
})
this.dequeue()
})
}
static dequeue() {
if (this.workingOnPromise) {
return false
}
const item = this.queue.shift()
if (!item) {
return false
}
try {
this.workingOnPromise = true
item.promise()
.then((value) => {
this.workingOnPromise = false
item.resolve(value)
this.dequeue()
})
.catch((err) => {
this.workingOnPromise = false
item.reject(err)
this.dequeue()
})
} catch (err) {
this.workingOnPromise = false
item.reject(err)
this.dequeue()
}
return true
}
}
module.exports = Queue

0
packages/cli/bin/cli.js Normal file → Executable file
View File

View File

@@ -0,0 +1,21 @@
const fs = require('fs-extra')
/**
* Copy files
*/
const copyFiles = async (from, to) => {
try {
await fs.copy(from, to)
console.log('success!')
} catch (err) {
console.error(err)
}
}
const copyBaseApp = async () => {
const BASEP_APP_PATH_FROM = `${process.cwd()}/starters/apps/base`
const BASEP_APP_PATH_TO = `${process.cwd()}/example-app-base`
await copyFiles(BASEP_APP_PATH_FROM, BASEP_APP_PATH_TO)
}
module.exports = { copyBaseApp }

View File

@@ -2,6 +2,7 @@ const prompts = require('prompts')
const { yellow, red } = require('kleur')
const { installAll } = require('../install')
const { cleanSession } = require('../clean')
const { copyBaseApp } = require('../create-app')
const { checkNodeVersion, checkOs } = require('../check')
const { jsonConfig } = require('../configuration')
@@ -9,10 +10,16 @@ const startInteractive = async () => {
const questions = [
{
type: 'text',
name: 'dependencies',
name: 'exampeOpt',
message:
'Quieres actualizar las librerias "whatsapp-web.js"? (Y/n)',
'Quieres crear una app de ejemplo "example-app-example"? (Y/n)',
},
// {
// type: 'text',
// name: 'dependencies',
// message:
// 'Quieres actualizar las librerias "whatsapp-web.js"? (Y/n)',
// },
{
type: 'text',
name: 'cleanTmp',
@@ -57,11 +64,12 @@ const startInteractive = async () => {
const {
dependencies = '',
cleanTmp = '',
exampeOpt = '',
providerDb = [],
providerWs = [],
} = response
/**
* Question #1
* Question
* @returns
*/
const installOrUdpateDep = async () => {
@@ -75,7 +83,7 @@ const startInteractive = async () => {
}
/**
* Question #2
* Question
* @returns
*/
const cleanAllSession = async () => {
@@ -88,6 +96,16 @@ const startInteractive = async () => {
}
}
const createApp = async () => {
const answer = exampeOpt.toLowerCase() || 'n'
if (answer.includes('n')) return true
if (answer.includes('y')) {
await copyBaseApp()
return true
}
}
const vendorProvider = async () => {
if (!providerWs.length) {
console.log(
@@ -117,6 +135,7 @@ const startInteractive = async () => {
}
}
await createApp()
await installOrUdpateDep()
await cleanAllSession()
await vendorProvider()

View File

@@ -1,3 +1,4 @@
const banner = require('../../config/banner.rollup.json')
const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { join } = require('path')
@@ -7,6 +8,7 @@ const PATH = join(__dirname, 'lib', 'cli', 'bundle.cli.cjs')
module.exports = {
input: join(__dirname, 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: PATH,
format: 'cjs',
},

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env node
const main = require('../lib/bin/bundle.create.cjs')
main()

View File

@@ -0,0 +1,12 @@
/**
* Main function
*/
const main = () => {
console.clear()
console.log(``)
console.log(`[PostInstall]: Este es el main function.`)
console.log(`[PostInstall]: 👌 Aqui podrias instalar cosas`)
console.log(``)
}
module.exports = main

View File

@@ -0,0 +1,13 @@
{
"name": "create-bot-whatsapp",
"version": "0.0.1",
"description": "",
"main": "./lib/bin/bundle.create.cjs",
"private": true,
"dependencies": {
"@bot-whatsapp/cli": "*"
},
"bin": {
"bot": "./lib/bin/bundle.create.cjs"
}
}

View File

@@ -0,0 +1,16 @@
const banner = require('../../config/banner.rollup.json')
const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { join } = require('path')
const PATH = join(__dirname, 'lib', 'bin', 'bundle.create.cjs')
module.exports = {
input: join(__dirname, 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: PATH,
format: 'cjs',
},
plugins: [commonjs(), nodeResolve()],
}

View File

@@ -25,6 +25,28 @@ const main = async () => {
}
```
#### CTX
```json
{
ref: 'ans_7d9981e5-5019-422c-a19a-565cbb021391',
keyword: 'ans_cfdad31b-ff6d-475f-873a-4ed6f8a79a43',
answer: 'Esperando respuesta...',
options: {
media: null,
buttons: [],
capture: true,
child: null,
nested: [Array],
keyword: {},
callback: true
},
refSerialize: '81f18f563fd26a6c6d12c62aed98095f',
from: 'NUMERO_PERSONA_QUE_ESCRIBE'
}
```
#### Video
> Video explicando como debes de agregar nuevos adaptadores

View File

@@ -7,13 +7,14 @@
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {},
"dependencies": {
"dotenv": "^16.0.3",
"mongodb": "^4.11.0"
"mongodb": "^4.11.0",
"mysql2": "^2.3.3"
},
"exports": {
"./mock": "./lib/mock/index.cjs",
"./mongo": "./lib/mongo/index.cjs"
"./mongo": "./lib/mongo/index.cjs",
"./mysql": "./lib/mysql/index.cjs"
}
}

View File

@@ -1,3 +1,4 @@
const banner = require('../../config/banner.rollup.json')
const commonjs = require('@rollup/plugin-commonjs')
const { join } = require('path')
@@ -5,6 +6,7 @@ module.exports = [
{
input: join(__dirname, 'src', 'mock', 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'mock', 'index.cjs'),
format: 'cjs',
},
@@ -13,9 +15,19 @@ module.exports = [
{
input: join(__dirname, 'src', 'mongo', 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'mongo', 'index.cjs'),
format: 'cjs',
},
plugins: [commonjs()],
},
{
input: join(__dirname, 'src', 'mysql', 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'mysql', 'index.cjs'),
format: 'cjs',
},
plugins: [commonjs()],
},
]

View File

@@ -1,15 +1,20 @@
/**
* Si necesitas saber que trae el "ctx"
* Puedes ver el README.md dentro packages/database
*/
class MockDatabase {
db
listHistory = []
constructor() {
/**
* Se debe cargar listHistory con historial de mensajes
* para que se pueda continuar el flow
*/
constructor() {}
getPrevByNumber = (from) => {
const history = this.listHistory.slice().reverse()
return history.find((a) => a.from === from)
}
save = (ctx) => {
console.log('Guardando DB...', ctx)
this.listHistory.push(ctx)
}
}

View File

@@ -0,0 +1,69 @@
const mysql = require('mysql2')
class MyslAdapter {
db
listHistory = []
credentials = { host: null, user: null, database: null }
constructor(_credentials) {
this.credentials = _credentials
this.init().then()
}
async init() {
this.db = mysql.createConnection(this.credentials)
await this.db.connect((error) => {
if (!error) {
console.log(`Solicitud de conexión a base de datos exitosa`)
}
if (error) {
console.log(`Solicitud de conexión fallida ${error.stack}`)
}
})
}
getPrevByNumber = (from) =>
new Promise((resolve, reject) => {
const sql = `SELECT * FROM history WHERE phone=${from} ORDER BY id DESC`
this.db.query(sql, (error, rows) => {
if (error) {
reject(error)
}
if (rows.length) {
const [row] = rows
row.options = JSON.parse(row.options)
resolve(row)
}
if (!rows.length) {
resolve(null)
}
})
})
save = (ctx) => {
const values = [
[
ctx.ref,
ctx.keyword,
ctx.answer,
ctx.refSerialize,
ctx.from,
JSON.stringify(ctx.options),
],
]
const sql =
'INSERT INTO history (ref, keyword, answer, refSerialize, phone, options ) values ?'
this.db.query(sql, [values], (err) => {
if (err) throw err
console.log('Guardado en DB...', values)
})
this.listHistory.push(ctx)
}
}
module.exports = MyslAdapter

View File

@@ -29,10 +29,13 @@
"@types/node": "latest",
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.43.0",
"autoprefixer": "10.4.11",
"eslint": "8.28.0",
"eslint-plugin-qwik": "0.14.1",
"node-fetch": "3.3.0",
"postcss": "^8.4.16",
"prettier": "2.7.1",
"tailwindcss": "^3.1.8",
"typescript": "4.9.3",
"vite": "3.2.4",
"vite-tsconfig-paths": "3.5.0",

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -63,4 +63,4 @@ code {
border-radius: 3px;
font-size: 0.9em;
border-bottom: 2px solid #bfbfbf;
}
}

View File

@@ -0,0 +1,21 @@
/** @type {import('tailwindcss').Config} */
const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");
module.exports = {
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
theme: {
extend: {
colors: {
primary: colors.purple,
secondary: colors.sky,
},
fontFamily: {
sans: ["'Inter'", ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [],
darkMode: "class",
};

View File

@@ -1,13 +0,0 @@
# @bot-whatsapp/provider
```js
// bootstrap.js Como iniciar el provider
const { inout, provider, database } = require('@bot-whatsapp')
provider.start()
provider.close()
```
- [ ] whatsapp-web.js _verificar update_
- [ ] Meta _verificar tokens_
- [ ] Twilio _verificar tokens_

View File

@@ -1,3 +1,4 @@
const banner = require('../../config/banner.rollup.json')
const { join } = require('path')
const commonjs = require('@rollup/plugin-commonjs')
@@ -5,6 +6,7 @@ module.exports = [
{
input: join(__dirname, 'src', 'web-whatsapp', 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'web-whatsapp', 'index.cjs'),
format: 'cjs',
},
@@ -13,6 +15,7 @@ module.exports = [
{
input: join(__dirname, 'src', 'twilio', 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'twilio', 'index.cjs'),
format: 'cjs',
},
@@ -21,6 +24,7 @@ module.exports = [
{
input: join(__dirname, 'src', 'mock', 'index.js'),
output: {
banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'mock', 'index.cjs'),
format: 'cjs',
},

View File

@@ -1,19 +1,59 @@
const twilio = require('twilio')
const { ProviderClass } = require('@bot-whatsapp/bot')
const TwilioVendor = new twilio(accountSid, authToken)
const TwilioWebHookServer = require('./server')
const { parseNumber } = require('./utils')
/**
* { accountSid, authToken, vendorNumber }
*/
class TwilioProvider extends ProviderClass {
constructor() {
super(TwilioVendor)
twilioHook
vendor
vendorNumber
constructor({ accountSid, authToken, vendorNumber }, _port = 3000) {
super()
this.vendor = new twilio(accountSid, authToken)
this.twilioHook = new TwilioWebHookServer(_port)
this.vendorNumber = vendorNumber
this.twilioHook.start()
const listEvents = this.busEvents()
for (const { event, func } of listEvents) {
this.twilioHook.on(event, func)
}
}
sendMessage = (message) =>
this.vendor.messages.create({
sendMessage = async (number, message) => {
return this.vendor.messages.create({
body: message,
to: '+12345678901', // Text this number
from: '+12345678901', // From a valid Twilio number
from: ['whatsapp:+', parseNumber(this.vendorNumber)].join(''),
to: ['whatsapp:+', parseNumber(number)].join(''),
})
}
/**
* Mapeamos los eventos nativos de whatsapp-web.js a los que la clase Provider espera
* para tener un standar de eventos
* @returns
*/
busEvents = () => [
{
event: 'auth_failure',
func: (payload) => this.emit('error', payload),
},
{
event: 'ready',
func: () => this.emit('ready', true),
},
{
event: 'message',
func: (payload) => {
this.emit('message', payload)
},
},
]
}
module.exports = TwilioProvider

View File

@@ -0,0 +1,63 @@
const { EventEmitter } = require('node:events')
const polka = require('polka')
const { urlencoded } = require('body-parser')
const { parseNumber } = require('./utils')
/**
* Encargado de levantar un servidor HTTP con una hook url
* [POST] /twilio-hook
*/
class TwilioWebHookServer extends EventEmitter {
twilioServer
twilioPort
constructor(_twilioPort) {
this.twilioServer = this.buildHTTPServer()
this.twilioPort = _twilioPort
}
/**
* Mensaje entrante
* emit: 'message'
* @param {*} req
* @param {*} res
*/
incomingMsg = (req, res) => {
const { body } = req
this.emit('message', {
from: parseNumber(body.From),
to: parseNumber(body.To),
body: body.Body,
})
const json = JSON.stringify({ body })
res.end(json)
}
/**
* Contruir HTTP Server
* @returns
*/
buildHTTPServer = () => {
return polka()
.use(urlencoded({ extended: true }))
.post('/twilio-hook', this.incomingMsg)
}
/**
* Puerto del HTTP
* @param {*} port default 3000
*/
start = () => {
this.twilioServer.listen(this.twilioPort, () => {
console.log(``)
console.log(`[Twilio]: Agregar esta url "WHEN A MESSAGE COMES IN"`)
console.log(
`[Twilio]: http://localhost:${this.twilioPort}/twilio-hook`
)
console.log(`[Twilio]: Más información en la documentacion`)
console.log(``)
})
this.emit('ready')
}
}
module.exports = TwilioWebHookServer

View File

@@ -0,0 +1,5 @@
const parseNumber = (number) => {
return `${number}`.replace('whatsapp:', '').replace('+', '')
}
module.exports = { parseNumber }

View File

@@ -1,13 +1,24 @@
const { Client, LocalAuth } = require('whatsapp-web.js')
const {
Client,
LocalAuth,
MessageMedia,
Buttons,
List,
} = require('whatsapp-web.js')
const { ProviderClass } = require('@bot-whatsapp/bot')
const { Console } = require('console')
const { createWriteStream } = require('fs')
const { createWriteStream, existsSync } = require('fs')
const { cleanNumber, generateImage, isValidNumber } = require('./utils')
const logger = new Console({
stdout: createWriteStream('./log'),
})
/**
* WebWhatsappProvider: Es una clase tipo adaptor
* que extiende clases de ProviderClass (la cual es como interfaz para sber que funciones rqueridas)
* https://github.com/pedroslopez/whatsapp-web.js
*/
class WebWhatsappProvider extends ProviderClass {
vendor
constructor() {
@@ -21,13 +32,14 @@ class WebWhatsappProvider extends ProviderClass {
for (const { event, func } of listEvents) {
this.vendor.on(event, func)
}
this.vendor.emit('preinit')
this.vendor.initialize().catch((e) => {
logger.log(e)
this.emit('require_action', {
instructions: [
`Debes eliminar la carpeta .wwebjs_auth`,
`y reiniciar nuevamente el bot `,
`(Opcion 1): Debes eliminar la carpeta .wwebjs_auth y reiniciar nuevamente el bot. `,
`(Opcion 2): Intenta actualizar el paquete [npm install whatsapp-web.js] `,
`(Opcion 3): Ir FORO de discord https://link.codigoencasa.com/DISCORD `,
],
})
})
@@ -60,10 +72,6 @@ class WebWhatsappProvider extends ProviderClass {
event: 'ready',
func: () => this.emit('ready', true),
},
{
event: 'authenticated',
func: () => this.emit('ready', true),
},
{
event: 'message',
func: (payload) => {
@@ -80,10 +88,87 @@ class WebWhatsappProvider extends ProviderClass {
},
]
sendMessage = async (userId, message) => {
const number = cleanNumber(userId)
/**
* Enviar un archivo multimedia
* https://docs.wwebjs.dev/MessageMedia.html
* @private
* @param {*} number
* @param {*} mediaInput
* @returns
*/
sendMedia = async (number, mediaInput = null) => {
if (!existsSync(mediaInput))
throw new Error(`NO_SE_ENCONTRO: ${mediaInput}`)
const media = MessageMedia.fromFilePath(mediaInput)
return this.vendor.sendMessage(number, media, {
sendAudioAsVoice: true,
})
}
/**
* Enviar botones
* https://docs.wwebjs.dev/Buttons.html
* @private
* @param {*} number
* @param {*} message
* @param {*} buttons []
* @returns
*/
sendButtons = async (number, message, buttons = []) => {
const buttonMessage = new Buttons(message, buttons, '', '')
return this.vendor.sendMessage(number, buttonMessage)
}
/**
* Enviar lista
* https://docs.wwebjs.dev/List.html
* @private
* @alpha No funciona en whatsapp bussines
* @param {*} number
* @param {*} message
* @param {*} buttons []
* @returns
*/
sendList = async (number, message, listInput = []) => {
let sections = [
{
title: 'sectionTitle',
rows: [
{ title: 'ListItem1', description: 'desc' },
{ title: 'ListItem2' },
],
},
]
let list = new List('List body', 'btnText', sections, 'Title', 'footer')
return this.vendor.sendMessage(number, list)
}
/**
* Enviar un mensaje solo texto
* https://docs.wwebjs.dev/Message.html
* @private
* @param {*} number
* @param {*} message
* @returns
*/
sendText = async (number, message) => {
return this.vendor.sendMessage(number, message)
}
/**
*
* @param {*} userId
* @param {*} message
* @param {*} param2
* @returns
*/
sendMessage = async (userId, message, { options }) => {
const number = cleanNumber(userId)
if (options?.media) return this.sendMedia(number, options.media)
if (options?.buttons?.length)
return this.sendButtons(number, message, options.buttons)
return this.sendText(number, message)
}
}
module.exports = WebWhatsappProvider

18
scripts/move.js Normal file
View File

@@ -0,0 +1,18 @@
const fs = require('fs-extra')
const PACKAGES_PATH = `${process.cwd()}/packages`
const NAME_PREFIX = `@bot-whatsapp`
const [, , appDir] = process.argv || []
const copyLibPkg = async (pkgName, to) => {
const FROM = `${PACKAGES_PATH}/${pkgName}`
const TO = `${process.cwd()}/${to}/node_modules/${NAME_PREFIX}/${pkgName}`
await fs.copy(FROM, TO)
}
Promise.all([
copyLibPkg('create-bot-whatsapp', appDir),
copyLibPkg('bot', appDir),
copyLibPkg('database', appDir),
copyLibPkg('provider', appDir),
]).then(() => console.log('Todas las lib copiadas'))

View File

@@ -0,0 +1,12 @@
### BASE APP
Este bot contiene un flujo basico en el cual una persona (cliente) escribe **"hola"** y el bot responde.
- Bienvenido a mi tienda
- Como puedo ayudarte?
- Tengo: Zapatos Bolsos etc..
------
- [Discord](https://link.codigoencasa.com/DISCORD)
- [Twitter](https://twitter.com/leifermendez)
- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR)
- [Telegram](https://t.me/leifermendez)

28
starters/apps/base/app.js Normal file
View File

@@ -0,0 +1,28 @@
const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock')
const flowPrincipal = addKeyword(['hola', 'ole', 'HOLA'])
.addAnswer('Bienvenido a mi tienda')
.addAnswer('Como puedo ayudarte?')
.addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc..'])
const main = async () => {
const adapterDB = new MockAdapter()
const adapterFlow = createFlow([flowPrincipal])
const adapterProvider = createProvider(WebWhatsappProvider)
createBot({
flow: adapterFlow,
provider: adapterProvider,
database: adapterDB,
})
}
main()

View File

@@ -0,0 +1,16 @@
{
"name": "bot-whatsapp-base",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"pre-copy": "cd .. && yarn run copy.lib example-app-base",
"start": "node app.js"
},
"keywords": [],
"dependencies": {
"whatsapp-web.js": "^1.18.3"
},
"author": "",
"license": "ISC"
}

View File

@@ -0,0 +1,12 @@
### BASIC APP
Este bot contiene un flujo basico en el cual una persona (cliente) escribe **"hola"** y el bot responde.
![](https://i.imgur.com/0z0G91w.png)
------
- [Discord](https://link.codigoencasa.com/DISCORD)
- [Twitter](https://twitter.com/leifermendez)
- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR)
- [Telegram](https://t.me/leifermendez)

View File

@@ -0,0 +1,55 @@
const {
createBot,
createProvider,
createFlow,
addKeyword,
addChild,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock')
/**
* Declarando flujo hijo
*/
const flowZapatos = addKeyword(['zapatos', 'ZAPATOS'])
.addAnswer('🤯 Veo que elegiste zapatos')
.addAnswer('Tengo muchos zapatos...bla bla')
const flowBolsos = addKeyword(['bolsos', 'BOLSOS'])
.addAnswer('🙌 Veo que elegiste bolsos')
.addAnswer('Tengo muchos bolsos...bla bla')
/**
* Declarando flujo principal
*/
const flowPrincipal = addKeyword(['hola', 'ole', 'HOLA'])
.addAnswer('Bienvenido a mi tienda')
.addAnswer('Como puedo ayudarte?')
.addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc..'])
.addAnswer('Escribe zapatos o bolsos')
.addAnswer(
'esperando respuesta...',
{ capture: true },
(ctx) => {
console.log('Aqui puedes ver más info del usuario...')
console.log('Puedes enviar un mail, hook, etc..')
console.log(ctx)
},
[...addChild(flowBolsos), ...addChild(flowZapatos)]
)
const main = async () => {
const adapterDB = new MockAdapter()
const adapterFlow = createFlow([flowPrincipal])
const adapterProvider = createProvider(WebWhatsappProvider)
createBot({
flow: adapterFlow,
provider: adapterProvider,
database: adapterDB,
})
}
main()

View File

@@ -0,0 +1,16 @@
{
"name": "bot-whatsapp-basic",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"pre-copy": "cd .. && yarn run copy.lib example-app-basic",
"start": "node app.js"
},
"keywords": [],
"dependencies": {
"whatsapp-web.js": "^1.18.3"
},
"author": "",
"license": "ISC"
}

8944
yarn-error.log Normal file

File diff suppressed because it is too large Load Diff

14058
yarn.lock

File diff suppressed because it is too large Load Diff