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

host.anzo.commons.socials.telegram.ATelegramBot Maven / Gradle / Ivy

There is a newer version: 1.29
Show newest version
package host.anzo.commons.socials.telegram;

import host.anzo.commons.graphics.text.TextAlignment;
import host.anzo.commons.graphics.text.TextFormat;
import host.anzo.commons.graphics.text.TextRenderer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient;
import org.telegram.telegrambots.longpolling.util.LongPollingSingleThreadUpdateConsumer;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.methods.send.SendPhoto;
import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage;
import org.telegram.telegrambots.meta.api.objects.InputFile;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.message.Message;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboardMarkup;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.generics.TelegramClient;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author ANZO
 */
@Slf4j
public abstract class ATelegramBot implements LongPollingSingleThreadUpdateConsumer {
    protected TelegramClient client;
	protected @Getter String botName;
    protected @Getter String token;
    protected boolean isAsync;

    protected static Font FONT_TITLE;
    protected static Font FONT_CONTENT;

    protected static final int MAX_MESSAGE_SIZE = 4096;
    protected static final int MAX_CAPTION_SIZE = 1024;

    private final ConcurrentHashMap failedToSendMessages = new ConcurrentHashMap<>();

    /**
     * Telegram bot default constructor
     * @param botName bot name
     * @param token bot token
     */
    public ATelegramBot(String botName, String token, boolean isAsync) {
        this.token = token;
        this.botName = botName;
        this.isAsync = isAsync;
        this.client = new OkHttpTelegramClient(token);

        try (InputStream is = getClass().getClassLoader().getResourceAsStream("strongsword_text.ttf")) {
            if (is == null) {
                log.error("Can't read font for TelegramService from resources!");
                return;
            }
            final Font font = Font.createFont(Font.TRUETYPE_FONT, is);
            FONT_TITLE = font.deriveFont(Font.BOLD, 25f);
            FONT_CONTENT = font.deriveFont(12.5f);
        }
        catch (Exception e) {
            log.error("Error while loading font", e);
        }
    }

    public void onTick() {
        for (Map.Entry entry : failedToSendMessages.entrySet()) {
            if (entry.getValue() <= System.currentTimeMillis()) {
                failedToSendMessages.remove(entry.getKey());
                try {
                    entry.getKey().run();
                }
                catch (Exception e) {
                    log.error("Error while resend message", e);
                }
            }
        }
    }

    /**
     * Attach keyboard to a specified update message
     * @param update update message
     * @return inline keyboard markup will be attached to the update message
     */
    protected abstract InlineKeyboardMarkup getInlineKeyboardMarkup(Update update);

    /**
     * @return inline keyboard markup used for current bot
     */
    protected abstract ReplyKeyboardMarkup getReplyKeyboardMarkup();

    /**
     * @return local path to image will be used as background for photo messages
     */
    protected abstract String getDefaultBackgroundImagePath();

    /**
     * Called when server going shutdown
     */
    public abstract void onServerShutdown();

    /**
     * @return {@code true} if bot enabled, {@code false} otherwise
     */
    public abstract boolean isEnabled();

    /**
     * Send a message synchronously
     * @param message message to send
     * @return sent message id, {@code 0} if message failed to send
     */
    protected int sendMessageWithResult(SendMessage message) {
        try {
            final Message sentMessage = client.execute(message);
            return sentMessage != null ? sentMessage.getMessageId() : 0;
        }
        catch (TelegramApiException ignored) {
            return 0;
        }
    }

    /**
     * Send a message asynchronously
     * @param message message to send
     */
    protected void sendMessage(SendMessage message) {
        try {
            if (isAsync) {
                client.executeAsync(message);
            }
            else {
                client.execute(message);
            }
        }
        catch (TelegramApiException ignored) {
        }
    }

    /**
     * Send text message to specified chat
     * @param chatId symbolical chat name
     * @param messageText message text
     */
    protected void sendMessage(String chatId, String messageText) {
        if (StringUtils.isEmpty(chatId)) {
            return;
        }
        final SendMessage message = SendMessage.builder().parseMode("Markdown")
                .chatId(chatId)
                .text(StringUtils.abbreviate(messageText, MAX_MESSAGE_SIZE)).build();
        sendMessage(message);
    }

    /**
     * Send text message to specified chat
     * @param chatId symbolical chat name
     * @param messageText message text
     */
    protected void sendMessage(long chatId, String messageText) {
        if (chatId != 0) {
            return;
        }
        final SendMessage message = SendMessage.builder().parseMode("Markdown")
                .chatId(chatId)
                .text(StringUtils.abbreviate(messageText, MAX_MESSAGE_SIZE)).build();
        sendMessage(message);
    }

    /**
     * Send a text message to specified chat with the possibility to attach inline keyboard
     * @param update update object
     * @param chatId channel chat id
     * @param messageText message text
     * @param withInlineKeyboard {@code true} if need attach inline keyboard, {@code false} otherwise
     */
    protected void sendMessage(Update update, Long chatId, String messageText, boolean withInlineKeyboard) {
        if (chatId == null) {
            return;
        }
        final SendMessage message = SendMessage.builder().parseMode("Markdown")
                .chatId(Long.toString(chatId))
                .text(StringUtils.abbreviate(messageText, MAX_MESSAGE_SIZE)).build();

        if (withInlineKeyboard) {
            final InlineKeyboardMarkup markupInline = getInlineKeyboardMarkup(update);
            if (markupInline != null) {
                message.setReplyMarkup(markupInline);
            }
        }
        this.sendMessage(message);
    }

    /**
     * Create image with specified text and additional image
     * @param chatId chat id where need a post created message
     * @param title message title
     * @param messageText message text
     * @param additionalImagePath additional image displayed on the right side
     */
    protected void sendMessage(Long chatId, String title, String messageText, String additionalImagePath) {
        if (chatId == null) {
            return;
        }
        try (InputStream imageStream = drawMessageImage(title, messageText, additionalImagePath)) {
            if (imageStream == null) {
                sendMessage(chatId, messageText);
                return;
            }
            final SendPhoto photo = SendPhoto.builder()
                    .chatId(Long.toString(chatId))
                    .photo(new InputFile(imageStream, title))
                    .build();
            final ReplyKeyboardMarkup replyKeyboardMarkup = getReplyKeyboardMarkup();
            if (replyKeyboardMarkup != null) {
                photo.setReplyMarkup(replyKeyboardMarkup);
            }
            client.execute(photo);
        } catch (Exception e) {
            log.error("Error while sendMessage", e);
        }
    }

    /**
     * Send a message with image to the channel with specified chatId
     * @param chatId channel chat id
     * @param title message title
     * @param messageText message text
     * @param caption image caption
     * @param additionalImagePath local path to image
     */
    protected void sendMessage(Long chatId, String title, String messageText, String caption, String additionalImagePath) {
        if (chatId == null) {
            return;
        }
        sendMessage(Long.toString(chatId), title, messageText, caption, additionalImagePath);
    }

    /**
     * Send a message with image to the channel with specified chatId
     * @param chatId channel chat id
     * @param title message title
     * @param messageText message text
     * @param caption image caption
     * @param additionalImagePath local path to image
     */
    protected void sendMessage(String chatId, String title, String messageText, String caption, String additionalImagePath) {
        if (StringUtils.isEmpty(chatId)) {
            return;
        }
        try (InputStream imageStream = drawMessageImage(title, messageText, additionalImagePath)) {
            if (imageStream == null) {
                sendMessage(chatId, messageText);
                return;
            }
            final SendPhoto photo = SendPhoto.builder()
                    .parseMode("Markdown")
                    .chatId(chatId)
                    .photo(new InputFile(imageStream, title))
                    .build();
            if (caption.length() <= MAX_CAPTION_SIZE) {
                photo.setCaption(caption);
                client.execute(photo);
            }
            else {
                client.execute(photo);
                final SendMessage message = SendMessage.builder().parseMode("Markdown")
                        .chatId(chatId)
                        .text(StringUtils.abbreviate(caption, MAX_MESSAGE_SIZE)).build();
                client.execute(message);
            }
        } catch (TelegramApiRequestException e) {
            if (e.getErrorCode() == 429) {
                final int retrySeconds = e.getParameters().getRetryAfter();
                if (retrySeconds > 0) {
                    addResendTask(() -> sendMessage(chatId, title, messageText, caption, additionalImagePath), retrySeconds);
                }
                log.warn("Can't sendMessage title=[{}] messageText=[{}] caption=[{}] additionalImagePath=[{}] apiResponse=[{}]", title, messageText, caption, additionalImagePath, e.getApiResponse());
            }
            else {
                log.error("Error while sendMessage title=[{}] messageText=[{}] caption=[{}] additionalImagePath=[{}] apiResponse=[{}]", title, messageText, caption, additionalImagePath, e.getApiResponse(), e);
            }
        }
        catch (Exception e) {
            log.error("Error while sendMessage title=[{}] messageText=[{}] caption=[{}] additionalImagePath=[{}]", title, messageText, caption, additionalImagePath, e);
        }
    }

    /**
     * Delete a message from channel with specified chatId
     * @param chatId channel chat id
     * @param message message to delete
     */
    protected void deleteMessage(String chatId, @NotNull Message message) {
        final DeleteMessage deleteMessage = DeleteMessage.builder()
                .chatId(chatId)
                .messageId(message.getMessageId())
                .build();
        try {
            if (isAsync) {
                client.executeAsync(deleteMessage);
            }
            else {
                client.execute(deleteMessage);
            }
        } catch (TelegramApiException ignored) {
        }
    }

    /**
     * Delete a message from channel with specified chatId
     * @param chatId channel chat id
     * @param messageId message ID to delete
     */
    protected void deleteMessage(String chatId, int messageId) {
        final DeleteMessage deleteMessage = DeleteMessage.builder()
                .chatId(chatId)
                .messageId(messageId)
                .build();
        try {
            if (isAsync) {
                client.executeAsync(deleteMessage);
            }
            else {
                client.execute(deleteMessage);
            }
        } catch (TelegramApiException ignored) {
        }
    }

    /**
     * Delete a message from channel with specified chatId
     * @param chatId channel chat id
     * @param messageId message to delete
     */
    protected void deleteMessage(long chatId, int messageId) {
        final DeleteMessage deleteMessage = DeleteMessage.builder()
                .chatId(chatId)
                .messageId(messageId)
                .build();
        try {
            if (isAsync) {
                client.executeAsync(deleteMessage);
            }
            else {
                client.execute(deleteMessage);
            }
        } catch (TelegramApiException ignored) {
        }
    }

    /**
     * Draw title and message text at specified image
     * @param title title to draw
     * @param messageText message to draw
     * @param additionalImagePath path to local image
     * @return result image with title and message InputStream
     */
    private @Nullable InputStream drawMessageImage(String title, String messageText, String additionalImagePath) {
        try {
            final String backgroundImagePath = getDefaultBackgroundImagePath();
            if (StringUtils.isEmpty(backgroundImagePath)) {
                return null;
            }
            
            final BufferedImage image = ImageIO.read(new File(backgroundImagePath));
            final Graphics2D graphics = (Graphics2D)image.getGraphics();
            graphics.setRenderingHint(
                    RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);

            // Draw title
            graphics.setFont(FONT_TITLE);
            graphics.setColor(Color.WHITE);
            graphics.drawString(title.toUpperCase(), 10, 30);

            graphics.drawLine(10, 40, 330, 40);

            final Rectangle bounds = new Rectangle(10, 50, 330, 75);
            TextRenderer.drawString(
                    graphics,
                    messageText,
                    FONT_CONTENT,
                    Color.WHITE,
                    bounds,
                    TextAlignment.TOP_LEFT,
                    TextFormat.NONE
            );

            // Draw additional image
            final File additionalImageFile = new File(additionalImagePath);
            if (additionalImageFile.exists()) {
                final BufferedImage additionalImage = ImageIO.read(additionalImageFile);
                graphics.drawImage(additionalImage, 345, 30, null);
            }
            graphics.dispose();

            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                ImageIO.write(image, "png", outputStream);
                return new ByteArrayInputStream(outputStream.toByteArray());
            }
        } catch (Exception e) {
            log.error("Error while drawMessageImage", e);
            return null;
        }
    }

    /**
     * Add specified runnable to resend list (a message will be resent after specified seconds passed)
     * @param runnable runnable with a message send task
     * @param retryAfterSeconds seconds to resend
     */
    protected void addResendTask(Runnable runnable, int retryAfterSeconds) {
        failedToSendMessages.put(runnable, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(retryAfterSeconds));
    }

    /**
     * Called when bot receives an update message
     * @param update update message
     */
    @Override
    public void consume(Update update) {
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy