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:
2025-11-07 23:45:30 +07:00
parent 77f4e08946
commit 7349501086
6 changed files with 157 additions and 1 deletions
@@ -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();
}