wcferry rust 客户端

This commit is contained in:
super_wang 2023-04-22 23:02:45 +08:00
parent fca65dd24b
commit e92a583cf5
10 changed files with 1303 additions and 30 deletions

65
.gitignore vendored
View File

@ -1,30 +1,35 @@
.*
!.gitignore
!.editorconfig
!.github/
# VS2019 build files
Debug/
Release/
x64/
Out/
# Generated files
*.pb.h
*.pb.c
*.pb.cc
*.dll
*.exe
build/
logs/
java/wcferry/bin/
*_pb2.py
*_pb2_grpc.py
__pycache__
python/dist/
python/*.egg-info
java/target/
WcfGrpc.java
WcfOuterClass.java
.*
!.gitignore
!.editorconfig
!.github/
# VS2019 build files
Debug/
Release/
x64/
Out/
# Generated files
*.pb.h
*.pb.c
*.pb.cc
*.dll
*.exe
build/
logs/
java/wcferry/bin/
*_pb2.py
*_pb2_grpc.py
__pycache__
python/dist/
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
View File

@ -0,0 +1 @@
测试方法在 wechat.rs 中

17
rust/wcferry/Cargo.toml Normal file
View 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
View 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(())
}

View File

@ -0,0 +1 @@
将最新的 dll、exe 放入此文件夹下

View File

@ -0,0 +1 @@
将最新的 wcf.proto 放入此文件夹下

View 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
View File

@ -0,0 +1,5 @@
mod wechat;
fn main() {
println!("Hello, wcferry!");
}

View 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
View 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: wxidwxid_xxxxxxxxxxxxxx
* roomidxxxxxxxxxx@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);
}
}