├── logo └── CatCodeLogo@0,1x.png ├── .idea └── .gitignore ├── UPDATE.md ├── src ├── main │ └── java │ │ └── catcode │ │ ├── collection │ │ ├── NoNeedInitializeLazy.kt │ │ ├── SimpleEntry.kt │ │ ├── NekoMap.kt │ │ └── LazyMap.kt │ │ ├── NekoObjects.kt │ │ ├── CatKV.kt │ │ ├── Msg.kt │ │ ├── NekoDsl.kt │ │ ├── WildcatCodeUtil.kt │ │ ├── CatCodeUtil.kt │ │ ├── CatIterators.kt │ │ ├── Neko.kt │ │ ├── WildcatCodeBuilder.kt │ │ ├── CodeBuilder.kt │ │ ├── codes │ │ ├── MapNeko.kt │ │ ├── LazyMapNeko.kt │ │ ├── MapNoraNeko.kt │ │ ├── LazyMapNoraNeko.kt │ │ ├── Nyanko.kt │ │ └── NoraNyanko.kt │ │ ├── WildcatStringTemplate.kt │ │ ├── CodeTemplate.kt │ │ └── NekoAibo.kt └── test │ └── java │ └── love │ └── forte │ └── test │ └── ReadmeTest1.java ├── LICENSE ├── .gitignore ├── README.md └── pom.xml /logo/CatCodeLogo@0,1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForteScarlet/CatCode/HEAD/logo/CatCodeLogo@0,1x.png -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /UPDATE.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | ## alpha.1 3 | - 第一个内测版本。 4 | 5 | ## alpha.2 6 | - 修复objects常量模板的`toString`异常问题。 7 | - 追加常量模板类 `NekoObjects` 。 8 | 9 | ## alpha.3 10 | - 追加 `lazy` (属性值懒加载)相关的neko,以及对应的builder。(`CatCodeUtil#getLazyNekoBuilder`) 11 | 12 | ## alpha.4 13 | - 包路径移动: `love.forte.catcode.*` -> `catcode.*` 14 | 15 | 16 | ## BETA.1 17 | - fix [#3](https://github.com/ForteScarlet/CatCode/issues/3) 参数转义缺少等号转义 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/catcode/collection/NoNeedInitializeLazy.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("NoNeedInitializeLazies") 2 | package catcode.collection 3 | 4 | 5 | /** 6 | * 不需要初始化的lazy实例。 7 | */ 8 | private data class NoNeedInitializeLazy(override val value: T) : Lazy { 9 | override fun isInitialized(): Boolean = true 10 | } 11 | 12 | 13 | /** 通过一个实例对象构建一个 [Lazy] 实例。 */ 14 | public fun lazyValue(value: T): Lazy = NoNeedInitializeLazy(value) 15 | -------------------------------------------------------------------------------- /src/main/java/catcode/collection/SimpleEntry.kt: -------------------------------------------------------------------------------- 1 | package catcode.collection 2 | 3 | 4 | /** Simple entry implementation. */ 5 | internal data class SimpleEntry(override val key: K, override val value: V) : Map.Entry 6 | 7 | /** Simple mutable entry implementation. */ 8 | internal data class SimpleMutableEntry(override val key: K, override var value: V) : 9 | MutableMap.MutableEntry { 10 | override fun setValue(newValue: V): V = value.apply { value = newValue } 11 | } -------------------------------------------------------------------------------- /src/main/java/catcode/NekoObjects.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | @file:JvmName("NekoObjects") 13 | package catcode 14 | 15 | import catcode.codes.Nyanko 16 | 17 | /* 18 | 提供一些可以作为单例使用的[KQCode]实例 19 | */ 20 | 21 | /** 22 | * at all 23 | * `[CAT:at,all=true]` 24 | */ 25 | val NekoAtAll : Neko = Nyanko.byCode("${CAT_HEAD}at,all=true$CAT_END") 26 | 27 | /** 28 | * rps 猜拳 29 | * 发送用的猜拳 30 | * `[CAT:rps]` 31 | */ 32 | val NekoRps : Neko = EmptyNeko("rps") 33 | 34 | 35 | /** 36 | * dice 骰子 37 | * 发送用的骰子 38 | * `[CAT:dice]` 39 | */ 40 | val NekoDice : Neko = EmptyNeko("dice") 41 | 42 | 43 | /** 44 | * 窗口抖动,戳一戳 45 | * `[CAT:shake]` 46 | */ 47 | val NekoShake : Neko = EmptyNeko("shake") 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/test/java/love/forte/test/ReadmeTest1.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | package love.forte.test; 14 | 15 | import love.forte.catcode.CatCodeUtil; 16 | import love.forte.catcode.CodeBuilder; 17 | import love.forte.catcode.Neko; 18 | import love.forte.catcode.WildcatCodeUtil; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * Readme test 1 for create a cat code by Builder. 24 | * 25 | * @author ForteScarlet 26 | */ 27 | public class ReadmeTest1 { 28 | public static void main(String[] args) { 29 | // get util instance. 30 | final CatCodeUtil catUtil = CatCodeUtil.INSTANCE; 31 | 32 | String at = "添加管理员[CAT:at,code=2473125346]"; 33 | 34 | List nekos = catUtil.getNekoList(at, "at"); 35 | 36 | for (Neko neko : nekos) { 37 | System.out.println(neko); 38 | } 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ForteScarlet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/catcode/CatKV.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet All rights reserved. 3 | * Project parent 4 | * File Bridging.kt 5 | * 6 | * You can contact the author through the following channels: 7 | * github https://github.com/ForteScarlet 8 | * gitee https://gitee.com/ForteScarlet 9 | * email ForteScarlet@163.com 10 | * QQ 1149159218 11 | */ 12 | @file:JvmName("CatKVs") 13 | package catcode 14 | 15 | 16 | /** 17 | * data class like [Pair]. 18 | */ 19 | public data class CatKV(val key: K, val value: V) { 20 | override fun toString(): String = "KV($key$CAT_KV$value)" 21 | 22 | /** 23 | * kv companion. 24 | */ 25 | companion object KV { 26 | @JvmStatic 27 | fun kv(k: K, v: V) = CatKV(k, v) 28 | } 29 | } 30 | 31 | /** 32 | * create [CatKV] instance. 33 | */ 34 | public infix fun K.cTo(v: V) = CatKV(this, v) 35 | 36 | /** 37 | * [CatKV] to [Pair]. 38 | */ 39 | public fun CatKV.toPair() = Pair(this.key, this.value) 40 | 41 | /** 42 | * [Pair] to [CatKV]. 43 | */ 44 | public fun Pair.toKV() = CatKV(this.first, this.second) 45 | 46 | /** 47 | * CatKV array to pair array. 48 | */ 49 | public fun Array>.toPair() = Array(this.size) { i -> this[i].toPair()} 50 | -------------------------------------------------------------------------------- /src/main/java/catcode/Msg.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet All rights reserved. 3 | * Project parent 4 | * File Msg.kt 5 | * 6 | * You can contact the author through the following channels: 7 | * github https://github.com/ForteScarlet 8 | * gitee https://gitee.com/ForteScarlet 9 | * email ForteScarlet@163.com 10 | * QQ 1149159218 11 | */ 12 | 13 | package catcode 14 | 15 | 16 | 17 | /** 18 | * msgs 消息链。 19 | */ 20 | @Deprecated("Not use.") 21 | class Msgs 22 | @JvmOverloads 23 | constructor(private val delimiter: CharSequence = " ", private val delegate: () -> MutableList = { mutableListOf() }): MutableList by delegate() { 24 | @JvmOverloads 25 | constructor(delimiter: CharSequence = " ", collection: Collection): this(delimiter){ mutableListOf(collection) } 26 | 27 | 28 | /** to string to show messages. delimiter is ' ' */ 29 | override fun toString(): String = this.joinToString(delimiter) 30 | 31 | /** to list string to show messages.*/ 32 | fun toListString(): String = this.joinToString(", ", "[", "]") 33 | 34 | /** 35 | * plus 36 | */ 37 | operator fun plus(other: List): Msgs { 38 | other.forEach { this.add(it ?: "null") } 39 | return this 40 | } 41 | 42 | /** 43 | * plus 44 | */ 45 | operator fun plus(other: CharSequence?): Msgs { 46 | this.add(other ?: "null") 47 | return this 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/catcode/NekoDsl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | package catcode 14 | 15 | 16 | 17 | /** 18 | * 19 | * @author ForteScarlet 20 | * 2020/8/12 21 | */ 22 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 23 | @Retention(AnnotationRetention.BINARY) 24 | @DslMarker 25 | annotation class NekoDsl 26 | 27 | // 28 | // /** 29 | // * Params 30 | // * @since 1.0-1.11 31 | // */ 32 | // @NekoDsl 33 | // open class Params { 34 | // private val plist: MutableList> = mutableListOf() 35 | // var param: Pair 36 | // get() = plist.last() 37 | // set(value) { plist.add(value) } 38 | // 39 | // operator fun set(param: String, value: String) { 40 | // this.param = param to value 41 | // } 42 | // 43 | // /** 添加全部 */ 44 | // open fun addTo(neko: MutableNeko): MutableNeko { 45 | // plist.forEach { 46 | // neko[it.first] = it.second 47 | // } 48 | // return neko 49 | // } 50 | // 51 | // override fun toString(): String = plist.toString() 52 | // } 53 | // 54 | // /** 55 | // * Builder 56 | // * @since 1.0-1.11 57 | // */ 58 | // @NekoDsl 59 | // class Builder { 60 | // var type: String = "" 61 | // internal val _params = Params() 62 | // var param: Pair 63 | // get() = _params.param 64 | // set(value) { _params.param = value } 65 | // /** 添加全部 */ 66 | // fun build(): Neko { 67 | // val kqCode = MapNeko.byCode(type) 68 | // return _params.addTo(kqCode.mutable()) 69 | // } 70 | // 71 | // override fun toString(): String = "$type:$_params" 72 | // } 73 | // 74 | // 75 | // 76 | // /** 77 | // * 78 | // * 考虑使用 [CodeBuilder]。 79 | // * DSL将更替为使用 [CodeBuilder]。 80 | // * 81 | // * @since 1.0-1.11 82 | // * @see CodeBuilder 83 | // * 84 | // */ 85 | // @NekoDsl 86 | // @Deprecated("see CodeBuilder") 87 | // fun kqCode(type: String, block: Params.() -> Unit): Neko { 88 | // val kqCode = MapNeko(type = type) 89 | // return Params().apply(block).addTo(kqCode.mutable()) 90 | // } 91 | // 92 | // /** 93 | // * DSL构建KQCode的参数列表 94 | // * @since 1.0-1.11 95 | // * 96 | // * @see CodeBuilder 97 | // * 98 | // */ 99 | // @NekoDsl 100 | // @Deprecated("see CodeBuilder") 101 | // fun kqCode(block: Builder.() -> Unit) = Builder().apply(block).build() 102 | // 103 | // /** 104 | // * DSL构建Builder中的params, 例如 105 | // * ``` 106 | // kqCode { 107 | // type = "at" 108 | // params { 109 | // param = "qq" to "1149" 110 | // param = "file" to "neko.jpg" 111 | // } 112 | // } 113 | // * ``` 114 | // * @since 1.0-1.11 115 | // */ 116 | // @NekoDsl 117 | // fun Builder.params(block: Params.() -> Unit) { 118 | // this._params.apply(block) 119 | // } 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Maven template 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | .mvn/timing.properties 12 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 13 | .mvn/wrapper/maven-wrapper.jar 14 | 15 | ### Java template 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Mobile Tools for Java (J2ME) 26 | .mtj.tmp/ 27 | 28 | # Package Files # 29 | *.jar 30 | *.war 31 | *.nar 32 | *.ear 33 | *.zip 34 | *.tar.gz 35 | *.rar 36 | 37 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 38 | hs_err_pid* 39 | 40 | ### JetBrains template 41 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 42 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 43 | 44 | # User-specific stuff 45 | .idea/**/workspace.xml 46 | .idea/**/tasks.xml 47 | .idea/**/usage.statistics.xml 48 | .idea/**/dictionaries 49 | .idea/**/shelf 50 | 51 | # Generated files 52 | .idea/**/contentModel.xml 53 | 54 | # Sensitive or high-churn files 55 | .idea/**/dataSources/ 56 | .idea/**/dataSources.ids 57 | .idea/**/dataSources.local.xml 58 | .idea/**/sqlDataSources.xml 59 | .idea/**/dynamic.xml 60 | .idea/**/uiDesigner.xml 61 | .idea/**/dbnavigator.xml 62 | 63 | # Gradle 64 | .idea/**/gradle.xml 65 | .idea/**/libraries 66 | 67 | # Gradle and Maven with auto-import 68 | # When using Gradle or Maven with auto-import, you should exclude module files, 69 | # since they will be recreated, and may cause churn. Uncomment if using 70 | # auto-import. 71 | # .idea/artifacts 72 | # .idea/compiler.xml 73 | # .idea/jarRepositories.xml 74 | # .idea/modules.xml 75 | # .idea/*.iml 76 | # .idea/modules 77 | # *.iml 78 | # *.ipr 79 | 80 | # CMake 81 | cmake-build-*/ 82 | 83 | # Mongo Explorer plugin 84 | .idea/**/mongoSettings.xml 85 | 86 | # File-based project format 87 | *.iws 88 | 89 | # IntelliJ 90 | out/ 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Cursive Clojure plugin 99 | .idea/replstate.xml 100 | 101 | # Crashlytics plugin (for Android Studio and IntelliJ) 102 | com_crashlytics_export_strings.xml 103 | crashlytics.properties 104 | crashlytics-build.properties 105 | fabric.properties 106 | 107 | # Editor-based Rest Client 108 | .idea/httpRequests 109 | 110 | # Android studio 3.1+ serialized cache file 111 | .idea/caches/build_file_checksums.ser 112 | 113 | ### Kotlin template 114 | # Compiled class file 115 | *.class 116 | 117 | # Log file 118 | *.log 119 | 120 | # BlueJ files 121 | *.ctxt 122 | 123 | # Mobile Tools for Java (J2ME) 124 | .mtj.tmp/ 125 | 126 | # Package Files # 127 | *.jar 128 | *.war 129 | *.nar 130 | *.ear 131 | *.zip 132 | *.tar.gz 133 | *.rar 134 | 135 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 136 | hs_err_pid* 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/catcode/WildcatCodeUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | 14 | package catcode 15 | 16 | import catcode.codes.MapNoraNeko 17 | import catcode.codes.NoraNyanko 18 | 19 | 20 | /** 21 | * 野良猫码的操作工具类。 22 | * 23 | * 构建此工具类需要提供一个 `codeType`参数以代表此野良猫码的类型。 24 | * 25 | * 所谓野良猫,即code类型不一定是`CAT`的cat码。 26 | * 例如:`[CAT:at,code=123]`, 此码的类型为`CAT`, 所以是标准猫猫码, 27 | * 而例如`[CQ:at,code=123]`, 此码的类型为`CQ`, 不是标准猫猫码,即为野良猫码。 28 | * 29 | * > 野良猫 -> のらねこ -> 野猫 -> wildcat 30 | * 31 | */ 32 | @Suppress("unused", "DeprecatedCallableAddReplaceWith") 33 | public class WildcatCodeUtil 34 | private constructor(codeType: String) : NekoAibo(codeType) { 35 | 36 | companion object { 37 | @JvmStatic 38 | fun getInstance(codeType: String): WildcatCodeUtil = WildcatCodeUtil(codeType) 39 | } 40 | 41 | 42 | /** 43 | * 获取一个String为载体的[模板][CodeTemplate] 44 | * @see StringTemplate 45 | */ 46 | override val stringTemplate: WildcatTemplate = WildcatStringTemplate(codeType, this) 47 | 48 | /** 49 | * 获取[Neko]为载体的[模板][CodeTemplate] 50 | * @see NekoTemplate 51 | */ 52 | override val nekoTemplate: WildcatTemplate = NoraNekoTemplate(codeType, stringTemplate) 53 | 54 | 55 | /** 56 | * 构建一个String为载体类型的[构建器][CodeBuilder]。默认开启转义。 57 | */ 58 | override fun getStringCodeBuilder(type: String, encode: Boolean): CodeBuilder = WildcatStringCodeBuilder(codeType, type, encode) 59 | 60 | /** 61 | * 构建一个[Neko]为载体类型的[构建器][CodeBuilder]。默认开启转义。 62 | */ 63 | override fun getNekoBuilder(type: String, encode: Boolean): CodeBuilder = NoraNekoBuilder(codeType, type) 64 | 65 | /** 66 | * 构建一个[Neko]为载体类型的[构建器][CodeBuilder]。默认开启转义。 67 | */ 68 | override fun getLazyNekoBuilder(type: String, encode: Boolean): LazyCodeBuilder = LazyNoraNekoBuilder(codeType, type) 69 | 70 | 71 | /** 72 | * 获取无参数的[Neko] 73 | * @param type 猫猫码的类型 74 | */ 75 | override fun toNeko(type: String): NoraNeko = EmptyNoraNeko(codeType, type) 76 | 77 | /** 78 | * 根据[Map]类型参数转化为[Neko]实例 79 | * 80 | * @param type 猫猫码的类型 81 | * @param params 参数列表 82 | */ 83 | override fun toNeko(type: String, params: Map): NoraNeko { 84 | return if (params.isEmpty()) { 85 | toNeko(type) 86 | } else { 87 | MapNoraNeko.byMap(codeType, type, params.asSequence().map { it.key to it.value.toString() }.toMap()) 88 | } 89 | } 90 | 91 | 92 | /** 93 | * 根据参数转化为[Neko]实例 94 | * @param type 猫猫码的类型 95 | * @param kv 参数列表 96 | */ 97 | override fun toNeko(type: String, vararg kv: CatKV): NoraNeko { 98 | return if (kv.isEmpty()) { 99 | toNeko(type) 100 | } else { 101 | MapNoraNeko.byMap(codeType, type, kv.asSequence().map { it.key to it.value.toString() }.toMap()) 102 | } 103 | } 104 | 105 | 106 | /** 107 | * 根据参数转化为[Neko]实例 108 | * @param type 猫猫码的类型 109 | * @param paramText 参数列表, 例如:"qq=123" 110 | */ 111 | override fun toNeko(type: String, encode: Boolean, vararg paramText: String): NoraNeko { 112 | return if (paramText.isEmpty()) { 113 | toNeko(type) 114 | } else { 115 | if (encode) { 116 | NoraNyanko.byCode(toCat(type, encode, *paramText)) 117 | } else { 118 | MapNoraNeko.byParamString(codeType, type, *paramText) 119 | } 120 | } 121 | } 122 | 123 | 124 | /** 125 | * 提取出文本中的猫猫码,并封装为[Neko]实例。 126 | * @param text 存在猫猫码的正文 127 | * @param type 要获取的猫猫码的类型,默认为所有类型 128 | * @param index 获取的索引位的猫猫码,默认为0,即第一个 129 | */ 130 | override fun getNeko(text: String, type: String, index: Int): Neko? { 131 | val cat: String = getCat(text, type, index) ?: return null 132 | return Neko.of(cat) 133 | } 134 | 135 | 136 | } 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/main/java/catcode/CatCodeUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("unused") 14 | @file:JvmName("Cats") 15 | package catcode 16 | 17 | /* 18 | & -> & 19 | [ -> [ 20 | ] -> ] 21 | */ 22 | /* 23 | & -> & 24 | [ -> [ 25 | ] -> ] 26 | , -> , 27 | */ 28 | 29 | internal val CAT_KV_SPLIT_ARRAY: Array = arrayOf(CAT_KV) 30 | internal val CAT_PS_SPLIT_ARRAY: Array = arrayOf(CAT_PS) 31 | 32 | /** Cat Decoder */ 33 | @Suppress("MemberVisibilityCanBePrivate") 34 | object CatDecoder { 35 | 36 | @JvmStatic 37 | val instance 38 | get() = this 39 | 40 | /** 非猫猫码文本消息解义 */ 41 | fun decodeText(str: String): String = 42 | str .replace("[", "[") 43 | .replace("]", "]") 44 | .replace(" ", "\t") 45 | .replace(" ", "\r") 46 | .replace(" ", "\n") 47 | .replace("&", "&") 48 | 49 | /** 非猫猫码文本消息解义,如果[str]为null则返回null */ 50 | fun decodeTextOrNull(str: String?) : String? = str?.let { decodeText(it) } 51 | 52 | 53 | /** 猫猫码参数值消息解义 */ 54 | fun decodeParams(str: String): String = 55 | str .replace("[", "[") 56 | .replace("]", "]") 57 | .replace(",", ",") 58 | .replace("=", "=") 59 | .replace(" ", "\t") 60 | .replace(" ", "\r") 61 | .replace(" ", "\n") 62 | .replace("&", "&") 63 | 64 | /** 猫猫码参数值消息解义,如果[str]为null则返回null */ 65 | fun decodeParamsOrNull(str: String?): String? = str?.let { decodeParams(it) } 66 | 67 | } 68 | 69 | 70 | public fun String.deCatParam(): String = CatDecoder.decodeParams(this) 71 | public fun String.deCatText(): String = CatDecoder.decodeText(this) 72 | 73 | 74 | /** Cat Encoder */ 75 | @Suppress("MemberVisibilityCanBePrivate") 76 | object CatEncoder { 77 | 78 | @JvmStatic 79 | val instance 80 | get() = this 81 | 82 | /** 非猫猫码文本消息转义 */ 83 | fun encodeText(str: String): String = 84 | str.replace("&", "&") 85 | .replace("[", "[") 86 | .replace("]", "]") 87 | .replace("\t", " ") 88 | .replace("\r", " ") 89 | .replace("\n", " ") 90 | 91 | /** 非猫猫码文本消息转义。如果[str]为null则返回null */ 92 | fun encodeTextOrNull(str: String?): String? = str?.let { encodeText(it) } 93 | 94 | /** 猫猫码参数值消息转义 */ 95 | fun encodeParams(str: String): String = 96 | str.replace("&", "&") 97 | .replace("[", "[") 98 | .replace("]", "]") 99 | .replace("=", "=") 100 | .replace(",", ",") 101 | .replace("\t", " ") 102 | .replace("\r", " ") 103 | .replace("\n", " ") 104 | 105 | /** 猫猫码参数值消息转义。如果[str]为null则返回null */ 106 | fun encodeParamsOrNull(str: String?): String? = str?.let { encodeParams(it) } 107 | 108 | } 109 | 110 | public fun String.enCatParam(): String = CatEncoder.encodeParams(this) 111 | public fun String.enCatText(): String = CatEncoder.encodeText(this) 112 | 113 | 114 | /** 115 | * 猫猫码的操作工具类 116 | */ 117 | public object CatCodeUtil : NekoAibo("CAT") { 118 | @JvmStatic 119 | val instance 120 | get() = this 121 | 122 | override val catCodeHead: String = CAT_HEAD 123 | /** 124 | * 获取一个String为载体的[模板][CodeTemplate] 125 | * @see StringTemplate 126 | */ 127 | override val stringTemplate: CodeTemplate get() = StringTemplate 128 | 129 | /** 130 | * 获取[Neko]为载体的[模板][CodeTemplate] 131 | * @see NekoTemplate 132 | */ 133 | override val nekoTemplate: CodeTemplate get() = NekoTemplate 134 | 135 | /** 136 | * 构建一个String为载体类型的[构建器][CodeBuilder] 137 | */ 138 | override fun getStringCodeBuilder(type: String, encode: Boolean): CodeBuilder = StringCodeBuilder(type, encode) 139 | 140 | /** 141 | * 构建一个[Neko]为载体类型的[构建器][CodeBuilder] 142 | */ 143 | override fun getNekoBuilder(type: String, encode: Boolean): CodeBuilder = NekoBuilder(type) 144 | 145 | /** 146 | * 构建一个[Neko]为载体类型的[懒加载构建器][LazyCodeBuilder] 147 | */ 148 | override fun getLazyNekoBuilder(type: String, encode: Boolean): LazyCodeBuilder = LazyNekoBuilder(type) 149 | 150 | } 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/main/java/catcode/collection/NekoMap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | package catcode.collection 14 | 15 | /** 16 | * 一个类似[Map]的猫猫Map。 17 | * 18 | * 因为[Map]在`Java`中进行实现还是会有些问题,所以杂事使用原生Map,并提供一个转化为[Map]的接口。 19 | * 20 | * 这样也可以为`Java`的调用提供更多的限制。 21 | */ 22 | public interface NekoMap { 23 | 24 | /** 25 | * 转化为 [Map]。 26 | */ 27 | fun toMap(): Map 28 | 29 | /** 30 | * 此码中的所有键值对。 31 | */ 32 | val entries: Set> 33 | 34 | /** 35 | * 此码中的所有键。 36 | */ 37 | val keys: Set 38 | 39 | /** 40 | * 此码中的所有值。 41 | */ 42 | val values: Collection 43 | 44 | /** 45 | * 此码中的键值对参数数量。 46 | */ 47 | val size: Int 48 | 49 | /** 50 | * 是否包含某个键。 51 | */ 52 | fun containsKey(key: K): Boolean 53 | 54 | /** 55 | * 是否包含某个值。 56 | */ 57 | fun containsValue(value: @UnsafeVariance V): Boolean 58 | 59 | /** 60 | * 是否没有参数。 61 | */ 62 | fun isEmpty(): Boolean 63 | 64 | /** 65 | * 获取某个键对应的值。 66 | */ 67 | operator fun get(key: K): V? 68 | 69 | /** 70 | * 获取某个键对应的值。如果不存在则返回一个默认值。 71 | */ 72 | @JvmDefault 73 | fun getOrDefault(key: K, defaultValue: @UnsafeVariance V): V = get(key) ?: defaultValue 74 | 75 | /** 76 | * foreach entries. 77 | */ 78 | fun forEach(action: (K, V) -> Unit) { 79 | entries.forEach { 80 | action(it.key, it.value) 81 | } 82 | } 83 | } 84 | 85 | 86 | /** 87 | * save as [MutableNekoMap.put], but is operator fun. 88 | */ 89 | public operator fun MutableNekoMap.set(key: K, value: V): V? = put(key, value) 90 | 91 | 92 | /** 93 | * [NekoMap] 委托为 [Map] 94 | */ 95 | public open class NekoMapDelegation(protected val nekoMap: NekoMap) : Map { 96 | override val entries: Set> 97 | get() = nekoMap.entries 98 | override val keys: Set 99 | get() = nekoMap.keys 100 | override val size: Int 101 | get() = nekoMap.size 102 | override val values: Collection 103 | get() = nekoMap.values 104 | 105 | override fun containsKey(key: K): Boolean = nekoMap.containsKey(key) 106 | override fun containsValue(value: V): Boolean = nekoMap.containsValue(value) 107 | override fun get(key: K): V? = nekoMap[key] 108 | override fun isEmpty(): Boolean = nekoMap.isEmpty() 109 | } 110 | 111 | 112 | /** 113 | * [NekoMap] 委托为 [Map] 114 | */ 115 | public fun nekoToMap(nekoMap: NekoMap): Map = NekoMapDelegation(nekoMap) 116 | 117 | /** 118 | * [NekoMap] 委托为 [Map] 119 | */ 120 | public fun NekoMap.mapDelegation(): Map = nekoToMap(this) 121 | 122 | 123 | /** 124 | * [NekoMap] 委托为 [Map] 125 | */ 126 | public open class MutableNekoMapDelegation(nekoMap: MutableNekoMap) : NekoMapDelegation(nekoMap), 127 | MutableMap { 128 | 129 | private val mutableNekoMap get() = (nekoMap as MutableNekoMap) 130 | 131 | override val entries: MutableSet> 132 | get() = mutableNekoMap.entries 133 | override val keys: MutableSet 134 | get() = mutableNekoMap.keys 135 | override val values: MutableCollection 136 | get() = mutableNekoMap.values 137 | override fun clear() { 138 | mutableNekoMap.clear() 139 | } 140 | 141 | override fun put(key: K, value: V): V? = mutableNekoMap.put(key, value) 142 | 143 | override fun putAll(from: Map) { 144 | from.forEach { (k: K, v: V) -> 145 | mutableNekoMap.put(k, v) 146 | } 147 | 148 | } 149 | 150 | override fun remove(key: K): V? = mutableNekoMap.remove(key) 151 | } 152 | 153 | 154 | /** 155 | * 一个类似[MutableMap]的猫猫可变Map。 156 | * 157 | * 因为[Map]在`Java`中进行实现还是会有些问题,所以杂事使用原生Map,并提供一个转化为[Map]的接口。 158 | * 159 | * 这样也可以为`Java`的调用提供更多的限制。 160 | */ 161 | public interface MutableNekoMap : NekoMap { 162 | /** 163 | * 转化为[MutableMap]。 164 | */ 165 | override fun toMap(): MutableMap 166 | 167 | /** 168 | * 置入一个键对应的值。 169 | * 170 | * @return 如果存在旧值,返回旧值。 171 | */ 172 | fun put(key: K, value: V): V? 173 | 174 | /** 175 | * 移除一个可能存在的键对应的值。 176 | * 177 | * @return 被移除的值。可能不存在。 178 | */ 179 | fun remove(key: K): V? 180 | 181 | /** 182 | * 存入多个键值对。 183 | */ 184 | fun putAll(from: NekoMap) { 185 | putAll(from.toMap()) 186 | } 187 | 188 | /** 189 | * 存入多个键值对。 190 | */ 191 | fun putAll(from: Map) { 192 | from.forEach { (k: K, v: V) -> 193 | put(k, v) 194 | } 195 | } 196 | 197 | 198 | /** 199 | * 此码中的所有键值对。 200 | */ 201 | override val entries: MutableSet> 202 | 203 | /** 204 | * 此码中的所有键。 205 | */ 206 | override val keys: MutableSet 207 | 208 | /** 209 | * 此码中的所有值。 210 | */ 211 | override val values: MutableCollection 212 | 213 | /** 214 | * 清除所有的键值对。 215 | */ 216 | fun clear() 217 | 218 | } -------------------------------------------------------------------------------- /src/main/java/catcode/collection/LazyMap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet All rights reserved. 3 | * Project parent 4 | * File LazyMap.kt 5 | * 6 | * You can contact the author through the following channels: 7 | * github https://github.com/ForteScarlet 8 | * gitee https://gitee.com/ForteScarlet 9 | * email ForteScarlet@163.com 10 | * QQ 1149159218 11 | */ 12 | 13 | @file:JvmName("LazyMaps") 14 | package catcode.collection 15 | 16 | 17 | 18 | /** 19 | * lazy map, 可以通过一个 `Map>` 实例进行实例化, 20 | * 其内部的value是懒加载的。 21 | * @param K Key type. 22 | * @param V Value type. 23 | * @property map Map> map instance. 24 | * @constructor 25 | */ 26 | public class LazyMap 27 | @JvmOverloads 28 | constructor( 29 | internal val map: Map> = mapOf(), 30 | ) : Map { 31 | @Suppress("UNCHECKED_CAST") 32 | override val entries: Set> 33 | get() = map.entries.asSequence().map { SimpleEntry(it.key, it.value.value) }.toSet() 34 | 35 | override val keys: Set 36 | get() = map.keys 37 | 38 | override val size: Int 39 | get() = map.size 40 | 41 | override val values: Collection 42 | get() = map.values.map { it.value } 43 | 44 | 45 | override fun containsKey(key: K): Boolean = map.containsKey(key) 46 | 47 | /** 48 | * 判断是否存在某个value。 49 | * 尽可能不要使用此方法,会导致大量的lazy-value被初始化。 50 | */ 51 | override fun containsValue(value: V): Boolean = map.values.any { it.value == value } 52 | 53 | override operator fun get(key: K): V? = map[key]?.value 54 | 55 | override fun isEmpty(): Boolean = map.isEmpty() 56 | 57 | fun copy(): LazyMap = LazyMap(map) 58 | 59 | 60 | override fun toString(): String { 61 | return if (isEmpty()) "{}" else { 62 | val builder = StringBuilder().append('{') 63 | 64 | var first = true 65 | 66 | map.forEach { (k, v) -> 67 | if (!first) { 68 | builder.append(",") 69 | } else { 70 | first = false 71 | } 72 | builder.append(k).append('=').append(v.value) 73 | } 74 | 75 | builder.append('}').toString() 76 | } 77 | } 78 | } 79 | 80 | 81 | public fun Map.toLazyMap(): LazyMap { 82 | return LazyMap(mapValues { lazyValue(it.value) }) 83 | } 84 | 85 | 86 | public fun Map.toMutableLazyMap(): MutableLazyMap { 87 | return MutableLazyMap(mapValues { lazyValue(it.value) }.toMutableMap()) 88 | } 89 | 90 | 91 | 92 | 93 | /** 94 | * lazy map, 可以通过一个 `MutableMap>` 实例进行实例化, 95 | * 其内部的value是懒加载的。 96 | * 97 | * 额外提供了一个 [put] 方法以实现存入可计算的懒加载值。 98 | * 99 | * @param K Key type. 100 | * @param V Value type. 101 | * @property mode Lazy的懒加载策略,在 [put] 的时候会使用此策略。默认为 [LazyThreadSafetyMode.PUBLICATION]. 102 | * @property map Map> map instance. 103 | * @constructor 104 | */ 105 | public data class MutableLazyMap 106 | @JvmOverloads 107 | constructor( 108 | internal val map: MutableMap> = mutableMapOf(), 109 | private val mode: LazyThreadSafetyMode = LazyThreadSafetyMode.PUBLICATION, 110 | ) : MutableMap { 111 | 112 | override val size: Int 113 | get() = map.size 114 | 115 | override fun containsKey(key: K): Boolean = map.containsKey(key) 116 | 117 | override fun containsValue(value: V): Boolean = map.values.any { it.value == value } 118 | 119 | override operator fun get(key: K): V? = map[key]?.value 120 | 121 | override fun isEmpty(): Boolean = map.isEmpty() 122 | 123 | override val keys: MutableSet 124 | get() = map.keys 125 | 126 | override val entries: MutableSet> 127 | get() = map.entries.asSequence().map { SimpleMutableEntry(it.key, it.value.value) }.toMutableSet() 128 | 129 | 130 | override val values: MutableCollection 131 | get() = map.values.asSequence().map { it.value }.toMutableList() 132 | 133 | 134 | override fun clear() { 135 | map.clear() 136 | } 137 | 138 | override fun put(key: K, value: V): V? { 139 | return map.put(key, lazyValue(value))?.value 140 | } 141 | 142 | /** 143 | * 添加一个待计算的懒加载值。 144 | * @param key Key. 145 | * @param initializer 初始化函数. 146 | * @return V? 147 | */ 148 | fun put(key: K, initializer: () -> V): V? { 149 | return map.put(key, lazy(mode = mode, initializer = initializer))?.value 150 | } 151 | 152 | override fun putAll(from: Map) { 153 | map.putAll(from.mapValues { lazyValue(it.value) }) 154 | } 155 | 156 | 157 | override fun remove(key: K): V? = map.remove(key)?.value 158 | 159 | 160 | fun copy(): MutableLazyMap = MutableLazyMap(map, mode) 161 | } 162 | 163 | 164 | /** 165 | * 将一个 [MutableLazyMap] 作为一个 [LazyMap]. 166 | * 不会复制其中的什么属性,因此如果原来的 [MutableLazyMap] 发生变动,[LazyMap] 也会变。 167 | * @receiver MutableLazyMap 168 | * @return LazyMap 169 | */ 170 | public fun MutableLazyMap.asLazyMap(): LazyMap = LazyMap(map) 171 | 172 | /** 173 | * 将一个 [MutableLazyMap] 作为一个 [LazyMap]. 174 | * 175 | * 会将 [MutableLazyMap.map] 复制一份,原 [MutableLazyMap] 发送变化不会变动结果。 176 | * 177 | * @receiver MutableLazyMap 178 | * @return LazyMap 179 | */ 180 | public fun MutableLazyMap.toLazyMap(): LazyMap = LazyMap(map.toMap()) 181 | 182 | /** 183 | * 将一个 [LazyMap] 转化为一个 [MutableLazyMap]. 184 | * 185 | * 会将 [LazyMap.map] 复制一份。 186 | * 187 | * @receiver LazyMap 188 | * @return MutableLazyMap 189 | */ 190 | public fun LazyMap.toMutableMap(): MutableLazyMap = MutableLazyMap(map.toMutableMap()) 191 | -------------------------------------------------------------------------------- /src/main/java/catcode/CatIterators.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | package catcode 14 | 15 | 16 | /** 17 | * 基于字符串操作的kq迭代器父类 18 | */ 19 | internal abstract class BaseCatIterator(protected val code: String): Iterator { 20 | init { 21 | if (!code.startsWith(CAT_HEAD) || !code.endsWith(CAT_END)) { 22 | throw IllegalArgumentException("text '$code' is not a cat code text.") 23 | } 24 | } 25 | 26 | // 从索引0开始寻找 27 | /** 下一个开始的查询索引 */ 28 | protected var index: Int = 0 29 | 30 | /** 下一个索引位 */ 31 | protected abstract fun nextIndex(): Int 32 | 33 | 34 | /** 35 | * 判断是否还有下一个参数 36 | */ 37 | override fun hasNext(): Boolean { 38 | // 如果有逗号,说明还有键值对 39 | return index >= 0 && nextIndex() > 0 40 | } 41 | 42 | } 43 | 44 | /** 45 | * 文本猫猫码迭代器,从一串文本中迭代出其中的猫猫码 46 | * @since 1.1-1.11 47 | */ 48 | internal class CatTextIterator(private val text: String, type: String = "") : Iterator { 49 | private var i = -1 50 | private var ti = 0 51 | private var e = 0 52 | private val het = CAT_HEAD + type 53 | private val ent = CAT_END 54 | 55 | private var next: String? = null 56 | private var get = true 57 | 58 | /** 59 | * Returns `true` if the iteration has more elements. 60 | */ 61 | override fun hasNext(): Boolean { 62 | if (!get) { 63 | return next != null 64 | } 65 | get = false 66 | do { 67 | ti = text.indexOf(het, e) 68 | if (ti >= 0) { 69 | e = text.indexOf(ent, ti) 70 | if (e >= 0) { 71 | i++ 72 | next = text.substring(ti, e + 1) 73 | } else { 74 | e = ti + 1 75 | } 76 | } 77 | } while (next == null && (ti >= 0 && e >= 0)) 78 | 79 | return next != null 80 | } 81 | 82 | /** 83 | * Returns the next element in the iteration. 84 | */ 85 | override fun next(): String { 86 | val n = next 87 | next = null 88 | get = true 89 | return n!! 90 | } 91 | } 92 | 93 | 94 | /** 95 | * 一串儿猫猫码字符串中的键迭代器 96 | * @since 1.8.0 97 | */ 98 | internal class CatParamKeyIterator(code: String): BaseCatIterator(code) { 99 | 100 | 101 | override fun nextIndex(): Int { 102 | return code.indexOf(CAT_PS, if (index == 0) 0 else index + 1) 103 | } 104 | 105 | /** 106 | * Returns the next element in the iteration. 107 | */ 108 | override fun next(): String { 109 | if(!hasNext()) throw NoSuchElementException() 110 | 111 | // 下一个逗号所在处 112 | index = nextIndex() 113 | // 下一个kv切割符所在 114 | val nextKv = code.indexOf(CAT_KV, index) 115 | return code.substring(index+1, nextKv) 116 | } 117 | 118 | } 119 | 120 | 121 | 122 | /** 123 | * 一串儿猫猫码字符串中的值迭代器 124 | * 得到的值会进行反转义。 125 | * @since 1.8.0 126 | */ 127 | internal class CatParamValueIterator(code: String): BaseCatIterator(code) { 128 | 129 | 130 | override fun nextIndex(): Int { 131 | return code.indexOf(CAT_KV, if(index == 0) 0 else index + 1) 132 | } 133 | 134 | /** 135 | * 判断是否还有下一个参数 136 | */ 137 | override fun hasNext(): Boolean { 138 | // 如果有逗号,说明还有键值对 139 | return index >= 0 && nextIndex() > 0 140 | } 141 | 142 | /** 143 | * Returns the next element in the iteration. 144 | */ 145 | override fun next(): String { 146 | if(!hasNext()) throw NoSuchElementException() 147 | 148 | // 下一个逗号所在处 149 | index = nextIndex() 150 | // 下一个逗号或结尾符所在处 151 | var nextSplit = code.indexOf(CAT_PS, index) 152 | if(nextSplit < 0){ 153 | nextSplit = code.lastIndex 154 | } 155 | return CatDecoder.decodeParams(code.substring(index+1, nextSplit)) 156 | } 157 | 158 | } 159 | 160 | 161 | 162 | 163 | /** 164 | * 一串儿猫猫码字符串中的键值对迭代器 165 | * 得到的值会进行反转义。 166 | * @since 1.8.0 167 | */ 168 | internal class CatParamKVIterator(code: String): BaseCatIterator>(code) { 169 | 170 | 171 | override fun nextIndex(): Int { 172 | return code.indexOf(CAT_PS, if(index == 0) 0 else index + 1) 173 | } 174 | 175 | 176 | /** 177 | * Returns the next element in the iteration. 178 | */ 179 | override fun next(): CatKV { 180 | if(!hasNext()) throw NoSuchElementException() 181 | 182 | // 下一个逗号所在处 183 | index = nextIndex() 184 | // 下下一个逗号或结尾符所在处 185 | var nextSplit = code.indexOf(CAT_PS, index + 1) 186 | if(nextSplit < 0){ 187 | nextSplit = code.lastIndex 188 | } 189 | val substr = code.substring(index + 1, nextSplit) 190 | val keyValue = substr.split(CAT_KV) 191 | return keyValue[0] cTo CatDecoder.decodeParams(keyValue[1]) 192 | } 193 | 194 | } 195 | 196 | 197 | /** 198 | * Entry iterator. 199 | */ 200 | internal class CatParamEntryIterator(code: String): BaseCatIterator>(code) { 201 | 202 | override fun nextIndex(): Int { 203 | return code.indexOf(CAT_PS, if(index == 0) 0 else index + 1) 204 | } 205 | 206 | /** 207 | * 判断是否还有下一个参数 208 | */ 209 | override fun hasNext(): Boolean { 210 | // 如果有逗号,说明还有键值对 211 | return index >= 0 && nextIndex() > 0 212 | } 213 | 214 | /** 215 | * Returns the next element in the iteration. 216 | */ 217 | override fun next(): Map.Entry { 218 | if(!hasNext()) throw NoSuchElementException() 219 | 220 | // 下一个逗号所在处 221 | index = nextIndex() 222 | // 下下一个逗号或结尾符所在处 223 | var nextSplit = code.indexOf(CAT_PS, index + 1) 224 | if(nextSplit < 0){ 225 | nextSplit = code.lastIndex 226 | } 227 | val substr = code.substring(index + 1, nextSplit) 228 | val keyValue = substr.split(CAT_KV) 229 | return KqEntry(keyValue[0], CatDecoder.decodeParams(keyValue[1])) 230 | } 231 | 232 | } 233 | 234 | 235 | /** 236 | * 针对于[Map.Entry]的简易实现 237 | */ 238 | internal data class KqEntry(override val key: String, override val value: String) : Map.Entry 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | - 😺 CatCode 😺 - 5 |

6 | 7 | github 8 | 9 |    |    10 | 11 | gitee 12 |
13 | > 感谢 Simple Robot 开发团队成员制作的猫猫logo <
14 | > 如果有点击一下⭐的话,猫猫会很开心哦~ <
15 | 16 | 17 | 18 | 19 |
20 | 21 | ***** 22 | 23 |
24 |

25 | 猫猫码(Cat code),一个可爱的通用特殊码,CQ码的精神延续。 26 |

27 |
28 | 29 | 30 | 猫猫码是一个具有特定格式的字符串格式特殊码,规则为`[CAT:xxx,param=value,param=value,...]`的格式,其中: 31 | 32 | - 以`[`开头,`]`结尾。 33 | - 开头后跟猫猫码的大类型(大小写数字或下划线,标准应为`CAT`) 34 | - 开大类型后为此码的小类型(大小写数字或下划线),与大类型之间使用冒号`:`分割。 35 | - 参数为多个key不重复的键值对,一对参数使用`=`连接键与值,多对参数使用`,`分割。 36 | - 可以没有参数。 37 | - 区分大小写。 38 | 39 | ## 🎉 CatCode2! 40 | 41 | [**CatCode2**](https://github.com/ForteScarlet/CatCode2) 现已公开!立刻前往 [👉**CatCode2**](https://github.com/ForteScarlet/CatCode2) 预览、体验、点赞与贡献! 42 | 43 | ## 应用 44 | 45 | 猫猫码将会被作为[simpler-robot](https://github.com/ForteScarlet/simpler-robot)框架 (即[simple-robot](https://github.com/ForteScarlet/simple-robot-core)的2.x版本) 的送信特殊码使用,以取代曾经的CQ码。 46 | 47 | 当然,猫猫码也属于一种消息格式,某种程度上猫猫码也可以表示为类似于`json`等消息类型,但是我不认为他会比json更好用。 48 | 49 | 但是如果你觉得它很可爱,也欢迎使用~ 50 | 51 | 52 | **如果你愿意送给猫猫一颗右上角的星星,猫猫会很开心的喔~** 53 | 54 | 55 | ### **猫猫是严格的。** 56 | 为了避免混淆整个存在猫猫码的文本中不允许出现:`[`、`]`、`&` 字符和制表符与换行符,猫猫码文本内中除了上述字符外,还不允许出现`,`与`=`字符。 57 | 因此,猫猫码的转义规则为: 58 | - `&` -> `&` 59 | - `[` -> `[` 60 | - `]` -> `]` 61 | - `,` -> `,` 62 | - `=` -> `=` 63 | - `\n` -> ` ` 64 | - `\r` -> ` ` 65 | - `\t` -> ` ` 66 | 67 | 68 | ### **猫猫是慵懒的。** 69 | 本库提供了丰富地猫猫码解析、封装相关内容。 70 | 71 | 一切工具类相关的均以`Cat`开头(或包含),例如`CatDecoder`、`CatCodeUtil`等, 72 | 而针对于猫猫码的封装类相关,大部分均以`Neko`为开头(或包含)命名。例如`Neko`、`Nyanko(FastNeko)`、`MapNeko`。 73 | 74 | ### **猫猫是多样的。** 75 | 猫猫码封装接口有`Neko`(保守猫猫,即参数不可变)、`MutableNeko`(善变猫猫,可动态变更参数,但是不再基于字符串操作)、`EmptyNeko`(佛系猫猫,即静态的空参实例)、 76 | 以及上述几个类型所各自对应的`NoraNeko`(野良猫猫,即猫猫码大类型不是`CAT`但是规则与猫猫码一致的野良猫猫码) 77 | 78 | ### **猫猫是警觉的。** 79 | 猫猫码工具主要基于字符串操作,减少资源占用与解析封装损耗,效率更高(尤其是在使用不变猫猫的时候)。 80 | 81 | ### **猫猫是包容的。** 82 | 任何符合规则:`[TYPE:type,param=value,param=value,...]`的特殊码均可以视为猫猫码,尽管`TYPE`并不是`CAT`。 83 | 84 | 这类其他类型的“猫猫码”称为`WildcatCode(野良猫码)`, 可以通过`WildcatCodeUtil`工具来实现对应的解析, 通过`NoraNeko`实例类型作为封装。 85 | 86 | 87 | 88 | 89 | ## 使用方式 90 | 91 | ## JVM 92 | 93 | ### Maven 94 | 95 | ※ 版本可参考上面的版本号小图标。 96 | 97 | ```xml 98 | 99 | love.forte 100 | catcode 101 | ${version} 102 | 103 | ``` 104 | 105 | ## JS 106 | 将会通过kotlin多平台实现,提供js库,并发布于github release\[、Maven和npm(待议)]。 107 | 108 | 抢先预览:[CatCode-multiplatform](https://github.com/ForteScarlet/CatCode-multiplatform) 109 | 110 | 111 | ## Native 112 | 将会通过kotlin多平台实现,提供`.h`头文件与对应的`.dll`(windows)、`.so`(linux)文件。 113 | 114 | 抢先预览:[CatCode-multiplatform](https://github.com/ForteScarlet/CatCode-multiplatform) 115 | 116 | 117 |
118 |
119 | 120 | > 你可以基于标准CAT码格式自行实现。 121 | 122 | 123 | ## 文档 124 | 文档地址:https://www.yuque.com/simpler-robot/catcode 125 | 126 | > 文档尚待建设。 127 | 128 | 129 | ## 常用猫猫 130 | 131 | 猫猫码存在一些十分常用的类型,这些类型的参数拥有已经约定俗成的参数格式。 132 | 133 | #### text 文本 134 | text类型是一个基础的类型,其代表了一段不包含cat码的普通文本。 135 | text类型的cat码只有一个参数 `text`, 其代表了文本的正文内容。 136 | 137 | 之所以会存在text类型的猫猫码是因为有些场景下可能会需要将一个文本内容表示为一个 `Neko` 实例。 138 | 139 | 140 | #### at 艾特 141 | 142 | at类型可以说是十分常见了,其代表 **@** 了某个人。 143 | at类型存在几个常见参数:`code`、`all`。 144 | 145 | `code`代表其对应被at的用户的账号,是字符串或者数字类型。 146 | 147 | 例如:`[CAT:at,code=1149159218]`。 148 | 149 | 而`all`代表此CAT是否代表**at全体**,类型为bool类型。 150 | 151 | 例如:`[CAT:at,all=true]`。 152 | 153 | 当然,也存在使用`code=all`来代表at全体的效果。 154 | 155 | 通常来讲,`code`参数与`at`参数二者只会存在一个。 156 | 157 | 158 | #### image 图片 159 | 160 | image类型代表一个**图片类型**的消息。 161 | image类型存在几个常见参数:`id`、`file`、`url`、`flash`。 162 | 163 | `id`一般代表这个图片的一个网络服务器标识。 164 | 165 | `file`一般代表这个图片的本地文件路径,有时候也会作为`id`参数的代替。 166 | 167 | `url`代表这个图片的网络路径。 168 | 169 | `flash`是bool类型,代表是否作为一个**闪照**或者**可销毁**图片。 170 | 171 | 例如:`[CAT:image,id=1,file=1.jpg,flash=true]` 172 | 173 | 通常来讲,`file`、`url`两个参数可能同时存在,或至少出现其中一种,而`id`则根据是否实际存在id值而决定。 174 | 175 | 176 | #### voice 语音 177 | voice类型代表一个**语音类型**的消息。 178 | voice类型存在几个常见参数:`id`、`file`、`url`,其含义与`image`类型基本一致。 179 | 180 | 181 | #### face 表情 182 | face类型也是一个十分常见的类型,其代表了一个通讯应用中的普通默认表情。 183 | 其存在一个 `id` 参数代表表情的标识。 184 | 185 | 例如:`[CAT:face,id=1]` 186 | 187 | 188 | #### share 分享 189 | share类型是一种十分常见的类型,但是它不一定能够被所有平台支持。尽管如此,他依然十分常见。 190 | 分享类型一般会存在4种常见参数:`title`、`content`、`url`、`image`, 191 | 其中: 192 | 193 | `title` 代表分享链接的标题。 194 | 195 | `content` 代表分享链接的简述。 196 | 197 | `url` 代表分享的链接。 198 | 199 | `image` 代表分享的链接的封面。 200 | 201 | 而这其中,`url`参数是必然存在的。 202 | 203 | 例如:`[CAT:share,url=www.baidu.com,title=百度一下,content=百度一下你就知道]` 204 | 205 | 206 | 207 | 208 | 209 | ## 构建Cat码 210 | 211 | ### Java 212 | 213 | #### 1. 通过Builder构建CatCode 214 | 215 | ```java 216 | // get util instance. 217 | final CatCodeUtil catUtil = CatCodeUtil.INSTANCE; 218 | 219 | // 构建一个猫猫码 - string 220 | // 例如构建一个字符串类型的: [CAT:at,code=123456,name=forte,age=12] 221 | // 1. StringCodeBuilder 222 | final CodeBuilder stringBuilder = catUtil.getStringCodeBuilder("at"); 223 | String catCode1 = stringBuilder 224 | .key("code").value(123456) 225 | .key("name").value("forte") 226 | .key("age").value(12) 227 | .build(); 228 | 229 | System.out.println(catCode1); 230 | 231 | // 构建一个猫猫码 - Neko 232 | // 例如构建一个Neko类型的: [CAT:image,file=1.jpg,dis=true] 233 | // 2. NekoBuilder 234 | final CodeBuilder nekoBuilder = catUtil.getNekoBuilder("image"); 235 | Neko catCode2 = nekoBuilder 236 | .key("file").value("1.jpg") 237 | .key("dis").value(true) 238 | .build(); 239 | 240 | System.out.println(catCode2); 241 | ``` 242 | 243 | #### 2. 直接获取部分无参模板 244 | ```java 245 | // NekoObjects中的可获取值均为部分无参数常量,例如at全体。 246 | final Neko alAll = NekoObjects.getNekoAtAll(); 247 | ``` 248 | 249 | #### 3. 通过工具类获取 250 | ```java 251 | final String at = CatCodeUtil.toCat("at", "qq=123"); 252 | ``` 253 | 254 | 255 | 256 |
257 |
258 |
259 | 260 | 261 | 262 | > Neko : ねこ(猫),意为“猫”。 263 | > 264 | > NoraNeko : のらねこ(野良猫),意为“野猫”。 265 | > 266 | > 267 | > 未来计划通过kotlin发布全平台通用库,可通过JVM/JS/Native操作猫猫码。 268 | > 代码移植为全平台没问题,native测试执行也没啥问题,但是我不知道怎么发布,所以。。再说吧。 269 | 270 | 271 | ### LICENSE 272 | see [LICENSE](./LICENSE) . 273 | -------------------------------------------------------------------------------- /src/main/java/catcode/Neko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("unused") 14 | @file:JvmName("CatSymbolConstant") 15 | 16 | package catcode 17 | 18 | import catcode.codes.MapNeko 19 | import catcode.codes.Nyanko 20 | import catcode.collection.MutableNekoMap 21 | import catcode.collection.NekoMap 22 | 23 | 24 | public const val CAT_TYPE = "CAT" 25 | public const val CAT_HEAD = "[$CAT_TYPE:" 26 | public const val CAT_END = "]" 27 | /** cat params split. */ 28 | public const val CAT_PS = "," 29 | /** cat key-value. */ 30 | public const val CAT_KV = "=" 31 | 32 | 33 | 34 | /** 35 | * cat码匹配正则。 36 | * 37 | * 匹配为:`[codeType:type(,param=value)*]`, 其中不可能出现其他的 `[` 或 `]` 字符。 38 | * 例如: 39 | * - 正确的:`[CAT:image,file=abc.jpg]` 40 | * - 正确的:`[CQ:image,file=abc.jpg]` 41 | * - 错误的:`[CAT:image,[file=abc.jpg]` 42 | * - 错误的:`[CQ:image;file=abc.jpg]` 43 | * 44 | * cat码中: 45 | * - codeType标准应为`CAT`, 非标准则为大小写字母、数字或下划线。 46 | * - type标准应为大小写字母、数字或下划线。 47 | * - codeType与type使用 `:` 分割。 48 | * - 尽可能不应出现空格。 49 | * - 不应出现换行。 50 | * 51 | */ 52 | public val nekoMatchRegex: Regex = Regex("\\[(\\w+:\\w+(,((?![\\[\\]]).)+?)*)]") 53 | 54 | 55 | /** 56 | * 获取一个[NoraNeko]的code head。 57 | * 建议大写。 58 | */ 59 | public fun catHead(codeType: String): String = "[$codeType:" 60 | 61 | 62 | /** 63 | * 定义一个不可变的 Neko码 标准接口 64 | * - KQCode实例应当实现[Map]接口,使其可以作为一个**不可变**Map使用。 65 | * - KQCode实例应当实现[CharSequence]接口,其可以作为一个字符序列以得到猫猫码字符串 66 | * 67 | * 其参数是不可变的,如果需要一个可变参数的实例,参考方法[mutable]与其返回的接口类型[MutableNeko] 68 | * 如果想要获得一个纯空参的实例,参考[EmptyNeko] 69 | * 70 | * 建议子类通过私有构造+ 静态/伴生对象 方法来获取实例,例如 [MapNeko.byCode] [Nyanko.byCode] 71 | * 而不是直接通过构造方法。 72 | * 73 | * @since 1.8.0 74 | */ 75 | public interface Neko : NekoMap, CharSequence { 76 | 77 | @JvmDefault 78 | val codeType: String 79 | get() = CAT_TYPE 80 | 81 | /** 82 | * 获取Code的类型。例如`at` 83 | */ 84 | val type: String 85 | 86 | /** 87 | * 获取转义前的值。一般普通的[get]方法得到的是反转义后的。 88 | * 此处为保留原本的值不做转义。 89 | */ 90 | fun getNoDecode(key: String): String? 91 | 92 | /** 93 | * 与其他字符序列拼接为[Msgs]实例 94 | */ 95 | operator fun plus(other: CharSequence): Msgs = Msgs(collection = listOf(this, other)) 96 | 97 | /** 98 | * 转化为可变参的[MutableNeko]。应当返回一个新的实例。 99 | */ 100 | fun mutable(): MutableNeko 101 | 102 | /** 103 | * 转化为不可变类型[Neko]。应当返回一个新的实例。 104 | */ 105 | fun immutable(): Neko 106 | 107 | 108 | companion object Of { 109 | 110 | /** 111 | * 得到一个空参的[Neko]实例。 112 | */ 113 | @JvmStatic 114 | fun ofType(type: String): Neko = EmptyNeko(type) 115 | 116 | /** 117 | * 通过猫猫码字符串得到一个[Neko]实例 118 | */ 119 | @JvmStatic 120 | fun of(code: String): Neko = Nyanko.byCode(code) 121 | 122 | /** 123 | * 从猫猫码字符串转到KQCode 124 | * 125 | * 1.8.0开始默认使用[Nyanko]作为静态工厂方法的[Neko]实例载体。 126 | * [Nyanko]是以字符串操作为基础的,因此不需要进行额外的转义。 127 | * 128 | * @since 1.1-1.11 129 | * @since 1.8.0 130 | * @param text 猫猫码字符串的正文 131 | * @param decode 因为这段猫猫码字符串可能已经转义过了,此处是否指定其转化的时候解码一次。默认为true 132 | */ 133 | @JvmStatic 134 | @Deprecated("just use of(text)", ReplaceWith("FastKQCode(text)", "com.simplerobot.modules.utils.FastKQCode")) 135 | fun of(text: String, decode: Boolean = true): Neko { 136 | return Nyanko.byCode(text) 137 | } 138 | } 139 | 140 | 141 | } 142 | 143 | /** 144 | * 定义一个可变的[Neko]标准接口。 145 | * - `MutableNeko`实例应当实现[MutableMap]接口,使其可以作为一个 **可变** Map使用。 146 | */ 147 | interface MutableNeko : Neko, MutableNekoMap { 148 | /** 149 | * type 也是可变类型 150 | */ 151 | override var type: String 152 | } 153 | 154 | /** 155 | * 定义一个任意类型的[Neko]实例。 156 | * 157 | * > nora neko -> のらねこ -> 野良猫 -> 野猫 , 即不是标准意义的cat code。 158 | * 159 | * 例如,`[CAT:at,code=123]`即为标准cat code, 160 | * 而`[CQ:at,code=123]` 则不是标注cat code, 但是除了code类型以外的规则全部一样。 161 | * 162 | * [NoraNeko] 接口继承自 [Neko] 接口, 并提供一个 [codeType] 属性以指定code类型。 163 | * 164 | */ 165 | interface NoraNeko : Neko { 166 | @JvmDefault 167 | override val codeType: String 168 | } 169 | 170 | 171 | /** 172 | * 定义一个任意类型的[MutableNeko]实例。 173 | * 174 | * > nora neko -> のらねこ -> 野良猫 -> 野猫 , 即不是标准意义的cat code。 175 | * 176 | * 例如,`[CAT:at,code=123]`即为标准cat code, 177 | * 而`[CQ:at,code=123]` 则不是标注cat code, 但是除了code类型以外的规则全部一样。 178 | * 179 | * [MutableNoraNeko] 接口继承自 [MutableNeko] 接口, 并提供一个 [codeType] 属性以指定code类型。 180 | * 181 | */ 182 | interface MutableNoraNeko : MutableNeko { 183 | /** 184 | * Code type也可变 185 | */ 186 | @JvmDefault 187 | override var codeType: String 188 | } 189 | abstract class BaseMutableNoraNeko : MutableNoraNeko 190 | 191 | /** 192 | * 一个纯空参的[Neko]实例。 193 | * 194 | * 此类只有**不可变**状态, 并且应当为无参[Neko]的优先使用类。由于没有参数,因此不存在任何多余的计算与转义。 195 | * 196 | * 由于不存在对应的**可变状态**, 197 | * 因此[mutable]所得到的实例为[love.forte.catcode.codes.MutableMapNeko]实例。 198 | * 199 | */ 200 | public data class EmptyNeko(override val type: String) : Neko { 201 | 202 | private val codeText = "$CAT_HEAD$type$CAT_END" 203 | 204 | override fun toString(): String = codeText 205 | 206 | /** 207 | * 转化为可变参的[MutableNeko] 208 | */ 209 | override fun mutable(): MutableNeko = MapNeko.mutableByCode(codeText) 210 | 211 | /** 212 | * 转化为不可变类型[Neko] 213 | */ 214 | override fun immutable(): Neko = copy() 215 | 216 | override fun toMap(): Map = emptyMap() 217 | override val entries: Set> = emptySet() 218 | override val keys: Set = emptySet() 219 | override val size: Int = 0 220 | override val values: Collection = emptyList() 221 | override fun containsKey(key: String): Boolean = false 222 | override fun containsValue(value: String): Boolean = false 223 | override operator fun get(key: String): String? = null 224 | override fun getNoDecode(key: String): String? = null 225 | override val length: Int = codeText.length 226 | override operator fun get(index: Int): Char = codeText[index] 227 | override fun isEmpty(): Boolean = true 228 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = codeText.subSequence(startIndex, endIndex) 229 | } 230 | 231 | 232 | /** 233 | * 一个纯空参的[NoraNeko]实例。 234 | * 235 | * 此类只有**不可变**状态, 并且应当为无参[NoraNeko]的优先使用类。由于没有参数,因此不存在任何多余的计算与转义。 236 | * 237 | * 由于不存在对应的**可变状态**, 238 | * 因此[mutable]所得到的实例为[love.forte.catcode.codes.MutableMapNeko]实例。 239 | * 240 | */ 241 | public data class EmptyNoraNeko(override val codeType: String, override val type: String) : NoraNeko { 242 | 243 | private val codeText = "${catHead(codeType)}$type$CAT_END" 244 | 245 | override fun toString(): String = codeText 246 | 247 | /** 248 | * 转化为可变参的[MutableNeko] 249 | */ 250 | override fun mutable(): MutableNeko = MapNeko.mutableByCode(codeText) 251 | 252 | /** 253 | * 转化为不可变类型[Neko] 254 | */ 255 | override fun immutable(): Neko = copy() 256 | 257 | override fun toMap(): Map = emptyMap() 258 | override val entries: Set> = emptySet() 259 | override val keys: Set = emptySet() 260 | override val size: Int = 0 261 | override val values: Collection = emptyList() 262 | override fun containsKey(key: String): Boolean = false 263 | override fun containsValue(value: String): Boolean = false 264 | override operator fun get(key: String): String? = null 265 | override fun getNoDecode(key: String): String? = null 266 | override val length: Int = codeText.length 267 | override operator fun get(index: Int): Char = codeText[index] 268 | override fun isEmpty(): Boolean = true 269 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = codeText.subSequence(startIndex, endIndex) 270 | } 271 | 272 | 273 | 274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /src/main/java/catcode/WildcatCodeBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet All rights reserved. 3 | * Project parent 4 | * File WildcatCodeBuilder.kt 5 | * 6 | * You can contact the author through the following channels: 7 | * github https://github.com/ForteScarlet 8 | * gitee https://gitee.com/ForteScarlet 9 | * email ForteScarlet@163.com 10 | * QQ 1149159218 11 | */ 12 | 13 | package catcode 14 | 15 | import catcode.codes.MapNeko 16 | import catcode.collection.MutableLazyMap 17 | 18 | /** 19 | * 野猫码构建器。 20 | */ 21 | public interface WildcatCodeBuilder : CodeBuilder { 22 | /** 23 | * 野猫码的大类型。 24 | */ 25 | val codeType: String 26 | 27 | /** 28 | * catcode param key. 29 | */ 30 | override fun key(key: String): WildcatCodeBuilderKey 31 | 32 | /** 33 | * wildcat builder key like [CodeBuilder.CodeBuilderKey] 34 | */ 35 | public interface WildcatCodeBuilderKey : CodeBuilder.CodeBuilderKey { 36 | override fun value(value: Any?): WildcatCodeBuilder 37 | override fun emptyValue(): WildcatCodeBuilder 38 | } 39 | 40 | } 41 | 42 | /** 43 | * 支持懒加载的 [WildcatCodeBuilder]. 44 | * @param T 45 | */ 46 | public interface LazyWildcatCodeBuilder : WildcatCodeBuilder, LazyCodeBuilder { 47 | 48 | /** 49 | * 指定一个code的key, 并通过这个key设置一个value. 50 | */ 51 | override fun key(key: String): LazyWildcatCodeBuilderKey 52 | 53 | 54 | /** 55 | * 懒加载codeBuilderKey 56 | * @param T 57 | */ 58 | interface LazyWildcatCodeBuilderKey : WildcatCodeBuilder.WildcatCodeBuilderKey, 59 | LazyCodeBuilder.LazyCodeBuilderKey { 60 | override fun value(value: Any?): LazyWildcatCodeBuilder 61 | override fun value(value: () -> Any?): LazyWildcatCodeBuilder 62 | } 63 | } 64 | 65 | 66 | /** 67 | * 以字符串为载体的 [WildcatCodeBuilder] 实现。 68 | */ 69 | public class WildcatStringCodeBuilder 70 | @JvmOverloads 71 | constructor( 72 | override val codeType: String, 73 | override val type: String, 74 | private val encode: Boolean = true 75 | ) : WildcatCodeBuilder { 76 | 77 | /** [StringBuilder] */ 78 | private val appender: StringBuilder = StringBuilder(CAT_HEAD).append(type) 79 | 80 | /** [StringCodeBuilderKey]实例 */ 81 | private val builderKey: StringCodeBuilderKey = StringCodeBuilderKey() 82 | 83 | /** 当前等待设置的key值 */ 84 | @Volatile 85 | private var key: String? = null 86 | 87 | /** 88 | * 指定一个code的key, 并通过这个key设置一个value. 89 | */ 90 | override fun key(key: String): WildcatCodeBuilder.WildcatCodeBuilderKey { 91 | return builderKey.also { this.key = key } 92 | } 93 | 94 | /** 95 | * 构建一个猫猫码, 并以其载体实例String返回. 96 | */ 97 | override fun build(): String = appender.toString() + CAT_END 98 | 99 | 100 | /** 101 | * [StringCodeBuilder]中[CodeBuilder.CodeBuilderKey]的实现类。 102 | * 此类在[StringCodeBuilder]中只会存在一个实例,因此 **线程不安全**。 103 | */ 104 | private inner class StringCodeBuilderKey : WildcatCodeBuilder.WildcatCodeBuilderKey { 105 | /** 106 | * 为当前Key设置一个value值并返回. 107 | */ 108 | override fun value(value: Any?): WildcatCodeBuilder { 109 | return key?.let { k -> 110 | appender.append(CAT_PS).append(k).append(CAT_KV) 111 | if (value != null) { 112 | val valueString = if (encode) { 113 | CatEncoder.encodeParams(value.toString()) 114 | } else value.toString() 115 | 116 | appender.append(valueString) 117 | } 118 | this@WildcatStringCodeBuilder 119 | }?.also { this@WildcatStringCodeBuilder.key = null } 120 | ?: throw NullPointerException("The 'key' has not been specified.") 121 | } 122 | 123 | /** 124 | * 为当前Key设置一个空的value值并返回. 125 | */ 126 | override fun emptyValue(): WildcatCodeBuilder = value(null) 127 | } 128 | 129 | 130 | } 131 | 132 | 133 | /** 134 | * 以[Neko]为载体的[CodeBuilder]实现类, 需要在构建实例的时候指定[类型][type]。 135 | * 136 | * 通过[哈希表][MutableMap]来进行[Neko]的构建, 且不是线程安全的。 137 | */ 138 | public class NoraNekoBuilder(override val codeType: String, override val type: String) : WildcatCodeBuilder { 139 | 140 | /** 当前参数map */ 141 | private val params: MutableMap = mutableMapOf() 142 | 143 | /** 当前等待设置的key值 */ 144 | private var key: String? = null 145 | 146 | /** [NoraNekoBuilderKey]实例 */ 147 | private val builderKey: NoraNekoBuilderKey = NoraNekoBuilderKey() 148 | 149 | /** 150 | * 指定一个code的key, 并通过这个key设置一个value. 151 | */ 152 | override fun key(key: String): WildcatCodeBuilder.WildcatCodeBuilderKey { 153 | return builderKey.also { this.key = key } 154 | } 155 | 156 | /** 157 | * 构建一个猫猫码, 并以其载体实例[T]返回. 158 | */ 159 | override fun build(): Neko { 160 | return MapNeko.byMap(type, params.toMap()) 161 | } 162 | 163 | /** 164 | * 以[Neko]作为载体的[CodeBuilder.CodeBuilderKey]实现类 165 | */ 166 | private inner class NoraNekoBuilderKey : WildcatCodeBuilder.WildcatCodeBuilderKey { 167 | /** 168 | * 为当前Key设置一个value值并返回. 169 | */ 170 | override fun value(value: Any?): WildcatCodeBuilder { 171 | return key?.let { k -> 172 | params[k] = value?.toString() ?: "" 173 | this@NoraNekoBuilder 174 | }?.also { this@NoraNekoBuilder.key = null } 175 | ?: throw NullPointerException("The 'key' has not been specified.") 176 | } 177 | 178 | /** 179 | * 为当前Key设置一个空的value值并返回. 180 | */ 181 | override fun emptyValue(): WildcatCodeBuilder = value("") 182 | } 183 | 184 | 185 | } 186 | 187 | 188 | /** 189 | * 以[Neko]为载体的[CodeBuilder]实现类, 需要在构建实例的时候指定[类型][type]。 190 | * 191 | * 通过[哈希表][MutableMap]来进行[Neko]的构建, 且不是线程安全的。 192 | */ 193 | public class LazyNoraNekoBuilder(override val codeType: String, override val type: String) : 194 | LazyWildcatCodeBuilder { 195 | 196 | /** 当前参数map */ 197 | private val params: MutableLazyMap = MutableLazyMap() 198 | 199 | /** 当前等待设置的key值 */ 200 | private var key: String? = null 201 | 202 | /** [NoraNekoBuilderKey]实例 */ 203 | private val builderKey: LazyNoraNekoBuilderKey = LazyNoraNekoBuilderKey() 204 | 205 | /** 206 | * 指定一个code的key, 并通过这个key设置一个value. 207 | */ 208 | override fun key(key: String): LazyWildcatCodeBuilder.LazyWildcatCodeBuilderKey { 209 | return builderKey.also { this.key = key } 210 | } 211 | 212 | /** 213 | * 构建一个猫猫码, 并以其载体实例[T]返回. 214 | */ 215 | override fun build(): Neko { 216 | return MapNeko.byMap(type, params.toMap()) 217 | } 218 | 219 | /** 220 | * 以[Neko]作为载体的[CodeBuilder.CodeBuilderKey]实现类 221 | */ 222 | private inner class LazyNoraNekoBuilderKey : LazyWildcatCodeBuilder.LazyWildcatCodeBuilderKey { 223 | /** 224 | * 为当前Key设置一个value值并返回. 225 | */ 226 | override fun value(value: Any?): LazyWildcatCodeBuilder { 227 | return key?.let { k -> 228 | params[k] = value?.toString() ?: "" 229 | this@LazyNoraNekoBuilder 230 | }?.also { this@LazyNoraNekoBuilder.key = null } 231 | ?: throw NullPointerException("The 'key' has not been specified.") 232 | } 233 | 234 | /** 235 | * 为当前Key设置一个value值并返回. 236 | */ 237 | override fun value(value: () -> Any?): LazyWildcatCodeBuilder { 238 | return key?.let { k -> 239 | @Suppress("ReplacePutWithAssignment") 240 | params.put(k, initializer = { value()?.toString() ?: "" }) 241 | this@LazyNoraNekoBuilder 242 | }?.also { this@LazyNoraNekoBuilder.key = null } 243 | ?: throw NullPointerException("The 'key' has not been specified.") 244 | } 245 | 246 | /** 247 | * 为当前Key设置一个空的value值并返回. 248 | */ 249 | override fun emptyValue(): WildcatCodeBuilder = value("") 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/catcode/CodeBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("RedundantInnerClassModifier", "RedundantVisibilityModifier") 14 | 15 | package catcode 16 | 17 | import catcode.CodeBuilder.CodeBuilderKey 18 | import catcode.codes.LazyMapNeko 19 | import catcode.codes.MapNeko 20 | import catcode.collection.MutableLazyMap 21 | import catcode.collection.asLazyMap 22 | 23 | /** 24 | * 25 | * 针对于猫猫码的构造器. 26 | * 27 | * 通过此构造器的实例, 来以 28 | * [builder][CodeBuilder].[key(String)][key].[value(Any?)][CodeBuilderKey.value].[build][CodeBuilder.build] 29 | * 的形式快速构造一个猫猫码实例并作为指定载体返回。 30 | * 31 | * @see StringCodeBuilder 以字符串为载体的builder。 32 | * @see NekoBuilder 以[Neko]作为载体的builder。 33 | * 34 | * @author ForteScarlet 35 | * @since 1.8.0 36 | **/ 37 | public interface CodeBuilder { 38 | 39 | /** 40 | * type类型 41 | */ 42 | val type: String 43 | 44 | /** 45 | * 指定一个code的key, 并通过这个key设置一个value. 46 | */ 47 | fun key(key: String): CodeBuilderKey 48 | 49 | /** 50 | * 构建一个猫猫码, 并以其载体实例[T]返回. 51 | */ 52 | fun build(): T 53 | 54 | /** 55 | * [CodeBuilder]在一次指定了[Key][key]之后得到的Key值载体. 56 | * 通过调用此类的[value]方法来得到自身所在的[CodeBuilder] 57 | * 58 | * 此类一般来讲是属于一次性临时类. 59 | * 60 | */ 61 | interface CodeBuilderKey { 62 | /** 63 | * 为当前Key设置一个value值并返回. 64 | */ 65 | fun value(value: Any?): CodeBuilder 66 | 67 | /** 68 | * 为当前Key设置一个空的value值并返回. 69 | */ 70 | fun emptyValue(): CodeBuilder 71 | } 72 | 73 | } 74 | 75 | /** 76 | * 懒加载CodeBuilder。 77 | * @param T 78 | */ 79 | public interface LazyCodeBuilder : CodeBuilder { 80 | 81 | /** 82 | * 指定一个code的key, 并通过这个key设置一个value. 83 | */ 84 | override fun key(key: String): LazyCodeBuilderKey 85 | 86 | 87 | /** 88 | * 懒加载codeBuilderKey 89 | * @param T 90 | */ 91 | interface LazyCodeBuilderKey : CodeBuilderKey { 92 | override fun value(value: Any?): LazyCodeBuilder 93 | fun value(value: () -> Any?): LazyCodeBuilder 94 | } 95 | } 96 | 97 | 98 | 99 | //************************************** 100 | //* string builder 101 | //************************************** 102 | 103 | 104 | /** 105 | * 以`String`为载体的[CodeBuilder]实现类, 需要在构建实例的时候指定[类型][type] 106 | * 107 | * 以对字符串的拼接为主要构建形式, 且不是线程安全的。 108 | * 109 | * 如果[encode] == true, 则会对value值进行转义 110 | */ 111 | public class StringCodeBuilder 112 | @JvmOverloads 113 | constructor(override val type: String, private val encode: Boolean = true) : CodeBuilder { 114 | /** [StringBuilder] */ 115 | private val appender: StringBuilder = StringBuilder(CAT_HEAD).append(type) 116 | 117 | /** [StringCodeBuilderKey]实例 */ 118 | private val builderKey: StringCodeBuilderKey = StringCodeBuilderKey() 119 | 120 | /** 当前等待设置的key值 */ 121 | @Volatile 122 | private var key: String? = null 123 | 124 | /** 125 | * 指定一个code的key, 并通过这个key设置一个value. 126 | */ 127 | override fun key(key: String): CodeBuilderKey { 128 | return builderKey.also { this.key = key } 129 | } 130 | 131 | /** 132 | * 构建一个猫猫码, 并以其载体实例String返回. 133 | */ 134 | override fun build(): String = appender.toString() + CAT_END 135 | 136 | 137 | /** 138 | * [StringCodeBuilder]中[CodeBuilder.CodeBuilderKey]的实现类。 139 | * 此类在[StringCodeBuilder]中只会存在一个实例,因此 **线程不安全**。 140 | */ 141 | private inner class StringCodeBuilderKey : CodeBuilderKey { 142 | /** 143 | * 为当前Key设置一个value值并返回. 144 | */ 145 | override fun value(value: Any?): CodeBuilder { 146 | return key?.let { k -> 147 | appender.append(CAT_PS).append(k).append(CAT_KV) 148 | if (value != null) { 149 | appender.append(CatEncoder.encodeParams(value.toString())) 150 | } 151 | this@StringCodeBuilder 152 | }?.also { this@StringCodeBuilder.key = null } 153 | ?: throw NullPointerException("The 'key' has not been specified.") 154 | } 155 | 156 | /** 157 | * 为当前Key设置一个空的value值并返回. 158 | */ 159 | override fun emptyValue(): CodeBuilder = value(null) 160 | } 161 | 162 | } 163 | 164 | 165 | //************************************** 166 | //* Neko Builder 167 | //************************************** 168 | 169 | /** 170 | * 以[Neko]为载体的[CodeBuilder]实现类, 需要在构建实例的时候指定[类型][type]。 171 | * 172 | * 通过[哈希表][MutableMap]来进行[Neko]的构建, 且不是线程安全的。 173 | */ 174 | public class NekoBuilder(override val type: String) : CodeBuilder { 175 | 176 | /** 当前参数map */ 177 | private val params: MutableMap = mutableMapOf() 178 | 179 | /** 当前等待设置的key值 */ 180 | private var key: String? = null 181 | 182 | /** [NekoBuilderKey]实例 */ 183 | private val builderKey: NekoBuilderKey = NekoBuilderKey() 184 | 185 | /** 186 | * 指定一个code的key, 并通过这个key设置一个value. 187 | */ 188 | override fun key(key: String): CodeBuilderKey { 189 | return builderKey.also { this.key = key } 190 | } 191 | 192 | /** 193 | * 构建一个猫猫码, 并以其载体实例[T]返回. 194 | */ 195 | override fun build(): Neko { 196 | return MapNeko.byMap(type, params.toMap()) 197 | } 198 | 199 | /** 200 | * 以[Neko]作为载体的[CodeBuilder.CodeBuilderKey]实现类 201 | */ 202 | private inner class NekoBuilderKey : CodeBuilderKey { 203 | /** 204 | * 为当前Key设置一个value值并返回. 205 | */ 206 | override fun value(value: Any?): CodeBuilder { 207 | return key?.let { k -> 208 | params[k] = value?.toString() ?: "" 209 | this@NekoBuilder 210 | }?.also { this@NekoBuilder.key = null } 211 | ?: throw NullPointerException("The 'key' has not been specified.") 212 | } 213 | 214 | /** 215 | * 为当前Key设置一个空的value值并返回. 216 | */ 217 | override fun emptyValue(): CodeBuilder = value("") 218 | } 219 | 220 | } 221 | 222 | 223 | 224 | /** 225 | * 以[Neko]为载体的[LazyCodeBuilder]实现类, 需要在构建实例的时候指定[类型][type]。 226 | * 227 | * 通过[懒加载哈希表][MutableLazyMap]来进行[Neko]的构建, 且不是线程安全的。 228 | */ 229 | public class LazyNekoBuilder( 230 | override val type: String, 231 | mode: LazyThreadSafetyMode = LazyThreadSafetyMode.PUBLICATION 232 | ) : LazyCodeBuilder { 233 | 234 | /** 当前参数map */ 235 | private val params: MutableLazyMap = MutableLazyMap(mode = mode) 236 | 237 | /** 当前等待设置的key值 */ 238 | private var key: String? = null 239 | 240 | /** [LazyNekoBuilderKey]实例 */ 241 | private val builderKey: LazyNekoBuilderKey = LazyNekoBuilderKey() 242 | 243 | /** 244 | * 指定一个code的key, 并通过这个key设置一个value. 245 | */ 246 | override fun key(key: String): LazyCodeBuilder.LazyCodeBuilderKey { 247 | return builderKey.also { this.key = key } 248 | } 249 | 250 | /** 251 | * 构建一个猫猫码, 并以其载体实例[T]返回. 252 | */ 253 | override fun build(): Neko { 254 | return LazyMapNeko.byLazyMap(type, params.asLazyMap()) 255 | } 256 | 257 | /** 258 | * 以[Neko]作为载体的[CodeBuilder.CodeBuilderKey]实现类 259 | */ 260 | private inner class LazyNekoBuilderKey : LazyCodeBuilder.LazyCodeBuilderKey { 261 | /** 262 | * 为当前Key设置一个value值并返回. 263 | */ 264 | override fun value(value: Any?): LazyCodeBuilder { 265 | return key?.let { k -> 266 | params[k] = value?.toString() ?: "" 267 | this@LazyNekoBuilder 268 | }?.also { this@LazyNekoBuilder.key = null } 269 | ?: throw NullPointerException("The 'key' has not been specified.") 270 | } 271 | 272 | /** 273 | * 为当前Key设置一个value值并返回. 274 | */ 275 | override fun value(value: () -> Any?): LazyCodeBuilder { 276 | return key?.let { k -> 277 | @Suppress("ReplacePutWithAssignment") 278 | params.put(k, initializer = { value()?.toString() ?: "" }) 279 | this@LazyNekoBuilder 280 | }?.also { this@LazyNekoBuilder.key = null } 281 | ?: throw NullPointerException("The 'key' has not been specified.") 282 | } 283 | 284 | /** 285 | * 为当前Key设置一个空的value值并返回. 286 | */ 287 | override fun emptyValue(): LazyCodeBuilder = value("") 288 | } 289 | 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/catcode/codes/MapNeko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("unused") 14 | @file:JvmName("NekoCodes") 15 | @file:JvmMultifileClass 16 | package catcode.codes 17 | 18 | import catcode.* 19 | 20 | 21 | private val MAP_SPLIT_REGEX = Regex(CAT_KV) 22 | 23 | /** 24 | * 猫猫码封装类, 以[Map]作为参数载体 25 | * 26 | * [MapNeko]通过[Map]保存各项参数,其对应的[可变类型][MutableNeko]实例为[MutableMapNeko], 27 | * 通过[mutable]与[immutable]进行相互转化。 28 | * 29 | * 相比较于[Nyanko], [MapNeko]在进行获取、迭代与遍历的时候表现尤佳, 30 | * 尤其是参数获取相比较于[Nyanko]的参数获取速度有好几百倍的差距。 31 | * 32 | * 但是在实例构建与静态参数获取的时候相比于[Nyanko]略逊一筹。 33 | * 34 | * @since 1.0-1.11 35 | * @since 1.8.0 36 | */ 37 | open class MapNeko 38 | protected constructor(protected open val params: Map, override var type: String) : 39 | Neko, 40 | Map by params { 41 | constructor(type: String) : this(emptyMap(), type) 42 | constructor(type: String, params: Map) : this(params.toMap(), type) 43 | constructor(type: String, vararg params: CatKV) : this(mapOf(*params.toPair()), type) 44 | constructor(type: String, vararg params: String) : this(mapOf(*params.map { 45 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 46 | split[0] to split[1] 47 | }.toTypedArray()), type) 48 | 49 | // /** internal constructor for mutable kqCode */ 50 | // constructor(mutableKQCode: MutableNeko) : this(mutableKQCode.toMap(), mutableKQCode.type) 51 | 52 | /** 53 | * Returns the length of this character sequence. 54 | */ 55 | override val length: Int 56 | get() = toString().length 57 | 58 | 59 | /** 60 | * 获取转义后的字符串 61 | */ 62 | override fun getNoDecode(key: String) = CatEncoder.encodeParamsOrNull(this[key]) 63 | 64 | /** 65 | * Returns the character at the specified [index] in this character sequence. 66 | * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this character sequence. 67 | * Note that the [String] implementation of this interface in Kotlin/JS has unspecified behavior 68 | * if the [index] is out of its bounds. 69 | */ 70 | override fun get(index: Int): Char = toString()[index] 71 | 72 | /** 73 | * get value or default. 74 | */ 75 | override fun getOrDefault(key: String, defaultValue: String): String = params.getOrDefault(key, defaultValue) 76 | 77 | /** 78 | * Returns a new character sequence that is a subsequence of this character sequence, 79 | * starting at the specified [startIndex] and ending right before the specified [endIndex]. 80 | * 81 | * @param startIndex the start index (inclusive). 82 | * @param endIndex the end index (exclusive). 83 | */ 84 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = 85 | toString().subSequence(startIndex, endIndex) 86 | 87 | 88 | /** 89 | * toString的值记录。因为是不可变类,因此toString是不会变的 90 | * 在获取的时候才会去实际计算,且仅计算一次。 91 | */ 92 | private val _toString: String by lazy { CatCodeUtil.toCat(type, map = this) } 93 | 94 | /** toString */ 95 | override fun toString(): String = _toString 96 | 97 | 98 | /** 99 | * 转化为参数可变的[MutableNeko] 100 | */ 101 | override fun mutable(): MutableNeko = MutableMapNeko(type, this.toMutableMap()) 102 | 103 | /** 104 | * 转化为不可变类型[Neko] 105 | */ 106 | override fun immutable(): Neko = MapNeko(params, type) 107 | 108 | /** 109 | * 获取 [params] 实例。 110 | */ 111 | override fun toMap(): Map = params 112 | 113 | 114 | override fun equals(other: Any?): Boolean { 115 | if (this === other) return true 116 | if (javaClass != other?.javaClass) return false 117 | 118 | other as MapNeko 119 | 120 | if (params != other.params) return false 121 | if (type != other.type) return false 122 | 123 | return true 124 | } 125 | 126 | override fun hashCode(): Int { 127 | var result: Int = params.hashCode() 128 | result = 31 * result + type.hashCode() 129 | return result 130 | } 131 | 132 | /** [MapNeko] companion object. */ 133 | companion object Of { 134 | /** 参数切割用的正则 */ 135 | private val TEMP_SPLIT_REGEX = Regex(" *, *") 136 | 137 | /** 138 | * 将猫猫码字符串切割为参数列表 139 | * 返回的键值对为 `type to split` 140 | */ 141 | @Suppress("NOTHING_TO_INLINE") 142 | private inline fun splitCode(code: String): CatKV> { 143 | var tempText = code.trim() 144 | // 不是[CAT:开头,或者不是]结尾都不行 145 | if (!tempText.startsWith(CAT_HEAD) || !tempText.endsWith(CAT_END)) { 146 | throw IllegalArgumentException("not starts with '$CAT_HEAD' or not ends with '$CAT_END'") 147 | } 148 | // 是[CAT:开头,]结尾,切割并转化 149 | tempText = tempText.substring(4, tempText.lastIndex) 150 | 151 | val split = tempText.split(TEMP_SPLIT_REGEX) 152 | val type = split[0] 153 | return type cTo split 154 | } 155 | 156 | /** 157 | * 根据猫猫码字符串获取[MapNeko]实例 158 | */ 159 | @JvmStatic 160 | @JvmOverloads 161 | fun byCode(code: String, decode: Boolean = true): MapNeko { 162 | val (type, split) = splitCode(code) 163 | 164 | return if (split.size > 1) { 165 | if (decode) { 166 | // 参数解码 167 | val map = split.subList(1, split.size).map { 168 | val sp = it.split(Regex("="), 2) 169 | sp[0] to sp[1].deCatParam() 170 | }.toMap() 171 | MapNeko(map, type) 172 | } else { 173 | MapNeko(type, *split.subList(1, split.size).toTypedArray()) 174 | } 175 | } else { 176 | MapNeko(type) 177 | } 178 | } 179 | 180 | /** 通过map参数获取 */ 181 | @JvmStatic 182 | fun byMap(type: String, params: Map): MapNeko = 183 | MapNeko(type, params.mapNotNull { 184 | if (it.value == null) null else it.key to it.value.toString() 185 | }.toMap()) 186 | 187 | /** 通过键值对获取 */ 188 | @JvmStatic 189 | fun byKV(type: String, vararg params: CatKV): MapNeko = 190 | MapNeko(type, params.mapNotNull { 191 | if (it.value == null) null else it.key to it.value.toString() 192 | }.toMap()) 193 | 194 | /** 通过键值对字符串获取 */ 195 | @JvmStatic 196 | fun byParamString(type: String, vararg params: String): MapNeko = MapNeko(type, *params) 197 | 198 | /** 199 | * 根据猫猫码字符串获取[MapNeko]实例 200 | */ 201 | @JvmStatic 202 | @JvmOverloads 203 | fun mutableByCode(code: String, decode: Boolean = true): MutableMapNeko { 204 | val (type, split) = splitCode(code) 205 | 206 | return if (split.size > 1) { 207 | if (decode) { 208 | // 参数解码 209 | val map: MutableMap = split.subList(1, split.size).map { 210 | val sp = it.split(Regex("="), 2) 211 | sp[0] to CatDecoder.decodeParams(sp[1]) 212 | }.toMap().toMutableMap() 213 | MutableMapNeko(type, map) 214 | } else { 215 | MutableMapNeko(type, *split.subList(1, split.size).toTypedArray()) 216 | } 217 | } else { 218 | MutableMapNeko(type) 219 | } 220 | } 221 | 222 | 223 | /** 通过map参数获取 */ 224 | @JvmStatic 225 | fun mutableByMap(type: String, params: Map): MutableMapNeko = MutableMapNeko(type, params) 226 | 227 | /** 通过键值对获取 */ 228 | @JvmStatic 229 | fun mutableByKV(type: String, vararg params: CatKV): MutableMapNeko = 230 | MutableMapNeko(type, *params) 231 | 232 | /** 通过键值对字符串获取 */ 233 | @JvmStatic 234 | fun mutableByParamString(type: String, vararg params: String): MutableMapNeko = MutableMapNeko(type, *params) 235 | } 236 | 237 | } 238 | 239 | /** 240 | * [Neko]对应的可变类型, 以[MutableMap]作为载体 241 | * 242 | * 目前来讲唯一的[MutableNeko]实例. 通过[MutableMap]作为参数载体需要一定程度的资源消耗, 243 | * 因此我认为最好应该避免频繁大量的使用[可变类型][MutableMap]. 244 | * 245 | * 如果想要动态的构建一个[Neko], 也可以试试[CodeBuilder], 246 | * 其中[StringCodeBuilder]则以字符串操作为主而避免了构建内部[Map] 247 | * 248 | * 但是无论如何, 都最好在构建之前便决定好参数 249 | * 250 | * @since 1.8.0 251 | */ 252 | @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") 253 | public class MutableMapNeko 254 | private constructor(protected override val params: MutableMap, type: String) : 255 | MapNeko(params, type), 256 | MutableNeko, 257 | MutableMap by params { 258 | constructor(type: String) : this(mutableMapOf(), type) 259 | constructor(type: String, params: Map) : this(params.toMutableMap(), type) 260 | constructor(type: String, vararg params: CatKV) : this(mutableMapOf(*params.toPair()), type) 261 | constructor(type: String, vararg params: String) : this(mutableMapOf(*params.map { 262 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 263 | split[0] to split[1] 264 | }.toTypedArray()), type) 265 | 266 | /** 267 | * 转化为参数可变的[MutableNeko] 268 | */ 269 | override fun mutable(): MutableNeko = MutableMapNeko(params, type) 270 | 271 | 272 | /** 273 | * 转化为不可变类型[Neko] 274 | */ 275 | override fun immutable(): Neko = MapNeko(type, this) 276 | 277 | /** toString */ 278 | override fun toString(): String = CatCodeUtil.toCat(type, map = this) 279 | 280 | 281 | /** 282 | * params map. 283 | */ 284 | override fun toMap(): MutableMap = params 285 | 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/catcode/codes/LazyMapNeko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("unused") 14 | @file:JvmName("NekoCodes") 15 | @file:JvmMultifileClass 16 | package catcode.codes 17 | 18 | import catcode.* 19 | import catcode.collection.* 20 | 21 | 22 | private val MAP_SPLIT_REGEX = Regex(CAT_KV) 23 | 24 | /** 25 | * 猫猫码封装类, 以[Map]作为参数载体 26 | * 27 | * [MapNeko]通过[Map]保存各项参数,其对应的[可变类型][MutableNeko]实例为[MutableMapNeko], 28 | * 通过[mutable]与[immutable]进行相互转化。 29 | * 30 | * 相比较于[Nyanko], [MapNeko]在进行获取、迭代与遍历的时候表现尤佳, 31 | * 尤其是参数获取相比较于[Nyanko]的参数获取速度有好几百倍的差距。 32 | * 33 | * 但是在实例构建与静态参数获取的时候相比于[Nyanko]略逊一筹。 34 | * 35 | * @since 1.0-1.11 36 | * @since 1.8.0 37 | */ 38 | open class LazyMapNeko 39 | protected constructor(private val params: LazyMap, override var type: String) : 40 | Neko, 41 | Map by params { 42 | constructor(type: String) : this(LazyMap(emptyMap()), type) 43 | constructor(type: String, params: Map) : this(params.toLazyMap(), type) 44 | constructor(type: String, vararg params: CatKV) : this(mapOf(*params.toPair()).toLazyMap(), type) 45 | constructor(type: String, vararg params: String) : this(mapOf(*params.map { 46 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 47 | split[0] to split[1] 48 | }.toTypedArray()).toLazyMap(), type) 49 | 50 | // /** internal constructor for mutable kqCode */ 51 | // constructor(mutableKQCode: MutableNeko) : this(mutableKQCode.toMap(), mutableKQCode.type) 52 | 53 | /** 54 | * Returns the length of this character sequence. 55 | */ 56 | override val length: Int 57 | get() = toString().length 58 | 59 | 60 | /** 61 | * 获取转义后的字符串 62 | */ 63 | override fun getNoDecode(key: String) = CatEncoder.encodeParamsOrNull(this[key]) 64 | 65 | /** 66 | * Returns the character at the specified [index] in this character sequence. 67 | * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this character sequence. 68 | * Note that the [String] implementation of this interface in Kotlin/JS has unspecified behavior 69 | * if the [index] is out of its bounds. 70 | */ 71 | override fun get(index: Int): Char = toString()[index] 72 | 73 | /** 74 | * get value or default. 75 | */ 76 | override fun getOrDefault(key: String, defaultValue: String): String = params.getOrDefault(key, defaultValue) 77 | 78 | /** 79 | * Returns a new character sequence that is a subsequence of this character sequence, 80 | * starting at the specified [startIndex] and ending right before the specified [endIndex]. 81 | * 82 | * @param startIndex the start index (inclusive). 83 | * @param endIndex the end index (exclusive). 84 | */ 85 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = 86 | toString().subSequence(startIndex, endIndex) 87 | 88 | 89 | /** 90 | * toString的值记录。因为是不可变类,因此toString是不会变的 91 | * 在获取的时候才会去实际计算,且仅计算一次。 92 | */ 93 | private val _toString: String by lazy(LazyThreadSafetyMode.PUBLICATION) { 94 | CatCodeUtil.toCat(type, true, toMap()) 95 | } 96 | 97 | /** toString */ 98 | override fun toString(): String = _toString 99 | 100 | 101 | /** 102 | * 转化为参数可变的[MutableNeko] 103 | */ 104 | override fun mutable(): MutableNeko = MutableMapNeko(type, this.toMutableMap()) 105 | 106 | /** 107 | * 转化为不可变类型[Neko] 108 | */ 109 | override fun immutable(): Neko = LazyMapNeko(params, type) 110 | 111 | /** 112 | * 获取 [params] 实例。 113 | */ 114 | override fun toMap(): Map = params.copy() 115 | 116 | 117 | override fun equals(other: Any?): Boolean { 118 | if (this === other) return true 119 | if (javaClass != other?.javaClass) return false 120 | 121 | other as LazyMapNeko 122 | 123 | if (params != other.params) return false 124 | if (type != other.type) return false 125 | 126 | return true 127 | } 128 | 129 | override fun hashCode(): Int { 130 | var result = params.hashCode() 131 | result = 31 * result + type.hashCode() 132 | return result 133 | } 134 | 135 | 136 | /** [MapNeko] companion object. */ 137 | companion object Of { 138 | /** 参数切割用的正则 */ 139 | private val TEMP_SPLIT_REGEX = Regex(" *, *") 140 | 141 | /** 142 | * 将猫猫码字符串切割为参数列表 143 | * 返回的键值对为 `type to split` 144 | */ 145 | @Suppress("NOTHING_TO_INLINE") 146 | private inline fun splitCode(code: String): CatKV> { 147 | var tempText = code.trim() 148 | // 不是[CAT:开头,或者不是]结尾都不行 149 | if (!tempText.startsWith(CAT_HEAD) || !tempText.endsWith(CAT_END)) { 150 | throw IllegalArgumentException("not starts with '$CAT_HEAD' or not ends with '$CAT_END'") 151 | } 152 | // 是[CAT:开头,]结尾,切割并转化 153 | tempText = tempText.substring(4, tempText.lastIndex) 154 | 155 | val split = tempText.split(TEMP_SPLIT_REGEX) 156 | val type = split[0] 157 | return type cTo split 158 | } 159 | 160 | /** 161 | * 根据猫猫码字符串获取[MapNeko]实例 162 | */ 163 | @JvmStatic 164 | @JvmOverloads 165 | fun byCode(code: String, decode: Boolean = true): LazyMapNeko { 166 | val (type, split) = splitCode(code) 167 | 168 | return if (split.size > 1) { 169 | if (decode) { 170 | // 参数解码 171 | val map = split.subList(1, split.size).map { 172 | val sp = it.split(Regex("="), 2) 173 | sp[0] to sp[1].deCatParam() 174 | }.toMap() 175 | LazyMapNeko(map.toLazyMap(), type) 176 | } else { 177 | LazyMapNeko(type, *split.subList(1, split.size).toTypedArray()) 178 | } 179 | } else { 180 | LazyMapNeko(type) 181 | } 182 | } 183 | 184 | /** 通过map参数获取 */ 185 | @JvmStatic 186 | fun byMap(type: String, params: Map): MapNeko = 187 | MapNeko(type, params.mapNotNull { 188 | if (it.value == null) null else it.key to it.value.toString() 189 | }.toMap()) 190 | 191 | /** 192 | * by lazy map. 193 | * @return LazyMapNeko 194 | */ 195 | fun byLazyMap(type: String, params: LazyMap): LazyMapNeko = 196 | LazyMapNeko(params.copy(), type) 197 | 198 | 199 | /** 通过键值对获取 */ 200 | @JvmStatic 201 | fun byKV(type: String, vararg params: CatKV): MapNeko = 202 | MapNeko(type, params.mapNotNull { 203 | if (it.value == null) null else it.key to it.value.toString() 204 | }.toMap()) 205 | 206 | /** 通过键值对字符串获取 */ 207 | @JvmStatic 208 | fun byParamString(type: String, vararg params: String): MapNeko = MapNeko(type, *params) 209 | 210 | /** 211 | * 根据猫猫码字符串获取[MapNeko]实例 212 | */ 213 | @JvmStatic 214 | @JvmOverloads 215 | fun mutableByCode(code: String, decode: Boolean = true): MutableMapNeko { 216 | val (type, split) = splitCode(code) 217 | 218 | return if (split.size > 1) { 219 | if (decode) { 220 | // 参数解码 221 | val map: MutableMap = split.subList(1, split.size).map { 222 | val sp = it.split(Regex("="), 2) 223 | sp[0] to CatDecoder.decodeParams(sp[1]) 224 | }.toMap().toMutableMap() 225 | MutableMapNeko(type, map) 226 | } else { 227 | MutableMapNeko(type, *split.subList(1, split.size).toTypedArray()) 228 | } 229 | } else { 230 | MutableMapNeko(type) 231 | } 232 | } 233 | 234 | 235 | /** 通过map参数获取 */ 236 | @JvmStatic 237 | fun mutableByMap(type: String, params: Map): MutableMapNeko = MutableMapNeko(type, params) 238 | 239 | /** 通过键值对获取 */ 240 | @JvmStatic 241 | fun mutableByKV(type: String, vararg params: CatKV): MutableMapNeko = 242 | MutableMapNeko(type, *params) 243 | 244 | /** 通过键值对字符串获取 */ 245 | @JvmStatic 246 | fun mutableByParamString(type: String, vararg params: String): MutableMapNeko = MutableMapNeko(type, *params) 247 | } 248 | 249 | } 250 | 251 | /** 252 | * [Neko]对应的可变类型, 以[MutableMap]作为载体 253 | * 254 | * 目前来讲唯一的[MutableNeko]实例. 通过[MutableMap]作为参数载体需要一定程度的资源消耗, 255 | * 因此我认为最好应该避免频繁大量的使用[可变类型][MutableMap]. 256 | * 257 | * 如果想要动态的构建一个[Neko], 也可以试试[CodeBuilder], 258 | * 其中[StringCodeBuilder]则以字符串操作为主而避免了构建内部[Map] 259 | * 260 | * 但是无论如何, 都最好在构建之前便决定好参数 261 | * 262 | * @since 1.8.0 263 | */ 264 | @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") 265 | public class LazyMutableMapNeko 266 | private constructor(private val params: MutableLazyMap, type: String) : 267 | LazyMapNeko(params.asLazyMap(), type), 268 | MutableNeko, 269 | MutableMap by params { 270 | constructor(type: String) : this(MutableLazyMap(), type) 271 | constructor(type: String, params: Map) : this(params.toMutableLazyMap(), type) 272 | constructor(type: String, vararg params: CatKV) : this(mutableMapOf(*params.toPair()).toMutableLazyMap(), type) 273 | constructor(type: String, vararg params: String) : this(mutableMapOf(*params.map { 274 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 275 | split[0] to split[1] 276 | }.toTypedArray()).toMutableLazyMap(), type) 277 | 278 | /** 279 | * 转化为参数可变的[MutableNeko] 280 | */ 281 | override fun mutable(): MutableNeko = LazyMutableMapNeko(params, type) 282 | 283 | 284 | /** 285 | * 转化为不可变类型[Neko] 286 | */ 287 | override fun immutable(): Neko = MapNeko(type, this) 288 | 289 | 290 | /** toString */ 291 | override fun toString(): String = CatCodeUtil.toCat(type, map = this) 292 | 293 | /** 294 | * params map. 295 | */ 296 | override fun toMap(): MutableMap = params.copy() 297 | 298 | 299 | } 300 | -------------------------------------------------------------------------------- /src/main/java/catcode/codes/MapNoraNeko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("unused") 14 | @file:JvmName("NekoCodes") 15 | @file:JvmMultifileClass 16 | package catcode.codes 17 | 18 | import catcode.* 19 | 20 | 21 | 22 | 23 | private val MAP_SPLIT_REGEX = Regex(CAT_KV) 24 | 25 | /** 26 | * 猫猫码封装类, 以[Map]作为参数载体 27 | * 28 | * [MapNeko]通过[Map]保存各项参数,其对应的[可变类型][MutableNeko]实例为[MutableMapNeko], 29 | * 通过[mutable]与[immutable]进行相互转化。 30 | * 31 | * 相比较于[Nyanko], [MapNeko]在进行获取、迭代与遍历的时候表现尤佳, 32 | * 尤其是参数获取相比较于[Nyanko]的参数获取速度有好几百倍的差距。 33 | * 34 | * 但是在实例构建与静态参数获取的时候相比于[Nyanko]略逊一筹。 35 | * 36 | * @since 1.0-1.11 37 | * @since 1.8.0 38 | */ 39 | open class MapNoraNeko 40 | protected constructor(override val codeType: String, protected open val params: Map, override var type: String) : 41 | NoraNeko, 42 | Map by params { 43 | constructor(codeType: String, type: String) : this(codeType, emptyMap(), type) 44 | constructor(codeType: String, type: String, params: Map) : this(codeType, params.toMap(), type) 45 | constructor(codeType: String, type: String, vararg params: CatKV) : this(codeType, mapOf(*params.toPair()), type) 46 | constructor(codeType: String, type: String, vararg params: String) : this(codeType, mapOf(*params.map { 47 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 48 | split[0] to split[1] 49 | }.toTypedArray()), type) 50 | 51 | // /** internal constructor for mutable kqCode */ 52 | // constructor(mutableKQCode: MutableNeko) : this(mutableKQCode.toMap(), mutableKQCode.type) 53 | 54 | /** 55 | * Returns the length of this character sequence. 56 | */ 57 | override val length: Int 58 | get() = toString().length 59 | 60 | 61 | /** 62 | * 获取转义后的字符串 63 | */ 64 | override fun getNoDecode(key: String) = CatEncoder.encodeParamsOrNull(this[key]) 65 | 66 | /** 67 | * Returns the character at the specified [index] in this character sequence. 68 | * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this character sequence. 69 | * Note that the [String] implementation of this interface in Kotlin/JS has unspecified behavior 70 | * if the [index] is out of its bounds. 71 | */ 72 | override fun get(index: Int): Char = toString()[index] 73 | 74 | /** 75 | * get value or default. 76 | */ 77 | override fun getOrDefault(key: String, defaultValue: String): String = params.getOrDefault(key, defaultValue) 78 | 79 | /** 80 | * Returns a new character sequence that is a subsequence of this character sequence, 81 | * starting at the specified [startIndex] and ending right before the specified [endIndex]. 82 | * 83 | * @param startIndex the start index (inclusive). 84 | * @param endIndex the end index (exclusive). 85 | */ 86 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = 87 | toString().subSequence(startIndex, endIndex) 88 | 89 | 90 | /** 91 | * toString的值记录。因为是不可变类,因此toString是不会变的 92 | * 在获取的时候才会去实际计算,且仅计算一次。 93 | */ 94 | private val _toString: String by lazy { CatCodeUtil.toCat(type, map = this) } 95 | 96 | /** toString */ 97 | override fun toString(): String = _toString 98 | 99 | 100 | /** 101 | * 转化为参数可变的[MutableNeko] 102 | */ 103 | override fun mutable(): MutableNeko = MutableMapNoraNeko(codeType, type, this.toMutableMap()) 104 | 105 | /** 106 | * 转化为不可变类型[Neko] 107 | */ 108 | override fun immutable(): Neko = MapNoraNeko(codeType, params, type) 109 | 110 | /** 111 | * 获取 [params] 实例。 112 | */ 113 | override fun toMap(): Map = params 114 | 115 | 116 | override fun equals(other: Any?): Boolean { 117 | if (this === other) return true 118 | if (javaClass != other?.javaClass) return false 119 | 120 | other as MapNoraNeko 121 | 122 | if (params != other.params) return false 123 | if (type != other.type) return false 124 | 125 | return true 126 | } 127 | 128 | override fun hashCode(): Int { 129 | var result: Int = params.hashCode() 130 | result = 31 * result + type.hashCode() 131 | return result 132 | } 133 | 134 | /** [MapNeko] companion object. */ 135 | companion object Of { 136 | /** 参数切割用的正则 */ 137 | private val TEMP_SPLIT_REGEX = Regex(" *, *") 138 | 139 | /** 140 | * 将猫猫码字符串切割为参数列表 141 | * 返回的键值对为 `type to split` 142 | */ 143 | @Suppress("NOTHING_TO_INLINE") 144 | private inline fun splitCode(code: String): CatKV> { 145 | var tempText = code.trim() 146 | // 不是[CAT:开头,或者不是]结尾都不行 147 | if (!tempText.startsWith(CAT_HEAD) || !tempText.endsWith(CAT_END)) { 148 | throw IllegalArgumentException("not starts with '$CAT_HEAD' or not ends with '$CAT_END'") 149 | } 150 | // 是[CAT:开头,]结尾,切割并转化 151 | tempText = tempText.substring(4, tempText.lastIndex) 152 | 153 | val split = tempText.split(TEMP_SPLIT_REGEX) 154 | val type = split[0] 155 | return type cTo split 156 | } 157 | 158 | /** 159 | * 根据猫猫码字符串获取[MapNeko]实例 160 | */ 161 | @JvmStatic 162 | @JvmOverloads 163 | fun byCode(codeType: String, code: String, decode: Boolean = true): MapNoraNeko { 164 | val (type, split) = splitCode(code) 165 | 166 | return if (split.size > 1) { 167 | if (decode) { 168 | // 参数解码 169 | val map = split.subList(1, split.size).map { 170 | val sp = it.split(Regex("="), 2) 171 | sp[0] to CatDecoder.decodeParams(sp[1]) 172 | }.toMap() 173 | MapNoraNeko(codeType, map, type) 174 | } else { 175 | MapNoraNeko(codeType, type, *split.subList(1, split.size).toTypedArray()) 176 | } 177 | } else { 178 | MapNoraNeko(codeType, type) 179 | } 180 | } 181 | 182 | /** 通过map参数获取 */ 183 | @JvmStatic 184 | fun byMap(codeType: String, type: String, params: Map): MapNoraNeko = 185 | MapNoraNeko(codeType, type, params) 186 | 187 | /** 通过键值对获取 */ 188 | @JvmStatic 189 | fun byKV(codeType: String, type: String, vararg params: CatKV): MapNoraNeko = 190 | MapNoraNeko(codeType, type, *params) 191 | 192 | /** 通过键值对字符串获取 */ 193 | @JvmStatic 194 | fun byParamString(codeType: String, type: String, vararg params: String): MapNoraNeko = 195 | MapNoraNeko(codeType, type, *params) 196 | 197 | /** 198 | * 根据猫猫码字符串获取[MapNeko]实例 199 | */ 200 | @JvmStatic 201 | @JvmOverloads 202 | fun mutableByCode(codeType: String, code: String, decode: Boolean = true): MutableMapNoraNeko { 203 | val (type, split) = splitCode(code) 204 | 205 | return if (split.size > 1) { 206 | if (decode) { 207 | // 参数解码 208 | val map: MutableMap = split.subList(1, split.size).map { 209 | val sp = it.split(Regex("="), 2) 210 | sp[0] to CatDecoder.decodeParams(sp[1]) 211 | }.toMap().toMutableMap() 212 | MutableMapNoraNeko(codeType, type, map) 213 | } else { 214 | MutableMapNoraNeko(codeType, type, *split.subList(1, split.size).toTypedArray()) 215 | } 216 | } else { 217 | MutableMapNoraNeko(codeType, type) 218 | } 219 | } 220 | 221 | 222 | /** 通过map参数获取 */ 223 | @JvmStatic 224 | fun mutableByMap(codeType: String, type: String, params: Map): MutableMapNoraNeko = 225 | MutableMapNoraNeko(codeType, type, params) 226 | 227 | /** 通过键值对获取 */ 228 | @JvmStatic 229 | fun mutableByKV(codeType: String, type: String, vararg params: CatKV): MutableMapNoraNeko = 230 | MutableMapNoraNeko(codeType, type, *params) 231 | 232 | /** 通过键值对字符串获取 */ 233 | @JvmStatic 234 | fun mutableByParamString(codeType: String, type: String, vararg params: String): MutableMapNoraNeko = 235 | MutableMapNoraNeko(codeType, type, *params) 236 | } 237 | 238 | } 239 | 240 | /** 241 | * [Neko]对应的可变类型, 以[MutableMap]作为载体 242 | * 243 | * 目前来讲唯一的[MutableNeko]实例. 通过[MutableMap]作为参数载体需要一定程度的资源消耗, 244 | * 因此我认为最好应该避免频繁大量的使用[可变类型][MutableMap]. 245 | * 246 | * 如果想要动态的构建一个[Neko], 也可以试试[CodeBuilder], 247 | * 其中[StringCodeBuilder]则以字符串操作为主而避免了构建内部[Map] 248 | * 249 | * 但是无论如何, 都最好在构建之前便决定好参数 250 | * 251 | * @since 1.8.0 252 | */ 253 | @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") 254 | public class MutableMapNoraNeko 255 | private constructor(codeType: String, protected override val params: MutableMap, type: String) : 256 | MapNoraNeko(codeType, params, type), 257 | MutableNeko, 258 | MutableMap by params { 259 | constructor(codeType: String, type: String) : this(codeType, mutableMapOf(), type) 260 | constructor(codeType: String, type: String, params: Map) : this(codeType, params.toMutableMap(), type) 261 | constructor(codeType: String, type: String, vararg params: CatKV) : this(codeType, mutableMapOf(*params.toPair()), type) 262 | constructor(codeType: String, type: String, vararg params: String) : this(codeType, mutableMapOf(*params.map { 263 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 264 | split[0] to split[1] 265 | }.toTypedArray()), type) 266 | 267 | /** 268 | * 转化为参数可变的[MutableNeko] 269 | */ 270 | override fun mutable(): MutableNeko = MutableMapNoraNeko(codeType, params, type) 271 | 272 | 273 | /** 274 | * 转化为不可变类型[Neko] 275 | */ 276 | override fun immutable(): Neko = MapNoraNeko(codeType, type, this) 277 | 278 | /** toString */ 279 | override fun toString(): String = CatCodeUtil.toCat(type, map = this) 280 | 281 | 282 | /** 283 | * params map. 284 | */ 285 | override fun toMap(): MutableMap = params 286 | 287 | 288 | } 289 | -------------------------------------------------------------------------------- /src/main/java/catcode/codes/LazyMapNoraNeko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | @file:Suppress("unused") 14 | @file:JvmName("NekoCodes") 15 | @file:JvmMultifileClass 16 | package catcode.codes 17 | 18 | import catcode.* 19 | import catcode.collection.LazyMap 20 | import catcode.collection.MutableLazyMap 21 | import catcode.collection.toLazyMap 22 | import catcode.collection.toMutableLazyMap 23 | 24 | 25 | private val MAP_SPLIT_REGEX = Regex(CAT_KV) 26 | 27 | /** 28 | * 猫猫码封装类, 以[Map]作为参数载体 29 | * 30 | * [MapNeko]通过[Map]保存各项参数,其对应的[可变类型][MutableNeko]实例为[MutableMapNeko], 31 | * 通过[mutable]与[immutable]进行相互转化。 32 | * 33 | * 相比较于[Nyanko], [MapNeko]在进行获取、迭代与遍历的时候表现尤佳, 34 | * 尤其是参数获取相比较于[Nyanko]的参数获取速度有好几百倍的差距。 35 | * 36 | * 但是在实例构建与静态参数获取的时候相比于[Nyanko]略逊一筹。 37 | * 38 | * @since 1.0-1.11 39 | * @since 1.8.0 40 | */ 41 | open class LazyMapNoraNeko 42 | protected constructor(override val codeType: String, private val params: LazyMap, override var type: String) : 43 | NoraNeko, 44 | Map by params { 45 | constructor(codeType: String, type: String) : this(codeType, LazyMap(emptyMap()), type) 46 | constructor(codeType: String, type: String, params: Map) : this(codeType, params.toLazyMap(), type) 47 | constructor(codeType: String, type: String, vararg params: CatKV) : this(codeType, mapOf(*params.toPair()).toLazyMap(), type) 48 | constructor(codeType: String, type: String, vararg params: String) : this(codeType, mapOf(*params.map { 49 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 50 | split[0] to split[1] 51 | }.toTypedArray()).toLazyMap(), type) 52 | 53 | // /** internal constructor for mutable kqCode */ 54 | // constructor(mutableKQCode: MutableNeko) : this(mutableKQCode.toMap(), mutableKQCode.type) 55 | 56 | /** 57 | * Returns the length of this character sequence. 58 | */ 59 | override val length: Int 60 | get() = toString().length 61 | 62 | 63 | /** 64 | * 获取转义后的字符串 65 | */ 66 | override fun getNoDecode(key: String) = CatEncoder.encodeParamsOrNull(this[key]) 67 | 68 | /** 69 | * Returns the character at the specified [index] in this character sequence. 70 | * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this character sequence. 71 | * Note that the [String] implementation of this interface in Kotlin/JS has unspecified behavior 72 | * if the [index] is out of its bounds. 73 | */ 74 | override fun get(index: Int): Char = toString()[index] 75 | 76 | /** 77 | * get value or default. 78 | */ 79 | override fun getOrDefault(key: String, defaultValue: String): String = params.getOrDefault(key, defaultValue) 80 | 81 | /** 82 | * Returns a new character sequence that is a subsequence of this character sequence, 83 | * starting at the specified [startIndex] and ending right before the specified [endIndex]. 84 | * 85 | * @param startIndex the start index (inclusive). 86 | * @param endIndex the end index (exclusive). 87 | */ 88 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = 89 | toString().subSequence(startIndex, endIndex) 90 | 91 | 92 | /** 93 | * toString的值记录。因为是不可变类,因此toString是不会变的 94 | * 在获取的时候才会去实际计算,且仅计算一次。 95 | */ 96 | private val _toString: String by lazy { CatCodeUtil.toCat(type, map = this) } 97 | 98 | /** toString */ 99 | override fun toString(): String = _toString 100 | 101 | 102 | /** 103 | * 转化为参数可变的[MutableNeko] 104 | */ 105 | override fun mutable(): MutableNeko = MutableMapNoraNeko(codeType, type, this.toMutableMap()) 106 | 107 | /** 108 | * 转化为不可变类型[Neko] 109 | */ 110 | override fun immutable(): Neko = LazyMapNoraNeko(codeType, params, type) 111 | 112 | /** 113 | * 获取 [params] 实例。 114 | */ 115 | override fun toMap(): Map = params.copy() 116 | 117 | 118 | override fun equals(other: Any?): Boolean { 119 | if (this === other) return true 120 | if (javaClass != other?.javaClass) return false 121 | 122 | other as LazyMapNoraNeko 123 | 124 | if (params != other.params) return false 125 | if (type != other.type) return false 126 | 127 | return true 128 | } 129 | 130 | override fun hashCode(): Int { 131 | var result: Int = params.hashCode() 132 | result = 31 * result + type.hashCode() 133 | return result 134 | } 135 | 136 | /** [MapNeko] companion object. */ 137 | companion object Of { 138 | /** 参数切割用的正则 */ 139 | private val TEMP_SPLIT_REGEX = Regex(" *, *") 140 | 141 | /** 142 | * 将猫猫码字符串切割为参数列表 143 | * 返回的键值对为 `type to split` 144 | */ 145 | @Suppress("NOTHING_TO_INLINE") 146 | private inline fun splitCode(code: String): CatKV> { 147 | var tempText = code.trim() 148 | // 不是[CAT:开头,或者不是]结尾都不行 149 | if (!tempText.startsWith(CAT_HEAD) || !tempText.endsWith(CAT_END)) { 150 | throw IllegalArgumentException("not starts with '$CAT_HEAD' or not ends with '$CAT_END'") 151 | } 152 | // 是[CAT:开头,]结尾,切割并转化 153 | tempText = tempText.substring(4, tempText.lastIndex) 154 | 155 | val split = tempText.split(TEMP_SPLIT_REGEX) 156 | val type = split[0] 157 | return type cTo split 158 | } 159 | 160 | /** 161 | * 根据猫猫码字符串获取[MapNeko]实例 162 | */ 163 | @JvmStatic 164 | @JvmOverloads 165 | fun byCode(codeType: String, code: String, decode: Boolean = true): LazyMapNoraNeko { 166 | val (type, split) = splitCode(code) 167 | 168 | return if (split.size > 1) { 169 | if (decode) { 170 | // 参数解码 171 | val map = split.subList(1, split.size).map { 172 | val sp = it.split(Regex("="), 2) 173 | sp[0] to CatDecoder.decodeParams(sp[1]) 174 | }.toMap() 175 | LazyMapNoraNeko(codeType, map.toLazyMap(), type) 176 | } else { 177 | LazyMapNoraNeko(codeType, type, *split.subList(1, split.size).toTypedArray()) 178 | } 179 | } else { 180 | LazyMapNoraNeko(codeType, type) 181 | } 182 | } 183 | 184 | /** 通过map参数获取 */ 185 | @JvmStatic 186 | fun byMap(codeType: String, type: String, params: Map): LazyMapNoraNeko = 187 | LazyMapNoraNeko(codeType, type, params) 188 | 189 | /** 通过键值对获取 */ 190 | @JvmStatic 191 | fun byKV(codeType: String, type: String, vararg params: CatKV): LazyMapNoraNeko = 192 | LazyMapNoraNeko(codeType, type, *params) 193 | 194 | /** 通过键值对字符串获取 */ 195 | @JvmStatic 196 | fun byParamString(codeType: String, type: String, vararg params: String): LazyMapNoraNeko = 197 | LazyMapNoraNeko(codeType, type, *params) 198 | 199 | /** 200 | * 根据猫猫码字符串获取[MapNeko]实例 201 | */ 202 | @JvmStatic 203 | @JvmOverloads 204 | fun mutableByCode(codeType: String, code: String, decode: Boolean = true): MutableMapNoraNeko { 205 | val (type, split) = splitCode(code) 206 | 207 | return if (split.size > 1) { 208 | if (decode) { 209 | // 参数解码 210 | val map: MutableMap = split.subList(1, split.size).map { 211 | val sp = it.split(Regex("="), 2) 212 | sp[0] to CatDecoder.decodeParams(sp[1]) 213 | }.toMap().toMutableMap() 214 | MutableMapNoraNeko(codeType, type, map) 215 | } else { 216 | MutableMapNoraNeko(codeType, type, *split.subList(1, split.size).toTypedArray()) 217 | } 218 | } else { 219 | MutableMapNoraNeko(codeType, type) 220 | } 221 | } 222 | 223 | 224 | /** 通过map参数获取 */ 225 | @JvmStatic 226 | fun mutableByMap(codeType: String, type: String, params: Map): MutableMapNoraNeko = 227 | MutableMapNoraNeko(codeType, type, params) 228 | 229 | /** 通过键值对获取 */ 230 | @JvmStatic 231 | fun mutableByKV(codeType: String, type: String, vararg params: CatKV): MutableMapNoraNeko = 232 | MutableMapNoraNeko(codeType, type, *params) 233 | 234 | /** 通过键值对字符串获取 */ 235 | @JvmStatic 236 | fun mutableByParamString(codeType: String, type: String, vararg params: String): MutableMapNoraNeko = 237 | MutableMapNoraNeko(codeType, type, *params) 238 | } 239 | 240 | } 241 | 242 | /** 243 | * [Neko]对应的可变类型, 以[MutableMap]作为载体 244 | * 245 | * 目前来讲唯一的[MutableNeko]实例. 通过[MutableMap]作为参数载体需要一定程度的资源消耗, 246 | * 因此我认为最好应该避免频繁大量的使用[可变类型][MutableMap]. 247 | * 248 | * 如果想要动态的构建一个[Neko], 也可以试试[CodeBuilder], 249 | * 其中[StringCodeBuilder]则以字符串操作为主而避免了构建内部[Map] 250 | * 251 | * 但是无论如何, 都最好在构建之前便决定好参数 252 | * 253 | * @since 1.8.0 254 | */ 255 | @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") 256 | public class LazyMutableMapNoraNeko 257 | private constructor(codeType: String, protected override val params: MutableLazyMap, type: String) : 258 | MapNoraNeko(codeType, params, type), 259 | MutableNeko, 260 | MutableMap by params { 261 | constructor(codeType: String, type: String) : this(codeType, MutableLazyMap(), type) 262 | constructor(codeType: String, type: String, params: Map) : this(codeType, params.toMutableLazyMap(), type) 263 | constructor(codeType: String, type: String, vararg params: CatKV) : this(codeType, mutableMapOf(*params.toPair()).toMutableLazyMap(), type) 264 | constructor(codeType: String, type: String, vararg params: String) : this(codeType, mutableMapOf(*params.map { 265 | val split = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 266 | split[0] to split[1] 267 | }.toTypedArray()).toMutableLazyMap(), type) 268 | 269 | /** 270 | * 转化为参数可变的[MutableNeko] 271 | */ 272 | override fun mutable(): MutableNeko = LazyMutableMapNoraNeko(codeType, params, type) 273 | 274 | 275 | /** 276 | * 转化为不可变类型[Neko] 277 | */ 278 | override fun immutable(): Neko = LazyMapNoraNeko(codeType, type, this) 279 | 280 | /** toString */ 281 | override fun toString(): String = CatCodeUtil.toCat(type, map = this) 282 | 283 | 284 | /** 285 | * params map. 286 | */ 287 | override fun toMap(): MutableMap = params.copy() 288 | 289 | 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/catcode/codes/Nyanko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | @file:JvmName("NekoCodes") 13 | @file:JvmMultifileClass 14 | package catcode.codes 15 | 16 | import catcode.* 17 | import catcode.collection.mapDelegation 18 | 19 | 20 | /** 21 | * 22 | * 23 | * 基于字符串操作的[Neko]实例。 24 | * 25 | * Nyanko,前身为 FastNeko 。 26 | * 27 | * Nyanko:にやんこ, 意同 ねこ, 更偏于爱称。而FastNeko基于字符串操作,小巧可爱,也符合爱称。 28 | * 29 | * 30 | * [Nyanko]没有对应以字符串操作为主的[MutableNeko], 因此他的[mutable]方法将会使用[MutableMapNeko] 31 | * 32 | * 相比于[MapNeko], [Nyanko]在实例化与较短猫猫码处理的消耗会更低, 33 | * 但是无论如何[Nyanko.get]的速度也永远比不上hash表的速度。 34 | * 但是对于直接通过字符串构建一个[Neko]来讲,[Nyanko]无疑是消耗更低的选择。 35 | * 36 | * [Nyanko]构建速度更快、资源消耗更少, 获取静态参数(例如[toString]、[size])的速度更快。 37 | * [Nyanko]的构建、获取静态参数速度相比较于[MapNeko]有着几百倍的差距。 38 | * 39 | * 但是在获取、迭代与遍历时相比较于[MapNeko]略逊一筹。 40 | * 41 | * 42 | */ 43 | open class Nyanko private constructor(private val code: String) : Neko { 44 | private val _type: String 45 | private val _size: Int 46 | 47 | private val empty: Boolean 48 | private val catHead: String 49 | private val startIndex: Int 50 | private val endIndex: Int 51 | 52 | init { 53 | if (!this.code.startsWith(CAT_HEAD) || !this.code.endsWith(CAT_END)) { 54 | throw IllegalArgumentException("text \"${this.code}\" is not a cat code text.") 55 | } 56 | // get type from string 57 | startIndex = CAT_HEAD.length 58 | endIndex = this.code.lastIndex 59 | val firstSplitIndex: Int = this.code.indexOf(CAT_PS, startIndex).let { 60 | if (it < 0) endIndex else it 61 | } 62 | // val typeEndIndex = if (firstSplitIndex < 0) _codeText.length else firstSplitIndex 63 | _type = this.code.substring(startIndex, firstSplitIndex) 64 | catHead = CAT_HEAD + _type 65 | empty = this.code.contains(CAT_PS) 66 | // 计算 key-value的个数, 即计算CAT_KV的个数 67 | val kvChar: Char = CAT_KV.first() 68 | _size = this.code.count { it == kvChar } 69 | } 70 | 71 | private val codeText: String = this.code 72 | 73 | override fun toString(): String = this.code 74 | 75 | override val length: Int = this.code.length 76 | override val size: Int = _size 77 | override val type: String = _type 78 | 79 | /** 80 | * 获取转义前的值。一般普通的[get]方法得到的是反转义后的。 81 | * 此处为保留原本的值不做转义。 82 | */ 83 | override fun getNoDecode(key: String): String? = getParam(key) 84 | 85 | /** 86 | * 转化为可变参的[MutableNeko] 87 | */ 88 | override fun mutable(): MutableNeko = MapNeko.mutableByCode(this.toString()) 89 | 90 | /** 91 | * 转化为不可变类型[Neko] 92 | */ 93 | override fun immutable(): Neko = Nyanko(this.code) 94 | 95 | /** 96 | * 得到一个[Map]委托 97 | */ 98 | override fun toMap(): Map = this.mapDelegation() 99 | 100 | /** 101 | * 查询猫猫码字符串中是否存在指定的key 102 | */ 103 | override fun containsKey(key: String): Boolean { 104 | if (empty) return false 105 | 106 | return codeText.contains("$CAT_PS$key$CAT_KV") 107 | } 108 | 109 | /** 110 | * 查询猫猫码字符串中是否存在对应的值。 111 | * 经过转义后进行字符串查询 112 | */ 113 | override fun containsValue(value: String): Boolean { 114 | if (empty) return false 115 | 116 | val encodeValue: String = CatEncoder.encodeParams(value) 117 | return codeText.contains("$CAT_KV$encodeValue$CAT_PS") || codeText.contains("$CAT_KV$encodeValue$CAT_END") 118 | } 119 | 120 | /** 121 | * 获取一个解码后的值 122 | */ 123 | override operator fun get(key: String): String? = CatDecoder.decodeParamsOrNull(getParam(key)) 124 | 125 | /** 126 | * 获取指定字符 127 | * @see CharSequence.get 128 | */ 129 | override operator fun get(index: Int): Char = codeText[index] 130 | 131 | /** 132 | * 如果不存在[CAT_PS],即参数切割符,则说明不存在参数 133 | */ 134 | override fun isEmpty(): Boolean = empty 135 | 136 | /** 137 | * Returns a new character sequence that is a subsequence of this character sequence, 138 | * starting at the specified [startIndex] and ending right before the specified [endIndex]. 139 | * 140 | * @see CharSequence.subSequence 141 | * @param startIndex the start index (inclusive). 142 | * @param endIndex the end index (exclusive). 143 | */ 144 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = codeText.subSequence(startIndex, endIndex) 145 | 146 | 147 | /** 148 | * 缓存上一次的查询结果 149 | * 线程不安全的 150 | */ 151 | private var paramBuffer: CatKV? = null 152 | 153 | /** 154 | * 获取参数 155 | * 得到的值不是反转义的值。如果需要,再转义 156 | * 参考于[CatCodeUtil.getParam] 157 | * @see CatCodeUtil.getParam 158 | */ 159 | private fun getParam(key: String): String? { 160 | val bufferFirst = paramBuffer?.key 161 | val bufferSecond = paramBuffer?.value 162 | if (bufferFirst != null && bufferFirst == key) { 163 | return bufferSecond 164 | } 165 | val paramFind = "$CAT_PS$key$CAT_KV" 166 | val phi: Int = codeText.indexOf(paramFind, startIndex) 167 | if (phi < 0) { 168 | return null 169 | } 170 | val startIndex: Int = phi + paramFind.length 171 | var pei: Int = codeText.indexOf(CAT_PS, startIndex) 172 | if (pei < 0 || pei > endIndex) { 173 | pei = endIndex 174 | } 175 | if (startIndex > codeText.lastIndex || startIndex > pei) { 176 | return null 177 | } 178 | val subParam = codeText.substring(startIndex, pei) 179 | paramBuffer = key cTo subParam 180 | return subParam 181 | } 182 | 183 | /** 184 | * Returns a read-only [Set] of all key/value pairs in this map. 185 | */ 186 | override val entries: Set> 187 | get() = FastNyankoEntrySet() 188 | 189 | 190 | /** 191 | * [Nyanko] 的 set内联类 192 | */ 193 | private inner class FastNyankoEntrySet : Set> { 194 | /** 键值对的长度 */ 195 | override val size: Int get() = _size 196 | 197 | /** 198 | * 查看是否包含了某个键值对 199 | */ 200 | override fun contains(element: Map.Entry): Boolean { 201 | if (empty) return false 202 | 203 | val kv = element.key + CAT_KV + CatEncoder.encodeParams(element.value) 204 | return this@Nyanko.code.contains(kv) 205 | } 206 | 207 | /** 208 | * 查看是否包含所有的键值对 209 | */ 210 | override fun containsAll(elements: Collection>): Boolean { 211 | if (empty) return false 212 | 213 | for (element in elements) { 214 | if (!contains(element)) return false 215 | } 216 | return true 217 | } 218 | 219 | /** 220 | * 是否为空set 221 | */ 222 | override fun isEmpty(): Boolean = this@Nyanko.empty 223 | 224 | /** 225 | * 键值对迭代器 226 | */ 227 | override fun iterator(): Iterator> = CatParamEntryIterator(this@Nyanko.code) 228 | 229 | override fun toString(): String = "Nyanko.entries(${this@Nyanko})" 230 | } 231 | 232 | 233 | /** 234 | * Returns a read-only [Set] of all keys in this map. 235 | */ 236 | override val keys: Set 237 | get() = FastNyankoKeySet() 238 | 239 | 240 | /** 241 | * [keys]的实现内部类 242 | */ 243 | private inner class FastNyankoKeySet : Set { 244 | override val size: Int get() = _size 245 | 246 | /** 247 | * 是否包含某个key 248 | */ 249 | override fun contains(element: String): Boolean { 250 | if (empty) return false 251 | // 判断是否包含:element= 这个字符串 252 | return this@Nyanko.code.contains("$element$CAT_KV") 253 | } 254 | 255 | /** 256 | * 是否contains all 257 | */ 258 | override fun containsAll(elements: Collection): Boolean { 259 | if (empty) return false 260 | for (element in elements) { 261 | if (!contains(element)) return false 262 | } 263 | return true 264 | } 265 | 266 | override fun isEmpty(): Boolean = this@Nyanko.empty 267 | 268 | 269 | override fun iterator(): Iterator { 270 | return CatParamKeyIterator(this@Nyanko.code) 271 | } 272 | 273 | } 274 | 275 | 276 | /** 277 | * Returns a read-only [Collection] of all values in this map. Note that this collection may contain duplicate values. 278 | */ 279 | override val values: Collection 280 | get() = FastNyankoValues() 281 | 282 | 283 | /** 284 | * [values]的实现。 285 | * 不是任何[List] 286 | */ 287 | private inner class FastNyankoValues : Collection { 288 | /** 289 | * Returns the size of the collection. 290 | */ 291 | override val size: Int get() = _size 292 | 293 | /** 294 | * Checks if the specified element is contained in this collection. 295 | */ 296 | override fun contains(element: String): Boolean { 297 | if (empty) return false 298 | // 判断有没有 '=element' 字符串 299 | return this@Nyanko.code.contains("$CAT_KV${CatEncoder.encodeParams(element)}") 300 | } 301 | 302 | /** 303 | * Checks if all elements in the specified collection are contained in this collection. 304 | */ 305 | override fun containsAll(elements: Collection): Boolean { 306 | if (empty) return false 307 | 308 | for (element in elements) { 309 | if (!contains(element)) return false 310 | } 311 | return true 312 | } 313 | 314 | /** 315 | * Returns `true` if the collection is empty (contains no elements), `false` otherwise. 316 | */ 317 | override fun isEmpty(): Boolean = this@Nyanko.empty 318 | 319 | 320 | /** 321 | * iterator 322 | */ 323 | override fun iterator(): Iterator { 324 | return CatParamValueIterator(this@Nyanko.code) 325 | } 326 | } 327 | 328 | override fun hashCode(): Int = this.code.hashCode() 329 | 330 | override fun equals(other: Any?): Boolean { 331 | if (other === this) return true 332 | if (other !is Neko) return false 333 | 334 | return if (other is Nyanko) { 335 | this.code == other.code 336 | } else { 337 | val sameType: Boolean = this.codeType == other.codeType && this.type == other.type 338 | if(!sameType) { 339 | false 340 | } else { 341 | keys.forEach { 342 | val thisValue: String? = this[it] 343 | val otherValue: String? = other[it] 344 | if(thisValue != otherValue) return false 345 | } 346 | true 347 | } 348 | } 349 | } 350 | 351 | 352 | companion object Of { 353 | /** 354 | * 得到[Nyanko]实例的工厂方法。 355 | * [code]应该是一个猫猫码字符串. 356 | */ 357 | @JvmStatic 358 | fun byCode(code: String): Nyanko = Nyanko(code.trim()) 359 | } 360 | 361 | } 362 | 363 | 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /src/main/java/catcode/codes/NoraNyanko.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | @file:JvmName("NekoCodes") 13 | @file:JvmMultifileClass 14 | package catcode.codes 15 | 16 | import catcode.* 17 | import catcode.collection.mapDelegation 18 | 19 | 20 | /** 21 | * 22 | * 基于字符串操作的[NoraNeko]实例。 23 | * 24 | * Nyanko,前身为 FastNeko 。 25 | * 26 | * Nyanko:にやんこ, 意同 ねこ, 更偏于爱称。而FastNeko基于字符串操作,小巧可爱,也符合爱称。 27 | * 28 | * [NoraNyanko]没有对应以字符串操作为主的[MutableNeko], 因此他的[mutable]方法将会使用[MutableMapNeko] 29 | * 30 | * 相比于[MapNeko], [NoraNyanko]在实例化与较短猫猫码处理的消耗会更低, 31 | * 但是无论如何[NoraNyanko.get]的速度也永远比不上hash表的速度。 32 | * 但是对于直接通过字符串构建一个[Neko]来讲,[NoraNyanko]无疑是消耗更低的选择。 33 | * 34 | * [NoraNyanko]构建速度更快、资源消耗更少, 获取静态参数(例如[toString]、[size])的速度更快。 35 | * [NoraNyanko]的构建、获取静态参数速度相比较于[MapNeko]有着几百倍的差距。 36 | * 37 | * 但是在获取、迭代与遍历时相比较于[MapNeko]略逊一筹。 38 | * 39 | * 40 | */ 41 | public class NoraNyanko private constructor(private val code: String) : NoraNeko { 42 | private val _type: String 43 | private val _size: Int 44 | 45 | private val empty: Boolean 46 | override val codeType: String 47 | private val catParentHead: String 48 | private val catHead: String 49 | private val startIndex: Int 50 | private val endIndex: Int 51 | 52 | init { 53 | if (!nekoMatchRegex.matches(code)) { 54 | throw IllegalArgumentException("text \"${this.code}\" is not a cat code text.") 55 | } 56 | 57 | // [CAT: 58 | 59 | // val codeTypeStart: Int = code.indexOf("[") 60 | val codeTypeEnd: Int = code.indexOf(":") 61 | 62 | codeType = code.substring(1, codeTypeEnd) 63 | catParentHead = catHead(codeType) 64 | 65 | // get type from string 66 | startIndex = catParentHead.length 67 | endIndex = this.code.lastIndex 68 | val firstSplitIndex: Int = this.code.indexOf(CAT_PS, startIndex).let { 69 | if (it < 0) endIndex else it 70 | } 71 | // val typeEndIndex = if (firstSplitIndex < 0) _codeText.length else firstSplitIndex 72 | _type = this.code.substring(startIndex, firstSplitIndex) 73 | catHead = catParentHead + _type 74 | empty = this.code.contains(CAT_PS) 75 | // 计算 key-value的个数, 即计算CAT_KV的个数 76 | val kvChar: Char = CAT_KV.first() 77 | _size = this.code.count { it == kvChar } 78 | } 79 | 80 | private val codeText: String = this.code 81 | override fun toString(): String = this.code 82 | override val length: Int = this.code.length 83 | override val size: Int = _size 84 | override val type: String = _type 85 | 86 | /** 87 | * 获取转义前的值。一般普通的[get]方法得到的是反转义后的。 88 | * 此处为保留原本的值不做转义。 89 | */ 90 | override fun getNoDecode(key: String): String? = getParam(key) 91 | 92 | /** 93 | * 转化为可变参的[MutableNeko] 94 | */ 95 | override fun mutable(): MutableNeko = MapNeko.mutableByCode(this.toString()) 96 | 97 | /** 98 | * 转化为不可变类型[Neko] 99 | */ 100 | override fun immutable(): Neko = NoraNyanko(this.code) 101 | 102 | /** 103 | * 转化为[Map] 104 | */ 105 | override fun toMap(): Map = this.mapDelegation() 106 | 107 | /** 108 | * 查询猫猫码字符串中是否存在指定的key 109 | */ 110 | override fun containsKey(key: String): Boolean { 111 | if (empty) return false 112 | return codeText.contains("$CAT_PS$key$CAT_KV") 113 | } 114 | 115 | /** 116 | * 查询猫猫码字符串中是否存在对应的值。 117 | * 经过转义后进行字符串查询 118 | */ 119 | override fun containsValue(value: String): Boolean { 120 | if (empty) return false 121 | val encodeValue: String = CatEncoder.encodeParams(value) 122 | return codeText.contains("$CAT_KV$encodeValue$CAT_PS") || codeText.contains("$CAT_KV$encodeValue$CAT_END") 123 | } 124 | 125 | /** 126 | * 获取一个解码后的值 127 | */ 128 | override operator fun get(key: String): String? = CatDecoder.decodeParamsOrNull(getParam(key)) 129 | 130 | /** 131 | * 获取指定字符 132 | * @see CharSequence.get 133 | */ 134 | override operator fun get(index: Int): Char = codeText[index] 135 | 136 | /** 137 | * 如果不存在[CAT_PS],即参数切割符,则说明不存在参数 138 | */ 139 | override fun isEmpty(): Boolean = empty 140 | 141 | /** 142 | * Returns a new character sequence that is a subsequence of this character sequence, 143 | * starting at the specified [startIndex] and ending right before the specified [endIndex]. 144 | * 145 | * @see CharSequence.subSequence 146 | * @param startIndex the start index (inclusive). 147 | * @param endIndex the end index (exclusive). 148 | */ 149 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = codeText.subSequence(startIndex, endIndex) 150 | 151 | 152 | /** 153 | * 缓存上一次的查询结果。 154 | * 线程不安全的。 155 | */ 156 | private var paramBuffer: CatKV? = null 157 | 158 | /** 159 | * 获取参数。 160 | * 得到的值不是反转义的值。如果需要,再转义。 161 | * 参考于[CatCodeUtil.getParam] 162 | * @see CatCodeUtil.getParam 163 | */ 164 | private fun getParam(key: String): String? { 165 | val bufferFirst = paramBuffer?.key 166 | val bufferSecond = paramBuffer?.value 167 | if (bufferFirst != null && bufferFirst == key) { 168 | return bufferSecond 169 | } 170 | val paramFind = "$CAT_PS$key$CAT_KV" 171 | val phi: Int = codeText.indexOf(paramFind, startIndex) 172 | if (phi < 0) { 173 | return null 174 | } 175 | val startIndex: Int = phi + paramFind.length 176 | var pei: Int = codeText.indexOf(CAT_PS, startIndex) 177 | if (pei < 0 || pei > endIndex) { 178 | pei = endIndex 179 | } 180 | if (startIndex > codeText.lastIndex || startIndex > pei) { 181 | return null 182 | } 183 | val subParam = codeText.substring(startIndex, pei) 184 | paramBuffer = key cTo subParam 185 | return subParam 186 | } 187 | 188 | /** 189 | * Returns a read-only [Set] of all key/value pairs in this map. 190 | */ 191 | override val entries: Set> 192 | get() = FastNoraNyankoEntrySet() 193 | 194 | 195 | /** 196 | * [NoraNyanko] 的 set内联类 197 | */ 198 | private inner class FastNoraNyankoEntrySet : Set> { 199 | /** 键值对的长度 */ 200 | override val size: Int get() = _size 201 | 202 | /** 203 | * 查看是否包含了某个键值对 204 | */ 205 | override fun contains(element: Map.Entry): Boolean { 206 | if (empty) return false 207 | 208 | val kv = element.key + CAT_KV + CatEncoder.encodeParams(element.value) 209 | return this@NoraNyanko.code.contains(kv) 210 | } 211 | 212 | /** 213 | * 查看是否包含所有的键值对 214 | */ 215 | override fun containsAll(elements: Collection>): Boolean { 216 | if (empty) return false 217 | 218 | for (element in elements) { 219 | if (!contains(element)) return false 220 | } 221 | return true 222 | } 223 | 224 | /** 225 | * 是否为空set 226 | */ 227 | override fun isEmpty(): Boolean = this@NoraNyanko.empty 228 | 229 | /** 230 | * 键值对迭代器 231 | */ 232 | override fun iterator(): Iterator> = CatParamEntryIterator(this@NoraNyanko.code) 233 | } 234 | 235 | 236 | /** 237 | * Returns a read-only [Set] of all keys in this map. 238 | */ 239 | override val keys: Set 240 | get() = FastNoraNyankoKeySet() 241 | 242 | 243 | /** 244 | * [keys]的实现内部类 245 | */ 246 | private inner class FastNoraNyankoKeySet : Set { 247 | override val size: Int get() = _size 248 | 249 | /** 250 | * 是否包含某个key 251 | */ 252 | override fun contains(element: String): Boolean { 253 | if (empty) return false 254 | // 判断是否包含:element= 这个字符串 255 | return this@NoraNyanko.code.contains("$element$CAT_KV") 256 | } 257 | 258 | /** 259 | * 是否contains all 260 | */ 261 | override fun containsAll(elements: Collection): Boolean { 262 | if (empty) return false 263 | for (element in elements) { 264 | if (!contains(element)) return false 265 | } 266 | return true 267 | } 268 | 269 | override fun isEmpty(): Boolean = this@NoraNyanko.empty 270 | 271 | 272 | override fun iterator(): Iterator { 273 | return CatParamKeyIterator(this@NoraNyanko.code) 274 | } 275 | 276 | } 277 | 278 | 279 | /** 280 | * Returns a read-only [Collection] of all values in this map. Note that this collection may contain duplicate values. 281 | */ 282 | override val values: Collection 283 | get() = FastNoraNyankoValues() 284 | 285 | 286 | /** 287 | * [values]的实现。 288 | * 不是任何[List] 289 | */ 290 | private inner class FastNoraNyankoValues : Collection { 291 | /** 292 | * Returns the size of the collection. 293 | */ 294 | override val size: Int get() = _size 295 | 296 | /** 297 | * Checks if the specified element is contained in this collection. 298 | */ 299 | override fun contains(element: String): Boolean { 300 | if (empty) return false 301 | // 判断有没有 '=element' 字符串 302 | return this@NoraNyanko.code.contains("$CAT_KV${CatEncoder.encodeParams(element)}") 303 | } 304 | 305 | /** 306 | * Checks if all elements in the specified collection are contained in this collection. 307 | */ 308 | override fun containsAll(elements: Collection): Boolean { 309 | if (empty) return false 310 | 311 | for (element in elements) { 312 | if (!contains(element)) return false 313 | } 314 | return true 315 | } 316 | 317 | /** 318 | * Returns `true` if the collection is empty (contains no elements), `false` otherwise. 319 | */ 320 | override fun isEmpty(): Boolean = this@NoraNyanko.empty 321 | 322 | 323 | /** 324 | * iterator 325 | */ 326 | override fun iterator(): Iterator { 327 | return CatParamValueIterator(this@NoraNyanko.code) 328 | } 329 | } 330 | 331 | override fun hashCode(): Int = this.code.hashCode() 332 | 333 | override fun equals(other: Any?): Boolean { 334 | if (other === this) return true 335 | if (other !is Neko) return false 336 | 337 | return if (other is NoraNyanko) { 338 | this.code == other.code 339 | } else { 340 | val sameType: Boolean = this.codeType == other.codeType && this.type == other.type 341 | if (!sameType) { 342 | false 343 | } else { 344 | keys.forEach { 345 | val thisValue: String? = this[it] 346 | val otherValue: String? = other[it] 347 | if (thisValue != otherValue) return false 348 | } 349 | true 350 | } 351 | } 352 | } 353 | 354 | 355 | companion object Of { 356 | /** 357 | * 得到[NoraNyanko]实例的工厂方法。 358 | * [code]应该是一个猫猫码字符串. 359 | */ 360 | @JvmStatic 361 | fun byCode(code: String): NoraNyanko = NoraNyanko(code.trim()) 362 | } 363 | 364 | } 365 | 366 | 367 | 368 | 369 | 370 | -------------------------------------------------------------------------------- /src/main/java/catcode/WildcatStringTemplate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet All rights reserved. 3 | * Project parent 4 | * File WildcatCodeTemplate.kt 5 | * 6 | * You can contact the author through the following channels: 7 | * github https://github.com/ForteScarlet 8 | * gitee https://gitee.com/ForteScarlet 9 | * email ForteScarlet@163.com 10 | * QQ 1149159218 11 | */ 12 | 13 | package catcode 14 | 15 | import catcode.codes.NoraNyanko 16 | 17 | 18 | /** 19 | * 野猫码模板接口。 20 | */ 21 | public interface WildcatTemplate : CodeTemplate 22 | 23 | 24 | /** 25 | * 野良猫的 [CodeTemplate] 模板实现,以字符串为载体。 26 | */ 27 | public class WildcatStringTemplate(codeType: String, private val utilInstance: WildcatCodeUtil) : WildcatTemplate { 28 | 29 | 30 | /** 31 | * at别人 32 | */ 33 | override fun at(code: String): String = 34 | utilInstance.toCat("at", false, "code=${code.enCatParam()}") 35 | 36 | override fun at(code: Long): String = 37 | utilInstance.toCat("at", false, "code=$code") 38 | 39 | private val atAll: String by lazy(LazyThreadSafetyMode.PUBLICATION) { utilInstance.toCat("at", false, "all=true") } 40 | 41 | /** 42 | * at所有人 43 | */ 44 | override fun atAll(): String = atAll 45 | 46 | /** 47 | * face 48 | */ 49 | override fun face(id: String): String = 50 | utilInstance.toCat("face", false, "id=${id.enCatParam()}") 51 | 52 | override fun face(id: Long): String = 53 | utilInstance.toCat("face", false, "id=$id") 54 | 55 | /** 56 | * big face 57 | */ 58 | override fun bface(id: String): String = 59 | utilInstance.toCat("bface", false, "id=${id.enCatParam()}") 60 | 61 | override fun bface(id: Long): String = 62 | utilInstance.toCat("bface", false, "id=$id") 63 | 64 | /** 65 | * small face 66 | */ 67 | override fun sface(id: String): String = 68 | utilInstance.toCat("sface", false, "id=${id.enCatParam()}") 69 | 70 | override fun sface(id: Long): String = 71 | utilInstance.toCat("sface", false, "id=$id") 72 | 73 | 74 | /** 75 | * image 76 | * @param file id 77 | * @param flash 闪图 78 | */ 79 | override fun image(file: String, flash: Boolean): String = 80 | utilInstance.toCat("image", false, "file=${file.enCatParam()}", "flash=$flash") 81 | 82 | override fun image(file: String): String = 83 | utilInstance.toCat("image", false, "file=${file.enCatParam()}") 84 | 85 | /** 86 | * 语言 87 | * [CAT:record,[file]={1},[magic]={2}] - 发送语音 88 | * {1}为音频文件名称,音频存放在酷Q目录的data\record\下 89 | * {2}为是否为变声,若该参数为true则显示变声标记。该参数可被忽略。 90 | * 举例:[CAT:record,file=1.silk,magic=true](发送data\record\1.silk,并标记为变声) 91 | */ 92 | override fun record(file: String, magic: Boolean): String = 93 | utilInstance.toCat("record", false, "file=${file.enCatParam()}", "magic=$magic") 94 | 95 | override fun record(file: String): String = 96 | utilInstance.toCat("record", false, "file=${file.enCatParam()}") 97 | 98 | 99 | /** 100 | * rps 猜拳 101 | * [CAT:rps,[type]={1}] - 发送猜拳魔法表情 102 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 103 | * 1 - 猜拳结果为石头 104 | * 2 - 猜拳结果为剪刀 105 | * 3 - 猜拳结果为布 106 | */ 107 | override fun rps(type: String): String = 108 | utilInstance.toCat("rps", false, "type=${type.enCatParam()}") 109 | 110 | override fun rps(type: Int): String = 111 | utilInstance.toCat("rps", false, "type=$type") 112 | 113 | 114 | private val rps: String by lazy(LazyThreadSafetyMode.PUBLICATION) { utilInstance.toCat("rps") } 115 | override fun rps(): String = rps 116 | 117 | 118 | private val dice: String by lazy(LazyThreadSafetyMode.PUBLICATION) { utilInstance.toCat("dice") } 119 | override fun dice(): String = dice 120 | 121 | /** 122 | * 骰子 123 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 124 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 125 | */ 126 | override fun dice(type: String): String = utilInstance.toCat("dice", false, "type=${type.enCatParam()}") 127 | override fun dice(type: Int): String = utilInstance.toCat("dice", false, "type=$type") 128 | 129 | 130 | /** 131 | * 戳一戳(原窗口抖动,仅支持好友消息使用) 132 | */ 133 | override fun shake(): String = shake 134 | private val shake: String by lazy(LazyThreadSafetyMode.PUBLICATION) { utilInstance.toCat("shake") } 135 | 136 | /** 137 | * 音乐 138 | * [CAT:music,type={1},id={2},style={3}] 139 | * {1} 音乐平台类型,目前支持qq、163 140 | * {2} 对应音乐平台的数字音乐id 141 | * {3} 音乐卡片的风格。仅 Pro 支持该参数,该参数可被忽略。 142 | * 注意:音乐只能作为单独的一条消息发送 143 | * 例子 144 | * [CAT:music,type=qq,id=422594](发送一首QQ音乐的“Time after time”歌曲到群内) 145 | * [CAT:music,type=163,id=28406557](发送一首网易云音乐的“桜咲く”歌曲到群内) 146 | */ 147 | override fun music(type: String, id: String, style: String?): String = 148 | utilInstance.toCat( 149 | "music", 150 | false, 151 | "type=${type.enCatParam()}", 152 | "id=${id.enCatParam()}", 153 | "style=${style?.enCatParam() ?: ""}" 154 | ) 155 | 156 | override fun music(type: String, id: String): String = 157 | utilInstance.toCat( 158 | "music", 159 | false, 160 | "type=${type.enCatParam()}", 161 | "id=${id.enCatParam()}", 162 | ) 163 | 164 | /** 165 | * [CAT:music,type=custom,url={1},audio={2},title={3},content={4},image={5}] - 发送音乐自定义分享 166 | * 注意:音乐自定义分享只能作为单独的一条消息发送 167 | * @param url {1}为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页)。 168 | * @param audio {2}为音频链接(如mp3链接)。 169 | * @param title {3}为音乐的标题,建议12字以内。 170 | * @param content {4}为音乐的简介,建议30字以内。该参数可被忽略。 171 | * @param image {5}为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片。 172 | * 173 | */ 174 | override fun customMusic(url: String, audio: String, title: String, content: String?, image: String?): String = 175 | utilInstance.toCat( 176 | "music", 177 | false, 178 | "type=custom", 179 | "url=${url.enCatParam()}", 180 | "audio=${audio.enCatParam()}", 181 | "title=${title.enCatParam()}", 182 | "content=${content?.enCatParam() ?: ""}", 183 | "image=${image?.enCatParam() ?: ""}", 184 | ) 185 | 186 | override fun customMusic(url: String, audio: String, title: String): String = 187 | utilInstance.toCat( 188 | "music", 189 | false, 190 | "type=custom", 191 | "url=${url.enCatParam()}", 192 | "audio=${audio.enCatParam()}", 193 | "title=${title.enCatParam()}", 194 | ) 195 | 196 | /** 197 | * [CAT:share,url={1},title={2},content={3},image={4}] - 发送链接分享 198 | * {1}为分享链接。 199 | * {2}为分享的标题,建议12字以内。 200 | * {3}为分享的简介,建议30字以内。该参数可被忽略。 201 | * {4}为分享的图片链接。若参数为空或被忽略,则显示默认图片。 202 | * 注意:链接分享只能作为单独的一条消息发送 203 | */ 204 | override fun share(url: String, title: String, content: String?, image: String?): String = 205 | utilInstance.toCat( 206 | "share", false, 207 | "url=${url.enCatParam()}", 208 | "title=${title.enCatParam()}", 209 | "content=${content?.enCatParam() ?: ""}", 210 | "image=${image?.enCatParam() ?: ""}", 211 | ) 212 | 213 | override fun share(url: String, title: String): String = 214 | utilInstance.toCat( 215 | "share", false, 216 | "url=${url.enCatParam()}", 217 | "title=${title.enCatParam()}", 218 | ) 219 | 220 | } 221 | 222 | 223 | /** 224 | * 野良猫码以 [Neko] 作为载体的 [CodeTemplate] 实现。 225 | */ 226 | public class NoraNekoTemplate(codeType: String, private val wildcatStringTemplate: WildcatTemplate) : WildcatTemplate { 227 | 228 | 229 | /** 230 | * at别人 231 | */ 232 | override fun at(code: String): Neko = NoraNyanko.byCode(wildcatStringTemplate.at(code)) 233 | override fun at(code: Long): Neko = NoraNyanko.byCode(wildcatStringTemplate.at(code)) 234 | 235 | private val atAll by lazy(LazyThreadSafetyMode.PUBLICATION) { NoraNyanko.byCode(wildcatStringTemplate.atAll()) } 236 | 237 | /** 238 | * at所有人 239 | */ 240 | override fun atAll(): Neko = atAll 241 | 242 | /** 243 | * face 244 | */ 245 | override fun face(id: String): Neko = NoraNyanko.byCode(wildcatStringTemplate.face(id)) 246 | override fun face(id: Long): Neko = NoraNyanko.byCode(wildcatStringTemplate.face(id)) 247 | 248 | /** 249 | * big face 250 | */ 251 | override fun bface(id: String): Neko = NoraNyanko.byCode(wildcatStringTemplate.bface(id)) 252 | override fun bface(id: Long): Neko = NoraNyanko.byCode(wildcatStringTemplate.bface(id)) 253 | 254 | /** 255 | * small face 256 | */ 257 | override fun sface(id: String): Neko = NoraNyanko.byCode(wildcatStringTemplate.sface(id)) 258 | override fun sface(id: Long): Neko = NoraNyanko.byCode(wildcatStringTemplate.sface(id)) 259 | 260 | /** 261 | * image 262 | * @param file id 263 | * @param flash 闪图 264 | */ 265 | override fun image(file: String, flash: Boolean): Neko = 266 | NoraNyanko.byCode(wildcatStringTemplate.image(file, flash)) 267 | 268 | override fun image(file: String): Neko = 269 | NoraNyanko.byCode(wildcatStringTemplate.image(file)) 270 | 271 | /** 272 | * 语言 273 | * [CAT:record,[file]={1},[magic]={2}] - 发送语音 274 | * {1}为音频文件名称,音频存放在酷Q目录的data\record\下 275 | * {2}为是否为变声,若该参数为true则显示变声标记。该参数可被忽略。 276 | * 举例:[CAT:record,file=1.silk,magic=true](发送data\record\1.silk,并标记为变声) 277 | */ 278 | override fun record(file: String, magic: Boolean): Neko = 279 | NoraNyanko.byCode(wildcatStringTemplate.record(file, magic)) 280 | 281 | override fun record(file: String): Neko = 282 | NoraNyanko.byCode(wildcatStringTemplate.record(file)) 283 | 284 | /** 285 | * rps 猜拳 286 | * [CAT:rps,[type]={1}] - 发送猜拳魔法表情 287 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 288 | * 1 - 猜拳结果为石头 289 | * 2 - 猜拳结果为剪刀 290 | * 3 - 猜拳结果为布 291 | */ 292 | override fun rps(type: String): Neko = 293 | NoraNyanko.byCode(wildcatStringTemplate.rps(type)) 294 | 295 | override fun rps(type: Int): Neko = 296 | NoraNyanko.byCode(wildcatStringTemplate.rps(type)) 297 | 298 | private val rps by lazy(LazyThreadSafetyMode.PUBLICATION) { NoraNyanko.byCode(wildcatStringTemplate.rps()) } 299 | 300 | override fun rps(): Neko = rps 301 | 302 | private val dice by lazy(LazyThreadSafetyMode.PUBLICATION) { NoraNyanko.byCode(wildcatStringTemplate.dice()) } 303 | 304 | /** 305 | * 骰子 306 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 307 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 308 | */ 309 | override fun dice(): Neko = dice 310 | 311 | override fun dice(type: String): Neko = 312 | NoraNyanko.byCode(wildcatStringTemplate.dice(type)) 313 | 314 | override fun dice(type: Int): Neko = 315 | NoraNyanko.byCode(wildcatStringTemplate.dice(type)) 316 | 317 | private val shake by lazy(LazyThreadSafetyMode.PUBLICATION) { NoraNyanko.byCode(wildcatStringTemplate.shake()) } 318 | 319 | /** 320 | * 戳一戳(原窗口抖动,仅支持好友消息使用) 321 | */ 322 | override fun shake(): Neko = shake 323 | 324 | /** 325 | * 音乐 326 | * [CAT:music,type={1},id={2},style={3}] 327 | * {1} 音乐平台类型,目前支持qq、163 328 | * {2} 对应音乐平台的数字音乐id 329 | * {3} 音乐卡片的风格。仅 Pro 支持该参数,该参数可被忽略。 330 | * 注意:音乐只能作为单独的一条消息发送 331 | * 例子 332 | * [CAT:music,type=qq,id=422594](发送一首QQ音乐的“Time after time”歌曲到群内) 333 | * [CAT:music,type=163,id=28406557](发送一首网易云音乐的“桜咲く”歌曲到群内) 334 | */ 335 | override fun music(type: String, id: String, style: String?): Neko = 336 | NoraNyanko.byCode(wildcatStringTemplate.music(type, id, style)) 337 | 338 | /** 339 | * [CAT:music,type=custom,url={1},audio={2},title={3},content={4},image={5}] - 发送音乐自定义分享 340 | * 注意:音乐自定义分享只能作为单独的一条消息发送 341 | * @param url {1}为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页)。 342 | * @param audio {2}为音频链接(如mp3链接)。 343 | * @param title {3}为音乐的标题,建议12字以内。 344 | * @param content {4}为音乐的简介,建议30字以内。该参数可被忽略。 345 | * @param image {5}为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片。 346 | * 347 | */ 348 | override fun customMusic(url: String, audio: String, title: String, content: String?, image: String?): Neko = 349 | NoraNyanko.byCode(wildcatStringTemplate.customMusic(url, audio, title, content, image)) 350 | 351 | override fun customMusic(url: String, audio: String, title: String): Neko = 352 | NoraNyanko.byCode(wildcatStringTemplate.customMusic(url, audio, title)) 353 | 354 | /** 355 | * [CAT:share,url={1},title={2},content={3},image={4}] - 发送链接分享 356 | * {1}为分享链接。 357 | * {2}为分享的标题,建议12字以内。 358 | * {3}为分享的简介,建议30字以内。该参数可被忽略。 359 | * {4}为分享的图片链接。若参数为空或被忽略,则显示默认图片。 360 | * 注意:链接分享只能作为单独的一条消息发送 361 | */ 362 | override fun share(url: String, title: String, content: String?, image: String?): Neko = 363 | NoraNyanko.byCode(wildcatStringTemplate.share(url, title, content, image)) 364 | 365 | override fun share(url: String, title: String): Neko = 366 | NoraNyanko.byCode(wildcatStringTemplate.share(url, title)) 367 | 368 | } 369 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | love.forte 9 | catcode 10 | 1.0.0-BETA.1 11 | 12 | catcode 13 | 14 | 15 | https://github.com/ForteScarlet/CatCode 16 | 17 | 18 | https://github.com/ForteScarlet/CatCode 19 | 20 | 猫猫码,一个可爱的通用特殊码。/ Cat code, the spirit of CQ code continues, a cute universal special code. 21 | 22 | 23 | 24 | 25 | 26 | 1.4.30-M1 27 | 28 | 1.4.0-rc 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.jetbrains.kotlin 36 | kotlin-stdlib 37 | ${kotlin.version} 38 | 39 | 40 | org.jetbrains.kotlin 41 | kotlin-stdlib-jdk8 42 | ${kotlin.version} 43 | 44 | 45 | 46 | 47 | org.jetbrains.kotlin 48 | kotlin-test 49 | ${kotlin.version} 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-compiler-plugin 61 | 3.7.0 62 | 63 | 64 | 65 | default-compile 66 | none 67 | 68 | 69 | 70 | default-testCompile 71 | none 72 | 73 | 74 | java-compile 75 | compile 76 | 77 | compile 78 | 79 | 80 | 81 | java-test-compile 82 | test-compile 83 | 84 | testCompile 85 | 86 | 87 | 88 | 89 | 1.8 90 | 1.8 91 | 92 | -parameters 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-source-plugin 100 | 3.0.0 101 | 102 | true 103 | 104 | 105 | 106 | compile 107 | 108 | jar-no-fork 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.jetbrains.kotlin 116 | kotlin-maven-plugin 117 | ${kotlin.version} 118 | 119 | 120 | compile 121 | process-sources 122 | 123 | compile 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -Xjvm-default=all 132 | -Xinline-classes 133 | 134 | 1.8 135 | 136 | 137 | 138 | 139 | org.jetbrains.dokka 140 | dokka-maven-plugin 141 | ${dokka.version} 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | install-docJar 153 | install 154 | 155 | 156 | 157 | javadocJar 158 | 159 | 160 | 161 | 162 | 163 | 164 | modules.md 165 | 166 | 167 | 168 | 169 | 8 170 | 171 | true 172 | 173 | true 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | maven-deploy-plugin 184 | 2.8.2 185 | 186 | 187 | default-deploy 188 | deploy 189 | 190 | deploy 191 | 192 | 193 | 194 | 195 | 196 | 197 | org.sonatype.plugins 198 | nexus-staging-maven-plugin 199 | 1.6.7 200 | true 201 | 202 | oss 203 | https://oss.sonatype.org/ 204 | true 205 | 206 | 207 | 208 | 209 | org.apache.maven.plugins 210 | maven-scm-plugin 211 | 1.8.1 212 | 213 | 214 | 215 | org.apache.maven.plugins 216 | maven-release-plugin 217 | 2.5.3 218 | 219 | forked-path 220 | false 221 | -Psonatype-oss-release 222 | false 223 | false 224 | true 225 | 226 | .idea/ 227 | .idea/* 228 | test/ 229 | test/* 230 | .idea/libraries/* 231 | pom.xml 232 | release-pom.xml 233 | 234 | jdonframework.iml 235 | JdonAccessory/jdon-hibernate3x/jdon-hibernate3x.iml 236 | 237 | JdonAccessory/jdon-jdbc/jdon-jdbc.iml 238 | JdonAccessory/jdon-remote/jdon-remote.iml 239 | JdonAccessory/jdon-struts1x/jdon-struts1x.iml 240 | 241 | 242 | 243 | 244 | 245 | org.apache.maven.plugins 246 | maven-scm-plugin 247 | 1.8.1 248 | 249 | 250 | 251 | 257 | 258 | org.apache.maven.plugins 259 | maven-gpg-plugin 260 | 1.5 261 | 262 | 263 | sign-artifacts 264 | install 265 | 266 | sign 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | jcenter 277 | JCenter 278 | https://jcenter.bintray.com/ 279 | 280 | 281 | 282 | 283 | 284 | 285 | MIT License 286 | https://opensource.org/licenses/MIT 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | a single programmer 296 | 297 | ForteScarlet 298 | ForteScarlet@163.com 299 | 300 | 301 | 302 | 303 | 304 | 305 | oss 306 | https://oss.sonatype.org/content/repositories/snapshots 307 | 308 | 309 | oss 310 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 311 | 312 | 313 | 314 | 315 | 316 | release 317 | 318 | 319 | 320 | org.sonatype.plugins 321 | nexus-staging-maven-plugin 322 | 1.6.7 323 | true 324 | 325 | oss 326 | https://oss.sonatype.org/ 327 | true 328 | 329 | 330 | 331 | org.apache.maven.plugins 332 | maven-gpg-plugin 333 | 1.5 334 | 335 | 336 | sign-artifacts 337 | verify 338 | 339 | sign 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | -------------------------------------------------------------------------------- /src/main/java/catcode/CodeTemplate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet All rights reserved. 3 | * Project parent 4 | * File CodeTemplate.kt 5 | * 6 | * You can contact the author through the following channels: 7 | * github https://github.com/ForteScarlet 8 | * gitee https://gitee.com/ForteScarlet 9 | * email ForteScarlet@163.com 10 | * QQ 1149159218 11 | */ 12 | 13 | 14 | @file:Suppress("unused") 15 | 16 | package catcode 17 | 18 | import catcode.codes.Nyanko 19 | 20 | /** 21 | * 定义特殊码的一些模板方法,例如at等。 22 | * 返回值类型全部相同 23 | * 主要基于猫猫码规范定义。 24 | */ 25 | interface CodeTemplate { 26 | /** 27 | * at别人 28 | * @see at 29 | */ 30 | @JvmDefault fun at(code: Long): T = at(code.toString()) 31 | 32 | /** 33 | * at别人 34 | */ 35 | fun at(code: String): T 36 | 37 | /** 38 | * at所有人。 39 | * at所有人即为at类型cat码中存在参数 `all=true` 40 | */ 41 | fun atAll(): T 42 | 43 | /** 44 | * face 45 | */ 46 | fun face(id: String): T 47 | @JvmDefault fun face(id: Long): T = face(id.toString()) 48 | 49 | /** 50 | * big face 51 | */ 52 | fun bface(id: String): T 53 | @JvmDefault fun bface(id: Long): T = bface(id.toString()) 54 | 55 | /** 56 | * small face 57 | */ 58 | fun sface(id: String): T 59 | @JvmDefault fun sface(id: Long): T = sface(id.toString()) 60 | 61 | /** 62 | * image 63 | * @param file 一般代表图片的路径,或者链接。 64 | * @param flash 闪图 65 | */ 66 | fun image(file: String, flash: Boolean): T 67 | @JvmDefault fun image(file: String): T = image(file, false) 68 | 69 | 70 | /** 71 | * 语言 72 | * [CAT:record,[file]={1},[magic]={2}] - 发送语音 73 | * {1}为音频文件名称,音频存放在酷Q目录的data\record\下 74 | * {2}为是否为变声,若该参数为true则显示变声标记。该参数可被忽略。 75 | * 举例:[CAT:record,file=1.silk,magic=true](发送data\record\1.silk,并标记为变声) 76 | */ 77 | fun record(file: String, magic: Boolean): T 78 | @JvmDefault fun record(file: String): T = record(file, false) 79 | 80 | 81 | 82 | /** 83 | * rps 猜拳 84 | * [CAT:rps,[type]={1}] - 发送猜拳魔法表情 85 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 86 | * 1 - 猜拳结果为石头 87 | * 2 - 猜拳结果为剪刀 88 | * 3 - 猜拳结果为布 89 | */ 90 | fun rps(type: String): T 91 | fun rps(): T 92 | @JvmDefault fun rps(type: Int) = rps(type.toString()) 93 | 94 | 95 | /** 96 | * 骰子 97 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 98 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 99 | */ 100 | fun dice(): T 101 | fun dice(type: String): T 102 | @JvmDefault fun dice(type: Int) = dice(type.toString()) 103 | 104 | /** 105 | * 窗口抖动 106 | */ 107 | fun shake(): T 108 | 109 | /** 110 | * 音乐 111 | * [CAT:music,type={1},id={2},style={3}] 112 | * {1} 音乐平台类型,目前支持qq、163 113 | * {2} 对应音乐平台的数字音乐id 114 | * {3} 音乐卡片的风格。仅 Pro 支持该参数,该参数可被忽略。 115 | * 注意:音乐只能作为单独的一条消息发送 116 | * 例子 117 | * [CAT:music,type=qq,id=422594](发送一首QQ音乐的“Time after time”歌曲到群内) 118 | * [CAT:music,type=163,id=28406557](发送一首网易云音乐的“桜咲く”歌曲到群内) 119 | */ 120 | fun music(type: String, id: String, style: String?): T 121 | @JvmDefault fun music(type: String, id: String): T = music(type, id, null) 122 | 123 | /** 124 | * [CAT:music,type=custom,url={1},audio={2},title={3},content={4},image={5}] - 发送音乐自定义分享 125 | * 注意:音乐自定义分享只能作为单独的一条消息发送 126 | * @param url {1}为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页)。 127 | * @param audio {2}为音频链接(如mp3链接)。 128 | * @param title {3}为音乐的标题,建议12字以内。 129 | * @param content {4}为音乐的简介,建议30字以内。该参数可被忽略。 130 | * @param image {5}为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片。 131 | * 132 | */ 133 | fun customMusic(url: String, audio: String, title: String, content: String?, image: String?): T 134 | @JvmDefault fun customMusic(url: String, audio: String, title: String): T = customMusic(url, audio, title, null, null) 135 | 136 | 137 | /** 138 | * [CAT:share,url={1},title={2},content={3},image={4}] - 发送链接分享 139 | * {1}为分享链接。 140 | * {2}为分享的标题,建议12字以内。 141 | * {3}为分享的简介,建议30字以内。该参数可被忽略。 142 | * {4}为分享的图片链接。若参数为空或被忽略,则显示默认图片。 143 | * 注意:链接分享只能作为单独的一条消息发送 144 | */ 145 | fun share(url: String, title: String, content: String?, image: String?): T 146 | @JvmDefault fun share(url: String, title: String): T = share(url, title, null, null) 147 | 148 | 149 | // /** 150 | // * 地点 151 | // * [CAT:location,lat={1},lon={2},title={3},content={4}] 152 | // * {1} 纬度 153 | // * {2} 经度 154 | // * {3} 分享地点的名称 155 | // * {4} 分享地点的具体地址 156 | // */ 157 | // fun location(lat: String, lon: String, title: String, content: String): T 158 | 159 | 160 | } 161 | 162 | 163 | //************************************** 164 | //* String template 165 | //************************************** 166 | 167 | 168 | /** 169 | * 基于 [NekoTemplate] 的模板实现, 以`string`作为猫猫码载体。 170 | * 默认内置于[CatCodeUtil.nekoTemplate] 171 | */ 172 | @Suppress("OverridingDeprecatedMember") 173 | object StringTemplate: CodeTemplate { 174 | @JvmStatic 175 | val instance get() = this 176 | private val utils: CatCodeUtil = CatCodeUtil 177 | private const val AT_ALL: String = "[CAT:at,all=true]" 178 | /** 179 | * at别人. code后面为账号 180 | */ 181 | override fun at(code: String): String = "[CAT:at,code=$code]" 182 | 183 | /** 184 | * at所有人 185 | */ 186 | override fun atAll(): String = AT_ALL 187 | 188 | /** 189 | * face 190 | */ 191 | override fun face(id: String): String = "[CAT:face,id=$id]" 192 | 193 | /** 194 | * big face 195 | */ 196 | override fun bface(id: String): String = "[CAT:bface,id=$id]" 197 | 198 | /** 199 | * small face 200 | */ 201 | override fun sface(id: String): String = "[CAT:sface,id=$id]" 202 | 203 | /** 204 | * image 205 | * @param file file/url/id 206 | * @param flash true=闪图 207 | */ 208 | override fun image(file: String, flash: Boolean): String = "[CAT:image,file=$file,flash=$flash]" 209 | 210 | 211 | 212 | /** 213 | * 语言 214 | * [CAT:record,file={1},magic={2}] - 发送语音 215 | * {1}为音频文件名称,音频存放在酷Q目录的data\record\下 216 | * {2}为是否为变声,若该参数为true则显示变声标记。该参数可被忽略。 217 | * 举例:[CAT:record,file=1.silk,magic=true](发送data\record\1.silk,并标记为变声) 218 | */ 219 | override fun record(file: String, magic: Boolean): String = 220 | "[CAT:record,file=$file,magic=$magic]" 221 | 222 | 223 | /** 224 | * const val for rps 225 | */ 226 | private const val RPS = "[CAT:rps]" 227 | 228 | /** 229 | * rps 猜拳 230 | * [CAT:rps,type={1}] - 发送猜拳魔法表情 231 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 232 | * 1 - 猜拳结果为石头 233 | * 2 - 猜拳结果为剪刀 234 | * 3 - 猜拳结果为布 235 | */ 236 | override fun rps(): String = RPS 237 | 238 | 239 | 240 | /** 241 | * rps 猜拳 242 | * [CAT:rps,type={1}] - 发送猜拳魔法表情 243 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 244 | * 1 - 猜拳结果为石头 245 | * 2 - 猜拳结果为剪刀 246 | * 3 - 猜拳结果为布 247 | */ 248 | override fun rps(type: String): String = "[CAT:rps,type=$type]" 249 | 250 | /** 251 | * const val for dice 252 | */ 253 | private const val DICE = "[CAT:dice]" 254 | 255 | /** 256 | * 骰子 257 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 258 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 259 | */ 260 | override fun dice(): String = DICE 261 | 262 | 263 | /** 264 | * 骰子 265 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 266 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 267 | */ 268 | override fun dice(type: String): String = "[CAT:dice,type=$type]" 269 | 270 | 271 | /** 272 | * const val for shake 273 | */ 274 | private const val SHAKE = "[CAT:shake]" 275 | 276 | /** 277 | * 戳一戳(原窗口抖动,仅支持好友消息使用) 278 | */ 279 | override fun shake(): String = SHAKE 280 | 281 | /** 282 | * 音乐 283 | * [CAT:music,type={1},id={2},style={3}] 284 | * {1} 音乐平台类型,目前支持qq、163 285 | * {2} 对应音乐平台的数字音乐id 286 | * {3} 音乐卡片的风格。仅 Pro 支持该参数,该参数可被忽略。 287 | * 注意:音乐只能作为单独的一条消息发送 288 | * 例子 289 | * [CAT:music,type=qq,id=422594](发送一首QQ音乐的“Time after time”歌曲到群内) 290 | * [CAT:music,type=163,id=28406557](发送一首网易云音乐的“桜咲く”歌曲到群内) 291 | */ 292 | override fun music(type: String, id: String, style: String?): String { 293 | return if (style != null) { 294 | "[CAT:music,type=$type,id=$id,style=$style]" 295 | }else{ 296 | "[CAT:music,type=$type,id=$id]" 297 | } 298 | } 299 | 300 | /** 301 | * [CAT:music,type=custom,url={1},audio={2},title={3},content={4},image={5}] - 发送音乐自定义分享 302 | * 注意:音乐自定义分享只能作为单独的一条消息发送 303 | * @param url {1}为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页)。 304 | * @param audio {2}为音频链接(如mp3链接)。 305 | * @param title {3}为音乐的标题,建议12字以内。 306 | * @param content {4}为音乐的简介,建议30字以内。该参数可被忽略。 307 | * @param image {5}为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片。 308 | * 309 | */ 310 | override fun customMusic(url: String, audio: String, title: String, content: String?, image: String?): String { 311 | return if(content != null && image != null){ 312 | "[CAT:music,type=custom,url=$url,audio=$audio,title=$title,content=$content,image=$image]" 313 | }else{ 314 | val list: MutableList> = mutableListOf("type" cTo "custom", "url" cTo url, "audio" cTo audio, "title" cTo title) 315 | content?.run { 316 | list.add("content" cTo this) 317 | } 318 | image?.run { 319 | list.add("image" cTo this) 320 | } 321 | utils.toCat("music", kv = list.toTypedArray()) 322 | } 323 | } 324 | 325 | /** 326 | * [CAT:share,url={1},title={2},content={3},image={4}] - 发送链接分享 327 | * {1}为分享链接。 328 | * {2}为分享的标题,建议12字以内。 329 | * {3}为分享的简介,建议30字以内。该参数可被忽略。 330 | * {4}为分享的图片链接。若参数为空或被忽略,则显示默认图片。 331 | * 注意:链接分享只能作为单独的一条消息发送 332 | */ 333 | override fun share(url: String, title: String, content: String?, image: String?): String { 334 | return if(content != null && image != null){ 335 | "[CAT:share,url=$url,title=$title,content=$content,image=$image]" 336 | }else{ 337 | val list: MutableList> = mutableListOf("url" cTo url, "title" cTo title) 338 | content?.run { 339 | list.add("content" cTo this) 340 | } 341 | image?.run { 342 | list.add("image" cTo this) 343 | } 344 | utils.toCat("share", kv = list.toTypedArray()) 345 | } 346 | } 347 | 348 | // /** 349 | // * 地点 350 | // * [CAT:location,lat={1},lon={2},title={3},content={4}] 351 | // * {1} 纬度 352 | // * {2} 经度 353 | // * {3} 分享地点的名称 354 | // * {4} 分享地点的具体地址 355 | // */ 356 | // override fun location(lat: String, lon: String, title: String, content: String): String = 357 | // "[CAT:location,lat=$lat,lon=$lon,title=$title,content=$content]" 358 | } 359 | 360 | 361 | 362 | //************************************** 363 | //* KQ template 364 | //************************************** 365 | 366 | 367 | 368 | /** 369 | * 基于 [NekoTemplate] 的模板实现, 以[Neko]作为猫猫码载体。 370 | * 默认内置于[CatCodeUtil.nekoTemplate] 371 | */ 372 | @Suppress("OverridingDeprecatedMember") 373 | object NekoTemplate: CodeTemplate { 374 | @JvmStatic 375 | val instance get() = this 376 | /** 377 | * at别人 378 | */ 379 | override fun at(code: String): Neko = Nyanko.byCode(StringTemplate.at(code)) 380 | 381 | 382 | /** 383 | * at所有人 384 | */ 385 | override fun atAll(): Neko = NekoAtAll 386 | 387 | /** 388 | * face 389 | */ 390 | override fun face(id: String): Neko = Nyanko.byCode(StringTemplate.face(id)) 391 | 392 | /** 393 | * big face 394 | */ 395 | override fun bface(id: String): Neko = Nyanko.byCode(StringTemplate.bface(id)) 396 | 397 | /** 398 | * small face 399 | */ 400 | override fun sface(id: String): Neko = Nyanko.byCode(StringTemplate.sface(id)) 401 | 402 | /** 403 | * image 404 | * @param file id 405 | * @param flash true=闪图 406 | */ 407 | override fun image(file: String, flash: Boolean): Neko = 408 | Nyanko.byCode(StringTemplate.image(file, flash)) 409 | 410 | 411 | /** 412 | * 语言 413 | * [CAT:record,file={1},magic={2}] - 发送语音 414 | * {1}为音频文件名称,音频存放在酷Q目录的data\record\下 415 | * {2}为是否为变声,若该参数为true则显示变声标记。该参数可被忽略。 416 | * 举例:[CAT:record,file=1.silk,magic=true](发送data\record\1.silk,并标记为变声) 417 | */ 418 | override fun record(file: String, magic: Boolean): Neko = 419 | Nyanko.byCode(StringTemplate.record(file, magic)) 420 | 421 | // /** rps */ 422 | // private val RPS: KQCode = Rps 423 | 424 | /** 425 | * rps 猜拳 426 | * [CAT:rps,type={1}] - 发送猜拳魔法表情 427 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 428 | * 1 - 猜拳结果为石头 429 | * 2 - 猜拳结果为剪刀 430 | * 3 - 猜拳结果为布 431 | */ 432 | override fun rps(): Neko = NekoRps 433 | 434 | 435 | /** 436 | * rps 猜拳 437 | * [CAT:rps,type={1}] - 发送猜拳魔法表情 438 | * {1}为猜拳结果的类型,暂不支持发送时自定义。该参数可被忽略。 439 | * 1 - 猜拳结果为石头 440 | * 2 - 猜拳结果为剪刀 441 | * 3 - 猜拳结果为布 442 | */ 443 | override fun rps(type: String): Neko = 444 | Nyanko.byCode(StringTemplate.rps(type)) 445 | 446 | // /** dice */ 447 | // private val DICE: KQCode = Dice 448 | 449 | /** 450 | * 骰子 451 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 452 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 453 | */ 454 | override fun dice(): Neko = NekoDice 455 | 456 | 457 | /** 458 | * 骰子 459 | * [CAT:dice,type={1}] - 发送掷骰子魔法表情 460 | * {1}对应掷出的点数,暂不支持发送时自定义。该参数可被忽略。 461 | * 462 | * @see dice 463 | * 464 | */ 465 | override fun dice(type: String): Neko = 466 | Nyanko.byCode(StringTemplate.dice(type)) 467 | 468 | 469 | // private val SHAKE: KQCode = Shake 470 | 471 | /** 472 | * 戳一戳(原窗口抖动,仅支持好友消息使用) 473 | */ 474 | override fun shake(): Neko = NekoShake 475 | 476 | 477 | /** 478 | * 音乐 479 | * [CAT:music,type={1},id={2},style={3}] 480 | * {1} 音乐平台类型,目前支持qq、163 481 | * {2} 对应音乐平台的数字音乐id 482 | * {3} 音乐卡片的风格。仅 Pro 支持该参数,该参数可被忽略。 483 | * 注意:音乐只能作为单独的一条消息发送 484 | * 例子 485 | * [CAT:music,type=qq,id=422594](发送一首QQ音乐的“Time after time”歌曲到群内) 486 | * [CAT:music,type=163,id=28406557](发送一首网易云音乐的“桜咲く”歌曲到群内) 487 | */ 488 | override fun music(type: String, id: String, style: String?): Neko = 489 | Nyanko.byCode(StringTemplate.music(type, id, style)) 490 | 491 | /** 492 | * [CAT:music,type=custom,url={1},audio={2},title={3},content={4},image={5}] - 发送音乐自定义分享 493 | * 注意:音乐自定义分享只能作为单独的一条消息发送 494 | * @param url {1}为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页)。 495 | * @param audio {2}为音频链接(如mp3链接)。 496 | * @param title {3}为音乐的标题,建议12字以内。 497 | * @param content {4}为音乐的简介,建议30字以内。该参数可被忽略。 498 | * @param image {5}为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片。 499 | * 500 | */ 501 | override fun customMusic(url: String, audio: String, title: String, content: String?, image: String?): Neko = 502 | Nyanko.byCode(StringTemplate.customMusic(url, audio, title, content, image)) 503 | 504 | /** 505 | * [CAT:share,url={1},title={2},content={3},image={4}] - 发送链接分享 506 | * {1}为分享链接。 507 | * {2}为分享的标题,建议12字以内。 508 | * {3}为分享的简介,建议30字以内。该参数可被忽略。 509 | * {4}为分享的图片链接。若参数为空或被忽略,则显示默认图片。 510 | * 注意:链接分享只能作为单独的一条消息发送 511 | */ 512 | override fun share(url: String, title: String, content: String?, image: String?): Neko = 513 | Nyanko.byCode(StringTemplate.share(url, title, content, image)) 514 | 515 | // /** 516 | // * 地点 517 | // * [CAT:location,lat={1},lon={2},title={3},content={4}] 518 | // * {1} 纬度 519 | // * {2} 经度 520 | // * {3} 分享地点的名称 521 | // * {4} 分享地点的具体地址 522 | // */ 523 | // override fun location(lat: String, lon: String, title: String, content: String): Neko = 524 | // Nyanko.byCode(StringTemplate.location(lat, lon, title, content)) 525 | 526 | 527 | 528 | } 529 | 530 | -------------------------------------------------------------------------------- /src/main/java/catcode/NekoAibo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020. ForteScarlet 3 | * 4 | * catCode库相关代码使用 MIT License 开源,请遵守协议相关条款。 5 | * 6 | * about MIT: https://opensource.org/licenses/MIT 7 | * 8 | * 9 | * 10 | * 11 | */ 12 | 13 | package catcode 14 | 15 | import catcode.codes.MapNeko 16 | import catcode.codes.Nyanko 17 | 18 | /** 19 | * 20 | * 猫猫伙伴。定义CatCodeUtil的相关操作。 21 | * 22 | * > Aibo -> 相棒 -> 伙伴 23 | * 24 | * 25 | */ 26 | public abstract class NekoAibo 27 | internal constructor(protected val codeType: String) { 28 | 29 | protected open val catCodeHead: String = catHead(codeType) 30 | 31 | /** 32 | * 获取一个String为载体的[模板][CodeTemplate] 33 | * @see StringTemplate 34 | */ 35 | abstract val stringTemplate: CodeTemplate 36 | 37 | /** 38 | * 获取[Neko]为载体的[模板][CodeTemplate] 39 | * @see NekoTemplate 40 | */ 41 | abstract val nekoTemplate: CodeTemplate 42 | 43 | /** 44 | * 构建一个String为载体类型的[构建器][CodeBuilder] 45 | */ 46 | abstract fun getStringCodeBuilder(type: String, encode: Boolean = true): CodeBuilder 47 | 48 | 49 | /** 50 | * 构建一个[Neko]为载体类型的[构建器][CodeBuilder] 51 | * @param encode 时候对value参数进行转义。 52 | * @param lazy 构建结果是否为 lazy neko。 53 | */ 54 | abstract fun getNekoBuilder(type: String, encode: Boolean): CodeBuilder 55 | 56 | /** 57 | * 构建一个[Neko]为载体类型的[构建器][CodeBuilder] 58 | * @param encode 时候对value参数进行转义。 59 | * @param lazy 构建结果是否为 lazy neko。 60 | */ 61 | abstract fun getLazyNekoBuilder(type: String, encode: Boolean): LazyCodeBuilder 62 | 63 | 64 | /** 65 | * 仅通过一个类型获取一个猫猫码。例如`\[Cat:hi]` 66 | */ 67 | fun toCat(type: String): String { 68 | return "$catCodeHead$type$CAT_END" 69 | } 70 | 71 | /** 72 | * 将参数转化为猫猫码字符串. 73 | * 如果[encode] == true, 则会对[kv]的值进行[转义][CatEncoder.encodeParams] 74 | * 75 | * @since 1.0-1.11 76 | */ 77 | @JvmOverloads 78 | fun toCat(type: String, encode: Boolean = true, vararg kv: CatKV): String { 79 | val pre = "$catCodeHead$type" 80 | return if (kv.isNotEmpty()) { 81 | kv.asSequence().filter { 82 | val v: Any? = it.key 83 | v != null && if (v is CharSequence) v.isNotBlank() else true 84 | }.joinToString(CAT_PS, "$pre$CAT_PS", CAT_END) { 85 | "${it.key}$CAT_KV${ 86 | if (encode) it.value.toString().enCatParam() else it.value 87 | }" 88 | } 89 | } else { 90 | pre + CAT_END 91 | } 92 | } 93 | 94 | /** 95 | * 将参数转化为猫猫码字符串 96 | * @since 1.0-1.11 97 | */ 98 | @JvmOverloads 99 | fun toCat(type: String, encode: Boolean = true, map: Map): String { 100 | val pre = "$catCodeHead$type" 101 | return if (map.isNotEmpty()) { 102 | map.asSequence().filter { 103 | val v: Any? = it.value 104 | v != null && if (v is CharSequence) v.isNotBlank() else true 105 | }.joinToString( 106 | CAT_PS, 107 | "$pre$CAT_PS", 108 | CAT_END 109 | ) { 110 | "${it.key}$CAT_KV${ 111 | if (encode) it.value.toString().enCatParam() else it.value 112 | }" 113 | } 114 | } else { 115 | pre + CAT_END 116 | } 117 | } 118 | 119 | /** 120 | * 将参数转化为猫猫码字符串, [params]的格式应当是`xxx=xxx` 121 | * 如果[encode] == true, 则说明需要对`=`后的值进行转义。 122 | * 如果[encode] == false, 则不会对参数值进行转义,直接拼接为Cat字符串 123 | * @since 1.8.0 124 | */ 125 | @JvmOverloads 126 | fun toCat(type: String, encode: Boolean = true, vararg params: String): String { 127 | // 如果参数为空 128 | return if (params.isNotEmpty()) { 129 | if (encode) { 130 | toCat(type, encode, *params.mapNotNull { 131 | val split: List = it.split(delimiters = CAT_KV_SPLIT_ARRAY, false, 2) 132 | val k: String = split[0] 133 | val v: String = split[1] 134 | if (v.isNotBlank()) k cTo v else null 135 | }.toTypedArray()) 136 | } else { 137 | // 不需要转义, 直接进行字符串拼接 138 | params 139 | .filter { !it.endsWith(CAT_KV) } 140 | .takeIf { it.isNotEmpty() } 141 | // if not empty, 142 | ?.let { "$catCodeHead$type$CAT_PS${it.joinToString(CAT_PS)}$CAT_END" } 143 | ?: "$catCodeHead$type$CAT_END" 144 | } 145 | } else { 146 | "$catCodeHead$type$CAT_END" 147 | } 148 | } 149 | 150 | /** 151 | * 获取无参数的[Neko] 152 | * @param type 猫猫码的类型 153 | */ 154 | open fun toNeko(type: String): Neko = EmptyNeko(type) 155 | 156 | /** 157 | * 根据[Map]类型参数转化为[Neko]实例 158 | * 159 | * @param type 猫猫码的类型 160 | * @param params 参数列表 161 | */ 162 | open fun toNeko(type: String, params: Map): Neko { 163 | return if (params.isEmpty()) { 164 | toNeko(type) 165 | } else { 166 | MapNeko.byMap(type, params) 167 | } 168 | } 169 | 170 | 171 | /** 172 | * 根据参数转化为[Neko]实例 173 | * @param type 猫猫码的类型 174 | * @param params 参数列表 175 | */ 176 | open fun toNeko(type: String, vararg params: CatKV): Neko { 177 | return if (params.isEmpty()) { 178 | toNeko(type) 179 | } else { 180 | MapNeko.byKV(type, *params) 181 | } 182 | } 183 | 184 | 185 | /** 186 | * 根据参数转化为[Neko]实例 187 | * @param type 猫猫码的类型 188 | * @param paramText 参数列表, 例如:"code=123" 189 | */ 190 | @JvmOverloads 191 | open fun toNeko(type: String, encode: Boolean = false, vararg paramText: String): Neko { 192 | return if (paramText.isEmpty()) { 193 | toNeko(type) 194 | } else { 195 | Nyanko.byCode(toCat(type, encode, *paramText)) 196 | } 197 | } 198 | 199 | /** 200 | * 将一段字符串根据字符串与猫猫码来进行切割。 201 | * 不会有任何转义操作。 202 | * @since 1.1-1.11 203 | */ 204 | fun split(text: String): List = split(text) { this } 205 | 206 | /** 207 | * 将一段字符串根据字符串与猫猫码来进行切割, 208 | * 并可以通过[postMap]对切割后的每条字符串进行后置处理。 209 | * 210 | * 不会有任何转义操作。 211 | * 212 | * @param text 文本字符串 213 | * @param postMap 后置转化函数 214 | * @since 1.8.0 215 | */ 216 | fun split(text: String, postMap: String.() -> T): List { 217 | // 准备list 218 | val list: MutableList = mutableListOf() 219 | 220 | val het = catCodeHead 221 | val ent = CAT_END 222 | 223 | // 查找最近一个[Cat:字符 224 | var h = text.indexOf(het) 225 | var le = -1 226 | var e = -1 227 | while (h >= 0) { 228 | // 从头部开始查询尾部 229 | if (e != -1) { 230 | le = e 231 | } 232 | e = text.indexOf(ent, h) 233 | h = if (e < 0) { 234 | // 没找到,查找下一个[Cat: 235 | text.indexOf(het, h + 1) 236 | } else { 237 | // 找到了,截取。 238 | // 首先截取前一段 239 | if (h > 0 && (le + 1) != h) { 240 | list.add(text.substring(le + 1, h).postMap()) 241 | } 242 | // 截取猫猫码 243 | list.add(text.substring(h, e + 1).postMap()) 244 | // 重新查询 245 | text.indexOf(het, e) 246 | } 247 | } 248 | if (list.isEmpty()) { 249 | list.add(text.postMap()) 250 | } 251 | if (e != text.length - 1) { 252 | if (e >= 0) { 253 | list.add(text.substring(e + 1, text.length).postMap()) 254 | } 255 | } 256 | return list 257 | } 258 | 259 | /** 260 | * 从消息字符串中提取出猫猫码字符串 261 | * @param text 消息字符串 262 | * @param index 第几个索引位的猫猫码,默认为0,即第一个 263 | * @since 1.1-1.11 264 | */ 265 | @JvmOverloads 266 | fun getCat(text: String, type: String = "", index: Int = 0): String? { 267 | if (index < 0) { 268 | throw IndexOutOfBoundsException("$index") 269 | } 270 | 271 | var i = -1 272 | var ti: Int 273 | var e = 0 274 | val het = catCodeHead + type 275 | val ent = CAT_END 276 | 277 | do { 278 | ti = text.indexOf(het, e) 279 | if (ti >= 0) { 280 | e = text.indexOf(ent, ti) 281 | if (e >= 0) { 282 | i++ 283 | } else { 284 | e = ti + 1 285 | } 286 | } 287 | } while (ti >= 0 && i < index) 288 | return if (i == index) { 289 | text.substring(ti, e + 1) 290 | } else { 291 | null 292 | } 293 | } 294 | 295 | /** 296 | * 从消息字符串中提取出猫猫码字符串 297 | * @param text 消息字符串 298 | * @param index 第几个索引位的猫猫码,默认为0,即第一个 299 | * @since 1.1-1.11 300 | */ 301 | fun getCat(text: String, index: Int = 0): String? = getCat(text = text, type = "", index = index) 302 | 303 | 304 | /** 305 | * 提取字符串中的全部猫猫码字符串 306 | * @since 1.1-1.11 307 | */ 308 | @JvmOverloads 309 | fun getCats(text: String, type: String = ""): List = getCats(text, type) { it } 310 | 311 | /** 312 | * 提取字符串中的全部猫猫码字符串 313 | * @since 1.8.0 314 | */ 315 | @JvmOverloads 316 | fun getCats(text: String, type: String = "", map: (String) -> T): List { 317 | var ti: Int 318 | var e = 0 319 | val het = catCodeHead + type 320 | val ent = CAT_END 321 | // temp list 322 | val list: MutableList = mutableListOf() 323 | 324 | do { 325 | ti = text.indexOf(het, e) 326 | if (ti >= 0) { 327 | e = text.indexOf(ent, ti) 328 | if (e >= 0) { 329 | list.add(map(text.substring(ti, e + 1))) 330 | } else { 331 | e = ti + 1 332 | } 333 | } 334 | } while (ti >= 0 && e >= 0) 335 | 336 | return list 337 | } 338 | 339 | /** 340 | * 获取文本中的猫猫码的参数。 341 | * 如果文本为null、找不到对应索引的猫猫码、找不到此key,返回null;如果找到了key但是无参数,返回空字符串 342 | * 343 | * 默认情况下获取第一个猫猫码的参数 344 | * @since 1.1-1.11 345 | */ 346 | fun getParam(text: String, paramKey: String, index: Int = 0): String? = 347 | getParam(text = text, paramKey = paramKey, type = "", index = index) 348 | 349 | /** 350 | * 获取文本中的猫猫码的参数。 351 | * 如果文本为null、找不到对应索引的猫猫码、找不到此key,返回null;如果找到了key但是无参数,返回空字符串 352 | * 353 | * 默认情况下获取第一个猫猫码的参数 354 | * 355 | * @param text 正文 356 | * @param type 猫猫码小类型。默认为任意类型。 357 | * @param paramKey 参数的key 358 | * @param index 第几个CAT码。默认为第一个。 359 | * 360 | */ 361 | @JvmOverloads 362 | fun getParam(text: String, type: String = "", paramKey: String, index: Int = 0): String? { 363 | val catHead = catCodeHead + type 364 | val catEnd = CAT_END 365 | val catSpl = CAT_PS 366 | 367 | var from = -1 368 | var end = -1 369 | var i = -1 370 | do { 371 | from = text.indexOf(catHead, from + 1) 372 | if (from >= 0) { 373 | // 寻找结尾 374 | end = text.indexOf(catEnd, from) 375 | if (end >= 0) { 376 | i++ 377 | } 378 | } 379 | } while (from >= 0 && i < index) 380 | 381 | // 索引对上了 382 | if (i == index) { 383 | // 从from开始找参数 384 | val paramFind = ",$paramKey=" 385 | val phi = text.indexOf(paramFind, from) 386 | if (phi < 0) { 387 | return null 388 | } 389 | // 找到了之后,找下一个逗号,如果没有,就用最终结尾的位置 390 | val startIndex = phi + paramFind.length 391 | var pei = text.indexOf(catSpl, startIndex) 392 | // 超出去了 393 | if (pei < 0 || pei > end) { 394 | pei = end 395 | } 396 | if (startIndex > text.lastIndex || startIndex > pei) { 397 | return null 398 | } 399 | return text.substring(startIndex, pei) 400 | } else { 401 | return null 402 | } 403 | } 404 | 405 | /** 406 | * 获取文本字符串中猫猫码字符串的迭代器 407 | * @since 1.1-1.11 408 | * @param text 存在猫猫码正文的文本 409 | * @param type 要获取的猫猫码的类型,如果为空字符串则视为所有,默认为所有。 410 | */ 411 | @JvmOverloads 412 | fun getCatIter(text: String, type: String = ""): Iterator = CatTextIterator(text, type) 413 | 414 | 415 | /** 416 | * 为一个猫猫码字符串得到他的key迭代器 417 | * @param code 猫猫码字符串 418 | * @since 1.8.0 419 | */ 420 | fun getCatKeyIter(code: String): Iterator = CatParamKeyIterator(code) 421 | 422 | /** 423 | * 为一个猫猫码字符串得到他的value迭代器 424 | * @param code 猫猫码字符串 425 | * @since 1.8.0 426 | */ 427 | fun getCatValueIter(code: String): Iterator = CatParamValueIterator(code) 428 | 429 | 430 | /** 431 | * 为一个猫猫码字符串得到他的key-value的键值对迭代器 432 | * @param code 猫猫码字符串 433 | * @since 1.8.0 434 | */ 435 | fun getCatKVIter(code: String): Iterator> = CatParamKVIterator(code) 436 | 437 | 438 | /** 439 | * @see getCats 440 | */ 441 | @Suppress("DEPRECATION") 442 | @Deprecated("param 'decode' not required.") 443 | open fun getNekoList(text: String, type: String, decode: Boolean): List { 444 | val iter = getCatIter(text, type) 445 | val list = mutableListOf() 446 | iter.forEach { list.add(Neko.of(it, decode)) } 447 | return list 448 | } 449 | 450 | /** 451 | * 以[getCatIter]方法为基础获取字符串中全部的[Neko]对象 452 | * @since 1.1-1.11 453 | * @param text 存在猫猫码正文的文本 454 | * @param type 要获取的猫猫码的类型,如果为空字符串则视为所有,默认为所有。 455 | */ 456 | @JvmOverloads 457 | open fun getNekoList(text: String, type: String = ""): List { 458 | val iter: Iterator = getCatIter(text, type) 459 | val list: MutableList = mutableListOf() 460 | iter.forEach { list.add(Neko.of(it)) } 461 | return list 462 | } 463 | 464 | 465 | /** 466 | * 提取出文本中的猫猫码,并封装为[Neko]实例。 467 | * @param text 存在猫猫码的正文 468 | * @param type 要获取的猫猫码的类型,默认为所有类型 469 | * @param index 获取的索引位的猫猫码,默认为0,即第一个 470 | */ 471 | @JvmOverloads 472 | open fun getNeko(text: String, type: String = "", index: Int = 0): Neko? { 473 | val cat: String = getCat(text, type, index) ?: return null 474 | return Neko.of(cat) 475 | } 476 | 477 | /** 478 | * 获取指定索引位的猫猫码,并封装为[Neko]实例。 479 | */ 480 | @Suppress("MemberVisibilityCanBePrivate") 481 | open fun getNeko(text: String, index: Int = 0): Neko? = getNeko(text = text, type = "", index = index) 482 | 483 | /** 484 | * 移除猫猫码,可指定类型 485 | * 具体使用参考[remove] 和 [removeByType] 486 | * @since 1.2-1.12 487 | */ 488 | private fun removeCode( 489 | text: String, 490 | type: String, 491 | trim: Boolean = true, 492 | ignoreEmpty: Boolean = true, 493 | delimiter: CharSequence = "" 494 | ): String { 495 | when { 496 | text.isEmpty() -> { 497 | return text 498 | } 499 | else -> { 500 | val sb = StringBuilder(text.length) 501 | // 移除所有的猫猫码 502 | val head = catCodeHead + type 503 | val end = CAT_END 504 | 505 | var hi: Int = -1 506 | var ei = -1 507 | var nextHi: Int 508 | var sps = 0 509 | var sub: String 510 | var next: Char 511 | 512 | if (text.length < head.length + end.length) { 513 | return text 514 | } 515 | 516 | if (!text.contains(head)) { 517 | return text 518 | } 519 | 520 | do { 521 | hi++ 522 | hi = text.indexOf(head, hi) 523 | next = text[hi + head.length] 524 | // 如果text存在内容,则判断:下一个不是逗号或者结尾 525 | if (type.isNotEmpty() && (next != ',' && next.toString() != end)) { 526 | continue 527 | } 528 | if (hi >= 0) { 529 | // 有一个头 530 | // 寻找下一个尾 531 | ei = text.indexOf(end, hi) 532 | if (ei > 0) { 533 | // 有一个尾,看看下一个头是不是在下一个尾之后 534 | nextHi = text.indexOf(head, hi + 1) 535 | // 如果中间包着一个头,则这个头作为当前头 536 | if (nextHi in 0 until ei) { 537 | hi = nextHi 538 | } 539 | if (hi > 0) { 540 | if (sps > 0) { 541 | sps++ 542 | } 543 | sub = text.substring(sps, hi) 544 | if (!ignoreEmpty || (ignoreEmpty && sub.isNotBlank())) { 545 | if (trim) { 546 | sub = sub.trim() 547 | } 548 | if (sb.isNotEmpty()) { 549 | sb.append(delimiter) 550 | } 551 | sb.append(sub) 552 | } 553 | sps = ei 554 | } else if (hi == 0) { 555 | sps = ei 556 | 557 | } 558 | } 559 | } 560 | } while (hi >= 0 && ei > 0) 561 | 562 | // 没有头了 563 | if (sps != text.lastIndex) { 564 | sub = text.substring(sps + 1) 565 | if (!ignoreEmpty || (ignoreEmpty && sub.isNotBlank())) { 566 | if (trim) { 567 | sub = sub.trim() 568 | } 569 | if (sb.isNotEmpty()) { 570 | sb.append(delimiter) 571 | } 572 | sb.append(sub) 573 | } 574 | } 575 | return sb.toString() 576 | } 577 | } 578 | } 579 | 580 | /** 581 | * 移除字符串中的所有的猫猫码,返回字符串。 582 | * 必须是完整的\[Cat:...]。 583 | * @param text 文本正文 584 | * @param trim 是否对文本执行trim,默认为true 585 | * @param ignoreEmpty 如果字符为纯空白字符,是否忽略 586 | * @param delimiter 切割字符串 587 | */ 588 | @JvmOverloads 589 | fun remove( 590 | text: String, 591 | trim: Boolean = true, 592 | ignoreEmpty: Boolean = true, 593 | delimiter: CharSequence = "" 594 | ): String { 595 | return removeCode(text = text, type = "", trim, ignoreEmpty, delimiter) 596 | } 597 | 598 | /** 599 | * 移除某个类型的字符串中的所有的猫猫码,返回字符串。 600 | * 必须是完整的\[Cat...]。 601 | * @param type 猫猫码的类型 602 | * @param text 文本正文 603 | * @param trim 是否对文本执行trim,默认为true 604 | * @param ignoreEmpty 如果字符为纯空白字符,是否忽略 605 | * @param delimiter 切割字符串 606 | */ 607 | @JvmOverloads 608 | fun removeByType( 609 | text: String, 610 | type: String, 611 | trim: Boolean = true, 612 | ignoreEmpty: Boolean = true, 613 | delimiter: CharSequence = "" 614 | ): String { 615 | return removeCode(text, type, trim, ignoreEmpty, delimiter) 616 | } 617 | 618 | 619 | /** 620 | * 判断某个文本中是否包含了指定条件的猫猫码。 621 | * @param type 猫猫码小类型,例如at, 或者空字符串。 622 | * @param text 正文文本。 623 | * @param params 要匹配的参数列表。只会用于匹配,不会进行转义等操作。 624 | */ 625 | private fun containsFromText( 626 | text: String, 627 | type: String, 628 | vararg params: String 629 | ): Boolean { 630 | val head = catCodeHead + type 631 | val end = CAT_END 632 | return when { 633 | // 文本为空 634 | text.isEmpty() -> false 635 | // 不需要匹配参数 636 | params.isEmpty() -> text.contains(head) 637 | // 需要匹配参数 638 | else -> { 639 | // 头 640 | var startIndex = text.indexOf(head) 641 | // find 642 | while (startIndex >= 0) { 643 | // 尾 644 | val endIndex: Int = text.indexOf(end, startIndex + 1) 645 | // sub text. 646 | val subText: String = text.substring(startIndex, endIndex) 647 | 648 | var allFound = true 649 | 650 | // 寻找其中的参数 651 | for (param in params) { 652 | if (!subText.contains(param)) { 653 | allFound = false 654 | break 655 | } 656 | } 657 | 658 | // 全部都能匹配,则说明存在,return true. 659 | if (allFound) return true 660 | 661 | // 本轮没有找到,找下一轮 662 | startIndex = text.indexOf(head, startIndex + 1) 663 | } 664 | false 665 | } 666 | } 667 | } 668 | 669 | /** 670 | * 判断某个文本中是否包含了指定条件的猫猫码。其中 [params] 参数代表了键值对,因此必须是2的倍数。 671 | * 例如 ` contains("at", text, true, "code", "123456") ` 则代表匹配at类型的猫猫码,其中有一个参数为"code=123456"。 672 | * 673 | * 默认会对参数进行转义。 674 | * 675 | * @see contains 676 | */ 677 | fun contains( 678 | text: String, 679 | type: String, 680 | vararg params: String 681 | ): Boolean = contains(text, type, true, *params) 682 | 683 | 684 | /** 685 | * 判断某个文本中是否包含了指定条件的猫猫码。其中 [params] 参数代表了键值对,因此必须是2的倍数。 686 | * 例如 ` contains("at", text, true, "code", "123456") ` 则代表匹配at类型的猫猫码,其中有一个参数为"code=123456"。 687 | * @param type 猫猫码小类型,例如at, 或者空字符串。 688 | * @param text 正文文本。 689 | * @param params 要匹配的参数列表。由于是键值对,因此必须是2的倍数。 690 | */ 691 | @JvmOverloads 692 | fun contains( 693 | text: String, 694 | type: String = "", 695 | encode: Boolean = true, 696 | vararg params: String = emptyArray() 697 | ): Boolean { 698 | val paramArray: Array = if (params.isNotEmpty()) { 699 | if (params.size % 2 != 0) { 700 | throw IllegalArgumentException("params.size % 2 != 0, params must be key-value type, but size: ${params.size}") 701 | } else { 702 | Array(params.size / 2) { i -> 703 | val index = i * 2 704 | val k: String = params[index] 705 | val v: String = with(params[index + 1]) { 706 | if (encode) this.enCatParam() 707 | else this 708 | } 709 | "$k$CAT_KV$v" 710 | } 711 | } 712 | } else emptyArray() 713 | 714 | // contains 715 | return containsFromText(text = text, type = type, *paramArray) 716 | } 717 | 718 | 719 | /** 720 | * 判断某个文本中是否包含了指定条件的猫猫码。其中 [params] 参数代表了键值对,因此必须是2的倍数。 721 | * 例如 ` contains("at", text, true, "code", "123456") ` 则代表匹配at类型的猫猫码,其中有一个参数为"code=123456"。 722 | * @param type 猫猫码小类型,例如at, 或者空字符串。 723 | * @param text 正文文本。 724 | * @param params 要匹配的参数列表。 725 | */ 726 | fun contains( 727 | text: String, 728 | type: String = "", 729 | encode: Boolean = true, 730 | vararg params: CatKV = emptyArray() 731 | ): Boolean { 732 | val paramArray: Array = if (params.isNotEmpty()) { 733 | Array(params.size) { i -> 734 | val k: String = params[i].key 735 | val v: String = if (encode) params[i].value.enCatParam() 736 | else params[i].value 737 | 738 | "$k$CAT_KV$v" 739 | } 740 | 741 | } else emptyArray() 742 | 743 | // contains. 744 | return containsFromText(text, type, *paramArray) 745 | } 746 | 747 | 748 | } --------------------------------------------------------------------------------