@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: Ele 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: E, 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: Ao 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:
-
Provavelmente, você terá que se registrar no heroku usando uma VPN se morar na Rússia.
-
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
-
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).
-
Não construa nenhum projeto usando comandos do pacote mvn, etc., o heroku montará tudo para você.
-
É aconselhável adicionar um gitignore ao projeto, por exemplo este:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
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).
-
Se o download foi bem-sucedido ("Build Success"), vá para Configurar Dynos:
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 ).
-
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á):
- 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.
GO TO FULL VERSION