diff --git a/src/main/java/com/miti99/storescraperbot/Main.java b/src/main/java/com/miti99/storescraperbot/Main.java
index c8ade4e..89c1b9a 100644
--- a/src/main/java/com/miti99/storescraperbot/Main.java
+++ b/src/main/java/com/miti99/storescraperbot/Main.java
@@ -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 %s".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);
+ }
}
diff --git a/src/main/java/com/miti99/storescraperbot/bot/StoreScrapeBot.java b/src/main/java/com/miti99/storescraperbot/bot/StoreScrapeBot.java
index 8670c0c..513a7d9 100644
--- a/src/main/java/com/miti99/storescraperbot/bot/StoreScrapeBot.java
+++ b/src/main/java/com/miti99/storescraperbot/bot/StoreScrapeBot.java
@@ -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();
+ 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();
+ 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("%d Apple Apps:\n".formatted(nonUpdatedAppleApps.size()));
+ sb.append("\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("\n");
+ }
+
+ if (!nonUpdatedGoogleApps.isEmpty()) {
+ sb.append("%d Google Apps:\n".formatted(nonUpdatedGoogleApps.size()));
+ sb.append("\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("");
+ }
+
+ 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);
+ }
+ }
}
diff --git a/src/main/java/com/miti99/storescraperbot/bot/entity/NonUpdatedApp.java b/src/main/java/com/miti99/storescraperbot/bot/entity/NonUpdatedApp.java
new file mode 100644
index 0000000..c3ba838
--- /dev/null
+++ b/src/main/java/com/miti99/storescraperbot/bot/entity/NonUpdatedApp.java
@@ -0,0 +1,5 @@
+package com.miti99.storescraperbot.bot.entity;
+
+import java.time.LocalDate;
+
+public record NonUpdatedApp(String appId, LocalDate updated, long days) {}
diff --git a/src/main/java/com/miti99/storescraperbot/bot/table/Table.java b/src/main/java/com/miti99/storescraperbot/bot/table/Table.java
index e976195..5350721 100644
--- a/src/main/java/com/miti99/storescraperbot/bot/table/Table.java
+++ b/src/main/java/com/miti99/storescraperbot/bot/table/Table.java
@@ -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();
}
diff --git a/src/main/java/com/miti99/storescraperbot/constant/Constant.java b/src/main/java/com/miti99/storescraperbot/constant/Constant.java
index 8f1859c..de5f361 100644
--- a/src/main/java/com/miti99/storescraperbot/constant/Constant.java
+++ b/src/main/java/com/miti99/storescraperbot/constant/Constant.java
@@ -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 WEEKENDS = Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
}
diff --git a/src/main/java/com/miti99/storescraperbot/util/SchedulerUtil.java b/src/main/java/com/miti99/storescraperbot/util/SchedulerUtil.java
new file mode 100644
index 0000000..3eac3af
--- /dev/null
+++ b/src/main/java/com/miti99/storescraperbot/util/SchedulerUtil.java
@@ -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();
+}