[email protected]
Twitter
LinkedIn
YouTube
GitHub
  • Serviços
  • Blog
  • Repositórios
  • GitHub
  • Currículo
  • Contato
Produto foi adicionado ao seu carrinho

Carrinho

Jfa Whatsapp Chatbot

Março 29, 2023Automação, Back-End, Ferramentasjfadev

Com isso node.js micro estrutura usando Bot Venom sob o capô, você pode facilmente criar um WhatsApp Chatbot 🤖 . Você só precisará editar seu fluxo de conversa em um único arquivo.

Começando

  1. Crie um novo repositório de este modelo
  2. Instalar no seu ambiente de desenvolvimento
  3. configurar porta(s), credenciais, etc
  4. Escreva o seu fluxo de conversa
  5. Começar

Instalar

Janela de encaixe

Requisitos: janela de encaixe

Construir e executar com Dockerfile

$ docker build -t wchatbot .
$ docker run --name wchatbot -p 3000:3000 -v /your_project_absolute_path/src:/wchatbot/src wchatbot

ou Construa e execute com o Docker Compose

$ docker-compose build
$ docker-compose up

Visite http://host local:3000 e brinque com seu chatbot!

Máquina virtual

Requisitos: nodejs (Mais recente maintenance LTS versão), yarn (ou npm), pm2, chrome/chromium

Use um nginx proxy reverso para expor publicamente o painel de controle http (exemplo de configuração).

$ yarn install

Inicie o chatbot e o painel de controle http

$ yarn start
$ yarn http-ctrl:start

Visite http://host local:3000 e brinque com seu chatbot!

Máquina local

Requisitos: nodejs (Mais recente maintenance LTS versão), yarn (ou npm), chrome/chromium

$ yarn install

Inicie o chatbot e o painel de controle http

$ yarn http-ctrl:dev:detach
$ yarn dev

Visite http://host local:3000 e brinque com seu chatbot!

Configuração

Editar ./src/config.js arquivo

Basic

export const chatbotOptions = {
  httpCtrl: {
    port: 3000, // httpCtrl port (http://localhost:3000/)
    username: "admin", // httpCtrl auth login
    password: "chatbot",
  },
};

Advançado

export const venomOptions = {
  ...
  browserArgs: [
    "--no-sandbox", // Will be passed to browser. Use --no-sandbox with Docker
  ],
  puppeteerOptions: { // Will be passed to puppeteer.launch.
    args: ["--no-sandbox"] // Use --no-sandbox with Docker
  },
  ...
};

Commands

Janela de encaixe

Controles do chatbot

$ docker exec wchatbot yarn start
$ docker exec wchatbot yarn stop
$ docker exec wchatbot yarn restart
$ docker exec wchatbot yarn reload

Controles do Painel de Controle HTTP

$ docker exec wchatbot yarn http-ctrl:start
$ docker exec wchatbot yarn http-ctrl:stop
$ docker exec wchatbot yarn http-ctrl:restart
$ docker exec wchatbot yarn http-ctrl:reload

Máquina virtual

Controles do chatbot

$ yarn start
$ yarn stop
$ yarn restart
$ yarn reload

Controles do Painel de Controle HTTP

$ yarn http-ctrl:start
$ yarn http-ctrl:stop
$ yarn http-ctrl:restart
$ yarn http-ctrl:reload

Máquina local

Direto no seu sistema operacional sem Docker

chatbot

$ yarn dev
$ yarn dev:detach

Iniciar Painel de Controle HTTP

$ yarn http-ctrl:dev
$ yarn http-ctrl:dev:detach

Sessões

Sessões e tokens de autenticação são gravados ./tokens pasta.

Histórico

Os logs são gravados ./logs pasta. Atenção: console.log e http-ctrl-console.log apenas escreva em ./logs pasta com yarn dev:detach e yarn http-ctrl:dev:detach de outra forma gerenciado por pm2.

Janela de encaixe

chatbot

$ docker exec wchatbot yarn log

Painel de controle HTTP

$ docker exec wchatbot yarn http-ctrl:log

Conversas

$ docker exec wchatbot yarn conversations

Máquina virtual

chatbot

$ yarn log

Painel de controle HTTP

$ yarn http-ctrl:log

Conversas

$ yarn conversations

Máquina local

chatbot

$ yarn log:dev

Painel de controle HTTP

$ yarn log:http-ctrl:dev

Conversas

$ yarn conversations

Fluxo da conversa

Editar ./src/conversations/conversation.js arquivo.

O fluxo da conversa é uma matriz de objetos de resposta ordenados. Uma resposta só é acionada se for parent (pode ser um número inteiro ou uma matriz) é igual ao id da resposta anterior.

Replies Relations

Para indicar que uma resposta é o fim da conversa, adicione a seguinte propriedade:

Propriedade Tipo Descrição
end boleano O fim da conversa

Você pode proteger para que apenas um número ou uma lista de números sejam atendidos com:

Propriedade Tipo Descrição
from Corda / Array Atenda apenas este ou estes números

Uma resposta precisa necessariamente das seguintes propriedades:

Tipos de respostas

Mande mensagem

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
message Corda Responder mensagem de texto

Exemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match with all text
    message: "Hi I am a Chatbot!",
  }
]

Enviar Botões

Atenção: No momento não está funcionando!.

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
message Corda Responder mensagem de texto
description Corda Responder legenda do texto
buttons Array Objeto de botão, Olhe para o exemplo

Exemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/,
    message: "Hello!",
    description: "Can I help with something?",
    buttons: buttons([
      "Website",
      "LinkedIn",
      "Github",
    ]),
  }
]

Enviar Lista

Atenção: No momento não está funcionando!.

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
message Corda Responder mensagem de texto
description Corda Responder legenda do texto
button Corda Texto do botão de lista
list Array Objeto de lista, Olhe para o exemplo

Exemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /other country/,
    message: "Choice one country",
    description: "Choice one option!",
    button: "Countries list",
    list: list([
      "Argentina",
      "Belize",
      "Bolivia",
    ]),
  },
]

Enviar Link

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
message Corda Responder mensagem de texto
link Corda URL da visualização do link gerado

Exemplo

[
  {
    id: 2,
    parent: 1, // Relation with id: 1
    pattern: /github/,
    message: "Check my Github repositories!",
    link: "https://github.com/jfadev",
  }
]

Enviar imagem

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
image Caminho / Objeto Caminho ou Objeto retornado por remoteImg() função

Exemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    image: remoteImg("https://remote-server.com/menu.jpg"),
    // image: "./images/menu.jpg",
  }
]

Enviar áudio

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
audio Caminho / Objeto Caminho ou Objeto retornado por remoteAudio() função.

Exemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    audio: remoteAudio("https://remote-server.com/audio.mp3"),
    // audio: "./audios/audio.mp3",
  }
]

Encaminhar mensagem

Propriedade Tipo Descrição
id inteiro Resposder id é usado para ligar com parent
parent inteiro Id do pai de resposta ou matriz de ids [2, 3]. Se não tem pai, é 0 por padrão
pattern RegExp Expressão regular para corresponder em minúsculas
message Corda Responder mensagem de texto
forward Corda Número para onde a mensagem é encaminhada

Exemplo

[
    {
    id: 1,
    parent: 0,
    pattern: /forward/,
    message: "Text to forward",
    forward: "[email protected]", // forward this message to this number
  }
]

ajudantes

Ajudante Retornar Descrição
buttons(buttonTexts) Array Gerar botões
remoteTxt(url) Corda Retornar um arquivo TXT remoto
remoteJson(url) JSON Retornar um arquivo JSON remoto
remoteImg(url) Objeto Retornar um arquivo de imagem remoto
remoteAudio(url) Objeto Retornar um arquivo de áudio remoto
list(listRows) Array Gerar lista
inp(id, parents) Corda Retorna a string de entrada por id de resposta. Use antes de responder, afterReply e beforeForward
med(id, parents) Mídia / nulo Devolução de mídia ({amortecedor, extensão}) por id de resposta. Use antes de responder, afterReply e beforeForward

ganchos

Propriedade Tipo Descrição
beforeReply(from, input, output, parents, media) Função Injetar código personalizado antes de uma resposta
afterReply(from, input, parents, media) Função Injetar código personalizado após uma resposta
beforeForward(from, forward, input, parents, media) Função Injetar código personalizado antes de um encaminhamento

rotações

Propriedade Tipo Descrição
goTo(from, input, output, parents, media) Função Deve retornar o id da resposta para onde pular
clearParents boleano Limpar dados dos pais, usar com goTo()

Painel de Controle Http

Com o painel de controle você pode fazer login, começar, pare ou reinicie o bot e monitore os logs.

Defina seu username e password para acessar seu painel de controle em arquivo ./src/config.js

export const chatbotOptions = {
  httpCtrl: {
    port: 3000, // httpCtrl port (http://localhost:3000/)
    username: "admin",
    password: "chatbot"
  }
};

Use um proxy reverso nginx para expor publicamente o painel de controle http (exemplo de configuração).

Http Control Panel

Exemplos

Edite seu arquivo ./src/conversations/conversation.js e crie seu fluxo de trabalho de conversa personalizado.

Exemplo 1

doc/examples/conversation1.js

import { buttons } from "../helpers";

/**
 * Chatbot conversation flow
 * Example 1
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /hello|hi|howdy|good day|good morning|hey|hi-ya|how are you|how goes it|howdy\-do/,
    message: "Hello! Thank you for contacting me, I am a Chatbot 🤖 , we will gladly assist you.",
    description: "Can I help with something?",
    buttons: buttons([
      "Website",
      "Linkedin",
      "Github",
      "Donate",
      "Leave a Message",
    ]),
  },
  {
    id: 2,
    parent: 1, // Relation with id: 1
    pattern: /website/,
    message: "Visit my website and learn more about me!",
    link: "https://jordifernandes.com/",
    end: true,
  },
  {
    id: 3,
    parent: 1, // Relation with id: 1
    pattern: /linkedin/,
    message: "Visit my LinkedIn profile!",
    link: "https://www.linkedin.com/in/jfadev",
    end: true,
  },
  {
    id: 4,
    parent: 1, // Relation with id: 1
    pattern: /github/,
    message: "Check my Github repositories!",
    link: "https://github.com/jfadev",
    end: true,
  },
  {
    id: 5,
    parent: 1, // Relation with id: 1
    pattern: /donate/,
    message: "A tip is always good!",
    link: "https://jordifernandes.com/donate/",
    end: true,
  },
  {
    id: 6,
    parent: 1, // Relation with id: 1
    pattern: /leave a message/,
    message: "Write your message, I will contact you as soon as possible!",
  },
  {
    id: 7,
    parent: 6, // Relation with id: 6
    pattern: /.*/, // Match with all text
    message: "Thank you very much, your message will be sent to Jordi! Sincerely the Chatbot 🤖 !",
    end: true,
  },
];

Exemplo 2

doc/examples/conversation2.js

import { buttons, remoteTxt, remoteJson } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 2
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/,
    message: "Hello! I am a Delivery Chatbot.",
    description: "Choice one option!",
    buttons: buttons([
      "See today's menu?",
      "Order directly!",
      "Talk to a human!",
    ]),
  },
  {
    id: 2,
    parent: 1, // Relation with id: 1
    pattern: /menu/,
    message: remoteTxt(`${customEndpoint}/menu.txt`),
    // message: remoteJson(`${customEndpoint}/menu.json`)[0].message,
    end: true,
  },
  {
    id: 3,
    parent: 1, // Relation with id: 1
    pattern: /order/,
    message: "Make a order!",
    link: `${customEndpoint}/delivery-order.php`,
    end: true,
  },
  {
    id: 4,
    parent: 1, // Relation with id: 1
    pattern: /human/,
    message: "Please call the following WhatsApp number: +1 206 555 0100",
    end: true,
  },
];

Exemplo 3

doc/examples/conversation3.js

import fetch from "sync-fetch";
import { remoteImg } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 3
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Hello! I am a Delivery Chatbot. Send a menu item number!",
  },
  {
    id: 2,
    parent: 0, // Same parent (send reply id=1 and id=2)
    pattern: /.*/, // Match all
    image: remoteImg(`${customEndpoint}/menu.jpg`),
  },
  {
    id: 3,
    parent: 1, // Relation with id: 1
    pattern: /\d+/, // Match any number
    message: "You are choice item number $input. How many units do you want?", // Inject input value ($input) in message
  },  
  {
    id: 4,
    parent: 2, // Relation with id: 2
    pattern: /\d+/, // Match any number
    message: "You are choice $input units. How many units do you want?",
    // Inject custom code or overwrite output 'message' property before reply
    beforeReply(from, input, output, parents) {
      // Example check external api and overwrite output 'message'
      const response = fetch(
        `${customEndpoint}/delivery-check-stock.php/?item=${input}&qty=${parents.pop()}`
      ).json();
      return response.stock === 0
        ? "Item number $input is not available in this moment!"
        : output;
    },
    end: true,
  },
];

Exemplo 4

doc/examples/conversation4.js

import { remoteImg } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 4
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Image local and remote! Send [local] or [remote]",
  },
  {
    id: 2,
    parent: 1,
    pattern: /local/, 
    image: "./images/image1.jpg",
    end: true,
  },
  {
    id: 3,
    parent: 1,
    pattern: /remote/, 
    image: remoteImg(`${customEndpoint}/image1.jpg`),
    end: true,
  },
];

Exemplo 5

doc/examples/conversation5.js

import { remoteImg } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 5
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Audio local and remote! Send [local] or [remote]",
  },
  {
    id: 2,
    parent: 1,
    pattern: /local/, 
    audio: "./audios/audio1.mp3",
    end: true,
  },
  {
    id: 3,
    parent: 1,
    pattern: /remote/, 
    audio: remoteAudio(`${customEndpoint}/audio1.mp3`),
    end: true,
  },
];

Exemplo 6

doc/examples/conversation6.js

import fetch from "sync-fetch";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 6
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "",
    // Inject custom code or overwrite output 'message' property before reply
    beforeReply(from, input, output, parents) {
      // Get reply from external api and overwrite output 'message'
      const response = fetch(`${customEndpoint}/ai-reply.php/?input=${input}`).json();
      return response.message;
    },
    end: true,
  },
];

Exemplo 7

doc/examples/conversation7.js

import fetch from "sync-fetch";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 7
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Hello!",
    // Inject custom code after reply
    afterReply(from, input, parents) {
      // Send WhatApp number to external api
      const response = fetch(`${customEndpoint}/number-lead.php/`, {
        method: "POST",
        body: JSON.stringify({ number: from }),
        headers: { "Content-Type": "application/json" },
      }).json();
      console.log('response:', response);
    },
    end: true,
  },
];

Exemplo 8

doc/examples/conversation8.js

import { buttons, inp } from "../helpers";

/**
 * Chatbot conversation flow
 * Example 8
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/,
    message: "Choice one option",
    description: "choice option:",
    buttons: buttons(["Option 1", "Option 2"]),
  },
  {
    id: 2,
    parent: 1,
    pattern: /.*/,
    message: "We have received your request. Thanks.\n\n",
    beforeReply(from, input, output, parents) {
      output += `Your option: ${inp(2, parents)}`;
      return output;
    },
    forward: "[email protected]", // default number or empty
    beforeForward(from, forward, input, parents) { // Overwrite forward number
      switch (inp(2, parents)) { // Access to replies inputs by id
        case "option 1":
          forward = "[email protected]";
          break;
        case "option 2":
          forward = "[email protected]";
          break;
        default:
          forward = "[email protected]";
          break;
      }
      return forward;
    },
    end: true,
  },
];

Exemplo 9

doc/examples/conversation9.js

/**
 * Chatbot conversation flow
 * Example 9
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "",
    // Inject custom code or overwrite output 'message' property before reply
    beforeReply(from, input, output, parents, media) {
      if (media) {
        console.log("media buffer", media.buffer);
        return `You send file with .${media.extension} extension!`;
      } else {
        return "Send a picture please!";
      }
    },
    end: true,
  },
];

Exemplo 10

doc/examples/conversation10.js

import { promises as fs } from "fs";

/**
 * Chatbot conversation flow
 * Example 10
 */
 export default [
  {
    id: 1,
    parent: 0,
    pattern: /\b(?!photo\b)\w+/, // different to photo
    message: `Write "photo" for starting.`,
  },
  {
    id: 2,
    parent: [0, 1],
    pattern: /photo/,
    message: `Hi I'm a Chatbot, send a photo(s)`,
  },
  {
    id: 3,
    parent: 2,
    pattern: /\b(?!finalize\b)\w+/, // different to finalize
    message: "",
    async beforeReply(from, input, output, parents, media) {
      const uniqId =  (new Date()).getTime();
      // Download media
      if (media) {
        const dirName = "./downloads";
        const fileName = `${uniqId}.${media.extension}`;
        const filePath = `${dirName}/${fileName}`;
        await fs.mkdir(dirName, { recursive: true });
        await fs.writeFile(filePath, await media.buffer);
        return `Photo download successfully! Send another or write "finalize".`;
      } else {
        return `Try send again or write "finalize".`;
      }
    },
    goTo(from, input, output, parents, media) {
      return 3; // return to id = 3
    },
  },
  {
    id: 4,
    parent: 2,
    pattern: /finalize/,
    message: "Thank's you!",
    end: true,
  },
];

Exemplo 11

doc/examples/conversation11.js

import { inp, med } from "../helpers";
import { promises as fs } from "fs";

const menu = "Menu:\n\n" +
  "1. Send Text\n" +
  "2. Send Image\n";

/**
 * Chatbot conversation flow
 * Example 11
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /\/admin/,
    from: "[email protected]", // only respond to this number
    message: menu
  },
  {
    id: 2,
    parent: [1, 5],
    pattern: /.*/,
    message: "",
    async beforeReply(from, input, output, parents, media) {
      switch (input) {
        case "1":
          return `Write your text:`;
        case "2":
          return `Send your image:`;
      }
    },
  },
  {
    id: 3,
    parent: 2,
    pattern: /.*/,
    message: `Write "/save" to save or cancel with "/cancel".`,
  },
  {
    id: 4,
    parent: 3,
    pattern: /\/save/,
    message: "",
    async beforeReply(from, input, output, parents, media) {
      let txt = "";
      let img = null;
      let filePath = null;
      const type = inp(2, parents);
      if (type === "1") {
        txt = inp(3, parents);
      } else if (type === "2") {
        img = med(3, parents); // media from parent replies
      }
      if (img) {
        const uniqId = new Date().getTime();
        const dirName = ".";
        const fileName = `${uniqId}.${img.extension}`;
        filePath = `${dirName}/${fileName}`;
        await fs.writeFile(filePath, await img.buffer);
      } else {
        const uniqId = new Date().getTime();
        const dirName = ".";
        const fileName = `${uniqId}.txt`;
        await fs.writeFile(filePath, txt);
      }
      return `Ok, text or image saved. Thank you very much!`;
    },
    end: true,
  },
  {
    id: 5,
    parent: 3,
    pattern: /\/cancel/,
    message: menu,
    goTo(from, input, output, parents, media) {
      return 2;
    },
    clearParents: true, // reset parents
  },
];

Advançado

Múltiplos Fluxos de Conversa

Editar ./src/main.js arquivo.

import { session } from "./core";
import info from "./conversations/info";
import delivery from "./conversations/delivery";

session("chatbotSession", info);
session("chatbotSession", delivery);

Múltiplas contas

Editar ./src/main.js arquivo.

import { session } from "./core";
import commercial from "./conversations/commercial";
import delivery from "./conversations/delivery";

session("commercial_1", commercial);
session("commercial_2", commercial);
session("delivery", delivery);

Editar ./src/httpCtrl.js arquivo.

import { httpCtrl } from "./core";

httpCtrl("commercial_1", 3000);
httpCtrl("commercial_2", 3001);
httpCtrl("delivery", 3002);

Acesso ao cliente Venom

Editar ./src/main.js arquivo.

import { session } from "./core";
import conversation from "./conversations/conversation";

// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);

Agendar trabalhos

Editar ./src/main.js arquivo.

import schedule from "node-schedule"; // Add node-schedule in your project
import { session, log } from "./core";
import { jobsOptions } from "./config";
import conversation from "./conversations/conversation";

// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);

const job1 = schedule.scheduleJob(
  jobsOptions.job1.rule, // "*/15 * * * *"
  async () => {
    // custom logic example
    await chatbot.sendText("[email protected]", "test");
  }
);

teste

Testes de unidade escrevem com É

$ yarn test

Teste sua estrutura de matriz de fluxo de conversação com conversa.teste.js arquivo como exemplo.

$ yarn test src/conversations/conversation  

Solução de problemas

Atenção: Não entre no whatsapp web com a mesma conta que o chatbot usa. Isso fará com que o chatbot não consiga ouvir as mensagens.

Atenção: Você precisa de uma conta whatsapp para o chatbot e outra conta para poder falar com ele.

Repositório

🤖 Com este micro framework node.js usando Venom Bot sob o capô, você pode facilmente criar um WhatsApp Chatbot. Você só precisará editar seu fluxo de conversa em um único arquivo.
https://github.com/jfadev/jfa-whatsapp-chatbot
8 forks.
39 estrelas.
1 questões em aberto.

commits recentes:
  • Atualizar veneno 5.0.1, jfadev
  • Atualizar veneno 5.0.0, jfadev
  • fic doc, jfadev
  • Adicionar med de propriedades e correções clearParents, jfadev
  • Consertar, jfadev

: Robô, chatbot, Janela de encaixe, Framework, JS, Node.js, Whatsapp

Serviços

  • Excel2chatGPT $10.00
  • Bot Tok $45.00
  • Correção de bugs em seu aplicativo PHP Symfony $70.00 / hora
  • Correção de bugs em seu site Wordpress $70.00 / hora
  • Automação de tarefas usando Node.js $70.00 / hora

Blog

  • Como pagar com um cartão bancário em Cryptomus
  • Guia completo para iniciantes para o Bot Tok: Comandos do terminal explicados
  • Melhor site para obter visualizações no TikTok
  • Jfa Whatsapp Chatbot
  • Bot do TikTok

Explorar

  • Livre 10 Curtidas no TikTok
  • Visualizações 2K gratuitas do TikTok
  • Livre 100 Favoritos do TikTok
  • Livre 300 Compartilhamentos do TikTok
  • Comprar visualizações do TikTok
  • Livre 100 Curtidas no Instagram
Twitter
LinkedIn
YouTube
GitHub

© 2013-2025 Jordi Fernandes Alves (@jfadev)