Compare commits

...

27 Commits

Author SHA1 Message Date
PiggyPiglet
3d81cc443b click event stuff 2026-01-18 20:07:47 +08:00
PiggyPiglet
2d686b679f we're really getting somewhere!@1
who the fuck decided to use 2 space instead of 4
2026-01-18 20:07:33 +08:00
PiggyPiglet
5fbc5ee1cb Start merging chars replacer logic into adventure replacer
very rough no judge
2026-01-18 20:03:16 +08:00
PiggyPiglet
4378a439fc Uhh 2026-01-18 20:03:16 +08:00
Funnycube
85aa6ae8ae Corrected ecloud link 2026-01-15 18:18:03 +11:00
PiggyPiglet
83aad38b09 Update readme api url 2026-01-12 18:22:42 +08:00
PiggyPiglet
b0fb784079 Move verification boolean to expansion version, move to v3 api 2026-01-12 17:40:35 +08:00
PiggyPiglet
84948101f4 Update API URL 2026-01-12 17:00:33 +08:00
PiggyPiglet
5dea70532c less "malicious" more "safety" 2025-11-21 20:07:28 +08:00
PiggyPiglet
403adeb217 cube doesn't like calling it malware 2025-11-21 20:01:53 +08:00
PiggyPiglet
a81ed63c0f Change malicious expansion message 2025-11-21 19:03:57 +08:00
PiggyPiglet
38da700168 Add checks for known malicious expansion checksums before expansion load 2025-11-21 18:56:49 +08:00
Andre_601
4a085682dc Merge pull request #1147 from petulikan1/master
Corrected grammar errors
2025-11-15 09:16:42 +01:00
Petulikan Dálnevím
cfed3ce31b Fix typo in pull request template 2025-11-15 05:29:41 +01:00
Petulikan Dálnevím
4c62318338 Fix grammatical error in CONTRIBUTING.md 2025-11-15 05:26:47 +01:00
PiggyPiglet
7247cbb9f7 2.11.8 dev 2025-11-03 16:58:54 +08:00
PiggyPiglet
9ed7a7ae56 Merge remote-tracking branch 'origin/master' 2025-11-03 16:56:42 +08:00
PiggyPiglet
9a356ceecf 2.11.7 2025-11-03 16:56:35 +08:00
Funnycube
e0ac7b1c66 Remove BuiltByBit link from README
Removed BuiltByBit link from the README.
2025-11-03 19:45:47 +11:00
PiggyPiglet
82230d8156 2.11.8 dev 2025-11-03 16:19:54 +08:00
PiggyPiglet
cda55f20aa fix javadoc build 2025-11-03 16:17:48 +08:00
PiggyPiglet
0df7a01fd8 Update NMS Version 2025-11-03 16:15:34 +08:00
PiggyPiglet
b49668ddee 2.11.7 Update 2025-11-03 16:07:19 +08:00
Funnycube
f14b081f0b Add modrinth 2025-08-15 17:06:52 +10:00
PiggyPiglet
b51fbf4e13 Fix jenkins build
disableAutoTargetJvm in gradle
2025-07-03 13:40:53 +08:00
PiggyPiglet
e48b6fe702 Update NMSVersion enum 2025-07-01 20:44:21 +08:00
PiggyPiglet
984f944daf Merge pull request #1127 from PlaceholderAPI/folia
Merge UniversalScheduler (Folia Support) #980
2025-07-01 20:24:13 +08:00
53 changed files with 3778 additions and 3168 deletions

View File

@@ -38,7 +38,7 @@ PlaceholderAPI provides a feature to have expansions (separate jar files) for pl
In those cases should you report the issue to the issue tracker of the expansion or plugin. In those cases should you report the issue to the issue tracker of the expansion or plugin.
## Pull requests ## Pull requests
As an open source project are we welcoming all contributions to improve PlaceholderAPI, being it changes to its code, or contributions to its documentation such as the [Wiki] or the Javadocs. As an open source project we are welcoming all contributions to improve PlaceholderAPI, being it changes to its code, or contributions to its documentation such as the [Wiki] or the Javadocs.
> [!IMPORTANT] > [!IMPORTANT]
> When contributing, make sure to both base of and target the mentioned branch. Pull requests targeting the wrong branch may get closed without a warning. > When contributing, make sure to both base of and target the mentioned branch. Pull requests targeting the wrong branch may get closed without a warning.

View File

@@ -21,7 +21,7 @@
### Description ### Description
<!-- What does your Pull request change? --> <!-- What does your Pull request change? -->
Closes N/A <!-- If your PR is based on an issue, change "N/A" the the issue ID (#id) --> Closes N/A <!-- If your PR is based on an issue, change "N/A" to the issue ID (#id) -->
<!-- DO NOT ALTER ANYTHING BELOW THIS LINE! --> <!-- DO NOT ALTER ANYTHING BELOW THIS LINE! -->

View File

@@ -8,8 +8,8 @@
[discord]: https://helpch.at/discord [discord]: https://helpch.at/discord
[spigot]: https://www.spigotmc.org/resources/6245/ [spigot]: https://www.spigotmc.org/resources/6245/
[hangar]: https://hangar.papermc.io/HelpChat/PlaceholderAPI [hangar]: https://hangar.papermc.io/HelpChat/PlaceholderAPI
[bbb]: https://builtbybit.com/resources/placeholderapi.24306 [modrinth]: https://modrinth.com/plugin/placeholderapi
[Expansions cloud]: https://api.extendedclip.com/home [Expansions cloud]: https://ecloud.placeholderapi.com
[placeholder list]: https://helpch.at/placeholders [placeholder list]: https://helpch.at/placeholders
[statistics]: https://bstats.org/plugin/bukkit/PlaceholderAPI [statistics]: https://bstats.org/plugin/bukkit/PlaceholderAPI
@@ -50,5 +50,5 @@ If you would like to create your own Placeholder Expansion for PlaceholderAPI, t
- [Placeholder List] - [Placeholder List]
- [Spigot Page][spigot] - [Spigot Page][spigot]
- [Hangar Page][hangar] - [Hangar Page][hangar]
- [BuiltByBit Page][bbb] - [Modrinth Page][modrinth]
- [Plugin Statistics][statistics] - [Plugin Statistics][statistics]

View File

@@ -8,7 +8,7 @@ plugins {
} }
group = "me.clip" group = "me.clip"
version = "2.11.7-DEV-${System.getProperty("BUILD_NUMBER")}" version = "2.11.8-DEV-${System.getProperty("BUILD_NUMBER")}"
description = "An awesome placeholder provider!" description = "An awesome placeholder provider!"
@@ -25,9 +25,10 @@ repositories {
dependencies { dependencies {
implementation("org.bstats:bstats-bukkit:3.0.1") implementation("org.bstats:bstats-bukkit:3.0.1")
implementation("net.kyori:adventure-platform-bukkit:4.3.3") implementation("net.kyori:adventure-platform-bukkit:4.4.1")
//compileOnly("org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT") //compileOnly("org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT")
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT") compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
compileOnlyApi("org.jetbrains:annotations:23.0.0") compileOnlyApi("org.jetbrains:annotations:23.0.0")
@@ -45,6 +46,8 @@ java {
withJavadocJar() withJavadocJar()
withSourcesJar() withSourcesJar()
disableAutoTargetJvm()
} }
license { license {
@@ -90,7 +93,12 @@ tasks {
archiveClassifier.set("") archiveClassifier.set("")
relocate("org.bstats", "me.clip.placeholderapi.metrics") relocate("org.bstats", "me.clip.placeholderapi.metrics")
relocate("net.kyori", "me.clip.placeholderapi.libs.kyori") // relocate("net.kyori", "me.clip.placeholderapi.libs.kyori") {
// exclude("me/clip/placeholderapi/PAPIComponents.java")
// exclude("me/clip/placeholderapi/commands/TestCommand.java")
// }
destinationDirectory = file("server/1.21/plugins/")
exclude("META-INF/versions/**") exclude("META-INF/versions/**")
} }

View File

@@ -0,0 +1,121 @@
package me.clip.placeholderapi;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Relational;
import me.clip.placeholderapi.replacer.ExactReplacer;
import me.clip.placeholderapi.replacer.Replacer;
import net.kyori.adventure.text.Component;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.checkerframework.checker.units.qual.N;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class PAPIComponents {
private static final Replacer EXACT_REPLACER = new ExactReplacer();
@NotNull
public static Component setPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
// TODO: explore a custom TextReplacementRenderer which doesn't use regex for performance benefits i.e. merge CharsReplacer with kyori TextReplacementRenderer
return component.replaceText(config -> config.match(PlaceholderAPI.PLACEHOLDER_PATTERN).replacement((result, builder) ->
builder.content(EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
}
@NotNull
public static List<Component> setPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components) {
return components.stream().map(component -> setPlaceholders(player, component)).collect(Collectors.toList());
}
@NotNull
public static Component setPlaceholders(final Player player, @NotNull final Component component) {
return setPlaceholders((OfflinePlayer) player, component);
}
@NotNull
public static List<Component> setPlaceholders(final Player player, @NotNull final List<Component> components) {
return setPlaceholders((OfflinePlayer) player, components);
}
@NotNull
public static Component setBracketPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
return component.replaceText(config -> config.match(PlaceholderAPI.BRACKET_PLACEHOLDER_PATTERN).replacement((result, builder) ->
builder.content(EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
}
@NotNull
public static List<Component> setBracketPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components) {
return components.stream().map(component -> setBracketPlaceholders(player, component)).collect(Collectors.toList());
}
@NotNull
public static Component setBracketPlaceholders(final Player player, @NotNull final Component component) {
return setBracketPlaceholders((OfflinePlayer) player, component);
}
@NotNull
public static List<Component> setBracketPlaceholders(final Player player, @NotNull final List<Component> components) {
return setBracketPlaceholders((OfflinePlayer) player, components);
}
// public static Component setRelationalPlaceholders(Player one, Player two, Component component) {
// return component.replaceText(config -> config.match(PlaceholderAPI.RELATIONAL_PLACEHOLDER_PATTERN).replacement((result, builder) -> {
//
// final String format = result.group(2);
// final int index = format.indexOf("_");
//
// if (index <= 0 || index >= format.length()) {
// continue;
// }
//
// String identifier = format.substring(0, index).toLowerCase(Locale.ROOT);
// String params = format.substring(index + 1);
// final PlaceholderExpansion expansion = PlaceholderAPIPlugin.getInstance()
// .getLocalExpansionManager().getExpansion(identifier);
//
// if (!(expansion instanceof Relational)) {
// continue;
// }
//
// final String value = ((Relational) expansion).onPlaceholderRequest(one, two, params);
//
// if (value != null) {
// text = text.replaceAll(Pattern.quote(matcher.group()), Matcher.quoteReplacement(value));
// }
//
//
// }));
//
// final Matcher matcher = PlaceholderAPI.RELATIONAL_PLACEHOLDER_PATTERN.matcher(text);
//
// while (matcher.find()) {
// final String format = matcher.group(2);
// final int index = format.indexOf("_");
//
// if (index <= 0 || index >= format.length()) {
// continue;
// }
//
// String identifier = format.substring(0, index).toLowerCase(Locale.ROOT);
// String params = format.substring(index + 1);
// final PlaceholderExpansion expansion = PlaceholderAPIPlugin.getInstance()
// .getLocalExpansionManager().getExpansion(identifier);
//
// if (!(expansion instanceof Relational)) {
// continue;
// }
//
// final String value = ((Relational) expansion).onPlaceholderRequest(one, two, params);
//
// if (value != null) {
// text = text.replaceAll(Pattern.quote(matcher.group()), Matcher.quoteReplacement(value));
// }
// }
//
// return text;
// }
}

View File

@@ -34,6 +34,7 @@ import me.clip.placeholderapi.expansion.manager.LocalExpansionManager;
import me.clip.placeholderapi.replacer.CharsReplacer; import me.clip.placeholderapi.replacer.CharsReplacer;
import me.clip.placeholderapi.replacer.Replacer; import me.clip.placeholderapi.replacer.Replacer;
import me.clip.placeholderapi.replacer.Replacer.Closure; import me.clip.placeholderapi.replacer.Replacer.Closure;
import me.clip.placeholderapi.replacer.ExactReplacer;
import me.clip.placeholderapi.util.Msg; import me.clip.placeholderapi.util.Msg;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -46,11 +47,12 @@ public final class PlaceholderAPI {
private static final Replacer REPLACER_PERCENT = new CharsReplacer(Closure.PERCENT); private static final Replacer REPLACER_PERCENT = new CharsReplacer(Closure.PERCENT);
private static final Replacer REPLACER_BRACKET = new CharsReplacer(Closure.BRACKET); private static final Replacer REPLACER_BRACKET = new CharsReplacer(Closure.BRACKET);
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]"); static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]");
private static final Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]"); static final Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]");
private static final Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern static final Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern
.compile("[%](rel_)([^%]+)[%]"); .compile("[%](rel_)([^%]+)[%]");
private static final Replacer TEST = new ExactReplacer();
private PlaceholderAPI() { private PlaceholderAPI() {
} }

View File

@@ -21,10 +21,10 @@
package me.clip.placeholderapi; package me.clip.placeholderapi;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import me.clip.placeholderapi.commands.PlaceholderCommandRouter; import me.clip.placeholderapi.commands.PlaceholderCommandRouter;
import me.clip.placeholderapi.commands.TestCommand;
import me.clip.placeholderapi.configuration.PlaceholderAPIConfig; import me.clip.placeholderapi.configuration.PlaceholderAPIConfig;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Version; import me.clip.placeholderapi.expansion.Version;
@@ -34,6 +34,7 @@ import me.clip.placeholderapi.listeners.ServerLoadEventListener;
import me.clip.placeholderapi.scheduler.UniversalScheduler; import me.clip.placeholderapi.scheduler.UniversalScheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler; import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.updatechecker.UpdateChecker; import me.clip.placeholderapi.updatechecker.UpdateChecker;
import me.clip.placeholderapi.util.ExpansionSafetyCheck;
import me.clip.placeholderapi.util.Msg; import me.clip.placeholderapi.util.Msg;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
@@ -93,6 +94,8 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
private BukkitAudiences adventure; private BukkitAudiences adventure;
private BukkitAudiences adventure;
private boolean safetyCheck = false;
/** /**
* Gets the static instance of the main class for PlaceholderAPI. This class is not the actual API * Gets the static instance of the main class for PlaceholderAPI. This class is not the actual API
@@ -152,17 +155,29 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
public void onLoad() { public void onLoad() {
instance = this; instance = this;
@Override
public void onLoad() {
saveDefaultConfig(); saveDefaultConfig();
safetyCheck = new ExpansionSafetyCheck(this).runChecks();
if (safetyCheck) {
return;
}
instance = this;
} }
@Override @Override
public void onEnable() { public void onEnable() {
if (safetyCheck) {
return;
}
setupCommand(); setupCommand();
setupMetrics(); setupMetrics();
setupExpansions(); setupExpansions();
adventure = BukkitAudiences.create(this);
if (config.isCloudEnabled()) { if (config.isCloudEnabled()) {
getCloudExpansionManager().load(); getCloudExpansionManager().load();
} }
@@ -177,7 +192,14 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
getCloudExpansionManager().kill(); getCloudExpansionManager().kill();
getLocalExpansionManager().kill(); getLocalExpansionManager().kill();
HandlerList.unregisterAll(this); @Override
public void onDisable() {
if (safetyCheck) {
return;
}
getCloudExpansionManager().kill();
getLocalExpansionManager().kill();
scheduler.cancelTasks(this); scheduler.cancelTasks(this);

View File

@@ -20,11 +20,14 @@
package me.clip.placeholderapi; package me.clip.placeholderapi;
import net.kyori.adventure.text.Component;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public abstract class PlaceholderHook { public abstract class PlaceholderHook {
@Nullable @Nullable
public String onRequest(final OfflinePlayer player, @NotNull final String params) { public String onRequest(final OfflinePlayer player, @NotNull final String params) {

View File

@@ -22,10 +22,12 @@ package me.clip.placeholderapi.commands;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View File

@@ -22,6 +22,7 @@ package me.clip.placeholderapi.commands;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -30,6 +31,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.impl.cloud.CommandECloud; import me.clip.placeholderapi.commands.impl.cloud.CommandECloud;
import me.clip.placeholderapi.commands.impl.local.CommandDump; import me.clip.placeholderapi.commands.impl.local.CommandDump;

View File

@@ -0,0 +1,57 @@
package me.clip.placeholderapi.commands;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import me.clip.placeholderapi.PAPIComponents;
import me.clip.placeholderapi.PlaceholderAPI;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.OfflinePlayer;
public class TestCommand implements BasicCommand {
private static final MiniMessage MINI = MiniMessage.miniMessage();
@Override
public void execute(final CommandSourceStack commandSourceStack, final String[] strings) {
// final Component component = Component.text("Woo! Test: %player_name%").color(TextColor.color(50, 168, 82)).hoverEvent(HoverEvent.showText(Component.text("OMG %player_gamemode%")));
final Component component = Component.text("Woo! Test: %player_name%");
String ser = MINI.serialize(component);
System.out.println(ser);
commandSourceStack.getSender().sendMessage(
PAPIComponents.setPlaceholders((OfflinePlayer) commandSourceStack.getSender(), component)
);
long tmp = System.currentTimeMillis();
for (int i = 0; i < 100000; ++i) {
PAPIComponents.setPlaceholders((OfflinePlayer) commandSourceStack.getSender(), component);
}
commandSourceStack.getSender().sendMessage(String.valueOf(System.currentTimeMillis() - tmp));
tmp = System.currentTimeMillis();
for (int i = 0; i < 100000; ++i) {
PlaceholderAPI.setPlaceholders((OfflinePlayer) commandSourceStack.getSender(), "Woo! Test: %player_name%");
}
commandSourceStack.getSender().sendMessage(String.valueOf(System.currentTimeMillis() - tmp));
tmp = System.currentTimeMillis();
for (int i = 0; i < 100000; ++i) {
final String serr = MINI.serialize(component);
final String repl = PlaceholderAPI.setPlaceholders((OfflinePlayer) commandSourceStack.getSender(), serr);
MINI.deserialize(repl);
}
commandSourceStack.getSender().sendMessage(String.valueOf(System.currentTimeMillis() - tmp));
Component.text()
.append(Component.text().content("yes ").color(TextColor.color(50,50,50)))
.append(Component.text("%player_name%"))
.append(Component.text(" omg").color(TextColor.color(200,200,200)));
}
}

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud; package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg; import me.clip.placeholderapi.util.Msg;

View File

@@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion; import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
@@ -64,9 +65,32 @@ public final class CommandECloudDownload extends PlaceholderCommand {
return; return;
} }
final CloudExpansion expansion = plugin.getCloudExpansionManager() final CloudExpansion.Version version;
.findCloudExpansionByName(params.get(0)).orElse(null); if (params.size() < 2) {
if (expansion == null) { version = expansion.getVersion(expansion.getLatestVersion());
if (version == null) {
Msg.msg(sender,
"&cCould not find latest version for expansion.");
return;
}
} else {
version = expansion.getVersion(params.get(1));
if (version == null) {
Msg.msg(sender,
"&cCould not find specified version: &f" + params.get(1),
"&7Available versions: &f" + expansion.getAvailableVersions());
return;
}
}
if (!version.isVerified()) {
Msg.msg(sender, "&cThe expansion '&f" + params.get(0) + "&c' is not verified and can only be downloaded manually from &fhttps://ecloud.placeholderapi.com");
return;
}
plugin.getCloudExpansionManager().downloadExpansion(expansion, version)
.whenComplete((file, exception) -> {
if (exception != null) {
Msg.msg(sender, Msg.msg(sender,
"&cFailed to find an expansion named: &f" + params.get(0)); "&cFailed to find an expansion named: &f" + params.get(0));
return; return;

View File

@@ -23,6 +23,7 @@ package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion; import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
@@ -55,17 +56,12 @@ public final class CommandECloudExpansionInfo extends PlaceholderCommand {
return; return;
} }
final StringBuilder builder = new StringBuilder();
builder.append("&bExpansion: &f") builder.append("&bExpansion: &f")
.append(expansion.shouldUpdate() ? "&e" : "&a") .append(expansion.shouldUpdate() ? "&e" : "&a")
.append(expansion.getName()) .append(expansion.getName())
.append('\n') .append('\n')
.append("&bAuthor: &f") .append("&bAuthor: &f")
.append(expansion.getAuthor()) .append(expansion.getAuthor())
.append('\n')
.append("&bVerified: ")
.append(expansion.isVerified() ? "&a&l✔" : "&c&l❌")
.append('\n'); .append('\n');
if (params.size() < 2) { if (params.size() < 2) {
@@ -76,6 +72,9 @@ public final class CommandECloudExpansionInfo extends PlaceholderCommand {
.append(expansion.getTimeSinceLastUpdate()) .append(expansion.getTimeSinceLastUpdate())
.append(" ago") .append(" ago")
.append('\n') .append('\n')
.append("&bVerified: ")
.append(expansion.getVersion().isVerified() ? "&a&l✔" : "&c&l❌")
.append('\n')
.append("&bRelease Notes: &f") .append("&bRelease Notes: &f")
.append(expansion.getVersion().getReleaseNotes()) .append(expansion.getVersion().getReleaseNotes())
.append('\n'); .append('\n');
@@ -88,6 +87,20 @@ public final class CommandECloudExpansionInfo extends PlaceholderCommand {
return; return;
} }
builder.append("&bVersion: &f")
.append(version.getVersion())
.append('\n')
.append("&bVerified: ")
.append(version.isVerified() ? "&a&l✔" : "&c&l❌")
.append('\n')
.append("&bRelease Notes: &f")
.append(version.getReleaseNotes())
.append('\n')
.append("&bDownload URL: &f")
.append(version.getUrl())
.append('\n');
}
builder.append("&bVersion: &f") builder.append("&bVersion: &f")
.append(version.getVersion()) .append(version.getVersion())
.append('\n') .append('\n')

View File

@@ -25,12 +25,14 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.configuration.ExpansionSort; import me.clip.placeholderapi.configuration.ExpansionSort;
@@ -65,7 +67,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
expansion -> "&f" + expansion.getAuthor(); expansion -> "&f" + expansion.getAuthor();
@NotNull @NotNull
private static final Function<CloudExpansion, Object> EXPANSION_VERIFIED = private static final Function<CloudExpansion, Object> EXPANSION_VERIFIED =
expansion -> expansion.isVerified() ? "&aY" : "&cN"; expansion -> expansion.getVersion().isVerified() ? "&aY" : "&cN";
@NotNull @NotNull
private static final Function<CloudExpansion, Object> EXPANSION_LATEST_VERSION = private static final Function<CloudExpansion, Object> EXPANSION_LATEST_VERSION =
expansion -> "&f" + expansion.getLatestVersion(); expansion -> "&f" + expansion.getLatestVersion();
@@ -173,11 +175,14 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
.append(text("Released: ", AQUA)).append(text(format.format(expansion.getLastUpdate()), WHITE)) .append(text("Released: ", AQUA)).append(text(format.format(expansion.getLastUpdate()), WHITE))
.toBuilder(); .toBuilder();
Optional.ofNullable(expansion.getDescription()) final TextComponent.Builder hoverText = text("Click to download this expansion!", AQUA)
.filter(description -> !description.isEmpty()) .append(newline()).append(newline())
.ifPresent(description -> hoverText.append(newline()).append(newline()) .append(text("Author: ", AQUA)).append(text(expansion.getAuthor(), WHITE))
.append(text(description.replace("\r", "").trim(), WHITE)) .append(newline())
); .append(text("Verified: ", AQUA)).append(text(expansion.getVersion().isVerified() ? "" : "", expansion.getVersion().isVerified() ? GREEN : RED, TextDecoration.BOLD))
.append(newline())
.append(text("Released: ", AQUA)).append(text(format.format(expansion.getLastUpdate()), WHITE))
.toBuilder();
line.hoverEvent(HoverEvent.showText(hoverText.build())); line.hoverEvent(HoverEvent.showText(hoverText.build()));

View File

@@ -21,9 +21,11 @@
package me.clip.placeholderapi.commands.impl.cloud; package me.clip.placeholderapi.commands.impl.cloud;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion; import me.clip.placeholderapi.expansion.cloud.CloudExpansion;

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud; package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg; import me.clip.placeholderapi.util.Msg;

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud; package me.clip.placeholderapi.commands.impl.cloud;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.manager.CloudExpansionManager; import me.clip.placeholderapi.expansion.manager.CloudExpansionManager;

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.cloud; package me.clip.placeholderapi.commands.impl.cloud;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -28,6 +29,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;

View File

@@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;

View File

@@ -22,6 +22,7 @@ package me.clip.placeholderapi.commands.impl.local;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.local; package me.clip.placeholderapi.commands.impl.local;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg; import me.clip.placeholderapi.util.Msg;

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.local; package me.clip.placeholderapi.commands.impl.local;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -21,9 +21,11 @@
package me.clip.placeholderapi.commands.impl.local; package me.clip.placeholderapi.commands.impl.local;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;

View File

@@ -21,8 +21,10 @@
package me.clip.placeholderapi.commands.impl.local; package me.clip.placeholderapi.commands.impl.local;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.ExpansionSafetyCheck;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.Unmodifiable;
@@ -37,7 +39,9 @@ public final class CommandReload extends PlaceholderCommand {
public void evaluate(@NotNull final PlaceholderAPIPlugin plugin, public void evaluate(@NotNull final PlaceholderAPIPlugin plugin,
@NotNull final CommandSender sender, @NotNull final String alias, @NotNull final CommandSender sender, @NotNull final String alias,
@NotNull @Unmodifiable final List<String> params) { @NotNull @Unmodifiable final List<String> params) {
if (!new ExpansionSafetyCheck(plugin).runChecks()) {
plugin.reloadConf(sender); plugin.reloadConf(sender);
} }
}
} }

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.commands.impl.local; package me.clip.placeholderapi.commands.impl.local;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.util.Msg; import me.clip.placeholderapi.util.Msg;

View File

@@ -86,4 +86,8 @@ public final class PlaceholderAPIConfig {
return plugin.getConfig().getString("boolean.false", "false"); return plugin.getConfig().getString("boolean.false", "false");
} }
public boolean detectMaliciousExpansions() {
return plugin.getConfig().getBoolean("detect_malicious_expansions", true);
}
} }

View File

@@ -23,6 +23,7 @@ package me.clip.placeholderapi.events;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
@@ -53,7 +54,7 @@ public class ExpansionsLoadedEvent extends Event {
* @return List of {@link PlaceholderExpansion registered PlaceholderExpansions}. * @return List of {@link PlaceholderExpansion registered PlaceholderExpansions}.
*/ */
@NotNull @NotNull
public final List<PlaceholderExpansion> getExpansions(){ public final List<PlaceholderExpansion> getExpansions() {
return expansions; return expansions;
} }

View File

@@ -51,7 +51,12 @@ public enum NMSVersion {
SPIGOT_1_20_R2("v1_20_R2"), SPIGOT_1_20_R2("v1_20_R2"),
SPIGOT_1_20_R3("v1_20_R3"), SPIGOT_1_20_R3("v1_20_R3"),
SPIGOT_1_20_R4("v1_20_R4"), SPIGOT_1_20_R4("v1_20_R4"),
SPIGOT_1_21_R1("v1_21_R1"); SPIGOT_1_21_R1("v1_21_R1"),
SPIGOT_1_21_R2("V1_21_R2"),
SPIGOT_1_21_R3("V1_21_R3"),
SPIGOT_1_21_R4("V1_21_R4"),
SPIGOT_1_21_R5("V1_21_R5"),
SPIGOT_1_21_R6("V1_21_R6");
private final String version; private final String version;

View File

@@ -23,6 +23,7 @@ package me.clip.placeholderapi.expansion.cloud;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.clip.placeholderapi.util.TimeUtil; import me.clip.placeholderapi.util.TimeUtil;
@@ -36,8 +37,7 @@ public class CloudExpansion {
dependency_url; dependency_url;
private boolean hasExpansion, private boolean hasExpansion,
shouldUpdate, shouldUpdate;
verified;
private long last_update, private long last_update,
ratings_count; ratings_count;
@@ -135,10 +135,6 @@ public class CloudExpansion {
this.shouldUpdate = shouldUpdate; this.shouldUpdate = shouldUpdate;
} }
public boolean isVerified() {
return verified;
}
public long getLastUpdate() { public long getLastUpdate() {
return last_update; return last_update;
} }
@@ -174,6 +170,7 @@ public class CloudExpansion {
public static class Version { public static class Version {
private String url, version, release_notes; private String url, version, release_notes;
private boolean verified;
public String getUrl() { public String getUrl() {
return url; return url;
@@ -199,4 +196,13 @@ public class CloudExpansion {
this.release_notes = release_notes; this.release_notes = release_notes;
} }
} }
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
} }

View File

@@ -25,6 +25,7 @@ import com.google.common.io.Resources;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -50,6 +51,7 @@ import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collector; import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion; import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
@@ -60,7 +62,7 @@ import org.jetbrains.annotations.Unmodifiable;
public final class CloudExpansionManager { public final class CloudExpansionManager {
@NotNull @NotNull
private static final String API_URL = "http://api.extendedclip.com/v2/"; private static final String API_URL = "https://ecloud.placeholderapi.com/api/v3/";
@NotNull @NotNull
private static final Gson GSON = new Gson(); private static final Gson GSON = new Gson();

View File

@@ -22,6 +22,7 @@ package me.clip.placeholderapi.expansion.manager;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.io.File; import java.io.File;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Arrays; import java.util.Arrays;
@@ -38,6 +39,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.events.ExpansionRegisterEvent; import me.clip.placeholderapi.events.ExpansionRegisterEvent;
import me.clip.placeholderapi.events.ExpansionUnregisterEvent; import me.clip.placeholderapi.events.ExpansionUnregisterEvent;
@@ -172,7 +174,7 @@ public final class LocalExpansionManager implements Listener {
try { try {
final PlaceholderExpansion expansion = createExpansionInstance(clazz); final PlaceholderExpansion expansion = createExpansionInstance(clazz);
if(expansion == null){ if (expansion == null) {
return Optional.empty(); return Optional.empty();
} }
@@ -213,6 +215,7 @@ public final class LocalExpansionManager implements Listener {
/** /**
* Attempt to register a {@link PlaceholderExpansion} * Attempt to register a {@link PlaceholderExpansion}
*
* @param expansion the expansion to register * @param expansion the expansion to register
* @return if the expansion was registered * @return if the expansion was registered
*/ */

View File

@@ -22,6 +22,7 @@ package me.clip.placeholderapi.replacer;
import java.util.Locale; import java.util.Locale;
import java.util.function.Function; import java.util.function.Function;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
@@ -43,6 +44,8 @@ public final class CharsReplacer implements Replacer {
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, public String apply(@NotNull final String text, @Nullable final OfflinePlayer player,
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) { @NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
final char[] chars = text.toCharArray(); final char[] chars = text.toCharArray();
// Woo! Hlello %player_name%
// Woo! GHsda PiggyPiglet
final StringBuilder builder = new StringBuilder(text.length()); final StringBuilder builder = new StringBuilder(text.length());
final StringBuilder identifier = new StringBuilder(); final StringBuilder identifier = new StringBuilder();

View File

@@ -0,0 +1,171 @@
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.kyori.adventure.text.*;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.Style;
import org.bukkit.OfflinePlayer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
public final class ComponentReplacer {
public Component replace(Component component, OfflinePlayer player, Function<String, PlaceholderExpansion> function) {
Component modified = component;
final List<Component> oldChildren = component.children();
final int oldChildrenSize = oldChildren.size();
List<Component> children = null;
if (component instanceof TextComponent) {
TextComponent tc = (TextComponent) component;
final String content = tc.content();
final char[] chars = content.toCharArray();
final StringBuilder identifier = new StringBuilder();
final StringBuilder parameters = new StringBuilder();
for (int i = 0; i < chars.length; i++) {
final char l = chars[i];
if (l != '%' || i + 1 >= chars.length) {
continue;
}
final int start = i;
boolean identified = false;
boolean invalid = true;
while (++i < chars.length) {
final char p = chars[i];
if (p == ' ' && !identified) {
break;
}
if (p == '%') {
invalid = false;
break;
}
if (p == '_' && !identified) {
identified = true;
continue;
}
if (identified) {
parameters.append(p);
} else {
identifier.append(p);
}
}
final String identifierString = identifier.toString();
final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT);
final String parametersString = parameters.toString();
identifier.setLength(0);
parameters.setLength(0);
if (invalid) {
continue;
}
final PlaceholderExpansion expansion = function.apply(lowercaseIdentifierString);
if (expansion == null) {
continue;
}
final String placeholderValue = expansion.onRequest(player, parametersString);
if (placeholderValue == null) {
continue;
}
if (start == 0) {
// if we're a full match, modify the component directly
if (i == content.length() - 1) {
final ComponentLike replacement = Component.text(placeholderValue).style(component.style());
modified = replacement.asComponent();
Style modStyle = modified.style();
if (modStyle.hoverEvent() != null) {
Object hoverValue = modStyle.hoverEvent().value();
if (hoverValue instanceof Component) {
final Object replacedValue = replace((Component) hoverValue, player, function);
if (replacedValue != hoverValue) {
((HoverEvent<Object>) modified.style().hoverEvent()).value(replacedValue);
}
}
}
if (modStyle.clickEvent() != null) {
final ClickEvent.Payload payload = modStyle.clickEvent().payload();
if (payload instanceof ClickEvent.Payload.Text) {
final ClickEvent.Payload.Text replacedPayload = ClickEvent.Payload.string(PlaceholderAPI.setPlaceholders(player, ((ClickEvent.Payload.Text) payload).value()));
modStyle.clickEvent(ClickEvent.clickEvent(modStyle.clickEvent().action(), replacedPayload));
} else if (payload instanceof ClickEvent.Payload.Dialog) {
final ClickEvent.Payload.Dialog replacedPayload;
// ((ClickEvent.Payload.Dialog) payload).dialog()
// apparently adventure doesn't have dialog support yet
}
}
if (children == null) {
children = new ArrayList<>(oldChildrenSize + modified.children().size());
children.addAll(modified.children());
}
} else {
modified = Component.text("", component.style());
// final ComponentLike child =
}
}
}
} else if (component instanceof TranslatableComponent) {
TranslatableComponent tc = (TranslatableComponent) component;
final List<TranslationArgument> args = tc.arguments();
List<TranslationArgument> newArgs = null;
for (int i = 0, size = args.size(); i < size; i++) {
final TranslationArgument original = args.get(i);
TranslationArgument replacement = original instanceof Component ? TranslationArgument.component(replace((Component) original, player, function)) : original;
if (original != replacement) {
if (newArgs == null) {
newArgs = new ArrayList<>(size);
if (i > 0) {
newArgs.addAll(args.subList(0, i));
}
}
}
if (newArgs != null) {
newArgs.add(replacement);
}
}
if (newArgs != null) {
modified = ((TranslatableComponent) modified).arguments(newArgs);
}
}
return modified;
}
private static <V> void test(HoverEvent<V> event, V value) {
event.value(value);
}
}

View File

@@ -0,0 +1,48 @@
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
import java.util.regex.Pattern;
public class ExactReplacer implements Replacer {
private static final Pattern DELIMITER = Pattern.compile("_");
@NotNull
@Override
public String apply(@NotNull String text, @Nullable final OfflinePlayer player,
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
text = text.substring(1, text.length() - 1);
final String[] parts = DELIMITER.split(text);
final PlaceholderExpansion expansion;
if (parts.length == 0) {
expansion = lookup.apply(text);
} else {
expansion = lookup.apply(parts[0]);
}
if (expansion == null) {
return text;
}
final String params;
if (text.endsWith("_")) {
params = "";
} else {
params = text.substring(text.indexOf('_') + 1);
}
final String result = expansion.onRequest(player, params);
if (result == null) {
return text;
}
return result;
}
}

View File

@@ -21,6 +21,7 @@
package me.clip.placeholderapi.replacer; package me.clip.placeholderapi.replacer;
import java.util.function.Function; import java.util.function.Function;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View File

@@ -37,7 +37,7 @@ public interface TaskScheduler {
/** /**
* <b>Folia</b>: Returns whether the current thread is ticking the global region <br> * <b>Folia</b>: Returns whether the current thread is ticking the global region <br>
* <b>Paper & Bukkit</b>: Returns {@link org.bukkit.Server#isPrimaryThread} * <b>Paper and Bukkit</b>: Returns {@link org.bukkit.Server#isPrimaryThread}
*/ */
boolean isGlobalThread(); boolean isGlobalThread();
@@ -49,7 +49,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Returns whether the current thread is ticking a region and that the region * <b>Folia and Paper</b>: Returns whether the current thread is ticking a region and that the region
* being ticked owns the specified entity. Note that this function is the only appropriate method of * being ticked owns the specified entity. Note that this function is the only appropriate method of
* checking for ownership of an entity, as retrieving the entity's location is undefined unless the * checking for ownership of an entity, as retrieving the entity's location is undefined unless the
* entity is owned by the current region * entity is owned by the current region
@@ -61,7 +61,7 @@ public interface TaskScheduler {
boolean isEntityThread(Entity entity); boolean isEntityThread(Entity entity);
/** /**
* <b>Folia & Paper</b>: Returns whether the current thread is ticking a region and that the region * <b>Folia and Paper</b>: Returns whether the current thread is ticking a region and that the region
* being ticked owns the chunk at the specified world and block position as included in the specified location * being ticked owns the chunk at the specified world and block position as included in the specified location
* <p> * <p>
* <b>Bukkit</b>: returns {@link org.bukkit.Server#isPrimaryThread} * <b>Bukkit</b>: returns {@link org.bukkit.Server#isPrimaryThread}
@@ -72,7 +72,7 @@ public interface TaskScheduler {
/** /**
* Schedules a task to be executed on the next tick <br> * Schedules a task to be executed on the next tick <br>
* <b>Folia & Paper</b>: ...on the global region <br> * <b>Folia and Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread * <b>Bukkit</b>: ...on the main thread
* *
* @param runnable The task to execute * @param runnable The task to execute
@@ -81,7 +81,7 @@ public interface TaskScheduler {
/** /**
* Schedules a task to be executed after the specified delay in ticks <br> * Schedules a task to be executed after the specified delay in ticks <br>
* <b>Folia & Paper</b>: ...on the global region <br> * <b>Folia and Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread * <b>Bukkit</b>: ...on the main thread
* *
* @param runnable The task to execute * @param runnable The task to execute
@@ -91,7 +91,7 @@ public interface TaskScheduler {
/** /**
* Schedules a repeating task to be executed after the initial delay with the specified period <br> * Schedules a repeating task to be executed after the initial delay with the specified period <br>
* <b>Folia & Paper</b>: ...on the global region <br> * <b>Folia and Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread * <b>Bukkit</b>: ...on the main thread
* *
* @param runnable The task to execute * @param runnable The task to execute
@@ -125,7 +125,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location on the next tick * <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location on the next tick
* <p> * <p>
* <b>Bukkit</b>: same as {@link #runTask(Runnable)} * <b>Bukkit</b>: same as {@link #runTask(Runnable)}
* *
@@ -137,7 +137,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location after the * <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location after the
* specified delay in ticks * specified delay in ticks
* <p> * <p>
* <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)} * <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)}
@@ -151,7 +151,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Schedules a repeating task to be executed on the region which owns the location * <b>Folia and Paper</b>: Schedules a repeating task to be executed on the region which owns the location
* after the initial delay with the specified period * after the initial delay with the specified period
* <p> * <p>
* <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)} * <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)}
@@ -190,7 +190,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location * <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location
* of given entity on the next tick * of given entity on the next tick
* <p> * <p>
* <b>Bukkit</b>: same as {@link #runTask(Runnable)} * <b>Bukkit</b>: same as {@link #runTask(Runnable)}
@@ -203,7 +203,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location * <b>Folia and Paper</b>: Schedules a task to be executed on the region which owns the location
* of given entity after the specified delay in ticks * of given entity after the specified delay in ticks
* <p> * <p>
* <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)} * <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)}
@@ -217,7 +217,7 @@ public interface TaskScheduler {
} }
/** /**
* <b>Folia & Paper</b>: Schedules a repeating task to be executed on the region which owns the * <b>Folia and Paper</b>: Schedules a repeating task to be executed on the region which owns the
* location of given entity after the initial delay with the specified period * location of given entity after the initial delay with the specified period
* <p> * <p>
* <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)} * <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)}
@@ -285,7 +285,7 @@ public interface TaskScheduler {
/** /**
* Calls a method on the main thread and returns a Future object. This task will be executed * Calls a method on the main thread and returns a Future object. This task will be executed
* by the main(Bukkit)/global(Folia&Paper) server thread. * by the main(Bukkit)/global(FoliaandPaper) server thread.
* <p> * <p>
* Note: The Future.get() methods must NOT be called from the main thread. * Note: The Future.get() methods must NOT be called from the main thread.
* <p> * <p>

View File

@@ -0,0 +1,80 @@
package me.clip.placeholderapi.util;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
public final class ExpansionSafetyCheck {
private static final String MESSAGE =
"\n###############################################\n" +
"###############################################\n" +
"PlaceholderAPI performs checks at startup and /papi reload for known malicious expansions. If you're seeing this message, there are the following malicious expansions in plugins/PlaceholderAPI/expansions.\n" +
"%s" +
"To prevent further infection PlaceholderAPI has stopped the server.\n" +
"If you're seeing this message after updating PAPI, your server may have been infected for some time, so best practice is a complete system wipe and reinstall of your server software and plugins to be safe.\n" +
"If you're seeing this after downloading an expansion however, PAPI hasn't loaded any of the malicious expansions above so you should be safe to simply delete the expansion in question.\n" +
"###############################################\n" +
"###############################################";
private final PlaceholderAPIPlugin main;
public ExpansionSafetyCheck(@NotNull final PlaceholderAPIPlugin main) {
this.main = main;
}
public boolean runChecks() {
if (!main.getPlaceholderAPIConfig().detectMaliciousExpansions()) {
return false;
}
final File expansionsFolder = new File(main.getDataFolder(), "expansions");
if (!expansionsFolder.exists()) {
return false;
}
final Set<String> knownMaliciousExpansions;
try {
final String hashes = Resources.toString(new URL("https://check.placeholderapi.com"), StandardCharsets.UTF_8);
knownMaliciousExpansions = Arrays.stream(hashes.split("\n")).collect(Collectors.toSet());
} catch (Exception e) {
main.getLogger().log(Level.SEVERE, "Failed to download anti malware hash check list from https://check.placeholderapi.com", e);
return false;
}
final Set<String> maliciousPaths = new HashSet<>();
for (File file : expansionsFolder.listFiles()) {
try {
final String hash = Hashing.sha256().hashBytes(Files.asByteSource(file).read()).toString();
if (knownMaliciousExpansions.contains(hash)) {
maliciousPaths.add(file.getAbsolutePath());
}
} catch (Exception e) {
main.getLogger().log(Level.SEVERE, "Error occurred while trying to read " + file.getAbsolutePath(), e);
}
}
if (maliciousPaths.isEmpty()) {
return false;
}
main.getLogger().severe(String.format(MESSAGE, maliciousPaths.stream().map(p -> "HASH OF " + p + " MATCHES KNOWN MALICIOUS EXPANSION DELETE IMMEDIATELY\n").collect(Collectors.joining())));
main.getServer().shutdown();
return true;
}
}

View File

@@ -29,6 +29,7 @@ import static java.util.stream.IntStream.range;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**

View File

@@ -23,6 +23,7 @@ package me.clip.placeholderapi.util;
import java.util.Arrays; import java.util.Arrays;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@@ -43,7 +44,7 @@ public final class Msg {
log(Level.WARNING, msg, args); log(Level.WARNING, msg, args);
} }
public static void warn(String msg, Throwable throwable, Object... args){ public static void warn(String msg, Throwable throwable, Object... args) {
PlaceholderAPIPlugin.getInstance().getLogger().log(Level.WARNING, String.format(msg, args), throwable); PlaceholderAPIPlugin.getInstance().getLogger().log(Level.WARNING, String.format(msg, args), throwable);
} }

View File

@@ -8,6 +8,8 @@
# Discord: https://helpch.at/discord # Discord: https://helpch.at/discord
# No placeholders are provided with this plugin by default. # No placeholders are provided with this plugin by default.
# Download placeholders: /papi ecloud # Download placeholders: /papi ecloud
#
# CHANGE use_regex_component_replacer to true if you notice new minecraft text features not getting their placeholders replaced properly -- may cause performance issues
check_updates: true check_updates: true
cloud_enabled: true cloud_enabled: true
cloud_sorting: "name" cloud_sorting: "name"
@@ -15,4 +17,6 @@ boolean:
'true': 'yes' 'true': 'yes'
'false': 'no' 'false': 'no'
date_format: MM/dd/yy HH:mm:ss date_format: MM/dd/yy HH:mm:ss
use_regex_component_replacer: false
detect_malicious_expansions: true
debug: false debug: false

View File

@@ -12,6 +12,8 @@ commands:
placeholderapi: placeholderapi:
description: "PlaceholderAPI Command" description: "PlaceholderAPI Command"
aliases: ["papi"] aliases: ["papi"]
test:
description: "yes"
permissions: permissions:
placeholderapi.*: placeholderapi.*: