host.anzo.commons.socials.telegram.ATelegramBot Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-core Show documentation
Show all versions of commons-core Show documentation
Commons library to make me happy.
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) {
}
}