229 lines
8.3 KiB
Java
229 lines
8.3 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.telegram.chat.ChatDto;
|
|
import de.ph87.kleinanzeigen.telegram.chat.ChatService;
|
|
import de.ph87.kleinanzeigen.telegram.chat.message.MessageDto;
|
|
import de.ph87.kleinanzeigen.telegram.chat.message.MessageService;
|
|
import de.ph87.kleinanzeigen.telegram.request.ChatRequestEnable;
|
|
import de.ph87.kleinanzeigen.telegram.request.ChatRequestUndo;
|
|
import de.ph87.kleinanzeigen.telegram.request.MessageRequestHide;
|
|
import de.ph87.kleinanzeigen.telegram.request.MessageRequestRemember;
|
|
import jakarta.annotation.PostConstruct;
|
|
import jakarta.annotation.PreDestroy;
|
|
import lombok.NonNull;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.context.event.EventListener;
|
|
import org.springframework.scheduling.annotation.Async;
|
|
import org.springframework.scheduling.annotation.EnableAsync;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.event.TransactionalEventListener;
|
|
import org.telegram.telegrambots.meta.api.methods.send.SendPhoto;
|
|
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageCaption;
|
|
import org.telegram.telegrambots.meta.api.objects.InputFile;
|
|
import org.telegram.telegrambots.meta.api.objects.Message;
|
|
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.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
@Slf4j
|
|
@Service
|
|
@EnableAsync
|
|
@RequiredArgsConstructor
|
|
public class TelegramAdapter {
|
|
|
|
private static final String ICON_CHECK = "✅";
|
|
|
|
private static final String ICON_REMOVE = "❌";
|
|
|
|
private final ChatService chatService;
|
|
|
|
private final MessageService messageService;
|
|
|
|
private final TelegramBot bot;
|
|
|
|
private final ObjectMapper objectMapper;
|
|
|
|
private byte[] NO_IMAGE = 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();
|
|
}
|
|
|
|
@PreDestroy
|
|
public void stop() {
|
|
log.info("Stopping Telegram bot...");
|
|
bot.stop();
|
|
log.info("Telegram bot stopped");
|
|
}
|
|
|
|
@TransactionalEventListener(OfferDto.class)
|
|
public void onOffer(@NonNull final OfferDto offer) {
|
|
final List<MessageDto> existingMessages = messageService.findAllDtoByOfferDto(offer);
|
|
final List<ChatDto> chats = chatService.findAllEnabled();
|
|
for (final ChatDto chat : chats) {
|
|
existingMessages
|
|
.stream()
|
|
.filter(m -> m.getChat().getId() == chat.getId())
|
|
.findFirst()
|
|
.ifPresentOrElse(this::update, () -> send(offer, chat));
|
|
}
|
|
}
|
|
|
|
@Async
|
|
@EventListener(ChatRequestEnable.class)
|
|
public void onChatEnable(@NonNull final ChatRequestEnable request) {
|
|
chatService.setEnabled(request, request.isEnable(), request.getUsername());
|
|
}
|
|
|
|
@Async
|
|
@EventListener(ChatRequestUndo.class)
|
|
public void onChatUndo(@NonNull final ChatRequestUndo request) {
|
|
messageService.undo(request);
|
|
}
|
|
|
|
@Async
|
|
@EventListener(MessageRequestHide.class)
|
|
public void onMessageHide(@NonNull final MessageRequestHide request) {
|
|
messageService.setHide(request);
|
|
}
|
|
|
|
@Async
|
|
@EventListener(MessageRequestRemember.class)
|
|
public void onMessageRemember(@NonNull final MessageRequestRemember request) {
|
|
messageService.setRemember(request, request.isRemember());
|
|
}
|
|
|
|
@TransactionalEventListener(MessageDto.class)
|
|
public void update(@NonNull final MessageDto message) {
|
|
if (maySend(message)) {
|
|
return;
|
|
}
|
|
if (mayDelete(message)) {
|
|
return;
|
|
}
|
|
if (message.getTelegramMessageId() == null) {
|
|
return;
|
|
}
|
|
edit(message);
|
|
}
|
|
|
|
private boolean maySend(final @NonNull MessageDto message) {
|
|
if (message.needsToBeSent()) {
|
|
send(message.getOffer(), message.getChat());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean mayDelete(final @NonNull MessageDto message) {
|
|
if (message.needsToBeDeleted()) {
|
|
TlgMessage.of(message).ifPresent(this::delete);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void send(@NonNull final OfferDto offerDto, @NonNull final ChatDto chatDto) {
|
|
try {
|
|
final InputFile inputFile = offerDto.getImageURL() == null ? new InputFile(new ByteArrayInputStream(NO_IMAGE), "[Kein Bild]") : new InputFile(offerDto.getImageURL());
|
|
final SendPhoto send = new SendPhoto(chatDto.getId() + "", inputFile);
|
|
send.setCaption(createText(offerDto));
|
|
send.setReplyMarkup(createKeyboard(false));
|
|
|
|
log.info("Sending: offer={} to chat={}", offerDto, chatDto);
|
|
final Message tlgMessage = bot.execute(send);
|
|
|
|
messageService.updateOrCreate(offerDto, chatDto, tlgMessage);
|
|
} catch (TelegramApiException | JsonProcessingException e) {
|
|
log.error("Failed to send: chat={}, offer={}: {}", chatDto, offerDto, e.toString());
|
|
}
|
|
}
|
|
|
|
private void edit(@NonNull final MessageDto message) {
|
|
try {
|
|
final EditMessageCaption edit = new EditMessageCaption(
|
|
message.getChat().getId() + "",
|
|
message.getTelegramMessageId(),
|
|
null,
|
|
createText(message.getOffer()),
|
|
createKeyboard(message.isRemember()),
|
|
null,
|
|
null
|
|
);
|
|
edit.setParseMode("Markdown");
|
|
log.info("Editing Message: {}", message);
|
|
bot.execute(edit);
|
|
} catch (TelegramApiException | JsonProcessingException e) {
|
|
if (e.toString().endsWith("Bad Request: message to edit not found")) {
|
|
log.info("Message has been deleted by User. Marking has hidden: {}", message);
|
|
TlgMessage.of(message).ifPresent(messageService::markDeleted);
|
|
} else if (e.toString().endsWith("Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message")) {
|
|
log.debug("Ignoring complaint from telegram-bot-api about unmodified message: {}", message);
|
|
} else {
|
|
log.error("Failed to edit Message chat={} message={}", message.getChat().getId(), message.getTelegramMessageId(), e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
private String createText(@NonNull final OfferDto offer) {
|
|
return "%s\n%s\n%s %s (%d km)\n%s\n%s\n".formatted(
|
|
offer.getTitle().replaceAll("\\[", "(").replaceAll("]", ")"),
|
|
offer.combinePrice(),
|
|
offer.getZipcode(),
|
|
offer.getLocation(),
|
|
offer.getDistance(),
|
|
offer.getDescription(),
|
|
offer.getArticleURL()
|
|
);
|
|
}
|
|
|
|
@NonNull
|
|
private InlineKeyboardMarkup createKeyboard(final boolean remember) throws JsonProcessingException {
|
|
final InlineKeyboardMarkup markup = new InlineKeyboardMarkup();
|
|
final ArrayList<List<InlineKeyboardButton>> keyboard = new ArrayList<>();
|
|
final ArrayList<InlineKeyboardButton> row = new ArrayList<>();
|
|
if (remember) {
|
|
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(@NonNull final ArrayList<InlineKeyboardButton> row, @NonNull final String caption, @NonNull 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 delete(@NonNull final TlgMessage tlgMessage) {
|
|
try {
|
|
log.info("Removing Message tlgMessage={}", tlgMessage);
|
|
bot.execute(tlgMessage.newDeleteMessage());
|
|
messageService.markDeleted(tlgMessage);
|
|
} catch (TelegramApiException e) {
|
|
log.error("Failed to remove Message tlgMessage={}", tlgMessage);
|
|
}
|
|
}
|
|
|
|
}
|