diff --git a/src/main/java/net/earthcomputer/clientcommands/command/WikiCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/WikiCommand.java index 5d6df37e..cce335e7 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/WikiCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/WikiCommand.java @@ -5,7 +5,14 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import net.earthcomputer.clientcommands.features.WikiRetriever; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; + +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import static com.mojang.brigadier.arguments.StringArgumentType.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; @@ -13,15 +20,39 @@ public class WikiCommand { private static final SimpleCommandExceptionType FAILED_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.cwiki.failed")); + private static final String WIKI_HOST = "https://minecraft.wiki/"; + private static final String WIKI_ARTICLE = WIKI_HOST + "w/%s"; + + public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("cwiki") .then(argument("page", greedyString()) .executes(ctx -> displayWikiPage(ctx.getSource(), getString(ctx, "page"))))); } + @Nullable + private static Component getLinkComponent(String page) { + String title = WikiRetriever.searchArticleName(page); + if (title == null) { + return null; + } + + String pageName = URLEncoder.encode(title, StandardCharsets.UTF_8).replace('+', '_'); + String url = String.format(WIKI_ARTICLE, pageName); + URI uri = URI.create(url); + + ClickEvent clickEvent = new ClickEvent.OpenUrl(uri); + + return Component.translatable("commands.cwiki.openArticle") + .withStyle(style -> style + .withClickEvent(clickEvent) + .withColor(ChatFormatting.GREEN) + .withUnderlined(true) + ); + } + private static int displayWikiPage(FabricClientCommandSource source, String page) throws CommandSyntaxException { String content = WikiRetriever.getWikiSummary(page); - if (content == null) { throw FAILED_EXCEPTION.create(); } @@ -31,6 +62,13 @@ private static int displayWikiPage(FabricClientCommandSource source, String page source.sendFeedback(Component.literal(line)); } + Component link = getLinkComponent(page); + if (link == null) { + throw FAILED_EXCEPTION.create(); + } + + source.sendFeedback(link); + return content.length(); } diff --git a/src/main/java/net/earthcomputer/clientcommands/features/WikiRetriever.java b/src/main/java/net/earthcomputer/clientcommands/features/WikiRetriever.java index 64a11f2b..ec13f25e 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/WikiRetriever.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/WikiRetriever.java @@ -13,14 +13,18 @@ import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class WikiRetriever { private static final String WIKI_HOST = "https://minecraft.wiki/"; + private static final String SEARCH_QUERY = WIKI_HOST + "api.php?action=query&list=search&srlimit=1&srprop=snippet&format=json&srsearch=%s"; private static final String PAGE_SUMMARY_QUERY = WIKI_HOST + "api.php?action=query&prop=extracts&exintro=true&format=json&titles=%s"; private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<\\s*(/)?\\s*(\\w+).*?>||\n", Pattern.DOTALL); private static final ChatFormatting CODE_COLOR = ChatFormatting.DARK_GREEN; @@ -184,19 +188,78 @@ public static String decode(String html) { } @Nullable - public static String getWikiSummary(String pageName) { + public static QueryResult getResult(URL url) { + QueryResult result; + try (InputStream in = url.openConnection().getInputStream()) { + result = GSON.fromJson(new InputStreamReader(in), QueryResult.class); + } catch (IOException e) { + return null; + } + return result; + } + + @Nullable + public static URL buildURL(String page, String query) { + String result = Arrays.stream(page.split("\\s+")) + .map(w -> w.substring(0, 1).toUpperCase(Locale.ROOT) + + w.substring(1).toLowerCase(Locale.ROOT)) + .collect(Collectors.joining(" ")); + + URL url; try { - String encodedPage = URLEncoder.encode(pageName, StandardCharsets.UTF_8); - url = URI.create(String.format(PAGE_SUMMARY_QUERY, encodedPage)).toURL(); + String encodedPage = URLEncoder.encode(result, StandardCharsets.UTF_8); + url = URI.create(String.format(query, encodedPage)).toURL(); } catch (MalformedURLException e) { return null; } + return url; + } + + @Nullable + public static String searchArticleName(String pageInput){ + URL url = buildURL(pageInput.trim(), SEARCH_QUERY); + if (url == null) { + return null; + } - QueryResult result; - try (InputStream in = url.openConnection().getInputStream()) { - result = GSON.fromJson(new InputStreamReader(in), QueryResult.class); - } catch (IOException e) { + QueryResult result = getResult(url); + if (result == null) { + return null; + } + + var query = result.query; + if (query == null) { + return null; + } + + var search = query.search; + var searchinfo = query.searchinfo; + if (search == null || search.isEmpty() || searchinfo == null || searchinfo.totalhits == 0) { + return null; + } + + if (searchinfo.suggestion != null) { + return searchinfo.suggestion; + } + + return query.search.getFirst().title; + } + + @Nullable + public static String getWikiSummary(String pageName) { + String title = searchArticleName(pageName); + if (title == null) { + return null; + } + + URL url = buildURL(title, PAGE_SUMMARY_QUERY); + if (url == null) { + return null; + } + + QueryResult result = getResult(url); + if (result == null) { return null; } @@ -212,12 +275,12 @@ public static String getWikiSummary(String pageName) { } @SuppressWarnings("unused") - private static class QueryResult { + public static class QueryResult { @Nullable public String batchcomplete; @Nullable public Query query; - private static class Query { + public static class Query { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Nullable private Map pages; @@ -230,6 +293,21 @@ private static class Page { @Nullable public String missing; } + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + @Nullable + private List search; + private static class Search { + @Nullable + public String title; + } + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + @Nullable + private SearchInfo searchinfo; + private static class SearchInfo { + public int totalhits; + @Nullable + public String suggestion; + } } } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 943ba578..54f0f69b 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -317,6 +317,7 @@ "commands.cwiki.failed": "Could not retrieve wiki content", "commands.cwiki.noContent": "There is no introductory paragraph in that article", + "commands.cwiki.openArticle": "[Open article]", "connectFourGame.draw": "Draw!", "connectFourGame.lost": "Lost!",