Clear and rewrite

Retire the Java implementation; history continues from the Go port (store-scraper-bot-go).
This commit is contained in:
tiennm99 committed 2026-08-04 22:59:44 +07:00
1 parent e794000df3
commit c5f3628f01
66 files changed
-2894

No files matched your search

@@ -1,58 +0,0 @@
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;
import com.miti99.storescraperbot.bot.StoreScrapeBot;
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;
@Log4j2
public class Main {
public static void main(String[] args) {
AdminRepository.INSTANCE.init();
try (var botsApplication = new TelegramBotsLongPollingApplication()) {
botsApplication.registerBot(Environment.TELEGRAM_BOT_TOKEN, StoreScrapeBot.INSTANCE);
log.info("Bot started! Version {}", SOURCE_COMMIT);
if (Environment.ENV == Env.PRODUCTION) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
CREATOR_ID, "Bot started! Version <code>%s</code>".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);
}
}
@@ -1,111 +0,0 @@
package com.miti99.storescraperbot.api.apple;
import com.miti99.storescraperbot.api.apple.request.AppleAppRequest;
import com.miti99.storescraperbot.api.apple.response.AppleAppResponse;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.model.AppleApp;
import com.miti99.storescraperbot.repository.AppleAppRepository;
import com.miti99.storescraperbot.util.GsonUtil;
import com.miti99.storescraperbot.util.Time;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AppStoreScraper {
public static final String BASE_URL = "https://store-scraper.vercel.app/apple/";
@SneakyThrows
public static String rawApp(AppleAppRequest request) {
var httpRequest =
HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/app"))
// .timeout(Duration.ofMillis(TIMEOUT))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(GsonUtil.toJson(request)))
.build();
try (var httpClient =
HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
// .connectTimeout(Duration.ofMillis(TIMEOUT))
.build()) {
return httpClient.send(httpRequest, BodyHandlers.ofString()).body();
} catch (Exception e) {
log.error("rawAppResponse error - request: '{}'", GsonUtil.toJson(request), e);
return null;
}
}
@SneakyThrows
public static AppleAppResponse app(AppleAppRequest request) {
return GsonUtil.fromJson(rawApp(request), AppleAppResponse.class);
}
public static AppleAppResponse getAppResponse(String appId, String country) {
long now = Time.currentTimeMillis();
AppleApp app = null;
if (AppleAppRepository.INSTANCE.exist(appId)) {
app = AppleAppRepository.INSTANCE.load(appId);
if (now - app.getMillis() > Constant.APP_CACHE_MILLIS) {
app = null;
}
}
if (app == null) {
var response = app(new AppleAppRequest(appId, country));
AppleAppRepository.INSTANCE.init(appId);
app = AppleAppRepository.INSTANCE.load(appId);
app.setApp(response);
app.setMillis(now);
AppleAppRepository.INSTANCE.save(appId, app);
}
return app.getApp();
}
public static LocalDate getAppUpdated(String appId, String country) {
var response = getAppResponse(appId, country);
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, String country) {
var response = getAppResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0.0;
}
return response.score();
}
public static long getAppReviews(String appId, String country) {
var response = getAppResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0L;
}
return response.reviews();
}
public static long getAppRatings(String appId, String country) {
var response = getAppResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0L;
}
return response.ratings();
}
}
@@ -1,12 +0,0 @@
package com.miti99.storescraperbot.api.apple.request;
public record AppleAppRequest(Long id, String appId, String country, Boolean ratings) {
public AppleAppRequest(Long id, String country) {
this(id, null, country, true);
}
public AppleAppRequest(String appId, String country) {
this(null, appId, country, true);
}
}
@@ -1,41 +0,0 @@
package com.miti99.storescraperbot.api.apple.response;
import java.util.List;
import java.util.Map;
public record AppleAppResponse(
long id,
String appId,
String title,
String url,
String description,
String icon,
List<String> genres,
List<String> genreIds,
String primaryGenre,
int primaryGenreId,
String contentRating,
List<String> languages,
String size,
String requiredOsVersion,
String released,
String updated,
String releaseNotes,
String version,
double price,
String currency,
boolean free,
long developerId,
String developer,
String developerUrl,
String developerWebsite,
double score,
int reviews,
double currentVersionScore,
int currentVersionReviews,
List<String> screenshots,
List<String> ipadScreenshots,
List<String> appletvScreenshots,
List<String> supportedDevices,
long ratings,
Map<String, Long> histogram) {}
@@ -1,112 +0,0 @@
package com.miti99.storescraperbot.api.google;
import com.miti99.storescraperbot.api.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.api.google.response.GoogleAppResponse;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.model.GoogleApp;
import com.miti99.storescraperbot.repository.GoogleAppRepository;
import com.miti99.storescraperbot.util.GsonUtil;
import com.miti99.storescraperbot.util.Time;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GooglePlayScraper {
public static final String BASE_URL = "https://store-scraper.vercel.app/google/";
@SneakyThrows
public static String rawApp(GoogleAppRequest request) {
var httpRequest =
HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/app"))
// .timeout(Duration.ofMillis(TIMEOUT))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(GsonUtil.toJson(request)))
.build();
try (var httpClient =
HttpClient.newBuilder()
// .connectTimeout(Duration.ofMillis(TIMEOUT))
.followRedirects(Redirect.NORMAL)
.build()) {
return httpClient.send(httpRequest, BodyHandlers.ofString()).body();
} catch (Exception e) {
log.error("rawAppResponse error - request: '{}'", GsonUtil.toJson(request), e);
return null;
}
}
public static GoogleAppResponse app(GoogleAppRequest request) {
return GsonUtil.fromJson(rawApp(request), GoogleAppResponse.class);
}
private static GoogleAppResponse getAppResponse(String appId, String country) {
long now = Time.currentTimeMillis();
GoogleApp app = null;
if (GoogleAppRepository.INSTANCE.exist(appId)) {
app = GoogleAppRepository.INSTANCE.load(appId);
if (now - app.getMillis() > Constant.APP_CACHE_MILLIS) {
app = null;
}
}
if (app == null) {
var response = app(new GoogleAppRequest(appId, country));
GoogleAppRepository.INSTANCE.init(appId);
app = GoogleAppRepository.INSTANCE.load(appId);
app.setApp(response);
app.setMillis(now);
GoogleAppRepository.INSTANCE.save(appId, app);
}
return app.getApp();
}
public static LocalDate getLastUpdateOfApp(String appId, String country) {
var response = getAppResponse(appId, country);
long lastUpdateMillis = 0;
if (response != null) {
lastUpdateMillis = response.updated();
} else {
log.error("response is null");
}
return LocalDate.ofInstant(Instant.ofEpochMilli(lastUpdateMillis), ZoneId.systemDefault());
}
public static double getAppScore(String appId, String country) {
var response = getAppResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0.0;
}
return response.score();
}
public static String getAppScoreText(String appId, String country) {
var response = getAppResponse(appId, country);
if (response == null) {
log.error("response is null");
return "";
}
return response.scoreText();
}
public static long getAppRatings(String appId, String country) {
var response = getAppResponse(appId, country);
if (response == null) {
log.error("response is null");
return 0L;
}
return response.ratings();
}
}
@@ -1,7 +0,0 @@
package com.miti99.storescraperbot.api.google.request;
public record GoogleAppRequest(String appId, String country) {
public GoogleAppRequest(String appId) {
this(appId, "vn");
}
}
@@ -1,67 +0,0 @@
package com.miti99.storescraperbot.api.google.response;
import java.util.List;
import java.util.Map;
public record GoogleAppResponse(
String title,
String description,
String descriptionHTML,
String summary,
String installs,
long minInstalls,
long maxInstalls,
double score,
String scoreText,
long ratings,
long reviews,
Map<String, Long> histogram,
double price,
boolean free,
String currency,
String priceText,
boolean offersIAP,
String IAPRange,
String androidVersion,
String androidVersionText,
String androidMaxVersion,
String developer,
String developerId,
String developerEmail,
String developerWebsite,
String developerAddress,
String developerLegalName,
String developerLegalEmail,
String developerLegalAddress,
String developerLegalPhoneNumber,
String privacyPolicy,
String developerInternalID,
String genre,
String genreId,
List<Category> categories,
String icon,
String headerImage,
List<String> screenshots,
String video,
String videoImage,
String previewVideo,
String contentRating,
String contentRatingDescription,
boolean adSupported,
String released,
long updated,
String version,
String recentChanges,
List<String> comments,
boolean preregister,
boolean earlyAccessEnabled,
boolean isAvailableInPlayPass,
boolean editorsChoice,
List<Feature> features,
String appId,
String url) {
public record Category(String name, String id) {}
public record Feature(String title, String description) {}
}
@@ -1,176 +0,0 @@
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;
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.DeleteGoogleAppCommand;
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.command.RawAppleAppCommand;
import com.miti99.storescraperbot.bot.command.RawGoogleAppCommand;
import com.miti99.storescraperbot.bot.entity.NonUpdatedApp;
import com.miti99.storescraperbot.bot.table.Table;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.env.Environment;
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;
@Log4j2
public class StoreScrapeBot extends CommandLongPollingTelegramBot {
public static final StoreScrapeBot INSTANCE = new StoreScrapeBot();
StoreScrapeBot() {
super(StoreScrapeBotTelegramClient.INSTANCE, true, StoreScrapeBotUsernameSupplier.INSTANCE);
register(InfoCommand.INSTANCE);
register(RawAppleAppCommand.INSTANCE);
register(RawGoogleAppCommand.INSTANCE);
register(AddGroupCommand.INSTANCE);
register(DeleteGroupCommand.INSTANCE);
register(ListGroupCommand.INSTANCE);
register(AddAppleAppCommand.INSTANCE);
register(DeleteAppleAppCommand.INSTANCE);
register(AddGoogleAppCommand.INSTANCE);
register(DeleteAppleAppCommand.INSTANCE);
register(DeleteGoogleAppCommand.INSTANCE);
register(ListAppCommand.INSTANCE);
register(CheckAppCommand.INSTANCE);
register(CheckAppScoreCommand.INSTANCE);
setMyCommands();
}
private void setMyCommands() {
try {
var commands =
getRegisteredCommands().stream()
.map(cmd -> new BotCommand(cmd.getCommandIdentifier(), cmd.getDescription()))
.toList();
var setMyCommands = SetMyCommands.builder().commands(commands).build();
StoreScrapeBotTelegramClient.INSTANCE.execute(setMyCommands);
} catch (TelegramApiException e) {
log.error("register error", e);
}
}
@Override
public void processNonCommandUpdate(Update update) {
// Ignore
}
public void runCheckApp() {
try {
var admin = AdminRepository.INSTANCE.load();
for (var groupId : admin.getGroups()) {
try {
checkAppForGroup(groupId);
} catch (Exception e) {
log.error("runCheckApp error - groupId: {}", groupId, e);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
Environment.CREATOR_ID, "runCheckApp error - groupId: %d".formatted(groupId));
}
}
} catch (Exception e) {
log.error("runCheckApp error", e);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
Environment.CREATOR_ID, "runCheckApp error");
}
}
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);
}
}
}
@@ -1,41 +0,0 @@
package com.miti99.storescraperbot.bot;
import com.miti99.storescraperbot.env.Environment;
import lombok.extern.log4j.Log4j2;
import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient;
import org.telegram.telegrambots.meta.api.methods.ParseMode;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
@Log4j2
public class StoreScrapeBotTelegramClient extends OkHttpTelegramClient {
public static final StoreScrapeBotTelegramClient INSTANCE = new StoreScrapeBotTelegramClient();
public StoreScrapeBotTelegramClient() {
super(Environment.TELEGRAM_BOT_TOKEN);
}
public void sendMessage(long chatId, String text) {
try {
var sendMessage =
SendMessage.builder().parseMode(ParseMode.HTML).chatId(chatId).text(text).build();
execute(sendMessage);
} catch (Exception e) {
log.error("sendMessage error", e);
}
}
public void sendMessage(long chatId, int threadId, String text) {
try {
var sendMessage =
SendMessage.builder()
.parseMode(ParseMode.HTML)
.chatId(chatId)
.messageThreadId(threadId)
.text(text)
.build();
execute(sendMessage);
} catch (Exception e) {
log.error("sendMessage error", e);
}
}
}
@@ -1,14 +0,0 @@
package com.miti99.storescraperbot.bot;
import com.miti99.storescraperbot.env.Environment;
import java.util.function.Supplier;
public class StoreScrapeBotUsernameSupplier implements Supplier<String> {
public static final StoreScrapeBotUsernameSupplier INSTANCE =
new StoreScrapeBotUsernameSupplier();
@Override
public String get() {
return Environment.TELEGRAM_BOT_USERNAME;
}
}
@@ -1,80 +0,0 @@
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.model.entity.AppleAppInfo;
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();
AddAppleAppCommand() {
super(
"addapple",
"<id/bundleId> [country]. Thêm Apple 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
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 < 1 || arguments.length > 2) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var appId = arguments[0];
long id = -1;
var country = arguments.length == 2 ? arguments[1] : "vn";
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, country));
} else {
response = AppStoreScraper.app(new AppleAppRequest(appId, country));
}
} 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);
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(new AppleAppInfo(appId, country));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(),
"Apple app <code>%s</code>, country <b>%s</b> added successfully"
.formatted(appId, country));
}
}
@@ -1,68 +0,0 @@
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.model.entity.GoogleAppInfo;
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> [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
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 < 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, country));
} 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);
if (group.getGoogleApps().stream().anyMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Google app <code>%s</code> is already added".formatted(appId));
return;
}
group.getGoogleApps().add(new GoogleAppInfo(appId, country));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(),
"Google app <code>%s</code>, country <b>%s</b> added successfully"
.formatted(appId, country));
}
}
@@ -1,54 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.env.Environment;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
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 AddGroupCommand extends BaseStoreScraperBotCommand {
public static final AddGroupCommand INSTANCE = new AddGroupCommand();
AddGroupCommand() {
super("addgroup", "<groupId>. Thêm group vào list group cho phép sử dụng bot");
}
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (!Environment.ADMIN_IDS.contains(user.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "You are not admin");
return;
}
if (arguments.length > 1) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
long groupId;
if (arguments.length == 1) {
try {
groupId = Long.parseLong(arguments[0]);
} catch (NumberFormatException e) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid groupId");
return;
}
} else {
groupId = chat.getId();
}
var admin = AdminRepository.INSTANCE.load();
if (admin.getGroups().contains(groupId)) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group is already added");
return;
}
GroupRepository.INSTANCE.init(groupId);
admin.getGroups().add(groupId);
AdminRepository.INSTANCE.save(admin);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group added successfully");
}
}
@@ -1,29 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import lombok.extern.log4j.Log4j2;
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;
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 abstract class BaseStoreScraperBotCommand extends BotCommand {
public BaseStoreScraperBotCommand(String commandIdentifier, String description) {
super(commandIdentifier, description);
}
@Override
public void execute(TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
try {
executeCommand(telegramClient, user, chat, arguments);
} catch (Exception e) {
log.error("execute error", e);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Internal server error");
}
}
protected abstract void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments);
}
@@ -1,73 +0,0 @@
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.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 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 CheckAppCommand extends BaseStoreScraperBotCommand {
public static final CheckAppCommand INSTANCE = new CheckAppCommand();
CheckAppCommand() {
super("checkapp", "Kiểm tra cập nhật các app");
}
@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 now = LocalDate.now();
var sb = new StringBuilder();
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
var appleTable = new Table("AppId", "Updated", "Days", "OK");
for (var app : group.getAppleApps()) {
var appId = app.appId();
var updated = AppStoreScraper.getAppUpdated(appId, app.country());
long days = ChronoUnit.DAYS.between(updated, now);
boolean passed = days <= Constant.NUM_DAYS_WARNING_NOT_UPDATED;
var icon = passed ? "✅" : "❌";
appleTable.addRow(appId, updated, days, icon);
}
sb.append(appleTable);
sb.append("</code>\n");
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
var googleTable = new Table("AppId", "Updated", "Days", "OK");
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;
var icon = passed ? "✅" : "❌";
googleTable.addRow(appId, updated, days, icon);
}
sb.append(googleTable);
sb.append("</code>");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
}
@@ -1,71 +0,0 @@
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.bot.table.Table;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
import org.apache.commons.math3.util.Precision;
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();
public static final int SCORE_ROUNDING_SCALE = 1;
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");
var appleTable = new Table("AppId", "Score", "Ratings");
for (var app : group.getAppleApps()) {
var appId = app.appId();
var country = app.country();
double score =
Precision.round(AppStoreScraper.getAppScore(appId, country), SCORE_ROUNDING_SCALE);
long ratings = AppStoreScraper.getAppRatings(appId, country);
appleTable.addRow(appId, score, ratings);
}
sb.append(appleTable);
sb.append("</code>\n");
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
var googleTable = new Table("AppId", "Score", "Ratings");
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var country = app.country();
double score =
Precision.round(GooglePlayScraper.getAppScore(appId, country), SCORE_ROUNDING_SCALE);
long ratings = GooglePlayScraper.getAppRatings(appId, country);
googleTable.addRow(appId, score, ratings);
}
sb.append(googleTable);
sb.append("</code>");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
}
@@ -1,46 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
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 DeleteAppleAppCommand extends BaseStoreScraperBotCommand {
public static final DeleteAppleAppCommand INSTANCE = new DeleteAppleAppCommand();
DeleteAppleAppCommand() {
super("delapple", "<appId>. Xoá Apple app khỏi danh sách theo dõi của nhóm");
}
@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 != 1) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var appId = arguments[0];
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.getAppleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app is not added");
return;
}
group.getAppleApps().removeIf(app -> appId.equals(app.appId()));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Apple app deleted successfully");
}
}
@@ -1,46 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
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 DeleteGoogleAppCommand extends BaseStoreScraperBotCommand {
public static final DeleteGoogleAppCommand INSTANCE = new DeleteGoogleAppCommand();
DeleteGoogleAppCommand() {
super("delgoogle", "<appId>. Xoá Google app khỏi danh sách theo dõi của nhóm");
}
@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 != 1) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var appId = arguments[0];
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.getGoogleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Google app is not added");
return;
}
group.getGoogleApps().removeIf(app -> appId.equals(app.appId()));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Google app deleted successfully");
}
}
@@ -1,52 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.env.Environment;
import com.miti99.storescraperbot.repository.AdminRepository;
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 DeleteGroupCommand extends BaseStoreScraperBotCommand {
public static final DeleteGroupCommand INSTANCE = new DeleteGroupCommand();
DeleteGroupCommand() {
super("delgroup", "<groupId>. Xoá group khỏi list group cho phép sử dụng bot");
}
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (!Environment.ADMIN_IDS.contains(user.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "You are not admin");
return;
}
if (arguments.length > 1) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
long groupId;
if (arguments.length == 1) {
try {
groupId = Long.parseLong(arguments[0]);
} catch (NumberFormatException e) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid groupId");
return;
}
} else {
groupId = chat.getId();
}
var admin = AdminRepository.INSTANCE.load();
if (!admin.getGroups().contains(groupId)) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group is not added");
return;
}
admin.getGroups().remove(groupId);
AdminRepository.INSTANCE.save(admin);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group deleted successfully");
}
}
@@ -1,28 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
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 InfoCommand extends BaseStoreScraperBotCommand {
public static final InfoCommand INSTANCE = new InfoCommand();
InfoCommand() {
super("info", "Lấy thông tin của nhóm (chatId, threadId,...)");
}
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (arguments.length != 0) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
long groupId = chat.getId();
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Id của nhóm là <code>%s</code>\n".formatted(groupId));
}
}
@@ -1,61 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.bot.table.Table;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.repository.GroupRepository;
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 ListAppCommand extends BaseStoreScraperBotCommand {
public static final ListAppCommand INSTANCE = new ListAppCommand();
ListAppCommand() {
super("listapp", "<appId>. Lấy danh sách app đang theo dõi của nhóm");
}
@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");
var appleTable = new Table("#", "AppId", "Country");
int i = 0;
for (var app : group.getAppleApps()) {
i++;
appleTable.addRow(i, app.appId(), app.country());
}
sb.append(appleTable);
sb.append("</code>\n");
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
var googleTable = new Table("#", "AppId", "Country");
i = 0;
for (var app : group.getGoogleApps()) {
i++;
googleTable.addRow(i, app.appId(), app.country());
}
sb.append(googleTable);
sb.append("</code>");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
}
@@ -1,39 +0,0 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.env.Environment;
import com.miti99.storescraperbot.repository.AdminRepository;
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 ListGroupCommand extends BaseStoreScraperBotCommand {
public static final ListGroupCommand INSTANCE = new ListGroupCommand();
ListGroupCommand() {
super("listgroup", "Lấy danh sách group được phép sử dụng bot hiện tại");
}
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (!Environment.ADMIN_IDS.contains(user.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "You are not admin");
return;
}
if (arguments.length != 0) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var admin = AdminRepository.INSTANCE.load();
var groups = admin.getGroups();
var sb = new StringBuilder();
sb.append("<b>Groups:</b>\n");
for (var groupId : groups) {
sb.append("- <code>%s</code>\n".formatted(groupId));
}
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
}
@@ -1,80 +0,0 @@
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.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.repository.AdminRepository;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.telegram.telegrambots.meta.api.methods.send.SendDocument;
import org.telegram.telegrambots.meta.api.objects.InputFile;
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 RawAppleAppCommand extends BaseStoreScraperBotCommand {
public static final RawAppleAppCommand INSTANCE = new RawAppleAppCommand();
RawAppleAppCommand() {
super(
"rawappleapp",
"<id/appId> [country]. Lấy raw response khi request lên service. id: <i>iTunes 'trackId'</i>, appId: <i>iTunes 'bundleId'</i>. Một số app cần country để hoạt động đúng, country mặc định là 'vn'");
}
@Override
@SneakyThrows
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 < 1 || arguments.length > 2) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var appId = arguments[0];
long id = -1;
var country = arguments.length == 2 ? arguments[1] : "vn";
String response = "";
try {
try {
id = Long.parseLong(appId);
} catch (Exception e) {
// Input không phải id, bỏ qua
}
if (id != -1) {
response = AppStoreScraper.rawApp(new AppleAppRequest(id, country));
} else {
response = AppStoreScraper.rawApp(new AppleAppRequest(appId, country));
}
} 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;
}
if (response == null) response = "";
var inputStream = new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
var file = new InputFile(inputStream, "%s.json".formatted(appId));
var sendDocument =
SendDocument.builder()
.chatId(chat.getId())
.document(file)
// .caption("raw")
.build();
StoreScrapeBotTelegramClient.INSTANCE.execute(sendDocument);
}
}
@@ -1,69 +0,0 @@
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.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.repository.AdminRepository;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.telegram.telegrambots.meta.api.methods.send.SendDocument;
import org.telegram.telegrambots.meta.api.objects.InputFile;
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 RawGoogleAppCommand extends BaseStoreScraperBotCommand {
public static final RawGoogleAppCommand INSTANCE = new RawGoogleAppCommand();
RawGoogleAppCommand() {
super(
"rawgoogleapp",
"<appId> [country]. Lấy raw response khi request lên service. Một số app cần country để hoạt động đúng, country mặc định là 'vn'");
}
@Override
@SneakyThrows
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 < 1 || arguments.length > 2) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Invalid arguments");
return;
}
var appId = arguments[0];
var country = arguments.length == 2 ? arguments[1] : "vn";
String response = "";
try {
response = GooglePlayScraper.rawApp(new GoogleAppRequest(appId, country));
} catch (Exception e) {
log.error("request app error for appId: '{}'", appId, e);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Error when request app info");
return;
}
if (response == null) response = "";
var inputStream = new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
var file = new InputFile(inputStream, "%s.json".formatted(appId));
var sendDocument =
SendDocument.builder()
.chatId(chat.getId())
.document(file)
// .caption("raw")
.build();
StoreScrapeBotTelegramClient.INSTANCE.execute(sendDocument);
}
}
@@ -1,5 +0,0 @@
package com.miti99.storescraperbot.bot.entity;
import java.time.LocalDate;
public record NonUpdatedApp(String appId, LocalDate updated, long days) {}
@@ -1,57 +0,0 @@
package com.miti99.storescraperbot.bot.table;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import kotlin.collections.ArrayDeque;
public class Table {
private final String[] headers;
private final List<String[]> rows = new ArrayDeque<>();
public Table(String... headers) {
this.headers = headers;
}
public void addRow(Object... objs) {
if (objs.length != headers.length) {
throw new IllegalArgumentException(
"objs.length (%d) != headers.length (%d)".formatted(objs.length, headers.length));
}
var row = new String[objs.length];
for (int i = 0; i < objs.length; i++) {
row[i] = String.valueOf(objs[i]);
}
rows.add(row);
}
@Override
public String toString() {
int[] maxWidths = new int[headers.length];
for (int i = 0; i < headers.length; i++) {
maxWidths[i] = headers[i].length();
}
for (var row : rows) {
for (int i = 0; i < row.length; i++) {
maxWidths[i] = Math.max(maxWidths[i], row[i].length());
}
}
var formater =
Arrays.stream(maxWidths)
.mapToObj(width -> "%-" + width + "s")
.collect(Collectors.joining(" │ ", "", "\n"));
var sb = new StringBuilder();
sb.append(formater.formatted((Object[]) headers));
var rule =
Arrays.stream(maxWidths).mapToObj("─"::repeat).collect(Collectors.joining("─┼─", "", "\n"));
int i = 0;
for (var row : rows) {
if (i % 5 == 0) {
sb.append(rule);
}
sb.append(formater.formatted((Object[]) row));
i++;
}
return sb.toString();
}
}
@@ -1,21 +0,0 @@
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 long APP_CACHE_SECONDS = 600;
public static final long APP_CACHE_MILLIS = APP_CACHE_SECONDS * 1000;
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<DayOfWeek> WEEKENDS = Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
public static final String DEFAULT_DATABASE_NAME = "store-scraper-bot";
}
@@ -1,34 +0,0 @@
package com.miti99.storescraperbot.env;
import com.google.common.base.Strings;
import com.miti99.storescraperbot.type.Env;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Environment {
public static final String MONGODB_CONNECTION_STRING = System.getenv("MONGODB_CONNECTION_STRING");
public static final String TELEGRAM_BOT_TOKEN = System.getenv("TELEGRAM_BOT_TOKEN");
public static final String TELEGRAM_BOT_USERNAME = System.getenv("TELEGRAM_BOT_USERNAME");
public static final Env ENV = Env.valueOf(System.getenv("ENV"));
public static final List<Long> ADMIN_IDS =
Optional.ofNullable(System.getenv("ADMIN_IDS"))
.map(
v ->
Arrays.stream(v.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Long::parseLong)
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
public static final long CREATOR_ID = ADMIN_IDS.getFirst();
public static final String SOURCE_COMMIT =
Strings.isNullOrEmpty(System.getenv("SOURCE_COMMIT"))
? "unknown"
: System.getenv("SOURCE_COMMIT");
}
@@ -1,21 +0,0 @@
package com.miti99.storescraperbot.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@NoArgsConstructor
@Setter
public abstract class AbstractModel {
@SerializedName("_id")
protected String id;
@SerializedName("class")
protected String clazz = getClass().getSimpleName();
public void setId(Object id) {
this.id = String.valueOf(id);
}
}
@@ -1,10 +0,0 @@
package com.miti99.storescraperbot.model;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
@Getter
public class Admin extends AbstractModel {
final Set<Long> groups = new HashSet<>();
}
@@ -1,12 +0,0 @@
package com.miti99.storescraperbot.model;
import com.miti99.storescraperbot.api.apple.response.AppleAppResponse;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AppleApp extends AbstractModel {
AppleAppResponse app;
long millis;
}
@@ -1,12 +0,0 @@
package com.miti99.storescraperbot.model;
import com.miti99.storescraperbot.api.google.response.GoogleAppResponse;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GoogleApp extends AbstractModel {
GoogleAppResponse app;
long millis;
}
@@ -1,15 +0,0 @@
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;
import lombok.Setter;
@Getter
@Setter
public class Group extends AbstractModel {
List<AppleAppInfo> appleApps = new ArrayList<>();
List<GoogleAppInfo> googleApps = new ArrayList<>();
}
@@ -1,3 +0,0 @@
package com.miti99.storescraperbot.model.entity;
public record AppleAppInfo(String appId, String country) {}
@@ -1,3 +0,0 @@
package com.miti99.storescraperbot.model.entity;
public record GoogleAppInfo(String appId, String country) {}
@@ -1,38 +0,0 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.AbstractModel;
import lombok.extern.log4j.Log4j2;
/**
* Các repository tương ứng với 1 MongoDB collection, public các method protected ở class
* AbstractRepository để các nơi khác gọi
*/
@Log4j2
public abstract class AbstractCollectionRepository<K, V extends AbstractModel>
extends AbstractRepository<K, V> {
@Override
public void init(K key) {
super.init(key);
}
@Override
public void save(K key, V data) {
super.save(key, data);
}
@Override
public boolean exist(K key) {
return super.exist(key);
}
@Override
public V load(K key) {
return super.load(key);
}
@Override
public void delete(K key) {
super.delete(key);
}
}
@@ -1,119 +0,0 @@
package com.miti99.storescraperbot.repository;
import static com.miti99.storescraperbot.repository.AbstractSingletonRepository.COMMON_COLLECTION_NAME;
import com.google.common.base.CaseFormat;
import com.miti99.storescraperbot.model.AbstractModel;
import com.miti99.storescraperbot.util.GsonUtil;
import com.miti99.storescraperbot.util.MongoDBUtil;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.ReplaceOptions;
import java.lang.reflect.ParameterizedType;
import lombok.extern.log4j.Log4j2;
import org.bson.Document;
/**
* @param <K> class Key
* @param <V> class Value
*/
@Log4j2
public abstract class AbstractRepository<K, V extends AbstractModel> {
protected final Class<V> classV = getDataClass();
protected final String collectionName;
protected final MongoCollection<Document> collection;
protected AbstractRepository(String collectionName) {
this.collectionName = collectionName;
MongoDBUtil.createCollectionIfNotExists(collectionName);
collection = MongoDBUtil.DATABASE.getCollection(collectionName, Document.class);
}
protected AbstractRepository() {
collectionName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, classV.getSimpleName());
if (COMMON_COLLECTION_NAME.equals(collectionName)) {
throw new RuntimeException(
"Collection named '%s' is reserved".formatted(COMMON_COLLECTION_NAME));
}
MongoDBUtil.createCollectionIfNotExists(collectionName);
collection = MongoDBUtil.DATABASE.getCollection(collectionName, Document.class);
}
/**
* Lấy ra class của V. Khi tạo 1 abstract class extends AbstractRepository mà không phải final thì
* sẽ cần override hàm này phù hợp
*
* @return Class&lt;V&gt;
*/
protected Class<V> getDataClass() {
return (Class<V>)
((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
protected String encode(V data) {
return GsonUtil.toJson(data);
}
protected V decode(String json) {
return GsonUtil.fromJson(json, classV);
}
protected void init(K key) {
try {
if (exist(key)) {
return;
}
V data = classV.getDeclaredConstructor().newInstance();
data.setId(key);
save(key, data);
} catch (Exception e) {
log.error("Error while initializing data", e);
}
}
protected String getDatabaseKey(K key) {
return String.valueOf(key);
}
protected void save(K key, V data) {
var databaseKey = getDatabaseKey(key);
try {
var json = encode(data);
var doc = Document.parse(json);
collection.replaceOne(Filters.eq("_id", databaseKey), doc, new ReplaceOptions().upsert(true));
} catch (Exception e) {
log.error("save error - key {}, databaseKey {}", key, databaseKey, e);
}
}
protected boolean exist(K key) {
var databaseKey = getDatabaseKey(key);
try {
return collection.countDocuments(Filters.eq("_id", databaseKey)) > 0;
} catch (Exception e) {
log.error("exist error - key {}, databaseKey {}", key, databaseKey, e);
return false;
}
}
protected V load(K key) {
var databaseKey = getDatabaseKey(key);
try {
var doc = collection.find(Filters.eq("_id", databaseKey)).first();
if (doc == null) return null;
return decode(doc.toJson());
} catch (Exception e) {
log.error("load error - key {}, databaseKey {}", key, databaseKey, e);
return null;
}
}
protected void delete(K key) {
var databaseKey = getDatabaseKey(key);
try {
collection.deleteOne(Filters.eq("_id", databaseKey));
} catch (Exception e) {
log.error("delete error", e);
}
}
}
@@ -1,42 +0,0 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.AbstractModel;
import lombok.extern.log4j.Log4j2;
/**
* Repository chỉ chứa 1 key duy nhất, public các method liên quan nhưng không cho truyền params
* vào. Các repository loại này được lưu trong 1 collection duy nhất là "common"
*/
@Log4j2
public abstract class AbstractSingletonRepository<K, V extends AbstractModel>
extends AbstractRepository<K, V> {
public static final String COMMON_COLLECTION_NAME = "common";
protected final K key = getKey();
protected AbstractSingletonRepository() {
super(COMMON_COLLECTION_NAME);
}
protected abstract K getKey();
public void init() {
init(key);
}
public void save(V data) {
save(key, data);
}
public boolean exist() {
return exist(key);
}
public V load() {
return load(key);
}
public void delete() {
delete(key);
}
}
@@ -1,13 +0,0 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.Admin;
public class AdminRepository extends AbstractSingletonRepository<String, Admin> {
public static final AdminRepository INSTANCE = new AdminRepository();
public static final String KEY = "admin";
@Override
protected String getKey() {
return KEY;
}
}
@@ -1,7 +0,0 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.AppleApp;
public class AppleAppRepository extends AbstractCollectionRepository<String, AppleApp> {
public static final AppleAppRepository INSTANCE = new AppleAppRepository();
}
@@ -1,7 +0,0 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.GoogleApp;
public class GoogleAppRepository extends AbstractCollectionRepository<String, GoogleApp> {
public static final GoogleAppRepository INSTANCE = new GoogleAppRepository();
}
@@ -1,7 +0,0 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.Group;
public class GroupRepository extends AbstractCollectionRepository<Long, Group> {
public static final GroupRepository INSTANCE = new GroupRepository();
}
@@ -1,6 +0,0 @@
package com.miti99.storescraperbot.type;
public enum Env {
DEVELOPMENT,
PRODUCTION
}
@@ -1,18 +0,0 @@
package com.miti99.storescraperbot.util;
import com.google.gson.Gson;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GsonUtil {
public static final Gson GSON = new Gson();
public static <T> T fromJson(String input, Class<T> valueType) {
return GSON.fromJson(input, valueType);
}
public static String toJson(Object input) {
return GSON.toJson(input);
}
}
@@ -1,67 +0,0 @@
package com.miti99.storescraperbot.util;
import static com.miti99.storescraperbot.env.Environment.MONGODB_CONNECTION_STRING;
import com.miti99.storescraperbot.constant.Constant;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoException;
import com.mongodb.ServerApi;
import com.mongodb.ServerApiVersion;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.bson.Document;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MongoDBUtil {
public static final MongoClient MONGO_CLIENT;
public static final MongoDatabase DATABASE;
static {
var serverApi = ServerApi.builder().version(ServerApiVersion.V1).build();
var connectionString = new ConnectionString(MONGODB_CONNECTION_STRING);
var settings =
MongoClientSettings.builder()
.applyConnectionString(connectionString)
.serverApi(serverApi)
.build();
MONGO_CLIENT = MongoClients.create(settings);
var databaseName =
connectionString.getDatabase() != null
? connectionString.getDatabase()
: Constant.DEFAULT_DATABASE_NAME;
DATABASE = MONGO_CLIENT.getDatabase(databaseName);
try {
DATABASE.runCommand(new Document("ping", 1));
log.info("Pinged your deployment. You successfully connected to MongoDB!");
} catch (MongoException e) {
log.error(e);
}
}
public static void createCollectionIfNotExists(String collectionName) {
try {
boolean collectionExists = false;
for (var name : DATABASE.listCollectionNames()) {
if (name.equals(collectionName)) {
collectionExists = true;
break;
}
}
if (!collectionExists) {
DATABASE.createCollection(collectionName);
log.info("Collection created: {}", collectionName);
} else {
log.info("Collection existed: {}", collectionName);
}
} catch (Exception e) {
log.error("createCollectionIfNotExists error - collectionName: '{}'", collectionName, e);
}
}
}
@@ -1,12 +0,0 @@
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();
}
@@ -1,57 +0,0 @@
package com.miti99.storescraperbot.util;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.TimeZone;
public class Time {
private static final TimeZone TIME_ZONE = TimeZone.getDefault();
private static final ZoneId ZONE_ID = TIME_ZONE.toZoneId();
private static long deltaMillis = 0L;
public static LocalDate currentDate() {
return LocalDate.ofInstant(currentInstant(), ZONE_ID);
}
public static LocalTime currentTime() {
return LocalTime.ofInstant(currentInstant(), ZONE_ID);
}
public static LocalDateTime currentDateTime() {
return LocalDateTime.ofInstant(currentInstant(), ZONE_ID);
}
public static OffsetDateTime currentOffsetDateTime() {
return OffsetDateTime.ofInstant(currentInstant(), ZONE_ID);
}
public static ZonedDateTime currentZonedDateTime() {
return ZonedDateTime.ofInstant(currentInstant(), ZONE_ID);
}
public static Instant currentInstant() {
return Instant.ofEpochMilli(currentTimeMillis());
}
public static long currentTimeMillis() {
return System.currentTimeMillis() + deltaMillis;
}
public static void useMockTime(LocalDateTime dateTime, ZoneId zoneId) {
deltaMillis = dateTime.atZone(zoneId).toInstant().toEpochMilli() - System.currentTimeMillis();
}
public static void useSystemDefaultZoneClock() {
deltaMillis = 0L;
}
private static Clock getClock() {
return Clock.fixed(currentInstant(), ZONE_ID);
}
}
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="CONSOLE_LOG_PATTERN">%d{HH:mm:ss.SSS} %5p --- [%t] %c : %m%n</Property>
<Property name="CONSOLE_LOG_CHARSET">UTF-8</Property>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${CONSOLE_LOG_PATTERN}" charset="${CONSOLE_LOG_CHARSET}"/>
<Filters>
<ThresholdFilter level="TRACE"/>
</Filters>
</Console>
<Loki name="Loki">
<host>${env:LOKI_HOST}</host>
<port>${env:LOKI_PORT}</port>
<username>${env:LOKI_USERNAME}</username>
<password>${env:LOKI_PASSWORD}</password>
<JsonLayout/>
<Label name="service_name" value="StoreScraperBot"/>
<Label name="environment" value="${env:ENV}"/>
</Loki>
</Appenders>
<Loggers>
<Root level="${env:LOG_LEVEL:-debug}">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="Loki"/>
</Root>
</Loggers>
</Configuration>
@@ -1,28 +0,0 @@
package com.miti99.storescraperbot.api.apple;
import com.miti99.storescraperbot.api.apple.request.AppleAppRequest;
import com.miti99.storescraperbot.util.GsonUtil;
import org.junit.jupiter.api.Test;
class AppStoreScraperTest {
@Test
void testComMptKvtm() {
var request = new AppleAppRequest("com.mpt.kvtm", "vn");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComMptBuraco() {
var request = new AppleAppRequest("com.mpt.buraco", "mx");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComMptDoudizhu() {
var request = new AppleAppRequest("com.mpt.doudizhu", "hk");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
}
@@ -1,28 +0,0 @@
package com.miti99.storescraperbot.api.google;
import com.miti99.storescraperbot.api.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.util.GsonUtil;
import org.junit.jupiter.api.Test;
class GooglePlayScraperTest {
@Test
void testPoolUs() {
var request = new GoogleAppRequest("pool.us");
var response = GooglePlayScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComZingplayBuracoMx() {
var request = new GoogleAppRequest("com.zingplay.buraco.mx", "mx");
var response = GooglePlayScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComZingplayDoudizhu() {
var request = new GoogleAppRequest("com.zingplay.doudizhu", "hk");
var response = GooglePlayScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
}