wcferry rust 客户端
This commit is contained in:
parent
fca65dd24b
commit
e92a583cf5
5
.gitignore
vendored
5
.gitignore
vendored
@ -28,3 +28,8 @@ python/*.egg-info
|
||||
java/target/
|
||||
WcfGrpc.java
|
||||
WcfOuterClass.java
|
||||
|
||||
rust/target/
|
||||
rust/wcferry/Cargo.lock
|
||||
rust/wcferry/.wcf.lock
|
||||
/rust/wcferry/target/CACHEDIR.TAG
|
||||
|
1
rust/README.MD
Normal file
1
rust/README.MD
Normal file
@ -0,0 +1 @@
|
||||
测试方法在 wechat.rs 中
|
17
rust/wcferry/Cargo.toml
Normal file
17
rust/wcferry/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "wcferry"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tonic = "0.8.3"
|
||||
prost = "0.11.5"
|
||||
nng = "1.0.1"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4.17"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.8.4"
|
9
rust/wcferry/build.rs
Normal file
9
rust/wcferry/build.rs
Normal file
@ -0,0 +1,9 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::configure()
|
||||
.build_client(true)
|
||||
.build_server(false)
|
||||
.out_dir("src/proto")
|
||||
.compile(&["proto/wcf.proto"], &["."])
|
||||
.expect("failed to compile protos");
|
||||
Ok(())
|
||||
}
|
1
rust/wcferry/lib/README.MD
Normal file
1
rust/wcferry/lib/README.MD
Normal file
@ -0,0 +1 @@
|
||||
将最新的 dll、exe 放入此文件夹下
|
1
rust/wcferry/proto/README.MD
Normal file
1
rust/wcferry/proto/README.MD
Normal file
@ -0,0 +1 @@
|
||||
将最新的 wcf.proto 放入此文件夹下
|
158
rust/wcferry/proto/wcf.proto
Normal file
158
rust/wcferry/proto/wcf.proto
Normal file
@ -0,0 +1,158 @@
|
||||
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;
|
||||
FUNC_GET_SELF_WXID = 0x10;
|
||||
FUNC_GET_MSG_TYPES = 0x11;
|
||||
FUNC_GET_CONTACTS = 0x12;
|
||||
FUNC_GET_DB_NAMES = 0x13;
|
||||
FUNC_GET_DB_TABLES = 0x14;
|
||||
FUNC_SEND_TXT = 0x20;
|
||||
FUNC_SEND_IMG = 0x21;
|
||||
FUNC_SEND_FILE = 0x22;
|
||||
FUNC_SEND_XML = 0x23;
|
||||
FUNC_SEND_EMOTION = 0x24;
|
||||
FUNC_ENABLE_RECV_TXT = 0x30;
|
||||
FUNC_DISABLE_RECV_TXT = 0x40;
|
||||
FUNC_EXEC_DB_QUERY = 0x50;
|
||||
FUNC_ACCEPT_FRIEND = 0x51;
|
||||
FUNC_ADD_ROOM_MEMBERS = 0x52;
|
||||
}
|
||||
|
||||
message Request
|
||||
{
|
||||
Functions func = 1;
|
||||
oneof msg
|
||||
{
|
||||
Empty empty = 2;
|
||||
string str = 3;
|
||||
TextMsg txt = 4;
|
||||
PathMsg file = 5;
|
||||
DbQuery query = 6;
|
||||
Verification v = 7;
|
||||
AddMembers m = 8;
|
||||
XmlMsg xml = 9;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
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; // 消息内容
|
||||
}
|
||||
|
||||
message TextMsg
|
||||
{
|
||||
string msg = 1; // 要发送的消息内容
|
||||
string receiver = 2; // 消息接收人,当为群时可@
|
||||
string aters = 3; // 要@的人列表,逗号分隔
|
||||
}
|
||||
|
||||
message PathMsg
|
||||
{
|
||||
string path = 1; // 要发送的图片的路径
|
||||
string receiver = 2; // 消息接收人
|
||||
}
|
||||
|
||||
message XmlMsg
|
||||
{
|
||||
string receiver = 1; // 消息接收人
|
||||
string content = 2; // xml 内容
|
||||
string path = 3; // 图片路径
|
||||
int32 type = 4; // 消息类型
|
||||
}
|
||||
|
||||
message MsgTypes { map<int32, string> types = 1; }
|
||||
|
||||
message RpcContact
|
||||
{
|
||||
string wxid = 1; // 微信 id
|
||||
string code = 2; // 微信号
|
||||
string name = 3; // 微信昵称
|
||||
string country = 4; // 国家
|
||||
string province = 5; // 省/州
|
||||
string city = 6; // 城市
|
||||
int32 gender = 7; // 性别
|
||||
}
|
||||
message RpcContacts { repeated RpcContact contacts = 1; }
|
||||
|
||||
message DbNames { repeated string names = 1; }
|
||||
|
||||
message DbTable
|
||||
{
|
||||
string name = 1; // 表名
|
||||
string sql = 2; // 建表 SQL
|
||||
}
|
||||
message DbTables { repeated DbTable tables = 1; }
|
||||
|
||||
message DbQuery
|
||||
{
|
||||
string db = 1; // 目标数据库
|
||||
string sql = 2; // 查询 SQL
|
||||
}
|
||||
|
||||
message DbField
|
||||
{
|
||||
int32 type = 1; // 字段类型
|
||||
string column = 2; // 字段名称
|
||||
bytes content = 3; // 字段内容
|
||||
}
|
||||
message DbRow { repeated DbField fields = 1; }
|
||||
message DbRows { repeated DbRow rows = 1; }
|
||||
|
||||
message Verification
|
||||
{
|
||||
string v3 = 1;
|
||||
string v4 = 2;
|
||||
}
|
||||
|
||||
message AddMembers
|
||||
{
|
||||
string roomid = 1; // 要加的群ID
|
||||
string wxids = 2; // 要加群的人列表,逗号分隔
|
||||
}
|
5
rust/wcferry/src/main.rs
Normal file
5
rust/wcferry/src/main.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod wechat;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, wcferry!");
|
||||
}
|
315
rust/wcferry/src/proto/wcf.rs
Normal file
315
rust/wcferry/src/proto/wcf.rs
Normal file
@ -0,0 +1,315 @@
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Request {
|
||||
#[prost(enumeration = "Functions", tag = "1")]
|
||||
pub func: i32,
|
||||
#[prost(oneof = "request::Msg", tags = "2, 3, 4, 5, 6, 7, 8, 9")]
|
||||
pub msg: ::core::option::Option<request::Msg>,
|
||||
}
|
||||
/// Nested message and enum types in `Request`.
|
||||
pub mod request {
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum Msg {
|
||||
#[prost(message, tag = "2")]
|
||||
Empty(super::Empty),
|
||||
#[prost(string, tag = "3")]
|
||||
Str(::prost::alloc::string::String),
|
||||
#[prost(message, tag = "4")]
|
||||
Txt(super::TextMsg),
|
||||
#[prost(message, tag = "5")]
|
||||
File(super::PathMsg),
|
||||
#[prost(message, tag = "6")]
|
||||
Query(super::DbQuery),
|
||||
#[prost(message, tag = "7")]
|
||||
V(super::Verification),
|
||||
#[prost(message, tag = "8")]
|
||||
M(super::AddMembers),
|
||||
#[prost(message, tag = "9")]
|
||||
Xml(super::XmlMsg),
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Response {
|
||||
#[prost(enumeration = "Functions", tag = "1")]
|
||||
pub func: i32,
|
||||
#[prost(oneof = "response::Msg", tags = "2, 3, 4, 5, 6, 7, 8, 9")]
|
||||
pub msg: ::core::option::Option<response::Msg>,
|
||||
}
|
||||
/// Nested message and enum types in `Response`.
|
||||
pub mod response {
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum Msg {
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Empty {}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct WxMsg {
|
||||
/// 是否自己发送的
|
||||
#[prost(bool, tag = "1")]
|
||||
pub is_self: bool,
|
||||
/// 是否群消息
|
||||
#[prost(bool, tag = "2")]
|
||||
pub is_group: bool,
|
||||
/// 消息类型
|
||||
#[prost(int32, tag = "3")]
|
||||
pub r#type: i32,
|
||||
/// 消息 id
|
||||
#[prost(string, tag = "4")]
|
||||
pub id: ::prost::alloc::string::String,
|
||||
/// 消息 xml
|
||||
#[prost(string, tag = "5")]
|
||||
pub xml: ::prost::alloc::string::String,
|
||||
/// 消息发送者
|
||||
#[prost(string, tag = "6")]
|
||||
pub sender: ::prost::alloc::string::String,
|
||||
/// 群 id(如果是群消息的话)
|
||||
#[prost(string, tag = "7")]
|
||||
pub roomid: ::prost::alloc::string::String,
|
||||
/// 消息内容
|
||||
#[prost(string, tag = "8")]
|
||||
pub content: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TextMsg {
|
||||
/// 要发送的消息内容
|
||||
#[prost(string, tag = "1")]
|
||||
pub msg: ::prost::alloc::string::String,
|
||||
/// 消息接收人,当为群时可@
|
||||
#[prost(string, tag = "2")]
|
||||
pub receiver: ::prost::alloc::string::String,
|
||||
/// 要@的人列表,逗号分隔
|
||||
#[prost(string, tag = "3")]
|
||||
pub aters: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PathMsg {
|
||||
/// 要发送的图片的路径
|
||||
#[prost(string, tag = "1")]
|
||||
pub path: ::prost::alloc::string::String,
|
||||
/// 消息接收人
|
||||
#[prost(string, tag = "2")]
|
||||
pub receiver: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct XmlMsg {
|
||||
/// 消息接收人
|
||||
#[prost(string, tag = "1")]
|
||||
pub receiver: ::prost::alloc::string::String,
|
||||
/// xml 内容
|
||||
#[prost(string, tag = "2")]
|
||||
pub content: ::prost::alloc::string::String,
|
||||
/// 图片路径
|
||||
#[prost(string, tag = "3")]
|
||||
pub path: ::prost::alloc::string::String,
|
||||
/// 消息类型
|
||||
#[prost(int32, tag = "4")]
|
||||
pub r#type: i32,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct MsgTypes {
|
||||
#[prost(map = "int32, string", tag = "1")]
|
||||
pub types: ::std::collections::HashMap<i32, ::prost::alloc::string::String>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RpcContact {
|
||||
/// 微信 id
|
||||
#[prost(string, tag = "1")]
|
||||
pub wxid: ::prost::alloc::string::String,
|
||||
/// 微信号
|
||||
#[prost(string, tag = "2")]
|
||||
pub code: ::prost::alloc::string::String,
|
||||
/// 微信昵称
|
||||
#[prost(string, tag = "3")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
/// 国家
|
||||
#[prost(string, tag = "4")]
|
||||
pub country: ::prost::alloc::string::String,
|
||||
/// 省/州
|
||||
#[prost(string, tag = "5")]
|
||||
pub province: ::prost::alloc::string::String,
|
||||
/// 城市
|
||||
#[prost(string, tag = "6")]
|
||||
pub city: ::prost::alloc::string::String,
|
||||
/// 性别
|
||||
#[prost(int32, tag = "7")]
|
||||
pub gender: i32,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RpcContacts {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub contacts: ::prost::alloc::vec::Vec<RpcContact>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbNames {
|
||||
#[prost(string, repeated, tag = "1")]
|
||||
pub names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbTable {
|
||||
/// 表名
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
/// 建表 SQL
|
||||
#[prost(string, tag = "2")]
|
||||
pub sql: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbTables {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub tables: ::prost::alloc::vec::Vec<DbTable>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbQuery {
|
||||
/// 目标数据库
|
||||
#[prost(string, tag = "1")]
|
||||
pub db: ::prost::alloc::string::String,
|
||||
/// 查询 SQL
|
||||
#[prost(string, tag = "2")]
|
||||
pub sql: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbField {
|
||||
/// 字段类型
|
||||
#[prost(int32, tag = "1")]
|
||||
pub r#type: i32,
|
||||
/// 字段名称
|
||||
#[prost(string, tag = "2")]
|
||||
pub column: ::prost::alloc::string::String,
|
||||
/// 字段内容
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub content: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbRow {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub fields: ::prost::alloc::vec::Vec<DbField>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DbRows {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub rows: ::prost::alloc::vec::Vec<DbRow>,
|
||||
}
|
||||
#[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,
|
||||
#[prost(string, tag = "2")]
|
||||
pub v4: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AddMembers {
|
||||
/// 要加的群ID
|
||||
#[prost(string, tag = "1")]
|
||||
pub roomid: ::prost::alloc::string::String,
|
||||
/// 要加群的人列表,逗号分隔
|
||||
#[prost(string, tag = "2")]
|
||||
pub wxids: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum Functions {
|
||||
FuncReserved = 0,
|
||||
FuncIsLogin = 1,
|
||||
FuncGetSelfWxid = 16,
|
||||
FuncGetMsgTypes = 17,
|
||||
FuncGetContacts = 18,
|
||||
FuncGetDbNames = 19,
|
||||
FuncGetDbTables = 20,
|
||||
FuncSendTxt = 32,
|
||||
FuncSendImg = 33,
|
||||
FuncSendFile = 34,
|
||||
FuncSendXml = 35,
|
||||
FuncSendEmotion = 36,
|
||||
FuncEnableRecvTxt = 48,
|
||||
FuncDisableRecvTxt = 64,
|
||||
FuncExecDbQuery = 80,
|
||||
FuncAcceptFriend = 81,
|
||||
FuncAddRoomMembers = 82,
|
||||
}
|
||||
impl Functions {
|
||||
/// String value of the enum field names used in the ProtoBuf definition.
|
||||
///
|
||||
/// The values are not transformed in any way and thus are considered stable
|
||||
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
Functions::FuncReserved => "FUNC_RESERVED",
|
||||
Functions::FuncIsLogin => "FUNC_IS_LOGIN",
|
||||
Functions::FuncGetSelfWxid => "FUNC_GET_SELF_WXID",
|
||||
Functions::FuncGetMsgTypes => "FUNC_GET_MSG_TYPES",
|
||||
Functions::FuncGetContacts => "FUNC_GET_CONTACTS",
|
||||
Functions::FuncGetDbNames => "FUNC_GET_DB_NAMES",
|
||||
Functions::FuncGetDbTables => "FUNC_GET_DB_TABLES",
|
||||
Functions::FuncSendTxt => "FUNC_SEND_TXT",
|
||||
Functions::FuncSendImg => "FUNC_SEND_IMG",
|
||||
Functions::FuncSendFile => "FUNC_SEND_FILE",
|
||||
Functions::FuncSendXml => "FUNC_SEND_XML",
|
||||
Functions::FuncSendEmotion => "FUNC_SEND_EMOTION",
|
||||
Functions::FuncEnableRecvTxt => "FUNC_ENABLE_RECV_TXT",
|
||||
Functions::FuncDisableRecvTxt => "FUNC_DISABLE_RECV_TXT",
|
||||
Functions::FuncExecDbQuery => "FUNC_EXEC_DB_QUERY",
|
||||
Functions::FuncAcceptFriend => "FUNC_ACCEPT_FRIEND",
|
||||
Functions::FuncAddRoomMembers => "FUNC_ADD_ROOM_MEMBERS",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"FUNC_RESERVED" => Some(Self::FuncReserved),
|
||||
"FUNC_IS_LOGIN" => Some(Self::FuncIsLogin),
|
||||
"FUNC_GET_SELF_WXID" => Some(Self::FuncGetSelfWxid),
|
||||
"FUNC_GET_MSG_TYPES" => Some(Self::FuncGetMsgTypes),
|
||||
"FUNC_GET_CONTACTS" => Some(Self::FuncGetContacts),
|
||||
"FUNC_GET_DB_NAMES" => Some(Self::FuncGetDbNames),
|
||||
"FUNC_GET_DB_TABLES" => Some(Self::FuncGetDbTables),
|
||||
"FUNC_SEND_TXT" => Some(Self::FuncSendTxt),
|
||||
"FUNC_SEND_IMG" => Some(Self::FuncSendImg),
|
||||
"FUNC_SEND_FILE" => Some(Self::FuncSendFile),
|
||||
"FUNC_SEND_XML" => Some(Self::FuncSendXml),
|
||||
"FUNC_SEND_EMOTION" => Some(Self::FuncSendEmotion),
|
||||
"FUNC_ENABLE_RECV_TXT" => Some(Self::FuncEnableRecvTxt),
|
||||
"FUNC_DISABLE_RECV_TXT" => Some(Self::FuncDisableRecvTxt),
|
||||
"FUNC_EXEC_DB_QUERY" => Some(Self::FuncExecDbQuery),
|
||||
"FUNC_ACCEPT_FRIEND" => Some(Self::FuncAcceptFriend),
|
||||
"FUNC_ADD_ROOM_MEMBERS" => Some(Self::FuncAddRoomMembers),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
761
rust/wcferry/src/wechat.rs
Normal file
761
rust/wcferry/src/wechat.rs
Normal file
@ -0,0 +1,761 @@
|
||||
use std::{env, path::PathBuf, process::Command, time::Duration, vec};
|
||||
|
||||
use log::{error, info, warn};
|
||||
use nng::options::{Options, RecvTimeout};
|
||||
use prost::Message;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const DEFAULT_URL: &'static str = "tcp://127.0.0.1:10086";
|
||||
const LISTEN_URL: &'static str = "tcp://127.0.0.1:10087";
|
||||
|
||||
pub mod wcf {
|
||||
include!("proto/wcf.rs");
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WeChat {
|
||||
pub url: String,
|
||||
pub wcf_path: PathBuf,
|
||||
pub debug: bool,
|
||||
pub socket: nng::Socket,
|
||||
pub listening: bool,
|
||||
pub enable_accept_firend: bool,
|
||||
}
|
||||
|
||||
impl Default for WeChat {
|
||||
fn default() -> Self {
|
||||
WeChat::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl WeChat {
|
||||
pub fn new(debug: bool) -> Self {
|
||||
let path = env::current_dir().unwrap().join("lib").join("wcf.exe");
|
||||
let _ = start(path.clone(), debug);
|
||||
let socket = connect(&DEFAULT_URL).unwrap();
|
||||
WeChat {
|
||||
url: String::from(DEFAULT_URL),
|
||||
wcf_path: path,
|
||||
debug,
|
||||
socket,
|
||||
listening: false,
|
||||
enable_accept_firend: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start(wcf_path: PathBuf, debug: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut args = vec!["start"];
|
||||
if debug {
|
||||
args.push("debug");
|
||||
}
|
||||
info!(
|
||||
"wcf_path: {}, debug: {}",
|
||||
wcf_path.clone().to_str().unwrap(),
|
||||
debug
|
||||
);
|
||||
let _ = match Command::new(wcf_path.to_str().unwrap()).args(args).output() {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
error!("命令行启动失败: {}", e);
|
||||
return Err("服务启动失败".into());
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(wechat: &mut WeChat) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _ = disable_listen(wechat);
|
||||
wechat.socket.close();
|
||||
let output = Command::new(wechat.wcf_path.to_str().unwrap())
|
||||
.args(["stop"])
|
||||
.output();
|
||||
let _output = match output {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
error!("服务停止失败: {}", e);
|
||||
return Err("服务停止失败".into());
|
||||
}
|
||||
};
|
||||
info!("服务已停止: {}", wechat.url);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect(url: &str) -> Result<nng::Socket, Box<dyn std::error::Error>> {
|
||||
let client = match nng::Socket::new(nng::Protocol::Pair1) {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
error!("Socket创建失败: {}", e);
|
||||
return Err("连接服务失败".into());
|
||||
}
|
||||
};
|
||||
match client.set_opt::<RecvTimeout>(Some(Duration::from_millis(5000))) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
error!("连接参数设置失败: {}", e);
|
||||
return Err("连接参数设置失败".into());
|
||||
}
|
||||
};
|
||||
match client.set_opt::<nng::options::SendTimeout>(Some(Duration::from_millis(5000))) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
error!("连接参数设置失败: {}", e);
|
||||
return Err("连接参数设置失败".into());
|
||||
}
|
||||
};
|
||||
match client.dial(url) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
error!("连接服务失败: {}", e);
|
||||
return Err("连接服务失败".into());
|
||||
}
|
||||
};
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
fn send_cmd(
|
||||
wechat: &WeChat,
|
||||
req: wcf::Request,
|
||||
) -> Result<Option<wcf::response::Msg>, Box<dyn std::error::Error>> {
|
||||
let mut buf = Vec::with_capacity(req.encoded_len());
|
||||
match req.encode(&mut buf) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
error!("序列化失败: {}", e);
|
||||
return Err("通信失败".into());
|
||||
}
|
||||
};
|
||||
let msg = nng::Message::from(&buf[..]);
|
||||
let _ = match wechat.socket.send(msg) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
error!("Socket发送失败: {:?}, {}", e.0, e.1);
|
||||
return Err("通信失败".into());
|
||||
}
|
||||
};
|
||||
let mut msg = match wechat.socket.recv() {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
error!("Socket接收失败: {}", e);
|
||||
return Err("通信失败".into());
|
||||
}
|
||||
};
|
||||
// 反序列化为prost消息
|
||||
let response = match wcf::Response::decode(msg.as_slice()) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("反序列化失败: {}", e);
|
||||
return Err("通信失败".into());
|
||||
}
|
||||
};
|
||||
msg.clear();
|
||||
Ok(response.msg)
|
||||
}
|
||||
|
||||
pub fn is_login(wechat: &WeChat) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncIsLogin.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(false);
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Status(status) => {
|
||||
return Ok(1 == status);
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_self_wx_id(wechat: &mut WeChat) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncGetSelfWxid.into(),
|
||||
msg: None,
|
||||
};
|
||||
let response = match send_cmd(wechat, req) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("命令发送失败: {}", e);
|
||||
return Err("获取微信ID失败".into());
|
||||
}
|
||||
};
|
||||
if response.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Str(wx_id) => {
|
||||
return Ok(Some(wx_id));
|
||||
}
|
||||
_ => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_contacts(
|
||||
wechat: &mut WeChat,
|
||||
) -> Result<Option<wcf::RpcContacts>, Box<dyn std::error::Error>> {
|
||||
info!("获取联系人");
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncGetContacts.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::Contacts(contact) => {
|
||||
return Ok(Some(contact));
|
||||
}
|
||||
_ => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_db_names(wechat: &mut WeChat) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncGetDbNames.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(vec![]);
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Dbs(dbs) => {
|
||||
return Ok(dbs.names);
|
||||
}
|
||||
_ => {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_db_tables(
|
||||
wechat: &mut WeChat,
|
||||
db: String,
|
||||
) -> Result<Vec<wcf::DbTable>, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncGetDbTables.into(),
|
||||
msg: Some(wcf::request::Msg::Str(db)),
|
||||
};
|
||||
let response = match send_cmd(wechat, req) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("命令发送失败: {}", e);
|
||||
return Err("获取数据库表失败".into());
|
||||
}
|
||||
};
|
||||
if response.is_none() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Tables(tables) => {
|
||||
let tables = tables.tables;
|
||||
return Ok(tables);
|
||||
}
|
||||
_ => {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn exec_db_query(
|
||||
wechat: &mut WeChat,
|
||||
db: String,
|
||||
sql: String,
|
||||
) -> Result<Vec<wcf::DbRow>, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncExecDbQuery.into(),
|
||||
msg: Some(wcf::request::Msg::Query(wcf::DbQuery { db, sql })),
|
||||
};
|
||||
let response = match send_cmd(wechat, req) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("命令发送失败: {}", e);
|
||||
return Err("执行SQL失败".into());
|
||||
}
|
||||
};
|
||||
if response.is_none() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Rows(rows) => {
|
||||
let rows = rows.rows;
|
||||
return Ok(rows);
|
||||
}
|
||||
_ => {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param msg: 消息内容(如果是 @ 消息则需要有跟 @ 的人数量相同的 @)
|
||||
* @param receiver: 消息接收人,私聊为 wxid(wxid_xxxxxxxxxxxxxx),群聊为
|
||||
* roomid(xxxxxxxxxx@chatroom)
|
||||
* @param aters: 群聊时要 @ 的人(私聊时为空字符串),多个用逗号分隔。@所有人 用
|
||||
* notify@all(必须是群主或者管理员才有权限)
|
||||
* @return int
|
||||
* @Description 发送文本消息
|
||||
* @author Changhua
|
||||
* @example sendText(" Hello @ 某人1 @ 某人2 ", " xxxxxxxx @ chatroom ",
|
||||
* "wxid_xxxxxxxxxxxxx1,wxid_xxxxxxxxxxxxx2");
|
||||
*/
|
||||
pub fn send_text(
|
||||
wechat: &mut WeChat,
|
||||
msg: String,
|
||||
receiver: String,
|
||||
aters: String,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let text_msg = wcf::TextMsg {
|
||||
msg,
|
||||
receiver,
|
||||
aters,
|
||||
};
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncSendTxt.into(),
|
||||
msg: Some(wcf::request::Msg::Txt(text_msg)),
|
||||
};
|
||||
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(1 == status);
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn send_image(
|
||||
wechat: &mut WeChat,
|
||||
path: PathBuf,
|
||||
receiver: String,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let image_msg = wcf::PathMsg {
|
||||
path: String::from(path.to_str().unwrap()),
|
||||
receiver,
|
||||
};
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncSendImg.into(),
|
||||
msg: Some(wcf::request::Msg::File(image_msg)),
|
||||
};
|
||||
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(1 == status);
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn send_file(
|
||||
wechat: &mut WeChat,
|
||||
path: PathBuf,
|
||||
receiver: String,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let image_msg = wcf::PathMsg {
|
||||
path: String::from(path.to_str().unwrap()),
|
||||
receiver,
|
||||
};
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncSendFile.into(),
|
||||
msg: Some(wcf::request::Msg::File(image_msg)),
|
||||
};
|
||||
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(1 == status);
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn send_xml(
|
||||
wechat: &mut WeChat,
|
||||
xml: String,
|
||||
path: PathBuf,
|
||||
receiver: String,
|
||||
xml_type: i32,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let xml_msg = wcf::XmlMsg {
|
||||
content: xml,
|
||||
path: String::from(path.to_str().unwrap()),
|
||||
receiver,
|
||||
r#type: xml_type,
|
||||
};
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncSendXml.into(),
|
||||
msg: Some(wcf::request::Msg::Xml(xml_msg)),
|
||||
};
|
||||
let response = match send_cmd(wechat, req) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("命令发送失败: {}", e);
|
||||
return Err("微信XML消息发送失败".into());
|
||||
}
|
||||
};
|
||||
if response.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Status(status) => {
|
||||
return Ok(1 == status);
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn send_emotion(
|
||||
wechat: &mut WeChat,
|
||||
path: PathBuf,
|
||||
receiver: String,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let image_msg = wcf::PathMsg {
|
||||
path: String::from(path.to_str().unwrap()),
|
||||
receiver,
|
||||
};
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncSendEmotion.into(),
|
||||
msg: Some(wcf::request::Msg::File(image_msg)),
|
||||
};
|
||||
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(1 == status);
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn enable_listen(wechat: &mut WeChat) -> Result<nng::Socket, Box<dyn std::error::Error>> {
|
||||
if wechat.listening {
|
||||
return Err("消息接收服务已开启".into());
|
||||
}
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncEnableRecvTxt.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 Err("消息接收服务启动失败".into());
|
||||
}
|
||||
let client = connect(LISTEN_URL).unwrap();
|
||||
wechat.listening = true;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn disable_listen(wechat: &mut WeChat) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
if !wechat.listening {
|
||||
return Ok(true);
|
||||
}
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncDisableRecvTxt.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 Err("消息接收服务停止失败".into());
|
||||
}
|
||||
wechat.listening = false;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
pub fn recv_msg(client: &nng::Socket) -> Result<Option<wcf::WxMsg>, Box<dyn std::error::Error>> {
|
||||
let mut msg = match client.recv() {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
warn!("Socket消息接收失败: {}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
// 反序列化为prost消息
|
||||
let res = match wcf::Response::decode(msg.as_slice()) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("反序列化失败: {}", e);
|
||||
return Err("微信消息接收失败".into());
|
||||
}
|
||||
};
|
||||
msg.clear();
|
||||
let res_msg = res.msg;
|
||||
match res_msg {
|
||||
Some(wcf::response::Msg::Wxmsg(msg)) => {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
_ => {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息类型
|
||||
* {"47": "石头剪刀布 | 表情图片", "62": "小视频", "43": "视频", "1": "文字", "10002": "撤回消息", "40": "POSSIBLEFRIEND_MSG", "10000": "红包、系统消息", "37": "好友确认", "48": "位置", "42": "名片", "49": "共享实时位置、文件、转账、链接", "3": "图片", "34": "语音", "9999": "SYSNOTICE", "52": "VOIPNOTIFY", "53": "VOIPINVITE", "51": "微信初始化", "50": "VOIPMSG"}
|
||||
*/
|
||||
pub fn get_msg_types(
|
||||
wechat: &mut WeChat,
|
||||
) -> Result<HashMap<i32, String>, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncGetMsgTypes.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(HashMap::default());
|
||||
}
|
||||
match response.unwrap() {
|
||||
wcf::response::Msg::Types(msg_types) => {
|
||||
let types = msg_types.types;
|
||||
return Ok(types);
|
||||
}
|
||||
_ => {
|
||||
return Ok(HashMap::default());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn accept_new_friend(
|
||||
v3: String,
|
||||
v4: String,
|
||||
wechat: &mut WeChat,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncAcceptFriend.into(),
|
||||
msg: Some(wcf::request::Msg::V(wcf::Verification { v3, v4 })),
|
||||
};
|
||||
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 add_chatroom_members(
|
||||
roomid: String,
|
||||
wxids: String,
|
||||
wechat: &mut WeChat,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let req = wcf::Request {
|
||||
func: wcf::Functions::FuncAddRoomMembers.into(),
|
||||
msg: Some(wcf::request::Msg::M(wcf::AddMembers { roomid, wxids })),
|
||||
};
|
||||
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]
|
||||
fn test_start_stop() {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
let mut wechat = crate::wechat::WeChat::new(false);
|
||||
thread::sleep(Duration::from_secs(20));
|
||||
let _ = crate::wechat::stop(&mut wechat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_login() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let is_login = crate::wechat::is_login(&mut wechat).unwrap();
|
||||
println!("IsLogin: {}", is_login);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_self_wx_id() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let wx_id = crate::wechat::get_self_wx_id(&mut wechat).unwrap().unwrap();
|
||||
println!("WxId: {}", wx_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_contacts() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let contacts = crate::wechat::get_contacts(&mut wechat).unwrap().unwrap();
|
||||
println!("WxId: {:?}", contacts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_text() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let status = crate::wechat::send_text(
|
||||
&mut wechat,
|
||||
String::from("Hello, wcferry!"),
|
||||
String::from("******@chatroom"),
|
||||
String::from(""),
|
||||
)
|
||||
.unwrap();
|
||||
println!("Success: {}", status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_image() {
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let status = crate::wechat::send_image(
|
||||
&mut wechat,
|
||||
PathBuf::from("C:\\Users\\Administrator\\Pictures\\1.jpg"),
|
||||
String::from("****@chatroom"),
|
||||
)
|
||||
.unwrap();
|
||||
println!("Success: {}", status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recv_msg() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let mut socket = crate::wechat::enable_listen(&mut wechat).unwrap();
|
||||
for _ in 1..100 {
|
||||
let msg = crate::wechat::recv_msg(&mut socket).unwrap();
|
||||
println!("WxMsg: {:?}", msg);
|
||||
}
|
||||
let _ = crate::wechat::disable_listen(&mut wechat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_msg_types() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let types = crate::wechat::get_msg_types(&mut wechat);
|
||||
println!("{:?}", types);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_accept_new_friend() {
|
||||
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();
|
||||
println!("Status: {}", status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_chatroom_members() {
|
||||
let mut wechat = crate::wechat::WeChat::default();
|
||||
let status = crate::wechat::add_chatroom_members(
|
||||
String::from("*****@chatroom"),
|
||||
String::from("****"),
|
||||
&mut wechat,
|
||||
)
|
||||
.unwrap();
|
||||
println!("Status: {}", status);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user