mirror of
https://github.com/tiennm99/store-scraper-bot-java.git
synced 2026-05-18 17:26:52 +00:00
Add scheduled app update checks and notifications
Introduces a scheduled task that checks for apps not updated within a warning threshold and sends notifications to groups. Adds scheduling utilities, constants for scheduling, and logic to format and send update reports for both Apple and Google apps. Also includes a new NonUpdatedApp record and improvements to the Table class for better formatting.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package com.miti99.storescraperbot;
|
||||
|
||||
import static com.miti99.storescraperbot.constant.Constant.SCHEDULE_CHECK_APP_TIME;
|
||||
import static com.miti99.storescraperbot.constant.Constant.SECONDS_PER_DAY;
|
||||
import static com.miti99.storescraperbot.constant.Constant.VIETNAM_ZONE_ID;
|
||||
import static com.miti99.storescraperbot.env.Environment.CREATOR_ID;
|
||||
import static com.miti99.storescraperbot.env.Environment.SOURCE_COMMIT;
|
||||
|
||||
@@ -8,6 +11,12 @@ import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
|
||||
import com.miti99.storescraperbot.env.Environment;
|
||||
import com.miti99.storescraperbot.repository.AdminRepository;
|
||||
import com.miti99.storescraperbot.type.Env;
|
||||
import com.miti99.storescraperbot.util.SchedulerUtil;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication;
|
||||
|
||||
@@ -24,9 +33,26 @@ public class Main {
|
||||
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
|
||||
CREATOR_ID, "Bot started! Version <code>%s</code>".formatted(SOURCE_COMMIT));
|
||||
}
|
||||
scheduleCheckApp();
|
||||
Thread.currentThread().join();
|
||||
} catch (Exception e) {
|
||||
log.error("Error while running bot", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void scheduleCheckApp() {
|
||||
var now = LocalDateTime.now();
|
||||
|
||||
var checkTime =
|
||||
LocalDateTime.of(LocalDate.now(VIETNAM_ZONE_ID), SCHEDULE_CHECK_APP_TIME)
|
||||
.atZone(VIETNAM_ZONE_ID)
|
||||
.withZoneSameInstant(ZoneId.systemDefault())
|
||||
.toLocalDateTime();
|
||||
long initialDelay = Duration.between(now, checkTime).getSeconds();
|
||||
if (initialDelay < 0) {
|
||||
initialDelay += SECONDS_PER_DAY;
|
||||
}
|
||||
SchedulerUtil.SCHEDULER.scheduleAtFixedRate(
|
||||
StoreScrapeBot.INSTANCE::runCheckApp, initialDelay, SECONDS_PER_DAY, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.miti99.storescraperbot.bot;
|
||||
|
||||
import static com.miti99.storescraperbot.constant.Constant.VIETNAM_ZONE_ID;
|
||||
import static com.miti99.storescraperbot.constant.Constant.WEEKENDS;
|
||||
|
||||
import com.miti99.storescraperbot.api.apple.AppStoreScraper;
|
||||
import com.miti99.storescraperbot.api.google.GooglePlayScraper;
|
||||
import com.miti99.storescraperbot.bot.command.AddAppleAppCommand;
|
||||
import com.miti99.storescraperbot.bot.command.AddGoogleAppCommand;
|
||||
import com.miti99.storescraperbot.bot.command.AddGroupCommand;
|
||||
@@ -10,9 +15,19 @@ import com.miti99.storescraperbot.bot.command.DeleteGroupCommand;
|
||||
import com.miti99.storescraperbot.bot.command.InfoCommand;
|
||||
import com.miti99.storescraperbot.bot.command.ListAppCommand;
|
||||
import com.miti99.storescraperbot.bot.command.ListGroupCommand;
|
||||
import com.miti99.storescraperbot.bot.entity.NonUpdatedApp;
|
||||
import com.miti99.storescraperbot.bot.table.Table;
|
||||
import com.miti99.storescraperbot.constant.Constant;
|
||||
import com.miti99.storescraperbot.repository.AdminRepository;
|
||||
import com.miti99.storescraperbot.repository.GroupRepository;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.telegram.telegrambots.extensions.bots.commandbot.CommandLongPollingTelegramBot;
|
||||
import org.telegram.telegrambots.meta.api.methods.ParseMode;
|
||||
import org.telegram.telegrambots.meta.api.methods.commands.SetMyCommands;
|
||||
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
import org.telegram.telegrambots.meta.api.objects.commands.BotCommand;
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
|
||||
@@ -56,4 +71,86 @@ public class StoreScrapeBot extends CommandLongPollingTelegramBot {
|
||||
public void processNonCommandUpdate(Update update) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
public void runCheckApp() {
|
||||
var admin = AdminRepository.INSTANCE.load();
|
||||
for (var groupId : admin.getGroups()) {
|
||||
checkAppForGroup(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkAppForGroup(long groupId) {
|
||||
var group = GroupRepository.INSTANCE.load(groupId);
|
||||
var now = LocalDate.now();
|
||||
|
||||
var nonUpdatedAppleApps = new ArrayList<NonUpdatedApp>();
|
||||
for (var app : group.getAppleApps()) {
|
||||
var appId = app.appId();
|
||||
var updated = AppStoreScraper.getAppUpdated(appId, app.country());
|
||||
long days = ChronoUnit.DAYS.between(updated, now);
|
||||
if (days > Constant.NUM_DAYS_WARNING_NOT_UPDATED) {
|
||||
nonUpdatedAppleApps.add(new NonUpdatedApp(appId, updated, days));
|
||||
}
|
||||
}
|
||||
var nonUpdatedGoogleApps = new ArrayList<NonUpdatedApp>();
|
||||
for (var app : group.getGoogleApps()) {
|
||||
var appId = app.appId();
|
||||
var updated = GooglePlayScraper.getLastUpdateOfApp(appId, app.country());
|
||||
long days = ChronoUnit.DAYS.between(updated, now);
|
||||
if (days > Constant.NUM_DAYS_WARNING_NOT_UPDATED) {
|
||||
nonUpdatedGoogleApps.add(new NonUpdatedApp(appId, updated, days));
|
||||
}
|
||||
}
|
||||
int numNonUpdatedApps = nonUpdatedAppleApps.size() + nonUpdatedGoogleApps.size();
|
||||
if (numNonUpdatedApps == 0) return;
|
||||
|
||||
var sb =
|
||||
new StringBuilder("You have %d app(s) need to be updated!\n".formatted(numNonUpdatedApps));
|
||||
if (!nonUpdatedAppleApps.isEmpty()) {
|
||||
sb.append("<b>%d Apple Apps:</b>\n".formatted(nonUpdatedAppleApps.size()));
|
||||
sb.append("<code>\n");
|
||||
var appleTable = new Table("#", "AppId", "Updated", "Days");
|
||||
int i = 0;
|
||||
for (var app : nonUpdatedAppleApps) {
|
||||
i++;
|
||||
var appId = app.appId();
|
||||
var updated = app.updated();
|
||||
long days = app.days();
|
||||
appleTable.addRow(i, appId, updated, days);
|
||||
}
|
||||
sb.append(appleTable);
|
||||
sb.append("</code>\n");
|
||||
}
|
||||
|
||||
if (!nonUpdatedGoogleApps.isEmpty()) {
|
||||
sb.append("<b>%d Google Apps:</b>\n".formatted(nonUpdatedGoogleApps.size()));
|
||||
sb.append("<code>\n");
|
||||
var googleTable = new Table("#", "AppId", "Updated", "Days");
|
||||
int i = 0;
|
||||
for (var app : nonUpdatedGoogleApps) {
|
||||
i++;
|
||||
var appId = app.appId();
|
||||
var updated = app.updated();
|
||||
long days = app.days();
|
||||
googleTable.addRow(i, appId, updated, days);
|
||||
}
|
||||
sb.append(googleTable);
|
||||
sb.append("</code>");
|
||||
}
|
||||
|
||||
var dayOfWeek = LocalDate.now(VIETNAM_ZONE_ID).getDayOfWeek();
|
||||
boolean mute = WEEKENDS.contains(dayOfWeek);
|
||||
try {
|
||||
var sendMessage =
|
||||
SendMessage.builder()
|
||||
.parseMode(ParseMode.HTML)
|
||||
.chatId(groupId)
|
||||
.disableNotification(mute)
|
||||
.text(sb.toString())
|
||||
.build();
|
||||
StoreScrapeBotTelegramClient.INSTANCE.execute(sendMessage);
|
||||
} catch (Exception e) {
|
||||
log.error("sendMessage error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.miti99.storescraperbot.bot.entity;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record NonUpdatedApp(String appId, LocalDate updated, long days) {}
|
||||
@@ -44,9 +44,13 @@ public class Table {
|
||||
sb.append(formater.formatted((Object[]) headers));
|
||||
var rule =
|
||||
Arrays.stream(maxWidths).mapToObj("─"::repeat).collect(Collectors.joining("─┼─", "", "\n"));
|
||||
sb.append(rule);
|
||||
int i = 0;
|
||||
for (var row : rows) {
|
||||
if (i % 5 == 0) {
|
||||
sb.append(rule);
|
||||
}
|
||||
sb.append(formater.formatted((Object[]) row));
|
||||
i++;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
package com.miti99.storescraperbot.constant;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Set;
|
||||
|
||||
public class Constant {
|
||||
public static final String COMMON_COLLECTION_NAME = "common";
|
||||
public static final long APP_CACHE_SECONDS = 600;
|
||||
public static final long NUM_DAYS_WARNING_NOT_UPDATED = 30;
|
||||
public static final LocalTime SCHEDULE_CHECK_APP_TIME = LocalTime.of(7, 0);
|
||||
|
||||
public static final String VIETNAM_ZONE_ID_STRING = "Asia/Ho_Chi_Minh";
|
||||
public static final ZoneId VIETNAM_ZONE_ID = ZoneId.of(VIETNAM_ZONE_ID_STRING);
|
||||
public static final long SECONDS_PER_DAY = ChronoUnit.DAYS.getDuration().getSeconds();
|
||||
public static final Set<DayOfWeek> WEEKENDS = Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.miti99.storescraperbot.util;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class SchedulerUtil {
|
||||
public static final ScheduledExecutorService SCHEDULER =
|
||||
Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
Reference in New Issue
Block a user