resendOnPriceChange
This commit is contained in:
parent
10f4cdac2a
commit
a856c8e01c
@ -42,7 +42,7 @@ public class KleinanzeigenApi {
|
||||
|
||||
@Scheduled(initialDelay = 0, fixedRate = 1, timeUnit = TimeUnit.MINUTES)
|
||||
public void fetch() {
|
||||
fetch(VERSCHENKEN_EPPELBORN_RADIUS.formatted(config.getRadiusKm()), config.getRadiusKm());
|
||||
fetch(VERSCHENKEN_EPPELBORN_RADIUS.formatted(config.getRadiusKm()), config.getRadiusKm(), false);
|
||||
searchService.findAllEnabledDto().forEach(this::fetch);
|
||||
}
|
||||
|
||||
@ -53,21 +53,21 @@ public class KleinanzeigenApi {
|
||||
search.getQueryEscaped(),
|
||||
search.getRadius()
|
||||
);
|
||||
fetch(url, search.getRadius());
|
||||
fetch(url, search.getRadius(), search.isResendOnPriceChange());
|
||||
}
|
||||
|
||||
public void fetch(final String url, final int radius) {
|
||||
public void fetch(final String url, final int radius, final boolean resendOnPriceChange) {
|
||||
final URI uri = URI.create(url);
|
||||
try {
|
||||
log.debug("Fetching page: {}", uri);
|
||||
final Document document = Jsoup.parse(uri.toURL(), 3000);
|
||||
document.select("li.ad-listitem:not(.is-topad) article.aditem").forEach(article -> tryParse(article, uri, radius));
|
||||
document.select("li.ad-listitem:not(.is-topad) article.aditem").forEach(article -> tryParse(article, uri, radius, resendOnPriceChange));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to fetch Kleinanzeigen: {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void tryParse(final Element article, final URI uri, final int radius) {
|
||||
private void tryParse(final Element article, final URI uri, final int radius, final boolean resendOnPriceChange) {
|
||||
try {
|
||||
final OfferCreate create = new OfferCreate(article, uri);
|
||||
if (create.getDistance() > radius) {
|
||||
@ -78,7 +78,7 @@ public class KleinanzeigenApi {
|
||||
log.info("Offer is blacklisted due to: blacklists={}, offer={}", blacklist.stream().map(BlacklistDto::getQuery).toList(), create);
|
||||
return;
|
||||
}
|
||||
offerService.updateOrCreate(create);
|
||||
offerService.updateOrCreate(create, resendOnPriceChange);
|
||||
} catch (NumberFormatException | DateTimeException | LocationNotFound e) {
|
||||
log.error("Failed to parse Offer:\n{}\n", article.outerHtml(), e);
|
||||
}
|
||||
|
||||
@ -73,6 +73,17 @@ public class Offer {
|
||||
@Nullable
|
||||
private BigDecimal price = null;
|
||||
|
||||
@Column
|
||||
@Nullable
|
||||
private BigDecimal priceLast = null;
|
||||
|
||||
@Column
|
||||
@Nullable
|
||||
private ZonedDateTime priceChanged = null;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean resendOnPriceChange;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean negotiable;
|
||||
|
||||
@ -80,14 +91,16 @@ public class Offer {
|
||||
@OneToMany(mappedBy = "offer")
|
||||
private List<Message> messages = new ArrayList<>();
|
||||
|
||||
public Offer(final @NonNull OfferCreate create) {
|
||||
public Offer(@NonNull final OfferCreate create, final boolean resendOnPriceChange) {
|
||||
this.articleId = create.getArticleId();
|
||||
this.articleDate = create.getArticleDate();
|
||||
change(create);
|
||||
this.resendOnPriceChange = resendOnPriceChange;
|
||||
update(create, resendOnPriceChange);
|
||||
}
|
||||
|
||||
public boolean change(final OfferCreate create) {
|
||||
public boolean update(@NonNull final OfferCreate create, final boolean resendOnPriceChange) {
|
||||
boolean changed = false;
|
||||
this.resendOnPriceChange = resendOnPriceChange;
|
||||
if (!Objects.equals(this.title, create.getTitle())) {
|
||||
this.title = create.getTitle();
|
||||
changed = true;
|
||||
@ -117,7 +130,9 @@ public class Offer {
|
||||
changed = true;
|
||||
}
|
||||
if ((this.price == null) != (create.getPrice() == null) || (this.price != null && this.price.compareTo(create.getPrice()) != 0)) {
|
||||
this.priceLast = this.price;
|
||||
this.price = create.getPrice();
|
||||
this.priceChanged = ZonedDateTime.now();
|
||||
changed = true;
|
||||
}
|
||||
if (!Objects.equals(this.negotiable, create.isNegotiable())) {
|
||||
|
||||
@ -55,6 +55,14 @@ public class OfferDto {
|
||||
@Nullable
|
||||
private final BigDecimal price;
|
||||
|
||||
@Nullable
|
||||
private final BigDecimal priceLast;
|
||||
|
||||
@Nullable
|
||||
private final ZonedDateTime priceChanged;
|
||||
|
||||
private final boolean resendOnPriceChange;
|
||||
|
||||
private final boolean negotiable;
|
||||
|
||||
public OfferDto(final @NonNull Offer offer) {
|
||||
@ -73,6 +81,9 @@ public class OfferDto {
|
||||
articleURL = offer.getArticleURL();
|
||||
imageURL = offer.getImageURL();
|
||||
price = offer.getPrice();
|
||||
priceLast = offer.getPriceLast();
|
||||
priceChanged = offer.getPriceChanged();
|
||||
resendOnPriceChange = offer.isResendOnPriceChange();
|
||||
negotiable = offer.isNegotiable();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package de.ph87.kleinanzeigen.kleinanzeigen.offer;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@ -16,16 +17,16 @@ public class OfferService {
|
||||
|
||||
private final ApplicationEventPublisher publisher;
|
||||
|
||||
public void updateOrCreate(final OfferCreate create) {
|
||||
public void updateOrCreate(@NonNull final OfferCreate create, final boolean resendOnPriceChange) {
|
||||
offerRepository.findByArticleId(create.getArticleId()).ifPresentOrElse(
|
||||
existing -> {
|
||||
if (existing.change(create)) {
|
||||
if (existing.update(create, resendOnPriceChange)) {
|
||||
final OfferDto dto = toDto(existing);
|
||||
publisher.publishEvent(dto);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
final Offer offer = offerRepository.save(new Offer(create));
|
||||
final Offer offer = offerRepository.save(new Offer(create, resendOnPriceChange));
|
||||
final OfferDto dto = toDto(offer);
|
||||
publisher.publishEvent(dto);
|
||||
}
|
||||
|
||||
@ -30,6 +30,9 @@ public class Search {
|
||||
@Column(nullable = false)
|
||||
private int radius;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean resendOnPriceChange;
|
||||
|
||||
@Column
|
||||
@Nullable
|
||||
private Integer priceMin = null;
|
||||
|
||||
@ -20,12 +20,15 @@ public class SearchCreate {
|
||||
@Nullable
|
||||
private final Integer priceMax;
|
||||
|
||||
public SearchCreate(final boolean enabled, @NonNull final String query, final int radius, @Nullable final Integer priceMin, @Nullable final Integer priceMax) {
|
||||
private final boolean resendOnPriceChange;
|
||||
|
||||
public SearchCreate(final boolean enabled, @NonNull final String query, final int radius, @Nullable final Integer priceMin, @Nullable final Integer priceMax, final boolean resendOnPriceChange) {
|
||||
this.enabled = enabled;
|
||||
this.query = query;
|
||||
this.radius = radius;
|
||||
this.priceMin = priceMin;
|
||||
this.priceMax = priceMax;
|
||||
this.resendOnPriceChange = resendOnPriceChange;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ public class SearchDto {
|
||||
@Nullable
|
||||
private final Integer priceMax;
|
||||
|
||||
private final boolean resendOnPriceChange;
|
||||
|
||||
public SearchDto(final Search search) {
|
||||
this.id = search.getId();
|
||||
this.enabled = search.isEnabled();
|
||||
@ -31,6 +33,7 @@ public class SearchDto {
|
||||
this.radius = search.getRadius();
|
||||
this.priceMin = search.getPriceMin();
|
||||
this.priceMax = search.getPriceMax();
|
||||
this.resendOnPriceChange = search.isResendOnPriceChange();
|
||||
}
|
||||
|
||||
public String getQueryEscaped() {
|
||||
|
||||
@ -10,6 +10,7 @@ import de.ph87.kleinanzeigen.telegram.chat.message.MessageDto;
|
||||
import de.ph87.kleinanzeigen.telegram.chat.message.MessageService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -66,7 +67,7 @@ public class TelegramService {
|
||||
}
|
||||
|
||||
@TransactionalEventListener(OfferDto.class)
|
||||
public void onOffer(final OfferDto offer) {
|
||||
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) {
|
||||
@ -144,43 +145,55 @@ public class TelegramService {
|
||||
log.info("Sending: offer={} to chat={}", offerDto, chatDto);
|
||||
final Message tlgMessage = bot.execute(send);
|
||||
|
||||
messageService.create(offerDto, chatDto, tlgMessage);
|
||||
messageService.updateOrCreate(offerDto, chatDto, tlgMessage);
|
||||
} catch (TelegramApiException | JsonProcessingException e) {
|
||||
log.error("Failed to send: chat={}, offer={}: {}", chatDto, offerDto, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void update(final MessageDto messageDto) {
|
||||
if (messageDto.isHide() && messageDto.getTelegramMessageId() != null) {
|
||||
remove(messageDto.getChat().getId(), messageDto.getTelegramMessageId());
|
||||
messageService.clearTelegramMessageId(messageDto);
|
||||
private void update(@NonNull final MessageDto message) {
|
||||
// resendOnPriceChange
|
||||
if (message.getOffer().isResendOnPriceChange() && message.getOffer().getPriceChanged() != null && message.getHide() != null && !message.getOffer().getPriceChanged().isBefore(message.getHide())) {
|
||||
messageService.setHide(message, false);
|
||||
if (message.getTelegramMessageId() == null) {
|
||||
send(message.getOffer(), message.getChat());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// hide: delete message from telegram
|
||||
if (message.getHide() != null && message.getTelegramMessageId() != null) {
|
||||
remove(message.getChat().getId(), message.getTelegramMessageId());
|
||||
messageService.clearTelegramMessageId(message);
|
||||
return;
|
||||
}
|
||||
if (messageDto.getTelegramMessageId() == null) {
|
||||
|
||||
// message
|
||||
if (message.getTelegramMessageId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final EditMessageCaption edit = new EditMessageCaption(
|
||||
messageDto.getChat().getId() + "",
|
||||
messageDto.getTelegramMessageId(),
|
||||
message.getChat().getId() + "",
|
||||
message.getTelegramMessageId(),
|
||||
null,
|
||||
createText(messageDto.getOffer()),
|
||||
createKeyboard(messageDto.isRemember()),
|
||||
createText(message.getOffer()),
|
||||
createKeyboard(message.isRemember()),
|
||||
null,
|
||||
null
|
||||
);
|
||||
edit.setParseMode("Markdown");
|
||||
log.info("Editing Message: {}", messageDto);
|
||||
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: {}", messageDto);
|
||||
messageService.setHide(messageDto, true);
|
||||
log.info("Message has been deleted by User. Marking has hidden: {}", message);
|
||||
messageService.setHide(message, true);
|
||||
} 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: {}", messageDto);
|
||||
log.debug("Ignoring complaint from telegram-bot-api about unmodified message: {}", message);
|
||||
} else {
|
||||
log.error("Failed to edit Message chat={} message={}", messageDto.getChat().getId(), messageDto.getTelegramMessageId(), e);
|
||||
log.error("Failed to edit Message chat={} message={}", message.getChat().getId(), message.getTelegramMessageId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@ -31,7 +33,8 @@ public class Message {
|
||||
|
||||
@Setter
|
||||
@Column
|
||||
private boolean hide = false;
|
||||
@Nullable
|
||||
private ZonedDateTime hide = null;
|
||||
|
||||
@Setter
|
||||
@Column
|
||||
|
||||
@ -6,6 +6,8 @@ import jakarta.annotation.Nullable;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Data
|
||||
public class MessageDto {
|
||||
|
||||
@ -20,7 +22,8 @@ public class MessageDto {
|
||||
@Nullable
|
||||
private final Integer telegramMessageId;
|
||||
|
||||
private final boolean hide;
|
||||
@Nullable
|
||||
private final ZonedDateTime hide;
|
||||
|
||||
private final boolean remember;
|
||||
|
||||
@ -29,7 +32,7 @@ public class MessageDto {
|
||||
chat = chatDto;
|
||||
offer = offerDto;
|
||||
telegramMessageId = message.getTelegramMessageId();
|
||||
hide = message.isHide();
|
||||
hide = message.getHide();
|
||||
remember = message.isRemember();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package de.ph87.kleinanzeigen.telegram.chat.message;
|
||||
|
||||
import de.ph87.kleinanzeigen.kleinanzeigen.offer.Offer;
|
||||
import de.ph87.kleinanzeigen.telegram.chat.Chat;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
|
||||
import java.util.List;
|
||||
@ -11,4 +13,6 @@ public interface MessageRepository extends ListCrudRepository<Message, Long> {
|
||||
|
||||
List<Message> findAllByOffer_Id(long id);
|
||||
|
||||
Optional<Message> findByChatAndOffer(Chat chat, Offer offer);
|
||||
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ 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;
|
||||
|
||||
@ -31,15 +32,18 @@ public class MessageService {
|
||||
private final ChatService chatService;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void create(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 Chat chat = chatService.getByDto(chatDto);
|
||||
messageRepository.save(new Message(offer, chat, tlgMessage));
|
||||
messageRepository.findByChatAndOffer(chat, offer).ifPresentOrElse(
|
||||
existing -> existing.setTelegramMessageId(tlgMessage.getMessageId()),
|
||||
() -> messageRepository.save(new Message(offer, chat, tlgMessage))
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public Optional<MessageDto> setHide(final MaybeInaccessibleMessage tlgMessage, final boolean hide) {
|
||||
return findByTelegramMessage(tlgMessage).stream().peek(offer -> offer.setHide(hide)).findFirst().map(this::toDto);
|
||||
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) {
|
||||
@ -55,7 +59,7 @@ public class MessageService {
|
||||
}
|
||||
|
||||
public void setHide(final MessageDto dto, final boolean hide) {
|
||||
findByDto(dto).ifPresent(offer -> offer.setHide(hide));
|
||||
findByDto(dto).ifPresent(offer -> offer.setHide(hide ? ZonedDateTime.now() : null));
|
||||
}
|
||||
|
||||
private Optional<Message> findByTelegramMessage(final MaybeInaccessibleMessage tlgMessage) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user