package de.ph87.kleinanzeigen.api; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.TelegramBotsApi; 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 org.telegram.telegrambots.updatesreceivers.DefaultBotSession; 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.*; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import static de.ph87.kleinanzeigen.api.JSON.objectMapper; @Slf4j public class Bot extends TelegramLongPollingBot { private static final long CHAT_ID = 101138682L; private static final String ICON_CHECK = "✅"; private static final String ICON_REMOVE = "❌"; private final byte[] NO_IMAGE; private final DefaultBotSession session; private final Consumer ignore; private final Function> find; private final BiFunction> remember; public Bot(final Consumer ignore, final Function> find, final BiFunction> remember) throws IOException, TelegramApiException { super(readToken()); 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(); this.ignore = ignore; this.find = find; this.remember = remember; log.info("Starting telegram bot..."); final TelegramBotsApi api = new TelegramBotsApi(DefaultBotSession.class); session = (DefaultBotSession) api.registerBot(this); log.info("Telegram bot registered."); } @Override public String getBotUsername() { return "BotKleinanzeigenBot"; } @Override public 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 IGNORE -> ignore(message); case REMEMBER -> remember(message); case UNREMEMBER -> unremember(message); default -> updateMessage(message); } } catch (JsonProcessingException e) { log.error("Failed to read InlineDto.", e); } } private void ignore(final MaybeInaccessibleMessage message) { ignore.accept(message); remove(message); } private void remember(final MaybeInaccessibleMessage message) { remember.apply(message, true).ifPresentOrElse( offer -> updateMessage(message, offer), () -> remove(message) ); } private void unremember(final MaybeInaccessibleMessage message) { remember.apply(message, false).ifPresentOrElse( offer -> updateMessage(message, offer), () -> remove(message) ); } private void updateMessage(final MaybeInaccessibleMessage message) { find.apply(message).ifPresentOrElse( offer -> updateMessage(message, offer), () -> remove(message) ); } private void updateMessage(final MaybeInaccessibleMessage message, final Offer offer) { try { final EditMessageCaption edit = new EditMessageCaption(message.getChatId() + "", message.getMessageId(), null, createText(offer), createKeyboard(offer), null, null); edit.setParseMode("Markdown"); execute(edit); } catch (TelegramApiException | JsonProcessingException e) { log.error("Failed to edit Message to #{}.", message.getChatId(), e); } } private static String readToken() throws IOException { try (final FileInputStream stream = new FileInputStream("./telegram.token")) { return new String(stream.readAllBytes(), StandardCharsets.UTF_8).trim(); } } public void send(final Offer offer) { try { final InputFile inputFile = offer.getImage().isEmpty() ? new InputFile(new ByteArrayInputStream(NO_IMAGE), "[Kein Bild]") : new InputFile(offer.getImage()); final SendPhoto send = new SendPhoto(CHAT_ID + "", inputFile); send.setCaption(createText(offer)); send.setReplyMarkup(createKeyboard(offer)); final Message message = execute(send); offer.setTelegramMessageId(message.getMessageId()); } catch (TelegramApiException | JsonProcessingException e) { log.error("Failed to send Message to #{}.", CHAT_ID, e); } } private String createText(final Offer offer) { return "[%s](%s)\n%s\n%s".formatted( offer.getTitle().replaceAll("\\[", "(").replaceAll("]", ")"), offer.getHref(), offer.calculateLocationString(), offer.getDescription() ); } private InlineKeyboardMarkup createKeyboard(final Offer offer) throws JsonProcessingException { final InlineKeyboardMarkup markup = new InlineKeyboardMarkup(); final ArrayList> keyboard = new ArrayList<>(); final ArrayList 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.IGNORE); addButton(row, ICON_CHECK + " Merken", InlineCommand.REMEMBER); } keyboard.add(row); markup.setKeyboard(keyboard); return markup; } private void addButton(final ArrayList 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)); } public void remove(final List offers) { _remove(offers.stream().map(Offer::getTelegramMessageId).filter(Objects::nonNull).toList()); } private void remove(final MaybeInaccessibleMessage... messages) { _remove(Arrays.stream(messages).map(MaybeInaccessibleMessage::getMessageId).toList()); } private void _remove(final List messageIds) { if (messageIds.isEmpty()) { return; } try { execute(new DeleteMessages(CHAT_ID + "", messageIds)); } catch (TelegramApiException e) { log.error("Failed to remove Message to #{}.", CHAT_ID, e); } } public void stop() { session.stop(); } }