Shamrock: 支持获取指定QQ收藏的内容 /fav/get_item_content

This commit is contained in:
WhiteChi 2023-12-23 04:16:11 +08:00
parent 88beaf8b6f
commit 1490450178
6 changed files with 192 additions and 56 deletions

View File

@ -51,6 +51,19 @@ internal object QFavSvc: BaseSvc() {
private const val MINOR_VERSION = 9 private const val MINOR_VERSION = 9
private var seq = 1 private var seq = 1
suspend fun getItemContent(
id: String
): Result<NetResp> {
val data = protobufMapOf {
it[1] = mapOf(
20001 to mapOf(
1 to id
)
)
}.toByteArray()
return sendWeiyunReq(20001, data)
}
suspend fun addImageMsg( suspend fun addImageMsg(
uin: Long, uin: Long,
name: String, name: String,
@ -215,7 +228,7 @@ internal object QFavSvc: BaseSvc() {
* 4 => pic_list * 4 => pic_list
* 5 => file_list * 5 => file_list
*/ */
2 to content 2 to content.textToHtml()
) )
) )
) )
@ -223,6 +236,10 @@ internal object QFavSvc: BaseSvc() {
return sendWeiyunReq(20009, data) return sendWeiyunReq(20009, data)
} }
private fun String.textToHtml(): String {
return replace("\n", "<div><br/></div>")
}
suspend fun sendPicUpBlock( suspend fun sendPicUpBlock(
fileSize: Long, fileSize: Long,
offset: Long, offset: Long,
@ -300,7 +317,6 @@ internal object QFavSvc: BaseSvc() {
override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {} override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
} }
val pSKey = getWeiYunPSKey() val pSKey = getWeiYunPSKey()
//LogCenter.log(pSKey)
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), body)) httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), body))
httpNetReq.mOutStream = outputStream httpNetReq.mOutStream = outputStream

View File

@ -52,7 +52,7 @@ internal object ActionManager {
GetWeatherCityCode, GetWeather, GetWeatherCityCode, GetWeather,
// FAV // FAV
FavAddRichMediaMsg, FavAddImageMsg, FavAddTextMsg, FavAddImageMsg, FavGetItemContent,
// OTHER // OTHER
GetDeviceBattery, DownloadFile GetDeviceBattery, DownloadFile

View File

@ -3,6 +3,8 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asUtf8String import moe.fuqiuluo.proto.asUtf8String
@ -40,8 +42,14 @@ internal object FavAddImageMsg: IActionHandler() {
FileUtils.parseAndSave(it) FileUtils.parseAndSave(it)
} }
} }
val options = BitmapFactory.Options() val options = BitmapFactory.Options()
BitmapFactory.decodeFile(image.absolutePath, options) BitmapFactory.decodeFile(image.absolutePath, options)
lateinit var picUrl: String
lateinit var picId: String
lateinit var itemId: String
lateinit var md5: String
QFavSvc.applyUpImageMsg(uin, nickName, QFavSvc.applyUpImageMsg(uin, nickName,
image = image, image = image,
groupName = groupName, groupName = groupName,
@ -49,14 +57,12 @@ internal object FavAddImageMsg: IActionHandler() {
width = options.outWidth, width = options.outWidth,
height = options.outHeight height = options.outHeight
).onSuccess { ).onSuccess {
return if (it.mHttpCode == 200 && it.mResult == 0) { if (it.mHttpCode == 200 && it.mResult == 0) {
val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData))
readPacket.discardExact(6) readPacket.discardExact(6)
val allLength = readPacket.readInt() val allLength = readPacket.readInt()
val dataLength = readPacket.readInt() val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16 val headLength = allLength - dataLength - 16
//LogCenter.log("上传图片请求成功: ${DeflateTools.ungzip(it.mRespData).toHexString()}")
//LogCenter.log("图片上传响应: allLength=$allLength, dataLength=$dataLength, headLength=$headLength")
readPacket.discardExact(2) readPacket.discardExact(2)
ByteArray(headLength).also { ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size) readPacket.readFully(it, 0, it.size)
@ -66,55 +72,82 @@ internal object FavAddImageMsg: IActionHandler() {
} }
val pb = ProtoUtils.decodeFromByteArray(data) val pb = ProtoUtils.decodeFromByteArray(data)
val resp = pb[2, 20010, 1, 2] val resp = pb[2, 20010, 1, 2]
val picUrl = resp[1].asUtf8String picUrl = resp[1].asUtf8String
val picId = resp[11].asUtf8String picId = resp[11].asUtf8String
val md5 = resp[4].asUtf8String md5 = resp[4].asUtf8String
val sha = CryptTools
.getSHA1("/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQ_Collection/pic/" + md5.uppercase() + "_0")
image.inputStream().use {
var offset = 0L
val block = ByteArray(131072)
var rest = image.length()
do {
val length = if (rest <= 131072) rest else 131072L
if(it.read(block, 0, length.toInt()) != -1) {
QFavSvc.sendPicUpBlock(
fileSize = image.length(),
offset = offset,
block = block,
blockSize = length,
pid = picId,
sha = sha
).onFailure {
return error(it.message ?: it.toString(), echo)
}
offset += length
rest -= length
} else {
rest = -1
}
} while (rest > 0)
}
QFavSvc.addImageMsg(
uin, nickName, groupId, groupName, picUrl, picId, options.outWidth, options.outHeight, image.length(), md5.uppercase()
).onFailure {
return error(it.message ?: it.toString(), echo)
}
ok(picUrl, echo)
} else { } else {
logic(it.mErrDesc, echo) return logic(it.mErrDesc, echo)
} }
}.onFailure { }.onFailure {
return error(it.message ?: it.toString(), echo) return error(it.message ?: it.toString(), echo)
} }
return ok("请求已提交", echo)
val sha = CryptTools
.getSHA1("/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQ_Collection/pic/" + md5.uppercase() + "_0")
image.inputStream().use {
var offset = 0L
val block = ByteArray(131072)
var rest = image.length()
do {
val length = if (rest <= 131072) rest else 131072L
if(it.read(block, 0, length.toInt()) != -1) {
QFavSvc.sendPicUpBlock(
fileSize = image.length(),
offset = offset,
block = block,
blockSize = length,
pid = picId,
sha = sha
).onFailure {
return error(it.message ?: it.toString(), echo)
}
offset += length
rest -= length
} else {
rest = -1
}
} while (rest > 0)
}
QFavSvc.addImageMsg(
uin, nickName, groupId, groupName, picUrl, picId, options.outWidth, options.outHeight, image.length(), md5.uppercase()
).onFailure {
return error(it.message ?: it.toString(), echo)
}.onSuccess {
if (it.mHttpCode == 200 && it.mResult == 0) {
val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData))
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
itemId = pb[2, 20009, 1].asUtf8String
}
}
return ok(PicInfo(
picUrl = picUrl,
picId = picId,
id = itemId
), echo)
} }
override fun path(): String = "fav.add_image_msg" override fun path(): String = "fav.add_image_msg"
override val requiredParams: Array<String> = arrayOf("user_id", "nick", "file") override val requiredParams: Array<String> = arrayOf("user_id", "nick", "file")
@Serializable
private data class PicInfo(
@SerialName("pic_url") val picUrl: String,
@SerialName("pic_id") val picId: String,
@SerialName("id") val id: String
)
} }

View File

@ -1,12 +1,21 @@
package moe.fuqiuluo.shamrock.remote.action.handlers package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.qqinterface.servlet.QFavSvc import moe.fuqiuluo.qqinterface.servlet.QFavSvc
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.action.ActionSession import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
internal object FavAddRichMediaMsg: IActionHandler() { internal object FavAddTextMsg: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String { override suspend fun internalHandle(session: ActionSession): String {
val uin = session.getLong("user_id") val uin = session.getLong("user_id")
val nickName = session.getString("nick") val nickName = session.getString("nick")
@ -33,7 +42,23 @@ internal object FavAddRichMediaMsg: IActionHandler() {
groupId = groupId groupId = groupId
).onSuccess { ).onSuccess {
return if (it.mHttpCode == 200 && it.mResult == 0) { return if (it.mHttpCode == 200 && it.mResult == 0) {
ok("成功", echo) val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData))
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
ok(data = QFavItem(
pb[2, 20009, 1].asUtf8String
), echo)
} else { } else {
logic(it.mErrDesc, echo) logic(it.mErrDesc, echo)
} }
@ -41,7 +66,12 @@ internal object FavAddRichMediaMsg: IActionHandler() {
return ok("请求已提交", echo) return ok("请求已提交", echo)
} }
override fun path(): String = "fav.add_rich_media_msg" override fun path(): String = "fav.add_text_msg"
override val requiredParams: Array<String> = arrayOf("user_id", "nick", "content") override val requiredParams: Array<String> = arrayOf("user_id", "nick", "content")
@Serializable
private data class QFavItem(
@SerialName("id") val id: String
)
} }

View File

@ -0,0 +1,55 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.qqinterface.servlet.QFavSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
internal object FavGetItemContent: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val id = session.getString("id")
return invoke(id, session.echo)
}
suspend operator fun invoke(
id: String,
echo: JsonElement = EmptyJsonString
): String {
val respData = DeflateTools.ungzip(QFavSvc.getItemContent(id).onSuccess {
if (it.mHttpCode != 200 || it.mResult != 0) {
return logic(it.mErrDesc, echo)
}
}.getOrThrow().mRespData)
val readPacket = ByteReadPacket(respData)
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
return ok(ItemContent(pb[2, 20001, 1, 8, 2].asUtf8String))
}
override fun path(): String = "fav.get_item_content"
@Serializable
private data class ItemContent(
@SerialName("content") val content: String
)
}

View File

@ -5,11 +5,8 @@ import io.ktor.server.application.call
import io.ktor.server.response.respondText import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddImageMsg import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddImageMsg
import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddRichMediaMsg import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddTextMsg
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList import moe.fuqiuluo.shamrock.remote.action.handlers.FavGetItemContent
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendSystemMsg
import moe.fuqiuluo.shamrock.remote.action.handlers.GetStrangerInfo
import moe.fuqiuluo.shamrock.remote.action.handlers.IsBlackListUin
import moe.fuqiuluo.shamrock.tools.fetchOrNull import moe.fuqiuluo.shamrock.tools.fetchOrNull
import moe.fuqiuluo.shamrock.tools.fetchOrThrow import moe.fuqiuluo.shamrock.tools.fetchOrThrow
import moe.fuqiuluo.shamrock.tools.getOrPost import moe.fuqiuluo.shamrock.tools.getOrPost
@ -24,7 +21,7 @@ fun Routing.fav() {
val content = call.fetchOrThrow("content") val content = call.fetchOrThrow("content")
val groupName = call.fetchOrNull("group_name") ?: "" val groupName = call.fetchOrNull("group_name") ?: ""
val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L
call.respondText(FavAddRichMediaMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json) call.respondText(FavAddTextMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json)
} }
getOrPost("/fav/add_image_msg") { getOrPost("/fav/add_image_msg") {
@ -35,4 +32,9 @@ fun Routing.fav() {
val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L
call.respondText(FavAddImageMsg(uin, nickName, file, groupName, groupId), ContentType.Application.Json) call.respondText(FavAddImageMsg(uin, nickName, file, groupName, groupId), ContentType.Application.Json)
} }
getOrPost("/fav/get_item_content") {
val id = call.fetchOrThrow("id")
call.respondText(FavGetItemContent(id), ContentType.Application.Json)
}
} }