diff --git a/src/main/java/com/miti99/storescraperbot/api/apple/AppStoreScraper.java b/src/main/java/com/miti99/storescraperbot/api/apple/AppStoreScraper.java index a518cf1..be55fbd 100644 --- a/src/main/java/com/miti99/storescraperbot/api/apple/AppStoreScraper.java +++ b/src/main/java/com/miti99/storescraperbot/api/apple/AppStoreScraper.java @@ -43,24 +43,45 @@ public class AppStoreScraper { } } - public static LocalDate getLastUpdateOfApp(String appId) { + public static AppleAppResponse getResponse(String appId) { boolean isInCache = AppleAppRepository.INSTANCE.exist(appId); - AppleAppResponse response = null; if (isInCache) { var app = AppleAppRepository.INSTANCE.load(appId); - response = app.getApp(); + return app.getApp(); } else { - response = app(new AppleAppRequest(appId)); + var response = app(new AppleAppRequest(appId)); AppleAppRepository.INSTANCE.init(appId); var app = AppleAppRepository.INSTANCE.load(appId); app.setApp(response); AppleAppRepository.INSTANCE.save(appId, app); + return response; } - if (response != null) { - return LocalDate.ofInstant(Instant.parse(response.updated()), ZoneId.systemDefault()); - } else { + } + + public static LocalDate getAppUpdated(String appId) { + var response = getResponse(appId); + if (response == null) { log.error("response is null"); return LocalDate.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault()); } + return LocalDate.ofInstant(Instant.parse(response.updated()), ZoneId.systemDefault()); + } + + public static double getAppScore(String appId) { + var response = getResponse(appId); + if (response == null) { + log.error("response is null"); + return 0.0; + } + return response.score(); + } + + public static long getAppReviews(String appId) { + var response = getResponse(appId); + if (response == null) { + log.error("response is null"); + return 0L; + } + return response.reviews(); } } diff --git a/src/main/java/com/miti99/storescraperbot/api/apple/request/AppleAppRequest.java b/src/main/java/com/miti99/storescraperbot/api/apple/request/AppleAppRequest.java index 526c97b..5541cd8 100644 --- a/src/main/java/com/miti99/storescraperbot/api/apple/request/AppleAppRequest.java +++ b/src/main/java/com/miti99/storescraperbot/api/apple/request/AppleAppRequest.java @@ -1,5 +1,11 @@ package com.miti99.storescraperbot.api.apple.request; -public record AppleAppRequest(String appId) { +public record AppleAppRequest(String appId, Long id) { + public AppleAppRequest(String appId) { + this(appId, null); + } + public AppleAppRequest(Long id) { + this(null, id); + } } diff --git a/src/main/java/com/miti99/storescraperbot/api/google/GooglePlayScraper.java b/src/main/java/com/miti99/storescraperbot/api/google/GooglePlayScraper.java index 159d466..b643943 100644 --- a/src/main/java/com/miti99/storescraperbot/api/google/GooglePlayScraper.java +++ b/src/main/java/com/miti99/storescraperbot/api/google/GooglePlayScraper.java @@ -43,19 +43,23 @@ public class GooglePlayScraper { } } - public static LocalDate getLastUpdateOfApp(String appId) { + private static GoogleAppResponse getResponse(String appId) { boolean isInCache = GoogleAppRepository.INSTANCE.exist(appId); - GoogleAppResponse response = null; if (isInCache) { var app = GoogleAppRepository.INSTANCE.load(appId); - response = app.getApp(); + return app.getApp(); } else { - response = app(new GoogleAppRequest(appId)); + var response = app(new GoogleAppRequest(appId)); GoogleAppRepository.INSTANCE.init(appId); var app = GoogleAppRepository.INSTANCE.load(appId); app.setApp(response); GoogleAppRepository.INSTANCE.save(appId, app); + return response; } + } + + public static LocalDate getLastUpdateOfApp(String appId) { + var response = getResponse(appId); long lastUpdateMillis = 0; if (response != null) { lastUpdateMillis = response.updated(); @@ -64,4 +68,22 @@ public class GooglePlayScraper { } return LocalDate.ofInstant(Instant.ofEpochMilli(lastUpdateMillis), ZoneId.systemDefault()); } + + public static double getAppScore(String appId) { + var response = getResponse(appId); + if (response == null) { + log.error("response is null"); + return 0.0; + } + return response.score(); + } + + public static long getAppRatings(String appId) { + var response = getResponse(appId); + if (response == null) { + log.error("response is null"); + return 0L; + } + return response.ratings(); + } } diff --git a/src/main/java/com/miti99/storescraperbot/bot/command/AddAppleAppCommand.java b/src/main/java/com/miti99/storescraperbot/bot/command/AddAppleAppCommand.java index 631e3df..dac916d 100644 --- a/src/main/java/com/miti99/storescraperbot/bot/command/AddAppleAppCommand.java +++ b/src/main/java/com/miti99/storescraperbot/bot/command/AddAppleAppCommand.java @@ -1,12 +1,17 @@ package com.miti99.storescraperbot.bot.command; +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.repository.AdminRepository; import com.miti99.storescraperbot.repository.GroupRepository; +import lombok.extern.log4j.Log4j2; import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.api.objects.chat.Chat; import org.telegram.telegrambots.meta.generics.TelegramClient; +@Log4j2 public class AddAppleAppCommand extends BaseStoreScraperBotCommand { public static final AddAppleAppCommand INSTANCE = new AddAppleAppCommand(); @@ -24,12 +29,32 @@ public class AddAppleAppCommand 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]; + long id = -1; + AppleAppResponse response = null; + try { + try { + id = Long.parseLong(appId); + } catch (Exception e) { + // Input không phải id, bỏ qua + } + if (id != -1) { + response = AppStoreScraper.app(new AppleAppRequest(id)); + } else { + response = AppStoreScraper.app(new AppleAppRequest(appId)); + } + } catch (Exception e) { + log.error("request app error for appId: '{}', id: '{}'", appId, id, e); + StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Error when request app info"); + return; + } + appId = response.appId(); + long groupId = chat.getId(); var group = GroupRepository.INSTANCE.load(groupId); @@ -40,6 +65,6 @@ public class AddAppleAppCommand extends BaseStoreScraperBotCommand { group.getAppleApps().add(appId); GroupRepository.INSTANCE.save(groupId, group); - StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app added successfully"); + StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app %s added successfully".formatted(appId)); } } diff --git a/src/main/java/com/miti99/storescraperbot/bot/command/AddGoogleAppCommand.java b/src/main/java/com/miti99/storescraperbot/bot/command/AddGoogleAppCommand.java index ed85769..9a83276 100644 --- a/src/main/java/com/miti99/storescraperbot/bot/command/AddGoogleAppCommand.java +++ b/src/main/java/com/miti99/storescraperbot/bot/command/AddGoogleAppCommand.java @@ -1,17 +1,22 @@ package com.miti99.storescraperbot.bot.command; +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.repository.AdminRepository; import com.miti99.storescraperbot.repository.GroupRepository; +import lombok.extern.log4j.Log4j2; import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.api.objects.chat.Chat; import org.telegram.telegrambots.meta.generics.TelegramClient; +@Log4j2 public class AddGoogleAppCommand extends BaseStoreScraperBotCommand { public static final AddGoogleAppCommand INSTANCE = new AddGoogleAppCommand(); AddGoogleAppCommand() { - super("addgoogle", ". Thêm Google app vào danh sách theo dõi của nhóm"); + super("addgoogle", " [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"); } @Override @@ -30,6 +35,16 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand { } var appId = arguments[0]; + GoogleAppResponse response = null; + try { + response = GooglePlayScraper.app(new GoogleAppRequest(appId)); + } catch (Exception e) { + log.error("request app error for appId: '{}'", appId, e); + StoreScrapeBotTelegramClient.INSTANCE.sendMessage( + chat.getId(), "Error when request app info"); + return; + } + long groupId = chat.getId(); var group = GroupRepository.INSTANCE.load(groupId); @@ -42,6 +57,6 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand { group.getGoogleApps().add(appId); GroupRepository.INSTANCE.save(groupId, group); StoreScrapeBotTelegramClient.INSTANCE.sendMessage( - chat.getId(), "Google app added successfully"); + chat.getId(), "Google app %s added successfully".formatted(appId)); } } diff --git a/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppCommand.java b/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppCommand.java index eca8d0c..be60201 100644 --- a/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppCommand.java +++ b/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppCommand.java @@ -7,7 +7,6 @@ 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.LocalDateTime; import java.time.temporal.ChronoUnit; import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.api.objects.chat.Chat; @@ -46,7 +45,7 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand { sb.append("-".repeat(46)); sb.append("\n"); for (var appId : group.getAppleApps()) { - var updated = AppStoreScraper.getLastUpdateOfApp(appId); + var updated = AppStoreScraper.getAppUpdated(appId); long days = ChronoUnit.DAYS.between(updated, now); boolean passed = days <= Constant.NUM_DAYS_WARNING_NOT_UPDATED; sb.append( diff --git a/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppScoreCommand.java b/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppScoreCommand.java new file mode 100644 index 0000000..850172d --- /dev/null +++ b/src/main/java/com/miti99/storescraperbot/bot/command/CheckAppScoreCommand.java @@ -0,0 +1,67 @@ +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; + +public class CheckAppScoreCommand extends BaseStoreScraperBotCommand { + public static final CheckAppScoreCommand INSTANCE = new CheckAppScoreCommand(); + + CheckAppScoreCommand() { + super("checkappscore", "Kiểm tra điểm đánh giá các app (sao)"); + } + + @Override + protected void executeCommand( + TelegramClient telegramClient, User user, Chat chat, String[] arguments) { + var admin = AdminRepository.INSTANCE.load(); + if (!admin.getGroups().contains(chat.getId())) { + StoreScrapeBotTelegramClient.INSTANCE.sendMessage( + chat.getId(), "Group is not allowed to use bot"); + return; + } + + if (arguments.length != 0) { + StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments"); + return; + } + + long groupId = chat.getId(); + var group = GroupRepository.INSTANCE.load(groupId); + + var sb = new StringBuilder(); + sb.append("Apple Apps:\n"); + sb.append("\n"); + sb.append("%-20s | %-10s | %-10s\n".formatted("AppId", "Score", "Reviews")); + sb.append("-".repeat(32)); + sb.append("\n"); + for (var appId : group.getAppleApps()) { + double score = AppStoreScraper.getAppScore(appId); + long reviews = AppStoreScraper.getAppReviews(appId); + sb.append("%-20s | %-10s | %-10s\n".formatted(appId, score, reviews)); + } + sb.append("\n"); + sb.append("\n"); + sb.append("Google Apps:\n"); + sb.append("\n"); + sb.append("%-20s | %-10s | %-10s\n".formatted("AppId", "Score", "Ratings")); + sb.append("-".repeat(46)); + sb.append("\n"); + for (var appId : group.getGoogleApps()) { + double score = GooglePlayScraper.getAppScore(appId); + long ratings = GooglePlayScraper.getAppRatings(appId); + sb.append("%-20s | %-10s | %-10s\n".formatted(appId, score, ratings)); + } + sb.append(""); + sb.append("\n"); + + StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), 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 24b3445..8f1859c 100644 --- a/src/main/java/com/miti99/storescraperbot/constant/Constant.java +++ b/src/main/java/com/miti99/storescraperbot/constant/Constant.java @@ -1,7 +1,7 @@ package com.miti99.storescraperbot.constant; public class Constant { - public static final String APP_NAME = "store_scraper_bot"; + 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; } diff --git a/src/main/java/com/miti99/storescraperbot/model/entity/AppleAppInfo.java b/src/main/java/com/miti99/storescraperbot/model/entity/AppleAppInfo.java new file mode 100644 index 0000000..10a6305 --- /dev/null +++ b/src/main/java/com/miti99/storescraperbot/model/entity/AppleAppInfo.java @@ -0,0 +1,3 @@ +package com.miti99.storescraperbot.model.entity; + +public class AppleAppInfo {} diff --git a/src/main/java/com/miti99/storescraperbot/model/entity/GoogleAppInfo.java b/src/main/java/com/miti99/storescraperbot/model/entity/GoogleAppInfo.java new file mode 100644 index 0000000..eb51c94 --- /dev/null +++ b/src/main/java/com/miti99/storescraperbot/model/entity/GoogleAppInfo.java @@ -0,0 +1,3 @@ +package com.miti99.storescraperbot.model.entity; + +public class GoogleAppInfo {} diff --git a/src/main/java/com/miti99/storescraperbot/repository/AbstractRepository.java b/src/main/java/com/miti99/storescraperbot/repository/AbstractRepository.java index d2865b8..594ab5a 100644 --- a/src/main/java/com/miti99/storescraperbot/repository/AbstractRepository.java +++ b/src/main/java/com/miti99/storescraperbot/repository/AbstractRepository.java @@ -16,9 +16,15 @@ public abstract class AbstractRepository> { // protected final Class classK = getKeyClass(); protected final Class classV = getDataClass(); protected final String scopeName = Config.ENV.name().toLowerCase(); - protected final String collectionName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, classV.getSimpleName()); + protected final String collectionName; + + protected AbstractRepository(String collectionName) { + this.collectionName = collectionName; + CouchbaseUtil.createCollection(scopeName, collectionName); + } protected AbstractRepository() { + collectionName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, classV.getSimpleName()); CouchbaseUtil.createCollection(scopeName, collectionName); } diff --git a/src/main/java/com/miti99/storescraperbot/repository/AdminRepository.java b/src/main/java/com/miti99/storescraperbot/repository/AdminRepository.java index 5911acf..a24f5a8 100644 --- a/src/main/java/com/miti99/storescraperbot/repository/AdminRepository.java +++ b/src/main/java/com/miti99/storescraperbot/repository/AdminRepository.java @@ -1,20 +1,57 @@ package com.miti99.storescraperbot.repository; +import com.miti99.storescraperbot.constant.Constant; import com.miti99.storescraperbot.model.Admin; -/** Đây là repository chỉ chứa 1 key duy nhất, key là "admin" */ +/** + * Đây là repository chỉ chứa 1 key duy nhất. Nên lưu trong default collection của Couchbase + * ("_default") + * + *

TODO: Refactor các logic của một single key repository sang abstract class để dễ hiểu hơn. + * Code hiện tại này chỉ là trick tạm thời nên chưa tốt lắm. Cần thiết kế lại tốt hơn + */ public class AdminRepository extends AbstractRepository { public static final AdminRepository INSTANCE = new AdminRepository(); + public static final String KEY = "admin"; + + public AdminRepository() { + super(Constant.COMMON_COLLECTION_NAME); + } + + @Override + public void init(String key) { + super.init(KEY); + } + + @Override + public void save(String key, Admin data) { + super.save(KEY, data); + } + + @Override + public boolean exist(String key) { + return super.exist(KEY); + } + + @Override + public Admin load(String key) { + return super.load(KEY); + } + + @Override + public void delete(String key) { + super.delete(KEY); + } public void init() { - init("admin"); + init(KEY); } public Admin load() { - return load("admin"); + return load(KEY); } public void save(Admin data) { - save("admin", data); + save(KEY, data); } }