diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/a.java b/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/a.java new file mode 100644 index 0000000..ea9b4d6 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/a.java @@ -0,0 +1,15 @@ +package com.tencent.mobileqq.l0.b; + +public class a { + public void b () { + + } + + public void c (c cVar) { + + } + + public void onUpdate(int progress, boolean z, Object obj) { + + } +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/b.java b/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/b.java new file mode 100644 index 0000000..1f49b80 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/b.java @@ -0,0 +1,23 @@ +package com.tencent.mobileqq.l0.b; + +import android.graphics.Point; + +import java.util.ArrayList; + +public class b { + // text + public String a; + + // confidence + public int b; + + // coordinates + public ArrayList c; + + /* renamed from: d */ + public int d; + + /* renamed from: e */ + public boolean e; + +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/c.java b/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/c.java new file mode 100644 index 0000000..eda0bee --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/l0/b/c.java @@ -0,0 +1,39 @@ +package com.tencent.mobileqq.l0.b; + +import java.util.ArrayList; +import java.util.HashMap; + +public class c { + // image + public String a; + + // width + public int b; + + // height + public int c; + + // lang + public String d; + + // url + public String e; + + // results + public ArrayList f; + + public ArrayList g; + + public HashMap h; + + public int i; + + public int j; + + public int k; + + public String l; + + public int m; + +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/ocr/a/a.java b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/a/a.java new file mode 100644 index 0000000..ed326a3 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/a/a.java @@ -0,0 +1,38 @@ +package com.tencent.mobileqq.ocr.a; + +public class a { + public String a; + + // 1 + public int b; + + // file location + public String c; + + // null + public String d; + + // 0 + public long e; + + // md5 + public String f; + + // null + public String g; + + // false + public boolean h; + + // 0 + public int i; + + // 0 + public int j; + + // 0 + public long k; + + // null + public String l; +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/IPicOcrService.java b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/IPicOcrService.java new file mode 100644 index 0000000..01acd1f --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/IPicOcrService.java @@ -0,0 +1,10 @@ +package com.tencent.mobileqq.ocr.api; + +import mqq.app.api.IRuntimeService; + +public interface IPicOcrService extends IRuntimeService { + void putOcrResult(String str, com.tencent.mobileqq.l0.b.c cVar); + + void requestOcr(com.tencent.mobileqq.ocr.a.a aVar, com.tencent.mobileqq.l0.b.a callback); + +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/impl/OcrServiceImpl.java b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/impl/OcrServiceImpl.java new file mode 100644 index 0000000..635c0ee --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/impl/OcrServiceImpl.java @@ -0,0 +1,4 @@ +package com.tencent.mobileqq.ocr.api.impl; + +public class OcrServiceImpl { +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/impl/PicOcrServiceImpl.java b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/impl/PicOcrServiceImpl.java new file mode 100644 index 0000000..af77ad3 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/ocr/api/impl/PicOcrServiceImpl.java @@ -0,0 +1,4 @@ +package com.tencent.mobileqq.ocr.api.impl; + +public class PicOcrServiceImpl { +} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt new file mode 100644 index 0000000..034b6be --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt @@ -0,0 +1,134 @@ +package moe.fuqiuluo.shamrock.remote.action.handlers + +import com.tencent.mobileqq.l0.b.c +import com.tencent.mobileqq.ocr.a.a +import com.tencent.mobileqq.ocr.api.IPicOcrService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import moe.fuqiuluo.qqinterface.servlet.BaseSvc +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +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.FileUtils +import moe.fuqiuluo.shamrock.utils.MD5 +import moe.fuqiuluo.symbols.OneBotHandler +import java.io.File +import kotlin.coroutines.resume + +@OneBotHandler("ocr_image") +internal object OcrImage: IActionHandler() { + override suspend fun internalHandle(session: ActionSession): String { + val image = session.getString("image") + return invoke(image, session.echo) + } + + suspend operator fun invoke(image: String, echo: JsonElement = EmptyJsonString): String { + val file = FileUtils.getFile(image).let { + if (!it.exists()) { + return@let FileUtils.parseAndSave(image) + } + it + } + LogCenter.log("对${file.absoluteFile.absolutePath}进行OCR", Level.DEBUG) + val retryCount = 10 + val delayMillis = 1000L + repeat(retryCount) { + val result = getOcrResult(file) + + if (result.isSuccess) { + return ok(result.getOrNull(), echo) + } else if (result.isFailure && result.exceptionOrNull()?.message == "no cache") { + withContext(Dispatchers.IO) { + Thread.sleep(delayMillis) + } + } else { + return error(result.exceptionOrNull()?.message ?: "", echo) + } + } + return error("ocr failed", echo) + } + + private suspend fun getOcrResult(file: File): Result { + val ocrService = BaseSvc.app.getRuntimeService(IPicOcrService::class.java, "all") + ?: return Result.failure(Error("获取OCR服务失败")) + return withTimeoutOrNull(5000) { + suspendCancellableCoroutine { continuation -> + ocrService.requestOcr( + a().apply { + this.b = 1 + this.c = file.absolutePath + this.f = MD5.genFileMd5Hex(file.absolutePath) + }, object : com.tencent.mobileqq.l0.b.a() { + override fun b() { + // call uploadOcrPic and then call b + continuation.resume(Result.failure(Error("no cache"))) + } + + override fun c(result: c?) { + continuation.resume(Result.success(OcrResult( + texts = result?.f?.map { + TextDetection( + text = it.a, + confidence = it.b, + coordinates = it.c.map { + ArrayList().apply { + add(it.x) + add(it.y) + } + } + ) + }!!, + language = result.h[result.d] ?: result.d, + url = result.e + ))) + } + override fun onUpdate(i2: Int, z: Boolean, obj: Any?) { + LogCenter.log("$i2, $z, ${obj.toString()}") + if (i2 == 100) { + val result = obj as c + continuation.resume(Result.success(OcrResult( + texts = result.f.map { + TextDetection( + text = it.a, + confidence = it.b, + coordinates = it.c.map { + ArrayList().apply { + add(it.x) + add(it.y) + } + } + ) + }, + language = result.h[result.d] ?: result.d, + url = result.e + ))) + } + } + }) + + } + } ?: Result.failure(Exception("OCR timed out")) + } + + override val requiredParams: Array = arrayOf("group_id", "user_id") + + @Serializable + data class OcrResult ( + val texts: List, + val language: String, + val url: String + ) + + @Serializable + data class TextDetection ( + val text: String, + val confidence: Int, + val coordinates: List> + ) +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt index 930426e..c3531de 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt @@ -1,5 +1,7 @@ package moe.fuqiuluo.shamrock.remote.api +import com.tencent.mobileqq.dt.app.Dtc +import com.tencent.mobileqq.dt.model.FEBound import io.ktor.http.ContentType import io.ktor.http.content.PartData import io.ktor.http.content.forEachPart @@ -12,7 +14,14 @@ import io.ktor.server.routing.post import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.remote.action.handlers.CleanCache import moe.fuqiuluo.shamrock.remote.action.handlers.DownloadFile import moe.fuqiuluo.shamrock.remote.action.handlers.GetDeviceBattery @@ -21,6 +30,8 @@ import moe.fuqiuluo.shamrock.remote.action.handlers.RestartMe import moe.fuqiuluo.shamrock.remote.action.handlers.UploadFileToShamrock import moe.fuqiuluo.shamrock.remote.structures.Status import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig +import moe.fuqiuluo.shamrock.remote.structures.CommonResult +import moe.fuqiuluo.shamrock.tools.GlobalJson5 import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.fetchOrNull import moe.fuqiuluo.shamrock.tools.fetchOrThrow @@ -32,8 +43,11 @@ import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.respond import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.MD5 +import moe.fuqiuluo.shamrock.utils.PlatformUtils +import mqq.app.MobileQQ import java.io.File +@OptIn(ExperimentalSerializationApi::class) fun Routing.otherAction() { if (ShamrockConfig.allowShell()) { @@ -144,4 +158,24 @@ fun Routing.otherAction() { ShamrockConfig[key] = value respond(true, Status.Ok, "success") } + + getOrPost("/dt") { + val version = PlatformUtils.getQQVersionCode() + val qua = PlatformUtils.getQUA() + call.respondText(Json.encodeToString( + buildJsonObject { + put("qua", JsonPrimitive(qua)) + put("version", JsonPrimitive(version)) + } + ), ContentType.Application.Json) + } + + getOrPost("/mmkv") { + val key = fetchOrThrow("key") + call.respondText(Json.encodeToString( + buildJsonObject { + put("value", JsonPrimitive(Dtc.mmKVValue(key))) + } + ), ContentType.Application.Json) + } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt index 2efc0cc..9f47b4d 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt @@ -37,6 +37,11 @@ fun Routing.fetchRes() { call.respondText(GetImage(file), ContentType.Application.Json) } + getOrPost("/ocr_image") { + val file = fetchOrThrow("image") + call.respondText(OcrImage(file), ContentType.Application.Json) + } + getOrPost("/upload_group_file") { val groupId = fetchOrThrow("group_id").toLong() val file = fetchOrThrow("file")