feat(provider): add location provider

feat(provider):  add location provider
This commit is contained in:
Leifer Mendez
2023-02-10 08:59:40 +01:00
committed by GitHub
256 changed files with 8187 additions and 3999 deletions

View File

@@ -1 +1,2 @@
packages/docs/* packages/docs/*
packages/portal/*

View File

@@ -25,20 +25,28 @@ jobs:
run: yarn install --immutable --network-timeout 300000 run: yarn install --immutable --network-timeout 300000
- name: Check Baileys - name: Check Baileys
run: yarn node ./scripts/checker.js --name=baileys --stable=false run: yarn node ./scripts/checker.js --name=baileys --stable=true
- name: Check Venom - name: Check Venom
run: yarn node ./scripts/checker.js --name=venom --stable=false run: yarn node ./scripts/checker.js --name=venom --stable=true
- name: Check web-whatsapp - name: Check web-whatsapp
run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=false run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true
- name: Check Meta - name: Check Meta
run: yarn node ./scripts/checker.js --name=meta --stable=false run: yarn node ./scripts/checker.js --name=meta --stable=true
- name: Check Twilio - name: Check Twilio
run: yarn node ./scripts/checker.js --name=twilio --stable=false run: yarn node ./scripts/checker.js --name=twilio --stable=true
- name: Add and commit changes to gh-pages branch
run: |
git config --local user.email 'action@github.com'
git config --local user.name 'GitHub Action'
git add .
- uses: stefanzweifel/git-auto-commit-action@v4 - uses: stefanzweifel/git-auto-commit-action@v4
with: with:
commit_message: 'ci(providers): 🚩 Check BREAKING CHANGE' commit_message: 'ci(providers): check provider versions'
create_branch: true
branch: feature/providers-major

View File

@@ -1,48 +0,0 @@
name: Rev Providers
on:
push:
branches:
- dev
pull_request:
branches:
- dev
jobs:
check-npm:
name: Install Dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'yarn'
registry-url: https://registry.npmjs.org/
- run: corepack enable
- name: Install NPM Dependencies
run: yarn install --immutable --network-timeout 300000
- name: Check Baileys
run: yarn node ./scripts/checker.js --name=baileys --stable=true
- name: Check Venom
run: yarn node ./scripts/checker.js --name=venom --stable=true
- name: Check web-whatsapp
run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true
- name: Check Meta
run: yarn node ./scripts/checker.js --name=meta --stable=true
- name: Check Twilio
run: yarn node ./scripts/checker.js --name=twilio --stable=true
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: 'ci(providers): 👍 updated versions stable providers'

View File

@@ -1,9 +1,6 @@
name: Build and Test name: Build and Test
on: on:
push:
branches:
- dev
pull_request: pull_request:
branches: branches:
- dev - dev
@@ -32,7 +29,7 @@ jobs:
run: yarn install --immutable --network-timeout 300000 run: yarn install --immutable --network-timeout 300000
- name: Build Package - name: Build Package
run: yarn build run: yarn build:full
- name: Build Eslint rules - name: Build Eslint rules
run: yarn lint:fix run: yarn lint:fix
@@ -59,3 +56,55 @@ jobs:
- name: Unit Tests - name: Unit Tests
run: yarn test run: yarn test
############ UNIT TEST ############
check-providers:
name: Check Providers Versions
runs-on: ubuntu-latest
outputs:
commit: ${{ steps.vars.outputs.commit }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{github.event.after}}
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'yarn'
registry-url: https://registry.npmjs.org/
- run: corepack enable
- name: Install NPM Dependencies
run: yarn install --immutable --network-timeout 300000
- name: Check Baileys
run: yarn node ./scripts/checker.js --name=baileys --stable=true
- name: Check Venom
run: yarn node ./scripts/checker.js --name=venom --stable=true
- name: Check web-whatsapp
run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true
- name: Check Meta
run: yarn node ./scripts/checker.js --name=meta --stable=true
- name: Check Twilio
run: yarn node ./scripts/checker.js --name=twilio --stable=true
- name: Set output
id: vars
run: echo "commit=$(git log --format=%B -n 1 ${{github.event.after}})" >> $GITHUB_OUTPUT
- name: Commit & Push changes
uses: actions-js/push@master
with:
branch: feature/providers-major
github_token: ${{ secrets.GITHUB_TOKEN }}
force: true

View File

@@ -13,7 +13,7 @@ name: 'CodeQL'
on: on:
push: push:
branches: ['main', dev, next-release] branches: [release/next]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: ['main'] branches: ['main']
@@ -22,6 +22,7 @@ on:
jobs: jobs:
analyze: analyze:
if: ${{ !github.event.act }}
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:

View File

@@ -1,13 +1,8 @@
name: Revisando Colaboradores name: Revisando Colaboradores
on: on:
push: schedule:
branches: - cron: '0 9 * * *'
- main
pull_request:
branches:
- dev
- main
types: [closed]
jobs: jobs:
contrib-readme-job: contrib-readme-job:

View File

@@ -13,6 +13,7 @@ on:
jobs: jobs:
############ DOCUMENTATION BUILD ############ ############ DOCUMENTATION BUILD ############
build-documentation: build-documentation:
if: ${{ !github.event.act }}
name: Build Package name: Build Package
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -3,12 +3,11 @@ name: 📄 (PROD) Desplegando documentacion
on: on:
push: push:
branches: branches:
- main - release/next
- next-release
jobs: jobs:
############ DOCUMENTATION BUILD ############ ############ DOCUMENTATION BUILD ############
build-documentation: build-documentation-prod:
name: Build Package name: Build Package
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -3,16 +3,22 @@ name: 🚀 (DEV) Liberando versiones
on: on:
push: push:
branches: branches:
- next-release - release/next
jobs: jobs:
############ RELEASE ############ ############ RELEASE ############
release: release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
commit: ${{ steps.vars.outputs.commit }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{github.event.after}}
persist-credentials: false
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@@ -27,7 +33,7 @@ jobs:
run: yarn install --immutable --network-timeout 300000 run: yarn install --immutable --network-timeout 300000
- name: Build Package - name: Build Package
run: yarn build run: yarn build:full
- name: Release @bot-whatsapp/bot - name: Release @bot-whatsapp/bot
run: yarn node ./scripts/release.js --name=bot --version= --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=bot --version= --token="${{ secrets.NPM_TOKEN }}"
@@ -44,8 +50,15 @@ jobs:
- name: Release @bot-whatsapp/provider - name: Release @bot-whatsapp/provider
run: yarn node ./scripts/release.js --name=provider --version= --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=provider --version= --token="${{ secrets.NPM_TOKEN }}"
- name: Commit Versioning & Push changes - name: Release @bot-whatsapp/contexts
uses: stefanzweifel/git-auto-commit-action@v4 run: yarn node ./scripts/release.js --name=contexts --version= --token="${{ secrets.NPM_TOKEN }}"
- name: Release @bot-whatsapp/portal
run: yarn node ./scripts/release.js --name=portal --version= --token="${{ secrets.NPM_TOKEN }}"
- name: Commit & Push changes
uses: actions-js/push@master
with: with:
commit_message: 'ci(version): :zap: automatic - "${date}" updated versions every packages' branch: release/next
branch: dev github_token: ${{ secrets.GITHUB_TOKEN }}
force: true

View File

@@ -2,12 +2,12 @@ name: 🚀⚡ Liberando versiones
on: on:
push: push:
tags: branches:
- 'v*.*.*' - release/production
jobs: jobs:
############ RELEASE ############ ############ RELEASE ############
release: release-prod:
if: ${{ !github.event.act }}
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -27,29 +27,49 @@ jobs:
- run: corepack enable - run: corepack enable
- name: Set User
run: git config --global user.email "leifer.contacto@gmail.com" && git config --global user.name "Leifer Mendez"
- name: Install NPM Dependencies - name: Install NPM Dependencies
run: yarn install --immutable --network-timeout 300000 run: yarn install --immutable --network-timeout 300000
- name: Set CHANGELOG
run: yarn release
- name: get-npm-version
id: package-version
uses: martinbeentjes/npm-get-version-action@main
- name: Build Package - name: Build Package
run: yarn build run: yarn build:full
- name: Release @bot-whatsapp/bot - name: Release @bot-whatsapp/bot
run: yarn node ./scripts/release.js --name=bot --version="${{ steps.vars.outputs.tag }}" --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=bot --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Release @bot-whatsapp/cli - name: Release @bot-whatsapp/cli
run: yarn node ./scripts/release.js --name=cli --version="${{ steps.vars.outputs.tag }}" --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=cli --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Release @bot-whatsapp/create-bot-whatsapp - name: Release @bot-whatsapp/create-bot-whatsapp
run: yarn node ./scripts/release.js --name=create-bot-whatsapp --version="${{ steps.vars.outputs.tag }}" --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=create-bot-whatsapp --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Release @bot-whatsapp/database - name: Release @bot-whatsapp/database
run: yarn node ./scripts/release.js --name=database --version="${{ steps.vars.outputs.tag }}" --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=database --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Release @bot-whatsapp/provider - name: Release @bot-whatsapp/provider
run: yarn node ./scripts/release.js --name=provider --version="${{ steps.vars.outputs.tag }}" --token="${{ secrets.NPM_TOKEN }}" run: yarn node ./scripts/release.js --name=provider --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Commit Versioning & Push changes - name: Release @bot-whatsapp/contexts
uses: stefanzweifel/git-auto-commit-action@v4 run: yarn node ./scripts/release.js --name=contexts --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Release @bot-whatsapp/portal
run: yarn node ./scripts/release.js --name=portal --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.NPM_TOKEN }}"
- name: Release Github
run: yarn node ./scripts/github.js --version="${{ steps.package-version.outputs.current-version}}" --token="${{ secrets.OCTO_TOKEN }}"
- name: Commit & Push changes
uses: actions-js/push@master
with: with:
commit_message: 'release(version): 🚀 - "${{ steps.vars.outputs.tag }}" release' branch: release/production
branch: dev github_token: ${{ secrets.GITHUB_TOKEN }}
force: true

6
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/node_modules /node_modules
/packages/repl
/packages/*/starters /packages/*/starters
/packages/*/node_modules /packages/*/node_modules
/packages/*/dist /packages/*/dist
@@ -14,6 +15,10 @@ mediaSend/*
!mediaSend/nota-de-voz.mp3 !mediaSend/nota-de-voz.mp3
.env .env
.wwebjs_auth .wwebjs_auth
/session
/session/*
/tokens
/tokens/*
packages/cli/config.json packages/cli/config.json
config.json config.json
.yarnrc.yml .yarnrc.yml
@@ -38,3 +43,4 @@ yarn-error.log
.npmrc .npmrc
# Local Netlify folder # Local Netlify folder
.netlify .netlify
.secrets

View File

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

View File

@@ -1,5 +1,5 @@
packages/**/lib packages/**/lib
packages/docs/*.json packages/docs/
**/.git **/.git
**/.svn **/.svn
**/.hg **/.hg

View File

@@ -2,5 +2,6 @@
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": false, "semi": false,
"singleQuote": true "singleQuote": true,
"printWidth": 120
} }

View File

@@ -1,3 +1,3 @@
{ {
"recommendations": ["xyc.vscode-mdx-preview"] "recommendations": ["xyc.vscode-mdx-preview", "vivaxy.vscode-conventional-commits", "mhutchie.git-graph"]
} }

View File

@@ -2,6 +2,199 @@
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. 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.1.20](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.19...v0.1.20) (2023-02-05)
### Features
* **cli:** :fire: add regex expression in addKeyworkd ([e34560c](https://github.com/leifermendez/bot-whatsapp/commit/e34560c77d4852d2e90930f0858e51aa67d4eeab))
* **provider:** :zap: possible get class provider ([76ba717](https://github.com/leifermendez/bot-whatsapp/commit/76ba717927a75b3d6299206aa0b8aee2bc25b726))
### Bug Fixes
* **cli:** :zap: working flowDynamic test ([c0113ca](https://github.com/leifermendez/bot-whatsapp/commit/c0113ca49295aff220d8defcb53f2ba7f2872d75))
* **cli:** :zap: working flowDynamic test ([aef52d2](https://github.com/leifermendez/bot-whatsapp/commit/aef52d2694fa6616d614338643db198b4f7f1fe8))
* **cli:** :zap: working flowDynamic test ([f769320](https://github.com/leifermendez/bot-whatsapp/commit/f76932021ce968d93241b55cfcdb8ae0e0e6c934))
* **cli:** :zap: working flowDynamic test ([23e09ef](https://github.com/leifermendez/bot-whatsapp/commit/23e09efaeccaf51018c55da492edff45b625f0a9))
* **database:** add support emoji in mysql ([9311aa0](https://github.com/leifermendez/bot-whatsapp/commit/9311aa0a65623a1bf40e96207a281625154dae90))
* **database:** fix naming ([cd082f2](https://github.com/leifermendez/bot-whatsapp/commit/cd082f235012cd5f5844c6437f51711beee0c865))
* **database:** fix naming ([1afc3ba](https://github.com/leifermendez/bot-whatsapp/commit/1afc3ba182070713b5bec40eaab0fa1f680830cd))
* **database:** fix naming ([c9831d2](https://github.com/leifermendez/bot-whatsapp/commit/c9831d202ab2c85f15a0247cd2a2426bc435270c))
* **provider:** :zap: baily wa.link ([96c2bff](https://github.com/leifermendez/bot-whatsapp/commit/96c2bffd093269be8e39474a84c156938504a6cb))
### [0.1.19](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.18...v0.1.19) (2023-01-29)
### Features
* :fire: bailey add media ([eab39e4](https://github.com/leifermendez/bot-whatsapp/commit/eab39e4ac06fd46f1a4671f8c15d1456b4400b97))
* :zap: more feature ([e19c3a2](https://github.com/leifermendez/bot-whatsapp/commit/e19c3a25a40259c74b4add9635af4844907eed26))
* **provider:** :rocket: fix issues in providers venom and wwebjs ([cbe438b](https://github.com/leifermendez/bot-whatsapp/commit/cbe438b77854e8df48b9dafaf7a837d21124ac5f))
* **provider:** :rocket: fix provider ([0ad4c58](https://github.com/leifermendez/bot-whatsapp/commit/0ad4c58457b548dc41c0f9e8470d59c48de7b95a))
* **provider:** :rocket: fix provider ([f8c7184](https://github.com/leifermendez/bot-whatsapp/commit/f8c7184487065443ab10f77aaf585e8bd63ca441))
* **provider:** :rocket: fix provider ([b2afa45](https://github.com/leifermendez/bot-whatsapp/commit/b2afa45352a7ab1f5d9775f3c1fde475bd8ca204))
* **provider:** :rocket: fix provider venom and wwebjs ([dcb0566](https://github.com/leifermendez/bot-whatsapp/commit/dcb0566d2bc3da40cd0c71554bb5ea0ec115d9ca))
* **provider:** :rocket: implements all send media to venom provider ([9dd7c02](https://github.com/leifermendez/bot-whatsapp/commit/9dd7c02b6a5474aff063f7d6be0ca8519504b93c))
* **provider:** :rocket: send file wwebjs ([6ff1a3a](https://github.com/leifermendez/bot-whatsapp/commit/6ff1a3a980196c01c66ed04ee07d0e7e57256504))
* **provider:** :zap: bailey add send file video audio ([14d1a61](https://github.com/leifermendez/bot-whatsapp/commit/14d1a61fa259c09135c37c55bd79e97c9c8367e4))
* **provider:** :zap: venom wweb ([fd2847a](https://github.com/leifermendez/bot-whatsapp/commit/fd2847aea0db17a0bdf33b5bca67a4cb8db2da16))
* **provider:** :zap: venom wweb ([f95331d](https://github.com/leifermendez/bot-whatsapp/commit/f95331d3dc70e76a3dfbe4c8d24059f0e7a164ef))
* **provider:** 🚀 implements all send media to venom provider ([bd7d150](https://github.com/leifermendez/bot-whatsapp/commit/bd7d150c047af41fdbb47f0a50a21e82cd79ee85))
### Bug Fixes
* **bot:** :fire: endFlow with ctx ([f6114af](https://github.com/leifermendez/bot-whatsapp/commit/f6114affadfbc324536a86167d1fdfe8da3c8de6))
* **bot:** :fire: endFlow with ctx ([b655ae4](https://github.com/leifermendez/bot-whatsapp/commit/b655ae449e7958ea940d8cc3c678fd66f60b6385))
* **bot:** :zap: endFlow butons ([87a4203](https://github.com/leifermendez/bot-whatsapp/commit/87a4203cd5b88f566387a76d586248e4265d6e4e))
* **bot:** :zap: fix fallback refactor ([e22780d](https://github.com/leifermendez/bot-whatsapp/commit/e22780d3faba94f71a70f1f201a20690608fa5bf))
* **cli:** :zap: endflow ([1c66f17](https://github.com/leifermendez/bot-whatsapp/commit/1c66f178a56d284bb8cb9df5ca17685c7e5d1ddd))
* **cli:** :zap: refactor fallback in child flow ([b33e346](https://github.com/leifermendez/bot-whatsapp/commit/b33e34692d3abcb6874308a9be79f74be4a2c3a8))
* **cli:** :zap: refactor fallback in child flow ([8da4b20](https://github.com/leifermendez/bot-whatsapp/commit/8da4b204b41125b5d0fa0aee4fa87c1f5faf5568))
### [0.1.18](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.17...v0.1.18) (2023-01-24)
### Features
* **bot:** :zap: add blacklist ([7078dc4](https://github.com/leifermendez/bot-whatsapp/commit/7078dc4c93d01bf90ef08ecb34e89a1abbe16fd2))
* **bot:** :zap: flowDynamic buttons, media ([3c4b1c0](https://github.com/leifermendez/bot-whatsapp/commit/3c4b1c0fc4b6d98d67c67806d918d3604bb2209b))
### Bug Fixes
* **bot:** :bug: body undefined ([bb6ed4a](https://github.com/leifermendez/bot-whatsapp/commit/bb6ed4a084ae98070dfdf0c4ba1eca574c4092cc))
* **bot:** :bug: body undefined ([9234cf1](https://github.com/leifermendez/bot-whatsapp/commit/9234cf1c5d00abdd35e62a826b3c450ab056987a))
* **bot:** :bug: body undefined ([a118bbb](https://github.com/leifermendez/bot-whatsapp/commit/a118bbbf7f0a7023cb7f33c23f37db72adad151f))
* **bot:** :bug: body undefined ([f54dea5](https://github.com/leifermendez/bot-whatsapp/commit/f54dea52b01063acd6122eeba1fbbe324aa7805d))
* **bot:** :bug: body undefined ([72e0a91](https://github.com/leifermendez/bot-whatsapp/commit/72e0a910503e9643db7dfbc6e09c41c96934e1f7))
* **bot:** :bug: body undefined ([70dd4d7](https://github.com/leifermendez/bot-whatsapp/commit/70dd4d73e814fc5636d19a887f3621c483b837c1))
* **bot:** :bug: body undefined ([ecf0eef](https://github.com/leifermendez/bot-whatsapp/commit/ecf0eef928917d76c59bd23886cb7a4108b421f1))
* **bot:** :bug: flowDynamic stranger behaviour ([877252b](https://github.com/leifermendez/bot-whatsapp/commit/877252bd4a8a7bbbbf083c3ceaeaeb952b0a1828))
* **bot:** :bug: flowDynamic stranger behaviour ([f5a7de3](https://github.com/leifermendez/bot-whatsapp/commit/f5a7de3a003c012e2164e51fff26892cfc3144be))
* **bot:** :memo: more docs ([98793d0](https://github.com/leifermendez/bot-whatsapp/commit/98793d0cfc1674830beaa3707f933c5a791eec14))
* **cli:** :zap: refactor ([a29b9d4](https://github.com/leifermendez/bot-whatsapp/commit/a29b9d4e1f85fc163cf1d633c0857f0c8b7f03e1))
* **cli:** :zap: refactor ([18ef4e9](https://github.com/leifermendez/bot-whatsapp/commit/18ef4e9d726575ca390ca24354825860328d3347))
* **cli:** :zap: refactor ([3648757](https://github.com/leifermendez/bot-whatsapp/commit/3648757fa083bdb88a16bf6c2e90c828c233bdb1))
* **cli:** :zap: refactor ([32f6a70](https://github.com/leifermendez/bot-whatsapp/commit/32f6a70f8f6fb26d8ea2a0f1a4aec4827b9d6a93))
* **cli:** :zap: refactor ([8c825e7](https://github.com/leifermendez/bot-whatsapp/commit/8c825e7f6b7133f7cc7f3041ce331b80a9fe60e0))
* **cli:** :zap: refactor ([0c0f437](https://github.com/leifermendez/bot-whatsapp/commit/0c0f4375b84549bee809340a85f9ce038ee2739e))
* **cli:** :zap: refactor ([039ce5d](https://github.com/leifermendez/bot-whatsapp/commit/039ce5dd7cac8115b335ad5de05f7bd871e24140))
* **cli:** :zap: refactor ([5e87918](https://github.com/leifermendez/bot-whatsapp/commit/5e879188b8bf9d486399b308a9a9c2612607d465))
* **cli:** :zap: refactor ([21a7270](https://github.com/leifermendez/bot-whatsapp/commit/21a72702817bc6b344223b34ca4513a7ff45fc93))
* **cli:** :zap: refactor ([82a99b2](https://github.com/leifermendez/bot-whatsapp/commit/82a99b2c80e6738566042ea738bbab8208a17758))
* **cli:** :zap: refactor ([cc19974](https://github.com/leifermendez/bot-whatsapp/commit/cc19974579379777b05cb69c38cec0fce6740471))
* **cli:** :zap: refactor ([56fcb8f](https://github.com/leifermendez/bot-whatsapp/commit/56fcb8fb72169bc21fce7c4fcdceccf2acd39c73))
* **cli:** :zap: refactor ([f36cff1](https://github.com/leifermendez/bot-whatsapp/commit/f36cff1eefdd96be4ab531e1cb2d3b630b1a81c3))
* **cli:** :zap: refactor ([b393c11](https://github.com/leifermendez/bot-whatsapp/commit/b393c11af6c0ebccb0a690be8b90b9df8877dad1))
* **cli:** :zap: refactor ([6683715](https://github.com/leifermendez/bot-whatsapp/commit/6683715ad617ea1075654a475a1c62ea607c733f))
* **contexts:** :bug: fixed [#524](https://github.com/leifermendez/bot-whatsapp/issues/524) issue ([79cc31a](https://github.com/leifermendez/bot-whatsapp/commit/79cc31a96f6a9836447cc4e6bb1e1521c54183fe))
* **contexts:** :bug: fixed [#524](https://github.com/leifermendez/bot-whatsapp/issues/524) issue ([7067b4a](https://github.com/leifermendez/bot-whatsapp/commit/7067b4a80b7938ccfaf1ed141a37d645a1a3a062))
* **provider:** wwebjs upgrade ([345f256](https://github.com/leifermendez/bot-whatsapp/commit/345f256a1b4a238519dafc15c9a31bc5e6bad4fe))
* se agrego @bot-whatsapp/portal a package.json ([46a9fa6](https://github.com/leifermendez/bot-whatsapp/commit/46a9fa6793e06600335de998d2bd9d0691b02ca4))
### [0.1.17](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.16...v0.1.17) (2023-01-13)
### Features
* mod de starters para habiltar portal ([eceb170](https://github.com/leifermendez/bot-whatsapp/commit/eceb170df03721dca4183b658c863b94fa04bc84))
### Bug Fixes
* **ci:** pre-release ([aaec075](https://github.com/leifermendez/bot-whatsapp/commit/aaec0751408ab49483d428810d94aaf7d46acb94))
* correccion en starters app.js para portal QR ([f430380](https://github.com/leifermendez/bot-whatsapp/commit/f430380b4f23d41702395c96c628bf13bf443278))
* **starters:** :zap: added dockerfile ([230981e](https://github.com/leifermendez/bot-whatsapp/commit/230981e2676361149cb2a99def7f705e75009260))
### [0.1.16](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.15...v0.1.16) (2023-01-11)
### [0.1.15](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.14...v0.1.15) (2023-01-11)
### [0.1.14](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.13...v0.1.14) (2023-01-11)
### [0.1.13](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.12...v0.1.13) (2023-01-11)
### [0.1.12](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.10...v0.1.12) (2023-01-11)
### [0.1.9-pre](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.9...v0.1.9-pre) (2023-01-10)
### [0.1.7-pre-1](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.7-alpha...v0.1.7-pre-1) (2023-01-10)
### [0.1.7-alpha](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.7-pre...v0.1.7-alpha) (2023-01-10)
### [0.1.11](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.10...v0.1.11) (2023-01-11)
### [0.1.9-pre](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.9...v0.1.9-pre) (2023-01-10)
### [0.1.7-pre-1](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.7-alpha...v0.1.7-pre-1) (2023-01-10)
### [0.1.7-alpha](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.7-pre...v0.1.7-alpha) (2023-01-10)
### [0.1.10](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.8...v0.1.10) (2023-01-11)
### Bug Fixes
* :fire: update qr package ([ecde23f](https://github.com/leifermendez/bot-whatsapp/commit/ecde23fdea65def209aa874af35a3f293e6b1a91))
### [0.1.8](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.7-pre...v0.1.8) (2023-01-10)
### [0.1.7](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.7-pre...v0.1.7) (2023-01-10)
### [0.1.6](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.5...v0.1.6) (2023-01-10)
### Features
* :zap: new portal web for qr scan ([cb2e869](https://github.com/leifermendez/bot-whatsapp/commit/cb2e8692a3f94c8b24993cd11dd564f094b0e4ef))
* :zap: new portal web for qr scan ([9e93795](https://github.com/leifermendez/bot-whatsapp/commit/9e93795e6fce38890045389da95184fef1fbe0da))
* :zap: new portal web for qr scan ([3c178ea](https://github.com/leifermendez/bot-whatsapp/commit/3c178ea113b140535a51f5dcd521dbb66251670e))
* :zap: new portal web for qr scan ([1f1f564](https://github.com/leifermendez/bot-whatsapp/commit/1f1f564f4e2e3aa13b84de500fe215e0c45c2770))
* :zap: new portal web for qr scan ([3de5f4b](https://github.com/leifermendez/bot-whatsapp/commit/3de5f4b77a10e30632ff7555f5af5d8e93cb2019))
* :zap: qr code filename ([d794f60](https://github.com/leifermendez/bot-whatsapp/commit/d794f604ac8a835e523709dbf18c9b1609bbd00e))
* :zap: qr portal ([246ecdc](https://github.com/leifermendez/bot-whatsapp/commit/246ecdc11a8c4e652867c842b612dc4ce73f9828))
* :zap: qr portal ([af8b401](https://github.com/leifermendez/bot-whatsapp/commit/af8b401d075e1c35065589ede61476461ce86b4d))
* agregamos dockerfile y webserver a starters ([f9e3bbc](https://github.com/leifermendez/bot-whatsapp/commit/f9e3bbc6655060408e4fdbe1d7e920c2ed4fca53))
### Bug Fixes
* :zap: add Dockerfile, starter ([4e0d33c](https://github.com/leifermendez/bot-whatsapp/commit/4e0d33c6bb46ad259774f6d0c38c6c0b5f8ca4a9))
* :zap: fix inject port args ([20f752e](https://github.com/leifermendez/bot-whatsapp/commit/20f752e6c1b1f7d11948fc4f2f8950f7834df7d9))
* :zap: fix inject port args ([7a23eb0](https://github.com/leifermendez/bot-whatsapp/commit/7a23eb0cc6f93ec21c5ab34e46981ae7a93f42ff))
* **provider:** :zap: fix send image baileys ([2ddea54](https://github.com/leifermendez/bot-whatsapp/commit/2ddea5468d235035478d4e91e63c821da19da179))
* **provider:** :zap: fix send image baileys ([391e11c](https://github.com/leifermendez/bot-whatsapp/commit/391e11ce738cd64792b5237d69f3739b0263c198))
* **provider:** :zap: fix send image baileys ([5d10cb9](https://github.com/leifermendez/bot-whatsapp/commit/5d10cb9026da60043e9a2f86117ebb04d0631a3f))
* **provider:** fix error docker as root user ([5a033da](https://github.com/leifermendez/bot-whatsapp/commit/5a033da83aee1f614120bccf27c9f330500cc7b0))
### [0.1.4](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.5...v0.1.4) (2023-01-10)
### Features
* :zap: new portal web for qr scan ([cb2e869](https://github.com/leifermendez/bot-whatsapp/commit/cb2e8692a3f94c8b24993cd11dd564f094b0e4ef))
* :zap: new portal web for qr scan ([9e93795](https://github.com/leifermendez/bot-whatsapp/commit/9e93795e6fce38890045389da95184fef1fbe0da))
* :zap: new portal web for qr scan ([3c178ea](https://github.com/leifermendez/bot-whatsapp/commit/3c178ea113b140535a51f5dcd521dbb66251670e))
* :zap: new portal web for qr scan ([1f1f564](https://github.com/leifermendez/bot-whatsapp/commit/1f1f564f4e2e3aa13b84de500fe215e0c45c2770))
* :zap: new portal web for qr scan ([3de5f4b](https://github.com/leifermendez/bot-whatsapp/commit/3de5f4b77a10e30632ff7555f5af5d8e93cb2019))
* :zap: qr code filename ([d794f60](https://github.com/leifermendez/bot-whatsapp/commit/d794f604ac8a835e523709dbf18c9b1609bbd00e))
* :zap: qr portal ([246ecdc](https://github.com/leifermendez/bot-whatsapp/commit/246ecdc11a8c4e652867c842b612dc4ce73f9828))
* :zap: qr portal ([af8b401](https://github.com/leifermendez/bot-whatsapp/commit/af8b401d075e1c35065589ede61476461ce86b4d))
* agregamos dockerfile y webserver a starters ([f9e3bbc](https://github.com/leifermendez/bot-whatsapp/commit/f9e3bbc6655060408e4fdbe1d7e920c2ed4fca53))
### Bug Fixes
* :zap: add Dockerfile, starter ([4e0d33c](https://github.com/leifermendez/bot-whatsapp/commit/4e0d33c6bb46ad259774f6d0c38c6c0b5f8ca4a9))
* :zap: fix inject port args ([20f752e](https://github.com/leifermendez/bot-whatsapp/commit/20f752e6c1b1f7d11948fc4f2f8950f7834df7d9))
* :zap: fix inject port args ([7a23eb0](https://github.com/leifermendez/bot-whatsapp/commit/7a23eb0cc6f93ec21c5ab34e46981ae7a93f42ff))
* **provider:** :zap: fix send image baileys ([2ddea54](https://github.com/leifermendez/bot-whatsapp/commit/2ddea5468d235035478d4e91e63c821da19da179))
* **provider:** :zap: fix send image baileys ([391e11c](https://github.com/leifermendez/bot-whatsapp/commit/391e11ce738cd64792b5237d69f3739b0263c198))
* **provider:** :zap: fix send image baileys ([5d10cb9](https://github.com/leifermendez/bot-whatsapp/commit/5d10cb9026da60043e9a2f86117ebb04d0631a3f))
* **provider:** fix error docker as root user ([5a033da](https://github.com/leifermendez/bot-whatsapp/commit/5a033da83aee1f614120bccf27c9f330500cc7b0))
### [0.1.3](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.2...v0.1.3) (2023-01-04) ### [0.1.3](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.2...v0.1.3) (2023-01-04)

View File

@@ -1,8 +1,7 @@
# Chatbot Library # Chatbot Library
![](https://img.shields.io/npm/v/@bot-whatsapp/bot?color=%2300c200&label=%40bot-whatsapp) ![](https://img.shields.io/npm/v/@bot-whatsapp/bot?color=%2300c200&label=%40bot-whatsapp)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![BotWhatsapp Releases(Prod)](https://github.com/codigoencasa/bot-whatsapp/actions/workflows/releases.yml/badge.svg)](https://github.com/codigoencasa/bot-whatsapp/actions/workflows/releases.yml) [![](https://img.shields.io/discord/915193197645402142?logo=discord)](https://link.codigoencasa.com/DISCORD)
<p align="center"> <p align="center">
<img width="300" src="https://i.imgur.com/Oauef6t.png"> <img width="300" src="https://i.imgur.com/Oauef6t.png">
@@ -34,6 +33,13 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<!-- readme: collaborators,contributors -start --> <!-- readme: collaborators,contributors -start -->
<table> <table>
<tr> <tr>
<td align="center">
<a href="https://github.com/cheveguerra">
<img src="https://avatars.githubusercontent.com/u/5891114?v=4" width="50;" alt="cheveguerra"/>
<br />
<sub><b>Jose Alberto Guerra Ugalde</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/leifermendez"> <a href="https://github.com/leifermendez">
<img src="https://avatars.githubusercontent.com/u/15802366?v=4" width="50;" alt="leifermendez"/> <img src="https://avatars.githubusercontent.com/u/15802366?v=4" width="50;" alt="leifermendez"/>
@@ -62,6 +68,21 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Leifer Mendez</b></sub> <sub><b>Leifer Mendez</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/danielcasta0398">
<img src="https://avatars.githubusercontent.com/u/98791147?v=4" width="50;" alt="danielcasta0398"/>
<br />
<sub><b>Juan Daniel Castaño</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/marianarolfo">
<img src="https://avatars.githubusercontent.com/u/68322254?v=4" width="50;" alt="marianarolfo"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/HKong31"> <a href="https://github.com/HKong31">
<img src="https://avatars.githubusercontent.com/u/113340082?v=4" width="50;" alt="HKong31"/> <img src="https://avatars.githubusercontent.com/u/113340082?v=4" width="50;" alt="HKong31"/>
@@ -75,8 +96,14 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<br /> <br />
<sub><b>Zvi</b></sub> <sub><b>Zvi</b></sub>
</a> </a>
</td></tr> </td>
<tr> <td align="center">
<a href="https://github.com/JosephVTX">
<img src="https://avatars.githubusercontent.com/u/91026290?v=4" width="50;" alt="JosephVTX"/>
<br />
<sub><b>Joseph Vega Callupe</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/Gonzalito87"> <a href="https://github.com/Gonzalito87">
<img src="https://avatars.githubusercontent.com/u/100331586?v=4" width="50;" alt="Gonzalito87"/> <img src="https://avatars.githubusercontent.com/u/100331586?v=4" width="50;" alt="Gonzalito87"/>
@@ -84,6 +111,42 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Null</b></sub> <sub><b>Null</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/devrlbusiness">
<img src="https://avatars.githubusercontent.com/u/66280283?v=4" width="50;" alt="devrlbusiness"/>
<br />
<sub><b>Developer RL Business</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/Gregoriotecnico">
<img src="https://avatars.githubusercontent.com/u/118696506?v=4" width="50;" alt="Gregoriotecnico"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jlferrete">
<img src="https://avatars.githubusercontent.com/u/36698913?v=4" width="50;" alt="jlferrete"/>
<br />
<sub><b>Jose Luis Ferrete Olarte</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lisandroprada">
<img src="https://avatars.githubusercontent.com/u/7232326?v=4" width="50;" alt="lisandroprada"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/6rak0">
<img src="https://avatars.githubusercontent.com/u/12260031?v=4" width="50;" alt="6rak0"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/tonyvazgar"> <a href="https://github.com/tonyvazgar">
<img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/> <img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/>
@@ -91,20 +154,14 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Luis Antonio Vázquez García</b></sub> <sub><b>Luis Antonio Vázquez García</b></sub>
</a> </a>
</td> </td>
<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"> <td align="center">
<a href="https://github.com/rrruuuyyy"> <a href="https://github.com/rrruuuyyy">
<img src="https://avatars.githubusercontent.com/u/33061671?v=4" width="50;" alt="rrruuuyyy"/> <img src="https://avatars.githubusercontent.com/u/33061671?v=4" width="50;" alt="rrruuuyyy"/>
<br /> <br />
<sub><b>Rodrigo Mendoza Cabrera</b></sub> <sub><b>Rodrigo Mendoza Cabrera</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/yond1994"> <a href="https://github.com/yond1994">
<img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/> <img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/>

36
__test__/01-case.test.js Normal file
View File

@@ -0,0 +1,36 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
test(`[Caso - 01] Flow Basico`, async () => {
const [VALUE_A, VALUE_B] = ['hola', 'buenas']
const flow = addKeyword(VALUE_A).addAnswer(VALUE_B)
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
createBot({
database,
flow: createFlow([flow]),
provider,
})
provider.delaySendMessage(100, 'message', {
from: '000',
body: VALUE_A,
})
await delay(100)
const prevMsg = database.getPrevByNumber('000')
assert.is(prevMsg.answer, VALUE_B)
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

94
__test__/02-case.test.js Normal file
View File

@@ -0,0 +1,94 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
console.log('⚡ Server request!')
await delay(50)
console.log('⚡ Server return!')
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
console.log(data)
return Promise.resolve(data)
}
test(`[Caso - 02] Flow (flowDynamic)`, async () => {
const MOCK_VALUES = [
'Bienvenido te envio muchas marcas (5510)',
'Seleccione marca del auto a cotizar, con el *número* correspondiente',
'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:',
'Los precios rondan:',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0], null, async (ctx, { flowDynamic }) => {
console.log('execute...')
const data = await fakeHTTP(['Ford', 'GM', 'BMW'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[1], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['Ranger', 'Explorer'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['Usado', 'Nuevos'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[3], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['1000', '2000', '3000'])
return flowDynamic(data)
})
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
//FlowDynamic
assert.is('1 Ford', getHistory[1])
assert.is('2 GM', getHistory[2])
assert.is('3 BMW', getHistory[3])
assert.is(MOCK_VALUES[1], getHistory[4])
//FlowDynamic
assert.is('1 Ranger', getHistory[5])
assert.is('2 Explorer', getHistory[6])
assert.is(MOCK_VALUES[2], getHistory[7])
//FlowDynamic
assert.is('1 Usado', getHistory[8])
assert.is('2 Nuevos', getHistory[9])
assert.is(MOCK_VALUES[3], getHistory[10])
//FlowDynamic
assert.is('1 1000', getHistory[11])
assert.is('2 2000', getHistory[12])
assert.is('3 3000', getHistory[13])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

37
__test__/03-case.test.js Normal file
View File

@@ -0,0 +1,37 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
test(`[Caso - 03] Flow puro`, async () => {
const MOCK_VALUES = ['Bienvenido a mi tienda', 'Como estas?']
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola']).addAnswer(MOCK_VALUES[0]).addAnswer(MOCK_VALUES[1])
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(10)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is(MOCK_VALUES[1], getHistory[1])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

77
__test__/04-case.test.js Normal file
View File

@@ -0,0 +1,77 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
console.log('⚡ Server request!')
await delay(50)
console.log('⚡ Server return!')
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
console.log(data)
return Promise.resolve(data)
}
test(`[Caso - 04] Romper flujo (endFlow)`, async () => {
const MOCK_VALUES = [
'Bienvenido te envio muchas marcas (5510)',
'Seleccione marca del auto a cotizar, con el *número* correspondiente',
'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:',
'Los precios rondan:',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(MOCK_VALUES[0], null, async (ctx, { flowDynamic }) => {
console.log('execute...')
const data = await fakeHTTP(['Ford', 'GM', 'BMW'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[1], null, async (ctx, { endFlow }) => {
return endFlow()
})
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['Usado', 'Nuevos'])
return flowDynamic(data)
})
.addAnswer(MOCK_VALUES[3], null, async (ctx, { flowDynamic }) => {
const data = await fakeHTTP(['1000', '2000', '3000'])
return flowDynamic(data)
})
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
//FlowDynamic
assert.is('1 Ford', getHistory[1])
assert.is('2 GM', getHistory[2])
assert.is('3 BMW', getHistory[3])
assert.is(MOCK_VALUES[1], getHistory[4])
assert.is(undefined, getHistory[5])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

100
__test__/05-case.test.js Normal file
View File

@@ -0,0 +1,100 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
await delay(5)
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
return Promise.resolve(data)
}
test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => {
const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?']
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
const validation = ctx.body.includes('@')
if (validation) {
const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta'])
return flowDynamic(getDataFromApi)
}
return fallBack(validation)
}
)
.addAnswer(MOCK_VALUES[1])
.addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => {
if (ctx.body !== '18') {
await delay(50)
return fallBack(false, 'Ups creo que no eres mayor de edad')
}
return flowDynamic('Bien tu edad es correcta!')
})
.addAnswer('Puedes pasar')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(10, 'message', {
from: '000',
body: 'this is not email value',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: 'test@test.com',
})
provider.delaySendMessage(90, 'message', {
from: '000',
body: '20',
})
provider.delaySendMessage(200, 'message', {
from: '000',
body: '18',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('this is not email value', getHistory[1])
assert.is(MOCK_VALUES[0], getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is('1 Gracias por tu email se ha validado de manera correcta', getHistory[4])
assert.is(MOCK_VALUES[1], getHistory[5])
assert.is(MOCK_VALUES[2], getHistory[6])
assert.is('20', getHistory[7])
assert.is('Ups creo que no eres mayor de edad', getHistory[8])
assert.is('18', getHistory[9])
assert.is('Bien tu edad es correcta!', getHistory[10])
assert.is('Puedes pasar', getHistory[11])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

93
__test__/06-case.test.js Normal file
View File

@@ -0,0 +1,93 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
await delay(5)
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
return Promise.resolve(data)
}
test(`[Caso - 06] Finalizar Flujo (endFlow)`, async () => {
const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?']
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
const validation = ctx.body.includes('@')
if (validation) {
const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta'])
return flowDynamic(getDataFromApi)
}
return fallBack(validation)
}
)
.addAnswer(MOCK_VALUES[1], null, async (_, { endFlow }) => {
return endFlow()
})
.addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => {
if (ctx.body !== '18') {
await delay(50)
return fallBack(false, 'Ups creo que no eres mayor de edad')
}
return flowDynamic('Bien tu edad es correcta!')
})
.addAnswer('Puedes pasar')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(10, 'message', {
from: '000',
body: 'this is not email value',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: 'test@test.com',
})
provider.delaySendMessage(90, 'message', {
from: '000',
body: '20',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('this is not email value', getHistory[1])
assert.is(MOCK_VALUES[0], getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is('1 Gracias por tu email se ha validado de manera correcta', getHistory[4])
assert.is(MOCK_VALUES[1], getHistory[5])
assert.is('20', getHistory[6])
assert.is(undefined, getHistory[7])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

92
__test__/07-case.test.js Normal file
View File

@@ -0,0 +1,92 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
await delay(5)
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
return Promise.resolve(data)
}
let STATE_APP = {}
test(`[Caso - 07] Retornar estado`, async () => {
const MOCK_VALUES = ['¿Cual es tu nombre?', '¿Cual es tu edad?', 'Tu datos son:']
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], name: ctx.body }
flowDynamic('Gracias por tu nombre!')
}
)
.addAnswer(
MOCK_VALUES[1],
{
capture: true,
},
async (ctx, { flowDynamic, endFlow }) => {
STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], age: ctx.body }
await flowDynamic('Gracias por tu edad!')
}
)
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
flowDynamic(`Nombre: ${STATE_APP[ctx.from].name} Edad: ${STATE_APP[ctx.from].age}`)
})
.addAnswer('🤖🤖 Gracias por tu participacion')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: 'Leifer',
})
provider.delaySendMessage(40, 'message', {
from: '000',
body: '90',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('Leifer', getHistory[1])
assert.is('Gracias por tu nombre!', getHistory[2])
assert.is('¿Cual es tu edad?', getHistory[3])
assert.is('90', getHistory[4])
assert.is('Gracias por tu edad!', getHistory[5])
assert.is('Tu datos son:', getHistory[6])
assert.is('Nombre: Leifer Edad: 90', getHistory[7])
assert.is('🤖🤖 Gracias por tu participacion', getHistory[8])
assert.is(undefined, getHistory[9])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

43
__test__/08-case.test.js Normal file
View File

@@ -0,0 +1,43 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
test(`[Caso - 08] Regular expression on keyword`, async () => {
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const REGEX_CREDIT_NUMBER = `/(^4[0-9]{12}(?:[0-9]{3})?$)|(^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$)|(3[47][0-9]{13})|(^3(?:0[0-5]|[68][0-9])[0-9]{11}$)|(^6(?:011|5[0-9]{2})[0-9]{12}$)|(^(?:2131|1800|35\d{3})\d{11}$)/gm`
const flujoPrincipal = addKeyword(REGEX_CREDIT_NUMBER, { regex: true })
.addAnswer(`Gracias por proporcionar un numero de tarjeta valido`)
.addAnswer('Fin!')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: '374245455400126',
})
await delay(40)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is('Gracias por proporcionar un numero de tarjeta valido', getHistory[0])
assert.is('Fin!', getHistory[1])
assert.is(undefined, getHistory[2])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

41
__test__/09-case.test.js Normal file
View File

@@ -0,0 +1,41 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_MOCK = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index')
let PROVIDER = undefined
test(`[Caso - 09] Check provider WS`, async () => {
const [VALUE_A, VALUE_B] = ['hola', 'buenas']
const flow = addKeyword(VALUE_A).addAnswer(VALUE_B, null, async (_, { provider }) => {
PROVIDER = provider
})
const provider = createProvider(PROVIDER_MOCK)
const database = new MOCK_DB()
createBot({
database,
flow: createFlow([flow]),
provider,
})
provider.delaySendMessage(100, 'message', {
from: '000',
body: VALUE_A,
})
await delay(100)
const prevMsg = database.getPrevByNumber('000')
assert.is(prevMsg.answer, VALUE_B)
assert.is(typeof PROVIDER.sendMessage, 'function')
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

View File

@@ -1,28 +1,10 @@
module.exports = { module.exports = {
disableEmoji: false, disableEmoji: false,
format: '{type}{scope}: {emoji}{subject}', format: '{type}{scope}: {emoji}{subject}',
list: [ list: ['test', 'feat', 'fix', 'chore', 'docs', 'refactor', 'style', 'ci', 'perf'],
'test',
'feat',
'fix',
'chore',
'docs',
'refactor',
'style',
'ci',
'perf',
],
maxMessageLength: 64, maxMessageLength: 64,
minMessageLength: 3, minMessageLength: 3,
questions: [ questions: ['type', 'scope', 'subject', 'body', 'breaking', 'issues', 'lerna'],
'type',
'scope',
'subject',
'body',
'breaking',
'issues',
'lerna',
],
scopes: [], scopes: [],
types: { types: {
chore: { chore: {
@@ -56,8 +38,7 @@ module.exports = {
value: 'perf', value: 'perf',
}, },
refactor: { refactor: {
description: description: 'A code change that neither fixes a bug or adds a feature',
'A code change that neither fixes a bug or adds a feature',
emoji: '(💡)', emoji: '(💡)',
value: 'refactor', value: 'refactor',
}, },
@@ -67,8 +48,7 @@ module.exports = {
value: 'release', value: 'release',
}, },
style: { style: {
description: description: 'Markup, white-space, formatting, missing semi-colons...',
'Markup, white-space, formatting, missing semi-colons...',
emoji: '(💄)', emoji: '(💄)',
value: 'style', value: 'style',
}, },
@@ -80,8 +60,7 @@ module.exports = {
messages: { messages: {
type: "Select the type of change that you're committing:", type: "Select the type of change that you're committing:",
customScope: 'Select the scope this component affects:', customScope: 'Select the scope this component affects:',
subject: subject: 'Write a short, imperative mood description of the change:\n',
'Write a short, imperative mood description of the change:\n',
body: 'Provide a longer description of the change:\n ', body: 'Provide a longer description of the change:\n ',
breaking: 'List any breaking changes:\n', breaking: 'List any breaking changes:\n',
footer: 'Issues this commit closes, e.g #123:', footer: 'Issues this commit closes, e.g #123:',

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/root", "name": "@bot-whatsapp/root",
"version": "0.1.3", "version": "0.1.20",
"description": "Bot de wahtsapp open source para MVP o pequeños negocios", "description": "Bot de wahtsapp open source para MVP o pequeños negocios",
"main": "app.js", "main": "app.js",
"private": true, "private": true,
@@ -13,15 +13,19 @@
"contexts:rollup": "rollup --config ./packages/contexts/rollup-contexts.config.js", "contexts:rollup": "rollup --config ./packages/contexts/rollup-contexts.config.js",
"database:rollup": "rollup --config ./packages/database/rollup-database.config.js", "database:rollup": "rollup --config ./packages/database/rollup-database.config.js",
"create-bot-whatsapp:rollup": "rollup --config ./packages/create-bot-whatsapp/rollup-create.config.js", "create-bot-whatsapp:rollup": "rollup --config ./packages/create-bot-whatsapp/rollup-create.config.js",
"portal:rollup": "rollup --config ./packages/portal/rollup-portal.config.js",
"format:check": "prettier --check ./packages", "format:check": "prettier --check ./packages",
"format:write": "prettier --write ./packages", "format:write": "prettier --write ./packages",
"fmt.staged": "pretty-quick --staged", "fmt.staged": "pretty-quick --staged",
"lint:check": "eslint ./packages", "lint:check": "eslint ./packages",
"lint:fix": "eslint --fix ./packages", "lint:fix": "eslint --fix ./packages",
"build": "yarn run cli:rollup && yarn run bot:rollup && yarn run provider:rollup && yarn run database:rollup && yarn run contexts:rollup && yarn run create-bot-whatsapp:rollup", "build:portal-web": "cd ./packages/portal/ && yarn run build.types && yarn run build.client && yarn run build.server && yarn run lint --fix",
"build:full": "yarn run build:portal-web && yarn run cli:rollup && yarn run bot:rollup && yarn run provider:rollup && yarn run database:rollup && yarn run contexts:rollup && yarn run create-bot-whatsapp:rollup && yarn run portal:rollup",
"build": "yarn run cli:rollup && yarn run bot:rollup && yarn run provider:rollup && yarn run database:rollup && yarn run contexts:rollup && yarn run create-bot-whatsapp:rollup && yarn run portal:rollup",
"copy.lib": "node ./scripts/move.js", "copy.lib": "node ./scripts/move.js",
"test.unit": "node ./node_modules/uvu/bin.js packages test", "test.unit": "node ./node_modules/uvu/bin.js packages test",
"test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit", "test.e2e": "node ./node_modules/uvu/bin.js __test__",
"test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit && npm run test.e2e",
"test": "npm run test.coverage", "test": "npm run test.coverage",
"cli": "node ./packages/cli/bin/cli.js", "cli": "node ./packages/cli/bin/cli.js",
"create": "node ./packages/create-bot-whatsapp/bin/create.js", "create": "node ./packages/create-bot-whatsapp/bin/create.js",
@@ -30,7 +34,7 @@
"prepare": "npx husky install", "prepare": "npx husky install",
"preinstall": "npx only-allow yarn", "preinstall": "npx only-allow yarn",
"postinstall": "npx prettier --write .", "postinstall": "npx prettier --write .",
"release": "standard-version -- --prerelease" "release": "standard-version -- --prerelease --global"
}, },
"workspaces": [ "workspaces": [
"packages/create-bot-whatsapp", "packages/create-bot-whatsapp",
@@ -39,6 +43,7 @@
"packages/database", "packages/database",
"packages/provider", "packages/provider",
"packages/contexts", "packages/contexts",
"packages/portal",
"packages/docs" "packages/docs"
], ],
"keywords": [ "keywords": [
@@ -63,6 +68,7 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.3.0", "@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0", "@commitlint/config-conventional": "^17.3.0",
"@octokit/core": "^4.1.0",
"@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-json": "^5.0.1", "@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
@@ -75,6 +81,7 @@
"fs-extra": "^11.1.0", "fs-extra": "^11.1.0",
"git-cz": "^4.9.0", "git-cz": "^4.9.0",
"husky": "^8.0.2", "husky": "^8.0.2",
"mime-types": "^2.1.35",
"only-allow": "^1.1.1", "only-allow": "^1.1.1",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",

View File

@@ -8,6 +8,9 @@ const { createWriteStream } = require('fs')
const logger = new Console({ const logger = new Console({
stdout: createWriteStream(`${process.cwd()}/core.class.log`), stdout: createWriteStream(`${process.cwd()}/core.class.log`),
}) })
const QueuePrincipal = new Queue()
/** /**
* [ ] Escuchar eventos del provider asegurarte que los provider emitan eventos * [ ] Escuchar eventos del provider asegurarte que los provider emitan eventos
* [ ] Guardar historial en db * [ ] Guardar historial en db
@@ -18,10 +21,12 @@ class CoreClass {
flowClass flowClass
databaseClass databaseClass
providerClass providerClass
constructor(_flow, _database, _provider) { generalArgs = { blackList: [] }
constructor(_flow, _database, _provider, _args) {
this.flowClass = _flow this.flowClass = _flow
this.databaseClass = _database this.databaseClass = _database
this.providerClass = _provider this.providerClass = _provider
this.generalArgs = { ...this.generalArgs, ..._args }
for (const { event, func } of this.listenerBusEvents()) { for (const { event, func } of this.listenerBusEvents()) {
this.providerClass.on(event, func) this.providerClass.on(event, func)
@@ -38,8 +43,7 @@ class CoreClass {
}, },
{ {
event: 'require_action', event: 'require_action',
func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) => func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) => printer(instructions, title),
printer(instructions, title),
}, },
{ {
event: 'ready', event: 'ready',
@@ -47,8 +51,7 @@ class CoreClass {
}, },
{ {
event: 'auth_failure', event: 'auth_failure',
func: ({ instructions }) => func: ({ instructions }) => printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'),
printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'),
}, },
{ {
@@ -66,14 +69,14 @@ class CoreClass {
logger.log(`[handleMsg]: `, messageCtxInComming) logger.log(`[handleMsg]: `, messageCtxInComming)
const { body, from } = messageCtxInComming const { body, from } = messageCtxInComming
let msgToSend = [] let msgToSend = []
let endFlowFlag = false
let fallBackFlag = false let fallBackFlag = false
if (this.generalArgs.blackList.includes(from)) return
if (!body) return
if (!body.length) return if (!body.length) return
const prevMsg = await this.databaseClass.getPrevByNumber(from) let prevMsg = await this.databaseClass.getPrevByNumber(from)
const refToContinue = this.flowClass.findBySerialize( const refToContinue = this.flowClass.findBySerialize(prevMsg?.refSerialize)
prevMsg?.refSerialize
)
if (prevMsg?.ref) { if (prevMsg?.ref) {
const ctxByNumber = toCtx({ const ctxByNumber = toCtx({
@@ -84,53 +87,128 @@ class CoreClass {
this.databaseClass.save(ctxByNumber) this.databaseClass.save(ctxByNumber)
} }
// 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje // 📄 Crar CTX de mensaje (uso private)
const fallBack = () => { const createCtxMessage = (payload = {}, index = 0) => {
fallBackFlag = true const body = typeof payload === 'string' ? payload : payload?.body ?? payload?.answer
msgToSend = this.flowClass.find(refToContinue?.keyword, true) || [] const media = payload?.media ?? null
this.sendFlow(msgToSend, from) const buttons = payload?.buttons ?? []
return refToContinue const capture = payload?.capture ?? false
}
// 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes return toCtx({
// para evitar bloque de whatsapp
const flowDynamic = (listMsg = [], optListMsg = { limit: 3 }) => {
if (!Array.isArray(listMsg))
throw new Error('Esto debe ser un ARRAY')
const parseListMsg = listMsg
.map(({ body }, index) =>
toCtx({
body, body,
from, from,
keyword: null, keyword: null,
index, index,
options: { media, buttons, capture },
}) })
) }
.slice(0, optListMsg.limit)
msgToSend = parseListMsg // 📄 Limpiar cola de procesos
this.sendFlow(msgToSend, from) const clearQueue = () => {
QueuePrincipal.pendingPromise = false
QueuePrincipal.queue = []
}
// 📄 Finalizar flujo
const endFlow = async (message = null) => {
endFlowFlag = true
if (message) this.sendProviderAndSave(from, createCtxMessage(message))
clearQueue()
sendFlow([])
return return
} }
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx
const sendFlow = async (messageToSend, numberOrId, options = { prev: prevMsg }) => {
if (options.prev?.options?.capture) await cbEveryCtx(options.prev?.ref)
const queue = []
for (const ctxMessage of messageToSend) {
if (endFlowFlag) return
const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs)
QueuePrincipal.enqueue(() =>
Promise.all([
this.sendProviderAndSave(numberOrId, ctxMessage).then(() => resolveCbEveryCtx(ctxMessage)),
])
)
}
return Promise.all(queue)
}
// 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje
const fallBack = async (validation = false, message = null) => {
QueuePrincipal.queue = []
if (validation) {
const currentPrev = await this.databaseClass.getPrevByNumber(from)
const nextFlow = await this.flowClass.find(refToContinue?.ref, true)
const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize)
return sendFlow(filterNextFlow, from, { prev: undefined })
}
await this.sendProviderAndSave(from, {
...prevMsg,
answer: typeof message === 'string' ? message : message?.body ?? prevMsg.answer,
options: {
...prevMsg.options,
buttons: prevMsg.options?.buttons,
},
})
return
}
// 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes
// para evitar bloque de whatsapp
const flowDynamic = async (listMsg = []) => {
if (!Array.isArray(listMsg)) listMsg = [listMsg]
const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index))
const currentPrev = await this.databaseClass.getPrevByNumber(from)
const skipContinueFlow = async () => {
const nextFlow = await this.flowClass.find(refToContinue?.ref, true)
const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize)
const isContinueFlow = filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref)
return {
continue: !isContinueFlow,
contexts: filterNextFlow,
}
}
if (endFlowFlag) return
for (const msg of parseListMsg) {
await this.sendProviderAndSave(from, msg)
}
const continueFlowData = await skipContinueFlow()
if (continueFlowData.continue) return sendFlow(continueFlowData.contexts, from, { prev: undefined })
return
}
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback
const resolveCbEveryCtx = async (ctxMessage) => {
if (!ctxMessage?.options?.capture) return await cbEveryCtx(ctxMessage?.ref)
}
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo // 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo
const cbEveryCtx = (inRef) => { const cbEveryCtx = async (inRef) => {
this.flowClass.allCallbacks[inRef](messageCtxInComming, { const provider = this.providerClass
if (!this.flowClass.allCallbacks[inRef]) return Promise.resolve()
return this.flowClass.allCallbacks[inRef](messageCtxInComming, {
provider,
fallBack, fallBack,
flowDynamic, flowDynamic,
endFlow,
}) })
} }
// 📄 [options: callback]: Si se tiene un callback se ejecuta
if (!fallBackFlag) {
if (refToContinue?.options?.capture) cbEveryCtx(refToContinue?.ref)
for (const ite of this.flowClass.find(body)) {
if (!ite?.options?.capture) cbEveryCtx(ite?.ref)
}
}
// 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa // 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa
if (!fallBackFlag && prevMsg?.options?.nested?.length) { if (!endFlowFlag && prevMsg?.options?.nested?.length) {
const nestedRef = prevMsg.options.nested const nestedRef = prevMsg.options.nested
const flowStandalone = nestedRef.map((f) => ({ const flowStandalone = nestedRef.map((f) => ({
...nestedRef.find((r) => r.refSerialize === f.refSerialize), ...nestedRef.find((r) => r.refSerialize === f.refSerialize),
@@ -138,28 +216,23 @@ class CoreClass {
msgToSend = this.flowClass.find(body, false, flowStandalone) || [] msgToSend = this.flowClass.find(body, false, flowStandalone) || []
for (const ite of msgToSend) { sendFlow(msgToSend, from)
cbEveryCtx(ite?.ref)
}
this.sendFlow(msgToSend, from)
return return
} }
// 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean // 📄🤘(tiene return) Si el mensaje previo implementa capture
if (!fallBackFlag && !prevMsg?.options?.nested?.length) { if (!endFlowFlag && !prevMsg?.options?.nested?.length) {
const typeCapture = typeof prevMsg?.options?.capture const typeCapture = typeof prevMsg?.options?.capture
const valueCapture = prevMsg?.options?.capture
if (['string', 'boolean'].includes(typeCapture) && valueCapture) { if (typeCapture === 'boolean' && fallBackFlag) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
this.sendFlow(msgToSend, from) sendFlow(msgToSend, from)
return return
} }
} }
msgToSend = this.flowClass.find(body) || [] msgToSend = this.flowClass.find(body) || []
this.sendFlow(msgToSend, from) sendFlow(msgToSend, from)
} }
/** /**
@@ -170,25 +243,13 @@ class CoreClass {
*/ */
sendProviderAndSave = (numberOrId, ctxMessage) => { sendProviderAndSave = (numberOrId, ctxMessage) => {
const { answer } = ctxMessage const { answer } = ctxMessage
return Promise.all([ return this.providerClass
this.providerClass.sendMessage(numberOrId, answer, ctxMessage), .sendMessage(numberOrId, answer, ctxMessage)
this.databaseClass.save({ ...ctxMessage, from: numberOrId }), .then(() => this.databaseClass.save({ ...ctxMessage, from: numberOrId }))
])
}
sendFlow = async (messageToSend, numberOrId) => {
const queue = []
for (const ctxMessage of messageToSend) {
const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs)
Queue.enqueue(() =>
this.sendProviderAndSave(numberOrId, ctxMessage)
)
}
return Promise.all(queue)
} }
/** /**
* @deprecated
* @private * @private
* @param {*} message * @param {*} message
* @param {*} ref * @param {*} ref
@@ -201,5 +262,22 @@ class CoreClass {
this.continue(null, responde.ref) this.continue(null, responde.ref)
} }
} }
/**
* Funcion dedicada a enviar el mensaje sin pasar por el flow
* (dialogflow)
* @param {*} messageToSend
* @param {*} numberOrId
* @returns
*/
sendFlowSimple = async (messageToSend, numberOrId) => {
const queue = []
for (const ctxMessage of messageToSend) {
const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs)
QueuePrincipal.enqueue(() => this.sendProviderAndSave(numberOrId, ctxMessage))
}
return Promise.all(queue)
}
} }
module.exports = CoreClass module.exports = CoreClass

View File

@@ -8,8 +8,7 @@ const { addKeyword, addAnswer, addChild, toSerialize } = require('./io/methods')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBot = async ({ flow, database, provider }) => const createBot = async ({ flow, database, provider }, args = {}) => new CoreClass(flow, database, provider, args)
new CoreClass(flow, database, provider)
/** /**
* Crear instancia de clase Io (Flow) * Crear instancia de clase Io (Flow)
@@ -29,8 +28,7 @@ const createFlow = (args) => {
*/ */
const createProvider = (providerClass = class {}, args = null) => { const createProvider = (providerClass = class {}, args = null) => {
const providerInstance = new providerClass(args) const providerInstance = new providerClass(args)
if (!providerClass.prototype instanceof ProviderClass) if (!providerClass.prototype instanceof ProviderClass) throw new Error('El provider no implementa ProviderClass')
throw new Error('El provider no implementa ProviderClass')
return providerInstance return providerInstance
} }

View File

@@ -25,9 +25,17 @@ class FlowClass {
let refSymbol = null let refSymbol = null
overFlow = overFlow ?? this.flowSerialize overFlow = overFlow ?? this.flowSerialize
const customRegex = (str = null) => {
if (typeof str !== 'string') return
const instanceRegex = new RegExp(str)
return instanceRegex.test(str)
}
/** Retornar expresion regular para buscar coincidencia */ /** Retornar expresion regular para buscar coincidencia */
const mapSensitive = (str, flag = false) => { const mapSensitive = (str, mapOptions = { sensitive: false, regex: false }) => {
const regexSensitive = flag ? 'g' : 'i' if (mapOptions.regex) return customRegex(str)
const regexSensitive = mapOptions.sensitive ? 'g' : 'i'
if (Array.isArray(str)) { if (Array.isArray(str)) {
return new RegExp(str.join('|'), regexSensitive) return new RegExp(str.join('|'), regexSensitive)
} }
@@ -36,6 +44,7 @@ class FlowClass {
const findIn = (keyOrWord, symbol = false, flow = overFlow) => { const findIn = (keyOrWord, symbol = false, flow = overFlow) => {
const sensitive = refSymbol?.options?.sensitive || false const sensitive = refSymbol?.options?.sensitive || false
const regex = refSymbol?.options?.regex || false
capture = refSymbol?.options?.capture || false capture = refSymbol?.options?.capture || false
if (capture) return messages if (capture) return messages
@@ -46,7 +55,7 @@ class FlowClass {
if (refSymbol?.ref) findIn(refSymbol.ref, true) if (refSymbol?.ref) findIn(refSymbol.ref, true)
} else { } else {
refSymbol = flow.find((c) => { refSymbol = flow.find((c) => {
return mapSensitive(c.keyword, sensitive).test(keyOrWord) return mapSensitive(c.keyword, { sensitive, regex }).test(keyOrWord)
}) })
if (refSymbol?.ref) findIn(refSymbol.ref, true) if (refSymbol?.ref) findIn(refSymbol.ref, true)
return messages return messages
@@ -56,8 +65,7 @@ class FlowClass {
return messages return messages
} }
findBySerialize = (refSerialize) => findBySerialize = (refSerialize) => this.flowSerialize.find((r) => r.refSerialize === refSerialize)
this.flowSerialize.find((r) => r.refSerialize === refSerialize)
findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref) findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref)
} }

View File

@@ -17,15 +17,10 @@ const addAnswer =
* @returns * @returns
*/ */
const getAnswerOptions = () => ({ const getAnswerOptions = () => ({
media: media: typeof options?.media === 'string' ? `${options?.media}` : null,
typeof options?.media === 'string' ? `${options?.media}` : null,
buttons: Array.isArray(options?.buttons) ? options.buttons : [], buttons: Array.isArray(options?.buttons) ? options.buttons : [],
capture: capture: typeof options?.capture === 'boolean' ? options?.capture : false,
typeof options?.capture === 'boolean' child: typeof options?.child === 'string' ? `${options?.child}` : null,
? options?.capture
: false,
child:
typeof options?.child === 'string' ? `${options?.child}` : null,
delay: typeof options?.delay === 'number' ? options?.delay : 0, delay: typeof options?.delay === 'number' ? options?.delay : 0,
}) })
@@ -49,8 +44,7 @@ const addAnswer =
* Esta funcion aplana y busca los callback anidados de los hijos * Esta funcion aplana y busca los callback anidados de los hijos
* @returns * @returns
*/ */
const getCbFromNested = () => const getCbFromNested = () => flatObject(Array.isArray(nested) ? nested : [nested])
flatObject(Array.isArray(nested) ? nested : [nested])
const callback = typeof cb === 'function' ? cb : () => null const callback = typeof cb === 'function' ? cb : () => null

View File

@@ -8,12 +8,14 @@ const { toJson } = require('./toJson')
* @param {*} options {sensitive:boolean} default false * @param {*} options {sensitive:boolean} default false
*/ */
const addKeyword = (keyword, options) => { const addKeyword = (keyword, options) => {
if (typeof keyword !== 'string' && !Array.isArray(keyword)) {
throw new Error('DEBE_SER_STRING_ARRAY_REGEX')
}
const parseOptions = () => { const parseOptions = () => {
const defaultProperties = { const defaultProperties = {
sensitive: sensitive: typeof options?.sensitive === 'boolean' ? options?.sensitive : false,
typeof options?.sensitive === 'boolean' regex: typeof options?.regex === 'boolean' ? options?.regex : false,
? options?.sensitive
: false,
} }
return defaultProperties return defaultProperties

View File

@@ -5,12 +5,12 @@ const { generateRef, generateRefSerialize } = require('../../utils/hash')
* @param options {media:string, buttons:[], capture:true default false} * @param options {media:string, buttons:[], capture:true default false}
* @returns * @returns
*/ */
const toCtx = ({ body, from, prevRef, index }) => { const toCtx = ({ body, from, prevRef, options = {}, index }) => {
return { return {
ref: generateRef(), ref: generateRef(),
keyword: prevRef, keyword: prevRef,
answer: body, answer: body,
options: {}, options: options ?? {},
from, from,
refSerialize: generateRefSerialize({ index, answer: body }), refSerialize: generateRefSerialize({ index, answer: body }),
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/bot", "name": "@bot-whatsapp/bot",
"version": "0.0.29-alpha.0", "version": "0.0.96-alpha.0",
"description": "", "description": "",
"main": "./lib/bundle.bot.cjs", "main": "./lib/bundle.bot.cjs",
"scripts": { "scripts": {
@@ -28,5 +28,9 @@
}, },
"dependencies": { "dependencies": {
"dotenv": "^16.0.3" "dotenv": "^16.0.3"
},
"repository": {
"type": "git",
"url": "https://github.com/codigoencasa/bot-whatsapp/tree/main/packages/bot"
} }
} }

View File

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

View File

@@ -10,6 +10,7 @@ module.exports = [
banner: banner['banner.output'].join(''), banner: banner['banner.output'].join(''),
file: join(__dirname, 'lib', 'bundle.bot.cjs'), file: join(__dirname, 'lib', 'bundle.bot.cjs'),
format: 'cjs', format: 'cjs',
sourcemap: true,
}, },
plugins: [commonjs(), nodeResolve()], plugins: [commonjs(), nodeResolve()],
}, },

View File

@@ -2,13 +2,7 @@ const { test } = require('uvu')
const assert = require('uvu/assert') const assert = require('uvu/assert')
const FlowClass = require('../io/flow.class') const FlowClass = require('../io/flow.class')
const MockProvider = require('../../../__mocks__/mock.provider') const MockProvider = require('../../../__mocks__/mock.provider')
const { const { createBot, CoreClass, createFlow, createProvider, ProviderClass } = require('../index')
createBot,
CoreClass,
createFlow,
createProvider,
ProviderClass,
} = require('../index')
class MockFlow { class MockFlow {
allCallbacks = { ref: () => 1 } allCallbacks = { ref: () => 1 }
@@ -100,20 +94,13 @@ test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
await createBot(setting) await createBot(setting)
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on( mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r))
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage( mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action)
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -121,21 +108,12 @@ test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is( assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action))
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is( assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure))
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is( assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message))
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test(`[Bot] Probando Flujos Internos`, async () => { test(`[Bot] Probando Flujos Internos`, async () => {
@@ -166,20 +144,13 @@ test(`[Bot] Probando Flujos Internos`, async () => {
await createBot(setting) await createBot(setting)
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on( mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r))
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage( mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action)
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -187,21 +158,12 @@ test(`[Bot] Probando Flujos Internos`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is( assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action))
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is( assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure))
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is( assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message))
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test(`[Bot] Probando Flujos Nested`, async () => { test(`[Bot] Probando Flujos Nested`, async () => {
@@ -234,20 +196,13 @@ test(`[Bot] Probando Flujos Nested`, async () => {
botInstance.sendProviderAndSave('xxxxx', 'xxxxx') botInstance.sendProviderAndSave('xxxxx', 'xxxxx')
botInstance.continue('xxxxx', 'xxxxx') botInstance.continue('xxxxx', 'xxxxx')
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on( mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r))
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage( mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action)
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -255,21 +210,12 @@ test(`[Bot] Probando Flujos Nested`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is( assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action))
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is( assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure))
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is( assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message))
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test.run() test.run()

View File

@@ -0,0 +1,28 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const FlowClass = require('../io/flow.class')
const { addKeyword } = require('../index')
test(`[FlowClass] Probando instanciamiento de clase`, async () => {
const MOCK_FLOW = addKeyword('hola').addAnswer('Buenas!')
const flowClass = new FlowClass([MOCK_FLOW])
assert.is(flowClass instanceof FlowClass, true)
})
test(`[FlowClass] Probando find`, async () => {
const MOCK_FLOW = addKeyword('hola').addAnswer('Buenas!')
const flowClass = new FlowClass([MOCK_FLOW])
flowClass.find('hola')
assert.is(flowClass instanceof FlowClass, true)
})
test(`[FlowClass] Probando findBySerialize`, async () => {
const MOCK_FLOW = addKeyword('hola').addAnswer('Buenas!')
const flowClass = new FlowClass([MOCK_FLOW])
flowClass.findBySerialize('')
assert.is(flowClass instanceof FlowClass, true)
})
test.run()

View File

@@ -35,10 +35,7 @@ test('Debere probar toSerialize', () => {
const ARRANGE = { const ARRANGE = {
keyword: ['hola!', 'ole'], keyword: ['hola!', 'ole'],
} }
const MAIN_CTX = addKeyword(ARRANGE.keyword) const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer('Segundo!').addAnswer('Segundo!').toJson()
.addAnswer('Segundo!')
.addAnswer('Segundo!')
.toJson()
const [ANSWER_A] = MAIN_CTX const [ANSWER_A] = MAIN_CTX
@@ -71,9 +68,7 @@ test('Debere probar la anidación', () => {
answer_A: 'Bienvenido', answer_A: 'Bienvenido',
answer_B: 'Continuar', answer_B: 'Continuar',
} }
const MAIN_CTX = addKeyword(ARRANGE.keyword) const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer(ARRANGE.answer_A).addAnswer(ARRANGE.answer_B)
.addAnswer(ARRANGE.answer_A)
.addAnswer(ARRANGE.answer_B)
assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B) assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B)
}) })
@@ -107,10 +102,7 @@ test('Debere probar error las addAnswer', () => {
}) })
test('Obtener toJson', () => { test('Obtener toJson', () => {
const [ctxA, ctxB, ctxC] = addKeyword('hola') const [ctxA, ctxB, ctxC] = addKeyword('hola').addAnswer('pera!').addAnswer('chao').toJson()
.addAnswer('pera!')
.addAnswer('chao')
.toJson()
assert.is(ctxA.keyword, 'hola') assert.is(ctxA.keyword, 'hola')
assert.match(ctxA.ref, /^key_/) assert.match(ctxA.ref, /^key_/)

View File

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

View File

@@ -3,9 +3,7 @@ const flatObject = (listArray = []) => {
if (!listArray.length) return {} if (!listArray.length) return {}
const cbNestedObj = cbNestedList const cbNestedObj = cbNestedList.map(({ ctx }) => ctx?.callbacks).filter((i) => !!i)
.map(({ ctx }) => ctx?.callbacks)
.filter((i) => !!i)
const queueCb = cbNestedObj.reduce((acc, current) => { const queueCb = cbNestedObj.reduce((acc, current) => {
const getKeys = Object.keys(current) const getKeys = Object.keys(current)
const parse = getKeys.map((icb, i) => ({ const parse = getKeys.map((icb, i) => ({

View File

@@ -16,9 +16,6 @@ const generateRef = (prefix = false) => {
* @returns * @returns
*/ */
const generateRefSerialize = ({ index, answer, keyword }) => const generateRefSerialize = ({ index, answer, keyword }) =>
crypto crypto.createHash('md5').update(JSON.stringify({ index, answer, keyword })).digest('hex')
.createHash('md5')
.update(JSON.stringify({ index, answer, keyword }))
.digest('hex')
module.exports = { generateRef, generateRefSerialize } module.exports = { generateRef, generateRefSerialize }

View File

@@ -4,9 +4,7 @@ const printer = (message, title) => {
if (NODE_ENV !== 'test') { if (NODE_ENV !== 'test') {
// console.clear() // console.clear()
if (title) console.log(bgRed(`${title}`)) if (title) console.log(bgRed(`${title}`))
console.log( console.log(yellow(Array.isArray(message) ? message.join('\n') : message))
yellow(Array.isArray(message) ? message.join('\n') : message)
)
console.log(``) console.log(``)
} }
} }

View File

@@ -1,8 +1,8 @@
class Queue { class Queue {
static queue = [] queue = []
static pendingPromise = false pendingPromise = false
static enqueue(promise) { enqueue(promise) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.queue.push({ this.queue.push({
promise, promise,
@@ -13,7 +13,7 @@ class Queue {
}) })
} }
static dequeue() { dequeue() {
if (this.workingOnPromise) { if (this.workingOnPromise) {
return false return false
} }

View File

@@ -1,22 +1,24 @@
const { red, yellow, green, bgCyan } = require('kleur') const { red, yellow, green, bgCyan } = require('kleur')
const { exec } = require('node:child_process')
const checkNodeVersion = () => { const checkNodeVersion = () => {
return new Promise((resolve, reject) => {
console.log(bgCyan('🚀 Revisando tu Node.js')) console.log(bgCyan('🚀 Revisando tu Node.js'))
const version = process.version const version = process.version
const majorVersion = parseInt(version.replace('v', '').split('.').shift()) const majorVersion = parseInt(version.replace('v', '').split('.').shift())
if (majorVersion < 16) { if (majorVersion < 16) {
console.error( console.error(red(`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`))
red(
`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`
)
)
process.exit(1)
}
console.log(green(`Node.js compatible ${version}`))
console.log(``) console.log(``)
reject('ERROR_NODE')
}
console.log(green(`Node.js: ${version} compatible ✅`))
console.log(``)
resolve()
})
} }
const checkOs = () => { const checkOs = () => {
return new Promise((resolve) => {
console.log(bgCyan('🙂 Revisando tu sistema operativo')) console.log(bgCyan('🙂 Revisando tu sistema operativo'))
const os = process.platform const os = process.platform
if (!os.includes('win32')) { if (!os.includes('win32')) {
@@ -31,8 +33,27 @@ const checkOs = () => {
console.log(yellow(messages.join(' \n'))) console.log(yellow(messages.join(' \n')))
} }
console.log(green(`OS: compatible ✅`))
console.log(``) console.log(``)
resolve()
})
} }
module.exports = { checkNodeVersion, checkOs } const checkGit = () => {
return new Promise((resolve, reject) => {
console.log(bgCyan('🤓 Revisando GIT'))
exec('git --version', (error) => {
if (error) {
console.error(red(`🔴 Se require instalar GIT`))
console.log(``)
reject('ERROR_GIT')
} else {
console.log(green(`Git: Compatible ✅`))
console.log(``)
resolve()
}
})
})
}
module.exports = { checkNodeVersion, checkOs, checkGit }

View File

@@ -2,10 +2,7 @@ const rimraf = require('rimraf')
const { yellow } = require('kleur') const { yellow } = require('kleur')
const { join } = require('path') const { join } = require('path')
const PATH_WW = [ const PATH_WW = [join(process.cwd(), '.wwebjs_auth'), join(process.cwd(), 'session.json')]
join(process.cwd(), '.wwebjs_auth'),
join(process.cwd(), 'session.json'),
]
const cleanSession = () => { const cleanSession = () => {
const queue = [] const queue = []

View File

@@ -23,11 +23,7 @@ const JSON_TEMPLATE = {
const PATH_CONFIG = join(process.cwd(), 'config.json') const PATH_CONFIG = join(process.cwd(), 'config.json')
const jsonConfig = () => { const jsonConfig = () => {
return writeFile( return writeFile(PATH_CONFIG, JSON.stringify(JSON_TEMPLATE, null, 2), 'utf-8')
PATH_CONFIG,
JSON.stringify(JSON_TEMPLATE, null, 2),
'utf-8'
)
} }
module.exports = { jsonConfig } module.exports = { jsonConfig }

View File

@@ -20,13 +20,9 @@ const installDeps = (pkgManager, packageList) => {
const installSingle = (pkgInstall) => () => { const installSingle = (pkgInstall) => () => {
new Promise((resolve) => { new Promise((resolve) => {
try { try {
childProcess = spawn( childProcess = spawn(pkgManager, [PKG_OPTION[pkgManager], pkgInstall], {
pkgManager,
[PKG_OPTION[pkgManager], pkgInstall],
{
stdio: 'inherit', stdio: 'inherit',
} })
)
childProcess.on('error', (e) => { childProcess.on('error', (e) => {
console.error(e) console.error(e)

View File

@@ -1,9 +1,9 @@
const prompts = require('prompts') const prompts = require('prompts')
const { join } = require('path') const { join } = require('path')
const { yellow, red, cyan, bgMagenta } = require('kleur') const { yellow, red, cyan, bgMagenta, bgRed } = require('kleur')
const { existsSync } = require('fs') const { existsSync } = require('fs')
const { copyBaseApp } = require('../create-app') const { copyBaseApp } = require('../create-app')
const { checkNodeVersion, checkOs } = require('../check') const { checkNodeVersion, checkOs, checkGit } = require('../check')
const bannerDone = () => { const bannerDone = () => {
console.log(``) console.log(``)
@@ -21,6 +21,20 @@ const bannerDone = () => {
} }
const startInteractive = async () => { const startInteractive = async () => {
try {
console.clear()
await checkNodeVersion()
checkOs()
await checkGit()
console.clear()
await nextSteps()
} catch (e) {
console.error(bgRed(`Ups! 🙄 algo no va bien.`))
console.error(bgRed(`Revisa los requerimientos minimos en la documentacion`))
}
}
const nextSteps = async () => {
const questions = [ const questions = [
{ {
type: 'text', type: 'text',
@@ -32,11 +46,11 @@ const startInteractive = async () => {
name: 'providerWs', name: 'providerWs',
message: '¿Cuál proveedor de whatsapp quieres utilizar?', message: '¿Cuál proveedor de whatsapp quieres utilizar?',
choices: [ choices: [
{ title: 'whatsapp-web.js (gratis)', value: 'wweb' },
{ title: 'Venom (gratis)', value: 'venom' },
{ title: 'Baileys (gratis)', value: 'baileys' }, { title: 'Baileys (gratis)', value: 'baileys' },
{ title: 'Venom (gratis)', value: 'venom' },
{ title: 'whatsapp-web.js (gratis)', value: 'wweb' },
{ title: 'Twilio', value: 'twilio' }, { title: 'Twilio', value: 'twilio' },
{ title: 'API Oficial (Meta)', value: 'meta' }, { title: 'Meta', value: 'meta' },
], ],
max: 1, max: 1,
hint: 'Espacio para seleccionar', hint: 'Espacio para seleccionar',
@@ -58,9 +72,6 @@ const startInteractive = async () => {
}, },
] ]
console.clear()
checkNodeVersion()
checkOs()
const onCancel = () => { const onCancel = () => {
console.log('¡Proceso cancelado!') console.log('¡Proceso cancelado!')
return true return true
@@ -69,8 +80,7 @@ const startInteractive = async () => {
const { outDir = '', providerDb = [], providerWs = [] } = response const { outDir = '', providerDb = [], providerWs = [] } = response
const createApp = async (templateName = null) => { const createApp = async (templateName = null) => {
if (!templateName) if (!templateName) throw new Error('TEMPLATE_NAME_INVALID: ', templateName)
throw new Error('TEMPLATE_NAME_INVALID: ', templateName)
const possiblesPath = [ const possiblesPath = [
join(__dirname, '..', '..', 'starters', 'apps', templateName), join(__dirname, '..', '..', 'starters', 'apps', templateName),
@@ -102,11 +112,7 @@ const startInteractive = async () => {
const vendorProvider = async () => { const vendorProvider = async () => {
const [answer] = providerWs const [answer] = providerWs
if (!providerWs.length) { if (!providerWs.length) {
console.log( console.log(red(`Debes seleccionar un proveedor de whatsapp. Tecla [Space] para seleccionar`))
red(
`Debes seleccionar un proveedor de whatsapp. Tecla [Space] para seleccionar`
)
)
process.exit(1) process.exit(1)
} }
return answer return answer
@@ -119,11 +125,7 @@ const startInteractive = async () => {
const dbProvider = async () => { const dbProvider = async () => {
const [answer] = providerDb const [answer] = providerDb
if (!providerDb.length) { if (!providerDb.length) {
console.log( console.log(red(`Debes seleccionar un proveedor de base de datos. Tecla [Space] para seleccionar`))
red(
`Debes seleccionar un proveedor de base de datos. Tecla [Space] para seleccionar`
)
)
process.exit(1) process.exit(1)
} }
return answer return answer

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/cli", "name": "@bot-whatsapp/cli",
"version": "0.0.36-alpha.0", "version": "0.0.72-alpha.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"devDependencies": { "devDependencies": {
@@ -15,5 +15,9 @@
], ],
"bin": { "bin": {
"bot": "./bin/cli.js" "bot": "./bin/cli.js"
},
"repository": {
"type": "git",
"url": "https://github.com/codigoencasa/bot-whatsapp/tree/main/packages/cli"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/contexts", "name": "@bot-whatsapp/contexts",
"version": "0.0.1", "version": "0.0.16-alpha.0",
"description": "", "description": "",
"main": "./lib/bundle.contexts.cjs", "main": "./lib/bundle.contexts.cjs",
"files": [ "files": [
@@ -13,5 +13,9 @@
}, },
"dependencies": { "dependencies": {
"@bot-whatsapp/bot": "*" "@bot-whatsapp/bot": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/codigoencasa/bot-whatsapp/tree/main/packages/contexts"
} }
} }

View File

@@ -38,10 +38,8 @@ class DialogFlowCXContext extends CoreClass {
* */ * */
} }
if (!this.optionsDX.location.length) if (!this.optionsDX.location.length) throw new Error('LOCATION_NO_ENCONTRADO')
throw new Error('LOCATION_NO_ENCONTRADO') if (!this.optionsDX.agentId.length) throw new Error('AGENTID_NO_ENCONTRADO')
if (!this.optionsDX.agentId.length)
throw new Error('AGENTID_NO_ENCONTRADO')
const rawJson = readFileSync(GOOGLE_ACCOUNT_PATH, 'utf-8') const rawJson = readFileSync(GOOGLE_ACCOUNT_PATH, 'utf-8')
const { project_id, private_key, client_email } = JSON.parse(rawJson) const { project_id, private_key, client_email } = JSON.parse(rawJson)
@@ -86,9 +84,7 @@ class DialogFlowCXContext extends CoreClass {
}, },
} }
const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [ const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [null]
null,
]
const listMessages = single.queryResult.responseMessages.map((res) => { const listMessages = single.queryResult.responseMessages.map((res) => {
if (res.message == 'text') { if (res.message == 'text') {
@@ -96,17 +92,11 @@ class DialogFlowCXContext extends CoreClass {
} }
if (res.message == 'payload') { if (res.message == 'payload') {
const { const { media = null, buttons = [], answer = '' } = res.payload.fields
media = null, const buttonsArray = buttons?.listValue?.values?.map((btnValue) => {
buttons = [],
answer = '',
} = res.payload.fields
const buttonsArray = buttons?.listValue?.values?.map(
(btnValue) => {
const { stringValue } = btnValue.structValue.fields.body const { stringValue } = btnValue.structValue.fields.body
return { body: stringValue } return { body: stringValue }
} })
)
return { return {
answer: answer?.stringValue, answer: answer?.stringValue,
options: { options: {
@@ -117,7 +107,7 @@ class DialogFlowCXContext extends CoreClass {
} }
}) })
this.sendFlow(listMessages, from) this.sendFlowSimple(listMessages, from)
} }
} }

View File

@@ -5,8 +5,7 @@ const DialogCXFlowClass = require('./dialogflow-cx.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotDialog = async ({ database, provider }, _options) => const createBotDialog = async ({ database, provider }, _options) => new DialogCXFlowClass(database, provider, _options)
new DialogCXFlowClass(database, provider, _options)
module.exports = { module.exports = {
createBotDialog, createBotDialog,

View File

@@ -65,10 +65,7 @@ class DialogFlowContext extends CoreClass {
* para evitar este problema. * para evitar este problema.
* https://github.com/codigoencasa/bot-whatsapp/pull/140 * https://github.com/codigoencasa/bot-whatsapp/pull/140
*/ */
const session = this.sessionClient.projectAgentSessionPath( const session = this.sessionClient.projectAgentSessionPath(this.projectId, from)
this.projectId,
from
)
const reqDialog = { const reqDialog = {
session, session,
queryInput: { queryInput: {
@@ -79,15 +76,11 @@ class DialogFlowContext extends CoreClass {
}, },
} }
const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [ const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [null]
null,
]
const { queryResult } = single const { queryResult } = single
const msgPayload = queryResult?.fulfillmentMessages?.find( const msgPayload = queryResult?.fulfillmentMessages?.find((a) => a.message === 'payload')
(a) => a.message === 'payload'
)
// Revisamos si el dialogFlow tiene multimedia // Revisamos si el dialogFlow tiene multimedia
if (msgPayload && msgPayload?.payload) { if (msgPayload && msgPayload?.payload) {
@@ -97,17 +90,25 @@ class DialogFlowContext extends CoreClass {
}) })
customPayload = { customPayload = {
options: {
media: fields?.media?.stringValue, media: fields?.media?.stringValue,
buttons: mapButtons, buttons: mapButtons,
} },
} }
const ctxFromDX = { const ctxFromDX = {
...customPayload, ...customPayload,
answer: fields?.answer?.stringValue,
}
this.sendFlowSimple([ctxFromDX], from)
return
}
const ctxFromDX = {
answer: queryResult?.fulfillmentText, answer: queryResult?.fulfillmentText,
} }
this.sendFlow([ctxFromDX], from) this.sendFlowSimple([ctxFromDX], from)
} }
} }

View File

@@ -5,8 +5,7 @@ const DialogFlowClass = require('./dialogflow.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotDialog = async ({ database, provider }) => const createBotDialog = async ({ database, provider }) => new DialogFlowClass(database, provider)
new DialogFlowClass(database, provider)
module.exports = { module.exports = {
createBotDialog, createBotDialog,

View File

@@ -5,8 +5,7 @@ const MockClass = require('./mock.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotMock = async ({ database, provider }) => const createBotMock = async ({ database, provider }) => new MockClass(database, provider)
new MockClass(database, provider)
module.exports = { module.exports = {
createBotMock, createBotMock,

View File

@@ -1,6 +1,6 @@
{ {
"name": "create-bot-whatsapp", "name": "create-bot-whatsapp",
"version": "0.0.47-alpha.0", "version": "0.0.93-alpha.0",
"description": "", "description": "",
"main": "./lib/bundle.create-bot-whatsapp.cjs", "main": "./lib/bundle.create-bot-whatsapp.cjs",
"files": [ "files": [
@@ -11,5 +11,9 @@
"bin": "./bin/create.js", "bin": "./bin/create.js",
"dependencies": { "dependencies": {
"@bot-whatsapp/cli": "*" "@bot-whatsapp/cli": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/codigoencasa/bot-whatsapp/tree/main/packages/create-bot-whatsapp"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/database", "name": "@bot-whatsapp/database",
"version": "0.0.28-alpha.0", "version": "0.0.64-alpha.0",
"description": "Esto es el conector a mysql, pg, mongo", "description": "Esto es el conector a mysql, pg, mongo",
"main": "./lib/mock/index.cjs", "main": "./lib/mock/index.cjs",
"keywords": [], "keywords": [],
@@ -19,5 +19,9 @@
"./mongo": "./lib/mongo/index.cjs", "./mongo": "./lib/mongo/index.cjs",
"./json": "./lib/json/index.cjs", "./json": "./lib/json/index.cjs",
"./mysql": "./lib/mysql/index.cjs" "./mysql": "./lib/mysql/index.cjs"
},
"repository": {
"type": "git",
"url": "https://github.com/codigoencasa/bot-whatsapp/tree/main/packages/database"
} }
} }

View File

@@ -10,7 +10,10 @@ class MockDatabase {
constructor() {} constructor() {}
getPrevByNumber = (from) => { getPrevByNumber = (from) => {
const history = this.listHistory.slice().reverse() const history = this.listHistory
.slice()
.reverse()
.filter((i) => !!i.keyword)
return history.find((a) => a.from === from) return history.find((a) => a.from === from)
} }

View File

@@ -24,12 +24,7 @@ class MongoAdapter {
} }
getPrevByNumber = async (from) => { getPrevByNumber = async (from) => {
const result = await this.db const result = await this.db.collection('history').find({ from }).sort({ _id: -1 }).limit(1).toArray()
.collection('history')
.find({ from })
.sort({ _id: -1 })
.limit(1)
.toArray()
return result[0] return result[0]
} }

View File

@@ -46,18 +46,8 @@ class MyslAdapter {
}) })
save = (ctx) => { save = (ctx) => {
const values = [ 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 ?'
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) => { this.db.query(sql, [values], (err) => {
if (err) throw err if (err) throw err
@@ -77,8 +67,8 @@ class MyslAdapter {
answer longtext NOT NULL, answer longtext NOT NULL,
refSerialize varchar(255) NOT NULL, refSerialize varchar(255) NOT NULL,
phone varchar(255) NOT NULL, phone varchar(255) NOT NULL,
options longtext NOT NULL options longtext NOT NULL)
)` CHARACTER SET utf8mb4 COLLATE utf8mb4_General_ci`
this.db.query(sql, (err) => { this.db.query(sql, (err) => {
if (err) throw err if (err) throw err

View File

@@ -8,6 +8,5 @@
font-family: IBMPlexMono-Regular; font-family: IBMPlexMono-Regular;
src: url(IBMPlexMono-Regular-subset.woff2) format('woff2'), src: url(IBMPlexMono-Regular-subset.woff2) format('woff2'),
url(IBMPlexMono-Regular-subset.zopfli.woff) format('woff'); url(IBMPlexMono-Regular-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+2C, U+2E, U+41-43, U+46, U+49, U+4B-4F, U+53-55, U+58, unicode-range: U+20, U+2C, U+2E, U+41-43, U+46, U+49, U+4B-4F, U+53-55, U+58, U+61-65, U+67-69, U+6C-76;
U+61-65, U+67-69, U+6C-76;
} }

View File

@@ -8,6 +8,6 @@
font-family: IBMPlexMono-SemiBold; font-family: IBMPlexMono-SemiBold;
src: url(IBMPlexMono-SemiBold-subset.woff2) format('woff2'), src: url(IBMPlexMono-SemiBold-subset.woff2) format('woff2'),
url(IBMPlexMono-SemiBold-subset.zopfli.woff) format('woff'); url(IBMPlexMono-SemiBold-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+24, U+2E, U+30, U+38, U+39, U+41, U+42, U+44, U+47, unicode-range: U+20, U+24, U+2E, U+30, U+38, U+39, U+41, U+42, U+44, U+47, U+4E, U+4F, U+52-55, U+57, U+59, U+65,
U+4E, U+4F, U+52-55, U+57, U+59, U+65, U+68, U+6F, U+72, U+74; U+68, U+6F, U+72, U+74;
} }

View File

@@ -6,9 +6,8 @@
@font-face { @font-face {
font-family: Pally-Variable; font-family: Pally-Variable;
src: url(Pally-Variable-subset.woff2) format('woff2'), src: url(Pally-Variable-subset.woff2) format('woff2'), url(Pally-Variable-subset.zopfli.woff) format('woff');
url(Pally-Variable-subset.zopfli.woff) format('woff'); unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+39, U+41-43, U+46, U+49-4D, U+53, U+55, U+58, U+61-65, U+67-69,
unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+39, U+41-43, U+46, U+6B-77, U+79;
U+49-4D, U+53, U+55, U+58, U+61-65, U+67-69, U+6B-77, U+79;
font-weight: 400 700; font-weight: 400 700;
} }

View File

@@ -8,6 +8,5 @@
font-family: SourceSerifPro-Regular; font-family: SourceSerifPro-Regular;
src: url(SourceSerifPro-Regular-subset.woff2) format('woff2'), src: url(SourceSerifPro-Regular-subset.woff2) format('woff2'),
url(SourceSerifPro-Regular-subset.zopfli.woff) format('woff'); url(SourceSerifPro-Regular-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+2C, U+2E, U+41-44, U+49, U+4A, U+4C, U+53, U+55, unicode-range: U+20, U+2C, U+2E, U+41-44, U+49, U+4A, U+4C, U+53, U+55, U+61-65, U+67-69, U+6B-76, U+79;
U+61-65, U+67-69, U+6B-76, U+79;
} }

View File

@@ -6,9 +6,8 @@
@font-face { @font-face {
font-family: Synonym-Variable; font-family: Synonym-Variable;
src: url(Synonym-Variable-subset.woff2) format('woff2'), src: url(Synonym-Variable-subset.woff2) format('woff2'), url(Synonym-Variable-subset.zopfli.woff) format('woff');
url(Synonym-Variable-subset.zopfli.woff) format('woff'); unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+35, U+41-44, U+46, U+47, U+49, U+4B-4F, U+53-55, U+57-59, U+61,
unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+35, U+41-44, U+46, U+47, U+63-65, U+67-69, U+6C-76;
U+49, U+4B-4F, U+53-55, U+57-59, U+61, U+63-65, U+67-69, U+6C-76;
font-weight: 400 700; font-weight: 400 700;
} }

View File

@@ -6,8 +6,7 @@
@font-face { @font-face {
font-family: TenorSans-Regular; font-family: TenorSans-Regular;
src: url(TenorSans-Regular-subset.woff2) format('woff2'), src: url(TenorSans-Regular-subset.woff2) format('woff2'), url(TenorSans-Regular-subset.zopfli.woff) format('woff');
url(TenorSans-Regular-subset.zopfli.woff) format('woff'); unicode-range: U+20, U+24, U+2E, U+30, U+36, U+46, U+49, U+4A, U+53, U+54, U+61, U+63, U+65, U+69, U+6B, U+6E, U+6F,
unicode-range: U+20, U+24, U+2E, U+30, U+36, U+46, U+49, U+4A, U+53, U+54, U+72-75, U+79;
U+61, U+63, U+65, U+69, U+6B, U+6E, U+6F, U+72-75, U+79;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,189 @@
export const DigitalOcean = () => (
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 604 129"
style="enable-background:new 0 0 604 129;"
xml:space="preserve"
>
<g>
<g>
<g>
<path
class="st0"
d="M174.3,3c4.9,0,8.7,2.9,8.7,8.6c0,5.6-3.8,8.5-8.7,8.5h-7.6v11.1h-3.5V3H174.3z M166.7,17.1h7.2
c3,0,5.6-1.8,5.6-5.5c0-3.8-2.5-5.5-5.6-5.5h-7.2V17.1z"
/>
<path
class="st0"
d="M208.8,21.7c0,6.1-4.3,10-9.9,10c-5.6,0-9.9-3.9-9.9-10c0-6.1,4.3-10,9.9-10
C204.5,11.7,208.8,15.6,208.8,21.7z M192.3,21.7c0,4.5,2.9,7.2,6.6,7.2c3.7,0,6.6-2.7,6.6-7.2c0-4.5-2.9-7.1-6.6-7.1
C195.2,14.5,192.3,17.2,192.3,21.7z"
/>
<path
class="st0"
d="M234.4,31.3l-5.2-13.8L224,31.3h-2.6L214.1,12h3.6l5.2,14l5.2-14h2.3l5.3,14l5.2-14h3.5L237,31.3H234.4z"
/>
<path
class="st0"
d="M253,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H253z M253,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C255.6,14.5,253.2,16.5,253,20.3z"
/>
<path
class="st0"
d="M285.4,14.9c-3.4,0-5.6,2.3-5.6,5.3v11.1h-3.2V12h3.2v2.9c0.7-1.6,2.5-3.1,5.7-3.1V14.9z"
/>
<path
class="st0"
d="M294.7,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H294.7z M294.7,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C297.4,14.5,294.9,16.5,294.7,20.3z"
/>
<path
class="st0"
d="M333.1,31.3v-3.1c-1.1,2-3.6,3.5-6.8,3.5c-5.3,0-9.3-3.8-9.3-10c0-6.2,4-10,9.3-10c3.2,0,5.6,1.4,6.6,3.2V2
h3.2v29.4H333.1z M320.3,21.7c0,4.6,2.8,7.2,6.5,7.2c3.6,0,6.2-2.2,6.2-6.6v-1.1c0-4.3-2.6-6.6-6.2-6.6
C323.1,14.5,320.3,17.1,320.3,21.7z"
/>
<path
class="st0"
d="M361.8,14.9c1.1-1.9,3.4-3.2,6.7-3.2c5.3,0,9.3,3.8,9.3,10c0,6.2-4,10-9.3,10c-3.3,0-5.7-1.5-6.8-3.5v3.1
h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1
C364.5,14.5,361.9,16.8,361.9,21.1z"
/>
<path class="st0" d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z" />
</g>
</g>
<g id="XMLID_2369_">
<g>
<g id="XMLID_281_">
<g id="XMLID_282_">
<g>
<g id="XMLID_283_">
<g id="XMLID_287_">
<path
id="XMLID_288_"
class="st0"
d="M64.4,127l0-24.2c25.6,0,45.5-25.4,35.7-52.3c-3.6-10-11.6-17.9-21.6-21.6
c-27-9.8-52.3,10-52.3,35.7c0,0,0,0,0,0L2,64.7C2,23.8,41.5-8,84.3,5.4c18.7,5.8,33.6,20.7,39.4,39.4
C137,87.6,105.2,127,64.4,127z"
/>
</g>
<polygon
id="XMLID_286_"
class="st1"
points="64.4,102.9 40.4,102.9 40.4,78.9 40.4,78.9 64.4,78.9 64.4,78.9 "
/>
<polygon
id="XMLID_285_"
class="st1"
points="40.3,121.5 21.8,121.5 21.8,121.5 21.8,102.9 40.4,102.9 40.4,121.5 "
/>
<path
id="XMLID_284_"
class="st1"
d="M21.9,102.9H6.3c0,0,0,0,0,0V87.4c0,0,0,0,0,0h15.5c0,0,0,0,0,0V102.9z"
/>
</g>
</g>
</g>
</g>
<g id="XMLID_254_">
<path
id="XMLID_278_"
class="st0"
d="M200.9,52.4c-5.5-3.8-12.4-5.8-20.5-5.8h-17.5v55.5h17.5c8,0,14.9-2.1,20.5-6.1
c3-2.1,5.4-5.1,7.1-8.9c1.7-3.7,2.5-8.2,2.5-13.1c0-4.9-0.8-9.3-2.5-13C206.3,57.4,203.9,54.4,200.9,52.4z M173.1,56h5.5
c6.1,0,11.1,1.2,15,3.6c4.2,2.6,6.4,7.4,6.4,14.4c0,7.2-2.2,12.3-6.4,15.1h0c-3.7,2.4-8.7,3.6-14.9,3.6h-5.6V56z"
/>
<path
id="XMLID_277_"
class="st0"
d="M222.6,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2
C225.7,46.5,224.3,45.9,222.6,45.9z"
/>
<rect id="XMLID_276_" x="217.6" y="63" class="st0" width="9.8" height="39.1" />
<path
id="XMLID_273_"
class="st0"
d="M263.2,66.3c-3-2.6-6.3-4.2-9.9-4.2c-5.4,0-9.9,1.9-13.4,5.6c-3.5,3.7-5.3,8.4-5.3,14.1
c0,5.5,1.8,10.2,5.2,14c3.5,3.7,8,5.5,13.5,5.5c3.8,0,7.1-1.1,9.7-3.1V99c0,3.2-0.9,5.8-2.6,7.5c-1.7,1.7-4.1,2.6-7.1,2.6
c-4.5,0-7.4-1.8-10.9-6.5l-6.7,6.4l0.2,0.3c1.4,2,3.7,4,6.6,5.9c2.9,1.9,6.6,2.8,10.9,2.8c5.8,0,10.6-1.8,14.1-5.4
c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7
c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z"
/>
<rect id="XMLID_272_" x="281.3" y="63" class="st0" width="9.8" height="39.1" />
<path
id="XMLID_271_"
class="st0"
d="M286.3,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2C289.4,46.5,288,45.9,286.3,45.9
z"
/>
<path
id="XMLID_270_"
class="st0"
d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2
c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z"
/>
<rect id="XMLID_269_" x="368" y="46.6" class="st0" width="9.8" height="55.5" />
<path
id="XMLID_268_"
class="st0"
d="M477.3,88.2c-1.8,2-3.6,3.7-4.9,4.6v0c-1.4,0.9-3.1,1.3-5.1,1.3c-2.9,0-5.2-1.1-7.1-3.2
c-1.9-2.2-2.8-4.9-2.8-8.3s0.9-6.1,2.8-8.2c1.9-2.2,4.2-3.2,7.1-3.2c3.2,0,6.5,2,9.4,5.4l6.5-6.2l0,0c-4.2-5.5-9.7-8.1-16.1-8.1
c-5.4,0-10.1,2-13.9,5.8c-3.8,3.9-5.7,8.8-5.7,14.6s1.9,10.7,5.7,14.6c3.8,3.9,8.5,5.9,13.9,5.9c7.1,0,12.9-3.1,16.8-8.7
L477.3,88.2z"
/>
<path
id="XMLID_265_"
class="st0"
d="M517.7,68.5c-1.4-1.9-3.3-3.5-5.7-4.7c-2.3-1.1-5.1-1.7-8.1-1.7c-5.5,0-10,2-13.4,6
c-3.3,4-4.9,8.9-4.9,14.7c0,5.9,1.8,10.8,5.4,14.6c3.6,3.7,8.4,5.6,14.2,5.6c6.6,0,12.1-2.7,16.2-8l0.2-0.3l-6.4-6.2l0,0
c-0.6,0.7-1.4,1.5-2.2,2.3c-1,0.9-1.9,1.6-2.9,2.1c-1.5,0.7-3.1,1.1-5,1.1c-2.7,0-5-0.8-6.7-2.4c-1.6-1.5-2.6-3.5-2.8-5.9h26.1
l0.1-3.6c0-2.5-0.3-5-1-7.3C520.1,72.6,519.1,70.4,517.7,68.5z M496.2,77.7c0.5-1.9,1.3-3.4,2.6-4.6c1.3-1.3,3.1-2,5.2-2
c2.4,0,4.2,0.7,5.5,2c1.2,1.2,1.8,2.8,2,4.6H496.2z"
/>
<path
id="XMLID_262_"
class="st0"
d="M555.5,66L555.5,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C560,72.2,558.5,68.5,555.5,66z M538,87.2c1.1-0.8,2.7-1.2,4.7-1.2c2.4,0,4.9,0.5,7.5,1.4
v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C536.4,89,536.9,88,538,87.2z"
/>
<path
id="XMLID_261_"
class="st0"
d="M597.9,66.7c-2.7-3.1-6.6-4.6-11.5-4.6c-3.9,0-7.1,1.1-9.4,3.3V63h-9.7v39.1h9.8V80.6
c0-3,0.7-5.3,2.1-7c1.4-1.7,3.3-2.5,5.8-2.5c2.2,0,3.9,0.7,5.2,2.2c1.3,1.5,1.9,3.6,1.9,6.2v22.7h9.8V79.5
C602,74.1,600.6,69.8,597.9,66.7z"
/>
<path
id="XMLID_258_"
class="st0"
d="M355.6,66L355.6,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C360.2,72.2,358.7,68.5,355.6,66z M338.2,87.2c1.1-0.8,2.7-1.2,4.7-1.2
c2.4,0,4.9,0.5,7.5,1.4v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C336.6,89,337.1,88,338.2,87.2z"
/>
<path
id="XMLID_255_"
class="st0"
d="M413.6,103c-15.8,0-28.6-12.8-28.6-28.6s12.8-28.6,28.6-28.6s28.6,12.8,28.6,28.6
S429.4,103,413.6,103z M413.6,55.8c-10.2,0-18.5,8.3-18.5,18.5s8.3,18.5,18.5,18.5s18.5-8.3,18.5-18.5S423.8,55.8,413.6,55.8z"
/>
</g>
</g>
</g>
</g>
</svg>
)

View File

@@ -5,14 +5,7 @@ import logoSrc from '~/assets/images/chatbot-whatsapp.png?width=64&height=64&png
export default component$(() => ( export default component$(() => (
<span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white flex items-center"> <span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white flex items-center">
<img <img src={logoSrc} class="inline-block mr-1" width={32} height={32} alt="Qwind Logo" loading="lazy" />
src={logoSrc}
class="inline-block mr-1"
width={32}
height={32}
alt="Qwind Logo"
loading="lazy"
/>
Chatbot Chatbot
</span> </span>
)) ))

View File

@@ -1,11 +1,5 @@
export const Netlify = () => ( export const Netlify = () => (
<svg <svg xmlns="http://www.w3.org/2000/svg" width={147} height={40} role="img" fill="currentColor">
xmlns="http://www.w3.org/2000/svg"
width={147}
height={40}
role="img"
fill="currentColor"
>
<g fill-rule="evenodd"> <g fill-rule="evenodd">
<path d="M53.37 12.978l.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568H60.79V19.676c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zM77.748 32.35c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726.918.914 2.073 1.371 3.464 1.371 1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35V26.76c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431V16.142H86.62v-3.164h3.122V8.355h4.261zm11.137 23.643h-4.262v-27h4.262v27zm9.172 0h-4.262v-19.02h4.262v19.02zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96V16.142h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0-1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117l3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595l4.016 12.903z" /> <path d="M53.37 12.978l.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568H60.79V19.676c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zM77.748 32.35c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726.918.914 2.073 1.371 3.464 1.371 1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35V26.76c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431V16.142H86.62v-3.164h3.122V8.355h4.261zm11.137 23.643h-4.262v-27h4.262v27zm9.172 0h-4.262v-19.02h4.262v19.02zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96V16.142h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0-1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117l3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595l4.016 12.903z" />
<path <path

View File

@@ -13,10 +13,7 @@ export const RouterHead = component$(() => {
<title>{head.title}</title> <title>{head.title}</title>
<link rel="canonical" href={loc.href} /> <link rel="canonical" href={loc.href} />
<meta <meta name="viewport" content="width=device-width, initial-scale=1.0" />
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
{head.meta.map((m) => ( {head.meta.map((m) => (

View File

@@ -34,14 +34,8 @@ export const Social = () => {
content="https://campaign.codigoencasa.com" content="https://campaign.codigoencasa.com"
/> />
<meta property="og:site_name" content="campaign.codigoencasa.com" /> */} <meta property="og:site_name" content="campaign.codigoencasa.com" /> */}
<meta <meta property="og:image" content="https://i.imgur.com/0HpzsEm.png"></meta>
property="og:image" <meta property="og:image:secure_url" content="https://i.imgur.com/0HpzsEm.png" />
content="https://i.imgur.com/0HpzsEm.png"
></meta>
<meta
property="og:image:secure_url"
content="https://i.imgur.com/0HpzsEm.png"
/>
<meta property="og:image:type" content="image/png"></meta> <meta property="og:image:type" content="image/png"></meta>
<meta property="og:image:width" content="1200"></meta> <meta property="og:image:width" content="1200"></meta>
<meta property="og:image:height" content="630"></meta> <meta property="og:image:height" content="630"></meta>
@@ -52,10 +46,7 @@ export const Social = () => {
name="twitter:title" name="twitter:title"
content="💻 Conviértete en un Programador Backend aprendiendo todo de Cloud y Nodejs" content="💻 Conviértete en un Programador Backend aprendiendo todo de Cloud y Nodejs"
/> />
<meta <meta name="twitter:image" content="https://i.imgur.com/0HpzsEm.png" />
name="twitter:image"
content="https://i.imgur.com/0HpzsEm.png"
/>
</> </>
) )
} }

View File

@@ -27,9 +27,7 @@ export default component$((props: ItemProps) => {
// TODO: // TODO:
document.body.classList.toggle('overflow-hidden') document.body.classList.toggle('overflow-hidden')
document.getElementById('header')?.classList.toggle('h-screen') document.getElementById('header')?.classList.toggle('h-screen')
document document.querySelector('#header nav')?.classList.toggle('hidden')
.querySelector('#header nav')
?.classList.toggle('hidden')
}} }}
> >
<IconMenu class={iconClass} /> <IconMenu class={iconClass} />

View File

@@ -10,16 +10,13 @@ interface ItemProps {
export default component$((props: ItemProps) => { export default component$((props: ItemProps) => {
const { iconClass } = props const { iconClass } = props
const store = useStore({ const store = useStore({
theme: theme: (typeof window !== 'undefined' && window?.localStorage?.theme) || undefined,
(typeof window !== 'undefined' && window?.localStorage?.theme) ||
undefined,
}) })
useClientEffect$(() => { useClientEffect$(() => {
store.theme = store.theme =
window.localStorage.theme === 'dark' || window.localStorage.theme === 'dark' ||
(!('theme' in window.localStorage) && (!('theme' in window.localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
window.matchMedia('(prefers-color-scheme: dark)').matches)
? 'dark' ? 'dark'
: 'light' : 'light'
}) })
@@ -42,11 +39,7 @@ export default component$((props: ItemProps) => {
} }
}} }}
> >
{store.theme == 'dark' ? ( {store.theme == 'dark' ? <IconMoon class={iconClass} /> : <IconSun class={iconClass} />}
<IconMoon class={iconClass} />
) : (
<IconSun class={iconClass} />
)}
</button> </button>
) )
}) })

View File

@@ -7,9 +7,7 @@ export const IconArrowDownRight = (props: ItemProps) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={`icon icon-tabler icon-tabler-arrow-down-right ${ class={`icon icon-tabler icon-tabler-arrow-down-right ${className || 'w-5 h-5'}`}
className || 'w-5 h-5'
}`}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -8,9 +8,7 @@ export const IconMenu = (props: ItemProps) => {
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"
class={`icon icon-tabler icon-tabler-menu ${ class={`icon icon-tabler icon-tabler-menu ${className || 'w-5 h-5'}`}
className || 'w-5 h-5'
}`}
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >

View File

@@ -7,9 +7,7 @@ export const IconMoon = (props: ItemProps) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={`icon icon-tabler icon-tabler-moon ${ class={`icon icon-tabler icon-tabler-moon ${className || 'w-5 h-5'}`}
className || 'w-5 h-5'
}`}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -14,7 +14,7 @@ export default component$(
<a href={props.user.html_url} target="_blank"> <a href={props.user.html_url} target="_blank">
<img <img
class="w-16 h-16 rounded-full mx-auto object-cover" class="w-16 h-16 rounded-full mx-auto object-cover"
src={props.user.avatar_url + '&s=80'} src={props.user.avatar_url}
alt={props.user.login} alt={props.user.login}
width="80" width="80"
height="80" height="80"
@@ -23,7 +23,7 @@ export default component$(
<div class="pt-2 space-y-4 justify-center flex"> <div class="pt-2 space-y-4 justify-center flex">
<figcaption class="text-sm"> <figcaption class="text-sm">
<div class={'font-semibold'}>{props.user.login}</div> <div class={'font-semibold truncate'}>{props.user.login}</div>
</figcaption> </figcaption>
</div> </div>
</figure> </figure>

View File

@@ -32,8 +32,7 @@ export default component$((props: { users: User[] }) => {
Super estrellas Super estrellas
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
Todo es posible gracias a el mayor recursos de todos, el Todo es posible gracias a el mayor recursos de todos, el recurso humano. Tu tambien puedes{' '}
recurso humano. Tu tambien puedes{' '}
<a class={'font-semibold'} href="/docs/contributing"> <a class={'font-semibold'} href="/docs/contributing">
formar parte formar parte
</a> </a>

View File

@@ -15,8 +15,8 @@ export default component$(() => {
📄 Editar esta pagina 📄 Editar esta pagina
</a> </a>
<p class={'text-xs'}> <p class={'text-xs'}>
Forma parte de esta comunidad mejorando la documentación Forma parte de esta comunidad mejorando la documentación siente libre de poder agregar o editar
siente libre de poder agregar o editar lo que quieras lo que quieras
</p> </p>
</li> </li>
</ul> </ul>

View File

@@ -56,12 +56,8 @@ export default component$(() => {
<IconArrowDownRight class="w-7 h-7 text-primary-600 inline-block" /> <IconArrowDownRight class="w-7 h-7 text-primary-600 inline-block" />
{question} {question}
</div> </div>
{answer {answer.split('\n\n').map((paragraph) => (
.split('\n\n') <p class="text-gray-700 dark:text-gray-400 mb-2">{paragraph}</p>
.map((paragraph) => (
<p class="text-gray-700 dark:text-gray-400 mb-2">
{paragraph}
</p>
))} ))}
</div> </div>
))} ))}

View File

@@ -50,13 +50,11 @@ export default component$(() => {
Caracteristicas Caracteristicas
</p> </p>
<h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading"> <h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading">
Nuestras principales{' '} Nuestras principales <span class="whitespace-nowrap">funciones</span>
<span class="whitespace-nowrap">funciones</span>
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
El secreto es mantener los procesos repetitivos en El secreto es mantener los procesos repetitivos en procesos automatizados simples, por eso te
procesos automatizados simples, por eso te mostramos en mostramos en que destacamos.
que destacamos.
</p> </p>
</div> </div>
<div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0"> <div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0">
@@ -70,12 +68,8 @@ export default component$(() => {
</div> </div>
</div> </div>
<div> <div>
<h3 class="mb-3 text-xl font-bold"> <h3 class="mb-3 text-xl font-bold">{title}</h3>
{title} <p class="text-gray-600 dark:text-slate-400">{description}</p>
</h3>
<p class="text-gray-600 dark:text-slate-400">
{description}
</p>
</div> </div>
</div> </div>
))} ))}

View File

@@ -9,36 +9,24 @@ import { src as placeholder } from '~/assets/images/chatbot-whatsapp.png?width=4
export default component$(() => { export default component$(() => {
return ( return (
<section <section class={` from-white via-purple-50 to-sky-100 dark:bg-none mt-[-95px]`}>
class={` from-white via-purple-50 to-sky-100 dark:bg-none mt-[-95px]`}
>
<div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]"> <div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]">
<div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left"> <div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left">
<div class="pb-12 md:pb-0 md:py-0 max-w-5xl mx-auto md:pr-16 flex items-center basis-[56%]"> <div class="pb-12 md:pb-0 md:py-0 max-w-5xl mx-auto md:pr-16 flex items-center basis-[56%]">
<div> <div>
<h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0"> <h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0">
Crear chatbot{' '} Crear chatbot <span class="sm:whitespace-nowrap text-[#25b637]">WhatsApp</span>
<span class="sm:whitespace-nowrap text-[#25b637]"> <br class="hidden lg:block" /> <span class="lg:inline">en minutos</span>
WhatsApp
</span>
<br class="hidden lg:block" />{' '}
<span class="lg:inline">en minutos</span>
</h1> </h1>
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
<p class="text-xl text-gray-600 mb-8 dark:text-slate-400"> <p class="text-xl text-gray-600 mb-8 dark:text-slate-400">
<span class="font-semibold ">Con esta libreria, </span>
<span class="font-semibold "> <span class="font-semibold ">
Con esta libreria,{' '} puedes configurar respuestas automatizadas para preguntas frecuentes
</span>
<span class="font-semibold ">
puedes configurar respuestas
automatizadas para preguntas frecuentes
</span>{' '} </span>{' '}
, recibir y responder mensajes de manera , recibir y responder mensajes de manera automatizada, y hacer un seguimiento de las
automatizada, y hacer un seguimiento de las interacciones con los clientes. Además, nuestro Chatbot se integra fácilmente con
interacciones con los clientes. Además, otros sistemas y herramientas que ya esté utilizando en su negocio.
nuestro Chatbot se integra fácilmente con
otros sistemas y herramientas que ya esté
utilizando en su negocio.
</p> </p>
<div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-col gap-4 m-auto md:m-0 justify-center md:justify-start"> <div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-col gap-4 m-auto md:m-0 justify-center md:justify-start">
@@ -47,12 +35,16 @@ export default component$(() => {
npm create bot-whatsapp@latest npm create bot-whatsapp@latest
</code> </code>
</div> </div>
<div class="flex w-full sm:w-auto"> <div class="flex w-full sm:w-auto gap-3">
<a href="/docs" class="btn bg-gray-50 dark:bg-transparent">
Ver documentación
</a>
<a <a
href="/docs" target={'_blank'}
href="https://youtu.be/UgoS8PXxe-A"
class="btn bg-gray-50 dark:bg-transparent" class="btn bg-gray-50 dark:bg-transparent"
> >
Ver documentación Ver video
</a> </a>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,49 @@
import { component$ } from '@builder.io/qwik'
import { RequestHandlerCloudflarePages } from '@builder.io/qwik-city/middleware/cloudflare-pages'
import { User } from '~/contexts'
import Collaborator from './Collaborator'
export const onRequest: RequestHandlerCloudflarePages = async () => {
console.log('??heree')
}
export const TaleUsers = component$((props: { users: User[] }) => {
return (
<>
{props.users.map((user) => (
<div class="col-span-2 ">
{' '}
<Collaborator user={user} />
</div>
))}
</>
)
})
export default component$((props: { users: User[] }) => {
return (
<section class="relative ">
<div class={'px-4 py-16 mx-auto max-w-6xl lg:py-20'}>
<div class="mb-10 md:mx-auto sm:text-center md:mb-12 max-w-3xl">
<p class="text-base text-primary-600 dark:text-purple-200 font-semibold tracking-wide uppercase">
Premium
</p>
<h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading">
Miembros
</h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
Conviértete en un miembro destacado y forma parte del proyecto y disfruta de manera adelantada
de las actualizaciones{' '}
<a class={'font-semibold'} target={'_blank'} href="https://opencollective.com/bot-whatsapp">
Únete
</a>
</p>
</div>
<div class="grid lg:grid-cols-12 grid-cols-1 gap-4 ">
<TaleUsers users={props.users} />
</div>
</div>
</section>
)
})

View File

@@ -5,8 +5,7 @@ import { DocumentationCtx } from '~/contexts'
/** /**
* options = [] array con la lista de opciones de la documentacion * options = [] array con la lista de opciones de la documentacion
*/ */
export default component$( export default component$(({ options = [] }: { options: DocumentationCtx[] }) => {
({ options = [] }: { options: DocumentationCtx[] }) => {
return ( return (
<div> <div>
{options.map((item, i) => ( {options.map((item, i) => (
@@ -14,26 +13,20 @@ export default component$(
))} ))}
</div> </div>
) )
} })
)
export const UlCompoent = component$( export const UlCompoent = component$((porps: { title: string; list: { link: string; name: string }[] }) => {
(porps: { title: string; list: { link: string; name: string }[] }) => {
return ( return (
<ul> <ul>
<li class="mt-2 lg:mt-2"> <li class="mt-2 lg:mt-2">
<h5 class="mb-8 lg:mb-3 font-semibold text-slate-900 dark:text-slate-200"> <h5 class="mb-8 lg:mb-3 font-semibold text-slate-900 dark:text-slate-200">{porps.title}</h5>
{porps.title}
</h5>
<LiComponent list={porps.list} /> <LiComponent list={porps.list} />
</li> </li>
</ul> </ul>
) )
} })
)
export const LiComponent = component$( export const LiComponent = component$((porps: { list: { link: string; name: string }[] }) => {
(porps: { list: { link: string; name: string }[] }) => {
const location = useLocation() const location = useLocation()
const currentPage = location.pathname const currentPage = location.pathname
return ( return (
@@ -42,9 +35,7 @@ export const LiComponent = component$(
<li> <li>
<Link <Link
class={[ class={[
currentPage === `${opt.link}/` currentPage === `${opt.link}/` ? 'font-semibold' : '',
? 'font-semibold'
: '',
'block border-l pl-4 -ml-px border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300 ', 'block border-l pl-4 -ml-px border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300 ',
]} ]}
href={opt.link} href={opt.link}
@@ -55,5 +46,4 @@ export const LiComponent = component$(
))} ))}
</ul> </ul>
) )
} })
)

View File

@@ -1,7 +1,6 @@
import { component$ } from '@builder.io/qwik' import { component$ } from '@builder.io/qwik'
export const ButtonLink = component$( export const ButtonLink = component$((props: { name: string; link: string; direction: 'left' | 'right' }) => {
(props: { name: string; link: string; direction: 'left' | 'right' }) => {
const ArrowRight = () => ( const ArrowRight = () => (
<svg <svg
viewBox="0 0 3 6" viewBox="0 0 3 6"
@@ -35,10 +34,7 @@ export const ButtonLink = component$(
) )
return ( return (
<a <a class="group flex items-center hover:text-slate-900 dark:hover:text-white" href={props.link}>
class="group flex items-center hover:text-slate-900 dark:hover:text-white"
href={props.link}
>
{props.direction === 'left' ? ( {props.direction === 'left' ? (
<> <>
<ArrowLeft /> <ArrowLeft />
@@ -52,23 +48,16 @@ export const ButtonLink = component$(
)} )}
</a> </a>
) )
} })
)
export default component$( export default component$((props: { pages: ({ name: string; link: string } | null)[] }) => {
(props: { pages: ({ name: string; link: string } | null)[] }) => {
const { pages } = props const { pages } = props
return ( return (
<div class="text-sm leading-6 mt-12"> <div class="text-sm leading-6 mt-12">
<div class="mb-10 text-slate-700 font-semibold flex justify-between items-center dark:text-slate-200"> <div class="mb-10 text-slate-700 font-semibold flex justify-between items-center dark:text-slate-200">
{pages[0] ? ( {pages[0] ? <ButtonLink direction="left" {...pages[0]} /> : null}
<ButtonLink direction="left" {...pages[0]} /> {pages[1] ? <ButtonLink direction="right" {...pages[1]} /> : null}
) : null}
{pages[1] ? (
<ButtonLink direction="right" {...pages[1]} />
) : null}
</div> </div>
</div> </div>
) )
} })
)

View File

@@ -0,0 +1,20 @@
import { component$ } from '@builder.io/qwik'
export const SearchModal = component$(() => {
// const state = useStore({
// open: false,
// src: '',
// })
return (
<div class={'bg-gray-100/75 fixed w-[100vw] h-[100vh] z-50'}>
<div class={'bg-red-200 w-1/3 m-auto mt-12'}>
<SingleModal />
</div>
</div>
)
})
export const SingleModal = component$(() => {
return <div class={'bg-blue-300 w-100 px-3 py-2'}>Modal singlke</div>
})

View File

@@ -7,6 +7,8 @@ import { src as qwik } from '~/assets/images/qwik.png?width=100&metadata'
import { src as leanga } from '~/assets/images/leanga.png?width=40&metadata' import { src as leanga } from '~/assets/images/leanga.png?width=40&metadata'
// @ts-ignore // @ts-ignore
import { src as netlify } from '~/assets/images/full-logo-light.png?width=100&metadata' import { src as netlify } from '~/assets/images/full-logo-light.png?width=100&metadata'
// @ts-ignore
import { src as digitalOcean } from '~/assets/images/digital-ocean.png?width=100&metadata'
/** /**
* options = [] array con la lista de opciones de la documentacion * options = [] array con la lista de opciones de la documentacion
@@ -41,7 +43,20 @@ export default component$(() => {
<img <img
src={netlify} src={netlify}
class="border border-slate-200 rounded my-2 p-1 bg-gray-50 dark:border-gray-600 dark:bg-gray-700" class="border border-slate-200 rounded my-2 p-1 bg-gray-50 dark:border-gray-600 dark:bg-gray-700"
alt="Qwind Hero Image (Cool dog)" alt="Netlify"
loading="eager"
decoding="async"
/>
</picture>
</a>
</li>
<li>
<a target={'_blank'} href="https://m.do.co/c/140291d21736">
<picture>
<img
src={digitalOcean}
class="border border-slate-200 rounded my-2 p-1 bg-gray-50 dark:border-gray-600 dark:bg-gray-700"
alt="DigitalOcean"
loading="eager" loading="eager"
decoding="async" decoding="async"
/> />

View File

@@ -5,33 +5,25 @@ export default component$(() => {
<div class="px-4 py-8 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl"> <div class="px-4 py-8 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl">
<div class="grid grid-cols-2 row-gap-8 md:grid-cols-4"> <div class="grid grid-cols-2 row-gap-8 md:grid-cols-4">
<div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0"> <div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading"> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">132K</div>
132K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Downloads Downloads
</p> </p>
</div> </div>
<div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0"> <div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading"> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">24.8K</div>
24.8K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Stars Stars
</p> </p>
</div> </div>
<div class="text-center md:border-r dark:md:border-slate-500 font-heading"> <div class="text-center md:border-r dark:md:border-slate-500 font-heading">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1]"> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1]">10.3K</div>
10.3K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Forks Forks
</p> </p>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading"> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">48.4K</div>
48.4K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Users Users
</p> </p>

View File

@@ -13,5 +13,4 @@ export interface User {
avatar_url: string avatar_url: string
} }
export const GlobalStore = export const GlobalStore = createContext<DocumentationCtx[]>('documentation-site')
createContext<DocumentationCtx[]>('documentation-site')

View File

@@ -1,14 +1,5 @@
import { import { component$, useContextProvider, useStore, useStyles$ } from '@builder.io/qwik'
component$, import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'
useContextProvider,
useStore,
useStyles$,
} from '@builder.io/qwik'
import {
QwikCityProvider,
RouterOutlet,
ServiceWorkerRegister,
} from '@builder.io/qwik-city'
import { RouterHead } from '~/components/core/RouterHead' import { RouterHead } from '~/components/core/RouterHead'
import { DarkThemeLauncher } from '~/components/core/DarkThemeLauncher' import { DarkThemeLauncher } from '~/components/core/DarkThemeLauncher'
@@ -52,16 +43,22 @@ export default component$(() => {
title: 'Avanzado', title: 'Avanzado',
list: [ list: [
{ name: 'Migración', link: '/docs/migration' }, { name: 'Migración', link: '/docs/migration' },
{ name: 'Extender funcionalidades', link: '/docs/custom' }, { name: 'MasterClass', link: '/docs/masterclass' },
],
},
{
title: 'Despliegue',
list: [
{ name: 'Local', link: '/docs/deploy/local' },
{ name: 'Docker', link: '/docs/deploy/docker' },
{ name: 'Cloud', link: '/docs/deploy/cloud' },
], ],
}, },
{ {
title: 'Comunidad', title: 'Comunidad',
list: [ list: [
{ name: 'MasterClass', link: '/docs/masterclass' },
{ name: 'Colabores', link: '/docs/contributing' }, { name: 'Colabores', link: '/docs/contributing' },
{ name: 'Unirme al proyecto', link: '/docs/join' }, { name: 'Unirme al proyecto', link: '/docs/join' },
{ name: 'Sponsors', link: '/docs/sponsors' },
], ],
}, },
]) ])
@@ -72,10 +69,7 @@ export default component$(() => {
<QwikCityProvider> <QwikCityProvider>
<head> <head>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<meta <meta name="viewport" content="width=device-width, initial-scale=1" />
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<RouterHead /> <RouterHead />

View File

@@ -4,11 +4,10 @@ import Navigation from '../../../components/widgets/Navigation'
# DataBase (Base de datos) # DataBase (Base de datos)
<Alert> <Alert>
⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar ⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar algunas configuracion adicional como
algunas configuracion adicional como **user, host, password** para esos **user, host, password** para esos casos te recomendamos guiarte de los
casos te recomendamos guiarte de los **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** documentación para ir agregando más info
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo

View File

@@ -0,0 +1,108 @@
import Alert from '../../../../components/widgets/Alert'
import Navigation from '../../../../components/widgets/Navigation'
# Entorno Cloud
Si deseas tener tu chatbot en ejecución en un servidor en la nueba esta, guía te ayudará.
El servidor deberá cumplir con los requisitos mínimos, puedes ver en [este enlace.](/docs/requirements)
---
Dependiendo de tu proveedor de **servicio Cloud** debes crear una instancia (máquina virtual), este ejemplo iremos orientando en un entorno de AWS.
En nuestro ejemplo creamos una maquina virtual con **Ubuntu 20.04**
![](https://i.imgur.com/5zRCz9q.png)
---
Posterior al proceso de crear la máquina esperamos unos minutos hasta que ya está operativo y tomamos nota del usuario y la IP pública para proceder a conectarnos vía SSH
## ![](https://i.imgur.com/ljyJPBm.png)
## Conectarse via SSH
Luego de obtener los datos necesarios para conectarnos a nuestra máquina, procedemos a hacerlo
```shell
ssh -i llaveBot.pem ubutnu@34.228.208.104
```
---
Luego puede aparecer un mensaje como el siguiente donde solo debes de responder **yes**
![](https://i.imgur.com/rUBASqR.png)
---
Una vez conectado ya estás dentro de la máquina virtual, te aconsejamos la primera vez hacer una actualización de dependencias de Ubuntu con los siguientes comandos
```shell
sudo apt-get update
```
```shell
sudo apt-get upgrade
```
---
## Recuerda instalar Node 16 o superior
Puedes ver más a detalle los pasos de la instalacion en este [blog](https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04-es)
```shell
curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh
```
```shell
sudo bash nodesource_setup.sh
```
```shell
sudo apt-get install -y nodejs
```
---
## Implementar el bot
Si tienes el código de tu chatbot en un repositorio, solo falta que clones el repo en el servidor y ejecutes `npm start`
Para escanear el **QR** puedes hacerlo vía WEB accediendo a la URL `http://[TU_IP_PUBLICA]:3000` en este ejemplo seria `http://34.228.208.10:3000`
![](https://i.imgur.com/xcovczm.png)
---
## Firewall
Si no te abre la pagina web asegurate de tener el puerto abierto en tu firewall.
Ejemplo permitir el puerto **3000**
![](https://i.imgur.com/0dAz0B1.png)
---
## Escanear QR
![](https://i.imgur.com/2m3NbXC.png)
---
## Ejecutar en Producción
Debes ubicarte en el directorio donde tienes el codigo fuente de tu chatbot.
Independientemente de tu sistema operativo deberás ejecutar el chatbot con el comando atrevés de un sistema que mantenga el proceso en ejecución.
Recomendamos [Pm2](https://pm2.keymetrics.io/)
```shell
pm2 start app.js --name=bot1
```
![](https://i.imgur.com/ilPS75H.png)
La consola de devolver un mensaje con una lista de procesos, en el ejemplo puedes observar, que tenemos un proceso llamado `bot1`
De esta manera ya puedes cerrar la terminal y tu bot seguirá en ejecución sin problema

View File

@@ -0,0 +1,32 @@
import Alert from '../../../../components/widgets/Alert'
import Navigation from '../../../../components/widgets/Navigation'
# Entorno Docker
Previamente, necesitas tener instalado Docker en tu servidor dependiendo del sistema operativo, los procesos cambian,
puedes encontrar toda la información oficial de docker en [este enlace.](https://docs.docker.com/get-docker/)
---
Dependiendo del proveedor que has elegido necesitaras una implementación de Docker específica, pero no te preocupes, ya que viene implementada automáticamente en un archivo llamado **Dockerfile**, también puedes ver los otros Dockerfile en el apartado de [plantillas.](https://github.com/codigoencasa/bot-whatsapp/tree/main/starters/apps)
![](https://i.imgur.com/cDspa0R.png)
---
## Contruir imagen
Solo es necesario construir la imagen del docker lo puedes hacer con el siguiente comando
```shell
docker build . -t botwhatsapp:latest
```
## Iniciar contenedor
Para iniciar el contenedor con la imagen previamente construida puedes realizarlo ejecutando el siguiente comando.
Se utiliza el puerto **3001** solo com un ejemplo puedes usar el puerto que tu quieras
```shell
docker run -e PORT=3001 -p 3001:3001 botwhatsapp:latest
```

View File

@@ -0,0 +1,26 @@
import Alert from '../../../../components/widgets/Alert'
import Navigation from '../../../../components/widgets/Navigation'
# Entorno Local
Si deseas tener tu chatbot en ejecución en un servidor local (computadora personal, etc.) esta, guía te ayudará.
El servidor local deberá cumplir con los requisitos mínimos, puedes ver en [este enlace.](/docs/requirements)
---
<Alert>Si deseas instalar pm2 puedes ejecutar `npm install pm2 --global`</Alert>
Debes ubicarte en el directorio donde tienes el codigo fuente de tu chatbot.
Independientemente de tu sistema operativo deberás ejecutar el chatbot con el comando atrevés de un sistema que mantenga el proceso en ejecución.
Recomendamos [Pm2](https://pm2.keymetrics.io/)
```shell
pm2 start app.js --name=bot1
```
![](https://i.imgur.com/ilPS75H.png)
La consola de devolver un mensaje con una lista de procesos, en el ejemplo puedes observar, que tenemos un proceso llamado `bot1`
De esta manera ya puedes cerrar la terminal y tu bot seguirá en ejecución sin problema

View File

@@ -22,12 +22,7 @@ Tan sencillo como decir **palabra/s clave** y **mensaje a responder**
Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles
```js ```js
const { const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot')
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const flowPrincipal = addKeyword(['hola', 'alo']) const flowPrincipal = addKeyword(['hola', 'alo'])
.addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?'])
@@ -39,11 +34,10 @@ const flowPrincipal = addKeyword(['hola', 'alo'])
## Provider (Proveedor) ## Provider (Proveedor)
<Alert> <Alert>
⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar ⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar algunas configuracion adicional como
algunas configuracion adicional como **token, api, etc.** para esos casos te **token, api, etc.** para esos casos te recomendamos guiarte de los
recomendamos guiarte de los **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** documentación para ir agregando más info
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio
@@ -71,11 +65,10 @@ Los proveedores disponibles hasta el momento son los siguientes:
## DataBase (Base de datos) ## DataBase (Base de datos)
<Alert> <Alert>
⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar ⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar algunas configuracion adicional como
algunas configuracion adicional como **user, host, password** para esos **user, host, password** para esos casos te recomendamos guiarte de los
casos te recomendamos guiarte de los **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** documentación para ir agregando más info
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo

View File

@@ -5,12 +5,7 @@ import Navigation from '../../../components/widgets/Navigation'
Si copias y pegas este codigo y tu entorno de trabajo cumple con todos los requesitos te debe funcionar abajo explico muy por encima Si copias y pegas este codigo y tu entorno de trabajo cumple con todos los requesitos te debe funcionar abajo explico muy por encima
```js ```js
const { const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot')
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp') const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock') const MockAdapter = require('@bot-whatsapp/database/mock')
@@ -42,12 +37,7 @@ main()
En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)** En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)**
```js ```js
const { const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot')
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp') const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock') const MockAdapter = require('@bot-whatsapp/database/mock')

View File

@@ -1,6 +1,6 @@
import Navigation from '../../../components/widgets/Navigation' import Navigation from '../../../components/widgets/Navigation'
# Flow (Flujos) # Flow
Los flujos hace referencia al hecho de construir un flujo de conversion. Esto es un flow podemos observar que estan presente dos metodos importantes **addKeyword** y el **addAnswer**. Los flujos hace referencia al hecho de construir un flujo de conversion. Esto es un flow podemos observar que estan presente dos metodos importantes **addKeyword** y el **addAnswer**.
@@ -9,12 +9,7 @@ Tan sencillo como decir **palabra/s clave** y **mensaje a responder**
Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles
```js ```js
const { const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot')
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const flowPrincipal = addKeyword(['hola', 'alo']) const flowPrincipal = addKeyword(['hola', 'alo'])
.addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?'])
@@ -23,6 +18,206 @@ const flowPrincipal = addKeyword(['hola', 'alo'])
--- ---
## blackList
Éste argumento se utiliza para **evitar que el bot se active** cuando los números de la lista activen el bot.
Es importante que el número **vaya acompañado de su prefijo**, en el caso de España "34".
```js
createBot(
{
flow: adapterFlow,
provider: adapterProvider,
database: adapterDB,
},
{
blackList: ['34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX'],
}
)
```
---
## addKeyword()
Esta funcion se utliza para iniciar un flujo de conversion. <br /> Recibe un `string` o un `array`
de string `['hola','buenas']`.
**Opciones**
- sensitive: Sensible a mayusculas y minusculas por defecto `false`
```js
const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola')
const flowArray = addKeyword(['hola', 'alo'])
const flowSensitive = addKeyword(['hola', 'alo'], {
sensitive: true,
})
```
---
## addAnswer()
Esta funcion se utliza para responder un mensaje despues del `addKeyword()`
**Opciones**
- delay: 0 (milisegundos)
- media: url de imagen
- buttons: array `[{body:'Boton1'}, {body:'Boton2'}, {body:'Boton3'}]`
- capture: true (para esperar respuesta)
- child: Objecto tipo flujo o arra de flujos hijos
```js
const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Este mensaje se enviara 1 segundo despues', {
delay: 1000,
})
const flowString = addKeyword('hola').addAnswer('Este mensaje envia una imagen', {
media: 'https://i.imgur.com/0HpzsEm.png',
})
const flowString = addKeyword('hola').addAnswer('Este mensaje envia tres botones', {
buttons: [{ body: 'Boton 1' }, { body: 'Boton 2' }, { body: 'Boton 3' }],
})
const flowString = addKeyword('hola').addAnswer('Este mensaje espera una respueta del usuario', {
capture: true,
})
```
---
## ctx
Este argumento se utiliza para obtener el contexto de la conversación
```js
const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Indica cual es tu email', null, (ctx) => {
console.log('👉 Informacion del contexto: ', ctx)
})
```
---
## fallBack()
Esta funcion se utliza para volver a enviar el ultimo mensaje abajo un ejemplo.
En el ejemplo de abajo esperamos que el usuario ingrese un mensaje que contenga `@` sino contiene
se repetira el mensaje `Indica cual es tu email`
```js
const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Indica cual es tu email', null, (ctx, { fallBack }) => {
if (!ctx.body.includes('@')) return fallBack()
})
```
---
## flowDynamic()
Esta funcion se utliza para devolver mensajes dinamicos que pueden venir de una API o Base de datos.
La funcion recibe un array que debe contener la siguiente estrucutura:
`[{body:'Mensaje}, {body:'Mensaje2}]`
```js
const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola')
.addAnswer('Indica cual es tu email', null, async (ctx, {flowDynamic}) => {
const mensajesDB = () => {
const categories = db.find(...)
const mapDatos = categories.map((c) => ({body:c.name}))
return mapDatos
}
await flowDynamic(mensajesDB())
})
```
---
## endFlow()
Esta funcion se utliza para finalizar un flujo con dos o más addAnswer. Un ejemplo de uso sería registrar 3 datos de un usuario en 3 preguntas distinas y
que el usuario pueda finalizar por él mismo el flujo.
Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones.
```js
const flowFormulario = addKeyword(['Hola'])
.addAnswer(
['Hola!', 'Escriba su *Nombre* para generar su solicitud'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') {
await flowDynamic([
{
body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
},
])
return endFlow()
}
}
)
.addAnswer(
['También necesito tus dos apellidos'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') {
await flowDynamic([
{
body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
},
])
return endFlow()
}
}
)
.addAnswer(
['Dejeme su número de teléfono y le llamaré lo antes posible.'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') {
await flowDynamic([
{
body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
},
])
return endFlow()
}
}
)
```
---
# QRPortalWeb
Argumento para asignar nombre y puerto al BOT
```js
const BOTNAME = 'bot'
QRPortalWeb({ name: BOTNAME, port: 3005 })
```
---
<Navigation <Navigation
pages={[ pages={[
{ name: 'Conceptos', link: '/docs/essential' }, { name: 'Conceptos', link: '/docs/essential' },

Some files were not shown because too many files have changed in this diff Show More