13 Commits

Author SHA1 Message Date
fb00e5c1ff Shamrock: support /create_guild_role 2024-02-03 16:31:54 +08:00
7bfb9b7b61 Shamrock: support /set_guild_member_role 2024-02-03 06:21:34 +08:00
c43689822b Shamrock: support /delete_guild_role 2024-02-03 05:59:49 +08:00
7952453137 Shamrock: support /get_guild_roles 2024-02-03 05:45:25 +08:00
2f61f6da00 Shamrock: support /get_guild_feeds 2024-02-03 03:05:38 +08:00
db252b6b6c Shamrock: support /send_guild_channel_msg 2024-02-02 22:47:38 +08:00
137c354acc Shamrock: support receiving all guild msg 2024-02-02 22:07:56 +08:00
1c7f6bd034 Shamrock: support /get_guild_member_profile 2024-02-02 19:11:45 +08:00
649d8771ca Shamrock: fix error as clover.cpp changed 2024-02-02 18:37:47 +08:00
af7b0f732e Merge remote-tracking branch 'origin/master' 2024-02-02 18:36:08 +08:00
12738fd52c Shamrock: fix #219 2024-02-02 18:35:49 +08:00
b165e1c0c2 Merge pull request #221 from PisLuanyao/master
👻
2024-02-02 18:30:03 +08:00
29c1ad8bc9 Clover.cpp
临时解决Nox提示 "QQ 屡次停止运行" ,不清楚副作用
2024-02-02 14:39:41 +08:00
89 changed files with 1802 additions and 251 deletions

View File

@ -249,6 +249,11 @@ object ShamrockConfig {
return preferences.getBoolean("enable_auto_start", false)
}
fun disableAutoSyncSetting(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("disable_auto_sync_setting", false)
}
fun enableAliveReply(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("alive_reply", false)
@ -264,6 +269,11 @@ object ShamrockConfig {
preferences.edit().putBoolean("enable_auto_start", v).apply()
}
fun setDisableAutoSyncSetting(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("disable_auto_sync_setting", v).apply()
}
fun setAliveReply(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("alive_reply", v).apply()
@ -322,6 +332,7 @@ object ShamrockConfig {
"shell" to preferences.getBoolean("shell", false),
"alive_reply" to preferences.getBoolean("alive_reply", false),
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
"disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false),
)
}

View File

@ -91,7 +91,7 @@ fun LabFragment() {
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.round_logo_dev_24),
title = "实验功能"
title = "基础设置"
) { color ->
Column {
Divider(
@ -142,6 +142,16 @@ fun LabFragment() {
return@Function true
}
Function(
title = "禁止Shamrock同步设置",
desc = "禁止Shamrock同步设置防止恢复手动修改后的配置文件。",
descColor = color,
isSwitch = ShamrockConfig.disableAutoSyncSetting(ctx)
) {
ShamrockConfig.setDisableAutoSyncSetting(ctx, it)
return@Function true
}
kotlin.runCatching {
ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE)
}.onSuccess {

View File

@ -1,7 +1,7 @@
@file:OptIn(ExperimentalSerializationApi::class)
@file:Suppress("ArrayInDataClass")
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
@ -12,4 +12,4 @@ data class WeiyunCommonReq (
@ProtoNumber(2009) val addRichMediaReq: WeiyunAddRichMediaReq? = null,
@ProtoNumber(2010) val fastUploadResourceReq: WeiyunFastUploadResourceReq? = null,
)
)

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,7 +1,7 @@
@file:OptIn(ExperimentalSerializationApi::class)
@file:Suppress("ArrayInDataClass")
package moe.whitechi73.protobuf.fav
package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.group_file_common
package protobuf.group_file_common
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@ -0,0 +1,405 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.guild
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import protobuf.qweb.QWebExtInfo
@Serializable
data class GetGuildFeedsReq(
@ProtoNumber(1) var count: Int,
@ProtoNumber(2) var from: Int? = null,
@ProtoNumber(3) var feedAttchInfo: ByteArray? = null,
@ProtoNumber(4) var guildId: ULong? = null,
@ProtoNumber(5) var getType: Int? = null,
@ProtoNumber(6) var sortOption: Int? = null,
@ProtoNumber(7) var u7: Int? = null,
@ProtoNumber(8) var u8: Int? = null,
@ProtoNumber(9) var u9: ByteArray? = null,
)
@Serializable
data class GetGuildFeedsRsp(
@ProtoNumber(1) var vecFeed: List<StFeed>? = null,
@ProtoNumber(2) var isFinish: Int = 0,
//@ProtoNumber(3) var feedAttchInfo: ByteArray? = null,
//@ProtoNumber(4) var traceId: String? = null,
)
@Serializable
data class StFeed(
@SerialName("id") @ProtoNumber(1) var id: String,
@SerialName("title") @ProtoNumber(2) var title: StRichText,
@SerialName("poster") @ProtoNumber(4) var poster: StUser? = null,
@SerialName("videos") @ProtoNumber(5) var videos: List<StVideo>? = null,
@SerialName("contents") @ProtoNumber(6) var contents: StRichText? = null,
@SerialName("create_time") @ProtoNumber(7) var createTime: ULong? = null,
@SerialName("comment_count") @ProtoNumber(9) var commentCount: UInt? = null,
@SerialName("comments") @ProtoNumber(10) var vecComment: List<StComment>? = null,
@SerialName("share") @ProtoNumber(11) var share: StShare? = null,
@SerialName("visitor_info") @ProtoNumber(12) var visitorInfo: StVisitor? = null,
@SerialName("images") @ProtoNumber(13) var images: List<StImage>? = null,
@SerialName("poi") @ProtoNumber(14) var poiInfo: StPoiInfoV2? = null,
@SerialName("op_mask") @ProtoNumber(17) var opMask: List<Int>? = null,
@SerialName("channel_info") @ProtoNumber(21) var channelInfo: StChannelInfo? = null,
@SerialName("create_time_ns") @ProtoNumber(22) var createTimeNs: ULong? = null,
@SerialName("update_time") @ProtoNumber(28) var updateTime: ULong? = null,
@SerialName("total_like") @ProtoNumber(29) var totalLike: StTotalLike? = null,
@SerialName("discussion_mum") @ProtoNumber(31) var discussionMum: UInt? = null,
@SerialName("feed_type") @ProtoNumber(32) var feedType: UInt? = null,
@SerialName("default_background_img") @ProtoNumber(34) var defaultBackgroundImg: String? = null,
@SerialName("group_code") @ProtoNumber(35) var groupCode: ULong? = null,
)
@Serializable
data class StTotalLike (
@SerialName("count") @ProtoNumber(1) var likeCount: UInt? = null,
@SerialName("is_clicked") @ProtoNumber(2) var isClicked: UInt? = null,
)
@Serializable
data class StPoiInfoV2(
@SerialName("poi_id") @ProtoNumber(1) var poiId: String? = null,
@SerialName("name") @ProtoNumber(2) var name: String? = null,
@SerialName("poi_type") @ProtoNumber(3) var poiType: Int? = null,
@SerialName("type_name") @ProtoNumber(4) var typeName: String? = null,
@SerialName("address") @ProtoNumber(5) var address: String? = null,
@SerialName("district_code") @ProtoNumber(6) var districtCode: Int? = null,
@SerialName("gps") @ProtoNumber(7) var gps: StGPSV2? = null,
@SerialName("distance") @ProtoNumber(8) var distance: Int? = null,
@SerialName("hot_value") @ProtoNumber(9) var hotValue: Int? = null,
@SerialName("phone") @ProtoNumber(10) var phone: String? = null,
@SerialName("country") @ProtoNumber(11) var country: String? = null,
@SerialName("province") @ProtoNumber(12) var province: String? = null,
@SerialName("city") @ProtoNumber(13) var city: String? = null,
@SerialName("poi_num") @ProtoNumber(14) var poiNum: Int? = null,
@SerialName("poi_order_type") @ProtoNumber(15) var poiOrderType: Int? = null,
@SerialName("default_name") @ProtoNumber(16) var defaultName: String? = null,
@SerialName("district") @ProtoNumber(17) var district: String? = null,
@SerialName("dian_ping_id") @ProtoNumber(18) var dianPingId: String? = null,
@SerialName("distance_text") @ProtoNumber(19) var distanceText: String? = null,
@SerialName("display_name") @ProtoNumber(20) var displayName: String? = null,
)
@Serializable
data class StGPSV2(
@SerialName("lat") @ProtoNumber(1) var latitude: Long? = null,
@SerialName("lon") @ProtoNumber(2) var longitude: Long? = null,
)
@Serializable
data class StShare(
@SerialName("title") @ProtoNumber(1) var title: String? = null,
@SerialName("desc") @ProtoNumber(2) var desc: String? = null,
@SerialName("type") @ProtoNumber(3) var type: UInt? = null,
@SerialName("url") @ProtoNumber(4) var url: String? = null,
@SerialName("author") @ProtoNumber(5) var author: StUser? = null,
@SerialName("poster") @ProtoNumber(6) var poster: StUser? = null,
@SerialName("videos") @ProtoNumber(7) var videos: List<StVideo>? = null,
@SerialName("short_url") @ProtoNumber(8) var shorturl: String? = null,
@SerialName("share_card_info") @ProtoNumber(9) var shareCardInfo: String? = null,
//@ProtoNumber(10) var shareQzoneInfo: Any? = null,
@SerialName("images") @ProtoNumber(11) var images: List<StImage>? = null,
@SerialName("publish_total_user") @ProtoNumber(12) var publishTotalUser: UInt? = null,
@SerialName("shared_count") @ProtoNumber(13) var sharedCount: UInt? = null,
//@ProtoNumber(14) var channelShareInfo: Any? = null,
)
@Serializable
data class StVisitor(
@SerialName("view_count")
@ProtoNumber(1) val viewCount: UInt? = null,
@SerialName("recome_count")
@ProtoNumber(3) val recomeCount: UInt? = null,
@SerialName("view_desc")
@ProtoNumber(4) val viewDesc: String? = null,
)
@Serializable
data class StComment(
@SerialName("id") @ProtoNumber(1) var id: String? = null,
@SerialName("poster") @ProtoNumber(2) var postUser: StUser? = null,
@SerialName("create_time") @ProtoNumber(3) var createTime: ULong? = null,
@SerialName("content") @ProtoNumber(4) var content: String? = null,
@SerialName("reply_count") @ProtoNumber(5) var replyCount: UInt? = null,
@SerialName("replies") @ProtoNumber(6) var vecReply: List<StReply>? = null,
//@ProtoNumber(7) var busiData: Any? = null,
@SerialName("like_info") @ProtoNumber(8) var likeInfo: StLike? = null,
@SerialName("type_flag") @ProtoNumber(9) var typeFlag: UInt? = null,
@SerialName("at_uin_list") @ProtoNumber(10) var atUinList: List<String>? = null,
@SerialName("type_flag2") @ProtoNumber(11) var typeFlag2: UInt? = null,
@SerialName("create_time_ns") @ProtoNumber(12) var createTimeNs: ULong? = null,
@SerialName("store_ext_info") @ProtoNumber(13) var storeExtInfo: List<QWebExtInfo>? = null,
@SerialName("third_id") @ProtoNumber(14) var thirdId: String? = null,
@SerialName("source_type") @ProtoNumber(15) var sourceType: UInt? = null,
@SerialName("rich_contents") @ProtoNumber(16) var richContents: StRichText? = null,
@SerialName("images") @ProtoNumber(17) var images: List<StImage>? = null,
@SerialName("sequence") @ProtoNumber(18) var sequence: ULong? = null,
@SerialName("next_page_reply") @ProtoNumber(19) var nextPageReply: Boolean? = null,
@SerialName("attach_info") @ProtoNumber(20) var attachInfo: String? = null,
)
@Serializable
data class StReply(
@SerialName("id") @ProtoNumber(1) var id: String? = null,
@SerialName("poster") @ProtoNumber(2) var postUser: StUser? = null,
@SerialName("create_time") @ProtoNumber(3) var createTime: ULong? = null,
@SerialName("content") @ProtoNumber(4) var content: String? = null,
@SerialName("target") @ProtoNumber(5) var targetUser: StUser? = null,
//@ProtoNumber(6) var busiData: ByteArray? = null,
@SerialName("like_info") @ProtoNumber(7) var likeInfo: StLike? = null,
@SerialName("type_flag") @ProtoNumber(8) var typeFlag: UInt? = null,
@SerialName("modify_flag") @ProtoNumber(9) var modifyflag: UInt? = null,
@SerialName("at_uin_list") @ProtoNumber(10) var atUinList: List<String>? = null,
@SerialName("type_flag2") @ProtoNumber(11) var typeFlag2: UInt? = null,
@SerialName("create_time_ns") @ProtoNumber(12) var createTimeNs: ULong? = null,
@SerialName("store_ext_info") @ProtoNumber(13) var storeExtInfo: List<QWebExtInfo>? = null,
@SerialName("third_id") @ProtoNumber(14) var thirdId: String? = null,
@SerialName("target_reply_id") @ProtoNumber(15) var targetReplyID: String? = null,
@SerialName("source_type") @ProtoNumber(16) var sourceType: UInt? = null,
@SerialName("rich_contents") @ProtoNumber(17) var richContents: StRichText? = null,
@SerialName("images") @ProtoNumber(18) var images: List<StImage>? = null,
)
@Serializable
data class StLike(
@SerialName("id")
@ProtoNumber(1) var id: String? = null,
@SerialName("count")
@ProtoNumber(2) var count: UInt? = null,
@SerialName("status")
@ProtoNumber(3) var status: UInt? = null,
@SerialName("like_uin_list")
@ProtoNumber(4) var vecUser: List<StUser>? = null,
@SerialName("poster")
@ProtoNumber(6) var postUser: StUser? = null,
@SerialName("has_liked_count")
@ProtoNumber(7) var hasLikedCount: UInt? = null,
@SerialName("owner_status")
@ProtoNumber(8) var ownerStatus: UInt? = null,
@SerialName("jump_url")
@ProtoNumber(9) var jumpUrl: String? = null,
)
@Serializable
data class StVideo(
@SerialName("file_id") @ProtoNumber(1) var fileId: String? = null,
@SerialName("file_size") @ProtoNumber(2) var fileSize: UInt? = null,
@SerialName("duration") @ProtoNumber(3) var duration: UInt? = null,
@SerialName("width") @ProtoNumber(4) var width: UInt? = null,
@SerialName("height") @ProtoNumber(5) var height: UInt? = null,
@SerialName("play_url") @ProtoNumber(6) var playUrl: String? = null,
@SerialName("trans_status") @ProtoNumber(7) var transStatus: UInt? = null,
@SerialName("video_prior") @ProtoNumber(8) var videoPrior: UInt? = null,
@SerialName("video_rate") @ProtoNumber(9) var videoRate: UInt? = null,
//@ProtoNumber(10) var vecVideoUrl: String? = null,
//@ProtoNumber(11) var busiData: Any? = null,
@SerialName("approval_status") @ProtoNumber(12) var approvalStatus: UInt? = null,
@SerialName("video_source") @ProtoNumber(13) var videoSource: UInt? = null,
@SerialName("media_quality_rank") @ProtoNumber(14) var mediaQualityRank: UInt? = null,
@SerialName("media_quality_score") @ProtoNumber(15) var mediaQualityScore: Float? = null,
@SerialName("md5") @ProtoNumber(16) var videoMD5: String? = null,
@SerialName("is_quic") @ProtoNumber(17) var isQuic: UInt? = null,
@SerialName("orientation") @ProtoNumber(18) var orientation: Int? = null,
@SerialName("cover") @ProtoNumber(19) var cover: StImage? = null,
@SerialName("pattern_id") @ProtoNumber(20) var patternId: String? = null,
@SerialName("display_index") @ProtoNumber(21) var displayIndex: UInt? = null,
)
@Serializable
data class StRichText(
@SerialName("contents") @ProtoNumber(1) var contents: List<StRichTextContent>? = null,
@SerialName("images") @ProtoNumber(2) var images: List<StImage>? = null,
)
@Serializable
data class StRichTextContent(
@SerialName("type") @ProtoNumber(1) var type: Int? = null,
@SerialName("pattern_id") @ProtoNumber(2) var patternId: String? = null,
@SerialName("text") @ProtoNumber(3) var textContent: StRichTextTextContent? = null,
@SerialName("at") @ProtoNumber(4) var atContent: StRichTextAtContent? = null,
@SerialName("url") @ProtoNumber(5) var urlContent: StRichTextURLContent? = null,
@SerialName("emoji") @ProtoNumber(6) var emojiContent: StRichTextEmojiContent? = null,
@SerialName("channel") @ProtoNumber(7) var channelContent: StRichTextChannelContent? = null,
@SerialName("guild") @ProtoNumber(8) var guildContent: StRichTextGuildContent? = null,
@SerialName("icon") @ProtoNumber(9) var iconContent: StRichTextIconContent? = null,
)
@Serializable
data class StRichTextIconContent(
@SerialName("url") @ProtoNumber(1) val url: String? = null
)
@Serializable
data class StRichTextGuildContent(
@SerialName("channel_info") @ProtoNumber(1) val channelInfo: StChannelInfo? = null
)
@Serializable
data class StRichTextChannelContent(
@SerialName("channel_info") @ProtoNumber(1) val channelInfo: StChannelInfo? = null
)
@Serializable
data class StChannelInfo(
//@SerialName("sign") @ProtoNumber(1) var sign: String? = null,
@SerialName("name")
@ProtoNumber(2) var name: String? = null,
@SerialName("icon_url")
@ProtoNumber(3) var iconUrl: String? = null,
@SerialName("type")
@ProtoNumber(4) var privateType: Int? = null,
@SerialName("guild_name")
@ProtoNumber(5) var guildName: String? = null,
@SerialName("hot_icon")
@ProtoNumber(6) var hotIcon: String? = null,
@SerialName("hot_index")
@ProtoNumber(7) var hotIndex: UInt? = null,
)
@Serializable
data class StRichTextEmojiContent(
@ProtoNumber(1) var id: String? = null,
@ProtoNumber(2) var type: String? = null,
@ProtoNumber(3) var name: String? = null,
@ProtoNumber(4) var url: String? = null,
)
@Serializable
data class StRichTextURLContent(
@ProtoNumber(1) var url: String? = null,
@SerialName("display") @ProtoNumber(2) var displayText: String? = null,
@ProtoNumber(3) var type: Int? = null,
@SerialName("play_url") @ProtoNumber(4) var playUrl: String? = null,
@SerialName("platform") @ProtoNumber(5) var thirdPlatform: ThirdPlatform? = null,
@SerialName("third_video_info") @ProtoNumber(6) var thirdVideoInfo: CommThirdVideoInfo? = null,
)
@Serializable
data class CommThirdVideoInfo(
@SerialName("cover") @ProtoNumber(1) val cover: String? = null,
@SerialName("duration") @ProtoNumber(2) val duration: ULong? = null,
)
@Serializable
data class ThirdPlatform(
@ProtoNumber(1) var icon: String? = null,
@ProtoNumber(2) var name: String? = null,
)
@Serializable
data class StRichTextTextContent(
@ProtoNumber(1) var text: String? = null
)
@Serializable
data class StRichTextAtContent(
@ProtoNumber(1) var type: Int? = null,
@SerialName("guild_info") @ProtoNumber(2) var guildInfo: GuildInfo? = null,
@SerialName("role_info") @ProtoNumber(3) var roleGroupId: RoleGroupInfo? = null,
@ProtoNumber(4) var user: StUser? = null,
)
@Serializable
data class StUser(
@ProtoNumber(1) var id: String? = null,
@ProtoNumber(2) var nick: String? = null,
@ProtoNumber(3) var icon: StIconInfo? = null,
@ProtoNumber(4) var desc: String? = null,
@SerialName("follow_state") @ProtoNumber(5) var followState: UInt? = null,
@ProtoNumber(6) var type: UInt? = null,
@ProtoNumber(7) var sex: UInt? = null,
@ProtoNumber(8) var birthday: ULong? = null,
@ProtoNumber(9) var school: String? = null,
@ProtoNumber(11) var location: String? = null,
//@ProtoNumber(12) var busiData: ByteArray? = null,
@SerialName("frd") @ProtoNumber(13) var frdState: UInt? = null,
@SerialName("relation_state") @ProtoNumber(14) var relationState: UInt? = null,
@SerialName("black_state") @ProtoNumber(15) var blackState: UInt? = null,
@ProtoNumber(16) var medal: StTagMedalInfo? = null,
@ProtoNumber(17) var constellation: Int? = null,
@SerialName("jump_url") @ProtoNumber(18) var jumpUrl: String? = null,
@SerialName("location_code") @ProtoNumber(19) var locationCode: String? = null,
@SerialName("third_id") @ProtoNumber(20) var thirdId: String? = null,
@ProtoNumber(21) var company: String? = null,
@SerialName("certification_desc") @ProtoNumber(22) var certificationDesc: String? = null,
@SerialName("desc_type") @ProtoNumber(23) var descType: UInt? = null,
//@ProtoNumber(24) var channelUserInfo: Any? = null,
//@SerialName("login_id") @ProtoNumber(25) var loginId: String? = null,
@ProtoNumber(26) var uin: ULong? = null,
@SerialName("nick_flag") @ProtoNumber(27) var nickFlag: UInt? = null,
@SerialName("manage_tag") @ProtoNumber(28) var manageTag: CustomManageTag? = null,
//@SerialName("personal_medal") @ProtoNumber(29) var personalMedal: PersonalMedal? = null,
)
@Serializable
data class PersonalMedal(
@SerialName("start") @ProtoNumber(1) val startTime: ULong? = null,
@SerialName("end") @ProtoNumber(2) val endTime: ULong? = null,
@ProtoNumber(3) var url: String? = null,
)
@Serializable
data class StTagMedalInfo(
@SerialName("id") @ProtoNumber(1) val tagId: ULong? = null,
@SerialName("name") @ProtoNumber(2) val tagName: String? = null,
@ProtoNumber(3) val rank: ULong? = null,
)
@Serializable
data class CustomManageTag(
@ProtoNumber(3) val color: UInt? = null,
@SerialName("name") @ProtoNumber(2) val tagName: String? = null,
)
@Serializable
data class StIconInfo(
//@SerialName("url_40") @ProtoNumber(1) var iconUrl40: String? = null,
//@SerialName("url_100") @ProtoNumber(2) var iconUrl100: String? = null,
//@SerialName("url_140") @ProtoNumber(3) var iconUrl140: String? = null,
//@SerialName("url_640") @ProtoNumber(4) var iconUrl640: String? = null,
@SerialName("url") @ProtoNumber(5) var iconUrl: String? = null,
)
@Serializable
data class RoleGroupInfo(
@SerialName("role") @ProtoNumber(1) var roleId: ULong? = null,
@ProtoNumber(2) var name: String? = null,
@ProtoNumber(3) var color: ULong? = null,
)
@Serializable
data class GuildInfo(
@SerialName("guild_id") @ProtoNumber(1) var guildId: ULong? = null,
@ProtoNumber(2) var name: String? = null,
@SerialName("join_time") @ProtoNumber(3) var joinTime: ULong? = null,
)
@Serializable
data class StImage(
@ProtoNumber(1) var width: UInt? = null,
@ProtoNumber(2) var height: UInt? = null,
@ProtoNumber(3) var picUrl: String? = null,
@SerialName("image_urls") @ProtoNumber(4) var vecImageUrl: List<StImageUrl>? = null,
@SerialName("id") @ProtoNumber(5) var picId: String? = null,
//@ProtoNumber(6) var busiData: Any? = null,
@SerialName("md5") @ProtoNumber(7) var imageMD5: String? = null,
@SerialName("layer_pic_url") @ProtoNumber(8) var layerPicUrl: String? = null,
@SerialName("pattern_id") @ProtoNumber(9) var patternId: String? = null,
@SerialName("display_index") @ProtoNumber(10) var displayIndex: Int? = null,
@SerialName("size") @ProtoNumber(11) var origSize: UInt? = null,
@SerialName("is_original") @ProtoNumber(12) var isOrig: Boolean? = null,
@SerialName("is_gif") @ProtoNumber(13) var isGif: Boolean? = null,
)
@Serializable
data class StImageUrl(
@SerialName("level_type") @ProtoNumber(1) var levelType: UInt? = null,
@ProtoNumber(2) var url: String? = null,
@ProtoNumber(3) var width: UInt? = null,
@ProtoNumber(4) var height: UInt? = null,
//@ProtoNumber(5) var busiData: Any? = null,
)

View File

@ -1,5 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.message
package protobuf.message
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,7 +1,5 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.oidb
package protobuf.oidb
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,11 +1,11 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.oidb.cmd0x6d7
package protobuf.oidb.cmd0x6d7
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.whitechi73.protobuf.group_file_common.FolderInfo
import protobuf.group_file_common.FolderInfo
@Serializable
data class Oidb0x6d7ReqBody(

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.oidb.cmd0x9082
package protobuf.oidb.cmd0x9082
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.oidb.cmd0xf16
package protobuf.oidb.cmd0xf16
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.oidb.cmd0xf88
package protobuf.oidb.cmd0xf88
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -0,0 +1,61 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.oidb.cmd0xfc2
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class Oidb0xfc2ReqBody(
@ProtoNumber(1) var msgCmd: Int? = null,
@ProtoNumber(3) var msgBusType: Int? = null,
@ProtoNumber(4) var msgChannelInfo: Oidb0xfc2ChannelInfo? = null,
@ProtoNumber(5) var msgTerminalType: Int? = null,
//@ProtoNumber(100) var msg_apply_upload_req: Any? = null,
//@ProtoNumber(200) var msg_upload_completed_req: Any? = null,
@ProtoNumber(300) var msgApplyDownloadReq: Oidb0xfc2MsgApplyDownloadReq? = null,
//@ProtoNumber(400) var msg_apply_preview_req: Any? = null,
//@ProtoNumber(500) var msg_apply_security_strike_req: Any? = null,
)
@Serializable
data class Oidb0xfc2RspBody(
@ProtoNumber(1) var msgCmd: Int? = null,
@ProtoNumber(5) var msgBusType: Int? = null,
//@ProtoNumber(110) var msg_apply_upload_rsp: Any? = null,
//@ProtoNumber(210) var msg_upload_completed_rsp: Any? = null,
@ProtoNumber(310) var msgApplyDownloadRsp: Oidb0xfc2MsgApplyDownloadRsp? = null,
//@ProtoNumber(410) var msg_apply_preview_rsp: Any? = null,
//@ProtoNumber(510) var msg_apply_security_strike_rsp: Any? = null,
)
@Serializable
data class Oidb0xfc2MsgApplyDownloadRsp(
@ProtoNumber(1) var msgDownloadInfo: Oidb0xfc2MsgDownloadInfo? = null,
//@ProtoNumber(2) var msgFileInfo: Any? = null,
//@ProtoNumber(3) var msgChacha20Param: Any? = null,
//@ProtoNumber(4) var useEncrypt: UInt = UInt.MIN_VALUE,
)
@Serializable
data class Oidb0xfc2MsgDownloadInfo(
@ProtoNumber(1) var downloadKey: ByteArray? = null,
//@ProtoNumber(2) var msg_out_addr: Any? = null,
//@ProtoNumber(3) var msg_inner_addr: Any? = null,
//@ProtoNumber(4) var msg_out_addr_ipv6: Any? = null,
@ProtoNumber(5) var downloadDomain: String? = null,
@ProtoNumber(6) var downloadUrl: String? = null,
//@ProtoNumber(7) var str_cookie: Any? = null,
)
@Serializable
data class Oidb0xfc2MsgApplyDownloadReq(
@ProtoNumber(1) val fieldId: String,
@ProtoNumber(2) val supportEncrypt: Int,
)
@Serializable
data class Oidb0xfc2ChannelInfo(
@ProtoNumber(3) val guildId: ULong,
@ProtoNumber(4) val channelId: ULong,
)

View File

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.whitechi73.protobuf.oidb.cmx0xf57
package protobuf.oidb.cmx0xf57
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,8 +1,8 @@
package moe.whitechi73.protobuf.push
package protobuf.push
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.whitechi73.protobuf.message.MessageBody
import protobuf.message.MessageBody
@Serializable
data class MessagePush(

View File

@ -0,0 +1,37 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.qweb
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class QWebReq(
@ProtoNumber(1) val seq: Int = 0,
@ProtoNumber(2) val qua: String = "",
@ProtoNumber(3) val deviceInfo: String = "",
@ProtoNumber(4) val buffer: ByteArray? = null,
@ProtoNumber(5) val traceId: String = "",
@ProtoNumber(6) val module: String = "",
@ProtoNumber(7) var cmdname: String? = null,
//@ProtoNumber(8) var loginSig: Any? = null,
//@ProtoNumber(9) var Crypto: Any? = null,
@ProtoNumber(10) var extinfo: List<QWebExtInfo>? = null,
//@ProtoNumber(11) var contentType: Any? = null,
)
@Serializable
data class QWebExtInfo(
@ProtoNumber(1) val key: String,
@ProtoNumber(2) val value: String,
)
@Serializable
data class QWebRsp(
@ProtoNumber(1) var seq: Int? = null,
//@ProtoNumber(2) var retCode: Int? = null,
//@ProtoNumber(3) var errMsg: String? = null,
@ProtoNumber(4) var buffer: ByteArray? = null,
//@ProtoNumber(5) var Extinfo: List<QWebExtInfo>? = null,
)

View File

@ -31,12 +31,8 @@ public final class GProRoleCreateInfo {
return "GProRoleCreateInfo{name=" + this.name + ",color=" + this.color + ",bHoist=" + this.bHoist + ",rolePermissions=" + this.rolePermissions + ",}";
}
public GProRoleCreateInfo(String str, long j2, boolean z, GProRolePermission gProRolePermission) {
public GProRoleCreateInfo(String name, long color, boolean hoist, GProRolePermission permissions) {
this.name = "";
this.rolePermissions = new GProRolePermission();
this.name = str;
this.color = j2;
this.bHoist = z;
this.rolePermissions = gProRolePermission;
}
}

View File

@ -0,0 +1,5 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IGProCreateRoleCallback {
void onCreateRoleResult(int code, String msg, GProSecurityResult result, GProGuildRole role);
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProFetchChannelInvisibleRoleListCallback {
void onFetchChannelInvisibleRoleList(int code, String reason, ArrayList<GProGuildRole> roles);
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProFetchChannelLiveableRoleListCallback {
void onFetchChannelLiveableRoleList(int code, String reason, int seq, ArrayList<GProGuildRole> roles);
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProFetchMemberRolesCallback {
void onFetchMemberRolesCallback(int code, String reason, ArrayList<GProGuildRole> roles);
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProFetchRoleListPermissionCallback {
void onFetchRoleListPermissionCallback(int code, String msg, ArrayList<GProGuildRole> roles, ArrayList<GProGuildRole> lvRoles, ArrayList<Long> myRoles, long unused);
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProFetchRolePermissionCallback {
void onFetchRolePermissionCallback(int code, String msg, GProGuildRole role, GProRolePermission permission, ArrayList<GProRolePermissionDesc> permissionDescs, ArrayList<GProRolePermissionCategory> permissionCategories);
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProFetchTopFeedsCallback {
void onResult(int code, String msg, ArrayList<GProTopFeed> feeds);
}

View File

@ -3,5 +3,5 @@ package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IGProGetUserInfoCallback {
void onGetUserInfo(int i2, String str, ArrayList<GProUser> arrayList, ArrayList<Long> arrayList2);
void onGetUserInfo(int code, String reason, ArrayList<GProUser> userList, ArrayList<Long> tinyIdList);
}

View File

@ -0,0 +1,5 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IGProResultCallback {
void onResult(int code, String msg, GProSecurityResult result);
}

View File

@ -32,7 +32,7 @@ public interface IKernelGuildService {
//void fetchGuestGuildInfoWithChannelList(String guildId, String str2, int i2, int seq, String str3,
// IGProFetchGuestGuildInfoWithChannelListCallback iGProFetchGuestGuildInfoWithChannelListCallback);
GProGuild getGuildInfoFromCache(long j2);
GProGuild getGuildInfoFromCache(long guildId);
// 第一次请求: startIndex = 0 , roleIdIndex = 2
void fetchMemberListWithRole(long guildId, long channelId, long startIndex, long roleIndex, int count, int seq, IGProFetchMemberListWithRoleCallback cb);
@ -41,7 +41,22 @@ public interface IKernelGuildService {
void refreshGuildInfoOnly(long j2, boolean z, int i2);
void fetchUserInfo(long j2, long j3, ArrayList<Long> tinyIdList, int i2, IGProGetUserInfoCallback iGProGetUserInfoCallback);
void refreshGuildUserProfileInfo(long guildId, long tinyId, int seq);
void fetchUserInfo(long guildId, long channelId, ArrayList<Long> tinyIdList, int seq, IGProGetUserInfoCallback cb);
@Deprecated(since = "QQ新版本不支持创建话题子频道")
void fetchTopFeeds(long guildId, long channelId, IGProFetchTopFeedsCallback cb);
void fetchChannelInvisibleRoleList(long guildId, long channelId, IGProFetchChannelInvisibleRoleListCallback cb);
void fetchChannelLiveableRoleList(long guildId, long channelId, IGProFetchChannelLiveableRoleListCallback cb);
void fetchMemberRoles(long guildId, long channelId, long tinyId, int seq, IGProFetchMemberRolesCallback cb);
void fetchRoleListWithPermission(long guildId, int seq, IGProFetchRoleListPermissionCallback cb);
void fetchRoleWithPermission(long guildId, long roleId, int seq, IGProFetchRolePermissionCallback cb);
GProSimpleProfile getSimpleProfile(long guildId, long tinyId, int seq);
@ -50,4 +65,12 @@ public interface IKernelGuildService {
String getGuildUserAvatarUrl(long guildId, long tinyId, int seq);
String getGuildUserNickname(long guildId);
void deleteRole(long guild, long role, IGProResultCallback cb);
void setMemberRoles(long guild, long u1, long u2, long tinyId, ArrayList<Long> addRoles, ArrayList<Long> removeRoles, IGProResultCallback cb);
void setRoleInfo(long guild, long role, GProRoleCreateInfo info, IGProResultCallback cb);
void createRole(long guildId, GProRoleCreateInfo info, ArrayList<Long> initialUsers, IGProCreateRoleCallback cb);
}

View File

@ -138,6 +138,12 @@ char * __cdecl my_strstr(const char *lhs, const char *rhs) {
}
int fake_memcmp(const void* __lhs, const void* __rhs, size_t __n) {
if (__lhs == nullptr || __rhs == nullptr) {
if (__n != 0) {
LOGI("[Shamrock] undefined behaviour in fake_memcmp");
}
return 0;
}
if (my_strstr((const char*) __rhs, "shamrock") && my_strstr((const char*) __lhs, "shamrock")) {
if (backup_memcmp(__lhs, __rhs, __n) == 0) {
// 底层广播判断

View File

@ -7,6 +7,10 @@ import com.tencent.mobileqq.app.QQAppInterface
import com.tencent.mobileqq.msf.core.MsfCore
import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.qphone.base.remote.ToServiceMsg
import io.ktor.utils.io.core.BytePacketBuilder
import io.ktor.utils.io.core.readBytes
import io.ktor.utils.io.core.writeFully
import io.ktor.utils.io.core.writeInt
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -15,11 +19,13 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest
import moe.whitechi73.protobuf.oidb.TrpcOidb
import protobuf.oidb.TrpcOidb
import mqq.app.MobileQQ
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume
@ -38,7 +44,7 @@ internal abstract class BaseSvc {
suspend fun sendOidbAW(cmd: String, cmdId: Int, serviceId: Int, data: ByteArray, trpc: Boolean = false, timeout: Long = 5000L): ByteArray? {
val seq = MsfCore.getNextSeq()
return withTimeoutOrNull(timeout) {
val buffer = withTimeoutOrNull(timeout) {
suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(cmd, seq) {
@ -53,11 +59,21 @@ internal abstract class BaseSvc {
if (it == null)
DynamicReceiver.unregister(seq)
}?.copyOf()
try {
if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) {
val builder = BytePacketBuilder()
val deBuffer = DeflateTools.uncompress(buffer.slice(4))
builder.writeInt(deBuffer.size)
builder.writeFully(deBuffer)
return builder.build().readBytes()
}
} catch (_: Exception) { }
return buffer
}
suspend fun sendBufferAW(cmd: String, isPb: Boolean, data: ByteArray, timeout: Long = 5000L): ByteArray? {
val seq = MsfCore.getNextSeq()
return withTimeoutOrNull<ByteArray?>(timeout) {
val buffer = withTimeoutOrNull<ByteArray?>(timeout) {
suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(cmd, seq) {
@ -71,6 +87,16 @@ internal abstract class BaseSvc {
if (it == null)
DynamicReceiver.unregister(seq)
}?.copyOf()
try {
if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) {
val builder = BytePacketBuilder()
val deBuffer = DeflateTools.uncompress(buffer.slice(4))
builder.writeInt(deBuffer.size)
builder.writeFully(deBuffer)
return builder.build().readBytes()
}
} catch (_: Exception) { }
return buffer
}
fun sendOidb(cmd: String, cmdId: Int, serviceId: Int, buffer: ByteArray, seq: Int = -1, trpc: Boolean = false) {
@ -125,7 +151,7 @@ internal abstract class BaseSvc {
protected suspend fun sendAW(toServiceMsg: ToServiceMsg, timeout: Long = 5000L): ByteArray? {
val seq = MsfCore.getNextSeq()
return withTimeoutOrNull<ByteArray?>(timeout) {
val buffer = withTimeoutOrNull<ByteArray?>(timeout) {
suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(toServiceMsg.serviceCmd, seq) {
@ -139,6 +165,16 @@ internal abstract class BaseSvc {
}.also {
if (it == null) DynamicReceiver.unregister(seq)
}?.copyOf()
try {
if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) {
val builder = BytePacketBuilder()
val deBuffer = DeflateTools.uncompress(buffer.slice(4))
builder.writeInt(deBuffer.size)
builder.writeFully(deBuffer)
return builder.build().readBytes()
}
} catch (_: Exception) { }
return buffer
}
protected fun sendExtra(cmd: String, builder: (Bundle) -> Unit) {

View File

@ -2,18 +2,20 @@ package moe.fuqiuluo.qqinterface.servlet
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.whitechi73.protobuf.oidb.cmd0x9082.Oidb0x9082
import protobuf.oidb.cmd0x9082.Oidb0x9082
internal object ChatSvc: BaseSvc() {
fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) {
val serviceId = if (isSet) 1 else 2
sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, ProtoBuf.encodeToByteArray(Oidb0x9082(
sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, ProtoBuf.encodeToByteArray(
Oidb0x9082(
peer = peer.toULong(),
msgSeq = msgSeq,
faceIndex = faceIndex,
flag = 1u,
u1 = 0u,
u2 = 0u
)))
)
))
}
}

View File

@ -12,27 +12,29 @@ import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.whitechi73.protobuf.oidb.cmd0x6d7.CreateFolderReq
import moe.whitechi73.protobuf.oidb.cmd0x6d7.DeleteFolderReq
import moe.whitechi73.protobuf.oidb.cmd0x6d7.MoveFolderReq
import moe.whitechi73.protobuf.oidb.cmd0x6d7.Oidb0x6d7ReqBody
import moe.whitechi73.protobuf.oidb.cmd0x6d7.Oidb0x6d7RespBody
import moe.whitechi73.protobuf.oidb.cmd0x6d7.RenameFolderReq
import protobuf.oidb.cmd0x6d7.CreateFolderReq
import protobuf.oidb.cmd0x6d7.DeleteFolderReq
import protobuf.oidb.cmd0x6d7.MoveFolderReq
import protobuf.oidb.cmd0x6d7.Oidb0x6d7ReqBody
import protobuf.oidb.cmd0x6d7.Oidb0x6d7RespBody
import protobuf.oidb.cmd0x6d7.RenameFolderReq
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
import tencent.im.oidb.oidb_sso
import moe.whitechi73.protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo
import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo
internal object FileSvc: BaseSvc() {
suspend fun createFileFolder(groupId: String, folderName: String, parentFolderId: String = "/"): Result<GroupFileCommonFolderInfo> {
val data = ProtoBuf.encodeToByteArray(Oidb0x6d7ReqBody(
val data = ProtoBuf.encodeToByteArray(
Oidb0x6d7ReqBody(
createFolder = CreateFolderReq(
groupCode = groupId.toULong(),
appId = 3u,
parentFolderId = parentFolderId,
folderName = folderName
)
))
)
)
val resultBuffer = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
?: return Result.failure(Exception("unable to fetch result"))
val oidbPkg = oidb_sso.OIDBSSOPkg()
@ -45,13 +47,15 @@ internal object FileSvc: BaseSvc() {
}
suspend fun deleteGroupFolder(groupId: String, folderUid: String): Boolean {
val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, ProtoBuf.encodeToByteArray(Oidb0x6d7ReqBody(
val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, ProtoBuf.encodeToByteArray(
Oidb0x6d7ReqBody(
deleteFolder = DeleteFolderReq(
groupCode = groupId.toULong(),
appId = 3u,
folderId = folderUid
)
))) ?: return false
)
)) ?: return false
val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(buffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray())
@ -59,14 +63,16 @@ internal object FileSvc: BaseSvc() {
}
suspend fun moveGroupFolder(groupId: String, folderUid: String, newParentFolderUid: String): Boolean {
val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, ProtoBuf.encodeToByteArray(Oidb0x6d7ReqBody(
val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, ProtoBuf.encodeToByteArray(
Oidb0x6d7ReqBody(
moveFolder = MoveFolderReq(
groupCode = groupId.toULong(),
appId = 3u,
folderId = folderUid,
parentFolderId = "/"
)
))) ?: return false
)
)) ?: return false
val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(buffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray())
@ -74,14 +80,16 @@ internal object FileSvc: BaseSvc() {
}
suspend fun renameFolder(groupId: String, folderUid: String, name: String): Boolean {
val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, ProtoBuf.encodeToByteArray(Oidb0x6d7ReqBody(
val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, ProtoBuf.encodeToByteArray(
Oidb0x6d7ReqBody(
renameFolder = RenameFolderReq(
groupCode = groupId.toULong(),
appId = 3u,
folderId = folderUid,
folderName = name
)
))) ?: return false
)
)) ?: return false
val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(buffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray())

View File

@ -3,8 +3,12 @@
package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.qqguildsdk.api.IGPSService
import com.tencent.qqnt.kernel.nativeinterface.GProGuildRole
import com.tencent.qqnt.kernel.nativeinterface.GProRoleCreateInfo
import com.tencent.qqnt.kernel.nativeinterface.GProRoleMemberList
import com.tencent.qqnt.kernel.nativeinterface.IGProFetchMemberListWithRoleCallback
import com.tencent.qqnt.kernel.nativeinterface.GProRolePermission
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.ExperimentalSerializationApi
@ -18,20 +22,28 @@ import moe.fuqiuluo.qqinterface.servlet.structures.GuildStatus
import moe.fuqiuluo.qqinterface.servlet.structures.SlowModeInfo
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.whitechi73.protobuf.oidb.cmd0xf88.GProFilter
import moe.whitechi73.protobuf.oidb.cmd0xf88.GProUserInfo
import moe.whitechi73.protobuf.oidb.cmd0xf88.Oidb0xf88Req
import moe.whitechi73.protobuf.oidb.cmd0xf88.Oidb0xf88Rsp
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57Filter
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57GuildInfo
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57MetaInfo
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57Req
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57Rsp
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57U1
import moe.whitechi73.protobuf.oidb.cmx0xf57.Oidb0xf57U2
import protobuf.guild.GetGuildFeedsReq
import protobuf.guild.GetGuildFeedsRsp
import protobuf.oidb.cmd0xf88.GProFilter
import protobuf.oidb.cmd0xf88.GProUserInfo
import protobuf.oidb.cmd0xf88.Oidb0xf88Req
import protobuf.oidb.cmd0xf88.Oidb0xf88Rsp
import protobuf.oidb.cmx0xf57.Oidb0xf57Filter
import protobuf.oidb.cmx0xf57.Oidb0xf57GuildInfo
import protobuf.oidb.cmx0xf57.Oidb0xf57MetaInfo
import protobuf.oidb.cmx0xf57.Oidb0xf57Req
import protobuf.oidb.cmx0xf57.Oidb0xf57Rsp
import protobuf.oidb.cmx0xf57.Oidb0xf57U1
import protobuf.oidb.cmx0xf57.Oidb0xf57U2
import protobuf.qweb.QWebExtInfo
import protobuf.qweb.QWebReq
import protobuf.qweb.QWebRsp
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume
@ -42,13 +54,15 @@ internal object GProSvc: BaseSvc() {
}
suspend fun getGuildInfo(guildId: ULong): Result<Oidb0xf57MetaInfo> {
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf57_9", 0xf57, 9, ProtoBuf.encodeToByteArray(Oidb0xf57Req(
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf57_9", 0xf57, 9, ProtoBuf.encodeToByteArray(
Oidb0xf57Req(
filter = Oidb0xf57Filter(
u1 = Oidb0xf57U1(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u),
u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u)
),
guildInfo = Oidb0xf57GuildInfo(guildId = guildId)
)))
)
))
val body = oidb_sso.OIDBSSOPkg()
if (respBuffer == null) {
return Result.failure(Exception("unable to send packet"))
@ -61,6 +75,35 @@ internal object GProSvc: BaseSvc() {
}
}
suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result<GetGuildFeedsRsp> {
val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, ProtoBuf.encodeToByteArray(QWebReq(
seq = 10,
qua = PlatformUtils.getQUA(),
deviceInfo = "i=&imsi=&mac=02:00:00:00:00:00&m=Shamrock&o=114514&a=1919810&sd=0&c64=1&sc=1&p=8000*8000&aid=123456789012345678901234567890abcdef&f=Tencent&mm=5610&cf=1726&cc=8&qimei=&qimei36=&sharpP=1&n=nether_world&support_xsj_live=false&client_mod=concise&timezone=America/La_Paz&material_sdk_version=&vh265=&refreshrate=10086&hwlevel=9&suphdr=1&is_teenager_mod=8&liveH265=&bmst=5&AV1=0",
buffer = ProtoBuf.encodeToByteArray(GetGuildFeedsReq(
count = 12,
from = startIndex,
feedAttchInfo = EMPTY_BYTE_ARRAY,
guildId = guildId,
getType = 1,
u7 = 0,
u8 = 1,
u9 = EMPTY_BYTE_ARRAY
)),
traceId = app.account + "_0_0",
extinfo = listOf(
QWebExtInfo("fc-appid", "96"),
QWebExtInfo("environment_id", "shamrock"),
QWebExtInfo("tiny_id", getSelfTinyId().toString()),
)
))) ?: return Result.failure(Exception("unable to send packet"))
val webRsp = ProtoBuf.decodeFromByteArray<QWebRsp>(buffer.slice(4))
if(webRsp.buffer == null) return Result.failure(Exception("server error"))
val wupBuffer = webRsp.buffer!!
val feeds = ProtoBuf.decodeFromByteArray<GetGuildFeedsRsp>(wupBuffer)
return Result.success(feeds)
}
fun getChannelList(guildId: ULong, refresh: Boolean = false): Result<ArrayList<GProChannelInfo>> {
if (refresh) {
refreshGuildInfo(guildId)
@ -108,6 +151,7 @@ internal object GProSvc: BaseSvc() {
result: ArrayList<GProRoleMemberList> = arrayListOf()
): Result<Pair<GetGuildMemberListNextToken, ArrayList<GProRoleMemberList>>> {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
val fetchGuildMemberListResult: Pair<GetGuildMemberListNextToken, ArrayList<GProRoleMemberList>> = (withTimeoutOrNull(5000) {
suspendCancellableCoroutine {
kernelGProService.fetchMemberListWithRole(guildId.toLong(), 0, startIndex, roleIndex, count, 0) { code, reason, finish, nextIndex, nextRoleIdIndex, _, seq, roleList ->
@ -144,12 +188,14 @@ internal object GProSvc: BaseSvc() {
guildId: ULong,
memberTinyId: ULong
): Result<GProUserInfo> {
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf88_1", 0xf88, 1, ProtoBuf.encodeToByteArray(Oidb0xf88Req(
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf88_1", 0xf88, 1, ProtoBuf.encodeToByteArray(
Oidb0xf88Req(
filter = GProFilter(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u),
memberId = 0uL,
tinyId = memberTinyId,
guildId = guildId
)))
)
))
val body = oidb_sso.OIDBSSOPkg()
if (respBuffer == null) {
return Result.failure(Exception("unable to send packet"))
@ -203,6 +249,21 @@ internal object GProSvc: BaseSvc() {
}
}
suspend fun fetchGuildMemberRoles(guildId: ULong, tinyId: ULong, refresh: Boolean = false): Result<ArrayList<GProGuildRole>> {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
if (refresh) {
kernelGProService.refreshGuildUserProfileInfo(guildId.toLong(), tinyId.toLong(), 1)
}
val result: ArrayList<GProGuildRole> = withTimeoutOrNull(5000) {
suspendCancellableCoroutine {
kernelGProService.fetchMemberRoles(guildId.toLong(), 0, tinyId.toLong(), 2) { code, reason, roles ->
it.resume(roles)
}
}
} ?: return Result.failure(Exception("unable to fetch guild member roles"))
return Result.success(result)
}
fun getGuildList(refresh: Boolean = false, forceOldApi: Boolean): ArrayList<GuildInfo> {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
if (refresh) {
@ -225,4 +286,85 @@ internal object GProSvc: BaseSvc() {
return result
}
suspend fun getGuildRoles(guildId: ULong): Result<List<GProGuildRole>> {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
val roles: List<GProGuildRole> = withTimeoutOrNull(5000) {
suspendCancellableCoroutine {
kernelGProService.fetchRoleListWithPermission(guildId.toLong(), 1) { code, _, roles, _, _, _ ->
if (code != 0) it.resume(null) else it.resume(roles)
}
}
} ?: return Result.failure(Exception("unable to fetch guild roles"))
return Result.success(roles)
}
fun deleteGuildRole(guildId: ULong, roleId: ULong) {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
kernelGProService.deleteRole(guildId.toLong(), roleId.toLong()) { code, msg, result ->
if (code != 0) {
LogCenter.log("deleteGuildRole failed: $code($msg) => $result", Level.WARN)
}
}
}
fun setMemberRole(guildId: ULong, tinyId: ULong, roleId: ULong, isSet: Boolean) {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
val addList = arrayListOf<Long>()
val rmList = arrayListOf<Long>()
(if (isSet) addList else rmList).add(roleId.toLong())
kernelGProService.setMemberRoles(guildId.toLong(), 0, 0, tinyId.toLong(), addList, rmList) { code, msg, result ->
if (code != 0) {
LogCenter.log("setMemberRole failed: $code($msg) => $result", Level.WARN)
}
}
}
suspend fun getGuildRolePermission(guildId: ULong, roleId: ULong): Result<GProGuildRole> {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
val role:GProGuildRole = withTimeoutOrNull(5000) {
suspendCancellableCoroutine {
kernelGProService.fetchRoleWithPermission(guildId.toLong(), roleId.toLong(), 1) { code, msg, role, _, _, _ ->
if (code != 0) {
LogCenter.log("getGuildRolePermission failed: $code($msg)", Level.WARN)
it.resume(null)
} else it.resume(role)
}
}
} ?: return Result.failure(Exception("unable to fetch guild role permission"))
return Result.success(role)
}
suspend fun updateGuildRole(guildId: ULong, roleId: ULong, name: String, color: Long): Result<Unit> {
val oldInfo = getGuildRolePermission(guildId, roleId).onFailure {
return Result.failure(it)
}.getOrThrow()
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
val info = GProRoleCreateInfo(
name, color, oldInfo.bHoist, oldInfo.rolePermissions
)
kernelGProService.setRoleInfo(guildId.toLong(), roleId.toLong(), info) { code, msg, result ->
if (code != 0) {
LogCenter.log("updateGuildRole failed: $code($msg) => $result", Level.WARN)
}
}
return Result.success(Unit)
}
suspend fun createGuildRole(guildId: ULong, name: String, color: Long, initialUsers: ArrayList<Long>): Result<GProGuildRole> {
val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService
val permission = GProRolePermission(false, arrayListOf())
val info = GProRoleCreateInfo(name, color, false, permission)
val role: GProGuildRole = withTimeoutOrNull(5000) {
suspendCancellableCoroutine {
kernelGProService.createRole(guildId.toLong(), info, initialUsers) { code, msg, result, role ->
if (code != 0) {
LogCenter.log("createGuildRole failed: $code($msg) => $result", Level.WARN)
it.resume(null)
} else it.resume(role)
}
}
} ?: return Result.failure(Exception("unable to create guild role"))
return Result.success(role)
}
}

View File

@ -14,7 +14,6 @@ import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.mobileqq.troop.api.ITroopInfoService
import com.tencent.mobileqq.troop.api.ITroopMemberInfoService
import com.tencent.protofile.join_group_link.join_group_link
import com.tencent.qphone.base.remote.FromServiceMsg
import com.tencent.qphone.base.remote.ToServiceMsg
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
@ -75,8 +74,8 @@ import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.whitechi73.protobuf.oidb.cmd0xf16.Oidb0xf16
import moe.whitechi73.protobuf.oidb.cmd0xf16.SetGroupRemarkReq
import protobuf.oidb.cmd0xf16.Oidb0xf16
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
import mqq.app.MobileQQ
import tencent.im.group.group_member_info
import tencent.im.oidb.cmd0x88d.oidb_0x88d
@ -272,13 +271,15 @@ internal object GroupSvc: BaseSvc() {
}
fun modifyGroupRemark(groupId: Long, remark: String): Boolean {
sendOidb("OidbSvc.0xf16_1", 3862, 1, ProtoBuf.encodeToByteArray(Oidb0xf16(
sendOidb("OidbSvc.0xf16_1", 3862, 1, ProtoBuf.encodeToByteArray(
Oidb0xf16(
setGroupRemarkReq = SetGroupRemarkReq(
groupCode = groupId.toULong(),
groupUin = groupCode2GroupUin(groupId).toULong(),
groupRemark = remark
)
)))
)
))
return true
}

View File

@ -61,7 +61,7 @@ internal object MsgSvc: BaseSvc() {
?: return Result.failure(Exception("没有对应消息映射,消息获取失败"))
val peerId = mapping.peerId
val contact = MessageHelper.generateContact(mapping.chatType, peerId)
val contact = MessageHelper.generateContact(mapping.chatType, peerId, mapping.subPeerId ?: "")
val msg = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
@ -89,11 +89,11 @@ internal object MsgSvc: BaseSvc() {
suspend fun getMsgByQMsgId(
chatType: Int,
peerId: String,
qqMsgId: Long
qqMsgId: Long,
subPeerId: String = ""
): Result<MsgRecord> {
val contact = MessageHelper.generateContact(chatType, peerId)
val service = QRoute.api(IMsgService::class.java) ?:
return Result.failure(Exception("获取消息服务"))
val contact = MessageHelper.generateContact(chatType, peerId, subPeerId)
val service = QRoute.api(IMsgService::class.java)
val msg = withTimeoutOrNull(5000) {
suspendCoroutine { continuation ->
@ -152,7 +152,7 @@ internal object MsgSvc: BaseSvc() {
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
?: return -1 to "无法找到消息映射"
val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId)
val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId, mapping.subPeerId ?: "")
return suspendCancellableCoroutine { continuation ->
msgService.recallMsg(contact, arrayListOf(mapping.qqMsgId)) { code, why ->
@ -184,7 +184,7 @@ internal object MsgSvc: BaseSvc() {
}
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
result.onFailure {
LogCenter.log(it.stackTraceToString(), Level.ERROR)
LogCenter.log("sendToAio: " + it.stackTraceToString(), Level.ERROR)
return result
}
val sendResult = result.getOrThrow()
@ -192,7 +192,7 @@ internal object MsgSvc: BaseSvc() {
// 发送失败,可能网络问题出现红色感叹号,重试
// 例如 rich media transfer failed
delay(100)
MessageHelper.resendMsg(chatType, peedId, fromId, sendResult.qqMsgId, 3, sendResult.msgHashId)
MessageHelper.resendMsg(chatType, peedId, fromId, sendResult.qqMsgId, retryCnt, sendResult.msgHashId)
} else {
result
}

View File

@ -1,6 +1,5 @@
package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.msf.core.MsfCore
import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
@ -16,14 +15,14 @@ import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
import moe.fuqiuluo.shamrock.tools.broadcast
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.whitechi73.protobuf.message.JsonElement
import moe.whitechi73.protobuf.message.MessageBody
import moe.whitechi73.protobuf.message.MessageContentHead
import moe.whitechi73.protobuf.message.MessageElement
import moe.whitechi73.protobuf.message.MessageElementList
import moe.whitechi73.protobuf.message.MessageHead
import moe.whitechi73.protobuf.message.RichMessage
import moe.whitechi73.protobuf.push.MessagePush
import protobuf.message.JsonElement
import protobuf.message.MessageBody
import protobuf.message.MessageContentHead
import protobuf.message.MessageElement
import protobuf.message.MessageElementList
import protobuf.message.MessageHead
import protobuf.message.RichMessage
import protobuf.push.MessagePush
import mqq.app.MobileQQ
import kotlin.coroutines.resume
@ -33,9 +32,11 @@ internal object PacketSvc: BaseSvc() {
*/
suspend fun fakeSelfRecvJsonMsg(msgService: IKernelMsgService, content: String): Long {
return fakeReceiveSelfMsg(msgService) {
listOf(MessageElement(
listOf(
MessageElement(
json = JsonElement((byteArrayOf(1) + DeflateTools.compress(content.toByteArray())))
))
)
)
}
}

View File

@ -2,7 +2,6 @@
package moe.fuqiuluo.qqinterface.servlet
import android.graphics.BitmapFactory
import com.tencent.mobileqq.app.QQAppInterface
import com.tencent.mobileqq.transfile.HttpNetReq
import com.tencent.mobileqq.transfile.INetEngineListener
@ -24,18 +23,18 @@ import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.whitechi73.protobuf.fav.WeiyunAddRichMediaReq
import moe.whitechi73.protobuf.fav.WeiyunAuthor
import moe.whitechi73.protobuf.fav.WeiyunCollectCommInfo
import moe.whitechi73.protobuf.fav.WeiyunComm
import moe.whitechi73.protobuf.fav.WeiyunCommonReq
import moe.whitechi73.protobuf.fav.WeiyunFastUploadResourceReq
import moe.whitechi73.protobuf.fav.WeiyunGetFavContentReq
import moe.whitechi73.protobuf.fav.WeiyunGetFavListReq
import moe.whitechi73.protobuf.fav.WeiyunMsgHead
import moe.whitechi73.protobuf.fav.WeiyunPicInfo
import moe.whitechi73.protobuf.fav.WeiyunRichMediaContent
import moe.whitechi73.protobuf.fav.WeiyunRichMediaSummary
import protobuf.fav.WeiyunAddRichMediaReq
import protobuf.fav.WeiyunAuthor
import protobuf.fav.WeiyunCollectCommInfo
import protobuf.fav.WeiyunComm
import protobuf.fav.WeiyunCommonReq
import protobuf.fav.WeiyunFastUploadResourceReq
import protobuf.fav.WeiyunGetFavContentReq
import protobuf.fav.WeiyunGetFavListReq
import protobuf.fav.WeiyunMsgHead
import protobuf.fav.WeiyunPicInfo
import protobuf.fav.WeiyunRichMediaContent
import protobuf.fav.WeiyunRichMediaSummary
import mqq.manager.TicketManager
import oicq.wlogin_sdk.request.Ticket
import oicq.wlogin_sdk.request.WtTicketPromise
@ -95,7 +94,8 @@ internal object QFavSvc: BaseSvc() {
getFavContentReq = WeiyunGetFavContentReq(
cidList = arrayListOf(id)
)
))
)
)
}
suspend fun addImageMsg(
@ -147,9 +147,11 @@ internal object QFavSvc: BaseSvc() {
),
contentType = 1u
),
richMediaContent = listOf(WeiyunRichMediaContent(
richMediaContent = listOf(
WeiyunRichMediaContent(
rawData = """<img src="$picUrl" />""".toByteArray(),
picList = listOf(WeiyunPicInfo(
picList = listOf(
WeiyunPicInfo(
uri = picUrl,
md5 = md5Bytes,
sha1 = md5.toByteArray(),
@ -160,10 +162,13 @@ internal object QFavSvc: BaseSvc() {
size = size.toULong(),
type = 0u,
picId = pid
))
))
)
))
)
)
)
)
)
)
}
suspend fun applyUpImageMsg(
@ -180,7 +185,8 @@ internal object QFavSvc: BaseSvc() {
val md5 = MD5.genFileMd5(image.absolutePath)
return sendWeiyunReq(20010, WeiyunCommonReq(
fastUploadResourceReq = WeiyunFastUploadResourceReq(
picInfoList = listOf(WeiyunPicInfo(
picInfoList = listOf(
WeiyunPicInfo(
md5 = md5,
name = md5.toHexString(),
width = width.toUInt(),
@ -195,9 +201,11 @@ internal object QFavSvc: BaseSvc() {
groupId = groupId.toULong(),
groupName = groupName
)
)),
)
))
),
)
)
)
}
suspend fun addRichMediaMsg(
@ -229,11 +237,14 @@ internal object QFavSvc: BaseSvc() {
brief = content,
contentType = 1u
),
richMediaContent = listOf(WeiyunRichMediaContent(
richMediaContent = listOf(
WeiyunRichMediaContent(
rawData = content.textToHtml().toByteArray(),
))
)
))
)
)
)
)
}
private fun String.textToHtml(): String {
@ -318,7 +329,9 @@ internal object QFavSvc: BaseSvc() {
}
val pSKey = getWeiYunPSKey()
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), ProtoBuf.encodeToByteArray(WeiyunComm(req = req))))
httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), ProtoBuf.encodeToByteArray(
WeiyunComm(req = req)
)))
httpNetReq.mOutStream = outputStream
httpNetReq.mStartDownOffset = 0L
httpNetReq.mReqProperties["Shamrock"] = "true"
@ -338,7 +351,8 @@ internal object QFavSvc: BaseSvc() {
}
private fun packHead(cmd: Int, pskey: String): ByteArray {
return ProtoBuf.encodeToByteArray(WeiyunMsgHead(
return ProtoBuf.encodeToByteArray(
WeiyunMsgHead(
uin = app.longAccountUin.toULong(),
seq = seq++.toUInt(),
type = 1u,
@ -350,7 +364,8 @@ internal object QFavSvc: BaseSvc() {
key = pskey.toByteArray(),
majorVersion = MAJOR_VERSION.toUInt(),
minorVersion = MINOR_VERSION.toUInt(),
))
)
)
}
private fun packData(head: ByteArray, body: ByteArray): ByteArray {

View File

@ -32,12 +32,12 @@ internal suspend fun MsgRecord.toCQCode(): String {
return MessageConvert.convertMessageRecordToCQCode(this)
}
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String): MessageSegmentList {
return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId)
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String, subPeer: String): MessageSegmentList {
return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId, subPeer)
}
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String): String {
return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId)
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String, subPeer: String): String {
return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId, subPeer)
}
@ -64,14 +64,15 @@ internal object MessageConvert {
suspend fun convertMessageElementsToMsgSegment(
chatType: Int,
elements: List<MsgElement>,
peerId: String
peerId: String,
subPeer: String
): ArrayList<MessageSegment> {
val messageData = arrayListOf<MessageSegment>()
elements.forEach { msg ->
kotlin.runCatching {
val elementId = msg.elementType
val converter = convertMap[elementId]
converter?.convert(chatType, peerId, msg)
converter?.convert(chatType, peerId, subPeer, msg)
?: throw UnsupportedOperationException("不支持的消息element类型$elementId")
}.onSuccess {
messageData.add(it)
@ -87,35 +88,45 @@ internal object MessageConvert {
}
suspend fun convertMessageRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList<MessageSegment> {
return convertMessageElementsToMsgSegment(chatType, record.elements, record.peerUin.toString())
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
return convertMessageElementsToMsgSegment(chatType, record.elements, peerId, record.channelId ?: peerId)
}
suspend fun convertMsgElementsToCQCode(
elements: List<MsgElement>,
chatType: Int,
peerId: String
peerId: String,
subPeer: String
): String {
if(elements.isEmpty()) {
return ""
}
val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId).map {
val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId, subPeer).map {
it.toJson()
}
return MessageHelper.encodeCQCode(msgList)
}
suspend fun convertMessageRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String {
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
return MessageHelper.encodeCQCode(
convertMessageElementsToMsgSegment(
chatType,
record.elements,
record.peerUin.toString()
peerId,
record.channelId ?: peerId
).map { it.toJson() }
)
}
}
internal fun interface IMessageConvert {
suspend fun convert(chatType: Int, peerId: String, element: MsgElement): MessageSegment
suspend fun convert(chatType: Int, peerId: String, subPeer: String, element: MsgElement): MessageSegment
}

View File

@ -19,7 +19,12 @@ internal sealed class MessageElemConverter: IMessageConvert {
* 文本 / 艾特 消息转换消息段
*/
data object TextConverter: MessageElemConverter() {
override suspend fun convert(chatType: Int, peerId: String, element: MsgElement): MessageSegment {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val text = element.textElement
return if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
MessageSegment(
@ -43,7 +48,12 @@ internal sealed class MessageElemConverter: IMessageConvert {
* 小表情 / 戳一戳 消息转换消息段
*/
data object FaceConverter: MessageElemConverter() {
override suspend fun convert(chatType: Int, peerId: String, element: MsgElement): MessageSegment {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.faceElement
if (face.faceType == 5) {
@ -112,6 +122,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val image = element.picElement
@ -131,6 +142,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(md5)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(md5)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(md5)
else -> unknownChatType(chatType)
},
"subType" to image.picSubType,
@ -147,6 +159,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val record = element.pttElement
@ -162,6 +175,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid)
else -> unknownChatType(chatType)
}
).also {
@ -183,6 +197,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val video = element.videoElement
@ -195,6 +210,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl(peerId, md5, video.fileUuid)
else -> unknownChatType(chatType)
}
).also {
@ -212,6 +228,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.marketFaceElement
@ -235,6 +252,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val data = element.arkElement.bytesData.asJsonObject
@ -297,6 +315,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val reply = element.replyElement
@ -329,6 +348,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val tip = element.grayTipElement
@ -364,6 +384,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val fileMsg = element.fileElement
@ -373,8 +394,11 @@ internal sealed class MessageElemConverter: IMessageConvert {
val fileId = fileMsg.fileUuid
val bizId = fileMsg.fileBizId ?: 0
val fileSubId = fileMsg.fileSubId ?: ""
val url = if (chatType == MsgConstant.KCHATTYPEC2C) RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
else RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
val url = when (chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
}
return MessageSegment(
type = "file",
@ -398,6 +422,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val multiMsg = element.multiForwardMsgElement
@ -414,6 +439,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val longMsg = element.structLongMsgElement
@ -430,6 +456,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val markdown = element.markdownElement
@ -446,6 +473,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val bubbleElement = element.faceBubbleElement

View File

@ -1,3 +1,4 @@
@file:OptIn(ExperimentalSerializationApi::class)
package moe.fuqiuluo.qqinterface.servlet.transfile
import com.tencent.mobileqq.pb.ByteStringMicro
@ -6,6 +7,10 @@ import com.tencent.mobileqq.transfile.api.IProtoReqManager
import com.tencent.mobileqq.transfile.protohandler.RichProto
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
@ -14,6 +19,10 @@ import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo
import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody
import protobuf.oidb.cmd0xfc2.Oidb0xfc2RspBody
import mqq.app.MobileQQ
import tencent.im.cs.cmd0x346.cmd0x346
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
@ -22,6 +31,32 @@ import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume
internal object RichProtoSvc: BaseSvc() {
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, ProtoBuf.encodeToByteArray(
Oidb0xfc2ReqBody(
msgCmd = 1200,
msgBusType = 4202,
msgChannelInfo = Oidb0xfc2ChannelInfo(
guildId = peerId.toULong(),
channelId = channelId.toULong()
),
msgTerminalType = 2,
msgApplyDownloadReq = Oidb0xfc2MsgApplyDownloadReq(
fieldId = fileId,
supportEncrypt = 0
)
)
)) ?: return ""
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
ProtoBuf.decodeFromByteArray<Oidb0xfc2RspBody>(body.bytes_bodybuffer.get().toByteArray()).msgApplyDownloadRsp?.let {
it.msgDownloadInfo?.let {
return "https://${it.downloadDomain}${it.downloadUrl}&fname=$fileId&isthumb=0"
}
}
return ""
}
suspend fun getGroupFileDownUrl(
peerId: Long,
fileId: String,
@ -34,10 +69,7 @@ internal object RichProtoSvc: BaseSvc() {
uint32_bus_id.set(bizId)
str_file_id.set(fileId)
})
}.toByteArray())
if (buffer == null) {
return ""
} else {
}.toByteArray()) ?: return ""
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
@ -55,7 +87,6 @@ internal object RichProtoSvc: BaseSvc() {
return "$domain/ftn_handler/$downloadUrl/?fname=$fileId&client_proto=qq&client_appid=$appId&client_type=android&client_ver=$version&client_down_type=auto&client_aio_type=unk"
}
}
suspend fun getC2CFileDownUrl(
fileId: String,
@ -83,7 +114,7 @@ internal object RichProtoSvc: BaseSvc() {
}.toByteArray())
if (buffer == null) {
if (retryCnt < 3) {
if (retryCnt < 5) {
return getC2CFileDownUrl(fileId, subId, retryCnt + 1)
}
return ""
@ -122,6 +153,10 @@ internal object RichProtoSvc: BaseSvc() {
return "https://c2cpicdw.qpic.cn/offpic_new/0/123-0-${md5.uppercase()}/0?term=2"
}
fun getGuildPicDownUrl(md5: String): String {
return "https://gchat.qpic.cn/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2"
}
suspend fun getC2CVideoDownUrl(
peerId: String,
md5Hex: String,
@ -287,4 +322,11 @@ internal object RichProtoSvc: BaseSvc() {
}
}
suspend fun getGuildPttDownUrl(
peerId: String,
md5Hex: String,
fileUUId: String
): String {
return "unsupported"
}
}

View File

@ -90,7 +90,7 @@ internal object MessageHelper {
// ActionMsg No Care
if (msg.isEmpty()) {
return Result.success(uniseq.copy(msgTime = System.currentTimeMillis(), msgHashId = 0))
return Result.success(uniseq.copy(msgTime = System.currentTimeMillis()))
}
val totalSize = msg.filter {
@ -107,12 +107,7 @@ internal object MessageHelper {
val sendRet = withTimeoutOrNull<Pair<Int, String>>(estimateTime) {
suspendCancellableCoroutine {
GlobalScope.launch {
sendResult = sendMessageWithoutMsgId(
chatType,
peerId,
msg,
fromId
) { code, message ->
sendResult = sendMessageWithoutMsgId(chatType, peerId, msg, fromId) { code, message ->
callback.onResult(code, message)
it.resume(code to message)
}
@ -248,10 +243,17 @@ internal object MessageHelper {
}
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
val peerId = when(chatType) {
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
ContactHelper.getUidByUinAsync(id.toLong())
} else id
return Contact(chatType, peerId, subId)
}
else -> id
}
return if (chatType == MsgConstant.KCHATTYPEGUILD) {
Contact(chatType, subId, peerId)
} else {
Contact(chatType, peerId, subId)
}
}
fun obtainMessageTypeByDetailType(detailType: String): Int {
@ -308,6 +310,7 @@ internal object MessageHelper {
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"
MsgConstant.KCHATTYPEGUILD -> "guild$msgId"
else -> error("不支持的消息来源类型 | generateMsgIdHash: $chatType")
}
return abs(key.hashCode())
@ -341,11 +344,12 @@ internal object MessageHelper {
time: Long,
chatType: Int,
peerId: String,
subPeerId: String,
msgSeq: Int,
subChatType: Int = chatType
) {
val database = MessageDB.getInstance()
val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq)
val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq, subPeerId)
database.messageMappingDao().insert(mapping)
}

View File

@ -12,7 +12,7 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import mqq.app.MobileQQ
@Entity(tableName = "message_mapping")
@Entity(tableName = "message_mapping_v2")
data class MessageMapping (
@PrimaryKey
val msgHashId: Int,
@ -21,7 +21,8 @@ data class MessageMapping (
val subChatType: Int, // 细化各种事件消息
val peerId: String,
val time: Long,
val msgSeq: Int
val msgSeq: Int,
val subPeerId: String,
)
@Dao
@ -29,25 +30,25 @@ interface MessageMappingDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(mapping: MessageMapping)
@Query("UPDATE message_mapping SET msgSeq = :msgSeq WHERE msgHashId = :hash")
@Query("UPDATE message_mapping_v2 SET msgSeq = :msgSeq WHERE msgHashId = :hash")
fun updateMsgSeqByMsgHash(hash: Int, msgSeq: Int)
@Query("DELETE FROM message_mapping WHERE msgHashId = :hash")
@Query("DELETE FROM message_mapping_v2 WHERE msgHashId = :hash")
fun deleteByMsgHash(hash: Int)
@Query("SELECT * FROM message_mapping WHERE msgHashId = :msgHashId")
@Query("SELECT * FROM message_mapping_v2 WHERE msgHashId = :msgHashId")
fun queryByMsgHashId(msgHashId: Int): MessageMapping?
@Query("SELECT * FROM message_mapping WHERE qqMsgId = :qqMsgId AND chatType = :chatType")
@Query("SELECT * FROM message_mapping_v2 WHERE qqMsgId = :qqMsgId AND chatType = :chatType")
fun queryByQqMsgId(chatType: Int, qqMsgId: Long): MessageMapping?
@Query("SELECT * FROM message_mapping WHERE chatType = :chatType")
@Query("SELECT * FROM message_mapping_v2 WHERE chatType = :chatType")
fun queryByChatType(chatType: Int): List<MessageMapping>
@Query("SELECT * FROM message_mapping WHERE subChatType = :subChatType AND chatType = :chatType")
@Query("SELECT * FROM message_mapping_v2 WHERE subChatType = :subChatType AND chatType = :chatType")
fun queryBySubChatType(chatType: Int, subChatType: Int): List<MessageMapping>
@Query("SELECT * FROM message_mapping WHERE peerId = :peerId AND chatType = :chatType")
@Query("SELECT * FROM message_mapping_v2 WHERE peerId = :peerId AND chatType = :chatType")
fun queryByPeerId(chatType: Int, peerId: String): List<MessageMapping>
//@Query("SELECT * FROM message_mapping WHERE msgSeq = :msgSeq AND chatType = :chatType")
@ -55,7 +56,7 @@ interface MessageMappingDao {
// 不要调用这个seq不唯一啊老哥
// 我就说怎么这么多bug
@Query("SELECT * FROM message_mapping WHERE msgSeq = :msgSeq AND chatType = :chatType AND peerId = :peerId")
@Query("SELECT * FROM message_mapping_v2 WHERE msgSeq = :msgSeq AND chatType = :chatType AND peerId = :peerId")
fun queryByMsgSeq(chatType: Int, peerId: String, msgSeq: Int): MessageMapping?
}
@ -64,7 +65,7 @@ internal abstract class MessageDB: RoomDatabase() {
abstract fun messageMappingDao(): MessageMappingDao
companion object {
private const val DB_NAME = "message_mapping.db"
private const val DB_NAME = "message_mapping_v2.db"
@Volatile
private var instance: MessageDB? = null

View File

@ -0,0 +1,42 @@
@file:Suppress("UNCHECKED_CAST")
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
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.asString
import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("create_guild_role")
internal object CreateGuildRole: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val name = session.getString("name")
val color = session.getLong("color")
val initialUsers = session.getArray("initial_users").map {
it.asString.toULong()
}
return invoke(guildId, color, name, initialUsers, session.echo)
}
suspend operator fun invoke(guildId: ULong, color: Long, name: String, initialUsers: List<ULong>, echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.createGuildRole(guildId, name, color, initialUsers as ArrayList<Long>).onFailure {
return error(it.message ?: "Unknown error", echo)
}.getOrThrow()
return ok(data = CreateGuildRoleResult(
result.roleId.toULong()
), echo = echo)
}
override val requiredParams: Array<String> = arrayOf("guild_id", "color", "name", "initial_users")
@Serializable
data class CreateGuildRoleResult(
@SerialName("role_id") val roleId: ULong
)
}

View File

@ -0,0 +1,24 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
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("delete_guild_role")
internal object DeleteGuildRole: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val roleId = session.getString("role_id").toULong()
return invoke(guildId, roleId, session.echo)
}
operator fun invoke(guildId: ULong, roleId: ULong, echo: JsonElement = EmptyJsonString): String {
GProSvc.deleteGuildRole(guildId, roleId)
return ok("success", echo = echo)
}
override val requiredParams: Array<String> = arrayOf("guild_id", "role_id")
}

View File

@ -16,7 +16,7 @@ import moe.fuqiuluo.shamrock.utils.CryptTools
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.symbols.OneBotHandler
import moe.whitechi73.protobuf.fav.WeiyunComm
import protobuf.fav.WeiyunComm
@OneBotHandler("fav.add_image_msg")
internal object FavAddImageMsg: IActionHandler() {

View File

@ -13,7 +13,7 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.symbols.OneBotHandler
import moe.whitechi73.protobuf.fav.WeiyunComm
import protobuf.fav.WeiyunComm
@OneBotHandler("fav.add_text_msg")
internal object FavAddTextMsg: IActionHandler() {

View File

@ -16,7 +16,7 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.symbols.OneBotHandler
import moe.whitechi73.protobuf.fav.WeiyunComm
import protobuf.fav.WeiyunComm
@OneBotHandler("fav.get_item_content")
internal object FavGetItemContent: IActionHandler() {

View File

@ -13,7 +13,7 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.symbols.OneBotHandler
import moe.whitechi73.protobuf.fav.WeiyunComm
import protobuf.fav.WeiyunComm
@OneBotHandler("fav.get_item_list")
internal object FavGetItemList: IActionHandler() {

View File

@ -41,7 +41,7 @@ internal object GetForwardMsg: IActionHandler() {
msg.senderUin, msg.sendNickName
.ifEmpty { msg.sendMemberName }
.ifEmpty { msg.sendRemarkName }
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()

View File

@ -0,0 +1,38 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
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
import protobuf.guild.StFeed
@OneBotHandler("get_guild_feeds")
internal object GetGuildFeeds: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val channelId = session.getStringOrNull("channel_id")?.toULong() ?: 0uL
val from = session.getIntOrNull("from") ?: 0
return invoke(guildId, channelId, from, session.echo)
}
suspend operator fun invoke(guildId: ULong, channelId: ULong, startIndex: Int, echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.getGuildFeeds(guildId, channelId, startIndex).getOrElse {
GProSvc.getGuildFeeds(guildId, 0uL, startIndex).onFailure {
return error(it.message ?: "server error", echo)
}.getOrThrow()
}
if (result.vecFeed == null) {
return error("server error", echo)
}
return ok(GetGuildFeedsResult(result.isFinish == 1, result.vecFeed!!), echo = echo)
}
@Serializable
data class GetGuildFeedsResult(
val isFinish: Boolean,
val feeds: List<StFeed>
)
}

View File

@ -0,0 +1,78 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
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.ifNullOrEmpty
import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("get_guild_member_profile")
internal object GetGuildMemberProfile: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val userId = session.getString("user_id").toULong()
return invoke(guildId, userId, session.echo)
}
suspend operator fun invoke(guildId: ULong, userId: ULong, echo: JsonElement = EmptyJsonString): String {
val userResult = GProSvc.getUserGuildInfo(guildId, userId).onFailure {
return error(it.message ?: "unable to fetch guild member info", echo)
}.getOrThrow()
val roles = GProSvc.fetchGuildMemberRoles(guildId, userId).onFailure {
return error(it.message ?: "unable to fetch guild member roles", echo)
}.getOrThrow()
return ok(GetGuildMemberInfo(
tinyId = userResult.memberTinyid,
nickname = userResult.nickName ?: "",
avatarUrl = userResult.url ?: "",
joinTime = userResult.joinTime,
roles = roles.map {
RoleInfo(
roleId = it.roleId.toString(),
roleName = it.name.ifNullOrEmpty(it.levelDsc.ifNullOrEmpty(it.displayTagName ?: ""))!!,
color = it.color,
permission = it.rolePermissions.permissionList.map {
Permission(
rootId = it.rootId,
childIds = it.childIds ?: emptyList()
)
},
type = it.type,
displayName = it.displayTagName ?: ""
)
}
), echo = echo)
}
override val requiredParams: Array<String> = arrayOf("guild_id", "user_id")
@Serializable
data class GetGuildMemberInfo(
@SerialName("tiny_id") val tinyId: ULong,
@SerialName("nickname") val nickname: String,
@SerialName("avatar_url") val avatarUrl: String,
@SerialName("join_time") val joinTime: ULong,
@SerialName("roles") val roles: List<RoleInfo>
)
@Serializable
data class RoleInfo(
@SerialName("role_id") val roleId: String,
@SerialName("role_name") val roleName: String,
@SerialName("color") val color: Long,
@SerialName("permission") val permission: List<Permission>,
@SerialName("type") val type: Int,
@SerialName("display_name") val displayName: String
)
@Serializable
data class Permission(
@SerialName("root_id") val rootId: Int,
@SerialName("child_ids") val childIds: List<Int>
)
}

View File

@ -0,0 +1,61 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMemberProfile.Permission
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("get_guild_roles")
internal object GetGuildRoles: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
return invoke(guildId, session.echo)
}
suspend operator fun invoke(guildId: ULong, echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.getGuildRoles(guildId).onFailure {
return error(it.message ?: "unable to fetch guild roles", echo)
}.getOrThrow()
return ok(GetGuildRolesResult(result.map {
GuildRole(
color = it.color,
disabled = it.bHoist,
independent = it.isChannelRole,
maxCount = it.memberLimit,
memberCount = it.count,
owned = it.isNotSort,
roleId = it.roleId,
roleName = it.name,
permission = it.rolePermissions.permissionList.map {
Permission(it.rootId, it.childIds)
},
)
}), echo = echo)
}
override val requiredParams: Array<String> = arrayOf("guild_id")
@Serializable
data class GetGuildRolesResult(
@SerialName("roles") val roles: List<GuildRole>
)
@Serializable
data class GuildRole(
@SerialName("argb_color") val color: Long,
@SerialName("disabled") val disabled: Boolean,
@SerialName("independent") val independent: Boolean,
@SerialName("max_count") val maxCount: Int,
@SerialName("member_count") val memberCount: Int,
@SerialName("owned") val owned: Boolean,
@SerialName("role_id") val roleId: Long,
@SerialName("role_name") val roleName: String,
@SerialName("permission") val permission: List<Permission>,
)
}

View File

@ -65,7 +65,7 @@ internal object GetHistoryMsg: IActionHandler() {
msgId = msgHash,
realId = msg.msgSeq.toInt(),
sender = MessageSender(
msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid
msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid, msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()
@ -89,7 +89,7 @@ internal object GetHistoryMsg: IActionHandler() {
msg.senderUin, msg.sendNickName
.ifEmpty { msg.sendMemberName }
.ifEmpty { msg.sendRemarkName }
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()

View File

@ -34,7 +34,10 @@ internal object GetMsg: IActionHandler() {
msg.senderUin, msg.sendNickName
.ifEmpty { msg.sendMemberName }
.ifEmpty { msg.sendRemarkName }
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
.ifEmpty { msg.peerName }, "unknown",
0,
msg.senderUid,
msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()

View File

@ -0,0 +1,112 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonArray
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.data.MessageResult
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.json
import moe.fuqiuluo.shamrock.tools.jsonArray
import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("send_guild_message", ["send_guild_msg", "send_guild_channel_msg"])
internal object SendGuildMessage: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val channelId = session.getString("channel_id").toULong()
val retryCnt = session.getIntOrNull("retry_cnt") ?: 3
val recallDuration = session.getLongOrNull("recall_duration")
return if (session.isString("message")) {
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
val message = session.getString("message")
return invoke(guildId, channelId, message, autoEscape, retryCnt, recallDuration, echo = session.echo)
} else if (session.isArray("message")) {
val message = session.getArray("message")
return invoke(guildId, channelId, message, echo = session.echo, retryCnt = retryCnt, recallDuration = recallDuration)
} else {
val message = session.getObject("message")
invoke(guildId, channelId, listOf(message).jsonArray, session.echo, retryCnt, recallDuration = recallDuration)
}
}
suspend operator fun invoke(
guildId: ULong,
channelId: ULong,
message: String,
autoEscape: Boolean,
retryCnt: Int,
recallDuration: Long?,
echo: JsonElement = EmptyJsonString
): String {
val result = if (autoEscape) {
MsgSvc.sendToAio(MsgConstant.KCHATTYPEGUILD, guildId.toString(), listOf(
mapOf(
"type" to "text",
"data" to mapOf(
"text" to message
)
)
).json, fromId = channelId.toString(), retryCnt)
} else {
val msg = MessageHelper.decodeCQCode(message)
if (msg.isEmpty()) {
LogCenter.log("CQ码不合法", Level.WARN)
return logic("CQCode is illegal", echo)
} else {
MsgSvc.sendToAio(MsgConstant.KCHATTYPEGUILD, guildId.toString(), msg, fromId = channelId.toString(), retryCnt)
}
}
if (result.isFailure) {
return logic(result.exceptionOrNull()?.message ?: "", echo)
}
val sendMsgResult = result.getOrThrow()
if (sendMsgResult.msgHashId <= 0) {
return logic("send message failed", echo = echo)
}
recallDuration?.let { autoRecall(sendMsgResult.msgHashId, it) }
return ok(
MessageResult(
msgId = sendMsgResult.msgHashId,
time = (sendMsgResult.msgTime * 0.001).toLong()
), echo = echo)
}
suspend operator fun invoke(
guildId: ULong, channelId: ULong, message: JsonArray, echo: JsonElement = EmptyJsonString, retryCnt: Int, recallDuration: Long?,
): String {
val result = MsgSvc.sendToAio(MsgConstant.KCHATTYPEGUILD, guildId.toString(), message, fromId = channelId.toString(), retryCnt)
if (result.isFailure) {
return logic(result.exceptionOrNull()?.message ?: "", echo)
}
val sendMsgResult = result.getOrThrow()
if (sendMsgResult.msgHashId <= 0) {
return logic("send message failed", echo = echo)
}
recallDuration?.let { autoRecall(sendMsgResult.msgHashId, it) }
return ok(MessageResult(
msgId = sendMsgResult.msgHashId,
time = (sendMsgResult.msgTime * 0.001).toLong()
), echo)
}
override val requiredParams: Array<String> = arrayOf("guild_id", "channel_id", "message")
private fun autoRecall(msgHash: Int, duration: Long) {
GlobalScope.launch(Dispatchers.Default) {
delay(duration)
MsgSvc.recallMsg(msgHash)
}
}
}

View File

@ -81,9 +81,6 @@ internal object SendMessage: IActionHandler() {
recallDuration: Long?,
echo: JsonElement = EmptyJsonString
): String {
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
// return logic("contact is not found", echo = echo)
//}
val result = if (autoEscape) {
MsgSvc.sendToAio(chatType, peerId, listOf(
mapOf(

View File

@ -0,0 +1,42 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
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.asString
import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("set_guild_member_role")
internal object SetGuildMemberRole: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val role = session.getString("role_id").toULong()
val set = session.getBooleanOrDefault("set", false)
return if (session.has("user_id")) {
val userId = session.getString("user_id").toULong()
invoke(guildId, userId, role, set, echo = session.echo)
} else if (session.isArray("users")) {
invoke(guildId, session.getArray("users").map {
it.asString.toULong()
}, role, set, echo = session.echo)
} else {
logic("missing user_id or users", echo = session.echo)
}
}
operator fun invoke(guildId: ULong, users: List<ULong>, roleId: ULong, set: Boolean, echo: JsonElement = EmptyJsonString): String {
users.forEach {
GProSvc.setMemberRole(guildId, it, roleId, set)
}
return ok("success", echo = echo)
}
operator fun invoke(guildId: ULong, user: ULong, roleId: ULong, set: Boolean, echo: JsonElement = EmptyJsonString): String {
GProSvc.setMemberRole(guildId, user, roleId, set)
return ok("success", echo = echo)
}
override val requiredParams: Array<String> = arrayOf("guild_id", "role_id")
}

View File

@ -0,0 +1,28 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GProSvc
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("update_guild_role")
internal object UpdateGuildRole: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val guildId = session.getString("guild_id").toULong()
val roleId = session.getString("role_id").toULong()
val name = session.getString("name")
val color = session.getLong("color")
return invoke(guildId, roleId, name, color, session.echo)
}
suspend operator fun invoke(guildId: ULong, roleId: ULong, name: String, color: Long, echo: JsonElement = EmptyJsonString): String {
val result = GProSvc.updateGuildRole(guildId, roleId, name, color).onFailure {
return error(it.message ?: "Unknown error", echo)
}
return ok("success", echo = echo)
}
override val requiredParams: Array<String> = arrayOf("role_id", "guild_id", "name", "color")
}

View File

@ -4,15 +4,38 @@ import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.remote.action.handlers.CreateGuildRole
import moe.fuqiuluo.shamrock.remote.action.handlers.DeleteGuildRole
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGProChannelList
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildFeeds
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildList
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMemberList
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMemberProfile
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMetaByGuest
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildRoles
import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildServiceProfile
import moe.fuqiuluo.shamrock.remote.action.handlers.SendGuildMessage
import moe.fuqiuluo.shamrock.remote.action.handlers.SendMessage
import moe.fuqiuluo.shamrock.remote.action.handlers.SetGuildMemberRole
import moe.fuqiuluo.shamrock.remote.action.handlers.UpdateGuildRole
import moe.fuqiuluo.shamrock.tools.fetchGetOrNull
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.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.isJsonData
import moe.fuqiuluo.shamrock.tools.isJsonObject
import moe.fuqiuluo.shamrock.tools.isJsonString
import moe.fuqiuluo.shamrock.tools.jsonArray
fun Routing.guildAction() {
getOrPost("/get_guild_service_profile") {
@ -40,4 +63,109 @@ fun Routing.guildAction() {
val refresh = fetchGetOrNull("refresh") ?: fetchOrNull("no_cache")
call.respondText(GetGProChannelList(guildId.toULong(), refresh?.toBoolean() ?: false), ContentType.Application.Json)
}
getOrPost("/get_guild_member_profile") {
val guildId = fetchOrThrow("guild_id")
val userId = fetchOrThrow("user_id")
call.respondText(GetGuildMemberProfile(guildId.toULong(), userId.toULong()), ContentType.Application.Json)
}
route("/(send_guild_channel_msg|send_guild_message|send_guild_msg)".toRegex()) {
get {
val guildId = fetchGetOrThrow("guild_id").toULong()
val channelId = fetchGetOrThrow("channel_id").toULong()
val message = fetchGetOrThrow("message")
val autoEscape = fetchGetOrNull("auto_escape")?.toBoolean() ?: false
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
val recallDuration = fetchGetOrNull("recall_duration")?.toLong()
call.respondText(SendGuildMessage(guildId, channelId, message, autoEscape, retryCnt, recallDuration), ContentType.Application.Json)
}
post {
val retryCnt = fetchOrNull("retry_cnt")?.toInt() ?: 3
val recallDuration = fetchOrNull("recall_duration")?.toLong()
val guildId = fetchOrThrow("guild_id").toULong()
val channelId = fetchOrThrow("channel_id").toULong()
call.respondText(if (isJsonData() && !isJsonString("message")) {
if (isJsonObject("message")) {
SendGuildMessage(
guildId = guildId,
channelId = channelId,
message = listOf(fetchPostJsonObject("message")).jsonArray,
retryCnt = retryCnt,
recallDuration = recallDuration
)
} else {
SendGuildMessage(
guildId = guildId,
channelId = channelId,
message = fetchPostJsonArray("message"),
retryCnt = retryCnt,
recallDuration = recallDuration
)
}
} else {
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
SendGuildMessage(
guildId = guildId,
channelId = channelId,
message = fetchOrThrow("message"),
autoEscape = autoEscape,
retryCnt = retryCnt,
recallDuration = recallDuration
)
}, ContentType.Application.Json)
}
}
getOrPost("/get_guild_feeds") {
val guildId = fetchOrThrow("guild_id").toULong()
val channelId = fetchOrNull("channel_id")?.toULong() ?: 0uL
val from = fetchOrNull("from")?.toInt() ?: 0
call.respondText(GetGuildFeeds(guildId, channelId, from), ContentType.Application.Json)
}
getOrPost("/get_guild_roles") {
val guildId = fetchOrThrow("guild_id").toULong()
call.respondText(GetGuildRoles(guildId), ContentType.Application.Json)
}
getOrPost("/delete_guild_role") {
val guildId = fetchOrThrow("guild_id").toULong()
val roleId = fetchOrThrow("role_id").toULong()
call.respondText(DeleteGuildRole(guildId, roleId), ContentType.Application.Json)
}
getOrPost("/set_guild_member_role") {
val guildId = fetchOrThrow("guild_id").toULong()
val roleId = fetchOrThrow("role_id").toULong()
val set = fetchOrNull("set")?.toBoolean() ?: false
val userId = fetchOrNull("user_id")?.toULong()
val users = fetchOrNull("users")?.split(",")?.map { it.toULong() }
call.respondText(
if (userId != null) {
SetGuildMemberRole(guildId, userId, roleId, set)
} else if (users != null) {
SetGuildMemberRole(guildId, users, roleId, set)
} else {
throw IllegalArgumentException("missing user_id or users")
},
ContentType.Application.Json
)
}
getOrPost("/update_guild_role") {
val guildId = fetchOrThrow("guild_id").toULong()
val roleId = fetchOrThrow("role_id").toULong()
val name = fetchOrThrow("name")
val color = fetchOrThrow("color").toLong()
call.respondText(UpdateGuildRole(guildId, roleId, name, color), ContentType.Application.Json)
}
getOrPost("/create_guild_role") {
val guildId = fetchOrThrow("guild_id").toULong()
val name = fetchOrThrow("name")
val color = fetchOrThrow("color").toLong()
val initialUsers = fetchOrThrow("initial_users").split(",").map { it.toULong() }
call.respondText(CreateGuildRole(guildId, color, name, initialUsers), ContentType.Application.Json)
}
}

View File

@ -74,7 +74,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
peerId = uin,
userId = record.senderUin,
message = if(ShamrockConfig.useCQ()) rawMsg.json
else elements.toSegments(record.chatType, record.peerUin.toString()).map {
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map {
it.toJson()
}.json,
rawMessage = rawMsg,
@ -129,7 +129,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
peerId = botUin,
userId = record.senderUin,
message = if(ShamrockConfig.useCQ()) rawMsg.json
else elements.toSegments(record.chatType, record.peerUin.toString()).map {
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map {
it.toJson()
}.json,
rawMessage = rawMsg,
@ -147,6 +147,54 @@ internal object GlobalEventTransmitter: BaseSvc() {
)
return true
}
/**
* 推送私聊消息
*/
suspend fun transGuildMessage(
record: MsgRecord,
elements: ArrayList<MsgElement>,
rawMsg: String,
msgHash: Int,
postType: PostType,
): Boolean {
val botUin = app.longAccountUin
var nickName = record.sendNickName
if (nickName.isNullOrEmpty()) {
CardSvc.getProfileCard(record.senderUin.toString()).onSuccess {
nickName = it.strNick ?: record.peerName
}
}
transMessageEvent(record,
MessageEvent(
time = record.msgTime,
selfId = botUin,
postType = postType,
messageType = MsgType.Guild,
subType = MsgSubType.Channel,
messageId = msgHash,
targetId = record.peerUin,
peerId = botUin,
userId = record.senderUid.toLong(),
message = if(ShamrockConfig.useCQ()) rawMsg.json
else elements.toSegments(record.chatType, record.guildId, record.channelId).map {
it.toJson()
}.json,
rawMessage = rawMsg,
font = 0,
sender = Sender(
userId = record.senderUid.toLong(),
nickname = nickName,
card = record.sendMemberName,
role = MemberRole.Member,
title = record.sendNickName,
level = record.roleId.toString(),
tinyId = record.senderUid
),
)
)
return true
}
}
/**

View File

@ -39,19 +39,46 @@ internal object ShamrockConfig {
fun updateConfig(intent: Intent) {
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
mmkv.apply {
putBoolean( "tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式
putInt( "port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
putBoolean( "ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
putBoolean( "http", intent.getBooleanExtra("http", false)) // HTTP回调开关
putString( "http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址
putBoolean( "ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关
putBoolean( "use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码
putBoolean( "inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包
putBoolean( "debug", intent.getBooleanExtra("debug", false)) // 调试模式
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
putBoolean(
"tablet",
intent.getBooleanExtra("tablet", false)
) // 强制平板模式
putInt("port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
putBoolean("ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
putBoolean(
"http",
intent.getBooleanExtra("http", false)
) // HTTP回调开关
putString(
"http_addr",
intent.getStringExtra("http_addr")
) // WebHook回调地址
putBoolean(
"ws_client",
intent.getBooleanExtra("ws_client", false)
) // 被动WS开关
putBoolean(
"use_cqcode",
intent.getBooleanExtra("use_cqcode", false)
) // 使用CQ码
putBoolean(
"inject_packet",
intent.getBooleanExtra("inject_packet", false)
) // 拦截无用包
putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息
}
Config.defaultToken = intent.getStringExtra("token")
Config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true)
val wsPort = intent.getIntExtra("ws_port", 5800)
Config.activeWebSocket = if (Config.activeWebSocket == null) ConnectionConfig(
address = "0.0.0.0",
@ -59,29 +86,18 @@ internal object ShamrockConfig {
) else Config.activeWebSocket?.also {
it.port = wsPort
}
Config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", "")?.filter { address ->
address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://"))
}?.map {
ConnectionConfig(address = it)
}?.toMutableList()
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息
putBoolean("isInit", true)
}
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
updateConfig()
}
}
private val mmkv: MMKV
get() = MMKVFetcher.mmkvWithId("shamrock_config")

View File

@ -35,6 +35,7 @@ internal data class MessageSender(
@SerialName("sex") val sex: String,
@SerialName("age") val age: Int,
@SerialName("uid") val uid: String,
@SerialName("tiny_id") val tinyId: String,
)
@Serializable

View File

@ -21,12 +21,15 @@ internal enum class MsgSubType {
@SerialName("group") GroupLess,
@SerialName("friend") Friend,
@SerialName("other") Other,
@SerialName("channel") Channel
}
@Serializable
internal enum class MsgType {
@SerialName("group") Group,
@SerialName("private") Private
@SerialName("private") Private,
@SerialName("guild") Guild
}
@Serializable
@ -94,4 +97,5 @@ internal data class Sender(
@SerialName("role") val role: MemberRole?,
@SerialName("title") val title: String,
@SerialName("level") val level: String,
@SerialName("tiny_id") val tinyId: String = "0",
)

View File

@ -38,8 +38,6 @@ internal object AioListener : IKernelMsgListener {
private suspend fun handleMsg(record: MsgRecord) {
try {
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
messageLessListenerMap.firstNotNullOfOrNull {
if (it.key == record.msgSeq) it else null
}?.let {
@ -50,17 +48,22 @@ internal object AioListener : IKernelMsgListener {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when(record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
MessageHelper.saveMsgMapping(
hash = msgHash,
qqMsgId = record.msgId,
chatType = record.chatType,
subChatType = record.chatType,
peerId = record.peerUin.toString(),
peerId = peerId,
msgSeq = record.msgSeq.toInt(),
time = record.msgTime
time = record.msgTime,
subPeerId = record.channelId ?: peerId
)
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
val rawMsg = record.elements.toCQCode(record.chatType, peerId, record.channelId ?: peerId)
if (rawMsg.isEmpty()) return
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
@ -119,6 +122,16 @@ internal object AioListener : IKernelMsgListener {
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
}
}
MsgConstant.KCHATTYPEGUILD -> {
LogCenter.log("频道消息(guildId = ${record.guildId}, sender=${record.senderUid}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)")
if(!GlobalEventTransmitter.MessageTransmitter
.transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType)
) {
LogCenter.log("频道消息推送失败 -> MessageTransmitter", Level.WARN)
}
}
else -> LogCenter.log("不支持PUSH事件: ${record.chatType}")
}
} catch (e: Throwable) {
@ -131,21 +144,25 @@ internal object AioListener : IKernelMsgListener {
}
override fun onAddSendMsg(record: MsgRecord) {
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
if (record.peerUin == TicketSvc.getLongUin()) return // 发给自己的消息不处理
GlobalScope.launch {
try {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when(record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
MessageHelper.saveMsgMapping(
hash = msgHash,
qqMsgId = record.msgId,
chatType = record.chatType,
subChatType = record.chatType,
peerId = record.peerUin.toString(),
peerId = peerId,
msgSeq = record.msgSeq.toInt(),
time = record.msgTime
time = record.msgTime,
subPeerId = record.channelId ?: peerId
)
LogCenter.log("预发送消息($msgHash | ${record.msgSeq} | ${record.msgId})")
@ -167,6 +184,10 @@ internal object AioListener : IKernelMsgListener {
GlobalScope.launch {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when(record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
if (mapping == null) {
@ -175,9 +196,10 @@ internal object AioListener : IKernelMsgListener {
qqMsgId = record.msgId,
chatType = record.chatType,
subChatType = record.chatType,
peerId = record.peerUin.toString(),
peerId = peerId,
msgSeq = record.msgSeq.toInt(),
time = record.msgTime
time = record.msgTime,
subPeerId = record.channelId ?: peerId
)
} else {
LogCenter.log("Update message info from ${mapping.msgSeq} to ${record.msgSeq}", Level.INFO)
@ -190,7 +212,7 @@ internal object AioListener : IKernelMsgListener {
|| record.peerUin == TicketSvc.getLongUin()
) return@launch
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
val rawMsg = record.elements.toCQCode(record.chatType, peerId, record.channelId ?: peerId)
if (rawMsg.isEmpty()) return@launch
LogCenter.log("自发消息(target = ${record.peerUin}, id = $msgHash, msg = $rawMsg)")

View File

@ -31,8 +31,21 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.readBuf32Long
import moe.fuqiuluo.shamrock.xposed.helper.PacketHandler
import moe.whitechi73.protobuf.message.*
import moe.whitechi73.protobuf.push.*
import protobuf.message.MessageContentHead
import protobuf.message.MessageHead
import protobuf.message.RichMessage
import protobuf.push.C2CCommonTipsEvent
import protobuf.push.C2CRecallEvent
import protobuf.push.FriendApplyEvent
import protobuf.push.GroupAdminChangeEvent
import protobuf.push.GroupApplyEvent
import protobuf.push.GroupBanEvent
import protobuf.push.GroupCommonTipsEvent
import protobuf.push.GroupInviteEvent
import protobuf.push.GroupInvitedApplyEvent
import protobuf.push.GroupListChangeEvent
import protobuf.push.MessagePush
import protobuf.push.MessagePushClientInfo
internal object PrimitiveListener {
fun registerListener() {
@ -579,7 +592,7 @@ internal object PrimitiveListener {
}
}
private suspend fun onInviteGroup(time: Long, msgHead: MessageHead,richMsg: RichMessage) {
private suspend fun onInviteGroup(time: Long, msgHead: MessageHead, richMsg: RichMessage) {
val event = ProtoBuf.decodeFromByteArray<GroupInviteEvent>(richMsg.rawBuffer!!)
val groupCode = event.groupCode
val invitorUid = event.inviterUid

View File

@ -18,6 +18,10 @@ import kotlin.random.Random
internal object PlatformUtils {
const val QQ_9_0_8_VER = 5540
fun getQUA(): String {
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
}
fun getQQVersion(context: Context): String {
val packageInfo: PackageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
return packageInfo.versionName