2
\$\begingroup\$

I started coding two months ago and as my first project I wrote a Telegram bot in Java. The purpose of the bot is to perform various tasks related to the Yugioh TCG (such as searching for cards in a database or keep track of points during a game). Since this is my first time using the Telegram API I have read its documentation but I'm not sure if my implementation of the commands is correct.

I created several classes to handle commands, each one extends TelegramLongPollingBot and then I call their onUpdateReceived method inside a Bot class:

package org.smnrpn.bot;

import org.smnrpn.database.CardDatabase;
import org.smnrpn.global.GlobalCommands;
import org.smnrpn.lifepoints.LifePointsCounter;
import org.smnrpn.links.UsefulLinks;
import org.smnrpn.prices.Prices;
import org.smnrpn.roll.RollDice;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.objects.Update;

public class Bot extends TelegramLongPollingBot {
    GlobalCommands globalCommands = new GlobalCommands();
    UsefulLinks usefulLinks = new UsefulLinks();
    RollDice rollDice = new RollDice();
    LifePointsCounter lifePointsCounter = new LifePointsCounter();
    CardDatabase cardDatabase = new CardDatabase();
    Prices cardPrices = new Prices();

    @Override
    public String getBotUsername() {
        return "yugiohdueling_bot";
    }

    @Override
    public String getBotToken() {
        return System.getenv("YUGIOH_BOT_TOKEN");
    }

    @Override
    public void onUpdateReceived(Update update) {
        globalCommands.onUpdateReceived(update);
        usefulLinks.onUpdateReceived(update);
        rollDice.onUpdateReceived(update);
        lifePointsCounter.onUpdateReceived(update);
        cardDatabase.onUpdateReceived(update);
        cardPrices.onUpdateReceived(update);
    }
}

Then, within each onUpdateReceived method I distinguish in this way which command the user entered (the example comes from the UsefulLinks class that handles the /links command, but is similar for the others):

/*
 * This class implements a list of useful links for Yu-Gi-Oh! players.
 */

package org.smnrpn.links;

import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

import java.util.ArrayList;
import java.util.List;

public class UsefulLinks extends TelegramLongPollingBot {
    @Override
    public String getBotUsername() {
        return "yugiohdueling_bot";
    }

    @Override
    public String getBotToken() {
        return System.getenv("YUGIOH_BOT_TOKEN");
    }

    @Override
    public void onUpdateReceived(Update update) {
        var message = update.getMessage();
        var user = message.getFrom();
        var id = user.getId();

        if (message.isCommand()) {
            if (message.getText().equals("/links")) {
                sendLinksMessage(id);
            }
        }
    }

    public void sendLinksMessage(Long id) {
        String LINE_BREAK = "\n\n";

        String startMessage = "Here's a list of useful links:" + LINE_BREAK +
                "<b>Yu-Gi-Oh! Card Database</b>\n" + "<i>The official Yu-Gi-Oh! card database by Konami</i>\n" +
                "To find all the information you need about every card in the game." + LINE_BREAK +
                "<b>YGOPRODECK</b>\n" + "<i>A Yu-Gi-Oh! community</i>\n" +
                "Here you can find many decklists, a pack opener simulator and other useful things." + LINE_BREAK +
                "<b>Yugipedia</b>\n" + "<i>A fan-made wiki for Yu-Gi-Oh!</i>\n" +
                "A free repository on every aspect of the franchise." + LINE_BREAK +
                "<b>Cardmarket</b>\n" + "<i>A site to sell and buy cards</i>\n" +
                "Europe's largest online marketplace for TCGs!" + LINE_BREAK +
                "<b>TCGPlayer</b>\n" + "<i>Another site for sell and buy cards</i>\n" +
                "The largest online TCGs marketplace in the US.";

        SendMessage sendLinksMessage = new SendMessage();
        sendLinksMessage.setChatId(id.toString());
        sendLinksMessage.setParseMode("html");
        sendLinksMessage.setText(startMessage);

        sendLinksMessage.setReplyMarkup(createInlineKeyboard());

        try {
            execute(sendLinksMessage);
        } catch (TelegramApiException e) {
            throw  new RuntimeException(e);
        }
    }

    public InlineKeyboardMarkup createInlineKeyboard() {
        InlineKeyboardMarkup markupInline = new InlineKeyboardMarkup(); // Create the Inline Keyboard Markup
        List<List<InlineKeyboardButton>> inlineKeyboard = new ArrayList<>(); // Create the Inline Keyboard
        List<InlineKeyboardButton> keyboardButtonsFirstRow = new ArrayList<>(); // Create a row of buttons
        List<InlineKeyboardButton> keyboardButtonsSecondRow = new ArrayList<>();
        List<InlineKeyboardButton> keyboardButtonsThirdRow = new ArrayList<>();

        // Create buttons
        InlineKeyboardButton yugiohDB = new InlineKeyboardButton("Official Yu-Gi-Oh! Card Database");
        yugiohDB.setUrl("https://www.db.yugioh-card.com/yugiohdb/?request_locale=en");

        InlineKeyboardButton ygoProDeck = new InlineKeyboardButton("YGOPRODECK");
        ygoProDeck.setUrl("https://ygoprodeck.com/");

        InlineKeyboardButton yugipedia = new InlineKeyboardButton("Yugipedia");
        yugipedia.setUrl("https://yugipedia.com/wiki/Yugipedia");

        InlineKeyboardButton cardmarket = new InlineKeyboardButton("Cardmarket");
        cardmarket.setUrl("https://www.cardmarket.com/en");

        InlineKeyboardButton tcgPlayer = new InlineKeyboardButton("TCGPlayer");
        tcgPlayer.setUrl("https://www.tcgplayer.com/");

        markupInline.setKeyboard(inlineKeyboard); // Add the keyboard to the markup

        // Add the rows to the keyboard
        inlineKeyboard.add(keyboardButtonsFirstRow);
        inlineKeyboard.add(keyboardButtonsSecondRow);
        inlineKeyboard.add(keyboardButtonsThirdRow);

        // Add the buttons to the row
        keyboardButtonsFirstRow.add(yugiohDB);
        keyboardButtonsSecondRow.add(ygoProDeck);
        keyboardButtonsSecondRow.add(yugipedia);
        keyboardButtonsThirdRow.add(cardmarket);
        keyboardButtonsThirdRow.add(tcgPlayer);

        return markupInline;
    }
}

The doubt comes from the fact that the way I coded the bot requires that each class that handles commands needs to extend TelegramLongPollingBot and implement getBotUsername, getBotToken and onUpdateReceived. The bot works correclty but I am pretty sure that this is not the right way to do it since I came up with this solution myself and the Telegram API documentation doesn't get in deep about this. I'm concerned that this approach may have a negative impact on the bot's performance or security (I may make it publicly available in the future so I don't want to take any risk for the users).

I have tried looking for code from other bots to see if my solution is correct but they are often either too simple (have one or two commands handled by the same class) or extremely complex that I cannot understand.

This is the full code for reference: https://github.com/smnprn/yugioh-duel-bot-telegram

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

I think your approach would be better if you could make use of methods rather than whole classes. So if we follow your example, we can do something like.

public class Bot extends TelegramLongPollingBot {
    GlobalCommands globalCommands;
    UsefulLinks usefulLinks;
    RollDice rollDice;
    LifePointsCounter lifePointsCounter;
    CardDatabase cardDatabase;
    Prices cardPrices;

    @Override
    public String getBotUsername() {
        return "yugiohdueling_bot";
    }

    @Override
    public String getBotToken() {
        return System.getenv("YUGIOH_BOT_TOKEN");
    }

    @Override
    public void onUpdateReceived(Update update) {
        var message = update.getMessage();
        var user = message.getFrom();
        var id = user.getId();
        if (message.isCommand()) {
            if (message.getText().equals("/links")) {
                usefulLinks.handle(id)
            }
            else if(){
                //follow the same thing here 
            }
        }
    }
}

and for the one class that you provided. it may look like this

public class UsefulLinks{

    public static void handle() {
       //your logic here like printing message to the user and note that you can 
       //the method returns something and continue your work on the original 
       //onUpdateReceived method like sending the message or sending photos ...
    }
}

this approach will save you from repeating the process of getting fields like the message and chat id and also from repeating extending the TelegramLongPollingBot and overriding the getBotToken and getBotUsername methods several times and you can easily inject the dependency of each class by passing the parameters it needs.

Note: according to your implementation to the inline keyboard markup you can make the implementation in a separate class and call when needed also passing the string parameters of button text, callbacks and URLs if any.

hope you enjoy your programming journey :)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.