mirror of
https://github.com/tiennm99/store-scraper-bot-java.git
synced 2026-05-15 07:53:01 +00:00
Add app score checking and refactor app info retrieval
Introduces CheckAppScoreCommand to display app scores and reviews/ratings for Apple and Google apps. Refactors AppStoreScraper and GooglePlayScraper to provide methods for retrieving app scores, reviews, and updated dates, and improves AddAppleAppCommand and AddGoogleAppCommand to validate and fetch app info before adding. Updates AdminRepository to use a common collection and single-key logic, and modifies Constant to use a common collection name.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <code>%s</code> added successfully".formatted(appId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", "<appId>. Thêm Google app vào danh sách theo dõi của nhóm");
|
||||
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");
|
||||
}
|
||||
|
||||
@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 <code>%s</code> added successfully".formatted(appId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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("<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("\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("</code>\n");
|
||||
sb.append("\n");
|
||||
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("\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("</code>");
|
||||
sb.append("\n");
|
||||
|
||||
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.miti99.storescraperbot.model.entity;
|
||||
|
||||
public class AppleAppInfo {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.miti99.storescraperbot.model.entity;
|
||||
|
||||
public class GoogleAppInfo {}
|
||||
@@ -16,9 +16,15 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
|
||||
// protected final Class<K> classK = getKeyClass();
|
||||
protected final Class<V> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
*
|
||||
* <p>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<String, Admin> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user