summaryrefslogtreecommitdiff
path: root/platform-paper/src
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-27 18:53:52 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-27 18:53:52 +0900
commit5c3a0d8027eb7be1d5decea71eae4d9bd03accc7 (patch)
treef8618b2b1e4b4dc99c8f4496acc6601270e81fc1 /platform-paper/src
parent406481cff49ad7116eaae0c9d5f6b8b0728d961b (diff)
downloadLunaticChat-5c3a0d8027eb7be1d5decea71eae4d9bd03accc7.tar.gz
LunaticChat-5c3a0d8027eb7be1d5decea71eae4d9bd03accc7.tar.bz2
LunaticChat-5c3a0d8027eb7be1d5decea71eae4d9bd03accc7.zip
feat: Add channe-chat feature
Diffstat (limited to 'platform-paper/src')
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt3
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt3
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt25
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt4
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelNotificationHandler.kt142
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt108
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/LunaticChatCommand.kt19
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt210
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt21
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt31
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt192
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt25
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt207
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt7
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt198
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt184
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt26
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt159
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt3
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt24
-rw-r--r--platform-paper/src/main/resources/languages/en.yml70
-rw-r--r--platform-paper/src/main/resources/languages/ja.yml72
-rw-r--r--platform-paper/src/main/resources/paper-plugin.yml14
23 files changed, 1716 insertions, 31 deletions
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<CommandSourceStack> {
@@ -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<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelBan::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ 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<CommandSourceStack> =
+ 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<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelInvite::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ 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<CommandSourceStack> =
+ 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<CommandSourceStack> {
@@ -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<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelKick::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ 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<CommandSourceStack> =
+ 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<CommandSourceStack> {
@@ -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<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelMod::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ 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<CommandSourceStack> =
+ 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<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelOwnership::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ 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<CommandSourceStack> =
+ 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<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelUnban::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ 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<CommandSourceStack> =
+ 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 <channelId> - Delete a channel"
+ invite: "/lc channel invite <player> - Invite a player to your active channel"
+ kick: "/lc channel kick <player> - Kick a player from your active channel"
+ ban: "/lc channel ban <player> - Ban a player from your active channel"
+ unban: "/lc channel unban <player> - Unban a player from your active channel"
+ mod: "/lc channel mod <player> - Promote or demote a player to/from moderator"
+ ownership: "/lc channel ownership <player> - 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: