diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..85b8c92 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "daily" diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index 22007db..7b2ec64 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -14,18 +14,25 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: false + + - name: Store short commit hash + run: echo "short_commit_hash=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV" - name: Build with Gradle run: ./gradlew build env: + PRESERVE_PRERELEASE_VERSION: true DISABLE_PROPERTIES_UPDATE: true + VERSION_SUFFIX: ${{ env.short_commit_hash }} - name: Delete common libs run: rm -r ./common/build/libs @@ -33,5 +40,5 @@ jobs: - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: Artifacts + name: Build Artifacts path: ./*/build/libs/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d7898d3..0df3b3c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,24 +16,27 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: false - name: Build with Gradle run: ./gradlew build env: + IS_RELEASE: true DISABLE_PROPERTIES_UPDATE: true - name: Upload to Modrinth run: ./gradlew modrinth env: + IS_RELEASE: true + DISABLE_PROPERTIES_UPDATE: true MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} VERSION_NAME: ${{ github.event.release.name }} VERSION_IS_PRERELEASE: ${{ github.event.release.prerelease }} diff --git a/build.gradle b/build.gradle index d0be512..1abbcdb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,24 +1,58 @@ import dex.plugins.outlet.v2.util.ReleaseType +import java.nio.file.Files + plugins { - id 'fabric-loom' version '1.10-SNAPSHOT' apply false + id 'fabric-loom' version '1.14-SNAPSHOT' apply false id 'io.github.dexman545.outlet' version '1.6.1' apply false id 'com.modrinth.minotaur' version '2.+' apply false } +ext { + versionPrefix = rootProject.mod_version + if (!Boolean.parseBoolean(System.getenv("IS_RELEASE"))) { + String preReleaseVersion = System.currentTimeMillis() + + if (Boolean.parseBoolean(System.getenv("PRESERVE_PRERELEASE_VERSION"))) { + var preReleaseVersionFile = file("preReleaseVersion.txt").toPath() + if (Files.exists(preReleaseVersionFile)) preReleaseVersion = Files.readString(preReleaseVersionFile) + Files.writeString(preReleaseVersionFile, preReleaseVersion) + } + + var separator = "-" + if (versionPrefix.contains("-")) separator = "." + versionPrefix = "${versionPrefix}${separator}${preReleaseVersion}" + } + final String versionSuffix = System.getenv("VERSION_SUFFIX") + if (versionSuffix != null && !versionSuffix.isEmpty()) { + versionPrefix = "${versionPrefix}+${versionSuffix}" + } + + System.out.println("Version Prefix: " + versionPrefix) +} + allprojects { group = "top.offsetmonkey538.githubresourcepackmanager" repositories { mavenLocal() + maven { + name = "OffsetMods538" + url = "https://maven.offsetmonkey538.top/releases" + content { + includeGroup "top.offsetmonkey538.meshlib" + includeGroup "top.offsetmonkey538.monkeylib538" + includeGroup "top.offsetmonkey538.offsetconfig538" + } + } } } subprojects { apply plugin: "java" - archivesBaseName = "github-resourcepack-manager-${project.nameSuffix}" - version = "${project.mod_version}+${project.minecraft_version}" + base.archivesName = "github-resourcepack-manager-${project.nameSuffix}" + version = "${rootProject.versionPrefix}+${project.minecraft_version}" java { withSourcesJar() @@ -60,5 +94,4 @@ configure(subprojects.findAll { it.name != "common" }) { } } tasks.modrinth.dependsOn(tasks.modrinthSyncBody) - } diff --git a/common/build.gradle b/common/build.gradle index 91a5e77..3f52771 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,30 +1,46 @@ plugins { + id 'java-library' id 'com.gradleup.shadow' version '9.0.0-beta4' } repositories { mavenCentral() maven { - name = "OffsetMods538" - url = "https://maven.offsetmonkey538.top/releases" + name = "Mojang Libraries" + url = "https://libraries.minecraft.net" content { - includeGroup "top.offsetmonkey538.meshlib" + includeGroup "com.mojang" } } } dependencies { - implementation "org.eclipse.jgit:org.eclipse.jgit:${project.jgit_version}" - implementation "blue.endless:jankson:${project.jankson_version}" + implementation "org.eclipse.jgit:org.eclipse.jgit:${rootProject.jgit_version}" - shadow compileOnly("top.offsetmonkey538.meshlib:mesh-lib-api:${project.meshlib_version}") + shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${project.monkeylib538_version}") - // should be bundeled with minecraft + shadow compileOnly("top.offsetmonkey538.meshlib:mesh-lib-api:${project.meshlib_version}") { + exclude(group: "top.offsetmonkey538.monkeylib538") + } + + // should be bundled with minecraft + shadow compileOnly("com.mojang:brigadier:1.3.10") //todo put in properties shadow compileOnly("com.google.guava:guava:${project.guava_version}") - shadow compileOnly("commons-io:commons-io:${project.commons_version}") + shadow compileOnly("commons-io:commons-io:${project.commonsio_version}") + shadow compileOnly("org.apache.commons:commons-lang3:${project.commonslang_version}") shadow compileOnly("com.google.code.gson:gson:${project.gson_version}") // Jetbrains annotations for gud code shadow compileOnly("org.jetbrains:annotations:24.0.0") } tasks.build.dependsOn(shadowJar) + +processResources { + final Map properties = [ + "modVersion": project.version + ] + inputs.properties(properties) + filesMatching("META-INF/neoforge.mods.toml") { + expand(properties) + } +} diff --git a/common/gradle.properties b/common/gradle.properties index 8f66902..f6977dd 100644 --- a/common/gradle.properties +++ b/common/gradle.properties @@ -1,8 +1,6 @@ -jgit_version = 6.9.0.202403050737-r guava_version = 33.4.0-jre -commons_version = 2.18.0 +commonsio_version = 2.18.0 +commonslang_version = 3.18.0 gson_version = 2.11.0 -# Jankson, check at https://github.com/falkreon/Jankson -jankson_version = 1.2.3 nameSuffix = api \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/GithubResourcepackManager.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/GithubResourcepackManager.java index 42f4c98..2222668 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/GithubResourcepackManager.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/GithubResourcepackManager.java @@ -1,52 +1,110 @@ package top.offsetmonkey538.githubresourcepackmanager; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.offsetmonkey538.githubresourcepackmanager.command.GitPackManagerCommand; import top.offsetmonkey538.githubresourcepackmanager.config.ModConfig; +import top.offsetmonkey538.githubresourcepackmanager.config.ConfigHandler; import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; +import top.offsetmonkey538.githubresourcepackmanager.handler.DataPackHandler; import top.offsetmonkey538.githubresourcepackmanager.handler.GitHandler; -import top.offsetmonkey538.githubresourcepackmanager.handler.PackHandler; +import top.offsetmonkey538.githubresourcepackmanager.handler.ResourcePackHandler; import top.offsetmonkey538.githubresourcepackmanager.networking.MainHttpHandler; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand; -import top.offsetmonkey538.githubresourcepackmanager.config.ConfigManager; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText; -import top.offsetmonkey538.githubresourcepackmanager.utils.*; -import top.offsetmonkey538.meshlib.api.HttpHandlerRegistry; +import top.offsetmonkey538.githubresourcepackmanager.platform.*; +import top.offsetmonkey538.githubresourcepackmanager.utils.StringUtils; +import top.offsetmonkey538.meshlib.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; +import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; +import top.offsetmonkey538.monkeylib538.api.lifecycle.ServerLifecycleApi; +import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; +import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; +import top.offsetmonkey538.monkeylib538.api.telemetry.TelemetryRegistry; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibStyle; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; +import top.offsetmonkey538.monkeylib538.api.text.TextFormattingApi; +import top.offsetmonkey538.monkeylib538.telemetry.TelemetryHandler; +import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; +import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.regex.Pattern; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; - public final class GithubResourcepackManager { private GithubResourcepackManager() { } public static final String MOD_ID = "github-resourcepack-manager"; + public static final MonkeyLibLogger LOGGER = MonkeyLibLogger.create(MOD_ID); public static final String MOD_URI = "gh-rp-manager"; - public static final Path RESOURCEPACK_FOLDER = PlatformMain.INSTANCE.getConfigDir().resolve(".resource-pack"); - public static final Path REPO_ROOT_FOLDER = RESOURCEPACK_FOLDER.resolve("git"); - public static final Path OUTPUT_FOLDER = RESOURCEPACK_FOLDER.resolve("output"); - public static final Pattern PACK_NAME_PATTERN = Pattern.compile("\\d+-"); - public static final UUID PACK_UUID = UUID.fromString("60ab8dc7-08d1-4f5f-a9a8-9a01d048b7b9"); + public static final Path DATA_FOLDER = PlatformUtil.getConfigDir().resolve(MOD_ID).resolve(".packs"); + public static final Path GIT_FOLDER = DATA_FOLDER.resolve("git"); + + public static final Path RESOURCEPACK_FOLDER = DATA_FOLDER.resolve("resource-pack"); + public static final Path DATAPACK_FOLDER = DATA_FOLDER.resolve("data-pack"); + + public static final Path RESOURCEPACK_OUTPUT_FOLDER = RESOURCEPACK_FOLDER.resolve("output"); + + public static final Pattern RESOURCEPACK_NAME_PATTERN = Pattern.compile("\\d+-"); + public static final UUID RESOURCEPACK_UUID = UUID.fromString("60ab8dc7-08d1-4f5f-a9a8-9a01d048b7b9"); + + public static final Map STATIC_PLACEHOLDERS = Map.of("{packUpdateCommand}", "/gh-rp-manager request-pack"); + + private static final Executor EXECUTOR = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(MOD_ID + "-%d").build()); + private static final List MESSAGE_QUEUE = new ArrayList<>(); - public static ModConfig config; + private static final MonkeyLibText MESSAGE_QUEUE_EMPTY_MESSAGE; + static { + try { + MESSAGE_QUEUE_EMPTY_MESSAGE = TextFormattingApi.styleText("Admin message queue can be emptied using the &{hoverText,'Click to run','&{runCommand,'/gh-rp-manager reset-admin-message-queue','[/gh-rp-manager reset-admin-message-queue]'}'} command."); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - public static GitHandler gitHandler; - public static PackHandler packHandler; + @SuppressWarnings("NotNullFieldNotInitialized") + public static @NotNull ConfigHolder config; + public static ResourcePackHandler resourcePackHandler; + + private static boolean disabled; public static void initialize() { - PlatformCommand.INSTANCE.registerGithubRpManagerCommand(); + TelemetryRegistry.register("git-pack-manager"); // TODO: replace with MOD_ID once I do that + + addLogToAdminListeners(); + PlatformUtil.sendMessagesToAdminsOnJoin(() -> { + if (MESSAGE_QUEUE.isEmpty()) return new MonkeyLibText[] {}; + + final MonkeyLibText[] result = new MonkeyLibText[MESSAGE_QUEUE.size() + 1]; + MESSAGE_QUEUE.toArray(result); + result[result.length - 1] = MESSAGE_QUEUE_EMPTY_MESSAGE; + return result; + }); - ConfigManager.loadConfig(); + // config should be initialized after the error listeners + config = ConfigManager.init(ConfigHolder.create(ModConfig::new, LOGGER::error)); + + GitPackManagerCommand.register(); + ConfigCommandApi.registerConfigCommand( + config, + () -> disabled = ConfigHandler.handleConfig(), + "gh-rp-manager", "config" + ); + + disabled = ConfigHandler.handleConfig(); try { createFolderStructure(); @@ -54,26 +112,68 @@ public static void initialize() { LOGGER.error("Failed to create folder structure!", e); } - HttpHandlerRegistry.INSTANCE.register(MOD_URI, new MainHttpHandler()); + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(registry -> { + registry.register(MOD_ID, new HttpRouter( + new PathHttpRule(MOD_URI), + new MainHttpHandler() + )); + }); + + ServerLifecycleApi.runOnServerStarted(() -> updatePack(UpdateType.RESTART, true)); + } - PlatformMain.INSTANCE.runOnServerStart(() -> updatePack(UpdateType.RESTART)); + private static void addLogToAdminListeners() { + LOGGER.addListener(MonkeyLibLogger.LogLevel.ERROR, createLogToAdminListener(MonkeyLibStyle.Color.RED)); + LOGGER.addListener(MonkeyLibLogger.LogLevel.WARN, createLogToAdminListener(MonkeyLibStyle.Color.YELLOW)); + } + + private static MonkeyLibLogger.LogListener createLogToAdminListener(final int textColor) { + return (message, error) -> { + final MonkeyLibText text = MonkeyLibText + .of("[%s] %s".formatted(MOD_ID, message)) + .applyStyle(style -> style.withColor(textColor)); + + if (error != null) text.applyStyle(style -> style.withShowText(MonkeyLibText.of(ExceptionUtils.getRootCauseMessage(error)))); + + PlatformMain.INSTANCE.sendMessageToAdmins(text); + MESSAGE_QUEUE.addLast(text); + }; } private static void createFolderStructure() throws GithubResourcepackManagerException { try { - Files.createDirectories(OUTPUT_FOLDER); + Files.createDirectories(RESOURCEPACK_OUTPUT_FOLDER); + Files.createDirectories(DATAPACK_FOLDER); + Files.createDirectories(GIT_FOLDER); } catch (IOException e) { - throw new GithubResourcepackManagerException("Failed to create directory '%s'!", OUTPUT_FOLDER); + throw new GithubResourcepackManagerException("Failed to create directory!", e); } } - public static void updatePack(final UpdateType updateType) { - LOGGER.info("Updating resourcepack..."); + public static CompletableFuture updatePack(final UpdateType updateType, final boolean onSameThread) { + if (!onSameThread) return CompletableFuture.runAsync(() -> updatePack(updateType), EXECUTOR); + + updatePack(updateType); + return CompletableFuture.completedFuture(null); + } + + private static void updatePack(final UpdateType updateType) { + if (updateType != UpdateType.RESTART) { + LOGGER.debug("Clearing admin message queue before updating after a restart..."); + MESSAGE_QUEUE.clear(); + } + + if (disabled) { + LOGGER.warn("Skipping pack updating because config was invalid!"); + return; + } + + LOGGER.info("Updating packs..."); if (updateType == UpdateType.COMMAND_FORCE) { LOGGER.warn("Forced pack update! Deleting data directory and continuing..."); try { - FileUtils.deleteDirectory(RESOURCEPACK_FOLDER.toFile()); + FileUtils.deleteDirectory(DATA_FOLDER.toFile()); } catch (IOException e) { LOGGER.error("Failed to delete directory!", e); return; @@ -87,7 +187,7 @@ public static void updatePack(final UpdateType updateType) { } // Git stuff - gitHandler = new GitHandler(); + final GitHandler gitHandler = new GitHandler(); LOGGER.info("Updating git repository..."); boolean failed = false; @@ -99,93 +199,175 @@ public static void updatePack(final UpdateType updateType) { } if (!failed) LOGGER.info("Successfully updated git repository!"); + if (config.get().resourcePackProvider.enabled) { + LOGGER.info(""); + LOGGER.info("Updating resource pack..."); + updateResourcePack(gitHandler, updateType, failed); + LOGGER.info("Resource pack updated!"); + } + if (config.get().dataPackProvider.enabled) { + LOGGER.info(""); + LOGGER.info("Updating data pack..."); + updateDataPack(gitHandler, updateType, failed); + LOGGER.info("Data pack updated!"); + } + } + private static void updateResourcePack(final GitHandler gitHandler, final UpdateType updateType, boolean updateFailed) { // Get the location of the old pack, if it exists. - final String oldPackName = getOldPackName(); - final Path oldPackPath = oldPackName == null ? null : OUTPUT_FOLDER.resolve(oldPackName); - + final String oldResourcePackName = getOldResourcePackName(); + final Path oldResourcePackPath = oldResourcePackName == null ? null : RESOURCEPACK_OUTPUT_FOLDER.resolve(oldResourcePackName); // Check if pack was updated - final boolean wasUpdated = gitHandler.getWasUpdated() || oldPackPath == null || !oldPackPath.toFile().exists(); - if (!wasUpdated) { - LOGGER.info("Pack hasn't changed since last update. Skipping new pack generation."); - } + final boolean wasUpdated = + gitHandler.getChangedFiles().map(changes -> changes.stream().anyMatch(it -> it.startsWith(config.get().resourcePackProvider.getRootLocation()))).orElse(true) + || oldResourcePackPath == null + || !oldResourcePackPath.toFile().exists(); + if (!wasUpdated) LOGGER.info("Pack hasn't changed since last update. Skipping new pack generation."); // Generate pack - packHandler = new PackHandler(); + resourcePackHandler = new ResourcePackHandler(); LOGGER.info("Getting pack location..."); - failed = false; + boolean failed = false; try { - packHandler.generatePack(wasUpdated, oldPackPath, oldPackName); + resourcePackHandler.generatePack(wasUpdated, oldResourcePackPath, oldResourcePackName); } catch (GithubResourcepackManagerException e) { LOGGER.error("Failed to generate pack!", e); - failed = true; + failed = updateFailed = true; } - if (!failed) LOGGER.info("Pack location is '%s'!", packHandler.getOutputPackPath()); + if (!failed) LOGGER.info("Pack location is '%s'!", resourcePackHandler.getOutputPackPath().toAbsolutePath()); // Update server.properties file. try { - PlatformServerProperties.INSTANCE.updatePackProperties(packHandler); + PlatformServerProperties.INSTANCE.updatePackProperties(resourcePackHandler); } catch (GithubResourcepackManagerException e) { LOGGER.error("Failed to update server.properties file!", e); } // Generate placeholder map - final Map placeholders = new HashMap<>(); - if (gitHandler.getCommitProperties() != null) placeholders.putAll(gitHandler.getCommitProperties().toPlaceholdersMap()); - placeholders.put("{downloadUrl}", config.getPackUrl(packHandler.getOutputPackName())); - placeholders.put("{updateType}", updateType.name()); - placeholders.put("{wasUpdated}", String.valueOf(wasUpdated)); - LOGGER.info("Placeholders: %s", placeholders); + final Map placeholders = generatePlaceholders(gitHandler, resourcePackHandler, updateType, "resource", wasUpdated); // Send chat message try { - sendUpdateMessage(wasUpdated, placeholders); + if (!failed) sendUpdateMessage(config.get().resourcePackProvider.updateMessage, wasUpdated, placeholders); } catch (GithubResourcepackManagerException e) { - LOGGER.error("Failed to send update message in chat!", e); + LOGGER.error("Failed to send update message for resource pack in chat!", e); } - // Trigger webhook - try { - triggerWebhook(wasUpdated, placeholders, updateType); + // Trigger webhooks + if (!wasUpdated) { + LOGGER.info("Not sending webhook because pack was not updated."); + return; + } + if (!updateFailed) try { + config.get().resourcePackProvider.successWebhook.trigger(true, placeholders, updateType); } catch (GithubResourcepackManagerException e) { - LOGGER.error("Failed to trigger webhook!", e); + LOGGER.error("Failed to trigger success webhook!", e); + } + else try { + config.get().resourcePackProvider.failWebhook.trigger(false, placeholders, updateType); + } catch (GithubResourcepackManagerException e) { + LOGGER.error("Failed to trigger fail webhook!", e); } - - - LOGGER.info("Resourcepack updated!"); } - private static void triggerWebhook(boolean wasUpdated, Map placeholders, UpdateType updateType) throws GithubResourcepackManagerException { - if (config.webhookUrl == null || config.webhookBody == null) return; - if (config.webhookBody.contains("discord") && !wasUpdated) { - LOGGER.info("Not sending discord webhook because pack was not updated."); + private static void updateDataPack(final GitHandler gitHandler, final UpdateType updateType, boolean updateFailed) { + // Check if pack was updated + final boolean wasUpdated = gitHandler.getChangedFiles().map(changes -> changes.stream().anyMatch(it -> it.startsWith(config.get().dataPackProvider.getRootLocation()))).orElse(true); + if (!wasUpdated) { + LOGGER.info("Pack hasn't changed since last update. Datapack processing will be skipped."); return; } + // Generate pack + final DataPackHandler dataPackHandler = new DataPackHandler(); + + LOGGER.info("Getting pack location..."); try { - //noinspection DataFlowIssue: Only returns null when `config.webhookBody` is null, which we have already checked - String webhookBody = Files.readString(config.getWebhookBody()); - webhookBody = StringUtils.replacePlaceholders(webhookBody, placeholders, true); + dataPackHandler.generatePack(); + } catch (GithubResourcepackManagerException e) { + LOGGER.error("Failed to generate pack!", e); + updateFailed = true; + } - WebhookSender.send(webhookBody, config.getWebhookUrl(), updateType, gitHandler.getWasUpdated()); - } catch (IOException e) { - throw new GithubResourcepackManagerException("Failed to read content of webhook body file '%s'!", e, config.webhookBody); + // Refresh datapack list + PlatformMain.INSTANCE.refreshDatapacks(); + + + // Generate placeholder map + final Map placeholders = generatePlaceholders(gitHandler, null, updateType, "data", true); + + // Send chat message + try { + sendUpdateMessage(config.get().dataPackProvider.updateMessage, true, placeholders, true); + } catch (GithubResourcepackManagerException e) { + LOGGER.error("Failed to send update message for datapack in chat!", e); } + + // Trigger webhooks + if (!updateFailed) try { + config.get().dataPackProvider.successWebhook.trigger(true, placeholders, updateType); + } catch (GithubResourcepackManagerException e) { + LOGGER.error("Failed to trigger success webhook!", e); + } + else try { + config.get().dataPackProvider.failWebhook.trigger(false, placeholders, updateType); + } catch (GithubResourcepackManagerException e) { + LOGGER.error("Failed to trigger fail webhook!", e); + } + } + + private static void sendUpdateMessage(final String[] updateMessage, boolean wasUpdated, final Map placeholders) throws GithubResourcepackManagerException { + sendUpdateMessage(updateMessage, wasUpdated, placeholders, false); } - private static void sendUpdateMessage(boolean wasUpdated, final Map placeholders) throws GithubResourcepackManagerException { + private static void sendUpdateMessage(final String[] updateMessage, boolean wasUpdated, final Map placeholders, boolean adminsOnly) throws GithubResourcepackManagerException { if (!wasUpdated) { LOGGER.info("Not sending chat message because pack was not updated."); return; } - PlatformText.INSTANCE.sendUpdateMessage(placeholders); + for (final MonkeyLibText line : createUpdateMessage(updateMessage, placeholders)) + PlatformText.INSTANCE.sendUpdateMessage(line, adminsOnly); + } + + public static MonkeyLibText[] createUpdateMessage(final String[] updateMessage, final Map placeholders) throws GithubResourcepackManagerException { + final MonkeyLibText[] result = new MonkeyLibText[updateMessage.length]; + + for (int lineIndex = 0; lineIndex < updateMessage.length; lineIndex++) { + final String line = StringUtils.replacePlaceholders(updateMessage[lineIndex], placeholders, true, false).replace("\\n", "\n"); + + try { + result[lineIndex] = TextFormattingApi.styleText(line); + } catch (Exception e) { + throw new GithubResourcepackManagerException("Failed to style update message at line %s!", e, lineIndex); + } + } + + return result; + } + + public static Map generatePlaceholders(final GitHandler gitHandler, final @Nullable ResourcePackHandler resourcePackHandler, final UpdateType updateType, final String packType, final boolean wasUpdated) { + final Map placeholders = new HashMap<>(); + + if (gitHandler.getCommitProperties() != null) placeholders.putAll(gitHandler.getCommitProperties().toPlaceholdersMap()); + if (resourcePackHandler != null) placeholders.put("{downloadUrl}", config.get().getPackUrl(resourcePackHandler.getOutputPackName())); + placeholders.putAll(STATIC_PLACEHOLDERS); + placeholders.put("{packType}", packType); + placeholders.put("{updateType}", updateType.name()); + placeholders.put("{wasUpdated}", String.valueOf(wasUpdated)); + LOGGER.info("Placeholders: %s", placeholders); + + return placeholders; + } + + public static void clearAdminMessageQueue() { + MESSAGE_QUEUE.clear(); } - private static String getOldPackName() { + private static String getOldResourcePackName() { final String oldPackUrl = PlatformServerProperties.INSTANCE.getResourcePackUrl(); if (oldPackUrl == null) return null; diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/command/GitPackManagerCommand.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/command/GitPackManagerCommand.java new file mode 100644 index 0000000..568d35b --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/command/GitPackManagerCommand.java @@ -0,0 +1,118 @@ +package top.offsetmonkey538.githubresourcepackmanager.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import org.apache.commons.lang3.exception.ExceptionUtils; +import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; +import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; +import top.offsetmonkey538.githubresourcepackmanager.handler.GitHandler; +import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand; +import top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi; +import top.offsetmonkey538.monkeylib538.api.command.CommandRegistrationApi; +import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibStyle; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; + +import java.util.Map; + +import static com.mojang.brigadier.arguments.BoolArgumentType.bool; +import static com.mojang.brigadier.arguments.BoolArgumentType.getBool; +import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; +import static top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi.literal; +import static top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi.sendText; + +public final class GitPackManagerCommand { + + public static void register() { + CommandRegistrationApi.registerCommand(createCommand()); + } + + private static LiteralArgumentBuilder createCommand() { + return literal("gh-rp-manager") + .then(literal("request-pack") + .requires(CommandAbstractionApi::executedByPlayer) + .executes(PlatformCommand.INSTANCE::executeRequestPackCommand) + ) + + .then(literal("trigger-update") + .requires(CommandAbstractionApi::isOp) + .executes( + context -> { + runTriggerUpdate(context, false); + return 1; + } + ) + .then(argument("force", bool()) + .executes( + context -> { + runTriggerUpdate(context, getBool(context, "force")); + return 1; + } + ) + ) + ) + + .then(literal("test-update-message") + .requires(CommandAbstractionApi::isOp) + .then(literal("resource").executes(context -> runTestUpdateMessage(context, true))) + .then(literal("data").executes(context -> runTestUpdateMessage(context, false))) + ) + + .then(literal("reset-admin-message-queue") + .requires(CommandAbstractionApi::isOp) + .executes(context -> { + clearAdminMessageQueue(); + CommandAbstractionApi.sendMessage(context, "Admin message queue cleared!"); + return 1; + }) + ); + } + + private static void runTriggerUpdate(CommandContext context, boolean force) { + final MonkeyLibLogger.LogListener infoListener = (message, error) -> { + sendText(context, MonkeyLibText.of(String.format("[%s] %s", MOD_ID, message)).applyStyle(style -> style.withColor(MonkeyLibStyle.Color.GRAY))); + }; + final MonkeyLibLogger.LogListener warnListener = (message, error) -> { + final MonkeyLibText text = MonkeyLibText + .of("[%s] %s".formatted(MOD_ID, message)) + .applyStyle(style -> style.withColor(MonkeyLibStyle.Color.YELLOW)); + + if (error != null) text.applyStyle(style -> style.withShowText(MonkeyLibText.of(ExceptionUtils.getRootCauseMessage(error)))); + + sendText(context, text); + }; + GithubResourcepackManager.LOGGER.addListener(MonkeyLibLogger.LogLevel.INFO, infoListener); + GithubResourcepackManager.LOGGER.addListener(MonkeyLibLogger.LogLevel.WARN, warnListener); + + GithubResourcepackManager.updatePack(force ? GithubResourcepackManager.UpdateType.COMMAND_FORCE : GithubResourcepackManager.UpdateType.COMMAND, false).thenRun(() -> { + GithubResourcepackManager.LOGGER.removeListener(MonkeyLibLogger.LogLevel.INFO, infoListener); + GithubResourcepackManager.LOGGER.removeListener(MonkeyLibLogger.LogLevel.WARN, warnListener); + }); + } + + private static int runTestUpdateMessage(CommandContext context, boolean isResource) { + final GitHandler gitHandler = new GitHandler(); + try { + gitHandler.updateRepositoryAndGenerateCommitProperties(); + } catch (GithubResourcepackManagerException e) { + CommandAbstractionApi.sendError(context, "Failed to update repository, git related placeholders will not be replaced!"); + CommandAbstractionApi.sendError(context, "Cause:\n%s\n", e); + LOGGER.error("Failed to update repository, git related placeholders will not be replaced!", e); + } + final Map placeholders = generatePlaceholders(gitHandler, isResource ? resourcePackHandler : null, UpdateType.COMMAND, isResource ? "resource" : "data", true); + + final MonkeyLibText[] text; + try { + text = createUpdateMessage(isResource ? config.get().resourcePackProvider.updateMessage : config.get().dataPackProvider.updateMessage, placeholders); + } catch (GithubResourcepackManagerException e) { + CommandAbstractionApi.sendError(context, "Failed to create update message!"); + CommandAbstractionApi.sendError(context, "Cause:\n%s\n", e); + LOGGER.error("Failed to create update message for %spack!", e, isResource ? "resource" : "data"); + return 0; + } + + for (final MonkeyLibText line : text) CommandAbstractionApi.sendText(context, line); + return 1; + } +} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ConfigHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ConfigHandler.java new file mode 100644 index 0000000..0b2fc06 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ConfigHandler.java @@ -0,0 +1,96 @@ +package top.offsetmonkey538.githubresourcepackmanager.config; + +import blue.endless.jankson.*; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.BasicWebhook; +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.DefaultWebhookBody; +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.basic.BasicFailMessage; +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.basic.BasicSuccessMessage; +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.embed.EmbedFailMessage; +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.embed.EmbedSuccessMessage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; + +public final class ConfigHandler { + private ConfigHandler() { + + } + + public static boolean handleConfig() { + LOGGER.info("Writing default webhook bodies"); + createDefaultWebhooks(); + + // Checking if config is valid + final List errors = checkConfigErrors(); + + // Return false when nothing's wrong (disables the mod) + if (errors.isEmpty()) return false; + + // There were errors, time to log em. + LOGGER.error("There were problems with the config for GitHub Resourcepack Manager, see below for more details!"); + errors.stream().map(string -> " " + string).forEach(LOGGER::error); + return true; + } + + private static void createDefaultWebhooks() { + final Jankson jankson = new Jankson.Builder().build(); + final List webhookBodies = List.of( + new BasicWebhook(), + + new BasicSuccessMessage(), + new BasicFailMessage(), + new EmbedSuccessMessage(), + new EmbedFailMessage() + ); + + for (DefaultWebhookBody webhook : webhookBodies) { + final Path location = config.get().getFilePath().getParent().resolve(webhook.getName()); + + if (Files.exists(location)) continue; + + try { + Files.createDirectories(location.getParent()); + Files.writeString(location, jankson.toJson(webhook).toJson(JsonGrammar.STRICT)); + } catch (IOException e) { + LOGGER.error("Failed to write default webhook body '%s'!", webhook.getName(), e); + } + } + } + + private static @NotNull List checkConfigErrors() { + final List errors = new ArrayList<>(); + + if (config.get().serverInfo.publicIp == null) errors.add("Field 'serverInfo.publicIp' not set!"); + if (config.get().repositoryInfo.url == null) errors.add("Field 'repositoryInfo.url' not set!"); + if (config.get().repositoryInfo.isPrivate) { + if (config.get().repositoryInfo.username == null) errors.add("Field 'repositoryInfo.username' not set, but 'repositoryInfo.isPrivate' is true!"); + if (config.get().repositoryInfo.token == null) errors.add("Field 'repositoryInfo.token' not set, but 'repositoryInfo.isPrivate' is true!"); + } else { + if (config.get().repositoryInfo.username != null) errors.add("Field 'repositoryInfo.username' set, but 'repositoryInfo.isPrivate' is false! Can be unset"); + if (config.get().repositoryInfo.token != null) errors.add("Field 'repositoryInfo.token' set, but 'repositoryInfo.isPrivate' is false! Can be unset"); + } + + checkWebhookErrors(errors, config.get().resourcePackProvider.successWebhook, "resourcePackProvider.successWebhook"); + checkWebhookErrors(errors, config.get().resourcePackProvider.failWebhook, "resourcePackProvider.failWebhook"); + checkWebhookErrors(errors, config.get().dataPackProvider.successWebhook, "dataPackProvider.successWebhook"); + checkWebhookErrors(errors, config.get().dataPackProvider.failWebhook, "dataPackProvider.failWebhook"); + + return errors; + } + + private static void checkWebhookErrors(final List errors, final ModConfig.WebhookInfo webhook, final String webhookPath) { + if (!webhook.enabled) return; + + if (webhook.url == null) errors.add("Field '%s.url' not set, but '%s.enabled' is true!".formatted(webhookPath, webhookPath)); + if (webhook.body == null) errors.add("Field '%s.body' not set, but '%s.enabled' is true!".formatted(webhookPath, webhookPath)); + + final Path bodyPath = webhook.getBodyPath(); + if (bodyPath != null && !Files.exists(bodyPath)) errors.add("Field '%s.body' points to file '%s', which doesn't exist!".formatted(webhookPath, bodyPath.toAbsolutePath())); + } +} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ConfigManager.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ConfigManager.java deleted file mode 100644 index 87dc2d9..0000000 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ConfigManager.java +++ /dev/null @@ -1,160 +0,0 @@ -package top.offsetmonkey538.githubresourcepackmanager.config; - -import blue.endless.jankson.Jankson; -import blue.endless.jankson.JsonElement; -import blue.endless.jankson.JsonObject; -import blue.endless.jankson.JsonPrimitive; -import blue.endless.jankson.api.SyntaxError; -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; - -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; - -public final class ConfigManager { - private ConfigManager() { - - } - - public static final String VERSION_KEY = "!!!version"; - - public static final Path OLD_CONFIG_FILE_PATH = PlatformMain.INSTANCE.getConfigDir().getParent().resolve(MOD_ID + ".json"); - public static final Path NEW_CONFIG_FILE_PATH = PlatformMain.INSTANCE.getConfigDir().resolve(MOD_ID + ".json"); - public static final Path CURRENT_CONFIG_FILE_PATH = PlatformMain.INSTANCE.getConfigDir().resolve("config").resolve("main.json"); - - - public static void loadConfig() { - if (Files.exists(OLD_CONFIG_FILE_PATH)) { - try { - Files.createDirectories(NEW_CONFIG_FILE_PATH.getParent()); - Files.move(OLD_CONFIG_FILE_PATH, NEW_CONFIG_FILE_PATH); - } catch (IOException e) { - throw new RuntimeException("Failed to move config file to new location!", e); - } - } - if (Files.exists(NEW_CONFIG_FILE_PATH)) { - try { - Files.createDirectories(CURRENT_CONFIG_FILE_PATH.getParent()); - Files.move(NEW_CONFIG_FILE_PATH, CURRENT_CONFIG_FILE_PATH); - } catch (IOException e) { - throw new RuntimeException("Failed to move config file to new location!", e); - } - } - - config = new ModConfig(); - if (CURRENT_CONFIG_FILE_PATH.toFile().exists()) config = load(); - save(config); - - LOGGER.info("Writing default webhook bodies"); - config.createDefaultWebhooks(); - - // Checking if config is valid - final List errors = checkConfigErrors(); - - // Return when nothing's wrong - if (errors.isEmpty()) return; - - // There were errors, time to log em. - LOGGER.error("There were problems with the config for GitHub Resourcepack Manager, see below for more details!"); - errors.stream().map(string -> "\t" + string).forEach(LOGGER::error); - LOGGER.error("There were problems with the config for Github Resourcepack Manager, see above for more details!"); - - throw new RuntimeException("There were problems with the config for Github Resourcepack Manager, see above for more details!"); - } - - private static @NotNull List checkConfigErrors() { - final List errors = new ArrayList<>(); - - if (config.serverPublicIp == null) errors.add("Field 'serverPublicIp' not set!"); - if (config.repoUrl == null) errors.add("Field 'repoUrl' not set!"); - if (config.isRepoPrivate) { - if (config.githubUsername == null) errors.add("Field 'githubUsername' not set, but 'isRepoPrivate' is true!"); - if (config.githubToken == null) errors.add("Field 'githubToken' not set, but 'isRepoPrivate' is true!"); - } else { - if (config.githubUsername != null) errors.add("Field 'githubUsername' set, but 'isRepoPrivate' is false! Can be unset"); - if (config.githubToken != null) errors.add("Field 'githubToken' set, but 'isRepoPrivate' is false! Can be unset"); - } - return errors; - } - - private static ModConfig load() { - final Jankson jankson = Jankson.builder().build(); - final File configFile = CURRENT_CONFIG_FILE_PATH.toFile(); - - // Load the config from disk - final JsonObject json; - try { - json = jankson.load(configFile); - } catch (IOException e) { - LOGGER.error("Config file '%s' could not be read!", CURRENT_CONFIG_FILE_PATH, e); - return config; - } catch (SyntaxError e) { - LOGGER.error("Config file '%s' is formatted incorrectly!", CURRENT_CONFIG_FILE_PATH, e); - return config; - } - - // Check if version matches the latest version - applyDatafixers(config, configFile.toPath(), json, jankson); - - // Load config class from the final json - return jankson.fromJson(json, config.getClass()); - } - - private static void applyDatafixers(ModConfig config, Path configFile, JsonObject json, Jankson jankson) { - int last_version = json.getInt(VERSION_KEY, 0); - int current_version = config.getConfigVersion(); - if (last_version < current_version) try { - final Path backupPath = configFile.resolveSibling(String.format("%s-%s-backup.json", configFile.getFileName(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss")))); - Files.copy(configFile, backupPath); - - for (Datafixer datafixer : config.getDatafixers().subList(last_version, current_version)) { - datafixer.apply(json, jankson); - } - save(config); - } catch (IOException e) { - LOGGER.error("Unable to create backup of config file '%s'! Continuing anyway cause I don't care if your config gets messed up and I can't think of a reason for this even happening cause like the initial config file has to be there so I'd imagine that the directory is writeable so like why wouldn't it be possible to write the backup of the file?", configFile, e); - } - - if (last_version > current_version) { - throw new IllegalStateException(String.format("Config file '%s' is for a newer version of the mod, please update! Expected config version '%s', found '%s'!", CURRENT_CONFIG_FILE_PATH, current_version, last_version)); - } - } - - private static void save(ModConfig config) { - final Jankson jankson = Jankson.builder().build(); - - // Convert config to json - final JsonElement jsonAsElement = jankson.toJson(config); - if (!(jsonAsElement instanceof final JsonObject json)) { - LOGGER.error("Could not cast '%s' to 'JsonObject'! Config will not be saved!", jsonAsElement.getClass().getName()); - return; - } - - // Write config version - json.put(VERSION_KEY, new JsonPrimitive(config.getConfigVersion()), "!!!!! DO NOT MODIFY THIS VALUE !!!!"); - - // Convert final json to string - final String result = json.toJson(true, true); - - try { - Files.createDirectories(CURRENT_CONFIG_FILE_PATH.getParent()); - Files.writeString(CURRENT_CONFIG_FILE_PATH, result); - } catch (IOException e) { - LOGGER.error("Config file '%s' could not be written to!", CURRENT_CONFIG_FILE_PATH, e); - } - } - - @FunctionalInterface - public interface Datafixer { - void apply(JsonObject original, Jankson jankson); - } -} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ModConfig.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ModConfig.java index 2cbb7fa..ca142d0 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ModConfig.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/ModConfig.java @@ -1,115 +1,315 @@ package top.offsetmonkey538.githubresourcepackmanager.config; -import blue.endless.jankson.Comment; -import blue.endless.jankson.Jankson; -import blue.endless.jankson.JsonGrammar; -import top.offsetmonkey538.githubresourcepackmanager.config.webhook.BasicWebhook; -import top.offsetmonkey538.githubresourcepackmanager.config.webhook.DefaultWebhookBody; -import top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.BasicMessage; -import top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.EmbedMessage; +import blue.endless.jankson.*; +import blue.endless.jankson.api.Marshaller; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties; +import top.offsetmonkey538.githubresourcepackmanager.utils.StringUtils; +import top.offsetmonkey538.githubresourcepackmanager.utils.WebhookSender; +import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; +import top.offsetmonkey538.offsetconfig538.api.config.Config; +import top.offsetmonkey538.offsetconfig538.api.config.Datafixer; import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; +import java.util.Map; import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.config; -import static top.offsetmonkey538.githubresourcepackmanager.config.ConfigManager.CURRENT_CONFIG_FILE_PATH; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; -public class ModConfig { +public class ModConfig implements Config { + @Comment("!!!!Please check the wiki for how to set up the mod. It is linked on both the Modrinth and GitHub pages!!!!") - public String packUpdateMessage = "Server resourcepack has been updated!\nPlease click {packUpdateCommand} to get the most up to date pack."; - public String packUpdateMessageHoverMessage = "{longDescription}"; - @Comment("The public ip of your server (\"123.45.67.89\" or \"play.coolserver.net\")") - public String serverPublicIp = null; - @Comment("If set, this port will be used in the server.properties file instead of the Minecraft server port. HTTP server will still be hosted on the Minecraft port. Only useful when running the server behind a proxy like nginx, traefik, cloudflare tunnel, etc.") - public String proxyPort = null; - @Comment("Should be \"[YOUR BRANCH NAME HERE]\". Common names include \"master\" and \"main\"") - public String branch = "master"; - public String repoUrl = null; - @Comment("Where the mod will search for resource packs in the cloned repository") - public String resourcePackRoot = ""; - public boolean isRepoPrivate = false; - public String githubUsername = null; - @Comment("PLEASE DON'T SHARE THIS WITH ANYONE EVER") - public String githubToken = null; - public String webhookUrl = null; - public String webhookBody = null; - - - protected String getName() { - return MOD_ID + "/" + MOD_ID; - } - - protected int getConfigVersion() { - return 2; - } - - protected List getDatafixers() { - return List.of( + public ServerInfo serverInfo = new ServerInfo(); + public RepositoryInfo repositoryInfo = new RepositoryInfo(); + public ResourcePackProvider resourcePackProvider = new ResourcePackProvider(); + public DataPackProvider dataPackProvider = new DataPackProvider(); + + + public static class ServerInfo { + @SuppressWarnings("HttpUrlsUsage") + @Comment("The public ip of your server, may also specify the protocol (\"123.45.67.89\" or \"http://play.coolserver.net\")") + public String publicIp = null; + + @Comment("If set, this port will be used in the server.properties file instead of the Minecraft server port. The HTTP server will still be hosted on the Minecraft port. Only useful when running the server behind a proxy like nginx, traefik, cloudflare tunnel, etc.") + public String proxyPort = null; + } + + public static class RepositoryInfo { + @Comment("Should be \"[YOUR BRANCH NAME HERE]\". Common names include \"master\" and \"main\"") + public String branch = "master"; + @Comment("The URL of your repository. For example \"https://github.com/MyName/MyRepository\"") + public String url = null; + + @Comment("Whether or not the repository is private. Username and token will need to be populated when this is set to 'true'!") + public boolean isPrivate = false; + + @Comment("The two values below only need to be set when 'isPrivate' is true!") + public String username = null; + @Comment("PLEASE DO NOT SHARE THIS WITH ANYONE") + public String token = null; + } + + public static class ResourcePackProvider { + @Comment("Whether or not the resource pack provider is enabled. Default: true") + public boolean enabled = true; + @Comment("Where the mod will search for resource packs in the cloned repository. MUST NOT be same as or child of the 'rootLocation' of the datapack provider") + public String rootLocation = "/resourcepacks"; + + @Comment("Messages sent in chat when pack has been updated. Each entry will be on a new line. May be 'null' or empty to disable.") + public String[] updateMessage = new String[] { + "&{hoverText,'{longDescription}','Server resourcepack has been updated!'}", + "&{hoverText,'{longDescription}','Please click &{hoverText,'Click to update pack','&{runCommand,'{packUpdateCommand}','[HERE]'}'} to get the most up to date pack.'}" + }; + + @Comment("Webhook to be sent when pack updating succeeded") + public WebhookInfo successWebhook = new WebhookInfo(); + @Comment("Webhook to be sent when pack updating failed") + public WebhookInfo failWebhook = new WebhookInfo(); + + public String getRootLocation() { + return rootLocation.startsWith("/") ? rootLocation.substring(1) : rootLocation; + } + public Path getPackRoot() { + return GIT_FOLDER.resolve(getRootLocation()); + } + public Path getPackPacksDir() { + return getPackRoot().resolve("packs"); + } + } + + public static class DataPackProvider { + @Comment("Whether or not the data pack provider is enabled. Default: false") + public boolean enabled = false; + @Comment("Where the mod will search for data packs in the cloned repository. MUST NOT be same as or child of the 'rootLocation' of the resourcepack provider") + public String rootLocation = "/datapacks"; + @Comment("Messages sent TO ADMINS in chat when pack has been updated. Each entry will be on a new line. May be 'null' or empty to disable.") + public String[] updateMessage = new String[] { + "&{hoverText,'{longDescription}','Server datapacks have been updated!'}", + "&{hoverText,'{longDescription}','New packs (if any) will need to be enabled with the &{hoverText,'Click to suggest','&{suggestCommand,'/datapack enable','&n/datapack enable'}'} command.'}", + "&{hoverText,'{longDescription}','Please run &{hoverText,'Click to suggest','&{suggestCommand,'/reload','&n/reload'}'} or restart the server to reload datapacks.'}" + }; + + @Comment("Webhook to be sent when pack updating succeeded") + public WebhookInfo successWebhook = new WebhookInfo(); + @Comment("Webhook to be sent when pack updating failed") + public WebhookInfo failWebhook = new WebhookInfo(); + + public String getRootLocation() { + return rootLocation.startsWith("/") ? rootLocation.substring(1) : rootLocation; + } + public Path getPackRoot() { + return GIT_FOLDER.resolve(getRootLocation()); + } + public Path getPackPacksDir() { + return getPackRoot().resolve("packs"); + } + } + + public static class WebhookInfo { + public WebhookInfo() { + + } + + @Comment("Whether or not this webhook is enabled") + public boolean enabled = false; + + @Comment("The URL to send the webhook to. For example \"https://discord.com/api/webhooks/1234567890123456789/eW91J3JlIG5vdCBzdGVhbGluZyBhIHRva2Vu_bm9wZQ==_eWVyJyBub3Q=\" or something custom like \"https://api.example.com/NDI6IHRoZSBtZWFuaW5nIG9mIGxpZmUsIHRoZSB1bml2ZXJzZSwgYW5kIGV2ZXJ5dGhpbmc=\"") + public String url = null; + @Comment("The relative path from the config directory to a webhook body file. For example \"discord/basic_message.json\" or \"discord/embed_message.json\"") + public String body = null; + + public void trigger(final boolean updateSucceeded, final Map placeholders, final UpdateType updateType) throws GithubResourcepackManagerException { + if (!enabled) return; + + try { + //noinspection DataFlowIssue: Only returns null when `body` is null, which we have already checked + String webhookBody = Files.readString(getBodyPath()); + webhookBody = StringUtils.replacePlaceholders(webhookBody, placeholders, false, true); + + WebhookSender.send(webhookBody, getWebhookUrl(), updateType, updateSucceeded); + } catch (IOException e) { + throw new GithubResourcepackManagerException("Failed to read content of webhook body file '%s'!", e, config.get().resourcePackProvider.successWebhook.body); + } + } + + public @Nullable URI getWebhookUrl() { + if (url == null) return null; + return URI.create(url); + } + + public @Nullable Path getBodyPath() { + return body == null ? null : config.get().getFilePath().getParent().resolve(body); + } + } + + @Override + public @NotNull Datafixer[] getDatafixers() { + return new Datafixer[]{ (original, jankson) -> { // 0 -> 1 original.put("branch", jankson.toJson(jankson.getMarshaller().marshall(String.class, original.get("githubRef")).replace("refs/heads/", ""))); - original.put("repoUrl", original.get("githubUrl")); - original.put("isRepoPrivate", original.get("isPrivate")); + datafixField(original, "githubUrl", "repoUrl"); + datafixField(original, "isPrivate", "isRepoPrivate"); }, (original, jankson) -> { // 1 -> 2 // noop + }, + (original, jankson) -> { + // 2 -> 3 + + final Marshaller marsh = jankson.getMarshaller(); + + // Server Info + final JsonObject serverInfo = new JsonObject(); + + datafixField(original, "serverPublicIp", serverInfo, "publicIp"); + datafixField(original, "proxyPort", serverInfo, "proxyPort"); + + original.put("serverInfo", serverInfo); + + // Repository Info + final JsonObject repositoryInfo = new JsonObject(); + + datafixField(original, "branch", repositoryInfo, "branch"); + datafixField(original, "repoUrl", repositoryInfo, "url"); + + datafixField(original, "isRepoPrivate", repositoryInfo, "isPrivate"); + + datafixField(original, "githubUsername", repositoryInfo, "username"); + datafixField(original, "githubToken", repositoryInfo, "token"); + + original.put("repositoryInfo", repositoryInfo); + + // Resource Pack Provider + final JsonObject resourcePackProvider = new JsonObject(); + + datafixField(original, "resourcePackRoot", serverInfo, "rootLocation"); + + datafixField(original, "packUpdateMessage", serverInfo, "updateMessage"); + datafixField(original, "packUpdateMessageHoverMessage", serverInfo, "updateMessageHoverMessage"); + + final String webhookUrl = datafixGetAndRemove(marsh, String.class, original, "webhookUrl"); + final String webhookBody = datafixGetAndRemove(marsh, String.class, original, "webhookBody"); + if (webhookUrl != null && webhookBody != null) { + final JsonObject webhookInfo = new JsonObject(); + webhookInfo.put("enabled", jankson.toJson(true)); + webhookInfo.put("url", jankson.toJson(webhookUrl)); + webhookInfo.put("body", jankson.toJson(webhookBody)); + + resourcePackProvider.put("successWebhook", webhookInfo); + if (!webhookBody.contains("discord")) resourcePackProvider.put("failWebhook", webhookInfo); + } + + original.put("resourcePackProvider", resourcePackProvider); + }, + (original, jankson) -> { + // 3 -> 4 + final Marshaller marsh = jankson.getMarshaller(); + + original.put("resourcePackProvider", datafix3to4UpdateMessage((JsonObject) original.get("resourcePackProvider"), marsh)); + original.put("dataPackProvider", datafix3to4UpdateMessage((JsonObject) original.get("dataPackProvider"), marsh)); } - ); + }; + } + + private static void datafixField(final @NotNull JsonObject originalObject, final @NotNull String originalKey, final @NotNull String newKey) { + datafixField(originalObject, originalKey, originalObject, newKey); + } + private static void datafixField(final @NotNull JsonObject originalObject, final @NotNull String originalKey, final @NotNull JsonObject newObject, final @NotNull String newKey) { + final JsonElement originalValue = originalObject.get(originalKey); + if (originalValue == null) { + LOGGER.warn("JSON of current config doesn't contain value with key '%s', new value with key '%s' will be reset to default!", originalKey, newKey); + return; + } + + newObject.put(newKey, originalValue); + originalObject.remove(originalKey); + } + + @Nullable + private static T datafixGetAndRemove(final @NotNull Marshaller marsh, final @NotNull Class type, final @NotNull JsonObject object, final @NotNull String key) { + final JsonElement jsonValue = object.get(key); + if (jsonValue == null) { + LOGGER.warn("JSON of current config doesn't contain value with key '%s'!", key); + return null; + } + object.remove(key); + return marsh.marshall(type, jsonValue); + } + + private static JsonObject datafix3to4UpdateMessage(final @NotNull JsonObject originalJson, final @NotNull Marshaller marsh) { + final String updateMessage = datafixGetAndRemove(marsh, String.class, originalJson, "updateMessage"); + if (updateMessage == null) { + originalJson.put("updateMessage", JsonNull.INSTANCE); + return originalJson; + } + + final String updateMessageHoverMessage = datafixGetAndRemove(marsh, String.class, originalJson, "updateMessageHoverMessage"); + + final String[] newUpdateMessage = updateMessage.split("\\n"); + for (int i = 0; i < newUpdateMessage.length; i++) { + newUpdateMessage[i] = newUpdateMessage[i].replaceAll("\\{packUpdateCommand}", "&{hoverText,'Click to update pack','&{runCommand,'{packUpdateCommand}','[HERE]'}'}"); + if (updateMessageHoverMessage != null) + newUpdateMessage[i] = "&{hoverText,'%s','%s'}".formatted(updateMessageHoverMessage, newUpdateMessage[i].replace("'", "\\'")); + } + originalJson.put("updateMessage", new JsonArray(newUpdateMessage, marsh)); + + return originalJson; } - public void createDefaultWebhooks() { - final Jankson jankson = new Jankson.Builder().build(); - final List webhookBodies = List.of(new BasicWebhook(), new BasicMessage(), new EmbedMessage()); + @Override + public int getConfigVersion() { + return 4; + } - for (DefaultWebhookBody webhook : webhookBodies) { - final Path location = CURRENT_CONFIG_FILE_PATH.getParent().resolve(webhook.getName()); + @Override + public @NotNull Path getConfigDirPath() { + return PlatformUtil.getConfigDir(); + } - if (Files.exists(location)) continue; + @Override + public @NotNull String getId() { + return MOD_ID + "config/main"; + } + @Override + public void beforeLoadStart() { + if (Files.exists(PlatformUtil.getConfigDir().resolve(MOD_ID + ".json"))) { try { - Files.createDirectories(location.getParent()); - Files.writeString(location, jankson.toJson(webhook).toJson(JsonGrammar.STRICT)); + Files.createDirectories(PlatformUtil.getConfigDir().resolve(MOD_ID).resolve(MOD_ID + ".json").getParent()); + Files.move(PlatformUtil.getConfigDir().resolve(MOD_ID + ".json"), PlatformUtil.getConfigDir().resolve(MOD_ID).resolve(MOD_ID + ".json")); } catch (IOException e) { - LOGGER.error("Failed to write default webhook body '%s'!", webhook.getName(), e); + throw new RuntimeException("Failed to move config file to new location!", e); + } + } + if (Files.exists(PlatformUtil.getConfigDir().resolve(MOD_ID).resolve(MOD_ID + ".json"))) { + try { + Files.createDirectories(getFilePath().getParent()); + Files.move(PlatformUtil.getConfigDir().resolve(MOD_ID).resolve(MOD_ID + ".json"), getFilePath()); + } catch (IOException e) { + throw new RuntimeException("Failed to move config file to new location!", e); } } } + @SuppressWarnings("HttpUrlsUsage") public String getPackUrl(String outputFileName) { return String.format( - "http://%s:%s/%s/%s", - serverPublicIp, - proxyPort == null ? PlatformServerProperties.INSTANCE.getServerPort() : proxyPort, + "%s%s:%s/%s/%s", + (serverInfo.publicIp.startsWith("http://") || serverInfo.publicIp.startsWith("https://") ? "" : "http://"), + serverInfo.publicIp, + serverInfo.proxyPort == null ? PlatformServerProperties.INSTANCE.getServerPort() : serverInfo.proxyPort, MOD_URI, outputFileName ); } - public URI getWebhookUrl() { - if (webhookUrl == null) return null; - return URI.create(webhookUrl); - } - - public Path getWebhookBody() { - if (webhookBody == null) return null; - return CURRENT_CONFIG_FILE_PATH.getParent().resolve(webhookBody); - } - - public Path getResourcePackRoot() { - return REPO_ROOT_FOLDER.resolve(config.resourcePackRoot.startsWith("/") ? config.resourcePackRoot.substring(1) : config.resourcePackRoot); - } - public Path getPacksDir() { - return getResourcePackRoot().resolve("packs"); - } - public String getGithubRef() { - return "refs/heads/" + branch; + return "refs/heads/" + repositoryInfo.branch; } } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/basic/BasicFailMessage.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/basic/BasicFailMessage.java new file mode 100644 index 0000000..a52ef58 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/basic/BasicFailMessage.java @@ -0,0 +1,15 @@ +package top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.basic; + +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.DefaultWebhookBody; + +public final class BasicFailMessage implements DefaultWebhookBody { + + public final String username = "GitHub Resource Pack Manager"; + public final String avatar_url = "https://github.com/OffsetMods538/Github-Resourcepack-Manager/blob/master/src/main/resources/assets/github-resourcepack-manager/icon.png?raw=true"; + public final String content = "Pack update failed!"; + + @Override + public String getName() { + return "discord/basic/fail.json"; + } +} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/BasicMessage.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/basic/BasicSuccessMessage.java similarity index 81% rename from common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/BasicMessage.java rename to common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/basic/BasicSuccessMessage.java index 640a4d4..6f29821 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/BasicMessage.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/basic/BasicSuccessMessage.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord; +package top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.basic; import top.offsetmonkey538.githubresourcepackmanager.config.webhook.DefaultWebhookBody; -public final class BasicMessage implements DefaultWebhookBody { +public final class BasicSuccessMessage implements DefaultWebhookBody { public final String username = "GitHub Resource Pack Manager"; public final String avatar_url = "https://github.com/OffsetMods538/Github-Resourcepack-Manager/blob/master/src/main/resources/assets/github-resourcepack-manager/icon.png?raw=true"; @@ -10,6 +10,6 @@ public final class BasicMessage implements DefaultWebhookBody { @Override public String getName() { - return "discord/basic_message.json"; + return "discord/basic/success.json"; } } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/embed/EmbedFailMessage.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/embed/EmbedFailMessage.java new file mode 100644 index 0000000..11e7313 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/embed/EmbedFailMessage.java @@ -0,0 +1,22 @@ +package top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.embed; + +import top.offsetmonkey538.githubresourcepackmanager.config.webhook.DefaultWebhookBody; + +public final class EmbedFailMessage implements DefaultWebhookBody { + + public final String username = "GitHub Resource Pack Manager"; + public final String avatar_url = "https://github.com/OffsetMods538/Github-Resourcepack-Manager/blob/master/src/main/resources/assets/github-resourcepack-manager/icon.png?raw=true"; + public final Embed[] embeds = new Embed[] { + new Embed( + "Pack update failed!", + 0xFF0000 + ) + }; + + public record Embed(String title, int color) {} + + @Override + public String getName() { + return "discord/embed/fail.json"; + } +} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/EmbedMessage.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/embed/EmbedSuccessMessage.java similarity index 86% rename from common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/EmbedMessage.java rename to common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/embed/EmbedSuccessMessage.java index b5057d2..3af4d13 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/EmbedMessage.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/config/webhook/discord/embed/EmbedSuccessMessage.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord; +package top.offsetmonkey538.githubresourcepackmanager.config.webhook.discord.embed; import top.offsetmonkey538.githubresourcepackmanager.config.webhook.DefaultWebhookBody; -public final class EmbedMessage implements DefaultWebhookBody { +public final class EmbedSuccessMessage implements DefaultWebhookBody { public final String username = "GitHub Resource Pack Manager"; public final String avatar_url = "https://github.com/OffsetMods538/Github-Resourcepack-Manager/blob/master/src/main/resources/assets/github-resourcepack-manager/icon.png?raw=true"; @@ -10,7 +10,7 @@ public final class EmbedMessage implements DefaultWebhookBody { new Embed( "New update for pack released!", "Download [here]({downloadUrl})!", - 16722304, + 0xFF2980, new Field[] { new Field( "Description", @@ -25,6 +25,6 @@ public record Field(String name, String value) {} @Override public String getName() { - return "discord/embed_message.json"; + return "discord/embed/success.json"; } } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/DataPackHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/DataPackHandler.java new file mode 100644 index 0000000..4b81956 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/DataPackHandler.java @@ -0,0 +1,165 @@ +package top.offsetmonkey538.githubresourcepackmanager.handler; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.apache.commons.io.file.PathUtils; +import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; +import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Stream; + +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; + +public class DataPackHandler { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + private static final Path STATE_FILE = DATAPACK_FOLDER.resolve("state.json"); + + public void generatePack() throws GithubResourcepackManagerException { + final Path datapacks = PlatformServerProperties.INSTANCE.getDatapacksDir(); + + + // Delete existing stuff + final State existingPacks; + try { + existingPacks = readStateFile(); + } catch (IOException e) { + throw new GithubResourcepackManagerException("Failed to read state file!", e); + } + + Arrays.stream(existingPacks.packs) + .map(datapacks::resolve) + .forEach(path -> { + if (Files.notExists(path)) { + LOGGER.warn("Not deleting pack at '%s' as it doesn't exist!", path.toAbsolutePath()); + return; + } + + try { + PathUtils.delete(path); + LOGGER.info("Deleted pack at '%s'!", path.toAbsolutePath()); + } catch (IOException e) { + LOGGER.error("Failed to delete pack at '%s'!", e, path.toAbsolutePath()); + } + }); + + + // Get new packs + final List sourcePacks; + + try { + sourcePacks = gatherSourcePacks(); + } catch (GithubResourcepackManagerException e) { + throw new GithubResourcepackManagerException("Failed to gather source packs!", e); + } + + // Delete ones with same name from datapacks + for (final Path sourcePack : sourcePacks) { + final Path path = datapacks.resolve(sourcePack.getFileName()); + if (Files.notExists(path)) continue; + + LOGGER.info("Deleting pack at '%s' to replace it...", path.toAbsolutePath()); + try { + PathUtils.delete(path); + LOGGER.info("Deleted pack at '%s'!", path.toAbsolutePath()); + } catch (IOException e) { + throw new GithubResourcepackManagerException("Failed to delete pack at '%s'!", e, path.toAbsolutePath()); + } + } + + // Finally copy over the new ones + for (final Path path : sourcePacks) { + try { + final Path destination = datapacks.resolve(path.getFileName()); + + if (Files.isDirectory(path)) PathUtils.copyDirectory(path, destination); + else Files.copy(path, destination); + + LOGGER.info("Copied pack from '%s' to '%s'.", path.toAbsolutePath(), destination.toAbsolutePath()); + } catch (IOException e) { + throw new GithubResourcepackManagerException("Failed to copy pack at '%s' to datapacks directory at '%s'!", e, path.toAbsolutePath(), datapacks.toAbsolutePath()); + } + } + + try { + writeStateFile(sourcePacks); + } catch (IOException e) { + throw new GithubResourcepackManagerException("Failed to write state file!", e); + } + } + + private void writeStateFile(List files) throws IOException { + Files.createDirectories(STATE_FILE.getParent()); + + final String json = GSON.toJson(new State(files.stream().map(Path::getFileName).map(Path::toString).toArray(String[]::new))); + + Files.writeString(STATE_FILE, json); + } + + private State readStateFile() throws IOException { + if (Files.notExists(STATE_FILE)) { + LOGGER.warn("State file '%s' not found! No datapacks will be deleted!".formatted(STATE_FILE)); + return new State(new String[]{}); + } + + return GSON.fromJson(Files.readString(STATE_FILE), State.class); + } + + private List gatherSourcePacks() throws GithubResourcepackManagerException { + LOGGER.info("Checking for 'pack.mcmeta' in data pack root..."); + final boolean hasPackMcmeta = Files.exists(config.get().dataPackProvider.getPackRoot().resolve("pack.mcmeta")); + LOGGER.info("%sFound!", hasPackMcmeta ? "" : "Not "); + + LOGGER.info("Checking for 'packs' directory in data pack root..."); + Path packsDir = config.get().dataPackProvider.getPackPacksDir(); + final boolean hasPacksFolder = Files.exists(packsDir) && Files.isDirectory(packsDir); + LOGGER.info("%sFound!", hasPacksFolder ? "" : "Not "); + + if (hasPackMcmeta && hasPacksFolder) { + throw new GithubResourcepackManagerException("Found both 'pack.mcmeta' and the 'packs' directory in data pack root '%s'!", config.get().dataPackProvider.getPackRoot().toAbsolutePath()); + } + if (!hasPackMcmeta && !hasPacksFolder) { + LOGGER.info("Found neither 'pack.mcmeta' nor the 'packs' directory in data pack root '%s'!", config.get().dataPackProvider.getPackPacksDir().toAbsolutePath()); + LOGGER.info("Assuming data pack root '%s' as 'packs' directory.", config.get().dataPackProvider.getPackPacksDir().toAbsolutePath()); + packsDir = config.get().dataPackProvider.getPackRoot(); + } + + + if (hasPackMcmeta) { + LOGGER.info("Using data pack root as data pack."); + return List.of(config.get().dataPackProvider.getPackRoot()); + } + + LOGGER.info("Using 'packs' directory for data packs."); + return gatherSourcePacksFrom(packsDir); + } + + private List gatherSourcePacksFrom(final Path packsDir) throws GithubResourcepackManagerException { + try (final Stream sourcePacks = Files.list(packsDir)) { + final List result = sourcePacks + .filter(path -> { + final boolean hidden = path.getFileName().startsWith("."); + if (!hidden) return true; + LOGGER.warn("Excluding hidden file '%s'", path.toAbsolutePath()); + return false; + }) + .toList(); + + if (result.isEmpty()) + throw new GithubResourcepackManagerException("Repository contains empty 'packs' folder!"); + + return result; + } catch (IOException e) { + throw new GithubResourcepackManagerException("Failed to list files in 'packs' folder'!", e); + } + } + + + private record State(String[] packs) { + + } +} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/GitHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/GitHandler.java index 94ed0c5..fdac1e4 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/GitHandler.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/GitHandler.java @@ -1,10 +1,12 @@ package top.offsetmonkey538.githubresourcepackmanager.handler; -import org.apache.commons.io.FileUtils; +import org.apache.commons.io.file.PathUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.PullResult; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.ContentMergeStrategy; @@ -13,18 +15,21 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.jetbrains.annotations.Nullable; import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; import top.offsetmonkey538.githubresourcepackmanager.git.CommitProperties; import java.io.IOException; +import java.util.List; +import java.util.Optional; import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; public class GitHandler { private CommitProperties commitProperties; - private boolean wasUpdated; + private @Nullable List changedFiles; public void updateRepositoryAndGenerateCommitProperties() throws GithubResourcepackManagerException { String originalCommitHash; @@ -41,7 +46,14 @@ public void updateRepositoryAndGenerateCommitProperties() throws GithubResourcep final String newCommitHash = getLatestCommitHash(); commitProperties = getLatestCommitProperties(originalCommitHash, newCommitHash); - wasUpdated = !newCommitHash.equals(originalCommitHash); + + try { + changedFiles = getDiff(originalCommitHash); + } catch (Exception e) { + // Most likely to happen with a fresh install or a forced update so should be fine to just assume this + LOGGER.error("Failed to get diff between commit '%s' and '%s'! Assuming pack directories were updated anyway...", e, originalCommitHash, newCommitHash); + changedFiles = null; + } } @@ -68,20 +80,20 @@ private static CommitProperties getLatestCommitProperties(String lastCommitHash, private static void updateRepository(boolean retry) throws GithubResourcepackManagerException { // Create credentials provider if repository is private CredentialsProvider credentialsProvider = null; - if (config.isRepoPrivate) - credentialsProvider = new UsernamePasswordCredentialsProvider(config.githubUsername, config.githubToken); + if (config.get().repositoryInfo.isPrivate) + credentialsProvider = new UsernamePasswordCredentialsProvider(config.get().repositoryInfo.username, config.get().repositoryInfo.token); // If the repo folder doesn't exist, clone the repository. - if (!REPO_ROOT_FOLDER.toFile().exists()) cloneRepository(credentialsProvider); + if (!GIT_FOLDER.toFile().exists()) cloneRepository(credentialsProvider); // Pull from the remote boolean updateFailed = false; - try (Git git = Git.open(REPO_ROOT_FOLDER.toFile())) { + try (Git git = Git.open(GIT_FOLDER.toFile())) { final PullResult result = git.pull() .setCredentialsProvider(credentialsProvider) .setContentMergeStrategy(ContentMergeStrategy.THEIRS) .setStrategy(MergeStrategy.THEIRS) - .setRemoteBranchName(config.getGithubRef()) + .setRemoteBranchName(config.get().getGithubRef()) .call(); // Handle errors @@ -106,7 +118,7 @@ private static void updateRepository(boolean retry) throws GithubResourcepackMan LOGGER.info("Deleting git folder and trying again..."); try { - FileUtils.deleteDirectory(REPO_ROOT_FOLDER.toFile()); + PathUtils.deleteDirectory(GIT_FOLDER); } catch (IOException e) { LOGGER.error("Failed to delete directory!", e); } @@ -119,9 +131,9 @@ private static void updateRepository(boolean retry) throws GithubResourcepackMan private static void cloneRepository(CredentialsProvider credentialsProvider) throws GithubResourcepackManagerException { try { Git git = Git.cloneRepository() - .setURI(config.repoUrl) - .setDirectory(REPO_ROOT_FOLDER.toFile()) - .setBranch(config.getGithubRef()) + .setURI(config.get().repositoryInfo.url) + .setDirectory(GIT_FOLDER.toFile()) + .setBranch(config.get().getGithubRef()) .setCredentialsProvider(credentialsProvider) .call(); git.close(); @@ -142,8 +154,38 @@ private static Ref getLatestCommit() throws GithubResourcepackManagerException { } } + private static List getDiff(String startingHash) throws IOException, GitAPIException { + try (Git git = Git.open(GIT_FOLDER.toFile())) { + final Repository repository = git.getRepository(); + + final ObjectId headCommit = repository.resolve("HEAD^{tree}"); + final ObjectId startingCommit = repository.resolve("%s^{tree}".formatted(startingHash)); + if (startingCommit == null) throw new IllegalArgumentException("Previous commit (hash '%s') doesn't exist!".formatted(startingHash)); + + try (final ObjectReader repoReader = repository.newObjectReader()) { + final CanonicalTreeParser headTreeParser = new CanonicalTreeParser(); + headTreeParser.reset(repoReader, headCommit); + + final CanonicalTreeParser startingTreeParser = new CanonicalTreeParser(); + startingTreeParser.reset(repoReader, startingCommit); + + return git + .diff() + .setNewTree(headTreeParser) + .setOldTree(startingTreeParser) + .call() + + .stream() + .map(entry -> "/dev/null".equals(entry.getNewPath()) ? entry.getOldPath() : entry.getNewPath()) + .toList(); + } + } catch (IOException e) { + throw new IOException("Failed to open repository!", e); + } + } + private static Repository getRepository() throws GithubResourcepackManagerException { - try (Git git = Git.open(REPO_ROOT_FOLDER.toFile())) { + try (Git git = Git.open(GIT_FOLDER.toFile())) { return git.getRepository(); } catch (IOException e) { throw new GithubResourcepackManagerException("Failed to open repository!", e); @@ -154,7 +196,7 @@ public CommitProperties getCommitProperties() { return commitProperties; } - public boolean getWasUpdated() { - return wasUpdated; + public Optional> getChangedFiles() { + return Optional.ofNullable(changedFiles); } } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/PackHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/ResourcePackHandler.java similarity index 74% rename from common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/PackHandler.java rename to common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/ResourcePackHandler.java index c20635f..7c4ac35 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/PackHandler.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/handler/ResourcePackHandler.java @@ -18,9 +18,8 @@ import java.util.stream.Stream; import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; -public class PackHandler { +public class ResourcePackHandler { private Path outputPackPath; public void generatePack(boolean wasUpdated, Path oldPackPath, String oldPackName) throws GithubResourcepackManagerException { @@ -34,12 +33,12 @@ public void generatePack(boolean wasUpdated, Path oldPackPath, String oldPackNam } private void generateNewPack() throws GithubResourcepackManagerException { - final boolean isMultiPack; + final List sourcePacks; try { - isMultiPack = getPackType(); + sourcePacks = gatherSourcePacks(); } catch (GithubResourcepackManagerException e) { - throw new GithubResourcepackManagerException("Failed to determine pack type!", e); + throw new GithubResourcepackManagerException("Failed to gather source packs!", e); } @@ -53,16 +52,15 @@ private void generateNewPack() throws GithubResourcepackManagerException { final File tempOutputDir = MyFileUtils.createDir(tmpDir.resolve("output").toFile()); // Extract packs into the temporary packs directory - final List sourcePacks; try { - sourcePacks = extractSourcePacks(isMultiPack, tempOutputDir); + extractSourcePacks(sourcePacks, tempOutputDir); } catch (GithubResourcepackManagerException e) { throw new GithubResourcepackManagerException("Failed to extract source packs!", e); } // Write file with source pack names try { - writeSourcePacksFile(isMultiPack, sourcePacks, tempOutputDir); + writeSourcePacksFile(sourcePacks, tempOutputDir); } catch (GithubResourcepackManagerException e) { throw new GithubResourcepackManagerException("Failed to write pack content file!", e); } @@ -82,8 +80,8 @@ private void generateNewPack() throws GithubResourcepackManagerException { } } - private void writeSourcePacksFile(boolean isMultiPack, List sourcePacks, File outputDir) throws GithubResourcepackManagerException { - if (!isMultiPack) return; + private void writeSourcePacksFile(List sourcePacks, File outputDir) throws GithubResourcepackManagerException { + if (sourcePacks.size() == 1) return; final Path sourcePacksFile = outputDir.toPath().resolve("content.txt"); MyFileUtils.createNewFile(sourcePacksFile.toFile()); @@ -101,13 +99,7 @@ private void writeSourcePacksFile(boolean isMultiPack, List sourcePacks, F } } - private List extractSourcePacks(boolean isMultiPack, File tempOutputDir) throws GithubResourcepackManagerException { - // Gather source packs - final List sourcePacks; - - if (!isMultiPack) sourcePacks = List.of(config.getResourcePackRoot().toFile()); - else sourcePacks = gatherSourcePacks(); - + private void extractSourcePacks(List sourcePacks, File tempOutputDir) throws GithubResourcepackManagerException { // Extract source packs for (File sourcePack : sourcePacks) { if (sourcePack.isDirectory()) { @@ -126,51 +118,52 @@ private List extractSourcePacks(boolean isMultiPack, File tempOutputDir) t LOGGER.error("'%s' is not a valid pack! Ignoring...", sourcePack); sourcePacks.remove(sourcePack); } - - return sourcePacks; } - private List gatherSourcePacks() throws GithubResourcepackManagerException { + private List gatherSourcePacksFrom(final Path packsDir) throws GithubResourcepackManagerException { // Gather resource packs - final File[] sourcePacksArray = config.getPacksDir().toFile().listFiles(); + final File[] sourcePacksArray = packsDir.toFile().listFiles(); if (sourcePacksArray == null) throw new GithubResourcepackManagerException("Repository contains empty 'packs' folder!"); // Return source packs sorted in correct order. return Stream.of(sourcePacksArray) + .filter(file -> { + final boolean hidden = file.getName().startsWith("."); + if (!hidden) return true; + LOGGER.warn("Excluding hidden file '%s'", file.getAbsolutePath()); + return false; + }) .sorted(Comparator.comparingInt(StringUtils::extractPriorityFromFile).reversed()) .toList(); } - /** - * Get the type of the pack. Either multi or single pack. - * - * @return true if type is multi pack, false if it's single pack. - * @throws GithubResourcepackManagerException when the type of the pack couldn't be determined. - */ - private boolean getPackType() throws GithubResourcepackManagerException { - LOGGER.info("Checking for 'pack.mcmeta' in repository root..."); - final boolean hasPackMcmeta = config.getResourcePackRoot().resolve("pack.mcmeta").toFile().exists(); + private List gatherSourcePacks() throws GithubResourcepackManagerException { + LOGGER.info("Checking for 'pack.mcmeta' in resource pack root..."); + final boolean hasPackMcmeta = config.get().resourcePackProvider.getPackRoot().resolve("pack.mcmeta").toFile().exists(); LOGGER.info("%sFound!", hasPackMcmeta ? "" : "Not "); - LOGGER.info("Checking for 'packs' directory in repository root..."); - final boolean hasPacksFolder = config.getPacksDir().toFile().exists() && config.getPacksDir().toFile().isDirectory(); + LOGGER.info("Checking for 'packs' directory in resource pack root..."); + Path packsDir = config.get().resourcePackProvider.getPackPacksDir(); + final boolean hasPacksFolder = Files.exists(packsDir) && Files.isDirectory(packsDir); LOGGER.info("%sFound!", hasPacksFolder ? "" : "Not "); if (hasPackMcmeta && hasPacksFolder) { - throw new GithubResourcepackManagerException("Found both 'pack.mcmeta' and the 'packs' directory in repository root '%s'!", config.getPacksDir().toAbsolutePath()); + throw new GithubResourcepackManagerException("Found both 'pack.mcmeta' and the 'packs' directory in resource pack root '%s'!", config.get().resourcePackProvider.getPackPacksDir().toAbsolutePath()); } if (!hasPackMcmeta && !hasPacksFolder) { - throw new GithubResourcepackManagerException("Found neither 'pack.mcmeta' nor the 'packs' directory in repository root '%s'!", config.getPacksDir().toAbsolutePath()); + LOGGER.info("Found neither 'pack.mcmeta' nor the 'packs' directory in resource pack root '%s'!", config.get().resourcePackProvider.getPackPacksDir().toAbsolutePath()); + LOGGER.info("Assuming resource pack root '%s' as 'packs' directory.", config.get().resourcePackProvider.getPackPacksDir().toAbsolutePath()); + packsDir = config.get().resourcePackProvider.getPackRoot(); } if (hasPackMcmeta) { - LOGGER.info("Using repository root as resource pack."); - return false; + LOGGER.info("Using resource pack root as resource pack."); + return List.of(config.get().resourcePackProvider.getPackRoot().toFile()); } LOGGER.info("Using 'packs' directory for resource packs."); - return true; + return gatherSourcePacksFrom(packsDir); } @@ -184,7 +177,7 @@ private Path handleOldPackAndGetOutputPackPath(boolean wasUpdated, @Nullable Pat } catch (IOException e) { LOGGER.error("Failed to delete old pack!", new GithubResourcepackManagerException("Failed to delete old pack '%s'!", e, oldPackPath)); } - return OUTPUT_FOLDER.resolve(newPackName); + return RESOURCEPACK_OUTPUT_FOLDER.resolve(newPackName); } private String generateRandomPackName(@Nullable String oldPackNameString) { diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/FileHttpHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/FileHttpHandler.java index 217b1a0..a534078 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/FileHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/FileHttpHandler.java @@ -10,14 +10,14 @@ import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.packHandler; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.resourcePackHandler; +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.LOGGER; public final class FileHttpHandler { private FileHttpHandler() {} public static void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { - final File fileToServe = packHandler.getOutputPackFile(); + final File fileToServe = resourcePackHandler.getOutputPackFile(); final long fileLength = fileToServe.length(); diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/MainHttpHandler.java index 4771df9..43ae2b2 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/MainHttpHandler.java @@ -4,23 +4,16 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpMethod; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; public class MainHttpHandler implements HttpHandler { @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { - if (!request.decoderResult().isSuccess()) { - HttpHandler.sendError(ctx, BAD_REQUEST); - return; - } - - LOGGER.debug("Received Request: " + request); - - + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { final HttpMethod method = request.method(); // GET request should go to fileserver @@ -36,6 +29,6 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR } // If we reach this point, then the request method isn't supported - HttpHandler.sendError(ctx, METHOD_NOT_ALLOWED); + sendError(ctx, METHOD_NOT_ALLOWED); } } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/WebhookHttpHandler.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/WebhookHttpHandler.java index 52bfd94..b7e4b9d 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/WebhookHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/networking/WebhookHttpHandler.java @@ -1,65 +1,17 @@ package top.offsetmonkey538.githubresourcepackmanager.networking; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; import io.netty.channel.*; import io.netty.handler.codec.http.*; import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; -import top.offsetmonkey538.meshlib.api.HttpHandler; - -import java.nio.charset.StandardCharsets; import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.config; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; public final class WebhookHttpHandler { private WebhookHttpHandler() {} - private static final Gson GSON = new GsonBuilder().create(); - public static void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { - if (!"application/json".contentEquals(HttpUtil.getMimeType(request))) { - LOGGER.warn(String.format("Bad request: POST request made with incorrect mime type '%s', expected 'application/json'", HttpUtil.getMimeType(request))); - HttpHandler.sendError(ctx, BAD_REQUEST); - return; - } - - - // Get the event header - final String githubEvent = request.headers().get("x-github-event"); - - if (githubEvent == null || githubEvent.isBlank()) { - HttpHandler.sendError(ctx, BAD_REQUEST, "Request headers don't contain 'x-github-event'"); - return; - } - - if (!githubEvent.contains("push")) { - HttpHandler.sendError(ctx, BAD_REQUEST, "Request isn't for push event"); - return; - } - LOGGER.debug("Received github push event"); - - // Get payload - final JsonObject payload = GSON.fromJson(request.content().toString(StandardCharsets.UTF_8), JsonObject.class); - if (payload == null) { - HttpHandler.sendError(ctx, BAD_REQUEST, "Request doesn't contain content"); - return; - } - - // Respond with "yeh bro everythins alright" ctx.writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, OK)).addListener(ChannelFutureListener.CLOSE); - - // Check which branch was pushed to - final String ref = payload.get("ref").getAsString(); - LOGGER.debug("Ref: %s", ref); - - if (!config.getGithubRef().equals(ref)) return; - - LOGGER.debug("Tracked branch has been updated, updating local pack..."); - GithubResourcepackManager.updatePack(GithubResourcepackManager.UpdateType.WEBHOOK); - LOGGER.debug("Local pack has been updated."); + GithubResourcepackManager.updatePack(GithubResourcepackManager.UpdateType.WEBHOOK, false); } } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformCommand.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformCommand.java index 425d1ff..5050000 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformCommand.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformCommand.java @@ -1,9 +1,13 @@ package top.offsetmonkey538.githubresourcepackmanager.platform; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import org.jetbrains.annotations.NotNull; + import static top.offsetmonkey538.githubresourcepackmanager.platform.ServiceLoader.load; public interface PlatformCommand { PlatformCommand INSTANCE = load(PlatformCommand.class); - void registerGithubRpManagerCommand(); + int executeRequestPackCommand(final @NotNull CommandContext ctx) throws CommandSyntaxException; } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformLogging.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformLogging.java deleted file mode 100644 index 9cf032d..0000000 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformLogging.java +++ /dev/null @@ -1,33 +0,0 @@ -package top.offsetmonkey538.githubresourcepackmanager.platform; - -import static top.offsetmonkey538.githubresourcepackmanager.platform.ServiceLoader.load; - -public interface PlatformLogging { - PlatformLogging LOGGER = load(PlatformLogging.class); - - default void debug(String message, Object... args) { - debug(String.format(message, args)); - } - default void info(String message, Object... args) { - info(String.format(message, args)); - } - default void warn(String message, Object... args) { - warn(String.format(message, args)); - } - default void warn(String message, Throwable error, Object... args) { - warn(String.format(message, args), error); - } - default void error(String message, Object... args) { - error(String.format(message, args)); - } - default void error(String message, Throwable error, Object... args) { - error(String.format(message, args), error); - } - - void debug(String message); - void info(String message); - void warn(String message); - void warn(String message, Throwable error); - void error(String message); - void error(String message, Throwable error); -} diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformMain.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformMain.java index a3f80d6..e37818a 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformMain.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformMain.java @@ -1,6 +1,8 @@ package top.offsetmonkey538.githubresourcepackmanager.platform; -import java.nio.file.Path; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; + +import java.util.List; import static top.offsetmonkey538.githubresourcepackmanager.platform.ServiceLoader.load; @@ -8,19 +10,16 @@ public interface PlatformMain { PlatformMain INSTANCE = load(PlatformMain.class); /** - * Must already contain the mod id. + * Tell the server to refresh the list of available datapacks. *

- * Example: .minecraft/config/github-resourcepack-manager/ - * Example: .minecraft/plugins/Github-Resourcepack-Manager/ - * - * @return + * This way the user won't have to run {@code /datapack list} before being able to enable them */ - Path getConfigDir(); + void refreshDatapacks(); /** - * Must be called when initializing + * Sends provided message to currently online admins * - * @param work the stuff to run + * @param message the message to send */ - void runOnServerStart(Runnable work); + void sendMessageToAdmins(final MonkeyLibText message); } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformServerProperties.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformServerProperties.java index bb728b0..5685317 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformServerProperties.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformServerProperties.java @@ -2,13 +2,13 @@ import com.google.common.hash.Hashing; import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; -import top.offsetmonkey538.githubresourcepackmanager.handler.PackHandler; +import top.offsetmonkey538.githubresourcepackmanager.handler.ResourcePackHandler; import java.io.IOException; +import java.nio.file.Path; import java.util.Map; import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.*; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; import static top.offsetmonkey538.githubresourcepackmanager.platform.ServiceLoader.load; public interface PlatformServerProperties { @@ -16,11 +16,12 @@ public interface PlatformServerProperties { String getResourcePackUrl(); String getServerPort(); + Path getDatapacksDir(); void setProperties(Map properties); void reload() throws GithubResourcepackManagerException; - default void updatePackProperties(PackHandler packHandler) throws GithubResourcepackManagerException { - final String resourcePackUrl = config.getPackUrl(packHandler.getOutputPackName()); + default void updatePackProperties(ResourcePackHandler packHandler) throws GithubResourcepackManagerException { + final String resourcePackUrl = config.get().getPackUrl(packHandler.getOutputPackName()); final String resourcePackSha1; try { // Ignore the fact that sha1 hashing is deprecated as Minecraft uses it for validating server resource packs. @@ -34,7 +35,7 @@ default void updatePackProperties(PackHandler packHandler) throws GithubResource LOGGER.info("New resource pack url: '%s'", resourcePackUrl); LOGGER.info("New resource pack sha1: '%s'", resourcePackSha1); setProperties(Map.of( - "resource-pack-id", PACK_UUID.toString(), + "resource-pack-id", RESOURCEPACK_UUID.toString(), "resource-pack", resourcePackUrl, "resource-pack-sha1", resourcePackSha1 )); diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformText.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformText.java index 3a7341c..84272d1 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformText.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/PlatformText.java @@ -1,13 +1,11 @@ package top.offsetmonkey538.githubresourcepackmanager.platform; -import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; - -import java.util.Map; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; import static top.offsetmonkey538.githubresourcepackmanager.platform.ServiceLoader.load; public interface PlatformText { PlatformText INSTANCE = load(PlatformText.class); - void sendUpdateMessage(Map placeholders) throws GithubResourcepackManagerException; + void sendUpdateMessage(final MonkeyLibText updateMessage, boolean adminsOnly); } diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/StringUtils.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/StringUtils.java index 67a731b..7468c7b 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/StringUtils.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/StringUtils.java @@ -6,8 +6,8 @@ import java.util.Map; import java.util.regex.Matcher; -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.PACK_NAME_PATTERN; -import static top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging.LOGGER; +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.RESOURCEPACK_NAME_PATTERN; +import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.LOGGER; public final class StringUtils { private StringUtils() { @@ -17,41 +17,40 @@ private StringUtils() { /** * Replaces all instances of the keys in the placeholders map with their values. *

- * Same as calling {@link StringUtils#replacePlaceholders(String, Map, boolean)} with {@code escapeQuotes} false. + * Replaces {@code '} with {@code \'} in the placeholders if {@code escapeSingleQuotes} is true. + *
+ * Replaces {@code "} with {@code \"} in the placeholders if {@code escapeDoubleQuotes} is true. * * @param string The string to replace placeholders in. * @param placeholders The placeholders to replace. + * @param escapeSingleQuotes Whether single quotes (') should be escaped. + * @param escapeDoubleQuotes Whether double quotes (") should be escaped. * @return The original string with all instances of the keys in the placeholders map replaced with their values. */ - public static String replacePlaceholders(String string, Map placeholders) { - return replacePlaceholders(string, placeholders, false); - } - - /** - * Replaces all instances of the keys in the placeholders map with their values. - *

- * Replaces {@code "} with {@code \"} in the placeholders if {@code escapeQuotes} is true. - * - * @param string The string to replace placeholders in. - * @param placeholders The placeholders to replace. - * @param escapeQuotes Whether quotes should be escaped. - * @return The original string with all instances of the keys in the placeholders map replaced with their values. - */ - public static String replacePlaceholders(String string, Map placeholders, boolean escapeQuotes) { + public static String replacePlaceholders(String string, Map placeholders, boolean escapeSingleQuotes, boolean escapeDoubleQuotes) { for (Map.Entry entry : placeholders.entrySet()) { - // I love strings. "\"" matches " and "\\\"" matches \" - string = string.replace(entry.getKey(), escapeQuotes ? entry.getValue().replace("\"", "\\\"") : entry.getValue()); + String placeholderValue = entry.getValue(); + if (escapeSingleQuotes) placeholderValue = placeholderValue.replace("'", "\\'"); + if (escapeDoubleQuotes) placeholderValue = placeholderValue.replace("\"", "\\\""); + + string = string.replace(entry.getKey(), placeholderValue); } return string; } public static int extractPriorityFromFile(File file) { + final int result = extractPriorityFromFileInternal(file); + if (result != -1) return result; + LOGGER.error("File '%s' doesn't start with priority!", file); + return -1; + } + + private static int extractPriorityFromFileInternal(File file) { final String filename = file.getName(); - final Matcher matcher = PACK_NAME_PATTERN.matcher(filename); + final Matcher matcher = RESOURCEPACK_NAME_PATTERN.matcher(filename); if (!matcher.find()) { - LOGGER.error("File '%s' doesn't start with priority!", file); return -1; } @@ -61,7 +60,7 @@ public static int extractPriorityFromFile(File file) { public static String nameWithoutPriorityString(File file) throws GithubResourcepackManagerException { final String filename = file.getName(); - final Matcher matcher = PACK_NAME_PATTERN.matcher(filename); + final Matcher matcher = RESOURCEPACK_NAME_PATTERN.matcher(filename); if (!matcher.find()) throw new GithubResourcepackManagerException("File '%s' doesn't start with priority!", file); diff --git a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/WebhookSender.java b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/WebhookSender.java index 9f88c8c..217655a 100644 --- a/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/WebhookSender.java +++ b/common/src/main/java/top/offsetmonkey538/githubresourcepackmanager/utils/WebhookSender.java @@ -14,11 +14,11 @@ private WebhookSender() { } - public static void send(String body, URI url, GithubResourcepackManager.UpdateType updateType, boolean isUpdated) throws GithubResourcepackManagerException { + public static void send(String body, URI url, GithubResourcepackManager.UpdateType updateType, boolean updateSucceeded) throws GithubResourcepackManagerException { final HttpRequest request = HttpRequest.newBuilder(url) .header("Content-Type", "application/json") .header("X-Resource-Pack-Update-Type", updateType.name()) - .header("X-Resource-Pack-Is-Updated", String.valueOf(isUpdated)) + .header("X-Resource-Pack-Update-Succeeded", String.valueOf(updateSucceeded)) .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); @@ -31,11 +31,6 @@ public static void send(String body, URI url, GithubResourcepackManager.UpdateTy throw new GithubResourcepackManagerException("Failed to send http request!", e); } - final int statusCode = response.statusCode(); - if (!(statusCode >= 200 && statusCode < 300)) { - throw new GithubResourcepackManagerException("Http status code '%s'! Response was: '%s'.", statusCode, response.body()); - } - // From JDK 21 the HttpClient class extends AutoCloseable, but as we want to support Minecraft versions // that use JDK 17, where HttpClient doesn't extend AutoCloseable, we need to check if it's // an instance of AutoCloseable before trying to close it. @@ -47,5 +42,10 @@ public static void send(String body, URI url, GithubResourcepackManager.UpdateTy throw new IllegalStateException(e); } } + + final int statusCode = response.statusCode(); + if (statusCode < 200 || statusCode >= 300) { + throw new GithubResourcepackManagerException("Http status code '%s'! Response was: '%s'.", statusCode, response.body()); + } } } diff --git a/common/src/main/resources/META-INF/neoforge.mods.toml b/common/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..0a5b461 --- /dev/null +++ b/common/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,14 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +license="MIT" + +issueTrackerURL="https://github.com/OffsetMods538/Github-Resourcepack-Manager/issues" + + +[[mods]] +modId="git_pack_manager_common" +displayName="Git Pack Manager Common" +version="${modVersion}" +displayURL="https://modrinth.com/mod/github-resourcepack-manager" +authors="OffsetMonkey538" +description="Modify server resourcepack from GitHub" diff --git a/fabric/build.gradle b/fabric/build.gradle index f671054..6ca899b 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,7 +1,7 @@ import dex.plugins.outlet.v2.util.ReleaseType plugins { - id 'fabric-loom' version '1.10-SNAPSHOT' + id 'fabric-loom' version '1.14-SNAPSHOT' id 'io.github.dexman545.outlet' version '1.6.1' } @@ -10,9 +10,9 @@ outlet { mcVersionRange = rootProject.supported_minecraft_versions allowedReleaseTypes = Set.of(ReleaseType.RELEASE) propertiesData = [ - 'yarn_version': outlet.yarnVersion(rootProject.minecraft_version), + 'yarn_version': outlet.yarnVersion(project.minecraft_version), 'loader_version': outlet.loaderVersion(), - 'fapi_version': outlet.fapiVersion(rootProject.minecraft_version) + 'fapi_version': outlet.fapiVersion(project.minecraft_version) ] } @@ -55,14 +55,6 @@ repositories { includeGroup "me.djtheredstoner" } } - maven { - name = "OffsetMods538" - url = "https://maven.offsetmonkey538.top/releases" - content { - includeGroup "top.offsetmonkey538.monkeylib538" - includeGroup "top.offsetmonkey538.meshlib" - } - } } configurations { @@ -89,13 +81,28 @@ dependencies { mappings "net.fabricmc:yarn:${project.yarn_version}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - modRuntimeOnly "me.djtheredstoner:DevAuth-fabric:${devauth_version}" + modRuntimeOnly "me.djtheredstoner:DevAuth-fabric:${project.devauth_version}" - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fapi_version}" + modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.21.9:${project.monkeylib538_version}+1.21.9") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") + } + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${project.monkeylib538_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + //modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${project.monkeylib538_version}") { + // exclude(group: "net.fabricmc.fabric-api") + //} + //modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${project.monkeylib538_version}") { + // exclude(group: "net.fabricmc.fabric-api") + //} - modImplementation "top.offsetmonkey538.monkeylib538:monkeylib538:${project.monkeylib538_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fapi_version}" - include runtimeOnly("top.offsetmonkey538.meshlib:mesh-lib-fabric:${rootProject.meshlib_version}") + runtimeOnly("top.offsetmonkey538.meshlib:mesh-lib-fabric:${rootProject.meshlib_version}+1.21.4") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "top.offsetmonkey538.monkeylib538") + } include project(path: ":common", configuration: "shadow") common project(":common") @@ -103,7 +110,7 @@ dependencies { processResources { final Map properties = Map.of( - "modVersion", project.mod_version, + "modVersion", project.version, "supportedMinecraftVersions", project.supported_minecraft_versions, "monkeylib538Version", project.monkeylib538_version ) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index fccff0f..e636565 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,15 +1,15 @@ +minecraft_version = 1.21.10 + # Fabric # Check at https://fabricmc.net/develop # These should be automatically updated, unless the environment # variable "DISABLE_PROPERTIES_UPDATE" is set. -yarn_version = 1.21.4+build.8 -loader_version = 0.16.14 -fapi_version = 0.119.3+1.21.4 +yarn_version = 1.21.10+build.2 +loader_version = 0.17.3 +fapi_version = 0.136.0+1.21.10 # Dependencies ## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth devauth_version = 1.2.1 -monkeylib538_version = 2.0.2+1.21 - nameSuffix = fabric diff --git a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformCommand.java b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformCommand.java index 3e58206..b309e01 100644 --- a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformCommand.java +++ b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformCommand.java @@ -1,75 +1,41 @@ package top.offsetmonkey538.githubresourcepackmanager.platform.fabric; -import com.mojang.brigadier.CommandDispatcher; -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; -import net.minecraft.command.CommandRegistryAccess; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.command.ControlFlowAware; import net.minecraft.network.packet.s2c.common.ResourcePackSendS2CPacket; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; -import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; +import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand; +import top.offsetmonkey538.monkeylib538.fabric.api.command.FabricCommandAbstractionApi; import java.util.Optional; -import static com.mojang.brigadier.arguments.BoolArgumentType.bool; -import static net.minecraft.server.command.CommandManager.argument; -import static net.minecraft.server.command.CommandManager.literal; - public class FabricPlatformCommand implements PlatformCommand { @Override - public void registerGithubRpManagerCommand() { - CommandRegistrationCallback.EVENT.register(FabricPlatformCommand::register); - } - - public static void register(CommandDispatcher dispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) { - dispatcher.register(literal("gh-rp-manager") - .requires(ServerCommandSource::isExecutedByPlayer) - .then(literal("request-pack") - .executes( - context -> { - final ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); - final MinecraftServer.ServerResourcePackProperties resourcePackProperties = context.getSource().getServer().getResourcePackProperties().orElse(null); - if (resourcePackProperties == null) { - context.getSource().sendFeedback(() -> Text.literal("Failed to send pack update packet to client!"), true); - return 0; - } - - player.networkHandler.send( - new ResourcePackSendS2CPacket( - resourcePackProperties.id(), - resourcePackProperties.url(), - resourcePackProperties.hash(), - resourcePackProperties.isRequired(), - Optional.ofNullable(resourcePackProperties.prompt()) - ), - null - ); - - return ControlFlowAware.Command.SINGLE_SUCCESS; - }) - ) - - .then(literal("trigger-update") - .requires(source -> source.hasPermissionLevel(2)) - .executes( - context -> { - GithubResourcepackManager.updatePack(GithubResourcepackManager.UpdateType.COMMAND); - return 1; - } - ) - .then(argument("force", bool()) - .executes( - context -> { - GithubResourcepackManager.updatePack(GithubResourcepackManager.UpdateType.COMMAND_FORCE); - return 1; - } - ) - ) - ) + public int executeRequestPackCommand(@NotNull CommandContext ctx) throws CommandSyntaxException { + final ServerCommandSource source = FabricCommandAbstractionApi.get(ctx); + final ServerPlayerEntity player = source.getPlayerOrThrow(); + final MinecraftServer.ServerResourcePackProperties resourcePackProperties = source.getServer().getResourcePackProperties().orElse(null); + if (resourcePackProperties == null) { + source.sendFeedback(() -> Text.literal("Failed to send pack update packet to client!"), true); + return 0; + } + + player.networkHandler.send( + new ResourcePackSendS2CPacket( + resourcePackProperties.id(), + resourcePackProperties.url(), + resourcePackProperties.hash(), + resourcePackProperties.isRequired(), + Optional.ofNullable(resourcePackProperties.prompt()) + ), + null ); + + return ControlFlowAware.Command.SINGLE_SUCCESS; } } diff --git a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformLogging.java b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformLogging.java deleted file mode 100644 index 0184c28..0000000 --- a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformLogging.java +++ /dev/null @@ -1,43 +0,0 @@ -package top.offsetmonkey538.githubresourcepackmanager.platform.fabric; - -import org.slf4j.Logger; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging; - -public class FabricPlatformLogging implements PlatformLogging { - - private static Logger logger; - - @Override - public void debug(String message) { - logger.debug(message); - } - - @Override - public void info(String message) { - logger.info(message); - } - - @Override - public void warn(String message) { - logger.warn(message); - } - - @Override - public void warn(String message, Throwable error) { - logger.warn(message, error); - } - - @Override - public void error(String message) { - logger.error(message); - } - - @Override - public void error(String message, Throwable error) { - logger.error(message, error); - } - - public static void setLogger(Logger logger) { - FabricPlatformLogging.logger = logger; - } -} diff --git a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformMain.java b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformMain.java index 132aac0..4509c87 100644 --- a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformMain.java +++ b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformMain.java @@ -2,43 +2,44 @@ import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.jetbrains.annotations.Nullable; import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; +import top.offsetmonkey538.monkeylib538.fabric.api.player.FabricPlayerApi; +import top.offsetmonkey538.monkeylib538.fabric.api.text.FabricMonkeyLibText; -import java.nio.file.Path; - -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.MOD_ID; +import java.util.List; public class FabricPlatformMain implements PlatformMain, DedicatedServerModInitializer { - public static final FabricPlatformMain INSTANCE = (FabricPlatformMain) PlatformMain.INSTANCE; - - private static MinecraftServer minecraftServer; + private static @Nullable MinecraftServer minecraftServer = null; @Override public void onInitializeServer() { - FabricPlatformLogging.setLogger(LoggerFactory.getLogger(MOD_ID)); - GithubResourcepackManager.initialize(); ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer1 -> minecraftServer = minecraftServer1); } - public static MinecraftServer getServer() { + public static @Nullable MinecraftServer getServer() { return minecraftServer; } @Override - public Path getConfigDir() { - return FabricLoader.getInstance().getConfigDir().resolve(MOD_ID); + public void sendMessageToAdmins(MonkeyLibText message) { + if (getServer() == null) return; + for (final PlayerEntity player : getServer().getPlayerManager().getPlayerList()) { + if (!FabricPlayerApi.isPlayerOp(getServer().getPlayerManager(), player)) continue; + player.sendMessage(FabricMonkeyLibText.of(message).getText(), false); + } } @Override - public void runOnServerStart(Runnable work) { - ServerLifecycleEvents.SERVER_STARTED.register(minecraftServer1 -> work.run()); + public void refreshDatapacks() { + getServer().getDataPackManager().scanPacks(); } } diff --git a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformServerProperties.java b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformServerProperties.java index b0885bf..f4b816f 100644 --- a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformServerProperties.java +++ b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformServerProperties.java @@ -3,12 +3,14 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.ServerPropertiesHandler; import net.minecraft.server.dedicated.ServerPropertiesLoader; +import net.minecraft.util.WorldSavePath; import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; import top.offsetmonkey538.githubresourcepackmanager.mixin.AbstractPropertiesHandlerAccessor; import top.offsetmonkey538.githubresourcepackmanager.mixin.MinecraftDedicatedServerAccessor; import top.offsetmonkey538.githubresourcepackmanager.mixin.ServerPropertiesLoaderAccessor; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties; +import java.nio.file.Path; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -27,6 +29,11 @@ public String getServerPort() { return String.valueOf(FabricPlatformMain.getServer().getServerPort()); } + @Override + public Path getDatapacksDir() { + return FabricPlatformMain.getServer().getSavePath(WorldSavePath.DATAPACKS); + } + @Override public void setProperties(Map properties) { final ServerPropertiesLoader propertiesLoader = ((MinecraftDedicatedServerAccessor) FabricPlatformMain.getServer()).getPropertiesLoader(); diff --git a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformText.java b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformText.java index ab38519..9fabeaa 100644 --- a/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformText.java +++ b/fabric/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/fabric/FabricPlatformText.java @@ -1,71 +1,27 @@ package top.offsetmonkey538.githubresourcepackmanager.platform.fabric; import net.minecraft.server.PlayerManager; -import net.minecraft.text.*; -import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; +import net.minecraft.server.network.ServerPlayerEntity; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText; -import top.offsetmonkey538.githubresourcepackmanager.utils.StringUtils; -import top.offsetmonkey538.monkeylib538.utils.TextUtils; - -import java.util.Map; - -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.config; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; +import top.offsetmonkey538.monkeylib538.fabric.api.player.FabricPlayerApi; +import top.offsetmonkey538.monkeylib538.fabric.api.text.FabricMonkeyLibText; public class FabricPlatformText implements PlatformText { @Override - public void sendUpdateMessage(Map placeholders) throws GithubResourcepackManagerException { + public void sendUpdateMessage(MonkeyLibText updateMessage, boolean adminsOnly) { + assert FabricPlatformMain.getServer() != null : "Literally HOW?? Why are we trying to send the update message when the server hasn't started yet!??!?!?"; final PlayerManager playerManager = FabricPlatformMain.getServer().getPlayerManager(); if (playerManager == null) return; - String message = config.packUpdateMessage; - final String[] splitMessage = message.split("\n"); - - final HoverEvent hoverEvent; - try { - hoverEvent = config.packUpdateMessageHoverMessage == null ? null : new HoverEvent( - HoverEvent.Action.SHOW_TEXT, - TextUtils.INSTANCE.getStyledText( - StringUtils.replacePlaceholders(config.packUpdateMessageHoverMessage, placeholders).replace("\\n", "\n") - ) - ); - } catch (Exception e) { - throw new GithubResourcepackManagerException("Failed to style update hover message!", e); + if (!adminsOnly) { + playerManager.broadcast(FabricMonkeyLibText.of(updateMessage).getText(), false); + return; } - for (int lineNumber = 0; lineNumber < splitMessage.length; lineNumber++) { - final String currentLineString = StringUtils.replacePlaceholders(splitMessage[lineNumber], placeholders).replace("\\n", "\n"); - final MutableText currentLine = Text.empty(); - try { - for (Text currentLineSibling : TextUtils.INSTANCE.getStyledText(currentLineString).getSiblings()) { - final MutableText sibling = currentLineSibling.copy(); - - if (hoverEvent != null) sibling.setStyle(sibling.getStyle().withHoverEvent(hoverEvent)); - - final String siblingString = sibling.getString(); - if (!siblingString.contains("{packUpdateCommand}")) { - currentLine.append(sibling); - continue; - } - - final Style siblingStyle = sibling.getStyle(); - final String[] splitSibling = siblingString.split("\\{packUpdateCommand}"); - - if (splitSibling.length > 0) - currentLine.append(Text.literal(splitSibling[0]).setStyle(siblingStyle)); - - currentLine.append(Text.literal("[HERE]").setStyle(siblingStyle - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("Click to update pack"))) - .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/gh-rp-manager request-pack")) - )); - - if (splitSibling.length > 1) - currentLine.append(Text.literal(splitSibling[1]).setStyle(siblingStyle)); - } - } catch (Exception e) { - throw new GithubResourcepackManagerException("Failed to style update message at line number '%s'!", e, lineNumber); - } - - playerManager.broadcast(currentLine, false); + for (final ServerPlayerEntity player : playerManager.getPlayerList()) { + if (!FabricPlayerApi.isPlayerOp(playerManager, player)) continue; + player.sendMessageToClient(FabricMonkeyLibText.of(updateMessage).getText(), false); } } } diff --git a/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging b/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging deleted file mode 100644 index 7695764..0000000 --- a/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.githubresourcepackmanager.platform.fabric.FabricPlatformLogging \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 512ef06..728dfcb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,8 +5,12 @@ org.gradle.parallel = true minecraft_version = 1.21.4 # MESH Lib, check at https://github.com/OffsetMods538/MESH-Lib -meshlib_version = 1.0.5+1.21.4 +meshlib_version = 2.0.0-alpha.0.1762972806024+d227635 +# MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 +monkeylib538_version = 3.0.0-alpha.3 +jgit_version = 6.9.0.202403050737-r + # Mod Properties -mod_version = 4.0.1 -supported_minecraft_versions = >=1.19.2 +mod_version = 5.0.0-alpha.0 +supported_minecraft_versions = >=1.20.5 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55..f8e1ee3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3735f26..49ab6fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionSha256Sum=df67a32e86e3276d011735facb1535f64d0d88df84fa87521e90becc2d735444 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/neoforge/build.gradle b/neoforge/build.gradle new file mode 100644 index 0000000..b495727 --- /dev/null +++ b/neoforge/build.gradle @@ -0,0 +1,100 @@ +import dex.plugins.outlet.v2.util.ReleaseType + +plugins { + id 'net.neoforged.moddev' version '2.0.112' + id 'io.github.dexman545.outlet' version '1.6.1' +} + +outlet { + mcVersionRange = rootProject.supported_minecraft_versions + allowedReleaseTypes = Set.of(ReleaseType.RELEASE) +} + +neoForge { + version = project.neoforge_version + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.minecraft_version + } + + mods { + "${project.name}" { + sourceSet(sourceSets.main) + } + } + + runs { + server { + server() + } + + configureEach { + systemProperty "forge.logging.markers", "REGISTRIES" + } + } +} + +repositories { + mavenCentral() + mavenLocal() + maven { + name = "DevAuth" + url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" + content { + includeGroup "me.djtheredstoner" + } + } +} + +configurations { + common { + canBeResolved = true + canBeConsumed = false + } + api.extendsFrom common + + runtimeClasspath.extendsFrom localRuntime +} + +dependencies { + localRuntime "me.djtheredstoner:DevAuth-neoforge:${project.devauth_version}" + + runtimeOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-1.20.6:${rootProject.monkeylib538_version}+1.20.6" + compileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge:${rootProject.monkeylib538_version}" + + + runtimeOnly("top.offsetmonkey538.meshlib:mesh-lib-neoforge:${rootProject.meshlib_version}+1.21.4") { + exclude(group: "top.offsetmonkey538.monkeylib538") + } + + jarJar(api(project(":common"))) + jarJar(additionalRuntimeClasspath("org.eclipse.jgit:org.eclipse.jgit:${rootProject.jgit_version}")) +} + +processResources { + final Map properties = Map.of( + "modVersion", project.version, + "supportedMinecraftVersions", project.minecraft_version_range, + "monkeylib538Version", project.monkeylib538_version, + "meshLibVersion", project.meshlib_version + ) + + inputs.properties(properties) + + filesMatching("META-INF/neoforge.mods.toml") { + expand(properties) + } + + exclude ".cache/**" +} + +modrinth { + loaders = ["neoforge"] + //todo idk:_uploadFile = remapJar.archiveFile + + dependencies { + required.project "monkeylib538" + // todo idk: required.project "fabric-api" + } +} \ No newline at end of file diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties new file mode 100644 index 0000000..406e37f --- /dev/null +++ b/neoforge/gradle.properties @@ -0,0 +1,12 @@ +# Minecraft version, check at https://fabricmc.net/develop +minecraft_version = 1.21.4 +neoforge_version = 21.4.154 +parchment_mappings_version = 2025.03.23 + +# Dependencies +## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth +devauth_version = 1.2.1 + +nameSuffix = neoforge + +minecraft_version_range=[1.20.6,) diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/DedicatedServerAccessor.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/DedicatedServerAccessor.java new file mode 100644 index 0000000..41d4c9a --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/DedicatedServerAccessor.java @@ -0,0 +1,13 @@ +package top.offsetmonkey538.githubresourcepackmanager.mixin; + +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.DedicatedServerSettings; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DedicatedServer.class) +public interface DedicatedServerAccessor { + + @Accessor + DedicatedServerSettings getSettings(); +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/DedicatedServerSettingsAccessor.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/DedicatedServerSettingsAccessor.java new file mode 100644 index 0000000..66638ba --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/DedicatedServerSettingsAccessor.java @@ -0,0 +1,20 @@ +package top.offsetmonkey538.githubresourcepackmanager.mixin; + +import net.minecraft.server.dedicated.DedicatedServerProperties; +import net.minecraft.server.dedicated.DedicatedServerSettings; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.nio.file.Path; + +@Mixin(DedicatedServerSettings.class) +public interface DedicatedServerSettingsAccessor { + + @Accessor + Path getSource(); + + @Mutable + @Accessor + void setProperties(DedicatedServerProperties properties); +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/SettingsAccessor.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/SettingsAccessor.java new file mode 100644 index 0000000..565a199 --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/mixin/SettingsAccessor.java @@ -0,0 +1,14 @@ +package top.offsetmonkey538.githubresourcepackmanager.mixin; + +import net.minecraft.server.dedicated.Settings; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Properties; + +@Mixin(Settings.class) +public interface SettingsAccessor { + + @Accessor + Properties getProperties(); +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformCommand.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformCommand.java new file mode 100644 index 0000000..a41ec2a --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformCommand.java @@ -0,0 +1,42 @@ +package top.offsetmonkey538.githubresourcepackmanager.platform.neoforge; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand; +import top.offsetmonkey538.monkeylib538.neoforge.api.command.NeoforgeCommandAbstractionApi; + +import java.util.Optional; + +import static com.mojang.brigadier.Command.SINGLE_SUCCESS; + +public class NeoforgePlatformCommand implements PlatformCommand { + @Override + public int executeRequestPackCommand(@NotNull CommandContext ctx) throws CommandSyntaxException { + final CommandSourceStack source = NeoforgeCommandAbstractionApi.get(ctx); + final ServerPlayer player = source.getPlayerOrException(); + final MinecraftServer.ServerResourcePackInfo resourcePackProperties = source.getServer().getServerResourcePack().orElse(null); + if (resourcePackProperties == null) { + source.sendSuccess(() -> Component.literal("Failed to send pack update packet to client!"), true); + return 0; + } + + player.connection.send( + new ClientboundResourcePackPushPacket( + resourcePackProperties.id(), + resourcePackProperties.url(), + resourcePackProperties.hash(), + resourcePackProperties.isRequired(), + Optional.ofNullable(resourcePackProperties.prompt()) + ), + null + ); + + return SINGLE_SUCCESS; + } +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformInitializer.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformInitializer.java new file mode 100644 index 0000000..0d9fd2a --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformInitializer.java @@ -0,0 +1,22 @@ +package top.offsetmonkey538.githubresourcepackmanager.platform.neoforge; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.server.ServerStartingEvent; +import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; + +@Mod( + value = "git_pack_manager", + dist = Dist.DEDICATED_SERVER +) +public class NeoforgePlatformInitializer { + + public NeoforgePlatformInitializer(IEventBus modEventBus, ModContainer modContainer) { + GithubResourcepackManager.initialize(); + + NeoForge.EVENT_BUS.addListener(ServerStartingEvent.class, serverStartingEvent -> NeoforgePlatformMain.setServer(serverStartingEvent.getServer())); + } +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformMain.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformMain.java new file mode 100644 index 0000000..f9d83d4 --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformMain.java @@ -0,0 +1,50 @@ +package top.offsetmonkey538.githubresourcepackmanager.platform.neoforge; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.jetbrains.annotations.Nullable; +import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; +import top.offsetmonkey538.monkeylib538.neoforge.api.text.NeoforgeMonkeyLibText; + +import java.util.List; + +public class NeoforgePlatformMain implements PlatformMain { + private static @Nullable MinecraftServer minecraftServer = null; + + public static @Nullable MinecraftServer getServer() { + return minecraftServer; + } + + public static void setServer(MinecraftServer server) { + minecraftServer = server; + } + + + @Override + public void sendMessageToAdmins(MonkeyLibText message) { + if (getServer() == null) return; + for (final Player player : getServer().getPlayerList().getPlayers()) { + if (!getServer().getPlayerList().isOp(player.getGameProfile())) continue; + player.displayClientMessage(NeoforgeMonkeyLibText.of(message).getText(), false); + } + } + + @Override + public void registerSendMessageQueueOnAdminJoin(List messageQueue, MonkeyLibText lastMessage) { + NeoForge.EVENT_BUS.addListener(PlayerEvent.PlayerLoggedInEvent.class, playerLoggedInEvent -> { + if (messageQueue.isEmpty()) return; + if (!playerLoggedInEvent.getEntity().getServer().getPlayerList().isOp(playerLoggedInEvent.getEntity().getGameProfile())) return; + + for (MonkeyLibText text : messageQueue) playerLoggedInEvent.getEntity().displayClientMessage(NeoforgeMonkeyLibText.of(text).getText(), false); + playerLoggedInEvent.getEntity().displayClientMessage(NeoforgeMonkeyLibText.of(lastMessage).getText(), false); + }); + } + + @Override + public void refreshDatapacks() { + getServer().getPackRepository().reload(); + } +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformServerProperties.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformServerProperties.java new file mode 100644 index 0000000..73d86a6 --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformServerProperties.java @@ -0,0 +1,57 @@ +package top.offsetmonkey538.githubresourcepackmanager.platform.neoforge; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServerProperties; +import net.minecraft.server.dedicated.DedicatedServerSettings; +import net.minecraft.world.level.storage.LevelResource; +import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; +import top.offsetmonkey538.githubresourcepackmanager.mixin.SettingsAccessor; +import top.offsetmonkey538.githubresourcepackmanager.mixin.DedicatedServerAccessor; +import top.offsetmonkey538.githubresourcepackmanager.mixin.DedicatedServerSettingsAccessor; +import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +public class NeoforgePlatformServerProperties implements PlatformServerProperties { + + @Override + public String getResourcePackUrl() { + final Optional resourcePackProperties = NeoforgePlatformMain.getServer().getServerResourcePack(); + + return resourcePackProperties.map(MinecraftServer.ServerResourcePackInfo::url).orElse(null); + } + + @Override + public String getServerPort() { + return String.valueOf(NeoforgePlatformMain.getServer().getPort()); + } + + @Override + public Path getDatapacksDir() { + return NeoforgePlatformMain.getServer().getWorldPath(LevelResource.DATAPACK_DIR); + } + + @Override + public void setProperties(Map properties) { + final DedicatedServerSettings propertiesLoader = ((DedicatedServerAccessor) NeoforgePlatformMain.getServer()).getSettings(); + + propertiesLoader.update(propertiesHandler -> { + final Properties serverProperties = ((SettingsAccessor) propertiesHandler).getProperties(); + + properties.forEach(serverProperties::setProperty); + + return propertiesHandler; + }); + } + + @Override + public void reload() throws GithubResourcepackManagerException { + final DedicatedServerSettings propertiesLoader = ((DedicatedServerAccessor) NeoforgePlatformMain.getServer()).getSettings(); + final DedicatedServerSettingsAccessor propertiesLoaderAccess = (DedicatedServerSettingsAccessor) propertiesLoader; + + propertiesLoaderAccess.setProperties(DedicatedServerProperties.fromFile(propertiesLoaderAccess.getSource())); + } +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformText.java b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformText.java new file mode 100644 index 0000000..a564e4f --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/neoforge/NeoforgePlatformText.java @@ -0,0 +1,25 @@ +package top.offsetmonkey538.githubresourcepackmanager.platform.neoforge; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; +import top.offsetmonkey538.monkeylib538.neoforge.api.text.NeoforgeMonkeyLibText; + +public class NeoforgePlatformText implements PlatformText { + @Override + public void sendUpdateMessage(MonkeyLibText updateMessage, boolean adminsOnly) { + assert NeoforgePlatformMain.getServer() != null : "Literally HOW?? Why are we trying to send the update message when the server hasn't started yet!??!?!?"; + final PlayerList playerManager = NeoforgePlatformMain.getServer().getPlayerList(); + + if (!adminsOnly) { + playerManager.broadcastSystemMessage(NeoforgeMonkeyLibText.of(updateMessage).getText(), false); + return; + } + + for (final ServerPlayer player : playerManager.getPlayers()) { + if (!playerManager.isOp(player.getGameProfile())) continue; + player.sendSystemMessage(NeoforgeMonkeyLibText.of(updateMessage).getText(), false); + } + } +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..ea42043 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,39 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +license="MIT" + +issueTrackerURL="https://github.com/OffsetMods538/Github-Resourcepack-manager" + + +[[mods]] +modId="git_pack_manager" +displayName="Git Pack Manager" +version="${modVersion}" +displayURL="https://modrinth.com/mod/github-resourcepack-manager" +authors="OffsetMonkey538" +description="Modify server resourcepack from GitHub" + +[[mixins]] +config="github-resourcepack-manager.mixins.json" + + +[[dependencies.git_pack_manager]] +modId="minecraft" +type="required" +versionRange="${supportedMinecraftVersions}" +ordering="NONE" +side="BOTH" + +[[dependencies.git_pack_manager]] +modId="monkeylib538_common_neoforge" +type="required" +versionRange="[${monkeylib538Version},)" +ordering="NONE" +side="BOTH" + +[[dependencies.git_pack_manager]] +modId="mesh_lib_common" +type="required" +versionRange="[${meshLibVersion},)" +ordering="NONE" +side="BOTH" diff --git a/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand new file mode 100644 index 0000000..e14ad3a --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand @@ -0,0 +1 @@ +top.offsetmonkey538.githubresourcepackmanager.platform.neoforge.NeoforgePlatformCommand \ No newline at end of file diff --git a/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain new file mode 100644 index 0000000..9d0e114 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.githubresourcepackmanager.platform.neoforge.NeoforgePlatformMain \ No newline at end of file diff --git a/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties new file mode 100644 index 0000000..425ca86 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties @@ -0,0 +1 @@ +top.offsetmonkey538.githubresourcepackmanager.platform.neoforge.NeoforgePlatformServerProperties \ No newline at end of file diff --git a/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText new file mode 100644 index 0000000..712b8d3 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText @@ -0,0 +1 @@ +top.offsetmonkey538.githubresourcepackmanager.platform.neoforge.NeoforgePlatformText \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/github-resourcepack-manager/icon.png b/neoforge/src/main/resources/assets/github-resourcepack-manager/icon.png new file mode 100644 index 0000000..a6544fa Binary files /dev/null and b/neoforge/src/main/resources/assets/github-resourcepack-manager/icon.png differ diff --git a/neoforge/src/main/resources/github-resourcepack-manager.mixins.json b/neoforge/src/main/resources/github-resourcepack-manager.mixins.json new file mode 100644 index 0000000..2262de0 --- /dev/null +++ b/neoforge/src/main/resources/github-resourcepack-manager.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "top.offsetmonkey538.githubresourcepackmanager.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "SettingsAccessor", + "DedicatedServerAccessor", + "DedicatedServerSettingsAccessor" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/paper/build.gradle b/paper/build.gradle index 540f806..c6ce13f 100644 --- a/paper/build.gradle +++ b/paper/build.gradle @@ -19,10 +19,10 @@ repositories { } } maven { - name = "OffsetMods538" - url = "https://maven.offsetmonkey538.top/releases" + name = "Mojang Libraries" + url = "https://libraries.minecraft.net" content { - includeGroup "top.offsetmonkey538.meshlib" + includeGroup "com.mojang" } } } @@ -41,7 +41,9 @@ dependencies { paperweight.paperDevBundle(project.paper_version) implementation "xyz.jpenilla:reflection-remapper:${project.reflection_remapper_version}" - implementation "top.offsetmonkey538.meshlib:mesh-lib-paper:${rootProject.meshlib_version}" + implementation("top.offsetmonkey538.meshlib:mesh-lib-paper:${rootProject.meshlib_version}+1.21.4") { + exclude(group: "top.offsetmonkey538.monkeylib538") + } common project(path: ":common", configuration: "shadow") } @@ -49,7 +51,7 @@ tasks.build.dependsOn(shadowJar) processResources { final Map properties = Map.of( - "modVersion", project.mod_version, + "modVersion", project.version, "lowestMinecraftVersion", outlet.mcVersions().first() // Hopefully outlet always does stuff in this order ) diff --git a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformCommand.java b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformCommand.java index 36b7082..5e64904 100644 --- a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformCommand.java +++ b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformCommand.java @@ -1,109 +1,46 @@ package top.offsetmonkey538.githubresourcepackmanager.platform.paper; import com.mojang.brigadier.Command; -import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.resource.ResourcePackInfo; import net.kyori.adventure.resource.ResourcePackRequest; import net.minecraft.server.MinecraftServer; import org.bukkit.entity.Player; -import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; +import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformCommand; import java.net.URI; -import static com.mojang.brigadier.arguments.BoolArgumentType.bool; -import static io.papermc.paper.command.brigadier.Commands.argument; -import static io.papermc.paper.command.brigadier.Commands.literal; - public class PaperPlatformCommand implements PlatformCommand { @SuppressWarnings("UnstableApiUsage") @Override - public void registerGithubRpManagerCommand() { - PaperPlatformMain.getPlugin().getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> { - final Commands commands = event. registrar(); - - commands.register(literal("gh-rp-manager") - .requires(commandSourceStack -> commandSourceStack.getExecutor() instanceof Player) - .then(literal("request-pack").executes( - context -> { - final Player player = (Player) context.getSource().getExecutor(); - if (player == null) return 0; - - final MinecraftServer.ServerResourcePackInfo resourcePackProperties = MinecraftServer.getServer().getServerResourcePack().orElse(null); - if (resourcePackProperties == null) { - context.getSource().getSender().sendMessage("Failed to send pack update packet to client!"); - return 0; - } - player.sendResourcePacks( - ResourcePackRequest.resourcePackRequest() - .packs( - ResourcePackInfo.resourcePackInfo( - resourcePackProperties.id(), - URI.create(resourcePackProperties.url()), - resourcePackProperties.hash() - ) - ) - .replace(true) - .required(resourcePackProperties.isRequired()) - .asResourcePackRequest() - ); - return Command.SINGLE_SUCCESS; - }) - ) - .then(literal("trigger-update") - .requires(source -> source.getSender().isOp()) - .executes( - context -> { - GithubResourcepackManager.updatePack(GithubResourcepackManager.UpdateType.COMMAND); - return 1; - } - ) - .then(argument("force", bool()) - .executes( - context -> { - GithubResourcepackManager.updatePack(GithubResourcepackManager.UpdateType.COMMAND_FORCE); - return 1; - } - ) - ) - ) - .build() - ); - }); + public int executeRequestPackCommand(@NotNull CommandContext ctx) throws CommandSyntaxException { + // TODO: once monke has papier: final CommandSourceStack source = PaperCommandAbstractionApi.get(ctx); + final CommandSourceStack source = null; + if (source == null) return 0; + final Player player = (Player) source.getExecutor(); + if (player == null) return 0; + + final MinecraftServer.ServerResourcePackInfo resourcePackProperties = MinecraftServer.getServer().getServerResourcePack().orElse(null); + if (resourcePackProperties == null) { + source.getSender().sendMessage("Failed to send pack update packet to client!"); + return 0; + } + player.sendResourcePacks( + ResourcePackRequest.resourcePackRequest() + .packs( + ResourcePackInfo.resourcePackInfo( + resourcePackProperties.id(), + URI.create(resourcePackProperties.url()), + resourcePackProperties.hash() + ) + ) + .replace(true) + .required(resourcePackProperties.isRequired()) + .asResourcePackRequest() + ); + return Command.SINGLE_SUCCESS; } - - //@Override - //public void registerGithubRpManagerCommand() { - // CommandRegistrationCallback.EVENT.register(FabricPlatformCommand::register); - //} - - //public static void register(CommandDispatcher dispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) { - // dispatcher.register(literal("gh-rp-manager") - // .requires(ServerCommandSource::isExecutedByPlayer) - // .then(literal("request-pack").executes( - // context -> { - // final ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); - // final MinecraftServer.ServerResourcePackProperties resourcePackProperties = context.getSource().getServer().getResourcePackProperties().orElse(null); - // if (resourcePackProperties == null) { - // context.getSource().sendFeedback(() -> Text.literal("Failed to send pack update packet to client!"), true); - // return 0; - // } - - // player.networkHandler.send( - // new ResourcePackSendS2CPacket( - // resourcePackProperties.id(), - // resourcePackProperties.url(), - // resourcePackProperties.hash(), - // resourcePackProperties.isRequired(), - // Optional.ofNullable(resourcePackProperties.prompt()) - // ), - // null - // ); - - // return ControlFlowAware.Command.SINGLE_SUCCESS; - // }) - // ) - // ); - //} } diff --git a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformLogging.java b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformLogging.java deleted file mode 100644 index e89673a..0000000 --- a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformLogging.java +++ /dev/null @@ -1,45 +0,0 @@ -package top.offsetmonkey538.githubresourcepackmanager.platform.paper; - -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class PaperPlatformLogging implements PlatformLogging { - - private static Logger logger; - - @Override - public void debug(String message) { - logger.log(Level.FINE, message); - } - - @Override - public void info(String message) { - logger.log(Level.INFO, message); - } - - @Override - public void warn(String message) { - logger.log(Level.WARNING, message); - } - - @Override - public void warn(String message, Throwable error) { - logger.log(Level.WARNING, message, error); - } - - @Override - public void error(String message) { - logger.log(Level.SEVERE, message); - } - - @Override - public void error(String message, Throwable error) { - logger.log(Level.SEVERE, message, error); - } - - public static void setLogger(Logger logger) { - PaperPlatformLogging.logger = logger; - } -} diff --git a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformMain.java b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformMain.java index c20d396..0046834 100644 --- a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformMain.java +++ b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformMain.java @@ -1,61 +1,57 @@ package top.offsetmonkey538.githubresourcepackmanager.platform.paper; -import org.bukkit.plugin.java.JavaPlugin; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; -import java.nio.file.Path; +import java.util.List; public class PaperPlatformMain implements PlatformMain { - private static JavaPlugin plugin; + private static PaperPlugin plugin; + + + @Override + public void refreshDatapacks() { + MinecraftServer.getServer().getPackRepository().reload(); + } @Override - public Path getConfigDir() { - return getPlugin().getDataPath(); + public void sendMessageToAdmins(MonkeyLibText message) { + for (final OfflinePlayer operator : plugin.getServer().getOperators()) { + if (operator.getPlayer() == null) continue; + // todo: once monkeylib has da paper: operator.getPlayer().sendMessage(PaperMonkeyLibText.of(text)); + } } @Override - public void runOnServerStart(Runnable work) { - work.run(); + public void registerSendMessageQueueOnAdminJoin(List messageQueue, MonkeyLibText lastMessage) { + record AdminMessageQueueEventHandler(List messageQueue) implements Listener { + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + if (messageQueue.isEmpty()) return; + if (!event.getPlayer().isOp()) return; + + for (MonkeyLibText text : messageQueue) { + // todo: once monkeylib has paper support: event.getPlayer().sendMessage(PaperMonkeyLibText.of(text).getText()); + } + // todo: once monkeylib has paper support: event.getPlayer().sendMessage(PaperMonkeyLibText.of(lastMessage).getText()); + } + } + + Bukkit.getPluginManager().registerEvents(new AdminMessageQueueEventHandler(messageQueue), getPlugin()); } - public static void setPlugin(JavaPlugin plugin) { + public static void setPlugin(PaperPlugin plugin) { PaperPlatformMain.plugin = plugin; } - public static JavaPlugin getPlugin() { + public static PaperPlugin getPlugin() { return plugin; } - - - //@Override - //public void onInitializeServer() { - // GithubResourcepackManager.initialize(); - - // ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer1 -> minecraftServer = minecraftServer1); - //} - - //public MinecraftServer getServer() { - // return minecraftServer; - //} - - - //@Override - //public Logger getLogger() { - // return LOGGER; - //} - - //@Override - //public Path getConfigDir() { - // return FabricLoader.getInstance().getConfigDir(); - //} - - //@Override - //public Path getGameDir() { - // return FabricLoader.getInstance().getGameDir(); - //} - - //@Override - //public void runOnServerStart(Runnable work) { - // ServerLifecycleEvents.SERVER_STARTED.register(minecraftServer1 -> work.run()); - //} } diff --git a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformServerProperties.java b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformServerProperties.java index 45ef937..8a56c04 100644 --- a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformServerProperties.java +++ b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformServerProperties.java @@ -1,25 +1,18 @@ package top.offsetmonkey538.githubresourcepackmanager.platform.paper; -import joptsimple.OptionSet; import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.DedicatedServerProperties; import net.minecraft.server.dedicated.DedicatedServerSettings; -import org.bukkit.packs.ResourcePack; +import net.minecraft.world.level.storage.LevelResource; import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; -import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformLogging; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformServerProperties; import xyz.jpenilla.reflectionremapper.ReflectionRemapper; -import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory; -import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter; -import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter; -import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies; import java.lang.reflect.Field; import java.nio.file.Path; import java.util.Map; import java.util.Optional; -import java.util.Properties; public class PaperPlatformServerProperties implements PlatformServerProperties { @Override @@ -34,6 +27,11 @@ public String getServerPort() { return String.valueOf(MinecraftServer.getServer().getPort()); } + @Override + public Path getDatapacksDir() { + return MinecraftServer.getServer().getWorldPath(LevelResource.DATAPACK_DIR); + } + @Override public void setProperties(Map properties) { final DedicatedServerSettings settings = ((DedicatedServer) MinecraftServer.getServer()).settings; @@ -67,13 +65,4 @@ public void reload() throws GithubResourcepackManagerException { throw new GithubResourcepackManagerException("Failed to reload 'server.properties' file!", e); } } - - - //@Override - //public void reload() { - // final ServerPropertiesLoader propertiesLoader = ((MinecraftDedicatedServerAccessor) FabricPlatformMain.INSTANCE.getServer()).getPropertiesLoader(); - // final ServerPropertiesLoaderAccessor propertiesLoaderAccess = (ServerPropertiesLoaderAccessor) propertiesLoader; - - // propertiesLoaderAccess.setPropertiesHandler(ServerPropertiesHandler.load(propertiesLoaderAccess.getPath())); - //} } diff --git a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformText.java b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformText.java index e3e44a1..50e3ab6 100644 --- a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformText.java +++ b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlatformText.java @@ -1,148 +1,26 @@ package top.offsetmonkey538.githubresourcepackmanager.platform.paper; -import net.minecraft.ChatFormatting; import net.minecraft.network.chat.*; import net.minecraft.server.MinecraftServer; -import top.offsetmonkey538.githubresourcepackmanager.exception.GithubResourcepackManagerException; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; import top.offsetmonkey538.githubresourcepackmanager.platform.PlatformText; -import top.offsetmonkey538.githubresourcepackmanager.utils.StringUtils; - -import java.util.List; -import java.util.Map; - -import static top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager.config; +import top.offsetmonkey538.monkeylib538.api.text.MonkeyLibText; public class PaperPlatformText implements PlatformText { @Override - public void sendUpdateMessage(Map placeholders) throws GithubResourcepackManagerException { - String message = config.packUpdateMessage; - final String[] splitMessage = message.split("\n"); - - final HoverEvent hoverEvent; - try { - hoverEvent = config.packUpdateMessageHoverMessage == null ? null : new HoverEvent( - HoverEvent.Action.SHOW_TEXT, - getStyledText( - StringUtils.replacePlaceholders(config.packUpdateMessageHoverMessage, placeholders).replace("\\n", "\n") - ) - ); - } catch (Exception e) { - throw new GithubResourcepackManagerException("Failed to style update hover message!", e); + public void sendUpdateMessage(MonkeyLibText updateMessage, boolean adminsOnly) { + final PlayerList players = MinecraftServer.getServer().getPlayerList(); + if (!adminsOnly) { + players.broadcastSystemMessage(Component.empty(), false); + // TODO: once I implement paper version of monke: players.broadcastSystemMessage(PaperMonkeyLibText.of(updateMessage).getText(), false); + return; } - for (int lineNumber = 0; lineNumber < splitMessage.length; lineNumber++) { - final String currentLineString = StringUtils.replacePlaceholders(splitMessage[lineNumber], placeholders).replace("\\n", "\n"); - final MutableComponent currentLine = Component.empty(); - try { - for (Component currentLineSibling : getStyledText(currentLineString).getSiblings()) { - final MutableComponent sibling = currentLineSibling.copy(); - - if (hoverEvent != null) sibling.setStyle(sibling.getStyle().withHoverEvent(hoverEvent)); - - final String siblingString = sibling.getString(); - if (!siblingString.contains("{packUpdateCommand}")) { - currentLine.append(sibling); - continue; - } - - final Style siblingStyle = sibling.getStyle(); - final String[] splitSibling = siblingString.split("\\{packUpdateCommand}"); - - if (splitSibling.length > 0) - currentLine.append(Component.literal(splitSibling[0]).setStyle(siblingStyle)); - - currentLine.append(Component.literal("[HERE]").setStyle(siblingStyle - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Click to update pack"))) - .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/gh-rp-manager request-pack")) - )); - - if (splitSibling.length > 1) - currentLine.append(Component.literal(splitSibling[1]).setStyle(siblingStyle)); - } - } catch (Exception e) { - throw new GithubResourcepackManagerException("Failed to style update message at line number '%s'!", e, lineNumber); - } - - MinecraftServer.getServer().getPlayerList().broadcastSystemMessage(currentLine, false); + for (final ServerPlayer player : players.players) { + if (!players.isOp(player.getGameProfile())) continue; + player.sendSystemMessage(Component.empty()); + // TODO: once I implement paper version of monke: player.sendSystemMessage(PaperMonkeyLibText.of(updateMessage).getText()); } } - - - private static final Style DEFAULT_STYLE = Style.EMPTY.withItalic(false).withColor(ChatFormatting.WHITE); - - private static MutableComponent getStyledText(String text) throws Exception { - final MutableComponent result = Component.empty(); - Style style = DEFAULT_STYLE; - - boolean isFormattingCode = false; - boolean isEscaped = false; - char[] characters = text.toCharArray(); - for (int characterIndex = 0; characterIndex < characters.length; characterIndex++) { - char currentChar = characters[characterIndex]; - - if (isFormattingCode) { - // Hex color - if (currentChar == '#') { - if (characterIndex + 7 >= characters.length) - throw new Exception("Unfinished hex code starting at character number '" + characterIndex + "'!"); - - try { - style = style.withColor(TextColor.parseColor(text.substring(characterIndex, characterIndex + 7)).getOrThrow(Exception::new)); - } catch (Exception e) { - throw new Exception("Failed to parse hex color starting at character number '" + characterIndex + "'!", e); - } - - // Move pointer 6 characters ahead as we already read the whole hex code - characterIndex += 6; - isFormattingCode = false; - continue; - } - - style = getStyleForFormattingCode(currentChar, style); - - if (style == null) - throw new Exception("Invalid formatting code at character number '" + characterIndex + "'!"); - - isFormattingCode = false; - continue; - } - - if (!isEscaped) { - switch (currentChar) { - case '&': - isFormattingCode = true; - continue; - case '\\': - isEscaped = true; - continue; - } - } - isEscaped = false; - - - final List siblings = result.getSiblings(); - final int lastSiblingIndex = siblings.size() - 1; - final Component lastSibling = siblings.isEmpty() ? Component.empty() : siblings.get(lastSiblingIndex); - - // Check if the style of the last sibling is the same as the current one - if (!siblings.isEmpty() && lastSibling.getStyle().equals(style)) { - // If so, set the last sibling to itself plus the new character - siblings.set(lastSiblingIndex, Component.literal(lastSibling.getString() + currentChar).setStyle(style)); - } else { - // Otherwise, just append a new sibling to the result - result.append(Component.literal(String.valueOf(currentChar)).setStyle(style)); - } - } - - return result; - } - - private static Style getStyleForFormattingCode(char formattingCode, Style currentStyle) { - if (formattingCode == 'r') return DEFAULT_STYLE; - - final ChatFormatting formatting = ChatFormatting.getByCode(formattingCode); - if (formatting == null) return null; - - return currentStyle.applyFormat(formatting); - } } diff --git a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlugin.java b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlugin.java index caf4270..fc6e210 100644 --- a/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlugin.java +++ b/paper/src/main/java/top/offsetmonkey538/githubresourcepackmanager/platform/paper/PaperPlugin.java @@ -2,16 +2,12 @@ import org.bukkit.plugin.java.JavaPlugin; import top.offsetmonkey538.githubresourcepackmanager.GithubResourcepackManager; -import top.offsetmonkey538.meshlib.MeshLib; public class PaperPlugin extends JavaPlugin { @Override public void onEnable() { - MeshLib.initialize(); - PaperPlatformMain.setPlugin(this); - PaperPlatformLogging.setLogger(getLogger()); GithubResourcepackManager.initialize(); } diff --git a/settings.gradle b/settings.gradle index e866fb5..66601c4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,4 +15,5 @@ rootProject.name = "github-resourcepack-manager" include "common" include "fabric" +include "neoforge" include "paper"