MiraiForum

    • 注册
    • 登录
    • 搜索
    • 热门
    • 最新
    • 未解决
    • 标签
    • 群组
    • 友情链接
    1. 主页
    2. PigeonYuze
    3. 主题
    • 资料
    • 关注 0
    • 粉丝 2
    • 主题 4
    • 帖子 29
    • 最佳 4
    • 有争议的 0
    • 群组 1

    PigeonYuze 创建的主题

    • PigeonYuze

      一些你可能不知道的神秘 Kotlin 语法糖
      技术交流板块 • kotlin 教程 • • PigeonYuze

      7
      3
      赞同
      7
      帖子
      910
      浏览

      PigeonYuze

      一些奇怪的语法糖

      这些语法糖在 Kotlin 1.7.10 的环境中都可以运行

      具体这些语法糖是在什么时候更新的,还请各位读者自己私下去探索

      本文仅是分析一些笔者在实际项目开发中遇到的神奇的糖,在此进行分享。

      你可以看作本文为抛砖引玉中的砖,可以在本文下进行更多奇怪糖分的讨论。

      不稳定更新中x

      重载运算符

      对应 Kotlin 文档 Kotlin Docs Operator overloading

      在一些常见的语言中,你可能会看到一些支持重载运算符的的语言,如:Rust,C#,C++等众多语言中都支持这一操作

      在编译期间,这些操作符一般都会编译为调用对应的函数

      例如,在kotlin中

      val list = listOf(0,1,2,3,4) val element = list[0]

      这段代码实则等同于

      val list = listOf(0,1,2,3,4) val element = list.get(0)

      其中的 [0] 被编译为了调用函数 get(0)

      因此,调用运算符其实本质上属于调用函数

      那我们可否实现像实现函数一样实现重载运算符呢?

      答案是可以的,让我们以mirai中MessageChain的部分代码为例:

      operator fun get(key: MessageKey<SingleMessage>): SingleMessage? { return firstOrNull { /* ... */ true } as SingleMessage? }

      为便于理解,删除了注解, 部分可省略的声明以及泛型声明

      这段代码的作用是从 MessageChain 中获取支持的元素源

      举个例子,从 MessageChain 中获取所包含的图片信息源

      val message: MessageChain = ...; val imageSource = message[Image.Key]

      在这段代码中,我们可以使用 [] 运算符

      operator fun 是一种标识符,它代表着这个函数会重载运算符
      (当为 覆盖override 时,可省略operator关键词)

      在操作符中,存在有

      一元运算符

      重载函数不需要参数

      例如我们常见的 递增 递减 等操作

      一般来说,重载运算符不应改变调用的对象

      二元运算符

      重载函数需要提供参数

      参数的数量不限, 甚至支持 vararg 选项

      一些特殊的运算符

      例如对于属性的委托(by) 也可以使用运算符实现

      以及如 迭代器 等 操作(并不进行阐述,因为没什么特殊的)都可以运算符重载

      重载操作符操作并不限制代码所在模块

      你可以使用 扩展函数 以扩展指定对象的运算符

      Kotlin 要求重载运算符为成员函数*(在一个对象中,包含接口)*或扩展函数

      运用重载运算符可以实现许多神奇的功能

      可惜的是,在Java中并不存在此类操作,所有在 kotlin 中的重载运算符在 Java 中并没有效果

      Java 会将 Kotlin重载运算符 视为一个方法

      当运算符位于一个类伴生对象中时,你可以直接从类调用运算符
      例如:

      class Example { companion object{ operator fun get(int: Int) { TODO() //... } } } fun testExample() { Example[114514] // 可通过编译 } 一元前缀运算符

      此类运算符并不要求提供参数

      运算符 对应函数名称 + unaryPlus() - unaryMinus() ! not()

      这些函数并不要求返回类型,你可以不返回任何内容.

      递增 & 递减

      此类运算符要求不提供参数

      此类重载函数必须返回一个值, 且应属于原类型 ,该值将分配给使用操作的变量。
      它们不应改变调用的对象。

      注: 前缀与后缀都属于此类操作 a++和++a都调用的是同一个函数

      运算符 对应函数名称 ++ inc() -- dec() 二元运算符

      此类运算符要求提供参数

      以下运算符中,要求提供一个参数

      运算符 对应函数名称 + plus - minus * times / div % rem .. rangeTo

      Kotlin 1.9.0 新增运算符

      ..< rangeUntil

      以下运算符在实际运算符中会为变量重新赋值,需要一个参数

      此类重载函数必须返回一个值, 且应属于原类型 ,该值将分配给使用操作的变量。
      它们不应改变调用的对象。

      运算符 对应函数名称 += plusAssign -= minusAssign *= timesAssign /= divAssign &= remAssign

      以下运算符需要传递一个参数,并且必须返回布尔值Boolean

      运算符 对应函数名称 in contains == equals

      !in 实则等同于 !contains

      在 == 中
      针对null 此操作永远不会调用equals, 始终为true

      一些特殊的运算符

      常有的 [] 索引访问运算符, ()调用运算符, 甚至是 by 都可以通过此重载

      索引访问运算符 []

      对应函数名: get或set

      get 用于访问元素, set 用于修改元素

      不限参数长度, 但至少有一个参数。 不限返回类型.

      对照表:

      运算符 对应函数 a[i] a.get(i) a[i,j] a.get(i,j) a[i] = b a.set(i,b) a[i, j] = b a.set(i, j, b)

      在 set 函数中

      修改后的元素始终在参数的最后一位

      调用运算符 ()

      对应函数名 invoke

      实现后,它看起来就像是在实例化一个对象一样.

      对于 lambda , 这通常被用来调用 lambda

      例如:

      fun example(lambda: String.(Int) -> Unit) { // 我们可以通过 () 调用代替 invoke lambda("String", 114514) // 看起来和调用函数一样 }

      但当 invoke 处于一个类的伴生对象时,可能会导致 构造函数 与 invoke 函数重合的状况

      在这种情况下 kotlin 会优先考虑是否为构造函数,随后被编译为实例化对象

      class TestOperator{ // 默认空构造参数 init { println("--init--") } companion object { operator fun invoke() { println("--operator invoke--") } } } fun main() { TestOperator() }

      在这段代码中,就会得到输出 --init--

      而当运算符未一致时,如:

      class TestOperator{ // 默认空构造参数 init { println("--init--") } companion object { operator fun invoke(int: Int) { println("--operator invoke $int--") } } } fun main() { TestOperator(114514) }

      这时候的输出就是 --operator invoke 114514-- 了,因为它被编译为了调用重载运算符(不符合构造函数)

      比较运算符

      此处逻辑与 Java 一致

      函数名为 compareTo 传入一个参数,始终返回一个Int类型的数据

      一般来说,

      当此对象小于另一个对象时, compareTo 返回小于0的数据(一般为-1) 当此对象值等于另一个对象时, compareTo 返回 0 当此对象大于另一个对象时, compareTo 返回大于0的数据(一般为1) 运算符 对应函数名称 a > b a.compareTo(b) > 0 a < b a.compareTo(b) < 0 a >= b a.compareTo(b) >= 0 a <= b a.compareTo(b) <= 0 委托属性运算符

      在 Kotlin 中存在有委托字段的操作
      例如:

      val finalDelegatedProperty by lazy { } // `lazy {}` 为委托器实现 var mutableDelegateProperty by lazy { }

      通过 重载运算符 你可以实现委托字段的操作

      对于 val 委托属性, 有 getValue(thisRef: Any?, property: KProperty<*>):字段类型

      对于 var 委托属性, 有 getValue(thisRef: Any?, property: KProperty<*>):字段类型, 以及 setValue(thisRef: Any?, property: KProperty<*>, value: 字段类型)

      thisRef 为属性需要的对象, 当为成员属性时为所属对象 反之则为null

      property 是为当前属性的相关信息,如属性名称等

      *在 Kotlin 中实现切片

      这是一段在rust中的字符串切片实现

      fn slice() { let mut string = String::from("Hello World!"); let slice = &string[0..3]; // 值为: Hell }

      Rust 中的切片还支持有如..y, .., x.. 的实现 但这些都难以在 kotlin 中实现,故不参与讨论

      在kotlin中,我们有着针对 x..y 的操作符实现 (range-to 操作符)

      所以, 0..3这段代码实则等于 IntRange(0,3)

      我们只需要一个接受 IntRange 的函数即可

      由于在 String 中并没有定义 get(IntRange) 这一函数, 所以在这里我们需要通过扩展函数以实现功能

      operator fun String.get(range: IntRange): String { return this.substring(range) // 截取字符串指定范围 }

      这样,我们就实现了字符串切片的内容。同样地,我们还可以实现集合切片:

      operator fun <T> List<T>.get(range: IntRange): List<T> { return this.subList(range.first,range.last) }

      这样,我们就可以在 kotlin 中运行以下代码了

      fun slice() { val str = "Hello World!" val strSlice = str[0..3] // 值为 Hell val list = listOf(0,1,2,3,4) val listSlice = list[0..3] // 值为 0,1,2,3 } operator fun String.get(range: IntRange): String { return this.substring(range) } operator fun <T> List<T>.get(range: IntRange): List<T> { return this.subList(range.first,range.last) } *可 null 值 的扩展函数

      或许你会有这样的需求

      一个Map中有多个嵌套Map,

      一般来说,Map通过get函数获取对应的值时,会返回一个可null值 V?

      此时我们尝试获取最后一个嵌套 Map 内的值,就会显得十分不优雅

      val example = mutableMapOf<String, Map<String, Map<String, Map<String,Long>>>>() // 真的会有人这样写吗? fun getFinalValue() { val finalValue: Long = example["Key 0"]!!["Key 1"]!!["Key 2"]!!["Key 3"] // Or ... val finalValue1: Long = example["Key 0"]?.get("Key 1")?.get("Key 2")?.get("Key 3") }

      由于 Kotlin 中的运算符仅支持对于 非null值的调用

      所以,

      要么,我们需要使用一连串的!!以将值转换为不可空值(无法为null) 要么,我们使用?.调用方法,而一连串的?.get(..)则显得更加不美观

      此时,我们可能使用造轮传统来防止这样的清空发生

      同样地,我们依旧需要使用扩展函数以支持从外界重写操作符

      operator fun <K,V> Map<K,V>?.get(key: K): V { return this!![key] // Or more... }

      在这里,我们通过 Map<K,V>? 使它支持了对于可空值的[]语法支持

      让我们尝试用这段代码代替原本的代码吧!

      operator fun <K,V> Map<K,V>?.get(key: K): V { return this!![key] } val example = mutableMapOf<String, Map<String, Map<String, Map<String,Long>>>>() fun getFinalValue() { val finalValue = example["Key 0"]["Key 1"]["Key 2"]["Key 3"] }

      这样,我们就成功使代码看着更加美观。

      不过,这样写具有一定的坏处———你无法从代码层次直接知晓这个值是否可能为null

      这可能会降低你的代码可读性,请酌情使用.

      以此类推,我们还有

      operator fun <T> List<T>?.get(index: Int) = this!![index]

      功能同上,可直接调用 索引运算符

      本地函数 Local Function

      此处的本地函数不指native函数,而是指函数嵌套内的函数

      不建议在实际生产中这么做,它会大大降低你的代码可读性

      总所周知,在Kotlin中,我们可以通过fun关键词声明一个函数

      它看起来会是这样

      fun helloWorld() { println("Hello World!") }

      然而你知道吗,我们还可以往一个函数中套更多的函数

      像这样

      fun helloWorld() { fun getHelloWorldMessage() = "Hello World" println(getHelloWorldMessage()) }

      我们可以发现,这段函数竟然被成功编译,并且成功运行输出Hello World

      让我们用idea自带的显示字节码,查看相关代码

      // class version 55.0 (55) // access flags 0x31 // 普通函数 helloWorld: Unit public final static helloWorld()V L0 // 调用内嵌函数 getHelloWorldMessage() LINENUMBER 5 L0 INVOKESTATIC com/pigeonyuze/DeliciousSugarKt.helloWorld$getHelloWorldMessage ()Ljava/lang/String; POP // More code... // helloWorld 嵌套本地函数 getHelloWorldMessage: String private final static helloWorld$getHelloWorldMessage()Ljava/lang/String; L0 LINENUMBER 4 L0 LDC "HelloWorld" ARETURN L1 MAXSTACK = 1 MAXLOCALS = 0 MAXLOCALS = 0

      由上可见,我们的代码实则被编译成了两个方法
      helloWorld() 和 helloWorld$getHelloWorldMessage() 方法

      而其中本地函数的标识符无论如何为private
      Note: 你始终无法为嵌套函数设置标识符,你始终只能从本地函数所在函数中调用它
      如:

      fun superFunction() { fun childFunction() {} childFunction() // 可以通过编译 private fun childFunction1() {} // 无法通过编译: Modifier 'private' is not applicable to 'local function' // NOTE: 无法向本地函数添加标识符 } fun elseFunction() { childFunction() // 无法通过编译: Unresolved reference : getHelloWorldMessage // NOTE: 无法从外处调用本地函数 }

      或许你可以猜到,你可以无限地嵌套函数

      fun helloWorld() { fun helloWorld0() { fun helloWorld1() { //More shit } } }

      这些函数经过编译后,都会被编译为一个单独的 private final函数。

      值得注意的是,kotlin中的inline函数并不支持本地函数的使用。

      kotlin的inline函数会将原函数体内联到调用方处,因此如果内联了本地函数可能会导致意想不到的错误。

      逻辑或|| Logical OR

      我们大家都知道, 我们可以通过逻辑或门||获取一串布尔值中是否包含真值(true)

      false || true // true
      true || true // true
      false || false //false

      它是按照从左到右的顺序来计算的。

      如果你有着乱翻kotlin代码的习惯的话

      你会在kotlin/native关于Throwable获取栈信息的源码里看到以下代码

      // kotlin.Throwable.kt line 77 private fun Throwable.dumpFullTrace(indent: String, qualifier: String) { this.dumpSelfTrace(indent, qualifier) || return var cause = this.cause while (cause != null) { cause.dumpSelfTrace(indent, "Caused by: ") cause = cause.cause } }

      一句神奇的代码躲藏在其中

      this.dumpSelfTrace(indent, qualifier) || return

      是的没错,这是一个逻辑或门,可是它有着这么一个与众不同的点
      运算符的左侧是布尔值,而右侧却是函数跳出点
      通过反编译为java代码

      if(!this.dumpSelfTrace(indent, qualifier)) { return; }else { Throwable cause = this.cause; while (cause != null) { cause.dumpSelfTrace(indent, "Caused by: "); cause = cause.cause; } }

      Kotlin 编译器将这段||的神奇代码,转变成了if-else。

      当逻辑或运算符左侧不满足时,理应向右继续计算,而此时的右值为return,函数的跳出点。
      那么此时,kotlin该做什么呢?没错,它执行了右侧的语句return代码,这使得程序直接跳出。

      此时,我们甚至可以执行run函数(需要为inline函数,因为这样可以跳出调用处函数),只要它跳出了函数就行

      fun getBoolean(): Boolean { /* More code */ } fun call() { getBoolean() || run { println("Wow, I'm running!") throw Throwable() // Also, you can just 'return' } println("Go go go!") /* More code... */ }

      调用call()后,如果getBoolean不满足则会运行run内联函数,因为内联函数中最后会跳出函数(抛出错误或return函数),run内联函数中的内容都会运行。
      这相当于

      fun getBoolean(): Boolean { /* More code */ } fun call() { if(!getBoolean()) { println("Wow, I'm running!") throw Throwable() //Also, you can just 'return' } println("Go go go!") /* More code... */ }

      理论上,你可以不断||下去,只要最右侧跳出即可

      Random.nextBoolean() || (Random.nextBoolean() && Random.nextBoolean()) || /* More Boolean*/ || return

      这依托代码你是可以成功编译并运行的,他将会像上文一样编译为一段if-else,就像:

      if (!Random.nextBoolean()) { if(!(Random.nextBoolean() && Random.nextBoolean())) { if(!/*More Boolean*/) return } }

      在循环体中,你还可以使用break或continue

      while(true) { booleanValue() || continue booleanValue() || break booleanValue() || return booleanValue() || throw Throwable() }

      这些代码同样也可以编译或运行
      是否在实际项目中使用可根据实际项目复杂度来看
      毕竟过度的||,何尝不是依托呢(笑

      需要 Lambda 的函数

      在讨论如何编写关于 lambda 的函数前,或许我们可以先来了解一下什么是lambda

      在kotlin中,几乎所有的表达式中的{}都会被编译为一个lambda对象

      如果你学习或使用过java,会发现这一点与java亦或是其他语言都有所不同。

      你可以猜想运行以下代码,你会得到哪些输出.

      fun main() { { println("Hello world") } if (true) { println("I am in an if expression") } when { true -> { println("I am in an when expression") } } { println("Invoked lambda by call invoke function") }.invoke(); { println("Invoked lambda by this.()") }() val lambda = { println("Um-mm. why i am in a local variable.") } lambda() // = lambda.invoke() val newLambda = { "Please invoke me." } // UNUSED run { println("Run!") } }

      运行以上代码,我们得到了以下输出

      I am in an if expression I am in an when expression Invoked lambda by call invoke function Invoked lambda by this.() Um-mm. why i am in a local variable Run!

      等等,是不是少了一句"应有的"输出。

      或许我该告诉你,kotlin的{}就是这么反直觉。

      { println("Hello world") }

      这段代码,被解析为了一个 lambda 表达式,而从未调用的 lambda 被编译器所忽略

      或许并没有忽略,在字节码中,这段代码看起来是这样的

      LINENUMBER 2 L0 GETSTATIC ... POP

      在字节码层面,它执行了压栈和出栈操作,但仅此而已。

      if (true) { println("I am in an if expression") } when { true -> { println("I am in an when expression") } }

      而这两段代码中的{}并没编译为 lambda 表达式,故可以直接运行。

      lambda 表达式并不会解析为一个单独的 函数/方法

      要想调用 lambda 表达式,你应该通过 invoke 函数进行调用

      或者你可以使用 kotlin.run 这一个内联函数

      public inline fun <R> run(block: () -> R): R { /* more codes */ return block() }

      这是一个需要传入 lambda 的函数

      我们来看看它的参数声明部分

      block: () -> R block 为参数名 () -> R 为参数类型

      让我们从 () -> R 中谈谈.

      -> 表明了这是一个 lambda 体 -> 后的内容(R)表明了这一个lambda体需要返回的类型 -> 前的内容(())表明了这一个lambda体需要提供的类型

      以此类推,我们现在由以下表达式

      String.(Int) -> Any

      如果你知道什么是扩展函数,这里会更好理解一些

      fun String.lambda(it: Int): Any String 是接收器, 在 lambda 体中使用 this 调用 Int 是形参, 在 lambda 体中通常使用 it 调用,也可以使用形参声明规定调用名称, 如`{ int -> /**/ } Any 是返回类型

      你也可以使用泛型,像是这样

      K.(V) -> R

      写在函数参数里面,它看起来像是这样:

      fun <K,V,R> invoke(name: K.(V) -> R) {} // 使用 `name` 调用 lambda

      或者也可以作为扩展函数,像这样:

      fun <K,V,R> (K.(V) -> R).invoke() {} // 使用 `this` 调用 lambda

      你甚至可以使用 suspend 标识这个 lambda 可以为挂起的。例如:

      suspend fun (suspend () -> Any).run() { this.invoke() } suspend fun run(arg: suspend () -> Any) { this.invoke() }

      总结一下,如果你需要将 lambda 作为函数的 形参/扩展函数接收器
      你可以这么写:

      suspend fun <K,V,R> (suspend K.(V) -> R).run0() {} suspend fun <K,V,R> (suspend K.(V) -> R).run1(run: suspend K.(V) -> R) {} fun <K,V,R> (K.(V) -> R).run2() {} // ... fun <K,R> (K.() -> R).run3() {} fun <R> (() -> R).run4() {} fun <R> run5(arg: () -> R) {}

      实际生产可参考案例

      suspend fun handleData() {} fun main() = suspend { handleData() }.runBlock() fun (suspend () -> Any).runBlock() { runBlocking { invoke() } }
    • PigeonYuze

      无法发送合并转发信息
      BUG反馈 • • PigeonYuze

      2
      0
      赞同
      2
      帖子
      266
      浏览

      PigeonYuze

      描述

      在尝试发送合并转发信息后会报错,导致无法发送信息

      并在控制台中打印以下内容

      023-02-10 11:41:39 I/stdout: MultiMsgApplyUpRsp#1116125958 {

      2023-02-10 11:41:39 I/stdout: bytesUpIpV6=[]

      2023-02-10 11:41:39 I/stdout: result=0x000000C0(192)

      2023-02-10 11:41:39 I/stdout: uint32UpIp=[]

      2023-02-10 11:41:39 I/stdout: uint32UpPort=[]

      2023-02-10 11:41:39 I/stdout: uint32UpV6Port=[]

      2023-02-10 11:41:39 I/stdout: }

      bots下日志(等级为ALL)

      [0m2023-02-10 11:41:39 V/Net 385528772: Send: SummaryCard.ReqSummaryCard(SummaryCard.ReqSummaryCard)

      2023-02-10 11:41:39 V/Net 385528772: Recv: SummaryCard.ReqSummaryCard

      2023-02-10 11:41:39 V/Net 385528772: Send: MultiMsg.ApplyUp(MultiMsg.ApplyUp)

      2023-02-10 11:41:39 E/Net 385528772: Exception in decoding packet.

      java.lang.IllegalStateException: Protocol error: MultiMsg.ApplyUp failed with result 192

      at net.mamoe.mirai.internal.network.protocol.packet.chat.MultiMsg$ApplyUp.decode(MultiMsg.kt:93)

      at net.mamoe.mirai.internal.network.components.PacketCodecImpl.processBody(PacketCodec.kt:491)

      at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$PacketDecodePipeline.processBody(CommonNetworkHandler.kt:157)

      at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$PacketDecodePipeline.access$processBody(CommonNetworkHandler.kt:102)

      at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$PacketDecodePipeline$1$3$1.invokeSuspend(CommonNetworkHandler.kt:126)

      at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$PacketDecodePipeline$1$3$1.invoke(CommonNetworkHandler.kt)

      at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$PacketDecodePipeline$1$3$1.invoke(CommonNetworkHandler.kt)

      at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)

      at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:112)

      at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)

      at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)

      at kotlinx.coroutines.BuildersKt.launch(Unknown Source)

      at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)

      at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)

      at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$PacketDecodePipeline$1.invokeSuspend(CommonNetworkHandler.kt:126)

      at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

      at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)

      at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)

      at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)

      at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)

      at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)

      at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)

      at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)

      at java.base/java.lang.Thread.run(Thread.java:833)

      环境: jdk

      java 17.0.1 2021-10-19 LTS

      Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39)

      Java HotSpot(TM) 64-Bit Server VM (build 17.0.1+12-LTS-39, mixed mode, sharing)

      mirai

      Running MiraiConsole v2.13.4, built on 2023-01-21 21:19:40.

      Frontend Terminal: version 2.13.4, provided by Mamoe Technologies

      Permission Service: Built In Permission Service

      登录协议
      IPAD

    • PigeonYuze

      GroupBotSuffix - 为你的bot群名片增加有趣的后缀
      插件发布 • kotlin jvm • • PigeonYuze

      45
      2
      赞同
      45
      帖子
      6129
      浏览

      PigeonYuze

      GroupBotSuffix

      基于mirai的自动增加qqbot群名后缀的插件

      通过本插件,你可以实现在bot的每一个群聊都自动修改群名片为含后缀 (如现在是北京时间xx:xx此类的表达)

      github链接

      releases链接

      请使用版本号大于 1.3.0 的包,在小于 1.3.0 的版本中,可能会出现被 mirai 服务器断开连接的问题

      如何使用?

      将 releases 下的最新包下载,随后丢到plugins里面

      随后打开mcl在加载完毕后停止,打开config页面,修改本插件的Setting.yml

      默认会在成功加载后每隔一分钟修改一次

      配置说明 waitTimeMS
      每次间隔的时间 单位为毫秒
      不建议将此项设得过快 因为mirai并不主动推送群bot名片修改的事件(用户查询bot群名片/发送信息时才可能修改) 过快并不一定有效 open
      添加后缀类型的值
      可以为以下内容 NOW_TIME 现在的时间 可提供参数自行设置 默认为HH:mm:ss HOW_LONG_TO_DISTANCE 距离什么时候还有多久 需要用参数提供指定日期
      年月日用-分割,且位于开头(可不提供年) 时分秒用:分割(可不提供)
      返回的格式与提供的格式一致 CPU_LOAD 系统cpu占用率 JVM_CPU_LOAD jvm可使用的cpu占用率 MEMORY_LOAD 系统内存占用率 JVM_MEMORY_LOAD jvm可使用内存占用率 content
      后缀的内容
      使用%s为默认配置的调用
      使用%加参数内容为有参数的配置调用 separator
      bot昵称到后缀的分割(默认会重命名为 bot 名称 + 后缀分隔符 + 后缀内容) waitGroupMS
      经过多少毫秒后修改下一个群聊的群名片 单位为毫秒
    • PigeonYuze

      YamlBot - 基于yaml配置的指令编写插件
      插件发布 • kotlin yaml 自定义 • • PigeonYuze

      21
      1
      赞同
      21
      帖子
      2481
      浏览

      PigeonYuze

      YamlBot

      https://github.com/PigeonYuze/YamlBot

      一个基于mirai自带的config中的yaml配置以开发指令的插件

      由于yaml对空格的敏感 在出现相关保存时,请检查您的配置文件是否正确

      同时插件中的部分功能需要ffmpeg的支持,您可以选择性地选择是否加入该功能(调用上传语音相关功能时会自动将.mp3格式转换为silk)

      详细内容可查看readme中的介绍

      以一言为例
      你可以通过以下的yaml代码

      # 指令处理 COMMAND: - name: - '/hikokoto' - 一言 answeringMethod: QUOTE answerContent: '『 %call-hitokoto% 』 —— %call-from%' run: - use: HTTP call: content args: - 'https://v1.hitokoto.cn' name: content - use: BASE call: parseJson args: - '%call-content%' - hitokoto name: hitokoto - use: BASE call: parseJson args: - '%call-content%' - from name: from

      实现以下的效果
      d810e081-a701-4a6e-a1cf-3e4b5b9868ea-image.png

      以上功能在插件成功加载后会自动生成

      另,您所编写的指令可以上传至your-config 以功能-原名的格式上传您的代码

      年轻人的第一个插件何止是瑟图插件

    • 1 / 1