diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/AccessDenied.java b/src/main/java/de/ph87/kleinanzeigen/telegram/AccessDenied.java deleted file mode 100644 index 42ffd22..0000000 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/AccessDenied.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.ph87.kleinanzeigen.telegram; - -public class AccessDenied extends Exception { - -} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramService.java b/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramAdapter.java similarity index 55% rename from src/main/java/de/ph87/kleinanzeigen/telegram/TelegramService.java rename to src/main/java/de/ph87/kleinanzeigen/telegram/TelegramAdapter.java index b1efa16..b60ab43 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramService.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramAdapter.java @@ -5,20 +5,26 @@ 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.MessageDeleted; 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.DeleteMessage; import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageCaption; -import org.telegram.telegrambots.meta.api.objects.*; +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; @@ -33,37 +39,37 @@ import java.util.List; @Slf4j @Service +@EnableAsync @RequiredArgsConstructor -public class TelegramService { +public class TelegramAdapter { private static final String ICON_CHECK = "✅"; private static final String ICON_REMOVE = "❌"; - private final TelegramConfig config; - private final ChatService chatService; private final MessageService messageService; + private final TelegramBot bot; + private final ObjectMapper objectMapper; private byte[] NO_IMAGE = null; - 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(config.getToken(), config.getUsername(), this::onUpdateReceived); - } catch (TelegramApiException | IOException e) { - log.error("Failed to start TelegramBot: {}", e.toString()); - } + @PreDestroy + public void stop() { + log.info("Stopping Telegram bot..."); + bot.stop(); + log.info("Telegram bot stopped"); } @TransactionalEventListener(OfferDto.class) @@ -75,72 +81,62 @@ public class TelegramService { .stream() .filter(m -> m.getChat().getId() == chat.getId()) .findFirst() - .ifPresentOrElse(message -> update(message, false), () -> send(offer, chat)); + .ifPresentOrElse(this::update, () -> send(offer, chat)); } } - @TransactionalEventListener(MessageDeleted.class) - public void onMessageDeleted(final MessageDeleted messageDeleted) { - remove(messageDeleted.getChatId(), messageDeleted.getMessageId()); + @Async + @EventListener(ChatRequestEnable.class) + public void onChatEnable(@NonNull final ChatRequestEnable request) { + chatService.setEnabled(request, request.isEnable(), request.getUsername()); } - @PreDestroy - public void stop() { - log.info("Stopping Telegram bot..."); - bot.stop(); - log.info("Telegram bot stopped"); + @Async + @EventListener(ChatRequestUndo.class) + public void onChatUndo(@NonNull final ChatRequestUndo request) { + messageService.undo(request); } - private void onUpdateReceived(@NonNull final Update update) { - try { - if (update.hasMessage() && update.getMessage().hasText()) { - handleMessage(update.getMessage()); - } else if (update.hasCallbackQuery()) { - handleCallback(update.getCallbackQuery()); - } - } catch (AccessDenied e) { - log.warn("Access denied: {}", update); + @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; } - } - - private void handleMessage(@NonNull final Message tlgMessage) throws AccessDenied { - switch (tlgMessage.getText()) { - case "/stop" -> chatService.setEnabled(tlgMessage.getChatId(), false, tlgMessage.getFrom().getUserName()); - case "undo" -> undo(tlgMessage); + if (mayDelete(message)) { + return; } - } - - private void undo(@NonNull final Message tlgMessage) { - messageService.undo(tlgMessage.getChatId()).ifPresent(message -> update(message, true)); - remove(tlgMessage); - } - - private void handleCallback(@NonNull final CallbackQuery callback) throws AccessDenied { - final MaybeInaccessibleMessage tlgMessage = callback.getMessage(); - chatService.setEnabled(tlgMessage.getChatId(), true, callback.getFrom().getUserName()); - try { - final InlineDto dto = objectMapper.readValue(callback.getData(), InlineDto.class); - switch (dto.getCommand()) { - case HIDE -> hide(tlgMessage); - case REMEMBER -> remember(tlgMessage, true); - case UNREMEMBER -> remember(tlgMessage, false); - default -> update(tlgMessage); - } - } catch (JsonProcessingException e) { - log.error("Failed to read InlineDto.", e); + if (message.getTelegramMessageId() == null) { + return; } + edit(message); } - private void hide(@NonNull final MaybeInaccessibleMessage tlgMessage) { - messageService.setHide(tlgMessage, true).ifPresentOrElse(message -> update(message, false), () -> remove(tlgMessage)); + private boolean maySend(final @NonNull MessageDto message) { + if (message.needsToBeSent()) { + send(message.getOffer(), message.getChat()); + return true; + } + return false; } - private void remember(@NonNull final MaybeInaccessibleMessage tlgMessage, final boolean remember) { - messageService.setRemember(tlgMessage, remember).ifPresentOrElse(message -> update(message, false), () -> remove(tlgMessage)); - } - - private void update(@NonNull final MaybeInaccessibleMessage tlgMessage) { - messageService.findDtoByTelegramMessage(tlgMessage).ifPresentOrElse(message -> update(message, false), () -> remove(tlgMessage)); + 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) { @@ -159,41 +155,7 @@ public class TelegramService { } } - private void update(@NonNull final MessageDto message, final boolean forceResend) { - if (resend(message, forceResend)) { - return; - } - if (deleteFromTelegram(message)) { - return; - } - if (message.getTelegramMessageId() == null) { - return; - } - _update(message); - } - - private boolean resend(final MessageDto message, final boolean forceResend) { - final boolean resendOnPriceChange = message.getOffer().isResendOnPriceChange() && message.getOffer().getPriceChanged() != null && message.getHide() != null && !message.getOffer().getPriceChanged().isBefore(message.getHide()); - if (forceResend || resendOnPriceChange) { - messageService.setHide(message, false); - if (message.getTelegramMessageId() == null) { - send(message.getOffer(), message.getChat()); - return true; - } - } - return false; - } - - private boolean deleteFromTelegram(final MessageDto message) { - if (message.getHide() != null && message.getTelegramMessageId() != null) { - remove(message.getChat().getId(), message.getTelegramMessageId()); - messageService.clearTelegramMessageId(message); - return true; - } - return false; - } - - private void _update(final MessageDto message) { + private void edit(@NonNull final MessageDto message) { try { final EditMessageCaption edit = new EditMessageCaption( message.getChat().getId() + "", @@ -210,7 +172,7 @@ public class TelegramService { } 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); - messageService.setHide(message, true); + 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 { @@ -219,7 +181,8 @@ public class TelegramService { } } - private String createText(final OfferDto offer) { + @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(), @@ -231,6 +194,7 @@ public class TelegramService { ); } + @NonNull private InlineKeyboardMarkup createKeyboard(final boolean remember) throws JsonProcessingException { final InlineKeyboardMarkup markup = new InlineKeyboardMarkup(); final ArrayList> keyboard = new ArrayList<>(); @@ -246,21 +210,18 @@ public class TelegramService { return markup; } - private void addButton(final ArrayList row, final String caption, final InlineCommand command) throws JsonProcessingException { + private void addButton(@NonNull final ArrayList 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 remove(final MaybeInaccessibleMessage tlgMessage) { - remove(tlgMessage.getChatId(), tlgMessage.getMessageId()); - } - - private void remove(final long chatId, final int messageId) { + private void delete(@NonNull final TlgMessage tlgMessage) { try { - log.info("Removing Message chat={} message={}", chatId, messageId); - bot.execute(new DeleteMessage(chatId + "", messageId)); + log.info("Removing Message tlgMessage={}", tlgMessage); + bot.execute(tlgMessage.newDeleteMessage()); + messageService.markDeleted(tlgMessage); } catch (TelegramApiException e) { - log.error("Failed to remove Message chat={} message={}", chatId, messageId); + log.error("Failed to remove Message tlgMessage={}", tlgMessage); } } diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramBot.java b/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramBot.java index 86d8f09..c9cd99e 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramBot.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/TelegramBot.java @@ -1,44 +1,106 @@ package de.ph87.kleinanzeigen.telegram; -import lombok.Getter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +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.PreDestroy; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.TelegramBotsApi; +import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage; +import org.telegram.telegrambots.meta.api.objects.CallbackQuery; +import org.telegram.telegrambots.meta.api.objects.MaybeInaccessibleMessage; +import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; -import java.io.IOException; -import java.util.function.Consumer; +import java.util.Locale; @Slf4j +@Service public class TelegramBot extends TelegramLongPollingBot { + private final TelegramConfig config; + + private final ObjectMapper objectMapper; + + private final ApplicationEventPublisher publisher; + private final DefaultBotSession session; - private final Consumer onUpdate; + public TelegramBot(final TelegramConfig config, final ObjectMapper objectMapper, final ApplicationEventPublisher publisher) throws TelegramApiException { + super(config.getToken()); - @Getter - private final String botUsername; + this.config = config; + this.publisher = publisher; - public TelegramBot(final String token, final String username, final Consumer onUpdate) throws IOException, TelegramApiException { - super(token); - this.botUsername = username; - this.onUpdate = onUpdate; getOptions().setGetUpdatesTimeout(10); log.info("Starting telegram bot..."); final TelegramBotsApi api = new TelegramBotsApi(DefaultBotSession.class); session = (DefaultBotSession) api.registerBot(this); log.info("Telegram bot registered."); + this.objectMapper = objectMapper; + } + + @PreDestroy + public void stop() { + log.info("Stopping Telegram bot..."); + session.stop(); + log.info("Telegram bot stopped"); } @Override - public void onUpdateReceived(final Update update) { - onUpdate.accept(update); + public String getBotUsername() { + return config.getUsername(); } - public void stop() { - session.stop(); + @Override + public void onUpdateReceived(@NonNull final Update update) { + if (update.hasMessage() && update.getMessage().hasText()) { + handleMessage(update.getMessage()); + } else if (update.hasCallbackQuery()) { + handleCallback(update.getCallbackQuery()); + } + } + + private void handleMessage(@NonNull final Message message) { + final String command = message.getText().toLowerCase(Locale.ROOT).replaceAll("\\W+", ""); + switch (command) { + case "start" -> publisher.publishEvent(new ChatRequestEnable(message, true)); + case "undo" -> publisher.publishEvent(new ChatRequestUndo(message)); + case "stop" -> publisher.publishEvent(new ChatRequestEnable(message, false)); + } + remove(new TlgMessage(message)); + } + + private void handleCallback(@NonNull final CallbackQuery callback) { + final MaybeInaccessibleMessage message = callback.getMessage(); + try { + final InlineDto dto = objectMapper.readValue(callback.getData(), InlineDto.class); + switch (dto.getCommand()) { + case HIDE -> publisher.publishEvent(new MessageRequestHide(message)); + case REMEMBER -> publisher.publishEvent(new MessageRequestRemember(message, true)); + case UNREMEMBER -> publisher.publishEvent(new MessageRequestRemember(message, false)); + } + } catch (JsonProcessingException e) { + log.error("Failed to read InlineDto.", e); + } + } + + private void remove(@NonNull final TlgMessage tlgMessage) { + try { + log.info("Removing TlgMessage: tlgMessage={}", tlgMessage); + execute(new DeleteMessage(tlgMessage.chatId + "", tlgMessage.messageId)); + } catch (TelegramApiException e) { + log.error("Failed to remove TlgMessage: tlgMessage={}", tlgMessage); + } } } diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/TlgChat.java b/src/main/java/de/ph87/kleinanzeigen/telegram/TlgChat.java new file mode 100644 index 0000000..b210e6d --- /dev/null +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/TlgChat.java @@ -0,0 +1,16 @@ +package de.ph87.kleinanzeigen.telegram; + +import lombok.NonNull; +import lombok.ToString; +import org.telegram.telegrambots.meta.api.objects.MaybeInaccessibleMessage; + +@ToString +public class TlgChat { + + public final long id; + + public TlgChat(@NonNull final MaybeInaccessibleMessage message) { + this.id = message.getChatId(); + } + +} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/TlgMessage.java b/src/main/java/de/ph87/kleinanzeigen/telegram/TlgMessage.java new file mode 100644 index 0000000..987b107 --- /dev/null +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/TlgMessage.java @@ -0,0 +1,39 @@ +package de.ph87.kleinanzeigen.telegram; + +import de.ph87.kleinanzeigen.telegram.chat.message.MessageDto; +import lombok.NonNull; +import lombok.ToString; +import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage; +import org.telegram.telegrambots.meta.api.objects.MaybeInaccessibleMessage; + +import java.util.Optional; + +@ToString +public class TlgMessage { + + public final long chatId; + + public final int messageId; + + private TlgMessage(final long chatId, final int messageId) { + this.chatId = chatId; + this.messageId = messageId; + } + + public TlgMessage(@NonNull final MaybeInaccessibleMessage message) { + this(message.getChatId(), message.getMessageId()); + } + + public static Optional of(@NonNull final MessageDto message) { + if (message.getTelegramMessageId() == null) { + return Optional.empty(); + } + return Optional.of(new TlgMessage(message.getChat().getId(), message.getTelegramMessageId())); + } + + @NonNull + public DeleteMessage newDeleteMessage() { + return new DeleteMessage(chatId + "", messageId); + } + +} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/Chat.java b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/Chat.java index 8aa8e90..3b576db 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/Chat.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/Chat.java @@ -1,6 +1,6 @@ package de.ph87.kleinanzeigen.telegram.chat; -import jakarta.annotation.Nullable; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import lombok.*; @@ -18,7 +18,8 @@ public class Chat { private boolean enabled; @Setter - @Nullable + @NonNull + @Column(nullable = false) private String name; public Chat(final long id, final boolean enabled, @NonNull final String name) { diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/ChatService.java b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/ChatService.java index 8f343d4..844707e 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/ChatService.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/ChatService.java @@ -1,7 +1,8 @@ package de.ph87.kleinanzeigen.telegram.chat; -import de.ph87.kleinanzeigen.telegram.AccessDenied; import de.ph87.kleinanzeigen.telegram.TelegramConfig; +import de.ph87.kleinanzeigen.telegram.TlgChat; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -23,26 +24,33 @@ public class ChatService { return chatRepository.findAllByEnabledTrue().stream().filter(chat -> config.isOnWhitelist(chat.getId())).map(this::toDto).toList(); } - public void setEnabled(final long id, final boolean enabled, final String name) throws AccessDenied { - if (!config.isOnWhitelist(id)) { - throw new AccessDenied(); + public void setEnabled(@NonNull final TlgChat tlgChat, final boolean enabled, final @NonNull String username) { + if (!config.isOnWhitelist(tlgChat.id)) { + log.warn("Not on whitelist: {}", tlgChat); + return; } + chatRepository - .findById(id) + .findById(tlgChat.id) .stream() - .peek(chat -> { - chat.setName(name); - if (chat.isEnabled() != enabled) { - chat.setEnabled(enabled); - log.info("Chat {}: {}", enabled ? "ENABLED" : "DISABLED", chat); - } - }) + .peek(chat -> update(chat, enabled, username)) .findFirst() - .orElseGet(() -> { - final Chat chat = chatRepository.save(new Chat(id, enabled, name)); - log.info("Chat created: {}", chat); - return chat; - }); + .orElseGet(() -> create(tlgChat, enabled, username)); + } + + @NonNull + private Chat create(@NonNull final TlgChat tlgChat, final boolean enabled, final @NonNull String username) { + final Chat chat = chatRepository.save(new Chat(tlgChat.id, enabled, username)); + log.info("Chat created: {}", chat); + return chat; + } + + private void update(@NonNull final Chat chat, final boolean enabled, final @NonNull String username) { + chat.setName(username); + if (chat.isEnabled() != enabled) { + chat.setEnabled(enabled); + log.info("Chat {}: {}", enabled ? "ENABLED" : "DISABLED", chat); + } } public Chat getByDto(final ChatDto chatDto) { diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/Message.java b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/Message.java index f664b27..8e855be 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/Message.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/Message.java @@ -31,7 +31,6 @@ public class Message { @Nullable private Integer telegramMessageId; - @Setter @Column @Nullable private ZonedDateTime hide = null; @@ -46,4 +45,19 @@ public class Message { this.telegramMessageId = tlgMessage.getMessageId(); } + public void setHide(final boolean hide) { + if (hide) { + if (this.hide == null) { + this.hide = ZonedDateTime.now(); + } + } else { + this.hide = null; + } + } + + @Nullable + public ZonedDateTime getHide() { + return hide; + } + } diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDeleted.java b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDeleted.java deleted file mode 100644 index 707acbc..0000000 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDeleted.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.ph87.kleinanzeigen.telegram.chat.message; - -import lombok.Data; - -@Data -public class MessageDeleted { - - private final long chatId; - - private final int messageId; - - public MessageDeleted(final long chatId, final int messageId) { - this.chatId = chatId; - this.messageId = messageId; - } - -} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDto.java b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDto.java index 47b7325..0922453 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDto.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageDto.java @@ -36,4 +36,12 @@ public class MessageDto { remember = message.isRemember(); } + public boolean needsToBeSent() { + return hide == null && telegramMessageId == null; + } + + public boolean needsToBeDeleted() { + return hide != null && telegramMessageId != null; + } + } diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageService.java b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageService.java index d503a49..5be0eda 100644 --- a/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageService.java +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/chat/message/MessageService.java @@ -3,26 +3,26 @@ package de.ph87.kleinanzeigen.telegram.chat.message; import de.ph87.kleinanzeigen.kleinanzeigen.offer.Offer; import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferDto; import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferService; +import de.ph87.kleinanzeigen.telegram.TlgChat; +import de.ph87.kleinanzeigen.telegram.TlgMessage; import de.ph87.kleinanzeigen.telegram.chat.Chat; import de.ph87.kleinanzeigen.telegram.chat.ChatDto; import de.ph87.kleinanzeigen.telegram.chat.ChatService; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.telegram.telegrambots.meta.api.objects.MaybeInaccessibleMessage; -import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; @Slf4j @Service @Transactional -@EnableScheduling @RequiredArgsConstructor public class MessageService { @@ -32,6 +32,8 @@ public class MessageService { private final ChatService chatService; + private final ApplicationEventPublisher applicationEventPublisher; + @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateOrCreate(final OfferDto offerDto, final ChatDto chatDto, final org.telegram.telegrambots.meta.api.objects.Message tlgMessage) { final Offer offer = offerService.getByDto(offerDto); @@ -42,48 +44,57 @@ public class MessageService { ); } - @SuppressWarnings("UnusedReturnValue") - public Optional setHide(final MaybeInaccessibleMessage tlgMessage, final boolean hide) { - return findByTelegramMessage(tlgMessage).stream().peek(offer -> offer.setHide(hide ? ZonedDateTime.now() : null)).findFirst().map(this::toDto); + public void setHide(@NonNull final TlgMessage tlgMessage) { + set(tlgMessage, message -> message.setHide(true)); } - public Optional setRemember(final MaybeInaccessibleMessage tlgMessage, final boolean remember) { - return findByTelegramMessage(tlgMessage).stream().peek(offer -> offer.setRemember(remember)).findFirst().map(this::toDto); + public void setRemember(@NonNull final TlgMessage tlgMessage, final boolean remember) { + set(tlgMessage, message -> message.setRemember(remember)); } - public Optional findDtoByTelegramMessage(final MaybeInaccessibleMessage tlgMessage) { - return findByTelegramMessage(tlgMessage).map(this::toDto); - } - - public void clearTelegramMessageId(final MessageDto dto) { - findByDto(dto).ifPresent(message -> message.setTelegramMessageId(null)); - } - - public void setHide(final MessageDto dto, final boolean hide) { - findByDto(dto).ifPresent(offer -> offer.setHide(hide ? ZonedDateTime.now() : null)); - } - - private Optional findByTelegramMessage(final MaybeInaccessibleMessage tlgMessage) { - return messageRepository.findByChat_IdAndTelegramMessageId(tlgMessage.getChatId(), tlgMessage.getMessageId()); - } - - private Optional findByDto(final MessageDto dto) { - return messageRepository.findById(dto.getId()); - } - - public List findAllDtoByOfferDto(final OfferDto offer) { + @NonNull + public List findAllDtoByOfferDto(@NonNull final OfferDto offer) { return messageRepository.findAllByOffer_Id(offer.getId()).stream().map(this::toDto).toList(); } - private MessageDto toDto(final Message message) { + public void undo(@NonNull final TlgChat chat) { + messageRepository.findFirstByChat_IdAndHideNotNullOrderByHideDesc(chat.id).ifPresentOrElse( + set(message -> message.setHide(false)), + () -> log.warn("Nothing to undo for chat: {}", chat) + ); + } + + public void markDeleted(@NonNull final TlgMessage tlgMessage) { + set(tlgMessage, message -> { + message.setHide(true); + message.setTelegramMessageId(null); + }); + } + + private void set(@NonNull final TlgMessage tlgMessage, @NonNull final Consumer setter) { + find(tlgMessage).ifPresent(set(setter)); + } + + private Consumer set(@NonNull final Consumer setter) { + return message -> { + setter.accept(message); + publish(message); + }; + } + + private Optional find(final @NonNull TlgMessage tlgMessage) { + return messageRepository.findByChat_IdAndTelegramMessageId(tlgMessage.chatId, tlgMessage.messageId); + } + + private void publish(@NonNull final Message message) { + applicationEventPublisher.publishEvent(toDto(message)); + } + + @NonNull + private MessageDto toDto(@NonNull final Message message) { final ChatDto chatDto = chatService.toDto(message.getChat()); final OfferDto offerDto = offerService.toDto(message.getOffer()); return new MessageDto(message, chatDto, offerDto); } - @NonNull - public Optional undo(final long chatId) { - return messageRepository.findFirstByChat_IdAndHideNotNullOrderByHideDesc(chatId).stream().peek(message -> message.setHide(null)).map(this::toDto).findFirst(); - } - } diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/request/ChatRequestEnable.java b/src/main/java/de/ph87/kleinanzeigen/telegram/request/ChatRequestEnable.java new file mode 100644 index 0000000..c8eb7da --- /dev/null +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/request/ChatRequestEnable.java @@ -0,0 +1,22 @@ +package de.ph87.kleinanzeigen.telegram.request; + +import de.ph87.kleinanzeigen.telegram.TlgChat; +import lombok.Getter; +import lombok.NonNull; +import org.telegram.telegrambots.meta.api.objects.Message; + +@Getter +public class ChatRequestEnable extends TlgChat { + + private final boolean enable; + + @NonNull + private final String username; + + public ChatRequestEnable(@NonNull final Message message, final boolean enable) { + super(message); + this.enable = enable; + this.username = message.getFrom().getUserName() == null ? "" : message.getFrom().getUserName(); + } + +} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/request/ChatRequestUndo.java b/src/main/java/de/ph87/kleinanzeigen/telegram/request/ChatRequestUndo.java new file mode 100644 index 0000000..ffb5d72 --- /dev/null +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/request/ChatRequestUndo.java @@ -0,0 +1,15 @@ +package de.ph87.kleinanzeigen.telegram.request; + +import de.ph87.kleinanzeigen.telegram.TlgChat; +import lombok.Getter; +import lombok.NonNull; +import org.telegram.telegrambots.meta.api.objects.Message; + +@Getter +public class ChatRequestUndo extends TlgChat { + + public ChatRequestUndo(@NonNull final Message message) { + super(message); + } + +} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/request/MessageRequestHide.java b/src/main/java/de/ph87/kleinanzeigen/telegram/request/MessageRequestHide.java new file mode 100644 index 0000000..e5aaea8 --- /dev/null +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/request/MessageRequestHide.java @@ -0,0 +1,15 @@ +package de.ph87.kleinanzeigen.telegram.request; + +import de.ph87.kleinanzeigen.telegram.TlgMessage; +import lombok.Getter; +import lombok.NonNull; +import org.telegram.telegrambots.meta.api.objects.MaybeInaccessibleMessage; + +@Getter +public class MessageRequestHide extends TlgMessage { + + public MessageRequestHide(@NonNull final MaybeInaccessibleMessage message) { + super(message); + } + +} diff --git a/src/main/java/de/ph87/kleinanzeigen/telegram/request/MessageRequestRemember.java b/src/main/java/de/ph87/kleinanzeigen/telegram/request/MessageRequestRemember.java new file mode 100644 index 0000000..85e160e --- /dev/null +++ b/src/main/java/de/ph87/kleinanzeigen/telegram/request/MessageRequestRemember.java @@ -0,0 +1,18 @@ +package de.ph87.kleinanzeigen.telegram.request; + +import de.ph87.kleinanzeigen.telegram.TlgMessage; +import lombok.Getter; +import lombok.NonNull; +import org.telegram.telegrambots.meta.api.objects.MaybeInaccessibleMessage; + +@Getter +public class MessageRequestRemember extends TlgMessage { + + private final boolean remember; + + public MessageRequestRemember(@NonNull final MaybeInaccessibleMessage message, final boolean remember) { + super(message); + this.remember = remember; + } + +}