Merge branch 'dev' of https://github.com/mamoe/mirai into dev

This commit is contained in:
sandtechnology 2020-08-18 02:28:11 +08:00
commit 410f371c74
16 changed files with 289 additions and 84 deletions

View File

@ -36,6 +36,10 @@ object Versions {
const val bintray = "1.8.5"
}
object Logging {
const val slf4j = "1.7.30"
const val log4j = "2.13.3"
}
}
@Suppress("unused")

View File

@ -8,33 +8,33 @@
package net.mamoe.mirai.javatest;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.CoroutineScopeKt;
import net.mamoe.mirai.event.*;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
public class SimpleListenerHostTest {
@Test
public void test() {
public void testJavaSimpleListenerHostWork() {
AtomicBoolean called = new AtomicBoolean();
final SimpleListenerHost host = new SimpleListenerHost() {
@EventHandler
public void testListen(
AbstractEvent event
) {
System.out.println(event);
}
@Override
public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception) {
exception.printStackTrace();
called.set(true);
}
};
CoroutineScope scope = CoroutineScopeKt.CoroutineScope(EmptyCoroutineContext.INSTANCE);
Events.registerEvents(scope, host);
EventKt.broadcast(new AbstractEvent() {
});
if (!called.get()) {
throw new AssertionError("JavaTest: SimpleListenerHost Failed.");
}
}
}

View File

@ -12,9 +12,7 @@
package net.mamoe.mirai.qqandroid.contact
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.io.core.Closeable
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.*
@ -33,9 +31,13 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.createToGroup
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.estimateLength
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.*
import java.io.InputStream
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
@ -130,18 +132,18 @@ internal class GroupImpl(
set(newValue) {
checkBotPermission(MemberPermission.ADMINISTRATOR)
//if (_announcement != newValue) {
val oldValue = _announcement
_announcement = newValue
launch {
bot.network.run {
TroopManagement.GroupOperation.memo(
client = bot.client,
groupCode = id,
newMemo = newValue
).sendWithoutExpect()
}
GroupEntranceAnnouncementChangeEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
val oldValue = _announcement
_announcement = newValue
launch {
bot.network.run {
TroopManagement.GroupOperation.memo(
client = bot.client,
groupCode = id,
newMemo = newValue
).sendWithoutExpect()
}
GroupEntranceAnnouncementChangeEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
}
//}
}
@ -151,18 +153,18 @@ internal class GroupImpl(
set(newValue) {
checkBotPermission(MemberPermission.ADMINISTRATOR)
//if (_allowMemberInvite != newValue) {
val oldValue = _allowMemberInvite
_allowMemberInvite = newValue
launch {
bot.network.run {
TroopManagement.GroupOperation.allowMemberInvite(
client = bot.client,
groupCode = id,
switch = newValue
).sendWithoutExpect()
}
GroupAllowMemberInviteEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
val oldValue = _allowMemberInvite
_allowMemberInvite = newValue
launch {
bot.network.run {
TroopManagement.GroupOperation.allowMemberInvite(
client = bot.client,
groupCode = id,
switch = newValue
).sendWithoutExpect()
}
GroupAllowMemberInviteEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
}
//}
}
@ -208,18 +210,18 @@ internal class GroupImpl(
set(newValue) {
checkBotPermission(MemberPermission.ADMINISTRATOR)
//if (_muteAll != newValue) {
val oldValue = _muteAll
_muteAll = newValue
launch {
bot.network.run {
TroopManagement.GroupOperation.muteAll(
client = bot.client,
groupCode = id,
switch = newValue
).sendWithoutExpect()
}
GroupMuteAllEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
val oldValue = _muteAll
_muteAll = newValue
launch {
bot.network.run {
TroopManagement.GroupOperation.muteAll(
client = bot.client,
groupCode = id,
switch = newValue
).sendWithoutExpect()
}
GroupMuteAllEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
}
//}
}
}
@ -443,5 +445,37 @@ internal class GroupImpl(
(image.input as? Closeable)?.close()
}
/**
* 上传一个语音消息以备发送.
* 请注意这是一个实验性api且随时会被删除
* @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 1 MB)
*/
@JvmSynthetic
@MiraiExperimentalAPI
override suspend fun uploadGroupVoice(input: InputStream): Voice {
val content = ByteArray(input.available())
input.read(content)
if (content.size > 1048576) {
throw OverFileSizeMaxException()
}
val md5 = MiraiPlatformUtils.md5(content)
return bot.network.run {
val response: PttStore.GroupPttUp.Response.RequireUpload =
PttStore.GroupPttUp(bot.client, bot.id, 0L, md5, content.size.toLong()).sendAndExpect()
HighwayHelper.uploadPttToServers(
bot,
response.uploadIpList.zip(response.uploadPortList),
content,
md5,
response.uKey,
response.fileKey
)
Voice("${md5.toUHexString("")}.amr", md5, content.size.toLong(), "")
}
}
override fun toString(): String = "Group($id)"
}

View File

@ -413,9 +413,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
logger.info { "Syncing friend message history..." }
withTimeoutOrNull(30000) {
launch(CoroutineName("Syncing friend message history")) { syncFromEvent<MessageSvcPbGetMsg.GetMsgSuccess, Unit> { Unit } }
// 别问我为什么要发两个 我也不知道 反正它能用
MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, null, firstSync = true).sendAndExpect<Packet>()
MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, null, firstSync = true).sendAndExpect<Packet>()
MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, null).sendAndExpect<Packet>()
} ?: error("timeout syncing friend message history")
logger.info { "Syncing friend message history: Success" }
}

View File

@ -128,8 +128,6 @@ internal open class QQAndroidClient(
lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin
internal val firstSyncPackets: AtomicInt = atomic(0) // 启动时候仅将所有好友信息设为已读的包
internal suspend inline fun useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
if (bot.client.serverList.isEmpty()) {
throw NoServerAvailableException(null)

View File

@ -61,14 +61,10 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
client: QQAndroidClient,
syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
syncCookie: ByteArray?, //PbPushMsg.msg.msgHead.msgTime
firstSync: Boolean = false
): OutgoingPacket = buildOutgoingUniPacket(
client
) {
//println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
if (firstSync) {
client.firstSyncPackets.getAndAdd(1)
}
writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(),
MsgSvc.PbGetMsgReq(
@ -150,10 +146,13 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
val messages = resp.uinPairMsgs.asFlow()
.filterNot { it.msg == null }
.flatMapConcat { it.msg!!.asFlow() }
.also {
MessageSvcPbDeleteMsg.delete(bot, it)
} // 删除消息
.flatMapConcat {
it.msg!!.asFlow()
.filter { msg: MsgComm.Msg -> msg.msgHead.msgTime > it.lastReadTime.toLong() and 4294967295L }
}.also {
MessageSvcPbDeleteMsg.delete(bot, it) // 删除消息
// todo 实现一个锁来防止重复收到消息
}
.mapNotNull<MsgComm.Msg, Packet> { msg ->
suspend fun createGroupForBot(groupUin: Long): Group? {
@ -277,9 +276,6 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
*/
166 -> {
if (bot.client.firstSyncPackets.value != 0) {
return@mapNotNull null
}
if (msg.msgHead.fromUin == bot.id) {
loop@ while (true) {
val instance = bot.client.getFriendSeq()
@ -383,9 +379,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
override suspend fun QQAndroidBot.handle(packet: Response) {
when (packet.syncFlagFromServer) {
MsgSvc.SyncFlag.STOP -> {
if (client.firstSyncPackets.value != 0) {
client.firstSyncPackets.getAndDecrement()
}
}
MsgSvc.SyncFlag.START -> {

View File

@ -37,9 +37,8 @@ internal class PttStore {
uin: Long,
groupCode: Long,
md5: ByteArray,
size: Long = 0,
voiceLength: Int = 0,
fileId: Long = 0
size: Long,
codec: Int = 0
): OutgoingPacket {
val pack = Cmd0x388.ReqBody(
netType = 3, // wifi
@ -48,7 +47,7 @@ internal class PttStore {
Cmd0x388.TryUpPttReq(
srcUin = uin,
groupCode = groupCode,
fileId = fileId,
fileId = 0,
fileSize = size,
fileMd5 = md5,
fileName = md5,
@ -57,8 +56,8 @@ internal class PttStore {
buType = 4,
innerIp = 0,
buildVer = "6.5.5.663".encodeToByteArray(),
voiceLength = voiceLength,
codec = 0,
voiceLength = 1,
codec = codec,
voiceType = 1,
boolNewUpChan = true
)

View File

@ -108,6 +108,8 @@ kotlin {
//api(kotlin("stdlib-jdk8"))
//api(kotlin("stdlib-jdk7"))
api(kotlin("reflect"))
compileOnly("org.apache.logging.log4j:log4j-api:" + Versions.Logging.log4j)
compileOnly("org.slf4j:slf4j-api:" + Versions.Logging.slf4j)
api(ktor("client-core-jvm", Versions.Kotlin.ktor))
implementation(kotlinx("io-jvm", Versions.Kotlin.io))

View File

@ -18,13 +18,11 @@ import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.isContentEmpty
import net.mamoe.mirai.message.data.toMessage
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.recall
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.runBlocking
import java.io.InputStream
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
@ -174,6 +172,19 @@ public abstract class Group : Contact(), CoroutineScope {
@JvmSynthetic
public abstract override suspend fun uploadImage(image: ExternalImage): Image
/**
* 上传一个语音消息以备发送.
* 请手动关闭输入流
* 请使用mar格式
* 请注意这是一个实验性api且随时会被删除
* @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 1 MB)
*/
@JvmSynthetic
@MiraiExperimentalAPI
public abstract suspend fun uploadGroupVoice(input: InputStream): Voice
public companion object {
/**
* 使用 groupCode 计算 groupUin. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意.

View File

@ -159,8 +159,11 @@ public open class BotConfigurationBase internal constructor() {
@MiraiExperimentalAPI
public var json: Json = kotlin.runCatching {
@OptIn(UnstableDefault::class)
Json(JsonConfiguration(isLenient = true, ignoreUnknownKeys = true))
}.getOrElse { Json(JsonConfiguration.Stable) }
Json {
isLenient = true
ignoreUnknownKeys = true
}
}.getOrElse { Json {} }
/**
* 不显示网络日志. 不推荐.

View File

@ -161,11 +161,11 @@ internal fun Method.registerEvent(
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this)
this@registerEvent.invoke(owner, this@subscribeAlways)
}
}
} else withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this)
this@registerEvent.invoke(owner, this@subscribeAlways)
}
}
}
@ -179,11 +179,11 @@ internal fun Method.registerEvent(
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) as ListeningStatus
this@registerEvent.invoke(owner, this@subscribe) as ListeningStatus
}
} else ListeningStatus.LISTENING
} else withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) as ListeningStatus
this@registerEvent.invoke(owner, this@subscribe) as ListeningStatus
}
}

View File

@ -12,14 +12,12 @@
package net.mamoe.mirai.message
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.toExternalImage
import net.mamoe.mirai.utils.upload
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.utils.*
import java.awt.image.BufferedImage
import java.io.File
import java.io.InputStream
@ -120,6 +118,19 @@ public suspend fun File.uploadAsImage(contact: Contact): Image {
return toExternalImage().upload(contact)
}
/**
* [Dispatchers.IO] 中将文件作为语音上传后构造 [Image]
* 请手动关闭输入流
* 请使用mar格式
* 注意这只是个实验性功能且随时可能会删除
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
@MiraiExperimentalAPI
public suspend fun InputStream.uploadAsGroupVoice(group: Group): Voice {
return group.uploadGroupVoice(this)
}
// endregion
// region Contact.sendImage(IMAGE)

View File

@ -0,0 +1,32 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
import net.mamoe.mirai.utils.internal.logging.JdkLogger
import net.mamoe.mirai.utils.internal.logging.Log4jLogger
import net.mamoe.mirai.utils.internal.logging.Slf4jLogger
@SinceMirai("1.2.0")
public object LoggerAdapters {
@JvmStatic
public fun java.util.logging.Logger.asMiraiLogger(): MiraiLogger {
return JdkLogger(this)
}
@JvmStatic
public fun org.apache.logging.log4j.Logger.asMiraiLogger(): MiraiLogger {
return Log4jLogger(this);
}
@JvmStatic
public fun org.slf4j.Logger.asMiraiLogger(): MiraiLogger {
return Slf4jLogger(this)
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils.internal.logging
import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
import java.util.logging.Level
import java.util.logging.Logger
internal class JdkLogger(private val logger: Logger) : MiraiLoggerPlatformBase() {
override fun verbose0(message: String?, e: Throwable?) {
logger.log(Level.FINER, message, e)
}
override fun debug0(message: String?, e: Throwable?) {
logger.log(Level.FINEST, message, e)
}
override fun info0(message: String?, e: Throwable?) {
logger.log(Level.INFO, message, e)
}
override fun warning0(message: String?, e: Throwable?) {
logger.log(Level.WARNING, message, e)
}
override fun error0(message: String?, e: Throwable?) {
logger.log(Level.SEVERE, message, e)
}
override val identity: String?
get() = logger.name
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils.internal.logging
import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
import org.apache.logging.log4j.Logger
internal class Log4jLogger(private val logger: Logger) : MiraiLoggerPlatformBase() {
override fun verbose0(message: String?, e: Throwable?) {
logger.trace(message, e)
}
override fun debug0(message: String?, e: Throwable?) {
logger.debug(message, e)
}
override fun info0(message: String?, e: Throwable?) {
logger.info(message, e)
}
override fun warning0(message: String?, e: Throwable?) {
logger.warn(message, e)
}
override fun error0(message: String?, e: Throwable?) {
logger.error(message, e)
}
override val identity: String?
get() = logger.name
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils.internal.logging
import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
import org.slf4j.Logger
internal class Slf4jLogger(private val logger: Logger) : MiraiLoggerPlatformBase() {
override fun verbose0(message: String?, e: Throwable?) {
logger.trace(message, e)
}
override fun debug0(message: String?, e: Throwable?) {
logger.debug(message, e)
}
override fun info0(message: String?, e: Throwable?) {
logger.info(message, e)
}
override fun warning0(message: String?, e: Throwable?) {
logger.warn(message, e)
}
override fun error0(message: String?, e: Throwable?) {
logger.error(message, e)
}
override val identity: String?
get() = logger.name
}