Desenvolvimento de bot Python TamTam


Olá Habr! Deixe-me apresentar: meu nome é Sergey Agaltsov e sou um "programador na vida". Isso significa que eu sou gerente de TI há muito tempo, e nem um programador de profissão, mas uso a programação constantemente, tanto na minha atividade principal quanto como hobby. Como um dos meus ex-chefes costumava dizer - "Seryoga! Você entrou novamente na programação!" É verdade que não posso dizer que ele ou qualquer outra pessoa tenha ficado muito insatisfeito com isso.


Após o surgimento da API Bot no messenger TamTam, eu, como programador verdadeiro e, portanto, preguiçoso, criei duas bibliotecas Python para trabalhar com ela:


  • API do cliente aberto (doravante - OAC) - gerou inicialmente usando o OpenAPI Generator baseado no esquema da API e adaptou-o levando em consideração os recursos do gerador;
  • o shell para esse cliente é o TamTamBot (daqui em diante - TTB), que simplifica o trabalho com o OAC.

Portanto, havia um certo TamTam Python SDK.


Fiz isso antes de tudo "por mim mesmo, pela alma", mas também sugeri que a comunidade TamTam, se desejado, a usasse. Mas, como você sabe, nem uma única boa ação fica impune - as pessoas pediram para escrever um artigo de treinamento. E aqui estou eu com este artigo. Nele, mostrarei como desenvolver um bot simples usando essas bibliotecas.


Desafio


Desenvolva um bot projetado para simplificar as ações dos desenvolvedores de bot. O bot deve funcionar no modo de pesquisa permanente do estado bot-api (pesquisa longa). Neste artigo, o bot será treinado para mostrar o interior da mensagem enviada a ele e também configurado para corresponder à funcionalidade desenvolvida.


Entende-se que o leitor instalou o Python 3 , git , conectado ao ambiente de desenvolvimento PyCharm (o ambiente de desenvolvimento pode ser diferente, mas a história será baseada no PyCharm). É desejável entender o básico do POO.


Obtendo um token de bot


O token é obtido através de uma chamada para o bot especializado @PrimeBot


Encontramos este bot no TamTam, digite o comando / create e responda às perguntas:


  • Digite o nome abreviado exclusivo do bot em letras latinas - este é o nome de usuário do bot pelo qual ele estará disponível via @ ou por um link no formato https://tt.me/username . Não há restrições especiais para o nome de usuário. Em particular, a palavra bot é opcional.
  • Digite um nome - este é o nome de exibição do bot. Aqui você já pode usar o alfabeto cirílico.

Se tudo for digitado corretamente, o bot criado será adicionado aos contatos e, em troca, receberemos um token - uma sequência de caracteres no formato: HDyDvomx6TfsXkgwfFCUY410fv-vbf4XVjr8JVSUu4c.


Configuração inicial


Mostrar

Crie um diretório:


 mkdir ttBotDevHelper 

Vá para ele:


 cd ttBotDevHelper/ 

Inicializamos o repositório git:


 git init 

Faça o download das bibliotecas necessárias, adicionando-as como sub-módulos do git:


 git submodule add https://github.com/asvbkr/openapi_client.git openapi_client git submodule add https://github.com/asvbkr/TamTamBot.git TamTamBot 

Abrimos o diretório criado no PyCharm (por exemplo, no explorer, no menu de contexto "Abrir pasta como projeto PyCharm") e criamos um arquivo que nosso bot conterá - Arquivo / Novo / Arquivo Python. Na caixa de diálogo exibida, digite o nome - ttBotDevHelper e responda positivamente à questão de adicionar ao git.


Agora precisamos criar um ambiente virtual para o nosso projeto.


Para criar um ambiente virtual, selecione Arquivo / Configurações e selecione a subchave Intérprete do projeto na guia Projeto. Em seguida, à direita, clique no ícone de roda dentada e selecione Adicionar:


imagem


O PyCharm oferecerá sua própria opção de acomodação.


imagem


Faz sentido concordar com ele.


Após criar o ambiente virtual, a tela anterior será aberta, mas já conterá informações sobre o ambiente criado. Nesta tela, você precisa instalar os pacotes necessários clicando no ícone "+" à direita e digitando os nomes dos pacotes:


  • pedidos
  • seis

Em seguida, adicionamos o arquivo .gitignore ao projeto, excluindo arquivos que não são necessários no git, com o seguinte conteúdo:


 venv/ .idea/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class *.log *.log.* .env ttb.sqlite3 

Adicione uma variável de ambiente chamada TT_BOT_API_TOKEN , na qual indicamos o valor do token do nosso bot recebido de https://tt.me/primebot e reinicie o PyCharm.


(!) Em vez de adicionar uma variável de ambiente diretamente ao ambiente do SO, o PyCharm utiliza de maneira ideal um arquivo .env especial. Sua configuração será discutida abaixo.


Parabéns, agora você pode prosseguir para a parte mais interessante - escrevendo seu bot.


Lançamento simples de bot


Abra o arquivo ttBotDevHelper.py e escreva as primeiras linhas:


 # -*- coding: UTF-8 -*- from TamTamBot.TamTamBot import TamTamBot class BotDevHelper(TamTamBot): pass 

Aqui criamos nossa classe de bot com base na classe TamTamBot.


PyCharm sugere que a classe BotDevHelper contém métodos abstratos que precisam ser implementados. Pressione Alt-Enter no nome da classe, selecione "Implementar métodos abstratos", selecione todos os métodos (2 deles) propostos pelo PyCharm e clique em OK. Como resultado, dois métodos de propriedade vazios serão adicionados: token e descrição. Modificamos o código resultante da seguinte maneira:


 # -*- coding: UTF-8 -*- import os from TamTamBot.TamTamBot import TamTamBot from TamTamBot.utils.lng import set_use_django class BotDevHelper(TamTamBot): @property def token(self): return os.environ.get('TT_BOT_API_TOKEN') @property def description(self): return '       .\n\n' \ 'This bot is an helper in the development and management of bots.' if __name__ == '__main__': set_use_django(False) BotDevHelper().polling() 

A propriedade token retorna o token do nosso bot, cujo valor é obtido da variável de ambiente TT_BOT_API_TOKEN . A propriedade description retorna uma descrição estendida do nosso bot, que será exibida aos usuários.


O código no final do arquivo é necessário para executar nosso bot no modo de pesquisa de status.


Observo que a classe base TamTamBot envolve o uso do servidor web django para trabalhar no modo gancho da web. Mas agora a tarefa é mais simples e não precisamos de django, que é o que estamos relatando na linha set_use_django(False) . Aqui, o método polling() é chamado para o objeto de nossa classe, o que garante a operação no modo necessário.


O mínimo necessário é feito. Este código já está funcionando. Execute-o para executar. Para fazer isso, pressione a combinação de teclas Ctrl-Shift-F10.


Se você não adicionou uma variável de ambiente anteriormente, diretamente ao sistema operacional, ocorrerá um erro com a mensagem "No access_token" na inicialização. Para corrigi-lo, configure o PyCharm para usar o arquivo .env.


Mostre como

Crie um arquivo de texto .env. Seu conteúdo em nosso caso deve ser o seguinte:


 TT_BOT_API_TOKEN=__ 

Agora você precisa conectá-lo à configuração de inicialização no PyCharm:


Selecionamos a configuração Executar / Editar e, na guia EnvFile, conectamos nosso arquivo .env:


imagem


Depois clique em Aplicar.


Depois de iniciar o bot, você pode ir ao TamTam, abrir um diálogo com o bot e clicar no botão "Iniciar". O bot reportará informações sobre suas superpotências ocultas. Isso significa que o bot está funcionando. Enquanto o bot está trabalhando no modo de demonstração, no qual 4 comandos estão disponíveis. Basta vê-los.


Apesar da opinião pronunciada do bot sobre sua frieza, ele timidamente sugere que até agora ele não pode fazer nada. Ensinar a ele tudo o necessário para a conquista do mundo é nossa tarefa.


Recebendo uma mensagem de origem e enviando uma mensagem de resposta com uma representação interna da mensagem de origem


Bloquearemos o método receive_text() , cujo controle é transferido ao enviar texto para o chat com o bot:


  def receive_text(self, update): res = self.msg.send_message(NewMessageBody(f' : {update.message}', link=update.link), user_id=update.user_id) return bool(res) 

O objeto de update da classe UpdateCmn , que é passado para esse método, contém várias informações úteis e, em particular, tudo o que precisamos agora:


  • update.message - um objeto que contém a própria mensagem;
  • update.link - link de resposta pronto para esta mensagem;
  • update.user_id - identificador do usuário que enviou a mensagem.

Para enviar uma mensagem do bot, usamos a variável self.msg , que contém o objeto MessagesApi que implementa a funcionalidade descrita na seção de mensagens da descrição da API . Este objeto contém o método send_message() que send_message() , que fornece o envio de mensagens. No mínimo, esse método deve receber um objeto da classe NewMessageBody e o destino - o ID do usuário, no nosso caso.


Por sua vez, um objeto da classe NewMessageBody nesse caso é criado transmitindo uma representação textual do objeto da mensagem de origem e um link de resposta para a mensagem de origem.


Reiniciamos nosso bot e verificamos em um diálogo com o bot que o bot gera uma resposta a qualquer uma de nossas mensagens contendo a representação interna do objeto de mensagem de origem.


O código fonte desse estado está aqui .


Adicionando um novo comando bot com um parâmetro - mostrando a representação interna da mensagem por seu identificador


Ao desenvolver bots, muitas vezes é necessário examinar a representação interna de uma mensagem usando um ou mais identificadores de mensagens conhecidos (id da mensagem). Adicione essa funcionalidade ao nosso bot. Para fazer isso, primeiro, retiramos em um método separado a funcionalidade de gerar informações sobre a representação interna de mensagens:


  def view_messages(self, update, list_mid, link=None): res = False msgs = self.msg.get_messages(message_ids=list_mid) if msgs: for msg in msgs.messages: r = self.msg.send_message(NewMessageBody(f' {msg.body.mid}:\n`{msg}`'[:NewMessageBody.MAX_BODY_LENGTH], link=link), user_id=update.user_id) res = res or r return res 

Neste método, passamos uma lista de meados.


Para obter objetos de mensagem, usamos o método self.msg.get_messages , que retorna uma lista de objetos na propriedade messages.


Além disso, uma representação textual de cada uma das mensagens recebidas é enviada ao nosso diálogo em mensagens separadas. Para evitar erros, o texto da mensagem gerada é truncado pela constante do tamanho máximo da mensagem - NewMessageBody.MAX_BODY_LENGTH .


Em seguida, adicione um método que processa o comando. Vamos chamá-lo de vmp . Você pode passar a lista intermediária para o comando com um espaço.


O TTB foi projetado para que o manipulador de comandos seja criado como um método com o nome cmd_handler_%s , em que% s é o nome do comando. I.e. para o comando vmp, o método será chamado cmd_handler_vmp . Um objeto da classe UpdateCmn é passado para o manipulador de comandos. Além disso, para um comando, ele pode conter a propriedade cmd_args , que contém um dicionário de linhas e palavras que foram inseridas com o comando


O código ficará assim:


  def cmd_handler_vmp(self, update): res = None if not update.this_cmd_response: #    ,       if update.cmd_args: #        list_id = [] parts = update.cmd_args.get('c_parts') or [] if parts: for line in parts: for part in line: list_id.append(str(part)) if list_id: res = self.view_messages(update, list_id, update.link) return bool(res) 

Nós reiniciamos o bot. Agora, se você digitar um comando no diálogo do bot como: /vmp mid1 mid2 (você pode fazer as verificações anteriores no meio), em troca, receberemos duas mensagens com uma representação interna dos objetos de mensagem de origem, para cada um dos meios transmitidos.


O código fonte desse estado está aqui .


Modificação de um comando bot para trabalhar com uma resposta de texto


Você também pode tentar encaminhar a mensagem de outro canal / chat. Mas, neste caso, apenas o conteúdo da mensagem de origem em diálogo com o bot será mostrado. Em particular, ao enviar uma mensagem, as informações do botão não são salvas.


Mas e se quisermos ver informações sobre a mensagem original? Nesse caso, você precisa tirar o meio da mensagem encaminhada.


Para implementar esse modo, modificamos o comando vmp para que, quando chamado sem parâmetros, espere que a mensagem seja encaminhada e, depois disso, pegue o meio da mensagem enviada e exiba informações sobre ela.


(!) Para o funcionamento correto dessa funcionalidade, o bot deve ter permissão de leitura do canal / fonte de bate-papo.


Nós modificamos o código de comando da seguinte maneira:


  def cmd_handler_vmp(self, update): res = None if not update.this_cmd_response: #    ,       if update.cmd_args: #        list_id = [] parts = update.cmd_args.get('c_parts') or [] if parts: for line in parts: for part in line: list_id.append(str(part)) if list_id: res = self.view_messages(update, list_id, update.link) else: #      self.msg.send_message(NewMessageBody(f' **  /    :'), user_id=update.user_id) update.required_cmd_response = True #       else: #    message = update.message link = message.link #       link #  -   . if link and link.type == MessageLinkType.FORWARD: res = self.view_messages(update, [link.message.mid], update.link) else: #         ,    . self.msg.send_message(NewMessageBody(f'.  **   /. , .'), user_id=update.user_id) return False return bool(res) 

E desde com essa abordagem, o risco aumenta devido à falta de acesso às mensagens; em seguida, no método view_messages() , adicionamos uma verificação da conformidade com o número de mensagens solicitadas / recebidas:


  def view_messages(self, update, list_mid, link=None): res = False msgs = self.msg.get_messages(message_ids=list_mid) if msgs: #    mid     if len(msgs.messages) < len(list_mid): self.msg.send_message(NewMessageBody( f'     .    @{self.username}  /  .', link=update.link ), user_id=update.user_id) return False else: for msg in msgs.messages: r = self.msg.send_message(NewMessageBody(f' {msg.body.mid}:\n`{msg}`'[:NewMessageBody.MAX_BODY_LENGTH], link=link), user_id=update.user_id) res = res or r return res 

Reiniciamos o bot, fornecemos o comando / vmp e, após o prompt sobre a necessidade de encaminhamento ser exibido, encaminhamos a mensagem do canal / chat. Se o bot tiver o direito de ler mensagens neste canal / chat, uma representação textual do objeto de mensagem encaminhada será exibida. Se não houver acesso, o bot relatará um possível problema e aguardará o encaminhamento da fonte correta.


Definindo propriedades do bot


Agora resta trazer brilho. Vamos fechar a propriedade about , que retorna o texto que o bot exibe quando começa a funcionar, bem como o comando / start.


  @property def about(self): return '       .' 

Bloquearemos o método get_commands() , que retorna a lista de comandos do nosso bot, que aparece na caixa de diálogo com o bot.


  def get_commands(self): # type: () -> [BotCommand] commands = [ BotCommand('start', ' '), BotCommand('menu', ' '), BotCommand('vmp', '  '), ] return commands 

Vamos desativar a propriedade main_menu_buttons, que retorna a lista de botões no menu principal, chamada pelo comando / menu.


  def main_menu_buttons(self): # type: () -> [] buttons = [ #       -  [CallbackButtonCmd(' ', 'start')], #        - .    -  [CallbackButtonCmd('  ', 'vmp', intent=Intent.POSITIVE)], ] return buttons 

Reiniciamos o bot, verifique se está tudo em ordem. Parabéns, seu primeiro bot foi criado e, apesar de algum brinquedo, ele tem exigido bastante funcionalidade.


O código fonte desse estado está aqui .


O bot @devhelpbot em funcionamento pode ser visto aqui .


Por enquanto é tudo. Se o tópico for de interesse, nos artigos a seguir eu posso considerar o desenvolvimento adicional do bot. Por exemplo, adicionando botões personalizados (em particular Sim / Não) e processando-os, enviando vários tipos de conteúdo (arquivos, fotos, etc.), trabalhando no modo webhook, etc.


A propósito, você pode rapidamente fazer perguntas em um bate-papo especial . Sugestões / idéias diretas lá.

Source: https://habr.com/ru/post/pt466373/


All Articles