ESP32Cam TimeLapse + NodeJS Server

ESP32Cam TimeLapse + NodeJS Server

Depois que o cara de Amarelo e Azul deixou o pacote com vários ESP32Cams aqui, veio a pressão interna em descobrir projetinhos para fazer essas camerazinhas funcionarem, um dos projetos que está destinado a elas aqui na minha “agenda de projetos” é fazer uma horta hidropônica e filmar ela, deixar uma câmera lá vendo tudo, mostrando cada centímetro de crescimento das hortaliças, pensando misso resolvi fazer um teste, daqueles bem “TESTE”, pendurei na janela com fita crepe fiz 1 sketch pro ESP mandar as imagens pra um servidorzinho em NodeJS pra chegarmos na maravilha abaixo

Primeiros testes de ESP32Cam

Ta bom, não estão assim tão maravilhoso, mas para um primeiro TimeLapse está “Ótimo” – Obrigado 😉

A ideia é que essa câmera suba no telhado, de modo consiga mirar ela para um horizonte um pouco menos poluído, porque tem muito fio logo de frente com minha casa, então depois de achar um lugar legal e conseguir deixar a câmera mais fixa, acredito que teremos alguns timelapses mais “glamorosos”, ao menos eu vou tentar! 😉

Porém a ideia aqui é registrar os aprendizados tidos até agora, facilitando o caminho pra quem quiser usar sua ESP32Cam pra se aventurar nos timelapses, tenho alguns links que compartilharei no final do post que me ajudou e com certeza poderá fazer diferença no seu setup também, ficarei mais focado nos dois pedaços do meio, a parte de tirar fotos com o ESP e como fazer elas chegarem no nosso servidor, porque a parte artistica sei que cada um tem a sua, e a minha como viram no vídeo é um tanto quanto limitada 😀 (mentira, são os fios que deixaram meu timelapse menos Monito!).

ESP32Cam

Parecia que seria fácil, porém nem tudo são flores, tirar a foto foi de fato bem tranquilo, usei o próprio sketch do exemplo, prestando atenção nas portas, no meu caso o meu HW é compatível com o AI Tinker, então depois de verificar se estava tirando as fotos fui ver como enviar essa foto para um servidor, e aqui começaram algumas coisas não tão usuais, vi alguns exemplos na internet, eles funcionavam, porém eles não mandavam nosso post no formato de formulário com uma imagem “embutida”, isso fez perder um tempo até descobrir um código que resolvesse isso. Nessa hora entrou os tutoriais da Randon Nerd Tutorials, que apresentou um sketch aonde contemplava essa forma de envio, abaixo o código completo

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Arduino.h>
#include <WiFi.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include <NTPClient.h>

// Wifi
const char* ssid     = "kaduzius";
const char* password = "umasenhabemlegal27";

// NTP
WiFiUDP udp;
NTPClient ntp(udp, "a.st1.ntp.br", -3 * 3600, 60000);
String hora;

// Upload server
String serverName    = "IP_OR_SERVER_NAME";   // REPLACE WITH YOUR Raspberry Pi IP ADDRESS
String serverPath    = "/upload";     // The default serverPath should be upload.php
const int serverPort = 3000;          // Server port

// photo config / timelapse
const int timeIntervalInMinutes = 5;
const int timerInterval         = timeIntervalInMinutes*60000; // time between each HTTP POST image
unsigned long previousMillis    = 0;                           // last time image was sent
framesize_t picSize             = FRAMESIZE_SVGA;              // Image size

WiFiClient client;

// CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

String sendPhoto() {
  String getAll;
  String getBody;

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }

  Serial.println("Connecting to server: " + serverName);

  if (client.connect(serverName.c_str(), serverPort)) {
    Serial.println("Connection successful!");
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"profile_pic\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint32_t imageLen = fb->len;
    uint32_t extraLen = head.length() + tail.length();
    uint32_t totalLen = imageLen + extraLen;

    client.println("POST " + serverPath + " HTTP/1.1");
    client.println("Host: " + serverName);
    client.println("Content-Length: " + String(totalLen));
    client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    client.println();
    client.print(head);

    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0; n<fbLen; n=n+1024) {
      if (n+1024 < fbLen) {
        client.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        client.write(fbBuf, remainder);
      }
    }
    client.print(tail);

    esp_camera_fb_return(fb);

    int timoutTimer = 10000;
    long startTimer = millis();
    boolean state = false;

    while ((startTimer + timoutTimer) > millis()) {
      Serial.print(".");
      delay(100);
      while (client.available()) {
        char c = client.read();
        if (c == '\n') {
          if (getAll.length()==0) { state=true; }
          getAll = "";
        }
        else if (c != '\r') { getAll += String(c); }
        if (state==true) { getBody += String(c); }
        startTimer = millis();
      }
      if (getBody.length()>0) { break; }
    }
    Serial.println();
    client.stop();
    Serial.println(getBody);
  }
  else {
    getBody = "Connection to " + serverName +  " failed.";
    Serial.println(getBody);
  }
  return getBody;
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  Serial.begin(115200);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);

  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  digitalWrite(4, LOW);
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  ntp.begin();
  ntp.forceUpdate();

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = picSize;  // RESOLUTION
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_CIF;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  sendPhoto();
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= timerInterval) {
    sendPhoto();
    previousMillis = currentMillis;
    hora = ntp.getFormattedTime();
    ntp.getDay();
  }
}

Agora podemos conversar sobre código e já vou destacar algumas informações aqui, na linha 20 e 21 temos as informações da conexão wifi, logo abaixo tem a configuração do NTP, que é pra ter o horário correto no nosso sistema, apesar de ainda não estar usando, se num futuro for por alguma metadata ou alguma outra informação, fica bem fácil. Depois nas linhas 28 a 31 fazemos o setup do nosso servidor, que é pra onde irá nossas fotos.
Abaixo partindo da linha 33 até a 37, fazemos a configuração do intervalo de fotos e do tamanho da imagem, eu estou usando FRAMESIZE_SVGA qué é 800×600, aqui vai uma listinha das constantes e valores:

FRAMESIZE_QQVGA,    // 160x120
FRAMESIZE_QQVGA2,   // 128x160
FRAMESIZE_QCIF,     // 176x144
FRAMESIZE_HQVGA,    // 240x176
FRAMESIZE_QVGA,     // 320x240
FRAMESIZE_CIF,      // 400x296
FRAMESIZE_VGA,      // 640x480
FRAMESIZE_SVGA,     // 800x600
FRAMESIZE_XGA,      // 1024x768
FRAMESIZE_SXGA,     // 1280x1024
FRAMESIZE_UXGA,     // 1600x1200
FRAMESIZE_QXGA,     // 2048*1536

Como você pode ver, resolução não é o problema, temos uma gama grande, vale observar que nem todos os ESP32Cams vão chegar em 2048×1536.

Padawan Perguntando

“Como assim Kadu, estamos na linha 37 e nosso código tem 210, aonde que acabou?”

Poxa, temos mais um código pra comentar aqui, se formos linha a linha, você para de ler aqui! não é!? 😛 – Agora pra você não ficar triste, varei mais algumas observações

String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"profile_pic\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";

Aqui temos a linha 75, destaco o “profile_pic” que é o nome do campo da foto no formulário, imaginando que estamos simulando o envio de um formulário com uma imagem anexa, já o “esp32-cam.jpg” é o nome do arquivo, eventualmente você pode querer mudar esses caras, de repente enviar a data no nome do arquivo, enfim, acho sábio termos essa informação na mente.

EU estava esquecendo de uma rotina mágica, e preciso comentar aqui antes de partimos pro nodeJS, se você olhar no ESP32Cam tem um Led de Flash, super forte, e eu resolvi usar ele como feedback visual para nossa conexão com a Internet, por algum motivo as vezes no primeiro boot o ESP32 não pega IP, fica lá tentando eternamente, tem várias formas de implementar um workaround, porém resolvi colocar esse led para ascender, e logo que ele pega IP, o led apaga, assim fica fácil de você sem nada por perto ver se o led está acesso, então não estranhe caso isso aconteça, só reiniciar o ESP (pode ser tomada off mesmo) 😉

Acho que agora podemos ir para o lado JS da força pequeno Padawan?

Recebendo as fotos com NodeJS

Aqui realmente podemos falar que nosso trabalho foi bastante encurtado pelas maravilhas do NodeJS, que já tras uma série de funcionalidades prontas, bastando dar aquela “juntada” maneira, estruturar algumas linhas e pronto, tudo está funcionando!

Nesse projeto usei:

  • express – Servidor Web [link]
  • express-fileupload – Um middleware que ajuda no fileupload [link]
  • e só – Me pareceu 2 pacotes simples de mais, resolvi adicionar esse pacotezinho dumb só de fuleragem 😛

O projeto conta com uma rota chamada “/upload”, nela acontece tudo, recebe o arquivo, renomeia para um nome baseado na data, evitando conflitos de nomes, copia para o diretório uploads e avisa que deu tudo certo

E na ultima linha tem o app.listen, que de fato inicia nosso servidor pra que tudo de certo. Vejamos o código

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();

const port = process.env.PORT || 3000;

function addZero(number){
  if (number <= 9)
      return "0" + number;
  else
      return number;
}

// default options
app.use(fileUpload({
    useTempFiles : true,
    tempFileDir : '/tmp/',
    debug: false
}));

app.post('/upload', function(req, res) {
  let sampleFile;
  let uploadPath;

  if (!req.files || Object.keys(req.files).length === 0) {
    return res.status(400).send('No files were uploaded.');
  }

  let date = new Date();
  let formatatedDate = (addZero(date.getDate() )) + "-" + (addZero(date.getMonth() + 1)) + "-" + date.getFullYear() + "_" + addZero(date.getHours()) + addZero(date.getMinutes()) + addZero(date.getSeconds());
  console.log(formatatedDate);

  // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
  sampleFile = req.files.profile_pic;
  uploadPath = __dirname + '\\uploads\\' + formatatedDate+".jpg";

  // Use the mv() method to place the file somewhere on your server
  sampleFile.mv(uploadPath, function(err) {
    if (err)
      return res.status(500).send(err);

    res.send('File uploaded!');
  });
});

“Simpre, né irmão!?”

Com tudo isso pronto, operando, temos as fotos, agora falta o “TimmmeeeeeeLaaaapppssseeeeeeee”

Transformando fotos em timelapse vídeos

Nesse ponto, pra quem já brincou um pouco com o FFMPEG sabe que ele será nosso Gandalf, fumará as fotos e produzira a magia do vídeo de fotos aceleradas 🙂

O comando que usei para ter a saída do vídeo acima, foi o seguinte, na pasta uploads, aonde estão todas as fotos:

$ ffmpeg -framerate 30 -pattern_type glob -i "./*.jpg" -s:v 800x600 -c:v libx264 -crf 17 -pix_fmt yuv420p my-timelapse.mp4

Nesse comando tempos algumas observações a fazer, a primeira é o -framerate, que é a quantidade de quadros que o vídeo terá por segundo, número acima de 24 fará vídeos menos travados, já o -pattern_type é pra que ele pegue todos jpg do diretório corrente, depois temos a resolução, que está igual a que eu usei nas fotos, alguns outros parametros e no final o nome do arquivo my-timelapse.mp4

Sei que tem ffmpeg pra windows, porém eu instalei o Ubuntu no meu Windows (Valeu por isso Microsoft) e então a coisa foi bem mais tranquila, já que as vezes pra esses programas funcionarem no windows puro, da trabalho.

Aqui está “quase” tudo que aprendi fazendo o meu primeiro Timelapse com o ESP32Cam + NodeJS + Express + Express-uploadfile, e gostaria de deixar compartilhado e registrado aqui, porém como disse lá no começo, aqui uma série de links que tem ainda mais informação, incluindo até como fazer o vídeo ficar ainda mais “empolgante”

+Links

  • Random Nerd Tutorials – Já apresentado no artigo, daqui veio o código do ESP que envia a imagem da maneira que precisamos
  • Express-Fileupload – Todo nosso servidor nodeJS se resume ao uso do Express com esse middleware, logo o README do github foi nosso guia, e aqui temos mais informações, podendo ainda dar uma turbinada no nosso server
  • Handling file uploads in nodejs with express and multer – Esse foi nosso primeiro código, tentamos fazer ele funcinar na live, porém como o código do nosso ESP32 não estava legal, acabou que não usamos, porém é um excelente tutorial, vale a pena a lida!
  • Creating a Time-Lapse Video Through the Command-Line (Using FFmpeg) – Talvez esse seja o link mais importante, e é exatamente por isso que ele é o último, se não você iria perder todas as piadas que eu fiz, e eu não iria gostar :P. Nesse tutorial tem as informações do FFMPEG, com as dicas do que cada parâmetro faz, como deixar o vídeo mais rápido ou mais devagar, e ainda como aplicar alguns efeitos de crop e pan, pra dar aquela impressão que a câmera está se mexendo, quando conseguir pendurar minha câmera no telhado, vou aplicar essas técnicas pra ver se de repente meu vídeo fica um pouco menos feio – torçam por mim!

Chegou aqui, deixa um comentário ai 😉

Meu primeiro BOT no Twitch.tv

Olá pessoinha, bom te ver por aqui 😉

Hoje vou mostrar como você pode criar um BOT para seu canal na twitch.tv de maneira bem simples, utilizando node.js, e você pode até não entender muito de programação, mas no final, lá estará um “Robozinho” bem simpático dando OI para quem aparecer na sua live.

Eu sei que tem vários serviços que você pode ter BOTs, com diversos recursos bem interessantes, porém aqui é “Faça Você Mesmo” na veia, então vamos juntos por a mão na massa, aprender duas ou três coisas e criar nosso próprio C3PO

Vamos entender como os BOTs funcionam, para compreender o que estaremos fazendo

O que vamos fazer é toda essa estrutura ai, e o que precisamos entender é que existe os servidores da twitch.tv, que recebem todas as mensagens, seja mensagens do chat, algum evento como uma raid, ou a notificação que alguém se inscreveu no seu canal, tudo isso ele coloca em um “pacote” e nos manda, eu represente esse pacote como sendo o Evento na imagem acima, esse evento pode ser um montão de coisas, uma listinha:

  • Quando alguem manda Bits (cheer)
  • Quando alguém entra no seu canal (join)
  • Quando o canal entra em modo somente Emote (emoteonly)
  • Quando seu bot recebe um sussurro (whisper)
  • Veja aqui a lista completa (link)

Quando nós recebemos esse pacote, precisaremos fazer alguns tratamentos para saber que tipo de informação veio nele, e com posse dessa informação determinar quais ações deveremos tomar, esse passo eu representei como “Tratar Mensagem” e “Executar ação” no desenho, você pode observar que os dois fluxos em branco, seguem esse padrão de comportamento.

Preparando o ambiente

Traduzindo isso pra – programa de computador – nós vamos usar algumas ferramentas pra nos ajudar a desenrolar essa tarefa, nosso bot vai rodar em nosso computador pessoal, o que significa que todo inicio de live precisaremos iniciar ele, e no final da live, parar ele. Como ele vai rodar em nosso computador, e nós mesmos iremos “botar a mão na massa” nossa primeira ferramenta será um Editor de Código, utilizaremos o Visual Studio Code, da microsoft, gratuito e muito bom! Você pode clicar aqui para fazer o download e a instalação dele.

Depois de instalar ele, nós já temos onde “digitar” os comandos mágicos que darão vida ao nosso bot, agora nos falta fazer a instalação da “linguagem de programação” que irá entender esses comandos mágicos e traduzir pra algo que só o computador entende, vamos usar o NodeJS para fazer isso, e você pode clicar aqui para fazer a instalação dele.

Estamos com quase tudo pronto, já temos o editor e a linguagem de programação, agora vamos baixar uma biblioteca, que é responsável por deixar nossa vida mais simples, essa biblioteca é um conjunto de códigos feito por outras pessoas, de maneira a poupar bastante do nosso trabalho, com isso com apenas poucas linhas seremos capazes de dar vida ao nosso C3PO.

Agora é mão na massa! ou no terminal, como preferir!

Você deverá procurar ai no seu windows (apertando a tecla windows), “Node.js Command Prompt“, isso vai abrir o terminal (aquela tela preta com letrinhas que os filmes costumam atribuir a hackers…. agora você é um deles), vamos criar um diretório e então começar a fazer nosso bot tomar vida, para isso digite os comandos da coluna “Comando a digitar”, um por vez.

Comando a digitar:O que o comando está fazendo
mkdir meuprimeirobotCriando uma Pasta chamada “meuprimeirobot”
cd meuprimeirobotEntrando na pasta que acabamos de criar
npm i tmi.jsInstala a biblioteca da twitch.tv
code .Abrindo o Editor (VS Code) com os arquivos que estão nessa pasta

Depois de executar a ultima linha, irá abrir o VS Code, sua tela deve parecer com a tela abaixo

Talvez seu editor não tenha tantos icones na barra lateral da esquerda, não tem problemas, isso ai são extensões que você pode instalar, porém não precisaremos dela agora, então tocaremos o barco 😉

Logo no espaço embaixo do escrito “MEUPRIMEIROBOT” clique com o botão direito do mouse e depois clique em “New File”, coloque “bot.js” (sem as aspas) e criamos nosso arquivo.

Para sabermos se estamos no caminho correto, vamos digitar nosso primeiro programa, o “Olá Planeta”, para isso digite o código abaixo lá no editor (como na imagem abaixo do código)

console.log("Olá planeta!");

Feito isso vamos rodar nosso programa, volte novamente a nossa tela hacker, ou melhor dizendo, volte ao terminal e execute o comando abaixo:

node bot.js

Não esqueça de salvar o arquivo 😉

Esse comando informa pro computador que ele vai usar a linguagem de programação que nós instalamos para “ler” o programa que acabamos de escrever, esse programa só imprime um Olá Planeta! na tela, e é isso que queremos ver, se aparecer isso pra ti, estamos no caminho, se não, deixe um comentário e tentarei te ajudar 😉

Agora temos o Pão, temos a faca, mas precisamos escrever o queijo, se é que você me entende.

Temos aqui dois links que serão nossos guias para a criação do Bot:

O primeiro é a informação da Twitch.tv informando como os chats funcionam, tem umas informações legais que podem complementar o que estou escrevendo aqui, uma informação importante é que lá nos temos “esse link -> https://twitchapps.com/tmi/“, que nos leva a página que usaremos logo mais ;), Já a segunda é a documentação da biblioteca que nós vamos utilizar, vou linkar aqui a página com todos os eventos que poderemos usar.

Vamos agora digitar o programa abaixo no nosso arquivo bot.js

const tmi = require('tmi.js');
const client = new tmi.Client({
	options: { debug: true, messagesLogLevel: "info" },
	connection: {
		reconnect: true,
		secure: true
	},
	identity: {
		username: 'bot-name',
		password: 'oauth:my-bot-token'
	},
	channels: [ 'my-channel' ]
});
client.connect().catch(console.error);
client.on('message', (channel, tags, message, self) => {
	if(self) return;
	if(message.toLowerCase() === '!hello') {
		client.say(channel, `@${tags.username}, heya!`);
	}
});

Nesse código temos algumas informações em negrito, e algumas coloridas, eu fiz uma lambança, mas espero que você consiga compreender, as informações em negrito precisaremos mudar, onde está ‘bot-name‘ você deverá substituir pelo seu usuário da twitch.tv, já aonde está “oauth:my-bot-token” você deverá alterar para a chave que você vai conseguir clicando no link que falei ali encima (https://twitchapps.com/tmi/), ao acessar a página clique em connect, ai então você vai receber a senha que você deve colocar nesse lugar, e em my-channel, você muda para o nome do seu canal.

No código acima também destaquei em azul (mais escuro) o comando que irá observar os eventos client.on( , logo depois em azul mais claro destaque message, esse é o nome do evento que vamos utilizar, e mais abaixo em vermelho destaquei message.toLowerCase() === ‘!hello’, essa linha toda, incluindo o if é quem irá verificar se a mensagem que recebemos contem a palavra “!hello“, se tiver essa mensagem, então a linha de baixo diz, mande uma mensagem client.say…, para o canal que você configurou e com a mensagem que ali está, no caso nosso programa faz uma mágica, você já deve estar imaginando que mágica é essa, tenho certeza 😉

Nesse ponto conforme prometido lá na primeira linha desse post, nosso bot já é capaz de dizer Oi, em inglês porque o bot é poliglota :P, para todos seus convidados que digitarem o comando !hello

Se você quer que o bot responda a um outro comando, então você deve duplicar as linhas e dizer qual mensagem o bot vai mandar, veja o código com algumas linhas alteradas abaixo

const tmi = require('tmi.js');
const client = new tmi.Client({
	options: { debug: true, messagesLogLevel: "info" },
	connection: {
		reconnect: true,
		secure: true
	},
	identity: {
		username: 'bot-name',
		password: 'oauth:my-bot-token'
	},
	channels: [ 'my-channel' ]
});
client.connect().catch(console.error);
client.on('message', (channel, tags, message, self) => {
  if(self) return;
    if(message.toLowerCase() === '!hello') {
      client.say(channel, `@${tags.username}, heya!`);
  }
  if(message.toLowerCase() === '!kaduzius') {
		client.say(channel, `@${tags.username}, vai lá na live do Kadu, ele me ajudou com esse comando!`);
  }
  if(message.toLowerCase() === '!instagrma') {
		client.say(channel, `Visite meu instagram é https://www.instagram.com/awesome.photographers!`);
  }
});

Dessa vez escolhi uma cor bem legal (:P)para destacar as duas linhas a mais representando os novos comandos que adicionamos

Po Kadu, você disse que está pronto, mas… cade o bot rodando no meu canal ?

Rodar o bot, agora nós iremos!

Para isso basta executar node bot.js como fizemos lá encima no Olá Planeta!

Se tudo certo estiver, bot no canal você vai ter!

Agradecimento especial aos streamers VictorZonta e Tairritadotio por fazer os testes no bot 😉

Feras, vocês são!