Compare commits
276 Commits
1.0
...
feature/tw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd3be909b | ||
|
|
24ac9fbf48 | ||
|
|
4350dff22a | ||
|
|
30e3d443bb | ||
|
|
f5ea7fe2c4 | ||
|
|
94d139e484 | ||
|
|
8049241f3f | ||
|
|
28d308ed4b | ||
|
|
e0862053d0 | ||
|
|
e0e76d3a56 | ||
|
|
242e44315b | ||
|
|
6b53ed13e2 | ||
|
|
fafccbcecc | ||
|
|
49698bfda9 | ||
|
|
4154cc2230 | ||
|
|
ce8a96b958 | ||
|
|
f373a3abc7 | ||
|
|
371ee0a780 | ||
|
|
327cf5730b | ||
|
|
717a7dc95f | ||
|
|
aa2417af12 | ||
|
|
f2533f1ed5 | ||
|
|
7d41699207 | ||
|
|
f9ccfef8e0 | ||
|
|
468a2ba251 | ||
|
|
08dbdcf4ae | ||
|
|
50d73f7bc8 | ||
|
|
d7ed9ff592 | ||
|
|
05c6fd4528 | ||
|
|
a99f424901 | ||
|
|
648354500b | ||
|
|
28c0480b8b | ||
|
|
f29ed6e29b | ||
|
|
903b4d79ac | ||
|
|
026c189901 | ||
|
|
f2b30ee349 | ||
|
|
5c02a9325a | ||
|
|
7645c8642f | ||
|
|
c5ebbe319f | ||
|
|
6f36eb1690 | ||
|
|
18f9e006a3 | ||
|
|
a7e334ebe9 | ||
|
|
a5e15d9d84 | ||
|
|
b3173517b4 | ||
|
|
06d2963163 | ||
|
|
df8282015d | ||
|
|
81b0aab850 | ||
|
|
a8705c5b44 | ||
|
|
efe739f9fc | ||
|
|
2e83a0508a | ||
|
|
d66adb2a1f | ||
|
|
e5cecdee03 | ||
|
|
9351af16b7 | ||
|
|
ad8831a75a | ||
|
|
c63018ff08 | ||
|
|
131bce3898 | ||
|
|
fff9316030 | ||
|
|
2b3148dc3c | ||
|
|
13a4202f08 | ||
|
|
f0df143aaf | ||
|
|
70a94ab2c6 | ||
|
|
e6d18d1a72 | ||
|
|
46cd57fb36 | ||
|
|
4ae389846d | ||
|
|
37d04e9e89 | ||
|
|
39d141ca67 | ||
|
|
befcc169e0 | ||
|
|
6bbb9c1b81 | ||
|
|
bb77afc4d2 | ||
|
|
b43697fd68 | ||
|
|
86eebbead6 | ||
|
|
7463b8badc | ||
|
|
37e857f093 | ||
|
|
2db240b32e | ||
|
|
3c5f6031e1 | ||
|
|
b25fee0d86 | ||
|
|
a6ee58ff28 | ||
|
|
fdb083f2a3 | ||
|
|
ee32a35a42 | ||
|
|
d17b41da38 | ||
|
|
870184b2a5 | ||
|
|
e787691efc | ||
|
|
859716e6c7 | ||
|
|
aab91b3842 | ||
|
|
f9dd0a6b03 | ||
|
|
f55cfae6e4 | ||
|
|
671c5b37f3 | ||
|
|
46c4ec7ab9 | ||
|
|
1856bd5022 | ||
|
|
21cfc498e8 | ||
|
|
ec7007071e | ||
|
|
e33509789c | ||
|
|
9fddcef271 | ||
|
|
d2acb641c5 | ||
|
|
82a6b634a9 | ||
|
|
f466b0cf7b | ||
|
|
b3f6fc852b | ||
|
|
976d892061 | ||
|
|
2a0a9e79da | ||
|
|
8d24093aec | ||
|
|
c6b23d353a | ||
|
|
b6a21b9c12 | ||
|
|
14fbae3c86 | ||
|
|
1dd88d117d | ||
|
|
f6d70b4f7d | ||
|
|
368bf29e63 | ||
|
|
c40c0c54bd | ||
|
|
0c850d47d7 | ||
|
|
4879df040f | ||
|
|
7cf013e52b | ||
|
|
4e0a1a59e0 | ||
|
|
6953c954a8 | ||
|
|
e3664cc973 | ||
|
|
417d938677 | ||
|
|
2042abb045 | ||
|
|
0f5efa9852 | ||
|
|
76968ded02 | ||
|
|
ce8e7be9d7 | ||
|
|
1290d6b478 | ||
|
|
a5c38658a8 | ||
|
|
5797beb0ca | ||
|
|
9178bc083e | ||
|
|
878840fc06 | ||
|
|
716f0587c3 | ||
|
|
03eed5131a | ||
|
|
3946c88ed7 | ||
|
|
59182f20f3 | ||
|
|
a20b128ee8 | ||
|
|
1edd9ab371 | ||
|
|
da8defc517 | ||
|
|
45272fb34f | ||
|
|
a8dc44b41e | ||
|
|
1954a5a90a | ||
|
|
228530a454 | ||
|
|
4216cdd1e5 | ||
|
|
6afb019f9d | ||
|
|
8410309e38 | ||
|
|
ceb6faa5af | ||
|
|
9de4777cdb | ||
|
|
83df967247 | ||
|
|
39e2356feb | ||
|
|
24484015b3 | ||
|
|
30e7b220cd | ||
|
|
576092fc96 | ||
|
|
2114800b84 | ||
|
|
d9492eeee6 | ||
|
|
2442b59a5f | ||
|
|
1c01e27a65 | ||
|
|
0a9b1907d7 | ||
|
|
0a9e14c460 | ||
|
|
33797ce9de | ||
|
|
97ff1402f8 | ||
|
|
5fa6660afd | ||
|
|
c05470c045 | ||
|
|
e24e648e07 | ||
|
|
a4d51304b9 | ||
|
|
4210214735 | ||
|
|
403dea665d | ||
|
|
5704300d75 | ||
|
|
deb238d423 | ||
|
|
b678041e68 | ||
|
|
df5fe085a8 | ||
|
|
46ee2c6dd0 | ||
|
|
eccbe59a1a | ||
|
|
3e2869b54a | ||
|
|
96b8a7626c | ||
|
|
7593d6e564 | ||
|
|
e00aacfe3e | ||
|
|
860c2bc8fb | ||
|
|
710f1b9f90 | ||
|
|
4e87ca790e | ||
|
|
5974f3c9f2 | ||
|
|
62f1b7eb88 | ||
|
|
1e9574e740 | ||
|
|
b6207ba447 | ||
|
|
7fe2611aed | ||
|
|
860bd8539f | ||
|
|
ceade85334 | ||
|
|
5dc81f60c0 | ||
|
|
40b08622ec | ||
|
|
a12d5dbb78 | ||
|
|
663fcafc9c | ||
|
|
f36ddd3014 | ||
|
|
3fadaaaf13 | ||
|
|
dfa569f29d | ||
|
|
601508f379 | ||
|
|
e7ad205268 | ||
|
|
f62ba0a076 | ||
|
|
a9efa0aa58 | ||
|
|
3276c21bc3 | ||
|
|
1114f25a71 | ||
|
|
f13a34ff75 | ||
|
|
d45ea85e7d | ||
|
|
10e2b138d3 | ||
|
|
a1bf5ba5c2 | ||
|
|
19102b7b3a | ||
|
|
5efcc2a9a6 | ||
|
|
8279c07a88 | ||
|
|
02d7b3bd98 | ||
|
|
f8f6a3000d | ||
|
|
9a92b152a4 | ||
|
|
f86700deaf | ||
|
|
4ba259b46c | ||
|
|
cf459e94d2 | ||
|
|
4f8ed1361c | ||
|
|
bad8802241 | ||
|
|
f09ac862d5 | ||
|
|
fe7567e1a9 | ||
|
|
9b0b7f4d54 | ||
|
|
3ddbf462a8 | ||
|
|
e6043c99a7 | ||
|
|
b1daa0020e | ||
|
|
190d35c9a5 | ||
|
|
e4378fe848 | ||
|
|
981a6bd928 | ||
|
|
676e48021f | ||
|
|
1d4daf10db | ||
|
|
3c9341d87d | ||
|
|
04982941a7 | ||
|
|
ba4f05ebb2 | ||
|
|
5aaf761fce | ||
|
|
12539d00fa | ||
|
|
ec8ad955ee | ||
|
|
d10504c40b | ||
|
|
d200100caa | ||
|
|
902431c533 | ||
|
|
e23540593a | ||
|
|
9b548d9418 | ||
|
|
c25de59a93 | ||
|
|
cfe2c17165 | ||
|
|
1309b7f806 | ||
|
|
1071469e53 | ||
|
|
1795e8de20 | ||
|
|
7414d958ab | ||
|
|
9487c795b4 | ||
|
|
a3ebebb19c | ||
|
|
4624cb6c60 | ||
|
|
c7bc021f93 | ||
|
|
ebfd232a6c | ||
|
|
a20f0654eb | ||
|
|
f0a9c41dc5 | ||
|
|
033b3fd411 | ||
|
|
952a87f941 | ||
|
|
17bc227295 | ||
|
|
7cdfcdac64 | ||
|
|
82c5af9605 | ||
|
|
1dbd8a8a67 | ||
|
|
4c67e35d8d | ||
|
|
0c26d96752 | ||
|
|
7ed67e0e2e | ||
|
|
7d1d009fe8 | ||
|
|
ad8ef25d2c | ||
|
|
a71204b384 | ||
|
|
c9a96ca0ac | ||
|
|
e32f5c9210 | ||
|
|
ffaa7b04a2 | ||
|
|
9a2ce98dfd | ||
|
|
f338b77d09 | ||
|
|
1f9af4f7b2 | ||
|
|
67bea61055 | ||
|
|
a63b4cb569 | ||
|
|
9123c58611 | ||
|
|
d3a086fc98 | ||
|
|
effeb3d4e9 | ||
|
|
826c5d9bf6 | ||
|
|
a21f70af1f | ||
|
|
982df6184e | ||
|
|
8a988dab67 | ||
|
|
cef5c618a3 | ||
|
|
4339d56870 | ||
|
|
69720b382a | ||
|
|
691880e628 | ||
|
|
0a5c3055ce | ||
|
|
1533161bbd | ||
|
|
ccca7f5612 | ||
|
|
1d3410ac91 |
8
.c8rc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"src": "./src",
|
||||||
|
"exclude": ["**/bot/lib", "__mocks__", "**/mock"],
|
||||||
|
"reporter": ["html"],
|
||||||
|
"report-dir": "./coverage",
|
||||||
|
"check-coverage": true,
|
||||||
|
"lines": 90
|
||||||
|
}
|
||||||
14
.eslintrc.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
commonjs: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: 'eslint:recommended',
|
||||||
|
overrides: [],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
}
|
||||||
26
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Test / Coverage
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [dev]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [16.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build --if-present
|
||||||
|
- run: npm run test.unit
|
||||||
|
- run: npm run test.coverage
|
||||||
20
.github/workflows/contributors.yml
vendored
Normal 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 }}
|
||||||
33
.gitignore
vendored
@@ -1,3 +1,32 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
/node_modules/*
|
/packages/*/node_modules
|
||||||
session.json
|
/packages/*/dist
|
||||||
|
/packages/*/docs/dist
|
||||||
|
session.json
|
||||||
|
chats/*
|
||||||
|
!chats/.gitkeep
|
||||||
|
media/*
|
||||||
|
!media/.gitkeep
|
||||||
|
mediaSend/*
|
||||||
|
!mediaSend/.gitkeep
|
||||||
|
!mediaSend/nota-de-voz.mp3
|
||||||
|
.env
|
||||||
|
.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*/
|
||||||
|
qr.svg
|
||||||
|
package-lock.json
|
||||||
|
yarn-error.log
|
||||||
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no -- commitlint --edit
|
||||||
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn run fmt.staged
|
||||||
4
.husky/pre-push
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run test
|
||||||
9
.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
packages/**/lib
|
||||||
|
packages/docs
|
||||||
|
**/.git
|
||||||
|
**/.svn
|
||||||
|
**/.hg
|
||||||
|
**/node_modules
|
||||||
|
*.mjs
|
||||||
|
*.cjs
|
||||||
|
*.md
|
||||||
6
.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense para saber los atributos posibles.
|
||||||
|
// Mantenga el puntero para ver las descripciones de los existentes atributos.
|
||||||
|
// Para más información, visite: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Iniciar el programa",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"program": "${workspaceFolder}\\example-app\\app.js"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"conventionalCommits.scopes": [
|
||||||
|
"hook",
|
||||||
|
"contributing",
|
||||||
|
"cli",
|
||||||
|
"bot",
|
||||||
|
"provider"
|
||||||
|
]
|
||||||
|
}
|
||||||
8
.yarn/plugins/@yarnpkg/plugin-postinstall.cjs
vendored
Normal 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
10
.yarnrc.yml
Normal 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
|
||||||
60
CHANGELOG.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 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
|
||||||
|
- remove ora and chalk
|
||||||
|
- add env
|
||||||
|
- add mysql
|
||||||
|
- add dialogflow
|
||||||
|
- add scan qr from webpage
|
||||||
|
- update route with middleware
|
||||||
|
- fix send message to story
|
||||||
|
- external download
|
||||||
|
- easy deploy heroku
|
||||||
|
- add support for ubuntu/linux
|
||||||
|
|
||||||
|
https://stackoverflow.com/questions/51855169/dialogflow-403-iam-permission-dialogflow-sessions-detectintent
|
||||||
59
CONTRIBUTING.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# CONTRIBUTING
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
__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)
|
||||||
88
EXAMPLE.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const {
|
||||||
|
createBot,
|
||||||
|
createProvider,
|
||||||
|
createFlow,
|
||||||
|
addKeyword,
|
||||||
|
toSerialize,
|
||||||
|
} = require('@bot-whatsapp/bot')
|
||||||
|
|
||||||
|
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
|
||||||
|
const MongoAdapter = require('@bot-whatsapp/database/mongo')
|
||||||
|
|
||||||
|
const flowArepa1 = toSerialize(
|
||||||
|
addKeyword(['1', 'AREPA14'])
|
||||||
|
.addAnswer('Esta es una arepa calificada ⭐⭐⭐⭐⭐')
|
||||||
|
.addAnswer(['Ingredientes:', '10g Aguacate', '20g Huevo'].join('\n'))
|
||||||
|
.toJson()
|
||||||
|
)
|
||||||
|
|
||||||
|
const flowArepa2_2 = toSerialize(
|
||||||
|
addKeyword('SI').addAnswer('te pongo huevo de mentira!').toJson()
|
||||||
|
)
|
||||||
|
|
||||||
|
const flowArepa2 = toSerialize(
|
||||||
|
addKeyword(['arepa2'])
|
||||||
|
.addAnswer('Esta es una arepa calificada ⭐⭐⭐⭐')
|
||||||
|
.addAnswer(
|
||||||
|
['Ingredientes:', '10g perico', '20g huevo', '10g queso'].join('\n')
|
||||||
|
)
|
||||||
|
.addAnswer(
|
||||||
|
'Eres Vegano SI o NO',
|
||||||
|
{
|
||||||
|
capture: true,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
[...flowArepa2_2]
|
||||||
|
)
|
||||||
|
.toJson()
|
||||||
|
)
|
||||||
|
|
||||||
|
const flowArepa3 = toSerialize(
|
||||||
|
addKeyword(['arepa3'])
|
||||||
|
.addAnswer('Esta es una arepa calificada LAMEJOR ⭐⭐⭐⭐⭐')
|
||||||
|
.toJson()
|
||||||
|
)
|
||||||
|
|
||||||
|
//////////////--MENU--PRINCIPAL--//////////////////
|
||||||
|
|
||||||
|
const flujoMenuArepa = addKeyword(['hola', 'ola', 'buenos'])
|
||||||
|
.addAnswer('Bienvenido "Arepera Aji Picante 🤯🚀😅"')
|
||||||
|
.addAnswer(
|
||||||
|
[
|
||||||
|
'El menú de hoy es el siguiente:',
|
||||||
|
'👉 [1 -AREPA14] - Arepa tradicional con Aguacate y Huevo',
|
||||||
|
'👉 [arepa2] - Arepa rellena de perico y huevo con un toque de queso',
|
||||||
|
'👉 [arepa3] - Rellena de Jamon y Queso',
|
||||||
|
].join('\n')
|
||||||
|
)
|
||||||
|
.addAnswer(
|
||||||
|
'Esperando respuesta...',
|
||||||
|
{
|
||||||
|
capture: true,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('Enviar un mail!')
|
||||||
|
},
|
||||||
|
[...flowArepa1, ...flowArepa2, ...flowArepa3]
|
||||||
|
)
|
||||||
|
.addAnswer('Gracias!')
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
const adapterDB = new MongoAdapter()
|
||||||
|
const adapterFlow = createFlow([flujoMenuArepa])
|
||||||
|
const adapterProvider = createProvider(WebWhatsappProvider)
|
||||||
|
|
||||||
|
createBot({
|
||||||
|
flow: adapterFlow,
|
||||||
|
provider: adapterProvider,
|
||||||
|
database: adapterDB,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
2
GLOSSARY.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
CTX: Es el objeto que representa un mensaje, con opciones, id, ref
|
||||||
|
messageInComming: Objeto entrante del provider {body, from,...}
|
||||||
137
README.md
@@ -1,51 +1,86 @@
|
|||||||
# BOT Whatsapp Gratis
|
[](https://github.com/leifermendez/bot-whatsapp/actions/workflows/ci.yml)
|
||||||
|
[](http://commitizen.github.io/cz-cli/)
|
||||||
Hola amigos este BOT se realizo en vivo en mi canal de Youtube si quieres ver como se hizo __Subscribete__
|
--------
|
||||||
[https://www.youtube.com/leifermendez](https://www.youtube.com/leifermendez)
|
🦊 Documentación: [https://bot-whatsapp.pages.dev/](https://bot-whatsapp.pages.dev/)
|
||||||
|
Video como hacer PR: https://youtu.be/Lxt8Acob6aU
|
||||||
🤖 Link video https://www.youtube.com/watch?v=A_Xu0OR_HkE
|
|
||||||
|
- [ ] Evitar dependencias
|
||||||
#### Node
|
|
||||||
> Debes de tener instalado NODE si no sabes como instalarlo te dejo un video en el cual explico como instalar node
|
|
||||||
__https://www.youtube.com/watch?v=6741ceWzsKQ&list=PL_WGMLcL4jzVY1y-SutA3N_PCNCAG7Y46&index=2&t=50s Minuto 0:50__
|
**Comunidad**
|
||||||
|
<!-- readme: collaborators,contributors -start -->
|
||||||
## Instruciones
|
<table>
|
||||||
__Descargar o Clonar repositorio__
|
<tr>
|
||||||

|
<td align="center">
|
||||||
|
<a href="https://github.com/leifermendez">
|
||||||
__Instalar paquetes (npm install)__
|
<img src="https://avatars.githubusercontent.com/u/15802366?v=4" width="50;" alt="leifermendez"/>
|
||||||
> Ubicate en le directorio que descargaste y via consola o terminal ejecuta el siguiente comando
|
<br />
|
||||||
|
<sub><b>Leifer Mendez</b></sub>
|
||||||
`npm install`
|
</a>
|
||||||
|
</td>
|
||||||

|
<td align="center">
|
||||||
|
<a href="https://github.com/aurik3">
|
||||||
__Ejecutar el script app.js__
|
<img src="https://avatars.githubusercontent.com/u/37228512?v=4" width="50;" alt="aurik3"/>
|
||||||
> Ubicate en le directorio que descargaste y via consola o terminal ejecuta el siguiente comando `node app.js` .
|
<br />
|
||||||
Escanea el el código QR desde tu aplicación de Whatsapp
|
<sub><b>Null</b></sub>
|
||||||
|
</a>
|
||||||
`node app.js`
|
</td>
|
||||||
|
<td align="center">
|
||||||

|
<a href="https://github.com/vicente1992">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/57806030?v=4" width="50;" alt="vicente1992"/>
|
||||||
> Ahora abre la aplicación de Whatsapp en tu dispositivo y escanea el código QR
|
<br />
|
||||||
<img src="https://i.imgur.com/RSbPtat.png" width="500" />
|
<sub><b>Manuel Vicente Ortiz</b></sub>
|
||||||
|
</a>
|
||||||
> Cuando sale este mensaje tu BOT está __listo__ para trabajar!
|
</td>
|
||||||

|
<td align="center">
|
||||||
|
<a href="https://github.com/leifermendezfroged">
|
||||||
## Como usarlo
|
<img src="https://avatars.githubusercontent.com/u/97020486?v=4" width="50;" alt="leifermendezfroged"/>
|
||||||
> Escribe un mensaje al whatsapp que vinculaste con tu BOT
|
<br />
|
||||||
|
<sub><b>Leifer Mendez</b></sub>
|
||||||

|
</a>
|
||||||
|
</td>
|
||||||
> Ahora deberías obtener un arespuesta por parte del BOT como la siguiente, ademas de esto tambien se crea un archivo excel
|
<td align="center">
|
||||||
con el historial de conversación con el número de tu cliente
|
<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>
|
||||||
## Preguntar al BOT
|
</td>
|
||||||
> Puedes interactuar con el bot ejemplo escribele __Quieromeme__ y el bot debe responderte con la imagen
|
<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)
|
||||||
50
TODO.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
### Genral
|
||||||
|
- [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
|
||||||
|
- [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
|
||||||
|
- [X] fallback respuesta en hijo: Se puede colocar en option el ref de la answer fallback
|
||||||
|
- [X] Cuando Envian Sticket devuelve mensaje raro
|
||||||
|
- [ ] colocar mensaje esperando conectando whatsapp (provider)
|
||||||
|
- [ ] createDatabase validar implementacion de funciones
|
||||||
|
- [ ] limitar caracteres de mensajes
|
||||||
|
- [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
|
||||||
|
- [X] MySQL adapter
|
||||||
|
- [ ] JsonFile adapter
|
||||||
|
|
||||||
|
### @bot-whatsapp/provider
|
||||||
|
- [X] agregar export package
|
||||||
|
- [ ] __(doc):__ Video para explicar como implementar nuevos providers
|
||||||
|
- [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
|
||||||
6
__mocks__/mobile.mock.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const MOCK_MOBILE_WS = {
|
||||||
|
from: 'XXXXXX',
|
||||||
|
hasMedia: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { MOCK_MOBILE_WS }
|
||||||
21
__mocks__/mock.provider.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const ProviderClass = require('../packages/bot/provider/provider.class')
|
||||||
|
class MockProvider extends ProviderClass {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
delaySendMessage = (miliseconds, eventName, payload) =>
|
||||||
|
new Promise((res) =>
|
||||||
|
setTimeout(() => {
|
||||||
|
this.emit(eventName, payload)
|
||||||
|
res
|
||||||
|
}, miliseconds)
|
||||||
|
)
|
||||||
|
|
||||||
|
sendMessage = async (userId, message) => {
|
||||||
|
console.log(`Enviando... ${userId}, ${message}`)
|
||||||
|
return Promise.resolve({ userId, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MockProvider
|
||||||
307
app.js
@@ -1,307 +0,0 @@
|
|||||||
/**
|
|
||||||
* ⚡⚡⚡ DECLARAMOS LAS LIBRERIAS y CONSTANTES A USAR! ⚡⚡⚡
|
|
||||||
*/
|
|
||||||
const fs = require('fs');
|
|
||||||
const mimeDb = require('mime-db')
|
|
||||||
const express = require('express');
|
|
||||||
const moment = require('moment');
|
|
||||||
const ora = require('ora');
|
|
||||||
const chalk = require('chalk');
|
|
||||||
const ExcelJS = require('exceljs');
|
|
||||||
const qrcode = require('qrcode-terminal');
|
|
||||||
const { flowConversation } = require('./conversation')
|
|
||||||
const { Client, MessageMedia } = require('whatsapp-web.js');
|
|
||||||
const app = express();
|
|
||||||
app.use(express.urlencoded({ extended: true }))
|
|
||||||
const SESSION_FILE_PATH = './session.json';
|
|
||||||
let client;
|
|
||||||
let sessionData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guardamos archivos multimedia que nuestro cliente nos envie!
|
|
||||||
* @param {*} media
|
|
||||||
*/
|
|
||||||
const saveMedia = (media) => {
|
|
||||||
|
|
||||||
const extensionProcess = mimeDb[media.mimetype]
|
|
||||||
const ext = extensionProcess.extensions[0]
|
|
||||||
fs.writeFile(`./media/${media.filename}.${ext}`, media.data, { encoding: 'base64' }, function (err) {
|
|
||||||
console.log('** Archivo Media Guardado **');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enviamos archivos multimedia a nuestro cliente
|
|
||||||
* @param {*} number
|
|
||||||
* @param {*} fileName
|
|
||||||
*/
|
|
||||||
const sendMedia = (number, fileName) => {
|
|
||||||
number = number.replace('@c.us', '');
|
|
||||||
number = `${number}@c.us`
|
|
||||||
const media = MessageMedia.fromFilePath(`./mediaSend/${fileName}`);
|
|
||||||
client.sendMessage(number, media);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enviamos un mensaje simple (texto) a nuestro cliente
|
|
||||||
* @param {*} number
|
|
||||||
*/
|
|
||||||
const sendMessage = (number = null, text = null) => {
|
|
||||||
number = number.replace('@c.us', '');
|
|
||||||
number = `${number}@c.us`
|
|
||||||
const message = text || `Hola soy un BOT recuerda https://www.youtube.com/leifermendez`;
|
|
||||||
client.sendMessage(number, message);
|
|
||||||
readChat(number, message)
|
|
||||||
console.log(`${chalk.red('⚡⚡⚡ Enviando mensajes....')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escuchamos cuando entre un mensaje
|
|
||||||
*/
|
|
||||||
const listenMessage = () => {
|
|
||||||
client.on('message', async msg => {
|
|
||||||
const { from, to, body } = msg;
|
|
||||||
//34691015468@c.us
|
|
||||||
console.log(msg.hasMedia);
|
|
||||||
if (msg.hasMedia) {
|
|
||||||
const media = await msg.downloadMedia();
|
|
||||||
saveMedia(media);
|
|
||||||
// do something with the media data here
|
|
||||||
}
|
|
||||||
|
|
||||||
await greetCustomer(from);
|
|
||||||
|
|
||||||
console.log(body);
|
|
||||||
|
|
||||||
await replyAsk(from, body);
|
|
||||||
|
|
||||||
// await readChat(from, body)
|
|
||||||
// console.log(`${chalk.red('⚡⚡⚡ Enviando mensajes....')}`);
|
|
||||||
// console.log('Guardar este número en tu Base de Datos:', from);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response a pregunta
|
|
||||||
*/
|
|
||||||
|
|
||||||
const replyAsk = (from, answer) => new Promise((resolve, reject) => {
|
|
||||||
console.log(`---------->`, answer);
|
|
||||||
if (answer === 'Quieromeme') {
|
|
||||||
sendMedia(from, 'meme-1.png')
|
|
||||||
resolve(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revisamos si tenemos credenciales guardadas para inciar sessio
|
|
||||||
* este paso evita volver a escanear el QRCODE
|
|
||||||
*/
|
|
||||||
const withSession = () => {
|
|
||||||
// Si exsite cargamos el archivo con las credenciales
|
|
||||||
const spinner = ora(`Cargando ${chalk.yellow('Validando session con Whatsapp...')}`);
|
|
||||||
sessionData = require(SESSION_FILE_PATH);
|
|
||||||
spinner.start();
|
|
||||||
client = new Client({
|
|
||||||
session: sessionData
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('ready', () => {
|
|
||||||
console.log('Client is ready!');
|
|
||||||
spinner.stop();
|
|
||||||
|
|
||||||
// sendMessage();
|
|
||||||
// sendMedia();
|
|
||||||
|
|
||||||
connectionReady();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
client.on('auth_failure', () => {
|
|
||||||
spinner.stop();
|
|
||||||
console.log('** Error de autentificacion vuelve a generar el QRCODE (Borrar el archivo session.json) **');
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
client.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generamos un QRCODE para iniciar sesion
|
|
||||||
*/
|
|
||||||
const withOutSession = () => {
|
|
||||||
console.log('No tenemos session guardada');
|
|
||||||
client = new Client();
|
|
||||||
client.on('qr', qr => {
|
|
||||||
qrcode.generate(qr, { small: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('ready', () => {
|
|
||||||
console.log('Client is ready!');
|
|
||||||
connectionReady();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('auth_failure', () => {
|
|
||||||
console.log('** Error de autentificacion vuelve a generar el QRCODE **');
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
client.on('authenticated', (session) => {
|
|
||||||
// Guardamos credenciales de de session para usar luego
|
|
||||||
sessionData = session;
|
|
||||||
fs.writeFile(SESSION_FILE_PATH, JSON.stringify(session), function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectionReady = () => {
|
|
||||||
listenMessage();
|
|
||||||
readExcel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Difundir mensaje a clientes
|
|
||||||
*/
|
|
||||||
const readExcel = async () => {
|
|
||||||
const pathExcel = `./chats/clientes-saludar.xlsx`;
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
|
||||||
await workbook.xlsx.readFile(pathExcel);
|
|
||||||
const worksheet = workbook.getWorksheet(1);
|
|
||||||
const columnNumbers = worksheet.getColumn('A');
|
|
||||||
columnNumbers.eachCell((cell, rowNumber) => {
|
|
||||||
const numberCustomer = cell.value
|
|
||||||
|
|
||||||
const columnDate = worksheet.getRow(rowNumber);
|
|
||||||
let prevDate = columnDate.getCell(2).value;
|
|
||||||
prevDate = moment.unix(prevDate);
|
|
||||||
const diffMinutes = moment().diff(prevDate, 'minutes');
|
|
||||||
|
|
||||||
// Si ha pasado mas de 60 minuitos podemos enviar nuevamente
|
|
||||||
if (diffMinutes > 60) {
|
|
||||||
sendMessage(numberCustomer)
|
|
||||||
columnDate.getCell(2).value = moment().format('X')
|
|
||||||
columnDate.commit();
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
workbook.xlsx.writeFile(pathExcel);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guardar historial de conversacion
|
|
||||||
* @param {*} number
|
|
||||||
* @param {*} message
|
|
||||||
*/
|
|
||||||
const readChat = async (number, message) => {
|
|
||||||
const pathExcel = `./chats/${number}.xlsx`;
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
|
||||||
const today = moment().format('DD-MM-YYYY hh:mm')
|
|
||||||
|
|
||||||
if (fs.existsSync(pathExcel)) {
|
|
||||||
/**
|
|
||||||
* Si existe el archivo de conversacion lo actualizamos
|
|
||||||
*/
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
|
||||||
workbook.xlsx.readFile(pathExcel)
|
|
||||||
.then(() => {
|
|
||||||
const worksheet = workbook.getWorksheet(1);
|
|
||||||
const lastRow = worksheet.lastRow;
|
|
||||||
var getRowInsert = worksheet.getRow(++(lastRow.number));
|
|
||||||
getRowInsert.getCell('A').value = today;
|
|
||||||
getRowInsert.getCell('B').value = message;
|
|
||||||
getRowInsert.commit();
|
|
||||||
workbook.xlsx.writeFile(pathExcel);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* NO existe el archivo de conversacion lo creamos
|
|
||||||
*/
|
|
||||||
const worksheet = workbook.addWorksheet('Chats');
|
|
||||||
worksheet.columns = [
|
|
||||||
{ header: 'Fecha', key: 'number_customer' },
|
|
||||||
{ header: 'Mensajes', key: 'message' }
|
|
||||||
];
|
|
||||||
worksheet.addRow([today, message]);
|
|
||||||
workbook.xlsx.writeFile(pathExcel)
|
|
||||||
.then(() => {
|
|
||||||
|
|
||||||
console.log("saved");
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log("err", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saludos a primera respuesta
|
|
||||||
* @param {*} req
|
|
||||||
* @param {*} res
|
|
||||||
*/
|
|
||||||
|
|
||||||
const greetCustomer = (from) => new Promise((resolve, reject) => {
|
|
||||||
from = from.replace('@c.us', '');
|
|
||||||
|
|
||||||
const pathExcel = `./chats/${from}@c.us.xlsx`;
|
|
||||||
if (!fs.existsSync(pathExcel)) {
|
|
||||||
const firstMessage = [
|
|
||||||
'👋 Ey! que pasa bro',
|
|
||||||
'Recuerda subscribirte a mi canal de YT',
|
|
||||||
'https://www.youtube.com/leifermendez',
|
|
||||||
'de regalo te dejo algunos de mis cursos',
|
|
||||||
'🔴 Aprende ANGULAR desde cero 2021 ⮕ https://bit.ly/367tJ32',
|
|
||||||
'✅ Aprende NODE desde cero 2021 ⮕ https://bit.ly/3od1Bl6',
|
|
||||||
'🔵 (Socket.io) NODE (Tutorial) ⮕ https://bit.ly/3pg1Q02',
|
|
||||||
'------',
|
|
||||||
'------',
|
|
||||||
'Veo que es la primera vez que nos escribes ¿Quieres que te envie un MEME?',
|
|
||||||
'Responde Quieromeme'
|
|
||||||
].join(' ')
|
|
||||||
|
|
||||||
sendMessage(from, firstMessage)
|
|
||||||
sendMedia(from, 'curso-1-1.png')
|
|
||||||
sendMedia(from, 'curso-2.png')
|
|
||||||
sendMedia(from, 'curso-3.png')
|
|
||||||
}
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controladores
|
|
||||||
*/
|
|
||||||
|
|
||||||
const sendMessagePost = (req, res) => {
|
|
||||||
const { message, number } = req.body
|
|
||||||
console.log(message, number);
|
|
||||||
sendMessage(number, message)
|
|
||||||
res.send({ status: 'Enviado!' })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rutas
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.post('/send', sendMessagePost);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revisamos si existe archivo con credenciales!
|
|
||||||
*/
|
|
||||||
(fs.existsSync(SESSION_FILE_PATH)) ? withSession() : withOutSession();
|
|
||||||
|
|
||||||
|
|
||||||
app.listen(9000, () => {
|
|
||||||
console.log('Server ready!');
|
|
||||||
})
|
|
||||||
91
changelog.config.js
Normal 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
@@ -0,0 +1 @@
|
|||||||
|
module.exports = { extends: ['@commitlint/config-conventional'] }
|
||||||
8
config/banner.rollup.json
Normal 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",
|
||||||
|
"*/"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
const flowConversation = () => { }
|
|
||||||
|
|
||||||
module.exports = { flowConversation }
|
|
||||||
25
docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo
|
||||||
|
container_name: app_enviroment
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '27019:27017'
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_DATABASE: bot
|
||||||
|
expose:
|
||||||
|
- 27019
|
||||||
|
mysql:
|
||||||
|
image: mysql
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: example
|
||||||
|
MYSQL_DATABASE: bot
|
||||||
|
container_name: app_mysql
|
||||||
|
ports:
|
||||||
|
- '3306:3306'
|
||||||
|
expose:
|
||||||
|
- 3306
|
||||||
BIN
media/undefined
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 307 KiB |
|
Before Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 447 KiB |
2257
package-lock.json
generated
114
package.json
@@ -1,25 +1,93 @@
|
|||||||
{
|
{
|
||||||
"name": "test-ws-bot",
|
"name": "@bot-whatsapp/root",
|
||||||
"version": "1.0.0",
|
"version": "0.2.0-alpha.0",
|
||||||
"description": "",
|
"description": "Bot de wahtsapp open source para MVP o pequeños negocios",
|
||||||
"main": "index.js",
|
"main": "app.js",
|
||||||
"scripts": {
|
"private": true,
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"scripts": {
|
||||||
},
|
"commit": "git-cz",
|
||||||
"keywords": [],
|
"cli:rollup": "rollup --config ./packages/cli/rollup-cli.config.js ",
|
||||||
"author": "",
|
"create-bot:rollup": "rollup --config ./packages/create-bot-whatsapp/rollup-create.config.js ",
|
||||||
"license": "ISC",
|
"bot:rollup": "rollup --config ./packages/bot/rollup-bot.config.js",
|
||||||
"dependencies": {
|
"provider:rollup": "rollup --config ./packages/provider/rollup-provider.config.js ",
|
||||||
"chalk": "^4.1.0",
|
"database:rollup": "rollup --config ./packages/database/rollup-database.config.js",
|
||||||
"excel4node": "^1.7.2",
|
"format:check": "prettier --check ./packages",
|
||||||
"exceljs": "^4.2.1",
|
"format:write": "prettier --write ./packages",
|
||||||
"express": "^4.17.1",
|
"fmt.staged": "pretty-quick --staged",
|
||||||
"file-type": "^16.3.0",
|
"lint:check": "eslint ./packages",
|
||||||
"mime-db": "^1.46.0",
|
"lint:fix": "eslint --fix ./packages",
|
||||||
"moment": "^2.29.1",
|
"build": "yarn run cli:rollup && yarn run bot:rollup && yarn run provider:rollup && yarn run database:rollup",
|
||||||
"ora": "^5.4.0",
|
"copy.lib": "node ./scripts/move.js",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"test.unit": "node ./node_modules/uvu/bin.js packages test",
|
||||||
"whatsapp-web.js": "^1.12.5",
|
"test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit",
|
||||||
"xlsx": "^0.16.9"
|
"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",
|
||||||
|
"postinstall": "npx prettier --write .",
|
||||||
|
"release": "standard-version"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"packages/create-bot-whatsapp",
|
||||||
|
"packages/bot",
|
||||||
|
"packages/cli",
|
||||||
|
"packages/database",
|
||||||
|
"packages/provider",
|
||||||
|
"packages/docs"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"whatsapp",
|
||||||
|
"bot-whatsapp",
|
||||||
|
"node-bot-whatsapp"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"email": "leifer33@gmail.com",
|
||||||
|
"name": "Leifer Mendez",
|
||||||
|
"url": "https://leifermendez.github.io"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aurik3",
|
||||||
|
"email": "aurik3@aurik3.com",
|
||||||
|
"url": "https://github.com/aurik3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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",
|
||||||
|
"conventional-changelog": "^3.1.25",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"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.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@3.3.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16",
|
||||||
|
"npm": "please-use-yarn",
|
||||||
|
"yarn": ">=3"
|
||||||
|
},
|
||||||
|
"author": "Leifer Mendez <leifer33@gmail.com>"
|
||||||
}
|
}
|
||||||
|
|||||||
110
packages/bot/USES_CASES.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# @bot-whatsapp/io
|
||||||
|
|
||||||
|
### Caso de uso
|
||||||
|
|
||||||
|
> Una persona escribe `hola`
|
||||||
|
|
||||||
|
**addKeyword** recibe `string | string[]`
|
||||||
|
|
||||||
|
> `sensitive` false _default_
|
||||||
|
|
||||||
|
- [x] addKeyword
|
||||||
|
- [x] addAnswer
|
||||||
|
- [x] addKeyword: Opciones
|
||||||
|
- [x] addAnswer: Opciones, media, buttons
|
||||||
|
- [x] Retornar JSON (options)
|
||||||
|
- [ ] Recibir JSON
|
||||||
|
|
||||||
|
```js
|
||||||
|
// bootstrap.js Como iniciar el provider
|
||||||
|
const { inout, provider, database } = require('@bot-whatsapp')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* async whatsapp-web, twilio, meta
|
||||||
|
* */
|
||||||
|
|
||||||
|
const bootstrap = async () => {
|
||||||
|
console.log(`Iniciando....`)
|
||||||
|
const client = await provider.start()
|
||||||
|
/**
|
||||||
|
* - QR
|
||||||
|
* - Endpoint
|
||||||
|
* - Check Token Meta, Twilio
|
||||||
|
* - Return events? on message
|
||||||
|
* */
|
||||||
|
console.log(`Fin...`)
|
||||||
|
// Esto es opcional ? no deberia ser necesario
|
||||||
|
client.on('message', ({number, body,...}) => {
|
||||||
|
// Incoming message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// flow.js Como agregar keywords y respuestas
|
||||||
|
const { inout, provider, database } = require('@bot-whatsapp')
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('hola')
|
||||||
|
.addAnswer('Bienvenido a tu tienda 🥲')
|
||||||
|
.addAnswer('escribe *catalogo* o *ofertas*')
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword(['catalogo', 'ofertas'])
|
||||||
|
.addAnswer('Este es nuestro CATALOGO mas reciente!', {
|
||||||
|
buttons: [{ body: 'Xiaomi' }, { body: 'Samsung' }],
|
||||||
|
})
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('Xiaomi')
|
||||||
|
.addAnswer('Estos son nuestro productos XIAOMI ....', {
|
||||||
|
media: 'https://....',
|
||||||
|
})
|
||||||
|
.addAnswer('Si quieres mas info escrbie *info*')
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('chao!')
|
||||||
|
.addAnswer('bye!')
|
||||||
|
.addAnswer('Recuerda que tengo esta promo', {
|
||||||
|
media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif',
|
||||||
|
})
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('Modelo C', { sensitive: false })
|
||||||
|
.addAnswer('100USD', { media: 'http//:...' })
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('hola!', { sensitive: false })
|
||||||
|
.addAnswer('Bievenido Escribe *productos*')
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('productos', { sensitive: false })
|
||||||
|
.addAnswer('Esto son los mas vendidos')
|
||||||
|
.addAnswer('*PC1* Precio 10USD', { media: 'https://....' })
|
||||||
|
.addAnswer('*PC2* Precio 10USD', { media: 'https://....' })
|
||||||
|
|
||||||
|
await inout
|
||||||
|
.addKeyword('PC1', { sensitive: false })
|
||||||
|
.addAnswer('Bievenido Escribe *productos*')
|
||||||
|
|
||||||
|
const answerOne = await inout.addAnswer({
|
||||||
|
message: 'Como estas!',
|
||||||
|
media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif',
|
||||||
|
})
|
||||||
|
|
||||||
|
const otherAnswer = await inout.addAnswer('Aprovecho para decirte!')
|
||||||
|
|
||||||
|
answerOne.push(otherAnswer)
|
||||||
|
|
||||||
|
inout.addKeywords(['hola', 'hi', 'ola'])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Comunidad**
|
||||||
|
|
||||||
|
> 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)
|
||||||
165
packages/bot/core/core.class.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
const { toCtx } = require('../io/methods')
|
||||||
|
const { printer } = require('../utils/interactive')
|
||||||
|
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
|
||||||
|
* [ ] Buscar mensaje en flow
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class CoreClass {
|
||||||
|
flowClass
|
||||||
|
databaseClass
|
||||||
|
providerClass
|
||||||
|
constructor(_flow, _database, _provider) {
|
||||||
|
this.flowClass = _flow
|
||||||
|
this.databaseClass = _database
|
||||||
|
this.providerClass = _provider
|
||||||
|
|
||||||
|
for (const { event, func } of this.listenerBusEvents()) {
|
||||||
|
this.providerClass.on(event, func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manejador de eventos
|
||||||
|
*/
|
||||||
|
listenerBusEvents = () => [
|
||||||
|
{
|
||||||
|
event: 'preinit',
|
||||||
|
func: () => printer('Iniciando provider espere...'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'require_action',
|
||||||
|
func: ({ instructions, title = '⚡⚡ ACCION REQUERIDA ⚡⚡' }) =>
|
||||||
|
printer(instructions, title),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'ready',
|
||||||
|
func: () => printer('Provider conectado y listo'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'auth_failure',
|
||||||
|
func: ({ instructions }) =>
|
||||||
|
printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
event: 'message',
|
||||||
|
func: (msg) => this.handleMsg(msg),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} messageInComming
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
handleMsg = async (messageInComming) => {
|
||||||
|
logger.log(`[handleMsg]: `, messageInComming)
|
||||||
|
const { body, from } = messageInComming
|
||||||
|
let msgToSend = []
|
||||||
|
let fallBackFlag = false
|
||||||
|
|
||||||
|
if (!body.length) return
|
||||||
|
|
||||||
|
const prevMsg = await this.databaseClass.getPrevByNumber(from)
|
||||||
|
const refToContinue = this.flowClass.findBySerialize(
|
||||||
|
prevMsg?.refSerialize
|
||||||
|
)
|
||||||
|
|
||||||
|
if (prevMsg?.ref) {
|
||||||
|
const ctxByNumber = toCtx({
|
||||||
|
body,
|
||||||
|
from,
|
||||||
|
prevRef: prevMsg.refSerialize,
|
||||||
|
})
|
||||||
|
this.databaseClass.save(ctxByNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📄 [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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📄 [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),
|
||||||
|
}))
|
||||||
|
|
||||||
|
msgToSend = this.flowClass.find(body, false, flowStandalone) || []
|
||||||
|
this.sendFlow(msgToSend, from)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📄🤘(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, ctxMessage),
|
||||||
|
this.databaseClass.save({ ...ctxMessage, from: numberOrId }),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFlow = (messageToSend, numberOrId) => {
|
||||||
|
const queue = []
|
||||||
|
for (const ctxMessage of messageToSend) {
|
||||||
|
queue.push(this.sendProviderAndSave(numberOrId, ctxMessage))
|
||||||
|
}
|
||||||
|
return Promise.all(queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {*} message
|
||||||
|
* @param {*} ref
|
||||||
|
*/
|
||||||
|
continue = (message, ref = false) => {
|
||||||
|
const responde = this.flowClass.find(message, ref)
|
||||||
|
if (responde) {
|
||||||
|
this.providerClass.sendMessage(responde.answer)
|
||||||
|
this.databaseClass.saveLog(responde.answer)
|
||||||
|
this.continue(null, responde.ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = CoreClass
|
||||||
45
packages/bot/index.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
const CoreClass = require('./core/core.class')
|
||||||
|
const ProviderClass = require('./provider/provider.class')
|
||||||
|
const FlowClass = require('./io/flow.class')
|
||||||
|
const { addKeyword, addAnswer, addChild, toSerialize } = require('./io/methods')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crear instancia de clase Bot
|
||||||
|
* @param {*} args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const createBot = async ({ flow, database, provider }) =>
|
||||||
|
new CoreClass(flow, database, provider)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crear instancia de clase Io (Flow)
|
||||||
|
* @param {*} args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const createFlow = (args) => {
|
||||||
|
return new FlowClass(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crear instancia de clase Provider
|
||||||
|
* @param {*} args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createBot,
|
||||||
|
createFlow,
|
||||||
|
createProvider,
|
||||||
|
addKeyword,
|
||||||
|
addAnswer,
|
||||||
|
addChild,
|
||||||
|
toSerialize,
|
||||||
|
ProviderClass,
|
||||||
|
CoreClass,
|
||||||
|
}
|
||||||
67
packages/bot/io/flow.class.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
const { toSerialize } = require('./methods/toSerialize')
|
||||||
|
|
||||||
|
class FlowClass {
|
||||||
|
allCallbacks = []
|
||||||
|
flowSerialize = []
|
||||||
|
flowRaw = []
|
||||||
|
constructor(_flow) {
|
||||||
|
if (!Array.isArray(_flow)) throw new Error('Esto debe ser un ARRAY')
|
||||||
|
this.flowRaw = _flow
|
||||||
|
|
||||||
|
this.allCallbacks = _flow
|
||||||
|
.map((cbIn) => cbIn.ctx.callbacks)
|
||||||
|
.flat(2)
|
||||||
|
.map((c, i) => ({ callback: c?.callback, index: i }))
|
||||||
|
|
||||||
|
const mergeToJsonSerialize = Object.keys(_flow)
|
||||||
|
.map((indexObjectFlow) => _flow[indexObjectFlow].toJson())
|
||||||
|
.flat(2)
|
||||||
|
|
||||||
|
this.flowSerialize = toSerialize(mergeToJsonSerialize)
|
||||||
|
}
|
||||||
|
|
||||||
|
find = (keyOrWord, symbol = false, overFlow = null) => {
|
||||||
|
keyOrWord = `${keyOrWord}`
|
||||||
|
let capture = false
|
||||||
|
let messages = []
|
||||||
|
let refSymbol = null
|
||||||
|
overFlow = overFlow ?? this.flowSerialize
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
}
|
||||||
|
return new RegExp(str, regexSensitive)
|
||||||
|
}
|
||||||
|
|
||||||
|
const findIn = (keyOrWord, symbol = false, flow = overFlow) => {
|
||||||
|
const sensitive = refSymbol?.options?.sensitive || false
|
||||||
|
capture = refSymbol?.options?.capture || false
|
||||||
|
|
||||||
|
if (capture) return messages
|
||||||
|
|
||||||
|
if (symbol) {
|
||||||
|
refSymbol = flow.find((c) => c.keyword === keyOrWord)
|
||||||
|
if (refSymbol?.answer) messages.push(refSymbol)
|
||||||
|
if (refSymbol?.ref) findIn(refSymbol.ref, true)
|
||||||
|
} else {
|
||||||
|
refSymbol = flow.find((c) => {
|
||||||
|
return mapSensitive(c.keyword, sensitive).test(keyOrWord)
|
||||||
|
})
|
||||||
|
if (refSymbol?.ref) findIn(refSymbol.ref, true)
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findIn(keyOrWord, symbol)
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
findBySerialize = (refSerialize) =>
|
||||||
|
this.flowSerialize.find((r) => r.refSerialize === refSerialize)
|
||||||
|
|
||||||
|
findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FlowClass
|
||||||
92
packages/bot/io/methods/addAnswer.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const { generateRef } = require('../../utils/hash')
|
||||||
|
const { toJson } = require('./toJson')
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param answer string
|
||||||
|
* @param options {media:string, buttons:[{"body":"😎 Cursos"}], 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
|
||||||
|
*/
|
||||||
|
const getAnswerOptions = () => ({
|
||||||
|
media:
|
||||||
|
typeof options?.media === 'string' ? `${options?.media}` : null,
|
||||||
|
buttons: Array.isArray(options?.buttons) ? options.buttons : [],
|
||||||
|
capture:
|
||||||
|
typeof options?.capture === 'boolean'
|
||||||
|
? options?.capture
|
||||||
|
: false,
|
||||||
|
child:
|
||||||
|
typeof options?.child === 'string' ? `${options?.child}` : null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getNested = () => ({
|
||||||
|
nested: Array.isArray(nested) ? nested : [],
|
||||||
|
})
|
||||||
|
|
||||||
|
const callback =
|
||||||
|
typeof cb === 'function'
|
||||||
|
? cb
|
||||||
|
: () => console.log('Callback no definida')
|
||||||
|
|
||||||
|
const lastCtx = inCtx.hasOwnProperty('ctx') ? inCtx.ctx : inCtx
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Esta funcion se encarga de mapear y transformar todo antes
|
||||||
|
* de retornar
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const ctxAnswer = () => {
|
||||||
|
const ref = `ans_${generateRef()}`
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...getAnswerOptions(),
|
||||||
|
...getNested(),
|
||||||
|
keyword: {},
|
||||||
|
callback: !!cb,
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = [].concat(inCtx.json).concat([
|
||||||
|
{
|
||||||
|
ref,
|
||||||
|
keyword: lastCtx.ref,
|
||||||
|
answer,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const callbacks = [].concat(inCtx.callbacks).concat([
|
||||||
|
{
|
||||||
|
ref: lastCtx.ref,
|
||||||
|
callback,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
...lastCtx,
|
||||||
|
ref,
|
||||||
|
answer,
|
||||||
|
json,
|
||||||
|
options,
|
||||||
|
callbacks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retornar contexto no colocar nada más abajo de esto
|
||||||
|
const ctx = ctxAnswer()
|
||||||
|
|
||||||
|
return {
|
||||||
|
ctx,
|
||||||
|
ref: ctx.ref,
|
||||||
|
addAnswer: addAnswer(ctx),
|
||||||
|
toJson: toJson(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { addAnswer }
|
||||||
15
packages/bot/io/methods/addChild.js
Normal 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 }
|
||||||
49
packages/bot/io/methods/addKeyword.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const { generateRef } = require('../../utils/hash')
|
||||||
|
const { addAnswer } = require('./addAnswer')
|
||||||
|
const { toJson } = require('./toJson')
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} message `string | string[]`
|
||||||
|
* @param {*} options {sensitive:boolean} default false
|
||||||
|
*/
|
||||||
|
const addKeyword = (keyword, options) => {
|
||||||
|
const parseOptions = () => {
|
||||||
|
const defaultProperties = {
|
||||||
|
sensitive:
|
||||||
|
typeof options?.sensitive === 'boolean'
|
||||||
|
? options?.sensitive
|
||||||
|
: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctxAddKeyword = () => {
|
||||||
|
const ref = `key_${generateRef()}`
|
||||||
|
const options = parseOptions()
|
||||||
|
const json = [
|
||||||
|
{
|
||||||
|
ref,
|
||||||
|
keyword,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
/**
|
||||||
|
* Se guarda en db
|
||||||
|
*/
|
||||||
|
|
||||||
|
return { ref, keyword, options, json }
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = ctxAddKeyword()
|
||||||
|
|
||||||
|
return {
|
||||||
|
ctx,
|
||||||
|
ref: ctx.ref,
|
||||||
|
addAnswer: addAnswer(ctx),
|
||||||
|
toJson: toJson(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { addKeyword }
|
||||||
8
packages/bot/io/methods/index.js
Normal file
@@ -0,0 +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, addChild, toCtx, toJson, toSerialize }
|
||||||
19
packages/bot/io/methods/toCtx.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const { generateRef, generateRefSerialize } = require('../../utils/hash')
|
||||||
|
/**
|
||||||
|
* @deprecate
|
||||||
|
* @param answer string
|
||||||
|
* @param options {media:string, buttons:[], capture:true default false}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const toCtx = ({ body, from, prevRef, index }) => {
|
||||||
|
return {
|
||||||
|
ref: generateRef(),
|
||||||
|
keyword: prevRef,
|
||||||
|
answer: body,
|
||||||
|
options: {},
|
||||||
|
from,
|
||||||
|
refSerialize: generateRefSerialize({ index, answer: body }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { toCtx }
|
||||||
6
packages/bot/io/methods/toJson.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const toJson = (inCtx) => () => {
|
||||||
|
const lastCtx = inCtx.hasOwnProperty('ctx') ? inCtx.ctx : inCtx
|
||||||
|
return lastCtx.json
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { toJson }
|
||||||
23
packages/bot/io/methods/toSerialize.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const { generateRefSerialize } = require('../../utils/hash')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crear referencia serializada
|
||||||
|
* @param {*} flowJson
|
||||||
|
* @returns array[]
|
||||||
|
*/
|
||||||
|
const toSerialize = (flowJson) => {
|
||||||
|
if (!Array.isArray(flowJson)) throw new Error('Esto debe ser un ARRAY')
|
||||||
|
|
||||||
|
const jsonToSerialize = flowJson.map((row, index) => ({
|
||||||
|
...row,
|
||||||
|
refSerialize: `${generateRefSerialize({
|
||||||
|
index,
|
||||||
|
keyword: row.keyword,
|
||||||
|
answer: row.answer,
|
||||||
|
})}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return jsonToSerialize
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { toSerialize }
|
||||||
14
packages/bot/io/rollup-cli.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const commonjs = require('@rollup/plugin-commonjs')
|
||||||
|
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||||
|
const { join } = require('path')
|
||||||
|
|
||||||
|
const PATH = join(__dirname, 'lib', 'io', 'bundle.io.cjs')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
input: 'index.js',
|
||||||
|
output: {
|
||||||
|
file: PATH,
|
||||||
|
format: 'cjs',
|
||||||
|
},
|
||||||
|
plugins: [commonjs(), nodeResolve()],
|
||||||
|
}
|
||||||
33
packages/bot/package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@bot-whatsapp/bot",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "./lib/bundle.bot.cjs",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"bot:rollup": "node ../../node_modules/.bin/rollup index.js --config ./rollup-cli.config.js",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"format:write": "prettier --write .",
|
||||||
|
"lint:check": "eslint .",
|
||||||
|
"lint:fix": "eslint --fix .",
|
||||||
|
"test.unit": "cross-env NODE_ENV=test node ../../node_modules/uvu/bin.js tests"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"files": [
|
||||||
|
"./lib/bundle.bot.cjs",
|
||||||
|
"./provider/*",
|
||||||
|
"./core/*",
|
||||||
|
"./io/*"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@bot-whatsapp/cli": "*",
|
||||||
|
"@bot-whatsapp/database": "*",
|
||||||
|
"@bot-whatsapp/provider": "*",
|
||||||
|
"kleur": "^4.1.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/bot/provider/provider.class.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const { EventEmitter } = require('node:events')
|
||||||
|
/**
|
||||||
|
* Esta clase debe siempre proporcionar los siguietes metodos
|
||||||
|
* sendMessage = Para enviar un mensaje
|
||||||
|
*
|
||||||
|
* @important
|
||||||
|
* Esta clase extiende de la clase del provider OJO
|
||||||
|
* Eventos
|
||||||
|
* - message
|
||||||
|
* - ready
|
||||||
|
* - error
|
||||||
|
* - require_action
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'dev'
|
||||||
|
class ProviderClass extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* events: message | auth | auth_error | ...
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
sendMessage = async (userId, message, sendMessage) => {
|
||||||
|
if (NODE_ENV !== 'production')
|
||||||
|
console.log('[sendMessage]', { userId, message })
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ProviderClass
|
||||||
16
packages/bot/rollup-bot.config.js
Normal 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', 'bundle.bot.cjs')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
input: join(__dirname, 'index.js'),
|
||||||
|
output: {
|
||||||
|
banner: banner['banner.output'].join(''),
|
||||||
|
file: PATH,
|
||||||
|
format: 'cjs',
|
||||||
|
},
|
||||||
|
plugins: [commonjs(), nodeResolve()],
|
||||||
|
}
|
||||||
279
packages/bot/tests/bot.class.test.js
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
const { test } = require('uvu')
|
||||||
|
const assert = require('uvu/assert')
|
||||||
|
const FlowClass = require('../io/flow.class')
|
||||||
|
const MockProvider = require('../../../__mocks__/mock.provider')
|
||||||
|
const {
|
||||||
|
createBot,
|
||||||
|
CoreClass,
|
||||||
|
createFlow,
|
||||||
|
createProvider,
|
||||||
|
ProviderClass,
|
||||||
|
} = require('../index')
|
||||||
|
|
||||||
|
class MockFlow {
|
||||||
|
allCallbacks = [{ callback: () => console.log('') }]
|
||||||
|
flowSerialize = []
|
||||||
|
flowRaw = []
|
||||||
|
find = (arg) => {
|
||||||
|
if (arg) {
|
||||||
|
return [{ answer: 'answer', ref: 'ref' }]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findBySerialize = () => ({})
|
||||||
|
findIndexByRef = () => 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockDBA {
|
||||||
|
listHistory = []
|
||||||
|
save = () => {}
|
||||||
|
getPrevByNumber = () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockDBB {
|
||||||
|
listHistory = []
|
||||||
|
save = () => {}
|
||||||
|
getPrevByNumber = () => ({
|
||||||
|
refSerialize: 'xxxxx',
|
||||||
|
ref: 'xxxx',
|
||||||
|
options: { callback: true },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockDBC {
|
||||||
|
listHistory = []
|
||||||
|
save = () => {}
|
||||||
|
getPrevByNumber = () => ({
|
||||||
|
refSerialize: 'xxxxx',
|
||||||
|
ref: 'xxxx',
|
||||||
|
options: { callback: true, nested: ['1', '2'] },
|
||||||
|
})
|
||||||
|
saveLog = () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test(`[CoreClass] Probando instanciamiento de clase`, async () => {
|
||||||
|
const setting = {
|
||||||
|
flow: new MockFlow(),
|
||||||
|
database: new MockDBA(),
|
||||||
|
provider: new MockProvider(),
|
||||||
|
}
|
||||||
|
const bot = await createBot(setting)
|
||||||
|
assert.is(bot instanceof CoreClass, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`[CoreClass createFlow] Probando instanciamiento de clase`, async () => {
|
||||||
|
const mockCreateFlow = createFlow([])
|
||||||
|
assert.is(mockCreateFlow instanceof FlowClass, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`[CoreClass createProvider] Probando instanciamiento de clase`, async () => {
|
||||||
|
const mockCreateProvider = createProvider(MockProvider)
|
||||||
|
assert.is(mockCreateProvider instanceof ProviderClass, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
|
||||||
|
let responseEvents = {}
|
||||||
|
|
||||||
|
const MOCK_EVENTS = {
|
||||||
|
require_action: {
|
||||||
|
instructions: 'Debes...',
|
||||||
|
},
|
||||||
|
ready: true,
|
||||||
|
auth_failure: {
|
||||||
|
instructions: 'Error...',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
from: 'XXXXXX',
|
||||||
|
body: 'hola',
|
||||||
|
hasMedia: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockProvider = new MockProvider()
|
||||||
|
|
||||||
|
const setting = {
|
||||||
|
flow: new MockFlow(),
|
||||||
|
database: new MockDBA(),
|
||||||
|
provider: mockProvider,
|
||||||
|
}
|
||||||
|
await createBot(setting)
|
||||||
|
|
||||||
|
/// Escuchamos eventos
|
||||||
|
mockProvider.on(
|
||||||
|
'require_action',
|
||||||
|
(r) => (responseEvents['require_action'] = r)
|
||||||
|
)
|
||||||
|
mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
|
||||||
|
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
|
||||||
|
mockProvider.on('message', (r) => (responseEvents['message'] = r))
|
||||||
|
|
||||||
|
/// Emitimos eventos
|
||||||
|
mockProvider.delaySendMessage(
|
||||||
|
0,
|
||||||
|
'require_action',
|
||||||
|
MOCK_EVENTS.require_action
|
||||||
|
)
|
||||||
|
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
|
||||||
|
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
|
||||||
|
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
|
||||||
|
|
||||||
|
await delay(0)
|
||||||
|
|
||||||
|
/// Testeamos eventos
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.require_action),
|
||||||
|
JSON.stringify(MOCK_EVENTS.require_action)
|
||||||
|
)
|
||||||
|
assert.is(responseEvents.ready, MOCK_EVENTS.ready)
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.auth_failure),
|
||||||
|
JSON.stringify(MOCK_EVENTS.auth_failure)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.message),
|
||||||
|
JSON.stringify(MOCK_EVENTS.message)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`[Bot] Probando Flujos Internos`, async () => {
|
||||||
|
let responseEvents = {}
|
||||||
|
|
||||||
|
const MOCK_EVENTS = {
|
||||||
|
require_action: {
|
||||||
|
instructions: 'Debes...',
|
||||||
|
},
|
||||||
|
ready: true,
|
||||||
|
auth_failure: {
|
||||||
|
instructions: 'Error...',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
from: 'XXXXXX',
|
||||||
|
body: 'hola',
|
||||||
|
hasMedia: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockProvider = new MockProvider()
|
||||||
|
|
||||||
|
const setting = {
|
||||||
|
flow: new MockFlow(),
|
||||||
|
database: new MockDBB(),
|
||||||
|
provider: mockProvider,
|
||||||
|
}
|
||||||
|
await createBot(setting)
|
||||||
|
|
||||||
|
/// Escuchamos eventos
|
||||||
|
mockProvider.on(
|
||||||
|
'require_action',
|
||||||
|
(r) => (responseEvents['require_action'] = r)
|
||||||
|
)
|
||||||
|
mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
|
||||||
|
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
|
||||||
|
mockProvider.on('message', (r) => (responseEvents['message'] = r))
|
||||||
|
|
||||||
|
/// Emitimos eventos
|
||||||
|
mockProvider.delaySendMessage(
|
||||||
|
0,
|
||||||
|
'require_action',
|
||||||
|
MOCK_EVENTS.require_action
|
||||||
|
)
|
||||||
|
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
|
||||||
|
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
|
||||||
|
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
|
||||||
|
|
||||||
|
await delay(0)
|
||||||
|
|
||||||
|
/// Testeamos eventos
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.require_action),
|
||||||
|
JSON.stringify(MOCK_EVENTS.require_action)
|
||||||
|
)
|
||||||
|
assert.is(responseEvents.ready, MOCK_EVENTS.ready)
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.auth_failure),
|
||||||
|
JSON.stringify(MOCK_EVENTS.auth_failure)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.message),
|
||||||
|
JSON.stringify(MOCK_EVENTS.message)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`[Bot] Probando Flujos Nested`, async () => {
|
||||||
|
let responseEvents = {}
|
||||||
|
|
||||||
|
const MOCK_EVENTS = {
|
||||||
|
require_action: {
|
||||||
|
instructions: 'Debes...',
|
||||||
|
},
|
||||||
|
ready: true,
|
||||||
|
auth_failure: {
|
||||||
|
instructions: 'Error...',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
from: 'XXXXXX',
|
||||||
|
body: 'hola',
|
||||||
|
hasMedia: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockProvider = new MockProvider()
|
||||||
|
|
||||||
|
const setting = {
|
||||||
|
flow: new MockFlow(),
|
||||||
|
database: new MockDBC(),
|
||||||
|
provider: mockProvider,
|
||||||
|
}
|
||||||
|
const botInstance = await createBot(setting)
|
||||||
|
|
||||||
|
botInstance.sendProviderAndSave('xxxxx', 'xxxxx')
|
||||||
|
botInstance.continue('xxxxx', 'xxxxx')
|
||||||
|
/// Escuchamos eventos
|
||||||
|
mockProvider.on(
|
||||||
|
'require_action',
|
||||||
|
(r) => (responseEvents['require_action'] = r)
|
||||||
|
)
|
||||||
|
mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
|
||||||
|
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
|
||||||
|
mockProvider.on('message', (r) => (responseEvents['message'] = r))
|
||||||
|
|
||||||
|
/// Emitimos eventos
|
||||||
|
mockProvider.delaySendMessage(
|
||||||
|
0,
|
||||||
|
'require_action',
|
||||||
|
MOCK_EVENTS.require_action
|
||||||
|
)
|
||||||
|
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
|
||||||
|
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
|
||||||
|
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
|
||||||
|
|
||||||
|
await delay(0)
|
||||||
|
|
||||||
|
/// Testeamos eventos
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.require_action),
|
||||||
|
JSON.stringify(MOCK_EVENTS.require_action)
|
||||||
|
)
|
||||||
|
assert.is(responseEvents.ready, MOCK_EVENTS.ready)
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.auth_failure),
|
||||||
|
JSON.stringify(MOCK_EVENTS.auth_failure)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
JSON.stringify(responseEvents.message),
|
||||||
|
JSON.stringify(MOCK_EVENTS.message)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.run()
|
||||||
|
|
||||||
|
function delay(ms) {
|
||||||
|
return new Promise((res) => setTimeout(res, ms))
|
||||||
|
}
|
||||||
161
packages/bot/tests/methods.test.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
const { test } = require('uvu')
|
||||||
|
const assert = require('uvu/assert')
|
||||||
|
const { generateRefSerialize } = require('../utils/hash')
|
||||||
|
const { addKeyword, addAnswer, toSerialize } = require('../io/methods')
|
||||||
|
|
||||||
|
test('Debere probar las propeidades', () => {
|
||||||
|
const ARRANGE = {
|
||||||
|
keyword: 'hola!',
|
||||||
|
}
|
||||||
|
const MAIN_CTX = addKeyword(ARRANGE.keyword)
|
||||||
|
|
||||||
|
assert.type(MAIN_CTX.addAnswer, 'function')
|
||||||
|
assert.is(MAIN_CTX.ctx.keyword, ARRANGE.keyword)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Debere probar las propeidades array', () => {
|
||||||
|
const ARRANGE = {
|
||||||
|
keyword: ['hola!', 'ole'],
|
||||||
|
}
|
||||||
|
const MAIN_CTX = addKeyword(ARRANGE.keyword)
|
||||||
|
|
||||||
|
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'],
|
||||||
|
}
|
||||||
|
const MAIN_CTX = addKeyword(ARRANGE.keyword)
|
||||||
|
.addAnswer('Segundo!')
|
||||||
|
.addAnswer('Segundo!')
|
||||||
|
.toJson()
|
||||||
|
|
||||||
|
const [ANSWER_A] = MAIN_CTX
|
||||||
|
|
||||||
|
assert.is(
|
||||||
|
toSerialize(MAIN_CTX)[0].refSerialize,
|
||||||
|
generateRefSerialize({
|
||||||
|
index: 0,
|
||||||
|
answer: ANSWER_A.answer,
|
||||||
|
keyword: ANSWER_A.keyword,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Debere probar el paso de contexto', () => {
|
||||||
|
const ARRANGE = {
|
||||||
|
keyword: 'hola!',
|
||||||
|
answer: 'Bienvenido',
|
||||||
|
}
|
||||||
|
const CTX_A = addKeyword(ARRANGE.keyword)
|
||||||
|
const CTX_B = addAnswer(CTX_A)(ARRANGE.answer)
|
||||||
|
|
||||||
|
assert.is(CTX_A.ctx.keyword, ARRANGE.keyword)
|
||||||
|
assert.is(CTX_B.ctx.keyword, ARRANGE.keyword)
|
||||||
|
assert.is(CTX_B.ctx.answer, ARRANGE.answer)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Debere probar la anidación', () => {
|
||||||
|
const ARRANGE = {
|
||||||
|
keyword: 'hola!',
|
||||||
|
answer_A: 'Bienvenido',
|
||||||
|
answer_B: 'Continuar',
|
||||||
|
}
|
||||||
|
const MAIN_CTX = addKeyword(ARRANGE.keyword)
|
||||||
|
.addAnswer(ARRANGE.answer_A)
|
||||||
|
.addAnswer(ARRANGE.answer_B)
|
||||||
|
|
||||||
|
assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Debere probar las poptions', () => {
|
||||||
|
const MAIN_CTX = addKeyword('etc', { sensitive: false })
|
||||||
|
|
||||||
|
assert.is(MAIN_CTX.ctx.options.sensitive, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Debere probar las addAnswer', () => {
|
||||||
|
const MOCK_OPT = {
|
||||||
|
media: 'http://image.mock/mock.png',
|
||||||
|
buttons: [1],
|
||||||
|
}
|
||||||
|
const MAIN_CTX = addKeyword('hola').addAnswer('etc', MOCK_OPT)
|
||||||
|
|
||||||
|
assert.is(MAIN_CTX.ctx.options.media, MOCK_OPT.media)
|
||||||
|
assert.is(MAIN_CTX.ctx.options.buttons.length, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Debere probar error las addAnswer', () => {
|
||||||
|
const MOCK_OPT = {
|
||||||
|
media: { a: 1, b: [] },
|
||||||
|
buttons: 'test',
|
||||||
|
}
|
||||||
|
const MAIN_CTX = addKeyword('hola').addAnswer('etc', MOCK_OPT)
|
||||||
|
|
||||||
|
assert.is(MAIN_CTX.ctx.options.media, null)
|
||||||
|
assert.is(MAIN_CTX.ctx.options.buttons.length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Obtener toJson', () => {
|
||||||
|
const [ctxA, ctxB, ctxC] = addKeyword('hola')
|
||||||
|
.addAnswer('pera!')
|
||||||
|
.addAnswer('chao')
|
||||||
|
.toJson()
|
||||||
|
|
||||||
|
assert.is(ctxA.keyword, 'hola')
|
||||||
|
assert.match(ctxA.ref, /^key_/)
|
||||||
|
|
||||||
|
assert.is(ctxB.answer, 'pera!')
|
||||||
|
assert.match(ctxB.ref, /^ans_/)
|
||||||
|
|
||||||
|
assert.is(ctxC.answer, 'chao')
|
||||||
|
assert.match(ctxC.ref, /^ans_/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addKeyword toJson con sensitive', () => {
|
||||||
|
const [ctxA] = addKeyword('hola').toJson()
|
||||||
|
assert.is(ctxA.options.sensitive, false)
|
||||||
|
const [ctxB] = addKeyword('hola', { sensitive: true }).toJson()
|
||||||
|
assert.is(ctxB.options.sensitive, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addAnswer toJson con IMG', () => {
|
||||||
|
const [, ctxB, ctxC] = addKeyword('hola')
|
||||||
|
.addAnswer('bye!', {
|
||||||
|
media: 'http://mock.img/file-a.png',
|
||||||
|
})
|
||||||
|
.addAnswer('otro!', {
|
||||||
|
media: 'http://mock.img/file-b.png',
|
||||||
|
})
|
||||||
|
.toJson()
|
||||||
|
|
||||||
|
assert.is(ctxB.options.media, 'http://mock.img/file-a.png')
|
||||||
|
assert.is(ctxC.options.media, 'http://mock.img/file-b.png')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addAnswer toJson con BUTTONS', () => {
|
||||||
|
const [, ctxB] = addKeyword('hola')
|
||||||
|
.addAnswer('mis opciones!', {
|
||||||
|
buttons: [{ body: 'BTN_1' }, { body: 'BTN_2' }],
|
||||||
|
})
|
||||||
|
.toJson()
|
||||||
|
|
||||||
|
assert.is(ctxB.options.buttons.length, 2)
|
||||||
|
|
||||||
|
const [btnA, btnB] = ctxB.options.buttons
|
||||||
|
|
||||||
|
assert.is(btnA.body, 'BTN_1')
|
||||||
|
assert.is(btnB.body, 'BTN_2')
|
||||||
|
})
|
||||||
|
|
||||||
|
test.run()
|
||||||
24
packages/bot/utils/hash.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generamos un UUID unico con posibilidad de tener un prefijo
|
||||||
|
* @param {*} prefix
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const generateRef = (prefix = false) => {
|
||||||
|
const id = crypto.randomUUID()
|
||||||
|
return prefix ? `${prefix}_${id}` : id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera un HASH MD5
|
||||||
|
* @param {*} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const generateRefSerialize = ({ index, answer, keyword }) =>
|
||||||
|
crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(JSON.stringify({ index, answer, keyword }))
|
||||||
|
.digest('hex')
|
||||||
|
|
||||||
|
module.exports = { generateRef, generateRefSerialize }
|
||||||
14
packages/bot/utils/interactive.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const { yellow, bgRed } = require('kleur')
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'dev'
|
||||||
|
const printer = (message, title) => {
|
||||||
|
if (NODE_ENV !== 'test') {
|
||||||
|
// console.clear()
|
||||||
|
if (title) console.log(bgRed(`${title}`))
|
||||||
|
console.log(
|
||||||
|
yellow(Array.isArray(message) ? message.join('\n') : message)
|
||||||
|
)
|
||||||
|
console.log(``)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { printer }
|
||||||
20
packages/cli/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# @bot-whatsapp/cli
|
||||||
|
|
||||||
|
- [x] Revisar version de NODE
|
||||||
|
- [x] Revisar OS
|
||||||
|
- [x] Obtener Package Manager
|
||||||
|
- [x] Revisar las libreria de WhatsappWeb para obtener version reciente
|
||||||
|
- [x] Opcion interactiva de limpiar session
|
||||||
|
- [x] Opcion de generar `json` con la configuracion
|
||||||
|
- [x] Agregar `rollup` para limpiar el codigo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Comunidad**
|
||||||
|
|
||||||
|
> 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)
|
||||||
3
packages/cli/bin/cli.js
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const index = require('../lib/cli/bundle.cli.cjs')
|
||||||
|
index.startInteractive()
|
||||||
38
packages/cli/check/index.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const { red, yellow, green, bgCyan } = require('kleur')
|
||||||
|
|
||||||
|
const checkNodeVersion = () => {
|
||||||
|
console.log(bgCyan('🚀 Revisando tu Node.js'))
|
||||||
|
const version = process.version
|
||||||
|
const majorVersion = parseInt(version.replace('v', '').split('.').shift())
|
||||||
|
if (majorVersion < 16) {
|
||||||
|
console.error(
|
||||||
|
red(
|
||||||
|
`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
console.log(green(`Node.js combatible ${version}`))
|
||||||
|
console.log(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkOs = () => {
|
||||||
|
console.log(bgCyan('🙂 Revisando tu Sistema Operativo'))
|
||||||
|
const os = process.platform
|
||||||
|
if (!os.includes('win32')) {
|
||||||
|
const messages = [
|
||||||
|
`El sistema operativo actual (${os}) posiblemente requiera`,
|
||||||
|
`una confiuración adicional referente al puppeter`,
|
||||||
|
``,
|
||||||
|
`Recuerda pasar por el WIKI`,
|
||||||
|
`🔗 https://github.com/leifermendez/bot-whatsapp/wiki/Instalaci%C3%B3n`,
|
||||||
|
``,
|
||||||
|
]
|
||||||
|
|
||||||
|
console.log(yellow(messages.join(' \n')))
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { checkNodeVersion, checkOs }
|
||||||
19
packages/cli/clean/index.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const rimraf = require('rimraf')
|
||||||
|
const { yellow } = require('kleur')
|
||||||
|
const { join } = require('path')
|
||||||
|
|
||||||
|
const PATH_WW = [
|
||||||
|
join(process.cwd(), '.wwebjs_auth'),
|
||||||
|
join(process.cwd(), 'session.json'),
|
||||||
|
]
|
||||||
|
|
||||||
|
const cleanSession = () => {
|
||||||
|
const queue = []
|
||||||
|
for (const PATH of PATH_WW) {
|
||||||
|
console.log(yellow(`😬 Eliminando: ${PATH}`))
|
||||||
|
queue.push(rimraf(PATH, () => Promise.resolve()))
|
||||||
|
}
|
||||||
|
return Promise.all(queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { cleanSession }
|
||||||
33
packages/cli/configuration/index.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const { writeFile } = require('fs').promises
|
||||||
|
const { join } = require('path')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON_TEMPLATE = {[key:string]{...pros}}
|
||||||
|
*/
|
||||||
|
const JSON_TEMPLATE = {
|
||||||
|
provider: {
|
||||||
|
vendor: '',
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
host: '',
|
||||||
|
password: '',
|
||||||
|
port: '',
|
||||||
|
username: '',
|
||||||
|
db: '',
|
||||||
|
},
|
||||||
|
io: {
|
||||||
|
vendor: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const PATH_CONFIG = join(process.cwd(), 'config.json')
|
||||||
|
|
||||||
|
const jsonConfig = () => {
|
||||||
|
return writeFile(
|
||||||
|
PATH_CONFIG,
|
||||||
|
JSON.stringify(JSON_TEMPLATE, null, 2),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { jsonConfig }
|
||||||
21
packages/cli/create-app/index.js
Normal 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 }
|
||||||
0
packages/cli/db/index.js
Normal file
3
packages/cli/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const { startInteractive } = require('./interactive')
|
||||||
|
if (process.env.NODE_ENV === 'dev') startInteractive()
|
||||||
|
module.exports = { startInteractive }
|
||||||
24
packages/cli/install/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const { readFileSync, existsSync } = require('fs')
|
||||||
|
const { join } = require('path')
|
||||||
|
const { installDeps, getPkgManage } = require('./tool')
|
||||||
|
|
||||||
|
const PATHS_DIR = [
|
||||||
|
join(__dirname, 'pkg-to-update.json'),
|
||||||
|
join(__dirname, '..', 'pkg-to-update.json'),
|
||||||
|
join(__dirname, '..', '..', 'pkg-to-update.json'),
|
||||||
|
]
|
||||||
|
|
||||||
|
const PKG_TO_UPDATE = () => {
|
||||||
|
const PATH_INDEX = PATHS_DIR.findIndex((a) => existsSync(a))
|
||||||
|
const data = readFileSync(PATHS_DIR[PATH_INDEX], 'utf-8')
|
||||||
|
const dataParse = JSON.parse(data)
|
||||||
|
const pkg = Object.keys(dataParse).map((n) => `${n}@${dataParse[n]}`)
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
const installAll = async () => {
|
||||||
|
const pkg = await getPkgManage()
|
||||||
|
installDeps(pkg, PKG_TO_UPDATE()).runInstall()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { installAll }
|
||||||
68
packages/cli/install/tool.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const { red } = require('kleur')
|
||||||
|
const spawn = require('cross-spawn')
|
||||||
|
// const { detect } = require('detect-package-manager')
|
||||||
|
const PKG_OPTION = {
|
||||||
|
npm: 'install',
|
||||||
|
yarn: 'add',
|
||||||
|
pnpm: 'add',
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPkgManage = async () => {
|
||||||
|
// const pkg = await detect()
|
||||||
|
// return pkg
|
||||||
|
return 'npm'
|
||||||
|
}
|
||||||
|
|
||||||
|
const installDeps = (pkgManager, packageList) => {
|
||||||
|
const errorMessage = `Ocurrio un error instalando ${packageList}`
|
||||||
|
let childProcess = []
|
||||||
|
|
||||||
|
const installSingle = (pkgInstall) => () => {
|
||||||
|
new Promise((resolve) => {
|
||||||
|
try {
|
||||||
|
childProcess = spawn(
|
||||||
|
pkgManager,
|
||||||
|
[PKG_OPTION[pkgManager], pkgInstall],
|
||||||
|
{
|
||||||
|
stdio: 'inherit',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
childProcess.on('error', (e) => {
|
||||||
|
console.error(e)
|
||||||
|
console.error(red(errorMessage))
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
childProcess.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
console.error(code)
|
||||||
|
console.error(red(errorMessage))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resolve()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
console.error(red(errorMessage))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof packageList === 'string') {
|
||||||
|
childProcess.push(installSingle(packageList))
|
||||||
|
} else {
|
||||||
|
for (const pkg of packageList) {
|
||||||
|
childProcess.push(installSingle(pkg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runInstall = () => {
|
||||||
|
return Promise.all(childProcess.map((i) => i()))
|
||||||
|
}
|
||||||
|
return { runInstall }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getPkgManage, installDeps }
|
||||||
146
packages/cli/interactive/index.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
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')
|
||||||
|
|
||||||
|
const startInteractive = async () => {
|
||||||
|
const questions = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'exampeOpt',
|
||||||
|
message:
|
||||||
|
'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',
|
||||||
|
message: 'Quieres limpiar la session del bot? (Y/n)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'multiselect',
|
||||||
|
name: 'providerWs',
|
||||||
|
message: 'Proveedor de Whatsapp',
|
||||||
|
choices: [
|
||||||
|
{ title: 'whatsapp-web.js', value: 'whatsapp-web.js' },
|
||||||
|
{ title: 'API Oficial (Meta)', value: 'meta', disabled: true },
|
||||||
|
{ title: 'Twilio', value: 'twilio', disabled: true },
|
||||||
|
],
|
||||||
|
max: 1,
|
||||||
|
hint: 'Espacio para selecionar',
|
||||||
|
instructions: '↑/↓',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'multiselect',
|
||||||
|
name: 'providerDb',
|
||||||
|
message: 'Cual base de datos quieres usar',
|
||||||
|
choices: [
|
||||||
|
{ title: 'JSONFile', value: 'json' },
|
||||||
|
{ title: 'MySQL', value: 'mysql', disabled: true },
|
||||||
|
{ title: 'Mongo', value: 'mongo', disabled: true },
|
||||||
|
],
|
||||||
|
max: 1,
|
||||||
|
hint: 'Espacio para selecionar',
|
||||||
|
instructions: '↑/↓',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
console.clear()
|
||||||
|
checkNodeVersion()
|
||||||
|
checkOs()
|
||||||
|
const onCancel = () => {
|
||||||
|
console.log('Proceso cancelado!')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const response = await prompts(questions, { onCancel })
|
||||||
|
const {
|
||||||
|
dependencies = '',
|
||||||
|
cleanTmp = '',
|
||||||
|
exampeOpt = '',
|
||||||
|
providerDb = [],
|
||||||
|
providerWs = [],
|
||||||
|
} = response
|
||||||
|
/**
|
||||||
|
* Question
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const installOrUdpateDep = async () => {
|
||||||
|
const answer = dependencies.toLowerCase() || 'n'
|
||||||
|
if (answer.includes('n')) return true
|
||||||
|
|
||||||
|
if (answer.includes('y')) {
|
||||||
|
await installAll()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Question
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const cleanAllSession = async () => {
|
||||||
|
const answer = cleanTmp.toLowerCase() || 'n'
|
||||||
|
if (answer.includes('n')) return true
|
||||||
|
|
||||||
|
if (answer.includes('y')) {
|
||||||
|
await cleanSession()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
red(
|
||||||
|
`Debes de seleccionar una WS Provider. Tecla [Space] para seleccionar`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
console.log(yellow(`'Deberia crer una carpeta en root/provider'`))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbProvider = async () => {
|
||||||
|
const answer = providerDb
|
||||||
|
if (!providerDb.length) {
|
||||||
|
console.log(
|
||||||
|
red(
|
||||||
|
`Debes de seleccionar una DB Provider. Tecla [Space] para seleccionar`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (answer === 'json') {
|
||||||
|
console.log('Deberia crer una carpeta en root/data')
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await createApp()
|
||||||
|
await installOrUdpateDep()
|
||||||
|
await cleanAllSession()
|
||||||
|
await vendorProvider()
|
||||||
|
await dbProvider()
|
||||||
|
await jsonConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { startInteractive }
|
||||||
19
packages/cli/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@bot-whatsapp/cli",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"cross-spawn": "^7.0.3",
|
||||||
|
"detect-package-manager": "^2.0.1",
|
||||||
|
"kleur": "^4.1.5"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./lib/cli/bundle.cli.cjs"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"bot": "./bin/cli.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/cli/pkg-to-update.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"whatsapp-web.js": "latest"
|
||||||
|
}
|
||||||
0
packages/cli/provider/index.js
Normal file
16
packages/cli/rollup-cli.config.js
Normal 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', 'cli', 'bundle.cli.cjs')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
input: join(__dirname, 'index.js'),
|
||||||
|
output: {
|
||||||
|
banner: banner['banner.output'].join(''),
|
||||||
|
file: PATH,
|
||||||
|
format: 'cjs',
|
||||||
|
},
|
||||||
|
plugins: [commonjs(), nodeResolve()],
|
||||||
|
}
|
||||||
3
packages/create-bot-whatsapp/bin/create.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const main = require('../lib/bin/bundle.create.cjs')
|
||||||
|
main()
|
||||||
12
packages/create-bot-whatsapp/index.js
Normal 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
|
||||||
13
packages/create-bot-whatsapp/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/create-bot-whatsapp/rollup-create.config.js
Normal 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()],
|
||||||
|
}
|
||||||
63
packages/database/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
### 🚀 Package (@bot-whatsapp/database)
|
||||||
|
|
||||||
|
Este package tiene como reponsabilidad proveer de diferentes adaptadores para la capa de datos.
|
||||||
|
La idea es brindar multiples opciones como un adaptador de MySQL, Mongo, entre otros.
|
||||||
|
|
||||||
|
Ejemplo de como se implementaria:
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
const MongoAdapter = require('@bot-whatsapp/database/mongo')
|
||||||
|
/// o
|
||||||
|
const MySQLAdapter = require('@bot-whatsapp/database/mysql')
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
|
||||||
|
const adapterDB = new MongoAdapter()
|
||||||
|
const adapterFlow = createFlow([flujoBot])
|
||||||
|
const adapterProvider = createProvider(WebWhatsappProvider)
|
||||||
|
|
||||||
|
createBot({
|
||||||
|
flow: adapterFlow,
|
||||||
|
provider: adapterProvider,
|
||||||
|
database: adapterDB,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
[](https://youtu.be/Sjzkpg1OJuY)
|
||||||
|
---
|
||||||
|
|
||||||
|
**Comunidad**
|
||||||
|
|
||||||
|
> 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)
|
||||||
20
packages/database/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@bot-whatsapp/database",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Esto es el conector a mysql, pg, mongo",
|
||||||
|
"main": "./lib/mock/index.cjs",
|
||||||
|
"private": true,
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"mongodb": "^4.11.0",
|
||||||
|
"mysql2": "^2.3.3"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
"./mock": "./lib/mock/index.cjs",
|
||||||
|
"./mongo": "./lib/mongo/index.cjs",
|
||||||
|
"./mysql": "./lib/mysql/index.cjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/database/rollup-database.config.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const banner = require('../../config/banner.rollup.json')
|
||||||
|
const commonjs = require('@rollup/plugin-commonjs')
|
||||||
|
const { join } = require('path')
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
input: join(__dirname, 'src', 'mock', 'index.js'),
|
||||||
|
output: {
|
||||||
|
banner: banner['banner.output'].join(''),
|
||||||
|
file: join(__dirname, 'lib', 'mock', 'index.cjs'),
|
||||||
|
format: 'cjs',
|
||||||
|
},
|
||||||
|
plugins: [commonjs()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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()],
|
||||||
|
},
|
||||||
|
]
|
||||||
22
packages/database/src/mock/index.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Si necesitas saber que trae el "ctx"
|
||||||
|
* Puedes ver el README.md dentro packages/database
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MockDatabase {
|
||||||
|
db
|
||||||
|
listHistory = []
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
getPrevByNumber = (from) => {
|
||||||
|
const history = this.listHistory.slice().reverse()
|
||||||
|
return history.find((a) => a.from === from)
|
||||||
|
}
|
||||||
|
|
||||||
|
save = (ctx) => {
|
||||||
|
this.listHistory.push(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MockDatabase
|
||||||
46
packages/database/src/mongo/index.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
require('dotenv').config()
|
||||||
|
const { MongoClient } = require('mongodb')
|
||||||
|
|
||||||
|
const DB_URI = process.env.DB_URI || 'mongodb://0.0.0.0:27017'
|
||||||
|
const DB_NAME = process.env.DB_NAME || 'db_bot'
|
||||||
|
|
||||||
|
class MongoAdapter {
|
||||||
|
db
|
||||||
|
listHistory = []
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.init().then()
|
||||||
|
}
|
||||||
|
|
||||||
|
init = async () => {
|
||||||
|
try {
|
||||||
|
const client = new MongoClient(DB_URI, {})
|
||||||
|
await client.connect()
|
||||||
|
console.log('🆗 Conexión Correcta DB')
|
||||||
|
const db = client.db(DB_NAME)
|
||||||
|
this.db = db
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error', e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrevByNumber = async (from) => {
|
||||||
|
const result = await this.db
|
||||||
|
.collection('history')
|
||||||
|
.find({ from })
|
||||||
|
.sort({ _id: -1 })
|
||||||
|
.limit(1)
|
||||||
|
.toArray()
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async (ctx) => {
|
||||||
|
await this.db.collection('history').insert(ctx)
|
||||||
|
console.log('Guardando DB...', ctx)
|
||||||
|
this.listHistory.push(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MongoAdapter
|
||||||
69
packages/database/src/mysql/index.js
Normal 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
|
||||||
33
packages/docs/.eslintignore
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
**/*.log
|
||||||
|
**/.DS_Store
|
||||||
|
*.
|
||||||
|
.vscode/settings.json
|
||||||
|
.history
|
||||||
|
.yarn
|
||||||
|
bazel-*
|
||||||
|
bazel-bin
|
||||||
|
bazel-out
|
||||||
|
bazel-qwik
|
||||||
|
bazel-testlogs
|
||||||
|
dist
|
||||||
|
dist-dev
|
||||||
|
lib
|
||||||
|
lib-types
|
||||||
|
etc
|
||||||
|
external
|
||||||
|
node_modules
|
||||||
|
temp
|
||||||
|
tsc-out
|
||||||
|
tsdoc-metadata.json
|
||||||
|
target
|
||||||
|
output
|
||||||
|
rollup.config.js
|
||||||
|
build
|
||||||
|
.cache
|
||||||
|
.vscode
|
||||||
|
.rollup.cache
|
||||||
|
dist
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
vite.config.ts
|
||||||
|
*.spec.tsx
|
||||||
|
*.spec.ts
|
||||||
40
packages/docs/.eslintrc.cjs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:qwik/recommended',
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/no-empty-interface': 'off',
|
||||||
|
'@typescript-eslint/no-namespace': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
|
'@typescript-eslint/no-this-alias': 'off',
|
||||||
|
'@typescript-eslint/ban-types': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
'prefer-spread': 'off',
|
||||||
|
'no-case-declarations': 'off',
|
||||||
|
'no-console': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': ['error'],
|
||||||
|
},
|
||||||
|
};
|
||||||
41
packages/docs/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Build
|
||||||
|
/dist
|
||||||
|
/lib
|
||||||
|
/lib-types
|
||||||
|
/server
|
||||||
|
|
||||||
|
# Development
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache
|
||||||
|
.mf
|
||||||
|
.vscode
|
||||||
|
.rollup.cache
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# Yarn
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/releases
|
||||||
|
|
||||||
|
# Cloudflare
|
||||||
|
functions/**/*.js
|
||||||
1
packages/docs/.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
16
|
||||||
6
packages/docs/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Files Prettier should not format
|
||||||
|
**/*.log
|
||||||
|
**/.DS_Store
|
||||||
|
*.
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
11
packages/docs/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
### 😎 Documentación Bot-Whatsapp
|
||||||
|
|
||||||
|
👉 [https://bot-whatsapp.pages.dev/](https://bot-whatsapp.pages.dev/)
|
||||||
|
|
||||||
|
Se esta iniciando una documentación oficial sobre como usar e implementar los diferentes funcionalidades del bot-wahtsapp
|
||||||
|
|
||||||
|
|
||||||
|
La idea es cada usuario pueda ir aportando a la documentacion y formar parte de este proyecto.
|
||||||
|
|
||||||
|
|
||||||
|
##### ¿Como agregar documentación? [Video]
|
||||||
19
packages/docs/adaptors/cloudflare-pages/vite.config.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { cloudflarePagesAdaptor } from '@builder.io/qwik-city/adaptors/cloudflare-pages/vite';
|
||||||
|
import { extendConfig } from '@builder.io/qwik-city/vite';
|
||||||
|
import baseConfig from '../../vite.config';
|
||||||
|
|
||||||
|
export default extendConfig(baseConfig, () => {
|
||||||
|
return {
|
||||||
|
build: {
|
||||||
|
ssr: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: ['src/entry.cloudflare-pages.tsx', '@qwik-city-plan'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
cloudflarePagesAdaptor({
|
||||||
|
staticGenerate: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
5
packages/docs/functions/[[path]].ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
|
||||||
|
// Cloudflare Pages Functions
|
||||||
|
// https://developers.cloudflare.com/pages/platform/functions/
|
||||||
|
export { onRequest } from '../server/entry.cloudflare-pages';
|
||||||
44
packages/docs/package.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "bot-whatsapp-docs",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Basic start point to build a docs site with Qwik",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=15.0.0"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "qwik build",
|
||||||
|
"build.client": "vite build",
|
||||||
|
"build.preview": "vite build --ssr src/entry.preview.tsx",
|
||||||
|
"build.server": "vite build -c adaptors/cloudflare-pages/vite.config.ts",
|
||||||
|
"build.types": "tsc --incremental --noEmit",
|
||||||
|
"deploy": "wrangler pages dev ./dist",
|
||||||
|
"dev": "vite --mode ssr",
|
||||||
|
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
|
||||||
|
"fmt": "prettier --write .",
|
||||||
|
"fmt.check": "prettier --check .",
|
||||||
|
"lint": "eslint \"src/**/*.ts*\"",
|
||||||
|
"preview": "qwik build preview && vite preview --open",
|
||||||
|
"start": "vite --open --mode ssr",
|
||||||
|
"qwik": "qwik"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@builder.io/qwik": "0.14.1",
|
||||||
|
"@builder.io/qwik-city": "0.0.127",
|
||||||
|
"@types/eslint": "8.4.10",
|
||||||
|
"@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",
|
||||||
|
"wrangler": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/docs/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
4
packages/docs/public/_headers
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# https://developers.cloudflare.com/pages/platform/headers/
|
||||||
|
|
||||||
|
/build/*
|
||||||
|
Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable
|
||||||
1
packages/docs/public/_redirects
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# https://developers.cloudflare.com/pages/platform/redirects/
|
||||||
1
packages/docs/public/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 500 500"><g clip-path="url(#a)"><circle cx="250" cy="250" r="250" fill="#fff"/><path fill="#18B6F6" d="m367.87 418.45-61.17-61.18-.94.13v-.67L175.7 227.53l32.05-31.13L188.9 87.73 99.56 199.09c-15.22 15.42-18.03 40.51-7.08 59.03l55.83 93.11a46.82 46.82 0 0 0 40.73 22.81l27.65-.27 151.18 44.68Z"/><path fill="#AC7EF4" d="m401.25 196.94-12.29-22.81-6.41-11.67-2.54-4.56-.26.26-33.66-58.63a47.07 47.07 0 0 0-41.27-23.75l-29.51.8-88.01.28a47.07 47.07 0 0 0-40.33 23.34L93.4 207l95.76-119.54L314.7 226.19l-22.3 22.67 13.35 108.54.13-.26v.26h-.26l.26.27 10.42 10.2 50.62 49.78c2.13 2 5.6-.4 4.13-2.96l-31.25-61.85 54.5-101.3 1.73-2c.67-.81 1.33-1.62 1.87-2.42a46.8 46.8 0 0 0 3.34-50.18Z"/><path fill="#fff" d="M315.1 225.65 189.18 87.6l17.9 108.14L175 227l130.5 130.27-11.75-108.14 21.37-23.48Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h500v500H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 947 B |
9
packages/docs/public/manifest.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
|
||||||
|
"name": "qwik-project-name",
|
||||||
|
"short_name": "Welcome to Qwik",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#fff",
|
||||||
|
"description": "A Qwik project app."
|
||||||
|
}
|
||||||
0
packages/docs/public/robots.txt
Normal file
25
packages/docs/src/components/breadcrumbs/breadcrumbs.css
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
nav.breadcrumbs {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs > span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs > span a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs > span::after {
|
||||||
|
content: '>';
|
||||||
|
padding: 0 5px;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs > span:last-child::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
74
packages/docs/src/components/breadcrumbs/breadcrumbs.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { component$, useStyles$ } from '@builder.io/qwik';
|
||||||
|
import { useContent, useLocation, ContentMenu } from '@builder.io/qwik-city';
|
||||||
|
import styles from './breadcrumbs.css?inline';
|
||||||
|
|
||||||
|
export const Breadcrumbs = component$(() => {
|
||||||
|
useStyles$(styles);
|
||||||
|
|
||||||
|
const { menu } = useContent();
|
||||||
|
const loc = useLocation();
|
||||||
|
|
||||||
|
const breadcrumbs = createBreadcrumbs(menu, loc.pathname);
|
||||||
|
if (breadcrumbs.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav class="breadcrumbs">
|
||||||
|
{breadcrumbs.map((b) => (
|
||||||
|
<span>{b.href ? <a href={b.href}>{b.text}</a> : b.text}</span>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function createBreadcrumbs(menu: ContentMenu | undefined, pathname: string) {
|
||||||
|
if (menu?.items) {
|
||||||
|
for (const indexA of menu.items) {
|
||||||
|
const breadcrumbA: ContentBreadcrumb = {
|
||||||
|
text: indexA.text,
|
||||||
|
};
|
||||||
|
if (typeof indexA.href === 'string') {
|
||||||
|
breadcrumbA.href = indexA.href;
|
||||||
|
}
|
||||||
|
if (indexA.href === pathname) {
|
||||||
|
return [breadcrumbA];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexA.items) {
|
||||||
|
for (const indexB of indexA.items) {
|
||||||
|
const breadcrumbB: ContentBreadcrumb = {
|
||||||
|
text: indexB.text,
|
||||||
|
};
|
||||||
|
if (typeof indexB.href === 'string') {
|
||||||
|
breadcrumbB.href = indexB.href;
|
||||||
|
}
|
||||||
|
if (indexB.href === pathname) {
|
||||||
|
return [breadcrumbA, breadcrumbB];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexB.items) {
|
||||||
|
for (const indexC of indexB.items) {
|
||||||
|
const breadcrumbC: ContentBreadcrumb = {
|
||||||
|
text: indexC.text,
|
||||||
|
};
|
||||||
|
if (typeof indexC.href === 'string') {
|
||||||
|
breadcrumbC.href = indexC.href;
|
||||||
|
}
|
||||||
|
if (indexC.href === pathname) {
|
||||||
|
return [breadcrumbA, breadcrumbB, breadcrumbC];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContentBreadcrumb {
|
||||||
|
text: string;
|
||||||
|
href?: string;
|
||||||
|
}
|
||||||
22
packages/docs/src/components/footer/footer.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
footer {
|
||||||
|
border-top: 0.5px solid #ddd;
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #9e9e9e;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||