<repo_name>dylech30th/sayaka<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/GetBotManagementViewCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.GetBotManagementViewCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetBotManagementViewCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetBotManagementViewCommandTranslator::class, GetBotManagementViewCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
@WithPrivilege(Privilege.ADMINISTRATOR)
class GetBotManagementViewCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GetBotManagementViewCommand> {
        override val match: String = "/view"
        override val description: String = "获取Bot当前的管理视图"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SwitchPluginEnabledCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.shared.console.SwitchPluginEnabledCommand
import com.github.rinacm.sayaka.common.util.throws
import net.mamoe.mirai.message.MessageEvent

class SwitchPluginEnabledCommandTranslator : AbstractMessageTranslator<SwitchPluginEnabledCommand>() {
    override fun translate(rawInput: MessageEvent): SwitchPluginEnabledCommand {
        val parameters = defaultTrailingParameters(rawInput)
        return SwitchPluginEnabledCommand(
            rawInput, parameters[0], when (parameters[1].toLowerCase()) {
                "enable" -> true
                "disable" -> false
                else -> throws<IrrelevantMessageException>()
            }
        )
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetGomokuCreditRankingCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GetGomokuCreditRankingCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.gomoku.GomokuCredit
import com.github.rinacm.sayaka.gomoku.PlayerCredit
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageChain

class GetGomokuCreditRankingCommandHandler : CommandHandler<GetGomokuCreditRankingCommand> {
    override suspend fun process(command: GetGomokuCreditRankingCommand): List<MessageChain>? {
        val group = command.messageEvent.subject as Group
        val sortedList = GomokuCredit.get().filter {
            group.members.any { m -> m.id.toString() == it.player }
        }.sortedByDescending(PlayerCredit::credit).take(10)
        return if (sortedList.isEmpty())
            "该群暂时没有任何对局记录".asSingleMessageChainList()
        else buildString {
            for (p in sortedList) {
                appendLine("玩家: ${group[p.player.toLong()].nick} QQ: ${p.player} 点数: ${p.credit}")
            }
        }.asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/ShutdownBotCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotFactory
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.ShutdownBotCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class ShutdownBotCommandHandler : CommandHandler<ShutdownBotCommand> {
    override suspend fun process(command: ShutdownBotCommand): List<MessageChain>? {
        BotFactory.requestShuttingDown(5000)
        return "Bot实例将会在5秒钟后关闭".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/ExemptPluginExcludeCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.plugin.ExcludeType
import com.github.rinacm.sayaka.common.shared.console.ExemptPluginExcludeCommand
import com.github.rinacm.sayaka.common.util.throws
import net.mamoe.mirai.message.MessageEvent

class ExemptPluginExcludeCommandTranslator : AbstractMessageTranslator<ExemptPluginExcludeCommand>() {
    override fun translate(rawInput: MessageEvent): ExemptPluginExcludeCommand {
        val parameters = defaultTrailingParameters(rawInput)
        return ExemptPluginExcludeCommand(rawInput, parameters[0], parameters[1], ExcludeType.toExcludeType(parameters[2]) ?: throws<IrrelevantMessageException>())
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetRandomRankingCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.pixiv.GetRandomRankingCommand
import net.mamoe.mirai.message.MessageEvent

class GetRandomRankingCommandTranslator : AbstractMessageTranslator<GetRandomRankingCommand>() {
    override fun translate(rawInput: MessageEvent): GetRandomRankingCommand {
        return GetRandomRankingCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/Judgement.kt
package com.github.rinacm.sayaka.gomoku

import com.github.rinacm.sayaka.common.util.mapAnnotation
import kotlin.reflect.KFunction

class Judgement(private val chessboard: Array<Array<Piece>>) {
    private data class Step(val leftX: Int, val leftY: Int, val rightX: Int, val rightY: Int)

    private fun getStep(attached: KFunction<*>): Step {
        val (leftX, leftY) = mapAnnotation<StepLeft, Pair<Int, Int>>(attached) { it.stepX to it.stepY }
        val (rightX, rightY) = mapAnnotation<StepRight, Pair<Int, Int>>(attached) { it.stepX to it.stepY }
        return Step(leftX, leftY, rightX, rightY)
    }

    fun check(piece: Piece): Role {
        val vertical = checkVertical(piece)
        val horizontal = checkHorizontal(piece)
        val leadingDiagonal = checkLeadingDiagonal(piece)
        val diagonal = checkDiagonal(piece)
        return when {
            vertical != Role.NONE -> vertical
            horizontal != Role.NONE -> horizontal
            leadingDiagonal != Role.NONE -> leadingDiagonal
            diagonal != Role.NONE -> diagonal
            else -> Role.NONE
        }
    }

    private fun check(piece: Piece, step: Step, kind: Role): Boolean {
        var counter = 0
        var check = piece
        while (check.x <= 14 && check.y <= 14 && check.kind == kind) {
            if (counter == 5) return true
            counter++
            try {
                check = chessboard.getPoint(check.x + step.rightX, check.y + step.rightY)
            } catch (e: IndexOutOfBoundsException) {
                break
            }
        }
        check = piece
        while (check.x >= 0 && check.y >= 0 && check.kind == kind) {
            if (counter == 5) return true
            counter++
            try {
                check = chessboard.getPoint(check.x + step.leftX, check.y + step.leftY)
            } catch (e: IndexOutOfBoundsException) {
                break
            }
        }
        return false
    }

    private fun checkBoth(piece: Piece, attached: KFunction<*>): Role {
        val step = getStep(attached)
        return when {
            check(piece, step, Role.WHITE) -> Role.WHITE
            check(piece, step, Role.BLACK) -> Role.BLACK
            else -> Role.NONE
        }
    }

    @StepLeft(0, -1)
    @StepRight(0, 1)
    private fun checkVertical(piece: Piece): Role = checkBoth(piece, ::checkVertical)

    @StepLeft(-1, 0)
    @StepRight(1, 0)
    private fun checkHorizontal(piece: Piece): Role = checkBoth(piece, ::checkHorizontal)

    @StepLeft(1, -1)
    @StepRight(-1, 1)
    private fun checkLeadingDiagonal(piece: Piece): Role = checkBoth(piece, ::checkLeadingDiagonal)

    @StepLeft(-1, -1)
    @StepRight(1, 1)
    private fun checkDiagonal(piece: Piece): Role = checkBoth(piece, ::checkDiagonal)
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetOrSetCurrentEmittingOptionCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.pixiv.GetOrSetCurrentEmittingOptionCommand
import com.github.rinacm.sayaka.common.util.throws
import net.mamoe.mirai.message.MessageEvent

class GetOrSetCurrentEmittingOptionCommandTranslator : AbstractMessageTranslator<GetOrSetCurrentEmittingOptionCommand>() {
    override fun translate(rawInput: MessageEvent): GetOrSetCurrentEmittingOptionCommand {
        val parameters = defaultTrailingParameters(rawInput)
        if (parameters.isEmpty()) return GetOrSetCurrentEmittingOptionCommand(rawInput, null, null)
        if (parameters.size != 2) throws<IllegalArgumentException>()
        return GetOrSetCurrentEmittingOptionCommand(rawInput, parameters[0], parameters[1])
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/Piece.kt
package com.github.rinacm.sayaka.gomoku

/**
 * Represents a piece in the chessboard, where [x] and [y] is the
 * virtual coordinate, e.g. (1, a) (2, b)
 * @param x the virtual x coordinate
 * @param y the virtual y coordinate
 * @param kind role of this piece ([Role.BLACK]/[Role.WHITE])
 */
data class Piece(val x: Int, val y: Int, val kind: Role) {
    companion object {
        private val invalid = Piece(-1, -1, Role.NONE)
        fun invalid(): Piece = invalid

        fun yAxisToNumber(c: Char): Int {
            val u = c.toUpperCase()
            if (u in 'A'..'O') {
                return u - 'A'
            }
            throw IndexOutOfBoundsException("$c")
        }

        fun numberToYAxis(num: Int): String {
            if (num in 0 until 15) {
                return ('A' + num).toString()
            }
            throw IndexOutOfBoundsException("$num")
        }
    }

    fun toActualPoint(): Point = Point(x * 40 + 50, y * 40 + 50)
}

fun Array<Array<Piece>>.getPoint(x: Int, y: Int): Piece {
    return this[x][y]
}

enum class Role(val roleName: String) {
    BLACK("黑"), WHITE("白"), NONE("默认");

    fun getNegative(): Role {
        return when (this) {
            BLACK -> WHITE
            WHITE -> BLACK
            NONE -> NONE
        }
    }

    override fun toString(): String {
        return roleName
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/SetAdministratorCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.SetAdministratorCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SetAdministratorCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(SetAdministratorCommandTranslator::class, SetAdministratorCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = QQ_ID_REGEX)
@WithPrivilege(Privilege.SUPERUSER)
data class SetAdministratorCommand(val qqId: String, override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<SetAdministratorCommand> {
        override val match: String = "/op"
        override val description: String = "将${Placeholder("id")}设置为管理员"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("id")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/ErrorStage.kt
package com.github.rinacm.sayaka.common.message.error

enum class ErrorStage(val localizedName: String) {
    VALIDATION("命令验证"), TRANSLATION("命令转译"), PROCESSING("命令处理")
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/AuthorizeException.kt
package com.github.rinacm.sayaka.common.message.error

import com.github.rinacm.sayaka.common.util.Privilege

class AuthorizeException(requiredPrivilege: Privilege) : Exception() {
    override val message: String = "你没有执行该命令所需要的${requiredPrivilege}权限"
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/AboutSayakaCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.AboutSayakaCommand
import net.mamoe.mirai.message.MessageEvent

class AboutSayakaCommandTranslator : AbstractMessageTranslator<AboutSayakaCommand>() {
    override fun translate(rawInput: MessageEvent): AboutSayakaCommand {
        return AboutSayakaCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/Game.kt
package com.github.rinacm.sayaka.gomoku

import com.github.rinacm.sayaka.common.init.BotFactory
import com.github.rinacm.sayaka.common.util.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain
import java.io.Closeable
import kotlin.random.Random
import kotlin.random.nextInt

enum class PlayerJoinState {
    SUCCESS, HAS_JOINED, GAME_FULL
}

/**
 * Every movement request of current [Game] will trigger an action
 * which will report a [GameState] to indicate the state of this movement
 */
enum class GameState {
    /** Game ended because of chessboard is full */
    CHESSBOARD_FULL,

    /** The movement request is legal */
    SUCCESS,

    /** The movement request is illegal because of the point is out of range or has already been placed by another piece */
    ILLEGAL_MOVEMENT,

    /** The movement request is illegal, which will be considered as an irrelevant request */
    IRRELEVANT_MESSAGE
}

data class DropResult(val gameState: GameState, val dropPoint: Point)

class Game(val gameId: String) : Closeable {
    abstract class Player {
        abstract val black: String
        abstract val white: String

        fun getNegative(p: String): String {
            return when (p) {
                black -> white
                white -> black
                else -> error("$p is not one of $black or $white")
            }
        }

        fun getByRole(role: Role): String {
            return when (role) {
                Role.BLACK -> black
                Role.WHITE -> white
                Role.NONE -> error("role must not be ${Role.NONE}")
            }
        }

        fun toRole(id: String): Role {
            return when (id) {
                black -> Role.BLACK
                white -> Role.WHITE
                else -> Role.NONE
            }
        }

        override fun toString(): String {
            return "($black, $white)"
        }
    }

    private class MutablePlayer : Player() {
        override lateinit var black: String
        override lateinit var white: String

        fun isBlackInitialized(): Boolean {
            return ::black.isInitialized
        }

        fun isWhiteInitialized(): Boolean {
            return ::white.isInitialized
        }

        fun switchRole() {
            if (!(isBlackInitialized() && isWhiteInitialized())) error("not initialized")
            black = white.also { white = black }
        }
    }

    // --- IN-GAME REQUIREMENTS/STATES ---
    var requestEnding = String.EMPTY to false
    private val gameRecordDirectory = GameConfiguration.gameRecordDirectory(gameId)
    private var gameStart = false
    private var imagePath = String.EMPTY
    private var chessboard: Array<Array<Piece>>

    // --- BUNDLED/EXTERNAL UTILITIES ---
    private val stopWatch = StopWatch()
    private val judgement: Judgement

    // --- PLAYERS ---
    private val player = MutablePlayer()

    // --- RECORDER FOR LAST 2 STEPS ---
    private lateinit var previousMove: Piece
    private lateinit var lastModified: Piece

    private var step = 0
    private var role = Role.BLACK

    init {
        gameRecordDirectory.toAbsolutePath().mkdirs()
        chessboard = Array(15) { Array(15) { Piece.invalid() } }
        judgement = Judgement(chessboard)
        for (i in 0 until 15) {
            for (j in 0 until 15) {
                chessboard[i][j] = Piece(i, j, Role.NONE)
            }
        }
    }

    fun join(playerId: String): PlayerJoinState {
        if (isFull()) return PlayerJoinState.GAME_FULL
        if (!player.isBlackInitialized()) {
            player.black = playerId
            return PlayerJoinState.SUCCESS
        }
        if (playerId != player.black) {
            player.white = playerId
            return PlayerJoinState.SUCCESS
        }
        return PlayerJoinState.HAS_JOINED
    }

    fun processGoCommand(command: String): DropResult {
        val (state, point) = parseCommand(command)

        if (state == GameState.SUCCESS) {
            val (success, draw) = go(point.x, point.y)
            if (success) {
                drawChessboard()
                return DropResult(if (draw) GameState.CHESSBOARD_FULL else GameState.SUCCESS, point)
            }
            return DropResult(GameState.ILLEGAL_MOVEMENT, point)
        }
        return DropResult(state, Point(-1, -1))
    }

    fun startGame() {
        stopWatch.start()
        gameStart = true
        randomizeBlack()
        drawChessboard()
    }

    fun isFull(): Boolean {
        return player.isBlackInitialized() && player.isWhiteInitialized()
    }

    fun isInRole(qq: String): Boolean {
        return player.black == qq && role == Role.BLACK || player.white == qq && role == Role.WHITE
    }

    fun isActivatedAndValid(qq: String): Boolean {
        return isFull() && isPlayer(qq)
    }

    fun isPlayer(qq: String): Boolean {
        return player.black == qq || player.white == qq
    }

    fun getImagePath(): String {
        return String(imagePath.toCharArray())
    }

    fun getPlayer(): Player {
        return player
    }

    fun getRole(): Role {
        return role
    }

    fun isWin(x: Int, y: Int): Role {
        return check(chessboard.getPoint(x, y)).apply {
            if (this != Role.NONE) stopWatch.stop()
        }
    }

    private fun check(piece: Piece): Role {
        return judgement.check(piece)
    }

    private fun randomizeBlack() {
        val i = Random.nextInt(0..1)
        if (i == 1) player.switchRole()
    }

    private fun switchRole() {
        role = role.getNegative()
    }

    private fun updateImagePath() {
        imagePath = "$gameRecordDirectory\\chessboard_${step}.jpg".toAbsolutePath().toString()
    }

    private fun drawChessboard() {
        runBlocking {
            updateImagePath()
            if (::lastModified.isInitialized) {
                Painter.save(imagePath, chessboard, lastModified)
            } else {
                Painter.save(imagePath)
            }
            delay(10)
        }
    }

    private fun parseCommand(command: String): Pair<GameState, Point> {
        val match = (command match "(?<x>\\d{1,2})(?<y>[a-oA-O])".toRegex())!!
        val xVal = match.groups["x"]?.value
        val yVal = match.groups["y"]?.value

        val illegalPoint = Point(-1, -1)

        if (xVal.isNullOrEmpty() || yVal.isNullOrEmpty()) {
            return GameState.IRRELEVANT_MESSAGE to illegalPoint
        }

        val x = xVal.toInt()
        val y = Piece.yAxisToNumber(yVal[0])

        if (x > 14) {
            return GameState.ILLEGAL_MOVEMENT to illegalPoint
        }
        return GameState.SUCCESS to Point(x, y)
    }

    /**
     * @return (successMovement, draw)
     */
    private fun go(x: Int, y: Int): Pair<Boolean, Boolean> {
        if (!validateMovement(x, y)) {
            return false to false
        }

        return when (role) {
            Role.WHITE -> whiteGo(x, y)
            Role.BLACK -> blackGo(x, y)
            Role.NONE -> throw IndexOutOfBoundsException("player role cannot be empty")
        }.let {
            if (::lastModified.isInitialized) previousMove = lastModified
            lastModified = chessboard.getPoint(x, y)
            true to isChessboardFull()
        }
    }

    private fun whiteGo(x: Int, y: Int) {
        switchRole()
        with(Piece(x, y, Role.WHITE)) {
            chessboard[x][y] = this
            lastModified = this
        }
        step++

    }

    private fun blackGo(x: Int, y: Int) {
        switchRole()
        with(Piece(x, y, Role.BLACK)) {
            chessboard[x][y] = this
            lastModified = this
        }
        step++
    }

    private fun isChessboardFull(): Boolean {
        return chessboard.flatten().all { it.kind != Role.NONE }
    }

    private fun validateMovement(x: Int, y: Int): Boolean {
        return x <= 14 && y <= 14 && chessboard[x][y].kind == Role.NONE
    }

    fun getWinMessageAndDisposeGame(win: Role): MessageChain {
        val elapsed = stopWatch.elapsed()

        val winner = player.getByRole(win)
        val loser = player.getNegative(winner)

        GomokuCredit.setOrIncreaseCredit(winner, 10000)
        GomokuCredit.setOrIncreaseCredit(loser, -10000)


        return buildMessageChain {
            val w = At(BotFactory.getInstance().getGroup(gameId.toLong())[player.getByRole(win).toLong()])
            val l = At(BotFactory.getInstance().getGroup(gameId.toLong())[player.getNegative(player.getByRole(win)).toLong()])
            add("${win.roleName}子" followedBy (w + "胜利！游戏结束！\n"))
            add("胜者" followedBy (w + "获得10000点数，现在一共有${GomokuCredit.getCredit(winner)}点数\n"))
            add("败者" followedBy (l + "扣去10000点数，现在一共有${GomokuCredit.getCredit(loser)}点数\n"))
            add("本局游戏共用时${elapsed.toHoursPart()}小时${elapsed.toMinutesPart()}分${elapsed.toSecondsPart()}秒")
        }.also { close() }
    }

    override fun close() {
        GomokuGameFactory.removeGame(gameId)
        gameStart = false
        player.black = String.EMPTY
        player.white = String.EMPTY
        stopWatch.stop()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/contextual/WrappedExecutor.kt
package com.github.rinacm.sayaka.common.message.contextual

/**
 * Execute another function in the [executeWrapped], inspired by
 * decorator pattern but more flexible
 */
interface WrappedExecutor<in T, out R> {
    suspend fun executeWrapped(parameter: T): R
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/SetGlobalDispatcherImplCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.SetGlobalDispatcherImplCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SetGlobalDispatcherImplCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(SetGlobalDispatcherImplCommandTranslator::class, SetGlobalDispatcherImplCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@WithPrivilege(Privilege.ADMINISTRATOR)
@Validator(RegexValidator::class, regex = "[a-zA-Z]+")
class SetGlobalDispatcherImplCommand(override val messageEvent: MessageEvent, val name: String) : Command {
    companion object Key : Command.Key<SetGlobalDispatcherImplCommand> {
        override val match: String = "/dispatchBy"
        override val description: String = "设置Bot的全局调度器"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("dispatcher_name")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GomokuPlayerMoveCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerMoveCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.common.util.followedBy
import com.github.rinacm.sayaka.common.util.uploadAsImage
import com.github.rinacm.sayaka.gomoku.GameState
import com.github.rinacm.sayaka.gomoku.GomokuGameFactory
import com.github.rinacm.sayaka.gomoku.Piece
import com.github.rinacm.sayaka.gomoku.Role
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.at
import net.mamoe.mirai.message.data.buildMessageChain

class GomokuPlayerMoveCommandHandler : CommandHandler<GomokuPlayerMoveCommand> {
    override suspend fun process(command: GomokuPlayerMoveCommand): List<MessageChain>? {
        val game = GomokuGameFactory.tryGetGameOrNull(command.messageEvent.subject.id.toString())
        val sender = command.messageEvent.sender.id.toString()
        if (game != null && game.isActivatedAndValid(sender) && game.isInRole(sender)) {
            val list = mutableListOf<MessageChain>()
            val dropResult = game.processGoCommand(command.messageEvent.message[PlainText]!!.content)
            with(command.messageEvent.subject) {
                when (dropResult.gameState) {
                    GameState.SUCCESS -> list.add(buildMessageChain {
                        add("${game.getRole()}棋成功落子: [${dropResult.dropPoint.x},${Piece.numberToYAxis(dropResult.dropPoint.y)}]\n")
                        add(game.getImagePath().uploadAsImage(this@with))
                    })
                    GameState.ILLEGAL_MOVEMENT -> return "哎呀，您落子的位置好像不太合理，再重新试试吧？".asSingleMessageChainList()
                    GameState.CHESSBOARD_FULL -> {
                        game.close()
                        return "棋盘上已经没有位置了，和棋！\n游戏结束！".asSingleMessageChainList()
                    }
                    GameState.IRRELEVANT_MESSAGE -> return null
                }
            }

            val win = game.isWin(dropResult.dropPoint.x, dropResult.dropPoint.y)
            with(command.messageEvent.subject as Group) {
                if (win != Role.NONE) {
                    list.add(game.getWinMessageAndDisposeGame(win))
                } else {
                    list.add("请${game.getRole()}方" followedBy (this[game.getPlayer().getByRole(game.getRole()).toLong()].at() + "棋手走子"))
                }
            }
            return list
        }
        return null
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetBotManagementViewCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.GetBotManagementViewCommand
import net.mamoe.mirai.message.MessageEvent

class GetBotManagementViewCommandTranslator : AbstractMessageTranslator<GetBotManagementViewCommand>() {
    override fun translate(rawInput: MessageEvent): GetBotManagementViewCommand {
        return GetBotManagementViewCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/RawCommand.kt
package com.github.rinacm.sayaka.common.shared

data class RawCommand(val name: String, val parameters: String)<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/ShowHelpMessageCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.ShowHelpMessageCommandHandler
import com.github.rinacm.sayaka.common.message.translator.ShowHelpMessageCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(ShowHelpMessageCommandTranslator::class, ShowHelpMessageCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "(/?[\\u4e00-\\u9fa5a-zA-Z0-9]+ *)*")
class ShowHelpMessageCommand(override val messageEvent: MessageEvent, val names: List<String> = emptyList()) : Command {
    companion object Key : Command.Key<ShowHelpMessageCommand> {
        override val match: String = "/help"
        override val description: String = "查看Sayaka Bot™的使用说明"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("plugin_name_or_command_name(with '/' prefix)", option = PlaceholderOption.OPTIONAL, repeatable = true)}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetOpenSourceMessageCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.GetOpenSourceMessageCommand
import net.mamoe.mirai.message.MessageEvent

class GetOpenSourceMessageCommandTranslator : AbstractMessageTranslator<GetOpenSourceMessageCommand>() {
    override fun translate(rawInput: MessageEvent): GetOpenSourceMessageCommand {
        return GetOpenSourceMessageCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetBotManagementViewCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.console.GetBotManagementViewCommand
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.data.MessageChain
import kotlin.reflect.full.companionObjectInstance

class GetBotManagementViewCommandHandler : CommandHandler<GetBotManagementViewCommand> {
    override suspend fun process(command: GetBotManagementViewCommand): List<MessageChain>? {
        val plugins = Plugin.all() ?: emptyList()
        return buildString {
            appendLine("管理员列表: ")
            appendLineIndent(BotContext.getAdmins().joinToString(" ", "[", "]"))
            appendLine("黑名单: ")
            for (p in plugins) {
                val key = (p.companionObjectInstance as Plugin.Key<*>)
                if (key.excludes.isNotEmpty()) {
                    appendLineIndent("${key.canonicalizeName()}: ")
                    for ((k, v) in key.excludes) {
                        appendLineIndent("$k -> ${v.joinToString(" ", "[", "]")}", indentLevel = 2)
                    }
                }
            }
            appendLine("插件列表: (使用/help查看所有插件的用法或使用/help ${Placeholder("name")}查看某个具体插件的用法)")
            appendIndent(plugins.joinToString(",", "[", "]", transform = {
                with(it.companionObjectInstance) {
                    if (this is Plugin.Key<*>) this.canonicalizeName() else String.EMPTY
                }
            }))
        }.asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GomokuPlayerExitCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GomokuPlayerExitCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GomokuPlayerExitCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GomokuPlayerExitCommandTranslator::class, GomokuPlayerExitCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(PurePlainTextValidator::class)
class GomokuPlayerExitCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GomokuPlayerExitCommand> {
        override val match: String = "/ge"
        override val description: String = "退出当前的五子棋对局"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SetGomokuPlayerCreditCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.SetGomokuPlayerCreditCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.gomoku.GomokuCredit
import net.mamoe.mirai.message.data.MessageChain

class SetGomokuPlayerCreditCommandHandler : CommandHandler<SetGomokuPlayerCreditCommand> {
    override suspend fun process(command: SetGomokuPlayerCreditCommand): List<MessageChain>? {
        val qqId = command.qqId.toLongOrNull()
        val amount = command.qqId.toLongOrNull()
        return if (qqId != null && amount != null) {
            GomokuCredit.setOrRewriteCredit(qqId.toString(), amount)
            "设置点数成功".asSingleMessageChainList()
        } else "参数格式错误，请仔细检查后重试".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SwitchPluginEnabledCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.console.SwitchPluginEnabledCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class SwitchPluginEnabledCommandHandler : CommandHandler<SwitchPluginEnabledCommand> {
    override suspend fun process(command: SwitchPluginEnabledCommand): List<MessageChain>? {
        val clazz = Plugin.getPluginKeyByName(command.name)
        if (clazz == ConsolePlugin) {
            return "不能禁用控制台插件".asSingleMessageChainList()
        }
        if (clazz != null) {
            return (if (clazz.enabled != command.enable) {
                clazz.enabled = command.enable
                if (command.enable)
                    "成功启用${clazz.canonicalizeName()}插件"
                else "成功禁用${clazz.canonicalizeName()}插件"
            } else {
                "${clazz.canonicalizeName()}的enabled属性已经是${command.enable}"
            }).asSingleMessageChainList()
        }
        return "未找到${command.name}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/CommandFactory.kt
package com.github.rinacm.sayaka.common.shared

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.error.AuthorizeException
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ExcludeType
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.util.*
import com.github.rinacm.sayaka.common.util.reflector.TypedSubclassesScanner
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.message.MessageEvent
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.primaryConstructor

object CommandFactory {
    private val commandKeywordToCommandClassCache = ConcurrentHashMap<String, KClass<out Command>>()
    private val commandClassToContextualCache = ConcurrentHashMap<KClass<out Command>, Contextual>()

    /**
     * Lookup a [KClass] of [Command] through the [TypedSubclassesScanner.markedMap]
     * by [cmd] as the match key, if matches found then put them into a [commandKeywordToCommandClassCache] to
     * improve the query speed
     */
    fun lookup(cmd: String, user: User? = null): KClass<out Command>? {
        var key = cmd
        if (user != null) {
            CommandMapping.findMappingCommandString(user, key)?.let { key = it }
        }
        if (key in commandKeywordToCommandClassCache.keys) {
            return commandKeywordToCommandClassCache[key]!!
        }
        val obj = TypedSubclassesScanner.markedMap[Command::class]
            ?.firstOrNull { tryMatch(key, it.companionObjectInstance as Command.Key<*>?) }
            ?.companionObject
            ?: return null
        @Suppress("UNCHECKED_CAST")
        return (obj.java.declaringClass.kotlin as KClass<out Command>).apply {
            commandKeywordToCommandClassCache[key] = this
        }
    }

    /**
     * Attempting to match a [cmd] string with given [commandKey] and return the
     * matching result
     */
    private fun tryMatch(cmd: String?, commandKey: Command.Key<*>?): Boolean {
        if (cmd == null || commandKey == null) return false
        return when (commandKey.matchOption) {
            MatchOption.REG_EXP -> cmd matches commandKey.match.toRegex()
            MatchOption.PLAIN_TEXT -> cmd == commandKey.match
        }
    }

    fun getContextual(cmdClass: KClass<out Command>): Contextual {
        if (cmdClass in commandClassToContextualCache.keys) {
            return commandClassToContextualCache[cmdClass]!!
        }
        return cmdClass.annotation<Contextual>().apply {
            commandClassToContextualCache[cmdClass] = this
        }
    }

    fun String.toRawCommand(): RawCommand {
        return RawCommand(substringBefore(' ').trim(), substringAfter(' ', String.EMPTY).trim().split("\\s+".toRegex()).joinToString(" "))
    }

    fun KClass<out Command>.getAuthority(): Privilege? {
        return this.annotations.filterIsInstance<WithPrivilege>().firstOrNull()?.privilege
    }

    /**
     * There're totally three checking steps:
     * 1. check the command is marked as [PermanentlyDisable]
     * 2. check the command's plugin is enabled and does not block the sender
     * 3. check the command's [Responding] comfort the sender
     */
    fun checkAccess(cmdClass: KClass<out Command>, messageEvent: MessageEvent): Boolean {
        val key = Plugin.getPluginKeyByAnnotation(cmdClass.annotations.singleIsInstance())
        return !isPermanentlyDisabled(cmdClass) && key != null && key.enabled && checkExcludes(key, messageEvent) && canRespond(cmdClass, messageEvent)
    }

    private fun canRespond(cmdClass: KClass<out Command>, messageEvent: MessageEvent): Boolean {
        return when (cmdClass.annotations.filterIsInstance<Responding>().firstOrNull()?.type) {
            RespondingType.GROUP -> messageEvent.subject is Group
            RespondingType.WHISPER -> messageEvent.subject is Friend || messageEvent.subject is Member
            else -> true
        }
    }

    private fun checkExcludes(key: Plugin.Key<*>, messageEvent: MessageEvent): Boolean {
        val id = messageEvent.subject.id.toString()
        if (id !in key.excludes || key.excludes[id] == null) return true
        return when (messageEvent.subject) {
            is Group -> ExcludeType.GROUP !in key.excludes[id]!!
            else -> ExcludeType.WHISPER !in key.excludes[id]!!
        }
    }

    private fun isPermanentlyDisabled(cmdClass: KClass<out Command>): Boolean {
        return cmdClass.annotations.any { it is PermanentlyDisable }
    }

    suspend fun validate(raw: MessageEvent, cmdClass: KClass<out Command>, annotation: Validator) {
        val clazz = annotation.validatorClass
        for (c in clazz) {
            val primaryConstructor = c.primaryConstructor!!
            when (clazz) {
                RegexValidator::class -> primaryConstructor.call(annotation.regex, cmdClass).executeWrapped(raw)
                PurePlainTextValidator::class -> primaryConstructor.call(cmdClass).executeWrapped(raw)
            }
        }
        // validate privilege
        when (cmdClass.annotations.filterIsInstance<WithPrivilege>().firstOrNull()?.privilege) {
            Privilege.SUPERUSER -> {
                if (raw.sender.id.toString() != BotContext.getOwner()) {
                    throw AuthorizeException(Privilege.SUPERUSER)
                }
            }
            Privilege.ADMINISTRATOR -> {
                if (raw.sender.id.toString() !in BotContext.getAdmins()) {
                    throw AuthorizeException(Privilege.ADMINISTRATOR)
                }
            }
            else -> return
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/ReloadRankingSettingsCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.pixiv.ReloadRankingSettingsCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.pixiv.core.RankingEmitter
import net.mamoe.mirai.message.data.MessageChain

class ReloadRankingSettingsCommandHandler : CommandHandler<ReloadRankingSettingsCommand> {
    override suspend fun process(command: ReloadRankingSettingsCommand): List<MessageChain>? {
        return with(RankingEmitter.getOrCreateByContact(command.messageEvent.subject)) {
            reload()
            "成功重置日期为: ${current.second}".asSingleMessageChainList()
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/RaiseExceptionCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.RaiseExceptionCommandHandler
import com.github.rinacm.sayaka.common.message.translator.RaiseExceptionCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(RaiseExceptionCommandTranslator::class, RaiseExceptionCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
@WithPrivilege(Privilege.ADMINISTRATOR)
class RaiseExceptionCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<RaiseExceptionCommand> {
        override val match: String = "/traise"
        override val description: String = "抛出一个被ProcessException所包装的java.lang.IllegalStateException，该命令仅用于测试"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/GetDispatchersMessageCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.GetDispatchersMessageCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetDispatchersMessageCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetDispatchersMessageCommandTranslator::class, GetDispatchersMessageCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "(global)?")
class GetDispatchersMessageCommand(override val messageEvent: MessageEvent, val global: Boolean = false) : Command {
    companion object Key : Command.Key<GetDispatchersMessageCommand> {
        override val match: String = "/dispatchers"
        override val description: String = "查看所有可用调度器/全局调度器"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("global", option = PlaceholderOption.OPTIONAL)}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GomokuPlayerMoveCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerMoveCommand
import net.mamoe.mirai.message.MessageEvent

class GomokuPlayerMoveCommandTranslator : AbstractMessageTranslator<GomokuPlayerMoveCommand>() {
    override fun translate(rawInput: MessageEvent): GomokuPlayerMoveCommand {
        return GomokuPlayerMoveCommand(rawInput)
    }
}
<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GomokuPlayerJoinGameCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GomokuPlayerJoinGameCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GomokuPlayerJoinGameCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GomokuPlayerJoinGameCommandTranslator::class, GomokuPlayerJoinGameCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(PurePlainTextValidator::class)
class GomokuPlayerJoinGameCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GomokuPlayerJoinGameCommand> {
        override val match: String = "/gj"
        override val description: String = "发起或加入五子棋对局"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/Annotations.kt
package com.github.rinacm.sayaka.common.util

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.message.contextual.MessageTranslator
import com.github.rinacm.sayaka.common.message.contextual.MessageValidator
import com.github.rinacm.sayaka.common.message.contextual.WrappedExecutor
import com.github.rinacm.sayaka.common.plugin.Plugin
import org.intellij.lang.annotations.Language
import kotlin.reflect.KClass

enum class MatchOption {
    REG_EXP, PLAIN_TEXT
}

/**
 * Mark the contextual [MessageTranslator] and [CommandHandler] of the attached class as
 * its command pipe processor
 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Contextual(val translator: KClass<out WrappedExecutor<*, *>>, val handler: KClass<out WrappedExecutor<*, Unit>>)

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Validator(vararg val validatorClass: KClass<out MessageValidator>, @Language("RegExp") val regex: String = "")

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class WithPrivilege(val privilege: Privilege)

enum class RespondingType {
    WHISPER, GROUP
}

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Responding(val type: RespondingType)

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class PermanentlyDisable

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class PluginOwnership(val clazz: KClass<out Plugin<*>>)

enum class TriggerPoint {
    STARTUP, BEFORE_CLOSED
}

@Suppress("unused")
enum class TriggerPriority {
    HIGHEST, HIGH, NORMAL, LOW, LOWEST
}

/**
 * This annotation mark a method as dynamically invokable, the marked method will be invoked
 * at the runtime automatically based on [priority] and [triggerPoint]
 *
 * @param triggerPoint indicates the when the method should be invoked
 * @param priority indicates the invoke priority, the method with higher [priority]
 *        will be executed earlier than others
 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class TriggerOn(val triggerPoint: TriggerPoint, val priority: TriggerPriority = TriggerPriority.NORMAL)<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/RaiseExceptionCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.RaiseExceptionCommand
import com.github.rinacm.sayaka.common.util.throws
import net.mamoe.mirai.message.data.MessageChain

class RaiseExceptionCommandHandler : CommandHandler<RaiseExceptionCommand> {
    override suspend fun process(command: RaiseExceptionCommand): List<MessageChain>? {
        throws<IllegalArgumentException>("THIS IS A TEST-PURPOSE EXCEPTION")
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/Painter.kt
package com.github.rinacm.sayaka.gomoku

import java.awt.*
import java.awt.geom.Rectangle2D
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO

object Painter {
    private const val chessboardImageSize = 650
    private const val chessboardSize = 560
    private val lines = intArrayOf(50, 90, 130, 170, 210, 250, 290, 330, 370, 410, 450, 490, 530, 570, 610)
    private val points = arrayOf(
        Point(170, 170),
        Point(170, 490),
        Point(330, 330),
        Point(490, 170),
        Point(490, 490),
    )

    fun save(path: String) {
        val img = drawNew()
        ImageIO.write(img, "png", File(path))
    }

    fun save(path: String, chessboard: Array<Array<Piece>>, toBeHighlight: Piece) {
        val img = drawNew()
        for (piece in chessboard.flatten()) {
            drawPiece(img, piece, piece == toBeHighlight)
        }
        ImageIO.write(img, "png", File(path))
    }

    private fun drawNew(): BufferedImage {
        val img = BufferedImage(chessboardImageSize, chessboardImageSize, BufferedImage.TYPE_INT_ARGB)
        val g = getGraphics(img)
        fillChessboard(g)
        drawBorder(g)
        drawCross(g)
        drawIdentifier(g)
        drawStars(g)
        return img
    }

    private fun getGraphics(img: BufferedImage): Graphics2D {
        val graphics = img.createGraphics()
        graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
        graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
        graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON)
        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
        graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)
        return graphics
    }

    private fun drawPiece(image: BufferedImage, piece: Piece, highlight: Boolean) {
        val g = image.createGraphics()
        val real = piece.toActualPoint()
        when (piece.kind) {
            Role.BLACK -> {
                g.paint = Color.BLACK
                g.fillOval(real.x - 20, real.y - 20, 40, 40)
            }
            Role.WHITE -> {
                g.paint = Color.WHITE
                g.fillOval(real.x - 20, real.y - 20, 40, 40)
                g.paint = Color.BLACK
                g.stroke = BasicStroke(0.5.toFloat(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)
                g.drawOval(real.x - 20, real.y - 20, 40, 40)
            }
            Role.NONE -> return
        }

        if (highlight) {
            g.paint = Color(220, 20, 60)
            g.stroke = BasicStroke(1.5.toFloat(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)
            with(real) {
                g.drawLine(x - 20, y - 20, x - 20, y - 15)
                g.drawLine(x - 20, y - 20, x - 15, y - 20)
                g.drawLine(x - 20, y + 20, x - 20, y + 15)
                g.drawLine(x - 20, y + 20, x - 15, y + 20)
                g.drawLine(x + 20, y - 20, x + 15, y - 20)
                g.drawLine(x + 20, y - 20, x + 20, y - 15)
                g.drawLine(x + 20, y + 20, x + 20, y + 15)
                g.drawLine(x + 20, y + 20, x + 15, y + 20)
            }
        }
    }

    private fun fillChessboard(g: Graphics2D) {
        g.paint = Color(232, 180, 130)
        g.fillRect(0, 0, chessboardImageSize, chessboardImageSize)
    }

    private fun drawBorder(g: Graphics2D) {
        g.paint = Color.BLACK
        g.stroke = BasicStroke(1.0.toFloat(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)
        g.draw(Rectangle2D.Double(0.5, 0.5, 649.0, 649.0))
    }

    private fun drawCross(g: Graphics2D) {
        g.paint = Color.BLACK
        g.stroke = BasicStroke(1.0.toFloat(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)
        for (line in lines) {
            g.drawLine(line, 50, line, chessboardSize + 50)
            g.drawLine(50, line, chessboardSize + 50, line)
        }
    }

    private fun drawIdentifier(g: Graphics2D) {
        g.font = Font("consolas", Font.PLAIN, 20)
        val metrics = g.fontMetrics
        for (i in lines.indices) {
            // centered align the string
            g.drawString(i.toString(), lines[i] - metrics.stringWidth(i.toString()) / 2, 25)
            g.drawString(Piece.numberToYAxis(i), 20, lines[i] - ((metrics.height / 2) - metrics.ascent - metrics.descent))
        }
    }

    private fun drawStars(g: Graphics2D) {
        g.paint = Color.BLACK
        for ((x, y) in points) {
            g.fillOval(x - 3, y - 3, 6, 6)
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/ExemptPluginExcludeCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.console.ExemptPluginExcludeCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class ExemptPluginExcludeCommandHandler : CommandHandler<ExemptPluginExcludeCommand> {
    override suspend fun process(command: ExemptPluginExcludeCommand): List<MessageChain>? {
        val key = Plugin.getPluginKeyByName(command.name)
        if (key != null) {
            val set = key.excludes[command.id]
            if (set == null || command.excludeType !in set) {
                return "${command.id}不在${command.name}的黑名单中".asSingleMessageChainList()
            }
            set.remove(command.excludeType)
            if (set.isEmpty()) {
                key.excludes.remove(command.id)
            }
            return "成功将${command.id}(${command.excludeType})从${key.canonicalizeName()}的黑名单中移除".asSingleMessageChainList()
        }
        return "未找到插件${command.name}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/RemoveCommandMappingCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.CommandMapping
import com.github.rinacm.sayaka.common.shared.console.RemoveCommandMappingCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class RemoveCommandMappingCommandHandler : CommandHandler<RemoveCommandMappingCommand> {
    override suspend fun process(command: RemoveCommandMappingCommand): List<MessageChain>? {
        if (CommandMapping.removeCommandMapping(command.messageEvent.sender, command.removal)) {
            return "成功移除对于${command.removal}的命令映射".asSingleMessageChainList()
        }
        return "未找到${command.removal}的任何映射".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SetSleepCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.SetSleepCommand
import net.mamoe.mirai.message.MessageEvent

class SetSleepCommandTranslator : AbstractMessageTranslator<SetSleepCommand>() {
    override fun translate(rawInput: MessageEvent): SetSleepCommand {
        return SetSleepCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetDispatchersMessageCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.message.factory.Dispatcher
import com.github.rinacm.sayaka.common.shared.console.GetDispatchersMessageCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain
import kotlin.reflect.full.companionObjectInstance

class GetDispatchersMessageCommandHandler : CommandHandler<GetDispatchersMessageCommand> {
    override suspend fun process(command: GetDispatchersMessageCommand): List<MessageChain>? {
        return if (command.global) {
            (Dispatcher.Global::class.companionObjectInstance as Dispatcher.Key<*>).canonicalizeName().asSingleMessageChainList()
        } else with(Dispatcher.availableDispatchers()) {
            this?.joinToString(",", "[", "]", transform = Dispatcher.Key<*>::canonicalizeName)?.asSingleMessageChainList()
                ?: "未找到任何可用的调度器".asSingleMessageChainList()
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/MandatoryEndGameCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GetGomokuCreditCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.gomoku.GomokuGameFactory
import net.mamoe.mirai.message.data.MessageChain

class MandatoryEndGameCommandHandler : CommandHandler<GetGomokuCreditCommand> {
    override suspend fun process(command: GetGomokuCreditCommand): List<MessageChain>? {
        val group = command.playerQQId.toLongOrNull()
        if (group != null) {
            with(GomokuGameFactory.tryGetGameOrNull(group.toString())) {
                return if (this != null) {
                    close()
                    "成功结束对局: $gameId".asSingleMessageChainList()
                } else "参数错误或者该群暂时没有对局正在进行".asSingleMessageChainList()
            }
        } else return "参数格式错误，必须是一个正确的群聊号码".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/Placeholder.kt
@file:Suppress("unused")

package com.github.rinacm.sayaka.common.util

import java.util.*

enum class PlaceholderOption {
    OPTIONAL, MUTUALLY_EXCLUSIVE, REQUIRED, ARRAY, EMPTY
}

class Placeholder(private vararg val names: String, private val option: PlaceholderOption = PlaceholderOption.REQUIRED, private val repeatable: Boolean = false) {
    constructor(
        vararg placeholders: Placeholder,
        option: PlaceholderOption = PlaceholderOption.REQUIRED,
        repeatable: Boolean = false
    ) : this(*placeholders.map(Placeholder::toString).toTypedArray(), option = option, repeatable = repeatable)

    companion object {
        val EMPTY: Placeholder = Placeholder(String.EMPTY, option = PlaceholderOption.EMPTY, repeatable = false)
    }

    private var next: LinkedList<Placeholder> = LinkedList(listOf(this))

    override fun toString(): String {
        return buildString {
            for (n in next) {
                val curr = when (option) {
                    PlaceholderOption.OPTIONAL -> "[${names[0]}${ellipsisIfRepeatable()}]"
                    PlaceholderOption.REQUIRED -> "<${names[0]}${ellipsisIfRepeatable()}>"
                    PlaceholderOption.MUTUALLY_EXCLUSIVE -> {
                        requires<IllegalArgumentException>(names.size > 1 && !repeatable, "size of placeholder names must be 2 and repeatable must be false when ${PlaceholderOption.MUTUALLY_EXCLUSIVE} is set")
                        names.joinToString("|", "{", "}")
                    }
                    PlaceholderOption.ARRAY -> {
                        requires<java.lang.IllegalArgumentException>(names.size > 1 && !repeatable, "size of placeholder names must be greater than 1 and repeatable must be false when ${PlaceholderOption.ARRAY} is set")
                        names.joinToString(",", "{", "}")
                    }
                    PlaceholderOption.EMPTY -> String.EMPTY
                }
                append(curr)
                if (n != next.last) append(" ")
            }
        }
    }

    operator fun plusAssign(another: Placeholder) {
        next.add(another)
    }

    private fun ellipsisIfRepeatable(): String {
        return if (repeatable) "..." else String.EMPTY
    }
}

@DslMarker
annotation class PlaceholderDsl

class PlaceholderBuilder {
    private var placeHolder: Placeholder = Placeholder.EMPTY

    fun require(name: String) {
        placeHolder += Placeholder(name)
    }

    fun requireRepeatable(name: String) {
        placeHolder += Placeholder(name, repeatable = true)
    }

    fun optional(name: String) {
        placeHolder += Placeholder(name, option = PlaceholderOption.OPTIONAL)
    }

    fun optionalRepeatable(name: String) {
        placeHolder += Placeholder(name, option = PlaceholderOption.OPTIONAL, repeatable = true)
    }

    fun mutually(first: String, second: String) {
        placeHolder += Placeholder(first, second, option = PlaceholderOption.MUTUALLY_EXCLUSIVE)
    }

    fun array(vararg name: String) {
        placeHolder += Placeholder(*name, option = PlaceholderOption.ARRAY)
    }

    fun build(): Placeholder {
        return placeHolder
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SetCommandMappingCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.CommandMapping
import com.github.rinacm.sayaka.common.shared.console.SetCommandMappingCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class SetCommandMappingCommandHandler : CommandHandler<SetCommandMappingCommand> {
    override suspend fun process(command: SetCommandMappingCommand): List<MessageChain>? {
        CommandMapping.addCommandMapping(command.messageEvent.sender, command.target, command.original)
        return "成功添加映射: ${command.original} -> ${command.target}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/ShutdownBotCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.ShutdownBotCommand
import net.mamoe.mirai.message.MessageEvent

class ShutdownBotCommandTranslator : AbstractMessageTranslator<ShutdownBotCommand>() {
    override fun translate(rawInput: MessageEvent): ShutdownBotCommand {
        return ShutdownBotCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SetCommandMappingCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.SetCommandMappingCommand
import net.mamoe.mirai.message.MessageEvent

class SetCommandMappingCommandTranslator : AbstractMessageTranslator<SetCommandMappingCommand>() {
    override fun translate(rawInput: MessageEvent): SetCommandMappingCommand {
        val p = defaultTrailingParameters(rawInput)
        return SetCommandMappingCommand(rawInput, p[0], p[1])
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/validation/RegexValidator.kt
package com.github.rinacm.sayaka.common.message.validation

import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.util.EMPTY
import com.github.rinacm.sayaka.common.util.requires
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.content
import org.intellij.lang.annotations.Language
import kotlin.reflect.KClass

/**
 * Validate whether the count of the text command's arguments comfort the [size]
 *
 * This validator requires the message must be [PlainText], thus we need to pass
 * the raw message to [PurePlainTextValidator.validate] first
 */
open class RegexValidator(@Language("RegExp") private val regex: String, toBeValidated: KClass<*>) : PurePlainTextValidator(toBeValidated) {
    override fun validate(messageEvent: MessageEvent) {
        super.validate(messageEvent)
        requires<IrrelevantMessageException>(messageEvent.message[1].content.replaceBefore(' ', String.EMPTY, String.EMPTY).trim() matches regex.toRegex())
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SetGlobalDispatcherImplCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.SetGlobalDispatcherImplCommand
import net.mamoe.mirai.message.MessageEvent

class SetGlobalDispatcherImplCommandTranslator : AbstractMessageTranslator<SetGlobalDispatcherImplCommand>() {
    override fun translate(rawInput: MessageEvent): SetGlobalDispatcherImplCommand {
        return SetGlobalDispatcherImplCommand(rawInput, defaultOnlyOneParameter(rawInput)!!)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GomokuPlayerJoinGameCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerJoinGameCommand
import com.github.rinacm.sayaka.common.util.addLine
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.common.util.uploadAsImage
import com.github.rinacm.sayaka.gomoku.Game
import com.github.rinacm.sayaka.gomoku.GomokuGameFactory
import com.github.rinacm.sayaka.gomoku.PlayerJoinState
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.at
import net.mamoe.mirai.message.data.buildMessageChain


class GomokuPlayerJoinGameCommandHandler : CommandHandler<GomokuPlayerJoinGameCommand> {
    override suspend fun process(command: GomokuPlayerJoinGameCommand): List<MessageChain>? {
        val game = GomokuGameFactory.getOrCreateGame(command.messageEvent.subject.id.toString())
        return when (game.join(command.messageEvent.sender.id.toString())) {
            PlayerJoinState.SUCCESS -> mutableListOf<MessageChain>().apply {
                add((command.messageEvent.sender as Member).at() + "成功加入！")
                if (game.isFull()) {
                    game.startGame()
                    add(getStartMessage(command.messageEvent.subject as Group, game))
                }
            }
            PlayerJoinState.HAS_JOINED -> "你已经加入过了".asSingleMessageChainList()
            PlayerJoinState.GAME_FULL -> "当前游戏已满，请等待游戏结束".asSingleMessageChainList()
        }
    }

    private suspend fun getStartMessage(group: Group, game: Game): MessageChain {
        return buildMessageChain {
            addLine("开始游戏！")
            addLine("---------------------------------")
            addLine(Plugin.help(GomokuPlugin))
            val at = group[game.getPlayer().black.toLong()].at()
            addLine(at + "是黑方先手！请" + (at + "走子"))
            add(game.getImagePath().uploadAsImage(group))
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/factory/DefaultDispatcherImpl.kt
package com.github.rinacm.sayaka.common.message.factory

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.WrappedExecutor
import com.github.rinacm.sayaka.common.message.error.AuthorizeException
import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.message.error.PipelineException
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.shared.CommandFactory
import com.github.rinacm.sayaka.common.shared.CommandFactory.toRawCommand
import com.github.rinacm.sayaka.common.shared.RawCommand
import com.github.rinacm.sayaka.common.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.at
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.message.data.content
import java.time.Duration
import kotlin.reflect.full.createInstance

open class DefaultDispatcherImpl : Dispatcher {
    companion object Key : Dispatcher.Key<DefaultDispatcherImpl> {
        override val match: String = "Default"
        override val description: String = "Bot的默认调度器, 将会发送异常信息并将调用栈记录到文件中"
        override val instance = DefaultDispatcherImpl()
    }

    // ========================================================================
    // message table to manage the request frequency
    // ========================================================================
    private val messageTable: MutableMap<User, Pair<Int, Long>> = mutableMapOf()

    // ========================================================================
    // blacklist of user's that sending commands too fast
    // ========================================================================
    private val blacklist: MutableSet<User> = mutableSetOf()

    /**
     * Command dispatching system cannot work out with any command when [Dispatcher.Sleeping] is on
     * we simply check if command is /awake or not to decide whether we need to wake the Bot up
     */
    private suspend fun MessageEvent.interceptAwaken(cmd: RawCommand) {
        if (cmd.name == "/awake" && cmd.parameters.isEmpty()) {
            Dispatcher.Sleeping = false
            subject.sendMessage("成功唤醒Bot")
        } else if (Dispatcher.Sleeping) {
            throws<IrrelevantMessageException>()
        }
    }

    override suspend fun translateMessage(messageEvent: MessageEvent): Command {
        // extract the PlainText message in the messageEvent
        val plainTextMessage = messageEvent.message[PlainText]?.content ?: throws<IrrelevantMessageException>()
        // attempts to translate the plainTextMessage into raw command
        val raw = plainTextMessage.toRawCommand()
        messageEvent.interceptAwaken(raw)
        val cmdClass = CommandFactory.lookup(raw.name, messageEvent.sender)
        // if the command is marked as PermanentlyDisable or RespondingType does not comfort MessageEvent
        // invoke thresholding before the null checking of cmdClass to handle the cases which cannot located
        // the Command class
        if (cmdClass == null || !CommandFactory.checkAccess(cmdClass, messageEvent) || !thresholding(messageEvent))
            throws<IrrelevantMessageException>()
        CommandFactory.validate(messageEvent, cmdClass, cmdClass.annotation())
        val contextual = CommandFactory.getContextual(cmdClass)
        @Suppress("UNCHECKED_CAST")
        return (contextual.translator.createInstance() as WrappedExecutor<MessageEvent, Command>).executeWrapped(messageEvent)
    }

    override suspend fun <T : Command> dispatchMessage(messageEvent: MessageEvent, command: T) {
        @Suppress("UNCHECKED_CAST")
        val handler = CommandFactory.getContextual(command::class).handler.createInstance() as WrappedExecutor<T, Unit>
        handler.executeWrapped(command)
    }

    override suspend fun dispatchError(messageEvent: MessageEvent, exception: Exception) {
        when {
            exception is IrrelevantMessageException || exception is PipelineException && exception.e is IrrelevantMessageException -> return
            exception is AuthorizeException -> messageEvent.subject.sendMessage(exception.message)
            else -> {
                val path = BotContext.getCrashReportPath()
                @Suppress("DuplicatedCode")
                File.write(path.toAbsolutePath().toString(), buildString {
                    appendLine("在处理由${messageEvent.senderName}(${messageEvent.sender.id})发来的${messageEvent.message.content}消息时出现了异常")
                    if (exception is PipelineException) {
                        appendLine("异常捕获于: ${exception.errorStage.localizedName}(${exception.errorStage})阶段，日志如下:")
                    }
                    appendLineIndent("异常信息: ${exception.message}")
                    appendLineIndent("异常堆栈: ")
                    appendIndent(exception.stackTraceToString(), indentLevel = 2)
                })
                try {
                    messageEvent.subject.sendMessage(buildString {
                        appendLine("在处理由${messageEvent.senderName}(${messageEvent.sender.id})发来的${messageEvent.message.content}消息时出现了异常，异常信息已经写入${path.toAbsolutePath()}")
                        appendLine("异常信息: $exception")
                        appendLine("由于: ${exception.cause?.toString()}")
                    })
                } catch (e: Exception) { /* ignore */
                }
            }
        }
    }

    override suspend fun thresholding(messageEvent: MessageEvent): Boolean {
        with(messageEvent.sender) {
            // if the sender is already in the blacklist
            if (messageEvent.sender in blacklist) return false
            // if sender is in the table
            if (this in messageTable.keys) {
                val (times, startTime) = messageTable[this]!!
                // increase the successfully match counts (received a message and successfully located the
                // corresponding Command class will be considered as a successfully match)
                val newTime = times + 1
                messageTable[this] = newTime to startTime
                // if the match counts is greater than 2 (means it has been successfully matched for 3 times)
                // and the total in milliseconds is smaller 4 means there're at least 3 successfully matches
                // in 4 seconds, and that's our frequency control threshold, any requests over this frequency
                // are treated at potential malicious behavior
                if (System.currentTimeMillis() - startTime <= 1000 * 4 && newTime >= 2) {
                    // remove the criminal from messageTable and add it to the blacklist
                    messageTable.remove(this)
                    blacklist.add(this).apply {
                        // start a new coroutine and release the criminal after 2 minutes
                        launch(Dispatchers.IO) {
                            delay(Duration.ofMinutes(20).toMillis())
                            blacklist.remove(this@with)
                            messageEvent.subject.sendMessage(buildMessageChain {
                                if (messageEvent.sender is Member) {
                                    add((messageEvent.sender as Member).at())
                                }
                                add("你已经被移出黑名单")
                            })
                        }
                    }
                    messageEvent.subject.sendMessage(buildMessageChain {
                        if (messageEvent.sender is Member) {
                            add((messageEvent.sender as Member).at())
                        }
                        add("你在四秒内发送了超过三条消息，已经被拉入黑名单，2分钟后将会解禁")
                    })
                    return false
                } else {
                    // if the match counts is greater than 2 but has normal frequency means it's a legal session
                    if (newTime >= 2)
                        messageTable.remove(this)
                    return true
                }
            } else {
                // add if the sender is not in the messageTable yet
                return if (this !in messageTable.keys) {
                    messageTable[this] = 0 to System.currentTimeMillis()
                    true
                } else false
            }
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GomokuPlayerMoveCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GomokuPlayerMoveCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GomokuPlayerMoveCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent
import org.intellij.lang.annotations.Language

@Contextual(GomokuPlayerMoveCommandTranslator::class, GomokuPlayerMoveCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(PurePlainTextValidator::class)
class GomokuPlayerMoveCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GomokuPlayerMoveCommand> {
        @Language("RegExp")
        override val match: String = "^\\d{1,2}[a-oA-O]$"
        override val description: String = "在当前五子棋对局中走一步子"
        override val matchOption: MatchOption get() = MatchOption.REG_EXP

        override fun canonicalizeName(): String {
            return "${Placeholder("x坐标y坐标")} 例: 1a, 3h"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetAdministratorListCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.GetAdministratorListCommand
import net.mamoe.mirai.message.MessageEvent

class GetAdministratorListCommandTranslator : AbstractMessageTranslator<GetAdministratorListCommand>() {
    override fun translate(rawInput: MessageEvent): GetAdministratorListCommand {
        return GetAdministratorListCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/RevokeAdministratorCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.RevokeAdministratorCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class RevokeAdministratorCommandHandler : CommandHandler<RevokeAdministratorCommand> {
    override suspend fun process(command: RevokeAdministratorCommand): List<MessageChain>? {
        return if (command.qqId.toLongOrNull() != null) {
            when {
                command.qqId == BotContext.getOwner() -> "Bot的持有者(Owner)不可以被移除出管理员列表"
                BotContext.removeAdmin(command.qqId) -> "成功将${command.qqId}移出管理员列表"
                else -> "${command.qqId}不在管理员列表中"
            }.asSingleMessageChainList()
        } else "参数格式错误，必须是一个正确的QQ号码".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetGomokuCreditCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GetGomokuCreditCommand
import com.github.rinacm.sayaka.common.util.toSingleList
import com.github.rinacm.sayaka.gomoku.GomokuCredit
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain

class GetGomokuCreditCommandHandler : CommandHandler<GetGomokuCreditCommand> {
    override suspend fun process(command: GetGomokuCreditCommand): List<MessageChain>? {
        return buildMessageChain {
            with(GomokuCredit.getCredit(command.playerQQId)) {
                if (this != null) {
                    add("${command.playerQQId}的点数为${this}")
                } else add("${command.playerQQId}尚没有对局记录")
            }
        }.toSingleList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/contextual/MessageValidator.kt
package com.github.rinacm.sayaka.common.message.contextual

import com.github.rinacm.sayaka.common.message.error.ValidationException
import net.mamoe.mirai.message.MessageEvent
import kotlin.reflect.KClass

abstract class MessageValidator(val toBeValidated: KClass<*>) : WrappedExecutor<MessageEvent, Unit> {
    /**
     * Perform a validation on [MessageEvent], if the messageEvent is validated
     * this method should return normally, and if not, it should throw a
     * [ValidationException] providing the failure messageEvent
     */
    @Throws(ValidationException::class)
    abstract fun validate(messageEvent: MessageEvent)

    override suspend fun executeWrapped(parameter: MessageEvent) {
        try {
            validate(parameter)
        } catch (v: ValidationException) {
            throw v
        } catch (e: Exception) {
            throw ValidationException(e)
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/Privilege.kt
package com.github.rinacm.sayaka.common.util

enum class Privilege {
    SUPERUSER, ADMINISTRATOR
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/PipelineException.kt
package com.github.rinacm.sayaka.common.message.error

abstract class PipelineException(val e: Exception) : RuntimeException(e) {
    abstract val errorStage: ErrorStage
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/Command.kt
package com.github.rinacm.sayaka.common.shared

import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.PlainText

/**
 * Base class for all [Command], a command is an abstraction of [MessageEvent]
 * where messages can be consist of multiple parts such as [Image], [PlainText]
 * or [At], for [Command] usage there must be at least one [PlainText] message
 * to match the command name
 */
interface Command {
    val messageEvent: MessageEvent

    interface Key<T : Command> : Descriptor {
        fun help(key: Key<*>): String {
            var str = "规范: ${key.canonicalizeName()} 用途: $description"
            with(key::class.java.declaringClass.annotations) {
                this.firstOrNull { it is WithPrivilege }?.run {
                    val withAuthorize = this as WithPrivilege
                    str += when (withAuthorize.privilege) {
                        Privilege.SUPERUSER -> " (该命令仅限Bot持有者)"
                        Privilege.ADMINISTRATOR -> " (该命令仅限Bot管理员)"
                    }
                }
                this.firstOrNull { it is Responding }?.run {
                    val responding = this as Responding
                    str += when (responding.type) {
                        RespondingType.WHISPER -> " (该命令仅限私聊可用)"
                        RespondingType.GROUP -> " (该命令仅限群聊可用)"
                    }
                }
            }
            return str
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetOrSetCurrentEmittingOptionCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.pixiv.GetOrSetCurrentEmittingOptionCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.pixiv.core.RankOption
import com.github.rinacm.sayaka.pixiv.core.RankingEmitter
import net.mamoe.mirai.message.data.MessageChain

class GetOrSetCurrentEmittingOptionCommandHandler : CommandHandler<GetOrSetCurrentEmittingOptionCommand> {
    override suspend fun process(command: GetOrSetCurrentEmittingOptionCommand): List<MessageChain>? {
        val emitter = RankingEmitter.getOrCreateByContact(command.messageEvent.subject)
        if (command.optionKey == null && command.optionValue == null) {
            return "榜单项: ${emitter.current.first.alias} 日期: ${emitter.current.second}".asSingleMessageChainList()
        } else if (command.optionKey != null && command.optionValue == null || command.optionKey == null && command.optionValue != null) {
            return "参数不全".asSingleMessageChainList()
        }
        when (command.optionKey) {
            "rank" -> emitter.setRank(RankOption.valueOf(command.optionValue!!.toUpperCase()))
            "date" -> emitter.current = emitter.current.first to command.optionValue!!
            "shuffle" -> emitter.shuffleMode = RankingEmitter.ShuffleMode.of(command.optionValue!!) ?: return "参数只能是{seq|rnd}".asSingleMessageChainList()
        }
        return "成功将${command.optionKey}的值设置为${command.optionValue}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/SetCommandMappingCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.SetCommandMappingCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SetCommandMappingCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Contextual
import com.github.rinacm.sayaka.common.util.Placeholder
import com.github.rinacm.sayaka.common.util.PluginOwnership
import com.github.rinacm.sayaka.common.util.Validator
import net.mamoe.mirai.message.MessageEvent

@Contextual(SetCommandMappingCommandTranslator::class, SetCommandMappingCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "\\w+ \\w+")
class SetCommandMappingCommand(override val messageEvent: MessageEvent, val original: String, var target: String) : Command {
    companion object Key : Command.Key<SetCommandMappingCommand> {
        override val match: String = "/map"
        override val description: String = "将给定的参数映射为某个命令"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("original")} ${Placeholder("target")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SetSleepCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.message.factory.Dispatcher
import com.github.rinacm.sayaka.common.shared.console.SetSleepCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class SetSleepCommandHandler : CommandHandler<SetSleepCommand> {
    override suspend fun process(command: SetSleepCommand): List<MessageChain>? {
        Dispatcher.Sleeping = true
        return "成功进入休眠状态，请管理员使用/awake指令唤醒".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/contextual/AbstractMessageTranslator.kt
package com.github.rinacm.sayaka.common.message.contextual

import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.shared.CommandFactory.toRawCommand
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.PlainText

abstract class AbstractMessageTranslator<T : Command> : MessageTranslator<T> {
    abstract override fun translate(rawInput: MessageEvent): T

    protected fun defaultTrailingParameters(rawInput: MessageEvent): List<String> {
        return with((rawInput.message[1] as PlainText).content.toRawCommand().parameters) {
            when {
                isEmpty() -> emptyList()
                indexOf(' ') == -1 -> listOf(this)
                else -> split(' ')
            }
        }
    }

    protected fun defaultOnlyOneParameter(rawInput: MessageEvent): String? {
        return defaultTrailingParameters(rawInput).firstOrNull()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GomokuPlayerExitCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerExitCommand
import com.github.rinacm.sayaka.common.util.toSingleList
import com.github.rinacm.sayaka.gomoku.GomokuCredit
import com.github.rinacm.sayaka.gomoku.GomokuGameFactory
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.at
import net.mamoe.mirai.message.data.buildMessageChain

class GomokuPlayerExitCommandHandler : CommandHandler<GomokuPlayerExitCommand> {
    override suspend fun process(command: GomokuPlayerExitCommand): List<MessageChain>? {
        val ctx = command.messageEvent
        val game = GomokuGameFactory.tryGetGameOrNull(ctx.subject.id.toString())
        if (game != null && game.isPlayer(ctx.sender.id.toString())) {
            val message = buildMessageChain {
                val at = (ctx.sender as Member).at()
                add(at + "离开游戏, 游戏结束!")
                if (game.isFull()) {
                    add("\n由于退赛惩罚机制, ")
                    add(at + "将会被扣除20000点数")
                }
            }
            GomokuCredit.setOrIncreaseCredit(ctx.sender.id.toString(), -20000)
            game.close()
            return message.toSingleList()
        }
        return null
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/contextual/CommandHandler.kt
package com.github.rinacm.sayaka.common.message.contextual

import com.github.rinacm.sayaka.common.message.error.ProcessException
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.shared.CommandFactory.getAuthority
import com.github.rinacm.sayaka.common.util.interceptedAuthority
import net.mamoe.mirai.message.data.MessageChain

interface CommandHandler<T : Command> : WrappedExecutor<T, Unit> {
    suspend fun process(command: T): List<MessageChain>?

    override suspend fun executeWrapped(parameter: T) {
        try {
            process(parameter)?.let {
                for (m in it) {
                    parameter.messageEvent.subject.sendMessage(m.interceptedAuthority(parameter::class.getAuthority()))
                }
            }
        } catch (e: Exception) {
            throw ProcessException(e)
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/RemoveCommandMappingCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.RemoveCommandMappingCommandHandler
import com.github.rinacm.sayaka.common.message.translator.RemoveCommandMappingCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Contextual
import com.github.rinacm.sayaka.common.util.Placeholder
import com.github.rinacm.sayaka.common.util.PluginOwnership
import com.github.rinacm.sayaka.common.util.Validator
import net.mamoe.mirai.message.MessageEvent

@Contextual(RemoveCommandMappingCommandTranslator::class, RemoveCommandMappingCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "\\w+")
class RemoveCommandMappingCommand(override val messageEvent: MessageEvent, val removal: String) : Command {
    companion object Key : Command.Key<RemoveCommandMappingCommand> {
        override val match: String = "/rmap"
        override val description: String = "删除对于${Placeholder("alias")}的命令映射"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("alias")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GomokuPlayerSurrenderCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GomokuPlayerSurrenderCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GomokuPlayerSurrenderCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GomokuPlayerSurrenderCommandTranslator::class, GomokuPlayerSurrenderCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(PurePlainTextValidator::class)
class GomokuPlayerSurrenderCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GomokuPlayerSurrenderCommand> {
        override val match: String = "/gf"
        override val description: String = "向当前五子棋对局中的对手投降"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetOwnerCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.GetOwnerCommand
import net.mamoe.mirai.message.MessageEvent

class GetOwnerCommandTranslator : AbstractMessageTranslator<GetOwnerCommand>() {
    override fun translate(rawInput: MessageEvent): GetOwnerCommand {
        return GetOwnerCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetOpenSourceMessageCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.GetOpenSourceMessageCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class GetOpenSourceMessageCommandHandler : CommandHandler<GetOpenSourceMessageCommand> {
    override suspend fun process(command: GetOpenSourceMessageCommand): List<MessageChain>? {
        return buildString {
            appendLine(
                """
                MIT License

                Copyright (c) 2020 Dylech30th

                Permission is hereby granted, free of charge, to any person obtaining a copy
                of this software and associated documentation files (the "Software"), to deal
                in the Software without restriction, including without limitation the rights
                to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                copies of the Software, and to permit persons to whom the Software is
                furnished to do so, subject to the following conditions:

                The above copyright notice and this permission notice shall be included in all
                copies or substantial portions of the Software.

                THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                SOFTWARE.
            """.trimIndent()
            )
        }.asSingleMessageChainList()
    }

}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/init/PreInit.kt
package com.github.rinacm.sayaka.common.init

import com.github.rinacm.sayaka.common.message.factory.Dispatcher
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import com.github.rinacm.sayaka.common.util.reflector.ScanningTarget
import com.github.rinacm.sayaka.common.util.reflector.TypedAnnotationScanner
import com.github.rinacm.sayaka.common.util.reflector.TypedSubclassesScanner
import java.io.Closeable
import java.lang.invoke.MethodHandles
import java.lang.reflect.Method
import kotlin.Exception
import kotlin.reflect.full.companionObjectInstance

object PreInit : Closeable {
    init {
        registerVariablesGlobal()
        executeTriggerPointcuts(TriggerPoint.STARTUP)
    }

    private fun registerVariablesGlobal() {
        TypedSubclassesScanner.registerScanner(ScanningTarget.CLASS, Command::class)
        TypedSubclassesScanner.registerScanner(ScanningTarget.CLASS, Plugin::class)
        TypedSubclassesScanner.registerScanner(ScanningTarget.CLASS, Dispatcher::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.METHOD, TriggerOn::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.CLASS, PluginOwnership::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.CLASS, Contextual::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.CLASS, PermanentlyDisable::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.CLASS, Validator::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.CLASS, WithPrivilege::class)
        TypedAnnotationScanner.registerScanner(ScanningTarget.CLASS, Responding::class)
    }

    private fun executeTriggerPointcuts(triggerPoint: TriggerPoint) {
        val ms = TypedAnnotationScanner.markedMap[TriggerOn::class]
        if (!ms.isNullOrEmpty()) {
            val methods = ms.cast<Method>()
                .asSequence()
                .map { it.annotations.toList().singleIsInstance<TriggerOn>() to it }
                .sortedBy { it.first.priority }
                .filter { it.first.triggerPoint == triggerPoint }
                .map(Pair<TriggerOn, Method>::second)
            for (m in methods) {
                m.isAccessible = true
                val lookup = MethodHandles.lookup()
                val mh = lookup.unreflect(m)
                mh.invoke(m.kotlinDeclaringClass.objectInstance)
            }
        }
    }

    override fun close() {
        executeTriggerPointcuts(TriggerPoint.BEFORE_CLOSED)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/Helper.kt
@file:Suppress("unused")

package com.github.rinacm.sayaka.common.util

import com.github.rinacm.sayaka.common.message.factory.Dispatcher
import com.google.common.collect.Multimap
import com.google.gson.Gson
import io.ktor.http.*
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.uploadAsImage
import org.intellij.lang.annotations.Language
import java.io.File
import java.lang.reflect.Member
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.MessageDigest
import java.time.Duration
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KFunction
import kotlin.reflect.full.createInstance

@Language("RegExp")
const val QQ_ID_REGEX = "(?!0+\\d*)\\d+"

fun String.toPath(): Path = Paths.get(this)

fun String.toAbsolutePath(): Path = toPath().toAbsolutePath()

fun Path.mkdirs() = File(toString()).mkdirs()

fun durationNow(): Duration = Duration.ofMillis(System.currentTimeMillis())

val Member.kotlinDeclaringClass get() = declaringClass.kotlin

fun <T: R, R> T.runIf(boolean: Boolean, block: T.() -> R): R {
    return if (boolean) block() else this
}

fun <T> buildListImmutable(block: MutableList<T>.() -> Unit): List<T> {
    return mutableListOf<T>().apply { block(this) }
}

fun <T> buildListMutable(block: MutableList<T>.() -> Unit): MutableList<T> {
    return mutableListOf<T>().apply { block(this) }
}

infix fun String.followedBy(content: MessageChain): MessageChain {
    return PlainText(this) + content
}

fun StringBuilder.appendIndent(content: String, indentLevel: Int = 1, indent: Int = 4): StringBuilder {
    append(String(CharArray(indentLevel * indent) { ' ' }))
    append(content)
    return this
}

fun StringBuilder.appendLineIndent(content: String, indentLevel: Int = 1, indent: Int = 4): StringBuilder {
    append(String(CharArray(indentLevel * indent) { ' ' }))
    append(content)
    appendLine()
    return this
}

fun indentString(content: String, indentLevel: Int = 1, indent: Int = 4): String {
    return buildString {
        appendIndent(content, indentLevel, indent)
    }
}

fun MessageChainBuilder.addLine(content: Message): Boolean {
    return add(content + PlainText("\n"))
}

fun MessageChainBuilder.addLine(content: String) {
    return add("$content\n")
}

inline fun <reified A : Annotation> KAnnotatedElement.annotation(): A {
    return annotations.singleIsInstance()
}

suspend fun String.uploadAsImage(c: Contact): Image {
    return File(this).uploadAsImage(c)
}

fun MessageChain.interceptedAuthority(privilege: Privilege?): MessageChain {
    return if (privilege != null) {
        "[# $privilege PRIVILEGE GRANTED #]\n" followedBy (this + "\n[# $privilege PRIVILEGE REVOKED #]")
    } else this
}

fun String.interceptedAuthority(privilege: Privilege?): MessageChain {
    return asMessageChain().interceptedAuthority(privilege)
}

fun String.asMessageChain(): MessageChain {
    return PlainText(this).asMessageChain()
}

fun String.asSingleMessageChainList(): List<MessageChain> {
    return asMessageChain().toSingleList()
}

fun Message.asSingleMessageChainList(): List<MessageChain> {
    return asMessageChain().toSingleList()
}

fun Bot.subscribeAlwaysUnderGlobalDispatcherSuspended(block: suspend Dispatcher.(MessageEvent) -> Unit) {
    subscribeMessages {
        always {
            with(Dispatcher.Global) {
                launch {
                    block(this@always)
                }
            }
        }
    }
}

fun <T> T.toSingleList(): List<T> {
    return listOf(this)
}

inline fun <reified E : Exception> requires(value: Boolean, message: String? = null) {
    if (!value) throws<E>(message)
}

fun <T, R> notEquals(second: T, transform: (T) -> R): (T) -> Boolean {
    return { transform(it) != transform(second) }
}

inline fun <reified E : Exception> throws(message: String? = null): Nothing {
    if (message == null)
        throw E::class.createInstance()
    throw (E::class.java.getConstructor(String::class.java)).newInstance(message)
}

object JsonFactory {
    val gson: Gson = Gson()
}

inline fun <reified T> T.toJson(): String {
    return JsonFactory.gson.toJson(this)
}

inline fun <reified T> String.fromJson(): T {
    return JsonFactory.gson.fromJson(this, T::class.java)
}

infix fun String.match(regex: Regex): MatchResult? = regex.find(this)

val String.Companion.EMPTY get() = ""

inline fun <reified I> Iterable<*>.singleIsInstance(): I = single { it is I } as I

operator fun <K, V> Multimap<K, V>.set(key: K, value: V) {
    put(key, value)
}

fun <E> Collection<E>.truncatePair(): Pair<E, E> {
    return take(2).run { this[0] to this[1] }
}

@Suppress("UNCHECKED_CAST")
fun <R> Iterable<*>.cast(): List<R> {
    return map { it as R }
}

@Suppress("UNCHECKED_CAST")
fun <R> Sequence<*>.seqCast(): Sequence<R> {
    return map { it as R }
}

inline fun <reified T : Annotation, R> mapAnnotation(f: KFunction<*>, mapper: (T) -> R): R {
    return mapper(f.annotation())
}

fun buildPlaceHolder(block: PlaceholderBuilder.() -> Unit): Placeholder {
    val builder = PlaceholderBuilder()
    block(builder)
    return builder.build()
}

inline fun <reified T> threadLocalOf(noinline supplier: () -> T): ThreadLocal<T> {
    return ThreadLocal.withInitial(supplier)
}

inline fun <T, R> ThreadLocal<T>.runAlso(block: T.() -> R): R {
    return get().block()
}

inline fun url(builder: URLBuilder.() -> Unit): Url {
    val b = URLBuilder()
    b.builder()
    return b.build()
}

inline fun urlBuilder(b: URLBuilder = URLBuilder(), builder: URLBuilder.() -> Unit): URLBuilder {
    b.builder()
    return b
}

inline fun parameter(default: ParametersBuilder = ParametersBuilder(), b: ParametersBuilder.() -> Unit): ParametersBuilder {
    default.b()
    return default
}

inline fun parameter(b: ParametersBuilder.() -> Unit): Parameters {
    return parameter(ParametersBuilder(), b).build()
}

fun String.md5(): String {
    val dig = MessageDigest.getInstance("MD5")
    return dig.digest(toByteArray(StandardCharsets.UTF_8)).joinToString("") { "%02x".format(it) }
}

object File {
    fun exists(path: String): Boolean {
        return Files.exists(path.toPath())
    }

    fun create(path: String) {
        val p = path.toPath().toAbsolutePath()
        val directory = p.parent
        if (!Files.exists(directory)) {
            Files.createDirectories(directory)
        }
        Files.createFile(p)
    }

    fun read(path: String): CompletableFuture<String> {
        return CompletableFuture.supplyAsync {
            File(path).readText(StandardCharsets.UTF_8)
        }!!
    }

    fun write(path: String, text: String) {
        if (!exists(path)) create(path)
        CompletableFuture.runAsync {
            File(path).writeText(text, StandardCharsets.UTF_8)
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GomokuPlayerExitCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerExitCommand
import net.mamoe.mirai.message.MessageEvent

class GomokuPlayerExitCommandTranslator : AbstractMessageTranslator<GomokuPlayerExitCommand>() {
    override fun translate(rawInput: MessageEvent): GomokuPlayerExitCommand {
        return GomokuPlayerExitCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SetAdministratorCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.SetAdministratorCommand
import net.mamoe.mirai.message.MessageEvent

class SetAdministratorCommandTranslator : AbstractMessageTranslator<SetAdministratorCommand>() {
    override fun translate(rawInput: MessageEvent): SetAdministratorCommand {
        return SetAdministratorCommand(defaultOnlyOneParameter(rawInput)!!, rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/factory/DebugDispatcherImpl.kt
@file:Suppress("unused")

package com.github.rinacm.sayaka.common.message.factory

import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.message.error.PipelineException
import com.github.rinacm.sayaka.common.util.appendIndent
import com.github.rinacm.sayaka.common.util.appendLineIndent
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.content

class DebugDispatcherImpl : DefaultDispatcherImpl() {
    companion object Key : Dispatcher.Key<DebugDispatcherImpl> {
        override val match: String = "Debug"
        override val description: String = "调试用调度器: 所有异常堆栈信息将被显式的发送 (对于所有包括来自QQ群或私聊的响应均有效)"
        override val instance = DebugDispatcherImpl()
    }

    override suspend fun dispatchError(messageEvent: MessageEvent, exception: Exception) {
        when {
            exception is IrrelevantMessageException || exception is PipelineException && exception.e is IrrelevantMessageException -> return
            else -> {
                @Suppress("DuplicatedCode")
                messageEvent.subject.sendMessage(buildString {
                    appendLine("在处理由${messageEvent.senderName}(${messageEvent.sender.id})发来的${messageEvent.message.content}消息时出现了异常")
                    if (exception is PipelineException) {
                        appendLine("异常捕获于: ${exception.errorStage.localizedName}(${exception.errorStage})阶段，日志如下:")
                    }
                    appendLineIndent("异常信息: ${exception.message}")
                    appendLineIndent("异常堆栈: ")
                    appendIndent(exception.stackTraceToString(), indentLevel = 2)
                })
            }
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/ProcessException.kt
package com.github.rinacm.sayaka.common.message.error

class ProcessException(cause: Exception) : PipelineException(cause) {
    override val errorStage: ErrorStage = ErrorStage.PROCESSING
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/Configuration.kt
package com.github.rinacm.sayaka.gomoku

object GameConfiguration {
    var gameRootDirectory = "gomoku-data"
    var gameRecordDirectory: (String) -> String = { "$gameRootDirectory\\data\\image\\$it" }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SetPluginExcludeCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.plugin.ExcludeType
import com.github.rinacm.sayaka.common.shared.console.SetPluginExcludeCommand
import com.github.rinacm.sayaka.common.util.throws
import net.mamoe.mirai.message.MessageEvent

class SetPluginExcludeCommandTranslator : AbstractMessageTranslator<SetPluginExcludeCommand>() {
    override fun translate(rawInput: MessageEvent): SetPluginExcludeCommand {
        val parameters = defaultTrailingParameters(rawInput)
        return SetPluginExcludeCommand(rawInput, parameters[0], parameters[1], ExcludeType.toExcludeType(parameters[2]) ?: throws<IrrelevantMessageException>())
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/TranslationException.kt
package com.github.rinacm.sayaka.common.message.error

class TranslationException(cause: Exception) : PipelineException(cause) {
    override val errorStage: ErrorStage = ErrorStage.TRANSLATION
}
<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GomokuForceEndGameCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.MandatoryEndGameCommandHandler
import com.github.rinacm.sayaka.common.message.translator.MandatoryEndGameCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@WithPrivilege(Privilege.ADMINISTRATOR)
@Contextual(MandatoryEndGameCommandTranslator::class, MandatoryEndGameCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(RegexValidator::class, regex = QQ_ID_REGEX)
data class GomokuForceEndGameCommand(val groupQQId: String, override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GomokuForceEndGameCommand> {
        override val match: String = "/ef"
        override val description: String = "强制结束群${Placeholder("id")}的五子棋对局"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("id")}"
        }
    }
}<file_sep>/gradle.properties
kotlin.code.style=official
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
miraiVersion=1.0.4
retrofitVersion=2.9.0<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/plugin/PixivPlugin.kt
package com.github.rinacm.sayaka.common.plugin

class PixivPlugin : Plugin<PixivPlugin>() {
    companion object Key : Plugin.Key<PixivPlugin> {
        override var enabled: Boolean = true
        override var excludes: MutableMap<String, MutableSet<ExcludeType>> = mutableMapOf()
        override val match: String = "Pixiv"
        override val description: String = "P站相关的内置插件"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/contextual/MessageTranslator.kt
package com.github.rinacm.sayaka.common.message.contextual

import com.github.rinacm.sayaka.common.message.error.TranslationException
import com.github.rinacm.sayaka.common.shared.Command
import net.mamoe.mirai.message.MessageEvent

interface MessageTranslator<T : Command> : WrappedExecutor<MessageEvent, T> {
    fun translate(rawInput: MessageEvent): T

    override suspend fun executeWrapped(parameter: MessageEvent): T {
        try {
            return translate(parameter)
        } catch (e: Exception) {
            throw TranslationException(e)
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/reflector/TypedAnnotationScanner.kt
package com.github.rinacm.sayaka.common.util.reflector

import com.github.rinacm.sayaka.common.util.set
import java.lang.reflect.GenericDeclaration
import kotlin.reflect.KClass

object TypedAnnotationScanner : TypedScanner<KClass<out Annotation>, GenericDeclaration>() {
    override val markedMap: Map<KClass<out Annotation>, List<GenericDeclaration>> by lazy {
        val map = mutableMapOf<KClass<out Annotation>, List<GenericDeclaration>>()
        for ((target, classes) in scanMap.asMap()) {
            for (cls in classes) {
                map[cls] = when (target!!) {
                    ScanningTarget.METHOD -> reflector.getMethodsAnnotatedWith(cls.java)
                    ScanningTarget.CLASS -> reflector.getTypesAnnotatedWith(cls.java)
                }.toList()
            }
        }
        return@lazy map
    }

    override fun registerScanner(scanningTarget: ScanningTarget, clazz: KClass<out Annotation>) {
        scanMap[scanningTarget] = clazz
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/SetGomokuPlayerCreditCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.SetGomokuPlayerCreditCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SetGomokuPlayerCreditCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@WithPrivilege(Privilege.ADMINISTRATOR)
@Contextual(SetGomokuPlayerCreditCommandTranslator::class, SetGomokuPlayerCreditCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Validator(RegexValidator::class, regex = "$QQ_ID_REGEX $QQ_ID_REGEX")
data class SetGomokuPlayerCreditCommand(val qqId: String, val amount: String, override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<SetGomokuPlayerCreditCommand> {
        override val match: String = "/gs"
        override val description: String = "设置${Placeholder("id")}的Gomoku点数"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("id")} ${Placeholder("amount")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/plugin/ExcludeType.kt
package com.github.rinacm.sayaka.common.plugin

enum class ExcludeType {
    GROUP, WHISPER;

    companion object {
        fun toExcludeType(str: String): ExcludeType? {
            return when (str.toLowerCase()) {
                "group" -> GROUP
                "whisper" -> WHISPER
                else -> null
            }
        }
    }
}<file_sep>/build.gradle
plugins {
    id 'java'
    id 'application'
    id 'org.jetbrains.kotlin.jvm' version '1.4.10'
}

group 'com.github.rinacm'
version '1.0.0'

repositories {
    mavenCentral()
    jcenter()
}

sourceCompatibility = targetCompatibility = "11"

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "net.mamoe:mirai-core:1.3.1"
    implementation "net.mamoe:mirai-core-qqandroid:1.3.1"
    implementation "io.ktor:ktor-client-gson:1.3.2"
    implementation "io.ktor:ktor-client-okhttp:1.3.2"
    implementation "com.google.code.gson:gson:2.8.6"
    implementation "org.reflections:reflections:0.9.12"
    implementation "com.google.guava:guava:29.0-jre"
}

compileKotlin {
    kotlinOptions.jvmTarget = "11"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "11"
}

jar {
    manifest {
        attributes 'Main-Class': 'com.github.rinacm.sayaka.SayakaKt'
    }
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.runtimeClasspath.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetGomokuCreditCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GetGomokuCreditCommand
import net.mamoe.mirai.message.MessageEvent

class GetGomokuCreditCommandTranslator : AbstractMessageTranslator<GetGomokuCreditCommand>() {
    override fun translate(rawInput: MessageEvent): GetGomokuCreditCommand {
        return GetGomokuCreditCommand(defaultOnlyOneParameter(rawInput)!!, rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/GetAdministratorListCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.GetAdministratorListCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetAdministratorListCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Contextual
import com.github.rinacm.sayaka.common.util.PluginOwnership
import com.github.rinacm.sayaka.common.util.Validator
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetAdministratorListCommandTranslator::class, GetAdministratorListCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
class GetAdministratorListCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GetAdministratorListCommand> {
        override val match: String = "/ops"
        override val description: String = "获取在该群的管理员列表 (Bot管理员而非QQ群管理)"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/plugin/ConsolePlugin.kt
package com.github.rinacm.sayaka.common.plugin

class ConsolePlugin : Plugin<ConsolePlugin>() {
    companion object Key : Plugin.Key<ConsolePlugin> {
        override val match: String = "Console"
        override val description: String = "Sayaka Bot™的控制台插件"
        override var enabled: Boolean = true
        override var excludes: MutableMap<String, MutableSet<ExcludeType>> = mutableMapOf()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/ExemptPluginExcludeCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.ExemptPluginExcludeCommandHandler
import com.github.rinacm.sayaka.common.message.translator.ExemptPluginExcludeCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.plugin.ExcludeType
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(ExemptPluginExcludeCommandTranslator::class, ExemptPluginExcludeCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "\\w+ $QQ_ID_REGEX (group|whisper)")
@WithPrivilege(Privilege.ADMINISTRATOR)
class ExemptPluginExcludeCommand(override val messageEvent: MessageEvent, val name: String, val id: String, val excludeType: ExcludeType) : Command {
    companion object Key : Command.Key<ExemptPluginExcludeCommand> {
        override val match: String = "/exempt"
        override val description: String = "将${Placeholder("id")}从${Placeholder("plugin_name")}的黑名单中移除"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("plugin_name")} ${Placeholder("id")} ${Placeholder("group", "whisper", option = PlaceholderOption.MUTUALLY_EXCLUSIVE)}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/init/BotContext.kt
package com.github.rinacm.sayaka.common.init

import com.github.rinacm.sayaka.common.config.SayakaConfiguration
import com.github.rinacm.sayaka.common.util.*
import com.github.rinacm.sayaka.pixiv.web.Session
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.runBlocking
import java.nio.file.Paths
import java.time.LocalDateTime

@Suppress("unused")
object BotContext {
    private var administrators = mutableSetOf<String>()
    private lateinit var configuration: SayakaConfiguration
    private lateinit var botOwner: String

    fun getCrashReportPath(): String {
        return Paths.get(configuration.crashReportPath, "ExceptionDump-${LocalDateTime.now().toString().replace(":", "-").replace(".", "-")}.txt").toAbsolutePath().toString()
    }

    fun addAdmin(qqId: String): Boolean {
        return administrators.add(qqId)
    }

    fun removeAdmin(qqId: String): Boolean {
        return administrators.remove(qqId)
    }

    fun getAdmins(): Set<String> {
        return administrators
    }

    fun getOwner(): String {
        return String(botOwner.toCharArray())
    }

    @TriggerOn(TriggerPoint.STARTUP, TriggerPriority.HIGHEST)
    private fun loadConfigurationAndLogin() {
        if (!File.exists(SayakaConfiguration.SAYAKA_CONFIGURATION_PATH))
            throws<IllegalStateException>("config.json must be created")
        configuration = SayakaConfiguration.read()
        administrators.addAll(configuration.admins)
        botOwner = configuration.owner
        administrators.add(botOwner)
        BotFactory.login(configuration.account.toLong(), configuration.password)
    }

    @TriggerOn(TriggerPoint.STARTUP, TriggerPriority.HIGH)
    private fun login() {
        runBlocking {
            Session.login(configuration.pixivAccount, configuration.pixivPassword)
        }
    }

    @TriggerOn(TriggerPoint.BEFORE_CLOSED)
    private fun saveConfiguration() {
        SayakaConfiguration.save(configuration)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/IrrelevantMessageException.kt
package com.github.rinacm.sayaka.common.message.error

/**
 * This is the only exception which will be thrown when translation or validation failed **NORMALLY**
 */
class IrrelevantMessageException : Exception()<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/config/SayakaConfiguration.kt
package com.github.rinacm.sayaka.common.config

import com.github.rinacm.sayaka.common.util.File
import com.github.rinacm.sayaka.common.util.fromJson
import com.github.rinacm.sayaka.common.util.toJson
import java.nio.file.Files
import java.nio.file.OpenOption
import java.nio.file.Paths
import java.nio.file.StandardOpenOption

data class SayakaConfiguration(
    val account: String,
    val password: String,
    val pixivAccount: String,
    val pixivPassword: String,
    val owner: String,
    val crashReportPath: String,
    val admins: List<String>
) {
    companion object {
        const val SAYAKA_CONFIGURATION_PATH = "config.json"

        fun read(): SayakaConfiguration {
            return File.read(SAYAKA_CONFIGURATION_PATH).get().fromJson()
        }

        fun save(conf: SayakaConfiguration) {
            Files.writeString(Paths.get(SAYAKA_CONFIGURATION_PATH), conf.toJson())
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetGomokuCreditRankingCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GetGomokuCreditRankingCommand
import net.mamoe.mirai.message.MessageEvent

class GetGomokuCreditRankingCommandTranslator : AbstractMessageTranslator<GetGomokuCreditRankingCommand>() {
    override fun translate(rawInput: MessageEvent): GetGomokuCreditRankingCommand {
        return GetGomokuCreditRankingCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/CommandMapping.kt
package com.github.rinacm.sayaka.common.shared

import com.google.common.collect.HashBiMap
import net.mamoe.mirai.contact.User

object CommandMapping {
    private val mapping = mutableMapOf<User, MutableMap<String, String>>()

    fun addCommandMapping(user: User, original: String, target: String) {
        if (mapping[user] == null) {
            mapping[user] = HashBiMap.create()
        }
        mapping[user]!![target] = original
    }

    fun removeCommandMapping(user: User, target: String): Boolean {
        if (mapping[user] == null || target !in mapping[user]!!.keys) return false
        mapping[user]!!.remove(target)
        return true
    }

    fun findMappingCommandString(user: User, str: String): String? {
        if (mapping[user] == null || str !in mapping[user]!!.keys) return null
        return mapping[user]!![str]
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/plugin/GomokuPlugin.kt
package com.github.rinacm.sayaka.common.plugin

class GomokuPlugin : Plugin<GomokuPlugin>() {
    companion object Key : Plugin.Key<GomokuPlugin> {
        override val match: String = "GomokuQQ"
        override val description: String = "一个能在群里下五子棋的QQ机器人"
        override var enabled: Boolean = true
        override var excludes: MutableMap<String, MutableSet<ExcludeType>> = mutableMapOf()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/GetOpenSourceMessageCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.GetOpenSourceMessageCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetOpenSourceMessageCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Contextual
import com.github.rinacm.sayaka.common.util.PluginOwnership
import com.github.rinacm.sayaka.common.util.Validator
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetOpenSourceMessageCommandTranslator::class, GetOpenSourceMessageCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
class GetOpenSourceMessageCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GetOpenSourceMessageCommand> {
        override val match: String = "/opensource"
        override val description: String = "获取Sayaka Bot™的开源许可证信息"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/AboutSayakaCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.AboutSayakaCommand
import com.github.rinacm.sayaka.common.util.appendIndent
import com.github.rinacm.sayaka.common.util.appendLineIndent
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class AboutSayakaCommandHandler : CommandHandler<AboutSayakaCommand> {
    override suspend fun process(command: AboutSayakaCommand): List<MessageChain>? {
        return buildString {
            appendLine("Sayaka Bot™ - A powerful qq assistant written in Kotlin and based on Mirai Framework")
            appendLine("Copyright (C) 2020 Dylech30th, Licensed under MIT(https://en.wikipedia.org/wiki/MIT_License)")
            appendLine("Project website: https://www.github.com/Rinacm/sayaka")
            appendLine("Open Source Libraries Usage:")
            appendLineIndent("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
            appendLineIndent("net.mamoe:mirai-core:1.3.1")
            appendLineIndent("net.mamoe:mirai-core-qqandroid:1.3.1")
            appendLineIndent("io.ktor:ktor-client-gson:1.3.2")
            appendLineIndent("com.google.code.gson:gson:2.8.6")
            appendLineIndent("org.reflections:reflections:0.9.12")
            appendLineIndent("com.google.guava:guava:29.0-jre")
            appendLine("Acknowledgements: ")
            appendLineIndent("Jetbrains s.r.o. for providing [Jetbrains Open Source License](https://www.jetbrains.com/community/opensource/)")
            appendIndent("mamoe/mirai for providing qq bot framework")
        }.asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GomokuPlayerJoinGameCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerJoinGameCommand
import net.mamoe.mirai.message.MessageEvent

class GomokuPlayerJoinGameCommandTranslator : AbstractMessageTranslator<GomokuPlayerJoinGameCommand>() {
    override fun translate(rawInput: MessageEvent): GomokuPlayerJoinGameCommand {
        return GomokuPlayerJoinGameCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GetGomokuCreditRankingCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GetGomokuCreditRankingCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetGomokuCreditRankingCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetGomokuCreditRankingCommandTranslator::class, GetGomokuCreditRankingCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(PurePlainTextValidator::class)
class GetGomokuCreditRankingCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GetGomokuCreditRankingCommand> {
        override val match: String = "/gr"
        override val description: String = "获取当前群的Gomoku点数排名"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetOwnerCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.GetOwnerCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class GetOwnerCommandHandler : CommandHandler<GetOwnerCommand> {
    override suspend fun process(command: GetOwnerCommand): List<MessageChain>? {
        return BotContext.getOwner().asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/SetPluginExcludeCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.SetPluginExcludeCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SetPluginExcludeCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.plugin.ExcludeType
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(SetPluginExcludeCommandTranslator::class, SetPluginExcludeCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "\\w+ $QQ_ID_REGEX (group|whisper)")
@WithPrivilege(Privilege.ADMINISTRATOR)
class SetPluginExcludeCommand(override val messageEvent: MessageEvent, val name: String, val id: String, val excludeType: ExcludeType) : Command {
    companion object Key : Command.Key<SetPluginExcludeCommand> {
        override val match: String = "/exclude"
        override val description: String = "将id为${Placeholder("id")}的群/好友添加进${Placeholder("plugin_name")}的黑名单"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("plugin_name")} ${Placeholder("id")} ${Placeholder("group", "whisper", option = PlaceholderOption.MUTUALLY_EXCLUSIVE)}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/Point.kt
package com.github.rinacm.sayaka.gomoku

data class Point(val x: Int, val y: Int)<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/ShuffleRankOptionCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.pixiv.ShuffleRankOptionCommand
import net.mamoe.mirai.message.MessageEvent

class ShuffleRankOptionCommandTranslator : AbstractMessageTranslator<ShuffleRankOptionCommand>() {
    override fun translate(rawInput: MessageEvent): ShuffleRankOptionCommand {
        return ShuffleRankOptionCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/RaiseExceptionCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.RaiseExceptionCommand
import net.mamoe.mirai.message.MessageEvent

class RaiseExceptionCommandTranslator : AbstractMessageTranslator<RaiseExceptionCommand>() {
    override fun translate(rawInput: MessageEvent): RaiseExceptionCommand {
        return RaiseExceptionCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/MandatoryEndGameCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuForceEndGameCommand
import net.mamoe.mirai.message.MessageEvent

class MandatoryEndGameCommandTranslator : AbstractMessageTranslator<GomokuForceEndGameCommand>() {
    override fun translate(rawInput: MessageEvent): GomokuForceEndGameCommand {
        return GomokuForceEndGameCommand(defaultOnlyOneParameter(rawInput)!!, rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SetPluginExcludeCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.console.SetPluginExcludeCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class SetPluginExcludeCommandHandler : CommandHandler<SetPluginExcludeCommand> {
    override suspend fun process(command: SetPluginExcludeCommand): List<MessageChain>? {
        val key = Plugin.getPluginKeyByName(command.name)
        if (key != null) {
            val set = key.excludes[command.id]
            if (command.id == BotContext.getOwner()) {
                return "Bot的实际持有者不能被加入到黑名单中".asSingleMessageChainList()
            }
            if (set == null) {
                key.excludes[command.id] = mutableSetOf(command.excludeType)
            } else set.add(command.excludeType)
            return "成功将${command.id}(${command.excludeType})添加到${key.canonicalizeName()}的黑名单中".asSingleMessageChainList()
        }
        return "未找到插件${command.name}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/ShutdownBotCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.ShutdownBotCommandHandler
import com.github.rinacm.sayaka.common.message.translator.ShutdownBotCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(ShutdownBotCommandTranslator::class, ShutdownBotCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
@WithPrivilege(Privilege.SUPERUSER)
class ShutdownBotCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<ShutdownBotCommand> {
        override val match: String = "/shutdown"
        override val description: String = "关闭当前Bot实例，再次使用需要重启进程"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/init/BotFactory.kt
package com.github.rinacm.sayaka.common.init

import com.github.rinacm.sayaka.common.util.requires
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.closeAndJoin
import net.mamoe.mirai.join
import net.mamoe.mirai.utils.SilentLogger

object BotFactory {
    private lateinit var bot: Bot

    fun getInstance(): Bot {
        synchronized(this) {
            requires<RuntimeException>(BotFactory::bot.isInitialized, "the bot has not been initialized yet")
            return bot
        }
    }

    suspend fun runAndJoin(block: Bot.() -> Unit) {
        block(getInstance())
        getInstance().join()
    }

    fun login(qq: Long, password: String) {
        synchronized(this) {
            requires<RuntimeException>(!BotFactory::bot.isInitialized, "the bot has been initialized")
            runBlocking {
                bot = Bot(qq, password) {
                    randomDeviceInfo()
                }.alsoLogin()
            }
        }
    }

    fun requestShuttingDown(milliseconds: Int) {
        synchronized(this) {
            requires<RuntimeException>(BotFactory::bot.isInitialized, "the bot has not been initialized yet")
            GlobalScope.launch {
                delay(milliseconds.toLong())
                bot.closeAndJoin()
            }
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GomokuPlayerRequestEndingCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GomokuPlayerRequestEndingCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GomokuPlayerRequestEndingCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GomokuPlayerRequestEndingCommandTranslator::class, GomokuPlayerRequestEndingCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Responding(RespondingType.GROUP)
@Validator(PurePlainTextValidator::class)
class GomokuPlayerRequestEndingCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GomokuPlayerRequestEndingCommand> {
        override val match: String = "/re"
        override val description: String = "发起或加入一次结束当前五子棋对局的投票"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/SetSleepCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.SetSleepCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SetSleepCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(SetSleepCommandTranslator::class, SetSleepCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
@WithPrivilege(Privilege.ADMINISTRATOR)
class SetSleepCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<SetSleepCommand> {
        override val match: String = "/sleep"
        override val description: String = "命令bot进入休眠状态"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/SwitchPluginEnabledCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.SwitchPluginEnabledCommandHandler
import com.github.rinacm.sayaka.common.message.translator.SwitchPluginEnabledCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(SwitchPluginEnabledCommandTranslator::class, SwitchPluginEnabledCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = "\\w+ (disable|enable)")
@WithPrivilege(Privilege.ADMINISTRATOR)
class SwitchPluginEnabledCommand(override val messageEvent: MessageEvent, val name: String, val enable: Boolean) : Command {
    companion object Key : Command.Key<SwitchPluginEnabledCommand> {
        @Suppress("SpellCheckingInspection")
        override val match: String = "/setswitch"
        override val description: String = "关闭或开启一个插件"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("plugin_name")} ${Placeholder("enable", "disable", option = PlaceholderOption.MUTUALLY_EXCLUSIVE)}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/ShowHelpMessageCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.plugin.Plugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.shared.CommandFactory
import com.github.rinacm.sayaka.common.shared.console.ShowHelpMessageCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain
import kotlin.reflect.full.companionObjectInstance

class ShowHelpMessageCommandHandler : CommandHandler<ShowHelpMessageCommand> {
    override suspend fun process(command: ShowHelpMessageCommand): List<MessageChain>? {
        val hint = "注: <>为必选参数，[]为可选参数, {1|2}意为从选择1或者2, ...意为可以重复填多个参数(用空格分隔)\n"
        if (command.names.isNotEmpty()) {
            return buildString {
                append(hint)
                for (n in command.names) {
                    if (n.startsWith('/')) {
                        val cmd = CommandFactory.lookup(n)
                        if (cmd == null) {
                            appendLine("未能找到名称为${n}的命令")
                        } else {
                            val key = cmd.companionObjectInstance as Command.Key<*>
                            appendLine(key.help(key))
                        }
                    } else {
                        val plg = Plugin.getPluginKeyByName(n)
                        append(if (plg != null) Plugin.help(plg) else "未能找到名称为${n}的插件")
                    }
                }
            }.asSingleMessageChainList()
        }
        return (hint + Plugin.help()).asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/validation/PurePlainTextValidator.kt
package com.github.rinacm.sayaka.common.message.validation

import com.github.rinacm.sayaka.common.message.contextual.MessageValidator
import com.github.rinacm.sayaka.common.message.error.IrrelevantMessageException
import com.github.rinacm.sayaka.common.util.requires
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.PlainText
import kotlin.reflect.KClass

/**
 * A validator requires all [MessageEvent.message] except [MessageEvent.source]
 * must be [PlainText]
 */
open class PurePlainTextValidator(toBeValidateClass: KClass<*>) : MessageValidator(toBeValidateClass) {
    override fun validate(messageEvent: MessageEvent) {
        with(messageEvent) {
            requires<IrrelevantMessageException>(message.size == 2 && message[1] is PlainText)
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/GomokuCredits.kt
@file:Suppress("unused")

package com.github.rinacm.sayaka.gomoku

import com.github.rinacm.sayaka.common.util.*

data class PlayerCredit(val player: String, var credit: Long)

object GomokuCredit {
    private var gomokuPlayerCredits = mutableListOf<PlayerCredit>()

    private const val fileName = "player_credit.json"

    fun getCredit(player: String): Long? {
        return gomokuPlayerCredits.firstOrNull { it.player == player }?.credit
    }

    fun setOrIncreaseCredit(id: String, creditIncremental: Long) {
        if (gomokuPlayerCredits.any { it.player == id }) {
            with(gomokuPlayerCredits.single { it.player == id }) {
                credit += creditIncremental
            }
        } else gomokuPlayerCredits.add(PlayerCredit(id, creditIncremental))
    }

    fun setOrRewriteCredit(id: String, creditIncremental: Long) {
        if (gomokuPlayerCredits.any { it.player == id }) {
            with(gomokuPlayerCredits.single { it.player == id }) {
                credit = creditIncremental
            }
        } else gomokuPlayerCredits.add(PlayerCredit(id, creditIncremental))
    }


    @TriggerOn(TriggerPoint.BEFORE_CLOSED)
    fun save() {
        if (!File.exists(fileName)) {
            File.create(fileName)
            File.write(fileName, gomokuPlayerCredits.toJson())
        } else {
            val future = File.read(fileName).thenApply<List<PlayerCredit>> { it.fromJson() }
            future.thenAccept {
                val added = gomokuPlayerCredits.apply {
                    addAll(it)
                    distinctBy(PlayerCredit::player)
                }
                File.write(fileName, added.toJson())
            }
        }
    }

    @TriggerOn(TriggerPoint.STARTUP)
    fun load() {
        if (File.exists(fileName)) {
            gomokuPlayerCredits = File.read(fileName).get().fromJson()
        }
    }

    fun get(): List<PlayerCredit> {
        return gomokuPlayerCredits
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SetGlobalDispatcherImplCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.message.factory.Dispatcher
import com.github.rinacm.sayaka.common.shared.console.SetGlobalDispatcherImplCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class SetGlobalDispatcherImplCommandHandler : CommandHandler<SetGlobalDispatcherImplCommand> {
    override suspend fun process(command: SetGlobalDispatcherImplCommand): List<MessageChain>? {
        with(Dispatcher.findDispatcherKeyByName(command.name)) {
            if (this != null) {
                Dispatcher.Global = this.instance
            } else {
                return "未知的调度器${command.name}".asSingleMessageChainList()
            }
        }
        return "调度器成功设置为${command.name}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/AboutSayakaCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.AboutSayakaCommandHandler
import com.github.rinacm.sayaka.common.message.translator.AboutSayakaCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Contextual
import com.github.rinacm.sayaka.common.util.PluginOwnership
import com.github.rinacm.sayaka.common.util.Validator
import net.mamoe.mirai.message.MessageEvent

@Contextual(AboutSayakaCommandTranslator::class, AboutSayakaCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(PurePlainTextValidator::class)
class AboutSayakaCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<AboutSayakaCommand> {
        override val match: String = "/about"
        override val description: String = "显示关于Sayaka Bot™的信息"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/RevokeAdministratorCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.RevokeAdministratorCommandHandler
import com.github.rinacm.sayaka.common.message.translator.RevokeAdministratorCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(RevokeAdministratorCommandTranslator::class, RevokeAdministratorCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@Validator(RegexValidator::class, regex = QQ_ID_REGEX)
@WithPrivilege(Privilege.SUPERUSER)
data class RevokeAdministratorCommand(val qqId: String, override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<RevokeAdministratorCommand> {
        override val match: String = "/revoke"
        override val description: String = "根据${Placeholder("id")}撤销Bot管理员(非QQ群管理)"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("id")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/RemoveCommandMappingCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.RemoveCommandMappingCommand
import net.mamoe.mirai.message.MessageEvent

class RemoveCommandMappingCommandTranslator : AbstractMessageTranslator<RemoveCommandMappingCommand>() {
    override fun translate(rawInput: MessageEvent): RemoveCommandMappingCommand {
        return RemoveCommandMappingCommand(rawInput, defaultOnlyOneParameter(rawInput)!!)
    }
}<file_sep>/settings.gradle
rootProject.name = 'sayaka'

<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/StopWatch.kt
@file:Suppress("unused")

package com.github.rinacm.sayaka.common.util

import java.time.Duration

class StopWatch {
    private var startTime = Duration.ZERO
    private var started = false

    fun start() {
        started = true
        startTime = durationNow()
    }

    fun reset() {
        startTime = durationNow()
    }

    fun stop() {
        startTime = Duration.ZERO
        started = false
    }

    fun elapsed(): Duration {
        return Duration.ofMillis(System.currentTimeMillis()).minus(startTime)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GomokuPlayerRequestEndingCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerRequestEndingCommand
import net.mamoe.mirai.message.MessageEvent

class GomokuPlayerRequestEndingCommandTranslator : AbstractMessageTranslator<GomokuPlayerRequestEndingCommand>() {
    override fun translate(rawInput: MessageEvent): GomokuPlayerRequestEndingCommand {
        return GomokuPlayerRequestEndingCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetAdministratorListCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.GetAdministratorListCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class GetAdministratorListCommandHandler : CommandHandler<GetAdministratorListCommand> {
    override suspend fun process(command: GetAdministratorListCommand): List<MessageChain>? {
        return BotContext.getAdmins().joinToString(" ", "[", "]").asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GetRandomRankingCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.pixiv.GetRandomRankingCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.pixiv.core.RankingEmitter
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.message.uploadAsImage

class GetRandomRankingCommandHandler : CommandHandler<GetRandomRankingCommand> {
    override suspend fun process(command: GetRandomRankingCommand): List<MessageChain>? {
        with(RankingEmitter.getOrCreateByContact(command.messageEvent.subject)) {
            if (coolDown) {
                return "命令正在冷却过程中，冷却时间为3秒".asSingleMessageChainList()
            }
            coolDown = true
            if (cache.isEmpty()) {
                command.messageEvent.subject.sendMessage("正在获取新的榜单...")
                construct()
                command.messageEvent.subject.sendMessage("榜单获取完成，正在随机...")
            }
            val illustration = emit()
            command.messageEvent.subject.sendMessage("正在下载图片...")
            val file = illustration.download()
            return buildMessageChain {
                add(file.uploadAsImage(command.messageEvent.subject))
                add(buildString {
                    appendLine("作品标题: ${illustration.title}")
                    appendLine("作品Pixiv ID: ${illustration.id}")
                    appendLine("作者: ${illustration.userName}")
                    appendLine("作者ID: ${illustration.userId}")
                    appendLine("收藏数: ${illustration.bookmark}")
                    appendLine("上传日期: ${illustration.publishDate}")
                    appendLine("总浏览量: ${illustration.viewCount}")
                    appendLine("下载链接: ${illustration.origin}")
                    append("Tags: ${illustration.tags.joinToString(",", "[", "]")}")
                })
            }.asSingleMessageChainList()
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/SetGomokuPlayerCreditCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.SetGomokuPlayerCreditCommand
import net.mamoe.mirai.message.MessageEvent

class SetGomokuPlayerCreditCommandTranslator : AbstractMessageTranslator<SetGomokuPlayerCreditCommand>() {
    override fun translate(rawInput: MessageEvent): SetGomokuPlayerCreditCommand {
        val args = defaultTrailingParameters(rawInput)
        return SetGomokuPlayerCreditCommand(args[0], args[1], rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/plugin/Plugin.kt
package com.github.rinacm.sayaka.common.plugin

import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import com.github.rinacm.sayaka.common.util.reflector.TypedAnnotationScanner
import com.github.rinacm.sayaka.common.util.reflector.TypedSubclassesScanner
import kotlin.reflect.KClass
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.isSubclassOf

abstract class Plugin<T : Plugin<T>> {
    interface Key<T : Plugin<T>> : Descriptor {
        var enabled: Boolean
        var excludes: MutableMap<String, MutableSet<ExcludeType>>
    }

    companion object {
        private val pluginKeyToCommandKeyMapping by lazy {
            (TypedAnnotationScanner.markedMap[PluginOwnership::class] ?: emptyList())
                .asSequence()
                .seqCast<Class<*>>()
                .map(Class<*>::kotlin)
                .filter { it.isSubclassOf(Command::class) }
                .filter { it.companionObjectInstance is Command.Key<*> }
                .groupBy({ it.annotations.singleIsInstance<PluginOwnership>().clazz }, KClass<*>::companionObjectInstance)
                .mapKeys { (plugin, _) -> plugin.companionObjectInstance as Key<*> }
                .mapValues { (_, commands) -> commands.cast<Command.Key<*>>() }
        }

        fun getPluginKeyByName(name: String): Key<*>? {
            return all()?.firstOrNull { (it.companionObjectInstance as Key<*>).match == name }
                ?.companionObjectInstance as Key<*>?
        }

        fun getPluginKeyByAnnotation(annotation: PluginOwnership): Key<*>? {
            return annotation.clazz.companionObjectInstance as Key<*>?
        }

        fun all(): List<KClass<out Any>>? {
            return TypedSubclassesScanner.markedMap[Plugin::class]
                ?.filter { it.companionObjectInstance is Key<*> }
        }

        fun help(pluginKey: Key<*>): String {
            if (pluginKeyToCommandKeyMapping.any { (key, _) -> key == pluginKey }) {
                val commands = pluginKeyToCommandKeyMapping[pluginKey]
                return buildString {
                    appendLine("插件 ${pluginKey.canonicalizeName()}: ${pluginKey.description} ")
                    appendLineIndent("命令列表: ")
                    for (cmd in commands!!) {
                        appendLineIndent(cmd.help(cmd), indentLevel = 2)
                    }
                }
            }
            return String.EMPTY
        }

        fun help(): String {
            return buildString {
                for (plugin in pluginKeyToCommandKeyMapping.keys) {
                    append(help(plugin))
                }
            }
        }
    }
}
<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GomokuPlayerSurrenderCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerSurrenderCommand
import net.mamoe.mirai.message.MessageEvent

class GomokuPlayerSurrenderCommandTranslator : AbstractMessageTranslator<GomokuPlayerSurrenderCommand>() {
    override fun translate(rawInput: MessageEvent): GomokuPlayerSurrenderCommand {
        return GomokuPlayerSurrenderCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/ShuffleRankOptionCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.pixiv.ShuffleRankOptionCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.pixiv.core.RankingEmitter
import net.mamoe.mirai.message.data.MessageChain

class ShuffleRankOptionCommandHandler : CommandHandler<ShuffleRankOptionCommand> {
    override suspend fun process(command: ShuffleRankOptionCommand): List<MessageChain>? {
        RankingEmitter.getOrCreateByContact(command.messageEvent.subject).shuffle()
        return "成功将榜单选项随机为${RankingEmitter.getOrCreateByContact(command.messageEvent.subject).current.first}".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/SetAdministratorCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.init.BotContext
import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.console.SetAdministratorCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import net.mamoe.mirai.message.data.MessageChain

class SetAdministratorCommandHandler : CommandHandler<SetAdministratorCommand> {
    override suspend fun process(command: SetAdministratorCommand): List<MessageChain>? {
        return if (command.qqId.toLongOrNull() != null) {
            if (BotContext.addAdmin(command.qqId)) {
                "成功将${command.qqId}添加到管理员列表成功".asSingleMessageChainList()
            } else "${command.qqId}已经是一名管理员".asSingleMessageChainList()
        } else "参数格式错误，必须是一个正确的QQ号码".asSingleMessageChainList()
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GomokuPlayerSurrenderCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerSurrenderCommand
import com.github.rinacm.sayaka.gomoku.GomokuGameFactory
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.at

class GomokuPlayerSurrenderCommandHandler : CommandHandler<GomokuPlayerSurrenderCommand> {
    override suspend fun process(command: GomokuPlayerSurrenderCommand): List<MessageChain>? {
        val game = GomokuGameFactory.tryGetGameOrNull(command.messageEvent.subject.id.toString())
        val member = command.messageEvent.sender as Member
        if (game != null && game.isActivatedAndValid(member.id.toString())) {
            val winner = game.getPlayer().getNegative(member.id.toString())
            return mutableListOf<MessageChain>().apply {
                add(member.at() + "选择了投降！")
                add(game.getWinMessageAndDisposeGame(game.getPlayer().toRole(winner)))
            }
        }
        return null
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/error/ValidationException.kt
package com.github.rinacm.sayaka.common.message.error

class ValidationException(cause: Exception) : PipelineException(cause) {
    override val errorStage: ErrorStage = ErrorStage.VALIDATION
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/handler/GomokuPlayerRequestEndingCommandHandler.kt
package com.github.rinacm.sayaka.common.message.handler

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.gomoku.GomokuPlayerRequestEndingCommand
import com.github.rinacm.sayaka.common.util.asSingleMessageChainList
import com.github.rinacm.sayaka.common.util.toSingleList
import com.github.rinacm.sayaka.gomoku.GomokuGameFactory
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.at

class GomokuPlayerRequestEndingCommandHandler : CommandHandler<GomokuPlayerRequestEndingCommand> {
    override suspend fun process(command: GomokuPlayerRequestEndingCommand): List<MessageChain>? {
        val ctx = command.messageEvent
        val game = GomokuGameFactory.tryGetGameOrNull(ctx.subject.id.toString())

        if (game != null && game.isActivatedAndValid(ctx.sender.id.toString())) {
            with(ctx.subject as Group) {
                val (id, requesting) = game.requestEnding
                if (requesting && id != ctx.sender.id.toString()) {
                    game.close()
                    return "投票通过，结束id为${game.gameId}的对局".asSingleMessageChainList()
                } else if (!requesting) {
                    game.requestEnding = ctx.sender.id.toString() to true
                    return (this[ctx.sender.id].at() + "发起结束对局投票！同意请输入/re").toSingleList()
                }
            }
        }
        return null
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/ReloadRankingSettingsCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.pixiv.ReloadRankingSettingsCommand
import net.mamoe.mirai.message.MessageEvent

class ReloadRankingSettingsCommandTranslator : AbstractMessageTranslator<ReloadRankingSettingsCommand>() {
    override fun translate(rawInput: MessageEvent): ReloadRankingSettingsCommand {
        return ReloadRankingSettingsCommand(rawInput)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/GetDispatchersMessageCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.GetDispatchersMessageCommand
import net.mamoe.mirai.message.MessageEvent

class GetDispatchersMessageCommandTranslator : AbstractMessageTranslator<GetDispatchersMessageCommand>() {
    override fun translate(rawInput: MessageEvent): GetDispatchersMessageCommand {
        return GetDispatchersMessageCommand(rawInput, defaultOnlyOneParameter(rawInput) != null)
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/StepMarker.kt
package com.github.rinacm.sayaka.gomoku

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class StepLeft(val stepX: Int, val stepY: Int)

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class StepRight(val stepX: Int, val stepY: Int)

<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/gomoku/GomokuGameFactory.kt
package com.github.rinacm.sayaka.gomoku

object GomokuGameFactory {
    private val games = mutableMapOf<String, Game>()

    @Synchronized
    fun getOrCreateGame(group: String): Game {
        if (group in games.keys) {
            return games[group]!!
        }
        return Game(group).apply { games[group] = this }
    }

    @Synchronized
    fun tryGetGameOrNull(group: String): Game? {
        if (group in games.keys) {
            return games[group]
        }
        return null
    }

    @Synchronized
    fun removeGame(gameId: String) {
        games.remove(gameId)
    }
}<file_sep>/src/main/java/Test.java
import java.util.function.Supplier;

public class Test {
    public static void waitConditional(Supplier<Boolean> condition) {
        while (!condition.get()) {
        }
    }

    public static void main(String[] args) {
        waitConditional(() -> false);
        System.out.println();
    }
}
<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/reflector/TypedSubclassesScanner.kt
package com.github.rinacm.sayaka.common.util.reflector

import com.github.rinacm.sayaka.common.util.set
import kotlin.reflect.KClass

object TypedSubclassesScanner : TypedScanner<KClass<*>, KClass<out Any>>() {
    override val markedMap: Map<KClass<*>, List<KClass<out Any>>> by lazy {
        val map = mutableMapOf<KClass<*>, List<KClass<out Any>>>()
        for ((target, classes) in scanMap.asMap()) {
            for (cls in classes) {
                if (target == ScanningTarget.CLASS) {
                    map[cls] = reflector.getSubTypesOf(cls.java).map(Class<*>::kotlin)
                }
            }
        }
        return@lazy map
    }

    override fun registerScanner(scanningTarget: ScanningTarget, clazz: KClass<*>) {
        scanMap[scanningTarget] = clazz
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/RevokeAdministratorCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.RevokeAdministratorCommand
import net.mamoe.mirai.message.MessageEvent

class RevokeAdministratorCommandTranslator : AbstractMessageTranslator<RevokeAdministratorCommand>() {
    override fun translate(rawInput: MessageEvent): RevokeAdministratorCommand {
        return RevokeAdministratorCommand(defaultOnlyOneParameter(rawInput)!!, rawInput)
    }
}
<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/factory/Dispatcher.kt
package com.github.rinacm.sayaka.common.message.factory

import com.github.rinacm.sayaka.common.message.contextual.CommandHandler
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Descriptor
import com.github.rinacm.sayaka.common.util.reflector.TypedSubclassesScanner
import net.mamoe.mirai.message.MessageEvent
import kotlin.reflect.full.companionObjectInstance

/**
 * Interface to dispatch received messages as a pipe and manage the request flow
 * 1. use [translateMessage] to translate a [MessageEvent] to an Command Object
 * 2. check if the [MessageEvent] comfort [thresholding]
 * 3. validation stage happens in translation stage, it will intercept all illegal
 *    messages(.i.e a message with correct command match but has no parameters), a
 *    [ValidationException] will be thrown when the illegal messages being captured
 * 4. use [dispatchMessage] to dispatch the message to the corresponds [CommandHandler]
 * and if [translateMessage] throws means there's an exception occurred during
 * the the translation stage therefore you should call the [dispatchError] to handle the
 * error message
 */
interface Dispatcher {
    interface Key<T : Dispatcher> : Descriptor {
        val instance: T
    }

    companion object {
        var Global: Dispatcher = DefaultDispatcherImpl.instance
        var Sleeping: Boolean = false

        fun availableDispatchers(): List<Key<*>>? {
            return TypedSubclassesScanner.markedMap[Dispatcher::class]
                ?.filter { it.companionObjectInstance is Key<*> }
                ?.map { it.companionObjectInstance as Key<*> }
        }

        fun findDispatcherKeyByName(name: String): Key<*>? {
            return TypedSubclassesScanner.markedMap[Dispatcher::class]
                ?.filter { it.companionObjectInstance is Key<*> }
                ?.map { it.companionObjectInstance as Key<*> }
                ?.firstOrNull { it.match == name }
        }

    }

    suspend fun translateMessage(messageEvent: MessageEvent): Command

    suspend fun <T : Command> dispatchMessage(messageEvent: MessageEvent, command: T)

    suspend fun dispatchError(messageEvent: MessageEvent, exception: Exception)

    suspend fun thresholding(messageEvent: MessageEvent): Boolean
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/gomoku/GetGomokuCreditCommand.kt
package com.github.rinacm.sayaka.common.shared.gomoku

import com.github.rinacm.sayaka.common.message.handler.GetGomokuCreditCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetGomokuCreditCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.RegexValidator
import com.github.rinacm.sayaka.common.plugin.GomokuPlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.*
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetGomokuCreditCommandTranslator::class, GetGomokuCreditCommandHandler::class)
@PluginOwnership(GomokuPlugin::class)
@Validator(RegexValidator::class, regex = QQ_ID_REGEX)
data class GetGomokuCreditCommand(val playerQQId: String, override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GetGomokuCreditCommand> {
        override val match: String = "/gc"
        override val description: String = "获取${Placeholder("id")}对应的Gomoku点数"

        override fun canonicalizeName(): String {
            return "$match ${Placeholder("id")}"
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/shared/console/GetOwnerCommand.kt
package com.github.rinacm.sayaka.common.shared.console

import com.github.rinacm.sayaka.common.message.handler.GetOwnerCommandHandler
import com.github.rinacm.sayaka.common.message.translator.GetOwnerCommandTranslator
import com.github.rinacm.sayaka.common.message.validation.PurePlainTextValidator
import com.github.rinacm.sayaka.common.plugin.ConsolePlugin
import com.github.rinacm.sayaka.common.shared.Command
import com.github.rinacm.sayaka.common.util.Contextual
import com.github.rinacm.sayaka.common.util.PermanentlyDisable
import com.github.rinacm.sayaka.common.util.PluginOwnership
import com.github.rinacm.sayaka.common.util.Validator
import net.mamoe.mirai.message.MessageEvent

@Contextual(GetOwnerCommandTranslator::class, GetOwnerCommandHandler::class)
@PluginOwnership(ConsolePlugin::class)
@PermanentlyDisable
@Validator(PurePlainTextValidator::class)
class GetOwnerCommand(override val messageEvent: MessageEvent) : Command {
    companion object Key : Command.Key<GetOwnerCommand> {
        override val match: String = "/owner"
        override val description: String = "获取该Bot的实际持有者"
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/message/translator/ShowHelpMessageCommandTranslator.kt
package com.github.rinacm.sayaka.common.message.translator

import com.github.rinacm.sayaka.common.message.contextual.AbstractMessageTranslator
import com.github.rinacm.sayaka.common.shared.console.ShowHelpMessageCommand
import net.mamoe.mirai.message.MessageEvent

class ShowHelpMessageCommandTranslator : AbstractMessageTranslator<ShowHelpMessageCommand>() {
    override fun translate(rawInput: MessageEvent): ShowHelpMessageCommand {
        return ShowHelpMessageCommand(rawInput, defaultTrailingParameters(rawInput))
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/Sayaka.kt
package com.github.rinacm.sayaka

import com.github.rinacm.sayaka.common.init.BotFactory
import com.github.rinacm.sayaka.common.init.PreInit
import com.github.rinacm.sayaka.common.util.subscribeAlwaysUnderGlobalDispatcherSuspended
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    PreInit.use {
        BotFactory.runAndJoin {
            subscribeAlwaysUnderGlobalDispatcherSuspended {
                try {
                    val msg = translateMessage(it)
                    dispatchMessage(it, msg)
                } catch (e: Exception) {
                    dispatchError(it, e)
                }
            }
        }
    }
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/Descriptor.kt
package com.github.rinacm.sayaka.common.util

interface Descriptor {
    val match: String

    val matchOption: MatchOption get() = MatchOption.PLAIN_TEXT

    val description: String

    /**
     * canonicalize string of [match], default value is [match] itself
     */
    fun canonicalizeName() = match
}<file_sep>/src/main/kotlin/com/github/rinacm/sayaka/common/util/reflector/TypedScanner.kt
package com.github.rinacm.sayaka.common.util.reflector

import com.github.rinacm.sayaka.common.util.reflector.TypedAnnotationScanner.markedMap
import com.google.common.collect.Multimap
import com.google.common.collect.MultimapBuilder
import org.reflections.Reflections
import org.reflections.scanners.MethodAnnotationsScanner
import org.reflections.scanners.SubTypesScanner
import org.reflections.scanners.TypeAnnotationsScanner

enum class ScanningTarget {
    METHOD, CLASS
}

/**
 * Provide a set of functionalities to scan through the project and get
 * a list which denotes for every condition [T] there's such a set of [R] exists
 */
abstract class TypedScanner<T : Any, R : Any> {
    companion object {
        @JvmStatic
        protected val reflector by lazy {
            Reflections("com.github.rinacm.sayaka" /* root package */, TypeAnnotationsScanner(), MethodAnnotationsScanner(), SubTypesScanner())
        }
    }

    protected val scanMap: Multimap<ScanningTarget, T> = MultimapBuilder.hashKeys().arrayListValues().build()

    abstract val markedMap: Map<T, List<R>>

    /**
     * [registerScanner] is not dynamically invokable, you must register every class before you use [markedMap]
     * this method will be invalid once you use [markedMap]
     */
    abstract fun registerScanner(scanningTarget: ScanningTarget, clazz: T)
}