diff --git a/src/main/java/creator/loki/di/BotModule.java b/src/main/java/creator/loki/di/BotModule.java index e36e88d..ffc0fc1 100644 --- a/src/main/java/creator/loki/di/BotModule.java +++ b/src/main/java/creator/loki/di/BotModule.java @@ -19,6 +19,7 @@ import creator.loki.feature.config.ConfigModule; import creator.loki.feature.counter.CounterModule; import creator.loki.feature.economy.EconomyModule; import creator.loki.feature.entertainment.EntertainmentModule; +import creator.loki.feature.fun.FunModule; import creator.loki.feature.image.ImageModule; import creator.loki.feature.level.LevelModule; import creator.loki.feature.metrics.HealthHttpServer; @@ -30,6 +31,7 @@ import creator.loki.feature.profile.ProfileModule; import creator.loki.feature.radio.RadioModule; import creator.loki.feature.reactroles.ReactionRolesModule; import creator.loki.feature.role.RoleModule; +import creator.loki.feature.space.SpaceModule; import creator.loki.feature.ticket.TicketModule; import creator.loki.feature.tiktok.TikTokModule; import creator.loki.feature.utility.UtilityModule; @@ -67,6 +69,8 @@ public class BotModule extends AbstractModule { install(new TikTokModule()); install(new EntertainmentModule()); install(new AnimalModule()); + install(new FunModule()); + install(new SpaceModule()); install(new TicketModule()); install(new UtilityModule()); install(new ReactionRolesModule()); diff --git a/src/main/java/creator/loki/feature/animals/AnimalCommandRegistrar.java b/src/main/java/creator/loki/feature/animals/AnimalCommandRegistrar.java index b6a5088..105e779 100644 --- a/src/main/java/creator/loki/feature/animals/AnimalCommandRegistrar.java +++ b/src/main/java/creator/loki/feature/animals/AnimalCommandRegistrar.java @@ -3,6 +3,7 @@ package creator.loki.feature.animals; import com.google.inject.Inject; import creator.loki.core.registry.CommandRegistrar; import creator.loki.core.registry.CommandRegistry; +import creator.loki.feature.animals.command.AnimalFactCommand; import creator.loki.feature.animals.command.AnimalImageCommand; import creator.loki.feature.animals.command.ZooPersonCommand; import creator.loki.feature.entertainment.service.RatingService; @@ -25,5 +26,8 @@ public class AnimalCommandRegistrar implements CommandRegistrar { for (AnimalImageCommand.Flavor flavor : AnimalImageCommand.Flavor.values()) { registry.register(new AnimalImageCommand(flavor, animalApi)); } + for (AnimalFactCommand.Flavor flavor : AnimalFactCommand.Flavor.values()) { + registry.register(new AnimalFactCommand(flavor)); + } } } diff --git a/src/main/java/creator/loki/feature/animals/command/AnimalFactCommand.java b/src/main/java/creator/loki/feature/animals/command/AnimalFactCommand.java new file mode 100644 index 0000000..5f994f8 --- /dev/null +++ b/src/main/java/creator/loki/feature/animals/command/AnimalFactCommand.java @@ -0,0 +1,131 @@ +package creator.loki.feature.animals.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import creator.loki.embed.EmbedTemplates; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class AnimalFactCommand implements Command { + + public enum Flavor { + CAT("catfact", "Случайный факт о коте", "🐱", "https://catfact.ninja/fact"), + DOG("dogfact", "Случайный факт о собаке", "🐶", "https://dog-api.kinduff.com/api/facts"); + + private final String name; + private final String description; + private final String emoji; + private final String apiUrl; + + Flavor(final String name, final String description, final String emoji, final String apiUrl) { + this.name = name; + this.description = description; + this.emoji = emoji; + this.apiUrl = apiUrl; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getEmoji() { + return emoji; + } + + public String getApiUrl() { + return apiUrl; + } + } + + private static final Color EMBED_COLOR = new Color(88, 101, 242); + + private final Flavor flavor; + private final OkHttpClient client; + + public AnimalFactCommand(final Flavor flavor) { + this.flavor = flavor; + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return flavor.getName(); + } + + @Override + public String getDescription() { + return flavor.getDescription(); + } + + @Override + public String getUsage() { + return flavor.getName(); + } + + @Override + public String getCategory() { + return CommandCategories.ANIMALS; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🔍 Ищу факт...") + .queue( + loadingMsg -> { + try { + String fact = fetchFact(); + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle(flavor.getEmoji() + " Факт") + .withDescription(fact) + .withColor(EMBED_COLOR) + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg + .editMessageEmbeds( + EmbedTemplates.error("Ошибка", "Не удалось получить факт, попробуй позже.")) + .queue(); + } + }); + } + + private String fetchFact() throws IOException { + Request request = new Request.Builder().url(flavor.getApiUrl()).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("API вернул " + response.code()); + } + JsonObject json = JsonParser.parseString(response.body().string()).getAsJsonObject(); + if (flavor == Flavor.CAT) { + return json.get("fact").getAsString(); + } + JsonArray facts = json.getAsJsonArray("facts"); + if (facts == null || facts.isEmpty()) { + throw new IOException("API вернул пустой список фактов"); + } + return facts.get(0).getAsString(); + } + } +} diff --git a/src/main/java/creator/loki/feature/entertainment/commands/MemeCommand.java b/src/main/java/creator/loki/feature/entertainment/commands/MemeCommand.java index 3d2eab2..a24c126 100644 --- a/src/main/java/creator/loki/feature/entertainment/commands/MemeCommand.java +++ b/src/main/java/creator/loki/feature/entertainment/commands/MemeCommand.java @@ -39,7 +39,7 @@ public class MemeCommand implements Command { @Override public String getUsage() { - return "meme"; + return "meme [субреддит]"; } @Override @@ -55,7 +55,8 @@ public class MemeCommand implements Command { .queue( loadingMsg -> { try { - JsonObject json = fetchMeme(); + String sub = context.getArgs().isEmpty() ? null : context.getArgs().get(0); + JsonObject json = fetchMeme(sub); String memeUrl = json.get("url").getAsString(); String title = json.get("title").getAsString(); String subreddit = json.get("subreddit").getAsString(); @@ -76,11 +77,13 @@ public class MemeCommand implements Command { }); } - private JsonObject fetchMeme() throws IOException { - Request request = new Request.Builder().url(MEME_API).get().build(); + private JsonObject fetchMeme(final String subreddit) throws IOException { + String url = subreddit == null ? MEME_API : MEME_API + "/" + subreddit; + Request request = new Request.Builder().url(url).get().build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful() || response.body() == null) { - throw new IOException("meme-api вернул " + response.code()); + throw new IOException( + subreddit == null ? "meme-api вернул " + response.code() : "сабреддит не найден"); } return JsonParser.parseString(response.body().string()).getAsJsonObject(); } diff --git a/src/main/java/creator/loki/feature/fun/FunCommandRegistrar.java b/src/main/java/creator/loki/feature/fun/FunCommandRegistrar.java new file mode 100644 index 0000000..19e6fd1 --- /dev/null +++ b/src/main/java/creator/loki/feature/fun/FunCommandRegistrar.java @@ -0,0 +1,16 @@ +package creator.loki.feature.fun; + +import creator.loki.core.registry.CommandRegistrar; +import creator.loki.core.registry.CommandRegistry; +import creator.loki.feature.fun.command.ChuckCommand; +import creator.loki.feature.fun.command.InspireCommand; +import creator.loki.feature.fun.command.JokeCommand; + +public class FunCommandRegistrar implements CommandRegistrar { + @Override + public void register(final CommandRegistry registry) { + registry.register(new JokeCommand()); + registry.register(new ChuckCommand()); + registry.register(new InspireCommand()); + } +} diff --git a/src/main/java/creator/loki/feature/fun/FunModule.java b/src/main/java/creator/loki/feature/fun/FunModule.java new file mode 100644 index 0000000..e69d688 --- /dev/null +++ b/src/main/java/creator/loki/feature/fun/FunModule.java @@ -0,0 +1,14 @@ +package creator.loki.feature.fun; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import creator.loki.core.registry.CommandRegistrar; + +public class FunModule extends AbstractModule { + @Override + protected void configure() { + Multibinder.newSetBinder(binder(), CommandRegistrar.class) + .addBinding() + .to(FunCommandRegistrar.class); + } +} diff --git a/src/main/java/creator/loki/feature/fun/command/ChuckCommand.java b/src/main/java/creator/loki/feature/fun/command/ChuckCommand.java new file mode 100644 index 0000000..93b81c0 --- /dev/null +++ b/src/main/java/creator/loki/feature/fun/command/ChuckCommand.java @@ -0,0 +1,84 @@ +package creator.loki.feature.fun.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import creator.loki.embed.EmbedTemplates; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class ChuckCommand implements Command { + private static final String CHUCK_API = "https://api.chucknorris.io/jokes/random"; + private static final Color EMBED_COLOR = new Color(189, 8, 28); + + private final OkHttpClient client; + + public ChuckCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "chuck"; + } + + @Override + public String getDescription() { + return "Случайная шутка про Чака Норриса"; + } + + @Override + public String getCategory() { + return CommandCategories.ENTERTAINMENT; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🔍 Ищу шутку про Чака Норриса...") + .queue( + loadingMsg -> { + try { + JsonObject json = fetchJoke(); + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle("🥋 Чак Норрис") + .withDescription(json.get("value").getAsString()) + .withColor(EMBED_COLOR) + .withThumbnail("https://api.chucknorris.io/img/avatar/chuck-norris.png") + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg + .editMessageEmbeds( + EmbedTemplates.error( + "Ошибка", "Не удалось загрузить шутку, попробуй позже.")) + .queue(); + } + }); + } + + private JsonObject fetchJoke() throws IOException { + Request request = new Request.Builder().url(CHUCK_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("Chuck Norris API вернул " + response.code()); + } + return JsonParser.parseString(response.body().string()).getAsJsonObject(); + } + } +} diff --git a/src/main/java/creator/loki/feature/fun/command/InspireCommand.java b/src/main/java/creator/loki/feature/fun/command/InspireCommand.java new file mode 100644 index 0000000..f36fbf4 --- /dev/null +++ b/src/main/java/creator/loki/feature/fun/command/InspireCommand.java @@ -0,0 +1,89 @@ +package creator.loki.feature.fun.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import creator.loki.embed.EmbedTemplates; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class InspireCommand implements Command { + private static final String INSPIRE_API = "https://zenquotes.io/api/random"; + private static final Color EMBED_COLOR = new Color(39, 174, 96); + + private final OkHttpClient client; + + public InspireCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "inspire"; + } + + @Override + public String getDescription() { + return "Случайная вдохновляющая цитата"; + } + + @Override + public String getCategory() { + return CommandCategories.ENTERTAINMENT; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🔍 Ищу цитату...") + .queue( + loadingMsg -> { + try { + JsonObject quote = fetchQuote(); + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle("✨ Цитата") + .withDescription("«" + quote.get("q").getAsString() + "»") + .withColor(EMBED_COLOR) + .withFooter("— " + quote.get("a").getAsString()) + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg + .editMessageEmbeds( + EmbedTemplates.error( + "Ошибка", "Не удалось загрузить цитату, попробуй позже.")) + .queue(); + } + }); + } + + private JsonObject fetchQuote() throws IOException { + Request request = new Request.Builder().url(INSPIRE_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("ZenQuotes вернул " + response.code()); + } + JsonArray array = JsonParser.parseString(response.body().string()).getAsJsonArray(); + if (array.isEmpty()) { + throw new IOException("ZenQuotes вернул пустой список"); + } + return array.get(0).getAsJsonObject(); + } + } +} diff --git a/src/main/java/creator/loki/feature/fun/command/JokeCommand.java b/src/main/java/creator/loki/feature/fun/command/JokeCommand.java new file mode 100644 index 0000000..7c5d1c1 --- /dev/null +++ b/src/main/java/creator/loki/feature/fun/command/JokeCommand.java @@ -0,0 +1,90 @@ +package creator.loki.feature.fun.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import creator.loki.embed.EmbedTemplates; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class JokeCommand implements Command { + private static final String JOKE_API = "https://v2.jokeapi.dev/joke/Any"; + private static final Color EMBED_COLOR = new Color(255, 193, 7); + + private final OkHttpClient client; + + public JokeCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "joke"; + } + + @Override + public String getDescription() { + return "Случайная шутка"; + } + + @Override + public String getCategory() { + return CommandCategories.ENTERTAINMENT; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🔍 Ищу шутку...") + .queue( + loadingMsg -> { + try { + JsonObject json = fetchJoke(); + String text = + json.get("type").getAsString().equals("twopart") + ? json.get("setup").getAsString() + + "\n\n" + + json.get("delivery").getAsString() + : json.get("joke").getAsString(); + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle("🤣 Шутка") + .withDescription(text) + .withColor(EMBED_COLOR) + .withFooter("Категория: " + json.get("category").getAsString()) + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg + .editMessageEmbeds( + EmbedTemplates.error( + "Ошибка", "Не удалось загрузить шутку, попробуй позже.")) + .queue(); + } + }); + } + + private JsonObject fetchJoke() throws IOException { + Request request = new Request.Builder().url(JOKE_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("JokeAPI вернул " + response.code()); + } + return JsonParser.parseString(response.body().string()).getAsJsonObject(); + } + } +} diff --git a/src/main/java/creator/loki/feature/space/SpaceCommandRegistrar.java b/src/main/java/creator/loki/feature/space/SpaceCommandRegistrar.java new file mode 100644 index 0000000..5678627 --- /dev/null +++ b/src/main/java/creator/loki/feature/space/SpaceCommandRegistrar.java @@ -0,0 +1,18 @@ +package creator.loki.feature.space; + +import creator.loki.core.registry.CommandRegistrar; +import creator.loki.core.registry.CommandRegistry; +import creator.loki.feature.space.command.ApodCommand; +import creator.loki.feature.space.command.IssCommand; +import creator.loki.feature.space.command.SpaceCommand; +import creator.loki.feature.space.command.XkcdCommand; + +public class SpaceCommandRegistrar implements CommandRegistrar { + @Override + public void register(final CommandRegistry registry) { + registry.register(new ApodCommand()); + registry.register(new IssCommand()); + registry.register(new SpaceCommand()); + registry.register(new XkcdCommand()); + } +} diff --git a/src/main/java/creator/loki/feature/space/SpaceModule.java b/src/main/java/creator/loki/feature/space/SpaceModule.java new file mode 100644 index 0000000..1fd2f36 --- /dev/null +++ b/src/main/java/creator/loki/feature/space/SpaceModule.java @@ -0,0 +1,14 @@ +package creator.loki.feature.space; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import creator.loki.core.registry.CommandRegistrar; + +public class SpaceModule extends AbstractModule { + @Override + protected void configure() { + Multibinder.newSetBinder(binder(), CommandRegistrar.class) + .addBinding() + .to(SpaceCommandRegistrar.class); + } +} diff --git a/src/main/java/creator/loki/feature/space/command/ApodCommand.java b/src/main/java/creator/loki/feature/space/command/ApodCommand.java new file mode 100644 index 0000000..b0dabc7 --- /dev/null +++ b/src/main/java/creator/loki/feature/space/command/ApodCommand.java @@ -0,0 +1,98 @@ +package creator.loki.feature.space.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class ApodCommand implements Command { + private static final String APOD_API = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"; + private static final int DESCRIPTION_LIMIT = 200; + private static final Color EMBED_COLOR = new Color(25, 25, 112); + + private final OkHttpClient client; + + public ApodCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "apod"; + } + + @Override + public String getDescription() { + return "Астрономическая картинка дня (NASA APOD)"; + } + + @Override + public String getCategory() { + return CommandCategories.INFORMATION; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🔍 Загружаю астрономическую картинку дня...") + .queue( + loadingMsg -> { + try { + JsonObject json = fetchApod(); + String title = json.get("title").getAsString(); + String image = + json.has("hdurl") + ? json.get("hdurl").getAsString() + : json.get("url").getAsString(); + String explanation = truncate(json.get("explanation").getAsString()); + String date = json.get("date").getAsString(); + + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle("🌌 " + title) + .withImage(image) + .withDescription(explanation) + .withColor(EMBED_COLOR) + .withFooter("NASA APOD · " + date) + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg + .editMessage("❌ Не удалось загрузить картинку дня: " + e.getMessage()) + .queue(); + } + }); + } + + private JsonObject fetchApod() throws IOException { + Request request = new Request.Builder().url(APOD_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("NASA APOD вернул " + response.code()); + } + return JsonParser.parseString(response.body().string()).getAsJsonObject(); + } + } + + private static String truncate(final String text) { + if (text.length() <= DESCRIPTION_LIMIT) { + return text; + } + return text.substring(0, DESCRIPTION_LIMIT).trim() + "..."; + } +} diff --git a/src/main/java/creator/loki/feature/space/command/IssCommand.java b/src/main/java/creator/loki/feature/space/command/IssCommand.java new file mode 100644 index 0000000..22a2e55 --- /dev/null +++ b/src/main/java/creator/loki/feature/space/command/IssCommand.java @@ -0,0 +1,107 @@ +package creator.loki.feature.space.command; + +import java.awt.Color; +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class IssCommand implements Command { + private static final String ISS_API = "https://api.wheretheiss.at/v1/satellites/25544"; + private static final Color EMBED_COLOR = new Color(52, 152, 219); + private static final DateTimeFormatter TIME_FORMAT = + DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss").withLocale(Locale.ROOT); + + private final OkHttpClient client; + + public IssCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "iss"; + } + + @Override + public String getDescription() { + return "Текущее положение МКС на орбите"; + } + + @Override + public String getCategory() { + return CommandCategories.INFORMATION; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🛰️ Получаю данные о МКС...") + .queue( + loadingMsg -> { + try { + JsonObject json = fetchIss(); + String latitude = formatCoordinate(json.get("latitude").getAsDouble(), "N", "S"); + String longitude = formatCoordinate(json.get("longitude").getAsDouble(), "E", "W"); + String altitude = + String.format(Locale.ROOT, "%.0f км", json.get("altitude").getAsDouble()); + String velocity = + String.format(Locale.ROOT, "%.0f км/ч", json.get("velocity").getAsDouble()); + String time = + TIME_FORMAT.format( + Instant.ofEpochSecond(json.get("timestamp").getAsLong()) + .atZone(ZoneOffset.UTC)); + + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle("🛰️ Международная космическая станция") + .addField("Широта", latitude, true) + .addField("Долгота", longitude, true) + .addField("Высота", altitude, true) + .addField("Скорость", velocity, true) + .withColor(EMBED_COLOR) + .withFooter("Время (UTC): " + time) + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg + .editMessage("❌ Не удалось получить данные о МКС: " + e.getMessage()) + .queue(); + } + }); + } + + private JsonObject fetchIss() throws IOException { + Request request = new Request.Builder().url(ISS_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("Where The ISS At вернул " + response.code()); + } + return JsonParser.parseString(response.body().string()).getAsJsonObject(); + } + } + + private static String formatCoordinate( + final double value, final String positive, final String negative) { + char sign = value >= 0 ? positive.charAt(0) : negative.charAt(0); + return String.format(Locale.ROOT, "%.4f° %s", Math.abs(value), sign); + } +} diff --git a/src/main/java/creator/loki/feature/space/command/SpaceCommand.java b/src/main/java/creator/loki/feature/space/command/SpaceCommand.java new file mode 100644 index 0000000..cb4a4ba --- /dev/null +++ b/src/main/java/creator/loki/feature/space/command/SpaceCommand.java @@ -0,0 +1,103 @@ +package creator.loki.feature.space.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class SpaceCommand implements Command { + private static final String ASTROS_API = "http://api.open-notify.org/astros.json"; + private static final int MAX_NAMES = 40; + private static final int FIELD_SIZE = 10; + private static final Color EMBED_COLOR = new Color(70, 70, 130); + + private final OkHttpClient client; + + public SpaceCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "space"; + } + + @Override + public String getDescription() { + return "Кто сейчас находится в космосе"; + } + + @Override + public String getCategory() { + return CommandCategories.INFORMATION; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🚀 Узнаю, кто сейчас в космосе...") + .queue( + loadingMsg -> { + try { + JsonObject json = fetchAstros(); + int number = json.get("number").getAsInt(); + JsonArray people = json.getAsJsonArray("people"); + + EmbedFactory embed = + EmbedFactory.create() + .withTitle("🚀 Люди в космосе") + .withDescription("Сейчас на орбите: " + number) + .withColor(EMBED_COLOR); + + int shown = Math.min(people.size(), MAX_NAMES); + for (int start = 0; start < shown; start += FIELD_SIZE) { + int end = Math.min(start + FIELD_SIZE, shown); + StringBuilder fieldValue = new StringBuilder(); + for (int i = start; i < end; i++) { + JsonObject person = people.get(i).getAsJsonObject(); + fieldValue + .append(person.get("name").getAsString()) + .append(" (") + .append(person.get("craft").getAsString()) + .append(")\n"); + } + embed.addField( + "Экипаж " + (start / FIELD_SIZE + 1), fieldValue.toString(), false); + } + if (people.size() > shown) { + embed.addField( + "…", "И ещё " + (people.size() - shown) + " человек на орбите", false); + } + + loadingMsg.editMessageEmbeds(embed.build()).queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg.editMessage("❌ Не удалось получить данные: " + e.getMessage()).queue(); + } + }); + } + + private JsonObject fetchAstros() throws IOException { + Request request = new Request.Builder().url(ASTROS_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("Open Notify вернул " + response.code()); + } + return JsonParser.parseString(response.body().string()).getAsJsonObject(); + } + } +} diff --git a/src/main/java/creator/loki/feature/space/command/XkcdCommand.java b/src/main/java/creator/loki/feature/space/command/XkcdCommand.java new file mode 100644 index 0000000..ef9231b --- /dev/null +++ b/src/main/java/creator/loki/feature/space/command/XkcdCommand.java @@ -0,0 +1,91 @@ +package creator.loki.feature.space.command; + +import java.awt.Color; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import creator.loki.command.Command; +import creator.loki.command.CommandCategories; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedFactory; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class XkcdCommand implements Command { + private static final String XKCD_API = "https://xkcd.com/info.0.json"; + private static final Color EMBED_COLOR = new Color(150, 150, 150); + + private final OkHttpClient client; + + public XkcdCommand() { + this.client = + new OkHttpClient.Builder() + .connectTimeout(6, TimeUnit.SECONDS) + .readTimeout(8, TimeUnit.SECONDS) + .build(); + } + + @Override + public String getName() { + return "xkcd"; + } + + @Override + public String getDescription() { + return "Последний комикс xkcd"; + } + + @Override + public String getCategory() { + return CommandCategories.INFORMATION; + } + + @Override + public void execute(final CommandContext context) { + context + .getChannel() + .sendMessage("🧐 Загружаю свежий xkcd...") + .queue( + loadingMsg -> { + try { + JsonObject json = fetchXkcd(); + int num = json.get("num").getAsInt(); + String title = json.get("title").getAsString(); + String image = json.get("img").getAsString(); + String alt = json.get("alt").getAsString(); + String date = + json.get("day").getAsString() + + "." + + json.get("month").getAsString() + + "." + + json.get("year").getAsString(); + + loadingMsg + .editMessageEmbeds( + EmbedFactory.create() + .withTitle("📐 " + title) + .withImage(image) + .withDescription(alt) + .withColor(EMBED_COLOR) + .withFooter("#" + num + " · " + date) + .build()) + .queue(); + } catch (IOException | IllegalStateException e) { + loadingMsg.editMessage("❌ Не удалось загрузить xkcd: " + e.getMessage()).queue(); + } + }); + } + + private JsonObject fetchXkcd() throws IOException { + Request request = new Request.Builder().url(XKCD_API).get().build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("xkcd вернул " + response.code()); + } + return JsonParser.parseString(response.body().string()).getAsJsonObject(); + } + } +}