REFACTOR WIP

This commit is contained in:
Patrick Haßel 2024-07-26 11:47:49 +02:00
parent 038b0b76c9
commit 1bcecaac51
15 changed files with 371 additions and 203 deletions

View File

@ -1,5 +0,0 @@
package de.ph87.kleinanzeigen.telegram;
public class AccessDenied extends Exception {
}

View File

@ -5,20 +5,26 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferDto; import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferDto;
import de.ph87.kleinanzeigen.telegram.chat.ChatDto; import de.ph87.kleinanzeigen.telegram.chat.ChatDto;
import de.ph87.kleinanzeigen.telegram.chat.ChatService; 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.MessageDto;
import de.ph87.kleinanzeigen.telegram.chat.message.MessageService; 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.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.event.TransactionalEventListener;
import org.telegram.telegrambots.meta.api.methods.send.SendPhoto; 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.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.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
@ -33,37 +39,37 @@ import java.util.List;
@Slf4j @Slf4j
@Service @Service
@EnableAsync
@RequiredArgsConstructor @RequiredArgsConstructor
public class TelegramService { public class TelegramAdapter {
private static final String ICON_CHECK = ""; private static final String ICON_CHECK = "";
private static final String ICON_REMOVE = ""; private static final String ICON_REMOVE = "";
private final TelegramConfig config;
private final ChatService chatService; private final ChatService chatService;
private final MessageService messageService; private final MessageService messageService;
private final TelegramBot bot;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private byte[] NO_IMAGE = null; private byte[] NO_IMAGE = null;
private TelegramBot bot = null;
@PostConstruct @PostConstruct
public void postConstruct() throws IOException { public void postConstruct() throws IOException {
final BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); final BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
final ByteArrayOutputStream stream = new ByteArrayOutputStream(); final ByteArrayOutputStream stream = new ByteArrayOutputStream();
ImageIO.write(img, "PNG", stream); ImageIO.write(img, "PNG", stream);
NO_IMAGE = stream.toByteArray(); 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) @TransactionalEventListener(OfferDto.class)
@ -75,72 +81,62 @@ public class TelegramService {
.stream() .stream()
.filter(m -> m.getChat().getId() == chat.getId()) .filter(m -> m.getChat().getId() == chat.getId())
.findFirst() .findFirst()
.ifPresentOrElse(message -> update(message, false), () -> send(offer, chat)); .ifPresentOrElse(this::update, () -> send(offer, chat));
} }
} }
@TransactionalEventListener(MessageDeleted.class) @Async
public void onMessageDeleted(final MessageDeleted messageDeleted) { @EventListener(ChatRequestEnable.class)
remove(messageDeleted.getChatId(), messageDeleted.getMessageId()); public void onChatEnable(@NonNull final ChatRequestEnable request) {
chatService.setEnabled(request, request.isEnable(), request.getUsername());
} }
@PreDestroy @Async
public void stop() { @EventListener(ChatRequestUndo.class)
log.info("Stopping Telegram bot..."); public void onChatUndo(@NonNull final ChatRequestUndo request) {
bot.stop(); messageService.undo(request);
log.info("Telegram bot stopped");
} }
private void onUpdateReceived(@NonNull final Update update) { @Async
try { @EventListener(MessageRequestHide.class)
if (update.hasMessage() && update.getMessage().hasText()) { public void onMessageHide(@NonNull final MessageRequestHide request) {
handleMessage(update.getMessage()); messageService.setHide(request);
} else if (update.hasCallbackQuery()) {
handleCallback(update.getCallbackQuery());
}
} catch (AccessDenied e) {
log.warn("Access denied: {}", update);
}
} }
private void handleMessage(@NonNull final Message tlgMessage) throws AccessDenied { @Async
switch (tlgMessage.getText()) { @EventListener(MessageRequestRemember.class)
case "/stop" -> chatService.setEnabled(tlgMessage.getChatId(), false, tlgMessage.getFrom().getUserName()); public void onMessageRemember(@NonNull final MessageRequestRemember request) {
case "undo" -> undo(tlgMessage); messageService.setRemember(request, request.isRemember());
}
} }
private void undo(@NonNull final Message tlgMessage) { @TransactionalEventListener(MessageDto.class)
messageService.undo(tlgMessage.getChatId()).ifPresent(message -> update(message, true)); public void update(@NonNull final MessageDto message) {
remove(tlgMessage); if (maySend(message)) {
return;
}
if (mayDelete(message)) {
return;
}
if (message.getTelegramMessageId() == null) {
return;
}
edit(message);
} }
private void handleCallback(@NonNull final CallbackQuery callback) throws AccessDenied { private boolean maySend(final @NonNull MessageDto message) {
final MaybeInaccessibleMessage tlgMessage = callback.getMessage(); if (message.needsToBeSent()) {
chatService.setEnabled(tlgMessage.getChatId(), true, callback.getFrom().getUserName()); send(message.getOffer(), message.getChat());
try { return true;
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);
} }
return false;
} }
private void hide(@NonNull final MaybeInaccessibleMessage tlgMessage) { private boolean mayDelete(final @NonNull MessageDto message) {
messageService.setHide(tlgMessage, true).ifPresentOrElse(message -> update(message, false), () -> remove(tlgMessage)); if (message.needsToBeDeleted()) {
TlgMessage.of(message).ifPresent(this::delete);
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 void send(@NonNull final OfferDto offerDto, @NonNull final ChatDto chatDto) { 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) { private void edit(@NonNull final MessageDto message) {
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) {
try { try {
final EditMessageCaption edit = new EditMessageCaption( final EditMessageCaption edit = new EditMessageCaption(
message.getChat().getId() + "", message.getChat().getId() + "",
@ -210,7 +172,7 @@ public class TelegramService {
} catch (TelegramApiException | JsonProcessingException e) { } catch (TelegramApiException | JsonProcessingException e) {
if (e.toString().endsWith("Bad Request: message to edit not found")) { if (e.toString().endsWith("Bad Request: message to edit not found")) {
log.info("Message has been deleted by User. Marking has hidden: {}", message); 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")) { } 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); log.debug("Ignoring complaint from telegram-bot-api about unmodified message: {}", message);
} else { } 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( return "%s\n%s\n%s %s (%d km)\n%s\n%s\n".formatted(
offer.getTitle().replaceAll("\\[", "(").replaceAll("]", ")"), offer.getTitle().replaceAll("\\[", "(").replaceAll("]", ")"),
offer.combinePrice(), offer.combinePrice(),
@ -231,6 +194,7 @@ public class TelegramService {
); );
} }
@NonNull
private InlineKeyboardMarkup createKeyboard(final boolean remember) throws JsonProcessingException { private InlineKeyboardMarkup createKeyboard(final boolean remember) throws JsonProcessingException {
final InlineKeyboardMarkup markup = new InlineKeyboardMarkup(); final InlineKeyboardMarkup markup = new InlineKeyboardMarkup();
final ArrayList<List<InlineKeyboardButton>> keyboard = new ArrayList<>(); final ArrayList<List<InlineKeyboardButton>> keyboard = new ArrayList<>();
@ -246,21 +210,18 @@ public class TelegramService {
return markup; return markup;
} }
private void addButton(final ArrayList<InlineKeyboardButton> row, final String caption, final InlineCommand command) throws JsonProcessingException { 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)); final String data = objectMapper.writeValueAsString(new InlineDto(command));
row.add(new InlineKeyboardButton(caption, null, data, null, null, null, null, null, null)); row.add(new InlineKeyboardButton(caption, null, data, null, null, null, null, null, null));
} }
private void remove(final MaybeInaccessibleMessage tlgMessage) { private void delete(@NonNull final TlgMessage tlgMessage) {
remove(tlgMessage.getChatId(), tlgMessage.getMessageId());
}
private void remove(final long chatId, final int messageId) {
try { try {
log.info("Removing Message chat={} message={}", chatId, messageId); log.info("Removing Message tlgMessage={}", tlgMessage);
bot.execute(new DeleteMessage(chatId + "", messageId)); bot.execute(tlgMessage.newDeleteMessage());
messageService.markDeleted(tlgMessage);
} catch (TelegramApiException e) { } catch (TelegramApiException e) {
log.error("Failed to remove Message chat={} message={}", chatId, messageId); log.error("Failed to remove Message tlgMessage={}", tlgMessage);
} }
} }

View File

@ -1,44 +1,106 @@
package de.ph87.kleinanzeigen.telegram; 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 lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.TelegramBotsApi; 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.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;
import java.io.IOException; import java.util.Locale;
import java.util.function.Consumer;
@Slf4j @Slf4j
@Service
public class TelegramBot extends TelegramLongPollingBot { public class TelegramBot extends TelegramLongPollingBot {
private final TelegramConfig config;
private final ObjectMapper objectMapper;
private final ApplicationEventPublisher publisher;
private final DefaultBotSession session; private final DefaultBotSession session;
private final Consumer<Update> onUpdate; public TelegramBot(final TelegramConfig config, final ObjectMapper objectMapper, final ApplicationEventPublisher publisher) throws TelegramApiException {
super(config.getToken());
@Getter this.config = config;
private final String botUsername; this.publisher = publisher;
public TelegramBot(final String token, final String username, final Consumer<Update> onUpdate) throws IOException, TelegramApiException {
super(token);
this.botUsername = username;
this.onUpdate = onUpdate;
getOptions().setGetUpdatesTimeout(10); getOptions().setGetUpdatesTimeout(10);
log.info("Starting telegram bot..."); log.info("Starting telegram bot...");
final TelegramBotsApi api = new TelegramBotsApi(DefaultBotSession.class); final TelegramBotsApi api = new TelegramBotsApi(DefaultBotSession.class);
session = (DefaultBotSession) api.registerBot(this); session = (DefaultBotSession) api.registerBot(this);
log.info("Telegram bot registered."); 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 @Override
public void onUpdateReceived(final Update update) { public String getBotUsername() {
onUpdate.accept(update); return config.getUsername();
} }
public void stop() { @Override
session.stop(); 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);
}
} }
} }

View File

@ -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();
}
}

View File

@ -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<TlgMessage> 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);
}
}

View File

@ -1,6 +1,6 @@
package de.ph87.kleinanzeigen.telegram.chat; package de.ph87.kleinanzeigen.telegram.chat;
import jakarta.annotation.Nullable; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import lombok.*; import lombok.*;
@ -18,7 +18,8 @@ public class Chat {
private boolean enabled; private boolean enabled;
@Setter @Setter
@Nullable @NonNull
@Column(nullable = false)
private String name; private String name;
public Chat(final long id, final boolean enabled, @NonNull final String name) { public Chat(final long id, final boolean enabled, @NonNull final String name) {

View File

@ -1,7 +1,8 @@
package de.ph87.kleinanzeigen.telegram.chat; package de.ph87.kleinanzeigen.telegram.chat;
import de.ph87.kleinanzeigen.telegram.AccessDenied;
import de.ph87.kleinanzeigen.telegram.TelegramConfig; import de.ph87.kleinanzeigen.telegram.TelegramConfig;
import de.ph87.kleinanzeigen.telegram.TlgChat;
import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; 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(); 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 { public void setEnabled(@NonNull final TlgChat tlgChat, final boolean enabled, final @NonNull String username) {
if (!config.isOnWhitelist(id)) { if (!config.isOnWhitelist(tlgChat.id)) {
throw new AccessDenied(); log.warn("Not on whitelist: {}", tlgChat);
return;
} }
chatRepository chatRepository
.findById(id) .findById(tlgChat.id)
.stream() .stream()
.peek(chat -> { .peek(chat -> update(chat, enabled, username))
chat.setName(name); .findFirst()
.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) { if (chat.isEnabled() != enabled) {
chat.setEnabled(enabled); chat.setEnabled(enabled);
log.info("Chat {}: {}", enabled ? "ENABLED" : "DISABLED", chat); log.info("Chat {}: {}", enabled ? "ENABLED" : "DISABLED", chat);
} }
})
.findFirst()
.orElseGet(() -> {
final Chat chat = chatRepository.save(new Chat(id, enabled, name));
log.info("Chat created: {}", chat);
return chat;
});
} }
public Chat getByDto(final ChatDto chatDto) { public Chat getByDto(final ChatDto chatDto) {

View File

@ -31,7 +31,6 @@ public class Message {
@Nullable @Nullable
private Integer telegramMessageId; private Integer telegramMessageId;
@Setter
@Column @Column
@Nullable @Nullable
private ZonedDateTime hide = null; private ZonedDateTime hide = null;
@ -46,4 +45,19 @@ public class Message {
this.telegramMessageId = tlgMessage.getMessageId(); 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;
}
} }

View File

@ -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;
}
}

View File

@ -36,4 +36,12 @@ public class MessageDto {
remember = message.isRemember(); remember = message.isRemember();
} }
public boolean needsToBeSent() {
return hide == null && telegramMessageId == null;
}
public boolean needsToBeDeleted() {
return hide != null && telegramMessageId != null;
}
} }

View File

@ -3,26 +3,26 @@ package de.ph87.kleinanzeigen.telegram.chat.message;
import de.ph87.kleinanzeigen.kleinanzeigen.offer.Offer; import de.ph87.kleinanzeigen.kleinanzeigen.offer.Offer;
import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferDto; import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferDto;
import de.ph87.kleinanzeigen.kleinanzeigen.offer.OfferService; 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.Chat;
import de.ph87.kleinanzeigen.telegram.chat.ChatDto; import de.ph87.kleinanzeigen.telegram.chat.ChatDto;
import de.ph87.kleinanzeigen.telegram.chat.ChatService; import de.ph87.kleinanzeigen.telegram.chat.ChatService;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; 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.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
@Slf4j @Slf4j
@Service @Service
@Transactional @Transactional
@EnableScheduling
@RequiredArgsConstructor @RequiredArgsConstructor
public class MessageService { public class MessageService {
@ -32,6 +32,8 @@ public class MessageService {
private final ChatService chatService; private final ChatService chatService;
private final ApplicationEventPublisher applicationEventPublisher;
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrCreate(final OfferDto offerDto, final ChatDto chatDto, final org.telegram.telegrambots.meta.api.objects.Message tlgMessage) { public void updateOrCreate(final OfferDto offerDto, final ChatDto chatDto, final org.telegram.telegrambots.meta.api.objects.Message tlgMessage) {
final Offer offer = offerService.getByDto(offerDto); final Offer offer = offerService.getByDto(offerDto);
@ -42,48 +44,57 @@ public class MessageService {
); );
} }
@SuppressWarnings("UnusedReturnValue") public void setHide(@NonNull final TlgMessage tlgMessage) {
public Optional<MessageDto> setHide(final MaybeInaccessibleMessage tlgMessage, final boolean hide) { set(tlgMessage, message -> message.setHide(true));
return findByTelegramMessage(tlgMessage).stream().peek(offer -> offer.setHide(hide ? ZonedDateTime.now() : null)).findFirst().map(this::toDto);
} }
public Optional<MessageDto> setRemember(final MaybeInaccessibleMessage tlgMessage, final boolean remember) { public void setRemember(@NonNull final TlgMessage tlgMessage, final boolean remember) {
return findByTelegramMessage(tlgMessage).stream().peek(offer -> offer.setRemember(remember)).findFirst().map(this::toDto); set(tlgMessage, message -> message.setRemember(remember));
} }
public Optional<MessageDto> findDtoByTelegramMessage(final MaybeInaccessibleMessage tlgMessage) { @NonNull
return findByTelegramMessage(tlgMessage).map(this::toDto); public List<MessageDto> findAllDtoByOfferDto(@NonNull final OfferDto offer) {
}
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<Message> findByTelegramMessage(final MaybeInaccessibleMessage tlgMessage) {
return messageRepository.findByChat_IdAndTelegramMessageId(tlgMessage.getChatId(), tlgMessage.getMessageId());
}
private Optional<Message> findByDto(final MessageDto dto) {
return messageRepository.findById(dto.getId());
}
public List<MessageDto> findAllDtoByOfferDto(final OfferDto offer) {
return messageRepository.findAllByOffer_Id(offer.getId()).stream().map(this::toDto).toList(); 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<Message> setter) {
find(tlgMessage).ifPresent(set(setter));
}
private Consumer<Message> set(@NonNull final Consumer<Message> setter) {
return message -> {
setter.accept(message);
publish(message);
};
}
private Optional<Message> 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 ChatDto chatDto = chatService.toDto(message.getChat());
final OfferDto offerDto = offerService.toDto(message.getOffer()); final OfferDto offerDto = offerService.toDto(message.getOffer());
return new MessageDto(message, chatDto, offerDto); return new MessageDto(message, chatDto, offerDto);
} }
@NonNull
public Optional<MessageDto> undo(final long chatId) {
return messageRepository.findFirstByChat_IdAndHideNotNullOrderByHideDesc(chatId).stream().peek(message -> message.setHide(null)).map(this::toDto).findFirst();
}
} }

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}