diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-26 02:58:23 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-26 02:58:23 +0900 |
| commit | 18a4bbc4ba1a6766af920fb0849215f058ff260e (patch) | |
| tree | c570633f29f2398cb263e2eb90a7d56f0ea41e78 /engine/src | |
| parent | 0fb8f4c92ad5dd7d20778de862b4d12f67f71d5e (diff) | |
| parent | c7714add1934baf55a5e6268b1a70780eaa84fea (diff) | |
| download | LunaticChat-18a4bbc4ba1a6766af920fb0849215f058ff260e.tar.gz LunaticChat-18a4bbc4ba1a6766af920fb0849215f058ff260e.tar.bz2 LunaticChat-18a4bbc4ba1a6766af920fb0849215f058ff260e.zip | |
Merge pull request #69 from m1sk9/feat-core-channel-chat
feat: Core Channel Features (Phase 1)
Diffstat (limited to 'engine/src')
20 files changed, 262 insertions, 0 deletions
diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatMode.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatMode.kt new file mode 100644 index 0000000..98bb79d --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatMode.kt @@ -0,0 +1,43 @@ +package dev.m1sk9.lunaticChat.engine.chat + +/** + * Represents the chat mode for a player. + * + * Chat mode determines where messages are sent by default: + * - GLOBAL: All messages go to global chat (visible to everyone) + * - CHANNEL: All messages go to the active channel (visible to channel members only) + * + * Players can temporarily override their mode by prefixing messages with '!'. + * Chat mode is persisted and maintains its state across server restarts. + * Default mode is GLOBAL. + */ +enum class ChatMode { + /** + * Global chat mode - messages visible to all players. + */ + GLOBAL, + + /** + * Channel chat mode - messages visible only to channel members. + * Requires player to be in an active channel. + */ + CHANNEL, + ; + + /** + * Returns the opposite chat mode. + * Used when player prefixes message with '!'. + */ + fun toggle(): ChatMode = + when (this) { + GLOBAL -> CHANNEL + CHANNEL -> GLOBAL + } + + companion object { + /** + * Default chat mode for all players. + */ + val DEFAULT = GLOBAL + } +} diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeData.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeData.kt new file mode 100644 index 0000000..b462ede --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeData.kt @@ -0,0 +1,14 @@ +package dev.m1sk9.lunaticChat.engine.chat + +import dev.m1sk9.lunaticChat.engine.settings.UUIDSerializer +import kotlinx.serialization.Serializable +import java.util.UUID + +@Serializable +data class ChatModeData( + val modes: Map< + @Serializable(with = UUIDSerializer::class) + UUID, + ChatMode, + > = emptyMap(), +) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/Channel.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/Channel.kt new file mode 100644 index 0000000..cc1f743 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/Channel.kt @@ -0,0 +1,40 @@ +package dev.m1sk9.lunaticChat.engine.chat.channel + +import dev.m1sk9.lunaticChat.engine.settings.UUIDSerializer +import kotlinx.serialization.Serializable +import java.util.UUID + +/** + * Represents a communication channel within the LunaticChat application. + * + * @property id Unique identifier for the channel. + * @property name Name of the channel. + * @property description Optional description of the channel. + * @property isPrivate Indicates whether the channel is private or public. + * @property ownerId UUID of the user who owns the channel. + * @property createdAt Timestamp of when the channel was created. + */ +@Serializable +data class Channel( + val id: String, + val name: String, + val description: String? = null, + val isPrivate: Boolean = false, + @Serializable(with = UUIDSerializer::class) + val ownerId: UUID, + val createdAt: Long = System.currentTimeMillis(), +) { + init { + require(id.matches(CHANNEL_ID_PATTERN)) { + "Channel ID must be 3-30 characters long and can only contain letters, numbers, underscores, and hyphens." + } + + require(name.isNotBlank()) { + "Channel name cannot be blank." + } + } + + companion object { + val CHANNEL_ID_PATTERN = Regex("^[a-zA-Z0-9_-]{3,30}$") + } +} diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelContext.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelContext.kt new file mode 100644 index 0000000..02dc1d3 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelContext.kt @@ -0,0 +1,7 @@ +package dev.m1sk9.lunaticChat.engine.chat.channel + +data class ChannelContext( + val channelId: String, + val channel: Channel, + val members: List<ChannelMember>, +) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelData.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelData.kt new file mode 100644 index 0000000..de942f7 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelData.kt @@ -0,0 +1,11 @@ +package dev.m1sk9.lunaticChat.engine.chat.channel + +import kotlinx.serialization.Serializable + +@Serializable +data class ChannelData( + val version: Int = 1, + val channels: Map<String, Channel> = emptyMap(), + val members: Map<String, List<ChannelMember>> = emptyMap(), + val activeChannels: Map<String, String> = emptyMap(), +) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelMember.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelMember.kt new file mode 100644 index 0000000..1eb49f4 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelMember.kt @@ -0,0 +1,22 @@ +package dev.m1sk9.lunaticChat.engine.chat.channel + +import dev.m1sk9.lunaticChat.engine.settings.UUIDSerializer +import kotlinx.serialization.Serializable +import java.util.UUID + +/** + * Represents a member of a channel with their role and join timestamp. + * + * @property channelId The ID of the channel. + * @property playerId The UUID of the player. + * @property role The role of the member in the channel. + * @property joinedAt The timestamp when the member joined the channel. + */ +@Serializable +data class ChannelMember( + val channelId: String, + @Serializable(with = UUIDSerializer::class) + val playerId: UUID, + val role: ChannelRole, + val joinedAt: Long = System.currentTimeMillis(), +) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelRole.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelRole.kt new file mode 100644 index 0000000..be7bf58 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelRole.kt @@ -0,0 +1,17 @@ +package dev.m1sk9.lunaticChat.engine.chat.channel + +import kotlinx.serialization.Serializable + +/** + * Represents the role of a user within a channel. + * + * - OWNER: The creator and primary administrator of the channel. + * - MODERATOR: A user with elevated permissions to manage channel content and users. + * - MEMBER: A regular user with standard access to the channel. + */ +@Serializable +enum class ChannelRole { + OWNER, + MODERATOR, + MEMBER, +} diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelAlreadyActiveException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelAlreadyActiveException.kt new file mode 100644 index 0000000..6d88a73 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelAlreadyActiveException.kt @@ -0,0 +1,8 @@ +package dev.m1sk9.lunaticChat.engine.exception + +import java.util.UUID + +class ChannelAlreadyActiveException( + val playerId: UUID, + val channelId: String, +) : Exception("Channel $channelId is already active for player $playerId") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberAlreadyException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberAlreadyException.kt new file mode 100644 index 0000000..deaacb1 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberAlreadyException.kt @@ -0,0 +1,8 @@ +package dev.m1sk9.lunaticChat.engine.exception + +import java.util.UUID + +class ChannelMemberAlreadyException( + playerId: UUID, + val channelId: String, +) : Exception("Player $playerId is already a member of channel $channelId") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt new file mode 100644 index 0000000..8dfd6f2 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelMemberNotFoundException.kt @@ -0,0 +1,5 @@ +package dev.m1sk9.lunaticChat.engine.exception + +class ChannelMemberNotFoundException( + channelId: String, +) : Exception("Channel member not found in channel with ID $channelId.") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt new file mode 100644 index 0000000..2cc6625 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNoOwnerPermissionException.kt @@ -0,0 +1,7 @@ +package dev.m1sk9.lunaticChat.engine.exception + +import java.util.UUID + +class ChannelNoOwnerPermissionException( + ownerId: UUID, +) : Exception("No permission for channel owned by player with ID $ownerId.") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt new file mode 100644 index 0000000..0f60766 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotFoundException.kt @@ -0,0 +1,5 @@ +package dev.m1sk9.lunaticChat.engine.exception + +class ChannelNotFoundException( + channelId: String, +) : Exception("Channel with ID $channelId not found.") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt new file mode 100644 index 0000000..b66c791 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelNotMemberException.kt @@ -0,0 +1,8 @@ +package dev.m1sk9.lunaticChat.engine.exception + +import java.util.UUID + +class ChannelNotMemberException( + playerId: UUID, + channelId: String, +) : Exception("Player $playerId is not a member of channel $channelId") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelRuntimeException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelRuntimeException.kt new file mode 100644 index 0000000..4805489 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelRuntimeException.kt @@ -0,0 +1,6 @@ +package dev.m1sk9.lunaticChat.engine.exception + +class ChannelRuntimeException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageLoadException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageLoadException.kt new file mode 100644 index 0000000..21bd654 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageLoadException.kt @@ -0,0 +1,12 @@ +package dev.m1sk9.lunaticChat.engine.exception + +/** + * Exception thrown when there is an error loading channel storage. + * + * @param message The detail message for the exception. + * @param cause The cause of the exception, if any. + */ +class ChannelStorageLoadException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageSaveException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageSaveException.kt new file mode 100644 index 0000000..0e9990b --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageSaveException.kt @@ -0,0 +1,12 @@ +package dev.m1sk9.lunaticChat.engine.exception + +/** + * Exception thrown when there is an error saving channel storage. + * + * @param message The detail message for the exception. + * @param cause The cause of the exception, if any. + */ +class ChannelStorageSaveException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChatModeStorageException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChatModeStorageException.kt new file mode 100644 index 0000000..85c1e69 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChatModeStorageException.kt @@ -0,0 +1,6 @@ +package dev.m1sk9.lunaticChat.engine.exception + +class ChatModeStorageException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNode.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNode.kt index 9328423..838a48b 100644 --- a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNode.kt +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNode.kt @@ -13,6 +13,26 @@ sealed class LunaticChatPermissionNode( object Status : LunaticChatPermissionNode("lunaticchat.command.lc.status") + object Channel : LunaticChatPermissionNode("lunaticchat.command.lc.channel") + + object ChannelCreate : LunaticChatPermissionNode("lunaticchat.command.lc.channel.create") + + object ChannelList : LunaticChatPermissionNode("lunaticchat.command.lc.channel.list") + + object ChannelJoin : LunaticChatPermissionNode("lunaticchat.command.lc.channel.join") + + object ChannelLeave : LunaticChatPermissionNode("lunaticchat.command.lc.channel.leave") + + object ChannelSwitch : LunaticChatPermissionNode("lunaticchat.command.lc.channel.switch") + + object ChannelStatus : LunaticChatPermissionNode("lunaticchat.command.lc.channel.status") + + object ChannelDelete : LunaticChatPermissionNode("lunaticchat.command.lc.channel.delete") + + object ChatMode : LunaticChatPermissionNode("lunaticchat.command.lc.chatmode") + + object ChatModeToggle : LunaticChatPermissionNode("lunaticchat.command.lc.chatmode.toggle") + object Spy : LunaticChatPermissionNode("lunaticchat.spy") object NoticeUpdate : LunaticChatPermissionNode("lunaticchat.noticeUpdate") diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerChatSettings.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerChatSettings.kt index e970ae9..4cb44b4 100644 --- a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerChatSettings.kt +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerChatSettings.kt @@ -9,6 +9,7 @@ import java.util.UUID * @property uuid The unique identifier of the player * @property japaneseConversionEnabled Whether romaji-to-Japanese conversion is enabled for this player * @property directMessageNotificationEnabled Whether direct message notifications are enabled for this player + * @property channelMessageNotificationEnabled Whether channel message notifications are enabled for this player */ @Serializable data class PlayerChatSettings( @@ -16,4 +17,5 @@ data class PlayerChatSettings( val uuid: UUID, val japaneseConversionEnabled: Boolean = true, val directMessageNotificationEnabled: Boolean = true, + val channelMessageNotificationEnabled: Boolean = true, ) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerSettingsData.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerSettingsData.kt index 3916bbf..0e3cd24 100644 --- a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerSettingsData.kt +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/settings/PlayerSettingsData.kt @@ -16,11 +16,15 @@ import java.util.UUID * directMessageNotification: * ceaea267-39dd-3bac-931c-761ada671ebe: true * another-uuid-here: false + * channelMessageNotification: + * ceaea267-39dd-3bac-931c-761ada671ebe: true + * another-uuid-here: false * ``` * * @property version The schema version for future compatibility * @property japaneseConversion Map of player UUIDs to their Japanese conversion enabled status * @property directMessageNotification Map of player UUIDs to their direct message notification enabled status + * @property channelMessageNotification Map of player UUIDs to their channel message notification enabled status */ @Serializable data class PlayerSettingsData( @@ -35,4 +39,9 @@ data class PlayerSettingsData( UUID, Boolean, > = emptyMap(), + val channelMessageNotification: Map< + @Serializable(with = UUIDASStringSerializer::class) + UUID, + Boolean, + > = emptyMap(), ) |
