All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.unafraid.telegrambot.bots.AbstractTelegramBot Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2017 Rumen Nikiforov 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.github.unafraid.telegrambot.bots;

import com.github.unafraid.telegrambot.handlers.*;
import com.github.unafraid.telegrambot.util.BotUtil;
import com.github.unafraid.telegrambot.util.IThrowableFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer;
import org.telegram.telegrambots.meta.api.methods.GetMe;
import org.telegram.telegrambots.meta.api.methods.botapimethods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.groupadministration.SetChatPhoto;
import org.telegram.telegrambots.meta.api.methods.send.*;
import org.telegram.telegrambots.meta.api.methods.stickers.*;
import org.telegram.telegrambots.meta.api.methods.updates.SetWebhook;
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageMedia;
import org.telegram.telegrambots.meta.api.objects.*;
import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMemberUpdated;
import org.telegram.telegrambots.meta.api.objects.inlinequery.ChosenInlineQuery;
import org.telegram.telegrambots.meta.api.objects.inlinequery.InlineQuery;
import org.telegram.telegrambots.meta.api.objects.message.Message;
import org.telegram.telegrambots.meta.api.objects.payments.PreCheckoutQuery;
import org.telegram.telegrambots.meta.api.objects.payments.ShippingQuery;
import org.telegram.telegrambots.meta.api.objects.polls.PollAnswer;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.generics.TelegramClient;

import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author UnAfraid
 */
public class AbstractTelegramBot implements LongPollingUpdateConsumer, TelegramClient {
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTelegramBot.class);
    private static final Pattern COMMAND_ARGS_PATTERN = Pattern.compile("\"([^\"]*)\"|([^\\s]+)");

    private final List handlers = new ArrayList<>();
    private volatile IAccessLevelValidator accessLevelValidator = null;
    private volatile String username;

    private final TelegramClient telegramClient;

    public AbstractTelegramBot(TelegramClient telegramClient) {
        this.telegramClient = telegramClient;
    }

    @Override
    public void consume(List updates) {
        for (Update update : updates) {
            processUpdate(update);
        }
    }

    private void processUpdate(Update update) {
        try {
            final List updateHandlers = getAvailableHandlers(IUpdateHandler.class);
            for (IUpdateHandler updateHandler : updateHandlers) {
                try {
                    if (updateHandler.onUpdate(this, update)) {
                        return;
                    }
                } catch (Exception ex) {
                    LOGGER.error("Uncaught exception in onUpdate: {}", update, ex);
                }
            }

            if (update.hasChosenInlineQuery()) {
                handleUpdate(IChosenInlineQueryHandler.class, update, Update::getChosenInlineQuery, ChosenInlineQuery::getFrom, handler -> handler.onChosenInlineQuery(this, update, update.getChosenInlineQuery()));
                return;
            }

            if (update.hasInlineQuery()) {
                handleUpdate(IInlineQueryHandler.class, update, Update::getInlineQuery, InlineQuery::getFrom, handler -> handler.onInlineQuery(this, update, update.getInlineQuery()));
                return;
            }

            if (update.hasCallbackQuery()) {
                handleUpdate(ICallbackQueryHandler.class, update, Update::getCallbackQuery, CallbackQuery::getFrom, handler -> handler.onCallbackQuery(this, update, update.getCallbackQuery()));
                return;
            }

            if (update.hasEditedMessage()) {
                handleUpdate(IEditedMessageHandler.class, update, Update::getEditedMessage, Message::getFrom, handler -> handler.onEditMessage(this, update, update.getEditedMessage()));
                return;
            }

            if (update.hasChannelPost()) {
                handleUpdate(IChannelPostHandler.class, update, Update::getChannelPost, Message::getFrom, handler -> handler.onChannelPost(this, update, update.getChannelPost()));
                return;
            }

            if (update.hasEditedChannelPost()) {
                handleUpdate(IEditedChannelPostHandler.class, update, Update::getChannelPost, Message::getFrom, handler -> handler.onEditedChannelPost(this, update, update.getEditedChannelPost()));
                return;
            }

            if (update.hasShippingQuery()) {
                handleUpdate(IShippingQueryHandler.class, update, Update::getShippingQuery, ShippingQuery::getFrom, handler -> handler.onShippingQuery(this, update, update.getShippingQuery()));
                return;
            }

            if (update.hasPreCheckoutQuery()) {
                handleUpdate(IPreCheckoutQueryHandler.class, update, Update::getPreCheckoutQuery, PreCheckoutQuery::getFrom, handler -> handler.onPreCheckoutQuery(this, update, update.getPreCheckoutQuery()));
                return;
            }

            if (update.hasPoll()) {
                handleUpdate(IPollHandler.class, update, u -> u, u -> u.getMessage().getFrom(), handler -> handler.onPoll(this, update, update.getPoll()));
                return;
            }

            if (update.hasPollAnswer()) {
                handleUpdate(IPollAnswerHandler.class, update, Update::getPollAnswer, PollAnswer::getUser, handler -> handler.onPollAnswer(this, update, update.getPollAnswer()));
                return;
            }

            if (update.hasMyChatMember()) {
                handleUpdate(IHasMyChatMemberHandler.class, update, Update::getMyChatMember, ChatMemberUpdated::getFrom, handler -> handler.onHasMyChatMember(this, update, update.getMyChatMember()));
                return;
            }

            if (update.hasChatMember()) {
                handleUpdate(IChatMemberHandler.class, update, Update::getChatMember, ChatMemberUpdated::getFrom, handler -> handler.onChatMember(this, update, update.getChatMember()));
                return;
            }

            if (update.hasChatJoinRequest()) {
                handleUpdate(IChatJoinRequestHandler.class, update, Update::getChatJoinRequest, ChatJoinRequest::getUser, handler -> handler.onChatJoinRequest(this, update, update.getChatJoinRequest()));
                return;
            }

            if (update.hasMessage()) {
                if (update.getMessage().hasDocument()) {
                    handleUpdate(IDocumentMessageHandler.class, update, Update::getMessage, Message::getFrom, handler -> handler.onDocumentSent(this, update, update.getMessage()));
                    return;
                }

                handleIncomingMessage(update);
                return;
            }

            final List unknownHandlers = getAvailableHandlers(IUnknownUpdateHandler.class);
            if (unknownHandlers.isEmpty()) {
                LOGGER.warn("Update doesn't contains neither ChosenInlineQuery/InlineQuery/CallbackQuery/EditedMessage/ChannelPost/EditedChannelPost/Message Update: {}", update);
                return;
            }

            for (IUnknownUpdateHandler unknownHandler : unknownHandlers) {
                try {
                    if (unknownHandler.onUnhandledUpdate(this, update)) {
                        return;
                    }
                } catch (Exception ex) {
                    LOGGER.error("Uncaught exception in onUnhandledUpdate: {}", update, ex);
                }
            }

        } catch (Exception e) {
            LOGGER.error("Failed to handle incoming update", e);
        }
    }

    /**
     * @param         the handler type
     * @param         the return type
     * @param clazz      the handler class
     * @param update     the update
     * @param dataMapper the data mapper function
     * @param idMapper   the id mapper function
     * @param action     the action to execute
     */
    private  void handleUpdate(Class clazz, Update update, Function dataMapper, Function idMapper, IThrowableFunction action) {
        final R query = dataMapper.apply(update);
        if (query == null) {
            return;
        }

        final User user = idMapper.apply(query);
        final List handlers = getAvailableHandlersForUser(clazz, user);
        for (T handler : handlers) {
            try {
                if (action.apply(handler)) {
                    break;
                }
            } catch (TelegramApiRequestException e) {
                LOGGER.warn("Exception caught on handler: {} error: {}", handler.getClass().getSimpleName(), e.getApiResponse(), e);
            } catch (Exception e) {
                LOGGER.warn("Exception caught on handler: {}", handler.getClass().getSimpleName(), e);
            }
        }
    }

    /**
     * @param text the message's text
     * @return Text without @BotNickname if specified
     */
    protected String processText(String text) {
        if ((text == null) || text.isEmpty()) {
            return null;
        }

        // Parse commands that goes like: @BotNickname help to /help
        if (text.startsWith("@" + getBotUsername() + " ")) {
            text = '/' + text.substring(("@" + getBotUsername() + " ").length());
        }
        // Parse commands that goes like: /help@BotNickname to /help
        else if (text.contains("@" + getBotUsername())) {
            text = text.replaceAll("@" + getBotUsername(), "");
            if (text.charAt(0) != '/') {
                text = '/' + text;
            }
        }
        return text;
    }

    private String getBotUsername() {
        if (username == null) {
            synchronized (this) {
                if (username == null) {
                    try {
                        final User user = telegramClient.execute(GetMe.builder().build());
                        username = user.getUserName();
                    } catch (TelegramApiException e) {
                        throw new IllegalStateException("failed to get bot username", e);
                    }
                }
            }
        }
        return username;
    }


    /**
     * @param update the update
     */
    private void handleIncomingMessage(Update update) {
        final Message message = update.getMessage();
        if (message == null) {
            return;
        }

        final String text = processText(message.getText());
        if ((text == null) || text.isEmpty()) {
            return;
        }

        // Parse arguments to a list
        final Matcher matcher = COMMAND_ARGS_PATTERN.matcher(text);
        if (matcher.find()) {
            String command = matcher.group();
            final List args = new ArrayList<>();
            String arg;
            while (matcher.find()) {
                arg = matcher.group(1);
                if (arg == null) {
                    arg = matcher.group(0);
                }

                args.add(arg);
            }

            final ICommandHandler handler = getHandler(command);
            if (handler != null) {
                try {
                    if (!validateAccessLevel(handler, message.getFrom())) {
                        BotUtil.sendMessage(telegramClient, message, message.getFrom().getUserName() + ": You are not authorized to use this function!", true, false, null);
                        return;
                    }

                    handler.onCommandMessage(this, update, message, args);
                } catch (TelegramApiRequestException e) {
                    LOGGER.warn("API Exception caught on handler: {}, response: {} message: {}", handler.getClass().getSimpleName(), e.getApiResponse(), message, e);
                } catch (Exception e) {
                    LOGGER.warn("Exception caught on handler: {}, message: {}", handler.getClass().getSimpleName(), message, e);
                }
            } else {
                for (IMessageHandler messageHandler : getAvailableHandlersForUser(IMessageHandler.class, message.getFrom())) {
                    try {
                        if (messageHandler.onMessage(this, update, message)) {
                            break;
                        }
                    } catch (TelegramApiRequestException e) {
                        LOGGER.warn("API Exception caught on handler: {}, response: {} message: {}", messageHandler.getClass().getSimpleName(), e.getApiResponse(), message, e);
                    } catch (Exception e) {
                        LOGGER.warn("Exception caught on handler: {}, message: {}", messageHandler.getClass().getSimpleName(), message, e);
                    }
                }
            }
        }
    }

    /**
     * Sets the Access Level Validator instance that will be used for future access level validations
     *
     * @param accessLevelValidator the access level validator implementation
     */
    public void setAccessLevelValidator(IAccessLevelValidator accessLevelValidator) {
        this.accessLevelValidator = accessLevelValidator;
    }

    /**
     * @return the Access Level Validator instance that will be used for future access level validations
     */
    public IAccessLevelValidator getAccessLevelValidator() {
        return accessLevelValidator;
    }

    /**
     * Registers ICommandHandler instance into a collection of handlers
     *
     * @param handler the ICommandHandler instance
     */
    public void addHandler(ITelegramHandler handler) {
        handlers.add(handler);
    }

    /**
     * Removes ICommandHandler instance from the collection of handlers
     *
     * @param handler the ICommandHandler instance
     * @return {@code true} if handler with such command name was previously registered, {@code false} otherwise
     */
    public boolean removeHandler(ITelegramHandler handler) {
        return handlers.remove(handler);
    }

    /**
     * @param command the command name
     * @return {@link ICommandHandler} command handler from the collection of handlers, {@code null} if not registered
     */
    public ICommandHandler getHandler(String command) {
        //@formatter:off
		return handlers.stream()
				.filter(handler -> handler instanceof ICommandHandler)
				.map(handler -> (ICommandHandler) handler)
				.filter(handler -> handler.getCommand().equalsIgnoreCase(command))
				.findFirst().orElse(null);
		//@formatter:on
    }

    /**
     * @return {@code Collection} the collection of ICommandHandler containing all currently registered handlers
     */
    public Collection getHandlers() {
        return Collections.unmodifiableCollection(handlers);
    }

    /**
     * Returns a {@code List} and verifies for access level if any of the handlers implements {@link ITelegramHandler}
     *
     * @param clazz the class of the handler
     * @param    the type of the handler
     * @return {@code List} with all handlers implementing the generic type provided
     */
    public  List getAvailableHandlers(Class clazz) {
        //@formatter:off
		return handlers.stream()
				.filter(clazz::isInstance)
				.map(clazz::cast)
				.collect(Collectors.toList());
		//@formatter:on
    }

    /**
     * Returns a {@code List} and verifies for access level if any of the handlers implements {@link ITelegramHandler}
     *
     * @param clazz the class of the handler
     * @param user  the user that requests this handler
     * @param    the type of the handler
     * @return {@code List} with all handlers implementing the generic type provided
     */
    public  List getAvailableHandlersForUser(Class clazz, User user) {
        //@formatter:off
		return handlers.stream()
				.filter(clazz::isInstance)
				.map(clazz::cast)
				.filter(messageHandler -> validateAccessLevel(messageHandler, user))
				.collect(Collectors.toList());
		//@formatter:on
    }

    /**
     * Validates access level
     *
     * @param      the type
     * @param handler the handler
     * @param user    the user requesting the that handler
     * @return {@code true} if user is able to use that handler, {@code false} otherwise (Not registered, doesn't have access and so)
     */
    public  boolean validateAccessLevel(T handler, User user) {
        final IAccessLevelValidator accessLevelValidator = this.accessLevelValidator;
        if (accessLevelValidator == null) {
            if (handler.getRequiredAccessLevel() > 0) {
                throw new IllegalStateException("Discovered handler with required access level > 0 but there's no access level validator implemented, please use DefaultTelegramBot#setAccessLevelValidator");
            }
            return true;
        }
        return accessLevelValidator.validate(handler, user);
    }

    @Override
    public > CompletableFuture executeAsync(Method method) throws TelegramApiException {
        return telegramClient.executeAsync(method);
    }

    @Override
    public > T execute(Method method) throws TelegramApiException {
        return telegramClient.execute(method);
    }

    @Override
    public Message execute(SendDocument sendDocument) throws TelegramApiException {
        return telegramClient.execute(sendDocument);
    }

    @Override
    public Message execute(SendPhoto sendPhoto) throws TelegramApiException {
        return telegramClient.execute(sendPhoto);
    }

    @Override
    public Boolean execute(SetWebhook setWebhook) throws TelegramApiException {
        return telegramClient.execute(setWebhook);
    }

    @Override
    public Message execute(SendVideo sendVideo) throws TelegramApiException {
        return telegramClient.execute(sendVideo);
    }

    @Override
    public Message execute(SendVideoNote sendVideoNote) throws TelegramApiException {
        return telegramClient.execute(sendVideoNote);
    }

    @Override
    public Message execute(SendSticker sendSticker) throws TelegramApiException {
        return telegramClient.execute(sendSticker);
    }

    @Override
    public Message execute(SendAudio sendAudio) throws TelegramApiException {
        return telegramClient.execute(sendAudio);
    }

    @Override
    public Message execute(SendVoice sendVoice) throws TelegramApiException {
        return telegramClient.execute(sendVoice);
    }

    @Override
    public List execute(SendMediaGroup sendMediaGroup) throws TelegramApiException {
        return telegramClient.execute(sendMediaGroup);
    }

    @Override
    public List execute(SendPaidMedia sendPaidMedia) throws TelegramApiException {
        return telegramClient.execute(sendPaidMedia);
    }

    @Override
    public Boolean execute(SetChatPhoto setChatPhoto) throws TelegramApiException {
        return telegramClient.execute(setChatPhoto);
    }

    @Override
    public Boolean execute(AddStickerToSet addStickerToSet) throws TelegramApiException {
        return telegramClient.execute(addStickerToSet);
    }

    @Override
    public Boolean execute(ReplaceStickerInSet replaceStickerInSet) throws TelegramApiException {
        return telegramClient.execute(replaceStickerInSet);
    }

    @Override
    public Boolean execute(SetStickerSetThumbnail setStickerSetThumbnail) throws TelegramApiException {
        return telegramClient.execute(setStickerSetThumbnail);
    }

    @Override
    public Boolean execute(CreateNewStickerSet createNewStickerSet) throws TelegramApiException {
        return telegramClient.execute(createNewStickerSet);
    }

    @Override
    public File execute(UploadStickerFile uploadStickerFile) throws TelegramApiException {
        return telegramClient.execute(uploadStickerFile);
    }

    @Override
    public Serializable execute(EditMessageMedia editMessageMedia) throws TelegramApiException {
        return telegramClient.execute(editMessageMedia);
    }

    @Override
    public java.io.File downloadFile(File file) throws TelegramApiException {
        return telegramClient.downloadFile(file);
    }

    @Override
    public InputStream downloadFileAsStream(File file) throws TelegramApiException {
        return telegramClient.downloadFileAsStream(file);
    }

    @Override
    public Message execute(SendAnimation sendAnimation) throws TelegramApiException {
        return telegramClient.execute(sendAnimation);
    }

    @Override
    public CompletableFuture executeAsync(SendDocument sendDocument) {
        return telegramClient.executeAsync(sendDocument);
    }

    @Override
    public CompletableFuture executeAsync(SendPhoto sendPhoto) {
        return telegramClient.executeAsync(sendPhoto);
    }

    @Override
    public CompletableFuture executeAsync(SetWebhook setWebhook) {
        return telegramClient.executeAsync(setWebhook);
    }

    @Override
    public CompletableFuture executeAsync(SendVideo sendVideo) {
        return telegramClient.executeAsync(sendVideo);
    }

    @Override
    public CompletableFuture executeAsync(SendVideoNote sendVideoNote) {
        return telegramClient.executeAsync(sendVideoNote);
    }

    @Override
    public CompletableFuture executeAsync(SendSticker sendSticker) {
        return telegramClient.executeAsync(sendSticker);
    }

    @Override
    public CompletableFuture executeAsync(SendAudio sendAudio) {
        return telegramClient.executeAsync(sendAudio);
    }

    @Override
    public CompletableFuture executeAsync(SendVoice sendVoice) {
        return telegramClient.executeAsync(sendVoice);
    }

    @Override
    public CompletableFuture> executeAsync(SendMediaGroup sendMediaGroup) {
        return telegramClient.executeAsync(sendMediaGroup);
    }

    @Override
    public CompletableFuture> executeAsync(SendPaidMedia sendPaidMedia) {
        return telegramClient.executeAsync(sendPaidMedia);
    }

    @Override
    public CompletableFuture executeAsync(SetChatPhoto setChatPhoto) {
        return telegramClient.executeAsync(setChatPhoto);
    }

    @Override
    public CompletableFuture executeAsync(AddStickerToSet addStickerToSet) {
        return telegramClient.executeAsync(addStickerToSet);
    }

    @Override
    public CompletableFuture executeAsync(ReplaceStickerInSet replaceStickerInSet) {
        return telegramClient.executeAsync(replaceStickerInSet);
    }

    @Override
    public CompletableFuture executeAsync(SetStickerSetThumbnail setStickerSetThumbnail) {
        return telegramClient.executeAsync(setStickerSetThumbnail);

    }

    @Override
    public CompletableFuture executeAsync(CreateNewStickerSet createNewStickerSet) {
        return telegramClient.executeAsync(createNewStickerSet);
    }

    @Override
    public CompletableFuture executeAsync(UploadStickerFile uploadStickerFile) {
        return telegramClient.executeAsync(uploadStickerFile);
    }

    @Override
    public CompletableFuture executeAsync(EditMessageMedia editMessageMedia) {
        return telegramClient.executeAsync(editMessageMedia);
    }

    @Override
    public CompletableFuture executeAsync(SendAnimation sendAnimation) {
        return telegramClient.executeAsync(sendAnimation);
    }

    @Override
    public CompletableFuture downloadFileAsync(File file) {
        return telegramClient.downloadFileAsync(file);
    }

    @Override
    public CompletableFuture downloadFileAsStreamAsync(File file) {
        return telegramClient.downloadFileAsStreamAsync(file);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy