From 5c3a0d8027eb7be1d5decea71eae4d9bd03accc7 Mon Sep 17 00:00:00 2001 From: Sho Sakuma Date: Tue, 27 Jan 2026 18:53:52 +0900 Subject: feat: Add channe-chat feature --- .../dev/m1sk9/lunaticChat/paper/LunaticChat.kt | 3 + .../m1sk9/lunaticChat/paper/ServiceContainer.kt | 3 + .../m1sk9/lunaticChat/paper/ServiceInitializer.kt | 25 ++- .../paper/chat/handler/ChannelMessageHandler.kt | 4 +- .../chat/handler/ChannelNotificationHandler.kt | 142 ++++++++++++++ .../paper/command/impl/lc/ChannelCommand.kt | 108 +++++++++++ .../paper/command/impl/lc/LunaticChatCommand.kt | 19 +- .../command/impl/lc/channel/ChannelBanCommand.kt | 210 +++++++++++++++++++++ .../impl/lc/channel/ChannelCreateCommand.kt | 21 ++- .../impl/lc/channel/ChannelDeleteCommand.kt | 31 ++- .../impl/lc/channel/ChannelInviteCommand.kt | 192 +++++++++++++++++++ .../command/impl/lc/channel/ChannelJoinCommand.kt | 25 ++- .../command/impl/lc/channel/ChannelKickCommand.kt | 207 ++++++++++++++++++++ .../command/impl/lc/channel/ChannelLeaveCommand.kt | 7 + .../command/impl/lc/channel/ChannelModCommand.kt | 198 +++++++++++++++++++ .../impl/lc/channel/ChannelOwnershipCommand.kt | 184 ++++++++++++++++++ .../impl/lc/channel/ChannelStatusCommand.kt | 26 ++- .../command/impl/lc/channel/ChannelUnbanCommand.kt | 159 ++++++++++++++++ .../paper/listener/EventListenerRegistry.kt | 3 + .../paper/listener/PlayerChatListener.kt | 24 ++- platform-paper/src/main/resources/languages/en.yml | 70 +++++++ platform-paper/src/main/resources/languages/ja.yml | 72 ++++++- platform-paper/src/main/resources/paper-plugin.yml | 14 ++ 23 files changed, 1716 insertions(+), 31 deletions(-) create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelNotificationHandler.kt create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt create mode 100644 platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt (limited to 'platform-paper/src') diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt index d238dd6..ba2eca6 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt @@ -4,6 +4,7 @@ import dev.m1sk9.lunaticChat.paper.chat.ChatModeManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelMessageHandler +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler import dev.m1sk9.lunaticChat.paper.chat.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.command.core.CommandRegistry import dev.m1sk9.lunaticChat.paper.command.impl.ReplyCommand @@ -36,6 +37,7 @@ class LunaticChat : var channelMembershipManager: ChannelMembershipManager? = null var chatModeManager: ChatModeManager? = null var channelMessageHandler: ChannelMessageHandler? = null + var channelNotificationHandler: ChannelNotificationHandler? = null // Private services private lateinit var services: ServiceContainer @@ -73,6 +75,7 @@ class LunaticChat : channelMembershipManager = services.channelMembershipManager chatModeManager = services.chatModeManager channelMessageHandler = services.channelMessageHandler + channelNotificationHandler = services.channelNotificationHandler // Schedule periodic tasks serviceInitializer.schedulePeriodicTasks() diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt index 3cd9e73..9069bc4 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt @@ -4,6 +4,7 @@ import dev.m1sk9.lunaticChat.paper.chat.ChatModeManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelMessageHandler +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler import dev.m1sk9.lunaticChat.paper.chat.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager @@ -23,6 +24,7 @@ import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager * @property channelMembershipManager Optional (only when channel chat feature is enabled) * @property chatModeManager Optional (only when channel chat feature is enabled) * @property channelMessageHandler Optional (only when channel chat feature is enabled) + * @property channelNotificationHandler Optional (only when channel chat feature is enabled) */ data class ServiceContainer( val languageManager: LanguageManager, @@ -33,4 +35,5 @@ data class ServiceContainer( val channelMembershipManager: ChannelMembershipManager? = null, val chatModeManager: ChatModeManager? = null, val channelMessageHandler: ChannelMessageHandler? = null, + val channelNotificationHandler: ChannelNotificationHandler? = null, ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt index 8135fe0..cea20bf 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt @@ -7,6 +7,7 @@ import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelStorage import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelMessageHandler +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler import dev.m1sk9.lunaticChat.paper.chat.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration import dev.m1sk9.lunaticChat.paper.converter.ConversionCache @@ -27,6 +28,7 @@ private data class ChannelComponents( val channelMembershipManager: ChannelMembershipManager, val chatModeManager: ChatModeManager, val channelMessageHandler: ChannelMessageHandler, + val channelNotificationHandler: ChannelNotificationHandler, ) /** @@ -46,6 +48,7 @@ class ServiceInitializer( private var channelMembershipManager: ChannelMembershipManager? = null private var chatModeManager: ChatModeManager? = null private var channelMessageHandler: ChannelMessageHandler? = null + private var channelNotificationHandler: ChannelNotificationHandler? = null /** * Initializes all services in dependency order. @@ -81,10 +84,10 @@ class ServiceInitializer( null } - // 4. Initialize channel manager, membership manager, chat mode manager, and channel message handler + // 4. Initialize channel manager, membership manager, chat mode manager, channel message handler, and notification handler val channelComponents = if (configuration.features.channelChat.enabled) { - initializeChannelManager(playerSettingsManager, romajiConverter) + initializeChannelManager(playerSettingsManager, romajiConverter, languageManager) } else { null } @@ -92,6 +95,7 @@ class ServiceInitializer( val channelMembershipManager = channelComponents?.channelMembershipManager val chatModeManager = channelComponents?.chatModeManager val channelMessageHandler = channelComponents?.channelMessageHandler + val channelNotificationHandler = channelComponents?.channelNotificationHandler // 5. Initialize handlers val directMessageHandler = @@ -109,6 +113,7 @@ class ServiceInitializer( channelMembershipManager = channelMembershipManager, chatModeManager = chatModeManager, channelMessageHandler = channelMessageHandler, + channelNotificationHandler = channelNotificationHandler, ) } @@ -173,11 +178,12 @@ class ServiceInitializer( } /** - * Initializes channel manager, membership manager, chat mode manager, and channel message handler with storage. + * Initializes channel manager, membership manager, chat mode manager, channel message handler, and notification handler with storage. */ private fun initializeChannelManager( settingsManager: PlayerSettingsManager, romajiConverter: RomanjiConverter?, + languageManager: LanguageManager, ): ChannelComponents { val channelsFile = plugin.dataFolder.resolve("channels.json").toPath() val storage = @@ -230,12 +236,23 @@ class ServiceInitializer( ) channelMessageHandler = messageHandler - logger.info("Channel manager, membership manager, chat mode manager, and channel message handler initialized successfully.") + val notificationHandler = + ChannelNotificationHandler( + channelManager = manager, + languageManager = languageManager, + ) + channelNotificationHandler = notificationHandler + + logger.info( + "Channel manager, membership manager, chat mode manager, " + + "channel message handler, and notification handler initialized successfully.", + ) return ChannelComponents( channelManager = manager, channelMembershipManager = membershipManager, chatModeManager = chatMode, channelMessageHandler = messageHandler, + channelNotificationHandler = notificationHandler, ) } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt index 45e9d6d..89adb6f 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt @@ -54,10 +54,12 @@ class ChannelMessageHandler( player.playMessageSendNotification() } + // Send to spy players (exclude sender and channel members) + val memberIds = context.members.map { it.playerId }.toSet() SpyPermissionManager .getDirectMessageSpyPlayers() .values - .filter { it.isOnline && it.uniqueId != playerId } + .filter { it.isOnline && it.uniqueId != playerId && it.uniqueId !in memberIds } .forEach { it.sendMessage(spyMessage) } context.members.forEach { member -> Bukkit.getPlayer(member.playerId)?.let { memberPlayer -> diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelNotificationHandler.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelNotificationHandler.kt new file mode 100644 index 0000000..0352780 --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelNotificationHandler.kt @@ -0,0 +1,142 @@ +package dev.m1sk9.lunaticChat.paper.chat.handler + +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import org.bukkit.Bukkit + +/** + * Handles broadcasting notifications to channel members. + */ +class ChannelNotificationHandler( + private val channelManager: ChannelManager, + private val languageManager: LanguageManager, +) { + /** + * Broadcasts a join notification to all members of a channel. + * + * @param channelId The ID of the channel. + * @param playerName The name of the player who joined. + */ + fun broadcastJoin( + channelId: String, + playerName: String, + ) { + val channel = channelManager.getChannel(channelId).getOrNull() ?: return + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: return + + val message = + languageManager.getMessage( + "channel.notification.playerJoined", + mapOf("player" to playerName, "channel" to channel.name), + ) + val formattedMessage = MessageFormatter.format(message) + + members.forEach { member -> + Bukkit.getPlayer(member.playerId)?.let { player -> + if (player.isOnline) { + player.sendMessage(formattedMessage) + } + } + } + } + + /** + * Broadcasts a leave notification to all members of a channel. + * + * @param channelId The ID of the channel. + * @param playerName The name of the player who left. + */ + fun broadcastLeave( + channelId: String, + playerName: String, + ) { + val channel = channelManager.getChannel(channelId).getOrNull() ?: return + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: return + + val message = + languageManager.getMessage( + "channel.notification.playerLeft", + mapOf("player" to playerName, "channel" to channel.name), + ) + val formattedMessage = MessageFormatter.format(message) + + members.forEach { member -> + Bukkit.getPlayer(member.playerId)?.let { player -> + if (player.isOnline) { + player.sendMessage(formattedMessage) + } + } + } + } + + /** + * Broadcasts a kick notification to all members of a channel. + * + * @param channelId The ID of the channel. + * @param kickedPlayerName The name of the player who was kicked. + * @param kickerName The name of the player who performed the kick. + */ + fun broadcastKick( + channelId: String, + kickedPlayerName: String, + kickerName: String, + ) { + val channel = channelManager.getChannel(channelId).getOrNull() ?: return + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: return + + val message = + languageManager.getMessage( + "channel.notification.playerKicked", + mapOf( + "player" to kickedPlayerName, + "channel" to channel.name, + "kicker" to kickerName, + ), + ) + val formattedMessage = MessageFormatter.format(message) + + members.forEach { member -> + Bukkit.getPlayer(member.playerId)?.let { player -> + if (player.isOnline) { + player.sendMessage(formattedMessage) + } + } + } + } + + /** + * Broadcasts a ban notification to all members of a channel. + * + * @param channelId The ID of the channel. + * @param bannedPlayerName The name of the player who was banned. + * @param bannerName The name of the player who performed the ban. + */ + fun broadcastBan( + channelId: String, + bannedPlayerName: String, + bannerName: String, + ) { + val channel = channelManager.getChannel(channelId).getOrNull() ?: return + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: return + + val message = + languageManager.getMessage( + "channel.notification.playerBanned", + mapOf( + "player" to bannedPlayerName, + "channel" to channel.name, + "banner" to bannerName, + ), + ) + val formattedMessage = MessageFormatter.format(message) + + members.forEach { member -> + Bukkit.getPlayer(member.playerId)?.let { player -> + if (player.isOnline) { + player.sendMessage(formattedMessage) + } + } + } + } +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt index 90d8f31..230a813 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt @@ -6,18 +6,25 @@ import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode import dev.m1sk9.lunaticChat.paper.LunaticChat import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler import dev.m1sk9.lunaticChat.paper.command.annotation.Permission import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly import dev.m1sk9.lunaticChat.paper.command.core.CommandContext import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelBanCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelCreateCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelDeleteCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelInfoCommand +import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelInviteCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelJoinCommand +import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelKickCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelLeaveCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelListCommand +import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelModCommand +import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelOwnershipCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelStatusCommand import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelSwitchCommand +import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelUnbanCommand import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter import io.papermc.paper.command.brigadier.CommandSourceStack @@ -29,6 +36,7 @@ class ChannelCommand( plugin: LunaticChat, private val channelManager: ChannelManager, private val membershipManager: ChannelMembershipManager, + private val notificationHandler: ChannelNotificationHandler, private val languageManager: LanguageManager, ) : LunaticCommand(plugin) { fun buildWithPermissionCheck(): LiteralArgumentBuilder { @@ -59,6 +67,7 @@ class ChannelCommand( plugin, channelManager, membershipManager, + notificationHandler, languageManager, ).buildWithPermissionCheck(), ).then( @@ -66,6 +75,7 @@ class ChannelCommand( plugin, channelManager, membershipManager, + notificationHandler, languageManager, ).buildWithPermissionCheck(), ).then( @@ -94,6 +104,50 @@ class ChannelCommand( channelManager, languageManager, ).buildWithPermissionCheck(), + ).then( + ChannelInviteCommand( + plugin, + channelManager, + membershipManager, + languageManager, + ).buildWithPermissionCheck(), + ).then( + ChannelKickCommand( + plugin, + channelManager, + membershipManager, + notificationHandler, + languageManager, + ).buildWithPermissionCheck(), + ).then( + ChannelBanCommand( + plugin, + channelManager, + membershipManager, + notificationHandler, + languageManager, + ).buildWithPermissionCheck(), + ).then( + ChannelUnbanCommand( + plugin, + channelManager, + membershipManager, + languageManager, + ).buildWithPermissionCheck(), + ).then( + ChannelModCommand( + plugin, + channelManager, + membershipManager, + languageManager, + ).buildWithPermissionCheck(), + ).then( + ChannelOwnershipCommand( + plugin, + channelManager, + membershipManager, + languageManager, + ).buildWithPermissionCheck(), ) // Default help message when no subcommand is provided @@ -188,6 +242,60 @@ class ChannelCommand( ), ), ) + sender.sendMessage( + Component + .text(" ") + .append( + MessageFormatter.formatSuccess( + languageManager.getMessage("channel.help.invite"), + ), + ), + ) + sender.sendMessage( + Component + .text(" ") + .append( + MessageFormatter.formatSuccess( + languageManager.getMessage("channel.help.kick"), + ), + ), + ) + sender.sendMessage( + Component + .text(" ") + .append( + MessageFormatter.formatSuccess( + languageManager.getMessage("channel.help.ban"), + ), + ), + ) + sender.sendMessage( + Component + .text(" ") + .append( + MessageFormatter.formatSuccess( + languageManager.getMessage("channel.help.unban"), + ), + ), + ) + sender.sendMessage( + Component + .text(" ") + .append( + MessageFormatter.formatSuccess( + languageManager.getMessage("channel.help.mod"), + ), + ), + ) + sender.sendMessage( + Component + .text(" ") + .append( + MessageFormatter.formatSuccess( + languageManager.getMessage("channel.help.ownership"), + ), + ), + ) return CommandResult.Success } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/LunaticChatCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/LunaticChatCommand.kt index af08089..9f608d8 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/LunaticChatCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/LunaticChatCommand.kt @@ -51,14 +51,17 @@ class LunaticChatCommand( // Add channel command if channel manager is available plugin.channelManager?.let { manager -> plugin.channelMembershipManager?.let { membershipManager -> - command.then( - ChannelCommand( - plugin, - manager, - membershipManager, - languageManager, - ).buildWithPermissionCheck(), - ) + plugin.channelNotificationHandler?.let { notificationHandler -> + command.then( + ChannelCommand( + plugin, + manager, + membershipManager, + notificationHandler, + languageManager, + ).buildWithPermissionCheck(), + ) + } } } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt new file mode 100644 index 0000000..90d4e6d --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt @@ -0,0 +1,210 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerAlreadyBannedException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerBypassBanException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler +import dev.m1sk9.lunaticChat.paper.command.annotation.Permission +import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import org.bukkit.Bukkit + +@PlayerOnly +class ChannelBanCommand( + plugin: LunaticChat, + private val channelManager: ChannelManager, + private val membershipManager: ChannelMembershipManager, + private val notificationHandler: ChannelNotificationHandler, + private val languageManager: LanguageManager, +) : LunaticCommand(plugin) { + fun buildWithPermissionCheck(): LiteralArgumentBuilder { + val builder = build() + return applyMethodPermission("build", builder) + } + + @Permission(LunaticChatPermissionNode.ChannelBan::class) + fun build(): LiteralArgumentBuilder = + Commands + .literal("ban") + .then( + Commands + .argument("playerName", StringArgumentType.word()) + .suggests { ctx, builder -> + val player = ctx.source.executor as? org.bukkit.entity.Player + if (player != null) { + val channelId = channelManager.getPlayerChannel(player.uniqueId) + if (channelId != null) { + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: emptyList() + members + .filter { it.playerId != player.uniqueId } + .forEach { member -> + Bukkit.getOfflinePlayer(member.playerId).name?.let { name -> + builder.suggest(name) + } + } + } + } + builder.buildFuture() + }.executes { ctx -> + val context = wrapContext(ctx) + checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } + + val playerName = StringArgumentType.getString(ctx, "playerName") + val result = execute(context, playerName) + handleResult(context, result) + }, + ) + + private fun execute( + ctx: CommandContext, + playerName: String, + ): CommandResult { + val sender = ctx.requirePlayer() + + val channelId = + channelManager.getPlayerChannel(sender.uniqueId) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ban.noActiveChannel"), + ), + ) + + // Check if sender has permission (OWNER or MODERATOR) + val senderRole = membershipManager.getMemberRoleOrNull(sender.uniqueId, channelId) + if (senderRole == null || senderRole == ChannelRole.MEMBER) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ban.noPermission"), + ), + ) + } + + // Find target player + val targetPlayer = Bukkit.getOfflinePlayer(playerName) + + // Check if player exists (has played before or is online) + if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.ban.playerNotFound", + mapOf("player" to playerName), + ), + ), + ) + } + + val targetPlayerId = targetPlayer.uniqueId + + // Check if banning self + if (targetPlayerId == sender.uniqueId) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ban.cannotBanSelf"), + ), + ) + } + + // Check if target has bypass permission + val onlineTargetPlayer = Bukkit.getPlayer(playerName) + if (onlineTargetPlayer != null && onlineTargetPlayer.hasPermission(LunaticChatPermissionNode.ChannelBypass.permissionNode)) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.ban.cannotBanBypass", + mapOf("player" to onlineTargetPlayer.name), + ), + ), + ) + } + + // Ban player from channel + val banResult = channelManager.banPlayer(channelId, targetPlayerId) + return banResult.fold( + onSuccess = { + val channel = channelManager.getChannel(channelId).getOrNull() + val channelName = channel?.name ?: channelId + + // Send notification to banned player if online + onlineTargetPlayer?.let { player -> + player.sendMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.ban.wasBanned", + mapOf("channel" to channelName, "banner" to sender.name), + ), + ), + ) + } + + // Broadcast ban notification to remaining members + notificationHandler.broadcastBan(channelId, playerName, sender.name) + + CommandResult.SuccessWithMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.ban.success", + mapOf("player" to playerName, "channel" to channelName), + ), + ), + ) + }, + onFailure = { error -> + when (error) { + is ChannelNotFoundException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ban.error"), + ), + ) + } + is ChannelPlayerBypassBanException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.ban.cannotBanBypass", + mapOf("player" to playerName), + ), + ), + ) + } + is ChannelPlayerAlreadyBannedException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.ban.alreadyBanned", + mapOf("player" to playerName), + ), + ), + ) + } + else -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ban.error"), + ), + ) + } + } + }, + ) + } + + override fun buildCommand(): LiteralArgumentBuilder = + throw UnsupportedOperationException( + "ChannelBanCommand should use build() method instead of buildCommand()", + ) +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt index 21c147a..cad2231 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt @@ -114,13 +114,22 @@ class ChannelCreateCommand( val result = channelManager.createChannel(channel) return result.fold( onSuccess = { + val successMessage = + languageManager.getMessage( + "channel.create.success", + mapOf("name" to name, "id" to channelId), + ) + + val message = + if (isPrivate) { + val privateNotice = languageManager.getMessage("channel.create.privateNotice") + "$successMessage\n$privateNotice" + } else { + successMessage + } + CommandResult.SuccessWithMessage( - MessageFormatter.format( - languageManager.getMessage( - "channel.create.success", - mapOf("name" to name, "id" to channelId), - ), - ), + MessageFormatter.format(message), ) }, onFailure = { error -> diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt index 9d39c70..8ca3a55 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt @@ -35,7 +35,33 @@ class ChannelDeleteCommand( .then( Commands .argument("channelId", StringArgumentType.word()) - .executes { ctx -> + .suggests { ctx, builder -> + val player = ctx.source.executor as? org.bukkit.entity.Player + if (player != null) { + val hasBypass = + player.hasPermission( + LunaticChatPermissionNode.ChannelBypass.permissionNode, + ) + + val channels = + if (hasBypass) { + // Show all channels if has bypass permission + channelManager.getAllChannels().getOrNull() ?: emptyList() + } else { + // Show only owned channels + channelManager + .getAllChannels() + .getOrNull() + ?.filter { it.ownerId == player.uniqueId } + ?: emptyList() + } + + channels.forEach { channel -> + builder.suggest(channel.id) + } + } + builder.buildFuture() + }.executes { ctx -> val context = wrapContext(ctx) checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } @@ -51,8 +77,9 @@ class ChannelDeleteCommand( channelId: String, ): CommandResult { val sender = ctx.requirePlayer() + val hasBypass = sender.hasPermission(LunaticChatPermissionNode.ChannelBypass.permissionNode) - val result = channelManager.deleteChannel(channelId, sender.uniqueId) + val result = channelManager.deleteChannel(channelId, sender.uniqueId, hasBypass) return result.fold( onSuccess = { CommandResult.SuccessWithMessage( diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt new file mode 100644 index 0000000..d4ce07a --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt @@ -0,0 +1,192 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberLimitExceededException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerBannedException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.annotation.Permission +import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import org.bukkit.Bukkit + +@PlayerOnly +class ChannelInviteCommand( + plugin: LunaticChat, + private val channelManager: ChannelManager, + private val membershipManager: ChannelMembershipManager, + private val languageManager: LanguageManager, +) : LunaticCommand(plugin) { + fun buildWithPermissionCheck(): LiteralArgumentBuilder { + val builder = build() + return applyMethodPermission("build", builder) + } + + @Permission(LunaticChatPermissionNode.ChannelInvite::class) + fun build(): LiteralArgumentBuilder = + Commands + .literal("invite") + .then( + Commands + .argument("playerName", StringArgumentType.word()) + .suggests { _, builder -> + Bukkit + .getOnlinePlayers() + .filter { it.isOnline } + .forEach { player -> + builder.suggest(player.name) + } + builder.buildFuture() + }.executes { ctx -> + val context = wrapContext(ctx) + checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } + + val playerName = StringArgumentType.getString(ctx, "playerName") + val result = execute(context, playerName) + handleResult(context, result) + }, + ) + + private fun execute( + ctx: CommandContext, + playerName: String, + ): CommandResult { + val sender = ctx.requirePlayer() + + // Get sender's active channel + val channelId = + channelManager.getPlayerChannel(sender.uniqueId) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.invite.noActiveChannel"), + ), + ) + + // Check if sender has permission (OWNER or MODERATOR) + val senderRole = membershipManager.getMemberRoleOrNull(sender.uniqueId, channelId) + if (senderRole == null || senderRole == ChannelRole.MEMBER) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.invite.noPermission"), + ), + ) + } + + // Find target player + val targetPlayer = + Bukkit.getPlayer(playerName) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.invite.playerNotFound", + mapOf("player" to playerName), + ), + ), + ) + + // Check if inviting self + if (targetPlayer.uniqueId == sender.uniqueId) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.invite.cannotInviteSelf"), + ), + ) + } + + // Check if player is banned + val isBanned = channelManager.isPlayerBanned(channelId, targetPlayer.uniqueId).getOrElse { false } + if (isBanned) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.invite.playerBanned", + mapOf("player" to targetPlayer.name), + ), + ), + ) + } + + // Attempt to join the target player to the channel (bypass private check for invites) + val result = membershipManager.joinChannel(targetPlayer.uniqueId, channelId, bypassPrivateCheck = true) + return result.fold( + onSuccess = { + // Send success message to sender + val channel = channelManager.getChannel(channelId).getOrNull() + val channelName = channel?.name ?: channelId + + // Send notification to invited player + targetPlayer.sendMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.invite.receivedInvite", + mapOf("channel" to channelName, "inviter" to sender.name), + ), + ), + ) + + CommandResult.SuccessWithMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.invite.success", + mapOf("player" to targetPlayer.name, "channel" to channelName), + ), + ), + ) + }, + onFailure = { error -> + when (error) { + is ChannelNotFoundException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.invite.error"), + ), + ) + } + is ChannelMemberLimitExceededException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.invite.channelFull", + mapOf("limit" to error.limit.toString()), + ), + ), + ) + } + is ChannelPlayerBannedException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.invite.playerBanned", + mapOf("player" to targetPlayer.name), + ), + ), + ) + } + else -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.invite.error"), + ), + ) + } + } + }, + ) + } + + override fun buildCommand(): LiteralArgumentBuilder = + throw UnsupportedOperationException( + "ChannelInviteCommand should use build() method instead of buildCommand()", + ) +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt index c7a86ad..83a40b9 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt @@ -7,11 +7,14 @@ import dev.m1sk9.lunaticChat.engine.exception.ChannelAlreadyActiveException import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberAlreadyException import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberLimitExceededException import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException -import dev.m1sk9.lunaticChat.engine.exception.PlayerChannelLimitExceededException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerBannedException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerMembershipLimitExceededException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPrivateRequiresInvitationException import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode import dev.m1sk9.lunaticChat.paper.LunaticChat import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler import dev.m1sk9.lunaticChat.paper.command.annotation.Permission import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly import dev.m1sk9.lunaticChat.paper.command.core.CommandContext @@ -27,6 +30,7 @@ class ChannelJoinCommand( plugin: LunaticChat, private val channelManager: ChannelManager, private val membershipManager: ChannelMembershipManager, + private val notificationHandler: ChannelNotificationHandler, private val languageManager: LanguageManager, ) : LunaticCommand(plugin) { fun buildWithPermissionCheck(): LiteralArgumentBuilder { @@ -72,6 +76,9 @@ class ChannelJoinCommand( // Play notification sound sender.playChannelJoinNotification() + // Broadcast join notification to all channel members + notificationHandler.broadcastJoin(channelId, sender.name) + CommandResult.SuccessWithMessage( MessageFormatter.format( languageManager.getMessage( @@ -125,7 +132,7 @@ class ChannelJoinCommand( ), ) } - is PlayerChannelLimitExceededException -> { + is ChannelPlayerMembershipLimitExceededException -> { CommandResult.Failure( MessageFormatter.formatError( languageManager.getMessage( @@ -135,6 +142,20 @@ class ChannelJoinCommand( ), ) } + is ChannelPlayerBannedException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.join.playerBanned"), + ), + ) + } + is ChannelPrivateRequiresInvitationException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.join.privateChannel"), + ), + ) + } else -> { CommandResult.Failure( MessageFormatter.formatError( diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt new file mode 100644 index 0000000..672cf2d --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt @@ -0,0 +1,207 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler +import dev.m1sk9.lunaticChat.paper.command.annotation.Permission +import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import org.bukkit.Bukkit + +@PlayerOnly +class ChannelKickCommand( + plugin: LunaticChat, + private val channelManager: ChannelManager, + private val membershipManager: ChannelMembershipManager, + private val notificationHandler: ChannelNotificationHandler, + private val languageManager: LanguageManager, +) : LunaticCommand(plugin) { + fun buildWithPermissionCheck(): LiteralArgumentBuilder { + val builder = build() + return applyMethodPermission("build", builder) + } + + @Permission(LunaticChatPermissionNode.ChannelKick::class) + fun build(): LiteralArgumentBuilder = + Commands + .literal("kick") + .then( + Commands + .argument("playerName", StringArgumentType.word()) + .suggests { ctx, builder -> + val player = ctx.source.executor as? org.bukkit.entity.Player + if (player != null) { + val channelId = channelManager.getPlayerChannel(player.uniqueId) + if (channelId != null) { + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: emptyList() + members + .filter { it.playerId != player.uniqueId } + .forEach { member -> + Bukkit.getOfflinePlayer(member.playerId).name?.let { name -> + builder.suggest(name) + } + } + } + } + builder.buildFuture() + }.executes { ctx -> + val context = wrapContext(ctx) + checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } + + val playerName = StringArgumentType.getString(ctx, "playerName") + val result = execute(context, playerName) + handleResult(context, result) + }, + ) + + private fun execute( + ctx: CommandContext, + playerName: String, + ): CommandResult { + val sender = ctx.requirePlayer() + + // Get sender's active channel + val channelId = + channelManager.getPlayerChannel(sender.uniqueId) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.kick.noActiveChannel"), + ), + ) + + // Check if sender has permission (OWNER or MODERATOR) + val senderRole = membershipManager.getMemberRoleOrNull(sender.uniqueId, channelId) + if (senderRole == null || senderRole == ChannelRole.MEMBER) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.kick.noPermission"), + ), + ) + } + + // Find target player + val targetPlayer = Bukkit.getOfflinePlayer(playerName) + + // Check if player exists (has played before or is online) + if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.kick.playerNotFound", + mapOf("player" to playerName), + ), + ), + ) + } + + val targetPlayerId = targetPlayer.uniqueId + + // Check if kicking self + if (targetPlayerId == sender.uniqueId) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.kick.cannotKickSelf"), + ), + ) + } + + // Check if target has bypass permission + val onlineTargetPlayer = Bukkit.getPlayer(playerName) + if (onlineTargetPlayer != null && onlineTargetPlayer.hasPermission(LunaticChatPermissionNode.ChannelBypass.permissionNode)) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.kick.cannotKickBypass", + mapOf("player" to onlineTargetPlayer.name), + ), + ), + ) + } + + // Check if target is a member + val isMember = membershipManager.isMember(targetPlayerId, channelId).getOrElse { false } + if (!isMember) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.kick.notMember", + mapOf("player" to playerName), + ), + ), + ) + } + + // Remove from channel + val removeResult = channelManager.removeMember(channelId, targetPlayerId) + return removeResult.fold( + onSuccess = { + // Clear their active channel if this was it + if (channelManager.getPlayerChannel(targetPlayerId) == channelId) { + channelManager.setPlayerChannel(targetPlayerId, null) + } + + val channel = channelManager.getChannel(channelId).getOrNull() + val channelName = channel?.name ?: channelId + + // Send notification to kicked player if online + onlineTargetPlayer?.let { player -> + player.sendMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.kick.wasKicked", + mapOf("channel" to channelName, "kicker" to sender.name), + ), + ), + ) + } + + // Broadcast kick notification to remaining members + notificationHandler.broadcastKick(channelId, playerName, sender.name) + + CommandResult.SuccessWithMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.kick.success", + mapOf("player" to playerName, "channel" to channelName), + ), + ), + ) + }, + onFailure = { error -> + when (error) { + is ChannelNotFoundException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.kick.error"), + ), + ) + } + else -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.kick.error"), + ), + ) + } + } + }, + ) + } + + override fun buildCommand(): LiteralArgumentBuilder = + throw UnsupportedOperationException( + "ChannelKickCommand should use build() method instead of buildCommand()", + ) +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt index 299ad47..5e45d27 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt @@ -7,6 +7,7 @@ import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode import dev.m1sk9.lunaticChat.paper.LunaticChat import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler import dev.m1sk9.lunaticChat.paper.command.annotation.Permission import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly import dev.m1sk9.lunaticChat.paper.command.core.CommandContext @@ -21,6 +22,7 @@ class ChannelLeaveCommand( plugin: LunaticChat, private val channelManager: ChannelManager, private val membershipManager: ChannelMembershipManager, + private val notificationHandler: ChannelNotificationHandler, private val languageManager: LanguageManager, ) : LunaticCommand(plugin) { fun buildWithPermissionCheck(): LiteralArgumentBuilder { @@ -48,6 +50,11 @@ class ChannelLeaveCommand( val result = membershipManager.leaveChannel(sender.uniqueId) return result.fold( onSuccess = { + // Broadcast leave notification to all channel members + if (currentChannelId != null) { + notificationHandler.broadcastLeave(currentChannelId, sender.name) + } + CommandResult.SuccessWithMessage( MessageFormatter.format( languageManager.getMessage( diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt new file mode 100644 index 0000000..82caa39 --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt @@ -0,0 +1,198 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.annotation.Permission +import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import org.bukkit.Bukkit + +@PlayerOnly +class ChannelModCommand( + plugin: LunaticChat, + private val channelManager: ChannelManager, + private val membershipManager: ChannelMembershipManager, + private val languageManager: LanguageManager, +) : LunaticCommand(plugin) { + fun buildWithPermissionCheck(): LiteralArgumentBuilder { + val builder = build() + return applyMethodPermission("build", builder) + } + + @Permission(LunaticChatPermissionNode.ChannelMod::class) + fun build(): LiteralArgumentBuilder = + Commands + .literal("mod") + .then( + Commands + .argument("playerName", StringArgumentType.word()) + .suggests { ctx, builder -> + val player = ctx.source.executor as? org.bukkit.entity.Player + if (player != null) { + val channelId = channelManager.getPlayerChannel(player.uniqueId) + if (channelId != null) { + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: emptyList() + members + .filter { it.playerId != player.uniqueId } + .forEach { member -> + Bukkit.getOfflinePlayer(member.playerId).name?.let { name -> + builder.suggest(name) + } + } + } + } + builder.buildFuture() + }.executes { ctx -> + val context = wrapContext(ctx) + checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } + + val playerName = StringArgumentType.getString(ctx, "playerName") + val result = execute(context, playerName) + handleResult(context, result) + }, + ) + + private fun execute( + ctx: CommandContext, + playerName: String, + ): CommandResult { + val sender = ctx.requirePlayer() + + // Get sender's active channel + val channelId = + channelManager.getPlayerChannel(sender.uniqueId) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.mod.noActiveChannel"), + ), + ) + + // Check if sender is OWNER + val senderRole = membershipManager.getMemberRoleOrNull(sender.uniqueId, channelId) + if (senderRole != ChannelRole.OWNER) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.mod.noPermission"), + ), + ) + } + + // Find target player + val targetPlayer = Bukkit.getOfflinePlayer(playerName) + + // Check if player exists (has played before or is online) + if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.mod.playerNotFound", + mapOf("player" to playerName), + ), + ), + ) + } + + val targetPlayerId = targetPlayer.uniqueId + + // Check if modding self + if (targetPlayerId == sender.uniqueId) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.mod.cannotModSelf"), + ), + ) + } + + // Check if target is a member + val targetRole = membershipManager.getMemberRoleOrNull(targetPlayerId, channelId) + if (targetRole == null) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.mod.notMember", + mapOf("player" to playerName), + ), + ), + ) + } + + // Toggle mod status + val newRole = + if (targetRole == ChannelRole.MODERATOR) { + ChannelRole.MEMBER + } else { + ChannelRole.MODERATOR + } + + val updateResult = channelManager.updateMemberRole(channelId, targetPlayerId, newRole) + return updateResult.fold( + onSuccess = { + val channel = channelManager.getChannel(channelId).getOrNull() + val channelName = channel?.name ?: channelId + + val action = + if (newRole == ChannelRole.MODERATOR) { + languageManager.getMessage("channel.mod.promoted") + } else { + languageManager.getMessage("channel.mod.demoted") + } + + val onlineTargetPlayer = Bukkit.getPlayer(playerName) + onlineTargetPlayer?.let { player -> + player.sendMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.mod.notification", + mapOf("action" to action, "channel" to channelName), + ), + ), + ) + } + + CommandResult.SuccessWithMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.mod.success", + mapOf("player" to playerName, "action" to action, "channel" to channelName), + ), + ), + ) + }, + onFailure = { error -> + when (error) { + is ChannelNotFoundException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.mod.error"), + ), + ) + } + else -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.mod.error"), + ), + ) + } + } + }, + ) + } + + override fun buildCommand(): LiteralArgumentBuilder = + throw UnsupportedOperationException( + "ChannelModCommand should use build() method instead of buildCommand()", + ) +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt new file mode 100644 index 0000000..576b072 --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt @@ -0,0 +1,184 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.annotation.Permission +import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import org.bukkit.Bukkit + +@PlayerOnly +class ChannelOwnershipCommand( + plugin: LunaticChat, + private val channelManager: ChannelManager, + private val membershipManager: ChannelMembershipManager, + private val languageManager: LanguageManager, +) : LunaticCommand(plugin) { + fun buildWithPermissionCheck(): LiteralArgumentBuilder { + val builder = build() + return applyMethodPermission("build", builder) + } + + @Permission(LunaticChatPermissionNode.ChannelOwnership::class) + fun build(): LiteralArgumentBuilder = + Commands + .literal("ownership") + .then( + Commands + .argument("playerName", StringArgumentType.word()) + .suggests { ctx, builder -> + val player = ctx.source.executor as? org.bukkit.entity.Player + if (player != null) { + val channelId = channelManager.getPlayerChannel(player.uniqueId) + if (channelId != null) { + val members = channelManager.getChannelMembers(channelId).getOrNull() ?: emptyList() + members + .filter { it.playerId != player.uniqueId } + .forEach { member -> + Bukkit.getOfflinePlayer(member.playerId).name?.let { name -> + builder.suggest(name) + } + } + } + } + builder.buildFuture() + }.executes { ctx -> + val context = wrapContext(ctx) + checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } + + val playerName = StringArgumentType.getString(ctx, "playerName") + val result = execute(context, playerName) + handleResult(context, result) + }, + ) + + private fun execute( + ctx: CommandContext, + playerName: String, + ): CommandResult { + val sender = ctx.requirePlayer() + + // Get sender's active channel + val channelId = + channelManager.getPlayerChannel(sender.uniqueId) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ownership.noActiveChannel"), + ), + ) + + // Check if sender is OWNER + val senderRole = membershipManager.getMemberRoleOrNull(sender.uniqueId, channelId) + if (senderRole != ChannelRole.OWNER) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ownership.noPermission"), + ), + ) + } + + // Find target player + val targetPlayer = Bukkit.getOfflinePlayer(playerName) + + // Check if player exists (has played before or is online) + if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.ownership.playerNotFound", + mapOf("player" to playerName), + ), + ), + ) + } + + val targetPlayerId = targetPlayer.uniqueId + + // Check if transferring to self + if (targetPlayerId == sender.uniqueId) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ownership.cannotTransferToSelf"), + ), + ) + } + + // Check if target is a member + val targetRole = membershipManager.getMemberRoleOrNull(targetPlayerId, channelId) + if (targetRole == null) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.ownership.notMember", + mapOf("player" to playerName), + ), + ), + ) + } + + // Transfer ownership + val updateResult = channelManager.updateChannelOwner(channelId, targetPlayerId) + return updateResult.fold( + onSuccess = { + val channelName = it.name + + // Notify target player + val onlineTargetPlayer = Bukkit.getPlayer(playerName) + onlineTargetPlayer?.let { player -> + player.sendMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.ownership.receivedOwnership", + mapOf("channel" to channelName, "previousOwner" to sender.name), + ), + ), + ) + } + + CommandResult.SuccessWithMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.ownership.success", + mapOf("player" to playerName, "channel" to channelName), + ), + ), + ) + }, + onFailure = { error -> + when (error) { + is ChannelNotFoundException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ownership.error"), + ), + ) + } + else -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.ownership.error"), + ), + ) + } + } + }, + ) + } + + override fun buildCommand(): LiteralArgumentBuilder = + throw UnsupportedOperationException( + "ChannelOwnershipCommand should use build() method instead of buildCommand()", + ) +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt index 571aa83..3b65765 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt @@ -1,6 +1,7 @@ package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole import dev.m1sk9.lunaticChat.engine.command.CommandResult import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode import dev.m1sk9.lunaticChat.paper.LunaticChat @@ -100,7 +101,14 @@ class ChannelStatusCommand( if (activeMembers.isNotEmpty()) { val memberNames = activeMembers.mapNotNull { member -> - Bukkit.getOfflinePlayer(member.playerId).name + val playerName = Bukkit.getOfflinePlayer(member.playerId).name ?: return@mapNotNull null + val roleText = + when (member.role) { + ChannelRole.OWNER -> " [OWNER]" + ChannelRole.MODERATOR -> " [MOD]" + ChannelRole.MEMBER -> "" + } + playerName + roleText } val membersText = @@ -113,16 +121,24 @@ class ChannelStatusCommand( ) Component .text(" ") - .append(Component.text(languageManager.getMessage("channel.info.members"), NamedTextColor.GRAY)) - .append(Component.text(": ", NamedTextColor.GRAY)) + .append( + Component.text( + languageManager.getMessage("channel.info.members"), + NamedTextColor.GRAY, + ), + ).append(Component.text(": ", NamedTextColor.GRAY)) .append(Component.text(displayNames.joinToString(", "), NamedTextColor.WHITE)) .append(Component.text(" ... ", NamedTextColor.GRAY)) .append(Component.text("($message)", NamedTextColor.YELLOW)) } else { Component .text(" ") - .append(Component.text(languageManager.getMessage("channel.info.members"), NamedTextColor.GRAY)) - .append(Component.text(": ", NamedTextColor.GRAY)) + .append( + Component.text( + languageManager.getMessage("channel.info.members"), + NamedTextColor.GRAY, + ), + ).append(Component.text(": ", NamedTextColor.GRAY)) .append(Component.text(memberNames.joinToString(", "), NamedTextColor.WHITE)) } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt new file mode 100644 index 0000000..e7a7295 --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt @@ -0,0 +1,159 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerNotBannedException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.annotation.Permission +import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter +import io.papermc.paper.command.brigadier.CommandSourceStack +import io.papermc.paper.command.brigadier.Commands +import org.bukkit.Bukkit + +@PlayerOnly +class ChannelUnbanCommand( + plugin: LunaticChat, + private val channelManager: ChannelManager, + private val membershipManager: ChannelMembershipManager, + private val languageManager: LanguageManager, +) : LunaticCommand(plugin) { + fun buildWithPermissionCheck(): LiteralArgumentBuilder { + val builder = build() + return applyMethodPermission("build", builder) + } + + @Permission(LunaticChatPermissionNode.ChannelUnban::class) + fun build(): LiteralArgumentBuilder = + Commands + .literal("unban") + .then( + Commands + .argument("playerName", StringArgumentType.word()) + .suggests { ctx, builder -> + val player = ctx.source.executor as? org.bukkit.entity.Player + if (player != null) { + val channelId = channelManager.getPlayerChannel(player.uniqueId) + if (channelId != null) { + val channel = channelManager.getChannel(channelId).getOrNull() + channel?.bannedPlayers?.forEach { bannedId -> + Bukkit.getOfflinePlayer(bannedId).name?.let { name -> + builder.suggest(name) + } + } + } + } + builder.buildFuture() + }.executes { ctx -> + val context = wrapContext(ctx) + checkPlayerOnly(context)?.let { return@executes handleResult(context, it) } + + val playerName = StringArgumentType.getString(ctx, "playerName") + val result = execute(context, playerName) + handleResult(context, result) + }, + ) + + private fun execute( + ctx: CommandContext, + playerName: String, + ): CommandResult { + val sender = ctx.requirePlayer() + + // Get sender's active channel + val channelId = + channelManager.getPlayerChannel(sender.uniqueId) + ?: return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.unban.noActiveChannel"), + ), + ) + + // Check if sender has permission (OWNER or MODERATOR) + val senderRole = membershipManager.getMemberRoleOrNull(sender.uniqueId, channelId) + if (senderRole == null || senderRole == ChannelRole.MEMBER) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.unban.noPermission"), + ), + ) + } + + // Find target player + val targetPlayer = Bukkit.getOfflinePlayer(playerName) + + // Check if player exists (has played before or is online) + if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline) { + return CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.unban.playerNotFound", + mapOf("player" to playerName), + ), + ), + ) + } + + val targetPlayerId = targetPlayer.uniqueId + + // Unban player from channel + val unbanResult = channelManager.unbanPlayer(channelId, targetPlayerId) + return unbanResult.fold( + onSuccess = { + val channel = channelManager.getChannel(channelId).getOrNull() + val channelName = channel?.name ?: channelId + + CommandResult.SuccessWithMessage( + MessageFormatter.format( + languageManager.getMessage( + "channel.unban.success", + mapOf("player" to playerName, "channel" to channelName), + ), + ), + ) + }, + onFailure = { error -> + when (error) { + is ChannelNotFoundException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.unban.error"), + ), + ) + } + is ChannelPlayerNotBannedException -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage( + "channel.unban.playerNotBanned", + mapOf("player" to playerName), + ), + ), + ) + } + else -> { + CommandResult.Failure( + MessageFormatter.formatError( + languageManager.getMessage("channel.unban.error"), + ), + ) + } + } + }, + ) + } + + override fun buildCommand(): LiteralArgumentBuilder = + throw UnsupportedOperationException( + "ChannelUnbanCommand should use build() method instead of buildCommand()", + ) +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt index f8e8181..1979b6b 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt @@ -42,15 +42,18 @@ object EventListenerRegistry { // Conditionally register chat listener when all required components are available if (services.chatModeManager != null && + services.channelManager != null && services.channelMessageHandler != null && services.romajiConverter != null ) { pluginManager.registerEvents( PlayerChatListener( chatModeManager = services.chatModeManager, + channelManager = services.channelManager, channelMessageHandler = services.channelMessageHandler, romajiConverter = services.romajiConverter, settingsManager = services.playerSettingsManager, + languageManager = services.languageManager, ), plugin, ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt index 47c8cd4..0fa4fa1 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt @@ -2,8 +2,11 @@ package dev.m1sk9.lunaticChat.paper.listener import dev.m1sk9.lunaticChat.engine.chat.ChatMode import dev.m1sk9.lunaticChat.paper.chat.ChatModeManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelMessageHandler import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager import io.papermc.paper.event.player.AsyncChatEvent import kotlinx.coroutines.runBlocking @@ -14,9 +17,11 @@ import org.bukkit.event.Listener class PlayerChatListener( private val chatModeManager: ChatModeManager, + private val channelManager: ChannelManager, private val channelMessageHandler: ChannelMessageHandler, private val romajiConverter: RomanjiConverter, private val settingsManager: PlayerSettingsManager, + private val languageManager: LanguageManager, ) : Listener { private val plainTextSerializer = PlainTextComponentSerializer.plainText() @@ -70,8 +75,23 @@ class PlayerChatListener( event.message(Component.text(displayMessage)) } ChatMode.CHANNEL -> { - event.isCancelled = true - channelMessageHandler.sendChannelMessage(player, messageWithoutPrefix) + val hasActiveChannel = channelManager.getPlayerChannel(player.uniqueId) != null + + if (hasActiveChannel) { + // Send to channel as normal + event.isCancelled = true + channelMessageHandler.sendChannelMessage(player, messageWithoutPrefix) + } else { + // Auto-fallback to global chat + event.message(Component.text(displayMessage)) + + // Send warning to player + player.sendMessage( + MessageFormatter.format( + languageManager.getMessage("channel.autoFallback"), + ), + ) + } } } } diff --git a/platform-paper/src/main/resources/languages/en.yml b/platform-paper/src/main/resources/languages/en.yml index 5887b25..2cbff48 100644 --- a/platform-paper/src/main/resources/languages/en.yml +++ b/platform-paper/src/main/resources/languages/en.yml @@ -52,8 +52,15 @@ channel: status: "/lc channel status - Show your channel status and memberships" info: "/lc channel info [channelId] - Show detailed information about a channel" delete: "/lc channel delete - Delete a channel" + invite: "/lc channel invite - Invite a player to your active channel" + kick: "/lc channel kick - Kick a player from your active channel" + ban: "/lc channel ban - Ban a player from your active channel" + unban: "/lc channel unban - Unban a player from your active channel" + mod: "/lc channel mod - Promote or demote a player to/from moderator" + ownership: "/lc channel ownership - Transfer channel ownership to a player" create: success: "Created channel '{name}' (ID: {id})" + privateNotice: "This is a private channel. Players can only join through invitations." alreadyExists: "Channel ID '{id}' is already in use" invalidId: "Invalid channel ID '{id}'. Must be 3-30 characters using only letters, numbers, underscores, and hyphens" limitExceeded: "Cannot create channel: server has reached the maximum limit of {limit} channels" @@ -71,6 +78,7 @@ channel: success: "Joined channel '{channelName}' (ID: {channelId})" alreadyActive: "Channel '{channelName}' is already your active channel" alreadyMember: "You are already a member of channel '{channelName}'" + playerBanned: "You are banned from this channel" notFound: "Channel '{channelId}' not found" privateChannel: "This is a private channel and requires an invitation" channelMemberLimitExceeded: "Cannot join channel: channel has reached the maximum member limit of {limit}" @@ -106,6 +114,68 @@ channel: error: "Failed to retrieve channel information" notification: login: "You are in channel '{channelName}'" + playerJoined: "{player} joined {channel}" + playerLeft: "{player} left {channel}" + playerKicked: "{player} was kicked from {channel} by {kicker}" + playerBanned: "{player} was banned from {channel} by {banner}" + autoFallback: "No active channel - message sent to global chat" + invite: + success: "Invited {player} to {channel}" + noActiveChannel: "You don't have an active channel" + noPermission: "Only channel owners and moderators can invite players" + playerNotFound: "Player '{player}' not found" + cannotInviteSelf: "You cannot invite yourself" + playerBanned: "{player} is banned from this channel" + channelFull: "Cannot invite: channel has reached the maximum member limit of {limit}" + receivedInvite: "You have been invited to {channel} by {inviter}" + error: "Failed to invite player" + kick: + success: "Kicked {player} from {channel}" + noActiveChannel: "You don't have an active channel" + noPermission: "Only channel owners and moderators can kick players" + playerNotFound: "Player '{player}' not found" + cannotKickSelf: "You cannot kick yourself" + cannotKickBypass: "Cannot kick {player}: player has bypass permission" + notMember: "{player} is not a member of this channel" + wasKicked: "You were kicked from {channel} by {kicker}" + error: "Failed to kick player" + ban: + success: "Banned {player} from {channel}" + noActiveChannel: "You don't have an active channel" + noPermission: "Only channel owners and moderators can ban players" + playerNotFound: "Player '{player}' not found" + cannotBanSelf: "You cannot ban yourself" + cannotBanBypass: "Cannot ban {player}: player has bypass permission" + alreadyBanned: "{player} is already banned from this channel" + wasBanned: "You were banned from {channel} by {banner}" + error: "Failed to ban player" + unban: + success: "Unbanned {player} from {channel}" + noActiveChannel: "You don't have an active channel" + noPermission: "Only channel owners and moderators can unban players" + playerNotFound: "Player '{player}' not found" + playerNotBanned: "{player} is not banned from this channel" + error: "Failed to unban player" + mod: + success: "{player} has been {action} in {channel}" + noActiveChannel: "You don't have an active channel" + noPermission: "Only channel owners can manage moderators" + playerNotFound: "Player '{player}' not found" + notMember: "{player} is not a member of this channel" + cannotModSelf: "You cannot modify your own moderator status" + promoted: "promoted to moderator" + demoted: "demoted from moderator" + notification: "You have been {action} in {channel}" + error: "Failed to manage moderator status" + ownership: + success: "Ownership of {channel} transferred to {player}" + noActiveChannel: "You don't have an active channel" + noPermission: "Only channel owners can transfer ownership" + playerNotFound: "Player '{player}' not found" + notMember: "{player} is not a member of this channel" + cannotTransferToSelf: "You cannot transfer ownership to yourself" + receivedOwnership: "You are now the owner of {channel} (previously owned by {previousOwner})" + error: "Failed to transfer ownership" chatmode: current: "Current chat mode" diff --git a/platform-paper/src/main/resources/languages/ja.yml b/platform-paper/src/main/resources/languages/ja.yml index 768466a..b51d605 100644 --- a/platform-paper/src/main/resources/languages/ja.yml +++ b/platform-paper/src/main/resources/languages/ja.yml @@ -52,8 +52,15 @@ channel: status: "/lc channel status - チャンネルのステータスとメンバーシップを表示します" info: "/lc channel info [チャンネルID] - チャンネルの詳細情報を表示します" delete: "/lc channel delete <チャンネルID> - チャンネルを削除します" + invite: "/lc channel invite <プレイヤー名> - プレイヤーをアクティブチャンネルに招待します" + kick: "/lc channel kick <プレイヤー名> - プレイヤーをアクティブチャンネルからキックします" + ban: "/lc channel ban <プレイヤー名> - プレイヤーをアクティブチャンネルからバンします" + unban: "/lc channel unban <プレイヤー名> - プレイヤーをアクティブチャンネルからアンバンします" + mod: "/lc channel mod <プレイヤー名> - プレイヤーをモデレーターに任命 / 任命解除します" + ownership: "/lc channel ownership <プレイヤー名> - チャンネルのオーナー権限を譲渡します" create: success: "チャンネル '{name}' (ID: {id}) を作成しました" + privateNotice: "これはプライベートチャンネルです。招待されたプレイヤーのみが参加できます。" alreadyExists: "チャンネルID '{id}' は既に使用されています" invalidId: "無効なチャンネルID '{id}' です。3-30文字の英数字、アンダースコア、ハイフンのみ使用できます" limitExceeded: "チャンネルを作成できません: サーバーはチャンネル数の上限 ({limit}件) に達しています" @@ -71,6 +78,7 @@ channel: success: "チャンネル '{channelName}' (ID: {channelId}) に参加しました" alreadyActive: "チャンネル '{channelName}' は既にアクティブです" alreadyMember: "既にチャンネル '{channelName}' のメンバーです" + playerBanned: "このチャンネルから永久追放されているため参加できません" notFound: "チャンネル '{channelId}' が見つかりません" privateChannel: "プライベートチャンネルには招待が必要です" channelMemberLimitExceeded: "チャンネルに参加できません: チャンネルのメンバー数が上限 ({limit}人) に達しています" @@ -79,7 +87,7 @@ channel: leave: success: "チャンネル '{channelName}' から退出しました" noActiveChannel: "アクティブなチャンネルがありません" - error: "チャンネルからの退出に失敗しました" + error: "チャンネルの退出に失敗しました" switch: success: "チャンネル '{channelName}' (ID: {channelId}) に切り替えました" alreadyActive: "チャンネル '{channelName}' は既にアクティブです" @@ -106,6 +114,68 @@ channel: error: "チャンネル情報の取得に失敗しました" notification: login: "チャンネル '{channelName}' に入室中です" + playerJoined: "{player}がチャンネル {channel} に参加しました" + playerLeft: "{player}がチャンネル {channel} から退出しました" + playerKicked: "{player}は{kicker}によってチャンネル {channel} から追放されました" + playerBanned: "{player}は{banner}によってチャンネル {channel} から永久追放されました" + autoFallback: "アクティブなチャンネルがないため、メッセージはグローバルチャットに送信されました" + invite: + success: "{player}をチャンネル {channel} に招待しました" + noActiveChannel: "アクティブなチャンネルがありません" + noPermission: "チャンネルのオーナーとモデレーターのみがプレイヤーを招待できます" + playerNotFound: "プレイヤー '{player}' が見つかりません" + cannotInviteSelf: "自分自身を招待することはできません" + playerBanned: "{player}はこのチャンネルから永久追放されています" + channelFull: "招待できません: チャンネルはメンバー数の上限 ({limit}人) に達しています" + receivedInvite: "チャンネル {channel} に{inviter}から招待されました" + error: "プレイヤーの招待に失敗しました" + kick: + success: "チャンネル {channel} から{player}を追放しました" + noActiveChannel: "アクティブなチャンネルがありません" + noPermission: "チャンネルのオーナーとモデレーターのみがプレイヤーを追放できます" + playerNotFound: "プレイヤー '{player}' が見つかりません" + cannotKickSelf: "自分自身を追放することはできません" + cannotKickBypass: "{player}は追放できません" + notMember: "{player}はこのチャンネルのメンバーではありません" + wasKicked: "{kicker}によってチャンネル {channel} から追放されました" + error: "プレイヤーの追放に失敗しました" + ban: + success: "チャンネル {channel} から{player}を永久追放しました" + noActiveChannel: "アクティブなチャンネルがありません" + noPermission: "チャンネルのオーナーとモデレーターのみがプレイヤーを永久追放できます" + playerNotFound: "プレイヤー '{player}' が見つかりません" + cannotBanSelf: "自分自身を永久追放することはできません" + cannotBanBypass: "{player}は追放できません" + alreadyBanned: "{player}は既にこのチャンネルから永久追放されています" + wasBanned: "{banner}によってチャンネル {channel} から永久追放されました" + error: "プレイヤーの永久追放に失敗しました" + unban: + success: "チャンネル {channel} から{player}の追放を解除しました" + noActiveChannel: "アクティブなチャンネルがありません" + noPermission: "チャンネルのオーナーとモデレーターのみがプレイヤーの追放を解除できます" + playerNotFound: "プレイヤー '{player}' が見つかりません" + playerNotBanned: "{player}はこのチャンネルから永久追放されていません" + error: "プレイヤーの追放の解除に失敗しました" + mod: + success: "チャンネル {channel} で{player}を{action}しました" + noActiveChannel: "アクティブなチャンネルがありません" + noPermission: "チャンネルのオーナーのみがモデレーターを管理できます" + playerNotFound: "プレイヤー '{player}' が見つかりません" + notMember: "{player}はこのチャンネルのメンバーではありません" + cannotModSelf: "自分自身のモデレーター状態を変更することはできません" + promoted: "モデレーターに昇格" + demoted: "モデレーターから降格" + notification: "チャンネル {channel} で{action}されました" + error: "モデレーター状態の変更に失敗しました" + ownership: + success: "チャンネル {channel} のオーナー権限を{player}に譲渡しました" + noActiveChannel: "アクティブなチャンネルがありません" + noPermission: "チャンネルのオーナーのみがオーナー権限を譲渡できます" + playerNotFound: "プレイヤー '{player}' が見つかりません" + notMember: "{player}はこのチャンネルのメンバーではありません" + cannotTransferToSelf: "自分自身にオーナー権限を譲渡することはできません" + receivedOwnership: "チャンネル {channel} のオーナーになりました (前のオーナー: {previousOwner})" + error: "オーナー権限の譲渡に失敗しました" chatmode: current: "現在のチャットモード" diff --git a/platform-paper/src/main/resources/paper-plugin.yml b/platform-paper/src/main/resources/paper-plugin.yml index 0ae5cd3..38d2026 100644 --- a/platform-paper/src/main/resources/paper-plugin.yml +++ b/platform-paper/src/main/resources/paper-plugin.yml @@ -33,8 +33,22 @@ permissions: default: true lunaticchat.command.lc.channel.status: default: true + lunaticchat.command.lc.channel.info: + default: true lunaticchat.command.lc.channel.delete: default: true + lunaticchat.command.lc.channel.invite: + default: true + lunaticchat.command.lc.channel.kick: + default: true + lunaticchat.command.lc.channel.ban: + default: true + lunaticchat.command.lc.channel.unban: + default: true + lunaticchat.command.lc.channel.mod: + default: true + lunaticchat.command.lc.channel.ownership: + default: true lunaticchat.command.lc.chatmode: default: true lunaticchat.command.lc.chatmode.toggle: -- cgit v1.2.1