Shamrock: 开源开发许可证

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-02-22 18:12:31 +08:00
parent 87629666f2
commit 4d5c054bc4
13 changed files with 191 additions and 23 deletions

BIN
.github/jetbrains-variant-3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@ -69,6 +69,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
[![][contrib-image]][contrib-link] [![][contrib-image]][contrib-link]
## 鸣谢
感谢[**JetBrains**](https://www.jetbrains.com/zh-cn/community/opensource/#support)提供的开源开发许可证JetBrains 通过为核心项目贡献者免费提供一套一流的开发者工具来支持非商业开源项目。
[<img src=".github/jetbrains-variant-3.png" width="200"/>](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 [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 [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 <http://www.gnu.org/licenses/>.
[contrib-image]: https://contrib.rocks/image?repo=whitechi73/OpenShamrock [contrib-image]: https://contrib.rocks/image?repo=whitechi73/OpenShamrock
[contrib-link]: https://github.com/whitechi73/OpenShamrock/graphs/contributors [contrib-link]: https://github.com/whitechi73/OpenShamrock/graphs/contributors

View File

@ -31,10 +31,24 @@ public class oidb_cmd0xb77 {
// public final PBUInt32Field service_id = PBField.initUInt32(0); // public final PBUInt32Field service_id = PBField.initUInt32(0);
// public final PBStringField xml = PBField.initString(""); // 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 final PBUInt64Field recv_guild_id = PBField.initUInt64(0);
} }
public static class MiniAppMsgBody extends MessageMicro<MiniAppMsgBody> {
//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<ArkMsgBody> { public static class ArkMsgBody extends MessageMicro<ArkMsgBody> {
public final PBStringField app = PBField.initString(""); public final PBStringField app = PBField.initString("");
public final PBStringField view = PBField.initString(""); public final PBStringField view = PBField.initString("");

View File

@ -80,9 +80,12 @@ internal object PacketSvc: BaseSvc() {
fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, ProtoBuf.encodeToByteArray(msgPush)) fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, ProtoBuf.encodeToByteArray(msgPush))
return withTimeoutOrNull(5000L) { return withTimeoutOrNull(5000L) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
AioListener.messageLessListenerMap[msgSeq] = { AioListener.registerTemporaryMsgListener(msgSeq) {
it.resume(this.msgId) it.resume(this.msgId)
} }
it.invokeOnCancellation {
AioListener.unregisterTemporaryMsgListener(msgSeq)
}
} }
} ?: -1L } ?: -1L
} }

View File

@ -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
)
}

View File

@ -1,18 +1,16 @@
package moe.fuqiuluo.qqinterface.servlet.ark package moe.fuqiuluo.qqinterface.servlet.ark
import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant 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.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 import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
import kotlin.coroutines.resume
sealed class ArkAppInfo( import kotlin.time.Duration.Companion.seconds
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")
}
internal object ArkMsgSvc: BaseSvc() { internal object ArkMsgSvc: BaseSvc() {
fun tryShareMusic( fun tryShareMusic(
@ -54,4 +52,49 @@ internal object ArkMsgSvc: BaseSvc() {
} }
sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray()) sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray())
} }
suspend fun tryShareJsonMessage(
jsonString: String,
arkAppInfo: ArkAppInfo = ArkAppInfo.DanMaKu,
): Result<String> {
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)
}
} }

View File

@ -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?
)

View File

@ -15,13 +15,6 @@ import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.tools.*
import java.lang.Exception import java.lang.Exception
@Serializable
internal data class Region(
val adcode: Int,
val province: String?,
val city: String?
)
internal object WeatherSvc { internal object WeatherSvc {
suspend fun fetchWeatherCard(code: Int): Result<JsonObject> { suspend fun fetchWeatherCard(code: Int): Result<JsonObject> {
val cookie = TicketSvc.getCookie("mp.qq.com") val cookie = TicketSvc.getCookie("mp.qq.com")

View File

@ -29,7 +29,7 @@ internal object MusicHelper {
chatType, chatType,
peerId, peerId,
msgId, msgId,
ArkAppInfo.NeteaseMusic, ArkAppInfo.NetEaseMusic,
title.ifBlank { name }, title.ifBlank { name },
singerName, singerName,
jumpUrl, jumpUrl,

View File

@ -149,6 +149,14 @@ internal class ActionSession {
return params[key].asBooleanOrNull ?: default as Boolean 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 { fun getObject(key: String): JsonObject {
return params[key].asJsonObject return params[key].asJsonObject
} }

View File

@ -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
)
}

View File

@ -17,11 +17,13 @@ import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow
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.fetchPostJsonArray import moe.fuqiuluo.shamrock.tools.fetchPostJsonArray
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject
import moe.fuqiuluo.shamrock.tools.fetchPostJsonString import moe.fuqiuluo.shamrock.tools.fetchPostJsonString
import moe.fuqiuluo.shamrock.tools.fetchPostOrNull import moe.fuqiuluo.shamrock.tools.fetchPostOrNull
import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow
import moe.fuqiuluo.shamrock.tools.getOrPost import moe.fuqiuluo.shamrock.tools.getOrPost
import moe.fuqiuluo.shamrock.tools.isJsonArray
import moe.fuqiuluo.shamrock.tools.isJsonData import moe.fuqiuluo.shamrock.tools.isJsonData
import moe.fuqiuluo.shamrock.tools.isJsonObject import moe.fuqiuluo.shamrock.tools.isJsonObject
import moe.fuqiuluo.shamrock.tools.isJsonString import moe.fuqiuluo.shamrock.tools.isJsonString
@ -29,6 +31,19 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
import moe.fuqiuluo.shamrock.tools.respond import moe.fuqiuluo.shamrock.tools.respond
fun Routing.messageAction() { 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()) { route("/send_group_forward_(msg|message)".toRegex()) {
post { post {
val groupId = fetchPostOrNull("group_id") val groupId = fetchPostOrNull("group_id")

View File

@ -25,7 +25,7 @@ import kotlin.collections.HashMap
internal object AioListener : IKernelMsgListener { internal object AioListener : IKernelMsgListener {
// 通过MSG SEQ临时监听器 // 通过MSG SEQ临时监听器
internal val messageLessListenerMap = Collections.synchronizedMap(HashMap<Long, MsgRecord.() -> Unit>()) private val tempMessageListenerMap = Collections.synchronizedMap(HashMap<Long, suspend MsgRecord.() -> Unit>())
override fun onRecvMsg(msgList: ArrayList<MsgRecord>) { override fun onRecvMsg(msgList: ArrayList<MsgRecord>) {
if (msgList.isEmpty()) return 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) { private suspend fun handleMsg(record: MsgRecord) {
try { try {
messageLessListenerMap.firstNotNullOfOrNull { tempMessageListenerMap.firstNotNullOfOrNull {
if (it.key == record.msgSeq) it else null if (it.key == record.msgSeq) it else null
}?.let { }?.let {
it.value(record) it.value(record)
messageLessListenerMap.remove(it.key) tempMessageListenerMap.remove(it.key)
return
} }
if (record.msgSeq < 0) return if (record.msgSeq < 0) return