<commit_before>package org.ozinger.ika.service.command.commands

import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.ozinger.ika.annotation.ServiceCommand
import org.ozinger.ika.channel.OutgoingPacketChannel
import org.ozinger.ika.command.CHGHOST
import org.ozinger.ika.command.METADATA
import org.ozinger.ika.database.ilike
import org.ozinger.ika.database.models.Account
import org.ozinger.ika.database.models.Accounts
import org.ozinger.ika.database.models.Nickname
import org.ozinger.ika.database.models.Nicknames
import org.ozinger.ika.definition.UniversalUserId
import org.ozinger.ika.enumeration.Permission
import org.ozinger.ika.service.Service
import org.ozinger.ika.state.Users
import java.time.LocalDateTime

object AccountServiceCommands {
    @ServiceCommand(
        name = "정보",
        aliases = ["INFO"],
        syntax = "[계정명 또는 채널명]",
        summary = "오징어 IRC 네트워크에 등록되어 있는 계정 또는 채널의 정보를 확인합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에 이미 등록되어 있는 계정 또는 채널에 대한 정보를 확인할 수 있습니다.\n" +
                      "자신의 계정이나 자신이 주인으로 등록되어 있는 채널 외 대상의 정보를 보기 위해서는 오퍼레이터 인증이 필요합니다.\n" +
                      "아무 인자도 없이 실행했을 경우 현재 로그인되어 있는 계정의 정보를 확인합니다.",
        permission = Permission.AUTHENTICATED,
    )
    fun info(sender: UniversalUserId, target: String?) {
        println("Info: $target")
    }

    @ServiceCommand(
        name = "등록",
        aliases = ["가입", "REGISTER", "SIGNUP"],
        syntax = "<이메일> <비밀번호>",
        summary = "오징어 IRC 네트워크에 로그인합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에 이미 등록되어 있는 계정으로 로그인하며,\n" +
                      "그 뒤로 네트워크에서 제공하는 여러 편의 기능들을 이용하실 수 있습니다.",
        permission = Permission.ANONYMOUS,
    )
    suspend fun register(sender: UniversalUserId, email: String, password: String) {
        val nickname = Users[sender].nickname
        if (transaction { Nickname.find { Nicknames.name ilike nickname }.count() } > 0) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "해당 닉네임 \u0002$nickname\u0002 은(는) 이미 오징어 IRC 네트워크에 등록되어 있습니다."
            )
            return
        }

        if (transaction { Account.find { Accounts.email ilike email }.count() } > 0) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "해당 이메일 \u0002$email\u0002 은(는) 이미 오징어 IRC 네트워크에 등록되어 있습니다."
            )
            return
        }

        transaction {
            val account = Account.new {
                this.email = email
                changePassword(password)
                vhost = ""
                createdAt = LocalDateTime.now()
                authenticatedAt = LocalDateTime.now()
            }

            Nickname.new {
                name = nickname
                isAccountName = true
                this.account = account
            }
        }

        Service.sendMessageAsServiceUser(sender, "환영합니다! 해당 닉네임 \u0002$nickname\u0002 의 계정 등록이 완료되었습니다.")
        Service.sendMessageAsServiceUser(
            sender,
            "앞으로 \u0002/msg ${Service.nickname} 로그인 $nickname $password \u0002 명령을 통해 로그인할 수 있습니다. 지금 로그인 해 보세요."
        )
    }

    @ServiceCommand(
        name = "로그인",
        aliases = ["인증", "LOGIN"],
        syntax = "[계정명] <비밀번호>",
        summary = "오징어 IRC 네트워크에 로그인합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에 이미 등록되어 있는 계정으로 로그인하며,\n" +
                      "그 뒤로 네트워크에서 제공하는 여러 편의 기능들을 이용하실 수 있습니다.",
        permission = Permission.ANONYMOUS,
    )
    suspend fun login(sender: UniversalUserId, name: String?, password: String) {
        if (Users[sender].accountName != null) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "이미 \u0002${Users[sender].accountName}\u0002 계정으로 로그인되어 있습니다."
            )
            return
        }

        newSuspendedTransaction {
            val account = Account.getByNickname(name ?: Users[sender].nickname)
            if (account != null && account.verifyPassword(password)) {
                account.authenticatedAt = LocalDateTime.now()
                Service.sendMessageAsServiceUser(sender, "환영합니다! \u0002${account.name}\u0002 계정으로 로그인되었습니다.")
                OutgoingPacketChannel.sendAsServer(METADATA(sender, "accountname", account.name))
                if (account.vhost.isNotEmpty()) {
                    OutgoingPacketChannel.sendAsServer(CHGHOST(sender, account.vhost))
                }
            } else {
                Service.sendErrorMessageAsServiceUser(
                    sender,
                    "등록되지 않은 계정이거나 잘못된 비밀번호입니다. 계정명이나 비밀번호를 모두 제대로 입력했는지 다시 한번 확인해주세요."
                )
            }
        }
    }

    @ServiceCommand(
        name = "로그아웃",
        aliases = ["인증해제", "LOGOUT"],
        syntax = "",
        summary = "오징어 IRC 네트워크에서 로그아웃합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에서 로그아웃합니다.",
        permission = Permission.AUTHENTICATED,
    )
    suspend fun logout(sender: UniversalUserId) {
        if (Users[sender].accountName == null) {
            Service.sendErrorMessageAsServiceUser(sender, "로그인되어있지 않습니다.")
            return
        }

        OutgoingPacketChannel.sendAsServer(METADATA(sender, "accountname", null))
        Service.sendMessageAsServiceUser(sender, "로그아웃했습니다.")
    }
}<commit_msg>닉네임 관련 명령 구현<commit_after>package org.ozinger.ika.service.command.commands

import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.ozinger.ika.annotation.ServiceCommand
import org.ozinger.ika.channel.OutgoingPacketChannel
import org.ozinger.ika.command.CHGHOST
import org.ozinger.ika.command.METADATA
import org.ozinger.ika.database.ilike
import org.ozinger.ika.database.models.Account
import org.ozinger.ika.database.models.Accounts
import org.ozinger.ika.database.models.Nickname
import org.ozinger.ika.database.models.Nicknames
import org.ozinger.ika.definition.UniversalUserId
import org.ozinger.ika.enumeration.Permission
import org.ozinger.ika.service.Service
import org.ozinger.ika.state.Users
import java.time.LocalDateTime

object AccountServiceCommands {
    @ServiceCommand(
        name = "정보",
        aliases = ["INFO"],
        syntax = "[계정명 또는 채널명]",
        summary = "오징어 IRC 네트워크에 등록되어 있는 계정 또는 채널의 정보를 확인합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에 이미 등록되어 있는 계정 또는 채널에 대한 정보를 확인할 수 있습니다.\n" +
                      "자신의 계정이나 자신이 주인으로 등록되어 있는 채널 외 대상의 정보를 보기 위해서는 오퍼레이터 인증이 필요합니다.\n" +
                      "아무 인자도 없이 실행했을 경우 현재 로그인되어 있는 계정의 정보를 확인합니다.",
        permission = Permission.AUTHENTICATED,
    )
    fun info(sender: UniversalUserId, target: String?) {
        println("Info: $target")
    }

    @ServiceCommand(
        name = "등록",
        aliases = ["가입", "REGISTER", "SIGNUP"],
        syntax = "<이메일> <비밀번호>",
        summary = "오징어 IRC 네트워크에 로그인합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에 이미 등록되어 있는 계정으로 로그인하며,\n" +
                      "그 뒤로 네트워크에서 제공하는 여러 편의 기능들을 이용하실 수 있습니다.",
        permission = Permission.ANONYMOUS,
    )
    suspend fun register(sender: UniversalUserId, email: String, password: String) {
        val nickname = Users[sender].nickname
        if (transaction { Nickname.find { Nicknames.name ilike nickname }.count() } > 0) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "해당 닉네임 \u0002$nickname\u0002 은(는) 이미 오징어 IRC 네트워크에 등록되어 있습니다."
            )
            return
        }

        if (transaction { Account.find { Accounts.email ilike email }.count() } > 0) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "해당 이메일 \u0002$email\u0002 은(는) 이미 오징어 IRC 네트워크에 등록되어 있습니다."
            )
            return
        }

        transaction {
            val account = Account.new {
                this.email = email
                changePassword(password)
                vhost = ""
                createdAt = LocalDateTime.now()
                authenticatedAt = LocalDateTime.now()
            }

            Nickname.new {
                name = nickname
                isAccountName = true
                this.account = account
            }
        }

        Service.sendMessageAsServiceUser(sender, "환영합니다! 해당 닉네임 \u0002$nickname\u0002 의 계정 등록이 완료되었습니다.")
        Service.sendMessageAsServiceUser(
            sender,
            "앞으로 \u0002/msg ${Service.nickname} 로그인 $nickname $password \u0002 명령을 통해 로그인할 수 있습니다. 지금 로그인 해 보세요."
        )
    }

    @ServiceCommand(
        name = "로그인",
        aliases = ["인증", "LOGIN"],
        syntax = "[계정명] <비밀번호>",
        summary = "오징어 IRC 네트워크에 로그인합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에 이미 등록되어 있는 계정으로 로그인하며,\n" +
                      "그 뒤로 네트워크에서 제공하는 여러 편의 기능들을 이용하실 수 있습니다.",
        permission = Permission.ANONYMOUS,
    )
    suspend fun login(sender: UniversalUserId, name: String?, password: String) {
        if (Users[sender].accountName != null) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "이미 \u0002${Users[sender].accountName}\u0002 계정으로 로그인되어 있습니다."
            )
            return
        }

        newSuspendedTransaction {
            val account = Account.getByNickname(name ?: Users[sender].nickname)
            if (account != null && account.verifyPassword(password)) {
                account.authenticatedAt = LocalDateTime.now()
                Service.sendMessageAsServiceUser(sender, "환영합니다! \u0002${account.name}\u0002 계정으로 로그인되었습니다.")
                OutgoingPacketChannel.sendAsServer(METADATA(sender, "accountname", account.name))
                if (account.vhost.isNotEmpty()) {
                    OutgoingPacketChannel.sendAsServer(CHGHOST(sender, account.vhost))
                }
            } else {
                Service.sendErrorMessageAsServiceUser(
                    sender,
                    "등록되지 않은 계정이거나 잘못된 비밀번호입니다. 계정명이나 비밀번호를 모두 제대로 입력했는지 다시 한번 확인해주세요."
                )
            }
        }
    }

    @ServiceCommand(
        name = "로그아웃",
        aliases = ["인증해제", "LOGOUT"],
        syntax = "",
        summary = "오징어 IRC 네트워크에서 로그아웃합니다.",
        description = "이 명령을 사용할 시 오징어 IRC 네트워크에서 로그아웃합니다.",
        permission = Permission.AUTHENTICATED,
    )
    suspend fun logout(sender: UniversalUserId) {
        if (Users[sender].accountName == null) {
            Service.sendErrorMessageAsServiceUser(sender, "로그인되어있지 않습니다.")
            return
        }

        OutgoingPacketChannel.sendAsServer(METADATA(sender, "accountname", null))
        Service.sendMessageAsServiceUser(sender, "로그아웃했습니다.")
    }

    @ServiceCommand(
        name = "닉네임등록",
        aliases = ["닉등록", "ADDNICK"],
        syntax = "",
        summary = "현재 오징어 IRC 네트워크에 로그인되어 있는 계정에 현재 사용중인 닉네임을 추가합니다.",
        description = "이 명령을 사용할 시 현재 로그인되어 있는 계정에 현재 사용중인 닉네임을 추가합니다.\n" +
                      "계정 1개에는 최대 5개의 닉네임을 등록할 수 있습니다.",
        permission = Permission.AUTHENTICATED,
    )
    suspend fun addNickname(sender: UniversalUserId) {
        val nickname = Users[sender].nickname
        if (transaction { Nickname.find { Nicknames.name ilike nickname }.count() } > 0) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "해당 닉네임 \u0002$nickname\u0002 은(는) 이미 오징어 IRC 네트워크에 등록되어 있습니다."
            )
            return
        }

        val accountName = Users[sender].accountName!!
        val account = transaction { Account.getByNickname(accountName)!! }
        if (transaction { account.nicknames.count() } >= 5) {
            Service.sendErrorMessageAsServiceUser(sender, "\u0002$accountName\u0002 계정에 등록할 수 있는 닉네임 제한을 초과했습니다 (5개).")
            return
        }

        transaction {
            Nickname.new {
                name = nickname
                isAccountName = false
                this.account = account
            }
        }

        Service.sendMessageAsServiceUser(sender, "\u0002$accountName\u0002 계정에 \u0002$nickname\u0002 닉네임을 추가했습니다.")
    }

    @ServiceCommand(
        name = "닉네임제거",
        aliases = ["닉제거", "닉삭제", "DELNICK"],
        syntax = "[제거할 닉네임]",
        summary = "현재 오징어 IRC 네트워크에 로그인되어 있는 계정에서 닉네임을 제거합니다.",
        description = "이 명령을 사용할 시 현재 로그인되어 있는 계정에서 닉네임을 제거할 수 있습니다.\n" +
                      "제거할 닉네임이 지정되지 않을 시 현재 사용중인 닉네임으로 시도합니다.",
        permission = Permission.AUTHENTICATED,
    )
    suspend fun deleteNickname(sender: UniversalUserId, nick: String?) {
        val nickname = nick ?: Users[sender].nickname

        val accountName = Users[sender].accountName!!
        val account = transaction { Account.getByNickname(accountName)!! }

        val nicknameToDelete = transaction { account.nicknames.firstOrNull { it.name == nickname } }
        if (nicknameToDelete == null) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "\u0002$accountName\u0002 계정에 \u0002$nickname\u0002 닉네임이 등록되어 있지 않습니다."
            )
            return
        }

        if (nicknameToDelete.isAccountName) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "\u0002$accountName\u0002 계정에 \u0002$nickname\u0002 닉네임이 대표 이름으로 등록되어 있어 제거할 수 없습니다."
            )
            Service.sendErrorMessageAsServiceUser(
                sender,
                "\u0002/msg ${Service.nickname} 이름변경\u0002 명령을 이용해 대표 이름을 수정해주세요."
            )
            return
        }

        transaction {
            nicknameToDelete.delete()
        }

        Service.sendMessageAsServiceUser(sender, "\u0002$accountName\u0002 계정에서 \u0002$nickname\u0002 닉네임을 제거했습니다.")
    }

    @ServiceCommand(
        name = "이름변경",
        aliases = ["대표닉네임변경", "CHANGENAME"],
        syntax = "<새 계정명>",
        summary = "현재 오징어 IRC 네트워크에 로그인되어 있는 계정의 이름을 변경합니다.",
        description = "이 명령을 사용할 시 현재 로그인되어 있는 계정의 이름을 변경합니다.\n" +
                      "기존 계정에 이미 \u0002닉네임등록\u0002 명령을 이용해 추가가 완료되어 있는 닉네임중 하나를 선택할 수 있습니다\n" +
                      "계정 이름이 바뀐 후에는 기존 계정명이 보조 계정명(등록된 닉네임)으로 자동으로 바뀌게 됩니다.",
        permission = Permission.AUTHENTICATED,
    )
    suspend fun changeName(sender: UniversalUserId, nick: String) {
        val account = transaction { Account.getByNickname(Users[sender].accountName!!)!! }
        val accountName = transaction { account.name }

        val currentNickname = transaction { account.nicknames.first { it.isAccountName } }
        if (currentNickname.name == nick) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "\u0002$accountName\u0002 계정의 대표 닉네임이 이미 \u0002$nick\u0002 입니다."
            )
            return
        }

        val newNickname = transaction { account.nicknames.firstOrNull { it.name == nick } }
        if (newNickname == null) {
            Service.sendErrorMessageAsServiceUser(
                sender,
                "\u0002$accountName\u0002 계정에 \u0002$nick\u0002 닉네임이 존재하지 않습니다."
            )
            Service.sendErrorMessageAsServiceUser(
                sender,
                "\u0002/msg ${Service.nickname} 닉네임등록\u0002 명령을 이용해 해당 닉네임을 계정에 추가해보세요."
            )
            return
        }

        transaction {
            currentNickname.isAccountName = false
            newNickname.isAccountName = true
        }

        OutgoingPacketChannel.sendAsServer(METADATA(sender, "accountname", newNickname.name))
        Service.sendMessageAsServiceUser(
            sender,
            "\u0002$accountName\u0002 계정의 대표 닉네임이 \u0002${newNickname.name}\u0002 (으)로 변경되었습니다."
        )
    }
}
{'fraction_non_alphanumeric': 0.09589302769818529, 'fraction_numerical': 0.016364215218083414, 'mean_word_length': 1.9439550140581068, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5715206', 'n_tokens_mistral': 6293, 'n_tokens_neox': 7090, 'n_words': 1129}