LP injecting, fake player fixes
#96 LuckPerms Added support for excluding plugins from certain events from code Also might have fixed some message timeouts by relocating netty
This commit is contained in:
parent
2bdba0af22
commit
bf538d9bd0
8 changed files with 1114 additions and 787 deletions
14
pom.xml
14
pom.xml
|
@ -69,6 +69,14 @@
|
||||||
</excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies -->
|
</excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies -->
|
||||||
</artifactSet>
|
</artifactSet>
|
||||||
<minimizeJar>true</minimizeJar>
|
<minimizeJar>true</minimizeJar>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>io.netty</pattern>
|
||||||
|
<shadedPattern>buttondevteam.discordplugin.io.netty</shadedPattern>
|
||||||
|
<excludes>
|
||||||
|
</excludes>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
@ -242,6 +250,12 @@
|
||||||
<artifactId>blockhound</artifactId>
|
<artifactId>blockhound</artifactId>
|
||||||
<version>1.0.0.M3</version>
|
<version>1.0.0.M3</version>
|
||||||
</dependency> -->
|
</dependency> -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.lucko</groupId>
|
||||||
|
<artifactId>LuckPerms</artifactId>
|
||||||
|
<version>v4.4</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -6,12 +6,16 @@ import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
|
||||||
import discord4j.core.object.entity.MessageChannel;
|
import discord4j.core.object.entity.MessageChannel;
|
||||||
import discord4j.core.object.entity.User;
|
import discord4j.core.object.entity.User;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> {
|
public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> {
|
||||||
private static int nextEntityId = 10000;
|
private static int nextEntityId = 10000;
|
||||||
private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener;
|
private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean loggedIn = false;
|
||||||
|
|
||||||
public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) {
|
public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) {
|
||||||
super(user, channel, nextEntityId++, uuid, mcname, module);
|
super(user, channel, nextEntityId++, uuid, mcname, module);
|
||||||
|
|
|
@ -3,15 +3,12 @@ package buttondevteam.discordplugin.mcchat;
|
||||||
import buttondevteam.core.ComponentManager;
|
import buttondevteam.core.ComponentManager;
|
||||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||||
import buttondevteam.discordplugin.DiscordPlayer;
|
import buttondevteam.discordplugin.DiscordPlayer;
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
|
||||||
import buttondevteam.lib.player.TBMCPlayer;
|
import buttondevteam.lib.player.TBMCPlayer;
|
||||||
import discord4j.core.object.entity.MessageChannel;
|
import discord4j.core.object.entity.MessageChannel;
|
||||||
import discord4j.core.object.entity.PrivateChannel;
|
import discord4j.core.object.entity.PrivateChannel;
|
||||||
import discord4j.core.object.entity.User;
|
import discord4j.core.object.entity.User;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.event.Event;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@ -32,11 +29,14 @@ public class MCChatPrivate {
|
||||||
val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm);
|
val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm);
|
||||||
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
|
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
|
||||||
if (p == null)// Player is offline - If the player is online, that takes precedence
|
if (p == null)// Player is offline - If the player is online, that takes precedence
|
||||||
MCListener.callLoginEvents(sender);
|
MCChatUtils.callLoginEvents(sender);
|
||||||
} else {
|
} else {
|
||||||
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
|
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
|
||||||
if (p == null)// Player is offline - If the player is online, that takes precedence
|
assert sender != null;
|
||||||
callEventSync(new PlayerQuitEvent(sender, ""));
|
if (p == null // Player is offline - If the player is online, that takes precedence
|
||||||
|
&& sender.isLoggedIn()) //Don't call the quit event if login failed
|
||||||
|
MCChatUtils.callLogoutEvent(sender, true);
|
||||||
|
sender.setLoggedIn(false);
|
||||||
}
|
}
|
||||||
} // ---- PermissionsEx warning is normal on logout ----
|
} // ---- PermissionsEx warning is normal on logout ----
|
||||||
if (!start)
|
if (!start)
|
||||||
|
@ -60,11 +60,8 @@ public class MCChatPrivate {
|
||||||
for (val entry : MCChatUtils.ConnectedSenders.entrySet())
|
for (val entry : MCChatUtils.ConnectedSenders.entrySet())
|
||||||
for (val valueEntry : entry.getValue().entrySet())
|
for (val valueEntry : entry.getValue().entrySet())
|
||||||
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out
|
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out
|
||||||
MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync
|
MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync
|
||||||
MCChatUtils.ConnectedSenders.clear();
|
MCChatUtils.ConnectedSenders.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void callEventSync(Event event) {
|
|
||||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> MCChatUtils.callEventExcludingSome(event));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import buttondevteam.discordplugin.*;
|
||||||
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
|
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import discord4j.core.object.entity.*;
|
import discord4j.core.object.entity.*;
|
||||||
import discord4j.core.object.util.Snowflake;
|
import discord4j.core.object.util.Snowflake;
|
||||||
import io.netty.util.collection.LongObjectHashMap;
|
import io.netty.util.collection.LongObjectHashMap;
|
||||||
|
@ -15,14 +16,20 @@ import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
import org.bukkit.event.HandlerList;
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
import org.bukkit.plugin.AuthorNagException;
|
import org.bukkit.plugin.AuthorNagException;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.RegisteredListener;
|
import org.bukkit.plugin.RegisteredListener;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -43,6 +50,7 @@ public class MCChatUtils {
|
||||||
static @Nullable LastMsgData lastmsgdata;
|
static @Nullable LastMsgData lastmsgdata;
|
||||||
static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
|
static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
|
||||||
private static MinecraftChatModule module;
|
private static MinecraftChatModule module;
|
||||||
|
private static HashMap<Class<? extends Event>, HashSet<String>> staticExcludedPlugins = new HashMap<>();
|
||||||
|
|
||||||
public static void updatePlayerList() {
|
public static void updatePlayerList() {
|
||||||
if (notEnabled()) return;
|
if (notEnabled()) return;
|
||||||
|
@ -227,9 +235,23 @@ public class MCChatUtils {
|
||||||
//If it gets here, it's sending a message to a non-chat channel
|
//If it gets here, it's sending a message to a non-chat channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addStaticExcludedPlugin(Class<? extends Event> event, String plugin) {
|
||||||
|
staticExcludedPlugins.compute(event, (e, hs) -> hs == null
|
||||||
|
? Sets.newHashSet(plugin)
|
||||||
|
: (hs.add(plugin) ? hs : hs));
|
||||||
|
}
|
||||||
|
|
||||||
public static void callEventExcludingSome(Event event) {
|
public static void callEventExcludingSome(Event event) {
|
||||||
if (notEnabled()) return;
|
if (notEnabled()) return;
|
||||||
callEventExcluding(event, false, module.excludedPlugins().get());
|
val second = staticExcludedPlugins.get(event.getClass());
|
||||||
|
String[] first = module.excludedPlugins().get();
|
||||||
|
String[] both = second == null ? first
|
||||||
|
: Arrays.copyOf(first, first.length + second.size());
|
||||||
|
int i = first.length;
|
||||||
|
if (second != null)
|
||||||
|
for (String plugin : second)
|
||||||
|
both[i++] = plugin;
|
||||||
|
callEventExcluding(event, false, both);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -290,6 +312,50 @@ public class MCChatUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call it from an async thread.
|
||||||
|
*/
|
||||||
|
public static void callLoginEvents(DiscordConnectedPlayer dcp) {
|
||||||
|
Consumer<Supplier<String>> loginFail = kickMsg -> {
|
||||||
|
dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg.get());
|
||||||
|
MCChatPrivate.privateMCChat(dcp.getChannel(), false, dcp.getUser(), dcp.getChromaUser());
|
||||||
|
}; //Probably also happens if the user is banned or so
|
||||||
|
val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId());
|
||||||
|
callEventExcludingSome(event);
|
||||||
|
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||||
|
loginFail.accept(event::getKickMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
|
||||||
|
val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress());
|
||||||
|
callEventExcludingSome(ev);
|
||||||
|
if (ev.getResult() != PlayerLoginEvent.Result.ALLOWED) {
|
||||||
|
loginFail.accept(ev::getKickMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
|
||||||
|
dcp.setLoggedIn(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only calls the events if the player is actually logged in
|
||||||
|
*
|
||||||
|
* @param dcp The player
|
||||||
|
* @param needsSync Whether we're in an async thread
|
||||||
|
*/
|
||||||
|
public static void callLogoutEvent(DiscordConnectedPlayer dcp, boolean needsSync) {
|
||||||
|
if (!dcp.isLoggedIn()) return;
|
||||||
|
val event = new PlayerQuitEvent(dcp, "");
|
||||||
|
if (needsSync) callEventSync(event);
|
||||||
|
else callEventExcludingSome(event);
|
||||||
|
dcp.setLoggedIn(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void callEventSync(Event event) {
|
||||||
|
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event));
|
||||||
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public static class LastMsgData {
|
public static class LastMsgData {
|
||||||
public Message message;
|
public Message message;
|
||||||
|
|
|
@ -18,12 +18,12 @@ import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
import org.bukkit.event.player.*;
|
import org.bukkit.event.player.PlayerKickEvent;
|
||||||
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||||
import org.bukkit.event.server.BroadcastMessageEvent;
|
import org.bukkit.event.server.BroadcastMessageEvent;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -38,7 +38,7 @@ class MCListener implements Listener {
|
||||||
return;
|
return;
|
||||||
MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
|
MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
|
||||||
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
||||||
.ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(dcp, "")));
|
.ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
|
@ -70,30 +70,13 @@ class MCListener implements Listener {
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
||||||
() -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
|
() -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
|
||||||
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
||||||
.ifPresent(MCListener::callLoginEvents));
|
.ifPresent(MCChatUtils::callLoginEvents));
|
||||||
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
||||||
ChromaBot.getInstance()::updatePlayerList, 5);
|
ChromaBot.getInstance()::updatePlayerList, 5);
|
||||||
final String message = e.GetPlayer().PlayerName().get() + " left the game";
|
final String message = e.GetPlayer().PlayerName().get() + " left the game";
|
||||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
|
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Call it from an async thread.
|
|
||||||
*/
|
|
||||||
public static void callLoginEvents(DiscordConnectedPlayer dcp) {
|
|
||||||
val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId());
|
|
||||||
MCChatUtils.callEventExcludingSome(event);
|
|
||||||
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED)
|
|
||||||
return;
|
|
||||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
|
|
||||||
val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress());
|
|
||||||
MCChatUtils.callEventExcludingSome(ev);
|
|
||||||
if (ev.getResult() != Result.ALLOWED)
|
|
||||||
return;
|
|
||||||
MCChatUtils.callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onPlayerKick(PlayerKickEvent e) {
|
public void onPlayerKick(PlayerKickEvent e) {
|
||||||
/*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
|
/*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package buttondevteam.discordplugin.mcchat;
|
package buttondevteam.discordplugin.mcchat;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
import buttondevteam.core.component.channel.Channel;
|
import buttondevteam.core.component.channel.Channel;
|
||||||
import buttondevteam.discordplugin.DPUtils;
|
import buttondevteam.discordplugin.DPUtils;
|
||||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
import buttondevteam.discordplugin.DiscordPlugin;
|
||||||
|
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||||
import buttondevteam.lib.architecture.Component;
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
@ -106,6 +108,12 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new LPInjector(MainPlugin.Instance);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,6 +5,7 @@ import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||||
import discord4j.core.object.entity.MessageChannel;
|
import discord4j.core.object.entity.MessageChannel;
|
||||||
import discord4j.core.object.entity.User;
|
import discord4j.core.object.entity.User;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.experimental.Delegate;
|
import lombok.experimental.Delegate;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.advancement.Advancement;
|
import org.bukkit.advancement.Advancement;
|
||||||
|
@ -17,6 +18,7 @@ import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||||
import org.bukkit.map.MapView;
|
import org.bukkit.map.MapView;
|
||||||
import org.bukkit.permissions.PermissibleBase;
|
import org.bukkit.permissions.PermissibleBase;
|
||||||
|
import org.bukkit.permissions.ServerOperator;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.scoreboard.Scoreboard;
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
|
|
||||||
|
@ -27,15 +29,28 @@ import java.util.*;
|
||||||
public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
|
public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
|
||||||
protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) {
|
protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) {
|
||||||
super(user, channel, entityId, uuid, module);
|
super(user, channel, entityId, uuid, module);
|
||||||
perm = new PermissibleBase(Bukkit.getOfflinePlayer(uuid));
|
origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid));
|
||||||
name = mcname;
|
name = mcname;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delegate
|
@Delegate(excludes = ServerOperator.class)
|
||||||
private PermissibleBase perm;
|
private PermissibleBase origPerm;
|
||||||
|
|
||||||
private @Getter String name;
|
private @Getter String name;
|
||||||
|
|
||||||
|
private @Getter OfflinePlayer basePlayer;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private PermissibleBase perm;
|
||||||
|
|
||||||
|
public void setOp(boolean value) { //CraftPlayer-compatible implementation
|
||||||
|
this.origPerm.setOp(value);
|
||||||
|
this.perm.recalculatePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOp() { return this.origPerm.isOp(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityType getType() {
|
public EntityType getType() {
|
||||||
return EntityType.PLAYER;
|
return EntityType.PLAYER;
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
package buttondevteam.discordplugin.playerfaker.perm;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||||
|
import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
|
||||||
|
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
|
||||||
|
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
|
||||||
|
import me.lucko.luckperms.bukkit.inject.permissible.LPPermissible;
|
||||||
|
import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener;
|
||||||
|
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||||
|
import me.lucko.luckperms.common.locale.message.Message;
|
||||||
|
import me.lucko.luckperms.common.model.User;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.permissions.PermissibleBase;
|
||||||
|
import org.bukkit.permissions.PermissionAttachment;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public final class LPInjector implements Listener { //Disable login event for LuckPerms
|
||||||
|
private LPBukkitPlugin plugin;
|
||||||
|
private BukkitConnectionListener connectionListener;
|
||||||
|
private Set<UUID> deniedLogin;
|
||||||
|
private Field detectedCraftBukkitOfflineMode;
|
||||||
|
private Method printCraftBukkitOfflineModeError;
|
||||||
|
private Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD;
|
||||||
|
private Method convertAndAddAttachments;
|
||||||
|
private Method getActive;
|
||||||
|
private Method setOldPermissible;
|
||||||
|
private Method getOldPermissible;
|
||||||
|
|
||||||
|
public LPInjector(MainPlugin mp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
|
||||||
|
LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms");
|
||||||
|
Field field = LPBukkitBootstrap.class.getDeclaredField("plugin");
|
||||||
|
field.setAccessible(true);
|
||||||
|
plugin = (LPBukkitPlugin) field.get(bs);
|
||||||
|
MCChatUtils.addStaticExcludedPlugin(PlayerLoginEvent.class, "LuckPerms");
|
||||||
|
MCChatUtils.addStaticExcludedPlugin(PlayerQuitEvent.class, "LuckPerms");
|
||||||
|
|
||||||
|
field = LPBukkitPlugin.class.getDeclaredField("connectionListener");
|
||||||
|
field.setAccessible(true);
|
||||||
|
connectionListener = (BukkitConnectionListener) field.get(plugin);
|
||||||
|
field = connectionListener.getClass().getDeclaredField("deniedLogin");
|
||||||
|
field.setAccessible(true);
|
||||||
|
//noinspection unchecked
|
||||||
|
deniedLogin = (Set<UUID>) field.get(connectionListener);
|
||||||
|
field = connectionListener.getClass().getDeclaredField("detectedCraftBukkitOfflineMode");
|
||||||
|
field.setAccessible(true);
|
||||||
|
detectedCraftBukkitOfflineMode = field;
|
||||||
|
printCraftBukkitOfflineModeError = connectionListener.getClass().getDeclaredMethod("printCraftBukkitOfflineModeError");
|
||||||
|
printCraftBukkitOfflineModeError.setAccessible(true);
|
||||||
|
|
||||||
|
//PERMISSIBLE_FIELD = DiscordFakePlayer.class.getDeclaredField("perm");
|
||||||
|
//PERMISSIBLE_FIELD.setAccessible(true); //Hacking my own plugin, while we're at it
|
||||||
|
PERMISSIBLE_BASE_ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments");
|
||||||
|
PERMISSIBLE_BASE_ATTACHMENTS_FIELD.setAccessible(true);
|
||||||
|
|
||||||
|
convertAndAddAttachments = LPPermissible.class.getDeclaredMethod("convertAndAddAttachments", Collection.class);
|
||||||
|
convertAndAddAttachments.setAccessible(true);
|
||||||
|
getActive = LPPermissible.class.getDeclaredMethod("getActive");
|
||||||
|
getActive.setAccessible(true);
|
||||||
|
setOldPermissible = LPPermissible.class.getDeclaredMethod("setOldPermissible", PermissibleBase.class);
|
||||||
|
setOldPermissible.setAccessible(true);
|
||||||
|
getOldPermissible = LPPermissible.class.getDeclaredMethod("getOldPermissible");
|
||||||
|
getOldPermissible.setAccessible(true);
|
||||||
|
|
||||||
|
TBMCCoreAPI.RegisterEventsForExceptions(this, mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Code copied from LuckPerms - me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
|
public void onPlayerLogin(PlayerLoginEvent e) {
|
||||||
|
/* Called when the player starts logging into the server.
|
||||||
|
At this point, the users data should be present and loaded. */
|
||||||
|
|
||||||
|
if (!(e.getPlayer() instanceof DiscordFakePlayer))
|
||||||
|
return; //Normal players must be handled by the plugin
|
||||||
|
|
||||||
|
final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer();
|
||||||
|
|
||||||
|
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
|
||||||
|
plugin.getLogger().info("Processing login for " + player.getUniqueId() + " - " + player.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
final User user = plugin.getUserManager().getIfLoaded(player.getUniqueId());
|
||||||
|
|
||||||
|
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
|
||||||
|
if (user == null) {
|
||||||
|
deniedLogin.add(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!connectionListener.getUniqueConnections().contains(player.getUniqueId())) {
|
||||||
|
|
||||||
|
plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() +
|
||||||
|
" doesn't have data pre-loaded, they have never been processed during pre-login in this session." +
|
||||||
|
" - denying login.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ((Boolean) detectedCraftBukkitOfflineMode.get(connectionListener)) {
|
||||||
|
printCraftBukkitOfflineModeError.invoke(connectionListener);
|
||||||
|
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR_CB_OFFLINE_MODE.asString(plugin.getLocaleManager()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() +
|
||||||
|
" doesn't currently have data pre-loaded, but they have been processed before in this session." +
|
||||||
|
" - denying login.");
|
||||||
|
}
|
||||||
|
|
||||||
|
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR.asString(plugin.getLocaleManager()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User instance is there, now we can inject our custom Permissible into the player.
|
||||||
|
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
|
||||||
|
try {
|
||||||
|
// get the existing PermissibleBase held by the player
|
||||||
|
PermissibleBase oldPermissible = player.getPerm();
|
||||||
|
|
||||||
|
// Make a new permissible for the user
|
||||||
|
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
|
||||||
|
|
||||||
|
// Inject into the player
|
||||||
|
inject(player, lpPermissible, oldPermissible);
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
plugin.getLogger().warn("Exception thrown when setting up permissions for " +
|
||||||
|
player.getUniqueId() + " - " + player.getName() + " - denying login.");
|
||||||
|
t.printStackTrace();
|
||||||
|
|
||||||
|
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.refreshAutoOp(player, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent e) {
|
||||||
|
if (!(e.getPlayer() instanceof DiscordFakePlayer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer();
|
||||||
|
|
||||||
|
connectionListener.handleDisconnect(player.getUniqueId());
|
||||||
|
|
||||||
|
// perform unhooking from bukkit objects 1 tick later.
|
||||||
|
// this allows plugins listening after us on MONITOR to still have intact permissions data
|
||||||
|
this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> {
|
||||||
|
// Remove the custom permissible
|
||||||
|
try {
|
||||||
|
uninject(player, true);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle auto op
|
||||||
|
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
|
||||||
|
player.setOp(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove their contexts cache
|
||||||
|
this.plugin.getContextManager().onPlayerQuit(player);
|
||||||
|
}, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
//me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector
|
||||||
|
private void inject(DiscordFakePlayer player, LPPermissible newPermissible, PermissibleBase oldPermissible) throws IllegalAccessException, InvocationTargetException {
|
||||||
|
|
||||||
|
// seems we have already injected into this player.
|
||||||
|
if (oldPermissible instanceof LPPermissible) {
|
||||||
|
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move attachments over from the old permissible
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
List<PermissionAttachment> attachments = (List<PermissionAttachment>) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible);
|
||||||
|
|
||||||
|
convertAndAddAttachments.invoke(newPermissible, attachments);
|
||||||
|
attachments.clear();
|
||||||
|
oldPermissible.clearPermissions();
|
||||||
|
|
||||||
|
// Setup the new permissible
|
||||||
|
((AtomicBoolean) getActive.invoke(newPermissible)).set(true);
|
||||||
|
setOldPermissible.invoke(newPermissible, oldPermissible);
|
||||||
|
|
||||||
|
// inject the new instance
|
||||||
|
player.setPerm(newPermissible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uninject(DiscordFakePlayer player, boolean dummy) throws Exception {
|
||||||
|
|
||||||
|
// gets the players current permissible.
|
||||||
|
PermissibleBase permissible = player.getPerm();
|
||||||
|
|
||||||
|
// only uninject if the permissible was a luckperms one.
|
||||||
|
if (permissible instanceof LPPermissible) {
|
||||||
|
LPPermissible lpPermissible = ((LPPermissible) permissible);
|
||||||
|
|
||||||
|
// clear all permissions
|
||||||
|
lpPermissible.clearPermissions();
|
||||||
|
|
||||||
|
// set to inactive
|
||||||
|
((AtomicBoolean) getActive.invoke(lpPermissible)).set(false);
|
||||||
|
|
||||||
|
// handle the replacement permissible.
|
||||||
|
if (dummy) {
|
||||||
|
// just inject a dummy class. this is used when we know the player is about to quit the server.
|
||||||
|
player.setPerm(DummyPermissibleBase.INSTANCE);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
PermissibleBase newPb = (PermissibleBase) getOldPermissible.invoke(lpPermissible);
|
||||||
|
if (newPb == null) {
|
||||||
|
newPb = new PermissibleBase(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.setPerm(newPb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue