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;
|
package buttondevteam.discordplugin;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
|
||||||
import buttondevteam.discordplugin.util.DPState;
|
import buttondevteam.discordplugin.util.DPState;
|
||||||
import org.apache.logging.log4j.Level;
|
import org.apache.logging.log4j.Level;
|
||||||
import org.apache.logging.log4j.core.Filter;
|
import org.apache.logging.log4j.core.Filter;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package buttondevteam.discordplugin;
|
package buttondevteam.discordplugin;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
|
||||||
import buttondevteam.discordplugin.playerfaker.DiscordInventory;
|
import buttondevteam.discordplugin.playerfaker.DiscordInventory;
|
||||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
||||||
import discord4j.core.object.entity.User;
|
import discord4j.core.object.entity.User;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package buttondevteam.discordplugin;
|
package buttondevteam.discordplugin;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatPrivate;
|
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
import buttondevteam.lib.player.UserClass;
|
import buttondevteam.lib.player.UserClass;
|
||||||
import discord4j.core.object.entity.User;
|
import discord4j.core.object.entity.User;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package buttondevteam.discordplugin;
|
package buttondevteam.discordplugin;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
|
||||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
||||||
import discord4j.core.object.entity.User;
|
import discord4j.core.object.entity.User;
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
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.DiscordPlayer;
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
import buttondevteam.discordplugin.DiscordPlugin;
|
||||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
|
||||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
|
||||||
import buttondevteam.discordplugin.util.DPState;
|
import buttondevteam.discordplugin.util.DPState;
|
||||||
import buttondevteam.lib.chat.Command2;
|
import buttondevteam.lib.chat.Command2;
|
||||||
import buttondevteam.lib.chat.CommandClass;
|
import buttondevteam.lib.chat.CommandClass;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package buttondevteam.discordplugin.playerfaker;
|
package buttondevteam.discordplugin.playerfaker;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
|
||||||
import com.destroystokyo.paper.profile.CraftPlayerProfile;
|
import com.destroystokyo.paper.profile.CraftPlayerProfile;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
|
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
|
||||||
|
|
|
@ -2,7 +2,6 @@ package buttondevteam.discordplugin.playerfaker;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||||
import buttondevteam.discordplugin.IMCPlayer;
|
import buttondevteam.discordplugin.IMCPlayer;
|
||||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
|
@ -2,7 +2,6 @@ package buttondevteam.discordplugin.playerfaker.perm;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
import buttondevteam.discordplugin.DiscordPlugin;
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
|
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
|
||||||
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
|
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
|
||||||
|
|
Loading…
Reference in a new issue