Converted mcchat classes to Scala
This commit is contained in:
parent
428361c46c
commit
9f47509dcb
24 changed files with 1578 additions and 1655 deletions
|
@ -1,6 +1,5 @@
|
|||
package buttondevteam.discordplugin;
|
||||
|
||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||
import buttondevteam.discordplugin.util.DPState;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.core.Filter;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package buttondevteam.discordplugin;
|
||||
|
||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||
import buttondevteam.discordplugin.playerfaker.DiscordInventory;
|
||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
||||
import discord4j.core.object.entity.User;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package buttondevteam.discordplugin;
|
||||
|
||||
import buttondevteam.discordplugin.mcchat.MCChatPrivate;
|
||||
import buttondevteam.lib.player.ChromaGamerBase;
|
||||
import buttondevteam.lib.player.UserClass;
|
||||
import discord4j.core.object.entity.User;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package buttondevteam.discordplugin;
|
||||
|
||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
||||
import discord4j.core.object.entity.User;
|
||||
import discord4j.core.object.entity.channel.MessageChannel;
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.component.channel.Channel;
|
||||
import buttondevteam.core.component.channel.ChatRoom;
|
||||
import buttondevteam.discordplugin.*;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
import buttondevteam.lib.chat.Command2;
|
||||
import buttondevteam.lib.chat.CommandClass;
|
||||
import buttondevteam.lib.player.TBMCPlayer;
|
||||
import discord4j.core.object.entity.Message;
|
||||
import discord4j.core.object.entity.User;
|
||||
import discord4j.core.object.entity.channel.GuildChannel;
|
||||
import discord4j.core.object.entity.channel.MessageChannel;
|
||||
import discord4j.rest.util.Permission;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("SimplifyOptionalCallChains") //Java 11
|
||||
@CommandClass(helpText = {"Channel connect", //
|
||||
"This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", //
|
||||
"You need to have access to the MC channel and have manage permissions on the Discord channel.", //
|
||||
"You also need to have your Minecraft account connected. In #bot use /connect <mcname>.", //
|
||||
"Call this command from the channel you want to use.", //
|
||||
"Usage: @Bot channelcon <mcchannel>", //
|
||||
"Use the ID (command) of the channel, for example `g` for the global chat.", //
|
||||
"To remove a connection use @ChromaBot channelcon remove in the channel.", //
|
||||
"Mentioning the bot is needed in this case because the / prefix only works in #bot.", //
|
||||
"Invite link: <Unknown>" //
|
||||
})
|
||||
@RequiredArgsConstructor
|
||||
public class ChannelconCommand extends ICommand2DC {
|
||||
private final MinecraftChatModule module;
|
||||
|
||||
@Command2.Subcommand
|
||||
public boolean remove(Command2DCSender sender) {
|
||||
val message = sender.getMessage();
|
||||
if (checkPerms(message, null)) return true;
|
||||
if (MCChatCustom.removeCustomChat(message.getChannelId()))
|
||||
DPUtils.reply(message, Mono.empty(), "channel connection removed.").subscribe();
|
||||
else
|
||||
DPUtils.reply(message, Mono.empty(), "this channel isn't connected.").subscribe();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Command2.Subcommand
|
||||
public boolean toggle(Command2DCSender sender, @Command2.OptionalArg String toggle) {
|
||||
val message = sender.getMessage();
|
||||
if (checkPerms(message, null)) return true;
|
||||
val cc = MCChatCustom.getCustomChat(message.getChannelId());
|
||||
if (cc == null)
|
||||
return respond(sender, "this channel isn't connected.");
|
||||
Supplier<String> togglesString = () -> Arrays.stream(ChannelconBroadcast.values()).map(t -> t.toString().toLowerCase() + ": " + ((cc.toggles & t.flag) == 0 ? "disabled" : "enabled")).collect(Collectors.joining("\n"))
|
||||
+ "\n\n" + TBMCSystemChatEvent.BroadcastTarget.stream().map(target -> target.getName() + ": " + (cc.brtoggles.contains(target) ? "enabled" : "disabled")).collect(Collectors.joining("\n"));
|
||||
if (toggle == null) {
|
||||
DPUtils.reply(message, Mono.empty(), "toggles:\n" + togglesString.get()).subscribe();
|
||||
return true;
|
||||
}
|
||||
String arg = toggle.toUpperCase();
|
||||
val b = Arrays.stream(ChannelconBroadcast.values()).filter(t -> t.toString().equals(arg)).findAny();
|
||||
if (!b.isPresent()) {
|
||||
val bt = TBMCSystemChatEvent.BroadcastTarget.get(arg);
|
||||
if (bt == null) {
|
||||
DPUtils.reply(message, Mono.empty(), "cannot find toggle. Toggles:\n" + togglesString.get()).subscribe();
|
||||
return true;
|
||||
}
|
||||
final boolean add;
|
||||
if (add = !cc.brtoggles.contains(bt))
|
||||
cc.brtoggles.add(bt);
|
||||
else
|
||||
cc.brtoggles.remove(bt);
|
||||
return respond(sender, "'" + bt.getName() + "' " + (add ? "en" : "dis") + "abled");
|
||||
}
|
||||
//A B | F
|
||||
//------- A: original - B: mask - F: new
|
||||
//0 0 | 0
|
||||
//0 1 | 1
|
||||
//1 0 | 1
|
||||
//1 1 | 0
|
||||
// XOR
|
||||
cc.toggles ^= b.get().flag;
|
||||
DPUtils.reply(message, Mono.empty(), "'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")).subscribe();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Command2.Subcommand
|
||||
public boolean def(Command2DCSender sender, String channelID) {
|
||||
val message = sender.getMessage();
|
||||
if (!module.allowCustomChat.get()) {
|
||||
sender.sendMessage("channel connection is not allowed on this Minecraft server.");
|
||||
return true;
|
||||
}
|
||||
val channel = message.getChannel().block();
|
||||
if (checkPerms(message, channel)) return true;
|
||||
if (MCChatCustom.hasCustomChat(message.getChannelId()))
|
||||
return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it.");
|
||||
val chan = Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase(channelID) || (Arrays.stream(ch.IDs.get()).anyMatch(cid -> cid.equalsIgnoreCase(channelID)))).findAny();
|
||||
if (!chan.isPresent()) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW)
|
||||
DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe();
|
||||
return true;
|
||||
}
|
||||
if (!message.getAuthor().isPresent()) return true;
|
||||
val author = message.getAuthor().get();
|
||||
val dp = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class);
|
||||
val chp = dp.getAs(TBMCPlayer.class);
|
||||
if (chp == null) {
|
||||
DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect <MCname>").subscribe();
|
||||
return true;
|
||||
}
|
||||
DiscordConnectedPlayer dcp = DiscordConnectedPlayer.create(message.getAuthor().get(), channel, chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName(), module);
|
||||
//Using a fake player with no login/logout, should be fine for this event
|
||||
String groupid = chan.get().getGroupID(dcp);
|
||||
if (groupid == null && !(chan.get() instanceof ChatRoom)) { //ChatRooms don't allow it unless the user joins, which happens later
|
||||
DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe();
|
||||
return true;
|
||||
}
|
||||
if (chan.get() instanceof ChatRoom) { //ChatRooms don't work well
|
||||
DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe();
|
||||
return true;
|
||||
}
|
||||
/*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) {
|
||||
DPUtils.reply(message, null, "sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm.");
|
||||
return true;
|
||||
}*/ //TODO: "Channel admins" that can connect channels?
|
||||
MCChatCustom.addCustomChat(channel, groupid, chan.get(), author, dcp, 0, new HashSet<>());
|
||||
if (chan.get() instanceof ChatRoom)
|
||||
DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe();
|
||||
else
|
||||
DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe();
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private boolean checkPerms(Message message, @Nullable MessageChannel channel) {
|
||||
if (channel == null)
|
||||
channel = message.getChannel().block();
|
||||
if (!(channel instanceof GuildChannel)) {
|
||||
DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe();
|
||||
return true;
|
||||
}
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
var perms = ((GuildChannel) channel).getEffectivePermissions(message.getAuthor().map(User::getId).get()).block();
|
||||
if (!perms.contains(Permission.ADMINISTRATOR) && !perms.contains(Permission.MANAGE_CHANNELS)) {
|
||||
DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getHelpText(Method method, Command2.Subcommand ann) {
|
||||
return new String[]{ //
|
||||
"Channel connect", //
|
||||
"This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", //
|
||||
"You need to have access to the MC channel and have manage permissions on the Discord channel.", //
|
||||
"You also need to have your Minecraft account connected. In " + DPUtils.botmention() + " use " + DiscordPlugin.getPrefix() + "connect <mcname>.", //
|
||||
"Call this command from the channel you want to use.", //
|
||||
"Usage: " + Objects.requireNonNull(DiscordPlugin.dc.getSelf().block()).getMention() + " channelcon <mcchannel>", //
|
||||
"Use the ID (command) of the channel, for example `g` for the global chat.", //
|
||||
"To remove a connection use @ChromaBot channelcon remove in the channel.", //
|
||||
"Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix() + " prefix only works in " + DPUtils.botmention() + ".", //
|
||||
"Invite link: <https://discordapp.com/oauth2/authorize?client_id=" + DiscordPlugin.dc.getApplicationInfo().map(info -> info.getId().asString()).blockOptional().orElse("Unknown") + "&scope=bot&permissions=268509264>"
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.core.component.channel.Channel
|
||||
import buttondevteam.core.component.channel.ChatRoom
|
||||
import buttondevteam.discordplugin._
|
||||
import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC}
|
||||
import buttondevteam.lib.TBMCSystemChatEvent
|
||||
import buttondevteam.lib.chat.Command2
|
||||
import buttondevteam.lib.chat.CommandClass
|
||||
import buttondevteam.lib.player.TBMCPlayer
|
||||
import discord4j.core.`object`.entity.Message
|
||||
import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel}
|
||||
import discord4j.rest.util.{Permission, PermissionSet}
|
||||
import lombok.RequiredArgsConstructor
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.CommandSender
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
import javax.annotation.Nullable
|
||||
import java.lang.reflect.Method
|
||||
import java.util
|
||||
import java.util.{Objects, Optional}
|
||||
import java.util.function.Supplier
|
||||
import java.util.stream.Collectors
|
||||
|
||||
@SuppressWarnings(Array("SimplifyOptionalCallChains")) //Java 11
|
||||
@CommandClass(helpText = Array(Array("Channel connect", //
|
||||
"This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).",
|
||||
"You need to have access to the MC channel and have manage permissions on the Discord channel.",
|
||||
"You also need to have your Minecraft account connected. In #bot use /connect <mcname>.",
|
||||
"Call this command from the channel you want to use.", "Usage: @Bot channelcon <mcchannel>",
|
||||
"Use the ID (command) of the channel, for example `g` for the global chat.",
|
||||
"To remove a connection use @ChromaBot channelcon remove in the channel.",
|
||||
"Mentioning the bot is needed in this case because the / prefix only works in #bot.",
|
||||
"Invite link: <Unknown>" //
|
||||
)))
|
||||
class ChannelconCommand(private val module: MinecraftChatModule) extends ICommand2DC {
|
||||
@Command2.Subcommand def remove(sender: Command2DCSender): Boolean = {
|
||||
val message = sender.getMessage
|
||||
if (checkPerms(message, null)) true
|
||||
else if (MCChatCustom.removeCustomChat(message.getChannelId))
|
||||
DPUtils.reply(message, Mono.empty, "channel connection removed.").subscribe
|
||||
else
|
||||
DPUtils.reply(message, Mono.empty, "this channel isn't connected.").subscribe
|
||||
true
|
||||
}
|
||||
|
||||
@Command2.Subcommand def toggle(sender: Command2DCSender, @Command2.OptionalArg toggle: String): Boolean = {
|
||||
val message = sender.getMessage
|
||||
if (checkPerms(message, null)) {
|
||||
return true
|
||||
}
|
||||
val cc: MCChatCustom.CustomLMD = MCChatCustom.getCustomChat(message.getChannelId)
|
||||
if (cc == null) {
|
||||
return respond(sender, "this channel isn't connected.")
|
||||
}
|
||||
val togglesString: Supplier[String] = () => util.Arrays.stream(ChannelconBroadcast.values)
|
||||
.map((t: ChannelconBroadcast) =>
|
||||
t.toString.toLowerCase + ": " + (if ((cc.toggles & t.flag) == 0) "disabled" else "enabled"))
|
||||
.collect(Collectors.joining("\n")) + "\n\n" +
|
||||
TBMCSystemChatEvent.BroadcastTarget.stream.map((target: TBMCSystemChatEvent.BroadcastTarget) =>
|
||||
target.getName + ": " + (if (cc.brtoggles.contains(target)) "enabled" else "disabled"))
|
||||
.collect(Collectors.joining("\n"))
|
||||
if (toggle == null) {
|
||||
DPUtils.reply(message, Mono.empty, "toggles:\n" + togglesString.get).subscribe
|
||||
return true
|
||||
}
|
||||
val arg: String = toggle.toUpperCase
|
||||
val b: Optional[ChannelconBroadcast] = util.Arrays.stream(ChannelconBroadcast.values).filter((t: ChannelconBroadcast) => t.toString == arg).findAny
|
||||
if (!b.isPresent) {
|
||||
val bt: TBMCSystemChatEvent.BroadcastTarget = TBMCSystemChatEvent.BroadcastTarget.get(arg)
|
||||
if (bt == null) {
|
||||
DPUtils.reply(message, Mono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe
|
||||
return true
|
||||
}
|
||||
val add: Boolean = !(cc.brtoggles.contains(bt))
|
||||
if (add) {
|
||||
cc.brtoggles.add(bt)
|
||||
}
|
||||
else {
|
||||
cc.brtoggles.remove(bt)
|
||||
}
|
||||
return respond(sender, "'" + bt.getName + "' " + (if (add) "en" else "dis") + "abled")
|
||||
}
|
||||
//A B | F
|
||||
//------- A: original - B: mask - F: new
|
||||
//0 0 | 0
|
||||
//0 1 | 1
|
||||
//1 0 | 1
|
||||
//1 1 | 0
|
||||
// XOR
|
||||
cc.toggles ^= b.get.flag
|
||||
DPUtils.reply(message, Mono.empty, "'" + b.get.toString.toLowerCase + "' "
|
||||
+ (if ((cc.toggles & b.get.flag) == 0) "disabled" else "enabled")).subscribe
|
||||
return true
|
||||
}
|
||||
|
||||
@Command2.Subcommand def `def`(sender: Command2DCSender, channelID: String): Boolean = {
|
||||
val message = sender.getMessage
|
||||
if (!(module.allowCustomChat.get)) {
|
||||
sender.sendMessage("channel connection is not allowed on this Minecraft server.")
|
||||
return true
|
||||
}
|
||||
val channel = message.getChannel.block
|
||||
if (checkPerms(message, channel)) {
|
||||
return true
|
||||
}
|
||||
if (MCChatCustom.hasCustomChat(message.getChannelId)) {
|
||||
return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it.")
|
||||
}
|
||||
val chan: Optional[Channel] = Channel.getChannels.filter((ch: Channel) => ch.ID.equalsIgnoreCase(channelID) || (util.Arrays.stream(ch.IDs.get).anyMatch((cid: String) => cid.equalsIgnoreCase(channelID)))).findAny
|
||||
if (!(chan.isPresent)) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW)
|
||||
DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe
|
||||
return true
|
||||
}
|
||||
if (!(message.getAuthor.isPresent)) {
|
||||
return true
|
||||
}
|
||||
val author = message.getAuthor.get
|
||||
val dp: DiscordPlayer = ChromaGamerBase.getUser(author.getId.asString, classOf[DiscordPlayer])
|
||||
val chp: TBMCPlayer = dp.getAs(classOf[TBMCPlayer])
|
||||
if (chp == null) {
|
||||
DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention + " do " + DiscordPlugin.getPrefix + "connect <MCname>").subscribe
|
||||
return true
|
||||
}
|
||||
val dcp: DiscordConnectedPlayer = DiscordConnectedPlayer.create(message.getAuthor.get, channel, chp.getUUID, Bukkit.getOfflinePlayer(chp.getUUID).getName, module)
|
||||
//Using a fake player with no login/logout, should be fine for this event
|
||||
val groupid: String = chan.get.getGroupID(dcp)
|
||||
if (groupid == null && !((chan.get.isInstanceOf[ChatRoom]))) { //ChatRooms don't allow it unless the user joins, which happens later
|
||||
DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe
|
||||
return true
|
||||
}
|
||||
if (chan.get.isInstanceOf[ChatRoom]) { //ChatRooms don't work well
|
||||
DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe
|
||||
return true
|
||||
}
|
||||
/*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) {
|
||||
DPUtils.reply(message, null, "sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm.");
|
||||
return true;
|
||||
}*/
|
||||
//TODO: "Channel admins" that can connect channels?
|
||||
MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, new util.HashSet[TBMCSystemChatEvent.BroadcastTarget])
|
||||
if (chan.get.isInstanceOf[ChatRoom]) {
|
||||
DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe
|
||||
}
|
||||
else {
|
||||
DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressWarnings(Array("ConstantConditions"))
|
||||
private def checkPerms(message: Message, @Nullable channel: MessageChannel): Boolean = {
|
||||
if (channel == null) {
|
||||
return checkPerms(message, message.getChannel.block)
|
||||
}
|
||||
if (!((channel.isInstanceOf[GuildChannel]))) {
|
||||
DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe
|
||||
return true
|
||||
}
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
val perms: PermissionSet = (channel.asInstanceOf[GuildChannel]).getEffectivePermissions(message.getAuthor.map(_.getId).get).block
|
||||
if (!(perms.contains(Permission.ADMINISTRATOR)) && !(perms.contains(Permission.MANAGE_CHANNELS))) {
|
||||
DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] =
|
||||
Array[String](
|
||||
"Channel connect",
|
||||
"This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).",
|
||||
"You need to have access to the MC channel and have manage permissions on the Discord channel.",
|
||||
"You also need to have your Minecraft account connected. In " + DPUtils.botmention + " use " + DiscordPlugin.getPrefix + "connect <mcname>.",
|
||||
"Call this command from the channel you want to use.", "Usage: " + Objects.requireNonNull(DiscordPlugin.dc.getSelf.block).getMention + " channelcon <mcchannel>",
|
||||
"Use the ID (command) of the channel, for example `g` for the global chat.",
|
||||
"To remove a connection use @ChromaBot channelcon remove in the channel.",
|
||||
"Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix + " prefix only works in " + DPUtils.botmention + ".",
|
||||
"Invite link: <https://discordapp.com/oauth2/authorize?client_id="
|
||||
+ DiscordPlugin.dc.getApplicationInfo.map((info) => info.getId.asString).blockOptional.orElse("Unknown")
|
||||
+ "&scope=bot&permissions=268509264>")
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.discordplugin.DPUtils;
|
||||
import buttondevteam.discordplugin.DiscordPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.lib.chat.Command2;
|
||||
import buttondevteam.lib.chat.CommandClass;
|
||||
import discord4j.core.object.entity.channel.PrivateChannel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.val;
|
||||
|
||||
@CommandClass(helpText = {
|
||||
"MC Chat",
|
||||
"This command enables or disables the Minecraft chat in private messages.", //
|
||||
"It can be useful if you don't want your messages to be visible, for example when talking in a private channel.", //
|
||||
"You can also run all of the ingame commands you have access to using this command, if you have your accounts connected." //
|
||||
})
|
||||
@RequiredArgsConstructor
|
||||
public class MCChatCommand extends ICommand2DC {
|
||||
|
||||
private final MinecraftChatModule module;
|
||||
|
||||
@Command2.Subcommand
|
||||
public boolean def(Command2DCSender sender) {
|
||||
if (!module.allowPrivateChat.get()) {
|
||||
sender.sendMessage("using the private chat is not allowed on this Minecraft server.");
|
||||
return true;
|
||||
}
|
||||
val message = sender.getMessage();
|
||||
val channel = message.getChannel().block();
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent") val author = message.getAuthor().get();
|
||||
if (!(channel instanceof PrivateChannel)) {
|
||||
DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe();
|
||||
return true;
|
||||
}
|
||||
final DiscordPlayer user = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class);
|
||||
boolean mcchat = !user.isMinecraftChatEnabled();
|
||||
MCChatPrivate.privateMCChat(channel, mcchat, author, user);
|
||||
DPUtils.reply(message, channel, "Minecraft chat " + (mcchat //
|
||||
? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." //
|
||||
: "disabled.")).subscribe();
|
||||
return true;
|
||||
} // TODO: Pin channel switching to indicate the current channel
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin}
|
||||
import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC}
|
||||
import buttondevteam.lib.chat.{Command2, CommandClass}
|
||||
import buttondevteam.lib.player.ChromaGamerBase
|
||||
import discord4j.core.`object`.entity.channel.PrivateChannel
|
||||
|
||||
@CommandClass(helpText = Array(Array(
|
||||
"MC Chat",
|
||||
"This command enables or disables the Minecraft chat in private messages.", //
|
||||
"It can be useful if you don't want your messages to be visible, for example when talking in a private channel.",
|
||||
"You can also run all of the ingame commands you have access to using this command, if you have your accounts connected." //
|
||||
)))
|
||||
class MCChatCommand(private val module: MinecraftChatModule) extends ICommand2DC {
|
||||
@Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = {
|
||||
if (!(module.allowPrivateChat.get)) {
|
||||
sender.sendMessage("using the private chat is not allowed on this Minecraft server.")
|
||||
return true
|
||||
}
|
||||
val message = sender.getMessage
|
||||
val channel = message.getChannel.block
|
||||
@SuppressWarnings(Array("OptionalGetWithoutIsPresent")) val author = message.getAuthor.get
|
||||
if (!((channel.isInstanceOf[PrivateChannel]))) {
|
||||
DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe
|
||||
return true
|
||||
}
|
||||
val user: DiscordPlayer = ChromaGamerBase.getUser(author.getId.asString, classOf[DiscordPlayer])
|
||||
val mcchat: Boolean = !(user.isMinecraftChatEnabled)
|
||||
MCChatPrivate.privateMCChat(channel, mcchat, author, user)
|
||||
DPUtils.reply(message, channel, "Minecraft chat " +
|
||||
(if (mcchat) "enabled. Use '" + DiscordPlugin.getPrefix + "mcchat' again to turn it off."
|
||||
else "disabled.")).subscribe
|
||||
true
|
||||
// TODO: Pin channel switching to indicate the current channel
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.component.channel.Channel;
|
||||
import buttondevteam.core.component.channel.ChatRoom;
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
import discord4j.common.util.Snowflake;
|
||||
import discord4j.core.object.entity.User;
|
||||
import discord4j.core.object.entity.channel.MessageChannel;
|
||||
import lombok.NonNull;
|
||||
import lombok.val;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MCChatCustom {
|
||||
/**
|
||||
* Used for town or nation chats or anything else
|
||||
*/
|
||||
static final ArrayList<CustomLMD> lastmsgCustom = new ArrayList<>();
|
||||
|
||||
public static void addCustomChat(MessageChannel channel, String groupid, Channel mcchannel, User user, DiscordConnectedPlayer dcp, int toggles, Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles) {
|
||||
synchronized (lastmsgCustom) {
|
||||
if (mcchannel instanceof ChatRoom) {
|
||||
((ChatRoom) mcchannel).joinRoom(dcp);
|
||||
if (groupid == null) groupid = mcchannel.getGroupID(dcp);
|
||||
}
|
||||
val lmd = new CustomLMD(channel, user, groupid, mcchannel, dcp, toggles, brtoggles);
|
||||
lastmsgCustom.add(lmd);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasCustomChat(Snowflake channel) {
|
||||
return lastmsgCustom.stream().anyMatch(lmd -> lmd.channel.getId().asLong() == channel.asLong());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static CustomLMD getCustomChat(Snowflake channel) {
|
||||
return lastmsgCustom.stream().filter(lmd -> lmd.channel.getId().asLong() == channel.asLong()).findAny().orElse(null);
|
||||
}
|
||||
|
||||
public static boolean removeCustomChat(Snowflake channel) {
|
||||
synchronized (lastmsgCustom) {
|
||||
MCChatUtils.lastmsgfromd.remove(channel.asLong());
|
||||
return lastmsgCustom.removeIf(lmd -> {
|
||||
if (lmd.channel.getId().asLong() != channel.asLong())
|
||||
return false;
|
||||
if (lmd.mcchannel instanceof ChatRoom)
|
||||
((ChatRoom) lmd.mcchannel).leaveRoom(lmd.dcp);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static List<CustomLMD> getCustomChats() {
|
||||
return Collections.unmodifiableList(lastmsgCustom);
|
||||
}
|
||||
|
||||
public static class CustomLMD extends MCChatUtils.LastMsgData {
|
||||
public final String groupID;
|
||||
public final DiscordConnectedPlayer dcp;
|
||||
public int toggles;
|
||||
public Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles;
|
||||
|
||||
private CustomLMD(@NonNull MessageChannel channel, @NonNull User user,
|
||||
@NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles, Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles) {
|
||||
super(channel, user);
|
||||
groupID = groupid;
|
||||
this.mcchannel = mcchannel;
|
||||
this.dcp = dcp;
|
||||
this.toggles = toggles;
|
||||
this.brtoggles = brtoggles;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.core.component.channel.{Channel, ChatRoom}
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer
|
||||
import buttondevteam.lib.TBMCSystemChatEvent
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.User
|
||||
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||
import lombok.NonNull
|
||||
|
||||
import java.util
|
||||
import java.util.Collections
|
||||
import javax.annotation.Nullable
|
||||
|
||||
object MCChatCustom {
|
||||
/**
|
||||
* Used for town or nation chats or anything else
|
||||
*/
|
||||
private[mcchat] val lastmsgCustom = new util.ArrayList[MCChatCustom.CustomLMD]
|
||||
|
||||
def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: util.Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = {
|
||||
lastmsgCustom synchronized {
|
||||
var gid: String = null
|
||||
mcchannel match {
|
||||
case room: ChatRoom =>
|
||||
room.joinRoom(dcp)
|
||||
gid = if (groupid == null) mcchannel.getGroupID(dcp) else groupid
|
||||
case _ =>
|
||||
gid = groupid
|
||||
}
|
||||
val lmd = new MCChatCustom.CustomLMD(channel, user, gid, mcchannel, dcp, toggles, brtoggles)
|
||||
lastmsgCustom.add(lmd)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
def hasCustomChat(channel: Snowflake): Boolean =
|
||||
lastmsgCustom.stream.anyMatch((lmd: MCChatCustom.CustomLMD) => lmd.channel.getId.asLong == channel.asLong)
|
||||
|
||||
@Nullable def getCustomChat(channel: Snowflake): CustomLMD =
|
||||
lastmsgCustom.stream.filter((lmd: MCChatCustom.CustomLMD) => lmd.channel.getId.asLong == channel.asLong).findAny.orElse(null)
|
||||
|
||||
def removeCustomChat(channel: Snowflake): Boolean = {
|
||||
lastmsgCustom synchronized MCChatUtils.lastmsgfromd.remove(channel.asLong)
|
||||
lastmsgCustom.removeIf((lmd: MCChatCustom.CustomLMD) => {
|
||||
def foo(lmd: MCChatCustom.CustomLMD): Boolean = {
|
||||
if (lmd.channel.getId.asLong != channel.asLong) return false
|
||||
lmd.mcchannel match {
|
||||
case room: ChatRoom => room.leaveRoom(lmd.dcp)
|
||||
case _ =>
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
foo(lmd)
|
||||
})
|
||||
}
|
||||
|
||||
def getCustomChats: util.List[CustomLMD] = Collections.unmodifiableList(lastmsgCustom)
|
||||
|
||||
class CustomLMD private(@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String,
|
||||
@NonNull val mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int,
|
||||
var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user) {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,416 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.ComponentManager;
|
||||
import buttondevteam.discordplugin.*;
|
||||
import buttondevteam.discordplugin.listeners.CommandListener;
|
||||
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
|
||||
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14;
|
||||
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener15;
|
||||
import buttondevteam.discordplugin.util.Timings;
|
||||
import buttondevteam.lib.*;
|
||||
import buttondevteam.lib.chat.ChatMessage;
|
||||
import buttondevteam.lib.chat.TBMCChatAPI;
|
||||
import buttondevteam.lib.player.TBMCPlayer;
|
||||
import com.vdurmont.emoji.EmojiParser;
|
||||
import discord4j.common.util.Snowflake;
|
||||
import discord4j.core.event.domain.message.MessageCreateEvent;
|
||||
import discord4j.core.object.Embed;
|
||||
import discord4j.core.object.entity.Attachment;
|
||||
import discord4j.core.object.entity.Guild;
|
||||
import discord4j.core.object.entity.Message;
|
||||
import discord4j.core.object.entity.User;
|
||||
import discord4j.core.object.entity.channel.GuildChannel;
|
||||
import discord4j.core.object.entity.channel.PrivateChannel;
|
||||
import discord4j.core.spec.EmbedCreateSpec;
|
||||
import discord4j.rest.util.Color;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MCChatListener implements Listener {
|
||||
private BukkitTask sendtask;
|
||||
private final LinkedBlockingQueue<AbstractMap.SimpleEntry<TBMCChatEvent, Instant>> sendevents = new LinkedBlockingQueue<>();
|
||||
private Runnable sendrunnable;
|
||||
private Thread sendthread;
|
||||
private final MinecraftChatModule module;
|
||||
private boolean stop = false; //A new instance will be created on enable
|
||||
|
||||
public MCChatListener(MinecraftChatModule minecraftChatModule) {
|
||||
module = minecraftChatModule;
|
||||
}
|
||||
|
||||
@EventHandler // Minecraft
|
||||
public void onMCChat(TBMCChatEvent ev) {
|
||||
if (!ComponentManager.isEnabled(MinecraftChatModule.class) || ev.isCancelled()) //SafeMode: Needed so it doesn't restart after server shutdown
|
||||
return;
|
||||
sendevents.add(new AbstractMap.SimpleEntry<>(ev, Instant.now()));
|
||||
if (sendtask != null)
|
||||
return;
|
||||
sendrunnable = () -> {
|
||||
sendthread = Thread.currentThread();
|
||||
processMCToDiscord();
|
||||
if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down
|
||||
sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable);
|
||||
};
|
||||
sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable);
|
||||
}
|
||||
|
||||
private void processMCToDiscord() {
|
||||
try {
|
||||
TBMCChatEvent e;
|
||||
Instant time;
|
||||
val se = sendevents.take(); // Wait until an element is available
|
||||
e = se.getKey();
|
||||
time = se.getValue();
|
||||
|
||||
final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName.get()) + "] " //
|
||||
+ ("Minecraft".equals(e.getOrigin()) ? "" : "[" + e.getOrigin().charAt(0) + "]") //
|
||||
+ (DPUtils.sanitizeStringNoEscape(ChromaUtils.getDisplayName(e.getSender())));
|
||||
val color = e.getChannel().Color.get();
|
||||
final Consumer<EmbedCreateSpec> embed = ecs -> {
|
||||
ecs.setDescription(e.getMessage()).setColor(Color.of(color.getRed(),
|
||||
color.getGreen(), color.getBlue()));
|
||||
String url = module.profileURL.get();
|
||||
if (e.getSender() instanceof Player)
|
||||
DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(),
|
||||
url.length() > 0 ? url + "?type=minecraft&id="
|
||||
+ ((Player) e.getSender()).getUniqueId() : null);
|
||||
else if (e.getSender() instanceof DiscordSenderBase)
|
||||
ecs.setAuthor(authorPlayer, url.length() > 0 ? url + "?type=discord&id="
|
||||
+ ((DiscordSenderBase) e.getSender()).getUser().getId().asString() : null,
|
||||
((DiscordSenderBase) e.getSender()).getUser().getAvatarUrl());
|
||||
else
|
||||
DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(), null);
|
||||
ecs.setTimestamp(time);
|
||||
};
|
||||
final long nanoTime = System.nanoTime();
|
||||
InterruptibleConsumer<MCChatUtils.LastMsgData> doit = lastmsgdata -> {
|
||||
if (lastmsgdata.message == null
|
||||
|| !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().map(Embed.Author::getName).orElse(null))
|
||||
|| lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120
|
||||
|| !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)
|
||||
|| lastmsgdata.content.length() + e.getMessage().length() + 1 > 2048) {
|
||||
lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block();
|
||||
lastmsgdata.time = nanoTime;
|
||||
lastmsgdata.mcchannel = e.getChannel();
|
||||
lastmsgdata.content = e.getMessage();
|
||||
} else {
|
||||
lastmsgdata.content = lastmsgdata.content + "\n"
|
||||
+ e.getMessage(); // The message object doesn't get updated
|
||||
lastmsgdata.message.edit(mes -> mes.setEmbed(embed.andThen(ecs ->
|
||||
ecs.setDescription(lastmsgdata.content)))).block();
|
||||
}
|
||||
};
|
||||
// Checks if the given channel is different than where the message was sent from
|
||||
// Or if it was from MC
|
||||
Predicate<Snowflake> isdifferentchannel = id -> !(e.getSender() instanceof DiscordSenderBase)
|
||||
|| ((DiscordSenderBase) e.getSender()).getChannel().getId().asLong() != id.asLong();
|
||||
|
||||
if (e.getChannel().isGlobal()
|
||||
&& (e.isFromCommand() || isdifferentchannel.test(module.chatChannel.get())))
|
||||
doit.accept(MCChatUtils.lastmsgdata == null
|
||||
? MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono().block(), null)
|
||||
: MCChatUtils.lastmsgdata);
|
||||
|
||||
for (MCChatUtils.LastMsgData data : MCChatPrivate.lastmsgPerUser) {
|
||||
if ((e.isFromCommand() || isdifferentchannel.test(data.channel.getId()))
|
||||
&& e.shouldSendTo(MCChatUtils.getSender(data.channel.getId(), data.user)))
|
||||
doit.accept(data);
|
||||
}
|
||||
|
||||
synchronized (MCChatCustom.lastmsgCustom) {
|
||||
val iterator = MCChatCustom.lastmsgCustom.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
val lmd = iterator.next();
|
||||
if ((e.isFromCommand() || isdifferentchannel.test(lmd.channel.getId())) //Test if msg is from Discord
|
||||
&& e.getChannel().ID.equals(lmd.mcchannel.ID) //If it's from a command, the command msg has been deleted, so we need to send it
|
||||
&& e.getGroupID().equals(lmd.groupID)) { //Check if this is the group we want to test - #58
|
||||
if (e.shouldSendTo(lmd.dcp)) //Check original user's permissions
|
||||
doit.accept(lmd);
|
||||
else {
|
||||
iterator.remove(); //If the user no longer has permission, remove the connection
|
||||
lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ex) { //Stop if interrupted anywhere
|
||||
sendtask.cancel();
|
||||
sendtask = null;
|
||||
} catch (Exception ex) {
|
||||
TBMCCoreAPI.SendException("Error while sending message to Discord!", ex, module);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChatPreprocess(TBMCChatPreprocessEvent event) {
|
||||
int start = -1;
|
||||
while ((start = event.getMessage().indexOf('@', start + 1)) != -1) {
|
||||
int mid = event.getMessage().indexOf('#', start + 1);
|
||||
if (mid == -1)
|
||||
return;
|
||||
int end_ = event.getMessage().indexOf(' ', mid + 1);
|
||||
if (end_ == -1)
|
||||
end_ = event.getMessage().length();
|
||||
final int end = end_;
|
||||
final int startF = start;
|
||||
val user = DiscordPlugin.dc.getUsers().filter(u -> u.getUsername().equals(event.getMessage().substring(startF + 1, mid)))
|
||||
.filter(u -> u.getDiscriminator().equals(event.getMessage().substring(mid + 1, end))).blockFirst();
|
||||
if (user != null) //TODO: Nicknames
|
||||
event.setMessage(event.getMessage().substring(0, startF) + "@" + user.getUsername()
|
||||
+ (event.getMessage().length() > end ? event.getMessage().substring(end) : "")); // TODO: Add formatting
|
||||
start = end; // Skip any @s inside the mention
|
||||
}
|
||||
}
|
||||
|
||||
// ......................DiscordSender....DiscordConnectedPlayer.DiscordPlayerSender
|
||||
// Offline public chat......x............................................
|
||||
// Online public chat.......x...........................................x
|
||||
// Offline private chat.....x.......................x....................
|
||||
// Online private chat......x.......................x...................x
|
||||
// If online and enabling private chat, don't login
|
||||
// If leaving the server and private chat is enabled (has ConnectedPlayer), call login in a task on lowest priority
|
||||
// If private chat is enabled and joining the server, logout the fake player on highest priority
|
||||
// If online and disabling private chat, don't logout
|
||||
// The maps may not contain the senders for UnconnectedSenders
|
||||
|
||||
/**
|
||||
* Stop the listener permanently. Enabling the module will create a new instance.
|
||||
*
|
||||
* @param wait Wait 5 seconds for the threads to stop
|
||||
*/
|
||||
public void stop(boolean wait) {
|
||||
stop = true;
|
||||
MCChatPrivate.logoutAll();
|
||||
MCChatUtils.LoggedInPlayers.clear();
|
||||
if (sendthread != null) sendthread.interrupt();
|
||||
if (recthread != null) recthread.interrupt();
|
||||
try {
|
||||
if (sendthread != null) {
|
||||
sendthread.interrupt();
|
||||
if (wait)
|
||||
sendthread.join(5000);
|
||||
}
|
||||
if (recthread != null) {
|
||||
recthread.interrupt();
|
||||
if (wait)
|
||||
recthread.join(5000);
|
||||
}
|
||||
MCChatUtils.lastmsgdata = null;
|
||||
MCChatPrivate.lastmsgPerUser.clear();
|
||||
MCChatCustom.lastmsgCustom.clear();
|
||||
MCChatUtils.lastmsgfromd.clear();
|
||||
MCChatUtils.UnconnectedSenders.clear();
|
||||
recthread = sendthread = null;
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace(); //This thread shouldn't be interrupted
|
||||
}
|
||||
}
|
||||
|
||||
private BukkitTask rectask;
|
||||
private final LinkedBlockingQueue<MessageCreateEvent> recevents = new LinkedBlockingQueue<>();
|
||||
private Runnable recrun;
|
||||
private Thread recthread;
|
||||
|
||||
// Discord
|
||||
public Mono<Boolean> handleDiscord(MessageCreateEvent ev) {
|
||||
Timings timings = CommonListeners.timings;
|
||||
timings.printElapsed("Chat event");
|
||||
val author = ev.getMessage().getAuthor();
|
||||
final boolean hasCustomChat = MCChatCustom.hasCustomChat(ev.getMessage().getChannelId());
|
||||
var prefix = DiscordPlugin.getPrefix();
|
||||
return ev.getMessage().getChannel().filter(channel -> {
|
||||
timings.printElapsed("Filter 1");
|
||||
return !(ev.getMessage().getChannelId().asLong() != module.chatChannel.get().asLong()
|
||||
&& !(channel instanceof PrivateChannel
|
||||
&& author.map(u -> MCChatPrivate.isMinecraftChatEnabled(u.getId().asString())).orElse(false))
|
||||
&& !hasCustomChat); //Chat isn't enabled on this channel
|
||||
}).filter(channel -> {
|
||||
timings.printElapsed("Filter 2");
|
||||
return !(channel instanceof PrivateChannel //Only in private chat
|
||||
&& ev.getMessage().getContent().length() < "/mcchat<>".length()
|
||||
&& ev.getMessage().getContent().replace(prefix + "", "")
|
||||
.equalsIgnoreCase("mcchat")); //Either mcchat or /mcchat
|
||||
//Allow disabling the chat if needed
|
||||
}).filterWhen(channel -> CommandListener.runCommand(ev.getMessage(), DiscordPlugin.plugin.commandChannel.get(), true))
|
||||
//Allow running commands in chat channels
|
||||
.filter(channel -> {
|
||||
MCChatUtils.resetLastMessage(channel);
|
||||
recevents.add(ev);
|
||||
timings.printElapsed("Message event added");
|
||||
if (rectask != null)
|
||||
return true;
|
||||
recrun = () -> { //Don't return in a while loop next time
|
||||
recthread = Thread.currentThread();
|
||||
processDiscordToMC();
|
||||
if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down
|
||||
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Continue message processing
|
||||
};
|
||||
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing
|
||||
return true;
|
||||
}).map(b -> false).defaultIfEmpty(true);
|
||||
}
|
||||
|
||||
private void processDiscordToMC() {
|
||||
MessageCreateEvent event;
|
||||
try {
|
||||
event = recevents.take();
|
||||
} catch (InterruptedException e1) {
|
||||
rectask.cancel();
|
||||
return;
|
||||
}
|
||||
val sender = event.getMessage().getAuthor().orElse(null);
|
||||
String dmessage = event.getMessage().getContent();
|
||||
try {
|
||||
final DiscordSenderBase dsender = MCChatUtils.getSender(event.getMessage().getChannelId(), sender);
|
||||
val user = dsender.getChromaUser();
|
||||
|
||||
for (User u : event.getMessage().getUserMentions().toIterable()) { //TODO: Role mentions
|
||||
dmessage = dmessage.replace(u.getMention(), "@" + u.getUsername()); // TODO: IG Formatting
|
||||
val m = u.asMember(DiscordPlugin.mainServer.getId()).onErrorResume(t -> Mono.empty()).blockOptional();
|
||||
if (m.isPresent()) {
|
||||
val mm = m.get();
|
||||
final String nick = mm.getDisplayName();
|
||||
dmessage = dmessage.replace(mm.getNicknameMention(), "@" + nick);
|
||||
}
|
||||
}
|
||||
for (GuildChannel ch : event.getGuild().flux().flatMap(Guild::getChannels).toIterable()) {
|
||||
dmessage = dmessage.replace(ch.getMention(), "#" + ch.getName()); // TODO: IG Formatting
|
||||
}
|
||||
|
||||
dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE); //Converts emoji to text- TODO: Add option to disable (resource pack?)
|
||||
dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:"); //Convert to Discord's format so it still shows up
|
||||
|
||||
dmessage = dmessage.replaceAll("<a?:(\\S+):(\\d+)>", ":$1:"); //We don't need info about the custom emojis, just display their text
|
||||
|
||||
Function<String, String> getChatMessage = msg -> //
|
||||
msg + (event.getMessage().getAttachments().size() > 0 ? "\n" + event.getMessage()
|
||||
.getAttachments().stream().map(Attachment::getUrl).collect(Collectors.joining("\n"))
|
||||
: "");
|
||||
|
||||
MCChatCustom.CustomLMD clmd = MCChatCustom.getCustomChat(event.getMessage().getChannelId());
|
||||
|
||||
boolean react = false;
|
||||
|
||||
val sendChannel = event.getMessage().getChannel().block();
|
||||
boolean isPrivate = sendChannel instanceof PrivateChannel;
|
||||
if (dmessage.startsWith("/")) { // Ingame command
|
||||
if (handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate)) return;
|
||||
} else {// Not a command
|
||||
react = handleIngameMessage(event, dmessage, dsender, user, getChatMessage, clmd, isPrivate);
|
||||
}
|
||||
if (react) {
|
||||
try {
|
||||
val lmfd = MCChatUtils.lastmsgfromd.get(event.getMessage().getChannelId().asLong());
|
||||
if (lmfd != null) {
|
||||
lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe(); // Remove it no matter what, we know it's there 99.99% of the time
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module);
|
||||
}
|
||||
MCChatUtils.lastmsgfromd.put(event.getMessage().getChannelId().asLong(), event.getMessage());
|
||||
event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e, module);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleIngameMessage(MessageCreateEvent event, String dmessage, DiscordSenderBase dsender, DiscordPlayer user, Function<String, String> getChatMessage, MCChatCustom.CustomLMD clmd, boolean isPrivate) {
|
||||
boolean react = false;
|
||||
if (dmessage.length() == 0 && event.getMessage().getAttachments().size() == 0
|
||||
&& !isPrivate && event.getMessage().getType() == Message.Type.CHANNEL_PINNED_MESSAGE) {
|
||||
val rtr = clmd != null ? clmd.mcchannel.getRTR(clmd.dcp)
|
||||
: dsender.getChromaUser().channel.get().getRTR(dsender);
|
||||
TBMCChatAPI.SendSystemMessage(clmd != null ? clmd.mcchannel : dsender.getChromaUser().channel.get(), rtr,
|
||||
(dsender instanceof Player ? ((Player) dsender).getDisplayName()
|
||||
: dsender.getName()) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL);
|
||||
} else {
|
||||
val cmb = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false);
|
||||
if (clmd != null)
|
||||
TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build(), clmd.mcchannel);
|
||||
else
|
||||
TBMCChatAPI.SendChatMessage(cmb.build());
|
||||
react = true;
|
||||
}
|
||||
return react;
|
||||
}
|
||||
|
||||
private boolean handleIngameCommand(MessageCreateEvent event, String dmessage, DiscordSenderBase dsender, DiscordPlayer user, MCChatCustom.CustomLMD clmd, boolean isPrivate) {
|
||||
if (!isPrivate)
|
||||
event.getMessage().delete().subscribe();
|
||||
final String cmd = dmessage.substring(1);
|
||||
final String cmdlowercased = cmd.toLowerCase();
|
||||
if (dsender instanceof DiscordSender && module.whitelistedCommands().get().stream()
|
||||
.noneMatch(s -> cmdlowercased.equals(s) || cmdlowercased.startsWith(s + " "))) {
|
||||
// Command not whitelisted
|
||||
dsender.sendMessage("Sorry, you can only access these commands from here:\n"
|
||||
+ module.whitelistedCommands().get().stream().map(uc -> "/" + uc)
|
||||
.collect(Collectors.joining(", "))
|
||||
+ (user.getConnectedID(TBMCPlayer.class) == null
|
||||
? "\nTo access your commands, first please connect your accounts, using /connect in "
|
||||
+ DPUtils.botmention()
|
||||
+ "\nThen y"
|
||||
: "\nY")
|
||||
+ "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!");
|
||||
return true;
|
||||
}
|
||||
module.log(dsender.getName() + " ran from DC: /" + cmd);
|
||||
if (dsender instanceof DiscordSender && runCustomCommand(dsender, cmdlowercased)) return true;
|
||||
val channel = clmd == null ? user.channel.get() : clmd.mcchannel;
|
||||
val ev = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, clmd == null ? dsender : clmd.dcp);
|
||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, //Commands need to be run sync
|
||||
() -> {
|
||||
Bukkit.getPluginManager().callEvent(ev);
|
||||
if (ev.isCancelled())
|
||||
return;
|
||||
try {
|
||||
String mcpackage = Bukkit.getServer().getClass().getPackage().getName();
|
||||
if (!module.enableVanillaCommands.get())
|
||||
Bukkit.dispatchCommand(dsender, cmd);
|
||||
else if (mcpackage.contains("1_12"))
|
||||
VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd);
|
||||
else if (mcpackage.contains("1_14"))
|
||||
VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd);
|
||||
else if (mcpackage.contains("1_15") || mcpackage.contains("1_16"))
|
||||
VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd);
|
||||
else
|
||||
Bukkit.dispatchCommand(dsender, cmd);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e, module);
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "! Vanilla commands are only supported in some MC versions.", e, module);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runCustomCommand(DiscordSenderBase dsender, String cmdlowercased) {
|
||||
if (cmdlowercased.startsWith("list")) {
|
||||
var players = Bukkit.getOnlinePlayers();
|
||||
dsender.sendMessage("There are " + players.stream().filter(MCChatUtils::checkEssentials).count() + " out of " + Bukkit.getMaxPlayers() + " players online.");
|
||||
dsender.sendMessage("Players: " + players.stream().filter(MCChatUtils::checkEssentials)
|
||||
.map(Player::getDisplayName).collect(Collectors.joining(", ")));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface InterruptibleConsumer<T> {
|
||||
void accept(T value) throws TimeoutException, InterruptedException;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.core.ComponentManager
|
||||
import buttondevteam.core.component.channel.Channel
|
||||
import buttondevteam.discordplugin._
|
||||
import buttondevteam.discordplugin.listeners.{CommandListener, CommonListeners}
|
||||
import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15}
|
||||
import buttondevteam.discordplugin.util.Timings
|
||||
import buttondevteam.lib._
|
||||
import buttondevteam.lib.chat.{ChatMessage, TBMCChatAPI}
|
||||
import buttondevteam.lib.player.TBMCPlayer
|
||||
import com.vdurmont.emoji.EmojiParser
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel}
|
||||
import discord4j.core.`object`.entity.{Member, Message, User}
|
||||
import discord4j.core.event.domain.message.MessageCreateEvent
|
||||
import discord4j.core.spec.{EmbedCreateSpec, MessageEditSpec}
|
||||
import discord4j.rest.util.Color
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.{EventHandler, Listener}
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
import java.time.Instant
|
||||
import java.util
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.{LinkedBlockingQueue, TimeoutException}
|
||||
import java.util.function.{Consumer, Function, Predicate}
|
||||
import java.util.stream.Collectors
|
||||
|
||||
object MCChatListener {
|
||||
|
||||
// ......................DiscordSender....DiscordConnectedPlayer.DiscordPlayerSender
|
||||
// Offline public chat......x............................................
|
||||
// Online public chat.......x...........................................x
|
||||
// Offline private chat.....x.......................x....................
|
||||
// Online private chat......x.......................x...................x
|
||||
// If online and enabling private chat, don't login
|
||||
// If leaving the server and private chat is enabled (has ConnectedPlayer), call login in a task on lowest priority
|
||||
// If private chat is enabled and joining the server, logout the fake player on highest priority
|
||||
// If online and disabling private chat, don't logout
|
||||
// The maps may not contain the senders for UnconnectedSenders
|
||||
@FunctionalInterface private trait InterruptibleConsumer[T] {
|
||||
@throws[TimeoutException]
|
||||
@throws[InterruptedException]
|
||||
def accept(value: T)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MCChatListener(val module: MinecraftChatModule) extends Listener {
|
||||
private var sendtask: BukkitTask = null
|
||||
final private val sendevents = new LinkedBlockingQueue[util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant]]
|
||||
private var sendrunnable: Runnable = null
|
||||
private var sendthread: Thread = null
|
||||
private var stop = false //A new instance will be created on enable
|
||||
@EventHandler // Minecraft
|
||||
def onMCChat(ev: TBMCChatEvent): Unit = {
|
||||
if (!(ComponentManager.isEnabled(classOf[MinecraftChatModule])) || ev.isCancelled) { //SafeMode: Needed so it doesn't restart after server shutdown
|
||||
return
|
||||
}
|
||||
|
||||
sendevents.add(new util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant](ev, Instant.now))
|
||||
if (sendtask != null) {
|
||||
return
|
||||
}
|
||||
sendrunnable = () => {
|
||||
def foo(): Unit = {
|
||||
sendthread = Thread.currentThread
|
||||
processMCToDiscord()
|
||||
if (DiscordPlugin.plugin.isEnabled && !(stop)) { //Don't run again if shutting down
|
||||
sendtask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable)
|
||||
}
|
||||
}
|
||||
|
||||
foo()
|
||||
}
|
||||
sendtask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable)
|
||||
}
|
||||
|
||||
private def processMCToDiscord(): Unit = {
|
||||
try {
|
||||
var e: TBMCChatEvent = null
|
||||
var time: Instant = null
|
||||
val se: util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant] = sendevents.take // Wait until an element is available
|
||||
e = se.getKey
|
||||
time = se.getValue
|
||||
val authorPlayer: String = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel.DisplayName.get) + "] " + //
|
||||
(if ("Minecraft" == e.getOrigin) "" else "[" + e.getOrigin.charAt(0) + "]") +
|
||||
DPUtils.sanitizeStringNoEscape(ChromaUtils.getDisplayName(e.getSender))
|
||||
val color: chat.Color = e.getChannel.Color.get
|
||||
val embed: Consumer[EmbedCreateSpec] = (ecs: EmbedCreateSpec) => {
|
||||
def foo(ecs: EmbedCreateSpec) = {
|
||||
ecs.setDescription(e.getMessage).setColor(Color.of(color.getRed, color.getGreen, color.getBlue))
|
||||
val url: String = module.profileURL.get
|
||||
e.getSender match {
|
||||
case player: Player =>
|
||||
DPUtils.embedWithHead(ecs, authorPlayer, e.getSender.getName,
|
||||
if (url.nonEmpty) url + "?type=minecraft&id=" + player.getUniqueId else null)
|
||||
case dsender: DiscordSenderBase =>
|
||||
ecs.setAuthor(authorPlayer,
|
||||
if (url.nonEmpty) url + "?type=discord&id=" + dsender.getUser.getId.asString else null,
|
||||
dsender.getUser.getAvatarUrl)
|
||||
case _ =>
|
||||
DPUtils.embedWithHead(ecs, authorPlayer, e.getSender.getName, null)
|
||||
}
|
||||
ecs.setTimestamp(time)
|
||||
}
|
||||
|
||||
foo(ecs)
|
||||
}
|
||||
val nanoTime: Long = System.nanoTime
|
||||
val doit: MCChatListener.InterruptibleConsumer[MCChatUtils.LastMsgData] = (lastmsgdata: MCChatUtils.LastMsgData) => {
|
||||
def foo(lastmsgdata: MCChatUtils.LastMsgData): Unit = {
|
||||
if (lastmsgdata.message == null || !(authorPlayer == lastmsgdata.message.getEmbeds.get(0).getAuthor.map(_.getName).orElse(null)) || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 || !(lastmsgdata.mcchannel.ID == e.getChannel.ID) || lastmsgdata.content.length + e.getMessage.length + 1 > 2048) {
|
||||
lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block
|
||||
lastmsgdata.time = nanoTime
|
||||
lastmsgdata.mcchannel = e.getChannel
|
||||
lastmsgdata.content = e.getMessage
|
||||
}
|
||||
else {
|
||||
lastmsgdata.content = lastmsgdata.content + "\n" + e.getMessage // The message object doesn't get updated
|
||||
lastmsgdata.message.edit((mes: MessageEditSpec) => mes.setEmbed(embed.andThen((ecs: EmbedCreateSpec) => ecs.setDescription(lastmsgdata.content)))).block
|
||||
}
|
||||
}
|
||||
|
||||
foo(lastmsgdata)
|
||||
}
|
||||
// Checks if the given channel is different than where the message was sent from
|
||||
// Or if it was from MC
|
||||
val isdifferentchannel: Predicate[Snowflake] = (id: Snowflake) => !((e.getSender.isInstanceOf[DiscordSenderBase])) || (e.getSender.asInstanceOf[DiscordSenderBase]).getChannel.getId.asLong != id.asLong
|
||||
if (e.getChannel.isGlobal && (e.isFromCommand || isdifferentchannel.test(module.chatChannel.get))) {
|
||||
if (MCChatUtils.lastmsgdata == null)
|
||||
MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null)
|
||||
doit.accept(MCChatUtils.lastmsgdata)
|
||||
}
|
||||
|
||||
for (data <- MCChatPrivate.lastmsgPerUser) {
|
||||
if ((e.isFromCommand || isdifferentchannel.test(data.channel.getId)) && e.shouldSendTo(MCChatUtils.getSender(data.channel.getId, data.user))) {
|
||||
doit.accept(data)
|
||||
}
|
||||
}
|
||||
MCChatCustom.lastmsgCustom synchronized
|
||||
val iterator = MCChatCustom.lastmsgCustom.iterator
|
||||
while ( {
|
||||
iterator.hasNext
|
||||
}) {
|
||||
val lmd = iterator.next
|
||||
if ((e.isFromCommand || isdifferentchannel.test(lmd.channel.getId)) //Test if msg is from Discord
|
||||
&& e.getChannel.ID == lmd.mcchannel.ID //If it's from a command, the command msg has been deleted, so we need to send it
|
||||
&& e.getGroupID == lmd.groupID) { //Check if this is the group we want to test - #58
|
||||
if (e.shouldSendTo(lmd.dcp)) { //Check original user's permissions
|
||||
doit.accept(lmd)
|
||||
}
|
||||
else {
|
||||
iterator.remove() //If the user no longer has permission, remove the connection
|
||||
lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: InterruptedException =>
|
||||
//Stop if interrupted anywhere
|
||||
sendtask.cancel()
|
||||
sendtask = null
|
||||
case ex: Exception =>
|
||||
TBMCCoreAPI.SendException("Error while sending message to Discord!", ex, module)
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler def onChatPreprocess(event: TBMCChatPreprocessEvent): Unit = {
|
||||
var start: Int = -(1)
|
||||
while ( {
|
||||
(start = event.getMessage.indexOf('@', start + 1), start) != ((), -1)
|
||||
}) {
|
||||
val mid: Int = event.getMessage.indexOf('#', start + 1)
|
||||
if (mid == -1) {
|
||||
return
|
||||
}
|
||||
var end_ = event.getMessage.indexOf(' ', mid + 1)
|
||||
if (end_ == -1) {
|
||||
end_ = event.getMessage.length
|
||||
}
|
||||
val end: Int = end_
|
||||
val startF: Int = start
|
||||
val user = DiscordPlugin.dc.getUsers.filter((u) => u.getUsername.equals(event.getMessage.substring(startF + 1, mid))).filter((u) => u.getDiscriminator.equals(event.getMessage.substring(mid + 1, end))).blockFirst
|
||||
if (user != null) { //TODO: Nicknames
|
||||
event.setMessage(event.getMessage.substring(0, startF) + "@" + user.getUsername + (if (event.getMessage.length > end) {
|
||||
event.getMessage.substring(end)
|
||||
}
|
||||
else {
|
||||
""
|
||||
})) // TODO: Add formatting
|
||||
}
|
||||
start = end // Skip any @s inside the mention
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the listener permanently. Enabling the module will create a new instance.
|
||||
*
|
||||
* @param wait Wait 5 seconds for the threads to stop
|
||||
*/
|
||||
def stop(wait: Boolean): Unit = {
|
||||
stop = true
|
||||
MCChatPrivate.logoutAll()
|
||||
MCChatUtils.LoggedInPlayers.clear()
|
||||
if (sendthread != null) {
|
||||
sendthread.interrupt()
|
||||
}
|
||||
if (recthread != null) {
|
||||
recthread.interrupt()
|
||||
}
|
||||
try {
|
||||
if (sendthread != null) {
|
||||
sendthread.interrupt()
|
||||
if (wait) {
|
||||
sendthread.join(5000)
|
||||
}
|
||||
}
|
||||
if (recthread != null) {
|
||||
recthread.interrupt()
|
||||
if (wait) {
|
||||
recthread.join(5000)
|
||||
}
|
||||
}
|
||||
MCChatUtils.lastmsgdata = null
|
||||
MCChatPrivate.lastmsgPerUser.clear()
|
||||
MCChatCustom.lastmsgCustom.clear()
|
||||
MCChatUtils.lastmsgfromd.clear()
|
||||
MCChatUtils.UnconnectedSenders.clear()
|
||||
recthread = null
|
||||
sendthread = null
|
||||
} catch {
|
||||
case e: InterruptedException =>
|
||||
e.printStackTrace() //This thread shouldn't be interrupted
|
||||
}
|
||||
}
|
||||
|
||||
private var rectask: BukkitTask = null
|
||||
final private val recevents: LinkedBlockingQueue[MessageCreateEvent] = new LinkedBlockingQueue[MessageCreateEvent]
|
||||
private var recrun: Runnable = null
|
||||
private var recthread: Thread = null
|
||||
|
||||
// Discord
|
||||
def handleDiscord(ev: MessageCreateEvent): Mono[Boolean] = {
|
||||
val timings: Timings = CommonListeners.timings
|
||||
timings.printElapsed("Chat event")
|
||||
val author: Optional[User] = ev.getMessage.getAuthor
|
||||
val hasCustomChat: Boolean = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId)
|
||||
val prefix: Char = DiscordPlugin.getPrefix
|
||||
return ev.getMessage.getChannel.filter((channel: MessageChannel) => {
|
||||
def foo(channel: MessageChannel) = {
|
||||
timings.printElapsed("Filter 1")
|
||||
return !((ev.getMessage.getChannelId.asLong != module.chatChannel.get.asLong && !((channel.isInstanceOf[PrivateChannel] && author.map((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)).orElse(false))) && !(hasCustomChat))) //Chat isn't enabled on this channel
|
||||
}
|
||||
|
||||
foo(channel)
|
||||
}).filter((channel: MessageChannel) => {
|
||||
def foo(channel: MessageChannel) = {
|
||||
timings.printElapsed("Filter 2")
|
||||
return !((channel.isInstanceOf[PrivateChannel] //Only in private chat && ev.getMessage.getContent.length < "/mcchat<>".length && ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")))//Either mcchat or /mcchat
|
||||
//Allow disabling the chat if needed
|
||||
}
|
||||
|
||||
foo(channel)
|
||||
}).filterWhen((channel: MessageChannel) => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, true)).filter //Allow running commands in chat channels
|
||||
((channel: MessageChannel) => {
|
||||
def foo(channel: MessageChannel) = {
|
||||
MCChatUtils.resetLastMessage(channel)
|
||||
recevents.add(ev)
|
||||
timings.printElapsed("Message event added")
|
||||
if (rectask != null) {
|
||||
return true
|
||||
}
|
||||
recrun = () => {
|
||||
def foo() = { //Don't return in a while loop next time
|
||||
recthread = Thread.currentThread
|
||||
processDiscordToMC()
|
||||
if (DiscordPlugin.plugin.isEnabled && !(stop)) {
|
||||
rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing
|
||||
}
|
||||
}
|
||||
|
||||
foo()
|
||||
}
|
||||
rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing
|
||||
return true
|
||||
}
|
||||
|
||||
foo(channel)
|
||||
}).map((b: MessageChannel) => false).defaultIfEmpty(true)
|
||||
}
|
||||
|
||||
private def processDiscordToMC(): Unit = {
|
||||
var event: MessageCreateEvent = null
|
||||
try event = recevents.take
|
||||
catch {
|
||||
case e1: InterruptedException =>
|
||||
rectask.cancel()
|
||||
return
|
||||
}
|
||||
val sender: User = event.getMessage.getAuthor.orElse(null)
|
||||
var dmessage: String = event.getMessage.getContent
|
||||
try {
|
||||
val dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender)
|
||||
val user: DiscordPlayer = dsender.getChromaUser
|
||||
|
||||
for (u <- event.getMessage.getUserMentions.toIterable) { //TODO: Role mentions
|
||||
dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting
|
||||
val m: Optional[Member] = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional
|
||||
if (m.isPresent) {
|
||||
val mm: Member = m.get
|
||||
val nick: String = mm.getDisplayName
|
||||
dmessage = dmessage.replace(mm.getNicknameMention, "@" + nick)
|
||||
}
|
||||
}
|
||||
|
||||
for (ch <- event.getGuild.flux.flatMap(_.getChannels).toIterable) {
|
||||
dmessage = dmessage.replace(ch.getMention, "#" + ch.getName)
|
||||
}
|
||||
dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?)
|
||||
dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:") //Convert to Discord's format so it still shows up
|
||||
dmessage = dmessage.replaceAll("<a?:(\\S+):(\\d+)>", ":$1:") //We don't need info about the custom emojis, just display their text
|
||||
val getChatMessage: Function[String, String] = (msg: String) => //
|
||||
msg + (if (event.getMessage.getAttachments.size > 0) {
|
||||
"\n" + event.getMessage.getAttachments.stream.map(_.getUrl).collect(Collectors.joining("\n"))
|
||||
}
|
||||
else {
|
||||
""
|
||||
})
|
||||
val clmd: MCChatCustom.CustomLMD = MCChatCustom.getCustomChat(event.getMessage.getChannelId)
|
||||
var react: Boolean = false
|
||||
val sendChannel: MessageChannel = event.getMessage.getChannel.block
|
||||
val isPrivate: Boolean = sendChannel.isInstanceOf[PrivateChannel]
|
||||
if (dmessage.startsWith("/")) { // Ingame command
|
||||
if (handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
else { // Not a command
|
||||
react = handleIngameMessage(event, dmessage, dsender, user, getChatMessage, clmd, isPrivate)
|
||||
}
|
||||
if (react) {
|
||||
try {
|
||||
val lmfd: Message = MCChatUtils.lastmsgfromd.get(event.getMessage.getChannelId.asLong)
|
||||
if (lmfd != null) {
|
||||
lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe // Remove it no matter what, we know it's there 99.99% of the time
|
||||
}
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module)
|
||||
}
|
||||
MCChatUtils.lastmsgfromd.put(event.getMessage.getChannelId.asLong, event.getMessage)
|
||||
event.getMessage.addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe
|
||||
}
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e, module)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleIngameMessage(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, getChatMessage: Function[String, String], clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = {
|
||||
var react: Boolean = false
|
||||
if (dmessage.isEmpty && event.getMessage.getAttachments.size == 0 && !(isPrivate) && (event.getMessage.getType eq Message.Type.CHANNEL_PINNED_MESSAGE)) {
|
||||
val rtr: Channel.RecipientTestResult = if (clmd != null) {
|
||||
clmd.mcchannel.getRTR(clmd.dcp)
|
||||
}
|
||||
else {
|
||||
dsender.getChromaUser.channel.get.getRTR(dsender)
|
||||
}
|
||||
TBMCChatAPI.SendSystemMessage(if (clmd != null) clmd.mcchannel else dsender.getChromaUser.channel.get, rtr,
|
||||
(dsender match {
|
||||
case player: Player =>
|
||||
player.getDisplayName
|
||||
case _ =>
|
||||
dsender.getName
|
||||
}) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL)
|
||||
}
|
||||
else {
|
||||
val cmb: ChatMessage.ChatMessageBuilder = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false)
|
||||
if (clmd != null) {
|
||||
TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build, clmd.mcchannel)
|
||||
}
|
||||
else {
|
||||
TBMCChatAPI.SendChatMessage(cmb.build)
|
||||
}
|
||||
react = true
|
||||
}
|
||||
return react
|
||||
}
|
||||
|
||||
private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = {
|
||||
if (!(isPrivate)) {
|
||||
event.getMessage.delete.subscribe
|
||||
}
|
||||
val cmd: String = dmessage.substring(1)
|
||||
val cmdlowercased: String = cmd.toLowerCase
|
||||
if (dsender.isInstanceOf[DiscordSender] && module.whitelistedCommands.get.stream.noneMatch((s: String) => cmdlowercased == s || cmdlowercased.startsWith(s + " "))) { // Command not whitelisted
|
||||
dsender.sendMessage("Sorry, you can only access these commands from here:\n" + module.whitelistedCommands.get.stream.map((uc: String) => "/" + uc).collect(Collectors.joining(", ")) + (if (user.getConnectedID(classOf[TBMCPlayer]) == null) {
|
||||
"\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + "\nThen y"
|
||||
}
|
||||
else {
|
||||
"\nY"
|
||||
}) + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!")
|
||||
return true
|
||||
}
|
||||
module.log(dsender.getName + " ran from DC: /" + cmd)
|
||||
if (dsender.isInstanceOf[DiscordSender] && runCustomCommand(dsender, cmdlowercased)) {
|
||||
return true
|
||||
}
|
||||
val channel: Channel = if (clmd == null) {
|
||||
user.channel.get
|
||||
}
|
||||
else {
|
||||
clmd.mcchannel
|
||||
}
|
||||
val ev: TBMCCommandPreprocessEvent = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, if (clmd == null) {
|
||||
dsender
|
||||
}
|
||||
else {
|
||||
clmd.dcp
|
||||
})
|
||||
Bukkit.getScheduler.runTask(DiscordPlugin.plugin, //Commands need to be run sync
|
||||
() => {
|
||||
def foo(): Unit = {
|
||||
Bukkit.getPluginManager.callEvent(ev)
|
||||
if (ev.isCancelled) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val mcpackage: String = Bukkit.getServer.getClass.getPackage.getName
|
||||
if (!(module.enableVanillaCommands.get)) {
|
||||
Bukkit.dispatchCommand(dsender, cmd)
|
||||
}
|
||||
else {
|
||||
if (mcpackage.contains("1_12")) {
|
||||
VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd)
|
||||
}
|
||||
else {
|
||||
if (mcpackage.contains("1_14")) {
|
||||
VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd)
|
||||
}
|
||||
else {
|
||||
if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) {
|
||||
VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd)
|
||||
}
|
||||
else {
|
||||
Bukkit.dispatchCommand(dsender, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: NoClassDefFoundError =>
|
||||
TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e, module)
|
||||
case e: Exception =>
|
||||
TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "! Vanilla commands are only supported in some MC versions.", e, module)
|
||||
}
|
||||
}
|
||||
|
||||
foo()
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
private def runCustomCommand(dsender: DiscordSenderBase, cmdlowercased: String): Boolean = {
|
||||
if (cmdlowercased.startsWith("list")) {
|
||||
val players: util.Collection[_ <: Player] = Bukkit.getOnlinePlayers
|
||||
dsender.sendMessage("There are " + players.stream.filter(MCChatUtils.checkEssentials).count + " out of " + Bukkit.getMaxPlayers + " players online.")
|
||||
dsender.sendMessage("Players: " + players.stream.filter(MCChatUtils.checkEssentials).map(Player.getDisplayName).collect(Collectors.joining(", ")))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.ComponentManager;
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||
import buttondevteam.lib.player.TBMCPlayer;
|
||||
import discord4j.core.object.entity.User;
|
||||
import discord4j.core.object.entity.channel.MessageChannel;
|
||||
import discord4j.core.object.entity.channel.PrivateChannel;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MCChatPrivate {
|
||||
|
||||
/**
|
||||
* Used for messages in PMs (mcchat).
|
||||
*/
|
||||
static ArrayList<MCChatUtils.LastMsgData> lastmsgPerUser = new ArrayList<>();
|
||||
|
||||
public static boolean privateMCChat(MessageChannel channel, boolean start, User user, DiscordPlayer dp) {
|
||||
synchronized (MCChatUtils.ConnectedSenders) {
|
||||
TBMCPlayer mcp = dp.getAs(TBMCPlayer.class);
|
||||
if (mcp != null) { // If the accounts aren't connected, can't make a connected sender
|
||||
val p = Bukkit.getPlayer(mcp.getUUID());
|
||||
val op = Bukkit.getOfflinePlayer(mcp.getUUID());
|
||||
val mcm = ComponentManager.getIfEnabled(MinecraftChatModule.class);
|
||||
if (start) {
|
||||
val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID(), op.getName(), mcm);
|
||||
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
|
||||
MCChatUtils.LoggedInPlayers.put(mcp.getUUID(), sender);
|
||||
if (p == null) // Player is offline - If the player is online, that takes precedence
|
||||
MCChatUtils.callLoginEvents(sender);
|
||||
} else {
|
||||
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
|
||||
assert sender != null;
|
||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
|
||||
if ((p == null || p instanceof DiscordSenderBase) // 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, false); //The next line has to run *after* this one, so can't use the needsSync parameter
|
||||
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId());
|
||||
sender.setLoggedIn(false);
|
||||
});
|
||||
}
|
||||
} // ---- PermissionsEx warning is normal on logout ----
|
||||
if (!start)
|
||||
MCChatUtils.lastmsgfromd.remove(channel.getId().asLong());
|
||||
return start //
|
||||
? lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs
|
||||
: lastmsgPerUser.removeIf(lmd -> lmd.channel.getId().asLong() == channel.getId().asLong());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isMinecraftChatEnabled(DiscordPlayer dp) {
|
||||
return isMinecraftChatEnabled(dp.getDiscordID());
|
||||
}
|
||||
|
||||
public static boolean isMinecraftChatEnabled(String did) { // Don't load the player data just for this
|
||||
return lastmsgPerUser.stream()
|
||||
.anyMatch(lmd -> ((PrivateChannel) lmd.channel)
|
||||
.getRecipientIds().stream().anyMatch(u -> u.asString().equals(did)));
|
||||
}
|
||||
|
||||
public static void logoutAll() {
|
||||
synchronized (MCChatUtils.ConnectedSenders) {
|
||||
for (val entry : MCChatUtils.ConnectedSenders.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
|
||||
MCChatUtils.callLogoutEvent(valueEntry.getValue(), !Bukkit.isPrimaryThread());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.core.ComponentManager
|
||||
import buttondevteam.discordplugin.{DiscordConnectedPlayer, DiscordPlayer, DiscordPlugin, DiscordSenderBase}
|
||||
import buttondevteam.lib.player.TBMCPlayer
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.User
|
||||
import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel}
|
||||
import org.bukkit.Bukkit
|
||||
|
||||
import java.util
|
||||
|
||||
object MCChatPrivate {
|
||||
/**
|
||||
* Used for messages in PMs (mcchat).
|
||||
*/
|
||||
private[mcchat] val lastmsgPerUser = new util.ArrayList[MCChatUtils.LastMsgData]
|
||||
|
||||
def privateMCChat(channel: MessageChannel, start: Boolean, user: User, dp: DiscordPlayer): Unit = {
|
||||
MCChatUtils.ConnectedSenders synchronized
|
||||
val mcp = dp.getAs(classOf[TBMCPlayer])
|
||||
if (mcp != null) { // If the accounts aren't connected, can't make a connected sender
|
||||
val p = Bukkit.getPlayer(mcp.getUUID)
|
||||
val op = Bukkit.getOfflinePlayer(mcp.getUUID)
|
||||
val mcm = ComponentManager.getIfEnabled(classOf[MinecraftChatModule])
|
||||
if (start) {
|
||||
val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID, op.getName, mcm)
|
||||
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender)
|
||||
MCChatUtils.LoggedInPlayers.put(mcp.getUUID, sender)
|
||||
if (p == null) { // Player is offline - If the player is online, that takes precedence
|
||||
MCChatUtils.callLoginEvents(sender)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId, user)
|
||||
assert(sender != null)
|
||||
Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => {
|
||||
def foo(): Unit = {
|
||||
if ((p == null || p.isInstanceOf[DiscordSenderBase]) // 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, false) //The next line has to run *after* this one, so can't use the needsSync parameter
|
||||
}
|
||||
|
||||
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId)
|
||||
sender.setLoggedIn(false)
|
||||
}
|
||||
|
||||
foo()
|
||||
}
|
||||
)
|
||||
}
|
||||
// ---- PermissionsEx warning is normal on logout ----
|
||||
}
|
||||
if (!start) MCChatUtils.lastmsgfromd.remove(channel.getId.asLong)
|
||||
if (start) lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs
|
||||
else lastmsgPerUser.removeIf((lmd: MCChatUtils.LastMsgData) => lmd.channel.getId.asLong == channel.getId.asLong)
|
||||
}
|
||||
|
||||
def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID)
|
||||
|
||||
def isMinecraftChatEnabled(did: String): Boolean = { // Don't load the player data just for this
|
||||
lastmsgPerUser.stream.anyMatch((lmd: MCChatUtils.LastMsgData) =>
|
||||
lmd.channel.asInstanceOf[PrivateChannel].getRecipientIds.stream.anyMatch((u: Snowflake) => u.asString == did))
|
||||
}
|
||||
|
||||
def logoutAll(): Unit = {
|
||||
MCChatUtils.ConnectedSenders synchronized
|
||||
for (entry <- MCChatUtils.ConnectedSenders.entrySet) {
|
||||
for (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
|
||||
MCChatUtils.callLogoutEvent(valueEntry.getValue, !Bukkit.isPrimaryThread)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.ComponentManager;
|
||||
import buttondevteam.core.MainPlugin;
|
||||
import buttondevteam.discordplugin.*;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
import com.google.common.collect.Sets;
|
||||
import discord4j.common.util.Snowflake;
|
||||
import discord4j.core.object.entity.Message;
|
||||
import discord4j.core.object.entity.User;
|
||||
import discord4j.core.object.entity.channel.Channel;
|
||||
import discord4j.core.object.entity.channel.MessageChannel;
|
||||
import discord4j.core.object.entity.channel.PrivateChannel;
|
||||
import discord4j.core.object.entity.channel.TextChannel;
|
||||
import io.netty.util.collection.LongObjectHashMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
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.Plugin;
|
||||
import org.bukkit.plugin.RegisteredListener;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MCChatUtils {
|
||||
/**
|
||||
* May contain P<DiscordID> as key for public chat
|
||||
*/
|
||||
public static final ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, DiscordSender>> UnconnectedSenders = new ConcurrentHashMap<>();
|
||||
public static final ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, DiscordConnectedPlayer>> ConnectedSenders = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* May contain P<DiscordID> as key for public chat
|
||||
*/
|
||||
public static final ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, DiscordPlayerSender>> OnlineSenders = new ConcurrentHashMap<>();
|
||||
public static final ConcurrentHashMap<UUID, DiscordConnectedPlayer> LoggedInPlayers = new ConcurrentHashMap<>();
|
||||
static @Nullable LastMsgData lastmsgdata;
|
||||
static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
|
||||
private static MinecraftChatModule module;
|
||||
private static final HashMap<Class<? extends Event>, HashSet<String>> staticExcludedPlugins = new HashMap<>();
|
||||
|
||||
public static void updatePlayerList() {
|
||||
val mod = getModule();
|
||||
if (mod == null || !mod.showPlayerListOnDC.get()) return;
|
||||
if (lastmsgdata != null)
|
||||
updatePL(lastmsgdata);
|
||||
MCChatCustom.lastmsgCustom.forEach(MCChatUtils::updatePL);
|
||||
}
|
||||
|
||||
private static boolean notEnabled() {
|
||||
return (module == null || !module.disabling) && getModule() == null; //Allow using things while disabling the module
|
||||
}
|
||||
|
||||
private static MinecraftChatModule getModule() {
|
||||
if (module == null || !module.isEnabled()) module = ComponentManager.getIfEnabled(MinecraftChatModule.class);
|
||||
//If disabled, it will try to get it again because another instance may be enabled - useful for /discord restart
|
||||
return module;
|
||||
}
|
||||
|
||||
private static void updatePL(LastMsgData lmd) {
|
||||
if (!(lmd.channel instanceof TextChannel)) {
|
||||
TBMCCoreAPI.SendException("Failed to update player list for channel " + lmd.channel.getId(),
|
||||
new Exception("The channel isn't a (guild) text channel."), getModule());
|
||||
return;
|
||||
}
|
||||
String topic = ((TextChannel) lmd.channel).getTopic().orElse("");
|
||||
if (topic.length() == 0)
|
||||
topic = ".\n----\nMinecraft chat\n----\n.";
|
||||
String[] s = topic.split("\\n----\\n");
|
||||
if (s.length < 3)
|
||||
return;
|
||||
String gid;
|
||||
if (lmd instanceof MCChatCustom.CustomLMD)
|
||||
gid = ((MCChatCustom.CustomLMD) lmd).groupID;
|
||||
else //If we're not using a custom chat then it's either can ("everyone") or can't (null) see at most
|
||||
gid = buttondevteam.core.component.channel.Channel.GROUP_EVERYONE; // (Though it's a public chat then rn)
|
||||
AtomicInteger C = new AtomicInteger();
|
||||
s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream()
|
||||
.filter(p -> (lmd.mcchannel == null
|
||||
? gid.equals(buttondevteam.core.component.channel.Channel.GROUP_EVERYONE) //If null, allow if public (custom chats will have their channel stored anyway)
|
||||
: gid.equals(lmd.mcchannel.getGroupID(p)))) //If they can see it
|
||||
.filter(MCChatUtils::checkEssentials)
|
||||
.filter(p -> C.incrementAndGet() > 0) //Always true
|
||||
.map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", "));
|
||||
s[0] = C + " player" + (C.get() != 1 ? "s" : "") + " online";
|
||||
((TextChannel) lmd.channel).edit(tce -> tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe(); //Don't wait
|
||||
}
|
||||
|
||||
static boolean checkEssentials(Player p) {
|
||||
var ess = MainPlugin.ess;
|
||||
if (ess == null) return true;
|
||||
return !ess.getUser(p).isHidden();
|
||||
}
|
||||
|
||||
public static <T extends DiscordSenderBase> T addSender(ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, T>> senders,
|
||||
User user, T sender) {
|
||||
return addSender(senders, user.getId().asString(), sender);
|
||||
}
|
||||
|
||||
public static <T extends DiscordSenderBase> T addSender(ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, T>> senders,
|
||||
String did, T sender) {
|
||||
var map = senders.get(did);
|
||||
if (map == null)
|
||||
map = new ConcurrentHashMap<>();
|
||||
map.put(sender.getChannel().getId(), sender);
|
||||
senders.put(did, map);
|
||||
return sender;
|
||||
}
|
||||
|
||||
public static <T extends DiscordSenderBase> T getSender(ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, T>> senders,
|
||||
Snowflake channel, User user) {
|
||||
var map = senders.get(user.getId().asString());
|
||||
if (map != null)
|
||||
return map.get(channel);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends DiscordSenderBase> T removeSender(ConcurrentHashMap<String, ConcurrentHashMap<Snowflake, T>> senders,
|
||||
Snowflake channel, User user) {
|
||||
var map = senders.get(user.getId().asString());
|
||||
if (map != null)
|
||||
return map.remove(channel);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Mono<?> forPublicPrivateChat(Function<Mono<MessageChannel>, Mono<?>> action) {
|
||||
if (notEnabled()) return Mono.empty();
|
||||
var list = new ArrayList<Mono<?>>();
|
||||
list.add(action.apply(module.chatChannelMono()));
|
||||
for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
|
||||
list.add(action.apply(Mono.just(data.channel)));
|
||||
// lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
|
||||
return Mono.whenDelayError(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* For custom and all MC chat
|
||||
*
|
||||
* @param action The action to act (cannot complete empty)
|
||||
* @param toggle The toggle to check
|
||||
* @param hookmsg Whether the message is also sent from the hook
|
||||
*/
|
||||
public static Mono<?> forCustomAndAllMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
|
||||
if (notEnabled()) return Mono.empty();
|
||||
var list = new ArrayList<Publisher<?>>();
|
||||
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
|
||||
list.add(forPublicPrivateChat(action));
|
||||
final Function<MCChatCustom.CustomLMD, Publisher<?>> customLMDFunction = cc -> action.apply(Mono.just(cc.channel));
|
||||
if (toggle == null)
|
||||
MCChatCustom.lastmsgCustom.stream().map(customLMDFunction).forEach(list::add);
|
||||
else
|
||||
MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).map(customLMDFunction).forEach(list::add);
|
||||
return Mono.whenDelayError(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
|
||||
*
|
||||
* @param action The action to do
|
||||
* @param sender The sender to check perms of or null to send to all that has it toggled
|
||||
* @param toggle The toggle to check or null to send to all allowed
|
||||
*/
|
||||
public static Mono<?> forAllowedCustomMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
|
||||
if (notEnabled()) return Mono.empty();
|
||||
Stream<Publisher<?>> st = MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
|
||||
//new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
|
||||
if (toggle != null && (clmd.toggles & toggle.flag) == 0)
|
||||
return false; //If null then allow
|
||||
if (sender == null)
|
||||
return true;
|
||||
return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender));
|
||||
}).map(cc -> action.apply(Mono.just(cc.channel))); //TODO: Send error messages on channel connect
|
||||
return Mono.whenDelayError(st::iterator); //Can't convert as an iterator or inside the stream, but I can convert it as a stream
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
|
||||
*
|
||||
* @param action The action to do
|
||||
* @param sender The sender to check perms of or null to send to all that has it toggled
|
||||
* @param toggle The toggle to check or null to send to all allowed
|
||||
* @param hookmsg Whether the message is also sent from the hook
|
||||
*/
|
||||
public static Mono<?> forAllowedCustomAndAllMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
|
||||
if (notEnabled()) return Mono.empty();
|
||||
var cc = forAllowedCustomMCChat(action, sender, toggle);
|
||||
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
|
||||
return Mono.whenDelayError(forPublicPrivateChat(action), cc);
|
||||
return Mono.whenDelayError(cc);
|
||||
}
|
||||
|
||||
public static Function<Mono<MessageChannel>, Mono<?>> send(String message) {
|
||||
return ch -> ch.flatMap(mc -> {
|
||||
resetLastMessage(mc);
|
||||
return mc.createMessage(DPUtils.sanitizeString(message));
|
||||
});
|
||||
}
|
||||
|
||||
public static Mono<?> forAllowedMCChat(Function<Mono<MessageChannel>, Mono<?>> action, TBMCSystemChatEvent event) {
|
||||
if (notEnabled()) return Mono.empty();
|
||||
var list = new ArrayList<Mono<?>>();
|
||||
if (event.getChannel().isGlobal())
|
||||
list.add(action.apply(module.chatChannelMono()));
|
||||
for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
|
||||
if (event.shouldSendTo(getSender(data.channel.getId(), data.user)))
|
||||
list.add(action.apply(Mono.just(data.channel))); //TODO: Only store ID?
|
||||
MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
|
||||
if (!clmd.brtoggles.contains(event.getTarget()))
|
||||
return false;
|
||||
return event.shouldSendTo(clmd.dcp);
|
||||
}).map(clmd -> action.apply(Mono.just(clmd.channel))).forEach(list::add);
|
||||
return Mono.whenDelayError(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will find the best sender to use: if the player is online, use that, if not but connected then use that etc.
|
||||
*/
|
||||
static DiscordSenderBase getSender(Snowflake channel, final User author) {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
return Stream.<Supplier<Optional<DiscordSenderBase>>>of( // https://stackoverflow.com/a/28833677/2703239
|
||||
() -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null
|
||||
() -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it
|
||||
() -> Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), //
|
||||
() -> Optional.of(addSender(UnconnectedSenders, author,
|
||||
new DiscordSender(author, (MessageChannel) DiscordPlugin.dc.getChannelById(channel).block())))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the last message, so it will start a new one instead of appending to it.
|
||||
* This is used when someone (even the bot) sends a message to the channel.
|
||||
*
|
||||
* @param channel The channel to reset in - the process is slightly different for the public, private and custom chats
|
||||
*/
|
||||
public static void resetLastMessage(Channel channel) {
|
||||
if (notEnabled()) return;
|
||||
if (channel.getId().asLong() == module.chatChannel.get().asLong()) {
|
||||
(lastmsgdata == null ? lastmsgdata = new LastMsgData(module.chatChannelMono().block(), null)
|
||||
: lastmsgdata).message = null;
|
||||
return;
|
||||
} // Don't set the whole object to null, the player and channel information should be preserved
|
||||
for (LastMsgData data : channel instanceof PrivateChannel ? MCChatPrivate.lastmsgPerUser : MCChatCustom.lastmsgCustom) {
|
||||
if (data.channel.getId().asLong() == channel.getId().asLong()) {
|
||||
data.message = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
//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) {
|
||||
if (notEnabled()) return;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an event with the given details.
|
||||
* <p>
|
||||
* This method only synchronizes when the event is not asynchronous.
|
||||
*
|
||||
* @param event Event details
|
||||
* @param only Flips the operation and <b>includes</b> the listed plugins
|
||||
* @param plugins The plugins to exclude. Not case sensitive.
|
||||
*/
|
||||
public static void callEventExcluding(Event event, boolean only, String... plugins) { // Copied from Spigot-API and modified a bit
|
||||
if (event.isAsynchronous()) {
|
||||
if (Thread.holdsLock(Bukkit.getPluginManager())) {
|
||||
throw new IllegalStateException(
|
||||
event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
|
||||
}
|
||||
if (Bukkit.getServer().isPrimaryThread()) {
|
||||
throw new IllegalStateException(
|
||||
event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
|
||||
}
|
||||
fireEventExcluding(event, only, plugins);
|
||||
} else {
|
||||
synchronized (Bukkit.getPluginManager()) {
|
||||
fireEventExcluding(event, only, plugins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void fireEventExcluding(Event event, boolean only, String... plugins) {
|
||||
HandlerList handlers = event.getHandlers(); // Code taken from SimplePluginManager in Spigot-API
|
||||
RegisteredListener[] listeners = handlers.getRegisteredListeners();
|
||||
val server = Bukkit.getServer();
|
||||
|
||||
for (RegisteredListener registration : listeners) {
|
||||
if (!registration.getPlugin().isEnabled()
|
||||
|| Arrays.stream(plugins).anyMatch(p -> only ^ p.equalsIgnoreCase(registration.getPlugin().getName())))
|
||||
continue; // Modified to exclude plugins
|
||||
|
||||
try {
|
||||
registration.callEvent(event);
|
||||
} catch (AuthorNagException ex) {
|
||||
Plugin plugin = registration.getPlugin();
|
||||
|
||||
if (plugin.isNaggable()) {
|
||||
plugin.setNaggable(false);
|
||||
|
||||
server.getLogger().log(Level.SEVERE,
|
||||
String.format("Nag author(s): '%s' of '%s' about the following: %s",
|
||||
plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(),
|
||||
ex.getMessage()));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to "
|
||||
+ registration.getPlugin().getDescription().getFullName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (module != null) {
|
||||
if (module.serverWatcher != null)
|
||||
module.serverWatcher.fakePlayers.add(dcp);
|
||||
module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged in from Discord");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (module != null) {
|
||||
module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged out from Discord");
|
||||
if (module.serverWatcher != null)
|
||||
module.serverWatcher.fakePlayers.remove(dcp);
|
||||
}
|
||||
}
|
||||
|
||||
static void callEventSync(Event event) {
|
||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event));
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class LastMsgData {
|
||||
public Message message;
|
||||
public long time;
|
||||
public String content;
|
||||
public final MessageChannel channel;
|
||||
public buttondevteam.core.component.channel.Channel mcchannel;
|
||||
public final User user;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,369 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.core.{ComponentManager, MainPlugin, component}
|
||||
import buttondevteam.discordplugin._
|
||||
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule
|
||||
import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD
|
||||
import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent}
|
||||
import com.google.common.collect.Sets
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.channel.{Channel, MessageChannel, PrivateChannel, TextChannel}
|
||||
import discord4j.core.`object`.entity.{Message, User}
|
||||
import discord4j.core.spec.TextChannelEditSpec
|
||||
import io.netty.util.collection.LongObjectHashMap
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.Event
|
||||
import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent}
|
||||
import org.bukkit.plugin.AuthorNagException
|
||||
import org.reactivestreams.Publisher
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
import java.net.InetAddress
|
||||
import java.util
|
||||
import java.util._
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.function.Supplier
|
||||
import java.util.logging.Level
|
||||
import java.util.stream.{Collectors, Stream}
|
||||
import javax.annotation.Nullable
|
||||
|
||||
object MCChatUtils {
|
||||
/**
|
||||
* May contain P<DiscordID> as key for public chat
|
||||
*/
|
||||
val UnconnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordSender]]
|
||||
val ConnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordConnectedPlayer]]
|
||||
val OnlineSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordPlayerSender]]
|
||||
val LoggedInPlayers = new ConcurrentHashMap[UUID, DiscordConnectedPlayer]
|
||||
@Nullable private[mcchat] var lastmsgdata: MCChatUtils.LastMsgData = null
|
||||
private[mcchat] val lastmsgfromd = new LongObjectHashMap[Message] // Last message sent by a Discord user, used for clearing checkmarks
|
||||
private var module: MinecraftChatModule = null
|
||||
private val staticExcludedPlugins = new util.HashMap[Class[_ <: Event], util.HashSet[String]]
|
||||
|
||||
def updatePlayerList(): Unit = {
|
||||
val mod = getModule
|
||||
if (mod == null || !mod.showPlayerListOnDC.get) return
|
||||
if (lastmsgdata != null) updatePL(lastmsgdata)
|
||||
MCChatCustom.lastmsgCustom.forEach(MCChatUtils.updatePL)
|
||||
}
|
||||
|
||||
private def notEnabled = (module == null || !module.disabling) && getModule == null //Allow using things while disabling the module
|
||||
private def getModule = {
|
||||
if (module == null || !module.isEnabled) module = ComponentManager.getIfEnabled(classOf[MinecraftChatModule])
|
||||
//If disabled, it will try to get it again because another instance may be enabled - useful for /discord restart
|
||||
module
|
||||
}
|
||||
|
||||
private def updatePL(lmd: MCChatUtils.LastMsgData): Unit = {
|
||||
if (!lmd.channel.isInstanceOf[TextChannel]) {
|
||||
TBMCCoreAPI.SendException("Failed to update player list for channel " + lmd.channel.getId, new Exception("The channel isn't a (guild) text channel."), getModule)
|
||||
return
|
||||
}
|
||||
var topic = lmd.channel.asInstanceOf[TextChannel].getTopic.orElse("")
|
||||
if (topic.isEmpty) topic = ".\n----\nMinecraft chat\n----\n."
|
||||
val s = topic.split("\\n----\\n")
|
||||
if (s.length < 3) return
|
||||
var gid: String = null
|
||||
lmd match {
|
||||
case clmd: CustomLMD => gid = clmd.groupID
|
||||
case _ => //If we're not using a custom chat then it's either can ("everyone") or can't (null) see at most
|
||||
gid = buttondevteam.core.component.channel.Channel.GROUP_EVERYONE // (Though it's a public chat then rn)
|
||||
}
|
||||
val C = new AtomicInteger
|
||||
s(s.length - 1) = "Players: " + Bukkit.getOnlinePlayers.stream.filter(p => if (lmd.mcchannel == null) {
|
||||
gid == buttondevteam.core.component.channel.Channel.GROUP_EVERYONE //If null, allow if public (custom chats will have their channel stored anyway)
|
||||
}
|
||||
else {
|
||||
gid == lmd.mcchannel.getGroupID(p)
|
||||
}
|
||||
).filter(MCChatUtils.checkEssentials) //If they can see it
|
||||
.filter(_ => C.incrementAndGet > 0) //Always true
|
||||
.map((p) => DPUtils.sanitizeString(p.getDisplayName)).collect(Collectors.joining(", "))
|
||||
s(0) = C + " player" + (if (C.get != 1) "s" else "") + " online"
|
||||
lmd.channel.asInstanceOf[TextChannel].edit((tce: TextChannelEditSpec) => tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe //Don't wait
|
||||
}
|
||||
|
||||
private[mcchat] def checkEssentials(p: Player): Boolean = {
|
||||
val ess = MainPlugin.ess
|
||||
if (ess == null) return true
|
||||
!ess.getUser(p).isHidden
|
||||
}
|
||||
|
||||
def addSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], user: User, sender: T): T =
|
||||
addSender(senders, user.getId.asString, sender)
|
||||
|
||||
def addSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = {
|
||||
var map = senders.get(did)
|
||||
if (map == null) map = new ConcurrentHashMap[Snowflake, T]
|
||||
map.put(sender.getChannel.getId, sender)
|
||||
senders.put(did, map)
|
||||
sender
|
||||
}
|
||||
|
||||
def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
|
||||
val map = senders.get(user.getId.asString)
|
||||
if (map != null) return map.get(channel)
|
||||
null
|
||||
}
|
||||
|
||||
def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
|
||||
val map = senders.get(user.getId.asString)
|
||||
if (map != null) return map.remove(channel)
|
||||
null
|
||||
}
|
||||
|
||||
def forPublicPrivateChat(action: Mono[MessageChannel] => Mono[_]): Mono[_] = {
|
||||
if (notEnabled) return Mono.empty
|
||||
val list = new util.ArrayList[Mono[_]]
|
||||
list.add(action.apply(module.chatChannelMono))
|
||||
for (data <- MCChatPrivate.lastmsgPerUser) {
|
||||
list.add(action.apply(Mono.just(data.channel)))
|
||||
}
|
||||
// lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
|
||||
Mono.whenDelayError(list)
|
||||
}
|
||||
|
||||
/**
|
||||
* For custom and all MC chat
|
||||
*
|
||||
* @param action The action to act (cannot complete empty)
|
||||
* @param toggle The toggle to check
|
||||
* @param hookmsg Whether the message is also sent from the hook
|
||||
*/
|
||||
def forCustomAndAllMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): Mono[_] = {
|
||||
if (notEnabled) return Mono.empty
|
||||
val list = new util.ArrayList[Publisher[_]]
|
||||
if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) list.add(forPublicPrivateChat(action))
|
||||
val customLMDFunction = (cc: MCChatCustom.CustomLMD) => action.apply(Mono.just(cc.channel))
|
||||
if (toggle == null) MCChatCustom.lastmsgCustom.stream.map(customLMDFunction).forEach(list.add(_))
|
||||
else MCChatCustom.lastmsgCustom.stream.filter((cc) => (cc.toggles & toggle.flag) ne 0).map(customLMDFunction).forEach(list.add(_))
|
||||
Mono.whenDelayError(list)
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
|
||||
*
|
||||
* @param action The action to do
|
||||
* @param sender The sender to check perms of or null to send to all that has it toggled
|
||||
* @param toggle The toggle to check or null to send to all allowed
|
||||
*/
|
||||
def forAllowedCustomMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): Mono[_] = {
|
||||
if (notEnabled) return Mono.empty
|
||||
val st = MCChatCustom.lastmsgCustom.stream.filter((clmd) => {
|
||||
def foo(clmd: CustomLMD): Boolean = { //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
|
||||
if (toggle != null && ((clmd.toggles & toggle.flag) eq 0)) return false //If null then allow
|
||||
if (sender == null) return true
|
||||
clmd.groupID.equals(clmd.mcchannel.getGroupID(sender))
|
||||
}
|
||||
|
||||
foo(clmd)
|
||||
}).map((cc) => action.apply(Mono.just(cc.channel))) //TODO: Send error messages on channel connect
|
||||
Mono.whenDelayError(st.iterator) //Can't convert as an iterator or inside the stream, but I can convert it as a stream
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
|
||||
*
|
||||
* @param action The action to do
|
||||
* @param sender The sender to check perms of or null to send to all that has it toggled
|
||||
* @param toggle The toggle to check or null to send to all allowed
|
||||
* @param hookmsg Whether the message is also sent from the hook
|
||||
*/
|
||||
def forAllowedCustomAndAllMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): Mono[_] = {
|
||||
if (notEnabled) return Mono.empty
|
||||
val cc = forAllowedCustomMCChat(action, sender, toggle)
|
||||
if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) return Mono.whenDelayError(forPublicPrivateChat(action), cc)
|
||||
Mono.whenDelayError(cc)
|
||||
}
|
||||
|
||||
def send(message: String): Mono[MessageChannel] => Mono[_] = (ch: Mono[MessageChannel]) => ch.flatMap((mc: MessageChannel) => {
|
||||
def foo(mc: MessageChannel) = {
|
||||
resetLastMessage(mc)
|
||||
mc.createMessage(DPUtils.sanitizeString(message))
|
||||
}
|
||||
|
||||
foo(mc)
|
||||
})
|
||||
|
||||
def forAllowedMCChat(action: Mono[MessageChannel] => Mono[_], event: TBMCSystemChatEvent): Mono[_] = {
|
||||
if (notEnabled) return Mono.empty
|
||||
val list = new util.ArrayList[Mono[_]]
|
||||
if (event.getChannel.isGlobal) list.add(action.apply(module.chatChannelMono))
|
||||
for (data <- MCChatPrivate.lastmsgPerUser)
|
||||
if (event.shouldSendTo(getSender(data.channel.getId, data.user))) list.add(action.apply(Mono.just(data.channel))) //TODO: Only store ID?}
|
||||
MCChatCustom.lastmsgCustom.stream.filter((clmd) => {
|
||||
def foo(clmd: CustomLMD): Boolean = {
|
||||
clmd.brtoggles.contains(event.getTarget) && event.shouldSendTo(clmd.dcp)
|
||||
}
|
||||
|
||||
foo(clmd)
|
||||
}).map((clmd) => action.apply(Mono.just(clmd.channel))).forEach(list.add(_))
|
||||
Mono.whenDelayError(list)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will find the best sender to use: if the player is online, use that, if not but connected then use that etc.
|
||||
*/
|
||||
private[mcchat] def getSender(channel: Snowflake, author: User) = { //noinspection OptionalGetWithoutIsPresent
|
||||
Stream.of[Supplier[Optional[DiscordSenderBase]]]( // https://stackoverflow.com/a/28833677/2703239
|
||||
() => Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null
|
||||
() => Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it
|
||||
() => Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), //
|
||||
() => Optional.of(addSender(UnconnectedSenders, author, new DiscordSender(author, DiscordPlugin.dc.getChannelById(channel).block.asInstanceOf[MessageChannel])))).map(_.get).filter(_.isPresent).map(_.get).findFirst.get
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the last message, so it will start a new one instead of appending to it.
|
||||
* This is used when someone (even the bot) sends a message to the channel.
|
||||
*
|
||||
* @param channel The channel to reset in - the process is slightly different for the public, private and custom chats
|
||||
*/
|
||||
def resetLastMessage(channel: Channel): Unit = {
|
||||
if (notEnabled) return
|
||||
if (channel.getId.asLong == module.chatChannel.get.asLong) {
|
||||
if (lastmsgdata == null) lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null)
|
||||
else lastmsgdata.message = null
|
||||
return
|
||||
} // Don't set the whole object to null, the player and channel information should be preserved
|
||||
for (data <- if (channel.isInstanceOf[PrivateChannel]) MCChatPrivate.lastmsgPerUser
|
||||
else MCChatCustom.lastmsgCustom) {
|
||||
if (data.channel.getId.asLong == channel.getId.asLong) {
|
||||
data.message = null
|
||||
return
|
||||
}
|
||||
}
|
||||
//If it gets here, it's sending a message to a non-chat channel
|
||||
}
|
||||
|
||||
def addStaticExcludedPlugin(event: Class[_ <: Event], plugin: String): util.HashSet[String] =
|
||||
staticExcludedPlugins.compute(event, (e: Class[_ <: Event], hs: util.HashSet[String]) =>
|
||||
if (hs == null) Sets.newHashSet(plugin) else if (hs.add(plugin)) hs else hs)
|
||||
|
||||
def callEventExcludingSome(event: Event): Unit = {
|
||||
if (notEnabled) return
|
||||
val second = staticExcludedPlugins.get(event.getClass)
|
||||
val first = module.excludedPlugins.get
|
||||
val both = if (second == null) first
|
||||
else util.Arrays.copyOf(first, first.length + second.size)
|
||||
var i = first.length
|
||||
if (second != null) {
|
||||
for (plugin <- second) {
|
||||
both({
|
||||
i += 1;
|
||||
i - 1
|
||||
}) = plugin
|
||||
}
|
||||
}
|
||||
callEventExcluding(event, false, both)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an event with the given details.
|
||||
* <p>
|
||||
* This method only synchronizes when the event is not asynchronous.
|
||||
*
|
||||
* @param event Event details
|
||||
* @param only Flips the operation and <b>includes</b> the listed plugins
|
||||
* @param plugins The plugins to exclude. Not case sensitive.
|
||||
*/
|
||||
def callEventExcluding(event: Event, only: Boolean, plugins: String*): Unit = { // Copied from Spigot-API and modified a bit
|
||||
if (event.isAsynchronous) {
|
||||
if (Thread.holdsLock(Bukkit.getPluginManager)) throw new IllegalStateException(event.getEventName + " cannot be triggered asynchronously from inside synchronized code.")
|
||||
if (Bukkit.getServer.isPrimaryThread) throw new IllegalStateException(event.getEventName + " cannot be triggered asynchronously from primary server thread.")
|
||||
fireEventExcluding(event, only, plugins)
|
||||
}
|
||||
else Bukkit.getPluginManager synchronized fireEventExcluding(event, only, plugins)
|
||||
}
|
||||
|
||||
private def fireEventExcluding(event: Event, only: Boolean, plugins: String*): Unit = {
|
||||
val handlers = event.getHandlers // Code taken from SimplePluginManager in Spigot-API
|
||||
val listeners = handlers.getRegisteredListeners
|
||||
val server = Bukkit.getServer
|
||||
for (registration <- listeners) {
|
||||
if (!registration.getPlugin.isEnabled || util.Arrays.stream(plugins).anyMatch((p: String) => only ^ p.equalsIgnoreCase(registration.getPlugin.getName))) {
|
||||
continue //todo: continue is not supported
|
||||
// Modified to exclude plugins
|
||||
}
|
||||
try registration.callEvent(event)
|
||||
catch {
|
||||
case ex: AuthorNagException =>
|
||||
val plugin = registration.getPlugin
|
||||
if (plugin.isNaggable) {
|
||||
plugin.setNaggable(false)
|
||||
server.getLogger.log(Level.SEVERE, String.format("Nag author(s): '%s' of '%s' about the following: %s", plugin.getDescription.getAuthors, plugin.getDescription.getFullName, ex.getMessage))
|
||||
}
|
||||
case ex: Throwable =>
|
||||
server.getLogger.log(Level.SEVERE, "Could not pass event " + event.getEventName + " to " + registration.getPlugin.getDescription.getFullName, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call it from an async thread.
|
||||
*/
|
||||
def callLoginEvents(dcp: DiscordConnectedPlayer): Unit = {
|
||||
val loginFail = (kickMsg: String) => {
|
||||
def foo(kickMsg: String): Unit = {
|
||||
dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg)
|
||||
MCChatPrivate.privateMCChat(dcp.getChannel, start = false, dcp.getUser, dcp.getChromaUser)
|
||||
}
|
||||
|
||||
foo(kickMsg)
|
||||
} //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 ne AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||
loginFail(event.getKickMessage)
|
||||
return
|
||||
}
|
||||
Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => {
|
||||
def foo(): Unit = {
|
||||
val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress)
|
||||
callEventExcludingSome(ev)
|
||||
if (ev.getResult ne PlayerLoginEvent.Result.ALLOWED) {
|
||||
loginFail(ev.getKickMessage)
|
||||
return
|
||||
}
|
||||
callEventExcludingSome(new PlayerJoinEvent(dcp, ""))
|
||||
dcp.setLoggedIn(true)
|
||||
if (module != null) {
|
||||
if (module.serverWatcher != null) module.serverWatcher.fakePlayers.add(dcp)
|
||||
module.log(dcp.getName + " (" + dcp.getUniqueId + ") logged in from Discord")
|
||||
}
|
||||
}
|
||||
|
||||
foo()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Only calls the events if the player is actually logged in
|
||||
*
|
||||
* @param dcp The player
|
||||
* @param needsSync Whether we're in an async thread
|
||||
*/
|
||||
def callLogoutEvent(dcp: DiscordConnectedPlayer, needsSync: Boolean): Unit = {
|
||||
if (!dcp.isLoggedIn) return
|
||||
val event = new PlayerQuitEvent(dcp, "")
|
||||
if (needsSync) callEventSync(event)
|
||||
else callEventExcludingSome(event)
|
||||
dcp.setLoggedIn(false)
|
||||
if (module != null) {
|
||||
module.log(dcp.getName + " (" + dcp.getUniqueId + ") logged out from Discord")
|
||||
if (module.serverWatcher != null) module.serverWatcher.fakePlayers.remove(dcp)
|
||||
}
|
||||
}
|
||||
|
||||
private[mcchat] def callEventSync(event: Event) = Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => callEventExcludingSome(event))
|
||||
|
||||
class LastMsgData(val channel: MessageChannel, val user: User) {
|
||||
var message: String = null
|
||||
var time = 0L
|
||||
var content: String = null
|
||||
var mcchannel: component.channel.Channel = null
|
||||
}
|
||||
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.discordplugin.*;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
import buttondevteam.lib.architecture.ConfigData;
|
||||
import buttondevteam.lib.player.TBMCPlayer;
|
||||
import buttondevteam.lib.player.TBMCPlayerBase;
|
||||
import buttondevteam.lib.player.TBMCYEEHAWEvent;
|
||||
import com.earth2me.essentials.CommandSource;
|
||||
import discord4j.common.util.Snowflake;
|
||||
import discord4j.core.object.entity.Role;
|
||||
import lombok.val;
|
||||
import net.ess3.api.events.AfkStatusChangeEvent;
|
||||
import net.ess3.api.events.MuteStatusChangeEvent;
|
||||
import net.ess3.api.events.NickChangeEvent;
|
||||
import net.ess3.api.events.VanishStatusChangeEvent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
import org.bukkit.event.server.BroadcastMessageEvent;
|
||||
import org.bukkit.event.server.TabCompleteEvent;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
class MCListener implements Listener {
|
||||
private final MinecraftChatModule module;
|
||||
private final ConfigData<Mono<Role>> muteRole;
|
||||
|
||||
public MCListener(MinecraftChatModule module) {
|
||||
this.module = module;
|
||||
muteRole = DPUtils.roleData(module.getConfig(), "muteRole", "Muted");
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent e) {
|
||||
if (e.getResult() != Result.ALLOWED)
|
||||
return;
|
||||
if (e.getPlayer() instanceof DiscordConnectedPlayer)
|
||||
return;
|
||||
var dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId());
|
||||
if (dcp != null)
|
||||
MCChatUtils.callLogoutEvent(dcp, false);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent e) {
|
||||
if (e.getPlayer() instanceof DiscordConnectedPlayer)
|
||||
return; // Don't show the joined message for the fake player
|
||||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
|
||||
final Player p = e.getPlayer();
|
||||
DiscordPlayer dp = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class).getAs(DiscordPlayer.class);
|
||||
if (dp != null) {
|
||||
DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID())).flatMap(user -> user.getPrivateChannel().flatMap(chan -> module.chatChannelMono().flatMap(cc -> {
|
||||
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(),
|
||||
DiscordPlayerSender.create(user, chan, p, module));
|
||||
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(),
|
||||
DiscordPlayerSender.create(user, cc, p, module)); //Stored per-channel
|
||||
return Mono.empty();
|
||||
}))).subscribe();
|
||||
}
|
||||
final String message = e.getJoinMessage();
|
||||
if (message != null && message.trim().length() > 0)
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
|
||||
ChromaBot.updatePlayerList();
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerLeave(PlayerQuitEvent e) {
|
||||
if (e.getPlayer() instanceof DiscordConnectedPlayer)
|
||||
return; // Only care about real users
|
||||
MCChatUtils.OnlineSenders.entrySet()
|
||||
.removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId())));
|
||||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
||||
() -> Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId())).ifPresent(MCChatUtils::callLoginEvents));
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
||||
ChromaBot::updatePlayerList, 5);
|
||||
final String message = e.getQuitMessage();
|
||||
if (message != null && message.trim().length() > 0)
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerKick(PlayerKickEvent e) {
|
||||
/*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
|
||||
&& !e.getReason().equals("Server closed")) // The leave messages errored with the previous setup, I could make it wait since I moved it here, but instead I have a special
|
||||
MCChatListener.forAllowedCustomAndAllMCChat(e.getPlayer().getName() + " left the game"); // message for this - Oh wait this doesn't even send normally because of the hook*/
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onPlayerDeath(PlayerDeathEvent e) {
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerAFK(AfkStatusChangeEvent e) {
|
||||
final Player base = e.getAffected().getBase();
|
||||
if (e.isCancelled() || !base.isOnline())
|
||||
return;
|
||||
final String msg = base.getDisplayName()
|
||||
+ " is " + (e.getValue() ? "now" : "no longer") + " AFK.";
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerMute(MuteStatusChangeEvent e) {
|
||||
final Mono<Role> role = muteRole.get();
|
||||
if (role == null) return;
|
||||
final CommandSource source = e.getAffected().getSource();
|
||||
if (!source.isPlayer())
|
||||
return;
|
||||
final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
|
||||
.getAs(DiscordPlayer.class);
|
||||
if (p == null) return;
|
||||
DPUtils.ignoreError(DiscordPlugin.dc().getUserById(Snowflake.of(p.getDiscordID()))
|
||||
.flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId()))
|
||||
.flatMap(user -> role.flatMap(r -> {
|
||||
if (e.getValue())
|
||||
user.addRole(r.getId());
|
||||
else
|
||||
user.removeRole(r.getId());
|
||||
val modlog = module.modlogChannel.get();
|
||||
String msg = (e.getValue() ? "M" : "Unm") + "uted user: " + user.getUsername() + "#" + user.getDiscriminator();
|
||||
module.log(msg);
|
||||
if (modlog != null)
|
||||
return modlog.flatMap(ch -> ch.createMessage(msg));
|
||||
return Mono.empty();
|
||||
}))).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChatSystemMessage(TBMCSystemChatEvent event) {
|
||||
MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBroadcastMessage(BroadcastMessageEvent event) {
|
||||
MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onYEEHAW(TBMCYEEHAWEvent event) { //TODO: Inherit from the chat event base to have channel support
|
||||
String name = event.getSender() instanceof Player ? ((Player) event.getSender()).getDisplayName()
|
||||
: event.getSender().getName();
|
||||
//Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
|
||||
DiscordPlugin.mainServer.getEmojis().filter(e -> "YEEHAW".equals(e.getName()))
|
||||
.take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).flatMap(yeehaw ->
|
||||
MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs"))))).subscribe();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onNickChange(NickChangeEvent event) {
|
||||
MCChatUtils.updatePlayerList();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onTabComplete(TabCompleteEvent event) {
|
||||
int i = event.getBuffer().lastIndexOf(' ');
|
||||
String t = event.getBuffer().substring(i + 1); //0 if not found
|
||||
if (!t.startsWith("@"))
|
||||
return;
|
||||
String token = t.substring(1);
|
||||
val x = DiscordPlugin.mainServer.getMembers()
|
||||
.flatMap(m -> Flux.just(m.getUsername(), m.getNickname().orElse("")))
|
||||
.filter(s -> s.startsWith(token))
|
||||
.map(s -> "@" + s)
|
||||
.doOnNext(event.getCompletions()::add).blockLast();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCommandSend(PlayerCommandSendEvent event) {
|
||||
event.getCommands().add("g");
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onVanish(VanishStatusChangeEvent event) {
|
||||
if (event.isCancelled()) return;
|
||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, MCChatUtils::updatePlayerList);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.discordplugin._
|
||||
import buttondevteam.lib.TBMCSystemChatEvent
|
||||
import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent}
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||
import discord4j.core.`object`.entity.{Member, Role, User}
|
||||
import net.ess3.api.events.{AfkStatusChangeEvent, MuteStatusChangeEvent, NickChangeEvent, VanishStatusChangeEvent}
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.{EventHandler, EventPriority, Listener}
|
||||
import org.bukkit.event.entity.PlayerDeathEvent
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result
|
||||
import org.bukkit.event.player._
|
||||
import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent}
|
||||
import reactor.core.publisher.{Flux, Mono}
|
||||
|
||||
import java.util.Optional
|
||||
|
||||
class MCListener(val module: MinecraftChatModule) extends Listener {
|
||||
final private val muteRole = DPUtils.roleData(module.getConfig, "muteRole", "Muted")
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST) def onPlayerLogin(e: PlayerLoginEvent): Unit = {
|
||||
if (e.getResult ne Result.ALLOWED) return
|
||||
if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return
|
||||
val dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId)
|
||||
if (dcp != null) MCChatUtils.callLogoutEvent(dcp, needsSync = false)
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR) def onPlayerJoin(e: PlayerJoinEvent): Unit = {
|
||||
if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return // Don't show the joined message for the fake player
|
||||
Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => {
|
||||
def foo(): Unit = {
|
||||
val p = e.getPlayer
|
||||
val dp = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer])
|
||||
if (dp != null) DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).flatMap((user) => user.getPrivateChannel.flatMap((chan) => module.chatChannelMono.flatMap((cc: MessageChannel) => {
|
||||
def foo(cc: MessageChannel) = {
|
||||
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module))
|
||||
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel
|
||||
Mono.empty
|
||||
}
|
||||
|
||||
foo(cc)
|
||||
}))).subscribe
|
||||
val message = e.getJoinMessage
|
||||
sendJoinLeaveMessage(message, e.getPlayer)
|
||||
ChromaBot.updatePlayerList()
|
||||
}
|
||||
|
||||
foo()
|
||||
})
|
||||
}
|
||||
|
||||
private def sendJoinLeaveMessage(message: String, player: Player): Unit =
|
||||
if (message != null && message.trim.nonEmpty)
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), player, ChannelconBroadcast.JOINLEAVE, hookmsg = true).subscribe
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR) def onPlayerLeave(e: PlayerQuitEvent): Unit = {
|
||||
if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return // Only care about real users
|
||||
MCChatUtils.OnlineSenders.entrySet.removeIf((entry) => entry.getValue.entrySet.stream.anyMatch((p) => p.getValue.getUniqueId.equals(e.getPlayer.getUniqueId)))
|
||||
Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId)).ifPresent(MCChatUtils.callLoginEvents))
|
||||
Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => ChromaBot.updatePlayerList(), 5)
|
||||
val message = e.getQuitMessage
|
||||
sendJoinLeaveMessage(message, e.getPlayer)
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST) def onPlayerKick(e: PlayerKickEvent): Unit = {
|
||||
/*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
|
||||
&& !e.getReason().equals("Server closed")) // The leave messages errored with the previous setup, I could make it wait since I moved it here, but instead I have a special
|
||||
MCChatListener.forAllowedCustomAndAllMCChat(e.getPlayer().getName() + " left the game"); // message for this - Oh wait this doesn't even send normally because of the hook*/
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW) def onPlayerDeath(e: PlayerDeathEvent): Unit =
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage), e.getEntity, ChannelconBroadcast.DEATH, hookmsg = true).subscribe
|
||||
|
||||
@EventHandler def onPlayerAFK(e: AfkStatusChangeEvent): Unit = {
|
||||
val base = e.getAffected.getBase
|
||||
if (e.isCancelled || !base.isOnline) return
|
||||
val msg = base.getDisplayName + " is " + (if (e.getValue) "now"
|
||||
else "no longer") + " AFK."
|
||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, hookmsg = false).subscribe
|
||||
}
|
||||
|
||||
@EventHandler def onPlayerMute(e: MuteStatusChangeEvent): Unit = {
|
||||
val role = muteRole.get
|
||||
if (role == null) return
|
||||
val source = e.getAffected.getSource
|
||||
if (!source.isPlayer) return
|
||||
val p = TBMCPlayerBase.getPlayer(source.getPlayer.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer])
|
||||
if (p == null) return
|
||||
DPUtils.ignoreError(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID)).flatMap((user: User) => user.asMember(DiscordPlugin.mainServer.getId)).flatMap((user: Member) => role.flatMap((r: Role) => {
|
||||
def foo(r: Role): Mono[_] = {
|
||||
if (e.getValue) user.addRole(r.getId)
|
||||
else user.removeRole(r.getId)
|
||||
val modlog = module.modlogChannel.get
|
||||
val msg = (if (e.getValue) "M"
|
||||
else "Unm") + "uted user: " + user.getUsername + "#" + user.getDiscriminator
|
||||
module.log(msg)
|
||||
if (modlog != null) return modlog.flatMap((ch: MessageChannel) => ch.createMessage(msg))
|
||||
Mono.empty
|
||||
}
|
||||
|
||||
foo(r)
|
||||
}))).subscribe
|
||||
}
|
||||
|
||||
@EventHandler def onChatSystemMessage(event: TBMCSystemChatEvent): Unit =
|
||||
MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage), event).subscribe
|
||||
|
||||
@EventHandler def onBroadcastMessage(event: BroadcastMessageEvent): Unit =
|
||||
MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage), ChannelconBroadcast.BROADCAST, hookmsg = false).subscribe
|
||||
|
||||
@EventHandler def onYEEHAW(event: TBMCYEEHAWEvent): Unit = { //TODO: Inherit from the chat event base to have channel support
|
||||
val name = event.getSender match {
|
||||
case player: Player => player.getDisplayName
|
||||
case _ => event.getSender.getName
|
||||
}
|
||||
//Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
|
||||
DiscordPlugin.mainServer.getEmojis.filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty
|
||||
.map(Optional.of(_)).defaultIfEmpty(Optional.empty)
|
||||
.flatMap((yeehaw) => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name +
|
||||
yeehaw.map((guildEmoji) => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").orElse(" YEEHAWs")))).subscribe
|
||||
}
|
||||
|
||||
@EventHandler def onNickChange(event: NickChangeEvent): Unit = MCChatUtils.updatePlayerList()
|
||||
|
||||
@EventHandler def onTabComplete(event: TabCompleteEvent): Unit = {
|
||||
val i = event.getBuffer.lastIndexOf(' ')
|
||||
val t = event.getBuffer.substring(i + 1) //0 if not found
|
||||
if (!t.startsWith("@")) return
|
||||
val token = t.substring(1)
|
||||
val x = DiscordPlugin.mainServer.getMembers.flatMap((m) => Flux.just(m.getUsername, m.getNickname.orElse("")))
|
||||
.filter((s) => s.startsWith(token)).map((s) => "@" + s).doOnNext(event.getCompletions.add(_)).blockLast
|
||||
}
|
||||
|
||||
@EventHandler def onCommandSend(event: PlayerCommandSendEvent): Boolean = event.getCommands.add("g")
|
||||
|
||||
@EventHandler def onVanish(event: VanishStatusChangeEvent): Unit = {
|
||||
if (event.isCancelled) return
|
||||
Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => MCChatUtils.updatePlayerList())
|
||||
}
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.component.channel.Channel;
|
||||
import buttondevteam.discordplugin.ChannelconBroadcast;
|
||||
import buttondevteam.discordplugin.DPUtils;
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.playerfaker.ServerWatcher;
|
||||
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
|
||||
import buttondevteam.discordplugin.util.DPState;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
import buttondevteam.lib.architecture.Component;
|
||||
import buttondevteam.lib.architecture.ConfigData;
|
||||
import buttondevteam.lib.architecture.ReadOnlyConfigData;
|
||||
import com.google.common.collect.Lists;
|
||||
import discord4j.common.util.Snowflake;
|
||||
import discord4j.core.object.entity.channel.MessageChannel;
|
||||
import discord4j.rest.util.Color;
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM.
|
||||
*/
|
||||
public class MinecraftChatModule extends Component<DiscordPlugin> {
|
||||
public static DPState state = DPState.RUNNING;
|
||||
private @Getter MCChatListener listener;
|
||||
ServerWatcher serverWatcher;
|
||||
private LPInjector lpInjector;
|
||||
boolean disabling = false;
|
||||
|
||||
/**
|
||||
* A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command!
|
||||
*/
|
||||
public ConfigData<ArrayList<String>> whitelistedCommands() {
|
||||
return getConfig().getData("whitelistedCommands", () -> Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki",
|
||||
"yeehaw", "lenny", "rp", "plugins"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel to use as the public Minecraft chat - everything public gets broadcasted here
|
||||
*/
|
||||
public ReadOnlyConfigData<Snowflake> chatChannel = DPUtils.snowflakeData(getConfig(), "chatChannel", 0L);
|
||||
|
||||
public Mono<MessageChannel> chatChannelMono() {
|
||||
return DPUtils.getMessageChannel(chatChannel.getPath(), chatChannel.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute
|
||||
*/
|
||||
public ReadOnlyConfigData<Mono<MessageChannel>> modlogChannel = DPUtils.channelData(getConfig(), "modlogChannel");
|
||||
|
||||
/**
|
||||
* The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here
|
||||
*/
|
||||
public ConfigData<String[]> excludedPlugins = getConfig().getData("excludedPlugins", new String[]{"ProtocolLib", "LibsDisguises", "JourneyMapServer"});
|
||||
|
||||
/**
|
||||
* If this setting is on then players logged in through the 'mcchat' command will be able to teleport using plugin commands.
|
||||
* They can then use commands like /tpahere to teleport others to that place.<br />
|
||||
* If this is off, then teleporting will have no effect.
|
||||
*/
|
||||
public ConfigData<Boolean> allowFakePlayerTeleports = getConfig().getData("allowFakePlayerTeleports", false);
|
||||
|
||||
/**
|
||||
* If this is on, each chat channel will have a player list in their description.
|
||||
* It only gets added if there's no description yet or there are (at least) two lines of "----" following each other.
|
||||
* Note that it will replace <b>everything</b> above the first and below the last "----" but it will only detect exactly four dashes.
|
||||
* So if you want to use dashes for something else in the description, make sure it's either less or more dashes in one line.
|
||||
*/
|
||||
public ConfigData<Boolean> showPlayerListOnDC = getConfig().getData("showPlayerListOnDC", true);
|
||||
|
||||
/**
|
||||
* This setting controls whether custom chat connections can be <i>created</i> (existing connections will always work).
|
||||
* Custom chat connections can be created using the channelcon command and they allow players to display town chat in a Discord channel for example.
|
||||
* See the channelcon command for more details.
|
||||
*/
|
||||
public ConfigData<Boolean> allowCustomChat = getConfig().getData("allowCustomChat", true);
|
||||
|
||||
/**
|
||||
* This setting allows you to control if players can DM the bot to log on the server from Discord.
|
||||
* This allows them to both chat and perform any command they can in-game.
|
||||
*/
|
||||
public ConfigData<Boolean> allowPrivateChat = getConfig().getData("allowPrivateChat", true);
|
||||
|
||||
/**
|
||||
* If set, message authors appearing on Discord will link to this URL. A 'type' and 'id' parameter will be added with the user's platform (Discord, Minecraft, ...) and ID.
|
||||
*/
|
||||
public ConfigData<String> profileURL = getConfig().getData("profileURL", "");
|
||||
|
||||
/**
|
||||
* Enables support for running vanilla commands through Discord, if you ever need it.
|
||||
*/
|
||||
public ConfigData<Boolean> enableVanillaCommands = getConfig().getData("enableVanillaCommands", true);
|
||||
|
||||
/**
|
||||
* Whether players logged on from Discord (mcchat command) should be recognised by other plugins. Some plugins might break if it's turned off.
|
||||
* But it's really hacky.
|
||||
*/
|
||||
private final ConfigData<Boolean> addFakePlayersToBukkit = getConfig().getData("addFakePlayersToBukkit", false);
|
||||
|
||||
/**
|
||||
* Set by the component to report crashes.
|
||||
*/
|
||||
private final ConfigData<Boolean> serverUp = getConfig().getData("serverUp", false);
|
||||
|
||||
private final MCChatCommand mcChatCommand = new MCChatCommand(this);
|
||||
private final ChannelconCommand channelconCommand = new ChannelconCommand(this);
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
if (DPUtils.disableIfConfigErrorRes(this, chatChannel, chatChannelMono()))
|
||||
return;
|
||||
listener = new MCChatListener(this);
|
||||
TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin());
|
||||
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled
|
||||
getPlugin().getManager().registerCommand(mcChatCommand);
|
||||
getPlugin().getManager().registerCommand(channelconCommand);
|
||||
|
||||
val chcons = getConfig().getConfig().getConfigurationSection("chcons");
|
||||
if (chcons == null) //Fallback to old place
|
||||
getConfig().getConfig().getRoot().getConfigurationSection("chcons");
|
||||
if (chcons != null) {
|
||||
val chconkeys = chcons.getKeys(false);
|
||||
for (val chconkey : chconkeys) {
|
||||
val chcon = chcons.getConfigurationSection(chconkey);
|
||||
val mcch = Channel.getChannels().filter(ch -> ch.ID.equals(chcon.getString("mcchid"))).findAny();
|
||||
val ch = DiscordPlugin.dc.getChannelById(Snowflake.of(chcon.getLong("chid"))).block();
|
||||
val did = chcon.getLong("did");
|
||||
val user = DiscordPlugin.dc.getUserById(Snowflake.of(did)).block();
|
||||
val groupid = chcon.getString("groupid");
|
||||
val toggles = chcon.getInt("toggles");
|
||||
val brtoggles = chcon.getStringList("brtoggles");
|
||||
if (!mcch.isPresent() || ch == null || user == null || groupid == null)
|
||||
continue;
|
||||
Bukkit.getScheduler().runTask(getPlugin(), () -> { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase)
|
||||
val dcp = DiscordConnectedPlayer.create(user, (MessageChannel) ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this);
|
||||
MCChatCustom.addCustomChat((MessageChannel) ch, groupid, mcch.get(), user, dcp, toggles, brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::get).filter(Objects::nonNull).collect(Collectors.toSet()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (lpInjector == null)
|
||||
lpInjector = new LPInjector(DiscordPlugin.plugin);
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
log("No LuckPerms, not injecting");
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
if (addFakePlayersToBukkit.get()) {
|
||||
try {
|
||||
serverWatcher = new ServerWatcher();
|
||||
serverWatcher.enableDisable(true);
|
||||
log("Finished hooking into the server");
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("Failed to hack the server (object)! Disable addFakePlayersToBukkit in the config.", e, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (state == DPState.RESTARTING_PLUGIN) { //These will only execute if the chat is enabled
|
||||
sendStateMessage(Color.CYAN, "Discord plugin restarted - chat connected."); //Really important to note the chat, hmm
|
||||
state = DPState.RUNNING;
|
||||
} else if (state == DPState.DISABLED_MCCHAT) {
|
||||
sendStateMessage(Color.CYAN, "Minecraft chat enabled - chat connected.");
|
||||
state = DPState.RUNNING;
|
||||
} else if (serverUp.get()) {
|
||||
sendStateMessage(Color.YELLOW, "Server started after a crash - chat connected.");
|
||||
val thr = new Throwable("The server shut down unexpectedly. See the log of the previous run for more details.");
|
||||
thr.setStackTrace(new StackTraceElement[0]);
|
||||
TBMCCoreAPI.SendException("The server crashed!", thr, this);
|
||||
} else
|
||||
sendStateMessage(Color.GREEN, "Server started - chat connected.");
|
||||
serverUp.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
disabling = true;
|
||||
if (state == DPState.RESTARTING_PLUGIN) //These will only execute if the chat is enabled
|
||||
sendStateMessage(Color.ORANGE, "Discord plugin restarting");
|
||||
else if (state == DPState.RUNNING) {
|
||||
sendStateMessage(Color.ORANGE, "Minecraft chat disabled");
|
||||
state = DPState.DISABLED_MCCHAT;
|
||||
} else {
|
||||
String kickmsg = Bukkit.getOnlinePlayers().size() > 0
|
||||
? (DPUtils
|
||||
.sanitizeString(Bukkit.getOnlinePlayers().stream()
|
||||
.map(Player::getDisplayName).collect(Collectors.joining(", ")))
|
||||
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
|
||||
+ "thrown out") //TODO: Make configurable
|
||||
: "";
|
||||
if (state == DPState.RESTARTING_SERVER)
|
||||
sendStateMessage(Color.ORANGE, "Server restarting", kickmsg);
|
||||
else if (state == DPState.STOPPING_SERVER)
|
||||
sendStateMessage(Color.RED, "Server stopping", kickmsg);
|
||||
else
|
||||
sendStateMessage(Color.GRAY, "Unknown state, please report.");
|
||||
} //If 'restart' is disabled then this isn't shown even if joinleave is enabled
|
||||
|
||||
serverUp.set(false); //Disable even if just the component is disabled because that way it won't falsely report crashes
|
||||
|
||||
try { //If it's not enabled it won't do anything
|
||||
if (serverWatcher != null) {
|
||||
serverWatcher.enableDisable(false);
|
||||
log("Finished unhooking the server");
|
||||
}
|
||||
} catch (
|
||||
Exception e) {
|
||||
TBMCCoreAPI.SendException("Failed to restore the server object!", e, this);
|
||||
}
|
||||
|
||||
val chcons = MCChatCustom.getCustomChats();
|
||||
val chconsc = getConfig().getConfig().createSection("chcons");
|
||||
for (
|
||||
val chcon : chcons) {
|
||||
val chconc = chconsc.createSection(chcon.channel.getId().asString());
|
||||
chconc.set("mcchid", chcon.mcchannel.ID);
|
||||
chconc.set("chid", chcon.channel.getId().asLong());
|
||||
chconc.set("did", chcon.user.getId().asLong());
|
||||
chconc.set("mcuid", chcon.dcp.getUniqueId().toString());
|
||||
chconc.set("mcname", chcon.dcp.getName());
|
||||
chconc.set("groupid", chcon.groupID);
|
||||
chconc.set("toggles", chcon.toggles);
|
||||
chconc.set("brtoggles", chcon.brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::getName).collect(Collectors.toList()));
|
||||
}
|
||||
if (listener != null) //Can be null if disabled because of a config error
|
||||
listener.stop(true);
|
||||
getPlugin().getManager().unregisterCommand(mcChatCommand);
|
||||
getPlugin().getManager().unregisterCommand(channelconCommand);
|
||||
disabling = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* It will block to make sure all messages are sent
|
||||
*/
|
||||
private void sendStateMessage(Color color, String message) {
|
||||
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color)
|
||||
.setTitle(message))), ChannelconBroadcast.RESTART, false).block();
|
||||
}
|
||||
|
||||
/**
|
||||
* It will block to make sure all messages are sent
|
||||
*/
|
||||
private void sendStateMessage(Color color, String message, String extra) {
|
||||
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color)
|
||||
.setTitle(message).setDescription(extra)).onErrorResume(t -> Mono.empty())), ChannelconBroadcast.RESTART, false).block();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
package buttondevteam.discordplugin.mcchat
|
||||
|
||||
import buttondevteam.core.component.channel.Channel
|
||||
import buttondevteam.discordplugin.{ChannelconBroadcast, DPUtils, DiscordConnectedPlayer, DiscordPlugin}
|
||||
import buttondevteam.discordplugin.playerfaker.ServerWatcher
|
||||
import buttondevteam.discordplugin.playerfaker.perm.LPInjector
|
||||
import buttondevteam.discordplugin.util.DPState
|
||||
import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent}
|
||||
import buttondevteam.lib.architecture.{Component, ConfigData, ReadOnlyConfigData}
|
||||
import com.google.common.collect.Lists
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||
import discord4j.rest.util.Color
|
||||
import org.bukkit.Bukkit
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
import java.util
|
||||
import java.util.{Objects, UUID}
|
||||
import java.util.stream.Collectors
|
||||
|
||||
/**
|
||||
* Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM.
|
||||
*/
|
||||
object MinecraftChatModule {
|
||||
var state = DPState.RUNNING
|
||||
}
|
||||
|
||||
class MinecraftChatModule extends Component[DiscordPlugin] {
|
||||
def getListener: MCChatListener = this.listener
|
||||
|
||||
private var listener: MCChatListener = null
|
||||
private[mcchat] var serverWatcher: ServerWatcher = null
|
||||
private var lpInjector: LPInjector = null
|
||||
private[mcchat] var disabling = false
|
||||
|
||||
/**
|
||||
* A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command!
|
||||
*/
|
||||
val whitelistedCommands: ConfigData[util.ArrayList[String]] =
|
||||
getConfig.getData("whitelistedCommands",
|
||||
() => Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki", "yeehaw", "lenny", "rp", "plugins"))
|
||||
|
||||
/**
|
||||
* The channel to use as the public Minecraft chat - everything public gets broadcasted here
|
||||
*/
|
||||
val chatChannel: ReadOnlyConfigData[Snowflake] = DPUtils.snowflakeData(getConfig, "chatChannel", 0L)
|
||||
|
||||
def chatChannelMono: Mono[MessageChannel] = DPUtils.getMessageChannel(chatChannel.getPath, chatChannel.get)
|
||||
|
||||
/**
|
||||
* The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute
|
||||
*/
|
||||
val modlogChannel: ReadOnlyConfigData[Mono[MessageChannel]] = DPUtils.channelData(getConfig, "modlogChannel")
|
||||
/**
|
||||
* The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here
|
||||
*/
|
||||
val excludedPlugins: ConfigData[Array[String]] = getConfig.getData("excludedPlugins", Array[String]("ProtocolLib", "LibsDisguises", "JourneyMapServer"))
|
||||
/**
|
||||
* If this setting is on then players logged in through the 'mcchat' command will be able to teleport using plugin commands.
|
||||
* They can then use commands like /tpahere to teleport others to that place.<br />
|
||||
* If this is off, then teleporting will have no effect.
|
||||
*/
|
||||
val allowFakePlayerTeleports: ConfigData[Boolean] = getConfig.getData("allowFakePlayerTeleports", false)
|
||||
/**
|
||||
* If this is on, each chat channel will have a player list in their description.
|
||||
* It only gets added if there's no description yet or there are (at least) two lines of "----" following each other.
|
||||
* Note that it will replace <b>everything</b> above the first and below the last "----" but it will only detect exactly four dashes.
|
||||
* So if you want to use dashes for something else in the description, make sure it's either less or more dashes in one line.
|
||||
*/
|
||||
val showPlayerListOnDC: ConfigData[Boolean] = getConfig.getData("showPlayerListOnDC", true)
|
||||
/**
|
||||
* This setting controls whether custom chat connections can be <i>created</i> (existing connections will always work).
|
||||
* Custom chat connections can be created using the channelcon command and they allow players to display town chat in a Discord channel for example.
|
||||
* See the channelcon command for more details.
|
||||
*/
|
||||
val allowCustomChat: ConfigData[Boolean] = getConfig.getData("allowCustomChat", true)
|
||||
/**
|
||||
* This setting allows you to control if players can DM the bot to log on the server from Discord.
|
||||
* This allows them to both chat and perform any command they can in-game.
|
||||
*/
|
||||
val allowPrivateChat: ConfigData[Boolean] = getConfig.getData("allowPrivateChat", true)
|
||||
/**
|
||||
* If set, message authors appearing on Discord will link to this URL. A 'type' and 'id' parameter will be added with the user's platform (Discord, Minecraft, ...) and ID.
|
||||
*/
|
||||
val profileURL: ConfigData[String] = getConfig.getData("profileURL", "")
|
||||
/**
|
||||
* Enables support for running vanilla commands through Discord, if you ever need it.
|
||||
*/
|
||||
val enableVanillaCommands: ConfigData[Boolean] = getConfig.getData("enableVanillaCommands", true)
|
||||
/**
|
||||
* Whether players logged on from Discord (mcchat command) should be recognised by other plugins. Some plugins might break if it's turned off.
|
||||
* But it's really hacky.
|
||||
*/
|
||||
final private val addFakePlayersToBukkit = getConfig.getData("addFakePlayersToBukkit", false)
|
||||
/**
|
||||
* Set by the component to report crashes.
|
||||
*/
|
||||
final private val serverUp = getConfig.getData("serverUp", false)
|
||||
final private val mcChatCommand = new MCChatCommand(this)
|
||||
final private val channelconCommand = new ChannelconCommand(this)
|
||||
|
||||
override protected def enable(): Unit = {
|
||||
if (DPUtils.disableIfConfigErrorRes(this, chatChannel, chatChannelMono)) return
|
||||
listener = new MCChatListener(this)
|
||||
TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin)
|
||||
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin) //These get undone if restarting/resetting - it will ignore events if disabled
|
||||
getPlugin.manager.registerCommand(mcChatCommand)
|
||||
getPlugin.manager.registerCommand(channelconCommand)
|
||||
val chcons = getConfig.getConfig.getConfigurationSection("chcons")
|
||||
if (chcons == null) { //Fallback to old place
|
||||
getConfig.getConfig.getRoot.getConfigurationSection("chcons")
|
||||
}
|
||||
if (chcons != null) {
|
||||
val chconkeys = chcons.getKeys(false)
|
||||
for (chconkey <- chconkeys) {
|
||||
val chcon = chcons.getConfigurationSection(chconkey)
|
||||
val mcch = Channel.getChannels.filter((ch: Channel) => ch.ID == chcon.getString("mcchid")).findAny
|
||||
val ch = DiscordPlugin.dc.getChannelById(Snowflake.of(chcon.getLong("chid"))).block
|
||||
val did = chcon.getLong("did")
|
||||
val user = DiscordPlugin.dc.getUserById(Snowflake.of(did)).block
|
||||
val groupid = chcon.getString("groupid")
|
||||
val toggles = chcon.getInt("toggles")
|
||||
val brtoggles = chcon.getStringList("brtoggles")
|
||||
if (!mcch.isPresent || ch == null || user == null || groupid == null) continue //todo: continue is not supported
|
||||
Bukkit.getScheduler.runTask(getPlugin, () => {
|
||||
def foo() = { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase)
|
||||
val dcp = DiscordConnectedPlayer.create(user, ch.asInstanceOf[MessageChannel], UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this)
|
||||
MCChatCustom.addCustomChat(ch.asInstanceOf[MessageChannel], groupid, mcch.get, user, dcp, toggles, brtoggles.stream.map(TBMCSystemChatEvent.BroadcastTarget.get).filter(Objects.nonNull).collect(Collectors.toSet))
|
||||
}
|
||||
|
||||
foo()
|
||||
})
|
||||
}
|
||||
}
|
||||
try if (lpInjector == null) lpInjector = new LPInjector(DiscordPlugin.plugin)
|
||||
catch {
|
||||
case e: Exception =>
|
||||
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this)
|
||||
case e: NoClassDefFoundError =>
|
||||
log("No LuckPerms, not injecting")
|
||||
//e.printStackTrace();
|
||||
}
|
||||
if (addFakePlayersToBukkit.get) try {
|
||||
serverWatcher = new ServerWatcher
|
||||
serverWatcher.enableDisable(true)
|
||||
log("Finished hooking into the server")
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
TBMCCoreAPI.SendException("Failed to hack the server (object)! Disable addFakePlayersToBukkit in the config.", e, this)
|
||||
}
|
||||
if (MinecraftChatModule.state eq DPState.RESTARTING_PLUGIN) { //These will only execute if the chat is enabled
|
||||
sendStateMessage(Color.CYAN, "Discord plugin restarted - chat connected.") //Really important to note the chat, hmm
|
||||
MinecraftChatModule.state = DPState.RUNNING
|
||||
}
|
||||
else if (MinecraftChatModule.state eq DPState.DISABLED_MCCHAT) {
|
||||
sendStateMessage(Color.CYAN, "Minecraft chat enabled - chat connected.")
|
||||
MinecraftChatModule.state = DPState.RUNNING
|
||||
}
|
||||
else if (serverUp.get) {
|
||||
sendStateMessage(Color.YELLOW, "Server started after a crash - chat connected.")
|
||||
val thr = new Throwable("The server shut down unexpectedly. See the log of the previous run for more details.")
|
||||
thr.setStackTrace(new Array[StackTraceElement](0))
|
||||
TBMCCoreAPI.SendException("The server crashed!", thr, this)
|
||||
}
|
||||
else sendStateMessage(Color.GREEN, "Server started - chat connected.")
|
||||
serverUp.set(true)
|
||||
}
|
||||
|
||||
override protected def disable(): Unit = {
|
||||
disabling = true
|
||||
if (MinecraftChatModule.state eq DPState.RESTARTING_PLUGIN) sendStateMessage(Color.ORANGE, "Discord plugin restarting")
|
||||
else if (MinecraftChatModule.state eq DPState.RUNNING) {
|
||||
sendStateMessage(Color.ORANGE, "Minecraft chat disabled")
|
||||
MinecraftChatModule.state = DPState.DISABLED_MCCHAT
|
||||
}
|
||||
else {
|
||||
val kickmsg = if (Bukkit.getOnlinePlayers.size > 0)
|
||||
DPUtils.sanitizeString(Bukkit.getOnlinePlayers.stream.map(_.getDisplayName).collect(Collectors.joining(", "))) +
|
||||
(if (Bukkit.getOnlinePlayers.size == 1) " was " else " were ") + "thrown out" //TODO: Make configurable
|
||||
else ""
|
||||
if (MinecraftChatModule.state eq DPState.RESTARTING_SERVER) sendStateMessage(Color.ORANGE, "Server restarting", kickmsg)
|
||||
else if (MinecraftChatModule.state eq DPState.STOPPING_SERVER) sendStateMessage(Color.RED, "Server stopping", kickmsg)
|
||||
else sendStateMessage(Color.GRAY, "Unknown state, please report.")
|
||||
//If 'restart' is disabled then this isn't shown even if joinleave is enabled}
|
||||
}
|
||||
serverUp.set(false) //Disable even if just the component is disabled because that way it won't falsely report crashes
|
||||
try //If it's not enabled it won't do anything
|
||||
if (serverWatcher != null) {
|
||||
serverWatcher.enableDisable(false)
|
||||
log("Finished unhooking the server")
|
||||
}
|
||||
catch {
|
||||
case e: Exception =>
|
||||
TBMCCoreAPI.SendException("Failed to restore the server object!", e, this)
|
||||
}
|
||||
val chcons = MCChatCustom.getCustomChats
|
||||
val chconsc = getConfig.getConfig.createSection("chcons")
|
||||
for (chcon <- chcons) {
|
||||
val chconc = chconsc.createSection(chcon.channel.getId.asString)
|
||||
chconc.set("mcchid", chcon.mcchannel.ID)
|
||||
chconc.set("chid", chcon.channel.getId.asLong)
|
||||
chconc.set("did", chcon.user.getId.asLong)
|
||||
chconc.set("mcuid", chcon.dcp.getUniqueId.toString)
|
||||
chconc.set("mcname", chcon.dcp.getName)
|
||||
chconc.set("groupid", chcon.groupID)
|
||||
chconc.set("toggles", chcon.toggles)
|
||||
chconc.set("brtoggles", chcon.brtoggles.stream.map(_.getName).collect(Collectors.toList))
|
||||
}
|
||||
if (listener != null) { //Can be null if disabled because of a config error
|
||||
listener.stop(true)
|
||||
}
|
||||
getPlugin.manager.unregisterCommand(mcChatCommand)
|
||||
getPlugin.manager.unregisterCommand(channelconCommand)
|
||||
disabling = false
|
||||
}
|
||||
|
||||
/**
|
||||
* It will block to make sure all messages are sent
|
||||
*/
|
||||
private def sendStateMessage(color: Color, message: String) =
|
||||
MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message))),
|
||||
ChannelconBroadcast.RESTART, hookmsg = false).block
|
||||
|
||||
private def sendStateMessage(color: Color, message: String, extra: String) =
|
||||
MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra))
|
||||
.onErrorResume((_: Throwable) => Mono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block
|
||||
}
|
|
@ -4,8 +4,6 @@ import buttondevteam.discordplugin.DPUtils;
|
|||
import buttondevteam.discordplugin.DiscordPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||
import buttondevteam.discordplugin.util.DPState;
|
||||
import buttondevteam.lib.chat.Command2;
|
||||
import buttondevteam.lib.chat.CommandClass;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package buttondevteam.discordplugin.playerfaker;
|
||||
|
||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import com.destroystokyo.paper.profile.CraftPlayerProfile;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
|
||||
|
|
|
@ -2,7 +2,6 @@ package buttondevteam.discordplugin.playerfaker;
|
|||
|
||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||
import buttondevteam.discordplugin.IMCPlayer;
|
||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
|
@ -2,7 +2,6 @@ package buttondevteam.discordplugin.playerfaker.perm;
|
|||
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
|
||||
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
|
||||
|
|
Loading…
Reference in a new issue