Merge remote-tracking branch 'origin/dev' into anonymous

# Conflicts:
#	mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt
This commit is contained in:
Karlatemp 2020-12-20 15:53:06 +08:00
commit de3fd9d698
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
26 changed files with 433 additions and 231 deletions

View File

@ -141,7 +141,10 @@ fun Project.configureJvmTarget() {
}
kotlinTargets.orEmpty().filterIsInstance<KotlinJvmTarget>().forEach { target ->
target.compilations.all { kotlinOptions.jvmTarget = "1.8" }
target.compilations.all {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.languageVersion = "1.4"
}
target.testRuns["test"].executionTask.configure { useJUnitPlatform() }
}

View File

@ -119,6 +119,9 @@ inline fun Project.configurePublishing(
setPublications("mavenJava")
setConfigurations("archives")
publish = true
override = true
pkg.apply {
repo = bintrayRepo
name = bintrayPkgName

View File

@ -28,6 +28,10 @@ mirai 已经可以用于各项工作,但我们并不保证完全的稳定。
**警告:** mirai 开发者自愿花费其休息时间,无偿维护 mirai 系列项目,**但没有义务提供任何方面的帮助**。
使用者应秉持对开发者无私贡献的尊重,而不应该期望得到帮助。
### 启动 Mirai 时遇到 java.security.NoSuchProviderException: JCE cannot authenticate the provider BC
请更换 OpenJDK。该问题是由于BouncyCastle在重打包后丢失Jar签名引起的。
~~多发于Oracle JDK~~
## 开发相关
> 欢迎帮助维护这个问题列表: 提交 PR 或者 issue.

View File

@ -10,7 +10,7 @@
publishing {
repositories {
maven {
url = "https://api.bintray.com/maven/him188moe/mirai/mirai-core/;publish=0"
url = "https://api.bintray.com/maven/him188moe/mirai/mirai-core/;publish=1"
credentials {
username = upload.Bintray.getUser(project)

View File

@ -73,7 +73,8 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
* returnCode = -10008 等原因掉线
*/
@MiraiInternalApi("This is very experimental and might be changed")
public data class PacketFactory10008 internal constructor(
public data class PacketFactoryErrorCode internal constructor(
val returnCode: Int,
public override val bot: Bot,
public override val cause: Throwable
) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware

View File

@ -262,6 +262,11 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
*/
public abstract val authorId: Long
/**
* 消息原发送人, `null` 表示原消息发送人已经不是 bot 的好友或已经被移出群
*/
public abstract val author: UserOrBot?
/**
* 消息 ids.
* @see MessageSource.ids
@ -282,7 +287,7 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
/**
* 好友消息撤回事件
*/
public data class FriendRecall internal constructor(
public data class FriendRecall @MiraiInternalApi public constructor(
public override val bot: Bot,
public override val messageIds: IntArray,
public override val messageInternalIds: IntArray,
@ -290,11 +295,25 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
/**
* 撤回操作人, 好友的 [User.id]
*/
public val operator: Long
public val operatorId: Long
) : MessageRecallEvent(), Packet {
public override val authorId: Long
get() = bot.id
@Deprecated("Binary compatibility.", level = DeprecationLevel.HIDDEN)
public fun getOperator(): Long = operatorId
/**
* 撤回操作人. `null` 表示该用户已经不是 bot 的好友
*/
public val operator: Friend? get() = bot.getFriend(operatorId)
/**
* 消息原发送人, 等于 [operator]
*/
override val author: Friend? get() = operator
public override val authorId: Long
get() = operatorId
@Suppress("DuplicatedCode")
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@ -305,7 +324,7 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
if (!messageIds.contentEquals(other.messageIds)) return false
if (!messageInternalIds.contentEquals(other.messageInternalIds)) return false
if (messageTime != other.messageTime) return false
if (operator != other.operator) return false
if (operatorId != other.operatorId) return false
return true
}
@ -315,7 +334,7 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
result = 31 * result + messageIds.contentHashCode()
result = 31 * result + messageInternalIds.contentHashCode()
result = 31 * result + messageTime
result = 31 * result + operator.hashCode()
result = 31 * result + operatorId.hashCode()
return result
}
}
@ -335,6 +354,8 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
public override val operator: Member?,
public override val group: Group
) : MessageRecallEvent(), GroupOperableEvent, Packet {
override val author: NormalMember? get() = group[authorId]
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@ -365,10 +386,12 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
}
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
public val MessageRecallEvent.GroupRecall.author: Member
get() = if (authorId == bot.id) group.botAsMember else group.getOrFail(authorId)
public val MessageRecallEvent.FriendRecall.isByBot: Boolean get() = this.operator == bot.id
public val MessageRecallEvent.FriendRecall.isByBot: Boolean get() = this.operatorId == bot.id
// val MessageRecallEvent.GroupRecall.isByBot: Boolean get() = (this as GroupOperableEvent).isByBot
// no need

View File

@ -14,9 +14,9 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiLogger
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.reflect.KClass
@ -26,7 +26,7 @@ internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listen
with(GlobalEventListeners[listener.priority]) {
@Suppress("UNCHECKED_CAST")
val node = ListenerRegistry(listener as Listener<Event>, this@subscribeInternal)
addLast(node)
add(node)
listener.invokeOnCompletion {
this.remove(node)
}
@ -98,18 +98,18 @@ internal class ListenerRegistry(
internal object GlobalEventListeners {
private val ALL_LEVEL_REGISTRIES: Map<EventPriority, LockFreeLinkedList<ListenerRegistry>>
private val ALL_LEVEL_REGISTRIES: Map<EventPriority, ConcurrentLinkedQueue<ListenerRegistry>>
init {
val map =
EnumMap<Listener.EventPriority, LockFreeLinkedList<ListenerRegistry>>(Listener.EventPriority::class.java)
EnumMap<Listener.EventPriority, ConcurrentLinkedQueue<ListenerRegistry>>(Listener.EventPriority::class.java)
EventPriority.values().forEach {
map[it] = LockFreeLinkedList()
map[it] = ConcurrentLinkedQueue()
}
this.ALL_LEVEL_REGISTRIES = map
}
operator fun get(priority: Listener.EventPriority): LockFreeLinkedList<ListenerRegistry> =
operator fun get(priority: Listener.EventPriority): ConcurrentLinkedQueue<ListenerRegistry> =
ALL_LEVEL_REGISTRIES[priority]!!
}
@ -121,54 +121,56 @@ internal suspend inline fun AbstractEvent.broadcastInternal() {
callAndRemoveIfRequired(this@broadcastInternal)
}
internal inline fun <E, T : Iterable<E>> T.forEach0(block: T.(E) -> Unit) {
forEach { block(it) }
}
@Suppress("DuplicatedCode")
internal suspend inline fun <E : AbstractEvent> callAndRemoveIfRequired(
event: E
) {
for (p in Listener.EventPriority.prioritiesExcludedMonitor) {
GlobalEventListeners[p].forEachNode { registeredRegistryNode ->
GlobalEventListeners[p].forEach0 { registeredRegistry ->
if (event.isIntercepted) {
return
}
val listenerRegistry = registeredRegistryNode.nodeValue
if (!listenerRegistry.type.isInstance(event)) return@forEachNode
val listener = listenerRegistry.listener
if (!registeredRegistry.type.isInstance(event)) return@forEach0
val listener = registeredRegistry.listener
when (listener.concurrencyKind) {
Listener.ConcurrencyKind.LOCKED -> {
(listener as Handler).lock!!.withLock {
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(registeredRegistryNode)
remove(registeredRegistry)
}
}
}
Listener.ConcurrencyKind.CONCURRENT -> {
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(registeredRegistryNode)
remove(registeredRegistry)
}
}
}
}
}
coroutineScope {
GlobalEventListeners[EventPriority.MONITOR].forEachNode { registeredRegistryNode ->
GlobalEventListeners[EventPriority.MONITOR].forEach0 { registeredRegistry ->
if (event.isIntercepted) {
return@coroutineScope
}
val listenerRegistry = registeredRegistryNode.nodeValue
if (!listenerRegistry.type.isInstance(event)) return@forEachNode
val listener = listenerRegistry.listener
if (!registeredRegistry.type.isInstance(event)) return@forEach0
val listener = registeredRegistry.listener
launch {
when (listener.concurrencyKind) {
Listener.ConcurrencyKind.LOCKED -> {
(listener as Handler).lock!!.withLock {
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(registeredRegistryNode)
remove(registeredRegistry)
}
}
}
Listener.ConcurrencyKind.CONCURRENT -> {
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(registeredRegistryNode)
remove(registeredRegistry)
}
}
}

View File

@ -56,11 +56,12 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
* @see nextMessage 挂起协程并等待下一条消息
*/
@Suppress("unused")
@BuilderInference
public suspend inline fun <reified T : MessageEvent> T.whileSelectMessages(
timeoutMillis: Long = -1,
filterContext: Boolean = true,
priority: Listener.EventPriority = EventPriority.MONITOR,
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
@BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
): Unit = whileSelectMessagesImpl(timeoutMillis, filterContext, priority, selectBuilder)
/**
@ -68,11 +69,12 @@ public suspend inline fun <reified T : MessageEvent> T.whileSelectMessages(
*/
@MiraiExperimentalApi
@JvmName("selectMessages1")
@BuilderInference
public suspend inline fun <reified T : MessageEvent> T.selectMessagesUnit(
timeoutMillis: Long = -1,
filterContext: Boolean = true,
priority: Listener.EventPriority = EventPriority.MONITOR,
crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit
@BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit
): Unit = selectMessagesImpl(timeoutMillis, true, filterContext, priority, selectBuilder)
@ -97,12 +99,12 @@ public suspend inline fun <reified T : MessageEvent> T.selectMessagesUnit(
* @see nextMessage 挂起协程并等待下一条消息
*/
@Suppress("unused") // false positive
// @BuilderInference // https://youtrack.jetbrains.com/issue/KT-37716
@BuilderInference
public suspend inline fun <reified T : MessageEvent, R> T.selectMessages(
timeoutMillis: Long = -1,
filterContext: Boolean = true,
priority: Listener.EventPriority = EventPriority.MONITOR,
// @BuilderInference
@BuilderInference
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, R>.() -> Unit
): R =
selectMessagesImpl(

View File

@ -157,12 +157,19 @@ public inline fun MessageChain.noneContent(block: (MessageContent) -> Boolean):
}
/**
* 获取第一个 [M] 类型的 [Message] 实例
*/
@JvmSynthetic
public inline fun <reified M : SingleMessage?> MessageChain.findIsInstance(): M? =
this.find { it is M } as M?
/**
* 获取第一个 [M] 类型的 [Message] 实例
*/
@JvmSynthetic
public inline fun <reified M : SingleMessage?> MessageChain.firstIsInstanceOrNull(): M? =
this.firstOrNull { it is M } as M?
this.find { it is M } as M?
/**
* 获取第一个 [M] 类型的 [Message] 实例

View File

@ -39,6 +39,7 @@ public class Voice @MiraiInternalApi constructor(
public override val fileName: String,
public override val md5: ByteArray,
public override val fileSize: Long,
public val codec: Int = 0,
private val _url: String
) : PttMessage() {

View File

@ -52,7 +52,7 @@ public class RetryLaterException @MiraiInternalApi constructor() :
* 无标准输入或 Kotlin 不支持此输入.
*/
public class NoStandardInputForCaptchaException @MiraiInternalApi constructor(
public override val cause: Throwable?
public override val cause: Throwable? = null
) : LoginFailedException(true, "no standard input for captcha")
/**

View File

@ -90,8 +90,17 @@ public open class BotConfiguration { // open for Java
/** 最多尝试多少次重连 */
public var reconnectionRetryTimes: Int = Int.MAX_VALUE
/** 验证码处理器 */
public var loginSolver: LoginSolver = LoginSolver.Default
/**
* 验证码处理器
*
* - Android 需要手动提供 [LoginSolver]
* - JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现
*
* 详见 [LoginSolver.Default]
*
* @see LoginSolver
*/
public var loginSolver: LoginSolver? = LoginSolver.Default
/** 使用协议类型 */
public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE
@ -115,6 +124,7 @@ public open class BotConfiguration { // open for Java
Json {
isLenient = true
ignoreUnknownKeys = true
prettyPrint = true
}
}.getOrElse { Json {} }
@ -133,6 +143,7 @@ public open class BotConfiguration { // open for Java
*
* @see deviceInfo
*/
@ConfigurationDsl
public fun loadDeviceInfoJson(json: String) {
deviceInfo = {
this.json.decodeFromString(DeviceInfo.serializer(), json)

View File

@ -20,6 +20,8 @@ import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.NoStandardInputForCaptchaException
import net.mamoe.mirai.utils.LoginSolver.Companion.Default
import net.mamoe.mirai.utils.StandardCharImageLoginSolver.Companion.createBlocking
import java.awt.Image
import java.awt.image.BufferedImage
import java.io.File
@ -29,6 +31,9 @@ import kotlin.coroutines.CoroutineContext
/**
* 验证码, 设备锁解决器
*
* @see Default
* @see BotConfiguration.loginSolver
*/
public abstract class LoginSolver {
/**
@ -61,109 +66,134 @@ public abstract class LoginSolver {
public abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
public companion object {
public val Default: LoginSolver = kotlin.run {
if (WindowHelperJvm.isDesktopSupported) {
SwingSolver
} else {
DefaultLoginSolver({ readLine() ?: throw NoStandardInputForCaptchaException(null) })
}
}
}
}
/**
* 自动选择 [SwingSolver] [StandardCharImageLoginSolver]
/**
* 当前平台默认的 [LoginSolver]
*
* 检测策略:
* 1. 检测 `android.util.Log`, 如果存在, 返回 `null`.
* 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 [StandardCharImageLoginSolver]
* 3. 检测 JVM 桌面环境, 若支持, 返回 [SwingSolver]
* 4. 返回 [StandardCharImageLoginSolver]
*
* @return [SwingSolver] [StandardCharImageLoginSolver] `null`
*/
@MiraiExperimentalApi
public class DefaultLoginSolver(
public val input: suspend () -> String,
overrideLogger: MiraiLogger? = null
) : LoginSolver() {
private val delegate: LoginSolver = StandardCharImageLoginSolver(input, overrideLogger)
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
return delegate.onSolvePicCaptcha(bot, data)
@JvmField
public val Default: LoginSolver? = when (WindowHelperJvm.platformKind) {
WindowHelperJvm.PlatformKind.ANDROID -> null
WindowHelperJvm.PlatformKind.SWING -> SwingSolver
WindowHelperJvm.PlatformKind.CLI -> StandardCharImageLoginSolver()
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
return delegate.onSolveSliderCaptcha(bot, url)
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
return delegate.onSolveUnsafeDeviceLoginVerify(bot, url)
@Suppress("unused")
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
public fun getDefault(): LoginSolver = Default
?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver")
}
}
/**
* 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [overrideLogger] 输出
* CLI 环境 [LoginSolver]. 将验证码图片转为字符画并通过 `output` 输出, [input] 获取用户输入.
*
* 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [loggerSupplier] 输出
*
* @see createBlocking
*/
@MiraiExperimentalApi
public class StandardCharImageLoginSolver(
input: suspend () -> String,
public class StandardCharImageLoginSolver @JvmOverloads constructor(
input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() },
/**
* `null` 时使用 [Bot.logger]
*/
private val overrideLogger: MiraiLogger? = null
private val loggerSupplier: (bot: Bot) -> MiraiLogger = { it.logger }
) : LoginSolver() {
public constructor(
input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() },
overrideLogger: MiraiLogger?
) : this(input, { overrideLogger ?: it.logger })
private val input: suspend () -> String = suspend {
withContext(Dispatchers.IO) { input() }
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
val logger = loggerSupplier(bot)
@Suppress("BlockingMethodInNonBlockingContext")
withContext(Dispatchers.IO) {
val tempFile: File = File.createTempFile("tmp", ".png").apply { deleteOnExit() }
tempFile.createNewFile()
logger.info("需要图片验证码登录, 验证码为 4 字母")
logger.info { "[PicCaptcha] 需要图片验证码登录, 验证码为 4 字母" }
logger.info { "[PicCaptcha] Picture captcha required. Captcha consists of 4 letters." }
try {
tempFile.writeChannel().apply { writeFully(data); close() }
logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
logger.info { "[PicCaptcha] 将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}" }
logger.info { "[PicCaptcha] Displaying char-image. If not clear, view file ${tempFile.absolutePath}" }
} catch (e: Exception) {
logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
logger.warning("[PicCaptcha] 无法写出验证码文件, 请尝试查看以上字符图片", e)
logger.warning("[PicCaptcha] Failed to export captcha image. Please see the char-image.", e)
}
tempFile.inputStream().use {
tempFile.inputStream().use { stream ->
try {
val img = ImageIO.read(it)
val img = ImageIO.read(stream)
if (img == null) {
logger.info("无法创建字符图片. 请查看文件")
logger.warning { "[PicCaptcha] 无法创建字符图片. 请查看文件" }
logger.warning { "[PicCaptcha] Failed to create char-image. Please see the file." }
} else {
logger.info("\n" + img.createCharImg())
logger.info { "[PicCaptcha] \n" + img.createCharImg() }
}
} catch (throwable: Throwable) {
logger.info("创建字符图片时出错($throwable)。请查看文件")
logger.warning("[PicCaptcha] 创建字符图片时出错. 请查看文件.", throwable)
logger.warning("[PicCaptcha] Failed to create char-image. Please see the file.", throwable)
}
}
}
logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
logger.info { "[PicCaptcha] 请输入 4 位字母验证码. 若要更换验证码, 请直接回车" }
logger.info { "[PicCaptcha] Please type 4-letter captcha. Press Enter directly to refresh." }
return input().takeUnless { it.isEmpty() || it.length != 4 }.also {
logger.info("正在提交[$it]中...")
logger.info { "[PicCaptcha] 正在提交 $it..." }
logger.info { "[PicCaptcha] Submitting $it..." }
}
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
logger.info("需要滑动验证码")
logger.info("请在任意浏览器中打开以下链接并完成验证码. ")
logger.info("完成后请输入任意字符 ")
val logger = loggerSupplier(bot)
logger.info { "[SliderCaptcha] 需要滑动验证码, 请在任意浏览器中打开以下链接并完成验证码, 完成后请输入任意字符." }
logger.info { "[SliderCaptcha] Slider captcha required, please open the following link in any browser and solve the captcha. Type anything here after completion." }
logger.info(url)
return input().also {
logger.info("正在提交中...")
logger.info { "[SliderCaptcha] 正在提交中..." }
logger.info { "[SliderCaptcha] Submitting..." }
}
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String = loginSolverLock.withLock {
val logger = overrideLogger ?: bot.logger
logger.info("需要进行账户安全认证")
logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
logger.info("这步操作将在后续的版本中优化")
logger.info(url)
val logger = loggerSupplier(bot)
logger.info { "[UnsafeLogin] 当前登录环境不安全,服务器要求账户认证。请在 QQ 浏览器打开 $url 并完成验证后输入任意字符。" }
logger.info { "[UnsafeLogin] Account verification required by the server. Please open $url in QQ browser and complete challenge, then type anything here to submit." }
return input().also {
logger.info("正在提交中...")
logger.info { "[UnsafeLogin] 正在提交中..." }
logger.info { "[UnsafeLogin] Submitting..." }
}
}
public companion object {
/**
* 创建 Java 阻塞版 [input] [StandardCharImageLoginSolver]
*
* @param input 将在协程 IO 池执行, 可以有阻塞调用
*/
@JvmStatic
public fun createBlocking(input: () -> String, output: MiraiLogger?): StandardCharImageLoginSolver {
return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }, output)
}
/**
* 创建 Java 阻塞版 [input] [StandardCharImageLoginSolver]
*
* @param input 将在协程 IO 池执行, 可以有阻塞调用
*/
@JvmStatic
public fun createBlocking(input: () -> String): StandardCharImageLoginSolver {
return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } })
}
}
}

View File

@ -70,20 +70,24 @@ public object SwingSolver : LoginSolver() {
// 不会触发各种 NoDefClassError
@Suppress("DEPRECATION")
internal object WindowHelperJvm {
internal val isDesktopSupported: Boolean = kotlin.run {
if (System.getProperty("mirai.no-desktop") === null) {
enum class PlatformKind {
ANDROID,
SWING,
CLI
}
internal val platformKind: PlatformKind = kotlin.run {
if (kotlin.runCatching { Class.forName("android.util.Log") }.isSuccess) {
// Android platform
return@run PlatformKind.ANDROID
}
if (System.getProperty("mirai.no-desktop") != null) return@run PlatformKind.CLI
kotlin.runCatching {
Class.forName("java.awt.Desktop")
Class.forName("java.awt.Toolkit")
}.onFailure { return@run false } // Android OS
kotlin.runCatching {
Toolkit.getDefaultToolkit()
}.onFailure { // AWT Error, #270
return@run false
}
kotlin.runCatching {
Desktop.isDesktopSupported().also { stat ->
if (stat) {
if (Desktop.isDesktopSupported()) {
MiraiLogger.TopLevel.info(
"""
Mirai 正在使用桌面环境. 如遇到验证码将会弹出对话框. 可添加 JVM 属性 `mirai.no-desktop` 以关闭.
@ -94,15 +98,12 @@ internal object WindowHelperJvm {
Mirai is using desktop. Captcha will be thrown by window popup. You can add `mirai.no-desktop` to JVM properties (-Dmirai.no-desktop) to disable it.
""".trimIndent()
)
}
return@run PlatformKind.SWING
} else {
return@run PlatformKind.CLI
}
}.getOrElse {
// Should not happen
MiraiLogger.TopLevel.warning("Exception in checking desktop support.", it)
false
}
} else {
false
return@run PlatformKind.CLI
}
}
}

View File

@ -103,7 +103,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
is BotOfflineEvent.MsfOffline,
is BotOfflineEvent.Dropped,
is BotOfflineEvent.RequireReconnect,
is BotOfflineEvent.PacketFactory10008
is BotOfflineEvent.PacketFactoryErrorCode
-> {
if (!_network.isActive) {
// normally closed

View File

@ -276,16 +276,6 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
if (bot.id != source.fromId) {
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
MessageRecallEvent.GroupRecall(
bot,
source.fromId,
source.ids,
source.internalIds,
source.time,
null,
group
).broadcast()
network.run {
PbMessageSvc.PbMsgWithDraw.createForGroupMessage(

View File

@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcP
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
import net.mamoe.mirai.internal.utils.MiraiPlatformUtils
import net.mamoe.mirai.internal.utils.estimateLength
import net.mamoe.mirai.internal.utils.toUHexString
@ -460,11 +461,13 @@ internal class GroupImpl(
response.fileKey,
codec
)
Voice("${md5.toUHexString("")}.amr", md5, content.size.toLong(), "")
Voice("${md5.toUHexString("")}.amr", md5, content.size.toLong(), codec, "")
}
}
override fun toString(): String = "Group($id)"
val groupPkgMsgParsingCache = GroupPkgMsgParsingCache()
}

View File

@ -23,6 +23,7 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
@ -234,24 +235,52 @@ internal fun MsgComm.Msg.toMessageChain(
isTemp: Boolean = false
): MessageChain = toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp)
internal fun List<MsgOnlinePush.PbPushMsg>.toMessageChain(
bot: Bot,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain = toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp)
@JvmName("toMessageChain1")
internal fun List<MsgOnlinePush.PbPushMsg>.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain = map{it.msg}.toMessageChain(bot, botId, groupIdOrZero, onlineSource, isTemp)
internal fun MsgComm.Msg.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain {
val elements = this.msgBody.richText.elems
): MessageChain = listOf(this).toMessageChain(bot, botId, groupIdOrZero, onlineSource, isTemp)
val pptMsg = msgBody.richText.ptt?.run {
internal fun List<MsgComm.Msg>.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain {
val elements = this.flatMap { it.msgBody.richText.elems }
@OptIn(ExperimentalStdlibApi::class)
val ppts = buildList<Message> {
this@toMessageChain.forEach { msg ->
msg.msgBody.richText.ptt?.run {
// when (fileType) {
// 4 -> Voice(String(fileName), fileMd5, fileSize.toLong(),String(downPara))
// else -> null
// }
Voice(String(fileName), fileMd5, fileSize.toLong(), String(downPara))
add(Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara)))
}
return buildMessageChain(elements.size + 1 + if (pptMsg == null) 0 else 1) {
}
}
return buildMessageChain(elements.size + 1 + ppts.size) {
if (onlineSource) {
checkNotNull(bot) { "bot is null" }
when {
@ -263,7 +292,6 @@ internal fun MsgComm.Msg.toMessageChain(
+OfflineMessageSourceImplByMsg(this@toMessageChain, botId)
}
elements.joinToMessageChain(groupIdOrZero, botId, this)
pptMsg?.let(::add)
}.cleanupRubbishMessageElements()
}

View File

@ -27,6 +27,7 @@ import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.utils.mapToIntArray
import java.util.concurrent.atomic.AtomicBoolean
internal interface MessageSourceInternal {
@ -65,44 +66,44 @@ internal suspend inline fun Message.ensureSequenceIdAvailable() {
internal class MessageSourceFromFriendImpl(
override val bot: Bot,
val msg: MsgComm.Msg
val msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceInternal {
override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds// msg.msgBody.richText.attr!!.random
override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random)
override val time: Int get() = msg.msgHead.msgTime
override val internalIds: IntArray get() = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
override val time: Int get() = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, bot.id, 0, false) }
override val sender: Friend get() = bot.getFriendOrFail(msg.msgHead.fromUin)
override val sender: Friend get() = bot.getFriendOrFail(msg.first().msgHead.fromUin)
private val jceData by lazy { msg.toJceDataFriendOrTemp(internalIds) }
override fun toJceData(): ImMsgBody.SourceMsg = jceData
}
private fun MsgComm.Msg.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg {
val elements = msgBody.richText.elems.toMutableList().also {
private fun List<MsgComm.Msg>.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg {
val elements = flatMap {it.msgBody.richText.elems}.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
return ImMsgBody.SourceMsg(
origSeqs = intArrayOf(this.msgHead.msgSeq),
senderUin = this.msgHead.fromUin,
toUin = this.msgHead.toUin,
origSeqs = mapToIntArray { it.msgHead.msgSeq },
senderUin = first().msgHead.fromUin,
toUin = first().msgHead.toUin,
flag = 1,
elems = this.msgBody.richText.elems,
elems = flatMap{it.msgBody.richText.elems},
type = 0,
time = this.msgHead.msgTime,
time = this.first().msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = ids.map { it.toLong() and 0xFFFF_FFFF }
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = this.msgHead.fromUin, // qq
toUin = this.msgHead.toUin, // group
msgType = this.msgHead.msgType, // 82?
c2cCmd = this.msgHead.c2cCmd,
msgSeq = this.msgHead.msgSeq,
msgTime = this.msgHead.msgTime,
fromUin = this.first().msgHead.fromUin, // qq
toUin = this.first().msgHead.toUin, // group
msgType = this.first().msgHead.msgType, // 82?
c2cCmd = this.first().msgHead.c2cCmd,
msgSeq = this.first().msgHead.msgSeq,
msgTime = this.first().msgHead.msgTime,
msgUid = ids.single().toLong() and 0xFFFF_FFFF, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = this.msgHead.groupInfo.groupCode),
isSrcMsg = true
@ -118,16 +119,24 @@ private fun MsgComm.Msg.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMs
internal class MessageSourceFromTempImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
private val msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal {
override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq)
override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray get() = msg.mapToIntArray{it.msgBody.richText.attr!!.random }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds//
override val time: Int get() = msg.msgHead.msgTime
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) }
override val time: Int get() = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(
bot,
bot.id,
groupIdOrZero = 0,
onlineSource = false,
isTemp = false,
)
}
override val sender: Member
get() = with(msg.msgHead) {
get() = with(msg.first().msgHead) {
bot.getGroupOrFail(c2cTmpMsgHead!!.groupUin).getOrFail(fromUin)
}
@ -137,24 +146,24 @@ internal class MessageSourceFromTempImpl(
internal data class MessageSourceFromGroupImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
private val msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal {
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq)
override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray get() = msg.mapToIntArray{ it.msgBody.richText.attr!!.random }
override val ids: IntArray get() = sequenceIds
override val time: Int get() = msg.msgHead.msgTime
override val time: Int get() = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = false)
msg.toMessageChain(bot, bot.id, groupIdOrZero = group.id, onlineSource = false)
}
override val sender: Member by lazy {
(bot.getGroup(
msg.msgHead.groupInfo?.groupCode
msg.first().msgHead.groupInfo?.groupCode
?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
) as GroupImpl).run {
get(msg.msgHead.fromUin)
?: msg.msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run {
get(msg.first().msgHead.fromUin)
?: msg.first().msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run {
newAnonymous(anonGroupMsg!!.anonNick.encodeToString(), anonGroupMsg.anonId.encodeToBase64())
}
?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
@ -163,13 +172,13 @@ internal data class MessageSourceFromGroupImpl(
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = intArrayOf(msg.msgHead.msgSeq),
senderUin = msg.msgHead.fromUin,
origSeqs = intArrayOf(msg.first().msgHead.msgSeq),
senderUin = msg.first().msgHead.fromUin,
toUin = 0,
flag = 1,
elems = msg.msgBody.richText.elems,
elems = msg.flatMap { it.msgBody.richText.elems },
type = 0,
time = msg.msgHead.msgTime,
time = msg.first().msgHead.msgTime,
pbReserve = EMPTY_BYTE_ARRAY,
srcMsg = EMPTY_BYTE_ARRAY
)

View File

@ -25,41 +25,41 @@ import java.util.concurrent.atomic.AtomicBoolean
internal class OfflineMessageSourceImplByMsg(
// from other sources' originalMessage
val delegate: MsgComm.Msg,
val delegate: List<MsgComm.Msg>,
override val botId: Long,
) : OfflineMessageSource(), MessageSourceInternal {
override val kind: MessageSourceKind =
if (delegate.msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND
if (delegate.first().msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND
override val ids: IntArray get() = sequenceIds
override val internalIds: IntArray = intArrayOf(delegate.msgHead.msgUid.toInt())
override val internalIds: IntArray = delegate.mapToIntArray { it.msgHead.msgUid.toInt() }
override val time: Int
get() = delegate.msgHead.msgTime
get() = delegate.first().msgHead.msgTime
override val fromId: Long
get() = delegate.msgHead.fromUin
get() = delegate.first().msgHead.fromUin
override val targetId: Long
get() = delegate.msgHead.groupInfo?.groupCode ?: delegate.msgHead.toUin
get() = delegate.first().msgHead.groupInfo?.groupCode ?: delegate.first().msgHead.toUin
override val originalMessage: MessageChain by lazy {
delegate.toMessageChain(
null,
botId,
groupIdOrZero = delegate.msgHead.groupInfo?.groupCode ?: 0,
groupIdOrZero = delegate.first().msgHead.groupInfo?.groupCode ?: 0,
onlineSource = false,
isTemp = delegate.msgHead.c2cTmpMsgHead != null
isTemp = delegate.first().msgHead.c2cTmpMsgHead != null
)
}
override val sequenceIds: IntArray = intArrayOf(delegate.msgHead.msgSeq)
override val sequenceIds: IntArray = delegate.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = intArrayOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
origSeqs = delegate.mapToIntArray { it.msgHead.msgSeq },
senderUin = delegate.first().msgHead.fromUin,
toUin = 0,
flag = 1,
elems = delegate.msgBody.richText.elems,
elems = delegate.flatMap { it.msgBody.richText.elems },
type = 0,
time = delegate.msgHead.msgTime,
time = delegate.first().msgHead.msgTime,
pbReserve = EMPTY_BYTE_ARRAY,
srcMsg = EMPTY_BYTE_ARRAY
)

View File

@ -30,7 +30,7 @@ import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.KnownPacketFactories.PacketFactoryIllegalState10008Exception
import net.mamoe.mirai.internal.network.protocol.packet.KnownPacketFactories.PacketFactoryIllegalStateException
import net.mamoe.mirai.internal.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
@ -152,17 +152,28 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
logger.info { "Connected to server $host:$port" }
startPacketReceiverJobOrKill(CancellationException("relogin", cause))
fun LoginSolver?.notnull(): LoginSolver {
checkNotNull(this) {
"No LoginSolver found. Please provide by BotConfiguration.loginSolver. " +
"For example use `BotFactory.newBot(...) { loginSolver = yourLoginSolver}` in Kotlin, " +
"use `BotFactory.newBot(..., new BotConfiguration() {{ setLoginSolver(yourLoginSolver) }})` in Java."
}
return this
}
fun loginSolverNotNull() = bot.configuration.loginSolver.notnull()
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
mainloop@ while (true) {
when (response) {
is WtLogin.Login.LoginPacketResponse.UnsafeLogin -> {
bot.configuration.loginSolver.onSolveUnsafeDeviceLoginVerify(bot, response.url)
loginSolverNotNull().onSolveUnsafeDeviceLoginVerify(bot, response.url)
response = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
}
is WtLogin.Login.LoginPacketResponse.Captcha -> when (response) {
is WtLogin.Login.LoginPacketResponse.Captcha.Picture -> {
var result = bot.configuration.loginSolver.onSolvePicCaptcha(bot, response.data)
var result = loginSolverNotNull().onSolvePicCaptcha(bot, response.data)
if (result == null || result.length != 4) {
//refresh captcha
result = "ABCD"
@ -172,7 +183,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
continue@mainloop
}
is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> {
val ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url).orEmpty()
val ticket = loginSolverNotNull().onSolveSliderCaptcha(bot, response.url).orEmpty()
response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
continue@mainloop
}
@ -443,9 +454,9 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
input.use {
try {
parsePacket(it)
} catch (e: PacketFactoryIllegalState10008Exception) {
} catch (e: PacketFactoryIllegalStateException) {
logger.warning { "Network force offline: ${e.message}" }
bot.launch { BotOfflineEvent.PacketFactory10008(bot, e).broadcast() }
bot.launch { BotOfflineEvent.PacketFactoryErrorCode(e.code, bot, e).broadcast() }
}
}
}

View File

@ -173,7 +173,8 @@ internal object KnownPacketFactories {
?: IncomingFactories.firstOrNull { it.receivingCommandName == commandName }
}
class PacketFactoryIllegalState10008Exception @JvmOverloads constructor(
class PacketFactoryIllegalStateException @JvmOverloads constructor(
val code: Int,
override val message: String? = null,
override val cause: Throwable? = null
) : RuntimeException()
@ -310,8 +311,9 @@ internal object KnownPacketFactories {
val returnCode = readInt()
check(returnCode == 0) {
if (returnCode == -10008) { // https://github.com/mamoe/mirai/issues/470
throw PacketFactoryIllegalState10008Exception("returnCode = $returnCode")
if (returnCode <= -10000) {
// https://github.com/mamoe/mirai/issues/470
throw PacketFactoryIllegalStateException(returnCode, "returnCode = $returnCode")
} else "returnCode = $returnCode"
}

View File

@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PttMessage
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -145,7 +146,14 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
boolValid = true,
fileSize = fileSize.toInt(),
fileType = 4,
pbReserve = byteArrayOf(0)
pbReserve = byteArrayOf(0),
format = let {
if (it is Voice) {
it.codec
} else {
0
}
}
)
}
)

View File

@ -61,19 +61,23 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
pbPushMsg.msg.msgHead.msgSeq
)
}
val group =
bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val msgs = group.groupPkgMsgParsingCache.put(pbPushMsg)
if (msgs.isEmpty()) return null
var extraInfo: ImMsgBody.ExtraInfo? = null
var anonymous: ImMsgBody.AnonymousGroupMsg? = null
for (elem in pbPushMsg.msg.msgBody.richText.elems) {
for (msg in msgs) {
for (elem in msg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
}
}
}
val group =
bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val sender = if (anonymous != null) {
group.newAnonymous(anonymous.anonNick.encodeToString(), anonymous.anonId.encodeToBase64())
} else {
@ -107,7 +111,7 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
}
},
sender = sender,
message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER

View File

@ -44,6 +44,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.encodeToString
import net.mamoe.mirai.internal.utils.io.ProtoBuf
@ -53,6 +54,7 @@ import net.mamoe.mirai.internal.utils.read
import net.mamoe.mirai.internal.utils.toUHexString
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.mapToIntArray
//0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C
@ -243,7 +245,7 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
val grayTip = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()).optGeneralGrayTip
when (grayTip?.templId) {
//戳一戳
10043L, 1134L, 1135L, 1136L -> {
10043L, 1133L, 1132L, 1134L, 1135L, 1136L -> {
//预置数据,服务器将不会提供己方已知消息
var action = ""
var from: Member = group.botAsMember
@ -357,22 +359,21 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
val operator =
if (recallReminder.uin == bot.id) group.botAsMember
else group[recallReminder.uin] ?: return@lambda732 emptySequence()
val firstPkg = recallReminder.recalledMsgList.firstOrNull() ?: return@lambda732 emptySequence()
return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg ->
when {
pkg.authorUin == bot.id && operator.id == bot.id -> null
else -> {
return@lambda732 when {
firstPkg.authorUin == bot.id && operator.id == bot.id -> emptySequence()
else -> sequenceOf(
MessageRecallEvent.GroupRecall(
bot,
pkg.authorUin,
intArrayOf(pkg.seq),
intArrayOf(pkg.msgRandom),
pkg.time,
firstPkg.authorUin,
recallReminder.recalledMsgList.mapToIntArray { it.seq },
recallReminder.recalledMsgList.mapToIntArray { it.msgRandom },
firstPkg.time,
operator,
group
)
}
}
)
}
}
)
@ -422,6 +423,8 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
@ProtoNumber(7) val pkgNum: Int, // 1
@ProtoNumber(8) val pkgIndex: Int, // 0
@ProtoNumber(9) val devSeq: Int, // 0
@ProtoNumber(10) val unknown1: Int = 0, // ? 补 id
@ProtoNumber(11) val unknown2: Int = 0, // ?
@ProtoNumber(12) val flag: Int // 1
) : ProtoBuf
@ -441,7 +444,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
messageIds = intArrayOf(it.srcId),
messageInternalIds = intArrayOf(it.srcInternalId),
messageTime = it.time,
operator = it.fromUin
operatorId = it.fromUin
)
}
},
@ -509,7 +512,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer())
when (body.templId) {
//戳一戳
1134L, 1135L, 1136L, 10043L -> {
1132L, 1133L, 1134L, 1135L, 1136L, 10043L -> {
//预置数据,服务器将不会提供己方已知消息
var from: Friend = bot.asFriend
var action = ""

View File

@ -0,0 +1,56 @@
/*
* Copyright 2019-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.internal.utils
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.utils.currentTimeMillis
import java.util.concurrent.locks.ReentrantLock
internal class GroupPkgMsgParsingCache {
class PkgMsg(
val size: Int,
val divSeq: Int,
val data: MutableMap<Int, MsgOnlinePush.PbPushMsg>,
) {
val createTime = currentTimeMillis()
}
private val deque = ArrayList<PkgMsg>(16)
private val accessLock = ReentrantLock()
private fun clearInvalid() {
deque.removeIf {
currentTimeMillis() - it.createTime > 10000L
}
}
fun put(msg: MsgOnlinePush.PbPushMsg): List<MsgOnlinePush.PbPushMsg> {
val head = msg.msg.contentHead ?: return listOf(msg)
val size = head.pkgNum
if (size < 2) return listOf(msg)
accessLock.withLock {
clearInvalid()
val seq = head.divSeq
val index = head.pkgIndex
val pkgMsg = deque.find {
it.divSeq == seq
} ?: PkgMsg(size, seq, mutableMapOf()).also { deque.add(it) }
pkgMsg.data[index] = msg
if (pkgMsg.data.size == pkgMsg.size) {
deque.removeIf { it.divSeq == seq }
return pkgMsg.data.entries.asSequence()
.sortedBy { it.key }
.map { it.value }
.toList()
}
return emptyList()
}
}
}