Compare commits

..

15 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
51 changed files with 3754 additions and 3151 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

@@ -9,7 +9,7 @@
[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
[modrinth]: https://modrinth.com/plugin/placeholderapi [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

View File

@@ -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")
@@ -92,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;

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;
@@ -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

@@ -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;

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.*: