From 4c7b28f9a298e65905497d8d7aab29785491e03b Mon Sep 17 00:00:00 2001 From: KuriyamaMirai Date: Sat, 12 Nov 2022 01:14:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=BE=A4=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=EF=BC=8C=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=BE=A4=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ricq-core/src/command/oidb_svc/builder.rs | 60 ++++++++++ ricq-core/src/command/oidb_svc/decoder.rs | 93 ++++++++++++++- ricq-core/src/error.rs | 4 + ricq-core/src/pb/oidb/oidb0x6d8.proto | 137 ++++++++++++++++++++++ ricq-core/src/structs.rs | 58 +++++++++ ricq/src/client/api/group.rs | 68 ++++++++++- 6 files changed, 417 insertions(+), 3 deletions(-) create mode 100644 ricq-core/src/pb/oidb/oidb0x6d8.proto diff --git a/ricq-core/src/command/oidb_svc/builder.rs b/ricq-core/src/command/oidb_svc/builder.rs index a00404bc..ec346d1e 100644 --- a/ricq-core/src/command/oidb_svc/builder.rs +++ b/ricq-core/src/command/oidb_svc/builder.rs @@ -379,4 +379,64 @@ impl super::super::super::Engine { let payload = self.transport.encode_oidb_packet(0xeb7, 1, body.to_bytes()); self.uni_packet("OidbSvc.0xeb7", payload) } + // OidbSvc.0x6d8_1 + pub fn build_group_file_list_request_packet( + &self, + group_code: u64, + folder_id: String, + start_index: u32, + ) -> Packet { + let body = pb::oidb::D6d8ReqBody { + file_list_info_req: Some(pb::oidb::GetFileListReqBody { + group_code: Some(group_code), + app_id: Some(3), + folder_id: Some(folder_id), + file_count: Some(20), + all_file_count: Some(0), + req_from: Some(3), + sort_by: Some(1), + filter_code: Some(0), + uin: Some(0), + start_index: Some(start_index), + context: None, + ..Default::default() + }), + ..Default::default() + }; + let payload = self.transport.encode_oidb_packet(0x6d8, 1, body.to_bytes()); + self.uni_packet("OidbSvc.0x6d8_1", payload) + } + // OidbSvc.0x6d6_2 + pub fn build_group_file_download_request_packet( + &self, + group_code: i64, + file_id: String, + bus_id: i32, + ) -> Packet { + let body = pb::oidb::D6d6ReqBody { + download_file_req: Some(pb::oidb::DownloadFileReqBody { + file_id: Some(file_id), + group_code: Some(group_code), + app_id: Some(3), + bus_id: Some(bus_id), + ..Default::default() + }), + ..Default::default() + }; + let payload = self.transport.encode_oidb_packet(1750, 2, body.to_bytes()); + self.uni_packet("OidbSvc.0x6d6_2", payload) + } + // OidbSvc.0x6d8_1 + pub fn build_group_file_count_request_packet(&self, group_code: u64) -> Packet { + let body = pb::oidb::D6d8ReqBody { + group_file_count_req: Some(pb::oidb::GetFileCountReqBody { + group_code: Some(group_code), + app_id: Some(3), + bus_id: Some(0), + }), + ..Default::default() + }; + let payload = self.transport.encode_oidb_packet(0x6d8, 2, body.to_bytes()); + self.uni_packet("OidbSvc.0x6d8_1", payload) + } } diff --git a/ricq-core/src/command/oidb_svc/decoder.rs b/ricq-core/src/command/oidb_svc/decoder.rs index 93ba7c99..cfaf984c 100644 --- a/ricq-core/src/command/oidb_svc/decoder.rs +++ b/ricq-core/src/command/oidb_svc/decoder.rs @@ -1,9 +1,12 @@ use std::collections::HashMap; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use crate::command::oidb_svc::GroupAtAllRemainInfo; -use crate::structs::{GroupInfo, GroupMemberPermission}; +use crate::structs::{ + GroupFileCount, GroupFileInfo, GroupFileItem, GroupFileList, GroupFolderInfo, GroupInfo, + GroupMemberPermission, +}; use crate::{pb, RQResult}; use prost::Message; @@ -103,4 +106,90 @@ impl super::super::super::Engine { }) .collect()) } + // OidbSvc.0x6d8_1 + pub fn decode_group_file_list_response(&self, payload: Bytes) -> RQResult { + let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; + let resp = pb::oidb::D6d8RspBody::decode(&*pkg.bodybuffer)?; + let resp = &resp.file_list_info_rsp.unwrap_or_default(); + Ok(GroupFileList { + all_file_count: resp.all_file_count(), + is_end: resp.is_end(), + items: resp + .item_list + .clone() + .into_iter() + .map(|f| { + if let Some(fi) = f.file_info { + let folder_info = f.folder_info.unwrap_or_default(); + GroupFileItem { + file_info: GroupFileInfo { + file_id: fi.file_id().to_string(), + bus_id: fi.bus_id(), + file_name: fi.file_name().to_string(), + sha: format!("{:x}", BytesMut::from(fi.sha())), + dead_time: fi.dead_time(), + file_size: fi.file_size(), + upload_time: fi.upload_time(), + uploader_uin: fi.uploader_uin(), + uploader_name: fi.uploader_name().to_string(), + parent_folder_id: fi.parent_folder_id().to_string(), + local_path: fi.local_path().to_string(), + modify_time: fi.modify_time(), + download_times: fi.download_times(), + md5: Bytes::from(fi.md5.unwrap_or_default()), + sha3: Bytes::from(fi.sha3.unwrap_or_default()), + uploaded_size: fi.uploaded_size.unwrap_or_default(), + }, + folder_info: GroupFolderInfo { + create_time: folder_info.create_time(), + create_uin: folder_info.create_uin(), + creator_name: folder_info.creator_name.unwrap_or_default(), + folder_id: folder_info.folder_id.unwrap_or_default(), + folder_name: folder_info.folder_name.unwrap_or_default(), + modify_time: folder_info.modify_time.unwrap_or_default(), + parent_folder_id: folder_info.parent_folder_id.unwrap_or_default(), + total_file_count: folder_info.total_file_count.unwrap_or_default(), + }, + r#type: f.r#type.unwrap_or_default(), + } + } else { + GroupFileItem::default() + } + }) + .collect(), + next_index: resp.next_index(), + role: resp.role(), + }) + } + // OidbSvc.0x6d6_2 + pub fn decode_group_file_download_response( + &self, + payload: Bytes, + filename: &str, + ) -> RQResult { + let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; + let resp = pb::oidb::D6d6RspBody::decode(&*pkg.bodybuffer)?; + let f_rsp = resp.download_file_rsp.unwrap(); + Ok(format!( + "http://{}/ftn_handler/{:x}/?fname={}", + f_rsp.download_ip(), + BytesMut::from(f_rsp.download_url()), + filename + )) + } + // OidbSvc.0x6d8_1 + pub fn decode_group_file_count_response(&self, payload: Bytes) -> RQResult { + let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; + let resp = pb::oidb::D6d8RspBody::decode(&*pkg.bodybuffer)?; + if let Some(file_count_rsp) = resp.file_count_rsp { + Ok(GroupFileCount { + is_full: file_count_rsp.is_full.unwrap_or_default(), + all_file_count: file_count_rsp.all_file_count.unwrap_or_default(), + limit_count: file_count_rsp.limit_count.unwrap_or_default(), + file_too_many: file_count_rsp.file_too_many.unwrap_or_default(), + }) + } else { + Err(crate::RQError::GetFileCountFailed) + } + } } diff --git a/ricq-core/src/error.rs b/ricq-core/src/error.rs index 0d192f45..15c522bf 100644 --- a/ricq-core/src/error.rs +++ b/ricq-core/src/error.rs @@ -54,4 +54,8 @@ pub enum RQError { #[error("Token login failed")] TokenLoginFailed, + #[error("获取文件总数失败")] + GetFileCountFailed, + #[error("获取文件总数失败")] + GetFileListFailed(String), } diff --git a/ricq-core/src/pb/oidb/oidb0x6d8.proto b/ricq-core/src/pb/oidb/oidb0x6d8.proto new file mode 100644 index 00000000..2b768d73 --- /dev/null +++ b/ricq-core/src/pb/oidb/oidb0x6d8.proto @@ -0,0 +1,137 @@ +syntax = "proto2"; + +package oidb; + +message D6D8ReqBody { + optional GetFileInfoReqBody fileInfoReq = 1; + optional GetFileListReqBody fileListInfoReq = 2; + optional GetFileCountReqBody groupFileCountReq = 3; + optional GetSpaceReqBody groupSpaceReq = 4; +} + +message D6D8RspBody { + optional GetFileInfoRspBody fileInfoRsp = 1; + optional GetFileListRspBody fileListInfoRsp = 2; + optional GetFileCountRspBody fileCountRsp = 3; + optional GetSpaceRspBody groupSpaceRsp = 4; +} + +message GetFileInfoReqBody { + optional uint64 groupCode = 1; + optional uint32 appId = 2; + optional uint32 busId = 3; + optional string fileId = 4; + optional uint32 fieldFlag = 5; +} + +message GetFileInfoRspBody { + optional int32 retCode = 1; + optional string retMsg = 2; + optional string clientWording = 3; + optional GroupFileInfo fileInfo = 4; +} + +message GetFileListRspBody { + optional int32 retCode = 1; + optional string retMsg = 2; + optional string clientWording = 3; + optional bool isEnd = 4; + message Item { + optional uint32 type = 1; + optional GroupFolderInfo folderInfo = 2; + optional GroupFileInfo fileInfo = 3; + } + repeated Item itemList = 5; + optional FileTimeStamp maxTimestamp = 6; + optional uint32 allFileCount = 7; + optional uint32 filterCode = 8; + optional bool safeCheckFlag = 11; + optional uint32 safeCheckRes = 12; + optional uint32 nextIndex = 13; + optional bytes context = 14; + optional uint32 role = 15; + optional uint32 openFlag = 16; +} + +message GroupFileInfo {/* renamed from FileInfo */ + optional string fileId = 1; + optional string fileName = 2; + optional uint64 fileSize = 3; + optional uint32 busId = 4; + optional uint64 uploadedSize = 5; + optional uint32 uploadTime = 6; + optional uint32 deadTime = 7; + optional uint32 modifyTime = 8; + optional uint32 downloadTimes = 9; + optional bytes sha = 10; + optional bytes sha3 = 11; + optional bytes md5 = 12; + optional string localPath = 13; + optional string uploaderName = 14; + optional uint64 uploaderUin = 15; + optional string parentFolderId = 16; +} + +message GroupFolderInfo {/* renamed from FolderInfo */ + optional string folderId = 1; + optional string parentFolderId = 2; + optional string folderName = 3; + optional uint32 createTime = 4; + optional uint32 modifyTime = 5; + optional uint64 createUin = 6; + optional string creatorName = 7; + optional uint32 totalFileCount = 8; +} + + +message GetFileListReqBody { + optional uint64 groupCode = 1; + optional uint32 appId = 2; + optional string folderId = 3; + optional FileTimeStamp startTimestamp = 4; + optional uint32 fileCount = 5; + optional FileTimeStamp maxTimestamp = 6; + optional uint32 allFileCount = 7; + optional uint32 reqFrom = 8; + optional uint32 sortBy = 9; + optional uint32 filterCode = 10; + optional uint64 uin = 11; + optional uint32 fieldFlag = 12; + optional uint32 startIndex = 13; + optional bytes context = 14; + optional uint32 clientVersion = 15; +} + +message GetFileCountReqBody { + optional uint64 groupCode = 1; + optional uint32 appId = 2; + optional uint32 busId = 3; +} + +message GetSpaceReqBody { + optional uint64 groupCode = 1; + optional uint32 appId = 2; +} + +message GetFileCountRspBody { + optional int32 retCode = 1; + optional string retMsg = 2; + optional string clientWording = 3; + optional uint32 allFileCount = 4; + optional bool fileTooMany = 5; + optional uint32 limitCount = 6; + optional bool isFull = 7; +} + +message GetSpaceRspBody { + optional int32 retCode = 1; + optional string retMsg = 2; + optional string clientWording = 3; + optional uint64 totalSpace = 4; + optional uint64 usedSpace = 5; +} + +message FileTimeStamp { + optional uint32 uploadTime = 1; + optional string fileId = 2; +} \ No newline at end of file diff --git a/ricq-core/src/structs.rs b/ricq-core/src/structs.rs index 0a25f734..16165ca6 100644 --- a/ricq-core/src/structs.rs +++ b/ricq-core/src/structs.rs @@ -261,3 +261,61 @@ pub struct FriendAudioMessage { pub from_nick: String, pub audio: FriendAudio, } +// 群文件总数 +#[derive(Debug, Clone, Default)] +pub struct GroupFileCount { + pub is_full: bool, + pub all_file_count: u32, + pub limit_count: u32, + pub file_too_many: bool, +} + +// 群文件列表 +#[derive(Debug, Clone, Default)] +pub struct GroupFileList { + pub all_file_count: u32, + pub is_end: bool, + pub items: Vec, + pub role: u32, + pub next_index: u32, +} +// 群文件列表 +#[derive(Debug, Clone, Default)] +pub struct GroupFileItem { + pub r#type: u32, + pub folder_info: GroupFolderInfo, + pub file_info: GroupFileInfo, +} + +// 群文件夹 +#[derive(Debug, Clone, Default)] +pub struct GroupFolderInfo { + pub folder_id: String, + pub parent_folder_id: String, + pub folder_name: String, + pub create_time: u32, + pub modify_time: u32, + pub create_uin: u64, + pub creator_name: String, + pub total_file_count: u32, +} +// 群文件 +#[derive(Debug, Clone, Default)] +pub struct GroupFileInfo { + pub file_id: String, + pub file_name: String, + pub file_size: u64, + pub bus_id: u32, + pub uploaded_size: u64, + pub upload_time: u32, + pub dead_time: u32, + pub modify_time: u32, + pub download_times: u32, + pub sha: String, + pub sha3: Bytes, + pub md5: Bytes, + pub local_path: String, + pub uploader_name: String, + pub uploader_uin: u64, + pub parent_folder_id: String, +} diff --git a/ricq/src/client/api/group.rs b/ricq/src/client/api/group.rs index 86787bf2..417fc394 100644 --- a/ricq/src/client/api/group.rs +++ b/ricq/src/client/api/group.rs @@ -16,7 +16,7 @@ use ricq_core::msg::elem::{Anonymous, GroupImage, RichMsg, VideoFile}; use ricq_core::msg::MessageChain; use ricq_core::pb; use ricq_core::pb::short_video::ShortVideoUploadRsp; -use ricq_core::structs::{ForwardMessage, MessageNode}; +use ricq_core::structs::{ForwardMessage, GroupFileCount, GroupFileList, MessageNode}; use ricq_core::structs::{GroupAudio, GroupMemberPermission}; use ricq_core::structs::{GroupInfo, GroupMemberInfo, MessageReceipt}; @@ -841,4 +841,70 @@ impl super::super::Client { self.send_and_wait(req).await?; Ok(()) } + // 获取群文件列表 + pub async fn get_group_file_list( + &self, + group_code: u64, + folder_id: &str, + start_index: u32, + ) -> RQResult { + let req = self + .engine + .read() + .await + .build_group_file_list_request_packet(group_code, folder_id.into(), start_index); + let resp = self.send_and_wait(req).await?; + self.engine + .read() + .await + .decode_group_file_list_response(resp.body) + } + + /// 获取群文件总数 + pub async fn get_group_files_count(&self, group_code: u64) -> RQResult { + let req = self + .engine + .read() + .await + .build_group_file_count_request_packet(group_code); + let resp = self.send_and_wait(req).await?; + self.engine + .read() + .await + .decode_group_file_count_response(resp.body) + } + /// 获取文件下载链接 + /// # Examples + /// ``` + /// let file_list = client.get_group_file_list(group_code, "/", 0).await.unwrap(); + /// for item_info in file_list.items { + /// let url = client + /// .get_group_file_download( + /// group_code, + /// &item_info.file_info.file_id, + /// item_info.file_info.bus_id, + /// &item_info.file_info.file_name, + /// ) + /// .await; + /// println!("{:?}", url); + /// } + ///``` + pub async fn get_group_file_download( + &self, + group_code: i64, + file_id: &str, + bus_id: u32, + file_name: &str, + ) -> RQResult { + let req = self + .engine + .read() + .await + .build_group_file_download_request_packet(group_code, file_id.into(), bus_id as i32); + let resp = self.send_and_wait(req).await?; + self.engine + .read() + .await + .decode_group_file_download_response(resp.body, file_name) + } }