Added Credits GUI for addons and BentoBox

Implements https://github.com/BentoBoxWorld/BentoBox/issues/993
This commit is contained in:
Florian CUNY 2019-10-24 13:56:06 +02:00
parent 856572941e
commit 551975d6c9
7 changed files with 321 additions and 85 deletions

View File

@ -1,24 +1,28 @@
package world.bentobox.bentobox.managers;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import io.github.TheBusyBiscuit.GitHubWebAPI4Java.GitHubWebAPI;
import io.github.TheBusyBiscuit.GitHubWebAPI4Java.objects.repositories.GitHubContributor;
import io.github.TheBusyBiscuit.GitHubWebAPI4Java.objects.repositories.GitHubRepository;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.web.catalog.CatalogEntry;
import world.bentobox.bentobox.web.credits.Contributor;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.gson.JsonParseException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.TheBusyBiscuit.GitHubWebAPI4Java.GitHubWebAPI;
import io.github.TheBusyBiscuit.GitHubWebAPI4Java.objects.repositories.GitHubRepository;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.web.catalog.CatalogEntry;
import java.util.stream.Collectors;
/**
* Handles web-related stuff.
@ -32,11 +36,13 @@ public class WebManager {
private @Nullable GitHubWebAPI gitHub;
private @NonNull List<CatalogEntry> addonsCatalog;
private @NonNull List<CatalogEntry> gamemodesCatalog;
private @NonNull Map<String, List<Contributor>> contributors;
public WebManager(@NonNull BentoBox plugin) {
this.plugin = plugin;
this.addonsCatalog = new ArrayList<>();
this.gamemodesCatalog = new ArrayList<>();
this.contributors = new HashMap<>();
// Setup the GitHub connection
if (plugin.getSettings().isGithubDownloadData()) {
@ -45,92 +51,128 @@ public class WebManager {
long connectionInterval = plugin.getSettings().getGithubConnectionInterval() * 20L * 60L;
if (connectionInterval <= 0) {
// If below 0, it means we shouldn't run this as a repeating task.
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> requestGitHubData(true), 20L);
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, this::requestGitHubData, 20L);
} else {
// Set connection interval to be at least 60 minutes.
connectionInterval = Math.max(connectionInterval, 60 * 20 * 60L);
plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, () -> requestGitHubData(true), 20L, connectionInterval);
plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, this::requestGitHubData, 20L, connectionInterval);
}
}
}
public void requestGitHubData(boolean clearCache) {
public void requestGitHubData() {
getGitHub().ifPresent(gh -> {
if (plugin.getSettings().isLogGithubDownloadData()) {
plugin.log("Downloading data from GitHub...");
plugin.log("Updating the Catalog...");
}
GitHubRepository repo;
GitHubRepository weblinkRepo;
try {
repo = new GitHubRepository(gh, "BentoBoxWorld/weblink");
weblinkRepo = new GitHubRepository(gh, "BentoBoxWorld/weblink");
} catch (Exception e) {
plugin.logError("An unhandled exception occurred when trying to connect to GitHub...");
plugin.logError("An unhandled exception occurred when connecting to the GitHub weblink..");
plugin.logStacktrace(e);
// Stop the execution of the method right away.
return;
weblinkRepo = null;
}
if (weblinkRepo != null) {
// Downloading the data
String tagsContent = getContent(weblinkRepo, "catalog/tags.json");
String topicsContent = getContent(weblinkRepo, "catalog/topics.json");
String catalogContent = getContent(weblinkRepo, "catalog/catalog.json");
/* Parsing the data */
parseCatalogContent(tagsContent, topicsContent, catalogContent);
}
if (plugin.getSettings().isLogGithubDownloadData()) {
plugin.log("Updating Contributors information...");
}
/* Download the contributors */
List<String> repositories = new ArrayList<>();
// Gather all the repositories of installed addons and or catalog entries.
repositories.add("BentoBoxWorld/BentoBox");
repositories.addAll(plugin.getAddonsManager().getEnabledAddons()
.stream().map(addon -> addon.getDescription().getRepository())
.filter(repo -> !repo.isEmpty())
.collect(Collectors.toList()));
repositories.addAll(addonsCatalog.stream().map(CatalogEntry::getRepository)
.filter(repo -> !repositories.contains(repo))
.collect(Collectors.toList()));
repositories.addAll(gamemodesCatalog.stream().map(CatalogEntry::getRepository)
.filter(repo -> !repositories.contains(repo))
.collect(Collectors.toList()));
for (String repository : repositories) {
GitHubRepository addonRepo;
try {
addonRepo = new GitHubRepository(gh, repository);
} catch (Exception e) {
plugin.logError("An unhandled exception occurred when gathering contributors data from the '" + repository + "' repository...");
plugin.logStacktrace(e);
addonRepo = null;
}
if (addonRepo != null) {
plugin.log("Gathering contribution data for: " + repository);
gatherContributors(addonRepo);
}
}
// Downloading the data
String tagsContent = getContent(repo, "catalog/tags.json");
String topicsContent = getContent(repo, "catalog/topics.json");
String catalogContent = getContent(repo, "catalog/catalog.json");
// People were concerned that the download took ages, so we need to tell them it's over now.
if (plugin.getSettings().isLogGithubDownloadData()) {
plugin.log("Successfully downloaded data from GitHub.");
}
/* Parsing the data */
// Register the tags translations in the locales
if (!tagsContent.isEmpty()) {
try {
JsonObject tags = new JsonParser().parse(tagsContent).getAsJsonObject();
tags.entrySet().forEach(entry -> plugin.getLocalesManager().getLanguages().values().forEach(locale -> {
JsonElement translation = entry.getValue().getAsJsonObject().get(locale.toLanguageTag());
if (translation != null) {
locale.set("catalog.tags." + entry.getKey(), translation.getAsString());
}
}));
} catch (JsonParseException e) {
plugin.log("Could not update the Catalog Tags: the gathered JSON data is malformed.");
}
}
// Register the topics translations in the locales
if (!topicsContent.isEmpty()) {
try {
JsonObject topics = new JsonParser().parse(topicsContent).getAsJsonObject();
topics.entrySet().forEach(entry -> plugin.getLocalesManager().getLanguages().values().forEach(locale -> {
JsonElement translation = entry.getValue().getAsJsonObject().get(locale.toLanguageTag());
if (translation != null) {
locale.set("catalog.topics." + entry.getKey(), translation.getAsString());
}
}));
} catch (JsonParseException e) {
plugin.log("Could not update the Catalog Topics: the gathered JSON data is malformed.");
}
}
// Register the catalog data
if (!catalogContent.isEmpty()) {
try {
JsonObject catalog = new JsonParser().parse(catalogContent).getAsJsonObject();
if (clearCache) {
this.addonsCatalog.clear();
this.gamemodesCatalog.clear();
}
catalog.getAsJsonArray("gamemodes").forEach(gamemode -> gamemodesCatalog.add(new CatalogEntry(gamemode.getAsJsonObject())));
catalog.getAsJsonArray("addons").forEach(addon -> addonsCatalog.add(new CatalogEntry(addon.getAsJsonObject())));
} catch (JsonParseException e) {
plugin.log("Could not update the Catalog content: the gathered JSON data is malformed.");
}
}
});
}
private void parseCatalogContent(String tagsContent, String topicsContent, String catalogContent) {
// Register the tags translations in the locales
if (!tagsContent.isEmpty()) {
try {
JsonObject tags = new JsonParser().parse(tagsContent).getAsJsonObject();
tags.entrySet().forEach(entry -> plugin.getLocalesManager().getLanguages().values().forEach(locale -> {
JsonElement translation = entry.getValue().getAsJsonObject().get(locale.toLanguageTag());
if (translation != null) {
locale.set("catalog.tags." + entry.getKey(), translation.getAsString());
}
}));
} catch (JsonParseException e) {
plugin.log("Could not update the Catalog Tags: the gathered JSON data is malformed.");
}
}
// Register the topics translations in the locales
if (!topicsContent.isEmpty()) {
try {
JsonObject topics = new JsonParser().parse(topicsContent).getAsJsonObject();
topics.entrySet().forEach(entry -> plugin.getLocalesManager().getLanguages().values().forEach(locale -> {
JsonElement translation = entry.getValue().getAsJsonObject().get(locale.toLanguageTag());
if (translation != null) {
locale.set("catalog.topics." + entry.getKey(), translation.getAsString());
}
}));
} catch (JsonParseException e) {
plugin.log("Could not update the Catalog Topics: the gathered JSON data is malformed.");
}
}
// Register the catalog data
if (!catalogContent.isEmpty()) {
try {
JsonObject catalog = new JsonParser().parse(catalogContent).getAsJsonObject();
this.addonsCatalog.clear();
this.gamemodesCatalog.clear();
catalog.getAsJsonArray("gamemodes").forEach(gamemode -> gamemodesCatalog.add(new CatalogEntry(gamemode.getAsJsonObject())));
catalog.getAsJsonArray("addons").forEach(addon -> addonsCatalog.add(new CatalogEntry(addon.getAsJsonObject())));
} catch (JsonParseException e) {
plugin.log("Could not update the Catalog content: the gathered JSON data is malformed.");
}
}
}
/**
*
* @param repo
@ -144,9 +186,7 @@ public class WebManager {
String content = repo.getContent(fileName).getContent().replaceAll("\\n", "");
return new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8);
} catch (IllegalAccessException e) {
if (plugin.getSettings().isLogGithubDownloadData()) {
plugin.log("Could not connect to GitHub.");
}
// Fail silently
} catch (Exception e) {
plugin.logError("An unhandled exception occurred when downloading '" + fileName + "' from GitHub...");
plugin.logStacktrace(e);
@ -154,6 +194,18 @@ public class WebManager {
return "";
}
private void gatherContributors(@NonNull GitHubRepository repo) {
try {
List<Contributor> addonContributors = new LinkedList<>();
for (GitHubContributor gitHubContributor : repo.getContributors()) {
addonContributors.add(new Contributor(gitHubContributor.getUsername(), gitHubContributor.getContributionsAmount()));
}
contributors.put(repo.getFullName(), addonContributors);
} catch (IllegalAccessException e) {
// Silently fail
}
}
/**
* Returns the contents of the addons catalog (may be an empty list).
* @return the contents of the addons catalog.
@ -174,6 +226,17 @@ public class WebManager {
return gamemodesCatalog;
}
/**
*
* @param repository
* @return
* @since 1.9.0
*/
@NonNull
public List<Contributor> getContributors(String repository) {
return contributors.getOrDefault(repository, new ArrayList<>());
}
/**
* Returns an optional that may contain the {@link GitHubWebAPI} instance only and only if {@link Settings#isGithubDownloadData()} is {@code true}.
* @return the GitHub instance.

View File

@ -5,6 +5,7 @@ import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.event.inventory.ClickType;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
@ -101,7 +102,11 @@ public class CatalogPanel {
// Send the link to the releases tab on click
itemBuilder.clickHandler((panel, user1, clickType, slot) -> {
user1.sendRawMessage(ChatColor.GRAY + "" + ChatColor.ITALIC + "https://github.com/" + addon.getRepository() + "/releases");
if (clickType.equals(ClickType.MIDDLE)) {
CreditsPanel.openPanel(user1, addon.getRepository());
} else {
user1.sendRawMessage(ChatColor.GRAY + "" + ChatColor.ITALIC + "https://github.com/" + addon.getRepository() + "/releases");
}
return true;
});

View File

@ -0,0 +1,91 @@
package world.bentobox.bentobox.panels;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.web.credits.Contributor;
/**
* @since 1.9.0
* @author Poslovitch
*/
public class CreditsPanel {
private static final String LOCALE_REF = "panel.credits.";
private static final int[] PANES = {0, 1, 2, 3, 4, 5, 6, 7, 8};
private CreditsPanel() {}
public static void openPanel(User user, String repository) {
BentoBox plugin = BentoBox.getInstance();
PanelBuilder builder = new PanelBuilder()
.name(user.getTranslation(LOCALE_REF + "title", TextVariables.NAME, repository.split("/")[1]))
.size(45);
// Setup header and corners
for (int i : PANES) {
builder.item(i, new PanelItemBuilder().icon(Material.LIGHT_BLUE_STAINED_GLASS_PANE).name(" ").build());
}
if (plugin.getWebManager().getContributors(repository).isEmpty()) {
looksEmpty(builder, user);
} else {
for (Contributor contributor : plugin.getWebManager().getContributors(repository)) {
int slot = getFirstAvailableSlot(builder);
if (slot == -1) {
break; //TODO support multi paging
}
PanelItem contributorItem = new PanelItemBuilder()
.icon(contributor.getName())
.name(user.getTranslation(LOCALE_REF + "contributor.name", TextVariables.NAME, contributor.getName()))
.description(user.getTranslation(LOCALE_REF + "contributor.description",
"[commits]", String.valueOf(contributor.getCommits())))
.clickHandler((panel, user1, clickType, slot1) -> {
user.sendRawMessage(ChatColor.GRAY + contributor.getURL());
return true;
})
.build();
builder.item(getFirstAvailableSlot(builder), contributorItem);
}
}
builder.build().open(user);
}
public static void openPanel(User user, Addon addon) {
openPanel(user, addon.getDescription().getRepository());
}
private static void looksEmpty(@NonNull PanelBuilder builder, @NonNull User user) {
PanelItem emptyHere = new PanelItemBuilder()
.icon(Material.STRUCTURE_VOID)
.name(user.getTranslation(LOCALE_REF + "empty-here.name"))
.description(user.getTranslation(LOCALE_REF + "empty-here.description"))
.build();
builder.item(22, emptyHere);
}
/**
* @param pb - panel builder
* @return first available slot, or -1 if none
*/
private static int getFirstAvailableSlot(PanelBuilder pb) {
for (int i = 0; i < 35; i++) {
if (!pb.slotOccupied(i)) {
return i;
}
}
return -1;
}
}

View File

@ -5,6 +5,7 @@ import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.event.inventory.ClickType;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
@ -64,6 +65,12 @@ public class ManagementPanel {
.name(user.getTranslation(LOCALE_REF + "views.gamemodes.gamemode.name", TextVariables.NAME, addon.getDescription().getName()))
.description(user.getTranslation(LOCALE_REF + "views.gamemodes.gamemode.description",
"[islands]", String.valueOf(addon.getIslands().getIslandCount(gameModeAddon.getOverWorld()))))
.clickHandler((panel, user1, clickType, slot) -> {
if (clickType.equals(ClickType.MIDDLE)) {
CreditsPanel.openPanel(user, addon);
}
return true;
})
.build();
builder.item(startSlot + i, addonItem);
@ -92,6 +99,12 @@ public class ManagementPanel {
PanelItem addonItem = new PanelItemBuilder()
.icon(addon.getDescription().getIcon())
.name(ChatColor.WHITE + addon.getDescription().getName())
.clickHandler((panel, user1, clickType, slot) -> {
if (clickType.equals(ClickType.MIDDLE)) {
CreditsPanel.openPanel(user, addon);
}
return true;
})
.build();
builder.item(startSlot + i, addonItem);
@ -135,6 +148,19 @@ public class ManagementPanel {
builder.item(17, catalog);
// Credits
PanelItem credits = new PanelItemBuilder()
.icon(Material.KNOWLEDGE_BOOK)
.name(user.getTranslation(LOCALE_REF + "buttons.credits.name"))
.description(user.getTranslation(LOCALE_REF + "buttons.credits.description"))
.clickHandler((panel, user1, clickType, slot) -> {
CreditsPanel.openPanel(user, "BentoBoxWorld/BentoBox");
return true;
})
.build();
builder.item(26, credits);
// Show it to the user
builder.build().open(user);
}

View File

@ -1,11 +1,10 @@
package world.bentobox.bentobox.web.catalog;
import org.bukkit.Material;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import org.bukkit.Material;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Poslovitch

View File

@ -0,0 +1,33 @@
package world.bentobox.bentobox.web.credits;
import org.eclipse.jdt.annotation.NonNull;
/**
*
* @since 1.9.0
* @author Poslovitch
*/
public class Contributor {
private @NonNull String name;
private int commits;
public Contributor(@NonNull String name, int commits) {
this.name = name;
this.commits = commits;
}
@NonNull
public String getName() {
return name;
}
public int getCommits() {
return commits;
}
@NonNull
public String getURL() {
return "https://github.com/" + name;
}
}

View File

@ -1197,6 +1197,9 @@ management:
catalog:
name: "&6Addons Catalog"
description: "&aOpens the Addons Catalog"
credits:
name: "&6Credits"
description: "&aOpens the Credits for BentoBox"
empty-here:
name: "&bThis looks empty here..."
description: "&aWhat if you take a look at our catalog?"
@ -1279,6 +1282,22 @@ catalog:
&aAllow BentoBox to connect to GitHub in
&athe configuration or try again later.
panel:
credits:
title: "&8[name] &2Credits"
contributor:
name: "&a[name]"
description: |
&aCommits: &b[commits]
empty-here:
name: "&cThis looks empty here..."
description: |+
&cBentoBox could not gather the Contributors
&cfor this Addon.
&aAllow BentoBox to connect to GitHub in
&athe configuration or try again later.
successfully-loaded: |
&6 ____ _ ____