diff --git a/src/main/java/world/bentobox/bentobox/managers/WebManager.java b/src/main/java/world/bentobox/bentobox/managers/WebManager.java index c17ad6028..ea3841d0b 100644 --- a/src/main/java/world/bentobox/bentobox/managers/WebManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/WebManager.java @@ -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 addonsCatalog; private @NonNull List gamemodesCatalog; + private @NonNull Map> 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 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 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 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. diff --git a/src/main/java/world/bentobox/bentobox/panels/CatalogPanel.java b/src/main/java/world/bentobox/bentobox/panels/CatalogPanel.java index daf2125e8..9bfaaedbb 100644 --- a/src/main/java/world/bentobox/bentobox/panels/CatalogPanel.java +++ b/src/main/java/world/bentobox/bentobox/panels/CatalogPanel.java @@ -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; }); diff --git a/src/main/java/world/bentobox/bentobox/panels/CreditsPanel.java b/src/main/java/world/bentobox/bentobox/panels/CreditsPanel.java new file mode 100644 index 000000000..beb0dd3b2 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/panels/CreditsPanel.java @@ -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; + } +} diff --git a/src/main/java/world/bentobox/bentobox/panels/ManagementPanel.java b/src/main/java/world/bentobox/bentobox/panels/ManagementPanel.java index e44af2b6f..7fa7d1134 100644 --- a/src/main/java/world/bentobox/bentobox/panels/ManagementPanel.java +++ b/src/main/java/world/bentobox/bentobox/panels/ManagementPanel.java @@ -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); } diff --git a/src/main/java/world/bentobox/bentobox/web/catalog/CatalogEntry.java b/src/main/java/world/bentobox/bentobox/web/catalog/CatalogEntry.java index fea1bc1cf..c1f03a2a6 100644 --- a/src/main/java/world/bentobox/bentobox/web/catalog/CatalogEntry.java +++ b/src/main/java/world/bentobox/bentobox/web/catalog/CatalogEntry.java @@ -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 diff --git a/src/main/java/world/bentobox/bentobox/web/credits/Contributor.java b/src/main/java/world/bentobox/bentobox/web/credits/Contributor.java new file mode 100644 index 000000000..c6d2f28ff --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/web/credits/Contributor.java @@ -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; + } +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 4f3745473..30440ef47 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -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 ____ _ ____