mirror of
https://github.com/tiennm99/store-scraper-bot-java.git
synced 2026-05-15 17:53:19 +00:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user