mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Shamrock
: 支持添加QQ收藏(图片) /fav/add_image_msg
This commit is contained in:
parent
df25b0bc76
commit
88beaf8b6f
@ -37,6 +37,26 @@ Java_moe_fuqiuluo_shamrock_utils_MD5_genFileMd5Hex(JNIEnv *env, jobject thiz, js
|
|||||||
return env->NewStringUTF(md5Hex.c_str());
|
return env->NewStringUTF(md5Hex.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_moe_fuqiuluo_shamrock_utils_MD5_genFileMd5(JNIEnv *env, jobject thiz, jstring file_path) {
|
||||||
|
auto cPathStr = env->GetStringUTFChars(file_path, nullptr);
|
||||||
|
std::filesystem::path filePath(cPathStr);
|
||||||
|
if (!std::filesystem::exists(filePath)) {
|
||||||
|
jclass exClass = env->FindClass("java/io/FileNotFoundException");
|
||||||
|
env->ThrowNew(exClass, "目标文件不存在");
|
||||||
|
env->DeleteLocalRef(exClass);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto file = std::ifstream(filePath.c_str(), std::ios::binary);
|
||||||
|
MD5 md5;
|
||||||
|
md5.update(file);
|
||||||
|
auto md5Bytes = md5.digest();
|
||||||
|
auto jByteArray = env->NewByteArray(16);
|
||||||
|
env->SetByteArrayRegion(jByteArray, 0, 16, reinterpret_cast<const jbyte*>(md5Bytes));
|
||||||
|
return jByteArray;
|
||||||
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_utils_MD5_getMd5Hex(JNIEnv *env, jobject thiz, jbyteArray bytes) {
|
Java_moe_fuqiuluo_shamrock_utils_MD5_getMd5Hex(JNIEnv *env, jobject thiz, jbyteArray bytes) {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.tencent.mobileqq.filemanager.api;
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.qroute.QRouteApi;
|
||||||
|
|
||||||
|
public interface IFileManagerUtil extends QRouteApi {
|
||||||
|
byte[] getSHA(String str);
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet
|
package moe.fuqiuluo.qqinterface.servlet
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import com.tencent.mobileqq.app.QQAppInterface
|
import com.tencent.mobileqq.app.QQAppInterface
|
||||||
import com.tencent.mobileqq.transfile.HttpNetReq
|
import com.tencent.mobileqq.transfile.HttpNetReq
|
||||||
import com.tencent.mobileqq.transfile.INetEngineListener
|
import com.tencent.mobileqq.transfile.INetEngineListener
|
||||||
@ -8,18 +9,23 @@ import com.tencent.mobileqq.transfile.NetResp
|
|||||||
import com.tencent.mobileqq.transfile.ServerAddr
|
import com.tencent.mobileqq.transfile.ServerAddr
|
||||||
import com.tencent.mobileqq.transfile.api.IHttpEngineService
|
import com.tencent.mobileqq.transfile.api.IHttpEngineService
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.io.core.BytePacketBuilder
|
||||||
|
import kotlinx.io.core.readBytes
|
||||||
|
import kotlinx.io.core.writeFully
|
||||||
import moe.fuqiuluo.proto.protobufMapOf
|
import moe.fuqiuluo.proto.protobufMapOf
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||||
import moe.fuqiuluo.shamrock.tools.toInnerValuesString
|
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
|
import moe.fuqiuluo.shamrock.utils.MD5
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||||
import mqq.manager.TicketManager
|
import mqq.manager.TicketManager
|
||||||
import oicq.wlogin_sdk.request.Ticket
|
import oicq.wlogin_sdk.request.Ticket
|
||||||
import oicq.wlogin_sdk.request.WtTicketPromise
|
import oicq.wlogin_sdk.request.WtTicketPromise
|
||||||
import oicq.wlogin_sdk.tools.ErrMsg
|
import oicq.wlogin_sdk.tools.ErrMsg
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@ -28,12 +34,16 @@ import kotlin.coroutines.resume
|
|||||||
* QQ收藏相关接口
|
* QQ收藏相关接口
|
||||||
*/
|
*/
|
||||||
internal object QFavSvc: BaseSvc() {
|
internal object QFavSvc: BaseSvc() {
|
||||||
private val SERVER_LIST = listOf(ServerAddr().also {
|
private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also {
|
||||||
it.isIpv6 = false
|
it.isIpv6 = false
|
||||||
it.mIp = "collector.weiyun.com"
|
it.mIp = "collector.weiyun.com"
|
||||||
it.port = 80
|
it.port = 80
|
||||||
})
|
})
|
||||||
private const val VT = 27
|
private val SERVER_LIST_PICUP = listOf(ServerAddr().also {
|
||||||
|
it.isIpv6 = false
|
||||||
|
it.mIp = "pic.pieceup.qq.com"
|
||||||
|
it.port = 80
|
||||||
|
})
|
||||||
private const val VERSION = 12820
|
private const val VERSION = 12820
|
||||||
private const val APPID = 30244
|
private const val APPID = 30244
|
||||||
private const val SUB_APPID = 538116905
|
private const val SUB_APPID = 538116905
|
||||||
@ -41,6 +51,108 @@ 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 addImageMsg(
|
||||||
|
uin: Long,
|
||||||
|
name: String,
|
||||||
|
groupId: Long = 0,
|
||||||
|
groupName: String = "",
|
||||||
|
picUrl: String,
|
||||||
|
pid: String,
|
||||||
|
width: Int, height: Int,
|
||||||
|
size: Long,
|
||||||
|
md5: String,
|
||||||
|
): Result<NetResp> {
|
||||||
|
val md5Bytes = md5.hex2ByteArray()
|
||||||
|
val data = protobufMapOf {
|
||||||
|
it[1] = mapOf(
|
||||||
|
20009 to mapOf(
|
||||||
|
1 to mapOf(
|
||||||
|
1 to 1, // bid
|
||||||
|
2 to 1, // category
|
||||||
|
3 to mapOf( // author
|
||||||
|
1 to if (groupId == 0L) 1 else 2, // type
|
||||||
|
2 to uin, // num_id
|
||||||
|
3 to name, // str_id
|
||||||
|
4 to groupId, // group_id
|
||||||
|
5 to groupName // group_name
|
||||||
|
),
|
||||||
|
4 to System.currentTimeMillis() - 2000, // create_time
|
||||||
|
5 to System.currentTimeMillis() - 1000, // sequence
|
||||||
|
7 to """{"recordAudioOnly":false,"audioOnly":false,"fileOnly":false}""",
|
||||||
|
9 to 0, // original_app_id
|
||||||
|
10 to 0 // custom_group_id
|
||||||
|
),
|
||||||
|
2 to mapOf(
|
||||||
|
1 to "",
|
||||||
|
3 to "[图片]",
|
||||||
|
4 to mapOf(
|
||||||
|
1 to picUrl,
|
||||||
|
2 to md5Bytes,
|
||||||
|
3 to md5,
|
||||||
|
6 to width,
|
||||||
|
7 to height,
|
||||||
|
8 to size,
|
||||||
|
9 to 0,
|
||||||
|
11 to pid
|
||||||
|
),
|
||||||
|
5 to 1
|
||||||
|
),
|
||||||
|
3 to mapOf(
|
||||||
|
2 to """<img src="$picUrl" />""",
|
||||||
|
4 to mapOf(
|
||||||
|
1 to picUrl,
|
||||||
|
2 to md5Bytes,
|
||||||
|
3 to md5,
|
||||||
|
6 to width,
|
||||||
|
7 to height,
|
||||||
|
8 to size,
|
||||||
|
9 to 0,
|
||||||
|
11 to pid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.toByteArray()
|
||||||
|
return sendWeiyunReq(20009, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun applyUpImageMsg(
|
||||||
|
uin: Long,
|
||||||
|
name: String,
|
||||||
|
groupId: Long = 0,
|
||||||
|
groupName: String = "",
|
||||||
|
width: Int, height: Int,
|
||||||
|
image: File
|
||||||
|
): Result<NetResp> {
|
||||||
|
if (!image.exists()) {
|
||||||
|
return Result.failure(IllegalArgumentException("image file not exists"))
|
||||||
|
}
|
||||||
|
val md5 = MD5.genFileMd5(image.absolutePath)
|
||||||
|
val data = protobufMapOf {
|
||||||
|
it[1] = mapOf(
|
||||||
|
20010 to mapOf(
|
||||||
|
1 to mapOf(
|
||||||
|
2 to md5,
|
||||||
|
4 to md5.toHexString(),
|
||||||
|
10 to mapOf( // author
|
||||||
|
1 to if (groupId == 0L) 1 else 2, // type
|
||||||
|
2 to uin, // num_id
|
||||||
|
3 to name, // str_id
|
||||||
|
4 to groupId, // group_id
|
||||||
|
5 to groupName // group_name
|
||||||
|
),
|
||||||
|
6 to width, // width
|
||||||
|
7 to height,
|
||||||
|
8 to image.length(),
|
||||||
|
9 to 1, // type
|
||||||
|
11 to "/storage/emulated/0/DCIM/ShamrockUpload.jpeg" // pic_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.toByteArray()
|
||||||
|
return sendWeiyunReq(20010, data)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addRichMediaMsg(
|
suspend fun addRichMediaMsg(
|
||||||
uin: Long,
|
uin: Long,
|
||||||
name: String,
|
name: String,
|
||||||
@ -111,7 +223,15 @@ internal object QFavSvc: BaseSvc() {
|
|||||||
return sendWeiyunReq(20009, data)
|
return sendWeiyunReq(20009, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendWeiyunReq(cmd: Int, body: ByteArray): Result<NetResp> {
|
suspend fun sendPicUpBlock(
|
||||||
|
fileSize: Long,
|
||||||
|
offset: Long,
|
||||||
|
block: ByteArray,
|
||||||
|
blockSize: Long,
|
||||||
|
sha: ByteArray,
|
||||||
|
pid: String,
|
||||||
|
outputStream: ByteArrayOutputStream = ByteArrayOutputStream(),
|
||||||
|
): Result<NetResp> {
|
||||||
return suspendCancellableCoroutine {
|
return suspendCancellableCoroutine {
|
||||||
val httpNetReq = HttpNetReq()
|
val httpNetReq = HttpNetReq()
|
||||||
httpNetReq.userData = null
|
httpNetReq.userData = null
|
||||||
@ -120,19 +240,73 @@ internal object QFavSvc: BaseSvc() {
|
|||||||
if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
|
if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
|
||||||
netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
|
netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
|
||||||
}
|
}
|
||||||
|
netResp.mRespData = outputStream.toByteArray().copyOf()
|
||||||
|
it.resume(Result.success(netResp))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
|
||||||
|
}
|
||||||
|
val vi = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin)
|
||||||
|
//LogCenter.log(pSKey)
|
||||||
|
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
|
||||||
|
httpNetReq.mSendData = BytePacketBuilder().apply {
|
||||||
|
writeInt(-1412589450)
|
||||||
|
writeInt(10000)
|
||||||
|
writeInt(0)
|
||||||
|
writeInt(sha.size + 16 + blockSize.toInt())
|
||||||
|
writeShort(0)
|
||||||
|
writeShort(sha.size.toShort())
|
||||||
|
writeFully(sha)
|
||||||
|
writeInt(fileSize.toInt())
|
||||||
|
writeInt(offset.toInt())
|
||||||
|
writeInt(blockSize.toInt())
|
||||||
|
writeFully(block)
|
||||||
|
}.build().readBytes()
|
||||||
|
httpNetReq.mOutStream = outputStream
|
||||||
|
httpNetReq.mStartDownOffset = 0L
|
||||||
|
httpNetReq.mReqProperties["Shamrock"] = "true"
|
||||||
|
httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;pid=%s;appid=%d", app.currentAccountUin, 8, vi, pid, APPID)
|
||||||
|
httpNetReq.mReqProperties["host"] = "pic.pieceup.qq.com"
|
||||||
|
httpNetReq.mReqProperties["Range"] = "bytes=0-"
|
||||||
|
httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
|
||||||
|
httpNetReq.mReqProperties["Accept-Encoding"] = "gzip"
|
||||||
|
httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
|
||||||
|
httpNetReq.mPrioty = 1
|
||||||
|
httpNetReq.mReqUrl = "https://pic.pieceup.qq.com/"
|
||||||
|
httpNetReq.mServerList = SERVER_LIST_PICUP
|
||||||
|
val service = AppRuntimeFetcher.appRuntime
|
||||||
|
.getRuntimeService(IHttpEngineService::class.java, "qqfav")
|
||||||
|
service.sendReq(httpNetReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun sendWeiyunReq(
|
||||||
|
cmd: Int,
|
||||||
|
body: ByteArray,
|
||||||
|
outputStream: ByteArrayOutputStream = ByteArrayOutputStream(),
|
||||||
|
): Result<NetResp> {
|
||||||
|
return suspendCancellableCoroutine {
|
||||||
|
val httpNetReq = HttpNetReq()
|
||||||
|
httpNetReq.userData = null
|
||||||
|
httpNetReq.mCallback = object: INetEngineListener {
|
||||||
|
override fun onResp(netResp: NetResp) {
|
||||||
|
if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
|
||||||
|
netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
|
||||||
|
}
|
||||||
|
netResp.mRespData = outputStream.toByteArray().copyOf()
|
||||||
it.resume(Result.success(netResp))
|
it.resume(Result.success(netResp))
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
//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 = ByteArrayOutputStream()
|
httpNetReq.mOutStream = outputStream
|
||||||
httpNetReq.mStartDownOffset = 0L
|
httpNetReq.mStartDownOffset = 0L
|
||||||
httpNetReq.mReqProperties["Shamrock"] = "true"
|
httpNetReq.mReqProperties["Shamrock"] = "true"
|
||||||
httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.currentAccountUin, VT, pSKey, APPID)
|
httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.currentAccountUin, 27, pSKey, APPID)
|
||||||
httpNetReq.mReqProperties["host"] = "collector.weiyun.com"
|
httpNetReq.mReqProperties["host"] = "collector.weiyun.com"
|
||||||
httpNetReq.mReqProperties["Range"] = "bytes=0-"
|
httpNetReq.mReqProperties["Range"] = "bytes=0-"
|
||||||
httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
|
httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
|
||||||
@ -140,7 +314,7 @@ internal object QFavSvc: BaseSvc() {
|
|||||||
httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
|
httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
|
||||||
httpNetReq.mPrioty = 1
|
httpNetReq.mPrioty = 1
|
||||||
httpNetReq.mReqUrl = "https://collector.weiyun.com/collector.fcg"
|
httpNetReq.mReqUrl = "https://collector.weiyun.com/collector.fcg"
|
||||||
httpNetReq.mServerList = SERVER_LIST
|
httpNetReq.mServerList = SERVER_LIST_COLLECTOR
|
||||||
val service = AppRuntimeFetcher.appRuntime
|
val service = AppRuntimeFetcher.appRuntime
|
||||||
.getRuntimeService(IHttpEngineService::class.java, "qqfav")
|
.getRuntimeService(IHttpEngineService::class.java, "qqfav")
|
||||||
service.sendReq(httpNetReq)
|
service.sendReq(httpNetReq)
|
||||||
|
@ -52,7 +52,7 @@ internal object ActionManager {
|
|||||||
GetWeatherCityCode, GetWeather,
|
GetWeatherCityCode, GetWeather,
|
||||||
|
|
||||||
// FAV
|
// FAV
|
||||||
FavAddRichMediaMsg,
|
FavAddRichMediaMsg, FavAddImageMsg,
|
||||||
|
|
||||||
// OTHER
|
// OTHER
|
||||||
GetDeviceBattery, DownloadFile
|
GetDeviceBattery, DownloadFile
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import kotlinx.io.core.ByteReadPacket
|
||||||
|
import kotlinx.io.core.discardExact
|
||||||
|
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.utils.CryptTools
|
||||||
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
|
|
||||||
|
internal object FavAddImageMsg: IActionHandler() {
|
||||||
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
|
val uin = session.getLong("user_id")
|
||||||
|
val nickName = session.getString("nick")
|
||||||
|
val groupName = session.getStringOrNull("group_name") ?: ""
|
||||||
|
val groupId = session.getLongOrNull("group_id") ?: 0L
|
||||||
|
val file = session.getString("file")
|
||||||
|
return invoke(uin, nickName, file, groupName, groupId, session.echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
uin: Long,
|
||||||
|
nickName: String,
|
||||||
|
fileText: String,
|
||||||
|
groupName: String = "",
|
||||||
|
groupId: Long = 0L,
|
||||||
|
echo: JsonElement = EmptyJsonString
|
||||||
|
): String {
|
||||||
|
val image = fileText.let {
|
||||||
|
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
|
||||||
|
if (md5.length == 32) {
|
||||||
|
FileUtils.getFile(it)
|
||||||
|
} else {
|
||||||
|
FileUtils.parseAndSave(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val options = BitmapFactory.Options()
|
||||||
|
BitmapFactory.decodeFile(image.absolutePath, options)
|
||||||
|
QFavSvc.applyUpImageMsg(uin, nickName,
|
||||||
|
image = image,
|
||||||
|
groupName = groupName,
|
||||||
|
groupId = groupId,
|
||||||
|
width = options.outWidth,
|
||||||
|
height = options.outHeight
|
||||||
|
).onSuccess {
|
||||||
|
return 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
|
||||||
|
//LogCenter.log("上传图片请求成功: ${DeflateTools.ungzip(it.mRespData).toHexString()}")
|
||||||
|
//LogCenter.log("图片上传响应: allLength=$allLength, dataLength=$dataLength, headLength=$headLength")
|
||||||
|
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)
|
||||||
|
val resp = pb[2, 20010, 1, 2]
|
||||||
|
val picUrl = resp[1].asUtf8String
|
||||||
|
val picId = resp[11].asUtf8String
|
||||||
|
val 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 {
|
||||||
|
logic(it.mErrDesc, echo)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
return error(it.message ?: it.toString(), echo)
|
||||||
|
}
|
||||||
|
return ok("请求已提交", echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun path(): String = "fav.add_image_msg"
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("user_id", "nick", "file")
|
||||||
|
}
|
@ -10,7 +10,7 @@ internal object FavAddRichMediaMsg: 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")
|
||||||
val groupName = session.getStringOrNull("groupName") ?: ""
|
val groupName = session.getStringOrNull("group_name") ?: ""
|
||||||
val groupId = session.getLongOrNull("group_id") ?: 0L
|
val groupId = session.getLongOrNull("group_id") ?: 0L
|
||||||
val time = session.getLongOrNull("time") ?: System.currentTimeMillis()
|
val time = session.getLongOrNull("time") ?: System.currentTimeMillis()
|
||||||
val content = session.getString("content")
|
val content = session.getString("content")
|
||||||
|
@ -4,6 +4,7 @@ import io.ktor.http.ContentType
|
|||||||
import io.ktor.server.application.call
|
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.FavAddRichMediaMsg
|
import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddRichMediaMsg
|
||||||
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList
|
||||||
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendSystemMsg
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendSystemMsg
|
||||||
@ -21,8 +22,17 @@ fun Routing.fav() {
|
|||||||
val nickName = call.fetchOrThrow("nick")
|
val nickName = call.fetchOrThrow("nick")
|
||||||
val time = call.fetchOrNull("time")?.toLong() ?: System.currentTimeMillis()
|
val time = call.fetchOrNull("time")?.toLong() ?: System.currentTimeMillis()
|
||||||
val content = call.fetchOrThrow("content")
|
val content = call.fetchOrThrow("content")
|
||||||
val groupName = call.fetchOrNull("groupName") ?: ""
|
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(FavAddRichMediaMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrPost("/fav/add_image_msg") {
|
||||||
|
val uin = call.fetchOrThrow("user_id").toLong()
|
||||||
|
val nickName = call.fetchOrThrow("nick")
|
||||||
|
val file = call.fetchOrThrow("file")
|
||||||
|
val groupName = call.fetchOrNull("groupName") ?: ""
|
||||||
|
val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L
|
||||||
|
call.respondText(FavAddImageMsg(uin, nickName, file, groupName, groupId), ContentType.Application.Json)
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.utils
|
||||||
|
|
||||||
|
object CryptTools {
|
||||||
|
|
||||||
|
fun getSHA1(string: String): ByteArray {
|
||||||
|
return getSHA1(string.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSHA1(bytes: ByteArray): ByteArray {
|
||||||
|
return getDigest(bytes, "SHA-1")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDigest(bytes: ByteArray, algorithm: String): ByteArray {
|
||||||
|
val digest = java.security.MessageDigest.getInstance(algorithm)
|
||||||
|
digest.update(bytes)
|
||||||
|
return digest.digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,9 +4,11 @@ import java.io.ByteArrayInputStream
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
|
|
||||||
|
|
||||||
object DeflateTools {
|
object DeflateTools {
|
||||||
fun uncompress(inputByte: ByteArray?): ByteArray {
|
fun uncompress(inputByte: ByteArray?): ByteArray {
|
||||||
var len: Int
|
var len: Int
|
||||||
@ -79,4 +81,20 @@ object DeflateTools {
|
|||||||
input.close()
|
input.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ungzip(bytes: ByteArray): ByteArray {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
val `in` = ByteArrayInputStream(bytes)
|
||||||
|
try {
|
||||||
|
val ungzip = GZIPInputStream(`in`)
|
||||||
|
val buffer = ByteArray(256)
|
||||||
|
var n: Int
|
||||||
|
while (ungzip.read(buffer).also { n = it } >= 0) {
|
||||||
|
out.write(buffer, 0, n)
|
||||||
|
}
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return out.toByteArray()
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,4 +4,6 @@ object MD5 {
|
|||||||
external fun getMd5Hex(bytes: ByteArray): String
|
external fun getMd5Hex(bytes: ByteArray): String
|
||||||
|
|
||||||
external fun genFileMd5Hex(filePath: String): String
|
external fun genFileMd5Hex(filePath: String): String
|
||||||
|
|
||||||
|
external fun genFileMd5(filePath: String): ByteArray
|
||||||
}
|
}
|
@ -16,17 +16,6 @@ import mqq.app.MobileQQ
|
|||||||
|
|
||||||
internal class HookForDebug: IAction {
|
internal class HookForDebug: IAction {
|
||||||
override fun invoke(ctx: Context) {
|
override fun invoke(ctx: Context) {
|
||||||
// MessageHelper.hookSendMessageOldChannel()
|
|
||||||
val oldHttpEngineProcessor = QRoute.api(IOldHttpEngineProcessor::class.java)
|
|
||||||
oldHttpEngineProcessor.javaClass.hookMethod("sendReq").before {
|
|
||||||
if (it.args[0] is HttpNetReq) {
|
|
||||||
LogCenter.log("已记录一个IOldHttpEngineProcessor请求")
|
|
||||||
val req = it.args[0] as HttpNetReq
|
|
||||||
if (req.mReqUrl.startsWith("https://")) {
|
|
||||||
req.mReqUrl = req.mReqUrl.replace("https://", "http://")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val httpEngineService = AppRuntimeFetcher.appRuntime
|
val httpEngineService = AppRuntimeFetcher.appRuntime
|
||||||
.getRuntimeService(IHttpEngineService::class.java, "all")
|
.getRuntimeService(IHttpEngineService::class.java, "all")
|
||||||
httpEngineService.javaClass.hookMethod("sendReq").before {
|
httpEngineService.javaClass.hookMethod("sendReq").before {
|
||||||
@ -36,12 +25,8 @@ internal class HookForDebug: IAction {
|
|||||||
return@before
|
return@before
|
||||||
}
|
}
|
||||||
LogCenter.log("已记录一个IHttpEngineService请求")
|
LogCenter.log("已记录一个IHttpEngineService请求")
|
||||||
if (req.mReqUrl == null || req.mReqUrl.isBlank()) {
|
LogCenter.log("请求地址: ${req.mReqUrl}")
|
||||||
val host = req.mReqProperties["host"] ?: "collector.weiyun.com"
|
LogCenter.log("请求: ${req.toInnerValuesString(NetReq::class.java)}")
|
||||||
req.mReqUrl = "http://$host"
|
|
||||||
} else if (req.mReqUrl.startsWith("https://")) {
|
|
||||||
req.mReqUrl = req.mReqUrl.replace("https://", "http://")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user