Merge branch 'lich0821:master' into master

This commit is contained in:
chandler 2025-05-01 11:34:11 +08:00 committed by GitHub
commit 79b7629b3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 709 additions and 452 deletions

View File

@ -4,4 +4,7 @@ charset = utf-8-bom
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
indent_size = 4
[*.{yml,yaml}]
indent_size = 2

View File

@ -1,114 +0,0 @@
name: Build-WeChatFerry
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
jobs:
build:
runs-on: windows-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 获取版本号和微信版本号
run: |
$version_full = (Select-String -Path "WeChatFerry/spy/spy.rc" -Pattern 'VALUE "FileVersion", "(.*)"').Matches.Groups[1].Value.Trim()
$wechat_version = (Select-String -Path "WeChatFerry/spy/spy.rc" -Pattern 'VALUE "ProductVersion", "(.*)"').Matches.Groups[1].Value.Trim()
$version = $version_full -replace '(\d+\.\d+\.\d+)\.\d+', '$1'
echo "version=$version" >> $env:GITHUB_ENV
echo "wechat_version=$wechat_version" >> $env:GITHUB_ENV
echo "Program Version: $version"
echo "WeChat Version: $wechat_version"
shell: pwsh
- name: 设置 Visual Studio 2019
uses: microsoft/setup-msbuild@v2
with:
vs-version: "16.0" # 16.x 对应 Visual Studio 2019
- name: 设置 Python 3
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: 安装 Python 依赖项
run: |
python -m pip install --upgrade pip
pip install grpcio-tools==1.48.2
- name: 设置缓存
id: cache-vcpkg
uses: actions/cache@v4
with:
path: |
C:/Tools/vcpkg
${{ github.workspace }}/WeChatFerry/vcpkg_installed
key: vcpkg-${{ hashFiles('WeChatFerry/vcpkg.json') }}
restore-keys: |
vcpkg-
- name: 安装 vcpkg 并初始化依赖项
run: |
# 设置 vcpkg 目录
if (!(Test-Path -Path 'C:/Tools')) {
New-Item -ItemType Directory -Force -Path 'C:/Tools'
}
cd C:/Tools
# 克隆并引导 vcpkg
if (!(Test-Path -Path 'C:/Tools/vcpkg')) {
git clone https://github.com/microsoft/vcpkg
}
.\vcpkg\bootstrap-vcpkg.bat
# 设置 VCPKG_ROOT 环境变量
echo "VCPKG_ROOT=C:/Tools/vcpkg" >> $GITHUB_ENV
$env:VCPKG_ROOT = 'C:/Tools/vcpkg'
# 返回到项目目录并安装依赖
cd ${{ github.workspace }}/WeChatFerry
C:/Tools/vcpkg/vcpkg install --triplet x64-windows-static
# 将 vcpkg 与 Visual Studio 集成
C:/Tools/vcpkg/vcpkg integrate install
- name: 解析并构建配置
run: |
$configurations = "Release,Debug".Split(',')
foreach ($config in $configurations) {
Write-Host "Building configuration: $config"
msbuild WeChatFerry/WeChatFerry.sln `
/p:Configuration=$config `
/p:Platform="x64" `
/p:VcpkgTriplet="x64-windows-static" `
/p:VcpkgEnableManifest=true `
/verbosity:minimal
}
shell: pwsh
- name: 打包输出文件及下载 WeChat 安装包
run: |
New-Item -ItemType Directory -Force -Path "WeChatFerry/tmp"
Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll", "WeChatFerry/Out/DISCLAIMER.md" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
Invoke-WebRequest -Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" -OutFile "WeChatFerry/tmp/WeChatSetup-${{ env.wechat_version }}.exe"
shell: pwsh
- name: 列出预发布文件
run: |
Get-ChildItem -Path "WeChatFerry/tmp" -Recurse
- name: 发布固件到 Github Releases
uses: ncipollo/release-action@main
with:
name: v${{ env.version }}
tag: v${{ env.version }}
token: ${{ secrets.REPO_TOKEN }}
allowUpdates: true
artifacts: "WeChatFerry/tmp/*"
body: |
程序版本:`v${{ env.version }}`
配套微信版本:`${{ env.wechat_version }}`
[📖 Python 文档](https://wechatferry.readthedocs.io/)

86
.github/workflows/build-ci.yml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Build CI
on:
workflow_call:
jobs:
build:
name: 编译校验
runs-on: windows-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置 Visual Studio 2019
uses: microsoft/setup-msbuild@v2
with:
vs-version: "16.0"
- name: 设置 Python 3
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: 安装 Python 依赖项
run: |
python -m pip install --upgrade pip
pip install grpcio-tools==1.48.2
shell: pwsh
- name: 设置 vcpkg 缓存
id: cache-vcpkg
uses: actions/cache@v4
with:
path: |
C:/Tools/vcpkg
${{ github.workspace }}/WeChatFerry/vcpkg_installed
key: vcpkg-${{ hashFiles('WeChatFerry/vcpkg.json') }}
restore-keys: |
vcpkg-
- name: Clone & bootstrap vcpkg (首次或缓存失效时)
if: steps.cache-vcpkg.outputs.cache-hit != 'true'
shell: pwsh
run: |
if (!(Test-Path 'C:/Tools')) { New-Item -ItemType Directory -Force -Path 'C:/Tools' | Out-Null }
cd C:/Tools
git clone --single-branch https://github.com/microsoft/vcpkg vcpkg
$retry = 0
while ($retry -lt 3) {
try { .\vcpkg\bootstrap-vcpkg.bat -disableMetrics ; break }
catch { $retry++; if ($retry -ge 3) { throw }; Write-Host "bootstrap 失败,重试第 $retry 次..." ; Start-Sleep 15 }
}
- name: 设置 VCPKG_ROOT
shell: pwsh
run: |
"VCPKG_ROOT=C:/Tools/vcpkg" | Out-File $Env:GITHUB_ENV -Encoding utf8 -Append
- name: 安装/更新第三方依赖
shell: pwsh
run: |
cd ${{ github.workspace }}/WeChatFerry
C:/Tools/vcpkg/vcpkg install --triplet x64-windows-static
C:/Tools/vcpkg/vcpkg integrate install
- name: 解析并构建 Release/Debug
run: |
$configs = "Release","Debug"
foreach ($cfg in $configs) {
Write-Host "Building $cfg"
msbuild WeChatFerry/WeChatFerry.sln `
/p:Configuration=$cfg `
/p:Platform="x64" `
/p:VcpkgTriplet="x64-windows-static" `
/p:VcpkgEnableManifest=true `
/verbosity:minimal
}
shell: pwsh
- name: 上传
uses: actions/upload-artifact@v4
with:
name: wechatferry-binaries
path: |
WeChatFerry/Out/*.dll
WeChatFerry/Out/*.md

14
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,14 @@
name: CI
on:
pull_request:
branches:
- master
permissions:
contents: read
actions: write
jobs:
build:
uses: ./.github/workflows/build-ci.yml

65
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: Release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
permissions:
contents: write
actions: write
jobs:
build:
uses: ./.github/workflows/build-ci.yml
release:
name: 打包 & 发布
needs: build
runs-on: windows-latest
if: ${{ github.event_name == 'push' }}
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 下载编译产物
uses: actions/download-artifact@v4
with:
name: wechatferry-binaries
path: tmp
- name: 获取版本号和微信版本号
shell: pwsh
run: |
$version_full = (Select-String -Path "WeChatFerry/spy/spy.rc" -Pattern 'VALUE "FileVersion", "(.*)"').Matches.Groups[1].Value.Trim()
$wechat_version = (Select-String -Path "WeChatFerry/spy/spy.rc" -Pattern 'VALUE "ProductVersion", "(.*)"').Matches.Groups[1].Value.Trim()
$version = $version_full -replace '(\d+\.\d+\.\d+)\.\d+', '$1'
echo "version=$version" >> $env:GITHUB_ENV
echo "wechat_version=$wechat_version" >> $env:GITHUB_ENV
echo "Program Version: $version"
echo "WeChat Version: $wechat_version"
- name: 打包输出文件及下载 WeChat 安装包
shell: pwsh
run: |
Compress-Archive `
-Path "tmp/*" `
-DestinationPath "tmp/v${{ env.version }}.zip"
# 下载对应版本微信安装包
Invoke-WebRequest `
-Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" `
-OutFile "tmp/WeChatSetup-${{ env.wechat_version }}.exe"
- name: 发布到 GitHub Releases
uses: ncipollo/release-action@main
with:
name: v${{ env.version }}
tag: v${{ env.version }}
token: ${{ secrets.GITHUB_TOKEN }}
allowUpdates: true
artifacts: "tmp/*"
body: |
程序版本:`v${{ env.version }}`
配套微信版本:`${{ env.wechat_version }}`
[📖 Python 文档](https://wechatferry.readthedocs.io/)

View File

@ -9,7 +9,7 @@
</details>
|[📖 Python 文档](https://wechatferry.readthedocs.io/)|[📺 Python 视频教程](https://mp.weixin.qq.com/s/APdjGyZ2hllXxyG_sNCfXQ)|[🙋 FAQ](https://mp.weixin.qq.com/s/UbzPuw3-2xZLEzUABXMEdw)|
|[📖 Python 文档](https://wechatferry.readthedocs.io/)|[📺 Python 视频教程](https://mp.weixin.qq.com/s/APdjGyZ2hllXxyG_sNCfXQ)|[🙋 FAQ](https://mp.weixin.qq.com/s/c2JggTBlOP8fP9j-MlMAvg)|
|:-:|:-:|:-:|
👉 [WeChatRobot🤖](https://github.com/lich0821/WeChatRobot),一个基于 WeChatFerry 的 Python 机器人示例。
@ -32,7 +32,7 @@
* 发送图片消息
* 发送文件消息
* 发送卡片消息
* 发送 XML
* 发送 XML 消息
* 发送 GIF 消息
* 拍一拍群友
* 转发消息
@ -59,7 +59,7 @@
## 感谢大佬们贡献代码
<a href="https://github.com/lich0821/WeChatFerry/graphs/contributors">![](https://contrib.rocks/image?repo=lich0821/WeChatFerry&columns=8)</a>
<a href="https://github.com/lich0821/WeChatFerry/graphs/contributors">![](https://contrib.rocks/image?repo=lich0821/WeChatFerry&columns=8&anon=1)</a>
## 快速开始
### Python
@ -207,9 +207,8 @@ WeChatFerry
## 版本更新
### v39.4.4
* 实现通发送 XML 功能。
### v39.5.2
* 没有新功能
<details><summary>点击查看更多</summary>
@ -221,6 +220,22 @@ WeChatFerry
* `y` 是 `WeChatFerry` 的版本,从 0 开始
* `z` 是各客户端的版本,从 0 开始
### v39.5.1
* 修复邀请进群偶发失败
* 修复获取 wxid 失败
### v39.5.0
* 适配 `3.9.12.51`。
### v39.4.5
* 修复发送 XML 功能。
### v39.4.4
* 实现发送 XML 功能。
### v39.4.3
* 实现通过好友申请功能。

View File

@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.15)
# Common compiler flags
# C C17
set(CMAKE_C_STANDARD 17)
#
# set(CMAKE_C_STANDARD_REQUIRED ON)
# GNU
set(CMAKE_C_EXTENSIONS OFF)
# C++ C++17
set(CMAKE_CXX_STANDARD 17)
# set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# vcpkg
set(VCPKG_TARGET_TRIPLET "x64-mingw-static" CACHE STRING "Vcpkg target triplet")
set(VCPKG_HOST_TRIPLET "x64-mingw-static" CACHE STRING "Vcpkg host triplet")
set(VCPKG_MANIFEST_MODE ON CACHE BOOL "Enable manifest mode")
if(DEFINED ENV{VCPKG_ROOT})
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
else()
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
endif()
# include(FetchContent)
# include(ExternalProject)
project(WeChatFerry LANGUAGES C CXX)
add_compile_options(
-Wall
-Wextra
-fPIC
)
add_link_options(
-static
)
# Include directories
include_directories(
${CMAKE_SOURCE_DIR}/com
${CMAKE_SOURCE_DIR}/rpc
${CMAKE_SOURCE_DIR}/rpc/nanopb
${CMAKE_SOURCE_DIR}/rpc/proto
${CMAKE_SOURCE_DIR}/sdk
${CMAKE_SOURCE_DIR}/spy
${CMAKE_SOURCE_DIR}/smc
)
# Add subdirectories
add_subdirectory(sdk)
add_subdirectory(spy)

View File

@ -13,6 +13,7 @@
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include "framework.h"
#define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__)
#define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__)

View File

@ -7,7 +7,6 @@
#include <strsafe.h>
#include <wchar.h>
#include "framework.h"
#include <Shlwapi.h>
#include <tlhelp32.h>
@ -64,8 +63,8 @@ static DWORD get_wechat_pid()
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
while (Process32Next(hSnapshot, &pe32)) {
PROCESSENTRY32W pe32 = { sizeof(PROCESSENTRY32W) };
while (Process32NextW(hSnapshot, &pe32)) {
if (pe32.szExeFile == s2w(WECHATEXE)) {
pid = pe32.th32ProcessID;
break;

View File

@ -14,7 +14,7 @@ struct PortPath {
char path[MAX_PATH];
};
DWORD get_wechat_pid();
static DWORD get_wechat_pid();
int open_wechat(DWORD &pid);
std::string get_wechat_version();
uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr);

View File

@ -36,6 +36,7 @@ enum Functions {
FUNC_ADD_ROOM_MEMBERS = 0x70;
FUNC_DEL_ROOM_MEMBERS = 0x71;
FUNC_INV_ROOM_MEMBERS = 0x72;
FUNC_SHUTDOWN = 0xFF;
}
message Request

View File

@ -2,9 +2,11 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: nanopb.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
@ -16,8 +18,52 @@ from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnanopb.proto\x1a google/protobuf/descriptor.proto\"\xa4\x07\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x12\n\nmax_length\x18\x0e \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05\x12&\n\x08int_size\x18\x07 \x01(\x0e\x32\x08.IntSize:\nIS_DEFAULT\x12$\n\x04type\x18\x03 \x01(\x0e\x32\n.FieldType:\nFT_DEFAULT\x12\x18\n\nlong_names\x18\x04 \x01(\x08:\x04true\x12\x1c\n\rpacked_struct\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\x1a\n\x0bpacked_enum\x18\n \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0cskip_message\x18\x06 \x01(\x08:\x05\x66\x61lse\x12\x18\n\tno_unions\x18\x08 \x01(\x08:\x05\x66\x61lse\x12\r\n\x05msgid\x18\t \x01(\r\x12\x1e\n\x0f\x61nonymous_oneof\x18\x0b \x01(\x08:\x05\x66\x61lse\x12\x15\n\x06proto3\x18\x0c \x01(\x08:\x05\x66\x61lse\x12#\n\x14proto3_singular_msgs\x18\x15 \x01(\x08:\x05\x66\x61lse\x12\x1d\n\x0e\x65num_to_string\x18\r \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0c\x66ixed_length\x18\x0f \x01(\x08:\x05\x66\x61lse\x12\x1a\n\x0b\x66ixed_count\x18\x10 \x01(\x08:\x05\x66\x61lse\x12\x1e\n\x0fsubmsg_callback\x18\x16 \x01(\x08:\x05\x66\x61lse\x12/\n\x0cmangle_names\x18\x11 \x01(\x0e\x32\x11.TypenameMangling:\x06M_NONE\x12(\n\x11\x63\x61llback_datatype\x18\x12 \x01(\t:\rpb_callback_t\x12\x34\n\x11\x63\x61llback_function\x18\x13 \x01(\t:\x19pb_default_field_callback\x12\x30\n\x0e\x64\x65scriptorsize\x18\x14 \x01(\x0e\x32\x0f.DescriptorSize:\x07\x44S_AUTO\x12\x1a\n\x0b\x64\x65\x66\x61ult_has\x18\x17 \x01(\x08:\x05\x66\x61lse\x12\x0f\n\x07include\x18\x18 \x03(\t\x12\x0f\n\x07\x65xclude\x18\x1a \x03(\t\x12\x0f\n\x07package\x18\x19 \x01(\t\x12\x41\n\rtype_override\x18\x1b \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.Type\x12\x19\n\x0bsort_by_tag\x18\x1c \x01(\x08:\x04true\x12.\n\rfallback_type\x18\x1d \x01(\x0e\x32\n.FieldType:\x0b\x46T_CALLBACK*i\n\tFieldType\x12\x0e\n\nFT_DEFAULT\x10\x00\x12\x0f\n\x0b\x46T_CALLBACK\x10\x01\x12\x0e\n\nFT_POINTER\x10\x04\x12\r\n\tFT_STATIC\x10\x02\x12\r\n\tFT_IGNORE\x10\x03\x12\r\n\tFT_INLINE\x10\x05*D\n\x07IntSize\x12\x0e\n\nIS_DEFAULT\x10\x00\x12\x08\n\x04IS_8\x10\x08\x12\t\n\x05IS_16\x10\x10\x12\t\n\x05IS_32\x10 \x12\t\n\x05IS_64\x10@*Z\n\x10TypenameMangling\x12\n\n\x06M_NONE\x10\x00\x12\x13\n\x0fM_STRIP_PACKAGE\x10\x01\x12\r\n\tM_FLATTEN\x10\x02\x12\x16\n\x12M_PACKAGE_INITIALS\x10\x03*E\n\x0e\x44\x65scriptorSize\x12\x0b\n\x07\x44S_AUTO\x10\x00\x12\x08\n\x04\x44S_1\x10\x01\x12\x08\n\x04\x44S_2\x10\x02\x12\x08\n\x04\x44S_4\x10\x04\x12\x08\n\x04\x44S_8\x10\x08:E\n\x0enanopb_fileopt\x12\x1c.google.protobuf.FileOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:G\n\rnanopb_msgopt\x12\x1f.google.protobuf.MessageOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:E\n\x0enanopb_enumopt\x12\x1c.google.protobuf.EnumOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptionsB\x1a\n\x18\x66i.kapsi.koti.jpa.nanopb')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'nanopb_pb2', globals())
_FIELDTYPE = DESCRIPTOR.enum_types_by_name['FieldType']
FieldType = enum_type_wrapper.EnumTypeWrapper(_FIELDTYPE)
_INTSIZE = DESCRIPTOR.enum_types_by_name['IntSize']
IntSize = enum_type_wrapper.EnumTypeWrapper(_INTSIZE)
_TYPENAMEMANGLING = DESCRIPTOR.enum_types_by_name['TypenameMangling']
TypenameMangling = enum_type_wrapper.EnumTypeWrapper(_TYPENAMEMANGLING)
_DESCRIPTORSIZE = DESCRIPTOR.enum_types_by_name['DescriptorSize']
DescriptorSize = enum_type_wrapper.EnumTypeWrapper(_DESCRIPTORSIZE)
FT_DEFAULT = 0
FT_CALLBACK = 1
FT_POINTER = 4
FT_STATIC = 2
FT_IGNORE = 3
FT_INLINE = 5
IS_DEFAULT = 0
IS_8 = 8
IS_16 = 16
IS_32 = 32
IS_64 = 64
M_NONE = 0
M_STRIP_PACKAGE = 1
M_FLATTEN = 2
M_PACKAGE_INITIALS = 3
DS_AUTO = 0
DS_1 = 1
DS_2 = 2
DS_4 = 4
DS_8 = 8
NANOPB_FILEOPT_FIELD_NUMBER = 1010
nanopb_fileopt = DESCRIPTOR.extensions_by_name['nanopb_fileopt']
NANOPB_MSGOPT_FIELD_NUMBER = 1010
nanopb_msgopt = DESCRIPTOR.extensions_by_name['nanopb_msgopt']
NANOPB_ENUMOPT_FIELD_NUMBER = 1010
nanopb_enumopt = DESCRIPTOR.extensions_by_name['nanopb_enumopt']
NANOPB_FIELD_NUMBER = 1010
nanopb = DESCRIPTOR.extensions_by_name['nanopb']
_NANOPBOPTIONS = DESCRIPTOR.message_types_by_name['NanoPBOptions']
NanoPBOptions = _reflection.GeneratedProtocolMessageType('NanoPBOptions', (_message.Message,), {
'DESCRIPTOR' : _NANOPBOPTIONS,
'__module__' : 'nanopb_pb2'
# @@protoc_insertion_point(class_scope:NanoPBOptions)
})
_sym_db.RegisterMessage(NanoPBOptions)
if _descriptor._USE_C_DESCRIPTORS == False:
google_dot_protobuf_dot_descriptor__pb2.FileOptions.RegisterExtension(nanopb_fileopt)
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(nanopb_msgopt)

View File

@ -0,0 +1,57 @@
# SDK project - dynamic library
project(SDK LANGUAGES C CXX)
find_package(spdlog REQUIRED)
add_library(sdk SHARED
dllmain.cpp
injector.cpp
injector.h
sdk.cpp
sdk.h
sdk.def
# Common files
${CMAKE_SOURCE_DIR}/com/util.cpp
${CMAKE_SOURCE_DIR}/com/util.h
)
target_link_libraries(sdk PRIVATE
version
shlwapi
spdlog::spdlog
c++
)
# Set compiler definitions
target_compile_definitions(sdk PRIVATE
SDK_EXPORTS
_WINDOWS
_USRDLL
)
# add_compile_options(
# # -Wall
# -fPIC
# # -fms-extensions
# )
# Set output name for debug builds
set_target_properties(sdk PROPERTIES
DEBUG_POSTFIX "d"
)
# # Post-build copy commands
# add_custom_command(TARGET sdk POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E copy
# $<TARGET_FILE:sdk>
# ${CMAKE_SOURCE_DIR}/Out
# COMMAND ${CMAKE_COMMAND} -E copy
# $<TARGET_FILE:sdk>
# ${CMAKE_SOURCE_DIR}/../clients/python/wcferry
# COMMAND ${CMAKE_COMMAND} -E copy
# ${CMAKE_SOURCE_DIR}/DISCLAIMER.md
# ${CMAKE_SOURCE_DIR}/Out
# COMMAND ${CMAKE_COMMAND} -E copy
# ${CMAKE_SOURCE_DIR}/DISCLAIMER.md
# ${CMAKE_SOURCE_DIR}/../clients/python/wcferry
# )

View File

@ -86,7 +86,8 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
<ModuleDefinitionFile>
</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Dev|x64'">
@ -103,7 +104,8 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
<ModuleDefinitionFile>
</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -130,7 +132,8 @@
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>sdk.def</ModuleDefinitionFile>
<ModuleDefinitionFile>
</ModuleDefinitionFile>
</Link>
<PostBuildEvent>
<Command>xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)Out
@ -142,7 +145,7 @@ xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry</Com
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\com\util.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="..\com\framework.h" />
<ClInclude Include="injector.h" />
<ClInclude Include="sdk.h" />
</ItemGroup>
@ -152,9 +155,6 @@ xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry</Com
<ClCompile Include="injector.cpp" />
<ClCompile Include="sdk.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="sdk.def" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

View File

@ -15,7 +15,7 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<ClInclude Include="..\com\framework.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="sdk.h">
@ -42,9 +42,4 @@
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="sdk.def">
<Filter>源文件</Filter>
</None>
</ItemGroup>
</Project>

View File

@ -122,7 +122,7 @@ static uint64_t get_func_offset(const string &dll_path, const string &func_name)
return 0;
}
LPVOID absAddr = GetProcAddress(dll, func_name.c_str());
LPVOID absAddr = reinterpret_cast<LPVOID>(GetProcAddress(dll, func_name.c_str()));
uint64_t offset = reinterpret_cast<uint64_t>(absAddr) - reinterpret_cast<uint64_t>(dll);
FreeLibrary(dll);

View File

@ -21,9 +21,16 @@ static HANDLE wcProcess = NULL;
static HMODULE spyBase = NULL;
static std::string spyDllPath;
//区分MSVC和MinGW
#ifdef _MSC_VER
constexpr char WCFSDKDLL[] = "sdk.dll";
constexpr char WCFSPYDLL[] = "spy.dll";
constexpr char WCFSPYDLL_DEBUG[] = "spy_debug.dll";
#else
constexpr char WCFSDKDLL[] = "libsdk.dll";
constexpr char WCFSPYDLL[] = "libspy.dll";
constexpr char WCFSPYDLL_DEBUG[] = "libspyd.dll";
#endif
constexpr std::string_view DISCLAIMER_FLAG = ".license_accepted.flag";
constexpr std::string_view DISCLAIMER_TEXT_FILE = "DISCLAIMER.md";
@ -91,8 +98,8 @@ static std::string get_dll_path(bool debug)
return path.string();
}
int WxInitSDK(bool debug, int port)
extern "C" {
__declspec(dllexport) int WxInitSDK(bool debug, int port)
{
if (!show_disclaimer()) {
exit(-1); // 用户拒绝协议,退出程序
@ -134,7 +141,7 @@ int WxInitSDK(bool debug, int port)
return status;
}
int WxDestroySDK()
__declspec(dllexport) int WxDestroySDK()
{
if (!injected) {
return 1; // 未注入
@ -151,3 +158,4 @@ int WxDestroySDK()
return 0;
}
}

View File

@ -1,3 +0,0 @@
EXPORTS
WxInitSDK
WxDestroySDK

View File

@ -1,4 +1,6 @@
#pragma once
int WxInitSDK(bool debug, int port);
int WxDestroySDK();
extern "C" {
__declspec(dllexport) int WxInitSDK(bool debug, int port);
__declspec(dllexport) int WxDestroySDK();
}

BIN
WeChatFerry/smc/libCodec.a Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,108 @@
# Spy project - dynamic library
project(Spy LANGUAGES C CXX)
find_package(spdlog REQUIRED)
find_package(magic_enum REQUIRED)
find_package(minhook CONFIG REQUIRED)
find_package(nng REQUIRED)
add_library(spy SHARED
chatroom_manager.cpp
chatroom_manager.h
misc_manager.cpp
misc_manager.h
database_executor.cpp
database_executor.h
contact_manager.cpp
contact_manager.h
message_handler.cpp
message_handler.h
rpc_server.cpp
rpc_server.h
message_sender.cpp
message_sender.h
spy.cpp
spy.h
spy_types.h
account_manager.cpp
account_manager.h
resource.h
rpc_helper.h
sqlite3.h
dllmain.cpp
spy.def
# Common files
${CMAKE_SOURCE_DIR}/com/util.cpp
${CMAKE_SOURCE_DIR}/com/util.h
${CMAKE_SOURCE_DIR}/com/log.hpp
# RPC files
${CMAKE_SOURCE_DIR}/rpc/pb_util.cpp
${CMAKE_SOURCE_DIR}/rpc/pb_util.h
${CMAKE_SOURCE_DIR}/rpc/pb_types.h
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb.h
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb_common.h
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb_decode.h
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb_encode.h
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb_common.c
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb_decode.c
${CMAKE_SOURCE_DIR}/rpc/nanopb/pb_encode.c
${CMAKE_SOURCE_DIR}/rpc/proto/wcf.pb.c
${CMAKE_SOURCE_DIR}/rpc/proto/wcf.pb.h
)
# link directories
target_link_directories(spy PRIVATE
${CMAKE_SOURCE_DIR}/smc
)
# Link dependencies
target_link_libraries(spy PRIVATE
Codec
mp3lame
version
shlwapi
iphlpapi
wsock32
ws2_32
crypt32
magic_enum::magic_enum
nng::nng
spdlog::spdlog
minhook::minhook
c++
)
add_custom_command(
OUTPUT
${CMAKE_SOURCE_DIR}/rpc/proto/wcf.pb.c
${CMAKE_SOURCE_DIR}/rpc/proto/wcf.pb.h
COMMAND ${CMAKE_SOURCE_DIR}/rpc/tool/protoc --nanopb_out=${CMAKE_SOURCE_DIR}/rpc/proto wcf.proto
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/rpc/proto
DEPENDS ${CMAKE_SOURCE_DIR}/rpc/proto/wcf.proto
COMMENT "Generated protobuf files"
)
# Add generated files as dependencies
add_custom_target(protobuf_generation
DEPENDS
${CMAKE_SOURCE_DIR}/rpc/proto/wcf.pb.c
${CMAKE_SOURCE_DIR}/rpc/proto/wcf.pb.h
)
add_dependencies(spy protobuf_generation)
# Set output name for debug builds
set_target_properties(spy PROPERTIES
DEBUG_POSTFIX "d"
)
# Post-build copy commands
# add_custom_command(TARGET spy POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E copy
# $<TARGET_FILE:spy>
# ${CMAKE_SOURCE_DIR}/Out
# COMMAND ${CMAKE_COMMAND} -E copy
# $<TARGET_FILE:spy>
# ${CMAKE_SOURCE_DIR}/../clients/python/wcferry
# )

View File

@ -105,7 +105,8 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>spy.def</ModuleDefinitionFile>
<ModuleDefinitionFile>
</ModuleDefinitionFile>
<AdditionalOptions> /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
<AdditionalLibraryDirectories>$(SolutionDir)smc;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>iphlpapi.lib;wsock32.lib;ws2_32.lib;crypt32.lib;Codec.lib;%(AdditionalDependencies)</AdditionalDependencies>
@ -158,7 +159,8 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry</Co
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>spy.def</ModuleDefinitionFile>
<ModuleDefinitionFile>
</ModuleDefinitionFile>
<AdditionalOptions> /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
<AdditionalLibraryDirectories>$(SolutionDir)smc;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>iphlpapi.lib;wsock32.lib;ws2_32.lib;crypt32.lib;Codec.lib;%(AdditionalDependencies)</AdditionalDependencies>
@ -212,7 +214,8 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry</Co
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>spy.def</ModuleDefinitionFile>
<ModuleDefinitionFile>
</ModuleDefinitionFile>
<AdditionalLibraryDirectories>$(SolutionDir)smc;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>iphlpapi.lib;wsock32.lib;ws2_32.lib;crypt32.lib;Codec.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalOptions> /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
@ -250,7 +253,7 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry</Co
<ClInclude Include="chatroom_manager.h" />
<ClInclude Include="misc_manager.h" />
<ClInclude Include="database_executor.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="..\com\framework.h" />
<ClInclude Include="contact_manager.h" />
<ClInclude Include="message_handler.h" />
<ClInclude Include="resource.h" />
@ -282,7 +285,6 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry</Co
</ItemGroup>
<ItemGroup>
<None Include="..\rpc\proto\wcf.proto" />
<None Include="spy.def" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="spy.rc" />

View File

@ -18,7 +18,7 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<ClInclude Include="..\com\framework.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="rpc_server.h">
@ -142,9 +142,6 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="spy.def">
<Filter>源文件</Filter>
</None>
<None Include="..\rpc\proto\wcf.proto">
<Filter>nnrpc</Filter>
</None>

View File

@ -17,14 +17,6 @@ namespace OsAcc = Offsets::Account;
using get_account_service_t = QWORD (*)();
using get_data_path_t = QWORD (*)(QWORD);
// 缓存避免重复查询
static std::optional<std::string> cachedWxid;
static std::optional<fs::path> cachedHomePath;
// 清除缓存
static void clear_cached_wxid() { cachedWxid.reset(); }
static void clear_cached_home_path() { cachedHomePath.reset(); }
static uint64_t get_account_service()
{
static auto GetService = Spy::getFunction<get_account_service_t>(OsAcc::SERVICE);
@ -39,37 +31,44 @@ static std::string get_string_value(uint64_t base_addr, uint64_t offset)
bool is_logged_in()
{
clear_cached_wxid();
clear_cached_home_path();
uint64_t service_addr = get_account_service();
return service_addr && util::get_qword(service_addr + OsAcc::LOGIN) != 0;
}
fs::path get_home_path()
{
if (cachedHomePath) {
return *cachedHomePath;
}
WxString home;
auto GetDataPath = Spy::getFunction<get_data_path_t>(OsAcc::PATH);
int64_t service_addr = get_account_service();
GetDataPath((QWORD)&home);
if (home.wptr) {
cachedHomePath = util::w2s(std::wstring(home.wptr, home.size));
}
return *cachedHomePath;
static fs::path home_path;
static std::once_flag home_once;
std::call_once(home_once, []() {
WxString home {};
if (auto getDataPath = Spy::getFunction<get_data_path_t>(OsAcc::PATH)) {
getDataPath(reinterpret_cast<QWORD>(&home));
if (home.wptr) {
std::wstring wstr(home.wptr, home.size);
home_path = util::w2s(std::move(wstr));
}
}
});
return home_path;
}
std::string get_self_wxid()
{
if (cachedWxid) {
return *cachedWxid;
}
uint64_t service_addr = get_account_service();
if (!service_addr) return "";
static std::string cached_wxid;
static std::once_flag wxid_once;
cachedWxid = get_string_value(service_addr, OsAcc::WXID);
return *cachedWxid;
std::call_once(wxid_once, []() {
if (uint64_t svc = get_account_service(); svc) {
cached_wxid = get_string_value(svc, OsAcc::WXID);
if (cached_wxid.empty()) {
cached_wxid = get_string_value(svc, OsAcc::ALIAS);
}
}
});
return cached_wxid;
}
UserInfo_t get_user_info()

View File

@ -12,10 +12,11 @@ namespace chatroom
{
namespace OsRoom = Offsets::Chatroom;
using new_t = QWORD (*)(QWORD, WxString *);
using get_mgr_t = QWORD (*)();
using add_member_t = QWORD (*)(QWORD, QWORD, WxString *, QWORD);
using delete_member_t = QWORD (*)(QWORD, QWORD, WxString *);
using invite_members_t = QWORD (*)(const wchar_t *, QWORD, WxString *, QWORD);
using invite_members_t = QWORD (*)(const wchar_t *, QWORD, QWORD, QWORD);
template <auto FillFunc, typename Func>
bool rpc_chatroom_common(const MemberMgmt &m, uint8_t *out, size_t *len, Func func)
@ -36,9 +37,11 @@ int add_chatroom_member(const string &roomid, const string &wxids)
WxString *wx_roomid = util::CreateWxString(roomid);
QWORD tmp[2] = { 0 };
auto wx_members = util::parse_wxids(wxids).wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
QWORD tmp[2] = { 0 };
auto split = util::parse_wxids(wxids);
auto &wx_members = split.wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
return static_cast<int>(add_members(get_chatroom_mgr(), p_members, wx_roomid, reinterpret_cast<QWORD>(tmp)));
}
@ -49,24 +52,32 @@ int del_chatroom_member(const string &roomid, const string &wxids)
auto del_members = Spy::getFunction<delete_member_t>(OsRoom::DEL);
WxString *wx_roomid = util::CreateWxString(roomid);
auto wx_members = util::parse_wxids(wxids).wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
auto split = util::parse_wxids(wxids);
auto &wx_members = split.wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
return static_cast<int>(del_members(get_chatroom_mgr(), p_members, wx_roomid));
}
int invite_chatroom_member(const string &roomid, const string &wxids)
{
auto init_roomid = Spy::getFunction<new_t>(OsRoom::NEW);
auto invite_members = Spy::getFunction<invite_members_t>(OsRoom::INV);
wstring ws_roomid = util::s2w(roomid);
WxString *wx_roomid = util::CreateWxString(roomid);
wstring ws_roomid = util::s2w(roomid);
WxString wx_roomid(ws_roomid);
QWORD tmp[2] = { 0 };
auto wx_members = util::parse_wxids(wxids).wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
QWORD tmp[2] = { 0 };
QWORD array[4] = { 0 };
return static_cast<int>(invite_members(ws_roomid.c_str(), p_members, wx_roomid, reinterpret_cast<QWORD>(tmp)));
auto split = util::parse_wxids(wxids);
auto &wx_members = split.wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
QWORD p_roomid = init_roomid(reinterpret_cast<QWORD>(&array), &wx_roomid);
LOG_BUFFER((uint8_t *)*(QWORD *)(*(QWORD *)p_members), 40);
return static_cast<int>(invite_members(ws_roomid.c_str(), p_members, p_roomid, reinterpret_cast<QWORD>(tmp)));
}
bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)

View File

@ -1,114 +0,0 @@
#include "framework.h"
#include <sstream>
#include <vector>
#include "chatroom_mgmt.h"
#include "log.h"
#include "util.h"
using namespace std;
extern QWORD g_WeChatWinDllAddr;
#define OS_GET_CHATROOM_MGR 0x1B894E0
#define OS_ADD_MEMBERS 0x215A820
#define OS_DELETE_MEMBERS 0x215AE60
#define OS_INVITE_MEMBERS 0x215A200
typedef QWORD (*GetChatRoomMgr_t)();
typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
typedef QWORD (*DelMemberFromChatRoom_t)(QWORD, QWORD, QWORD);
typedef QWORD (*InviteMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
int AddChatroomMember(string roomid, string wxids)
{
int status = -1;
if (roomid.empty() || wxids.empty()) {
LOG_ERROR("Empty roomid or wxids.");
return status;
}
GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR);
AddMemberToChatRoom_t AddMembers = (AddMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_ADD_MEMBERS);
vector<wstring> vMembers;
vector<WxString> vWxMembers;
wstringstream wss(String2Wstring(wxids));
while (wss.good()) {
wstring wstr;
getline(wss, wstr, L',');
vMembers.push_back(wstr);
WxString wxMember(vMembers.back());
vWxMembers.push_back(wxMember);
}
QWORD temp[2] = { 0 };
WxString *pWxRoomid = NewWxStringFromStr(roomid);
QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start;
QWORD mgr = GetChatRoomMgr();
status = (int)AddMembers(mgr, pMembers, (QWORD)pWxRoomid, (QWORD)temp);
return status;
}
int DelChatroomMember(string roomid, string wxids)
{
int status = -1;
if (roomid.empty() || wxids.empty()) {
LOG_ERROR("Empty roomid or wxids.");
return status;
}
GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR);
DelMemberFromChatRoom_t DelMembers = (DelMemberFromChatRoom_t)(g_WeChatWinDllAddr + OS_DELETE_MEMBERS);
vector<wstring> vMembers;
vector<WxString> vWxMembers;
wstringstream wss(String2Wstring(wxids));
while (wss.good()) {
wstring wstr;
getline(wss, wstr, L',');
vMembers.push_back(wstr);
WxString wxMember(vMembers.back());
vWxMembers.push_back(wxMember);
}
WxString *pWxRoomid = NewWxStringFromStr(roomid);
QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start;
QWORD mgr = GetChatRoomMgr();
status = (int)DelMembers(mgr, pMembers, (QWORD)pWxRoomid);
return status;
}
int InviteChatroomMember(string roomid, string wxids)
{
int status = -1;
if (roomid.empty() || wxids.empty()) {
LOG_ERROR("Empty roomid or wxids.");
return status;
}
InviteMemberToChatRoom_t InviteMembers = (InviteMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_INVITE_MEMBERS);
vector<wstring> vMembers;
vector<WxString> vWxMembers;
wstringstream wss(String2Wstring(wxids));
while (wss.good()) {
wstring wstr;
getline(wss, wstr, L',');
vMembers.push_back(wstr);
WxString wxMember(vMembers.back());
vWxMembers.push_back(wxMember);
}
QWORD temp[2] = { 0 };
wstring wsRoomid = String2Wstring(roomid);
WxString *pWxRoomid = NewWxStringFromWstr(wsRoomid);
QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start;
status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp);
return status;
}

View File

@ -18,7 +18,7 @@ namespace OsCon = Offsets::Contact;
using get_contact_mgr_t = QWORD (*)();
using get_contact_list_t = QWORD (*)(QWORD, QWORD);
using func_verify_new_t = QWORD (*)(QWORD, WxString *);
using func_verify_ok_t = QWORD (*)(QWORD, WxString *, QWORD *, QWORD, QWORD, QWORD *, WxString *, QWORD *, WxString *);
using func_verify_ok_t = QWORD (*)(QWORD, WxString *, QWORD *, QWORD, QWORD, QWORD, QWORD, QWORD, WxString *);
#define FEAT_LEN 5
static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 };
@ -92,26 +92,24 @@ vector<RpcContact_t> get_contacts()
int accept_new_friend(const std::string &v3, const std::string &v4, int scene)
{
// TODO: 处理来源、备注、标签等
// TODO: 备注、标签等
auto func_new = Spy::getFunction<func_verify_new_t>(OsCon::VERIFY_NEW);
auto func_verify = Spy::getFunction<func_verify_ok_t>(OsCon::VERIFY_OK);
QWORD helper = util::get_qword(Spy::WeChatDll.load() + OsCon::ADD_FRIEND_HELPER);
QWORD fvdf = util::get_qword(Spy::WeChatDll.load() + OsCon::FVDF);
QWORD mgr = util::get_qword(Spy::WeChatDll.load() + OsCon::VERIFY_MGR);
QWORD a8 = util::get_qword(Spy::WeChatDll.load() + OsCon::VERIFY_A8);
auto pV3 = util::CreateWxString(v3);
auto pV4 = util::CreateWxString(v4);
QWORD v4Array[4] = { 0 };
QWORD p_v4Buff = func_new(reinterpret_cast<QWORD>(&v4Array), pV4);
QWORD pV4Buff = func_new(reinterpret_cast<QWORD>(&v4Array), pV4);
char buff[0x100] = { 0 };
memcpy(buff, &helper, sizeof(&helper));
QWORD a1 = reinterpret_cast<QWORD>(&buff);
QWORD ret = func_verify(a1, pV3, &fvdf, 0x3A08A4, p_v4Buff, &mgr, pV4, &a8, pV4);
QWORD ret = func_verify(a1, pV3, &fvdf, 0x1D08B4, pV4Buff, 0x1, pV4Buff, scene, 0x0);
util::FreeWxString(pV3);
util::FreeWxString(pV4);

View File

@ -1,5 +0,0 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>

View File

@ -5,8 +5,6 @@
#include <mutex>
#include <queue>
#include "framework.h"
#include "account_manager.h"
#include "log.hpp"
#include "offsets.h"
@ -192,8 +190,8 @@ int Handler::EnableLog()
funcWxLog = Spy::getFunction<funcWxLog_t>(OsLog::CALL);
if (InitializeHook() != MH_OK) return -1;
if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast<LPVOID *>(&realWxLog)) != MH_OK) return -2;
if (MH_EnableHook(funcWxLog) != MH_OK) return -3;
if (MH_CreateHook(reinterpret_cast<LPVOID>(funcWxLog), reinterpret_cast<LPVOID>(&PrintWxLog), reinterpret_cast<LPVOID *>(&realWxLog)) != MH_OK) return -2;
if (MH_EnableHook(reinterpret_cast<LPVOID>(funcWxLog)) != MH_OK) return -3;
*pLogLevel = 0;
isLogging = true;
@ -203,8 +201,9 @@ int Handler::EnableLog()
int Handler::DisableLog()
{
if (!isLogging) return 1;
if (MH_DisableHook(funcWxLog) != MH_OK) return -1;
if (UninitializeHook() != MH_OK) return -2;
if (MH_DisableHook(reinterpret_cast<LPVOID>(funcWxLog)) != MH_OK) return -1;
if (MH_RemoveHook(reinterpret_cast<LPVOID>(funcWxLog)) != MH_OK) return -2;
if (UninitializeHook() != MH_OK) return -3;
*pLogLevel = 6;
isLogging = false;
return 0;
@ -216,8 +215,8 @@ int Handler::ListenMsg()
funcRecvMsg = Spy::getFunction<funcRecvMsg_t>(OsRecv::CALL);
if (InitializeHook() != MH_OK) return -1;
if (MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast<LPVOID *>(&realRecvMsg)) != MH_OK) return -1;
if (MH_EnableHook(funcRecvMsg) != MH_OK) return -1;
if (MH_CreateHook(reinterpret_cast<LPVOID>(funcRecvMsg), reinterpret_cast<LPVOID>(&DispatchMsg), reinterpret_cast<LPVOID *>(&realRecvMsg)) != MH_OK) return -2;
if (MH_EnableHook(reinterpret_cast<LPVOID>(funcRecvMsg)) != MH_OK) return -3;
isListeningMsg = true;
return 0;
@ -226,8 +225,9 @@ int Handler::ListenMsg()
int Handler::UnListenMsg()
{
if (!isListeningMsg) return 1;
if (MH_DisableHook(funcRecvMsg) != MH_OK) return -1;
if (UninitializeHook() != MH_OK) return -1;
if (MH_DisableHook(reinterpret_cast<LPVOID>(funcRecvMsg)) != MH_OK) return -1;
if (MH_RemoveHook(reinterpret_cast<LPVOID>(funcRecvMsg)) != MH_OK) return -2;
if (UninitializeHook() != MH_OK) return -3;
isListeningMsg = false;
return 0;
}
@ -238,8 +238,8 @@ int Handler::ListenPyq()
funcRecvPyq = Spy::getFunction<funcRecvPyq_t>(OsRecv::PYQ_CALL);
if (InitializeHook() != MH_OK) return -1;
if (MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast<LPVOID *>(&realRecvPyq)) != MH_OK) return -1;
if (MH_EnableHook(funcRecvPyq) != MH_OK) return -1;
if (MH_CreateHook(reinterpret_cast<LPVOID>(funcRecvPyq), reinterpret_cast<LPVOID>(&DispatchPyq), reinterpret_cast<LPVOID *>(&realRecvPyq)) != MH_OK) return -1;
if (MH_EnableHook(reinterpret_cast<LPVOID>(funcRecvPyq)) != MH_OK) return -1;
isListeningPyq = true;
return 0;
@ -248,8 +248,9 @@ int Handler::ListenPyq()
int Handler::UnListenPyq()
{
if (!isListeningPyq) return 1;
if (MH_DisableHook(funcRecvPyq) != MH_OK) return -1;
if (UninitializeHook() != MH_OK) return -1;
if (MH_DisableHook(reinterpret_cast<LPVOID>(funcRecvPyq)) != MH_OK) return -1;
if (MH_RemoveHook(reinterpret_cast<LPVOID>(funcRecvPyq)) != MH_OK) return -2;
if (UninitializeHook() != MH_OK) return -3;
isListeningPyq = false;
return 0;
}

View File

@ -313,8 +313,8 @@ bool Sender::rpc_send_xml(const XmlMsg &xml, uint8_t *out, size_t *len)
LOG_ERROR("Empty content or receiver.");
rsp.msg.status = -1;
} else {
// send_xml(xml.receiver, xml.content, xml.path, xml.type);
rsp.msg.status = -1;
send_xml(xml.receiver, xml.content, xml.path, xml.type);
rsp.msg.status = 0;
}
});
}

View File

@ -4,14 +4,13 @@
#include <filesystem>
#include <fstream>
#include "framework.h"
#include "codec.h"
#include "database_executor.h"
#include "log.hpp"
#include "message_handler.h"
#include "offsets.h"
#include "rpc_helper.h"
#include "rpc_server.h"
#include "spy.h"
#include "spy_types.h"
#include "util.h"
@ -418,4 +417,17 @@ bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len)
return fill_response<Functions_FUNC_RECV_TRANSFER>(
out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); });
}
bool rpc_shutdown(uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_SHUTDOWN>(out, len, [&](Response &rsp) {
rsp.msg.status = 0;
std::thread([]() {
Sleep(100);
RpcServer::destroyInstance();
Spy::Cleanup();
}).detach();
return true;
});
}
} // namespace misc

View File

@ -36,5 +36,6 @@ bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len);
bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len);
bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len);
bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len);
bool rpc_shutdown(uint8_t *out, size_t *len);
// clang-format on
} // namespace misc

View File

@ -7,8 +7,8 @@ namespace Offsets
namespace Account
{
constexpr uint64_t SERVICE = 0x1B58B50; // 账户服务
constexpr uint64_t PATH = 0x25E9090; // 数据路径
constexpr uint64_t SERVICE = 0x1B5CA40; // 账户服务
constexpr uint64_t PATH = 0x25F4A40; // 数据路径
constexpr uint64_t WXID = 0x80; // WXID
constexpr uint64_t NAME = 0x1E8; // 昵称
constexpr uint64_t MOBILE = 0x128; // 手机号
@ -18,16 +18,17 @@ namespace Account
namespace Chatroom
{
constexpr uint64_t MGR = 0x1B86F60;
constexpr uint64_t DEL = 0x2158830;
constexpr uint64_t ADD = 0x21581F0;
constexpr uint64_t INV = 0x2157BD0;
constexpr uint64_t MGR = 0x1B8AE40;
constexpr uint64_t NEW = 0x262D800;
constexpr uint64_t DEL = 0x2163070;
constexpr uint64_t ADD = 0x2162A30;
constexpr uint64_t INV = 0x2162410;
}
namespace Contact
{
constexpr uint64_t MGR = 0x1B44B20;
constexpr uint64_t LIST = 0x21A1E00;
constexpr uint64_t MGR = 0x1B489D0;
constexpr uint64_t LIST = 0x21ACBE0;
constexpr uint64_t BIN = 0x200;
constexpr uint64_t BIN_LEN = 0x208;
constexpr uint64_t WXID = 0x10;
@ -37,18 +38,16 @@ namespace Contact
constexpr uint64_t GENDER = 0x0E;
constexpr uint64_t STEP = 0x6A8;
constexpr uint64_t VERIFY_NEW = 0x2621B00;
constexpr uint64_t VERIFY_OK = 0x1F421E0;
constexpr uint64_t VERIFY_MGR = 0x4F022A8;
constexpr uint64_t VERIFY_A8 = 0x2621B91;
constexpr uint64_t ADD_FRIEND_HELPER = 0x4EE4A20;
constexpr uint64_t FVDF = 0x4F02768; // FriendVeriyDialogFragment
constexpr uint64_t VERIFY_NEW = Chatroom::NEW;
constexpr uint64_t VERIFY_OK = 0x1F48850;
constexpr uint64_t ADD_FRIEND_HELPER = 0x4F7FB18; // a1
constexpr uint64_t FVDF = 0x4F9DE28; // FriendVeriyDialogFragment
}
namespace Db
{
constexpr uint64_t INSTANCE = 0x59226C8; // 数据库实例地址
constexpr uint64_t MSG_I = 0x5980420; // MSGi.db & MediaMsgi.db
constexpr uint64_t INSTANCE = 0x59D2008; // 数据库实例地址
constexpr uint64_t MSG_I = 0x5A30158; // MSGi.db & MediaMsgi.db
constexpr uint64_t MICROMSG = 0xB8;
constexpr uint64_t CHAT_MSG = 0x2C8;
constexpr uint64_t MISC = 0x5F0;
@ -59,7 +58,7 @@ namespace Db
constexpr uint64_t NAME = 0x28;
// SQLITE3
constexpr uint64_t EXEC = 0x3A76430;
constexpr uint64_t EXEC = 0x3A820A0;
// constexpr uint64_t BACKUP_INIT = EXEC - 0x1D113E0;
constexpr uint64_t PREPARE = EXEC + 0x7CB0;
// constexpr uint64_t OPEN = EXEC - 0x1CA2430;
@ -83,13 +82,13 @@ namespace Message
{
namespace Log
{
constexpr uint64_t LEVEL = 0x56E4244; // 日志级别
constexpr uint64_t CALL = 0x261B890; // 日志函数
constexpr uint64_t LEVEL = 0x578DF28; // 日志级别
constexpr uint64_t CALL = 0x2627590; // 日志函数
}
namespace Receive
{
constexpr uint64_t CALL = 0x2141E80; // 接收消息 Call
constexpr uint64_t CALL = 0x214C6C0; // 接收消息 Call
constexpr uint64_t ID = 0x30; // 消息 ID
constexpr uint64_t TYPE = 0x38; // 消息类型
constexpr uint64_t SELF = 0x3C; // 消息是否来自自己
@ -102,7 +101,7 @@ namespace Message
constexpr uint64_t EXTRA = 0x2A0; // 原图路径
constexpr uint64_t XML = 0x308; // 消息 XML
constexpr uint64_t PYQ_CALL = 0x2E56080; // 接收朋友圈 Call
constexpr uint64_t PYQ_CALL = 0x2E621D0; // 接收朋友圈 Call
constexpr uint64_t PYQ_START = 0x30; // 开始地址
constexpr uint64_t PYQ_END = 0x38; // 结束地址
constexpr uint64_t PYQ_SENDER = 0x18; // 发布者
@ -113,45 +112,45 @@ namespace Message
namespace Send
{
constexpr uint64_t MGR = 0x1B57350;
constexpr uint64_t INSTANCE = 0x1B614C0;
constexpr uint64_t FREE = 0x1B58BD0;
constexpr uint64_t TEXT = 0x22C9CA0;
constexpr uint64_t IMAGE = 0x22BF430;
constexpr uint64_t APP_MGR = 0x1B5C2F0;
constexpr uint64_t FILE = 0x20D30E0;
constexpr uint64_t XML = 0x20D2210;
constexpr uint64_t XML_BUF_SIGN = 0x24F95C0;
constexpr uint64_t EMOTION_MGR = 0x1BD2310;
constexpr uint64_t EMOTION = 0x21B8100;
constexpr uint64_t MGR = 0x1B5B210;
constexpr uint64_t INSTANCE = 0x1B653B0;
constexpr uint64_t FREE = 0x1B5CAC0;
constexpr uint64_t TEXT = 0x22D4A90;
constexpr uint64_t IMAGE = 0x22CA2A0;
constexpr uint64_t APP_MGR = 0x1B601E0;
constexpr uint64_t FILE = 0x20DE200;
constexpr uint64_t XML = 0x20DD330;
constexpr uint64_t XML_BUF_SIGN = 0x2503760;
constexpr uint64_t EMOTION_MGR = 0x1BD6300;
constexpr uint64_t EMOTION = 0x21C2EE0;
constexpr uint64_t NEW_MM_READER = 0x1B60A10;
constexpr uint64_t FREE_MM_READER = 0x1B5FDE0;
constexpr uint64_t RICH_TEXT = 0x20DD0C0;
constexpr uint64_t NEW_MM_READER = 0x1B64900;
constexpr uint64_t FREE_MM_READER = 0x1B63CD0;
constexpr uint64_t RICH_TEXT = 0x20E81E0;
constexpr uint64_t PAT = 0x2CC1E90;
constexpr uint64_t PAT = 0x2CCDDC0;
constexpr uint64_t FORWARD = 0x22C9220;
constexpr uint64_t FORWARD = 0x22D4010;
}
}
namespace Misc
{
constexpr uint64_t QR_CODE = 0x2025A80;
constexpr uint64_t QR_CODE = 0x202D3C0;
constexpr uint64_t INSATNCE = Message::Send::INSTANCE;
constexpr uint64_t FREE = Message::Send::FREE;
constexpr uint64_t CHAT_MGR = 0x1B8AA50;
constexpr uint64_t PRE_LOCAL_ID_MGR = 0x2142BF0;
constexpr uint64_t PRE_DOWNLOAD_MGR = 0x1C12260;
constexpr uint64_t PUSH_ATTACH_TASK = 0x1CE3050;
constexpr uint64_t CHAT_MGR = 0x1B8E930;
constexpr uint64_t PRE_LOCAL_ID_MGR = 0x214D430;
constexpr uint64_t PRE_DOWNLOAD_MGR = 0x1C17660;
constexpr uint64_t PUSH_ATTACH_TASK = 0x1CE8500;
namespace Sns
{
constexpr uint64_t DATA_MGR = 0x21E52F0;
constexpr uint64_t TIMELINE = 0x2DC6180;
constexpr uint64_t FIRST = 0x2E346C0;
constexpr uint64_t NEXT = 0x2E5A270;
constexpr uint64_t DATA_MGR = 0x21F00D0;
constexpr uint64_t TIMELINE = 0x2DD2320;
constexpr uint64_t FIRST = 0x2E40810;
constexpr uint64_t NEXT = 0x2E663C0;
}
}
}

View File

@ -2,6 +2,8 @@
#include <unordered_map>
#define MAGIC_ENUM_RANGE_MIN 0
#define MAGIC_ENUM_RANGE_MAX 256
#include <magic_enum/magic_enum.hpp>
#include "wcf.pb.h"
@ -41,7 +43,8 @@ static const std::unordered_map<Functions, int> rpc_tag_map
{ Functions_FUNC_EXEC_OCR, Response_ocr_tag },
{ Functions_FUNC_ADD_ROOM_MEMBERS, Response_status_tag },
{ Functions_FUNC_DEL_ROOM_MEMBERS, Response_status_tag },
{ Functions_FUNC_INV_ROOM_MEMBERS, Response_status_tag } };
{ Functions_FUNC_INV_ROOM_MEMBERS, Response_status_tag },
{ Functions_FUNC_SHUTDOWN, Response_status_tag } };
template <Functions FuncType, typename AssignFunc> bool fill_response(uint8_t *out, size_t *len, AssignFunc assign)
{

View File

@ -13,7 +13,6 @@
#include <string>
#include <thread>
#include <magic_enum/magic_enum.hpp>
#include <nng/protocol/pair1/pair.h>
#include <nng/supplemental/util/platform.h>
@ -214,7 +213,7 @@ bool RpcServer::start_message_listener(bool pyq, uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_ENABLE_RECV_TXT>(out, len, [&](Response &rsp) {
rsp.msg.status = handler_.ListenMsg();
if (rsp.msg.status == 0) {
if (rsp.msg.status >= 0) {
if (pyq) {
handler_.ListenPyq();
}
@ -227,7 +226,7 @@ bool RpcServer::stop_message_listener(uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_DISABLE_RECV_TXT>(out, len, [&](Response &rsp) {
rsp.msg.status = handler_.UnListenMsg();
if (rsp.msg.status == 0) {
if (rsp.msg.status >= 0) {
handler_.UnListenPyq();
if (msgThread_.joinable()) {
msgThread_.join();
@ -269,6 +268,7 @@ const std::unordered_map<Functions, RpcServer::FunctionHandler> RpcServer::rpcFu
{ Functions_FUNC_ADD_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_add_chatroom_member(r.msg.m, out, len); } },
{ Functions_FUNC_DEL_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_delete_chatroom_member(r.msg.m, out, len); } },
{ Functions_FUNC_INV_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_invite_chatroom_member(r.msg.m, out, len); } },
{ Functions_FUNC_SHUTDOWN, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_shutdown(out, len); }}
// clang-format on
};

View File

@ -14,7 +14,7 @@ int Init(void *args)
auto *pp = static_cast<util::PortPath *>(args);
Log::InitLogger(pp->path);
if (auto dll_addr = GetModuleHandle(L"WeChatWin.dll")) {
if (auto dll_addr = GetModuleHandleW(L"WeChatWin.dll")) {
WeChatDll.store(reinterpret_cast<uint64_t>(dll_addr));
} else {
LOG_ERROR("获取 WeChatWin.dll 模块地址失败");
@ -44,5 +44,5 @@ void Cleanup()
extern "C" {
__declspec(dllexport) int InitSpy(void *args) { return Spy::Init(args); }
__declspec(dllexport) void CleanupSpy() { Spy::Cleanup(); }
__declspec(dllexport) int CleanupSpy() { Spy::Cleanup(); return 0;}
}

View File

@ -1,3 +0,0 @@
EXPORTS
InitSpy
CleanupSpy

View File

@ -6,7 +6,7 @@
namespace Spy
{
constexpr std::string_view SUPPORT_VERSION = "3.9.12.17";
constexpr std::string_view SUPPORT_VERSION = "3.9.12.51";
inline std::atomic<std::uintptr_t> WeChatDll { 0 };
template <typename T> inline T getFunction(std::uintptr_t offset) { return reinterpret_cast<T>(WeChatDll + offset); }

View File

@ -51,8 +51,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 39,4,4,0
PRODUCTVERSION 3,9,12,17
FILEVERSION 39,5,2,0
PRODUCTVERSION 3,9,12,51
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -69,12 +69,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "WeChatFerry"
VALUE "FileDescription", "WeChatFerry"
VALUE "FileVersion", "39.4.4.0"
VALUE "FileVersion", "39.5.2.0"
VALUE "InternalName", "spy.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "spy.dll"
VALUE "ProductName", "WeChatFerry"
VALUE "ProductVersion", "3.9.12.17"
VALUE "ProductVersion", "3.9.12.51"
END
END
BLOCK "VarFileInfo"

View File

@ -2,14 +2,10 @@
"name": "wcf",
"version-string": "1.0.0",
"dependencies": [
{
"name": "protobuf",
"features": [ "zlib" ]
},
"spdlog",
"nng",
"magic-enum",
"minhook"
],
"builtin-baseline": "80d54ff62d528339c626a6fbc3489a7f25956ade"
"builtin-baseline": "d6995a0cf3cafda5e9e52749fad075dd62bfd90c"
}

View File

@ -1,7 +1,7 @@
# WeChatFerry Python 客户端
[![PyPi](https://img.shields.io/pypi/v/wcferry.svg)](https://pypi.python.org/pypi/wcferry) [![Downloads](https://static.pepy.tech/badge/wcferry)](https://pypi.python.org/pypi/wcferry) [![Documentation Status](https://readthedocs.org/projects/wechatferry/badge/?version=latest)](https://wechatferry.readthedocs.io/zh/latest/?badge=latest)
|[📖 Python 文档](https://wechatferry.readthedocs.io/)|[📺 Python 视频教程](https://mp.weixin.qq.com/s/APdjGyZ2hllXxyG_sNCfXQ)|[🙋 FAQ](https://mp.weixin.qq.com/s/YvgFFhF6D-79kXDzRqtg6w)|
|[📖 Python 文档](https://wechatferry.readthedocs.io/)|[📺 Python 视频教程](https://mp.weixin.qq.com/s/APdjGyZ2hllXxyG_sNCfXQ)|[🙋 FAQ](https://mp.weixin.qq.com/s/c2JggTBlOP8fP9j-MlMAvg)|
|:-:|:-:|:-:|
🤖示例机器人框架:[WeChatRobot](https://github.com/lich0821/WeChatRobot)。
@ -44,8 +44,8 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc
## 版本更新
### v39.4.4.0
* 实现发送 XML 功能
### v39.5.2.0
* 没有新功能
<details><summary>点击查看更多</summary>
@ -71,7 +71,7 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc
* 发送图片消息
* 发送文件消息
* 发送卡片消息
* 发送 XML
* 发送 XML 消息
* 发送 GIF 消息
* 拍一拍群友
* 转发消息

View File

@ -1,11 +1,13 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
__version__ = "39.4.4.0"
__version__ = "39.5.2.0"
import atexit
import base64
import ctypes
import ctypes.wintypes
import gc
import logging
import mimetypes
import os
@ -80,10 +82,7 @@ class Wcf():
if host is None:
self._local_mode = True
self.host = "127.0.0.1"
self.sdk = ctypes.cdll.LoadLibrary(f"{self._wcf_root}/sdk.dll")
if self.sdk.WxInitSDK(debug, port) != 0:
self.LOG.error("初始化失败!")
os._exit(-1)
self._sdk_init(debug, port)
self.cmd_url = f"tcp://{self.host}:{self.port}"
@ -125,15 +124,42 @@ class Wcf():
except subprocess.CalledProcessError as e:
self.LOG.error(f"修改控制台代码页失败: {e}")
def _sdk_init(self, debug, port):
sdk = ctypes.cdll.LoadLibrary(f"{self._wcf_root}/sdk.dll")
if sdk.WxInitSDK(debug, port) != 0:
self.LOG.error("初始化失败!")
os._exit(-1)
# 主动卸载
ctypes.windll.kernel32.FreeLibrary.argtypes = [ctypes.wintypes.HMODULE]
ctypes.windll.kernel32.FreeLibrary(sdk._handle)
del sdk # 删除 Python 对象、触发垃圾回收
gc.collect()
def _sdk_destroy(self):
sdk = ctypes.cdll.LoadLibrary(f"{self._wcf_root}/sdk.dll")
sdk.WxDestroySDK()
# 主动卸载
ctypes.windll.kernel32.FreeLibrary.argtypes = [ctypes.wintypes.HMODULE]
ctypes.windll.kernel32.FreeLibrary(sdk._handle)
del sdk # 删除 Python 对象、触发垃圾回收
gc.collect()
def cleanup(self) -> None:
"""关闭连接,回收资源"""
if not self._is_running:
return
self.disable_recv_msg()
self.cmd_socket.close()
if self._local_mode and self.sdk and self.sdk.WxDestroySDK() != 0:
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SHUTDOWN
_ = self._send_request(req)
self.cmd_socket.close()
self.msg_socket.close()
if self._local_mode and self.sdk and self._sdk_destroy() != 0:
self.LOG.error("退出失败!")
self._is_running = False
@ -537,9 +563,6 @@ class Wcf():
else:
self.msgQ.put(WxMsg(rsp.wxmsg))
# 退出前关闭通信通道
self.msg_socket.close()
if self._is_receiving_msg:
return True
@ -574,8 +597,6 @@ class Wcf():
pass
else:
callback(WxMsg(rsp.wxmsg))
# 退出前关闭通信通道
self.msg_socket.close()
if self._is_receiving_msg:
return True
@ -666,9 +687,9 @@ class Wcf():
friends = []
for cnt in self.get_contacts():
if (cnt["wxid"].endswith("@chatroom") or # 群聊
cnt["wxid"].startswith("gh_") or # 公众号
cnt["wxid"] in not_friends.keys() # 其他杂号
):
cnt["wxid"].startswith("gh_") or # 公众号
cnt["wxid"] in not_friends.keys() # 其他杂号
):
continue
friends.append(cnt)
@ -843,7 +864,7 @@ class Wcf():
Returns:
str: 成功返回存储路径空字符串为失败原因见日志
"""
sleep(1) # 强制等待 1 秒让数据入库,避免那帮人总是嗷嗷叫超时
sleep(1) # 强制等待 1 秒让数据入库,避免那帮人总是嗷嗷叫超时
if (not os.path.exists(extra)) and (self.download_attach(id, "", extra) != 0):
self.LOG.error(f"下载失败")
return ""
@ -870,7 +891,7 @@ class Wcf():
Returns:
str: 成功返回存储路径空字符串为失败原因见日志
"""
sleep(1) # 强制等待 1 秒让数据入库,避免那帮人总是嗷嗷叫超时
sleep(1) # 强制等待 1 秒让数据入库,避免那帮人总是嗷嗷叫超时
base, _ = os.path.splitext(thumb)
file_path = base + ".mp4"
file_name = os.path.basename(file_path)

File diff suppressed because one or more lines are too long