Refactor config to Environment and add Table utility

Replaces Config with Environment throughout the codebase for environment variable management. Introduces a Table utility class for formatting tabular data in bot commands, refactoring commands to use this for improved output formatting. Removes unused AppType enum. Updates test cases and build configuration for better IDE integration.
This commit is contained in:
2025-11-07 23:11:16 +07:00
parent 4c62fde11f
commit 77f4e08946
17 changed files with 134 additions and 69 deletions
+8
View File
@@ -1,5 +1,6 @@
plugins {
application
idea
java
id("com.gradleup.shadow") version "8.3.5"
}
@@ -37,6 +38,13 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
idea {
module {
isDownloadJavadoc = true
isDownloadSources = true
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
@@ -1,12 +1,13 @@
package com.miti99.storescraperbot;
import static com.miti99.storescraperbot.config.Config.CREATOR_ID;
import static com.miti99.storescraperbot.config.Config.SOURCE_COMMIT;
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.config.Config;
import com.miti99.storescraperbot.env.Environment;
import com.miti99.storescraperbot.repository.AdminRepository;
import com.miti99.storescraperbot.type.Env;
import lombok.extern.log4j.Log4j2;
import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication;
@@ -17,10 +18,12 @@ public class Main {
AdminRepository.INSTANCE.init();
try (var botsApplication = new TelegramBotsLongPollingApplication()) {
botsApplication.registerBot(Config.TELEGRAM_BOT_TOKEN, StoreScrapeBot.INSTANCE);
botsApplication.registerBot(Environment.TELEGRAM_BOT_TOKEN, StoreScrapeBot.INSTANCE);
log.info("StoreScrapeBot successfully started!");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
CREATOR_ID, "Bot started! Version <code>%s</code>".formatted(SOURCE_COMMIT));
if (Environment.ENV == Env.PRODUCTION) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(
CREATOR_ID, "Bot started! Version <code>%s</code>".formatted(SOURCE_COMMIT));
}
Thread.currentThread().join();
} catch (Exception e) {
log.error("Error while running bot", e);
@@ -1,6 +1,6 @@
package com.miti99.storescraperbot.bot;
import com.miti99.storescraperbot.config.Config;
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;
@@ -11,7 +11,7 @@ public class StoreScrapeBotTelegramClient extends OkHttpTelegramClient {
public static final StoreScrapeBotTelegramClient INSTANCE = new StoreScrapeBotTelegramClient();
public StoreScrapeBotTelegramClient() {
super(Config.TELEGRAM_BOT_TOKEN);
super(Environment.TELEGRAM_BOT_TOKEN);
}
public void sendMessage(long chatId, String text) {
@@ -1,6 +1,6 @@
package com.miti99.storescraperbot.bot;
import com.miti99.storescraperbot.config.Config;
import com.miti99.storescraperbot.env.Environment;
import java.util.function.Supplier;
public class StoreScrapeBotUsernameSupplier implements Supplier<String> {
@@ -9,6 +9,6 @@ public class StoreScrapeBotUsernameSupplier implements Supplier<String> {
@Override
public String get() {
return Config.TELEGRAM_BOT_USERNAME;
return Environment.TELEGRAM_BOT_USERNAME;
}
}
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.config.Config;
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;
@@ -18,7 +18,7 @@ public class AddGroupCommand extends BaseStoreScraperBotCommand {
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (!Config.ADMIN_IDS.contains(user.getId())) {
if (!Environment.ADMIN_IDS.contains(user.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "You are not admin");
return;
}
@@ -3,6 +3,7 @@ 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;
@@ -41,34 +42,31 @@ public class CheckAppCommand extends BaseStoreScraperBotCommand {
var sb = new StringBuilder();
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-20s | %-10s | %-4s | %-2s\n".formatted("AppId", "Updated", "Days", "OK"));
sb.append("-".repeat(46));
sb.append("\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;
sb.append(
"%-20s | %-10s | %-4s | %-2s\n".formatted(appId, updated, days, passed ? "" : ""));
var icon = passed ? "" : "";
appleTable.addRow(appId, updated, days, icon);
}
sb.append(appleTable);
sb.append("</code>\n");
sb.append("\n");
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-20s | %-10s | %-4s | %-2s\n".formatted("AppId", "Updated", "Days", "OK"));
sb.append("-".repeat(46));
sb.append("\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;
sb.append(
"%-20s | %-10s | %-4s | %-2s\n".formatted(appId, updated, days, passed ? "" : ""));
var icon = passed ? "" : "";
googleTable.addRow(appId, updated, days, icon);
}
sb.append(googleTable);
sb.append("</code>");
sb.append("\n");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
@@ -3,6 +3,7 @@ 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.telegram.telegrambots.meta.api.objects.User;
@@ -37,32 +38,29 @@ public class CheckAppScoreCommand extends BaseStoreScraperBotCommand {
var sb = new StringBuilder();
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-20s | %-10s | %-10s\n".formatted("AppId", "Score", "Ratings"));
sb.append("-".repeat(43));
sb.append("\n");
var appleTable = new Table("AppId", "Score", "Ratings");
for (var app : group.getAppleApps()) {
var appId = app.appId();
var country = app.country();
double score = AppStoreScraper.getAppScore(appId, country);
long ratings = AppStoreScraper.getAppRatings(appId, country);
sb.append("%-20s | %-10s | %-10s\n".formatted(appId, score, ratings));
appleTable.addRow(appId, score, ratings);
}
sb.append(appleTable);
sb.append("</code>\n");
sb.append("\n");
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-20s | %-10s | %-10s\n".formatted("AppId", "Score", "Ratings"));
sb.append("-".repeat(43));
sb.append("\n");
var googleTable = new Table("AppId", "Score", "Ratings");
for (var app : group.getGoogleApps()) {
var appId = app.appId();
var country = app.country();
double score = GooglePlayScraper.getAppScore(appId, country);
long ratings = GooglePlayScraper.getAppRatings(appId, country);
sb.append("%-20s | %-10s | %-10s\n".formatted(appId, score, ratings));
googleTable.addRow(appId, score, ratings);
}
sb.append(googleTable);
sb.append("</code>");
sb.append("\n");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.config.Config;
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;
@@ -17,7 +17,7 @@ public class DeleteGroupCommand extends BaseStoreScraperBotCommand {
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (!Config.ADMIN_IDS.contains(user.getId())) {
if (!Environment.ADMIN_IDS.contains(user.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "You are not admin");
return;
}
@@ -1,6 +1,7 @@
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;
@@ -35,29 +36,25 @@ public class ListAppCommand extends BaseStoreScraperBotCommand {
var sb = new StringBuilder();
sb.append("<b>Apple Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-2s | %-20s | %-7s\n".formatted("#", "AppId", "Country"));
sb.append("-".repeat(25));
sb.append("\n");
var appleTable = new Table("#", "AppId", "Country");
int i = 0;
for (var app : group.getAppleApps()) {
i++;
sb.append("%-2s | %-20s | %-7s\n".formatted(i, app.appId(), app.country()));
appleTable.addRow(i, app.appId(), app.country());
}
sb.append(appleTable);
sb.append("</code>\n");
sb.append("\n");
sb.append("\n<b>Google Apps:</b>\n");
sb.append("<b>Google Apps:</b>\n");
sb.append("<code>\n");
sb.append("%-2s | %-20s | %-7s\n".formatted("#", "AppId", "Country"));
sb.append("-".repeat(35));
sb.append("\n");
var googleTable = new Table("#", "AppId", "Country");
i = 0;
for (var app : group.getGoogleApps()) {
i++;
sb.append("%-2s | %-20s | %-7s\n".formatted(i, app.appId(), app.country()));
googleTable.addRow(i, app.appId(), app.country());
}
sb.append("</code>\n");
sb.append("\n");
sb.append(googleTable);
sb.append("</code>");
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), sb.toString());
}
@@ -1,7 +1,7 @@
package com.miti99.storescraperbot.bot.command;
import com.miti99.storescraperbot.bot.StoreScrapeBotTelegramClient;
import com.miti99.storescraperbot.config.Config;
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;
@@ -17,7 +17,7 @@ public class ListGroupCommand extends BaseStoreScraperBotCommand {
@Override
protected void executeCommand(
TelegramClient telegramClient, User user, Chat chat, String[] arguments) {
if (!Config.ADMIN_IDS.contains(user.getId())) {
if (!Environment.ADMIN_IDS.contains(user.getId())) {
StoreScrapeBotTelegramClient.INSTANCE.sendMessage(chat.getId(), "You are not admin");
return;
}
@@ -0,0 +1,53 @@
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"));
sb.append(rule);
for (var row : rows) {
sb.append(formater.formatted((Object[]) row));
}
return sb.toString();
}
}
@@ -1,4 +1,4 @@
package com.miti99.storescraperbot.config;
package com.miti99.storescraperbot.env;
import com.google.common.base.Strings;
import com.miti99.storescraperbot.type.Env;
@@ -8,7 +8,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Config {
public class Environment {
public static final String COUCHBASE_CONNECTION_STRING =
System.getenv("COUCHBASE_CONNECTION_STRING");
public static final String COUCHBASE_USERNAME = System.getenv("COUCHBASE_USERNAME");
@@ -3,7 +3,7 @@ package com.miti99.storescraperbot.repository;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.kv.UpsertOptions;
import com.google.common.base.CaseFormat;
import com.miti99.storescraperbot.config.Config;
import com.miti99.storescraperbot.env.Environment;
import com.miti99.storescraperbot.model.AbstractModel;
import com.miti99.storescraperbot.util.CouchbaseUtil;
import java.lang.reflect.ParameterizedType;
@@ -15,7 +15,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
public static final String SEPARATOR = "_";
// protected final Class<K> classK = getKeyClass();
protected final Class<V> classV = getDataClass();
protected final String scopeName = Config.ENV.name().toLowerCase();
protected final String scopeName = Environment.ENV.name().toLowerCase();
protected final String collectionName;
protected AbstractRepository(String collectionName) {
@@ -1,6 +0,0 @@
package com.miti99.storescraperbot.type;
public enum AppType {
GOOGLE,
APPLE
}
@@ -1,9 +1,9 @@
package com.miti99.storescraperbot.util;
import static com.miti99.storescraperbot.config.Config.COUCHBASE_BUCKET_NAME;
import static com.miti99.storescraperbot.config.Config.COUCHBASE_CONNECTION_STRING;
import static com.miti99.storescraperbot.config.Config.COUCHBASE_PASSWORD;
import static com.miti99.storescraperbot.config.Config.COUCHBASE_USERNAME;
import static com.miti99.storescraperbot.env.Environment.COUCHBASE_BUCKET_NAME;
import static com.miti99.storescraperbot.env.Environment.COUCHBASE_CONNECTION_STRING;
import static com.miti99.storescraperbot.env.Environment.COUCHBASE_PASSWORD;
import static com.miti99.storescraperbot.env.Environment.COUCHBASE_USERNAME;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
@@ -6,16 +6,23 @@ import org.junit.jupiter.api.Test;
class AppStoreScraperTest {
@Test
void testApp() {
var request = new AppleAppRequest("com.mpt.kvtm");
void testComMptKvtm() {
var request = new AppleAppRequest("com.mpt.kvtm", "vn");
var response = AppStoreScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComMPTBuraco() {
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));
}
}
@@ -6,16 +6,23 @@ import org.junit.jupiter.api.Test;
class GooglePlayScraperTest {
@Test
void testApp() {
void PoolUs() {
var request = new GoogleAppRequest("pool.us");
var response = GooglePlayScraper.app(request);
System.out.println(GsonUtil.toJson(response));
}
@Test
void testComZingplayBuracoMX() {
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));
}
}