Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ repositories {
maven("https://repo.bluecolored.de/releases") {
name = "bluemap"
}
maven("https://repo.extendedclip.com/releases/") {
name = "clip"
}
}

dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
compileOnly("net.luckperms:api:5.4")
compileOnly("de.bluecolored:bluemap-api:2.7.4")
compileOnly("me.clip:placeholderapi:2.11.7")
implementation("org.bstats:bstats-bukkit:3.1.0")
}

Expand Down Expand Up @@ -101,6 +105,7 @@ tasks.shadowJar {
val plugins = runPaper.downloadPluginsSpec {
modrinth("viaversion", "5.6.0") // makes testing much easier
modrinth("bluemap", "5.5-paper")
modrinth("placeholderapi", "2.11.7")
}

// Paper (non-Folia!) server
Expand Down
23 changes: 19 additions & 4 deletions src/main/java/org/modernbeta/admintoolbox/AdminToolboxPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import org.modernbeta.admintoolbox.commands.*;
import org.modernbeta.admintoolbox.integration.BlueMapIntegration;
import org.modernbeta.admintoolbox.integration.luckperms.LuckPermsIntegration;
import org.modernbeta.admintoolbox.integration.placeholderapi.PlaceholderAPIIntegration;
import org.modernbeta.admintoolbox.managers.FreezeManager;
import org.modernbeta.admintoolbox.managers.StreamerModeManager;
import org.modernbeta.admintoolbox.managers.admin.AdminManager;

import javax.annotation.Nullable;
Expand All @@ -26,8 +28,9 @@
public class AdminToolboxPlugin extends JavaPlugin {
static AdminToolboxPlugin instance;

AdminManager adminManager;
FreezeManager freezeManager;
private AdminManager adminManager;
private FreezeManager freezeManager;
private @Nullable StreamerModeManager streamerModeManager;

PermissionAudience broadcastAudience;

Expand All @@ -36,6 +39,7 @@ public class AdminToolboxPlugin extends JavaPlugin {

private @Nullable BlueMapIntegration blueMapIntegration = null;
private @Nullable LuckPermsIntegration luckPermsIntegration = null;
private @Nullable PlaceholderAPIIntegration placeholderAPIIntegration = null;

private static final String ADMIN_STATE_CONFIG_FILENAME = "admin-state.yml";

Expand All @@ -50,7 +54,6 @@ public void onEnable() {

this.adminManager = new AdminManager();
this.freezeManager = new FreezeManager();

this.broadcastAudience = new PermissionAudience(BROADCAST_AUDIENCE_PERMISSION);

createAdminStateConfig();
Expand Down Expand Up @@ -78,7 +81,8 @@ public void onEnable() {
this.luckPermsIntegration = new LuckPermsIntegration(provider.getProvider());
this.luckPermsIntegration.registerCalculator();

getCommand("streamermode").setExecutor(new StreamerModeCommand());
this.streamerModeManager = new StreamerModeManager(this, luckPermsIntegration);
getCommand("streamermode").setExecutor(new StreamerModeCommand(streamerModeManager));
}
} catch (NoClassDefFoundError e) {
getLogger().warning("LuckPerms not found! Some features will be unavailable.");
Expand All @@ -94,6 +98,13 @@ public void onEnable() {
getLogger().warning("BlueMap API not found! Some features will be unavailable.");
}

try {
this.placeholderAPIIntegration = new PlaceholderAPIIntegration(this);
this.placeholderAPIIntegration.registerPlaceholders();
} catch (NoClassDefFoundError e) {
getLogger().warning("PlaceholderAPI is not available! Some features will be unavailable.");
}

// bStats - plugin analytics. Toggleable in server-level bStats config.
new Metrics(this, BSTATS_PLUGIN_ID);

Expand Down Expand Up @@ -149,6 +160,10 @@ public FreezeManager getFreezeManager() {
return freezeManager;
}

public Optional<StreamerModeManager> getStreamerModeManager() {
return Optional.ofNullable(streamerModeManager);
}

public PermissionAudience getAdminAudience() {
return broadcastAudience;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package org.modernbeta.admintoolbox.commands;

import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.types.MetaNode;
import net.luckperms.api.node.types.PermissionNode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
Expand All @@ -15,6 +9,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.modernbeta.admintoolbox.AdminToolboxPlugin;
import org.modernbeta.admintoolbox.managers.StreamerModeManager;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
Expand All @@ -25,16 +20,19 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.modernbeta.admintoolbox.managers.StreamerModeManager.STREAMER_MODE_USE_PERMISSION;

public class StreamerModeCommand implements CommandExecutor, TabCompleter {
private final AdminToolboxPlugin plugin = AdminToolboxPlugin.getInstance();
private final StreamerModeManager manager;

private static final String STREAMER_MODE_COMMAND_PERMISSION = "admintoolbox.streamermode";
private static final String STREAMER_MODE_BYPASS_MAX_DURATION_PERMISSION = "admintoolbox.streamermode.unlimited";
private static final String STREAMER_MODE_LP_META_KEY = "at-streamer-mode-enabled";
public StreamerModeCommand(StreamerModeManager streamerModeManager) {
this.manager = streamerModeManager;
}

@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!sender.hasPermission(STREAMER_MODE_COMMAND_PERMISSION))
if (!sender.hasPermission(STREAMER_MODE_USE_PERMISSION))
return false; // Bukkit should handle this for us, just a sanity-check
if (!(sender instanceof Player player)) {
sender.sendRichMessage("<red>Only players may use Streamer Mode.");
Expand All @@ -50,21 +48,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
sender.sendRichMessage("<red>LuckPerms is required to use Streamer Mode. Is it enabled?");
return true;
}
LuckPerms luckPerms = plugin.getLuckPerms().get().api();

List<String> disablePermissions = plugin.getConfig().getStringList("streamer-mode.disable-permissions");
User user = luckPerms.getPlayerAdapter(Player.class).getUser(player);

if (args.length == 0 && isStreamerModeActive(luckPerms, player)) {
user.data().clear(NodeType.META.predicate((node) -> node.getMetaKey().equals(STREAMER_MODE_LP_META_KEY)));
user.data().clear(NodeType.PERMISSION.predicate((node) -> // only delete negated, expiring nodes that match configured permissions
node.isNegated()
&& node.getExpiryDuration() != null
&& disablePermissions.contains(node.getPermission())
));
luckPerms.getUserManager().saveUser(user);

sender.sendRichMessage("<gold>Streamer Mode has been disabled.");
if (args.length == 0 && manager.isActive(player)) {
manager.disable(player)
.thenAccept(state -> {
sender.sendRichMessage("<gold>Streamer Mode has been disabled.");
});
return true;
}

Expand All @@ -85,40 +74,16 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command

Duration duration = parsedDuration.get();

final double maxDurationMinutes = plugin.getConfig().getDouble("streamer-mode.max-duration");
if ((duration.getSeconds() > (maxDurationMinutes * 60))
&& !sender.hasPermission(STREAMER_MODE_BYPASS_MAX_DURATION_PERMISSION)) {
if (!manager.isAllowableDuration(duration, player)) {
sender.sendRichMessage("<red>That duration is above the maximum allowed!");
return true;
}

MetaNode metaNode = MetaNode.builder()
.key(STREAMER_MODE_LP_META_KEY)
.value(Boolean.toString(true))
.expiry(duration)
.build();

user.data().clear(NodeType.META.predicate((node) -> node.getMetaKey().equals(STREAMER_MODE_LP_META_KEY)));
user.data().add(metaNode);

// using LuckPerms API, add negated/'false' versions of permissions from config.yml to user for duration
for (String permission : disablePermissions) {
Node permissionNode = PermissionNode.builder()
.permission(permission)
.expiry(duration)
.negated(true)
.build();

user.data().clear(NodeType.PERMISSION.predicate(
(node) -> node.getPermission().equals(permission) && node.isNegated()
));
user.data().add(permissionNode);
}

luckPerms.getUserManager().saveUser(user);

sender.sendRichMessage("<gold>Streamer Mode will be enabled for <green><duration></green>.",
Placeholder.unparsed("duration", formatDuration(duration)));
manager.enable(player, duration)
.thenAccept(state -> {
sender.sendRichMessage("<gold>Streamer Mode will be enabled for <green><duration></green>.",
Placeholder.unparsed("duration", formatDuration(state.duration())));
});
return true;
}

Expand All @@ -130,7 +95,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command

String partialEntry = args[0];

if(partialEntry.isEmpty()) {
if (partialEntry.isEmpty()) {
// Suggest durations if nothing is entered yet -- this is a good UX hint for how to use the command!
return List.of("15m", "30m", "5h", "8h");
}
Expand Down Expand Up @@ -159,7 +124,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
/// <strong>Only one duration segment is supported.</strong> That means durations such as
/// '1h15m' will fail to parse.
private Optional<Duration> parseDuration(String input) {
Pattern durationPattern = Pattern.compile("^\\s*(?<num>\\d{1,3})(?<unit>[mh])\\s*$", Pattern.CASE_INSENSITIVE);
Pattern durationPattern = Pattern.compile("^\\s*(?<num>[1-9]\\d{0,2})(?<unit>[mh])\\s*$", Pattern.CASE_INSENSITIVE);
Matcher matcher = durationPattern.matcher(input);

if (!matcher.matches())
Expand Down Expand Up @@ -197,11 +162,4 @@ private String formatDuration(Duration duration) {

return String.join(" ", resultList);
}

private boolean isStreamerModeActive(LuckPerms luckPerms, Player player) {
return luckPerms.getPlayerAdapter(Player.class)
.getMetaData(player)
.getMetaValue(STREAMER_MODE_LP_META_KEY, Boolean::valueOf)
.orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.modernbeta.admintoolbox.integration.placeholderapi;

import me.clip.placeholderapi.PlaceholderAPI;
import org.modernbeta.admintoolbox.AdminToolboxPlugin;
import org.modernbeta.admintoolbox.integration.placeholderapi.expansion.StreamerModePlaceholder;

public class PlaceholderAPIIntegration {
private final AdminToolboxPlugin plugin;

private final StreamerModePlaceholder streamerModePlaceholder;

public PlaceholderAPIIntegration(AdminToolboxPlugin plugin) {
this.plugin = plugin;
this.streamerModePlaceholder = new StreamerModePlaceholder(plugin);
}

public boolean registerPlaceholders() {
return this.streamerModePlaceholder.register();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.modernbeta.admintoolbox.integration.placeholderapi.expansion;

import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Relational;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.modernbeta.admintoolbox.AdminToolboxPlugin;

import javax.annotation.Nullable;

public class StreamerModePlaceholder extends PlaceholderExpansion implements Relational {
private final AdminToolboxPlugin plugin;

private static final String SM_VIEW_PERMISSION = "admintoolbox.streamermode.placeholder.view";
private static final String SM_WEAR_PERMISSION = "admintoolbox.streamermode.placeholder.wear";

public StreamerModePlaceholder(AdminToolboxPlugin plugin) {
this.plugin = plugin;
}

@Override
public @NotNull String getIdentifier() {
return "streamermode";
}

@SuppressWarnings("UnstableApiUsage")
@Override
public @NotNull String getAuthor() {
return String.join(", ", plugin.getPluginMeta().getAuthors());
}

@SuppressWarnings("UnstableApiUsage")
@Override
public @NotNull String getVersion() {
return plugin.getPluginMeta().getVersion();
}

@Override
public boolean persist() {
return true;
}

@Override
public String onPlaceholderRequest(Player viewer, Player wearer, String identifier) {
if (viewer == null || wearer == null) return "";
if (!viewer.hasPermission(SM_VIEW_PERMISSION)) return "";
if (!wearer.hasPermission(SM_WEAR_PERMISSION)) return "";

boolean isActive = plugin.getStreamerModeManager()
.map(sm -> sm.isActive(wearer))
.orElse(false);
if (!isActive) return "";

String tag = ChatColor.RED + "[SM]";
return switch (identifier.toLowerCase()) {
case "prefix" -> tag + " ";
case "suffix" -> " " + tag;
case "tag" -> tag;
default -> null;
};
}
}
Loading