feat(api): update base url

This commit is contained in:
tiennm99 committed 2025-11-11 20:59:56 +07:00
1 parent de1f84d278
commit 22a3f6bc5c
34 files changed
+111 -985

No files matched your search

-8
View File
@@ -1,11 +1,3 @@
# store-scraper-bot
Telegram bot that support scrape infos of an app on stores
## Credits
Many thanks to [facundoolano](https://github.com/facundoolano)'
s [google-play-scraper](https://github.com/facundoolano/google-play-scraper)
and [app-store-scraper](https://github.com/facundoolano/app-store-scraper), myy api code are based
on
their logics.
-1
View File
@@ -1,4 +1,3 @@
lombok.accessors.fluent = true
lombok.addLombokGeneratedAnnotation = true
lombok.experimental.flagUsage = error
lombok.log.apacheCommons.flagUsage = error
@@ -1,15 +1,14 @@
package com.miti99.storescraperbot.api.apple;
import com.miti99.storescraperbot.api.apple.entity.AppleAppDetail;
import com.miti99.storescraperbot.api.apple.request.ITunesLookupRequest;
import com.miti99.storescraperbot.api.apple.response.ITunesLookupResponse;
import com.miti99.storescraperbot.api.apple.request.AppleAppRequest;
import com.miti99.storescraperbot.api.apple.response.AppleAppResponse;
import com.miti99.storescraperbot.repository.AppleAppRepository;
import com.miti99.storescraperbot.util.GsonUtil;
import com.miti99.storescraperbot.util.RequestUtil;
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;
@@ -22,16 +21,16 @@ import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AppStoreScraper {
private static final String LOOKUP_URL = "https://itunes.apple.com/lookup?";
public static final String BASE_URL = "https://store-scraper.vercel.app/apple/";
@SneakyThrows
public static String rawLookup(ITunesLookupRequest request) {
public static String rawApp(AppleAppRequest request) {
var httpRequest =
HttpRequest.newBuilder()
.uri(URI.create(LOOKUP_URL + RequestUtil.makeGetParams(request)))
.uri(URI.create(BASE_URL + "/app"))
// .timeout(Duration.ofMillis(TIMEOUT))
.header("Content-Type", "application/json")
.GET()
.POST(BodyPublishers.ofString(GsonUtil.toJson(request)))
.build();
try (var httpClient =
@@ -41,55 +40,64 @@ public class AppStoreScraper {
.build()) {
return httpClient.send(httpRequest, BodyHandlers.ofString()).body();
} catch (Exception e) {
log.error("rawLookup error - request: '{}'", GsonUtil.toJson(request), e);
log.error("rawAppResponse error - request: '{}'", GsonUtil.toJson(request), e);
return null;
}
}
@SneakyThrows
public static AppleAppDetail app(ITunesLookupRequest request) {
return GsonUtil.fromJson(rawLookup(request), ITunesLookupResponse.class).getAppDetail();
public static AppleAppResponse app(AppleAppRequest request) {
return GsonUtil.fromJson(rawApp(request), AppleAppResponse.class);
}
public static AppleAppDetail app(String appId) {
return app(new ITunesLookupRequest(appId));
}
public static AppleAppDetail app(long id) {
return app(new ITunesLookupRequest(id));
}
public static AppleAppDetail getAppResponse(String appId) {
public static AppleAppResponse getAppResponse(String appId, String country) {
boolean isInCache = AppleAppRepository.INSTANCE.exist(appId);
if (isInCache) {
var app = AppleAppRepository.INSTANCE.load(appId);
return app.detail();
return app.getApp();
} else {
var response = app(appId);
var response = app(new AppleAppRequest(appId, country));
AppleAppRepository.INSTANCE.init(appId);
var app = AppleAppRepository.INSTANCE.load(appId);
app.detail(response);
app.setApp(response);
AppleAppRepository.INSTANCE.save(appId, app);
return response;
}
}
public static LocalDate getAppUpdated(String appId) {
var detail = getAppResponse(appId);
if (detail == null) {
log.error("detail is null");
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(detail.currentVersionReleaseDate()), ZoneId.systemDefault());
return LocalDate.ofInstant(Instant.parse(response.updated()), ZoneId.systemDefault());
}
public static double getAppScore(String appId) {
var response = getAppResponse(appId);
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.averageUserRating();
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,13 +0,0 @@
package com.miti99.storescraperbot.api.apple.entity;
import lombok.Value;
/**
* Chỉ define một số field cần thiết nên không đầy đủ. Khi cần thì check raw response để define thêm
*/
@Value
public class AppleAppDetail {
String bundleId;
String currentVersionReleaseDate;
double averageUserRating;
}
@@ -1,4 +1,4 @@
package com.miti99.storescraperbot.api.old.apple.request;
package com.miti99.storescraperbot.api.apple.request;
public record AppleAppRequest(Long id, String appId, String country, Boolean ratings) {
@@ -1,20 +0,0 @@
package com.miti99.storescraperbot.api.apple.request;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@RequiredArgsConstructor
@Value
public class ITunesLookupRequest {
Long id;
String bundleId;
String entity = "software";
public ITunesLookupRequest(Long id) {
this(id, null);
}
public ITunesLookupRequest(String bundleId) {
this(null, bundleId);
}
}
@@ -1,4 +1,4 @@
package com.miti99.storescraperbot.api.old.apple.response;
package com.miti99.storescraperbot.api.apple.response;
import java.util.List;
import java.util.Map;
@@ -1,23 +0,0 @@
package com.miti99.storescraperbot.api.apple.response;
import com.miti99.storescraperbot.api.apple.entity.AppleAppDetail;
import com.miti99.storescraperbot.util.GsonUtil;
import java.util.List;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
@Log4j2
@Value
public class ITunesLookupResponse {
int resultCount;
List<AppleAppDetail> results;
public AppleAppDetail getAppDetail() {
if (resultCount != 1) {
log.warn("resultCount('{}') != 1", resultCount);
log.warn("results: {}", GsonUtil.toJson(results));
}
return results.getFirst();
}
}
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.api.old.google;
package com.miti99.storescraperbot.api.google;
import com.miti99.storescraperbot.api.old.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.api.old.google.response.GoogleAppResponse;
import com.miti99.storescraperbot.api.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.api.google.response.GoogleAppResponse;
import com.miti99.storescraperbot.repository.GoogleAppRepository;
import com.miti99.storescraperbot.util.GsonUtil;
import java.net.URI;
@@ -21,7 +21,7 @@ import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GooglePlayScraper {
public static final String BASE_URL = "https://miti-google-play-scraper.vercel.app/";
public static final String BASE_URL = "https://store-scraper.vercel.app/google/";
@SneakyThrows
public static String rawApp(GoogleAppRequest request) {
@@ -53,12 +53,12 @@ public class GooglePlayScraper {
boolean isInCache = GoogleAppRepository.INSTANCE.exist(appId);
if (isInCache) {
var app = GoogleAppRepository.INSTANCE.load(appId);
return app.app();
return app.getApp();
} else {
var response = app(new GoogleAppRequest(appId, country));
GoogleAppRepository.INSTANCE.init(appId);
var app = GoogleAppRepository.INSTANCE.load(appId);
app.app(response);
app.setApp(response);
GoogleAppRepository.INSTANCE.save(appId, app);
return response;
}
@@ -1,4 +1,4 @@
package com.miti99.storescraperbot.api.old.google.request;
package com.miti99.storescraperbot.api.google.request;
public record GoogleAppRequest(String appId, String country) {
public GoogleAppRequest(String appId) {
@@ -1,4 +1,4 @@
package com.miti99.storescraperbot.api.old.google.response;
package com.miti99.storescraperbot.api.google.response;
import java.util.List;
import java.util.Map;
@@ -1,103 +0,0 @@
package com.miti99.storescraperbot.api.old.apple;
import com.miti99.storescraperbot.api.old.apple.request.AppleAppRequest;
import com.miti99.storescraperbot.api.old.apple.response.AppleAppResponse;
import com.miti99.storescraperbot.repository.AppleAppRepository;
import com.miti99.storescraperbot.util.GsonUtil;
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://miti-app-store-scraper.vercel.app/";
@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) {
boolean isInCache = AppleAppRepository.INSTANCE.exist(appId);
if (isInCache) {
var app = AppleAppRepository.INSTANCE.load(appId);
return app.app();
} else {
var response = app(new AppleAppRequest(appId, country));
AppleAppRepository.INSTANCE.init(appId);
var app = AppleAppRepository.INSTANCE.load(appId);
app.app(response);
AppleAppRepository.INSTANCE.save(appId, app);
return response;
}
}
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,2 +0,0 @@
@Deprecated
package com.miti99.storescraperbot.api.old;
@@ -3,8 +3,8 @@ 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.old.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.old.google.GooglePlayScraper;
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;
@@ -79,7 +79,7 @@ public class StoreScrapeBot extends CommandLongPollingTelegramBot {
public void runCheckApp() {
var admin = AdminRepository.INSTANCE.load();
for (var groupId : admin.groups()) {
for (var groupId : admin.getGroups()) {
checkAppForGroup(groupId);
}
}
@@ -89,7 +89,7 @@ public class StoreScrapeBot extends CommandLongPollingTelegramBot {
var now = LocalDate.now();
var nonUpdatedAppleApps = new ArrayList<NonUpdatedApp>();
for (var app : group.appleApps()) {
for (var app : group.getAppleApps()) {
var appId = app.appId();
var updated = AppStoreScraper.getAppUpdated(appId, app.country());
long days = ChronoUnit.DAYS.between(updated, now);
@@ -98,7 +98,7 @@ public class StoreScrapeBot extends CommandLongPollingTelegramBot {
}
}
var nonUpdatedGoogleApps = new ArrayList<NonUpdatedApp>();
for (var app : group.googleApps()) {
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var updated = GooglePlayScraper.getLastUpdateOfApp(appId, app.country());
long days = ChronoUnit.DAYS.between(updated, now);
@@ -1,8 +1,8 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.old.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.old.apple.request.AppleAppRequest;
import com.miti99.storescraperbot.api.old.apple.response.AppleAppResponse;
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;
@@ -26,7 +26,7 @@ public class AddAppleAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -64,13 +64,13 @@ public class AddAppleAppCommand extends BaseStoreScraperBotCommand {
var group = GroupRepository.INSTANCE.load(groupId);
var finalAppId = appId;
if (group.appleApps().stream().anyMatch(app -> finalAppId.equals(app.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.appleApps().add(new AppleAppInfo(appId, country));
group.getAppleApps().add(new AppleAppInfo(appId, country));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(),
@@ -1,8 +1,8 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.old.google.GooglePlayScraper;
import com.miti99.storescraperbot.api.old.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.api.old.google.response.GoogleAppResponse;
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;
@@ -26,7 +26,7 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -52,13 +52,13 @@ public class AddGoogleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.googleApps().stream().anyMatch(app -> appId.equals(app.appId()))) {
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.googleApps().add(new GoogleAppInfo(appId, country));
group.getGoogleApps().add(new GoogleAppInfo(appId, country));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(),
@@ -41,13 +41,13 @@ public class AddGroupCommand extends BaseStoreScraperBotCommand {
}
var admin = AdminRepository.INSTANCE.load();
if (admin.groups().contains(groupId)) {
if (admin.getGroups().contains(groupId)) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group is already added");
return;
}
GroupRepository.INSTANCE.init(groupId);
admin.groups().add(groupId);
admin.getGroups().add(groupId);
AdminRepository.INSTANCE.save(admin);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group added successfully");
}
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.old.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.old.google.GooglePlayScraper;
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;
@@ -24,7 +24,7 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -43,7 +43,7 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand {
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
var appleTable = new Table("AppId", "Updated", "Days", "OK");
for (var app : group.appleApps()) {
for (var app : group.getAppleApps()) {
var appId = app.appId();
var updated = AppStoreScraper.getAppUpdated(appId, app.country());
long days = ChronoUnit.DAYS.between(updated, now);
@@ -57,7 +57,7 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand {
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
var googleTable = new Table("AppId", "Updated", "Days", "OK");
for (var app : group.googleApps()) {
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var updated = GooglePlayScraper.getLastUpdateOfApp(appId, app.country());
long days = ChronoUnit.DAYS.between(updated, now);
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.old.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.old.google.GooglePlayScraper;
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;
@@ -23,7 +23,7 @@ public class CheckAppScoreCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -41,7 +41,7 @@ public class CheckAppScoreCommand extends BaseStoreScraperBotCommand {
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
var appleTable = new Table("AppId", "Score", "Ratings");
for (var app : group.appleApps()) {
for (var app : group.getAppleApps()) {
var appId = app.appId();
var country = app.country();
double score =
@@ -55,7 +55,7 @@ public class CheckAppScoreCommand extends BaseStoreScraperBotCommand {
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
var googleTable = new Table("AppId", "Score", "Ratings");
for (var app : group.googleApps()) {
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var country = app.country();
double score =
@@ -18,7 +18,7 @@ public class DeleteAppleAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -33,12 +33,12 @@ public class DeleteAppleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.appleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
if (group.getAppleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Apple app is not added");
return;
}
group.appleApps().removeIf(app -> appId.equals(app.appId()));
group.getAppleApps().removeIf(app -> appId.equals(app.appId()));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Apple app deleted successfully");
@@ -18,7 +18,7 @@ public class DeleteGoogleAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -33,12 +33,12 @@ public class DeleteGoogleAppCommand extends BaseStoreScraperBotCommand {
long groupId = chat.getId();
var group = GroupRepository.INSTANCE.load(groupId);
if (group.googleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
if (group.getGoogleApps().stream().noneMatch(app -> appId.equals(app.appId()))) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Google app is not added");
return;
}
group.googleApps().removeIf(app -> appId.equals(app.appId()));
group.getGoogleApps().removeIf(app -> appId.equals(app.appId()));
GroupRepository.INSTANCE.save(groupId, group);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Google app deleted successfully");
@@ -40,12 +40,12 @@ public class DeleteGroupCommand extends BaseStoreScraperBotCommand {
}
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(groupId)) {
if (!admin.getGroups().contains(groupId)) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group is not added");
return;
}
admin.groups().remove(groupId);
admin.getGroups().remove(groupId);
AdminRepository.INSTANCE.save(admin);
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "Group deleted successfully");
}
@@ -19,7 +19,7 @@ public class ListAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -38,7 +38,7 @@ public class ListAppCommand extends BaseStoreScraperBotCommand {
sb.append("<code>\n");
var appleTable = new Table("#", "AppId", "Country");
int i = 0;
for (var app : group.appleApps()) {
for (var app : group.getAppleApps()) {
i++;
appleTable.addRow(i, app.appId(), app.country());
}
@@ -49,7 +49,7 @@ public class ListAppCommand extends BaseStoreScraperBotCommand {
sb.append("<code>\n");
var googleTable = new Table("#", "AppId", "Country");
i = 0;
for (var app : group.googleApps()) {
for (var app : group.getGoogleApps()) {
i++;
googleTable.addRow(i, app.appId(), app.country());
}
@@ -28,7 +28,7 @@ public class ListGroupCommand extends BaseStoreScraperBotCommand {
}
var admin = AdminRepository.INSTANCE.load();
var groups = admin.groups();
var groups = admin.getGroups();
var sb = new StringBuilder();
sb.append("<b>Groups:</b>\n");
for (var groupId : groups) {
@@ -1,9 +1,10 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.old.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.old.apple.request.AppleAppRequest;
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 com.miti99.storescraperbot.util.GsonUtil;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import lombok.SneakyThrows;
@@ -29,7 +30,7 @@ public class RawAppleAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -1,9 +1,10 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.api.old.google.GooglePlayScraper;
import com.miti99.storescraperbot.api.old.google.request.GoogleAppRequest;
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 com.miti99.storescraperbot.util.GsonUtil;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import lombok.SneakyThrows;
@@ -29,7 +30,7 @@ public class RawGoogleAppCommand extends BaseStoreScraperBotCommand {
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
var admin = AdminRepository.INSTANCE.load();
if (!admin.groups().contains(chat.getId())) {
if (!admin.getGroups().contains(chat.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
chat.getId(), "Group is not allowed to use bot");
return;
@@ -1,7 +1,6 @@
package com.miti99.storescraperbot.model;
import com.miti99.storescraperbot.api.apple.entity.AppleAppDetail;
import com.miti99.storescraperbot.api.old.apple.response.AppleAppResponse;
import com.miti99.storescraperbot.api.apple.response.AppleAppResponse;
import lombok.Getter;
import lombok.Setter;
@@ -9,5 +8,4 @@ import lombok.Setter;
@Setter
public class AppleApp extends AbstractModel<String> {
AppleAppResponse app;
AppleAppDetail detail;
}
@@ -1,6 +1,6 @@
package com.miti99.storescraperbot.model;
import com.miti99.storescraperbot.api.old.google.response.GoogleAppResponse;
import com.miti99.storescraperbot.api.google.response.GoogleAppResponse;
import lombok.Getter;
import lombok.Setter;
@@ -71,7 +71,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
return;
}
V data = classV.getDeclaredConstructor().newInstance();
data.key(key);
data.setKey(key);
save(key, data);
} catch (Exception e) {
log.error("Error while initializing data", e);
@@ -1,654 +0,0 @@
package com.miti99.storescraperbot.util;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;
import com.google.gson.*;
public class GooglePlayAppScraper {
private static final String BASE_URL = "https://play.google.com";
private static final String PLAYSTORE_URL = BASE_URL + "/store/apps/details";
/**
* Main method to fetch app details by appId
* @param appId The application ID (package name) to fetch details for
* @return JsonObject containing app details
* @throws Exception if there's an error during the request or parsing
*/
public static JsonObject getAppDetails(String appId) throws Exception {
return getAppDetails(appId, "en", "us");
}
/**
* Fetch app details by appId with language and country parameters
* @param appId The application ID (package name) to fetch details for
* @param lang Language code (e.g., "en")
* @param country Country code (e.g., "us")
* @return JsonObject containing app details
* @throws Exception if there's an error during the request or parsing
*/
public static JsonObject getAppDetails(String appId, String lang, String country)
throws Exception {
if (appId == null || appId.isEmpty()) {
throw new IllegalArgumentException("appId missing");
}
// Set default values for lang and country if not provided
if (lang == null || lang.isEmpty()) lang = "en";
if (country == null || country.isEmpty()) country = "us";
// Construct URL with query parameters
// String url = String.format("%s?id=%s&hl=%s&gl=%s", PLAYSTORE_URL, appId, lang, country);
String url = String.format("%s?id=%s", PLAYSTORE_URL, appId);
// Make HTTP request
String response = makeRequest(url);
// Parse the response and extract data
JsonObject result = parseResponse(response);
// Add appId and URL to the result
result.addProperty("appId", appId);
result.addProperty("url", url);
return result;
}
/**
* Make HTTP request to the Google Play Store
* @param url The URL to request
* @return The response as a String
* @throws Exception if there's an error during the request
*/
private static String makeRequest(String url) throws Exception {
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty(
"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
int responseCode = con.getResponseCode();
if (responseCode != 200) {
throw new RuntimeException("HTTP request failed with code: " + responseCode);
}
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
}
/**
* Parse the Google Play Store response and extract app data
* @param response The HTML response from Google Play Store
* @return JsonObject with extracted app data
*/
private static JsonObject parseResponse(String response) {
JsonObject result = new JsonObject();
try {
// Extract script data using regex (similar to scriptData.js)
Map<String, Object> parsedData = parseScriptData(response);
// Extract fields using mappings (similar to MAPPINGS in app.js)
extractFields(result, parsedData);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* Parse JavaScript objects from AF_initDataCallback functions
* @param response The HTML response
* @return Map of parsed data
*/
private static Map<String, Object> parseScriptData(String response) {
Map<String, Object> parsedData = new HashMap<>();
// Regex patterns to match script data
Pattern scriptPattern =
Pattern.compile(">AF_initDataCallback[\\s\\S]*?</script", Pattern.CASE_INSENSITIVE);
Pattern keyPattern = Pattern.compile("(ds:.*?)'");
Pattern valuePattern = Pattern.compile("data:([\\s\\S]*?), sideChannel: \\{}}\\);<");
Matcher scriptMatcher = scriptPattern.matcher(response);
while (scriptMatcher.find()) {
String data = scriptMatcher.group();
Matcher keyMatcher = keyPattern.matcher(data);
Matcher valueMatcher = valuePattern.matcher(data);
if (keyMatcher.find() && valueMatcher.find()) {
String key = keyMatcher.group(1);
String valueStr = valueMatcher.group(1);
try {
// Try to parse as JSON
Object value = parseJson(valueStr);
parsedData.put(key, value);
} catch (Exception e) {
// If parsing fails, store as string
parsedData.put(key, valueStr);
}
}
}
return parsedData;
}
/**
* Simple JSON parser for the extracted data
* @param jsonStr The JSON string to parse
* @return Parsed object
*/
private static Object parseJson(String jsonStr) {
JsonElement element = JsonParser.parseString(jsonStr);
return element;
}
/**
* Extract fields from parsed data using mappings
* @param result The result JsonObject to populate
* @param parsedData The parsed data map
*/
private static void extractFields(JsonObject result, Map<String, Object> parsedData) {
// Extract all fields (full versions of the MAPPINGS from app.js)
try {
// Get the main data array
JsonElement ds5 = (JsonElement) parsedData.get("ds:5");
// Title - ['ds:5', 1, 2, 0, 0]
Object titleObj = getPath(ds5, 1, 2, 0, 0);
if (titleObj instanceof JsonElement && ((JsonElement) titleObj).isJsonPrimitive()) {
result.addProperty("title", ((JsonElement) titleObj).getAsString());
}
// Description - ['ds:5', 1, 2] with helper function
Object descriptionObj = getPath(ds5, 1, 2);
if (descriptionObj instanceof JsonElement) {
String description = extractDescriptionText((JsonElement) descriptionObj);
result.addProperty("description", description);
}
// Description HTML - ['ds:5', 1, 2] with helper function
Object descriptionHtmlObj = getPath(ds5, 1, 2);
if (descriptionHtmlObj instanceof JsonElement) {
String descriptionHtml = extractDescriptionHtmlLocalized((JsonElement) descriptionHtmlObj);
result.addProperty("descriptionHTML", descriptionHtml);
}
// Summary - ['ds:5', 1, 2, 73, 0, 1]
Object summaryObj = getPath(ds5, 1, 2, 73, 0, 1);
if (summaryObj instanceof JsonElement && ((JsonElement) summaryObj).isJsonPrimitive()) {
result.addProperty("summary", ((JsonElement) summaryObj).getAsString());
}
// Installs - ['ds:5', 1, 2, 13, 0]
Object installsObj = getPath(ds5, 1, 2, 13, 0);
if (installsObj instanceof JsonElement && ((JsonElement) installsObj).isJsonPrimitive()) {
result.addProperty("installs", ((JsonElement) installsObj).getAsString());
}
// Min Installs - ['ds:5', 1, 2, 13, 1]
Object minInstallsObj = getPath(ds5, 1, 2, 13, 1);
if (minInstallsObj instanceof JsonElement
&& ((JsonElement) minInstallsObj).isJsonPrimitive()) {
result.addProperty("minInstalls", ((JsonElement) minInstallsObj).getAsLong());
}
// Max Installs - ['ds:5', 1, 2, 13, 2]
Object maxInstallsObj = getPath(ds5, 1, 2, 13, 2);
if (maxInstallsObj instanceof JsonElement
&& ((JsonElement) maxInstallsObj).isJsonPrimitive()) {
result.addProperty("maxInstalls", ((JsonElement) maxInstallsObj).getAsLong());
}
// Score - ['ds:5', 1, 2, 51, 0, 1]
Object scoreObj = getPath(ds5, 1, 2, 51, 0, 1);
if (scoreObj instanceof JsonElement && ((JsonElement) scoreObj).isJsonPrimitive()) {
result.addProperty("score", ((JsonElement) scoreObj).getAsFloat());
}
// Score Text - ['ds:5', 1, 2, 51, 0, 0]
Object scoreTextObj = getPath(ds5, 1, 2, 51, 0, 0);
if (scoreTextObj instanceof JsonElement && ((JsonElement) scoreTextObj).isJsonPrimitive()) {
result.addProperty("scoreText", ((JsonElement) scoreTextObj).getAsString());
}
// Ratings - ['ds:5', 1, 2, 51, 2, 1]
Object ratingsObj = getPath(ds5, 1, 2, 51, 2, 1);
if (ratingsObj instanceof JsonElement && ((JsonElement) ratingsObj).isJsonPrimitive()) {
result.addProperty("ratings", ((JsonElement) ratingsObj).getAsLong());
}
// Reviews - ['ds:5', 1, 2, 51, 3, 1]
Object reviewsObj = getPath(ds5, 1, 2, 51, 3, 1);
if (reviewsObj instanceof JsonElement && ((JsonElement) reviewsObj).isJsonPrimitive()) {
result.addProperty("reviews", ((JsonElement) reviewsObj).getAsLong());
}
// Histogram - ['ds:5', 1, 2, 51, 1] with helper function
Object histogramObj = getPath(ds5, 1, 2, 51, 1);
JsonObject histogram = buildHistogram(histogramObj);
result.add("histogram", histogram);
// Price - ['ds:5', 1, 2, 57, 0, 0, 0, 0, 1, 0, 0] with function
Object priceObj = getPath(ds5, 1, 2, 57, 0, 0, 0, 0, 1, 0, 0);
double price = 0;
if (priceObj instanceof JsonElement && ((JsonElement) priceObj).isJsonPrimitive()) {
price = ((JsonElement) priceObj).getAsLong() / 1000000.0;
}
result.addProperty("price", price);
// Original Price - ['ds:5', 1, 2, 57, 0, 0, 0, 0, 1, 1, 0] with function
Object originalPriceObj = getPath(ds5, 1, 2, 57, 0, 0, 0, 0, 1, 1, 0);
if (originalPriceObj instanceof JsonElement
&& ((JsonElement) originalPriceObj).isJsonPrimitive()) {
long originalPriceValue = ((JsonElement) originalPriceObj).getAsLong();
if (originalPriceValue > 0) {
result.addProperty("originalPrice", originalPriceValue / 1000000.0);
}
}
// Discount End Date - ['ds:5', 1, 2, 57, 0, 0, 0, 0, 14, 1]
Object discountEndDateObj = getPath(ds5, 1, 2, 57, 0, 0, 0, 0, 14, 1);
if (discountEndDateObj instanceof JsonElement
&& ((JsonElement) discountEndDateObj).isJsonPrimitive()) {
result.addProperty("discountEndDate", ((JsonElement) discountEndDateObj).getAsLong());
}
// Free - ['ds:5', 1, 2, 57, 0, 0, 0, 0, 1, 0, 0] with function
boolean isFree = false;
if (priceObj instanceof JsonElement && ((JsonElement) priceObj).isJsonPrimitive()) {
isFree = ((JsonElement) priceObj).getAsLong() == 0;
}
result.addProperty("free", isFree);
// Currency - ['ds:5', 1, 2, 57, 0, 0, 0, 0, 1, 0, 1]
Object currencyObj = getPath(ds5, 1, 2, 57, 0, 0, 0, 0, 1, 0, 1);
if (currencyObj instanceof JsonElement && ((JsonElement) currencyObj).isJsonPrimitive()) {
result.addProperty("currency", ((JsonElement) currencyObj).getAsString());
}
// Price Text - ['ds:5', 1, 2, 57, 0, 0, 0, 0, 1, 0, 2] with helper
Object priceTextPathObj = getPath(ds5, 1, 2, 57, 0, 0, 0, 0, 1, 0, 2);
String priceText = "Free";
if (priceTextPathObj instanceof JsonElement
&& ((JsonElement) priceTextPathObj).isJsonPrimitive()) {
priceText = ((JsonElement) priceTextPathObj).getAsString();
}
result.addProperty("priceText", priceText);
// Available - ['ds:5', 1, 2, 18, 0] with Boolean function
Object availableObj = getPath(ds5, 1, 2, 18, 0);
boolean isAvailable = false;
if (availableObj instanceof JsonElement && ((JsonElement) availableObj).isJsonPrimitive()) {
isAvailable = ((JsonElement) availableObj).getAsInt() == 1;
}
result.addProperty("available", isAvailable);
// Offers IAP - ['ds:5', 1, 2, 19, 0] with Boolean function
Object offersIAPObj = getPath(ds5, 1, 2, 19, 0);
boolean offersIAP = false;
if (offersIAPObj instanceof JsonElement && ((JsonElement) offersIAPObj).isJsonPrimitive()) {
offersIAP = ((JsonElement) offersIAPObj).getAsInt() == 1;
}
result.addProperty("offersIAP", offersIAP);
// IAP Range - ['ds:5', 1, 2, 19, 0]
Object iapRangeObj = getPath(ds5, 1, 2, 19, 0);
if (iapRangeObj instanceof JsonElement && ((JsonElement) iapRangeObj).isJsonPrimitive()) {
result.addProperty("IAPRange", ((JsonElement) iapRangeObj).getAsString());
}
// Android Version - ['ds:5', 1, 2, 140, 1, 1, 0, 0, 1] with helper
Object androidVersionObj = getPath(ds5, 1, 2, 140, 1, 1, 0, 0, 1);
String androidVersion = "VARY";
if (androidVersionObj instanceof JsonElement
&& ((JsonElement) androidVersionObj).isJsonPrimitive()) {
androidVersion = normalizeAndroidVersion(((JsonElement) androidVersionObj).getAsString());
}
result.addProperty("androidVersion", androidVersion);
// Android Version Text - ['ds:5', 1, 2, 140, 1, 1, 0, 0, 1] with helper
Object androidVersionTextObj = getPath(ds5, 1, 2, 140, 1, 1, 0, 0, 1);
String androidVersionText = "Varies with device";
if (androidVersionTextObj instanceof JsonElement
&& ((JsonElement) androidVersionTextObj).isJsonPrimitive()) {
androidVersionText = ((JsonElement) androidVersionTextObj).getAsString();
}
result.addProperty("androidVersionText", androidVersionText);
// Developer - ['ds:5', 1, 2, 68, 0]
Object developerObj = getPath(ds5, 1, 2, 68, 0);
if (developerObj instanceof JsonElement && ((JsonElement) developerObj).isJsonPrimitive()) {
result.addProperty("developer", ((JsonElement) developerObj).getAsString());
}
// Developer Id - ['ds:5', 1, 2, 68, 1, 4, 2] with function
Object developerIdObj = getPath(ds5, 1, 2, 68, 1, 4, 2);
if (developerIdObj instanceof JsonElement
&& ((JsonElement) developerIdObj).isJsonPrimitive()) {
String devUrl = ((JsonElement) developerIdObj).getAsString();
String devId = extractDeveloperId(devUrl);
if (devId != null) {
result.addProperty("developerId", devId);
}
}
// Developer Email - ['ds:5', 1, 2, 69, 1, 0]
Object developerEmailObj = getPath(ds5, 1, 2, 69, 1, 0);
if (developerEmailObj instanceof JsonElement
&& ((JsonElement) developerEmailObj).isJsonPrimitive()) {
result.addProperty("developerEmail", ((JsonElement) developerEmailObj).getAsString());
}
// Developer Website - ['ds:5', 1, 2, 69, 0, 5, 2]
Object developerWebsiteObj = getPath(ds5, 1, 2, 69, 0, 5, 2);
if (developerWebsiteObj instanceof JsonElement
&& ((JsonElement) developerWebsiteObj).isJsonPrimitive()) {
result.addProperty("developerWebsite", ((JsonElement) developerWebsiteObj).getAsString());
}
// Developer Address - ['ds:5', 1, 2, 69, 2, 0]
Object developerAddressObj = getPath(ds5, 1, 2, 69, 2, 0);
if (developerAddressObj instanceof JsonElement
&& ((JsonElement) developerAddressObj).isJsonPrimitive()) {
result.addProperty("developerAddress", ((JsonElement) developerAddressObj).getAsString());
}
// Privacy Policy - ['ds:5', 1, 2, 99, 0, 5, 2]
Object privacyPolicyObj = getPath(ds5, 1, 2, 99, 0, 5, 2);
if (privacyPolicyObj instanceof JsonElement
&& ((JsonElement) privacyPolicyObj).isJsonPrimitive()) {
result.addProperty("privacyPolicy", ((JsonElement) privacyPolicyObj).getAsString());
}
// Genre - ['ds:5', 1, 2, 79, 0, 0, 0]
Object genreObj = getPath(ds5, 1, 2, 79, 0, 0, 0);
if (genreObj instanceof JsonElement && ((JsonElement) genreObj).isJsonPrimitive()) {
result.addProperty("genre", ((JsonElement) genreObj).getAsString());
}
// Genre Id - ['ds:5', 1, 2, 79, 0, 0, 2]
Object genreIdObj = getPath(ds5, 1, 2, 79, 0, 0, 2);
if (genreIdObj instanceof JsonElement && ((JsonElement) genreIdObj).isJsonPrimitive()) {
result.addProperty("genreId", ((JsonElement) genreIdObj).getAsString());
}
// Icon - ['ds:5', 1, 2, 95, 0, 3, 2]
Object iconObj = getPath(ds5, 1, 2, 95, 0, 3, 2);
if (iconObj instanceof JsonElement && ((JsonElement) iconObj).isJsonPrimitive()) {
result.addProperty("icon", ((JsonElement) iconObj).getAsString());
}
// Header Image - ['ds:5', 1, 2, 96, 0, 3, 2]
Object headerImageObj = getPath(ds5, 1, 2, 96, 0, 3, 2);
if (headerImageObj instanceof JsonElement
&& ((JsonElement) headerImageObj).isJsonPrimitive()) {
result.addProperty("headerImage", ((JsonElement) headerImageObj).getAsString());
}
// Screenshots - ['ds:5', 1, 2, 78, 0] with function
Object screenshotsObj = getPath(ds5, 1, 2, 78, 0);
JsonArray screenshots = extractScreenshots(screenshotsObj);
result.add("screenshots", screenshots);
// Content Rating - ['ds:5', 1, 2, 9, 0]
Object contentRatingObj = getPath(ds5, 1, 2, 9, 0);
if (contentRatingObj instanceof JsonElement
&& ((JsonElement) contentRatingObj).isJsonPrimitive()) {
result.addProperty("contentRating", ((JsonElement) contentRatingObj).getAsString());
}
// Ad Supported - ['ds:5', 1, 2, 48] with Boolean function
Object adSupportedObj = getPath(ds5, 1, 2, 48);
boolean adSupported = false;
if (adSupportedObj instanceof JsonElement
&& ((JsonElement) adSupportedObj).isJsonPrimitive()) {
adSupported = ((JsonElement) adSupportedObj).getAsInt() == 1;
}
result.addProperty("adSupported", adSupported);
// Released - ['ds:5', 1, 2, 10, 0]
Object releasedObj = getPath(ds5, 1, 2, 10, 0);
if (releasedObj instanceof JsonElement && ((JsonElement) releasedObj).isJsonPrimitive()) {
result.addProperty("released", ((JsonElement) releasedObj).getAsString());
}
// Updated - ['ds:5', 1, 2, 145, 0, 1, 0] with function
Object updatedObj = getPath(ds5, 1, 2, 145, 0, 1, 0);
if (updatedObj instanceof JsonElement && ((JsonElement) updatedObj).isJsonPrimitive()) {
long timestamp = ((JsonElement) updatedObj).getAsLong();
result.addProperty("updated", timestamp * 1000);
}
// Version - ['ds:5', 1, 2, 140, 0, 0, 0] with function
Object versionObj = getPath(ds5, 1, 2, 140, 0, 0, 0);
String version = "VARY";
if (versionObj instanceof JsonElement && ((JsonElement) versionObj).isJsonPrimitive()) {
version = ((JsonElement) versionObj).getAsString();
if (version == null || version.isEmpty()) {
version = "VARY";
}
}
result.addProperty("version", version);
// Recent Changes - ['ds:5', 1, 2, 144, 1, 1]
Object recentChangesObj = getPath(ds5, 1, 2, 144, 1, 1);
if (recentChangesObj instanceof JsonElement
&& ((JsonElement) recentChangesObj).isJsonPrimitive()) {
result.addProperty("recentChanges", ((JsonElement) recentChangesObj).getAsString());
}
} catch (Exception e) {
// In case of any error, we continue with what we have
e.printStackTrace();
}
}
/**
* Extract description text from search array
* @param searchArray The JSON element containing description data
* @return The processed description text
*/
private static String extractDescriptionText(JsonElement searchArray) {
String descriptionHtml = extractDescriptionHtmlLocalized(searchArray);
if (descriptionHtml == null) return "";
// This is a simplified version of the descriptionText function
// The original uses cheerio to convert HTML to text while preserving line breaks
// We'll do a basic HTML tag removal with line break preservation
return descriptionHtml.replaceAll("<br>", "\n").replaceAll("<[^>]+>", "").trim();
}
/**
* Extract description HTML localized from search array
* @param searchArray The JSON element containing description data
* @return The HTML localized description
*/
private static String extractDescriptionHtmlLocalized(JsonElement searchArray) {
// This mimics descriptionHtmlLocalized from mappingHelpers.js
// It looks for description translation or original description
JsonElement descriptionTranslation = getPath(searchArray, 12, 0, 0, 1);
JsonElement descriptionOriginal = getPath(searchArray, 72, 0, 1);
if (descriptionTranslation != null && descriptionTranslation.isJsonPrimitive()) {
return descriptionTranslation.getAsString();
} else if (descriptionOriginal != null && descriptionOriginal.isJsonPrimitive()) {
return descriptionOriginal.getAsString();
} else {
return "";
}
}
/**
* Build histogram from container
* @param container The histogram data container
* @return JsonObject with histogram data
*/
private static JsonObject buildHistogram(Object container) {
JsonObject histogram = new JsonObject();
if (container == null) {
histogram.addProperty("1", 0);
histogram.addProperty("2", 0);
histogram.addProperty("3", 0);
histogram.addProperty("4", 0);
histogram.addProperty("5", 0);
return histogram;
}
JsonElement containerElement = (JsonElement) container;
if (containerElement.isJsonArray()) {
JsonArray arr = containerElement.getAsJsonArray();
if (arr.size() > 5) {
JsonObject hist = new JsonObject();
JsonElement one = getPath(arr, 1, 1);
JsonElement two = getPath(arr, 2, 1);
JsonElement three = getPath(arr, 3, 1);
JsonElement four = getPath(arr, 4, 1);
JsonElement five = getPath(arr, 5, 1);
hist.addProperty("1", one != null && one.isJsonPrimitive() ? one.getAsInt() : 0);
hist.addProperty("2", two != null && two.isJsonPrimitive() ? two.getAsInt() : 0);
hist.addProperty("3", three != null && three.isJsonPrimitive() ? three.getAsInt() : 0);
hist.addProperty("4", four != null && four.isJsonPrimitive() ? four.getAsInt() : 0);
hist.addProperty("5", five != null && five.isJsonPrimitive() ? five.getAsInt() : 0);
return hist;
}
}
// Default values if histogram data is not available
histogram.addProperty("1", 0);
histogram.addProperty("2", 0);
histogram.addProperty("3", 0);
histogram.addProperty("4", 0);
histogram.addProperty("5", 0);
return histogram;
}
/**
* Normalize Android version text
* @param androidVersionText The original Android version text
* @return Normalized Android version
*/
private static String normalizeAndroidVersion(String androidVersionText) {
if (androidVersionText == null) return "VARY";
String number = androidVersionText.split(" ")[0];
try {
double num = Double.parseDouble(number);
return Double.toString(num);
} catch (NumberFormatException e) {
return "VARY";
}
}
/**
* Extract developer ID from developer URL
* @param devUrl The developer URL
* @return The extracted developer ID
*/
private static String extractDeveloperId(String devUrl) {
if (devUrl != null && devUrl.contains("id=")) {
String[] parts = devUrl.split("id=");
if (parts.length > 1) {
return parts[1];
}
}
return null;
}
/**
* Extract screenshots from screenshots data
* @param screenshotsObj The screenshots data object
* @return JsonArray of screenshot URLs
*/
private static JsonArray extractScreenshots(Object screenshotsObj) {
JsonArray screenshots = new JsonArray();
if (screenshotsObj == null) return screenshots;
JsonElement screenshotsElement = (JsonElement) screenshotsObj;
if (screenshotsElement.isJsonArray()) {
JsonArray array = screenshotsElement.getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
JsonElement screenshotItem = array.get(i);
JsonElement url = getPath(screenshotItem, 3, 2);
if (url != null && url.isJsonPrimitive()) {
screenshots.add(url.getAsString());
}
}
}
return screenshots;
}
/**
* Get value at specified path in nested JSON structure
* @param element The root JSON element
* @param path The path indices
* @return The value at the specified path, or null if not found
*/
private static JsonElement getPath(JsonElement element, int... path) {
if (element == null) return null;
JsonElement current = element;
for (int index : path) {
if (current == null) return null;
if (current.isJsonArray()) {
JsonArray array = current.getAsJsonArray();
if (index < array.size()) {
current = array.get(index);
} else {
return null;
}
} else if (current.isJsonObject()) {
// For objects, we might need a different approach
return null;
} else {
return null;
}
}
return current;
}
/**
* Main method for testing
* @param args Command line arguments
*/
public static void main(String[] args) {
try {
// if (args.length < 1) {
// System.out.println("Usage: java GooglePlayAppScraper <appId>");
// System.out.println("Example: java GooglePlayAppScraper com.android.chrome");
// return;
// }
// String appId = args[0];
String appId = "vn.kvtm.js";
System.out.println("Fetching details for app: " + appId);
JsonObject appDetails = getAppDetails(appId);
// Pretty print the JSON response
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(appDetails));
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}
@@ -1,29 +0,0 @@
package com.miti99.storescraperbot.util;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestUtil {
private static final Type MAP_STRING_STRING_TYPE =
new TypeToken<Map<String, String>>() {}.getType();
private static Map<String, String> objToMapStringString(Object obj) {
return GsonUtil.GSON.fromJson(GsonUtil.GSON.toJson(obj), MAP_STRING_STRING_TYPE);
}
public static String makeGetParams(Map<String, String> params) {
return params.entrySet().stream()
.filter(entry -> entry.getValue() != null)
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
public static String makeGetParams(Object obj) {
return makeGetParams(objToMapStringString(obj));
}
}
@@ -1,27 +1,28 @@
package com.miti99.storescraperbot.api.apple;
import static org.junit.jupiter.api.Assertions.*;
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 response = AppStoreScraper.app("com.mpt.kvtm");
var request = new AppleAppRequest("com.mpt.kvtm", "vn");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComMptBuraco() {
// var response = AppStoreScraper.app("com.mpt.buraco");
var response = AppStoreScraper.app(1638264682);
var request = new AppleAppRequest("com.mpt.buraco", "mx");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComMptDoudizhu() {
var response = AppStoreScraper.app("com.mpt.doudizhu");
var request = new AppleAppRequest("com.mpt.doudizhu", "hk");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
}
@@ -1,7 +1,6 @@
package com.miti99.storescraperbot.api.old.google;
package com.miti99.storescraperbot.api.google;
import com.miti99.storescraperbot.api.old.google.GooglePlayScraper;
import com.miti99.storescraperbot.api.old.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.api.google.request.GoogleAppRequest;
import com.miti99.storescraperbot.util.GsonUtil;
import org.junit.jupiter.api.Test;
@@ -1,29 +0,0 @@
package com.miti99.storescraperbot.api.old.apple;
import com.miti99.storescraperbot.api.old.apple.AppStoreScraper;
import com.miti99.storescraperbot.api.old.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));
}
}