diff --git a/.gitignore b/.gitignore index 39e939c..aee790f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ local.properties .settings/ .loadpath .recommenders +.gradle/ +target/ # External tool builders .externalToolBuilders/ @@ -118,3 +120,4 @@ dist/ nbdist/ .nb-gradle/ nbactions.xml +/.gradle/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..b9853bd --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' + id 'maven-publish' +} +allprojects { + group = 'com.extendedclip.papi.expansion.javascript' + version = '1.6.1' + description = 'PAPI-Expansion-Javascript' +} + +repositories { + mavenCentral() +} + +dependencies { + +} diff --git a/evaluator-api/build.gradle b/evaluator-api/build.gradle new file mode 100644 index 0000000..2d2273a --- /dev/null +++ b/evaluator-api/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' +} + +group 'com.extendedclip.papi.expansion.javascript' +version '1.6.1' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'junit', name: 'junit', version: '4.12' +} diff --git a/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/EvaluatorException.java b/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/EvaluatorException.java new file mode 100644 index 0000000..c2c2def --- /dev/null +++ b/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/EvaluatorException.java @@ -0,0 +1,7 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +public final class EvaluatorException extends RuntimeException { + public EvaluatorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/ScriptEvaluator.java b/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/ScriptEvaluator.java new file mode 100644 index 0000000..87217a3 --- /dev/null +++ b/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/ScriptEvaluator.java @@ -0,0 +1,8 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +import javax.script.ScriptException; +import java.util.Map; + +public interface ScriptEvaluator { + Object execute(final Map additionalBindings, final String script) throws EvaluatorException, ScriptException; +} diff --git a/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/ScriptEvaluatorFactory.java b/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/ScriptEvaluatorFactory.java new file mode 100644 index 0000000..25133ef --- /dev/null +++ b/evaluator-api/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/ScriptEvaluatorFactory.java @@ -0,0 +1,10 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +import java.util.Map; + +public interface ScriptEvaluatorFactory { + + ScriptEvaluator create(final Map bindings); + + default void cleanBinaries() {} +} diff --git a/evaluator/build.gradle b/evaluator/build.gradle new file mode 100644 index 0000000..5c6d432 --- /dev/null +++ b/evaluator/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '7.0.0' +} + +group 'com.extendedclip.papi.expansion.javascript' +version '1.6.1' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() + maven { + url = "https://repo.vshnv.tech/" + } +} + +dependencies { + compileOnly project(':evaluator-api') + compileOnly fileTree("libs") + implementation 'io.github.slimjar:slimjar:1.2.3' + testImplementation group: 'junit', name: 'junit', version: '4.12' +} diff --git a/evaluator/libs/asm-9.2.jar b/evaluator/libs/asm-9.2.jar new file mode 100644 index 0000000..3557ae4 Binary files /dev/null and b/evaluator/libs/asm-9.2.jar differ diff --git a/evaluator/libs/asm-commons-9.2.jar b/evaluator/libs/asm-commons-9.2.jar new file mode 100644 index 0000000..01028a0 Binary files /dev/null and b/evaluator/libs/asm-commons-9.2.jar differ diff --git a/evaluator/libs/asm-util-9.2.jar b/evaluator/libs/asm-util-9.2.jar new file mode 100644 index 0000000..3afe6e6 Binary files /dev/null and b/evaluator/libs/asm-util-9.2.jar differ diff --git a/evaluator/libs/nashorn-core-15.1.jar b/evaluator/libs/nashorn-core-15.1.jar new file mode 100644 index 0000000..cc1517f Binary files /dev/null and b/evaluator/libs/nashorn-core-15.1.jar differ diff --git a/evaluator/libs/quickjs-1.0.0.jar b/evaluator/libs/quickjs-1.0.0.jar new file mode 100644 index 0000000..f1be9d4 Binary files /dev/null and b/evaluator/libs/quickjs-1.0.0.jar differ diff --git a/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/LibraryInjectionException.java b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/LibraryInjectionException.java new file mode 100644 index 0000000..ae907e5 --- /dev/null +++ b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/LibraryInjectionException.java @@ -0,0 +1,7 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +public final class LibraryInjectionException extends RuntimeException { + public LibraryInjectionException(final Throwable cause) { + super(String.format("Java Version: %s", System.getProperty("java.version")),cause); + } +} diff --git a/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/NashornScriptEvaluator.java b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/NashornScriptEvaluator.java new file mode 100644 index 0000000..5dcf77b --- /dev/null +++ b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/NashornScriptEvaluator.java @@ -0,0 +1,32 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +import com.koushikdutta.quack.QuackContext; +import org.openjdk.nashorn.api.scripting.NashornScriptEngine; +import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptException; +import java.util.Map; +import java.util.stream.Stream; + +public final class NashornScriptEvaluator implements ScriptEvaluator { + private final NashornScriptEngineFactory scriptEngineFactory; + private final Map bindings; + + public NashornScriptEvaluator(final NashornScriptEngineFactory scriptEngineFactory, final Map bindings) { + this.scriptEngineFactory = scriptEngineFactory; + this.bindings = bindings; + } + + @Override + public Object execute(final Map additionalBindings, final String script) throws EvaluatorException, ScriptException { + final ScriptEngine engine = scriptEngineFactory.getScriptEngine(); + final Bindings globalBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + globalBindings.putAll(bindings); + globalBindings.putAll(additionalBindings); + engine.setBindings(globalBindings, ScriptContext.GLOBAL_SCOPE); + return engine.eval(script); + } +} diff --git a/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/NashornScriptEvaluatorFactory.java b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/NashornScriptEvaluatorFactory.java new file mode 100644 index 0000000..5bfeeb4 --- /dev/null +++ b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/NashornScriptEvaluatorFactory.java @@ -0,0 +1,37 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +import com.extendedclip.papi.expansion.javascript.evaluator.util.InjectionUtil; +import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +public final class NashornScriptEvaluatorFactory implements ScriptEvaluatorFactory { + public static final Collection LIBRARIES = Arrays.asList( + "nashorn-core-15.1.isolated-jar", + "asm-commons-9.2.isolated-jar", + "asm-util-9.2.isolated-jar", + "asm-9.2.isolated-jar" + ); + private final NashornScriptEngineFactory engineFactory; + + private NashornScriptEvaluatorFactory(final NashornScriptEngineFactory engineFactory) { + this.engineFactory = engineFactory; + } + + + @Override + public ScriptEvaluator create(final Map bindings) { + return new NashornScriptEvaluator(engineFactory, bindings); + } + + public static ScriptEvaluatorFactory create() throws URISyntaxException, ReflectiveOperationException, NoSuchAlgorithmException, IOException { + InjectionUtil.inject(LIBRARIES); + return new NashornScriptEvaluatorFactory(new NashornScriptEngineFactory()); + } + +} diff --git a/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/QuickJsScriptEvaluator.java b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/QuickJsScriptEvaluator.java new file mode 100644 index 0000000..c6ef4e8 --- /dev/null +++ b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/QuickJsScriptEvaluator.java @@ -0,0 +1,43 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +import com.koushikdutta.quack.*; + +import java.util.Map; + +public final class QuickJsScriptEvaluator implements ScriptEvaluator { + private final Map bindings; + + public QuickJsScriptEvaluator(final Map bindings) { + this.bindings = bindings; + } + + @Override + public Object execute(final Map additionalBindings, final String script) throws EvaluatorException { + try (final QuackContext context = QuackContext.create(true)) { + for (Map.Entry entry : bindings.entrySet()) { + bind(context, entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : additionalBindings.entrySet()) { + bind(context, entry.getKey(), entry.getValue()); + } + return context.evaluate(script); + } catch (final Exception exception) { + throw new EvaluatorException("Failed to evaluate requested script.", exception); + } + } + + private void bind(final QuackContext ctx, final String key, final Object value) { + ctx.getGlobalObject().set(key, coerce(ctx, value)); + } + private Object coerce(final QuackContext ctx, final Object value) { + if (value.getClass().isArray()) { + final Object[] array = (Object[]) value; + final JavaScriptObject jsObj = ctx.evaluateForJavaScriptObject("[]"); + for (int i = 0; i < array.length; i++) { + jsObj.set(i, coerce(ctx, array[i])); + } + return jsObj; + } + return ctx.coerceJavaToJavaScript(value); + } +} diff --git a/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/QuickJsScriptEvaluatorFactory.java b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/QuickJsScriptEvaluatorFactory.java new file mode 100644 index 0000000..ad90784 --- /dev/null +++ b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/QuickJsScriptEvaluatorFactory.java @@ -0,0 +1,61 @@ +package com.extendedclip.papi.expansion.javascript.evaluator; + +import com.extendedclip.papi.expansion.javascript.evaluator.util.InjectionUtil; +import io.github.slimjar.injector.loader.Injectable; +import io.github.slimjar.injector.loader.InjectableFactory; + +import javax.script.ScriptException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.StandardOpenOption; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.function.Function; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +public final class QuickJsScriptEvaluatorFactory implements ScriptEvaluatorFactory { + private static final String TEST_EVALUATION_SCRIPT = "10 * 10"; + private static final int TEST_EVALUATION_RESULT = 100; + + public static final Collection LIBRARIES = Collections.singletonList("quickjs-1.0.0.isolated-jar"); + private static final URL SELF_JAR_URL = QuickJsScriptEvaluatorFactory.class.getProtectionDomain() + .getCodeSource().getLocation(); + + private QuickJsScriptEvaluatorFactory() { + + } + + @Override + public ScriptEvaluator create(final Map bindings) { + return new QuickJsScriptEvaluator(bindings); + } + + public static ScriptEvaluatorFactory createWithFallback(final Function evaluatorFactoryProducer) { + try { + final ScriptEvaluatorFactory evaluatorFactory = create(); + attemptBasicEvaluation(evaluatorFactory); + return evaluatorFactory; + } catch (final Exception exception) { + return evaluatorFactoryProducer.apply(null); + } + } + + private static void attemptBasicEvaluation(final ScriptEvaluatorFactory evaluatorFactory) throws ScriptException { + final Object result = evaluatorFactory.create(Collections.emptyMap()).execute(Collections.emptyMap(), "10 * 10"); + if (result instanceof Integer && ((Integer) result).intValue() == TEST_EVALUATION_RESULT) { + return; + } + throw new RuntimeException("Failed basic evaluation test"); + } + + public static ScriptEvaluatorFactory create() throws URISyntaxException, ReflectiveOperationException, NoSuchAlgorithmException, IOException { + InjectionUtil.inject(LIBRARIES); + return new QuickJsScriptEvaluatorFactory(); + } +} diff --git a/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/util/InjectionUtil.java b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/util/InjectionUtil.java new file mode 100644 index 0000000..bd7a3b6 --- /dev/null +++ b/evaluator/src/main/java/com/extendedclip/papi/expansion/javascript/evaluator/util/InjectionUtil.java @@ -0,0 +1,87 @@ +package com.extendedclip.papi.expansion.javascript.evaluator.util; + +import com.extendedclip.papi.expansion.javascript.evaluator.LibraryInjectionException; +import com.extendedclip.papi.expansion.javascript.evaluator.QuickJsScriptEvaluatorFactory; +import io.github.slimjar.injector.loader.Injectable; +import io.github.slimjar.injector.loader.InjectableFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.StandardOpenOption; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +public final class InjectionUtil { + private static final URL SELF_JAR_URL = QuickJsScriptEvaluatorFactory.class.getProtectionDomain() + .getCodeSource().getLocation(); + + private InjectionUtil() { + + } + + public static boolean tryInject(final Collection libraries) { + try { + inject(libraries); + return true; + } catch (final Exception exception) { + // Fail silently + return false; + } + } + + public static void inject(final Collection libraries) throws LibraryInjectionException { + try { + final Collection libraryURLs = extractLibraries(libraries); + final ClassLoader bukkitClassLoader = InjectionUtil.class.getClassLoader().getParent(); + final Injectable injectable = InjectableFactory.create(bukkitClassLoader); + for (final URL libraryURL : libraryURLs) { + injectable.inject(libraryURL); + } + } catch (final Exception exception) { + throw new LibraryInjectionException(exception); + } + } + + private static Collection extractLibraries(final Collection libraries) throws IOException, URISyntaxException, NoSuchAlgorithmException, ReflectiveOperationException { + final Collection extracted = new ArrayList<>(); + final File selfFile = new File(SELF_JAR_URL.toURI()); + final JarFile jarFile = new JarFile(selfFile); + for (final String library : libraries) { + final File extractedFile = getExtractionFile(library, selfFile); + if (extractedFile.exists()) { + extracted.add(extractedFile.toURI().toURL()); + continue; + } + final ZipEntry entry = jarFile.getEntry(library); + if (entry == null) { + continue; + } + //noinspection ResultOfMethodCallIgnored + extractedFile.getParentFile().mkdirs(); + //noinspection ResultOfMethodCallIgnored + extractedFile.createNewFile(); + try (final InputStream stream = jarFile.getInputStream(entry); + final ReadableByteChannel inChannel = Channels.newChannel(stream); + final FileChannel outChannel = FileChannel.open(extractedFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { + outChannel.transferFrom(inChannel, 0, entry.getSize()); + } + extracted.add(extractedFile.toURI().toURL()); + } + return extracted; + } + + private static File getExtractionFile(final String name, final File selfFile) { + return new File(selfFile.getParentFile(), "libraries/" + name.replace("isolated-jar", "jar")); + } +} diff --git a/expansion/build.gradle b/expansion/build.gradle new file mode 100644 index 0000000..0c4d267 --- /dev/null +++ b/expansion/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'com.coditory.manifest' version '0.1.14' +} + +group 'com.extendedclip.papi.expansion.javascript' +version '1.6.1' +archivesBaseName = "Javascript-Expansion" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 +repositories { + mavenCentral() + maven { + url = "https://repo.vshnv.tech/" + } + maven { + url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' + } + + maven { + url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' + } +} + +dependencies { + implementation project(':evaluator') + implementation project(':evaluator-api') + compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' + compileOnly 'me.clip:placeholderapi:2.10.9' + compileOnly 'org.jetbrains:annotations:21.0.1' + testImplementation group: 'junit', name: 'junit', version: '4.12' +} + +//shadowJar { +// dependsOn(project(':evaluator').shadowJar) +// doFirst { +// copy { +// from project(':evaluator').getTasks().getByName("shadowJar").outputs.files.singleFile +// into layout.buildDirectory.file("resources/main/") +// include('*.jar') +// } +// } +//} + +shadowJar { + doFirst { + copy { + from project(':evaluator').getProjectDir().toPath().resolve("libs").toFile() + into layout.buildDirectory.file("resources/main/") + include('*.jar') + rename ('(.*).jar', '$1.isolated-jar') + } + } + relocate 'io.github.slimjar', 'com.extendedclip.papi.expansion.javascript.slimjar' +} \ No newline at end of file diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/ExpansionUtils.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/ExpansionUtils.java similarity index 75% rename from src/main/java/com/extendedclip/papi/expansion/javascript/ExpansionUtils.java rename to expansion/src/main/java/com/extendedclip/papi/expansion/javascript/ExpansionUtils.java index d2eaace..62e32f9 100644 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/ExpansionUtils.java +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/ExpansionUtils.java @@ -1,6 +1,5 @@ package com.extendedclip.papi.expansion.javascript; -import jdk.nashorn.api.scripting.ScriptObjectMirror; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -66,28 +65,6 @@ public class ExpansionUtils { } } - // Only support for Nashorn engine! - protected static Object jsonToJava(Object jsObj) { - if (jsObj instanceof ScriptObjectMirror) { - ScriptObjectMirror jsObjectMirror = (ScriptObjectMirror) jsObj; - if (jsObjectMirror.isArray()) { - List list = new ArrayList<>(); - for (Map.Entry entry : jsObjectMirror.entrySet()) { - list.add(jsonToJava(entry.getValue())); - } - return list; - } else { - Map map = new HashMap<>(); - for (Map.Entry entry : jsObjectMirror.entrySet()) { - map.put(entry.getKey(), jsonToJava(entry.getValue())); - } - return map; - } - } else { - return jsObj; - } - } - protected static Object ymlToJavaObj(Object obj) { if (obj instanceof MemorySection) { MemorySection ymlMem = (MemorySection) obj; diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansion.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansion.java new file mode 100644 index 0000000..4372d27 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansion.java @@ -0,0 +1,181 @@ +/* + * + * Javascript-Expansion + * Copyright (C) 2020 Ryan McCarthy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ +package com.extendedclip.papi.expansion.javascript; + +import com.extendedclip.papi.expansion.javascript.cloud.*; +import com.extendedclip.papi.expansion.javascript.commands.router.CommandRegistrar; +import com.extendedclip.papi.expansion.javascript.config.*; +import com.extendedclip.papi.expansion.javascript.evaluator.*; +import com.extendedclip.papi.expansion.javascript.script.ConfigurationScriptLoader; +import com.extendedclip.papi.expansion.javascript.script.ScriptLoader; +import com.extendedclip.papi.expansion.javascript.script.ScriptRegistry; +import me.clip.placeholderapi.expansion.Cacheable; +import me.clip.placeholderapi.expansion.Configurable; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.logging.Level; + +public class JavascriptExpansion extends PlaceholderExpansion implements Cacheable, Configurable { + public static final String AUTHOR = "clip"; + public static final String IDENTIFIER = "javascript"; + public static final String VERSION = JavascriptExpansion.class.getPackage().getImplementationVersion(); + + private static final URL SELF_JAR_URL = JavascriptExpansion.class.getProtectionDomain() + .getCodeSource().getLocation(); + + private final ScriptRegistry registry = new ScriptRegistry(); + private final GitScriptManager scriptManager = GitScriptManager.createDefault(getPlaceholderAPI()); + + private String argumentSeparator = ""; + private boolean useQuickJS = false; + private ScriptLoader loader; + private ScriptEvaluatorFactory scriptEvaluatorFactory; + private CommandRegistrar commandRegistrar; + + @NotNull + @Override + public String getAuthor() { + return AUTHOR; + } + + @NotNull + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @NotNull + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean register() { + argumentSeparator = getString("argument_split", ","); + if (argumentSeparator.equals("_")) { + argumentSeparator = ","; + ExpansionUtils.warnLog("Underscore character will not be allowed for splitting. Defaulting to ',' for this", null); + } + + useQuickJS = (boolean) get("use_quick_js", false); + + if (useQuickJS) { + this.scriptEvaluatorFactory = QuickJsScriptEvaluatorFactory.createWithFallback(i -> { + getPlaceholderAPI().getLogger().log(Level.WARNING, "Failed to use QuickJS Engine. Falling back to Nashorn"); + return createNashornEvaluatorFactory(); + }); + } else { + this.scriptEvaluatorFactory = createNashornEvaluatorFactory(); + } + + + final HeaderWriter headerWriter = HeaderWriter.fromJar(SELF_JAR_URL); + + final File dataFolder = getPlaceholderAPI().getDataFolder(); + final Path scriptDirectoryPath = dataFolder.toPath().resolve("javascripts"); + try { + Files.createDirectories(scriptDirectoryPath); + } catch (IOException exception) { + ExpansionUtils.errorLog("Failed to create script folder.", exception); + } + final File configFile = new File(dataFolder, "javascript_placeholders.yml"); + final ScriptConfiguration scriptConfiguration = new YamlScriptConfiguration(configFile, headerWriter, scriptDirectoryPath); + final JavascriptPlaceholderFactory placeholderFactory = new SimpleJavascriptPlaceholderFactory(this, scriptEvaluatorFactory); + this.loader = new ConfigurationScriptLoader(registry, scriptConfiguration, placeholderFactory); + try { + this.commandRegistrar = new CommandRegistrar(scriptManager, placeholderFactory, scriptConfiguration, registry, loader); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + + try { + final int amountLoaded = loader.reload(); + ExpansionUtils.infoLog(amountLoaded + " script" + ExpansionUtils.plural(amountLoaded) + " loaded!"); + } catch (final IOException exception) { + ExpansionUtils.errorLog("Failed to load scripts", exception); + } + if ((boolean) get("github_script_downloads", false)) { + scriptManager.getIndexProvider().refreshIndex(scriptIndex -> { + long gitIndexed = scriptIndex.getCount(); + ExpansionUtils.infoLog("Indexed " + gitIndexed + " gitscript" + ExpansionUtils.plural(Math.toIntExact(gitIndexed))); + }); + } + commandRegistrar.register(); + return super.register(); + } + + @Override + public void clear() { + commandRegistrar.unregister(); + loader.clear(); + scriptEvaluatorFactory.cleanBinaries(); + } + + @Override + public String onRequest(OfflinePlayer player, @NotNull String identifier) { + if (player == null) { + return ""; + } + + for (JavascriptPlaceholder script : registry.getAllPlaceholders()) { + if (identifier.startsWith(script.getIdentifier() + "_")) { + identifier = identifier.replaceFirst(script.getIdentifier() + "_", ""); + + return !identifier.contains(argumentSeparator) ? script.evaluate(player, identifier) : script.evaluate(player, identifier.split(argumentSeparator)); + } + + if (identifier.equalsIgnoreCase(script.getIdentifier())) { + return script.evaluate(player); + } + } + + return ""; + } + + @Override + public Map getDefaults() { + final Map defaults = new HashMap<>(); + defaults.put("debug", false); + defaults.put("argument_split", ","); + defaults.put("github_script_downloads", false); + defaults.put("use_quick_js", false); + return defaults; + } + + private static ScriptEvaluatorFactory createNashornEvaluatorFactory() { + try { + return NashornScriptEvaluatorFactory.create(); + } catch (URISyntaxException | ReflectiveOperationException | NoSuchAlgorithmException | IOException exception) { + throw new RuntimeException("Failed to create fallback evaluator: Nashorn" ,exception); // Unrecoverable + } + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholder.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholder.java new file mode 100644 index 0000000..91a2ac9 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholder.java @@ -0,0 +1,157 @@ +/* + * + * Javascript-Expansion + * Copyright (C) 2020 Ryan McCarthy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ +package com.extendedclip.papi.expansion.javascript; + + + +import com.extendedclip.papi.expansion.javascript.evaluator.ScriptEvaluator; +import com.extendedclip.papi.expansion.javascript.evaluator.ScriptEvaluatorFactory; +import com.extendedclip.papi.expansion.javascript.script.ScriptData; +import com.extendedclip.papi.expansion.javascript.script.data.PersistableData; +import com.extendedclip.papi.expansion.javascript.script.data.YmlPersistableData; +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; + +import javax.script.ScriptException; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class JavascriptPlaceholder { + private final String identifier; + private final String script; + private final PersistableData persistableData; + private final Pattern pattern = Pattern.compile("//.*|/\\*[\\S\\s]*?\\*/|%([^%]+)%"); + private final ScriptEvaluatorFactory evaluatorFactory; + private final JavascriptExpansion expansion; + + public JavascriptPlaceholder(@NotNull final String identifier, @NotNull final String script, @NotNull final ScriptEvaluatorFactory evaluatorFactory, @NotNull final JavascriptExpansion expansion) { + final Path dataFilePath = expansion.getPlaceholderAPI().getDataFolder() + .toPath() + .resolve("javascripts") + .resolve("javascript_data") + .resolve(identifier + "_data.yml"); + + try { + this.persistableData = YmlPersistableData.create(identifier, dataFilePath); + } catch (final IOException exception) { + ExpansionUtils.errorLog("Unable to create placeholder data file", exception); + throw new RuntimeException(exception); + } + this.identifier = identifier; + this.script = script; + this.evaluatorFactory = evaluatorFactory; + this.expansion = expansion; + } + + public String getIdentifier() { + return identifier; + } + + public String evaluate(final OfflinePlayer player, final String... args) { + // A checker to deny all placeholders inside comment codes + final Matcher matcher = pattern.matcher(script); + final StringBuffer buffer = new StringBuffer(); + while (matcher.find()) { + final String matched = matcher.group(0); + if (!matched.startsWith("%") || matched.startsWith("/*") || matched.startsWith("//")) continue; + matcher.appendReplacement(buffer, PlaceholderAPI.setPlaceholders(player, matched)); + } + matcher.appendTail(buffer); + final String parsedScript = buffer.toString(); + try { + final int length; + if (args != null) { + length = args.length; + } else { + length = 0; + } + final String[] arguments = new String[length]; + + for (int i = 0; i < length; i++) { + if (args[i] == null || args[i].isEmpty()) { + continue; + } + arguments[i] = PlaceholderAPI.setBracketPlaceholders(player, args[i]); + } + + final Map defaultBindings = prepareDefaultBindings(); + + final ScriptEvaluator evaluator = evaluatorFactory.create(defaultBindings); + + final Map additionalBindings = new HashMap<>(); + additionalBindings.put("args", arguments); + if (player != null && player.isOnline()) { + additionalBindings.put("BukkitPlayer", player.getPlayer()); + additionalBindings.put("Player", player.getPlayer()); + } + additionalBindings.put("OfflinePlayer", player); + try { + Object result = evaluator.execute(additionalBindings, parsedScript); + return result != null ? PlaceholderAPI.setBracketPlaceholders(player, result.toString()) : ""; + } catch (RuntimeException | ScriptException exception) { // todo:: prepare specific exception and catch that instead of all runtime exceptions + ExpansionUtils.errorLog("An error occurred while executing the script '" + identifier , exception); + } + } catch (ArrayIndexOutOfBoundsException ex) { + ExpansionUtils.errorLog("Argument out of bound while executing script '" + identifier + "':\n\t" + ex.getMessage(), null); + } + return "Script error (check console)"; + } + + private Map prepareDefaultBindings() { + final Map bindings = new HashMap<>(); + bindings.put("Data", persistableData.getScriptData()); + bindings.put("DataVar", persistableData.getScriptData().getData()); + bindings.put("BukkitServer", Bukkit.getServer()); + bindings.put("Expansion", expansion); + bindings.put("Placeholder", this); + bindings.put("PlaceholderAPI", PlaceholderAPI.class); + return bindings; + } + + public String getScript() { + return script; + } + + public ScriptData getData() { + return persistableData.getScriptData(); + } + + public void saveData() { + persistableData.save(); + } + + public PersistableData getPersistableData() { + return persistableData; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholderFactory.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholderFactory.java new file mode 100644 index 0000000..477fe3b --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholderFactory.java @@ -0,0 +1,5 @@ +package com.extendedclip.papi.expansion.javascript; + +public interface JavascriptPlaceholderFactory { + JavascriptPlaceholder create(final String identifier, final String script); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/SimpleJavascriptPlaceholderFactory.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/SimpleJavascriptPlaceholderFactory.java new file mode 100644 index 0000000..b706a5c --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/SimpleJavascriptPlaceholderFactory.java @@ -0,0 +1,18 @@ +package com.extendedclip.papi.expansion.javascript; + +import com.extendedclip.papi.expansion.javascript.evaluator.ScriptEvaluatorFactory; + +public final class SimpleJavascriptPlaceholderFactory implements JavascriptPlaceholderFactory { + private final JavascriptExpansion expansion; + private final ScriptEvaluatorFactory evaluatorFactory; + + public SimpleJavascriptPlaceholderFactory(final JavascriptExpansion expansion, final ScriptEvaluatorFactory evaluatorFactory) { + this.expansion = expansion; + this.evaluatorFactory = evaluatorFactory; + } + + @Override + public JavascriptPlaceholder create(final String identifier, final String script) { + return new JavascriptPlaceholder(identifier, script, evaluatorFactory, expansion); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ActiveStateSetter.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ActiveStateSetter.java new file mode 100644 index 0000000..17539a4 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ActiveStateSetter.java @@ -0,0 +1,6 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +public interface ActiveStateSetter { + void setActive(boolean state); + boolean isActive(); +} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GithubScript.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScript.java similarity index 79% rename from src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GithubScript.java rename to expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScript.java index e1b5dda..24718aa 100644 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GithubScript.java +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScript.java @@ -20,15 +20,22 @@ */ package com.extendedclip.papi.expansion.javascript.cloud; -public class GithubScript { +import org.jetbrains.annotations.NotNull; +public final class GitScript { private final String name; private final String version; private final String author; private final String description; private final String url; - public GithubScript(String name, String version, String author, String description, String url) { + public GitScript( + @NotNull final String name, + @NotNull final String version, + @NotNull final String author, + @NotNull final String description, + @NotNull final String url + ) { this.name = name; this.version = version; this.author = author; @@ -36,22 +43,27 @@ public class GithubScript { this.url = url; } + @NotNull public String getName() { return name; } + @NotNull public String getVersion() { return version; } + @NotNull public String getAuthor() { return author; } + @NotNull public String getDescription() { return description; } + @NotNull public String getUrl() { return url; } diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptActiveStateSetter.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptActiveStateSetter.java new file mode 100644 index 0000000..217b3d3 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptActiveStateSetter.java @@ -0,0 +1,25 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +import org.bukkit.plugin.java.JavaPlugin; + +public final class GitScriptActiveStateSetter implements ActiveStateSetter { + private static final String ACTIVE_STATE_KEY = "expansions.javascript.github_script_downloads"; + private final JavaPlugin plugin; + + public GitScriptActiveStateSetter(final JavaPlugin plugin) { + this.plugin = plugin; + } + + + @Override + public void setActive(boolean state) { + plugin.getConfig().set(ACTIVE_STATE_KEY, state); + plugin.saveConfig(); + plugin.reloadConfig(); + } + + @Override + public boolean isActive() { + return plugin.getConfig().getBoolean(ACTIVE_STATE_KEY, false); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptIndex.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptIndex.java new file mode 100644 index 0000000..4ce44b2 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptIndex.java @@ -0,0 +1,33 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +public final class GitScriptIndex implements ScriptIndex { + + @NotNull + private final Map scriptMap; + + public GitScriptIndex(@NotNull final Map scriptMap) { + this.scriptMap = scriptMap; + } + + @Override + public Collection getAllScripts() { + return scriptMap.values(); + } + + @Override + @NotNull + public Optional getScript(final String name) { + return Optional.ofNullable(scriptMap.get(name)); + } + + @Override + public long getCount() { + return scriptMap.size(); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptIndexProvider.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptIndexProvider.java new file mode 100644 index 0000000..47a09ac --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptIndexProvider.java @@ -0,0 +1,60 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class GitScriptIndexProvider implements ScriptIndexProvider { + private static final Gson GSON = new Gson(); + private static final String INDEX_URL = + "https://raw.githubusercontent.com/PlaceholderAPI/" + + "Javascript-Expansion/master/scripts/master_list.json"; + + @NotNull + private final JavaPlugin plugin; + + private ScriptIndex index = null; + + public GitScriptIndexProvider(@NotNull final JavaPlugin plugin) { + this.plugin = plugin; + } + + @Override + public synchronized Optional getScriptIndex() { + return Optional.ofNullable(index); + } + + @Override + public void refreshIndex(@Nullable Consumer indexConsumer) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try(final Reader indexReader = new InputStreamReader(new URL(INDEX_URL).openStream())) { + final List scripts = GSON.fromJson(indexReader, new TypeToken>() {}.getType()); + final Map map = scripts.stream().collect( + Collectors.toMap(GitScript::getName, Function.identity()) + ); + final GitScriptIndex localIndex = new GitScriptIndex(map); + synchronized (this) { + index = localIndex; + } + if (indexConsumer != null) { + indexConsumer.accept(localIndex); + } + } catch (final IOException e) { + e.printStackTrace(); + } + }); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptManager.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptManager.java new file mode 100644 index 0000000..2c19373 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GitScriptManager.java @@ -0,0 +1,47 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +import com.extendedclip.papi.expansion.javascript.cloud.download.ChanneledScriptDownloader; +import com.extendedclip.papi.expansion.javascript.cloud.download.GitScriptPathSelector; +import com.extendedclip.papi.expansion.javascript.cloud.download.PathSelector; +import com.extendedclip.papi.expansion.javascript.cloud.download.ScriptDownloader; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; + +public final class GitScriptManager { + private final ActiveStateSetter activeStateSetter; + private final GitScriptIndexProvider indexProvider; + private final ScriptDownloader scriptDownloader; + private final PathSelector downloadPathSelector; + + public GitScriptManager(ActiveStateSetter activeStateSetter, GitScriptIndexProvider indexProvider, ScriptDownloader scriptDownloader, PathSelector downloadPathSelector) { + this.activeStateSetter = activeStateSetter; + this.indexProvider = indexProvider; + this.scriptDownloader = scriptDownloader; + this.downloadPathSelector = downloadPathSelector; + } + + public ActiveStateSetter getActiveStateSetter() { + return activeStateSetter; + } + + public GitScriptIndexProvider getIndexProvider() { + return indexProvider; + } + + public ScriptDownloader getScriptDownloader() { + return scriptDownloader; + } + + public PathSelector getDownloadPathSelector() { + return downloadPathSelector; + } + + public static GitScriptManager createDefault(final JavaPlugin plugin) { + final PathSelector pathSelector = new GitScriptPathSelector(new File(plugin.getDataFolder(), "javascripts")); + final ScriptDownloader downloader = new ChanneledScriptDownloader(pathSelector); + final GitScriptIndexProvider indexProvider = new GitScriptIndexProvider(plugin); + final ActiveStateSetter activeStateSetter = new GitScriptActiveStateSetter(plugin); + return new GitScriptManager(activeStateSetter, indexProvider, downloader, pathSelector); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ScriptIndex.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ScriptIndex.java new file mode 100644 index 0000000..1580114 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ScriptIndex.java @@ -0,0 +1,10 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +import java.util.Collection; +import java.util.Optional; + +public interface ScriptIndex { + Collection getAllScripts(); + Optional getScript(final String name); + long getCount(); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ScriptIndexProvider.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ScriptIndexProvider.java new file mode 100644 index 0000000..8f7fbf6 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/ScriptIndexProvider.java @@ -0,0 +1,9 @@ +package com.extendedclip.papi.expansion.javascript.cloud; + +import java.util.Optional; +import java.util.function.Consumer; + +public interface ScriptIndexProvider { + Optional getScriptIndex(); + void refreshIndex(final Consumer indexConsumer); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/ChanneledScriptDownloader.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/ChanneledScriptDownloader.java new file mode 100644 index 0000000..ba67586 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/ChanneledScriptDownloader.java @@ -0,0 +1,33 @@ +package com.extendedclip.papi.expansion.javascript.cloud.download; + +import com.extendedclip.papi.expansion.javascript.cloud.GitScript; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.*; +import java.nio.file.*; + +public final class ChanneledScriptDownloader implements ScriptDownloader { + private final PathSelector pathSelector; + + public ChanneledScriptDownloader(final PathSelector pathSelector) { + this.pathSelector = pathSelector; + } + + + @Override + public Path download(final GitScript script) throws IOException { + final URL url = new URL(script.getUrl()); + final URLConnection urlConnection = url.openConnection(); + final long length = urlConnection.getContentLength(); + + final Path to = pathSelector.select(script.getName()); + try (ReadableByteChannel fromChannel = Channels.newChannel(urlConnection.getInputStream())) { + try (FileChannel toChannel = FileChannel.open(to, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { + toChannel.transferFrom(fromChannel, 0, length); + } + } + return to; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/GitScriptPathSelector.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/GitScriptPathSelector.java new file mode 100644 index 0000000..03f0e05 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/GitScriptPathSelector.java @@ -0,0 +1,17 @@ +package com.extendedclip.papi.expansion.javascript.cloud.download; + +import java.io.File; +import java.nio.file.Path; + +public final class GitScriptPathSelector implements PathSelector { + private final String expansionPath; + + public GitScriptPathSelector(final File expansionFolder) { + this.expansionPath = expansionFolder.getAbsolutePath(); + } + + @Override + public Path select(final String name) { + return Path.of(expansionPath, name + ".js"); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/PathSelector.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/PathSelector.java new file mode 100644 index 0000000..1391e84 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/PathSelector.java @@ -0,0 +1,7 @@ +package com.extendedclip.papi.expansion.javascript.cloud.download; + +import java.nio.file.Path; + +public interface PathSelector { + Path select(final String name); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/ScriptDownloader.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/ScriptDownloader.java new file mode 100644 index 0000000..5338af5 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/download/ScriptDownloader.java @@ -0,0 +1,10 @@ +package com.extendedclip.papi.expansion.javascript.cloud.download; + +import com.extendedclip.papi.expansion.javascript.cloud.GitScript; + +import java.io.IOException; +import java.nio.file.Path; + +public interface ScriptDownloader { + Path download(final GitScript script) throws IOException; +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/DebugCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/DebugCommand.java new file mode 100644 index 0000000..f0b3732 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/DebugCommand.java @@ -0,0 +1,72 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; +import com.extendedclip.papi.expansion.javascript.script.ScriptRegistry; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import org.bukkit.command.CommandSender; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public final class DebugCommand extends ExpansionCommand { + private static final String ARG_LOAD = "loaddata"; + private static final String ARG_SAVE = "savedata"; + private static final String NAME = "debug"; + + private final ScriptRegistry registry; + + public DebugCommand(final String parentCommandName, final ScriptRegistry registry) { + super(parentCommandName, NAME); + this.registry = registry; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (args.length < 2) { + ExpansionUtils.sendMsg(sender, "&cIncorrect usage! Type '&f/" + getParentCommandName() + "&c' for more help."); + return; + } + + JavascriptPlaceholder jsp = registry.getPlaceholder(getIdentifier(args)); + if (jsp == null) { + ExpansionUtils.sendMsg(sender, "&cInvalid javascript identifier! Please re-check your typo"); + return; + } + + if (args[0].equals(ARG_SAVE)) { + jsp.saveData(); + ExpansionUtils.sendMsg(sender, "&aJavascript data '" + args[1] + "' successfully saved"); + } else if (args[0].equals(ARG_LOAD)) { + jsp.getPersistableData().reload(); + ExpansionUtils.sendMsg(sender, "&aJavascript data '" + args[1] + "' successfully loaded"); + } + } + + @Override + public @NotNull List tabComplete(final CommandSender sender, final String[] args) { + if (args.length == 1) { + return StringUtil.copyPartialMatches(args[0], Arrays.asList(ARG_SAVE, ARG_LOAD), new ArrayList<>()); + } + return Collections.emptyList(); + } + + @Override + protected @NotNull String getCommandFormat() { + return "debug [savedata/loaddata] [identifier]"; + } + + @Override + protected @NotNull String getDescription() { + return "Test JavaScript code in chat"; + } + + public String getIdentifier(final String[] args) { + return Arrays.stream(args).skip(1).collect(Collectors.joining(" ")); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitCommand.java new file mode 100644 index 0000000..3a320d1 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitCommand.java @@ -0,0 +1,72 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.cloud.*; +import com.extendedclip.papi.expansion.javascript.cloud.download.PathSelector; +import com.extendedclip.papi.expansion.javascript.cloud.download.ScriptDownloader; +import com.extendedclip.papi.expansion.javascript.commands.router.CommandRouter; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import org.bukkit.command.CommandSender; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +public final class GitCommand extends ExpansionCommand { + private static final String ARG_REFRESH = "refresh"; + private static final String ARG_LIST = "list"; + private static final String ARG_INFO = "info"; + private static final String ARG_DOWNLOAD = "download"; + private static final String ARG_ENABLED = "enabled"; + + private final ActiveStateSetter activeStateSetter; + private final CommandRouter subCommandRouter; + + public GitCommand(final String parentCommandName, final ActiveStateSetter activeStateSetter, final CommandRouter subCommandRouter) { + super(parentCommandName, "git"); + this.activeStateSetter = activeStateSetter; + this.subCommandRouter = subCommandRouter; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (args.length < 1) { + ExpansionUtils.sendMsg(sender, "&cIncorrect usage! Type '&f/" + getParentCommandName() + "&c' for more help."); + return; + } + if (!activeStateSetter.isActive() && !"enabled".equalsIgnoreCase(args[0])) { + ExpansionUtils.sendMsg(sender, "&cThis feature is disabled in the PlaceholderAPI config."); + return; + } + + subCommandRouter.execute(sender, getParentCommandName() + " git", args); + } + + @Override + @NotNull + public List tabComplete(final CommandSender sender, final String[] args) { + if (args.length == 1) { + return StringUtil.copyPartialMatches(args[0], Arrays.asList(ARG_REFRESH, ARG_LIST, ARG_DOWNLOAD, ARG_ENABLED, ARG_INFO), new ArrayList<>()); + } else if (args.length > 1) { + return subCommandRouter.tabComplete(sender, args[0], args); + } + return Collections.emptyList(); + } + + @Override + @NotNull + protected String getCommandFormat() { + final String args = String.join("/", Arrays.asList(ARG_REFRESH, ARG_LIST, ARG_DOWNLOAD, ARG_ENABLED, ARG_INFO)); + return "git [" + args + "] [params]"; + } + + @Override + @NotNull + protected String getDescription() { + return "Manage github scripts"; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitDownloadCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitDownloadCommand.java new file mode 100644 index 0000000..e47776e --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitDownloadCommand.java @@ -0,0 +1,94 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.cloud.*; +import com.extendedclip.papi.expansion.javascript.cloud.download.PathSelector; +import com.extendedclip.papi.expansion.javascript.cloud.download.ScriptDownloader; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommandRouter; +import com.extendedclip.papi.expansion.javascript.config.ScriptConfiguration; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public final class GitDownloadCommand extends ExpansionCommand { + private final GitScriptManager scriptManager; + private final ScriptConfiguration configuration; + + public GitDownloadCommand(final GitScriptManager scriptManager, final ScriptConfiguration configuration) { + super(ExpansionCommandRouter.COMMAND_NAME + " git", "download"); + this.scriptManager = scriptManager; + this.configuration = configuration; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (args.length < 1) { + ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/" + getParentCommandName() + " git info [name]"); + return; + } + final ScriptIndexProvider indexProvider = scriptManager.getIndexProvider(); + final GitScript script = indexProvider.getScriptIndex().flatMap(index -> index.getScript(args[0])).orElse(null); + + if (script == null) { + ExpansionUtils.sendMsg(sender, "&cThe script &f" + args[0] + " &cdoes not exist!"); + return; + } + final PathSelector selector = scriptManager.getDownloadPathSelector(); + final Path path = selector.select(script.getName()); + if (Files.exists(path)) { + ExpansionUtils.sendMsg(sender, "&cCould not download " + script.getName() + " because a file with the same name already exist in the javascripts folder."); + return; + } + final ScriptDownloader downloader = scriptManager.getScriptDownloader(); + CompletableFuture.supplyAsync(() -> { + ExpansionUtils.sendMsg(sender, "&aDownload started. &eCheck the scripts folder in a moment..."); + try { + return downloader.download(script); + } catch (IOException exception) { + ExpansionUtils.errorLog("Failed to download expansion!", exception); + return null; + } + }).thenAccept(downloadedPath -> { + if (downloadedPath == null) return; + ExpansionUtils.sendMsg(sender, "&aDownload complete! " + script.getName()); + configuration.setPath(script.getName(), downloadedPath.getFileName().toString()); + configuration.save(); + }); + } + + @Override + public @NotNull List tabComplete(CommandSender sender, String[] args) { + if (args.length > 0) { + final ScriptIndexProvider indexProvider = scriptManager.getIndexProvider(); + final List scripts = indexProvider.getScriptIndex() + .map(ScriptIndex::getAllScripts) + .orElse(Collections.emptyList()).stream() + .map(GitScript::getName) + .collect(Collectors.toList()); + return StringUtil.copyPartialMatches(args[0], scripts, new ArrayList<>()); + } + return Collections.emptyList(); + } + + @Override + protected @NotNull String getCommandFormat() { + return "download [name]"; + } + + @Override + protected @NotNull String getDescription() { + return "Downloads specified git-script"; + } +} + diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitEnabledCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitEnabledCommand.java new file mode 100644 index 0000000..9805c3c --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitEnabledCommand.java @@ -0,0 +1,56 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.cloud.*; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommandRouter; +import org.bukkit.command.CommandSender; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public final class GitEnabledCommand extends ExpansionCommand { + private static final List boolCompletion = Arrays.asList("true", "false"); + private final ActiveStateSetter activeStateSetter; + + public GitEnabledCommand(final ActiveStateSetter activeStateSetter) { + super(ExpansionCommandRouter.COMMAND_NAME + " git", "enabled"); + this.activeStateSetter = activeStateSetter; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (args.length < 1) { + ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/jsexpansion git enabled (true/false)"); + return; + } + + final boolean enabled = Boolean.parseBoolean(args[0]); + activeStateSetter.setActive(enabled); + + ExpansionUtils.sendMsg(sender, "&6Git script downloads set to: &e" + enabled); + } + + @Override + public @NotNull List tabComplete(CommandSender sender, String[] args) { + if (args.length > 0) { + return StringUtil.copyPartialMatches(args[0], boolCompletion, new ArrayList<>()); + } + return Collections.emptyList(); + } + + @Override + protected @NotNull String getCommandFormat() { + return "enabled (true/false)"; + } + + @Override + protected @NotNull String getDescription() { + return "Enables/Disables usage of git-script management"; + } +} \ No newline at end of file diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitInfoCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitInfoCommand.java new file mode 100644 index 0000000..933672c --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitInfoCommand.java @@ -0,0 +1,71 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.cloud.GitScript; +import com.extendedclip.papi.expansion.javascript.cloud.GitScriptIndexProvider; +import com.extendedclip.papi.expansion.javascript.cloud.ScriptIndex; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommandRouter; +import org.bukkit.command.CommandSender; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public final class GitInfoCommand extends ExpansionCommand { + private final GitScriptIndexProvider indexProvider; + + public GitInfoCommand(final GitScriptIndexProvider indexProvider) { + super(ExpansionCommandRouter.COMMAND_NAME + " git", "info"); + this.indexProvider = indexProvider; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (args.length < 1) { + ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/" + getParentCommandName() + " git info [name]"); + return; + } + + final GitScript script = indexProvider.getScriptIndex().flatMap(index -> index.getScript(args[0])).orElse(null); + + if (script == null) { + ExpansionUtils.sendMsg(sender, "&cThe script &f" + args[1] + " &cdoes not exist!"); + return; + } + + ExpansionUtils.sendMsg(sender, + "&eName: &f" + script.getName(), + "&eVersion: &f" + script.getVersion(), + "&eDescription: &f" + script.getDescription(), + "&eAuthor: &f" + script.getAuthor(), + "&eSource URL: &f" + script.getUrl() + ); + } + + @Override + public @NotNull List tabComplete(CommandSender sender, String[] args) { + if (args.length > 0) { + final List scripts = indexProvider.getScriptIndex() + .map(ScriptIndex::getAllScripts) + .orElse(Collections.emptyList()).stream() + .map(GitScript::getName) + .collect(Collectors.toList()); + return StringUtil.copyPartialMatches(args[0], scripts, new ArrayList<>()); + } + return Collections.emptyList(); + } + + @Override + protected @NotNull String getCommandFormat() { + return "info [name]"; + } + + @Override + protected @NotNull String getDescription() { + return "Fetches info about a git-script"; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitListCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitListCommand.java new file mode 100644 index 0000000..0e4c3ef --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitListCommand.java @@ -0,0 +1,46 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.cloud.GitScript; +import com.extendedclip.papi.expansion.javascript.cloud.GitScriptIndexProvider; +import com.extendedclip.papi.expansion.javascript.cloud.ScriptIndex; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommandRouter; +import org.bukkit.command.CommandSender; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Collectors; + +public final class GitListCommand extends ExpansionCommand { + private final GitScriptIndexProvider indexProvider; + + public GitListCommand(final GitScriptIndexProvider indexProvider) { + super(ExpansionCommandRouter.COMMAND_NAME + " git", "list"); + this.indexProvider = indexProvider; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + final Collection availableScripts = indexProvider.getScriptIndex().map(ScriptIndex::getAllScripts).orElse(Collections.emptyList()); + final Set scripts = availableScripts.stream().map(GitScript::getName).collect(Collectors.toSet()); + + ExpansionUtils.sendMsg(sender, availableScripts.size() + " &escript" + ExpansionUtils.plural(availableScripts.size()) + " available on Github.", String.join(", ", scripts)); + } + + @Override + public @NotNull List tabComplete(CommandSender sender, String[] args) { + return Collections.emptyList(); + } + + @Override + protected @NotNull String getCommandFormat() { + return "list"; + } + + @Override + protected @NotNull String getDescription() { + return "Lists loaded git-scripts"; + } +} \ No newline at end of file diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitRefreshCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitRefreshCommand.java new file mode 100644 index 0000000..81ac4ad --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/GitRefreshCommand.java @@ -0,0 +1,46 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.cloud.GitScript; +import com.extendedclip.papi.expansion.javascript.cloud.GitScriptIndexProvider; +import com.extendedclip.papi.expansion.javascript.cloud.ScriptIndex; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommandRouter; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public final class GitRefreshCommand extends ExpansionCommand { + private final GitScriptIndexProvider indexProvider; + + public GitRefreshCommand(final GitScriptIndexProvider indexProvider) { + super(ExpansionCommandRouter.COMMAND_NAME + " git", "refresh"); + this.indexProvider = indexProvider; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + ExpansionUtils.sendMsg(sender, "&aFetching available scripts... Check back in a sec!"); + indexProvider.refreshIndex(index -> { + ExpansionUtils.sendMsg(sender, "&aFetched " + index.getCount() + " scripts to index!"); + }); + } + + @Override + public @NotNull List tabComplete(CommandSender sender, String[] args) { + return Collections.emptyList(); + } + + @Override + protected @NotNull String getCommandFormat() { + return "refresh"; + } + + @Override + protected @NotNull String getDescription() { + return "Re-indexes git-scripts from master list"; + } + +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ListCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ListCommand.java new file mode 100644 index 0000000..ce588ce --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ListCommand.java @@ -0,0 +1,49 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; +import com.extendedclip.papi.expansion.javascript.script.ScriptRegistry; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public final class ListCommand extends ExpansionCommand { + + private final ScriptRegistry registry; + + public ListCommand(final String parentCommandName, final ScriptRegistry registry) { + super(parentCommandName, "list"); + this.registry = registry; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + + final List loaded = registry.getAllPlaceholders().stream().map(JavascriptPlaceholder::getIdentifier).collect(Collectors.toList()); + ExpansionUtils.sendMsg(sender,loaded.size() + " &7script" + ExpansionUtils.plural(loaded.size()) + " loaded.", + String.join(", ", loaded)); + } + + @Override + @NotNull + public List tabComplete(final CommandSender sender, final String[] args) { + return Collections.emptyList(); + } + + @Override + @NotNull + protected String getCommandFormat() { + return "list"; + } + + @Override + @NotNull + protected String getDescription() { + return "List loaded script identifiers"; + } + +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ParseCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ParseCommand.java new file mode 100644 index 0000000..f3c9b86 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ParseCommand.java @@ -0,0 +1,83 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholderFactory; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.evaluator.ScriptEvaluatorFactory; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public final class ParseCommand extends ExpansionCommand { + private static final String ARG_ME = "me"; + private static final String ARG_PLAYER = "player"; + + private final JavascriptPlaceholderFactory placeholderFactory; + + public ParseCommand(final String parentCommand, final JavascriptPlaceholderFactory placeholderFactory) { + super(parentCommand, "parse"); + this.placeholderFactory = placeholderFactory; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (args.length < 2) { + ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/" + getParentCommandName() + " parse [me/player] [code]"); + return; + } + final OfflinePlayer player; + + if ("me".equalsIgnoreCase(args[0])) { + if (!(sender instanceof Player)) { + ExpansionUtils.sendMsg(sender, "&cOnly players can run this command!"); + return; + } + + player = (OfflinePlayer) sender; + } else { + player = Bukkit.getOfflinePlayer(args[0]); + } + + final String script = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + final JavascriptPlaceholder placeholder = placeholderFactory.create( "parse-command", String.join(" ", script)); + + + if (!player.hasPlayedBefore() || player.getName() == null) { + ExpansionUtils.sendMsg(sender, "&cUnknown player " + args[0]); + return; + } + + sender.sendMessage(placeholder.evaluate(player)); + } + + @Override + @NotNull + public List tabComplete(final CommandSender sender, final String[] args) { + if (args.length == 1) { + return StringUtil.copyPartialMatches(args[0], Arrays.asList(ARG_ME, ARG_PLAYER), new ArrayList<>()); + } + return Collections.emptyList(); + } + + @Override + @NotNull + protected String getCommandFormat() { + return "parse [me/player] [code]"; + } + + @Override + @NotNull + protected String getDescription() { + return "Test JavaScript code in chat"; + } + +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ReloadCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ReloadCommand.java new file mode 100644 index 0000000..09dbfda --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/ReloadCommand.java @@ -0,0 +1,54 @@ +package com.extendedclip.papi.expansion.javascript.commands; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; +import com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommand; +import com.extendedclip.papi.expansion.javascript.script.ScriptLoader; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public final class ReloadCommand extends ExpansionCommand { + + private final ScriptLoader loader; + + public ReloadCommand(final String parentCommandName, final ScriptLoader loader) { + super(parentCommandName, "reload"); + this.loader = loader; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + + ExpansionUtils.sendMsg(sender, "&aJavascriptExpansion reloading..."); + try { + final int scripts = loader.reload(); + ExpansionUtils.sendMsg(sender, scripts + " &7script" + ExpansionUtils.plural(scripts) + " loaded"); + } catch (final IOException exception) { + ExpansionUtils.errorLog("&7Failed to reload scripts.", exception); + ExpansionUtils.sendMsg(sender, "&7Failed to reload scripts."); + exception.printStackTrace(); + } + } + + @Override + @NotNull + public List tabComplete(final CommandSender sender, final String[] args) { + return Collections.emptyList(); + } + + @Override + @NotNull + protected String getCommandFormat() { + return "reload"; + } + + @Override + @NotNull + protected String getDescription() { + return "Reload your javascripts without reloading PlaceholderAPI"; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/CommandRegistrar.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/CommandRegistrar.java new file mode 100644 index 0000000..ab87494 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/CommandRegistrar.java @@ -0,0 +1,99 @@ +package com.extendedclip.papi.expansion.javascript.commands.router; + +import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholderFactory; +import com.extendedclip.papi.expansion.javascript.script.ScriptLoader; +import com.extendedclip.papi.expansion.javascript.script.ScriptRegistry; +import com.extendedclip.papi.expansion.javascript.cloud.GitScriptManager; +import com.extendedclip.papi.expansion.javascript.commands.*; +import com.extendedclip.papi.expansion.javascript.config.ScriptConfiguration; +import com.extendedclip.papi.expansion.javascript.evaluator.ScriptEvaluatorFactory; +import com.google.common.collect.ImmutableMap; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.plugin.java.JavaPlugin; + +import java.lang.reflect.Field; +import java.util.Map; + +import static com.extendedclip.papi.expansion.javascript.commands.router.ExpansionCommandRouter.COMMAND_NAME; + +public final class CommandRegistrar { + private static final String WIKI_LINK = "https://github.com/PlaceholderAPI/Javascript-Expansion/wiki"; + private final CommandRouter router; + private final CommandMap commandMap; + + public CommandRegistrar(final GitScriptManager gitScriptManager, final JavascriptPlaceholderFactory placeholderFactory, final ScriptConfiguration configuration, final ScriptRegistry registry, final ScriptLoader loader) throws ReflectiveOperationException { + final GitRefreshCommand gitRefreshCommand = new GitRefreshCommand(gitScriptManager.getIndexProvider()); + final GitListCommand gitListCommand = new GitListCommand(gitScriptManager.getIndexProvider()); + final GitDownloadCommand gitDownloadCommand = new GitDownloadCommand(gitScriptManager, configuration); + final GitInfoCommand gitInfoCommand = new GitInfoCommand(gitScriptManager.getIndexProvider()); + final GitEnabledCommand gitEnabledCommand = new GitEnabledCommand(gitScriptManager.getActiveStateSetter()); + + + final Map gitCommandMap = ImmutableMap.builder() + .put("refresh", gitRefreshCommand) + .put("list", gitListCommand) + .put("download", gitDownloadCommand) + .put("info", gitInfoCommand) + .put("enabled", gitEnabledCommand) + .build(); + + final CommandRouter gitCommandRouter = new ExpansionCommandRouter(JavascriptExpansion.VERSION, JavascriptExpansion.AUTHOR, WIKI_LINK, gitCommandMap); + + final GitCommand gitCommand = new GitCommand(COMMAND_NAME, gitScriptManager.getActiveStateSetter(), gitCommandRouter); + final ListCommand listCommand = new ListCommand(COMMAND_NAME, registry); + final DebugCommand debugCommand = new DebugCommand(COMMAND_NAME, registry); + final ParseCommand parseCommand = new ParseCommand(COMMAND_NAME, placeholderFactory); + final ReloadCommand reloadCommand = new ReloadCommand(COMMAND_NAME, loader); + final Map commandMap = ImmutableMap.builder() + .put("git", gitCommand) + .put("list", listCommand) + .put("debug", debugCommand) + .put("parse", parseCommand) + .put("reload", reloadCommand) + .build(); + + this.router = new ExpansionCommandRouter(JavascriptExpansion.VERSION, JavascriptExpansion.AUTHOR, WIKI_LINK, commandMap); + final Field field = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + field.setAccessible(true); + this.commandMap = (CommandMap) field.get(Bukkit.getServer()); + } + + public void register() { + commandMap.register("papi" + router.getName(), router); + } + + public void unregister() { + + try { + Class cmdMapClass = commandMap.getClass(); + final Field knownCommandsField; + + //Check if the server's in 1.13+ + if (cmdMapClass.getSimpleName().equals("CraftCommandMap")) { + knownCommandsField = cmdMapClass.getSuperclass().getDeclaredField("knownCommands"); + } else { + knownCommandsField = cmdMapClass.getDeclaredField("knownCommands"); + } + + knownCommandsField.setAccessible(true); + + //noinspection unchecked + final Map knownCommands = (Map) knownCommandsField.get(commandMap); + knownCommands.remove(router.getName()); + for (String alias : router.getAliases()) { + if (knownCommands.containsKey(alias) && knownCommands.get(alias).toString().contains(router.getName())) { + knownCommands.remove(alias); + } + } + + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + router.unregister(commandMap); + } + +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/CommandRouter.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/CommandRouter.java new file mode 100644 index 0000000..de576fb --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/CommandRouter.java @@ -0,0 +1,102 @@ +package com.extendedclip.papi.expansion.javascript.commands.router; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +public abstract class CommandRouter extends Command { + private final Map subCommandMap; + + protected CommandRouter( + @NotNull final String name, + @NotNull final String description, + @NotNull final String usageMessage, + @NotNull final List aliases, + @Nullable final String permission, + @NotNull final Map commandMap + ) { + super(name, description, usageMessage, aliases); + setPermission(permission); + this.subCommandMap = commandMap; + } + + public abstract List getHelpHeader(); + + public abstract String getSubCommandHelpFormat(); + + public abstract String getInvalidCommandMessage(); + + @Override + public final boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + final String perm = getPermission(); + if (perm != null && !sender.hasPermission(perm)) { + sender.sendMessage(CommandRouter.translateColors("&cYou don't have permission to do that!")); + return true; + } + if (args.length == 0) { + final String format = getSubCommandHelpFormat(); + final List header = getHelpHeader(); + final List subCommandHelp = subCommandMap + .values() + .stream() + .map(cmd -> + String.format( + format, + cmd.getParentCommandName(), + cmd.getCommandFormat(), + cmd.getDescription() + ) + ) + .collect(Collectors.toList()); + header.stream().map(CommandRouter::translateColors).forEach(sender::sendMessage); + subCommandHelp.stream().map(CommandRouter::translateColors).forEach(sender::sendMessage); + return true; + } + + final String subCommand = args[0].toLowerCase(); + final ExpansionCommand matchedCommand = subCommandMap.get(subCommand); + if (matchedCommand == null) { + final String invalidMatchMessage = getInvalidCommandMessage(); + sender.sendMessage(translateColors(String.format(invalidMatchMessage, getName()))); + return true; + } + final String[] subArgs = new String[args.length - 1]; + System.arraycopy(args, 1, subArgs, 0, args.length - 1); + matchedCommand.execute(sender, subArgs); + return true; + } + + @Override + @NotNull + public final List tabComplete(@NotNull final CommandSender sender, @NotNull final String alias, @NotNull final String[] args) throws IllegalArgumentException { + final int length = args.length; + if (length == 1) { // User requires tab completion for subcommand names + final String partialString = args[length - 1]; + return StringUtil.copyPartialMatches( + partialString, subCommandMap.keySet(), + new ArrayList<>() + ); + } else if (length > 1) { // User requires per-command tab completion + final String selectedCommandName = args[0]; + final ExpansionCommand command = subCommandMap.get(selectedCommandName); + if (command == null) { + return Collections.emptyList(); + } + final String[] subArgs = new String[args.length - 1]; + System.arraycopy(args, 1, subArgs, 0, args.length - 1); + return command.tabComplete(sender, subArgs); + } + return super.tabComplete(sender, alias, args); + } + + public static String translateColors(final String input) { + return ChatColor.translateAlternateColorCodes('&', input); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/ExpansionCommand.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/ExpansionCommand.java new file mode 100644 index 0000000..9655bc1 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/ExpansionCommand.java @@ -0,0 +1,38 @@ +package com.extendedclip.papi.expansion.javascript.commands.router; + +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public abstract class ExpansionCommand { + + private final String parentCommandName; + public final String name; + + public ExpansionCommand(@NotNull final String parentCommandName, @NotNull final String name) { + this.parentCommandName = parentCommandName; + this.name = name; + } + + public abstract void execute(final CommandSender sender, final String[] args); + + @NotNull + public abstract List tabComplete(final CommandSender sender, final String[] args); + + @NotNull + protected abstract String getCommandFormat(); + + @NotNull + protected abstract String getDescription(); + + @NotNull + protected final String getParentCommandName() { + return parentCommandName; + } + + @NotNull + protected final String getName() { + return name; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/ExpansionCommandRouter.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/ExpansionCommandRouter.java new file mode 100644 index 0000000..17d5d3f --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/commands/router/ExpansionCommandRouter.java @@ -0,0 +1,60 @@ +package com.extendedclip.papi.expansion.javascript.commands.router; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class ExpansionCommandRouter extends CommandRouter { + public static final String COMMAND_NAME = "jsexpansion"; + private static final String PERMISSION = "placeholderapi.js.admin"; + private static final String DESCRIPTION = "JavaScript Expansion Commands"; + private static final String USAGE = "/jsexpansion "; + private static final List ALIASES = Arrays.asList("javascriptexpansion", "jsexp"); + private static final Collection HELP_HEADER = Arrays.asList( + "&eJavascript expansion &7v: &f{version}", + "&eCreated by: &f{author}", + "&eWiki: &f{wiki}", + "&r" + ); + private final String expansionVersion; + private final String authorName; + private final String wikiLink; + + public ExpansionCommandRouter( + @NotNull final String expansionVersion, + @NotNull final String authorName, + @NotNull final String wikiLink, + @NotNull final Map commandMap + ) { + super(COMMAND_NAME, DESCRIPTION, USAGE, ALIASES, PERMISSION, commandMap); + this.expansionVersion = expansionVersion; + this.authorName = authorName; + this.wikiLink = wikiLink; + } + + @Override + public List getHelpHeader() { + return HELP_HEADER.stream().map(this::replacePlaceholders).collect(Collectors.toList()); + } + + @Override + public String getSubCommandHelpFormat() { + return "&e/%1$s %2$s &7- &f%3$s"; + } + + @Override + public String getInvalidCommandMessage() { + return "&cInvalid expansion sub-command! Type&f /%1$s &cfor help"; + } + + private String replacePlaceholders(final String input) { + return input + .replace("{version}", expansionVersion) + .replace("{author}", authorName) + .replace("{wiki}", wikiLink); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/HeaderWriter.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/HeaderWriter.java new file mode 100644 index 0000000..e67082e --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/HeaderWriter.java @@ -0,0 +1,13 @@ +package com.extendedclip.papi.expansion.javascript.config; + +import org.bukkit.configuration.file.FileConfiguration; + +import java.net.URL; + +public interface HeaderWriter { + void writeTo(final FileConfiguration configuration); + + static HeaderWriter fromJar(final URL jarUrl) { + return new ResourceHeaderWriter(new JarResourceProvider(jarUrl)); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/JarResourceProvider.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/JarResourceProvider.java new file mode 100644 index 0000000..f21ccd8 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/JarResourceProvider.java @@ -0,0 +1,40 @@ +package com.extendedclip.papi.expansion.javascript.config; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.function.Function; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +public final class JarResourceProvider implements Function { + private final URL resourceJar; + + public JarResourceProvider(final URL resourceJar) { + this.resourceJar = resourceJar; + } + + @Override + @Nullable + public InputStream apply(@Nullable final String fileName) { + try { + final JarFile jarFile = new JarFile(new File(resourceJar.toURI())); + final ZipEntry zipEntry = jarFile.getEntry(fileName); + if (zipEntry == null) { + return null; + } + return jarFile.getInputStream(zipEntry); + } catch (final IOException | URISyntaxException exception) { + exception.printStackTrace(); + } + return null; + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/ResourceHeaderWriter.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/ResourceHeaderWriter.java new file mode 100644 index 0000000..356f4a5 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/ResourceHeaderWriter.java @@ -0,0 +1,30 @@ +package com.extendedclip.papi.expansion.javascript.config; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class ResourceHeaderWriter implements HeaderWriter { + @NotNull + private final Function inputStreamFunction; + + public ResourceHeaderWriter(@NotNull final Function inputStreamFunction) { + this.inputStreamFunction = inputStreamFunction; + } + + @Override + public void writeTo(@NotNull final FileConfiguration configuration) { + try (final InputStream stream = inputStreamFunction.apply("header.txt")) { + final String headerString = new BufferedReader(new InputStreamReader(stream)).lines() + .collect(Collectors.joining("\n")); + configuration.options().header(headerString); + } catch (final IOException exception) { + ExpansionUtils.errorLog("Failed to read header file", exception); + } + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/ScriptConfiguration.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/ScriptConfiguration.java new file mode 100644 index 0000000..f305a5b --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/ScriptConfiguration.java @@ -0,0 +1,25 @@ +package com.extendedclip.papi.expansion.javascript.config; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; + +public interface ScriptConfiguration { + @Nullable + Path getPath(@NotNull String scriptName); + + void setPath(@NotNull String scriptName, @Nullable final String name); + + @NotNull + Collection getScripts(); + + @NotNull + Map getEntries(); + + void reload(); + + void save(); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/YamlScriptConfiguration.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/YamlScriptConfiguration.java new file mode 100644 index 0000000..64b0d5f --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/config/YamlScriptConfiguration.java @@ -0,0 +1,105 @@ +package com.extendedclip.papi.expansion.javascript.config; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class YamlScriptConfiguration implements ScriptConfiguration { + private final FileConfiguration fileConfiguration; + private final File configurationFile; + private final HeaderWriter headerWriter; + private final Path scriptDirectoryPath; + + public YamlScriptConfiguration(final File configurationFile, final HeaderWriter headerWriter, final Path scriptDirectoryPath) { + this( + YamlConfiguration.loadConfiguration(configurationFile), + configurationFile, + headerWriter, + scriptDirectoryPath + ); + } + + public YamlScriptConfiguration(final FileConfiguration configuration, final File configurationFile, final HeaderWriter headerWriter, final Path scriptDirectoryPath) { + this.fileConfiguration = configuration; + this.configurationFile = configurationFile; + this.headerWriter = headerWriter; + this.scriptDirectoryPath = scriptDirectoryPath; + if (!Files.isDirectory(scriptDirectoryPath)) { + throw new AssertionError("Expected directory for scripts to be saved/loaded from. Found non-directory path."); + } + } + + @Override + @Nullable + public Path getPath(@NotNull final String scriptName) { + final ConfigurationSection scriptSection = fileConfiguration.getConfigurationSection(scriptName); + if (scriptSection == null) { + return null; + } + String fileName = scriptSection.getString("file"); + if (fileName == null) { + fileName = scriptName + ".js"; + } + return scriptDirectoryPath.resolve(fileName); + } + + @Override + public void setPath(@NotNull final String scriptName, @Nullable final String name) { + final String key = scriptName + ".file"; + fileConfiguration.set(key, name); + } + + @Override + @NotNull + public Collection getScripts() { + return fileConfiguration.getKeys(false); + } + + @Override + @NotNull + public Map getEntries() { + //noinspection ConstantConditions + return getScripts().stream().collect(Collectors.toMap(Function.identity(), this::getPath)); + } + + @Override + public void reload() { + try { + if (!configurationFile.exists()) { + //noinspection ResultOfMethodCallIgnored + configurationFile.getParentFile().mkdirs(); + //noinspection ResultOfMethodCallIgnored + configurationFile.createNewFile(); + } + fileConfiguration.load(configurationFile); + setPath("example", "example.js"); + // Ensure presence of header in case user re-wrote the entire file + headerWriter.writeTo(fileConfiguration); + save(); + } catch (final IOException | InvalidConfigurationException exception) { + ExpansionUtils.errorLog("Failed to reload configuration", exception); + } + } + + @Override + public void save() { + try { + fileConfiguration.save(configurationFile); + } catch (final IOException exception) { + ExpansionUtils.errorLog("Failed to save configuration", exception); + } + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ConfigurationScriptLoader.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ConfigurationScriptLoader.java new file mode 100644 index 0000000..9f3c558 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ConfigurationScriptLoader.java @@ -0,0 +1,50 @@ +package com.extendedclip.papi.expansion.javascript.script; + +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholderFactory; +import com.extendedclip.papi.expansion.javascript.config.ScriptConfiguration; +import com.extendedclip.papi.expansion.javascript.evaluator.ScriptEvaluatorFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public final class ConfigurationScriptLoader implements ScriptLoader { + private final ScriptRegistry registry; + private final ScriptConfiguration configuration; + private final JavascriptPlaceholderFactory placeholderFactory; + + public ConfigurationScriptLoader(ScriptRegistry registry, ScriptConfiguration configuration, JavascriptPlaceholderFactory placeholderFactory) { + this.registry = registry; + this.configuration = configuration; + this.placeholderFactory = placeholderFactory; + } + + @Override + public int reload() throws IOException { + registry.getAllPlaceholders().forEach(JavascriptPlaceholder::saveData); + registry.clearRegistry(); + configuration.reload(); + int loaded = 0; + for (final String scriptIdentifier: configuration.getScripts()) { + final Path path = configuration.getPath(scriptIdentifier); + if (path == null) continue; + if (!Files.exists(path)) { + Files.createDirectories(path.getParent()); + Files.createFile(path); + } + final String script = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + final JavascriptPlaceholder placeholder = placeholderFactory.create(scriptIdentifier, script); + registry.register(placeholder); + loaded++; + } + return loaded; + } + + @Override + public void clear() { + registry.getAllPlaceholders().forEach(JavascriptPlaceholder::saveData); + registry.clearRegistry(); + } +} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/ScriptData.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptData.java similarity index 74% rename from src/main/java/com/extendedclip/papi/expansion/javascript/ScriptData.java rename to expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptData.java index 427099b..5a20a23 100644 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/ScriptData.java +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptData.java @@ -18,25 +18,22 @@ * * */ -package com.extendedclip.papi.expansion.javascript; +package com.extendedclip.papi.expansion.javascript.script; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -public class ScriptData { +public final class ScriptData { - private Map map; + private final Map map; - public ScriptData(Map data) { + public ScriptData(final Map data) { this.map = data; } - public ScriptData() { - this.map = new HashMap<>(); - } - public Map getData() { - return map; + return Collections.unmodifiableMap(map); } public void clear() { @@ -56,11 +53,7 @@ public class ScriptData { } public void set(String key, Object value) { - map.put(key, ExpansionUtils.jsonToJava(value)); - } - - public void setIfNull(String key, Object value) { - map.putIfAbsent(key, ExpansionUtils.jsonToJava(value)); + map.put(key, value); } public boolean isEmpty() { diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptLoader.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptLoader.java new file mode 100644 index 0000000..61bc362 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptLoader.java @@ -0,0 +1,8 @@ +package com.extendedclip.papi.expansion.javascript.script; + +import java.io.IOException; + +public interface ScriptLoader { + int reload() throws IOException; + void clear(); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptRegistry.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptRegistry.java new file mode 100644 index 0000000..6552641 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/ScriptRegistry.java @@ -0,0 +1,42 @@ +package com.extendedclip.papi.expansion.javascript.script; + +import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public final class ScriptRegistry { + private final Map registeredScripts; + + public ScriptRegistry() { + this(new HashMap<>()); + } + + public ScriptRegistry(final Map registeredScripts) { + this.registeredScripts = registeredScripts; + } + + public boolean register(final JavascriptPlaceholder placeholder) { + final JavascriptPlaceholder previousPlaceholder = registeredScripts.putIfAbsent(placeholder.getIdentifier(), placeholder); + return previousPlaceholder == null; // Registered only if there was not prior script with the same name. + } + + public void unregister(final JavascriptPlaceholder placeholder) { + registeredScripts.remove(placeholder.getIdentifier()); + } + + public void clearRegistry() { + registeredScripts.clear(); + } + + @Nullable + public JavascriptPlaceholder getPlaceholder(final String identifier) { + return registeredScripts.get(identifier); + } + + public Collection getAllPlaceholders() { + return registeredScripts.values(); + } +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/ConfigurationMap.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/ConfigurationMap.java new file mode 100644 index 0000000..311fdd5 --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/ConfigurationMap.java @@ -0,0 +1,119 @@ +package com.extendedclip.papi.expansion.javascript.script.data; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public final class ConfigurationMap implements Map { + private final FileConfiguration configuration; + + public ConfigurationMap(final FileConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public int size() { + return this.configuration.getKeys(false).size(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(final Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(final Object value) { + if (value == null) { + return false; + } + return keySet().stream() + .anyMatch(key -> value.equals(get(key))); + } + + @Override + public Object get(final Object key) { + return this.configuration.get(key.toString()); + } + + @Nullable + @Override + public Object put(String key, Object value) { + final Object old = this.configuration.get(key); + this.configuration.set(key, value); + return old; + } + + @Override + public Object remove(Object key) { + return put(key.toString(), null); + } + + @Override + public void putAll(@NotNull Map m) { + for (final Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + keySet().forEach(this::remove); + } + + @NotNull + @Override + public Set keySet() { + return configuration.getKeys(false); + } + + @NotNull + @Override + public Collection values() { + return keySet().stream().map(this::get).collect(Collectors.toList()); + } + + @NotNull + @Override + public Set> entrySet() { + return keySet().stream().map(key -> new Entry(key, get(key))).collect(Collectors.toSet()); + } + + private static final class Entry implements Map.Entry { + private final String key; + private Object value; + + public Entry(String key, Object value) { + this.key = key; + this.value = value; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public Object setValue(final Object value) { + final Object old = this.value; + this.value = value; + return old; + } + } + +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/PersistableData.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/PersistableData.java new file mode 100644 index 0000000..446de5d --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/PersistableData.java @@ -0,0 +1,9 @@ +package com.extendedclip.papi.expansion.javascript.script.data; + +import com.extendedclip.papi.expansion.javascript.script.ScriptData; + +public interface PersistableData { + ScriptData getScriptData(); + void save(); + void reload(); +} diff --git a/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/YmlPersistableData.java b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/YmlPersistableData.java new file mode 100644 index 0000000..1353c6a --- /dev/null +++ b/expansion/src/main/java/com/extendedclip/papi/expansion/javascript/script/data/YmlPersistableData.java @@ -0,0 +1,60 @@ +package com.extendedclip.papi.expansion.javascript.script.data; + +import com.extendedclip.papi.expansion.javascript.ExpansionUtils; +import com.extendedclip.papi.expansion.javascript.script.ScriptData; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + + +public final class YmlPersistableData implements PersistableData { + private final String identifier; + private final ScriptData scriptData; + private final File dataFile; + private final YamlConfiguration configuration; + + private YmlPersistableData(final String identifier, final ScriptData scriptData, final File dataFile, final YamlConfiguration configuration) { + this.identifier = identifier; + this.scriptData = scriptData; + this.dataFile = dataFile; + this.configuration = configuration; + } + + @Override + public ScriptData getScriptData() { + return scriptData; + } + + @Override + public void save() { + try { + configuration.save(dataFile); + } catch (IOException e) { + ExpansionUtils.errorLog(ExpansionUtils.PREFIX + "An error occurred while saving data for " + identifier, e); + } + } + + @Override + public void reload() { + try { + configuration.load(dataFile); + } catch (IOException | InvalidConfigurationException e) { + ExpansionUtils.errorLog(ExpansionUtils.PREFIX + "An error occurred while saving data for " + identifier, e); + } + } + + public static PersistableData create(final String identifier, final Path dataPath) throws IOException { + if (!Files.exists(dataPath)) { + Files.createDirectories(dataPath.getParent()); + Files.createFile(dataPath); + } + final YamlConfiguration configuration = YamlConfiguration.loadConfiguration(dataPath.toFile()); + final Map map = new ConfigurationMap(configuration); + return new YmlPersistableData(identifier, new ScriptData(map), dataPath.toFile(), configuration); + } +} diff --git a/expansion/src/main/resources/header.txt b/expansion/src/main/resources/header.txt new file mode 100644 index 0000000..09f8268 --- /dev/null +++ b/expansion/src/main/resources/header.txt @@ -0,0 +1,23 @@ +Javascript Expansion: @VERSION@ +This is the main configuration file for the Javascript Expansion. + +You will define your javascript placeholders in this file. + +Javascript files must be located in the: + /plugins/placeholderapi/javascripts/ folder + +A detailed guide on how to create your own javascript placeholders +can be found here: +https://github.com/PlaceholderAPI-Expansions/Javascript-Expansion/wiki + +Your javascript placeholders will be identified by: %javascript_% + +Configuration format: + +: + file: . + +Example: + +'my_placeholder': + file: 'my_placeholder.js' \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f371643 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..fbd7c51 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index b84fc22..0000000 --- a/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - 4.0.0 - com.extendedclip.papi.expansion.javascript - javascript-expansion - 1.6.1 - PAPI-Expansion-Javascript - PlaceholderAPI expansion for javascript placeholders - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - placeholderapi - https://repo.extendedclip.com/content/repositories/placeholderapi/ - - - - - - org.spigotmc - spigot-api - 1.16.5-R0.1-SNAPSHOT - provided - - - me.clip - placeholderapi - 2.10.9 - provided - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - ${name} - - - true - true - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - UTF-8 - false - - - - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1c1d979 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'javascript-expansion' +include 'evaluator' +include 'evaluator-api' +include 'expansion' \ No newline at end of file diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansion.java b/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansion.java deleted file mode 100644 index 3ed4c0a..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansion.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * - * Javascript-Expansion - * Copyright (C) 2020 Ryan McCarthy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ -package com.extendedclip.papi.expansion.javascript; - -import com.extendedclip.papi.expansion.javascript.cloud.GithubScriptManager; -import me.clip.placeholderapi.expansion.Cacheable; -import me.clip.placeholderapi.expansion.Configurable; -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; -import org.jetbrains.annotations.NotNull; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; -import java.lang.reflect.Field; -import java.util.*; -import java.util.stream.Collectors; - -public class JavascriptExpansion extends PlaceholderExpansion implements Cacheable, Configurable { - - private ScriptEngine globalEngine = null; - - private JavascriptPlaceholdersConfig config; - private final Set scripts; - private final String VERSION; - private static JavascriptExpansion instance; - private boolean debug; - private GithubScriptManager githubManager; - private JavascriptExpansionCommands commands; - private CommandMap commandMap; - private String argument_split; - - public JavascriptExpansion() { - instance = this; - this.VERSION = getClass().getPackage().getImplementationVersion(); - this.scripts = new HashSet<>(); - - try { - final Field field = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - field.setAccessible(true); - commandMap = (CommandMap) field.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException e) { - ExpansionUtils.errorLog("An error occurred while accessing CommandMap.", e, true); - } - } - - @Override - public String getAuthor() { - return "clip"; - } - - @Override - public String getIdentifier() { - return "javascript"; - } - - @Override - public String getVersion() { - return VERSION; - } - - @Override - public boolean register() { - String defaultEngine = ExpansionUtils.DEFAULT_ENGINE; - - if (globalEngine == null) { - try { - globalEngine = new ScriptEngineManager(null).getEngineByName(getString("engine", defaultEngine)); - } catch (NullPointerException ex) { - ExpansionUtils.warnLog("Javascript engine type was invalid! Defaulting to '" + defaultEngine + "'", null); - globalEngine = new ScriptEngineManager(null).getEngineByName(defaultEngine); - } - } - - argument_split = getString("argument_split", ","); - if (argument_split.equals("_")) { - argument_split = ","; - ExpansionUtils.warnLog("Underscore character will not be allowed for splitting. Defaulting to ',' for this", null); - } - - debug = (boolean) get("debug", false); - config = new JavascriptPlaceholdersConfig(this); - - int amountLoaded = config.loadPlaceholders(); - ExpansionUtils.infoLog(amountLoaded + " script" + ExpansionUtils.plural(amountLoaded) + " loaded!"); - - - if (debug) { - ExpansionUtils.infoLog("Java version: " + System.getProperty("java.version")); - - final ScriptEngineManager manager = new ScriptEngineManager(null); - final List factories = manager.getEngineFactories(); - ExpansionUtils.infoLog("Displaying all script engine factories.", false); - - for (ScriptEngineFactory factory : factories) { - System.out.println(factory.getEngineName()); - System.out.println(" Version: " + factory.getEngineVersion()); - System.out.println(" Lang name: " + factory.getLanguageName()); - System.out.println(" Lang version: " + factory.getLanguageVersion()); - System.out.println(" Extensions: ." + String.join(", .", factory.getExtensions())); - System.out.println(" Mime types: " + String.join(", ", factory.getMimeTypes())); - System.out.println(" Names: " + String.join(", ", factory.getNames())); - } - } - - if ((boolean) get("github_script_downloads", false)) { - githubManager = new GithubScriptManager(this); - githubManager.fetch(); - } - - registerCommand(); - return super.register(); - } - - @Override - public void clear() { - unregisterCommand(); - - scripts.forEach(script -> { - script.saveData(); - script.cleanup(); - }); - - if (githubManager != null) { - githubManager.clear(); - githubManager = null; - } - - scripts.clear(); - globalEngine = null; - instance = null; - } - - @Override - public String onRequest(OfflinePlayer player, @NotNull String identifier) { - if (player == null || scripts.size() == 0) { - return ""; - } - - for (JavascriptPlaceholder script : scripts) { - if (identifier.startsWith(script.getIdentifier() + "_")) { - identifier = identifier.replaceFirst(script.getIdentifier() + "_", ""); - - return !identifier.contains(argument_split) ? script.evaluate(player, identifier) : script.evaluate(player, identifier.split(argument_split)); - } - - if (identifier.equalsIgnoreCase(script.getIdentifier())) { - return script.evaluate(player); - } - } - - return null; - } - - public boolean addJSPlaceholder(JavascriptPlaceholder placeholder) { - if (placeholder == null) { - return false; - } - - if (scripts.isEmpty()) { - scripts.add(placeholder); - return true; - } - - if (getJSPlaceholder(placeholder.getIdentifier()) != null) { - return false; - } - - scripts.add(placeholder); - return true; - } - -// public Set getJSPlaceholders() { -// return scripts; -// } - - public List getLoadedIdentifiers() { - return scripts.stream() - .map(JavascriptPlaceholder::getIdentifier) - .collect(Collectors.toList()); - } - - public JavascriptPlaceholder getJSPlaceholder(String identifier) { - return scripts.stream() - .filter(s -> s.getIdentifier().equalsIgnoreCase(identifier)) - .findFirst() - .orElse(null); - } - - public int getAmountLoaded() { - return scripts.size(); - } - - public ScriptEngine getGlobalEngine() { - return globalEngine; - } - - public JavascriptPlaceholdersConfig getConfig() { - return config; - } - - @Override - public Map getDefaults() { - final Map defaults = new HashMap<>(); - defaults.put("engine", "javascript"); - defaults.put("debug", false); - defaults.put("argument_split", ","); - defaults.put("github_script_downloads", false); - - return defaults; - } - - public int reloadScripts() { - scripts.forEach(script -> { - script.saveData(); - script.cleanup(); - }); - - scripts.clear(); - config.reload(); - return config.loadPlaceholders(); - } - - public static JavascriptExpansion getInstance() { - return instance; - } - - public GithubScriptManager getGithubScriptManager() { - return githubManager; - } - - public void setGithubScriptManager(GithubScriptManager manager) { - this.githubManager = manager; - } - - private void unregisterCommand() { - if (commandMap != null && commands != null) { - - try { - Class cmdMapClass = commandMap.getClass(); - final Field f; - - //Check if the server's in 1.13+ - if (cmdMapClass.getSimpleName().equals("CraftCommandMap")) { - f = cmdMapClass.getSuperclass().getDeclaredField("knownCommands"); - } else { - f = cmdMapClass.getDeclaredField("knownCommands"); - } - - f.setAccessible(true); - Map knownCmds = (Map) f.get(commandMap); - knownCmds.remove(commands.getName()); - for (String alias : commands.getAliases()) { - if (knownCmds.containsKey(alias) && knownCmds.get(alias).toString().contains(commands.getName())) { - knownCmds.remove(alias); - } - } - - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - } - - commands.unregister(commandMap); - } - } - - private void registerCommand() { - if (commandMap == null) { - return; - } - - commands = new JavascriptExpansionCommands(this); - commandMap.register("papi" + commands.getName(), commands); - commands.isRegistered(); - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansionCommands.java b/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansionCommands.java deleted file mode 100644 index c7b1b3b..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptExpansionCommands.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * - * Javascript-Expansion - * Copyright (C) 2020 Ryan McCarthy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ -package com.extendedclip.papi.expansion.javascript; - -import com.extendedclip.papi.expansion.javascript.cloud.GithubScript; -import com.extendedclip.papi.expansion.javascript.command.*; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -public class JavascriptExpansionCommands extends Command { - - private final JavascriptExpansion expansion; - private final String PERMISSION = "placeholderapi.js.admin"; - private final String command; - private List subCommands; - - public JavascriptExpansionCommands(JavascriptExpansion expansion) { - super("jsexpansion"); - command = getName(); - this.expansion = expansion; - this.setDescription("Javascript expansion commands"); - this.setUsage("/" + command + " "); - this.setAliases(new ArrayList<>(Arrays.asList("javascriptexpansion", "jsexp"))); - this.setPermission(PERMISSION); - initCommands(); - } - - public void initCommands() { - if (subCommands != null) { - subCommands.clear(); - } - subCommands = new ArrayList<>(Arrays.asList( - new GitCommand(expansion), - new ListCommand(expansion), - new ParseCommand(expansion), - new ReloadCommand(expansion), - new DebugCommand(expansion)) - ); - } - - @Override - public boolean execute(CommandSender sender, @NotNull String label, String[] args) { - - if (!sender.hasPermission(PERMISSION)) { - ExpansionUtils.sendMsg(sender, "&cYou don't have permission to do that!"); - return true; - } - - if (args.length == 0) { - sendHelp(sender); - return true; - } - - ICommand command = null; - for (ICommand icmd : subCommands) { - if (icmd.getAlias().equalsIgnoreCase(args[0])) { - command = icmd; - command.command = getName(); - break; - } - } - - if (command == null) { - ExpansionUtils.sendMsg(sender, "&cInvalid expansion sub-command! Type&f /" + getName() + " &cfor help"); - return true; - } - - command.execute(sender, sliceFirstArr(args)); - - return true; - } - - //TODO: This thing here has to be organized thoroughly later... - @Override - public List tabComplete(CommandSender sender, @NotNull String alias, String[] args) throws IllegalArgumentException { - if (!sender.hasPermission(PERMISSION)) { - return Collections.emptyList(); - } - - final List commands = new ArrayList<>(Arrays.asList("list", "parse", "reload")); - final List completion = new ArrayList<>(); - - if (expansion.getGithubScriptManager() != null) { - commands.add(0, "git"); - } - - if (args.length == 1) { - return StringUtil.copyPartialMatches(args[0], commands, completion); - } - - if (args[0].equalsIgnoreCase("git")) { - if (expansion.getGithubScriptManager() == null) { - return Collections.emptyList(); - } - - if (args.length == 2) { - return StringUtil.copyPartialMatches(args[1], Arrays.asList("download", "enable", "info", "list", "refresh"), completion); - } - - if (args.length == 3 && args[1].equalsIgnoreCase("download")) { - if (expansion.getGithubScriptManager().getAvailableScripts() == null) { - return Collections.emptyList(); - } - - return StringUtil.copyPartialMatches(args[2], expansion.getGithubScriptManager().getAvailableScripts().stream().map(GithubScript::getName).collect(Collectors.toList()), completion); - } - } - - return Collections.emptyList(); - } - - private void sendHelp(CommandSender sender) { - ExpansionUtils.sendMsg(sender, - "&eJavascript expansion &7v: &f" + expansion.getVersion(), - "&eCreated by: &f" + expansion.getAuthor(), - "&eWiki: &fhttps://github.com/PlaceholderAPI/Javascript-Expansion/wiki", - "&r", - "&e/" + command + " reload &7- &fReload your javascripts without reloading PlaceholderAPI.", - "&e/" + command + " list &7- &fList loaded script identifiers.", - "&e/" + command + " parse [me/player] [code] &7- &fTest JavaScript code in chat.", - "&e/" + command + " debug [savedata/loaddata] [identifier] &7- &fTest JavaScript code in chat." - ); - - if (expansion.getGithubScriptManager() != null) { - ExpansionUtils.sendMsg(sender, - "&e/" + command + " git refresh &7- &fRefresh available Github scripts", - "&e/" + command + " git download [name] &7- &fDownload a script from the js expansion github.", - "&e/" + command + " git list &7- &fList available scripts in the js expansion github.", - "&e/" + command + " git info [name] &7- &fGet the description and url of a specific script." - ); - } else { - ExpansionUtils.sendMsg(sender, - "&e/" + command + " git &7- &fGithub command &7(please enable in config)" - ); - } - } - - public String[] sliceFirstArr(String[] args) { - return Arrays.stream(args).skip(1).toArray(String[]::new); - } - -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholder.java b/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholder.java deleted file mode 100644 index 5b8e053..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholder.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * - * Javascript-Expansion - * Copyright (C) 2020 Ryan McCarthy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ -package com.extendedclip.papi.expansion.javascript; - -import me.clip.placeholderapi.PlaceholderAPI; -import me.clip.placeholderapi.PlaceholderAPIPlugin; -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; - -import javax.script.ScriptEngine; -import javax.script.ScriptException; -import java.io.File; -import java.io.IOException; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class JavascriptPlaceholder { - - private final ScriptEngine engine; - private final String identifier; - private final String script; - private ScriptData scriptData; - private final File dataFile; - private YamlConfiguration yaml; - private final Pattern pattern; - - @SuppressWarnings("ResultOfMethodCallIgnored") - public JavascriptPlaceholder(ScriptEngine engine, String identifier, String script) { - Validate.notNull(engine, "ScriptEngine can not be null"); - Validate.notNull(identifier, "Identifier can not be null"); - Validate.notNull(script, "Script can not be null"); - - String dir = PlaceholderAPIPlugin.getInstance().getDataFolder() + "/javascripts/javascript_data"; - this.engine = engine; - this.identifier = identifier; - this.script = script; - final File directory = new File(dir); - - if (!directory.exists()) { - directory.mkdirs(); - } - - pattern = Pattern.compile("//.*|/\\*[\\S\\s]*?\\*/|%([^%]+)%"); - scriptData = new ScriptData(); - dataFile = new File(directory, identifier + "_data.yml"); - engine.put("Data", scriptData); - engine.put("DataVar", scriptData.getData()); - engine.put("BukkitServer", Bukkit.getServer()); - engine.put("Expansion", JavascriptExpansion.getInstance()); - engine.put("Placeholder", this); - engine.put("PlaceholderAPI", PlaceholderAPI.class); - - } - - public String getIdentifier() { - return identifier; - } - - public String evaluate(OfflinePlayer player, String... args) { - - // A checker to deny all placeholders inside comment codes - Matcher matcher = pattern.matcher(script); - StringBuffer buffer = new StringBuffer(); - - while (matcher.find()) { - String matched = matcher.group(0); - if (!matched.startsWith("%") || matched.startsWith("/*") || matched.startsWith("//")) continue; - - matcher.appendReplacement(buffer, PlaceholderAPI.setPlaceholders(player, matched)); - } - - matcher.appendTail(buffer); - String exp = buffer.toString(); - - try { - String[] arguments = null; - - if (args != null && args.length > 0) { - arguments = new String[args.length]; - - for (int i = 0; i < args.length; i++) { - if (args[i] == null || args[i].isEmpty()) { - continue; - } - arguments[i] = PlaceholderAPI.setBracketPlaceholders(player, args[i]); - } - } - - if (arguments == null) { - arguments = new String[]{}; - } - - engine.put("args", arguments); - - if (player != null && player.isOnline()) { - engine.put("BukkitPlayer", player.getPlayer()); - engine.put("Player", player.getPlayer()); - } - - engine.put("OfflinePlayer", player); - Object result = engine.eval(exp); - return result != null ? PlaceholderAPI.setBracketPlaceholders(player, result.toString()) : ""; - - } catch (ScriptException ex) { - ExpansionUtils.errorLog("An error occurred while executing the script '" + identifier + "':\n\t" + ex.getMessage(), null); - } catch (ArrayIndexOutOfBoundsException ex) { - ExpansionUtils.errorLog("Argument out of bound while executing script '" + identifier + "':\n\t" + ex.getMessage(), null); - } - return "Script error (check console)"; - } - - public String getScript() { - return script; - } - - public ScriptData getData() { - if (scriptData == null) { - scriptData = new ScriptData(); - } - return scriptData; - } - - public void setData(ScriptData data) { - this.scriptData = data; - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - public boolean loadData() { - yaml = new YamlConfiguration(); - dataFile.getParentFile().mkdirs(); - - if (!dataFile.exists()) { - try { - dataFile.createNewFile(); - } catch (IOException e) { - ExpansionUtils.errorLog("An error occurred while creating data file for " + getIdentifier(), e); - return false; - } - } - - try { - yaml.load(dataFile); - } catch (IOException | InvalidConfigurationException e) { - ExpansionUtils.errorLog("An error occurred while loading for " + getIdentifier(), e); - return false; - } - - final Set keys = yaml.getKeys(true); - - if (keys.size() == 0) { - return false; - } - - if (scriptData == null) - scriptData = new ScriptData(); - else scriptData.clear(); - - keys.forEach(key -> scriptData.set(key, ExpansionUtils.ymlToJavaObj(yaml.get(key)))); - - if (!scriptData.isEmpty()) { - setData(scriptData); - return true; - } - return false; - } - - public void saveData() { - if (scriptData == null || scriptData.isEmpty() || yaml == null) { - return; - } - - // Function for merging JSON. - // TODO: This will be removed along with Nashorn in a later future - scriptData.getData().forEach((key, value) -> yaml.set(key, ExpansionUtils.jsonToJava(value))); - - try { - yaml.save(dataFile); - } catch (IOException e) { - ExpansionUtils.errorLog(ExpansionUtils.PREFIX + "An error occurred while saving data for " + getIdentifier(), e); - } - } - - public void cleanup() { - if (this.scriptData != null) { - this.scriptData.clear(); - this.scriptData = null; - } - this.yaml = null; - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholdersConfig.java b/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholdersConfig.java deleted file mode 100644 index 508462c..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/JavascriptPlaceholdersConfig.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * - * Javascript-Expansion - * Copyright (C) 2020 Ryan McCarthy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ -package com.extendedclip.papi.expansion.javascript; - -import me.clip.placeholderapi.PlaceholderAPIPlugin; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; - -public class JavascriptPlaceholdersConfig { - - private final JavascriptExpansion ex; - private final PlaceholderAPIPlugin plugin; - private FileConfiguration config; - private File file; - - public JavascriptPlaceholdersConfig(JavascriptExpansion ex) { - this.ex = ex; - plugin = ex.getPlaceholderAPI(); - reload(); - } - - public void reload() { - if (file == null) { - file = new File(plugin.getDataFolder(), "javascript_placeholders.yml"); - } - - config = YamlConfiguration.loadConfiguration(file); - config.options().header("Javascript Expansion: " + ex.getVersion() - + "\nThis is the main configuration file for the Javascript Expansion." - + "\n" - + "\nYou will define your javascript placeholders in this file." - + "\n" - + "\nJavascript files must be located in the:" - + "\n /plugins/placeholderapi/javascripts/ folder" - + "\n" - + "\nA detailed guide on how to create your own javascript placeholders" - + "\ncan be found here:" - + "\nhttps://github.com/PlaceholderAPI-Expansions/Javascript-Expansion/wiki" - + "\n" - + "\nYour javascript placeholders will be identified by: %javascript_%" - + "\n" - + "\nConfiguration format:" - + "\n" - + "\n:" - + "\n file: ." - + "\n engine: (name of script engine)" - + "\n" - + "\n" - + "\nExample:" - + "\n" - + "\n'my_placeholder':" - + "\n file: 'my_placeholder.js'" - + "\n engine: 'nashorn'"); - - if (config.getKeys(false).isEmpty()) { - config.set("example.file", "example.js"); - config.set("example.engine", ExpansionUtils.DEFAULT_ENGINE); - } - - save(); - } - - public FileConfiguration load() { - if (config == null) reload(); - return config; - } - - public void save() { - if (config == null || file == null) { - return; - } - - try { - load().save(file); - } catch (IOException ex) { - ExpansionUtils.warnLog("Could not save to " + file, ex); - } - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - public int loadPlaceholders() { - if (config == null || config.getKeys(false).isEmpty()) { - return 0; - } - - final File directory = new File(plugin.getDataFolder(), "javascripts"); - - try { - if (!directory.exists()) { - directory.mkdirs(); - ExpansionUtils.infoLog("Creating directory: " + directory.getPath()); - } - } catch (SecurityException e) { - ExpansionUtils.errorLog("Could not create directory: " + directory.getPath(), e); - } - - for (String identifier : config.getKeys(false)) { - final String fileName = config.getString(identifier + ".file"); - if (!config.contains(identifier + ".file") || fileName == null) { - ExpansionUtils.warnLog("Javascript placeholder: " + identifier + " does not have a file specified", null); - continue; - } - - final File scriptFile = new File(plugin.getDataFolder() + "/javascripts", fileName); - - if (!scriptFile.exists()) { - ExpansionUtils.infoLog(scriptFile.getName() + " does not exist. Creating one for you..."); - - try { - scriptFile.createNewFile(); - ExpansionUtils.infoLog(scriptFile.getName() + " created! Add your javascript to this file and use '/jsexpansion reload' to load it!"); - } catch (IOException e) { - ExpansionUtils.errorLog("An error occurred while creating " + scriptFile.getName(), e); - } - - continue; - } - - final String script = getContents(scriptFile); - - if (script == null || script.isEmpty()) { - ExpansionUtils.warnLog("File: " + scriptFile.getName() + " for Javascript placeholder: " + identifier + " is empty", null); - continue; - } - - boolean debug = (boolean) ex.get("debug", false); - int errScriptEngine = 0; - - ScriptEngine engine; - if (!config.contains(identifier + ".engine")) { - engine = ex.getGlobalEngine(); - if (debug) { - ExpansionUtils.warnLog("ScriptEngine type for javascript placeholder: " + identifier + " is empty! Defaulting to global", null); - } else { - errScriptEngine++; - } - } else { - try { - engine = new ScriptEngineManager(null).getEngineByName(config.getString(identifier + ".engine", "nashorn")); - } catch (NullPointerException e) { - if (debug) { - ExpansionUtils.warnLog("ScriptEngine type for javascript placeholder: " + identifier + " is invalid! Defaulting to global", null); - } else { - errScriptEngine++; - } - engine = ex.getGlobalEngine(); - } - } - - if (errScriptEngine > 0) { - ExpansionUtils.warnLog("ScriptEngine type for " + errScriptEngine + " javascript placeholder" + ExpansionUtils.plural(errScriptEngine) + - " failed! Defaulting all to global. More information by enabling debug mode", null); - } - - if (engine == null) { - ExpansionUtils.warnLog("Failed to set ScriptEngine for javascript placeholder: " + identifier, null); - continue; - } - - final JavascriptPlaceholder placeholder = new JavascriptPlaceholder(engine, identifier, script); - final boolean added = ex.addJSPlaceholder(placeholder); - - if (added) { - if (placeholder.loadData()) { - ExpansionUtils.infoLog("Data for placeholder &b" + identifier + "&r has been loaded"); - } - - ExpansionUtils.infoLog("Placeholder &b%javascript_" + identifier + "%&r has been loaded"); - } else { - ExpansionUtils.warnLog("Javascript placeholder %javascript_" + identifier + "% is duplicated!", null); - } - } - return ex.getAmountLoaded(); - } - - private String getContents(File file) { - final StringBuilder sb = new StringBuilder(); - - try { - List lines = Files.readAllLines(file.toPath()); - lines.forEach((line) -> sb.append(line).append("\n")); - } catch (IOException e) { - return null; - } - - return sb.toString(); - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GithubScriptManager.java b/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GithubScriptManager.java deleted file mode 100644 index 717594d..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/cloud/GithubScriptManager.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * - * Javascript-Expansion - * Copyright (C) 2020 Ryan McCarthy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ -package com.extendedclip.papi.expansion.javascript.cloud; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; -import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholdersConfig; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import jdk.nashorn.api.scripting.ScriptUtils; -import org.bukkit.Bukkit; - -import java.io.*; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.logging.Level; -import java.util.stream.Collectors; - -public class GithubScriptManager { - - private final JavascriptExpansion expansion; - private final String JAVASCRIPTS_FOLDER; - private List availableScripts; - private final String MASTER_LIST_URL = "https://raw.githubusercontent.com/PlaceholderAPI/Javascript-Expansion/master/scripts/master_list.json"; - private final Gson GSON = new Gson(); - - public GithubScriptManager(JavascriptExpansion expansion) { - this.expansion = expansion; - JAVASCRIPTS_FOLDER = expansion.getPlaceholderAPI().getDataFolder() - + File.separator - + "javascripts" - + File.separator; - } - - public void clear() { - availableScripts = null; - } - - public void fetch() { - Bukkit.getScheduler().runTaskAsynchronously(expansion.getPlaceholderAPI(), () -> { - final String json = getContents(MASTER_LIST_URL); - - if (json.isEmpty()) { - return; - } - - availableScripts = GSON.fromJson(json, new TypeToken>() {}.getType()); - }); - } - - public void downloadScript(GithubScript script) { - Bukkit.getScheduler().runTaskAsynchronously(expansion.getPlaceholderAPI(), () -> { - final List contents = read(script.getUrl()); - - if (contents.isEmpty()) { - return; - } - - try (final PrintStream out = new PrintStream(new FileOutputStream(new File(JAVASCRIPTS_FOLDER, script.getName() + ".js")))) { - contents.forEach(out::println); - } catch (FileNotFoundException e) { - ExpansionUtils.errorLog("An error occurred while downloading " + script.getName(), e); - return; - } - - Bukkit.getScheduler().runTask(expansion.getPlaceholderAPI(), () -> { - JavascriptPlaceholdersConfig config = expansion.getConfig(); - config.load().set(script.getName() + ".file", script.getName() + ".js"); - config.load().set(script.getName() + ".engine", "javascript"); - - config.save(); - }); - }); - } - - private String getContents(String url) { - return String.join("", read(url)); - } - - private List read(final String url) { - final List lines = new ArrayList<>(); - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) { - lines.addAll(reader.lines().filter(Objects::nonNull).collect(Collectors.toList())); - } catch (Exception ex) { - ex.printStackTrace(); - } - - return lines; - } - - public List getAvailableScripts() { - return availableScripts; - } - - public GithubScript getScript(final String name) { - if (availableScripts == null) { - return null; - } - - return availableScripts.stream() - .filter(s -> s.getName().equalsIgnoreCase(name)) - .findFirst() - .orElse(null); - } - - public String getJavascriptsFolder() { - return JAVASCRIPTS_FOLDER; - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/command/DebugCommand.java b/src/main/java/com/extendedclip/papi/expansion/javascript/command/DebugCommand.java deleted file mode 100644 index 38315da..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/command/DebugCommand.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.extendedclip.papi.expansion.javascript.command; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; -import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.stream.Collectors; - -public class DebugCommand extends ICommand { - - private final JavascriptExpansion expansion; - - public DebugCommand(JavascriptExpansion expansion) { - this.expansion = expansion; - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (args.length < 2) { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! Type '&f/" + command + "&c' for more help."); - return; - } - - JavascriptPlaceholder jsp = expansion.getJSPlaceholder(getIdentifier(args)); - if (jsp == null) { - ExpansionUtils.sendMsg(sender, "&cInvalid javascript identifier! Please re-check your typo"); - return; - } - - if (args[0].equals("savedata")) { - jsp.saveData(); - ExpansionUtils.sendMsg(sender, "&aJavascript data '" + args[1] + "' successfully saved"); - } else if (args[0].equals("loaddata")) { - jsp.loadData(); - ExpansionUtils.sendMsg(sender, "&aJavascript data '" + args[1] + "' successfully loaded"); - } - } - - public String getIdentifier(String[] args) { - return Arrays.stream(args).skip(1).collect(Collectors.joining(" ")); - } - - @Override - public @NotNull String getAlias() { - return "debug"; - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/command/GitCommand.java b/src/main/java/com/extendedclip/papi/expansion/javascript/command/GitCommand.java deleted file mode 100644 index 37743d8..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/command/GitCommand.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.extendedclip.papi.expansion.javascript.command; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; -import com.extendedclip.papi.expansion.javascript.cloud.GithubScript; -import com.extendedclip.papi.expansion.javascript.cloud.GithubScriptManager; -import me.clip.placeholderapi.PlaceholderAPIPlugin; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -public class GitCommand extends ICommand { - - private final JavascriptExpansion expansion; - - public GitCommand(JavascriptExpansion expansion) { - this.expansion = expansion; - } - - @Override - public void execute(CommandSender sender, String[] args) { - - if (expansion.getGithubScriptManager() == null) { - ExpansionUtils.sendMsg(sender, "&cThis feature is disabled in the PlaceholderAPI config."); - return; - } - - if (args.length < 1) { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! Type '&f/" + command + "&c' for more help."); - return; - } - - final GithubScriptManager manager = expansion.getGithubScriptManager(); - - switch (args[0].toLowerCase()) { - case "refresh": { - expansion.getGithubScriptManager().fetch(); - ExpansionUtils.sendMsg(sender, "&aFetching available scripts... Check back in a sec!"); - return; - } - - case "list": { - final List availableScripts = manager.getAvailableScripts(); - final Set scripts = availableScripts.stream().map(GithubScript::getName).collect(Collectors.toSet()); - - ExpansionUtils.sendMsg(sender, availableScripts.size() + " &escript" + ExpansionUtils.plural(availableScripts.size()) + " available on Github.", String.join(", ", scripts)); - return; - } - - case "info": { - if (args.length < 2) { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/" + command + " git info [name]"); - return; - } - - final GithubScript script = manager.getScript(args[1]); - - if (script == null) { - ExpansionUtils.sendMsg(sender, "&cThe script &f" + args[1] + " &cdoes not exist!"); - return; - } - - ExpansionUtils.sendMsg(sender, - "&eName: &f" + script.getName(), - "&eVersion: &f" + script.getVersion(), - "&eDescription: &f" + script.getDescription(), - "&eAuthor: &f" + script.getAuthor(), - "&eSource URL: &f" + script.getUrl() - ); - return; - } - - case "download": { - if (args.length < 2) { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/" + command + " git download [name]"); - return; - } - - final GithubScript script = manager.getScript(args[1]); - - if (script == null) { - ExpansionUtils.sendMsg(sender, "&cThe script &f" + args[1] + " &cdoes not exist!"); - return; - } - - if (new File(expansion.getGithubScriptManager().getJavascriptsFolder(), script.getName() + ".js").exists()) { - ExpansionUtils.sendMsg(sender, "&cCould not download " + script.getName() + " because a file with the same name already exist in the javascripts folder."); - return; - } - - manager.downloadScript(script); - ExpansionUtils.sendMsg(sender, "&aDownload started. &eCheck the scripts folder in a moment..."); - return; - } - - case "enabled": - if (args.length < 2) { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/jsexpansion git enabled [true/false]"); - return; - } - - final boolean enabled = Boolean.parseBoolean(args[1]); - final PlaceholderAPIPlugin papi = expansion.getPlaceholderAPI(); - - papi.getConfig().set("expansions." + command + ".github_script_downloads", enabled); - papi.saveConfig(); - papi.reloadConfig(); - - if (!enabled) { - if (expansion.getGithubScriptManager() != null) { - expansion.getGithubScriptManager().clear(); - expansion.setGithubScriptManager(null); - } - } else { - if (expansion.getGithubScriptManager() == null) { - expansion.setGithubScriptManager(new GithubScriptManager(expansion)); - } - expansion.getGithubScriptManager().fetch(); - } - - ExpansionUtils.sendMsg(sender, "&6Git script downloads set to: &e" + enabled); - return; - - default: { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! Type '&f/" + command + "&c' for more help."); - } - } - } - - @Override - public @NotNull String getAlias() { - return "git"; - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ICommand.java b/src/main/java/com/extendedclip/papi/expansion/javascript/command/ICommand.java deleted file mode 100644 index e8bf311..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ICommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.extendedclip.papi.expansion.javascript.command; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.Objects; -import java.util.stream.Collectors; - -public abstract class ICommand { - - public String command; - - public abstract void execute(CommandSender sender, String[] args); - - public abstract @NotNull String getAlias(); - -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ListCommand.java b/src/main/java/com/extendedclip/papi/expansion/javascript/command/ListCommand.java deleted file mode 100644 index fc77a97..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ListCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.extendedclip.papi.expansion.javascript.command; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class ListCommand extends ICommand { - - private final JavascriptExpansion expansion; - - public ListCommand(JavascriptExpansion expansion) { - this.expansion = expansion; - } - - @Override - public void execute(CommandSender sender, String[] args) { - - final List loaded = expansion.getLoadedIdentifiers(); - ExpansionUtils.sendMsg(sender,loaded.size() + " &7script" + ExpansionUtils.plural(loaded.size()) + " loaded.", - String.join(", ", loaded)); - } - - @Override - @NotNull - public String getAlias() { - return "list"; - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ParseCommand.java b/src/main/java/com/extendedclip/papi/expansion/javascript/command/ParseCommand.java deleted file mode 100644 index 70b8b98..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ParseCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.extendedclip.papi.expansion.javascript.command; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; -import com.extendedclip.papi.expansion.javascript.JavascriptPlaceholder; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; - -public class ParseCommand extends ICommand { - - private final JavascriptExpansion expansion; - - public ParseCommand(JavascriptExpansion expansion) { - this.expansion = expansion; - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (args.length < 2) { - ExpansionUtils.sendMsg(sender, "&cIncorrect usage! &f/" + command + " parse [me/player] [code]"); - return; - } - - final String script = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); - final JavascriptPlaceholder placeholder = new JavascriptPlaceholder(expansion.getGlobalEngine(), "parse-command", String.join(" ", script)); - - if ("me".equalsIgnoreCase(args[0])) { - if (!(sender instanceof Player)) { - ExpansionUtils.sendMsg(sender, "&cOnly players can run this command!"); - return; - } - - sender.sendMessage(placeholder.evaluate((Player) sender)); - return; - } - - final OfflinePlayer player = Bukkit.getOfflinePlayer(args[1]); - - if (!player.hasPlayedBefore() || player.getName() == null) { - ExpansionUtils.sendMsg(sender, "&cUnknown player " + args[1]); - return; - } - - sender.sendMessage(placeholder.evaluate(player)); - } - - @Override - @NotNull - public String getAlias() { - return "parse"; - } -} diff --git a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ReloadCommand.java b/src/main/java/com/extendedclip/papi/expansion/javascript/command/ReloadCommand.java deleted file mode 100644 index a33e7a1..0000000 --- a/src/main/java/com/extendedclip/papi/expansion/javascript/command/ReloadCommand.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.extendedclip.papi.expansion.javascript.command; - -import com.extendedclip.papi.expansion.javascript.ExpansionUtils; -import com.extendedclip.papi.expansion.javascript.JavascriptExpansion; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -public class ReloadCommand extends ICommand { - - private final JavascriptExpansion expansion; - - public ReloadCommand(JavascriptExpansion expansion) { - this.expansion = expansion; - } - - @Override - public void execute(CommandSender sender, String[] args) { - - ExpansionUtils.sendMsg(sender, "&aJavascriptExpansion reloading..."); - final int scripts = expansion.reloadScripts(); - ExpansionUtils.sendMsg(sender, scripts + " &7script" + ExpansionUtils.plural(scripts) + " loaded"); - } - - @Override - @NotNull - public String getAlias() { - return "reload"; - } -}