Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-02-07 20:32:09 +08:00
commit 8de1fe9150
20 changed files with 105 additions and 103 deletions

View File

@ -25,12 +25,12 @@ data class QQDTO(
@Serializable
data class MemberDTO(
override val id: Long,
val memberName: String = "",
val memberName: String,
val permission: MemberPermission,
val group: GroupDTO
) : ContactDTO() {
constructor(member: Member, name: String = "") : this (
member.id, name, member.permission, GroupDTO(member.group)
constructor(member: Member) : this (
member.id, member.groupCard, member.permission, GroupDTO(member.group)
)
}

View File

@ -31,13 +31,13 @@ data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
data class AtDTO(val target: Long, val display: String) : MessageDTO()
@Serializable
@SerialName("Face")
data class FaceDTO(val faceID: Int) : MessageDTO()
data class FaceDTO(val faceId: Int) : MessageDTO()
@Serializable
@SerialName("Plain")
data class PlainDTO(val text: String) : MessageDTO()
@Serializable
@SerialName("Image")
data class ImageDTO(val path: String) : MessageDTO()
data class ImageDTO(val imageId: String) : MessageDTO()
@Serializable
@SerialName("Xml")
data class XmlDTO(val xml: String) : MessageDTO()
@ -64,7 +64,7 @@ sealed class MessageDTO : DTO
*/
suspend fun MessagePacket<*, *>.toDTO(): MessagePacketDTO = when (this) {
is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender, senderName))
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
else -> UnKnownMessagePacketDTO("UnKnown Message Packet")
}.apply { messageChain = Array(message.size){ message[it].toDTO() }}
@ -76,7 +76,7 @@ fun Message.toDTO() = when (this) {
is At -> AtDTO(target, display)
is Face -> FaceDTO(id.value.toInt())
is PlainText -> PlainDTO(stringValue)
is Image -> ImageDTO(this.toString())
is Image -> ImageDTO(imageId)
is XMLMessage -> XmlDTO(stringValue)
else -> UnknownMessageDTO("未知消息类型")
}
@ -84,9 +84,9 @@ fun Message.toDTO() = when (this) {
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
fun MessageDTO.toMessage() = when (this) {
is AtDTO -> At(target, display)
is FaceDTO -> Face(FaceId(faceID.toUByte()))
is FaceDTO -> Face(FaceId(faceId.toUByte()))
is PlainDTO -> PlainText(text)
is ImageDTO -> PlainText("[暂时不支持图片]")
is ImageDTO -> Image(imageId)
is XmlDTO -> XMLMessage(xml)
is UnknownMessageDTO -> PlainText("assert cannot reach")
}

View File

@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
@Serializable
abstract class VerifyDTO : DTO {
@ -60,6 +61,22 @@ data class GroupInfoDTO(
)
}
@Serializable
data class MemberConfigDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long,
val config: MemberInfoDTO
) : VerifyDTO()
@Serializable
data class MemberInfoDTO(
val name: String? = null,
val specialTitle: String? = null
) : DTO {
constructor(member: Member) : this(member.groupCard, member.specialTitle)
}
@Serializable
open class StateCode(val code: Int, var msg: String) {
object Success : StateCode(0, "success") // 成功

View File

@ -3,17 +3,14 @@ package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import net.mamoe.mirai.api.http.dto.GroupConfigDTO
import net.mamoe.mirai.api.http.dto.GroupInfoDTO
import net.mamoe.mirai.api.http.dto.MuteDTO
import net.mamoe.mirai.api.http.dto.StateCode
import net.mamoe.mirai.api.http.dto.*
fun Application.groupManageModule() {
routing {
/**
* 禁言
* 禁言需要相关权限
*/
miraiVerify<MuteDTO>("/muteAll") {
it.session.bot.getGroup(it.target).muteAll = true
@ -61,5 +58,22 @@ fun Application.groupManageModule() {
call.respondStateCode(StateCode.Success)
}
/**
* 群员信息管理需要相关权限
*/
miraiGet("/memberInfo") {
val member = it.bot.getGroup(paramOrNull("target"))[paramOrNull("memberID")]
call.respondDTO(MemberInfoDTO(member))
}
miraiVerify<MemberConfigDTO>("/memberInfo") { dto ->
val member = dto.session.bot.getGroup(dto.target)[dto.memberId]
with(dto.config) {
name?.let { member.groupCard = it }
specialTitle?.let { member.specialTitle = it }
}
call.respondStateCode(StateCode.Success)
}
}
}

View File

@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
*/
actual object QQAndroid : BotFactory {
@UseExperimental(MiraiInternalAPI::class)
override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot {
actual override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot {
return QQAndroidBot(context, BotAccount(qq, password), configuration)
}
}

View File

@ -1,8 +1,17 @@
package net.mamoe.mirai.qqandroid
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
/**
* QQ for Android
*/
expect object QQAndroid : BotFactory
expect object QQAndroid : BotFactory {
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot
}

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.coroutines.CoroutineContext
import kotlin.properties.Delegates
@ -50,7 +51,7 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
}
}
override suspend fun uploadImage(image: ExternalImage): Image {
override suspend fun uploadImage(image: ExternalImage): Image = try {
bot.network.run {
val response = LongConn.OffPicUp(
bot.client, Cmd0x352.TryUpImgReq(
@ -59,12 +60,11 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
fileId = 0,
fileMd5 = image.md5,
fileSize = image.inputSize.toInt(),
fileName = image.filename,
fileName = image.md5.toUHexString("") + "." + image.format,
imgOriginal = 1,
imgWidth = image.width,
imgHeight = image.height,
imgType = image.imageType,
buType = 1
imgType = image.imageType
)
).sendAndExpect<LongConn.OffPicUp.Response>()
@ -80,9 +80,8 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
is LongConn.OffPicUp.Response.RequireUpload -> {
HighwayHelper.uploadImage(
client = bot.client,
uin = bot.uin,
serverIp = response.serverIp[2].toIpV4AddressString(),
serverPort = response.serverPort[2],
serverIp = response.serverIp[0].toIpV4AddressString(),
serverPort = response.serverPort[0],
imageInput = image.input,
inputSize = image.inputSize.toInt(),
md5 = image.md5,
@ -102,6 +101,8 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
is LongConn.OffPicUp.Response.Failed -> error(response.message)
}
}
} finally {
image.input.close()
}
override suspend fun queryProfile(): Profile {
@ -332,6 +333,7 @@ internal class GroupImpl(
override var botPermission: MemberPermission = MemberPermission.MEMBER
override suspend fun quit(): Boolean {
check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" }
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@ -363,7 +365,7 @@ internal class GroupImpl(
}
}
override suspend fun uploadImage(image: ExternalImage): Image {
override suspend fun uploadImage(image: ExternalImage): Image = try {
bot.network.run {
val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
bot.client,
@ -401,7 +403,6 @@ internal class GroupImpl(
HighwayHelper.uploadImage(
client = bot.client,
uin = bot.uin,
serverIp = response.uploadIpList.first().toIpV4AddressString(),
serverPort = response.uploadPortList.first(),
imageInput = image.input,
@ -444,6 +445,8 @@ internal class GroupImpl(
}
}
}
} finally {
image.input.close()
}
override fun equals(other: Any?): Boolean {

View File

@ -110,6 +110,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
override suspend fun init() {
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
this.bot.logger.error("被挤下线")
close()
}
}

View File

@ -104,8 +104,11 @@ internal open class QQAndroidClient(
private val requestPacketRequestId: AtomicInt = atomic(1921334513)
internal fun nextRequestPacketRequestId(): Int = requestPacketRequestId.getAndAdd(2)
private val highwayDataTransSequenceId: AtomicInt = atomic(87017)
internal fun nextHighwayDataTransSequenceId(): Int = highwayDataTransSequenceId.getAndAdd(2)
private val highwayDataTransSequenceIdForGroup: AtomicInt = atomic(87017)
internal fun nextHighwayDataTransSequenceIdForGroup(): Int = highwayDataTransSequenceIdForGroup.getAndAdd(2)
private val highwayDataTransSequenceIdForFriend: AtomicInt = atomic(40717)
internal fun nextHighwayDataTransSequenceIdForFriend(): Int = highwayDataTransSequenceIdForFriend.getAndAdd(2)
val appClientVersion: Int = 0

View File

@ -69,7 +69,7 @@ object Highway {
sequenceId: Int,
appId: Int = 537062845,
dataFlag: Int = 4096,
commandId: Int = 2,
commandId: Int,
localId: Int = 2052,
uKey: ByteArray,
@ -78,6 +78,9 @@ object Highway {
md5: ByteArray
): ByteReadPacket {
require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" }
require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" }
require(data !is IoBuffer || data.readRemaining == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as IoBuffer).readRemaining}" }
val dataHighwayHead = CSDataHighwayHead.DataHighwayHead(
version = 1,
uin = uin.toString(),
@ -91,7 +94,7 @@ object Highway {
)
val segHead = CSDataHighwayHead.SegHead(
datalength = dataSize,
filesize = dataSize.toLong() and 0xFFffFFff,
filesize = dataSize.toLong(),
serviceticket = uKey,
md5 = md5,
fileMd5 = md5,

View File

@ -15,7 +15,6 @@ internal object HighwayHelper {
suspend fun uploadImage(
client: QQAndroidClient,
uin: Long,
serverIp: String,
serverPort: Int,
uKey: ByteArray,
@ -24,14 +23,20 @@ internal object HighwayHelper {
md5: ByteArray,
commandId: Int // group=2, friend=1
) {
require(md5.size == 16) { "bad md5. Required size=16, got ${md5.size}" }
require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" }
require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
val socket = PlatformSocket()
socket.connect(serverIp, serverPort)
socket.use {
socket.send(
Highway.RequestDataTrans(
uin = uin,
uin = client.uin,
command = "PicUp.DataUp",
sequenceId = client.nextHighwayDataTransSequenceId(),
sequenceId =
if (commandId == 2) client.nextHighwayDataTransSequenceIdForGroup()
else client.nextHighwayDataTransSequenceIdForFriend(),
uKey = uKey,
data = imageInput,
dataSize = inputSize,

View File

@ -91,7 +91,7 @@ internal class Cmd0x352 : ProtoBuf {
@SerialId(2) val msgTryupImgReq: List<TryUpImgReq>? = null,// optional
@SerialId(3) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,// optional
@SerialId(4) val msgDelImgReq: List<DelImgReq>? = null,
@SerialId(10) val netType: Int = 0// 数据网络=5
@SerialId(10) val netType: Int = 3// 数据网络=5
) : ProtoBuf
@Serializable
@ -117,11 +117,10 @@ internal class Cmd0x352 : ProtoBuf {
@SerialId(9) val innerIP: Int = 0,
@SerialId(10) val addressBook: Int = 0,//chatType == 1006为1 我觉得发0没问题
@SerialId(11) val retry: Int = 0,//default
@SerialId(12) val buType: Int,//1或96 不确定
@SerialId(12) val buType: Int = 1,//1或96 不确定
@SerialId(13) val imgOriginal: Int,//是否为原图
@SerialId(14) val imgWidth: Int,
@SerialId(15) val imgHeight: Int,
@SerialId(16) val imgType: Int = 1000,
/**
* ImgType:
* JPG: 1000
@ -131,7 +130,8 @@ internal class Cmd0x352 : ProtoBuf {
* GIG: 2000
* APNG: 2001
* SHARPP: 1004
* */
*/
@SerialId(16) val imgType: Int = 1000,
@SerialId(17) val buildVer: String = "8.2.0.1296",//版本号
@SerialId(18) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,//default
@SerialId(19) val fileStoreDays: Int = 0,//default

View File

@ -15,7 +15,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
@UseExperimental(MiraiInternalAPI::class)
actual object QQAndroid : BotFactory {
override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot {
actual override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot {
return QQAndroidBot(context, BotAccount(qq, password), configuration)
}

View File

@ -85,7 +85,7 @@ kotlin {
api(ktor("network", ktorVersion))
//implementation("io.ktor:ktor-io:1.3.0-beta-1")
runtimeOnly(files("build/classes/kotlin/metadata/main")) // classpath is not properly set by IDE
//runtimeOnly(files("build/classes/kotlin/metadata/main")) // classpath is not properly set by IDE
}
}
commonTest {
@ -93,7 +93,7 @@ kotlin {
api(kotlin("test-annotations-common"))
api(kotlin("test-common"))
runtimeOnly(files("build/classes/kotlin/metadata/test")) // classpath is not properly set by IDE
//runtimeOnly(files("build/classes/kotlin/metadata/test")) // classpath is not properly set by IDE
}
}

View File

@ -3,8 +3,6 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream
@ -13,16 +11,9 @@ import java.io.EOFException
import java.io.InputStream
import java.net.InetAddress
import java.security.MessageDigest
import java.util.concurrent.Executors
import java.util.zip.CRC32
import java.util.zip.Inflater
/**
* 设备名
*/
actual val deviceName: String get() = InetAddress.getLocalHost().hostName
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@ -75,16 +66,6 @@ private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
}
}
/**
* CRC32 算法
*/
actual fun crc32(key: ByteArray): Int = CRC32().apply { update(key) }.value.toInt()
/**
* hostname 解析 ipv4
*/
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
@ -104,6 +85,3 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
}
}
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {
return Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
}

View File

@ -64,14 +64,14 @@ private fun calculateImageMd5ByImageId(imageId: String): ByteArray {
return if (imageId.startsWith('/')) {
imageId
.drop(1)
.replace('-', ' ')
.replace("-", "")
.take(16 * 2)
.chunkedHexToBytes()
} else {
imageId
.substringAfter('{')
.substringBefore('}')
.replace('-', ' ')
.replace("-", "")
.chunkedHexToBytes()
}
}

View File

@ -29,7 +29,7 @@ class ExternalImage(
val filename: String
) {
init {
check(inputSize in Int.MIN_VALUE.toLong()..Int.MAX_VALUE.toLong()) { "file is too big" }
check(inputSize in 0L..Int.MAX_VALUE.toLong()) { "file is too big" }
}
companion object {
@ -43,13 +43,13 @@ class ExternalImage(
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename)
}
private val format: String = when (val it = imageFormat.toLowerCase()) {
"jpeg" -> "jpg" //必须转换
else -> it
}
val format: String =
when (val it = imageFormat.toLowerCase()) {
"jpeg" -> "jpg" //必须转换
else -> it
}
/**
*
* ImgType:
* JPG: 1000
* PNG: 1001

View File

@ -14,16 +14,6 @@ inline val currentTimeMillis: Long get() = GMTDate().timestamp
inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000
/**
* 设备名
*/
expect val deviceName: String
/**
* CRC32 算法
*/
expect fun crc32(key: ByteArray): Int
/**
* zip 压缩
@ -39,11 +29,6 @@ expect fun md5(byteArray: ByteArray): ByteArray
inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/**
* hostname 解析 ipv4
*/
expect fun solveIpAddress(hostname: String): String
/**
* Localhost 解析
*/
@ -54,8 +39,6 @@ expect fun localIpAddress(): String
*/
expect val Http: HttpClient
expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
require(offset >= 0) { "offset shouldn't be negative: $offset" }
require(length >= 0) { "length shouldn't be negative: $length" }

View File

@ -44,7 +44,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage {
})
}
return ExternalImage(width, height, digest.digest(), formatName, buffer, getRandomString(10) + "." + formatName)
return ExternalImage(width, height, digest.digest(), formatName, buffer, getRandomString(16) + "." + formatName)
}
suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
@ -102,8 +102,8 @@ suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO)
@Throws(IOException::class)
fun InputStream.toExternalImage(): ExternalImage {
val file = createTempFile().apply { deleteOnExit() }
file.outputStream().asOutput().use {
this.asInput().copyTo(it)
file.outputStream().use {
this.copyTo(it)
}
this.close()
return file.toExternalImage()

View File

@ -4,21 +4,13 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.*
import java.net.InetAddress
import java.security.MessageDigest
import java.util.concurrent.Executors
import java.util.zip.CRC32
import java.util.zip.Inflater
actual val deviceName: String = InetAddress.getLocalHost().hostName
actual fun crc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
fun InputStream.md5(): ByteArray = this.use {
@ -51,8 +43,6 @@ fun DataInput.md5(): ByteArray {
return digest.digest()
}
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual val Http: HttpClient get() = HttpClient(CIO)
@ -75,7 +65,3 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
return output.toByteArray()
}
}
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {
return Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
}