SHELLBOT |
No dia 10/03/2017 realizei uma postagem divulgando um pequeno projeto chamado ShellBot, uma API desenvolvida em shell script para a criação de bots na plataforma Telegram. Desde então ela vem ganhando notoriedade e por consequência sua utilização vem crescendo exponencialmente. Após essa data novas atualizações foram disponibilizadas e algumas modificações realizadas para melhor atender os requisitos de desempenho e estabilidade do script.
Uma atualização ocorrida na versão 3.8 adicionava o suporte a criação de Threads, melhorando o desempenho no tratamento das requisições, em contra partida impactava o usuário no quesito usabilidade e inviabilizava o código modelo postado anteriormente. Devido a isso estou realizando uma atualização na postagem afim de abordar com maiores detalhes os tópicos relevantes do projeto.
Informo que a API continuará à receber atualizações sem impactar a experiência do usuário.
1. Criando o bot no Telegram
Antes de iniciar o download da API é necessário criar o seu bot utilizando o bot oficial do Telegram clicando aqui @BotFather.
Clique no botão INICIAR e faça os procedimentos a seguir:
1. Envie o comando /newbot
2. Envie o nome do seu bot.
3. Se o nome informado for válido, será solicitado o username.
4. Se o procedimento tiver êxito será retornado o número do token com o qual faremos a comunicação.
2. Baixando o SHELLBOT.
O projeto está disponível no Github oficial do blog.
Clonando o repositório do git.
$ git clone https://github.com/shellscriptx/ShellBot.git && cd ShellBot
3. Adicionando ao seu projeto.
Copie o arquivo ShellBot.sh para a pasta do seu projeto.
$ cp ShellBot.sh ~/pasta_projeto
4. Importando API
Para utilizar os métodos da api é necessário importar o source em seu script utilizando a seguinte linha de comando no inicio do código.
#!/bin/bash
#
# INICIO
# bot_script
source ShellBot.sh
5. Inicializando o bot
A inicialização é altamente necessária a para comunicação entre seu código e o servidor do Telegram e é realiza por meio do método ShellBot.init com a sintaxe a seguir:
ShellBot.init --token <num_token>
Onde
num_token
é o número de identificação exclusivo do bot obtido durante a etapa 1.Considere o código abaixo com um token hipotético.
# Importando API
#
# INICIO
# bot_script
# Importando API
source ShellBot.sh
# Token do bot
bot_token='123456789:dAe0QdEXXMoEd8pEBympiBQ0iNNQOx1YPix'
# Inicializando o bot
ShellBot.init --token "$bot_token"
# Imprime o username do bot.
ShellBot.username
Salve as alterações e execute o script. Se tudo estiver correto será impresso na saída padrão o
username
do bot, caso contrário uma mensagem de erro será apresentada.Saída:
$ ./bot_script
grupox_bot # Nome do usuário do bot.
6. Obtendo atualizações
Todas as requisições feitas ao bot são armazenas em uma lista de objetos que contém as atualizações e que podem ser obtidas através do método ShellBot.getUpdates.
Sintaxe:
ShellBot.getUpdates --limit <max> --offset <update_id> --timeout <seconds>
limit - Limita o número de atualizações a serem recuperadas. Valores entre 1-100 são aceitos. Valor padrão é 100. (Opcional)
offset - Identificador da primeira atualização a ser retornada. Deve ser maior que os identificadores de atualizações recebidas anteriormente. Por padrão, as atualizações que começam com a atualização mais antiga não confirmada são retornadas. Uma atualização é considerada confirmada assim que o ShellBot.getUpdates é chamado com um deslocamento maior do que o update_id. O deslocamento negativo pode ser especificado para recuperar as atualizações a partir da atualização offset do final da fila de atualizações. Todas as atualizações anteriores serão esquecidas. (Opcional)
timeout - Tempo limite em segundos em espera por atualizações. O padrão é 0, ou seja, o acerto curto habitual. Deve ser positivo, a consulta curta deve ser usada somente para fins de teste. (Opcional)
Para obter constantemente as atualizações é necessário que o método seja executado continuamente e para isso é preciso inseri-lo em um loop infinito.
# Importando API
#
# INICIO
# bot_script
# Importando API
source ShellBot.sh
# Token do bot
bot_token='123456789:dAe0QdEXXMoEd8pEBympiBQ0iNNQOx1YPix'
# Inicializando o bot
ShellBot.init --token "$bot_token"
# Imprime o username do bot.
ShellBot.username
# loop
while :
do
# Obtendo atualizações
ShellBot.getUpdates --limit 100 --offset $(ShellBot.OffsetNext) --timeout 30
done
7. Tratando as requisições
A api possui uma gama de variáveis reservadas onde são armazenados os valores do objetos obtidos na chamada do método ShellBot.getUpdates. Para mais informações consulte a documentação na wiki.
As variáveis são declaradas como sendo do tipo array indexado, ou seja, uma estrutura de dados que armazena uma coleção de elementos de tal forma que cada um dos elementos possa ser identificado pelo seu índice e que são listados utilizando a função ShellBot.ListUpdates.
O código baixo demonstra a criação de um comando chamado
/ajuda
e associa um evento ao mesmo.# Importando API
#
# INICIO
# bot_script
# Importando API
source ShellBot.sh
# Token do bot
bot_token='123456789:dAe0QdEXXMoEd8pEBympiBQ0iNNQOx1YPix'
# Inicializando o bot
ShellBot.init --token "$bot_token"
# Imprime o username do bot.
ShellBot.username
# loop
while :
do
# Obtendo atualizações
ShellBot.getUpdates --limit 100 --offset $(ShellBot.OffsetNext) --timeout 30
# Lista os índices das atualizações.
for id in $(ShellBot.ListUpdates)
do
# Bloco de instruções
(
# Verifica se a mensagem é do tipo comando.
if [[ ${message_entities_type[$id]} == bot_command ]]
then
# Remove o sufixo contendo o username do bot e o '@' inclusive, extraindo somente o comando.
case ${message_text[$id]%%@*} in
'/ajuda')
# Envia mensagem ao remetente.
ShellBot.sendMessage --chat_id ${message_chat_id[$id]} \
--text "Olá *${message_from_first_name[$id]}*, em que posso ajudar?" \
--parse_mode markdown
;;
esac
fi
) & # Thread
done
done
Observe que os valores das variáveis são acessados pelo índice do elemento armazenado em
id
. A função ShellBot.ListUpdates retorna todos os índices disponíveis e a cada interação o valor de id
é atualizado para o próximo elemento.Enviando o comando
/ajuda
para visualizar o resultado.Note que ambos os comandos foram aceitos, pois o uso da expansão
${message...%%@*}
remove o sufixo @grupox_bot
e avalia somente o expressão /ajuda
.Função de boas-vindas
Acredito que para qualquer grupo essa seja a função fundamental de um bot, enviar uma mensagem de boas-vindas aos novos membros, mostrando que sua presença não passou desapercebida e até mesmo apresentar informações prévias sobre o tema. Vou implementar o código anterior com essa função.
# Importando API
#
# INICIO
# bot_script
# Importando API
source ShellBot.sh
# Token do bot
bot_token='123456789:dAe0QdEXXMoEd8pEBympiBQ0iNNQOx1YPix'
# Inicializando o bot
ShellBot.init --token "$bot_token"
# Imprime o username do bot.
ShellBot.username
# Função para envio da mensagem de boas-vindas
msg_bem_vindo()
{
local msg
# Texto da mensagem
msg="🆔 [@${message_new_chat_member_username[$id]:-null}]\n"
msg+="🗣 Olá *${message_new_chat_member_first_name[$id]}*"'!!\n\n'
msg+="Seja bem-vindo(a) ao *${message_chat_title[$id]}*.\n\n"
msg+='`Se precisar de ajuda ou informações sobre meus comandos, é só me chamar no privado.`'"[@$(ShellBot.username)]"
# Envia a mensagem de boas vindas.
ShellBot.sendMessage --chat_id ${message_chat_id[$id]} \
--text "$(echo -e $msg)" \
--parse_mode markdown
return 0
}
# loop
while :
do
# Obtendo atualizações
ShellBot.getUpdates --limit 100 --offset $(ShellBot.OffsetNext) --timeout 30
# Lista os índices das atualizações.
for id in $(ShellBot.ListUpdates)
do
# Bloco de instruções
(
# Chama a função 'msg_bem_vindo' se o valor de 'message_new_chat_member_id' não for nulo.
[[ ${message_new_chat_member_id[$id]} ]] && msg_bem_vindo
# Verifica se a mensagem é do tipo comando.
if [[ ${message_entities_type[$id]} == bot_command ]]
then
# Remove o sufixo contendo o username do bot e o '@' inclusive, extraindo somente o comando.
case ${message_text[$id]%%@*} in
'/ajuda')
# Envia mensagem ao remetente.
ShellBot.sendMessage --chat_id ${message_chat_id[$id]} \
--text "Olá *${message_from_first_name[$id]}*, em que posso ajudar?" \
--parse_mode markdown
;;
esac
fi
) & # Thread
done
done
Repare que na chamada no método ShellBot.sendMessage no parâmetro
--text
, executei o comando $(echo -e $msg)
ao invés de só passar o nome da variável. Por que ? Porque o corpo da mensagem armazenado em $msg
contém o caractere de formatação \n
(nova linha) que precisa ser interpretado e para isso é necessário o seu uso. Caso contrário seria impresso de forma literal. Utilize-o sempre que precisar formatar o texto.Adicionado um novo membro e visualizando o resultado.
Modo monitor
Todo o processo de carregamento dos objetos, leitura e atribuição de valores ocorre em modo silencioso, exibindo apenas na saída padrão o retorno dos métodos. O modo monitor quando ativado lista na saída padrão todas as variáveis inicializadas referentes a mensagem tratada, seja ela recebida no privado, grupo ou canal. Esse recurso oferece ao usuário uma visualização dinâmica de eventos, facilitando assim a aplicação de testes.
Para ativar utilize o parâmetro
-m
ou --monitor
na chamada do método ShellBot.init.ShellBot.init --token <seu_token> --monitor
Saída padrão após o envio de uma mensagem.
=================== MONITOR ===================
Data: sáb set 16 09:06:31 -03 2017
Script: meu_bot.sh
Bot (nome): ▪️❌▫️
Bot (usuario): beta_bot
Bot (id): 123456789
-----------------------------------------------
Mensagem: 1
-----------------------------------------------
update_id = '870716554'
message_date = '1505563591'
message_message_id = '29159'
message_text = '/comando_teste'
message_chat_all_members_are_administrators = 'false'
message_chat_id = '-1122334455'
message_chat_title = 'grupoteste'
message_chat_type = 'group'
message_entities_length = '14'
message_entities_offset = '0'
message_entities_type = 'bot_command'
message_from_first_name = 'SHAMAN'
message_from_id = '11443322'
message_from_is_bot = 'false'
message_from_language_code = 'pt-BR'
message_from_username = 'x_SHAMAN_x'
As variáveis são exibidas com seus respectivos valores e separadas por mensagem.Limpando histórico de mensagens (flush)
Todas as mensagens recebidas ficam salvas em uma longa lista contendo todos os objetos do corpo da mensagem e que podem ser acessadas utilizando seu update_id. Quando o bot fica offline por muito tempo e se durante esse período forem realizadas inúmeras requisições ao mesmo, tais requisições são colocadas em fila aguardando um futuro tratamento ao retorno do serviço. Mas em alguns casos isso pode ser um problema, considerando o número de mensagens contidas na fila e possivelmente gerando um retorno excessivo de informações na linha do tempo.
O recurso flush descarta todas as mensagens recebidas desde sua inatividade até o presente momento. Para ativar o recurso utilize o parâmetro
-f
ou --flush
na chamada do método ShellBot.init.ShellBot.init --token <seu_token> --flush
Veja o retorno do método ShellBot.init após a restauração do serviço.
123456789|grupox_bot|▪️❌▫️|870716600|870716628
Os dois últimos campos contém o flush_first_id
e o flush_last_id
que representam um intervalo de ids que foram ignorados. Em nosso caso especifico foi um total de 28 mensagens.Monitorando serviços do sistema.
Um cenário bastante interessante para essa implementação seria na auditoria e monitoração de um servidor. Empenhando a função de notificar a disponibilidade de um determinado serviço ou serviços ao administrador do sistema.
O exemplo abaixo ilustra uma simples aplicação que monitora o serviço ssh e notifica o usuário caso ocorra eventos
stop
ou start
no serviço.#!/bin/bash
# script: Monitor.sh
#
# Para melhor compreensão foram utilizados parâmetros longos nas funções; Podendo
# ser substituidos pelos parâmetros curtos respectivos.
# Importando API
source ShellBot.sh
# Token do bot
bot_token='123456789:dAe0QdEXXMoEd8pEBympiBQ0iNNQOx1YPix'
# Inicializando o bot
ShellBot.init --token "$bot_token"
ShellBot.username
# Serviço
service=sshd
# default
old=0
# Monitorar
while :
do
# Testa o serviço e salva a saída em 'output'.
output=$(systemctl -q status $service)
# Atualiza o status.
err=$?
# Se o serviço for interrompido ou restaurado, envia uma mensagem notificando o usuário.
(( ((err ^ old)) != 0 )) && ShellBot.sendMessage --chat_id 123456789 \
--text "$(echo -e "<b>Serviço: sshd</b>\n\n<b>Detalhes:</b>\n\n$output")" \
--parse_mode html
# Salva o status anterior
old=$err
# delay
sleep 2
done
Interrompendo o serviço.
$ sudo systemctl stop sshd
Resultado:
Iniciando serviço.
$ sudo systemctl start sshd
Resultado:
Isso é apenas uma simples e humilde aplicação mas que nos fornece um deslumbre do que podemos criar. Quem sabe um bot autômato capaz de realizar intervenções pré-definidas ou até mesmo fornecer ao administrador opções de atuação para determinados tipos de problemas. Cabe a sua imaginação decidir que caminho tomar e como chegar até lá.
Espero que esse post seja útil para você e para mais informações não deixe de consultar a documentação do projeto na wiki clicando aqui.
Participe deixando
seu comentário ou sugestão sobre o assunto.
Gostaria de apoiar o
trabalho da comunidade? clique aqui
Showw
ResponderExcluirVlw jovem, é nós. Tentando sempre criar alguma coisa. Kkkkkkk
ExcluirValeu SHAMAN, ficou show de bola o texto, simples e direto!
ResponderExcluirObrigado. Espero que possa ser útil para a galera é quem sabe novos bots em Shell venham surgir.
ExcluirParabéns pelo trabalho.
ResponderExcluirTentei fazer o clone com "git clone git@github.com:shellscriptx/ShellBot.git" e deu
"Permission denied (publickey).
fatal: Could not read from remote repository.
"
Com "git clone https://github.com/shellscriptx/ShellBot.git
" deu certo.
Valeu.
git clone https://github.com/shellscriptx/ShellBot.git
Obrigado pela observação e a correção já foi realizada na postagem.
ExcluirEste comentário foi removido pelo autor.
ResponderExcluirOlá,
ResponderExcluirAcabei de testar o script e achei super prático, agora gostaria de saber se existe a possibilidade dele executar outro script. Ex : quando eu executar o comando /uptime ele retornar a informação do servidor ?
Toooooooooooooooooooooop, muito show
ResponderExcluir