All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.message.data.impl.kt Maven / Gradle / Ivy

/*
 * Copyright 2019-2021 Mamoe Technologies and contributors.
 *
 *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
 *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
 *
 *  https://github.com/mamoe/mirai/blob/master/LICENSE
 */

@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:JvmMultifileClass
@file:JvmName("MessageUtils")

package net.mamoe.mirai.message.data

import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.asImmutable
import net.mamoe.mirai.utils.replaceAllKotlin

// region image

/////////////////////////
//// IMPLEMENTATIONS ////
/////////////////////////

@Suppress("unused", "UNUSED_PARAMETER")
private fun Message.hasDuplicationOfConstrain(key: MessageKey<*>): Boolean {
    return true
    /*
     return when (this) {
         is SingleMessage -> (this as? ConstrainSingle)?.key == key
         is CombinedMessage -> return this.left.hasDuplicationOfConstrain(key) || this.tail.hasDuplicationOfConstrain(key)
         is MessageChainImplByCollection -> this.delegate.any { (it as? ConstrainSingle)?.key == key }
         else -> error("stub")
     }*/
}

@JvmSynthetic
internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boolean): Boolean {
    if (!this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase)) return false
    return when {
        this is SingleMessage && another is SingleMessage -> true
        this is SingleMessage && another is MessageChain -> another.all { it is MessageMetadata || it is PlainText }
        this is MessageChain && another is SingleMessage -> this.all { it is MessageMetadata || it is PlainText }
        this is MessageChain && another is MessageChain -> {
            val anotherIterator = another.iterator()

            /**
             * 逐个判断非 [PlainText] 的 [Message] 是否 [equals]
             */
            this.contentsSequence().forEach { thisElement ->
                if (thisElement is PlainText) return@forEach
                for (it in anotherIterator) {
                    if (it is PlainText || it !is MessageContent) continue
                    if (thisElement != it) return false
                }
            }
            return true
        }
        else -> error("shouldn't be reached")
    }
}

@JvmSynthetic
internal fun Message.followedByImpl(tail: Message): MessageChain {
    return MessageChainImplBySequence(this.toMessageChain().asSequence() + tail.toMessageChain().asSequence())
    /*
    when {
        this is SingleMessage && tail is SingleMessage -> {
            if (this is ConstrainSingle && tail is ConstrainSingle) {
                if (this.key == tail.key)
                    return SingleMessageChainImpl(tail)
            }
            return CombinedMessage(this, tail)
        }

        this is SingleMessage -> { // tail is not
            tail as MessageChain

            if (this is ConstrainSingle) {
                val key = this.key
                if (tail.any { (it as? ConstrainSingle)?.key == key }) {
                    return tail
                }
            }
            return CombinedMessage(this, tail)
        }

        tail is SingleMessage -> {
            this as MessageChain

            if (tail is ConstrainSingle && this.hasDuplicationOfConstrain(tail.key)) {
                return MessageChainImplByCollection(constrainSingleMessagesImpl(this.asSequence() + tail))
            }

            return CombinedMessage(this, tail)
        }

        else -> { // both chain
            this as MessageChain
            tail as MessageChain

            return MessageChainImplByCollection(
                constrainSingleMessagesImpl(this.asSequence() + tail)
            )
        }
    }*/
}


@JvmSynthetic
internal fun Sequence.constrainSingleMessages(): List =
    constrainSingleMessagesImpl(this)

/**
 * - [Sequence.toMutableList]
 * - Replace in-place with marker null
 */
@MiraiExperimentalApi
@JvmSynthetic
internal fun constrainSingleMessagesImpl(sequence: Sequence): List {
    val list: MutableList = sequence.toMutableList()

    for (singleMessage in list.asReversed()) {
        if (singleMessage is ConstrainSingle) {
            val key = singleMessage.key.topmostKey
            val firstOccurrence = list.first { it != null && key.isInstance(it) } // may be singleMessage itself
            list.replaceAllKotlin {
                when {
                    it == null -> null
                    it === firstOccurrence -> singleMessage
                    key.isInstance(it) -> null // remove duplicates
                    else -> it
                }
            }
        }
    }

    return list.filterNotNull().asImmutable()
}

@JvmSynthetic
internal fun Iterable.constrainSingleMessages(): List =
    constrainSingleMessagesImpl(this.asSequence())

@JvmName("constrainSingleMessages_Sequence")
@JvmSynthetic
internal fun Sequence.constrainSingleMessages(): List =
    this.flatMap { it.toMessageChain() }.constrainSingleMessages()


@JvmSynthetic
@Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR", "DEPRECATION")
internal fun  MessageChain.getImpl(key: MessageKey): M? {
    return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
}

/**
 * @return [EmptyMessageChain] if [delegate] is empty, otherwise [MessageChainImpl]
 */
internal fun createMessageChainImplOptimized(delegate: List): MessageChain {
    return if (delegate.isEmpty()) EmptyMessageChain
    else MessageChainImpl(delegate)
}

/**
 * 使用 [Collection] 作为委托的 [MessageChain]
 */
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(MessageChain.Serializer::class)
internal data class MessageChainImpl constructor(
    @JvmField
    internal val delegate: List // 必须 constrainSingleMessages, 且为 immutable
) : Message, MessageChain, List by delegate {
    override val size: Int get() = delegate.size
    override fun iterator(): Iterator = delegate.iterator()

    private val toStringTemp: String by lazy { this.delegate.joinToString("") { it.toString() } }
    override fun toString(): String = toStringTemp

    private val contentToStringTemp: String by lazy { this.delegate.joinToString("") { it.contentToString() } }
    override fun contentToString(): String = contentToStringTemp

    override fun hashCode(): Int = delegate.hashCode()
    override fun equals(other: Any?): Boolean = other is MessageChainImpl && other.delegate == this.delegate
}

@Suppress("FunctionName") // source compatibility with 1.x
internal fun MessageChainImplBySequence(
    delegate: Sequence // 可以有重复 ConstrainSingle
): MessageChain = createMessageChainImplOptimized(delegate.constrainSingleMessages())


//////////////////////
// region Image impl
//////////////////////


@get:JvmSynthetic
internal val EMPTY_BYTE_ARRAY = ByteArray(0)


@JvmSynthetic
@Suppress("NOTHING_TO_INLINE") // no stack waste
internal inline fun Char.hexDigitToByte(): Int {
    return when (this) {
        in '0'..'9' -> this - '0'
        in 'A'..'F' -> 10 + (this - 'A')
        in 'a'..'f' -> 10 + (this - 'a')
        else -> throw IllegalArgumentException("Illegal hex digit: $this")
    }
}

@JvmSynthetic
internal fun String.skipToSecondHyphen(): Int {
    var count = 0
    this.forEachIndexed { index, c ->
        if (c == '-' && ++count == 2) return index
    }
    error("Internal error: failed skipToSecondHyphen, cannot find two hyphens. Input=$this")
}

@JvmSynthetic
internal fun String.imageIdToMd5(offset: Int): ByteArray {
    val result = ByteArray(16)
    var cur = 0
    var hasCurrent = false
    var lastChar: Char = 0.toChar()
    for (index in offset..this.lastIndex) {
        val char = this[index]
        if (char == '-') continue
        if (hasCurrent) {
            result[cur++] = (lastChar.hexDigitToByte().shl(4) or char.hexDigitToByte()).toByte()
            if (cur == 16) return result
            hasCurrent = false
        } else {
            lastChar = char
            hasCurrent = true
        }
    }
    error("Internal error: failed imageIdToMd5, no enough chars. Input=$this, offset=$offset")
}

internal val ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE: String =
    "ImageId must match Regex `${IMAGE_RESOURCE_ID_REGEX_1.pattern}`, " +
            "`${IMAGE_RESOURCE_ID_REGEX_2.pattern}` or " +
            "`${IMAGE_ID_REGEX.pattern}`"

// endregion




© 2015 - 2025 Weber Informatics LLC | Privacy Policy