JavaRush /Blogue Java /Random-PT /Bot Telegram como primeiro projeto e seu significado para...
Pavel Mironov (Miroha)
Nível 16
Москва

Bot Telegram como primeiro projeto e seu significado para o crescimento profissional a partir da experiência pessoal

Publicado no grupo Random-PT
Saudações a todos! Conte-nos sobre você. Tenho 24 anos, me formei em uma universidade técnica no ano passado e ainda não tenho experiência profissional. Olhando para o futuro, quero dizer que inicialmente, no plano traçado (elaborado no outono de 2019), planejei ir trabalhar em março-abril de 2020, mas, infelizmente, a quarentena interveio, então adiei tudo para meados -verão e no futuro espero escrever minha própria história de sucesso. Bot Telegram como primeiro projeto e seu significado para o crescimento profissional baseado na experiência pessoal - 1Nunca fui atraído por programação. Na universidade eles ensinavam bastante programação, mas esse ofício não poderia me interessar na época. Havia também linguagens procedurais (C), um curso de um ano de OOP (Java), bancos de dados, até mesmo assembler e C++. Mas, para ser sincero, geralmente era indiferente aos estudos, pois a maioria das disciplinas ministradas me pareciam inúteis, adequadas apenas para fins de reportagem (em princípio é assim). Depois de me formar na universidade, tive que tomar uma decisão: não adquiri algumas habilidades, mas precisava trabalhar. Tive que pensar em autoeducação (ah, já perdi pelo menos 2 anos inteiros ficando de braços cruzados) e a escolha recaiu naturalmente sobre Java, já que em um curso de POO na universidade um dos caras recomendou o curso javarush , e ele, como você sabe, é dedicado especificamente à linguagem Java. Fiquei interessado na apresentação do curso. Sim, eu não gostava de programar naquela época, porque desisti imediatamente quando encontrei alguma dificuldade, e há dificuldades mais do que suficientes na programação. Mas, ao mesmo tempo, senti que queria escrever código, então no final decidi entrar na área de programação. Vou contar brevemente sobre minha experiência com javarush. Comecei em agosto de 2019, comprei imediatamente uma assinatura de um mês, mas no nível 7 percebi que as tarefas eram difíceis. Deixei o curso de lado e peguei Shildt. Então, paralelamente, concluí o curso por 3 meses. Cheguei ao nível 20 (esta é minha segunda conta), li Schildt quase completamente, depois cansei das tarefas aqui, nas quais parei de ver benefícios práticos para mim. Fui para codewars, leetcode e comecei a assistir cursos em vídeo. Aliás, em 3 meses passei de "Ah, não, o que é um array? Como trabalhar com ele e por que é tão assustador"? para um estudo detalhado do código-fonte das classes de coleção (ArrayList, HashMap, etc.). Com base na experiência pessoal, direi aos iniciantes: o principal aqui é superar o sentimento que surge se você não entende nada e não consegue resolver nada. Quando surge, você só quer desistir de tudo e parece que é estúpido demais para esse assunto. Se você superar esses momentos dentro de você e descansar mentalmente, o sucesso virá. Acho que muitas pessoas não conseguem lidar com isso, então desistem rapidamente de tais esforços. Com isso, em dezembro de 2019 comecei a pensar no meu projeto. Resolvi escolher um bot do Telegram, mas não tive ideia. Ao mesmo tempo, um amigo precisava de funcionalidades para seu grupo em um telegrama, que gostaria de automatizar. Ele percebeu que eu estava estudando programação a fundo e me ofereceu um projeto. Para mim, pela experiência e currículo futuro, para ele, pelo desenvolvimento do grupo. Permitir-me-ei até citar a sua ideia: “Недавно софтину хотел у программиста заказать, которая загружала бы в выбранное Облако файлы по прямым linkм. Это интересно, так How аналогов нет. И просто очень удобно. Суть: копируешь ссылку, вставляешь в окно и выбираешь нужное Облако (GDrive, Mail, Яндекс Диск и т.п), в своё время софт всё делает на стороне serverа и юзеру ничего не нужно загружать на свою машину (особенно круто, когда у тебя сборка на SSD-накопителях). Думали сделать в web-интерфейсе, чтобы можно было запускать How с телефонов, так и с десктопа... Можно в принципе через приложение реализовать, а не через web-интерфейс. Тебе такое по силам?“Comecei a trabalhar, mas no final, depois de alguns dias, percebi que nada daria certo para nós, em grande parte por falta de conhecimento. Um amigo precisava desses mesmos links para Cloud.Mail, mas ainda não precisa. Não tenho uma API. Houve uma tentativa de montar algo via GDrive, mas a implementação foi fraca, além disso, esse serviço de nuvem não se adequava ao “cliente”. Embora inicialmente ele tenha oferecido várias nuvens para escolher, ele acabou rejeitando tudo, exceto correio. .ru, para o qual não foi encontrada nenhuma solução. De alguma forma tudo acabou saindo caro, foi necessário conectar o banco de dados, usar um servidor para armazenamento, etc. Aliás, ainda precisa dessa aplicação web. Já que as coisas não aconteceram Se não deu certo para nós, decidi fazer um bot de informações, que deveria receber links para o jogo da Google Play Store, analisar o link e salvar as informações recebidas na biblioteca e depois gravá-las em um arquivo json. Assim, a cada solicitação, a biblioteca pode se expandir graças ao esforço dos usuários.No futuro, você não poderá obter informações sobre o jogo de forma conveniente acessando o Google Play. Basta escrever o comando /libraryHere_game_name e obter tudo o que precisa. Mas existem várias dificuldades das quais falarei mais tarde. No início progredi lentamente, pois comecei a fazer dois cursos de SQL ao mesmo tempo. Eu simplesmente não conseguia entender como o bot funcionava e como processar solicitações. Conheci um amigo que também estava interessado em trabalhar no projeto. A primeira versão do bot ficou pronta em cerca de um mês, mas surgiram desentendimentos com um amigo (da minha parte). Assumi a parte do bot responsável pela análise, e ele trabalhou diretamente nas solicitações ao bot e no seu processamento. Por algum motivo, ele começou a complicar o bot, introduzir algum tipo de autorização, inventar administradores, adicionar funcionalidades desnecessárias, e eu realmente não gostei do estilo de codificação dele. Na minha opinião, isso não era necessário em um bot de informação. Então decidi que eu mesmo escreveria um bot do zero com a funcionalidade que precisava. Agora vou contar o que o bot realmente faz (usando um exemplo do código do projeto). Vou anexar o código completo do projeto no final do artigo e, infelizmente, não poderei comentá-lo integralmente. Qualquer mensagem do usuário enviada ao bot é um objeto da classe Update. Ele contém muitas informações (ID da mensagem, ID do bate-papo, ID do usuário exclusivo, etc.). Existem vários tipos de atualização: pode ser uma mensagem de texto, pode ser uma resposta do teclado do telegrama (callback), uma foto, áudio, etc. Para evitar que o usuário mexa muito, processo apenas solicitações de texto e retornos de chamada do teclado. Caso o usuário envie uma foto, o bot irá avisá-lo de que não pretende fazer nada com ela. Na classe principal do bot, no método onUpdateReceived, o bot recebe uma atualização.
@Override
    public void onUpdateReceived(Update update) {
        UpdatesReceiver.handleUpdates(update);
    }
que passo para o manipulador (própria classe UpdatesReceiver):
public static void handleUpdates(Update update) {
        ...
        if (update.hasMessage() && update.getMessage().hasText()){
            log.info("[Update (id {}) типа \"Текстовое сообщение\"]", update.getUpdateId());
            new TextMessageHandler(update, replyGenerator).handleTextMessage();
        }
        else if (update.hasCallbackQuery()) {
            //логгирование
            new CallbackQueryHandler(update, replyGenerator).handleCallBackQuery();
        }
        else {
           //логгирование
            replyGenerator.sendTextMessage(update.getMessage().getChatId(), "Я могу принимать только текстовые messages!");
        }
    }
UpdatesReceiver é um manipulador central que, dependendo do tipo de atualização, transfere o controle para outro manipulador especializado: TextMessageHandler ou CallbackQueryHandler, para cujos construtores passo a atualização mais abaixo na cadeia. A atualização é o mais importante quando se trabalha com um bot e não pode ser perdida, pois com a ajuda das informações armazenadas na atualização descobrimos para qual usuário e para qual chat a resposta deve ser enviada. Para gerar respostas ao usuário, escrevi uma classe separada. Ele pode enviar mensagem de texto normal, mensagem com teclado embutido, mensagem com imagem e mensagem com teclado de resposta. Um teclado inline se parece com isto: Bot Telegram como primeiro projeto e seu significado para o crescimento profissional baseado na experiência pessoal - 1Ele define botões que, ao clicar neles, o usuário envia um retorno de chamada ao servidor, que pode ser processado quase da mesma forma que as mensagens normais. Para “mantê-lo”, você precisa de seu próprio manipulador. Definimos uma ação para cada botão, que é então gravada no objeto Update. Aqueles. para o botão "Custo" definimos a descrição "/preço" para o retorno de chamada, que poderemos obter posteriormente na atualização. A seguir, em uma classe separada, já posso processar este retorno de chamada:
public void handleCallBackQuery() {
  String call_data = update.getCallbackQuery().getData();
  long message_id = update.getCallbackQuery().getMessage().getMessageId();
  long chat_id = update.getCallbackQuery().getMessage().getChatId();
    switch (call_date){
      case "/price" :
        //тут что-то сделать
        break;
...
O teclado de resposta tem esta aparência: Bot Telegram como primeiro projeto e seu significado para o crescimento profissional a partir da experiência pessoal - 2E, em essência, substitui a digitação do usuário. Clicar no botão “Biblioteca” enviará rapidamente uma mensagem “Biblioteca” ao bot. Para cada tipo de teclado, escrevi minha própria classe, implementando o padrão Builder: inline e responda . Como resultado, você pode essencialmente “desenhar” o teclado desejado dependendo de suas necessidades. Isso é extremamente conveniente, pois os teclados podem ser diferentes, mas o princípio permanece o mesmo. Aqui está um método intuitivo para enviar uma mensagem com um teclado embutido:
public synchronized void sendInlineKeyboardMessage(long chat_id, String gameTitle) {
        SendMessage keyboard = InlineKeyboardMarkupBuilder.create(chat_id)
                .setText("Вы может узнать следующую информацию об игре " + gameTitle)
                .row()
                .button("Стоимость " + "\uD83D\uDCB0", "/price " + gameTitle)
                .button("Обновлено " + "\uD83D\uDDD3", "/updated " + gameTitle)
                .button("Версия " + "\uD83D\uDEE0", "/version " + gameTitle)
                .endRow()
                .row()
                .button("Требования " + "\uD83D\uDCF5", "/requirements " + gameTitle)
                .button("Покупки " + "\uD83D\uDED2", "/iap " + gameTitle)
                .button("Размер " + "\uD83D\uDD0E", "/size " + gameTitle)
                .endRow()
                .row()
                .button("Получить всю информацию об игре" + "\uD83D\uDD79", "/all " + gameTitle)
                .endRow()
                .row()
                .button("Скрыть клавиатуру", "close")
                .endRow()
                .build();
        try {
            execute(keyboard);
        } catch (TelegramApiException e) {
            log.error("[Не удалось отправить сообщение с -inline- клавиатурой]: {}", e.getMessage());
        }
    }
Para dar funcionalidade estrita ao bot, foram inventados comandos especiais usando o caractere de barra: /library, /help, /game, etc. Caso contrário, teríamos que processar qualquer lixo que o usuário pudesse escrever. Na verdade, é para isso que o MessageHandler foi escrito:
if (message.equals(ChatCommands.START.getDescription())) {
     replyGenerator.sendTextMessage(chat_id, new StartMessageHandler().reply());
     replyGenerator.sendReplyKeyboardMessage(chat_id);
}
else if (message.equals(ChatCommands.HELP.getDescription())
             || message.equalsIgnoreCase("Помощь")) {
      replyGenerator.sendTextMessage(chat_id, new HelpMessageHandler().reply());
}
 ...
Assim, dependendo do comando que você enviar ao bot, um manipulador especial será incluído no trabalho. Vamos mais longe e veremos o trabalho do analisador e da biblioteca. Se você enviar ao bot um link para um jogo na Google Play Store, um manipulador especial funcionará automaticamente . Em resposta, o usuário receberá informações sobre o jogo da seguinte forma: Bot Telegram como primeiro projeto e seu significado para o crescimento profissional a partir da experiência pessoal - 3Ao mesmo tempo, será chamado um método que tentará adicionar o jogo à biblioteca do bot (primeiro no mapa local, depois no -> arquivo json ). Se o jogo já estiver na biblioteca, será realizada uma verificação (como em um hashmap normal) e se os dados do campo (por exemplo, o número da versão foram alterados), o jogo na biblioteca será substituído. Se nenhuma alteração for detectada, nenhuma entrada será feita. Se não houver nenhum jogo na biblioteca, ele será primeiro gravado no mapa local (um objeto como tyk ) e depois gravado em um arquivo json, pois se o aplicativo no servidor for fechado inesperadamente, os dados serão perdido, mas sempre pode ser lido usando o arquivo. Na verdade, quando o programa é iniciado, a biblioteca é sempre carregada pela primeira vez a partir de um arquivo de um bloco estático:
static {
        TypeFactory typeFactory = mapper.getTypeFactory();
        MapType mapType = typeFactory.constructMapType(ConcurrentSkipListMap.class, String.class, GooglePlayGame.class);

        try {
            Path path = Paths.get(LIBRARY_PATH);
            if (!Files.exists(path)) {
                Files.createDirectories(path.getParent());
                Files.createFile(path);
                log.info("[Файл библиотеки создан]");
            }
            else {
                ConcurrentMap<string, googleplaygame=""> temporary = mapper.readValue(new File(LIBRARY_PATH), mapType);
                games.putAll(temporary);
                log.info("[Количество игр в загруженной библиотеке] = " + games.size());
            }
        }
        catch (IOException e) {
            log.error("[Ошибка при чтении/записи file] {}", e.getMessage());
        }
    }
Aqui você também deve ler os dados do arquivo em um mapa temporário, que é então “copiado” em um mapa completo para manter a distinção entre maiúsculas e minúsculas ao procurar por um jogo no arquivo (ao escrever tITan QuEST, o bot ainda encontrará o jogo Titan Quest na biblioteca). Não foi possível encontrar outra solução, esses são os recursos de desserialização usando Jackson. Assim, a cada solicitação de link, o jogo é adicionado à biblioteca, se possível, e a biblioteca se expande. Mais informações sobre um jogo específico podem ser obtidas usando o comando /libraryGame_Name. Você pode descobrir um parâmetro específico (por exemplo, a versão atual) e todos os parâmetros de uma só vez. Isso é implementado usando o teclado embutido, discutido anteriormente. Durante o trabalho, também apliquei as habilidades aqui adquiridas na resolução de problemas. Por exemplo, uma lista de nomes de jogos aleatórios localizados na biblioteca (a opção está disponível usando o comando /library):
private String getRandomTitles(){
        if (LibraryService.getLibrary().size() < 10){
            return String.join("\n", LibraryService.getLibrary().keySet());
        }
        List<string> keys = new ArrayList<>(LibraryService.getLibrary().keySet());
        Collections.shuffle(keys);
        List<string> randomKeys = keys.subList(0, 10);
        return String.join("\n", randomKeys);
    }
Como o bot processa links? Ele os verifica para ver se pertencem ao Google Play (host, protocolo, porta):
private static class GooglePlayCorrectURL {

        private static final String VALID_HOST = "play.google.com";

        private static final String VALID_PROTOCOL = "https";

        private static final int VALID_PORT = -1;

        private static boolean isLinkValid(URI link) {
            return (isHostExist(link) && isProtocolExist(link) && link.getPort() == VALID_PORT);
        }

        private static boolean isProtocolExist(URI link) {
            if (link.getScheme() != null) {
                return link.getScheme().equals(VALID_PROTOCOL);
            }
            else {
                return false;
            }
        }

        private static boolean isHostExist(URI link) {
            if (link.getHost() != null) {
                return link.getHost().equals(VALID_HOST);
            }
            else {
                return false;
            }
        }
Se tudo estiver em ordem, o bot se conecta por meio de um link usando a biblioteca Jsoup, que permite obter o código HTML da página, que está sujeito a análises e análises adicionais. Você não conseguirá enganar o bot com um link incorreto ou prejudicial.
if (GooglePlayCorrectURL.isLinkValid(link)){
     if (!link.getPath().contains("apps")){
         throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
     }
     URL = forceToRusLocalization(URL);
     document = Jsoup.connect(URL).get();
 }
     else {
         throw new NotGooglePlayLinkException();
      }
...
Aqui tivemos que resolver um problema com configurações regionais. O bot se conecta à Google Play Store a partir de um servidor localizado na Europa, para que a página da Google Play Store seja aberta no idioma apropriado. Tive que escrever uma muleta que forçasse um “redirecionamento” para a versão russa da página (afinal, o projeto era voltado para o nosso público). Para fazer isso, no final do link você precisa adicionar cuidadosamente o parâmetro hl: &hl=ru na solicitação GET ao servidor Google Play .
private String forceToRusLocalization(String URL) {
        if (URL.endsWith("&hl=ru")){
            return URL;
        }
        else {
            if (URL.contains("&hl=")){
                URL = URL.replace(
                        URL.substring(URL.length()-"&hl=ru".length()), "&hl=ru");
            }
            else {
                URL += "&hl=ru";
            }
        }
        return URL;
    }
Após uma conexão bem-sucedida, recebemos um documento HTML pronto para análise e análise, mas isso está além do escopo deste artigo. O código do analisador está aqui . O próprio analisador recupera as informações necessárias e cria um objeto com o jogo, que posteriormente é adicionado à biblioteca, se necessário. <h2>Para resumir</h2>O bot suporta vários comandos que contêm determinadas funcionalidades. Ele recebe mensagens do usuário e as combina com seus comandos. Se for um link ou o comando /game + link, ele verifica esse link para ver se pertence ao Google Play. Se o link estiver correto, ele se conecta via Jsoup e recebe o documento HTML. Este documento é analisado com base no analisador escrito. As informações necessárias sobre o jogo são extraídas do documento e, em seguida, o objeto com o jogo é preenchido com esses dados. Em seguida, o objeto com o jogo é colocado no armazenamento local (se o jogo ainda não estiver lá) e imediatamente gravado em um arquivo para evitar perda de dados. Um jogo gravado na biblioteca (o nome do jogo é a chave do mapa, o objeto com o jogo é o valor do mapa) pode ser obtido usando o comando /library Game_name. Se o jogo especificado for encontrado na biblioteca do bot, o usuário receberá um teclado embutido, com o qual poderá obter informações sobre o jogo. Se o jogo não for encontrado, você deve certificar-se de que o nome está escrito corretamente (deve corresponder completamente ao nome do jogo na Google Play Store, exceto neste caso) ou adicionar o jogo à biblioteca enviando o bot um link para o jogo. Implantei o bot no heroku e para aqueles que planejam escrever seu próprio bot no futuro e hospedá-lo gratuitamente no heroku, darei algumas recomendações para resolver as dificuldades que você possa encontrar (já que eu mesmo as encontrei). Infelizmente, devido à natureza do Heroku, a biblioteca de bots é constantemente “reiniciada” uma vez a cada 24 horas. Meu plano não oferece suporte ao armazenamento de arquivos em servidores Heroku, então ele simplesmente extrai o arquivo do meu jogo do Github. As soluções foram diversas: usar um banco de dados, ou procurar outro servidor que armazenasse esse arquivo com o jogo. Decidi não fazer nada por enquanto, pois essencialmente o bot não é tão útil. Eu precisava disso para ganhar uma experiência completa, que foi basicamente o que consegui. Então, recomendações para Heroku:
  1. Provavelmente, você terá que se registrar no heroku usando uma VPN se morar na Rússia.

  2. Na raiz do projeto você precisa colocar um arquivo sem extensão chamado Procfile. Seu conteúdo deve ser assim: https://github.com/miroha/Telegram-Bot/blob/master/Procfile

  3. No pom.xml, adicione as seguintes linhas conforme o exemplo , onde na tag mainClass indique o caminho para a classe que contém o método principal: bot.BotApplication (se a classe BotApplication estiver na pasta bot).

  4. Não construa nenhum projeto usando comandos do pacote mvn, etc., o heroku montará tudo para você.

  5. É aconselhável adicionar um gitignore ao projeto, por exemplo este:

    # Log file
    *.log
    
    # Compiled resources
    target
    
    # Tests
    test
    
    # IDEA files
    .idea
    *.iml
  6. Na verdade, carregue o projeto no github e conecte o repositório ao Heroku (ou use outros métodos, existem 3 deles, se não me engano).

  7. Se o download foi bem-sucedido ("Build Success"), vá para Configurar Dynos:

    Bot Telegram como primeiro projeto e seu significado para o crescimento profissional a partir da experiência pessoal - 4

    e mude o controle deslizante e, em seguida, certifique-se de que ele esteja na posição LIGADO (devido ao fato de eu não ter feito isso, meu bot não funcionou e eu quebrei a cabeça por alguns dias e fiz muitos movimentos desnecessários ).

  8. Oculte o token do bot no Github. Para fazer isso, você precisa obter o token da variável de ambiente:

    public class Bot extends TelegramLongPollingBot {
    
        private static final String BOT_TOKEN = System.getenv("TOKEN");
    
        @Override
        public String getBotToken() {
            return BOT_TOKEN;
        }
    ...
    }

    E depois de implantar o bot, defina esta variável no painel do Heroku na guia Configurações (à direita do TOKEN haverá um campo VALUE, copie o token do seu bot lá):

    Bot Telegram como primeiro projeto e seu significado para o crescimento profissional a partir da experiência pessoal - 5
No total, em 2 meses trabalhando em meu próprio projeto eu:
  • recebeu um projeto totalmente funcional escrito em Java;
  • aprendi a trabalhar com API de terceiros (Telegram Bot API);
  • na prática me aprofundei na serialização, trabalhei muito com JSON e a biblioteca Jackson (inicialmente usei GSON, mas houve problemas com ela);
  • fortaleci minhas habilidades no trabalho com arquivos, familiarizei-me com Java NIO;
  • aprendi a trabalhar com arquivos .xml de configuração e me acostumei com o logging;
  • maior proficiência em ambiente de desenvolvimento (IDEA);
  • aprendi a trabalhar com git e aprendi o valor do gitignore;
  • adquiriu habilidades em análise de páginas da web (biblioteca Jsoup);
  • aprendeu e utilizou diversos padrões de design;
  • desenvolveu um senso e desejo de melhorar o código (refatoração);
  • Aprendi a encontrar soluções online e a não ter vergonha de fazer perguntas para as quais não encontrava resposta.
Bot do Telegram como primeiro projeto e seu significado para o crescimento profissional a partir da experiência pessoal - 7Não sei quão útil ou inútil o bot acabou sendo, ou quão bonito/feio era o código, mas a experiência que obtive definitivamente valeu a pena. Senti um senso de responsabilidade pelo meu projeto. De vez em quando quero melhorá-lo, acrescentar algo novo. Quando consegui executá-lo e ver que tudo funcionava como eu queria, foi uma verdadeira emoção. Não é isso o principal? Aproveite o que você faz e aproveite cada linha de código funcional, como a última barra de chocolate. Portanto, se você está dominando a programação, então meu conselho para você: não fique aqui até o nível 40, mas comece seu próprio projeto o mais cedo possível. Se alguém estiver interessado, o código fonte do projeto está aqui (reescrito para Spring): https://github.com/miroha/GooglePlayGames-TelegramBot Nos últimos dois meses quase não tenho estudado material novo, pois me parece que cheguei a um beco sem saída. Sem trabalho, não vejo mais onde me desenvolver, a não ser talvez para ensinar o Spring Framework, que é o que pretendo fazer no próximo mês. E então tentarei “reescrever” o bot usando esta estrutura. Pronto para responder a quaisquer perguntas. :) Boa sorte a todos! ATUALIZAÇÃO de 07/07/2020 O repositório com o bot em Java puro foi perdido (apaguei, ficou uma cópia em outra máquina local), mas baixei o bot reescrito para Spring Boot: https://github.com/miroha /GooglePlayGames-TelegramBot
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION