Refactor app tracking to use app info records

Replaces string-based app tracking in Group with AppleAppInfo and GoogleAppInfo records, enabling storage of additional metadata (e.g., country for Google apps). Updates all related commands and logic to use the new records, improves command argument handling, and enhances output formatting for app lists and checks.
This commit is contained in:
2025-11-06 23:18:49 +07:00
parent e46c842b3e
commit 3ea1e824fd
15 changed files with 87 additions and 52 deletions
@@ -1,11 +1,11 @@
package com.miti99.storescraperbot.api.apple.request;
public record AppleAppRequest(String appId, Long id) {
public record AppleAppRequest(String appId, Long id, Boolean ratings) {
public AppleAppRequest(String appId) {
this(appId, null);
this(appId, null, true);
}
public AppleAppRequest(Long id) {
this(null, id);
this(null, id, true);
}
}
@@ -43,13 +43,13 @@ public class GooglePlayScraper {
}
}
private static GoogleAppResponse getResponse(String appId) {
private static GoogleAppResponse getResponse(String appId, String country) {
boolean isInCache = GoogleAppRepository.INSTANCE.exist(appId);
if (isInCache) {
var app = GoogleAppRepository.INSTANCE.load(appId);
return app.getApp();
} else {
var response = app(new GoogleAppRequest(appId));
var response = app(new GoogleAppRequest(appId, country));
GoogleAppRepository.INSTANCE.init(appId);
var app = GoogleAppRepository.INSTANCE.load(appId);
app.setApp(response);
@@ -58,8 +58,8 @@ public class GooglePlayScraper {
}
}
public static LocalDate getLastUpdateOfApp(String appId) {
var response = getResponse(appId);
public static LocalDate getLastUpdateOfApp(String appId, String country) {
var response = getResponse(appId, country);
long lastUpdateMillis = 0;
if (response != null) {
lastUpdateMillis = response.updated();
@@ -69,8 +69,8 @@ public class GooglePlayScraper {
return LocalDate.ofInstant(Instant.ofEpochMilli(lastUpdateMillis), ZoneId.systemDefault());
}
public static double getAppScore(String appId) {
var response = getResponse(appId);
public static double getAppScore(String appId, String country) {
var response = getResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0.0;
@@ -78,8 +78,8 @@ public class GooglePlayScraper {
return response.score();
}
public static long getAppRatings(String appId) {
var response = getResponse(appId);
public static long getAppRatings(String appId, String country) {
var response = getResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0L;
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.api.google.request;
public record GoogleAppRequest(String appId, String lang, String country) {
public record GoogleAppRequest(String appId, String country) {
public GoogleAppRequest(String appId) {
this(appId, "vi", "vn");
this(appId, "vn");
}
}
@@ -4,6 +4,7 @@ import com.miti99.storescraperbot.bot.command.AddAppleAppCommand;
import com.miti99.storescraperbot.bot.command.AddGoogleAppCommand;
import com.miti99.storescraperbot.bot.command.AddGroupCommand;
import com.miti99.storescraperbot.bot.command.CheckAppCommand;
import com.miti99.storescraperbot.bot.command.CheckAppScoreCommand;
import com.miti99.storescraperbot.bot.command.DeleteAppleAppCommand;
import com.miti99.storescraperbot.bot.command.DeleteGroupCommand;
import com.miti99.storescraperbot.bot.command.InfoCommand;
@@ -34,6 +35,7 @@ public class StoreScrapeBot extends CommandLongPollingTelegramBot {
register(DeleteAppleAppCommand.INSTANCE);
register(ListAppCommand.INSTANCE);
register(CheckAppCommand.INSTANCE);
register(CheckAppScoreCommand.INSTANCE);
setMyCommands();
}
@@ -4,6 +4,7 @@ import com.miti99.storescraperbot.api.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.apple.request.AppleAppRequest;
import com.miti99.storescraperbot.api.apple.response.AppleAppResponse;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.model.entity.AppleAppInfo;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
import lombok.extern.log4j.Log4j2;
@@ -16,7 +17,9 @@ public class AddAppleAppCommand extends BaseStoreScraperBotCommand {
public static final AddAppleAppCommand INSTANCE = new AddAppleAppCommand();
AddAppleAppCommand() {
super("addapple", "<appId>. Thêm Apple app vào danh sách theo dõi của nhóm");
super(
"addapple",
"<id/appId>. Thêm Apple app vào danh sách theo dõi của nhóm. id: <i>iTunes 'trackId'</i>, appId: <i>iTunes 'bundleId'</i>");
}
@Override
@@ -50,7 +53,8 @@ public class AddAppleAppCommand extends BaseStoreScraperBotCommand {
}
} catch (Exception e) {
log.error("request app error for appId: '{}', id: '{}'", appId, id, e);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Error when request app info");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Error when request app info");
return;
}
appId = response.appId();
@@ -58,13 +62,16 @@ public class AddAppleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.getAppleApps().contains(appId)) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app is already added");
var finalAppId = appId;
if (group.getAppleApps().stream().anyMatch(app -> finalAppId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Apple app <code>%s</code> is already added".formatted(appId));
return;
}
group.getAppleApps().add(appId);
group.getAppleApps().add(new AppleAppInfo(appId));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app <code>%s</code> added successfully".formatted(appId));
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Apple app <code>%s</code> added successfully".formatted(appId));
}
}
@@ -4,6 +4,7 @@ import com.miti99.storescraperbot.api.google.GooglePlayScraper;
import com.miti99.storescraperbot.api.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.api.google.response.GoogleAppResponse;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.model.entity.GoogleAppInfo;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
import lombok.extern.log4j.Log4j2;
@@ -16,7 +17,9 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand {
public static final AddGoogleAppCommand INSTANCE = new AddGoogleAppCommand();
AddGoogleAppCommand() {
super("addgoogle", "<appId> [country]. Thêm Google app vào danh sách theo dõi của nhóm. Một số app cần country để hoạt động đúng");
super(
"addgoogle",
"<appId> [country]. Thêm Google app vào danh sách theo dõi của nhóm. Một số app cần country để hoạt động đúng, country mặc định là 'vn'");
}
@Override
@@ -29,15 +32,16 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand {
return;
}
if (arguments.length != 1) {
if (arguments.length < 1 || arguments.length > 2) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var appId = arguments[0];
var country = arguments.length == 2 ? arguments[1] : "vn";
GoogleAppResponse response = null;
try {
response = GooglePlayScraper.app(new GoogleAppRequest(appId));
response = GooglePlayScraper.app(new GoogleAppRequest(appId, country));
} catch (Exception e) {
log.error("request app error for appId: '{}'", appId, e);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
@@ -48,13 +52,13 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.getGoogleApps().contains(appId)) {
if (group.getGoogleApps().stream().anyMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Google app is already added");
chat.getId(), "Google app <code>%s</code> is already added".formatted(appId));
return;
}
group.getGoogleApps().add(appId);
group.getGoogleApps().add(new GoogleAppInfo(appId, country));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Google app <code>%s</code> added successfully".formatted(appId));
@@ -44,7 +44,8 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand {
sb.append("%-20s | %-10s | %-4s | %-2s\n".formatted("AppId", "Updated", "Days", "OK"));
sb.append("-".repeat(46));
sb.append("\n");
for (var appId : group.getAppleApps()) {
for (var app : group.getAppleApps()) {
var appId = app.appId();
var updated = AppStoreScraper.getAppUpdated(appId);
long days = ChronoUnit.DAYS.between(updated, now);
boolean passed = days <= Constant.NUM_DAYS_WARNING_NOT_UPDATED;
@@ -58,8 +59,9 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand {
sb.append("%-20s | %-10s | %-4s | %-2s\n".formatted("AppId", "Updated", "Days", "OK"));
sb.append("-".repeat(46));
sb.append("\n");
for (var appId : group.getGoogleApps()) {
var updated = GooglePlayScraper.getLastUpdateOfApp(appId);
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var updated = GooglePlayScraper.getLastUpdateOfApp(appId, app.country());
long days = ChronoUnit.DAYS.between(updated, now);
boolean passed = days <= Constant.NUM_DAYS_WARNING_NOT_UPDATED;
sb.append(
@@ -3,10 +3,8 @@ package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.google.GooglePlayScraper;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
import java.time.temporal.ChronoUnit;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.chat.Chat;
import org.telegram.telegrambots.meta.generics.TelegramClient;
@@ -40,9 +38,10 @@ public class CheckAppScoreCommand extends BaseStoreScraperBotCommand {
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-20s | %-10s | %-10s\n".formatted("AppId", "Score", "Reviews"));
sb.append("-".repeat(32));
sb.append("-".repeat(43));
sb.append("\n");
for (var appId : group.getAppleApps()) {
for (var app : group.getAppleApps()) {
var appId = app.appId();
double score = AppStoreScraper.getAppScore(appId);
long reviews = AppStoreScraper.getAppReviews(appId);
sb.append("%-20s | %-10s | %-10s\n".formatted(appId, score, reviews));
@@ -52,11 +51,13 @@ public class CheckAppScoreCommand extends BaseStoreScraperBotCommand {
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-20s | %-10s | %-10s\n".formatted("AppId", "Score", "Ratings"));
sb.append("-".repeat(46));
sb.append("-".repeat(43));
sb.append("\n");
for (var appId : group.getGoogleApps()) {
double score = GooglePlayScraper.getAppScore(appId);
long ratings = GooglePlayScraper.getAppRatings(appId);
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var country = app.country();
double score = GooglePlayScraper.getAppScore(appId, country);
long ratings = GooglePlayScraper.getAppRatings(appId, country);
sb.append("%-20s | %-10s | %-10s\n".formatted(appId, score, ratings));
}
sb.append("</code>");
@@ -33,12 +33,12 @@ public class DeleteAppleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (!group.getAppleApps().contains(appId)) {
if (group.getAppleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app is not added");
return;
}
group.getAppleApps().remove(appId);
group.getAppleApps().removeIf(app -> appId.equals(app.appId()));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app deleted successfully");
}
@@ -33,12 +33,12 @@ public class DeleteGoogleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (!group.getGoogleApps().contains(appId)) {
if (group.getGoogleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Google app is not added");
return;
}
group.getGoogleApps().remove(appId);
group.getGoogleApps().removeIf(app -> appId.equals(app.appId()));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Google app deleted successfully");
@@ -11,7 +11,7 @@ public class ListAppCommand extends BaseStoreScraperBotCommand {
public static final ListAppCommand INSTANCE = new ListAppCommand();
ListAppCommand() {
super("listapp", "<appId>. Thêm Google app vào danh sách theo dõi của nhóm");
super("listapp", "<appId>. Lấy danh sách app đang theo dõi của nhóm");
}
@Override
@@ -34,13 +34,30 @@ public class ListAppCommand extends BaseStoreScraperBotCommand {
var sb = new StringBuilder();
sb.append("<b>Apple Apps:</b>\n");
for (var appId : group.getAppleApps()) {
sb.append("- <code>%s</code>\n".formatted(appId));
sb.append("<code>\n");
sb.append("%-2s | %-20s\n".formatted("#", "AppId"));
sb.append("-".repeat(25));
sb.append("\n");
int i = 0;
for (var app : group.getAppleApps()) {
i++;
sb.append("%-2d | %-20s\n".formatted(i,app.appId()));
}
sb.append("</code>\n");
sb.append("\n");
sb.append("\n<b>Google Apps:</b>\n");
for (var appId : group.getGoogleApps()) {
sb.append("- <code>%s</code>\n".formatted(appId));
sb.append("<code>\n");
sb.append("%-2s | %-20s | %-7s\n".formatted("#", "AppId", "Country"));
sb.append("-".repeat(35));
sb.append("\n");
i = 0;
for (var app : group.getGoogleApps()) {
i++;
sb.append("%-2s | %-20s | %-7s\n".formatted(i, app.appId(), app.country()));
}
sb.append("</code>\n");
sb.append("\n");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
@@ -1,10 +1,10 @@
package com.miti99.storescraperbot.model;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
@Getter
public class Admin extends AbstractModel<String> {
final List<Long> groups = new ArrayList<>();
final Set<Long> groups = new HashSet<>();
}
@@ -1,5 +1,7 @@
package com.miti99.storescraperbot.model;
import com.miti99.storescraperbot.model.entity.AppleAppInfo;
import com.miti99.storescraperbot.model.entity.GoogleAppInfo;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
@@ -8,6 +10,6 @@ import lombok.Setter;
@Getter
@Setter
public class Group extends AbstractModel<Long> {
List<String> appleApps = new ArrayList<>();
List<String> googleApps = new ArrayList<>();
List<AppleAppInfo> appleApps = new ArrayList<>();
List<GoogleAppInfo> googleApps = new ArrayList<>();
}
@@ -1,3 +1,3 @@
package com.miti99.storescraperbot.model.entity;
public class AppleAppInfo {}
public record AppleAppInfo(String appId) {}
@@ -1,3 +1,3 @@
package com.miti99.storescraperbot.model.entity;
public class GoogleAppInfo {}
public record GoogleAppInfo(String appId, String country) {}