diff --git a/rust/wcferry/proto/wcf.proto b/rust/wcferry/proto/wcf.proto index dce93f6..34d1d83 100644 --- a/rust/wcferry/proto/wcf.proto +++ b/rust/wcferry/proto/wcf.proto @@ -1,25 +1,8 @@ -syntax = "proto3"; +syntax = "proto3"; package wcf; option java_package = "com.iamteer"; -/* -service Wcf { - rpc RpcIsLogin(Empty) returns (Response) {} - rpc RpcGetSelfWxid(Empty) returns (String) {} - rpc RpcEnableRecvMsg(Empty) returns (stream WxMsg) {} - rpc RpcDisableRecvMsg(Empty) returns (Response) {} - rpc RpcSendTextMsg(TextMsg) returns (Response) {} - rpc RpcSendPathMsg(PathMsg) returns (Response) {} - rpc RpcGetMsgTypes(Empty) returns (MsgTypes) {} - rpc RpcGetContacts(Empty) returns (Contacts) {} - rpc RpcGetDbNames(Empty) returns (DbNames) {} - rpc RpcGetDbTables(String) returns (DbTables) {} - rpc RpcExecDbQuery(DbQuery) returns (DbRows) {} - rpc RpcAcceptNewFriend(Verification) returns (Response) {} -} -*/ - enum Functions { FUNC_RESERVED = 0x00; FUNC_IS_LOGIN = 0x01; @@ -28,6 +11,7 @@ enum Functions { FUNC_GET_CONTACTS = 0x12; FUNC_GET_DB_NAMES = 0x13; FUNC_GET_DB_TABLES = 0x14; + FUNC_GET_USER_INFO = 0x15; FUNC_SEND_TXT = 0x20; FUNC_SEND_IMG = 0x21; FUNC_SEND_FILE = 0x22; @@ -38,6 +22,8 @@ enum Functions { FUNC_EXEC_DB_QUERY = 0x50; FUNC_ACCEPT_FRIEND = 0x51; FUNC_ADD_ROOM_MEMBERS = 0x52; + FUNC_RECV_TRANSFER = 0x53; + FUNC_DECRYPT_IMAGE = 0x60; } message Request @@ -53,6 +39,8 @@ message Request Verification v = 7; AddMembers m = 8; XmlMsg xml = 9; + DecPath dec = 10; + Transfer tf = 11; } } @@ -61,14 +49,15 @@ message Response Functions func = 1; oneof msg { - int32 status = 2; - string str = 3; - WxMsg wxmsg = 4; - MsgTypes types = 5; - RpcContacts contacts = 6; - DbNames dbs = 7; - DbTables tables = 8; - DbRows rows = 9; + int32 status = 2; // Int 状态,通用 + string str = 3; // 字符串 + WxMsg wxmsg = 4; // 微信消息 + MsgTypes types = 5; // 消息类型 + RpcContacts contacts = 6; // 联系人 + DbNames dbs = 7; // 数据库列表 + DbTables tables = 8; // 表列表 + DbRows rows = 9; // 行列表 + UserInfo ui = 10; // 个人信息 }; } @@ -76,14 +65,16 @@ message Empty { } message WxMsg { - bool is_self = 1; // 是否自己发送的 - bool is_group = 2; // 是否群消息 - int32 type = 3; // 消息类型 - string id = 4; // 消息 id - string xml = 5; // 消息 xml - string sender = 6; // 消息发送者 - string roomid = 7; // 群 id(如果是群消息的话) - string content = 8; // 消息内容 + bool is_self = 1; // 是否自己发送的 + bool is_group = 2; // 是否群消息 + int32 type = 3; // 消息类型 + string id = 4; // 消息 id + string xml = 5; // 消息 xml + string sender = 6; // 消息发送者 + string roomid = 7; // 群 id(如果是群消息的话) + string content = 8; // 消息内容 + string thumb = 9; // 缩略图 + string extra = 10; // 附加内容 } message TextMsg @@ -113,11 +104,12 @@ message RpcContact { string wxid = 1; // 微信 id string code = 2; // 微信号 - string name = 3; // 微信昵称 - string country = 4; // 国家 - string province = 5; // 省/州 - string city = 6; // 城市 - int32 gender = 7; // 性别 + string remark = 3; // 备注 + string name = 4; // 微信昵称 + string country = 5; // 国家 + string province = 6; // 省/州 + string city = 7; // 城市 + int32 gender = 8; // 性别 } message RpcContacts { repeated RpcContact contacts = 1; } @@ -147,8 +139,9 @@ message DbRows { repeated DbRow rows = 1; } message Verification { - string v3 = 1; - string v4 = 2; + string v3 = 1; // 加密的用户名 + string v4 = 2; // Ticket + int32 scene = 3; // 添加方式:17 名片,30 扫码 } message AddMembers @@ -156,3 +149,23 @@ message AddMembers string roomid = 1; // 要加的群ID string wxids = 2; // 要加群的人列表,逗号分隔 } + +message UserInfo +{ + string wxid = 1; // 微信ID + string name = 2; // 昵称 + string mobile = 3; // 手机号 + string home = 4; // 文件/图片等父路径 +} + +message DecPath +{ + string src = 1; // 源路径 + string dst = 2; // 目标路径 +} + +message Transfer +{ + string wxid = 1; // 转账人 + string tid = 2; // 转账id transferid +} diff --git a/rust/wcferry/src/proto/wcf.rs b/rust/wcferry/src/proto/wcf.rs index 8d2dda1..5b77e9e 100644 --- a/rust/wcferry/src/proto/wcf.rs +++ b/rust/wcferry/src/proto/wcf.rs @@ -3,7 +3,7 @@ pub struct Request { #[prost(enumeration = "Functions", tag = "1")] pub func: i32, - #[prost(oneof = "request::Msg", tags = "2, 3, 4, 5, 6, 7, 8, 9")] + #[prost(oneof = "request::Msg", tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11")] pub msg: ::core::option::Option, } /// Nested message and enum types in `Request`. @@ -27,6 +27,10 @@ pub mod request { M(super::AddMembers), #[prost(message, tag = "9")] Xml(super::XmlMsg), + #[prost(message, tag = "10")] + Dec(super::DecPath), + #[prost(message, tag = "11")] + Tf(super::Transfer), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -34,7 +38,7 @@ pub mod request { pub struct Response { #[prost(enumeration = "Functions", tag = "1")] pub func: i32, - #[prost(oneof = "response::Msg", tags = "2, 3, 4, 5, 6, 7, 8, 9")] + #[prost(oneof = "response::Msg", tags = "2, 3, 4, 5, 6, 7, 8, 9, 10")] pub msg: ::core::option::Option, } /// Nested message and enum types in `Response`. @@ -42,22 +46,33 @@ pub mod response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Msg { + /// Int 状态,通用 #[prost(int32, tag = "2")] Status(i32), + /// 字符串 #[prost(string, tag = "3")] Str(::prost::alloc::string::String), + /// 微信消息 #[prost(message, tag = "4")] Wxmsg(super::WxMsg), + /// 消息类型 #[prost(message, tag = "5")] Types(super::MsgTypes), + /// 联系人 #[prost(message, tag = "6")] Contacts(super::RpcContacts), + /// 数据库列表 #[prost(message, tag = "7")] Dbs(super::DbNames), + /// 表列表 #[prost(message, tag = "8")] Tables(super::DbTables), + /// 行列表 #[prost(message, tag = "9")] Rows(super::DbRows), + /// 个人信息 + #[prost(message, tag = "10")] + Ui(super::UserInfo), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -90,6 +105,12 @@ pub struct WxMsg { /// 消息内容 #[prost(string, tag = "8")] pub content: ::prost::alloc::string::String, + /// 缩略图 + #[prost(string, tag = "9")] + pub thumb: ::prost::alloc::string::String, + /// 附加内容 + #[prost(string, tag = "10")] + pub extra: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -145,20 +166,23 @@ pub struct RpcContact { /// 微信号 #[prost(string, tag = "2")] pub code: ::prost::alloc::string::String, - /// 微信昵称 + /// 备注 #[prost(string, tag = "3")] + pub remark: ::prost::alloc::string::String, + /// 微信昵称 + #[prost(string, tag = "4")] pub name: ::prost::alloc::string::String, /// 国家 - #[prost(string, tag = "4")] + #[prost(string, tag = "5")] pub country: ::prost::alloc::string::String, /// 省/州 - #[prost(string, tag = "5")] + #[prost(string, tag = "6")] pub province: ::prost::alloc::string::String, /// 城市 - #[prost(string, tag = "6")] + #[prost(string, tag = "7")] pub city: ::prost::alloc::string::String, /// 性别 - #[prost(int32, tag = "7")] + #[prost(int32, tag = "8")] pub gender: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -227,10 +251,15 @@ pub struct DbRows { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Verification { + /// 加密的用户名 #[prost(string, tag = "1")] pub v3: ::prost::alloc::string::String, + /// Ticket #[prost(string, tag = "2")] pub v4: ::prost::alloc::string::String, + /// 添加方式:17 名片,30 扫码 + #[prost(int32, tag = "3")] + pub scene: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -242,6 +271,42 @@ pub struct AddMembers { #[prost(string, tag = "2")] pub wxids: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UserInfo { + /// 微信ID + #[prost(string, tag = "1")] + pub wxid: ::prost::alloc::string::String, + /// 昵称 + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + /// 手机号 + #[prost(string, tag = "3")] + pub mobile: ::prost::alloc::string::String, + /// 文件/图片等父路径 + #[prost(string, tag = "4")] + pub home: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DecPath { + /// 源路径 + #[prost(string, tag = "1")] + pub src: ::prost::alloc::string::String, + /// 目标路径 + #[prost(string, tag = "2")] + pub dst: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Transfer { + /// 转账人 + #[prost(string, tag = "1")] + pub wxid: ::prost::alloc::string::String, + /// 转账id transferid + #[prost(string, tag = "2")] + pub tid: ::prost::alloc::string::String, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum Functions { @@ -252,6 +317,7 @@ pub enum Functions { FuncGetContacts = 18, FuncGetDbNames = 19, FuncGetDbTables = 20, + FuncGetUserInfo = 21, FuncSendTxt = 32, FuncSendImg = 33, FuncSendFile = 34, @@ -262,6 +328,8 @@ pub enum Functions { FuncExecDbQuery = 80, FuncAcceptFriend = 81, FuncAddRoomMembers = 82, + FuncRecvTransfer = 83, + FuncDecryptImage = 96, } impl Functions { /// String value of the enum field names used in the ProtoBuf definition. @@ -277,6 +345,7 @@ impl Functions { Functions::FuncGetContacts => "FUNC_GET_CONTACTS", Functions::FuncGetDbNames => "FUNC_GET_DB_NAMES", Functions::FuncGetDbTables => "FUNC_GET_DB_TABLES", + Functions::FuncGetUserInfo => "FUNC_GET_USER_INFO", Functions::FuncSendTxt => "FUNC_SEND_TXT", Functions::FuncSendImg => "FUNC_SEND_IMG", Functions::FuncSendFile => "FUNC_SEND_FILE", @@ -287,6 +356,8 @@ impl Functions { Functions::FuncExecDbQuery => "FUNC_EXEC_DB_QUERY", Functions::FuncAcceptFriend => "FUNC_ACCEPT_FRIEND", Functions::FuncAddRoomMembers => "FUNC_ADD_ROOM_MEMBERS", + Functions::FuncRecvTransfer => "FUNC_RECV_TRANSFER", + Functions::FuncDecryptImage => "FUNC_DECRYPT_IMAGE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -299,6 +370,7 @@ impl Functions { "FUNC_GET_CONTACTS" => Some(Self::FuncGetContacts), "FUNC_GET_DB_NAMES" => Some(Self::FuncGetDbNames), "FUNC_GET_DB_TABLES" => Some(Self::FuncGetDbTables), + "FUNC_GET_USER_INFO" => Some(Self::FuncGetUserInfo), "FUNC_SEND_TXT" => Some(Self::FuncSendTxt), "FUNC_SEND_IMG" => Some(Self::FuncSendImg), "FUNC_SEND_FILE" => Some(Self::FuncSendFile), @@ -309,6 +381,8 @@ impl Functions { "FUNC_EXEC_DB_QUERY" => Some(Self::FuncExecDbQuery), "FUNC_ACCEPT_FRIEND" => Some(Self::FuncAcceptFriend), "FUNC_ADD_ROOM_MEMBERS" => Some(Self::FuncAddRoomMembers), + "FUNC_RECV_TRANSFER" => Some(Self::FuncRecvTransfer), + "FUNC_DECRYPT_IMAGE" => Some(Self::FuncDecryptImage), _ => None, } } diff --git a/rust/wcferry/src/wechat.rs b/rust/wcferry/src/wechat.rs index abfb1a0..06d35f5 100644 --- a/rust/wcferry/src/wechat.rs +++ b/rust/wcferry/src/wechat.rs @@ -22,6 +22,14 @@ pub struct WeChat { pub enable_accept_firend: bool, } +#[derive(Clone, Debug)] +pub struct UserInfo { + pub wxid: String, + pub name: String, + pub mobile: String, + pub home: String, +} + impl Default for WeChat { fn default() -> Self { WeChat::new(false) @@ -202,6 +210,36 @@ pub fn get_self_wx_id(wechat: &mut WeChat) -> Result, Box Result, Box> { + let req = wcf::Request { + func: wcf::Functions::FuncGetUserInfo.into(), + msg: None, + }; + let response = match send_cmd(wechat, req) { + Ok(res) => res, + Err(e) => { + error!("命令发送失败: {}", e); + return Err("获取用户信息失败".into()); + } + }; + if response.is_none() { + return Ok(None); + } + match response.unwrap() { + wcf::response::Msg::Ui(user_info) => { + return Ok(Some(UserInfo { + wxid: user_info.wxid, + name: user_info.name, + mobile: user_info.mobile, + home: user_info.home, + })); + } + _ => { + return Ok(None); + } + }; +} + pub fn get_contacts( wechat: &mut WeChat, ) -> Result, Box> { @@ -605,11 +643,12 @@ pub fn get_msg_types( pub fn accept_new_friend( v3: String, v4: String, + scene: i32, wechat: &mut WeChat, ) -> Result> { let req = wcf::Request { func: wcf::Functions::FuncAcceptFriend.into(), - msg: Some(wcf::request::Msg::V(wcf::Verification { v3, v4 })), + msg: Some(wcf::request::Msg::V(wcf::Verification { v3, v4, scene })), }; let response = match send_cmd(wechat, req) { Ok(res) => res, @@ -660,6 +699,67 @@ pub fn add_chatroom_members( }; } +pub fn decrypt_image( + src: String, + dst: String, + wechat: &mut WeChat, +) -> Result> { + let req = wcf::Request { + func: wcf::Functions::FuncDecryptImage.into(), + msg: Some(wcf::request::Msg::Dec(wcf::DecPath { src, dst })), + }; + let response = match send_cmd(wechat, req) { + Ok(res) => res, + Err(e) => { + error!("命令发送失败: {}", e); + return Err("图片解密失败".into()); + } + }; + if response.is_none() { + return Ok(false); + } + match response.unwrap() { + wcf::response::Msg::Status(status) => { + return Ok(status == 1); + } + _ => { + return Ok(false); + } + }; +} + +pub fn recv_transfer( + wxid: String, + transferid: String, + wechat: &mut WeChat, +) -> Result> { + let req = wcf::Request { + func: wcf::Functions::FuncRecvTransfer.into(), + msg: Some(wcf::request::Msg::Tf(wcf::Transfer { + wxid, + tid: transferid, + })), + }; + let response = match send_cmd(wechat, req) { + Ok(res) => res, + Err(e) => { + error!("命令发送失败: {}", e); + return Err("接收转账失败".into()); + } + }; + if response.is_none() { + return Ok(false); + } + match response.unwrap() { + wcf::response::Msg::Status(status) => { + return Ok(status == 1); + } + _ => { + return Ok(false); + } + }; +} + mod test { #[test] @@ -743,7 +843,7 @@ mod test { let mut wechat = crate::wechat::WeChat::default(); let v3 = String::from("v3_020b3826fd03010000000000d65613e9435fd2000000501ea9a3dba12f95f6b60a0536a1adb6b4e20a513856625d11892e0635fe745d9c7ee96937f341a860c34107c6417414e5b41e427fc3d26a6af2590a1f@stranger"); let v4 = String::from("v4_000b708f0b0400000100000000003c3767b326120d5b5795b98031641000000050ded0b020927e3c97896a09d47e6e9eac7eea28e4a39b49644b3b702b82268c1d40370261e3ae6eb543d231fbd29ee7a326598ba810316c10171871103ad967ca4d147d9f6dd8fa5ccd4986042520a1173c8138e5afe21f795ee50fecf58b4ac5269acd80028627dbf65fd17ca57c0e479fbe0392288a6f42@stranger"); - let status = crate::wechat::accept_new_friend(v3, v4, &mut wechat).unwrap(); + let status = crate::wechat::accept_new_friend(v3, v4, 17, &mut wechat).unwrap(); println!("Status: {}", status); } @@ -758,4 +858,35 @@ mod test { .unwrap(); println!("Status: {}", status); } + + #[test] + fn test_get_user_info() { + let mut wechat = crate::wechat::WeChat::default(); + let user_info = crate::wechat::get_user_info(&mut wechat).unwrap(); + println!("UserInfo: {:?}", user_info); + } + + #[test] + fn test_recv_transfer() { + let mut wechat = crate::wechat::WeChat::default(); + let status = crate::wechat::recv_transfer( + String::from("****"), + String::from("1000050001202305070217704377865"), + &mut wechat, + ) + .unwrap(); + println!("Status: {}", status); + } + + #[test] + fn test_decrypt_image() { + let mut wechat = crate::wechat::WeChat::default(); + let status = crate::wechat::decrypt_image( + String::from("C:\\Users\\Administrator\\Documents\\WeChat Files\\****\\FileStorage\\MsgAttach\\c963b851e0578c320c2966c6fc49e35c\\Image\\2023-05\\c66044e188c64452e236e53eff73324b.dat"), + String::from("C:\\foo"), + &mut wechat, + ) + .unwrap(); + println!("Status: {}", status); + } }