Kleinanzeigen/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramService.java

211 lines
7.7 KiB
Java

package de.ph87.kleinanzeigen.telegram;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferDto;
import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.telegram.telegrambots.meta.api.methods.send.SendPhoto;
import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessages;
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageCaption;
import org.telegram.telegrambots.meta.api.objects.*;
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 javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Slf4j
@Service
@RequiredArgsConstructor
public class TelegramService {
private static final long CHAT_ID = 101138682L;
private static final String ICON_CHECK = "";
private static final String ICON_REMOVE = "";
private byte[] NO_IMAGE = null;
private final ObjectMapper objectMapper;
private final OfferService offerService;
private TelegramBot bot = null;
@PostConstruct
public void postConstruct() throws IOException {
final BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
ImageIO.write(img, "PNG", stream);
NO_IMAGE = stream.toByteArray();
try {
bot = new TelegramBot(readToken(), this::onUpdateReceived);
} catch (TelegramApiException | IOException e) {
log.error("Failed to start TelegramBot: {}", e.toString());
}
}
@EventListener(OfferDto.class)
public void onOfferChanged(final OfferDto offer) {
if (offer.is_existing_()) {
if (offer.getTelegramMessageId() == null) {
send(offer);
}else{
updateMessage(offer.getTelegramMessageId(), offer.getTelegramMessageId().getChatId());
}
} else {
remove(List.of(offer));
}
}
@PreDestroy
public void stop() {
log.info("Stopping Telegram bot...");
bot.stop();
log.info("Telegram bot stopped");
}
private static String readToken() throws IOException {
try (final FileInputStream stream = new FileInputStream("./telegram.token")) {
return new String(stream.readAllBytes(), StandardCharsets.UTF_8).trim();
}
}
private void onUpdateReceived(final Update update) {
if (update.hasMessage() && update.getMessage().hasText()) {
log.info("#{} \"{}\": {}", update.getMessage().getChat().getId(), update.getMessage().getChat().getUserName(), update.getMessage().getText());
} else if (update.hasCallbackQuery()) {
handleCallbackQuery(update.getCallbackQuery());
}
}
private void handleCallbackQuery(final CallbackQuery query) {
final MaybeInaccessibleMessage message = query.getMessage();
if (message.getChatId() != CHAT_ID) {
return;
}
try {
final InlineDto dto = objectMapper.readValue(query.getData(), InlineDto.class);
switch (dto.getCommand()) {
case HIDE -> hide(message);
case REMEMBER -> remember(message, true);
case UNREMEMBER -> remember(message, false);
default -> updateMessage(message, message.getChatId());
}
} catch (JsonProcessingException e) {
log.error("Failed to read InlineDto.", e);
}
}
private void hide(final MaybeInaccessibleMessage message) {
offerService.hideByTelegramMessageId(message.getMessageId(), true);
remove(message);
}
private void remember(final MaybeInaccessibleMessage message, final boolean remember) {
offerService.rememberByTelegramMessageId(message.getMessageId(), remember).ifPresentOrElse(
offer -> updateMessage(offer, message.getChatId(), message.getMessageId()),
() -> remove(message)
);
}
private void updateMessage(final MaybeInaccessibleMessage message, final Long chatId) {
offerService.findByTelegramMessageId(message.getMessageId()).ifPresentOrElse(
offer -> updateMessage(offer, chatId, message.getMessageId()),
() -> remove(message)
);
}
private void updateMessage(final OfferDto offer, final long chatId, final int messageId) {
try {
final EditMessageCaption edit = new EditMessageCaption(chatId + "", messageId, null, createText(offer), createKeyboard(offer), null, null);
edit.setParseMode("Markdown");
bot.execute(edit);
} catch (TelegramApiException | JsonProcessingException e) {
log.error("Failed to edit Message to #{}.", chatId, e);
}
}
private void send(final OfferDto offer) {
try {
final InputFile inputFile = offer.getImageURL() == null ? new InputFile(new ByteArrayInputStream(NO_IMAGE), "[Kein Bild]") : new InputFile(offer.getImageURL());
final SendPhoto send = new SendPhoto(CHAT_ID + "", inputFile);
log.debug("{}", send);
send.setCaption(createText(offer));
send.setReplyMarkup(createKeyboard(offer));
final Message message = bot.execute(send);
offerService.setTelegramMessageId(offer, message.getMessageId());
} catch (TelegramApiException | JsonProcessingException e) {
log.error("Failed to send Message to #{}.", CHAT_ID, e);
}
}
private String createText(final OfferDto offer) {
return "[%s](%s)\n%s\n%s".formatted(
offer.getTitle().replaceAll("\\[", "(").replaceAll("]", ")"),
offer.getArticleURL(),
offer.combineLocation(),
offer.getDescription()
);
}
private InlineKeyboardMarkup createKeyboard(final OfferDto offer) throws JsonProcessingException {
final InlineKeyboardMarkup markup = new InlineKeyboardMarkup();
final ArrayList<List<InlineKeyboardButton>> keyboard = new ArrayList<>();
final ArrayList<InlineKeyboardButton> row = new ArrayList<>();
if (offer.isRemember()) {
addButton(row, ICON_CHECK + ICON_CHECK + ICON_CHECK + " Gemerkt " + ICON_CHECK + ICON_CHECK + ICON_CHECK, InlineCommand.UNREMEMBER);
} else {
addButton(row, ICON_REMOVE + " Entfernen", InlineCommand.HIDE);
addButton(row, ICON_CHECK + " Merken", InlineCommand.REMEMBER);
}
keyboard.add(row);
markup.setKeyboard(keyboard);
return markup;
}
private void addButton(final ArrayList<InlineKeyboardButton> row, final String caption, final InlineCommand command) throws JsonProcessingException {
final String data = objectMapper.writeValueAsString(new InlineDto(command));
row.add(new InlineKeyboardButton(caption, null, data, null, null, null, null, null, null));
}
private void remove(final List<OfferDto> offers) {
_remove(offers.stream().map(OfferDto::getTelegramMessageId).filter(Objects::nonNull).toList());
}
private void remove(final MaybeInaccessibleMessage... messages) {
_remove(Arrays.stream(messages).map(MaybeInaccessibleMessage::getMessageId).toList());
}
private void _remove(final List<Integer> messageIds) {
if (messageIds.isEmpty()) {
return;
}
try {
bot.execute(new DeleteMessages(CHAT_ID + "", messageIds));
} catch (TelegramApiException e) {
log.error("Failed to remove Message to #{}.", CHAT_ID, e);
}
}
}