diff --git a/.github/jetbrains-variant-3.png b/.github/jetbrains-variant-3.png new file mode 100644 index 0000000..1502460 Binary files /dev/null and b/.github/jetbrains-variant-3.png differ diff --git a/README.md b/README.md index adda800..e994771 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,12 @@ along with this program. If not, see . [![][contrib-image]][contrib-link] +## 鸣谢 + +感谢[**JetBrains**](https://www.jetbrains.com/zh-cn/community/opensource/#support)提供的开源开发许可证,JetBrains 通过为核心项目贡献者免费提供一套一流的开发者工具来支持非商业开源项目。 + +[](https://www.jetbrains.com/zh-cn/community/opensource/#support) + [banner]: https://socialify.git.ci/whitechi73/OpenShamrock/image?description=1&forks=1&issues=1&logo=https%3A%2F%2Fwhitechi73.github.io%2FOpenShamrock%2Fshamrock.jpg&pattern=Plus&pulls=1&stargazers=1&theme=Auto [actions]: https://img.shields.io/github/actions/workflow/status/whitechi73/OpenShamrock/build-apk.yml?style=for-the-badge @@ -102,3 +108,4 @@ along with this program. If not, see . [contrib-image]: https://contrib.rocks/image?repo=whitechi73/OpenShamrock [contrib-link]: https://github.com/whitechi73/OpenShamrock/graphs/contributors + diff --git a/qqinterface/src/main/java/tencent/im/oidb/cmd0xb77/oidb_cmd0xb77.java b/qqinterface/src/main/java/tencent/im/oidb/cmd0xb77/oidb_cmd0xb77.java index a5ed896..2713b32 100644 --- a/qqinterface/src/main/java/tencent/im/oidb/cmd0xb77/oidb_cmd0xb77.java +++ b/qqinterface/src/main/java/tencent/im/oidb/cmd0xb77/oidb_cmd0xb77.java @@ -31,10 +31,24 @@ public class oidb_cmd0xb77 { // public final PBUInt32Field service_id = PBField.initUInt32(0); // public final PBStringField xml = PBField.initString(""); //}; - //public oidb_cmd0xb77$MiniAppMsgBody mini_app_msg_body = new oidb_cmd0xb77$MiniAppMsgBody(); + public MiniAppMsgBody mini_app_msg_body = new MiniAppMsgBody(); public final PBUInt64Field recv_guild_id = PBField.initUInt64(0); } + public static class MiniAppMsgBody extends MessageMicro { + //static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap( + // new int[]{8, 18, 26, 32, 42, 50, 82}, new String[]{"mini_app_appid", "mini_app_path", "web_page_url", "mini_app_type", "title", "desc", "json_str"} + + //, new Object[]{0L, "", "", 0, "", "", ""}, oidb_cmd0xb77$MiniAppMsgBody.class); + public final PBUInt64Field mini_app_appid = PBField.initUInt64(0); + public final PBStringField mini_app_path = PBField.initString(""); + public final PBStringField web_page_url = PBField.initString(""); + public final PBUInt32Field mini_app_type = PBField.initUInt32(0); + public final PBStringField title = PBField.initString(""); + public final PBStringField desc = PBField.initString(""); + public final PBStringField json_str = PBField.initString(""); + } + public static class ArkMsgBody extends MessageMicro { public final PBStringField app = PBField.initString(""); public final PBStringField view = PBField.initString(""); diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt index 646d6a7..78d868f 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt @@ -80,9 +80,12 @@ internal object PacketSvc: BaseSvc() { fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, ProtoBuf.encodeToByteArray(msgPush)) return withTimeoutOrNull(5000L) { suspendCancellableCoroutine { - AioListener.messageLessListenerMap[msgSeq] = { + AioListener.registerTemporaryMsgListener(msgSeq) { it.resume(this.msgId) } + it.invokeOnCancellation { + AioListener.unregisterTemporaryMsgListener(msgSeq) + } } } ?: -1L } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkAppInfo.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkAppInfo.kt new file mode 100644 index 0000000..59edb02 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkAppInfo.kt @@ -0,0 +1,29 @@ +package moe.fuqiuluo.qqinterface.servlet.ark +sealed class ArkAppInfo( + val appId: Long, + val version: String, + val packageName: String, + val signature: String, + val miniAppId: Long = 0 +) { + data object QQMusic: ArkAppInfo( + appId = 100497308, + version = "0.0.0", + packageName = "com.tencent.qqmusic", + signature = "cbd27cd7c861227d013a25b2d10f0799" + ) + data object NetEaseMusic: ArkAppInfo( + appId = 100495085, + version = "0.0.0", + packageName = "com.netease.cloudmusic", + signature = "da6b069da1e2982db3e386233f68d76d" + ) + + data object DanMaKu: ArkAppInfo( + appId = 100951776, + version = "0.0.0", + packageName = "tv.danmaku.bili", + signature = "7194d531cbe7960a22007b9f6bdaa38b", + miniAppId = 1109937557 + ) +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsg.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsg.kt index a595f2a..a25f3cc 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsg.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsg.kt @@ -1,18 +1,16 @@ package moe.fuqiuluo.qqinterface.servlet.ark +import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.qqinterface.servlet.BaseSvc +import moe.fuqiuluo.qqinterface.servlet.TicketSvc +import moe.fuqiuluo.shamrock.helper.MessageHelper +import moe.fuqiuluo.shamrock.remote.service.listener.AioListener import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77 - -sealed class ArkAppInfo( - val appId: Long, - val version: String, - val packageName: String, - val signature: String -) { - object QQMusic: ArkAppInfo(100497308, "0.0.0", "com.tencent.qqmusic", "cbd27cd7c861227d013a25b2d10f0799") - object NeteaseMusic: ArkAppInfo(100495085, "0.0.0", "com.netease.cloudmusic", "da6b069da1e2982db3e386233f68d76d") -} +import kotlin.coroutines.resume +import kotlin.time.Duration.Companion.seconds internal object ArkMsgSvc: BaseSvc() { fun tryShareMusic( @@ -54,4 +52,49 @@ internal object ArkMsgSvc: BaseSvc() { } sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray()) } + + suspend fun tryShareJsonMessage( + jsonString: String, + arkAppInfo: ArkAppInfo = ArkAppInfo.DanMaKu, + ): Result { + val msgSeq = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEC2C).qqMsgId + val req = oidb_cmd0xb77.ReqBody() + req.appid.set(arkAppInfo.appId) + req.app_type.set(1) + req.msg_style.set(10) + req.client_info.set(oidb_cmd0xb77.ClientInfo().also { + it.platform.set(1) + it.sdk_version.set(arkAppInfo.version) + it.android_package_name.set(arkAppInfo.packageName) + it.android_signature.set(arkAppInfo.signature) + }) + req.ext_info.set(oidb_cmd0xb77.ExtInfo().also { + it.tag_name.set(ByteStringMicro.copyFromUtf8("shamrock")) + it.msg_seq.set(msgSeq) + }) + req.send_type.set(0) + req.recv_uin.set(TicketSvc.getLongUin()) + req.mini_app_msg_body.set(oidb_cmd0xb77.MiniAppMsgBody().also { + it.mini_app_appid.set(arkAppInfo.miniAppId) + it.mini_app_path.set("pages") + it.web_page_url.set("https://im.qq.com/index/") + it.title.set("title") + it.desc.set("desc") + it.json_str.set(jsonString) + }) + sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray()) + val signedJson: String = withTimeoutOrNull(5.seconds) { + suspendCancellableCoroutine { + AioListener.registerTemporaryMsgListener(msgSeq) { + it.resume(elements.first { + it.elementType == MsgConstant.KELEMTYPEARKSTRUCT + }.arkElement.bytesData) + } + it.invokeOnCancellation { + AioListener.unregisterTemporaryMsgListener(msgSeq) + } + } + } ?: return Result.failure(Exception("unable to sign json")) + return Result.success(signedJson) + } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/Region.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/Region.kt new file mode 100644 index 0000000..2841abd --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/Region.kt @@ -0,0 +1,10 @@ +package moe.fuqiuluo.qqinterface.servlet.ark + +import kotlinx.serialization.Serializable + +@Serializable +internal data class Region( + val adcode: Int, + val province: String?, + val city: String? +) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt index 1868420..fbb904b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt @@ -15,13 +15,6 @@ import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.tools.* import java.lang.Exception -@Serializable -internal data class Region( - val adcode: Int, - val province: String?, - val city: String? -) - internal object WeatherSvc { suspend fun fetchWeatherCard(code: Int): Result { val cookie = TicketSvc.getCookie("mp.qq.com") diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt index a8c0c56..751f612 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt @@ -29,7 +29,7 @@ internal object MusicHelper { chatType, peerId, msgId, - ArkAppInfo.NeteaseMusic, + ArkAppInfo.NetEaseMusic, title.ifBlank { name }, singerName, jumpUrl, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt index 5ba34c6..62d5ddc 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt @@ -149,6 +149,14 @@ internal class ActionSession { return params[key].asBooleanOrNull ?: default as Boolean } + fun getJsonElement(key: String): JsonElement { + return params[key]!! + } + + fun getJsonElementOrNull(key: String): JsonElement? { + return params[key] + } + fun getObject(key: String): JsonObject { return params[key].asJsonObject } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SignArkMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SignArkMessage.kt new file mode 100644 index 0000000..0cc4140 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SignArkMessage.kt @@ -0,0 +1,33 @@ +package moe.fuqiuluo.shamrock.remote.action.handlers + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc +import moe.fuqiuluo.shamrock.remote.action.ActionSession +import moe.fuqiuluo.shamrock.remote.action.IActionHandler +import moe.fuqiuluo.shamrock.tools.EmptyJsonString +import moe.fuqiuluo.symbols.OneBotHandler + +@OneBotHandler("sign_ark_message") +internal object SignArkMessage: IActionHandler() { + override suspend fun internalHandle(session: ActionSession): String { + val json = if(session.isString("json")) session.getString("json") + else session.getJsonElement("json").toString() + return invoke(json, session.echo) + } + + suspend operator fun invoke(json: String, echo: JsonElement = EmptyJsonString): String { + /* + ArkMsgSvc.tryShareJsonMessage(json).onSuccess { + return ok(SignArkMessageResult(it), echo = echo) + }.onFailure { + return error(it.message ?: it.toString(), echo) + }*/ + return logic("logic error", echo) + } + + @Serializable + data class SignArkMessageResult( + val result: String + ) +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt index 430ac8e..158cc29 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt @@ -17,11 +17,13 @@ import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow import moe.fuqiuluo.shamrock.tools.fetchOrNull import moe.fuqiuluo.shamrock.tools.fetchOrThrow import moe.fuqiuluo.shamrock.tools.fetchPostJsonArray +import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject import moe.fuqiuluo.shamrock.tools.fetchPostJsonString import moe.fuqiuluo.shamrock.tools.fetchPostOrNull import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow import moe.fuqiuluo.shamrock.tools.getOrPost +import moe.fuqiuluo.shamrock.tools.isJsonArray import moe.fuqiuluo.shamrock.tools.isJsonData import moe.fuqiuluo.shamrock.tools.isJsonObject import moe.fuqiuluo.shamrock.tools.isJsonString @@ -29,6 +31,19 @@ import moe.fuqiuluo.shamrock.tools.jsonArray import moe.fuqiuluo.shamrock.tools.respond fun Routing.messageAction() { + route("/sign_ark_message") { + get { + val json = fetchGetOrThrow("json") + call.respondText(SignArkMessage(json), ContentType.Application.Json) + } + post { + val json = if (isJsonData() && (isJsonObject("json") || isJsonArray("json"))) + fetchPostJsonElement("json").toString() + else fetchPostOrThrow("json") + call.respondText(SignArkMessage(json), ContentType.Application.Json) + } + } + route("/send_group_forward_(msg|message)".toRegex()) { post { val groupId = fetchPostOrNull("group_id") diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt index 272e111..eb3476f 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt @@ -25,7 +25,7 @@ import kotlin.collections.HashMap internal object AioListener : IKernelMsgListener { // 通过MSG SEQ临时监听器 - internal val messageLessListenerMap = Collections.synchronizedMap(HashMap Unit>()) + private val tempMessageListenerMap = Collections.synchronizedMap(HashMap Unit>()) override fun onRecvMsg(msgList: ArrayList) { if (msgList.isEmpty()) return @@ -37,13 +37,26 @@ internal object AioListener : IKernelMsgListener { } } + fun registerTemporaryMsgListener( + msgSeq: Long, + listener: suspend MsgRecord.() -> Unit + ) { + LogCenter.log({ "注册临时消息监听器: $msgSeq" }, Level.DEBUG) + tempMessageListenerMap[msgSeq] = listener + } + + fun unregisterTemporaryMsgListener(msgSeq: Long) { + tempMessageListenerMap.remove(msgSeq) + } + private suspend fun handleMsg(record: MsgRecord) { try { - messageLessListenerMap.firstNotNullOfOrNull { + tempMessageListenerMap.firstNotNullOfOrNull { if (it.key == record.msgSeq) it else null }?.let { it.value(record) - messageLessListenerMap.remove(it.key) + tempMessageListenerMap.remove(it.key) + return } if (record.msgSeq < 0) return