Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Getting Started
- Users guide
- Getting Started
- Errors Handling
- Using HTTP Proxy
- FAQ
- Handling Bot Tokens
- Understanding the Library
- Simple Example
- Hello Ability
- Using Replies
- Ability Toggle
- State Machines
- Database Handling
- Ability Extensions
- Bot Testing
- Bot Recovery
- Advanced
- Additional Examples
- How To Update
Clone this wiki locally
So, you’d like to create your own Telegram bot with TelegramBots? Then Let’s get You started quickly.
First you need to acquire the library and add it to your project. There are several ways to do this:
- If you use Maven, Gradle, etc; you should be able to import the dependency directly from Maven Central Repository. For example:
With Maven:dependency> groupId>org.telegramgroupId> artifactId>telegrambotsartifactId> version>6.7.0version> dependency>
implementation 'org.telegram:telegrambots:6.7.0'
Now that you have the library, you can start coding. There are few steps to follow, in this tutorial (for the sake of simplicity), we are going to build a Long Polling Bot:
- Creating your actual bot: The class must extends TelegramLongPollingBot and implement necessary methods:
public class MyAmazingBot extends TelegramLongPollingBot < @Override public void onUpdateReceived(Update update) < // TODO > @Override public String getBotUsername() < // TODO return null; > @Override public String getBotToken() < // TODO return null; > >
- getBotUsername() : This method must always return your Bot username. May look like:
@Override public String getBotUsername() < return "myamazingbot"; >
@Override public String getBotToken() < return "123456789:qwertyuioplkjhgfdsazxcvbnm"; >
@Override public void onUpdateReceived(Update update) < // We check if the update has a message and the message has text if (update.hasMessage() && update.getMessage().hasText()) < SendMessage message = new SendMessage(); // Create a SendMessage object with mandatory fields message.setChatId(update.getMessage().getChatId().toString()); message.setText(update.getMessage().getText()); try < execute(message); // Call method to send the message > catch (TelegramApiException e) < e.printStackTrace(); > > >
public class Main < public static void main(String[] args) < // TODO Instantiate Telegram Bots API // TODO Register our bot > >
- Instantiate Telegram Bots API: Easy as well, just create a new instance. Remember that a single instance can handle different bots but each bot can run only once (Telegram doesn’t support concurrent calls to GetUpdates ):
public class Main < public static void main(String[] args) < // You can use your own BotSession implementation if needed. TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class); // TODO Register our bot > >
public class Main < public static void main(String[] args) < try < TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class); botsApi.registerBot(new MyAmazingBot()); > catch (TelegramApiException e) < e.printStackTrace(); > > >
Telegram боты на Java и где они обитают
В этом посте хочется разобрать создание ботов в телеграмме, ведь их
очень интереснописать (по крайней мере, для новичков).Для начала нам нужно создать приложение на спринге. Но я думаю, каждый уже умеет это делать.
Затем добавим зависимости, многие пользуются telegrambots-spring-boot-starter, но мне как-то не довелось увидеться с ним, поэтому используем самый обычный API.
org.telegram telegrambots 6.5.0 Теперь создадим файл application.yaml в папке resources. В нём напишем токен бота.
Telegram-bots ещё требует имя, но вводить настоящее — не обязательно.bot: token: 6098243395:AAFwSeKCFxh6kOTPPfcSYTdTuhqRZyBfULA
Создадим наш первый и основной компонент. В нём мы будем регистрировать бота и обрабатывать сообщения.
@Component public class BotComponent extends TelegramLongPollingBot < // Создаём их объект для регистрации private final TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class); // Достаём токен бота @Value("$") private String botToken; @PostConstruct private void init() throws TelegramApiException < telegramBotsApi.registerBot(this); // Регистрируем бота >public BotComponent() throws TelegramApiException <> @Override public void onUpdateReceived(Update update) < //Проверим, работает ли наш бот. System.out.println(update.getMessage().getText()); >@Override public String getBotUsername() < return "bot"; >@Override public String getBotToken() < return botToken; >>
Теперь начинаем работать с косяками api телеграмма и как-то их обрабатывать.
Самая главная проблема — у api телеграмма отсутствует один общий интерфейс, который бы объединял все возможные виды апдейта (за исключением BotApiMethod). Обычное сообщение и SendPhoto разделены и у них нет ничего общего, а нам нужно выдавить абстракции для того, чтобы всё легко расширялось, поэтому нам придётся поговнокодить. (Возможно реализация этого может выглядеть лучше).В том числе, нам нужно определить тип сообщения, для дальнейшего правильного использования.
Для этого создадим класс ClassifiedUpdate. Я использую Lombok, если вас это испугало, то почитайте, что это такое.
public class ClassifiedUpdate < @Getter private final TelegramType telegramType; // enum, чтобы всё выглядило красиво @Getter private final Long userId; // тот же chat-id, но выглядит красивее и получить его легче @Getter private String name; // получим имя пользователя. Именно имя, не @username @Getter private String commandName; // если это команда, то запишем её @Getter private final Update update; // сохраним сам update, чтобы в случае чего, его можно было достать @Getter private final Listargs; // просто поделим текст сообщения, в будущем это поможет @Getter private String userName; // @username public ClassifiedUpdate(Update update) < this.update = update; this.telegramType = handleTelegramType(); this.userId = handleUserId(); this.args = handleArgs(); this.commandName = handleCommandName(); >//Обработаем команду. public String handleCommandName() < if(update.hasMessage()) < if(update.getMessage().hasText()) < if(update.getMessage().getText().startsWith("/")) < return update.getMessage().getText().split(" ")[0]; >else return update.getMessage().getText(); > > if(update.hasCallbackQuery()) < return update.getCallbackQuery().getData().split(" ")[0]; >return ""; > //Обработаем тип сообщения private TelegramType handleTelegramType() < if(update.hasCallbackQuery()) return TelegramType.CallBack; if(update.hasMessage()) < if(update.getMessage().hasText()) < if(update.getMessage().getText().startsWith("/")) return TelegramType.Command; else return TelegramType.Text; >else if(update.getMessage().hasSuccessfulPayment()) < return TelegramType.SuccessPayment; >else if(update.getMessage().hasPhoto()) return TelegramType.Photo; > else if(update.hasPreCheckoutQuery()) < return TelegramType.PreCheckoutQuery; >else if(update.hasChatJoinRequest()) < return TelegramType.ChatJoinRequest; >else if(update.hasChannelPost()) < return TelegramType.ChannelPost; >else if(update.hasMyChatMember()) < return TelegramType.MyChatMember; >if(update.getMessage().hasDocument()) < return TelegramType.Text; >return TelegramType.Unknown; > //Достанем userId, имя и username из любого типа сообщений. private Long handleUserId() < if (telegramType == TelegramType.PreCheckoutQuery) < name = getNameByUser(update.getPreCheckoutQuery().getFrom()); userName = update.getPreCheckoutQuery().getFrom().getUserName(); return update.getPreCheckoutQuery().getFrom().getId(); >else if(telegramType == TelegramType.ChatJoinRequest) < name = getNameByUser(update.getChatJoinRequest().getUser()); userName = update.getChatJoinRequest().getUser().getUserName(); return update.getChatJoinRequest().getUser().getId(); >else if (telegramType == TelegramType.CallBack) < name = getNameByUser(update.getCallbackQuery().getFrom()); userName = update.getCallbackQuery().getFrom().getUserName(); return update.getCallbackQuery().getFrom().getId(); >else if(telegramType == TelegramType.MyChatMember) < name = update.getMyChatMember().getChat().getTitle(); userName = update.getMyChatMember().getChat().getUserName(); return update.getMyChatMember().getFrom().getId(); >else < name = getNameByUser(update.getMessage().getFrom()); userName = update.getMessage().getFrom().getUserName(); return update.getMessage().getFrom().getId(); >> //Разделим сообщение на аргументы private List handleArgs() < Listlist = new LinkedList<>(); if(telegramType == TelegramType.Command) < String[] args = getUpdate().getMessage().getText().split(" "); Collections.addAll(list, args); list.remove(0); return list; >else if (telegramType == TelegramType.Text) < list.add(getUpdate().getMessage().getText()); return list; >else if (telegramType == TelegramType.CallBack) < String[] args = getUpdate().getCallbackQuery().getData().split(" "); Collections.addAll(list, args); list.remove(0); return list; >return new ArrayList<>(); > //Вынесли имя в другой метод private String getNameByUser(User user) < if(user.getIsBot()) return "BOT"; if(!user.getFirstName().isBlank() || !user.getFirstName().isEmpty()) return user.getFirstName(); if(!user.getUserName().isBlank() || !user.getUserName().isEmpty()) return user.getUserName(); return "noname"; >//Лог public String getLog()
Это выглядит ужасно и некрасиво, обязательно как-то отрефакторим это, но не сегодня.
Хотел бы объяснить, зачем я разделил @username и Имя Фамилия.Дело в том, что некоторые пользователи не имеют имя и фамилию в настройках профиля, а некоторые имеют только это. В общем, мы предусмотрели этот момент. И теперь если мы захотим написать: Привет, Илья! У нас никогда не будет: Привет, null!. Мы ведь не хотим отставать от глаза бога.
Тем, кому лень писать код, держите TelegramType:
Двигаемся дальше, мы обработали их апдейт и теперь нам пора обработать свой апдейт, но перед этим нам нужно создать ещё свой ответ. Выглядит он не так ужасно, но ужасно 🙂
Это нам очень сильно поможет в будущем, нужно только верить.
@Data public class Answer < private SendDocument sendDocument; private SendPhoto sendPhoto; private SendVideo sendVideo; private SendVideoNote sendVideoNote; private SendSticker sendSticker; private SendAudio sendAudio; private SendVoice sendVoice; private SendMediaGroup sendMediaGroup; private SetChatPhoto setChatPhoto; private AddStickerToSet addStickerToSet; private SetStickerSetThumb setStickerSetThumb; private CreateNewStickerSet createNewStickerSet; private UploadStickerFile uploadStickerFile; private EditMessageMedia editMessageMedia; private SendAnimation sendAnimation; private BotApiMethodbotApiMethod; >
На самом деле, всё можно сделать и без этого класса, если вы собираетесь отвечать пользователю только сообщениями или коллбэками. Потому что в будущем этот класс ещё и увеличит немного кода. Я лишь стараюсь увеличить расширяемость, чтобы внедрение новой фичи делалось быстро и легко.
Теперь нам как-то нужно работать с пользователями, поэтому с помощью Spring JPA создадим сущность пользователя.
@Entity @Table(name = "users") @Getter @Setter public class User
Как вы можете заметить, у пользователя есть состояние, это поможет нам для проведения интерактивов и т.д. Также я использую у permissions тип Long, потому что обычно это:
Это просто и удобно и лениво, но если кто-то хочет, то может заморочиться.
Вернёмся к состоянию, напишем простую сущность для состояния :@Entity @Table(name = "state") @Getter @Setter public class State < @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Long Id; @Column(name = "value") private String stateValue; public boolean inState() < return stateValue != null; >@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn private User user; >
Для чего нам нужно состояние?
К примеру, пользователь захотел пополнить баланс, и мы просим его ввести сумму пополнения. Если мы не узнаем, что прямо сейчас он вводит сумму пополнения, то будем обрабатывать его команду: 100, как обычную. В общем, нам нужно состояние.Дальше нам нужно создать обработчик сообщений, в нашем случае они будут разные и их будет много, поэтому создадим интерфейс Handler.
@MappedSuperclass public interface Handler < // Какой тип сообщения будет обработан TelegramType getHandleType(); // Приоритет обработчика int priority(); // Условия, при которых мы воспользуемся этим обработчиком boolean condition(User user, ClassifiedUpdate update); // В этом методе, с помощью апдейта мы будем получать answer Answer getAnswer(User user, ClassifiedUpdate update); >
Обработчик выполняет функцию хранения комманд. Теперь нам нужно создать команды для обработчика. Создадим интерфейс Command.
@MappedSuperclass public interface Command < // Каким обработчиком будет пользоваться команда Class handler(); // С помощью чего мы найдём эту команду Object getFindBy(); // Ну и тут мы уже получим ответ на самом деле Answer getAnswer(ClassifiedUpdate update, User user); >
Теперь как-то надо найти команды для обработчика, поэтому создадим класс AbstractHandler.
@MappedSuperclass public abstract class AbstractHandler implements Handler < protected final MapallCommands = new HashMap<>(); // Найдём все команды для обработчика @Autowired private List commands; protected abstract HashMap createMap(); // Тут мы распихиваем команды по хэшмапе, чтобы потом было удобнее доставать :/ @PostConstruct private void init() < commands.forEach(c -> < allCommands.put(c.getFindBy(), c); if(Objects.equals(c.handler().getName(), this.getClass().getName())) < createMap().put(c.getFindBy(), c); System.out.println(c.getClass().getSimpleName() + " was added for " + this.getClass().getSimpleName()); >>); > >
Это конечно всё хорошо, но нам нужно собрать все обработчики в одном месте. И отправить наш ClassifiedUpdate в эту бездонную бочку. Назовём эту штуку HandlersMap, просто потому что я снова распихиваю обработчики по хэшмапе 🙂
@Component public class HandlersMap < private HashMap> hashMap = new HashMap<>(); private final List handlers; // Тут точно также находим все обработчики, просто в первом случае я использовал // @Autowired. Это немного лучше. public HandlersMap(List handlers) < this.handlers = handlers; >@PostConstruct private void init() < for(Handler handler : handlers) < if(!hashMap.containsKey(handler.getHandleType())) hashMap.put(handler.getHandleType(), new ArrayList<>()); hashMap.get(handler.getHandleType()).add(handler); > hashMap.values().forEach(h -> h.sort(new Comparator() < @Override public int compare(Handler o1, Handler o2) < return o2.priority() - o1.priority(); >>)); > public Answer execute(ClassifiedUpdate classifiedUpdate, User user) < if(!hashMap.containsKey(classifiedUpdate.getTelegramType())) return new Answer(); for (Handler handler : hashMap.get(classifiedUpdate.getTelegramType())) < if(handler.condition(user, classifiedUpdate)) return handler.getAnswer(user, classifiedUpdate); >return null; > >
Теперь нам нужна ещё прослойка в виде ClassifiedUpdateHandler’a. Там мы будем доставать пользователя из базы данных и может что-то ещё. Просто добавим его.
Класс ClassifiedUpdateHandler:@Service public class ClassifiedUpdateHandler < private final UserService userService; private final HandlersMap commandMap; public ClassifiedUpdateHandler(UserService userService, HandlersMap commandMap) < this.userService = userService; this.commandMap = commandMap; >public Answer request(ClassifiedUpdate classifiedUpdate) < return commandMap.execute(classifiedUpdate, userService.findUserByUpdate(classifiedUpdate)); >>
Тут ничего особенного, пропустим объяснения. Намного интереснее в классе UserService.
До этого, благо, мы успели всё обработать и на 100% достать id пользователя и его имя.@Service public class UserService < private final UserRepository userRepository; private final StateRepository stateRepository; public UserService(UserRepository userRepository, StateRepository stateRepository) < this.userRepository = userRepository; this.stateRepository = stateRepository; >public User findUserByUpdate(ClassifiedUpdate classifiedUpdate) < // Проверим, существует ли этот пользователь. if(userRepository.findByChatId(classifiedUpdate.getUserId()) != null) < User user = userRepository.findByChatId(classifiedUpdate.getUserId()); // Если мы не смогли до этого записать имя пользователя, то запишем его. if(user.getUserName() == null && classifiedUpdate.getUserName() != null) user.setUserName(classifiedUpdate.getUserName()); // Проверим менял ли пользователя имя. if(user.getUserName() != null) if (!user.getUserName().equals(classifiedUpdate.getUserName())) user.setUserName(classifiedUpdate.getUserName()); if(!user.getName().equals(classifiedUpdate.getName())) user.setName(classifiedUpdate.getName()); return user; >try < User user = new User(); user.setName(classifiedUpdate.getName()); user.setPermissions(0L); user.setChatId(classifiedUpdate.getUserId()); user.setUserName(classifiedUpdate.getUserName()); State state = new State(); state.setStateValue(null); state.setUser(user); stateRepository.save(state); user.setState(state); userRepository.save(user); return user; >catch (Exception e) < e.printStackTrace(); >return null; > >
Всё готово, теперь пора создать наш первый Handler и Command для примера. Но для начала напишем Builder для сообщений.
public class SendMessageBuilder < private SendMessage sendMessage; public SendMessageBuilder() < this.sendMessage = new SendMessage(); >public SendMessageBuilder chatId(Long chatId) < this.sendMessage.setChatId(chatId); return this; >public SendMessageBuilder message(String message) < this.sendMessage.setText(message); return this; >public Answer build() throws Exception < if(sendMessage.getChatId() == null) throw new Exception("Id must be not null"); Answer answer = new Answer(); answer.setBotApiMethod(sendMessage); return answer; >>
Вот теперь можем написать Handler и Command.
@Component public class CommandHandler extends AbstractHandler < private HashMaphashMap = new HashMap<>(); @Override protected HashMap createMap() < return hashMap; >@Override public TelegramType getHandleType() < return TelegramType.Command; >@Override public int priority() < return 1; >@Override public boolean condition(User user, ClassifiedUpdate update) < return hashMap.containsKey(update.getCommandName()); >@Override public Answer getAnswer(User user, ClassifiedUpdate update) < return hashMap.get(update.getCommandName()).getAnswer(update, user); >>
@Component public class StartCommand implements Command < @Override public Class handler() < return CommandHandler.class; >@Override public Object getFindBy() < return "/start"; >@SneakyThrows @Override public Answer getAnswer(ClassifiedUpdate update, User user) < return new SendMessageBuilder().chatId(user.getChatId()).message("Hello!").build(); >>
Я постарался сделать практическое пособие. Тут нужно много чего дорабатывать.
Код я писал очень давно, поэтому что-то возможно уже нужно обновить, просто решил опубликовать свои наработки в открытый доступ.
В итоге должен получиться простой и расширяемый бот.
Если эта статья вам понравиться, то можно всё допилить и получить невероятно мощную штуку для написания телеграмм ботов, к примеру, выкатить свои аннотации и т.д.
Спасибо за внимание!