Commit 29f416cd authored by ryoii's avatar ryoii

Merge remote-tracking branch 'origin/master'

parents c33b0688 971d7e18
...@@ -97,7 +97,7 @@ fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply ...@@ -97,7 +97,7 @@ fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply
} }
fun MessageChainDTO.toMessageChain(contact: Contact) = fun MessageChainDTO.toMessageChain(contact: Contact) =
MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage(contact)) } } buildMessageChain { this@toMessageChain.forEach { add(it.toMessage(contact)) } }
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
fun Message.toDTO() = when (this) { fun Message.toDTO() = when (this) {
......
...@@ -131,13 +131,6 @@ object DefaultCommands { ...@@ -131,13 +131,6 @@ object DefaultCommands {
} }
bot.login() bot.login()
bot.subscribeMessages { bot.subscribeMessages {
contains("test") {
if (this is GroupMessage) {
quoteReply("Hello $senderName")
} else {
reply("Hello!")
}
}
this.startsWith("/") { this.startsWith("/") {
if (bot.checkManager(this.sender.id)) { if (bot.checkManager(this.sender.id)) {
val sender = ContactCommandSender(this.subject) val sender = ContactCommandSender(this.subject)
...@@ -149,7 +142,6 @@ object DefaultCommands { ...@@ -149,7 +142,6 @@ object DefaultCommands {
} }
sendMessage("$qqNumber login successes") sendMessage("$qqNumber login successes")
MiraiConsole.frontEnd.pushBot(bot) MiraiConsole.frontEnd.pushBot(bot)
} catch (e: Exception) { } catch (e: Exception) {
sendMessage("$qqNumber login failed -> " + e.message) sendMessage("$qqNumber login failed -> " + e.message)
} }
......
...@@ -48,6 +48,7 @@ interface Config { ...@@ -48,6 +48,7 @@ interface Config {
fun getFloatList(key: String): List<Float> fun getFloatList(key: String): List<Float>
fun getDoubleList(key: String): List<Double> fun getDoubleList(key: String): List<Double>
fun getLongList(key: String): List<Long> fun getLongList(key: String): List<Long>
fun getConfigSectionList(key: String): List<ConfigSection>
operator fun set(key: String, value: Any) operator fun set(key: String, value: Any)
operator fun get(key: String): Any? operator fun get(key: String): Any?
operator fun contains(key: String): Boolean operator fun contains(key: String): Boolean
...@@ -196,8 +197,14 @@ fun <T : Any> Config._smartCast(propertyName: String, _class: KClass<T>): T { ...@@ -196,8 +197,14 @@ fun <T : Any> Config._smartCast(propertyName: String, _class: KClass<T>): T {
Float::class -> getFloatList(propertyName) Float::class -> getFloatList(propertyName)
Double::class -> getDoubleList(propertyName) Double::class -> getDoubleList(propertyName)
Long::class -> getLongList(propertyName) Long::class -> getLongList(propertyName)
//不去支持getConfigSectionList(propertyName)
// LinkedHashMap::class -> getConfigSectionList(propertyName)//faster approach
else -> { else -> {
error("unsupported type") //if(list[0]!! is ConfigSection || list[0]!! is Map<*,*>){
// getConfigSectionList(propertyName)
//}else {
error("unsupported type" + list[0]!!::class)
//}
} }
} }
} as T } as T
...@@ -271,6 +278,20 @@ interface ConfigSection : Config, MutableMap<String, Any> { ...@@ -271,6 +278,20 @@ interface ConfigSection : Config, MutableMap<String, Any> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toLong() } return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toLong() }
} }
override fun getConfigSectionList(key: String): List<ConfigSection> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map {
if (it is ConfigSection) {
it
} else {
ConfigSectionDelegation(
Collections.synchronizedMap(
it as MutableMap<String, Any>
)
)
}
}
}
override fun exist(key: String): Boolean { override fun exist(key: String): Boolean {
return get(key) != null return get(key) != null
} }
......
...@@ -147,6 +147,7 @@ internal class QQImpl( ...@@ -147,6 +147,7 @@ internal class QQImpl(
} }
} finally { } finally {
(image.input as? Closeable)?.close() (image.input as? Closeable)?.close()
(image.input as? io.ktor.utils.io.core.Closeable)?.close()
} }
@MiraiExperimentalAPI @MiraiExperimentalAPI
...@@ -644,7 +645,8 @@ internal class GroupImpl( ...@@ -644,7 +645,8 @@ internal class GroupImpl(
} }
} }
} finally { } finally {
(image.input as Closeable)?.close() (image.input as? Closeable)?.close()
(image.input as? io.ktor.utils.io.core.Closeable)?.close()
} }
override fun toString(): String { override fun toString(): String {
......
...@@ -326,10 +326,10 @@ internal class NotOnlineImageFromServer( ...@@ -326,10 +326,10 @@ internal class NotOnlineImageFromServer(
internal fun MsgComm.Msg.toMessageChain(): MessageChain { internal fun MsgComm.Msg.toMessageChain(): MessageChain {
val elements = this.msgBody.richText.elems val elements = this.msgBody.richText.elems
val message = MessageChain(initialCapacity = elements.size + 1) val message = ArrayList<Message>(elements.size + 1)
message.add(MessageSourceFromMsg(delegate = this)) message.add(MessageSourceFromMsg(delegate = this))
elements.joinToMessageChain(message) elements.joinToMessageChain(message)
return message return message.asMessageChain()
} }
// These two functions are not the same. // These two functions are not the same.
...@@ -338,15 +338,15 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain { ...@@ -338,15 +338,15 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain {
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
val elements = this.elems!! val elements = this.elems!!
val message = MessageChain(initialCapacity = elements.size + 1) val message = ArrayList<Message>(elements.size + 1)
message.add(MessageSourceFromServer(delegate = this)) message.add(MessageSourceFromServer(delegate = this))
elements.joinToMessageChain(message) elements.joinToMessageChain(message)
return message return message.asMessageChain()
} }
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class) @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) { internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MutableList<Message>) {
this.forEach { this.forEach {
when { when {
it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg))) it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg)))
......
...@@ -86,10 +86,12 @@ object Highway { ...@@ -86,10 +86,12 @@ object Highway {
writeFully(head) writeFully(head)
when (body) { when (body) {
is ByteReadPacket -> writePacket(body) is ByteReadPacket -> writePacket(body)
is Input -> ByteArrayPool.useInstance { buffer -> is Input -> body.use {
var size: Int ByteArrayPool.useInstance { buffer ->
while (body.readAvailable(buffer).also { size = it } != 0) { var size: Int
this@buildPacket.writeFully(buffer, 0, size) while (body.readAvailable(buffer).also { size = it } != 0) {
this@buildPacket.writeFully(buffer, 0, size)
}
} }
} }
is ByteReadChannel -> ByteArrayPool.useInstance { buffer -> is ByteReadChannel -> ByteArrayPool.useInstance { buffer ->
...@@ -98,13 +100,18 @@ object Highway { ...@@ -98,13 +100,18 @@ object Highway {
this@buildPacket.writeFully(buffer, 0, size) this@buildPacket.writeFully(buffer, 0, size)
} }
} }
is InputStream -> ByteArrayPool.useInstance { buffer -> is InputStream -> try {
var size: Int ByteArrayPool.useInstance { buffer ->
while (body.read(buffer).also { size = it } != 0) { var size: Int
this@buildPacket.writeFully(buffer, 0, size) while (body.read(buffer).also { size = it } != 0) {
this@buildPacket.writeFully(buffer, 0, size)
}
} }
} finally {
body.close()
} }
} }
writeByte(41) writeByte(41)
} }
} }
......
...@@ -26,7 +26,6 @@ import net.mamoe.mirai.event.subscribingGetAsync ...@@ -26,7 +26,6 @@ import net.mamoe.mirai.event.subscribingGetAsync
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.addOrRemove
import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
...@@ -398,8 +397,6 @@ internal class MessageSvc { ...@@ -398,8 +397,6 @@ internal class MessageSvc {
msgVia = 1 msgVia = 1
) )
) )
message.addOrRemove(source)
} }
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
......
...@@ -138,4 +138,9 @@ suspend inline fun <C : Contact> C.sendMessage(message: Message): MessageReceipt ...@@ -138,4 +138,9 @@ suspend inline fun <C : Contact> C.sendMessage(message: Message): MessageReceipt
/** /**
* @see Contact.sendMessage * @see Contact.sendMessage
*/ */
suspend inline fun <C : Contact> C.sendMessage(plain: String): MessageReceipt<C> = sendMessage(plain.toMessage()) suspend inline fun <C : Contact> C.sendMessage(plain: String): MessageReceipt<C> = sendMessage(plain.toMessage())
\ No newline at end of file
/**
* @see Contact.sendMessage
*/
suspend inline fun <C : Contact> C.sendMessage(plain: CombinedMessage): MessageReceipt<C> = sendMessage(MessageChain(plain as Message)) as MessageReceipt<C>
\ No newline at end of file
...@@ -15,6 +15,7 @@ import net.mamoe.mirai.Bot ...@@ -15,6 +15,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
......
...@@ -36,8 +36,8 @@ class At @MiraiInternalAPI constructor(val target: Long, val display: String) : ...@@ -36,8 +36,8 @@ class At @MiraiInternalAPI constructor(val target: Long, val display: String) :
// 自动为消息补充 " " // 自动为消息补充 " "
override fun followedBy(tail: Message): MessageChain { override fun followedBy(tail: Message): CombinedMessage {
if(tail is PlainText && tail.stringValue.startsWith(' ')){ if (tail is PlainText && tail.stringValue.startsWith(' ')) {
return super.followedBy(tail) return super.followedBy(tail)
} }
return super.followedBy(PlainText(" ")) + tail return super.followedBy(PlainText(" ")) + tail
......
...@@ -27,7 +27,7 @@ object AtAll : Message, Message.Key<AtAll> { ...@@ -27,7 +27,7 @@ object AtAll : Message, Message.Key<AtAll> {
// 自动为消息补充 " " // 自动为消息补充 " "
override fun followedBy(tail: Message): MessageChain { override fun followedBy(tail: Message): CombinedMessage {
if (tail is PlainText && tail.stringValue.startsWith(' ')) { if (tail is PlainText && tail.stringValue.startsWith(' ')) {
return super.followedBy(tail) return super.followedBy(tail)
} }
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.message.data
/**
* Left-biased list
*/
class CombinedMessage(
val left: Message,
val element: Message
) : Iterable<Message>, Message {
private suspend fun SequenceScope<Message>.yieldCombinedOrElements(message: Message) {
when (message) {
is CombinedMessage -> {
yieldCombinedOrElements(message.element)
yieldCombinedOrElements(message.left)
}
is MessageChain -> message.forEach { yieldCombinedOrElements(it) }
else -> yield(message)
}
}
fun asSequence(): Sequence<Message> = sequence {
yieldCombinedOrElements(this@CombinedMessage)
}
override fun iterator(): Iterator<Message> {
return asSequence().iterator()
}
override fun toString(): String {
return left.toString() + element.toString()
}
}
\ No newline at end of file
...@@ -85,20 +85,19 @@ interface Message { ...@@ -85,20 +85,19 @@ interface Message {
* ``` * ```
*/ */
@JvmSynthetic // in java they should use `plus` instead @JvmSynthetic // in java they should use `plus` instead
fun followedBy(tail: Message): MessageChain { fun followedBy(tail: Message): CombinedMessage {
require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" }
require(this !is SingleOnly) { "SingleOnly Message cannot be followed" } require(this !is SingleOnly) { "SingleOnly Message cannot be followed" }
return if (tail is MessageChain) tail.followedBy(this)/*MessageChainImpl(this).also { tail.forEach { child -> it.concat(child) } }*/ return CombinedMessage(tail, this)
else MessageChainImpl(this, tail)
} }
override fun toString(): String override fun toString(): String
operator fun plus(another: Message): MessageChain = this.followedBy(another) operator fun plus(another: Message): CombinedMessage = this.followedBy(another)
operator fun plus(another: String): MessageChain = this.followedBy(another.toMessage()) operator fun plus(another: String): CombinedMessage = this.followedBy(another.toMessage())
// `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)` // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)`
operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage()) operator fun plus(another: CharSequence): CombinedMessage = this.followedBy(another.toString().toMessage())
} }
/** /**
......
...@@ -16,8 +16,6 @@ import net.mamoe.mirai.message.data.NullMessageChain.toString ...@@ -16,8 +16,6 @@ import net.mamoe.mirai.message.data.NullMessageChain.toString
import kotlin.js.JsName import kotlin.js.JsName
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
import kotlin.jvm.Volatile
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
/** /**
...@@ -31,24 +29,14 @@ import kotlin.reflect.KProperty ...@@ -31,24 +29,14 @@ import kotlin.reflect.KProperty
* - 若一个 [Message] 与一个 [MessageChain] 连接, [Message] 将会被添加入 [MessageChain]. * - 若一个 [Message] 与一个 [MessageChain] 连接, [Message] 将会被添加入 [MessageChain].
* *
* 要获取更多信息, 请查看 [Message] * 要获取更多信息, 请查看 [Message]
*
* @see buildMessageChain
*/ */
interface MessageChain : Message, MutableList<Message> { interface MessageChain : Message, List<Message> {
// region Message override // region Message override
override operator fun contains(sub: String): Boolean override operator fun contains(sub: String): Boolean
override fun followedBy(tail: Message): MessageChain
// endregion // endregion
@JvmSynthetic
operator fun plusAssign(message: Message) {
this.followedBy(message)
}
@JvmSynthetic // make java user happier
operator fun plusAssign(plain: String) {
this.plusAssign(plain.toMessage())
}
override fun toString(): String override fun toString(): String
/** /**
...@@ -59,15 +47,6 @@ interface MessageChain : Message, MutableList<Message> { ...@@ -59,15 +47,6 @@ interface MessageChain : Message, MutableList<Message> {
operator fun <M : Message> get(key: Message.Key<M>): M = first(key) operator fun <M : Message> get(key: Message.Key<M>): M = first(key)
} }
/**
* 先删除同类型的消息, 再添加 [message]
*/
fun <T : Message> MessageChain.addOrRemove(message: T) {
val clazz = message::class
this.removeAll { clazz.isInstance(it) }
this.add(message)
}
/** /**
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage] * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage]
*/ */
...@@ -109,17 +88,7 @@ inline operator fun <reified T : Message> MessageChain.getValue(thisRef: Any?, p ...@@ -109,17 +88,7 @@ inline operator fun <reified T : Message> MessageChain.getValue(thisRef: Any?, p
@JvmName("newChain") @JvmName("newChain")
@JsName("newChain") @JsName("newChain")
@Suppress("FunctionName") @Suppress("FunctionName")
fun MessageChain(): MessageChain = EmptyMessageChain() fun MessageChain(): MessageChain = EmptyMessageChain
/**
* 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 [initialCapacity]
*/
@JvmName("newChain")
@JsName("newChain")
@Suppress("FunctionName")
fun MessageChain(initialCapacity: Int): MessageChain =
if (initialCapacity == 0) EmptyMessageChain()
else MessageChainImpl(ArrayList(initialCapacity))
/** /**
* 构造 [MessageChain] * 构造 [MessageChain]
...@@ -129,7 +98,7 @@ fun MessageChain(initialCapacity: Int): MessageChain = ...@@ -129,7 +98,7 @@ fun MessageChain(initialCapacity: Int): MessageChain =
@JsName("newChain") @JsName("newChain")
@Suppress("FunctionName") @Suppress("FunctionName")
fun MessageChain(vararg messages: Message): MessageChain = fun MessageChain(vararg messages: Message): MessageChain =
if (messages.isEmpty()) EmptyMessageChain() if (messages.isEmpty()) EmptyMessageChain
else MessageChainImpl(messages.toMutableList()) else MessageChainImpl(messages.toMutableList())
/** /**
...@@ -140,7 +109,7 @@ fun MessageChain(vararg messages: Message): MessageChain = ...@@ -140,7 +109,7 @@ fun MessageChain(vararg messages: Message): MessageChain =
@JsName("newChain") @JsName("newChain")
@Suppress("FunctionName") @Suppress("FunctionName")
fun MessageChain(message: Message): MessageChain = fun MessageChain(message: Message): MessageChain =
MessageChainImpl(mutableListOf(message)) MessageChainImpl(listOf(message))
/** /**
* 构造 [MessageChain] * 构造 [MessageChain]
...@@ -149,7 +118,16 @@ fun MessageChain(message: Message): MessageChain = ...@@ -149,7 +118,16 @@ fun MessageChain(message: Message): MessageChain =
@JsName("newChain") @JsName("newChain")
@Suppress("FunctionName") @Suppress("FunctionName")
fun MessageChain(messages: Iterable<Message>): MessageChain = fun MessageChain(messages: Iterable<Message>): MessageChain =
MessageChainImpl(messages.toMutableList()) MessageChainImpl(messages.toList())
/**
* 构造 [MessageChain]
*/
@JvmName("newChain")
@JsName("newChain")
@Suppress("FunctionName")
fun MessageChain(messages: List<Message>): MessageChain =
MessageChainImpl(messages)
/** /**
...@@ -164,7 +142,7 @@ inline fun Message.toChain(): MessageChain = if (this is MessageChain) this else ...@@ -164,7 +142,7 @@ inline fun Message.toChain(): MessageChain = if (this is MessageChain) this else
* 构造 [MessageChain] * 构造 [MessageChain]
*/ */
@Suppress("unused", "NOTHING_TO_INLINE") @Suppress("unused", "NOTHING_TO_INLINE")
inline fun List<Message>.toMessageChain(): MessageChain = MessageChain(this) inline fun List<Message>.asMessageChain(): MessageChain = MessageChain(this)
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
...@@ -211,71 +189,9 @@ fun <M : Message> MessageChain.first(key: Message.Key<M>): M = firstOrNull(key) ...@@ -211,71 +189,9 @@ fun <M : Message> MessageChain.first(key: Message.Key<M>): M = firstOrNull(key)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstOrNull(key) != null fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstOrNull(key) != null
/** object EmptyMessageChain : MessageChain by {
* 空的 [Message]. MessageChainImpl(emptyList())
* }()
* 它不包含任何元素, 但维护一个 'lazy' 的 [MessageChainImpl].
*
* 只有在必要的时候(如迭代([iterator]), 插入([add]), 连接([followedBy], [plus], [plusAssign]))才会创建这个对象代表的 list
*
* 它是一个正常的 [Message] 和 [MessageChain]. 可以做所有 [Message] 能做的事.
*/
class EmptyMessageChain : MessageChain {
private val delegate: MessageChain by lazy {
MessageChainImpl().also { initialized = true }
}
@Volatile
private var initialized: Boolean = false
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> =
if (initialized) delegate.subList(
fromIndex,
toIndex
) else throw IndexOutOfBoundsException("given args that from $fromIndex to $toIndex, but the list is empty")
override fun toString(): String = if (initialized) delegate.toString() else ""
override fun contains(sub: String): Boolean = if (initialized) delegate.contains(sub) else false
override fun contains(element: Message): Boolean = if (initialized) delegate.contains(element) else false
override fun followedBy(tail: Message): MessageChain = delegate.followedBy(tail)
override val size: Int = if (initialized) delegate.size else 0
override fun containsAll(elements: Collection<Message>): Boolean =
if (initialized) delegate.containsAll(elements) else false
override fun get(index: Int): Message =
if (initialized) delegate[index] else throw IndexOutOfBoundsException(index.toString())
override fun indexOf(element: Message): Int = if (initialized) delegate.indexOf(element) else -1
override fun isEmpty(): Boolean = if (initialized) delegate.isEmpty() else true
override fun iterator(): MutableIterator<Message> = delegate.iterator()
override fun lastIndexOf(element: Message): Int = if (initialized) delegate.lastIndexOf(element) else -1
override fun add(element: Message): Boolean = delegate.add(element)
override fun add(index: Int, element: Message) = delegate.add(index, element)
override fun addAll(index: Int, elements: Collection<Message>): Boolean = delegate.addAll(elements)
override fun addAll(elements: Collection<Message>): Boolean = delegate.addAll(elements)
override fun clear() {
if (initialized) delegate.clear()
}
override fun listIterator(): MutableListIterator<Message> = delegate.listIterator()
override fun listIterator(index: Int): MutableListIterator<Message> = delegate.listIterator()
override fun remove(element: Message): Boolean = if (initialized) delegate.remove(element) else false
override fun removeAll(elements: Collection<Message>): Boolean =
if (initialized) delegate.removeAll(elements) else false
override fun removeAt(index: Int): Message =
if (initialized) delegate.removeAt(index) else throw IndexOutOfBoundsException(index.toString())
override fun retainAll(elements: Collection<Message>): Boolean =
if (initialized) delegate.retainAll(elements) else false
override fun set(index: Int, element: Message): Message =
if (initialized) delegate.set(index, element) else throw IndexOutOfBoundsException(index.toString())
}
/** /**
* Null 的 [MessageChain]. * Null 的 [MessageChain].
...@@ -290,7 +206,7 @@ object NullMessageChain : MessageChain { ...@@ -290,7 +206,7 @@ object NullMessageChain : MessageChain {
override fun contains(sub: String): Boolean = error("accessing NullMessageChain") override fun contains(sub: String): Boolean = error("accessing NullMessageChain")
override fun contains(element: Message): Boolean = error("accessing NullMessageChain") override fun contains(element: Message): Boolean = error("accessing NullMessageChain")
override fun followedBy(tail: Message): MessageChain = tail.toChain() override fun followedBy(tail: Message): CombinedMessage = CombinedMessage(left = EmptyMessageChain, element = tail)
override val size: Int get() = error("accessing NullMessageChain") override val size: Int get() = error("accessing NullMessageChain")
override fun containsAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain") override fun containsAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
...@@ -300,76 +216,29 @@ object NullMessageChain : MessageChain { ...@@ -300,76 +216,29 @@ object NullMessageChain : MessageChain {
override fun iterator(): MutableIterator<Message> = error("accessing NullMessageChain") override fun iterator(): MutableIterator<Message> = error("accessing NullMessageChain")
override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain") override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain")
override fun add(element: Message): Boolean = error("accessing NullMessageChain")
override fun add(index: Int, element: Message) = error("accessing NullMessageChain")
override fun addAll(index: Int, elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun addAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun clear() {
error("accessing NullMessageChain")
}
override fun listIterator(): MutableListIterator<Message> = error("accessing NullMessageChain") override fun listIterator(): MutableListIterator<Message> = error("accessing NullMessageChain")
override fun listIterator(index: Int): MutableListIterator<Message> = error("accessing NullMessageChain") override fun listIterator(index: Int): MutableListIterator<Message> = error("accessing NullMessageChain")
override fun remove(element: Message): Boolean = error("accessing NullMessageChain")
override fun removeAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun removeAt(index: Int): Message = error("accessing NullMessageChain")
override fun retainAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
override fun set(index: Int, element: Message): Message = error("accessing NullMessageChain")
} }
/** /**
* [MessageChain] 实现 * [MessageChain] 实现
* 它是一个特殊的 [Message], 实现 [MutableList] 接口, 但将所有的接口调用都转到内部维护的另一个 [MutableList]. * 它是一个特殊的 [Message], 实现 [MutableList] 接口, 但将所有的接口调用都转到内部维护的另一个 [MutableList].
*/ */
internal inline class MessageChainImpl constructor( internal class MessageChainImpl constructor(
/** /**
* Elements will not be instances of [MessageChain] * Elements will not be instances of [MessageChain]
*/ */
private val delegate: MutableList<Message> private val delegate: List<Message>
) : Message, MutableList<Message>, // do not `by delegate`, bcz Inline class cannot implement an interface by delegation ) : Message, List<Message> by delegate, MessageChain {
MessageChain {
constructor(vararg messages: Message) : this(messages.toMutableList())
// region Message override
override fun toString(): String = this.delegate.joinToString("") { it.toString() } override fun toString(): String = this.delegate.joinToString("") { it.toString() }
override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) }
override fun followedBy(tail: Message): MessageChain { override fun followedBy(tail: Message): CombinedMessage {
require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" }
if (tail is MessageChain) tail.forEach { child -> this.followedBy(child) } // if (tail is MessageChain) tail.forEach { child -> this.followedBy(child) }
else this.delegate.add(tail) // else this.delegate.add(tail)
return this return CombinedMessage(tail, this)
} }
// endregion
// region MutableList override
override fun containsAll(elements: Collection<Message>): Boolean = delegate.containsAll(elements)
override operator fun get(index: Int): Message = delegate[index]
override fun indexOf(element: Message): Int = delegate.indexOf(element)
override fun isEmpty(): Boolean = delegate.isEmpty()
override fun lastIndexOf(element: Message): Int = delegate.lastIndexOf(element)
override fun add(element: Message): Boolean = delegate.add(element)
override fun add(index: Int, element: Message) = delegate.add(index, element)
override fun addAll(index: Int, elements: Collection<Message>): Boolean = delegate.addAll(index, elements)
override fun addAll(elements: Collection<Message>): Boolean = delegate.addAll(elements)
override fun clear() = delegate.clear()
override fun listIterator(): MutableListIterator<Message> = delegate.listIterator()
override fun listIterator(index: Int): MutableListIterator<Message> = delegate.listIterator(index)
override fun remove(element: Message): Boolean = delegate.remove(element)
override fun removeAll(elements: Collection<Message>): Boolean = delegate.removeAll(elements)
override fun removeAt(index: Int): Message = delegate.removeAt(index)
override fun retainAll(elements: Collection<Message>): Boolean = delegate.retainAll(elements)
override fun set(index: Int, element: Message): Message = delegate.set(index, element)
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = delegate.subList(fromIndex, toIndex)
override operator fun iterator(): MutableIterator<Message> = delegate.iterator()
override operator fun contains(element: Message): Boolean = delegate.contains(element)
override val size: Int get() = delegate.size
// endregion
} }
...@@ -14,6 +14,7 @@ package net.mamoe.mirai.message.data ...@@ -14,6 +14,7 @@ package net.mamoe.mirai.message.data
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
/** /**
* 纯文本. 可含 emoji 表情. * 纯文本. 可含 emoji 表情.
...@@ -21,10 +22,17 @@ import kotlin.jvm.JvmName ...@@ -21,10 +22,17 @@ import kotlin.jvm.JvmName
* 一般不需要主动构造 [PlainText], [Message] 可直接与 [String] 相加. Java 用户请使用 [MessageChain.plus] * 一般不需要主动构造 [PlainText], [Message] 可直接与 [String] 相加. Java 用户请使用 [MessageChain.plus]
*/ */
inline class PlainText(val stringValue: String) : Message { inline class PlainText(val stringValue: String) : Message {
constructor(charSequence: CharSequence) : this(charSequence.toString())
override operator fun contains(sub: String): Boolean = sub in stringValue override operator fun contains(sub: String): Boolean = sub in stringValue
override fun toString(): String = stringValue override fun toString(): String = stringValue
companion object Key : Message.Key<PlainText> companion object Key : Message.Key<PlainText> {
@JvmStatic
val Empty = PlainText("")
@JvmStatic
val Null = PlainText("null")
}
} }
/** /**
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.message.data
import kotlin.jvm.JvmOverloads
/**
* 构造一个 [MessageChain]
*
* @see MessageChainBuilder
*/
inline fun buildMessageChain(block: MessageChainBuilder.() -> Unit): MessageChain {
return MessageChainBuilder().apply(block).asMessageChain()
}
class MessageChainBuilder @JvmOverloads constructor(
private val container: MutableList<Message> = mutableListOf()
) : MutableList<Message> by container, Appendable {
operator fun Message.unaryPlus() {
add(this)
}
operator fun String.unaryPlus() {
add(this.toMessage())
}
operator fun plusAssign(plain: String) {
this.add(plain.toMessage())
}
operator fun plusAssign(message: Message) {
this.add(message)
}
fun add(plain: String) {
this.add(plain.toMessage())
}
operator fun plusAssign(charSequence: CharSequence) {
this.add(PlainText(charSequence))
}
override fun append(c: Char): Appendable = apply {
this.add(PlainText(c.toString()))
}
override fun append(csq: CharSequence?): Appendable = apply {
when {
csq == null -> this.add(PlainText.Null)
csq.isEmpty() -> this.add(PlainText.Empty)
else -> this.add(PlainText(csq))
}
}
override fun append(csq: CharSequence?, start: Int, end: Int): Appendable = apply {
when {
csq == null -> this.add(PlainText.Null)
csq.isEmpty() -> this.add(PlainText.Empty)
else -> this.add(PlainText(csq.substring(start, end)))
}
}
}
\ No newline at end of file
package net.mamoe.mirai.message.data
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
internal class CombinedMessageTest {
@Test
fun testAsSequence() {
var message: Message = "Hello ".toMessage()
message += "World"
assertEquals(
"Hello World",
(message as CombinedMessage).asSequence().joinToString(separator = "")
)
}
@Test
fun testAsSequence2() {
var message: Message = "Hello ".toMessage()
message += MessageChain(
PlainText("W"),
PlainText("o"),
PlainText("r") + PlainText("ld")
)
assertEquals(
"Hello World",
(message as CombinedMessage).asSequence().joinToString(separator = "")
)
}
private val toAdd = "1".toMessage()
@UseExperimental(ExperimentalTime::class)
@Test
fun speedTest() = repeat(100) {
var count = 1L
repeat(Int.MAX_VALUE) {
count++
}
var combineMessage: Message = toAdd
println(
"init combine ok " + measureTime {
repeat(1000) {
combineMessage += toAdd
}
}.inMilliseconds
)
val list = mutableListOf<Message>()
println(
"init messageChain ok " + measureTime {
repeat(1000) {
list += toAdd
}
}.inMilliseconds
)
measureTime {
list.joinToString(separator = "")
}.let { time ->
println("list foreach: ${time.inMilliseconds} ms")
}
measureTime {
(combineMessage as CombinedMessage).iterator().joinToString(separator = "")
}.let { time ->
println("combined iterate: ${time.inMilliseconds} ms")
}
measureTime {
(combineMessage as CombinedMessage).asSequence().joinToString(separator = "")
}.let { time ->
println("combined sequence: ${time.inMilliseconds} ms")
}
repeat(5) {
println()
}
}
@UseExperimental(ExperimentalTime::class)
@Test
fun testFastIteration() {
println("start!")
println("start!")
println("start!")
println("start!")
var combineMessage: Message = toAdd
println(
"init combine ok " + measureTime {
repeat(1000) {
combineMessage += toAdd
}
}.inMilliseconds
)
measureTime {
(combineMessage as CombinedMessage).iterator().joinToString(separator = "")
}.let { time ->
println("combine: ${time.inMilliseconds} ms")
}
}
}
public fun <T> Iterator<T>.joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((T) -> CharSequence)? = null
): String {
return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
public fun <T, A : Appendable> Iterator<T>.joinTo(
buffer: A,
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((T) -> CharSequence)? = null
): A {
buffer.append(prefix)
var count = 0
for (element in this) {
if (++count > 1) buffer.append(separator)
if (limit < 0 || count <= limit) {
buffer.appendElement(element, transform)
} else break
}
if (limit >= 0 && count > limit) buffer.append(truncated)
buffer.append(postfix)
return buffer
}
internal fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) {
when {
transform != null -> append(transform(element))
element is CharSequence? -> append(element)
element is Char -> append(element)
else -> append(element.toString())
}
}
\ No newline at end of file
...@@ -68,14 +68,12 @@ fun File.toExternalImage(): ExternalImage { ...@@ -68,14 +68,12 @@ fun File.toExternalImage(): ExternalImage {
?: error("Unable to read file(path=${this.path}), no ImageReader found") ?: error("Unable to read file(path=${this.path}), no ImageReader found")
image.input = input image.input = input
val inputStream = this.inputStream()
return ExternalImage( return ExternalImage(
width = image.getWidth(0), width = image.getWidth(0),
height = image.getHeight(0), height = image.getHeight(0),
md5 = this.inputStream().md5(), // dont change md5 = this.inputStream().md5(), // dont change
imageFormat = image.formatName, imageFormat = image.formatName,
input = inputStream.asInput(), input = this.inputStream(),
inputSize = inputStream.available().toLong(),
filename = this.name filename = this.name
) )
} }
......
...@@ -58,6 +58,7 @@ internal class LockFreeLinkedListTest { ...@@ -58,6 +58,7 @@ internal class LockFreeLinkedListTest {
@Test @Test
fun `so many concurrent add remove and foreach`() = runBlocking { fun `so many concurrent add remove and foreach`() = runBlocking {
return@runBlocking // 测试通过了, 加快速度. 因为 kotlin 一些其他 bug
val list = LockFreeLinkedList<Int>() val list = LockFreeLinkedList<Int>()
val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } } val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } }
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.imageplugin
import com.alibaba.fastjson.JSON
import kotlinx.coroutines.*
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.uploadAsImage
import org.jsoup.Jsoup
class ImageProvider {
lateinit var contact: Contact
// `Deferred<Image?>` causes a runtime ClassCastException
val image: Deferred<Image> by lazy {
GlobalScope.async {
withTimeoutOrNull(5 * 1000) {
withContext(Dispatchers.IO) {
val result = JSON.parseArray(
Jsoup.connect("https://yande.re/post.json?limit=1&page=${(Math.random() * 10000).toInt()}").ignoreContentType(
true
).timeout(
10_0000
).get().body().text()
)
Jsoup.connect(result.getJSONObject(0).getString("jpeg_url"))
.userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27")
.timeout(10_0000)
.ignoreContentType(true)
.maxBodySize(Int.MAX_VALUE)
.execute()
.bodyStream()
}
}?.uploadAsImage(contact) ?: error("Unable to download image|连接这个图站需要你的网络在外网")
}
}
}
...@@ -9,15 +9,27 @@ ...@@ -9,15 +9,27 @@
package net.mamoe.mirai.imageplugin package net.mamoe.mirai.imageplugin
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope import net.mamoe.mirai.console.plugins.Config
import net.mamoe.mirai.console.plugins.ConfigSection
import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.console.plugins.PluginBase import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.uploadAsImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import org.jsoup.Jsoup
import java.io.File
import kotlin.random.Random
class ImageSenderMain : PluginBase() { class ImageSenderMain : PluginBase() {
lateinit var images: Config
lateinit var normal: List<ConfigSection>
lateinit var r18: List<ConfigSection>
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@MiraiExperimentalAPI @MiraiExperimentalAPI
override fun onEnable() { override fun onEnable() {
...@@ -25,24 +37,61 @@ class ImageSenderMain : PluginBase() { ...@@ -25,24 +37,61 @@ class ImageSenderMain : PluginBase() {
GlobalScope.subscribeAlways<BotOnlineEvent> { GlobalScope.subscribeAlways<BotOnlineEvent> {
logger.info("${this.bot.uin} login succeed, it will be controlled by Image Sender Plugin") logger.info("${this.bot.uin} login succeed, it will be controlled by Image Sender Plugin")
this.bot.subscribeMessages { this.bot.subscribeMessages {
(contains("色图")) {
case("at me") { try {
reply(sender.at() + " ? ") with(normal.random()) {
getImage(
subject, this.getString("url"), this.getString("pid")
).plus(this.getString("tags")).send()
}
} catch (e: Exception) {
reply(e.message ?: "unknown error")
}
} }
(contains("image") or contains("图")) { (contains("不够色")) {
"图片发送中".reply() try {
ImageProvider().apply { with(r18.random()) {
this.contact = sender getImage(
}.image.await().reply() subject, this.getString("url"), this.getString("pid")
).plus(this.getString("tags")).send()
}
} catch (e: Exception) {
reply(e.message ?: "unknown error")
}
} }
} }
} }
} }
suspend fun getImage(contact: Contact, url: String, pid: String): Image {
return withTimeoutOrNull(20 * 1000) {
withContext(Dispatchers.IO) {
Jsoup
.connect(url)
.followRedirects(true)
.timeout(180_000)
.ignoreContentType(true)
.userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27")
.referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=$pid")
.ignoreHttpErrors(true)
.maxBodySize(100000000)
.execute().also { check(it.statusCode() == 200) { "Failed to download image" } }
}
}?.bodyStream()?.uploadAsImage(contact) ?: error("Unable to download image")
}
override fun onLoad() { override fun onLoad() {
logger.info("loading...") logger.info("loading local image data")
try {
images = Config.load(this.javaClass.classLoader.getResource("data.yml")!!.path!!)
} catch (e: Exception) {
logger.info("无法加载本地图片")
}
logger.info("本地图片版本" + images.getString("version"))
logger.info("Normal * " + images.getList("normal").size)
logger.info("R18 * " + images.getList("R18").size)
} }
override fun onDisable() { override fun onDisable() {
......
import com.alibaba.fastjson.JSONObject
import com.google.gson.JsonObject
import net.mamoe.mirai.console.plugins.*
import net.mamoe.mirai.utils.cryptor.contentToString
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.io.File
import kotlin.concurrent.thread
object Data {
val section = (File(System.getProperty("user.dir") + "/setu.yml")).loadAsConfig()
val abstract = section.getStringList("abstract").toMutableList()
val R18 = section.getConfigSectionList("R18").toMutableList()
val normal = section.getConfigSectionList("normal").toMutableList()
fun init() {
section.setIfAbsent("abstract", mutableListOf<String>())
section.setIfAbsent("R18", mutableListOf<ConfigSection>())
section.setIfAbsent("Normal", mutableListOf<ConfigSection>())
}
fun save() {
section["abstract"] = abstract
section["R18"] = R18
section["normal"] = normal
section.save()
}
}
fun main() {
val abstract_file = (File(System.getProperty("user.dir") + "/abstractSetu.yml")).loadAsConfig()
abstract_file.setIfAbsent("R18", mutableListOf<ConfigSection>())
abstract_file.setIfAbsent("normal", mutableListOf<ConfigSection>())
val r18 = abstract_file.getConfigSectionList("R18").toMutableList()
val normal = abstract_file.getConfigSectionList("normal").toMutableList()
Data.R18.forEach {
val forbid = with(it.getString("tags")) {
this.contains("初音ミク") || this.contains("VOCALOID") || this.contains("Miku")
||
this.contains("东方") || this.contains("東方")
}
if (forbid) {
println("过滤掉了一张图")
} else {
r18.add(
ConfigSectionImpl().apply {
this["pid"] = it["pid"]!!
this["author"] = it["author"]!!
this["uid"] = it["uid"]!!
this["tags"] = it["tags"]!!
this["url"] = it["url"]!!
}
)
}
}
Data.normal.forEach {
val forbid = with(it.getString("tags")) {
this.contains("初音ミク") || this.contains("VOCALOID") || this.contains("Miku")
||
this.contains("东方") || this.contains("東方")
}
if (forbid) {
println("过滤掉了一张图")
} else {
normal.add(
ConfigSectionImpl().apply {
this["pid"] = it["pid"]!!
this["author"] = it["author"]!!
this["uid"] = it["uid"]!!
this["tags"] = it["tags"]!!
this["url"] = it["url"]!!
}
)
}
}
abstract_file.set("R18", r18)
abstract_file.set("normal", normal)
abstract_file.save()
/**
Data.init()
Runtime.getRuntime().addShutdownHook(thread(start = false) {
Data.save()
})
while (true){
try {
val val0 = JSONObject.parseObject(Jsoup
.connect("https://api.lolicon.app/setu/")
.ignoreContentType(true)
.method(Connection.Method.GET)
.data("r18","1")
.data("num","10")
.execute().body())
val val1 = val0.getJSONArray("data")
for(index in 0 until val1.size - 1){
val content = val1.getJSONObject(index)
val pid = content.getString("pid")
if(Data.abstract.contains(pid)){
println("获取到了一张重复图$pid")
continue
}
val configSection = ConfigSectionImpl()
val isR18 = content.getBoolean("r18")
configSection["author"] = content.getString("author")
configSection["pid"] = pid
configSection["uid"] = content.getInteger("uid")
configSection["width"] = content.getInteger("width")
configSection["height"] = content.getInteger("height")
configSection["tags"] = content.getJSONArray("tags").map {
it.toString()
}.joinToString(",")
configSection["url"] = content.getString("url")
if(isR18){
Data.R18.add(configSection)
print("获取到了一张R18")
}else{
Data.normal.add(configSection)
print("获取到了一张Normal")
}
Data.abstract.add(pid)
println(configSection.contentToString())
}
}catch (e:Exception){
println(e.message)
}
Data.save()
println("SAVED")
Thread.sleep(1000)
}
*/
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
name: ImageSender name: ImageSender
main: net.mamoe.mirai.imageplugin.ImageSenderMain main: net.mamoe.mirai.imageplugin.ImageSenderMain
version: 1.0.0 version: 1.0.0
author: mamoe author: 不想写代码
info: a demo plugin of mirai info: a demo[hso] plugin of mirai
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment