Skip to content

Commit

Permalink
Expand the !wiki command to enable searching the wiki.
Browse files Browse the repository at this point in the history
  • Loading branch information
Saladoc committed Sep 11, 2018
1 parent 8980020 commit 3ee8e3a
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 7 deletions.
114 changes: 107 additions & 7 deletions src/main/java/org/javacord/bot/commands/WikiCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,123 @@

import de.btobastian.sdcf4j.Command;
import de.btobastian.sdcf4j.CommandExecutor;
import org.javacord.api.DiscordApi;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.embed.EmbedBuilder;
import org.javacord.api.util.logging.ExceptionLogger;
import org.javacord.bot.Constants;
import org.javacord.bot.util.wiki.parser.WikiPage;
import org.javacord.bot.util.wiki.parser.WikiParser;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* The !wiki command which is used to link to Javacord's wiki.
*/
public class WikiCommand implements CommandExecutor {

@Command(aliases = {"!wiki"}, async = true)
public void onCommand(TextChannel channel) {
EmbedBuilder embed = new EmbedBuilder()
.setTitle("Javacord Wiki")
.setDescription("https://javacord.org/wiki")
.setThumbnail(getClass().getClassLoader().getResourceAsStream("javacord3_icon.png"), "png")
.setColor(Constants.JAVACORD_ORANGE);
channel.sendMessage(embed).join();
public void onCommand(TextChannel channel, String[] args) {
try {
if (args.length == 0) { // Just an overview
EmbedBuilder embed = new EmbedBuilder()
.setTitle("Javacord Wiki")
.setUrl(WikiParser.BASE_URL + "/wiki")
.addField("Hint", "You can search the wiki using `!wiki [title|full] <search>")
.setThumbnail(getClass().getClassLoader().getResourceAsStream("javacord3_icon.png"), "png")
.setColor(Constants.JAVACORD_ORANGE);
channel.sendMessage(embed).join();
} else {
EmbedBuilder embed = new EmbedBuilder()
.setThumbnail(getClass().getClassLoader().getResourceAsStream("javacord3_icon.png"), "png")
.setColor(Constants.JAVACORD_ORANGE);
String searchString = String.join(" ", Arrays.copyOfRange(args, 1, args.length)).toLowerCase();
if (args[0].matches("(page|p|title|t)")) {
populatePages(channel.getApi(), embed, titleOnly(searchString));
} else if (args[0].matches("(full|f|content|c)")) {
populatePages(channel.getApi(), embed,
titleOnly(searchString).or(keywordsOnly(searchString)).or(contentOnly(searchString)));
} else {
searchString = String.join(" ", Arrays.copyOfRange(args, 0, args.length)).toLowerCase();
populatePages(channel.getApi(), embed, titleOnly(searchString).or(keywordsOnly(searchString)));
}
channel.sendMessage(embed).join();
}
} catch (Throwable t) {
channel.sendMessage("Something went wrong: ```" + ExceptionLogger.unwrapThrowable(t).getMessage() + "```").join();
// Throw the caught exception again. The sdcf4j will log it.
throw t;
}
}

private Predicate<WikiPage> titleOnly(String searchString) {
return p -> p.getTitle().toLowerCase().contains(searchString);
}

private Predicate<WikiPage> keywordsOnly(String searchString) {
return p -> Arrays.stream(p.getKeywords())
.map(String::toLowerCase)
.anyMatch(k -> k.contains(searchString));
}

private Predicate<WikiPage> contentOnly(String searchString) {
return p -> p.getContent().toLowerCase().contains(searchString);
}


private void populatePages(DiscordApi api, EmbedBuilder embed, Predicate<WikiPage> searchCriteria) {
List<WikiPage> pages = new WikiParser(api)
.getPages().join().stream()
.filter(searchCriteria)
.sorted()
.collect(Collectors.toList());
if (pages.isEmpty()) {
embed.setTitle("Javacord Wiki");
embed.setUrl(WikiParser.BASE_URL + "/wiki/");
embed.setDescription("No pages found. Maybe try another search.");
embed.addField("Standard Search", "Use `!wiki <search>` to search page titles and keywords.");
embed.addField("Title Search", "Use `!wiki [page|p|title|t] <search>` to exclusively search page titles.");
embed.addField("Full Search", "Use `!wiki [full|f|content|c] <search>` to perform a full search.");
} else if (pages.size() == 1) {
WikiPage page = pages.get(0);
embed.setUrl(WikiParser.BASE_URL + page.getUrl());
embed.setTitle("Wiki: " + page.getTitle());
String cleanedDescription = page.getContent().replaceAll("<[^>]+>", "").trim();
int length = 0;
int sentences = 0;
while (length < 600 && sentences < 3) {
int tmpLength = cleanedDescription.indexOf(". ", length);
length = (tmpLength > length) ? tmpLength : cleanedDescription.indexOf(".\n");

sentences++;
}
embed.setDescription(cleanedDescription.substring(0, length+1));
} else {
embed.setTitle("Javacord Wiki");
embed.setUrl(WikiParser.BASE_URL + "/wiki/");

StringBuilder builder = new StringBuilder();
int counter = 0;
for (WikiPage page : pages) {
builder.append("• [")
.append(page.getTitle())
.append("](")
.append(WikiParser.BASE_URL)
.append(page.getUrl())
.append(") \n");
counter++;
if (builder.length() > 1950) { // Prevent hitting the description size limit
break;
}
}
if (pages.size() - counter > 0) {
builder.append("and ").append(pages.size() - counter).append(" more ...");
}
embed.setDescription(builder.toString());
}
}

}
38 changes: 38 additions & 0 deletions src/main/java/org/javacord/bot/util/wiki/parser/WikiPage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.javacord.bot.util.wiki.parser;

public class WikiPage implements Comparable<WikiPage> {

private final String title;
private final String[] keywords;
private final String url;
private final String content;

public WikiPage(String title, String[] keywords, String url, String content) {
this.title = title;
this.keywords = keywords;
this.url = url;
this.content = content;
}

public String getTitle() {
return title;
}

public String[] getKeywords() {
return keywords;
}

public String getUrl() {
return url;
}

public String getContent() {
return content;
}

@Override
public int compareTo(WikiPage that) {
return this.title.compareTo(that.title);
}

}
91 changes: 91 additions & 0 deletions src/main/java/org/javacord/bot/util/wiki/parser/WikiParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.javacord.bot.util.wiki.parser;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.javacord.api.DiscordApi;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

public class WikiParser {

public static final String API_URL = "https://javacord.org/api/wiki.json";
public static final String BASE_URL = "https://javacord.org"; // the /wiki/ part of the url will be returned by the API

private static final OkHttpClient client = new OkHttpClient();
private static final ObjectMapper mapper = new ObjectMapper();

private final DiscordApi discordAPi;
private final String apiUrl;
private final String baseUrl;

public WikiParser(DiscordApi api) {
this(api, API_URL, BASE_URL);
}

public WikiParser(DiscordApi api, String apiUrl, String baseUrl) {
this.discordAPi = api;
this.apiUrl = apiUrl;
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length()-1) : baseUrl;
}

public CompletableFuture<Set<WikiPage>> getPages() {
return CompletableFuture.supplyAsync(() -> {
try {
return getPagesBlocking();
} catch (Throwable t) {
throw new CompletionException(t);
}
}, discordAPi.getThreadPool().getExecutorService());
}

private Set<WikiPage> getPagesBlocking() throws IOException {
Request request = new Request.Builder()
.url(apiUrl)
.build();

Response response = client.newCall(request).execute();
ResponseBody body = response.body();
Set<WikiPage> pages = new HashSet<>();
if (body == null) {
return pages;
}
JsonNode array = mapper.readTree(body.charStream());
if (!array.isArray()) {
throw new AssertionError("Format of Wiki page list not as expected");
}
for (JsonNode node : array) {
if (node.has("title") && node.has("keywords") && node.has("url") && node.has("content")) {
pages.add(new WikiPage(
node.get("title").asText(),
asStringArray(node.get("keywords")),
node.get("url").asText(),
node.get("content").asText()
));
} else {
throw new AssertionError("Format of Wiki page list not as expected");
}
}
return pages;
}

private String[] asStringArray(JsonNode arrayNode) {
if (!arrayNode.isArray()) {
return new String[] {};
}
String[] result = new String[arrayNode.size()];
int i = 0;
for (JsonNode node : arrayNode) {
result[i++] = node.asText();
}
return result;
}

}

0 comments on commit 3ee8e3a

Please sign in to comment.