添加用户消息记录接口;

This commit is contained in:
karlcarlo
2025-12-11 03:53:47 +08:00
parent 6029da058a
commit 420fed4bcf
49 changed files with 7574 additions and 426 deletions

View File

@@ -0,0 +1,389 @@
#!/usr/bin/env python3
"""
企业微信会话存档解密脚本
使用RSA私钥解密加密的会话数据
"""
import json
import os
import sys
import base64
from pathlib import Path
from datetime import datetime
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 添加当前目录到路径,以便导入wework_ctypes
sys.path.insert(0, str(Path(__file__).parent))
from wework_ctypes import WeWorkFinanceSDK, WeWorkSDKError
def load_rsa_private_key(key_path: str) -> str:
"""
加载RSA私钥
Args:
key_path: 私钥文件路径
Returns:
私钥内容字符串
"""
try:
with open(key_path, 'r') as f:
key_content = f.read()
return key_content
except Exception as e:
raise Exception(f"加载RSA私钥失败: {e}")
def rsa_decrypt_base64(encrypted_data_base64: str, private_key: str) -> str:
"""
使用RSA私钥解密Base64编码的数据
Args:
encrypted_data_base64: Base64编码的加密数据
private_key: RSA私钥字符串
Returns:
解密后的字符串
"""
try:
# 使用cryptography库进行RSA解密
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import base64
# 加载私钥
private_key_obj = serialization.load_pem_private_key(
private_key.encode('utf-8'),
password=None,
backend=default_backend()
)
# Base64解码
encrypted_data = base64.b64decode(encrypted_data_base64)
# RSA解密 (使用PKCS1v15填充)
decrypted_data = private_key_obj.decrypt(
encrypted_data,
padding.PKCS1v15()
)
return decrypted_data.decode('utf-8')
except ImportError:
raise Exception("需要安装cryptography库: pip install cryptography")
except Exception as e:
raise Exception(f"RSA解密失败: {e}")
def decrypt_chat_message(encrypt_random_key: str, encrypt_chat_msg: str,
rsa_private_key_path: str, publickey_ver: int = None) -> tuple:
"""
解密聊天消息
Args:
encrypt_random_key: 加密的随机密钥(Base64编码)
encrypt_chat_msg: 加密的聊天消息
rsa_private_key_path: RSA私钥文件路径或目录
publickey_ver: publickey_ver版本号,用于动态匹配私钥
Returns:
(解密后的消息字典, 实际使用的私钥路径)
"""
print(f"正在解密消息...")
print(f"加密密钥长度: {len(encrypt_random_key)}")
print(f"加密消息长度: {len(encrypt_chat_msg)}")
# 动态确定私钥路径
actual_key_path = rsa_private_key_path
# 检查是否使用默认私钥路径(用户未指定自定义路径)
# 通过检查路径是否指向默认文件来判断
default_key_path = Path("cert/1_rsa.key")
specified_key_path = Path(rsa_private_key_path)
# 如果使用默认私钥路径且提供了publickey_ver,则动态匹配
if publickey_ver is not None and specified_key_path == default_key_path:
# 构建动态私钥路径:cert/{publickey_ver}_rsa.key
dynamic_key_path = f"cert/{publickey_ver}_rsa.key"
dynamic_path = Path(dynamic_key_path)
if dynamic_path.exists():
actual_key_path = dynamic_key_path
print(f"检测到publickey_ver={publickey_ver},使用动态私钥: {actual_key_path}")
else:
print(f"警告: 动态私钥文件不存在: {dynamic_key_path},使用默认私钥: {actual_key_path}")
# 1. 加载RSA私钥
print(f"加载RSA私钥: {actual_key_path}")
private_key = load_rsa_private_key(actual_key_path)
print(f"✓ RSA私钥加载成功")
# 2. 使用RSA私钥解密encrypt_random_key得到encrypt_key
print("正在使用RSA解密随机密钥...")
try:
encrypt_key = rsa_decrypt_base64(encrypt_random_key, private_key)
print(f"✓ RSA解密成功,encrypt_key长度: {len(encrypt_key)}")
except Exception as e:
print(f"✗ RSA解密失败: {e}")
# 尝试直接使用encrypt_random_key作为encrypt_key(某些情况下可能已经解密)
print("尝试使用原始encrypt_random_key作为encrypt_key...")
encrypt_key = encrypt_random_key
# 3. 使用SDK解密聊天消息
print("正在使用SDK解密聊天消息...")
try:
# 初始化SDK(只需要解密功能,不需要corpid和secret)
with WeWorkFinanceSDK("dummy", "dummy") as sdk:
decrypted_msg = sdk.decrypt_data(encrypt_key, encrypt_chat_msg)
print("✓ SDK解密成功")
# 尝试解析JSON
try:
result = json.loads(decrypted_msg)
return result, actual_key_path
except json.JSONDecodeError:
# 如果不是JSON,返回原始文本
return {"raw_message": decrypted_msg}, actual_key_path
except WeWorkSDKError as e:
raise Exception(f"SDK解密失败: {e}")
except Exception as e:
raise Exception(f"解密过程出错: {e}")
def decrypt_file(input_file: str, rsa_private_key_path: str, output_file: str = None):
"""
解密文件中的聊天数据
Args:
input_file: 输入文件路径(JSON格式)
rsa_private_key_path: RSA私钥文件路径
output_file: 输出文件路径(可选)
"""
print("=" * 60)
print("企业微信会话存档解密脚本")
print("=" * 60)
# 检查输入文件
input_path = Path(input_file)
if not input_path.exists():
print(f"错误: 输入文件不存在: {input_file}")
return 1
# 检查RSA私钥文件(只检查用户指定的路径,动态路径在解密时检查)
rsa_path = Path(rsa_private_key_path)
if not rsa_path.exists():
print(f"错误: 指定的RSA私钥文件不存在: {rsa_private_key_path}")
return 1
print(f"输入文件: {input_path.absolute()}")
print(f"指定私钥: {rsa_path.absolute()}")
print("-" * 60)
# 用于记录实际使用的私钥路径
actual_key_paths_used = set()
try:
# 读取输入文件
with open(input_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"文件加载成功")
# 检查数据格式
if "chatdata" not in data:
print("错误: 文件中没有chatdata字段")
return 1
chatdata = data.get("chatdata", [])
chat_count = len(chatdata)
print(f"共发现 {chat_count} 条聊天记录")
if chat_count == 0:
print("没有可解密的聊天记录")
return 0
# 解密所有消息
decrypted_messages = []
success_count = 0
fail_count = 0
for i, msg in enumerate(chatdata):
# print('msg', type(msg))
seq = msg.get("seq", i + 1)
msgid = msg.get("msgid", f"msg_{i}")
encrypt_random_key = msg.get("encrypt_random_key", "")
encrypt_chat_msg = msg.get("encrypt_chat_msg", "")
print(f"\n[{i+1}/{chat_count}] 解密消息 seq={seq}, msgid={msgid[:20]}...")
if not encrypt_random_key or not encrypt_chat_msg:
print(f" 跳过: 缺少加密数据")
fail_count += 1
decrypted_messages.append({
"seq": seq,
"msgid": msgid,
"error": "缺少加密数据",
"original": msg
})
continue
try:
# 获取publickey_ver
publickey_ver = msg.get("publickey_ver")
# 解密消息
decrypted_msg, actual_key_path = decrypt_chat_message(
encrypt_random_key,
encrypt_chat_msg,
rsa_private_key_path,
publickey_ver
)
# 记录实际使用的私钥路径
actual_key_paths_used.add(actual_key_path)
# 添加元数据
if isinstance(decrypted_msg, dict):
decrypted_msg["_metadata"] = {
"seq": seq,
"msgid": msgid,
"decrypt_time": datetime.now().isoformat(),
"original_has_encryption": True
}
decrypted_messages.append({
"seq": seq,
"msgid": msgid,
"decrypted": decrypted_msg,
"original_summary": {
"publickey_ver": msg.get("publickey_ver"),
"encrypt_key_len": len(encrypt_random_key),
"encrypt_msg_len": len(encrypt_chat_msg)
}
})
success_count += 1
print(f" ✓ 解密成功")
# 显示解密后的消息摘要
if isinstance(decrypted_msg, dict):
msg_type = decrypted_msg.get("msgtype", "unknown")
from_user = decrypted_msg.get("from", "unknown")
to_user = decrypted_msg.get("tolist", ['unknown'])[0]
if isinstance(decrypted_msg.get("tolist"), list) and len(decrypted_msg.get("tolist")) > 1:
to_user += f" 等 {len(decrypted_msg.get('tolist'))} 人"
print(f" 类型: {msg_type}, 来自: {from_user}, 发送给: {to_user}")
# 成功解密后继续处理下一条消息
continue
except Exception as e:
print(f" ✗ 解密失败: {e}")
fail_count += 1
decrypted_messages.append({
"seq": seq,
"msgid": msgid,
"error": str(e),
"original": msg
})
print("\n" + "-" * 60)
print(f"解密完成: 成功 {success_count} 条, 失败 {fail_count} 条")
# 显示实际使用的私钥信息
if actual_key_paths_used:
print(f"实际使用的私钥文件:")
for key_path in sorted(actual_key_paths_used):
print(f" - {key_path}")
# 生成输出文件名
if output_file is None:
timestamp = datetime.now().strftime("%y%m%d_%H%M%S")
output_file = f"data/decrypted_{input_path.stem}_{timestamp}.json"
output_path = Path(output_file)
# 准备输出数据
output_data = {
"metadata": {
"input_file": str(input_path.absolute()),
"decrypt_time": datetime.now().isoformat(),
"total_messages": chat_count,
"successful_decrypts": success_count,
"failed_decrypts": fail_count,
"specified_key": str(rsa_path.absolute()),
"actual_keys_used": sorted([str(Path(p).absolute()) for p in actual_key_paths_used])
},
"decrypted_messages": decrypted_messages,
"original_metadata": data.get("_metadata", {})
}
# 保存解密结果
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(output_data, f, indent=2, ensure_ascii=False)
print(f"解密结果已保存到: {output_path.absolute()}")
# 显示一些解密后的消息示例
if success_count > 0:
print("\n解密消息示例:")
for i, msg in enumerate(decrypted_messages[:3]): # 显示前3条
if "decrypted" in msg:
decrypted = msg["decrypted"]
if isinstance(decrypted, dict):
seq = msg.get("seq", "N/A")
msg_type = decrypted.get("msgtype", "unknown")
content = decrypted.get("content", "无内容")[:100]
print(f" [{i+1}] seq={seq}, 类型={msg_type}")
print(f" 内容: {content}...")
print("\n" + "=" * 60)
print("解密完成")
return 0
except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}")
return 1
except Exception as e:
print(f"解密过程出错: {e}")
import traceback
traceback.print_exc()
return 1
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(
description="企业微信会话存档解密脚本",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
# 解密data目录下的文件
%(prog)s data/chat_0_100_251203.json
# 指定RSA私钥路径
%(prog)s data/chat_0_100_251203.json --key /path/to/private_key.pem
# 指定输出文件
%(prog)s data/chat_0_100_251203.json --output decrypted_result.json
"""
)
parser.add_argument('input_file', help='输入文件路径(JSON格式)')
parser.add_argument('--key', '-k', default='cert/1_rsa.key',
help='RSA私钥文件路径,默认: cert/1_rsa.key')
parser.add_argument('--output', '-o', help='输出文件路径,默认自动生成')
args = parser.parse_args()
return decrypt_file(args.input_file, args.key, args.output)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,704 @@
#!/usr/bin/env python3
"""
企业微信会话存档SDK测试脚本
简单测试脚本,调用SDK获取会话数据并自动解密和存储到数据库
使用示例:
python test_chat_data.py # 使用默认seq=0
python test_chat_data.py --seq 1000 # 从seq=1000开始拉取
python test_chat_data.py -s 5000 # 从seq=5000开始拉取
python test_chat_data.py --help # 显示帮助信息
"""
import argparse
import asyncio
import json
import os
import sys
import base64
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Any
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 添加当前目录到路径,以便导入wework_ctypes
sys.path.insert(0, str(Path(__file__).parent))
# 添加backend目录到路径,以便导入模型
sys.path.insert(0, str(Path(__file__).parent.parent))
from wework_ctypes import WeWorkFinanceSDK, WeWorkSDKError, ERROR_CODES
def load_rsa_private_key(key_path: str) -> str:
"""
加载RSA私钥
Args:
key_path: 私钥文件路径
Returns:
私钥内容字符串
"""
try:
with open(key_path, "r") as f:
key_content = f.read()
return key_content
except Exception as e:
raise Exception(f"加载RSA私钥失败: {e}")
def rsa_decrypt_base64(encrypted_data_base64: str, private_key: str) -> str:
"""
使用RSA私钥解密Base64编码的数据
Args:
encrypted_data_base64: Base64编码的加密数据
private_key: RSA私钥字符串
Returns:
解密后的字符串
"""
try:
# 使用cryptography库进行RSA解密
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import base64
# 加载私钥
private_key_obj = serialization.load_pem_private_key(
private_key.encode("utf-8"), password=None, backend=default_backend()
)
# Base64解码
encrypted_data = base64.b64decode(encrypted_data_base64)
# RSA解密 (使用PKCS1v15填充)
decrypted_data = private_key_obj.decrypt(encrypted_data, padding.PKCS1v15())
return decrypted_data.decode("utf-8")
except ImportError:
raise Exception("需要安装cryptography库: pip install cryptography")
except Exception as e:
raise Exception(f"RSA解密失败: {e}")
def decrypt_chat_message(
sdk,
encrypt_random_key: str,
encrypt_chat_msg: str,
rsa_private_key_path: str,
publickey_ver: int = None,
) -> dict:
"""
解密聊天消息
Args:
sdk: WeWorkFinanceSDK实例
encrypt_random_key: 加密的随机密钥(Base64编码)
encrypt_chat_msg: 加密的聊天消息
rsa_private_key_path: RSA私钥文件路径或目录
publickey_ver: publickey_ver版本号,用于动态匹配私钥
Returns:
解密后的消息字典
"""
# 动态确定私钥路径
actual_key_path = rsa_private_key_path
# 检查是否使用默认私钥路径(用户未指定自定义路径)
# 通过检查路径是否指向默认文件来判断
default_key_path = Path("cert/1_rsa.key")
specified_key_path = Path(rsa_private_key_path)
# 如果使用默认私钥路径且提供了publickey_ver,则动态匹配
if publickey_ver is not None and specified_key_path == default_key_path:
# 构建动态私钥路径:cert/{publickey_ver}_rsa.key
dynamic_key_path = f"cert/{publickey_ver}_rsa.key"
dynamic_path = Path(dynamic_key_path)
if dynamic_path.exists():
actual_key_path = dynamic_key_path
else:
# 如果动态私钥不存在,使用默认私钥
pass
# 1. 加载RSA私钥
private_key = load_rsa_private_key(actual_key_path)
# 2. 使用RSA私钥解密encrypt_random_key得到encrypt_key
try:
encrypt_key = rsa_decrypt_base64(encrypt_random_key, private_key)
except Exception as e:
# 尝试直接使用encrypt_random_key作为encrypt_key(某些情况下可能已经解密)
encrypt_key = encrypt_random_key
# 3. 使用SDK解密聊天消息
decrypted_msg = sdk.decrypt_data(encrypt_key, encrypt_chat_msg)
# 4. 解析JSON
try:
result = json.loads(decrypted_msg)
return result
except json.JSONDecodeError:
# 如果不是JSON,返回原始文本
return {"raw_message": decrypted_msg}
def decrypt_chat_data(chatdata, sdk, rsa_private_key_path="cert/1_rsa.key"):
"""
解密聊天数据
Args:
chatdata: 原始聊天数据列表
sdk: WeWorkFinanceSDK实例
rsa_private_key_path: RSA私钥文件路径
Returns:
(decrypted_chatdata, stats)
decrypted_chatdata: 解密后的聊天数据列表
stats: 解密统计信息字典
"""
decrypted_chatdata = []
stats = {
"total_encrypted_messages": 0,
"successful_decrypts": 0,
"failed_decrypts": 0,
}
# 检查私钥文件是否存在
rsa_path = Path(rsa_private_key_path)
if not rsa_path.exists():
print(f"警告: RSA私钥文件不存在: {rsa_private_key_path}")
print("将跳过解密,保存原始加密数据")
return chatdata, stats
print(f"使用RSA私钥: {rsa_path.absolute()}")
print("开始解密聊天数据...")
for i, msg in enumerate(chatdata):
encrypt_random_key = msg.get("encrypt_random_key", "")
encrypt_chat_msg = msg.get("encrypt_chat_msg", "")
# 检查是否有加密数据
if not encrypt_random_key or not encrypt_chat_msg:
# 没有加密数据,直接添加到结果中
decrypted_chatdata.append(msg)
continue
stats["total_encrypted_messages"] += 1
try:
# 获取publickey_ver
publickey_ver = msg.get("publickey_ver")
# 解密消息
decrypted_msg = decrypt_chat_message(
sdk,
encrypt_random_key,
encrypt_chat_msg,
rsa_private_key_path,
publickey_ver,
)
# 创建新的消息对象,保留原始字段但移除加密字段
new_msg = {}
for key, value in msg.items():
if key not in ["encrypt_random_key", "encrypt_chat_msg"]:
new_msg[key] = value
# 将解密后的字段合并到消息中
if isinstance(decrypted_msg, dict):
for key, value in decrypted_msg.items():
new_msg[key] = value
decrypted_chatdata.append(new_msg)
stats["successful_decrypts"] += 1
# 显示进度
if stats["successful_decrypts"] % 100 == 0:
print(f" 已解密 {stats['successful_decrypts']} 条消息...")
except Exception as e:
stats["failed_decrypts"] += 1
# 解密失败,跳过此消息(不添加到结果中)
continue
return decrypted_chatdata, stats
async def save_messages_to_database(messages: List[Dict[str, Any]]) -> Dict[str, int]:
"""
将消息保存到数据库
Args:
messages: 消息字典列表
Returns:
存储统计信息字典
"""
stats = {
"total_messages": len(messages),
"saved": 0,
"skipped_duplicates": 0,
"failed": 0,
}
if not messages:
print("没有消息需要存储到数据库")
return stats
print(f"\n开始将 {len(messages)} 条消息存储到数据库...")
try:
# 导入数据库相关模块
from pymongo import AsyncMongoClient
from beanie import init_beanie
from app.models import models
from app.models.message import Message
# 从环境变量获取数据库配置
mongodb_url = os.getenv(
"MONGODB_URL",
"mongodb://localhost:27017",
)
database_name = os.getenv("DATABASE_NAME", "douba_chat_dev")
# 初始化数据库连接
client = AsyncMongoClient(mongodb_url)
await init_beanie(database=client[database_name], document_models=models)
print(f"数据库连接成功: {database_name}")
# 批量处理消息
batch_size = 100
for i in range(0, len(messages), batch_size):
batch = messages[i : i + batch_size]
batch_msgids = [msg.get("msgid") for msg in batch if msg.get("msgid")]
# 检查哪些消息已经存在
existing_messages = await Message.find(
{"msgid": {"$in": batch_msgids}}
).to_list()
existing_msgids = {msg.msgid for msg in existing_messages}
# 过滤掉已存在的消息
new_messages = []
for msg in batch:
msgid = msg.get("msgid")
if not msgid:
stats["failed"] += 1
continue
if msgid in existing_msgids:
stats["skipped_duplicates"] += 1
continue
try:
# 创建Message实例
# 注意:使用by_alias=True来处理"from"字段的别名
message_data = {
"msgid": msg.get("msgid"),
"action": msg.get("action"),
"from": msg.get("from"), # 使用别名
"tolist": msg.get("tolist", []),
"roomid": msg.get("roomid"),
"text": msg.get("text", {}),
"msgtime": msg.get("msgtime"),
"msgtype": msg.get("msgtype"),
"remark": msg.get("remark"),
"seq": msg.get("seq", 1000),
"isEnabled": True,
"created_at": datetime.now(),
"updated_at": datetime.now(),
}
# 移除None值
message_data = {
k: v for k, v in message_data.items() if v is not None
}
message = Message(**message_data)
new_messages.append(message)
except Exception as e:
print(f"创建消息实例失败 (msgid: {msgid}): {e}")
stats["failed"] += 1
# 批量插入新消息
if new_messages:
try:
await Message.insert_many(new_messages)
stats["saved"] += len(new_messages)
print(
f" 已存储 {len(new_messages)} 条消息 (进度: {i + len(batch)}/{len(messages)})"
)
except Exception as e:
print(f"批量插入失败: {e}")
stats["failed"] += len(new_messages)
# 关闭数据库连接
await client.close()
print(f"数据库存储完成:")
print(f" 总计消息: {stats['total_messages']}")
print(f" 成功存储: {stats['saved']}")
print(f" 跳过重复: {stats['skipped_duplicates']}")
print(f" 存储失败: {stats['failed']}")
except Exception as e:
print(f"数据库存储过程中发生错误: {e}")
import traceback
traceback.print_exc()
stats["failed"] = len(messages)
return stats
def main():
"""主函数:测试获取会话数据"""
# 解析命令行参数
parser = argparse.ArgumentParser(
description="企业微信会话存档SDK测试脚本",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
%(prog)s # 使用默认seq=0
%(prog)s --seq 1000 # 从seq=1000开始拉取
%(prog)s -s 5000 # 从seq=5000开始拉取
%(prog)s --limit 500 # 一次拉取500条消息
%(prog)s --timeout 30 # 设置超时时间为30秒
""",
)
parser.add_argument(
"-s",
"--seq",
type=int,
default=0,
help="起始seq值,首次拉取使用0,后续拉取使用上次返回的最大seq值 (默认: 0)",
)
parser.add_argument(
"-l",
"--limit",
type=int,
default=1000,
help="一次拉取的消息数量限制 (默认: 1000)",
)
parser.add_argument(
"-t", "--timeout", type=int, default=10, help="请求超时时间(秒) (默认: 10)"
)
parser.add_argument(
"--proxy",
type=str,
default="",
help="代理服务器地址,格式: http://proxy.example.com:8080 (默认: 空)",
)
parser.add_argument(
"--passwd", type=str, default="", help="代理服务器密码 (默认: 空)"
)
args = parser.parse_args()
print("=" * 60)
print("企业微信会话存档SDK测试脚本")
print("=" * 60)
print(f"命令行参数: seq={args.seq}, limit={args.limit}, timeout={args.timeout}s")
if args.proxy:
print(f"代理设置: {args.proxy}")
# 从环境变量获取配置
corpid = os.getenv("CORPID", "wwdf65802ca25ec195")
secret = os.getenv("WEWORK_SECRET", "-Ta6WMWxBhfGolWnnlO15nQckj3DRKAowUOdX2fwvzE")
print(f"企业ID: {corpid}")
print(f"Secret: {secret[:10]}...") # 只显示前10个字符,避免泄露完整密钥
# 检查动态库是否存在
lib_path = Path(__file__).parent.parent / "lib" / "sdk" / "libWeWorkFinanceSdk_C.so"
if not lib_path.exists():
print(f"错误: 动态库不存在: {lib_path}")
print("请确保已正确放置libWeWorkFinanceSdk_C.so文件")
return 1
print(f"动态库路径: {lib_path}")
# 确保data目录存在
data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
print(f"数据目录: {data_dir.absolute()}")
print("-" * 60)
try:
# 初始化SDK(使用上下文管理器确保资源正确释放)
print("正在初始化SDK...")
with WeWorkFinanceSDK(corpid, secret, str(lib_path)) as sdk:
print("✓ SDK初始化成功")
# 设置拉取参数(从命令行参数获取)
seq = args.seq
limit = args.limit
timeout = args.timeout
print(f"\n正在拉取会话数据...")
print(f"参数: seq={seq}, limit={limit}, timeout={timeout}s")
if args.proxy:
print(f"代理: {args.proxy}")
# 调用SDK获取聊天数据
result_str = sdk.get_chat_data(
seq=seq,
limit=limit,
proxy=args.proxy, # 使用命令行指定的代理
passwd=args.passwd, # 使用命令行指定的代理密码
timeout=timeout,
)
print("✓ 数据拉取成功")
print("-" * 60)
# 尝试解析JSON
try:
result = json.loads(result_str)
# 检查错误码
errcode = result.get("errcode", -1)
errmsg = result.get("errmsg", "")
if errcode == 0:
print(f"成功: {errmsg}")
# 获取聊天数据
chatdata = result.get("chatdata", [])
original_chat_count = len(chatdata)
print(f"共获取 {original_chat_count} 条聊天记录")
if original_chat_count > 0:
# 显示第一条消息的摘要信息
print("\n第一条消息摘要:")
first_msg = chatdata[0]
print(f" seq: {first_msg.get('seq', 'N/A')}")
print(f" msgid: {first_msg.get('msgid', 'N/A')}")
print(
f" publickey_ver: {first_msg.get('publickey_ver', 'N/A')}"
)
# 检查是否有加密数据
encrypt_random_key = first_msg.get("encrypt_random_key", "")
encrypt_chat_msg = first_msg.get("encrypt_chat_msg", "")
if encrypt_random_key and encrypt_chat_msg:
print(f" 加密密钥长度: {len(encrypt_random_key)}")
print(f" 加密消息长度: {len(encrypt_chat_msg)}")
print(" 注意: 消息已加密,将自动解密")
else:
print(" 消息未加密")
# 解密聊天数据
decrypted_chatdata, decrypt_stats = decrypt_chat_data(chatdata, sdk)
# 更新结果中的chatdata
result["chatdata"] = decrypted_chatdata
decrypted_chat_count = len(decrypted_chatdata)
# 显示解密统计
print("\n" + "-" * 60)
print("解密完成:")
print(f" 原始消息总数: {original_chat_count}")
print(
f" 加密消息数量: {decrypt_stats['total_encrypted_messages']}"
)
print(f" 成功解密: {decrypt_stats['successful_decrypts']}")
print(f" 解密失败: {decrypt_stats['failed_decrypts']}")
print(f" 最终输出消息数: {decrypted_chat_count}")
# 显示最大seq值,用于下次拉取
if decrypted_chat_count > 0:
max_seq = max(msg.get("seq", 0) for msg in decrypted_chatdata)
print(f"\n最大seq值: {max_seq} (下次拉取建议使用此值)")
else:
max_seq = seq
# 生成文件名:chat_{start_seq}_{end_seq}_{date}.json
start_seq = seq
end_seq = max_seq if decrypted_chat_count > 0 else seq
date_str = datetime.now().strftime("%y%m%d") # YYMMDD格式
filename = f"chat_{start_seq}_{end_seq}_{date_str}.json"
output_path = data_dir / filename
# 添加元数据到结果中
if "_metadata" not in result:
result["_metadata"] = {}
# 更新chat_count为解密后的消息数量
result["_metadata"].update(
{
"filename": filename,
"save_path": str(output_path),
"save_time": datetime.now().isoformat(),
"seq_range": f"{start_seq}-{end_seq}",
"chat_count": decrypted_chat_count, # 使用解密后的数量
"parameters": {
"request_seq": seq,
"limit": limit,
"timeout": timeout,
"proxy": args.proxy if args.proxy else "未使用",
"command_line_args": {
"seq": args.seq,
"limit": args.limit,
"timeout": args.timeout,
"proxy": args.proxy,
"passwd": "已设置" if args.passwd else "未设置",
},
},
"decrypt_info": {
"total_encrypted_messages": decrypt_stats[
"total_encrypted_messages"
],
"successful_decrypts": decrypt_stats[
"successful_decrypts"
],
"failed_decrypts": decrypt_stats["failed_decrypts"],
"decrypt_time": datetime.now().isoformat(),
},
}
)
# 保存到data目录
with open(output_path, "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n解密结果已保存到: {output_path}")
print(f"文件名格式: chat_{start_seq}_{end_seq}_{date_str}.json")
print(
f"说明: seq范围 {start_seq}-{end_seq}, 日期 {datetime.now().strftime('%Y-%m-%d')}"
)
print(f"包含 {decrypted_chat_count} 条解密后的消息")
# 显示一些解密后的消息示例
if decrypted_chat_count > 0:
print("\n解密消息示例:")
for i, msg in enumerate(decrypted_chatdata[:3]): # 显示前3条
seq = msg.get("seq", "N/A")
msgid = msg.get("msgid", "N/A")[:20]
msgtype = msg.get("msgtype", "unknown")
from_user = msg.get("from", "unknown")
print(
f" [{i+1}] seq={seq}, msgid={msgid}..., 类型={msgtype}, 来自={from_user}"
)
# 存储消息到数据库
if decrypted_chat_count > 0:
print("\n" + "=" * 60)
print("开始存储消息到数据库...")
try:
# 运行异步函数
db_stats = asyncio.run(
save_messages_to_database(decrypted_chatdata)
)
# 更新元数据中的数据库存储信息
if "db_store_info" not in result["_metadata"]:
result["_metadata"]["db_store_info"] = {}
result["_metadata"]["db_store_info"].update(
{
"db_stats": db_stats,
"db_store_time": datetime.now().isoformat(),
}
)
# 重新保存JSON文件以包含数据库存储信息
with open(output_path, "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"数据库存储信息已更新到: {output_path}")
except Exception as e:
print(f"数据库存储过程中发生错误: {e}")
import traceback
traceback.print_exc()
else:
print(f"错误: errcode={errcode}, errmsg={errmsg}")
# 显示详细的错误说明
if errcode in ERROR_CODES:
print(f"详细说明: {ERROR_CODES[errcode]}")
# 根据错误码提供建议
if errcode == 10000:
print("建议: 请检查请求参数是否正确")
elif errcode == 10001:
print("建议: 请检查网络连接和代理设置")
elif errcode == 10002:
print("建议: 数据格式可能不正确,请检查响应数据")
elif errcode == 10004:
print("建议: 请检查密钥配置是否正确")
elif errcode == 10006:
print("建议: 解密失败,请检查私钥和加密数据")
elif errcode == 10007:
print("建议: 找不到消息加密版本的私钥,需要重新传入私钥对")
elif errcode == 10009:
print("建议: IP地址不在白名单中,请检查IP配置")
elif errcode == 10010:
print("建议: 数据已过期,请重新拉取数据")
elif errcode == 10011:
print("建议: 证书错误,请检查证书配置")
print("\n完整响应:")
print(json.dumps(result, indent=2, ensure_ascii=False))
except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}")
print("原始响应数据:")
print(result_str[:500] + "..." if len(result_str) > 500 else result_str)
# 保存原始数据到文件(使用日期命名)
date_str = datetime.now().strftime("%y%m%d")
raw_filename = f"chat_raw_{date_str}.txt"
raw_output_path = data_dir / raw_filename
with open(raw_output_path, "w", encoding="utf-8") as f:
f.write(result_str)
print(f"原始数据已保存到: {raw_output_path}")
# 同时保存一份到当前目录
with open("chat_data_raw.txt", "w", encoding="utf-8") as f:
f.write(result_str)
print(f"备份原始数据: chat_data_raw.txt")
print("\n" + "=" * 60)
print("测试完成")
except WeWorkSDKError as e:
print(f"SDK错误: {e}")
return 1
except FileNotFoundError as e:
print(f"文件错误: {e}")
return 1
except Exception as e:
print(f"未知错误: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,430 @@
#!/usr/bin/env python3
"""
企业微信会话存档SDK的ctypes封装层
基于libWeWorkFinanceSdk_C.so动态库
"""
import ctypes
import os
import sys
from pathlib import Path
from typing import Optional, Union, Any
# 错误码定义(从WeWorkFinanceSdk_C.h提取)
ERROR_CODES = {
10000: "参数错误,请求参数错误",
10001: "网络错误,网络请求错误",
10002: "数据解析失败",
10003: "系统失败",
10004: "密钥错误导致加密失败",
10005: "fileid错误",
10006: "解密失败",
10007: "找不到消息加密版本的私钥,需要重新传入私钥对",
10008: "解析encrypt_key出错",
10009: "ip非法",
10010: "数据过期",
10011: "证书错误"
}
class WeWorkSDKError(Exception):
"""企业微信SDK错误基类"""
def __init__(self, code: int, message: str = ""):
self.code = code
if not message and code in ERROR_CODES:
message = ERROR_CODES[code]
super().__init__(f"SDK错误 {code}: {message}")
# 定义C结构体
class Slice_t(ctypes.Structure):
"""Slice结构体,对应C中的Slice_t"""
_fields_ = [
("buf", ctypes.c_char_p),
("len", ctypes.c_int)
]
def to_string(self) -> str:
"""将C字符串转换为Python字符串"""
if self.buf and self.len > 0:
return self.buf[:self.len].decode('utf-8', errors='ignore')
return ""
def __str__(self) -> str:
return self.to_string()
class MediaData_t(ctypes.Structure):
"""媒体数据结构体,对应C中的MediaData_t"""
_fields_ = [
("outindexbuf", ctypes.c_char_p),
("out_len", ctypes.c_int),
("data", ctypes.c_char_p),
("data_len", ctypes.c_int),
("is_finish", ctypes.c_int)
]
def get_outindex(self) -> str:
"""获取输出索引"""
if self.outindexbuf and self.out_len > 0:
return self.outindexbuf[:self.out_len].decode('utf-8', errors='ignore')
return ""
def get_data(self) -> bytes:
"""获取媒体数据"""
if self.data and self.data_len > 0:
return self.data[:self.data_len]
return b""
def is_finished(self) -> bool:
"""是否完成"""
return self.is_finish == 1
# WeWorkFinanceSdk_t是不透明指针
WeWorkFinanceSdk_t = ctypes.c_void_p
class WeWorkFinanceSDK:
"""企业微信会话存档SDK Python封装"""
def __init__(self, corpid: str, secret: str, lib_path: Optional[str] = None):
"""
初始化SDK
Args:
corpid: 企业ID
secret: 聊天内容存档的Secret
lib_path: 动态库路径,默认为lib/sdk/libWeWorkFinanceSdk_C.so
"""
self.corpid = corpid
self.secret = secret
self._sdk_ptr = None
self._lib = None
# 设置库路径
if lib_path is None:
# 默认路径
base_dir = Path(__file__).parent.parent
lib_path = str(base_dir / "lib" / "sdk" / "libWeWorkFinanceSdk_C.so")
self._load_library(lib_path)
self._init_sdk()
def _load_library(self, lib_path: str):
"""加载动态库并设置函数原型"""
if not os.path.exists(lib_path):
raise FileNotFoundError(f"动态库不存在: {lib_path}")
# 加载库
self._lib = ctypes.CDLL(lib_path)
# 定义函数原型
# NewSdk: 返回WeWorkFinanceSdk_t指针
self._lib.NewSdk.restype = WeWorkFinanceSdk_t
self._lib.NewSdk.argtypes = []
# Init: 返回int
self._lib.Init.restype = ctypes.c_int
self._lib.Init.argtypes = [WeWorkFinanceSdk_t, ctypes.c_char_p, ctypes.c_char_p]
# DestroySdk: 无返回值
self._lib.DestroySdk.restype = None
self._lib.DestroySdk.argtypes = [WeWorkFinanceSdk_t]
# GetChatData: 返回int
self._lib.GetChatData.restype = ctypes.c_int
self._lib.GetChatData.argtypes = [
WeWorkFinanceSdk_t,
ctypes.c_ulonglong, # seq
ctypes.c_uint, # limit
ctypes.c_char_p, # proxy
ctypes.c_char_p, # passwd
ctypes.c_int, # timeout
ctypes.POINTER(Slice_t) # chatDatas
]
# GetMediaData: 返回int
self._lib.GetMediaData.restype = ctypes.c_int
self._lib.GetMediaData.argtypes = [
WeWorkFinanceSdk_t,
ctypes.c_char_p, # indexbuf
ctypes.c_char_p, # sdkFileid
ctypes.c_char_p, # proxy
ctypes.c_char_p, # passwd
ctypes.c_int, # timeout
ctypes.POINTER(MediaData_t) # media_data
]
# DecryptData: 返回int
self._lib.DecryptData.restype = ctypes.c_int
self._lib.DecryptData.argtypes = [
ctypes.c_char_p, # encrypt_key
ctypes.c_char_p, # encrypt_msg
ctypes.POINTER(Slice_t) # msg
]
# NewSlice: 返回Slice_t指针
self._lib.NewSlice.restype = ctypes.POINTER(Slice_t)
self._lib.NewSlice.argtypes = []
# FreeSlice: 无返回值
self._lib.FreeSlice.restype = None
self._lib.FreeSlice.argtypes = [ctypes.POINTER(Slice_t)]
# NewMediaData: 返回MediaData_t指针
self._lib.NewMediaData.restype = ctypes.POINTER(MediaData_t)
self._lib.NewMediaData.argtypes = []
# FreeMediaData: 无返回值
self._lib.FreeMediaData.restype = None
self._lib.FreeMediaData.argtypes = [ctypes.POINTER(MediaData_t)]
# 辅助函数(可选)
if hasattr(self._lib, 'GetContentFromSlice'):
self._lib.GetContentFromSlice.restype = ctypes.c_char_p
self._lib.GetContentFromSlice.argtypes = [ctypes.POINTER(Slice_t)]
if hasattr(self._lib, 'GetSliceLen'):
self._lib.GetSliceLen.restype = ctypes.c_int
self._lib.GetSliceLen.argtypes = [ctypes.POINTER(Slice_t)]
def _init_sdk(self):
"""初始化SDK实例"""
# 创建SDK实例
self._sdk_ptr = self._lib.NewSdk()
if not self._sdk_ptr:
raise WeWorkSDKError(-1, "创建SDK实例失败")
# 初始化SDK
corpid_bytes = self.corpid.encode('utf-8')
secret_bytes = self.secret.encode('utf-8')
ret = self._lib.Init(self._sdk_ptr, corpid_bytes, secret_bytes)
if ret != 0:
self._lib.DestroySdk(self._sdk_ptr)
self._sdk_ptr = None
raise WeWorkSDKError(ret, f"SDK初始化失败,错误码: {ret}")
def get_chat_data(self, seq: int = 0, limit: int = 100,
proxy: str = "", passwd: str = "",
timeout: int = 5) -> str:
"""
拉取会话存档
Args:
seq: 从指定的seq开始拉取消息,首次使用请使用0
limit: 一次拉取的消息条数,最大值1000条
proxy: 代理链接,如:socks5://10.0.0.1:8081 或 http://10.0.0.1:8081
passwd: 代理账号密码,如:user_name:passwd_123
timeout: 超时时间,单位秒
Returns:
返回JSON格式的聊天数据字符串
Raises:
WeWorkSDKError: SDK操作失败
"""
if not self._sdk_ptr:
raise WeWorkSDKError(-1, "SDK未初始化")
# 创建Slice
chat_datas_ptr = self._lib.NewSlice()
if not chat_datas_ptr:
raise WeWorkSDKError(-1, "创建Slice失败")
try:
# 准备参数
proxy_bytes = proxy.encode('utf-8') if proxy else b""
passwd_bytes = passwd.encode('utf-8') if passwd else b""
# 调用GetChatData
ret = self._lib.GetChatData(
self._sdk_ptr,
ctypes.c_ulonglong(seq),
ctypes.c_uint(limit),
proxy_bytes,
passwd_bytes,
ctypes.c_int(timeout),
chat_datas_ptr
)
if ret != 0:
raise WeWorkSDKError(ret, f"获取聊天数据失败,错误码: {ret}")
# 获取数据
chat_datas = chat_datas_ptr.contents
result = chat_datas.to_string()
return result
finally:
# 释放资源
self._lib.FreeSlice(chat_datas_ptr)
def get_media_data(self, sdk_fileid: str, save_path: str,
proxy: str = "", passwd: str = "",
timeout: int = 5) -> bool:
"""
拉取媒体文件(自动处理分片)
Args:
sdk_fileid: 从GetChatData返回的聊天消息中,媒体消息包括的sdkfileid
save_path: 媒体文件保存路径
proxy: 代理链接
passwd: 代理账号密码
timeout: 超时时间,单位秒
Returns:
True表示成功,False表示失败
Raises:
WeWorkSDKError: SDK操作失败
"""
if not self._sdk_ptr:
raise WeWorkSDKError(-1, "SDK未初始化")
index = ""
is_finish = 0
file_written = False
# 确保目录存在
save_dir = os.path.dirname(save_path)
if save_dir and not os.path.exists(save_dir):
os.makedirs(save_dir, exist_ok=True)
# 打开文件准备写入(二进制追加模式)
try:
with open(save_path, 'ab') as f:
pass
except IOError as e:
raise WeWorkSDKError(-1, f"无法创建文件 {save_path}: {e}")
while is_finish == 0:
# 创建MediaData
media_data_ptr = self._lib.NewMediaData()
if not media_data_ptr:
raise WeWorkSDKError(-1, "创建MediaData失败")
try:
# 准备参数
index_bytes = index.encode('utf-8') if index else b""
sdk_fileid_bytes = sdk_fileid.encode('utf-8')
proxy_bytes = proxy.encode('utf-8') if proxy else b""
passwd_bytes = passwd.encode('utf-8') if passwd else b""
# 调用GetMediaData
ret = self._lib.GetMediaData(
self._sdk_ptr,
index_bytes,
sdk_fileid_bytes,
proxy_bytes,
passwd_bytes,
ctypes.c_int(timeout),
media_data_ptr
)
if ret != 0:
raise WeWorkSDKError(ret, f"获取媒体数据失败,错误码: {ret}")
# 获取数据
media_data = media_data_ptr.contents
# 写入文件
data = media_data.get_data()
if data:
try:
with open(save_path, 'ab') as f:
f.write(data)
file_written = True
except IOError as e:
raise WeWorkSDKError(-1, f"写入文件失败: {e}")
# 更新索引和完成状态
index = media_data.get_outindex()
is_finish = media_data.is_finish
finally:
# 释放资源
self._lib.FreeMediaData(media_data_ptr)
return file_written
def decrypt_data(self, encrypt_key: str, encrypt_msg: str) -> str:
"""
解密会话内容
Args:
encrypt_key: getchatdata返回的encrypt_random_key,使用企业自持对应版本秘钥RSA解密后的内容
encrypt_msg: getchatdata返回的encrypt_chat_msg
Returns:
解密后的消息明文
Raises:
WeWorkSDKError: SDK操作失败
"""
# 创建Slice
msgs_ptr = self._lib.NewSlice()
if not msgs_ptr:
raise WeWorkSDKError(-1, "创建Slice失败")
try:
# 准备参数
encrypt_key_bytes = encrypt_key.encode('utf-8')
encrypt_msg_bytes = encrypt_msg.encode('utf-8')
# 调用DecryptData
ret = self._lib.DecryptData(
encrypt_key_bytes,
encrypt_msg_bytes,
msgs_ptr
)
if ret != 0:
raise WeWorkSDKError(ret, f"解密数据失败,错误码: {ret}")
# 获取解密后的数据
msgs = msgs_ptr.contents
result = msgs.to_string()
return result
finally:
# 释放资源
self._lib.FreeSlice(msgs_ptr)
def close(self):
"""释放SDK资源"""
if self._sdk_ptr and self._lib:
self._lib.DestroySdk(self._sdk_ptr)
self._sdk_ptr = None
def __enter__(self):
"""上下文管理器入口"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""上下文管理器退出,自动释放资源"""
self.close()
def __del__(self):
"""析构函数,确保资源释放"""
self.close()
if __name__ == "__main__":
# 简单测试
print("企业微信SDK ctypes封装层")
print(f"Slice_t大小: {ctypes.sizeof(Slice_t)} 字节")
print(f"MediaData_t大小: {ctypes.sizeof(MediaData_t)} 字节")
# 测试库加载
try:
base_dir = Path(__file__).parent.parent
lib_path = str(base_dir / "lib" / "sdk" / "libWeWorkFinanceSdk_C.so")
if os.path.exists(lib_path):
print(f"动态库存在: {lib_path}")
# 尝试加载库但不初始化SDK(需要有效的corpid和secret)
lib = ctypes.CDLL(lib_path)
print("动态库加载成功")
else:
print(f"警告: 动态库不存在: {lib_path}")
except Exception as e:
print(f"加载动态库时出错: {e}")

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""
企业微信会话存档SDK命令行工具
Python重构版本,替代原始的tool_testSdk.cpp
"""
import argparse
import json
import sys
import os
from pathlib import Path
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 添加父目录到路径,以便导入wework_ctypes
sys.path.insert(0, str(Path(__file__).parent))
from wework_ctypes import WeWorkFinanceSDK, WeWorkSDKError
def parse_args():
"""解析命令行参数"""
# 从环境变量获取默认值
default_corpid = os.getenv("CORPID", "wwdf65802ca25ec195")
default_secret = os.getenv("WEWORK_SECRET", "-Ta6WMWxBhfGolWnnlO15nQckj3DRKAowUOdX2fwvzE")
parser = argparse.ArgumentParser(
description="企业微信会话存档SDK工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
# 拉取会话存档
%(prog)s chatmsg 0 100 "" "" 5
# 拉取媒体文件
%(prog)s mediadata "fileid" "" "" 5 "/tmp/media.file"
# 解密数据
%(prog)s decryptdata "encrypt_key" "encrypt_chat_msg"
# 使用代理拉取会话存档
%(prog)s chatmsg 0 100 "http://proxy.example.com:8080" "user:pass" 10
"""
)
subparsers = parser.add_subparsers(dest='command', help='命令')
# chatmsg命令
chatmsg_parser = subparsers.add_parser('chatmsg', help='拉取会话存档')
chatmsg_parser.add_argument('seq', type=int, help='从指定的seq开始拉取消息,首次使用请使用0')
chatmsg_parser.add_argument('limit', type=int, help='一次拉取的消息条数,最大值1000条')
chatmsg_parser.add_argument('proxy', type=str, help='代理链接,如:socks5://10.0.0.1:8081 或 http://10.0.0.1:8081')
chatmsg_parser.add_argument('passwd', type=str, help='代理账号密码,如:user_name:passwd_123')
chatmsg_parser.add_argument('timeout', type=int, help='超时时间,单位秒')
# mediadata命令
mediadata_parser = subparsers.add_parser('mediadata', help='拉取媒体文件')
mediadata_parser.add_argument('fileid', type=str, help='媒体文件id,从解密后的会话存档中得到')
mediadata_parser.add_argument('proxy', type=str, help='代理链接')
mediadata_parser.add_argument('passwd', type=str, help='代理账号密码')
mediadata_parser.add_argument('timeout', type=int, help='超时时间,单位秒')
mediadata_parser.add_argument('savefile', type=str, help='媒体文件保存路径')
# decryptdata命令
decryptdata_parser = subparsers.add_parser('decryptdata', help='解密会话存档内容')
decryptdata_parser.add_argument('encrypt_key', type=str, help='拉取会话存档返回的encrypt_random_key,使用配置在企业微信管理台的rsa公钥对应的私钥解密后得到encrypt_key')
decryptdata_parser.add_argument('encrypt_chat_msg', type=str, help='拉取会话存档返回的encrypt_chat_msg')
# 添加全局选项
parser.add_argument('--corpid', type=str,
default=default_corpid,
help=f'企业ID,默认从环境变量CORPID读取 (当前: {default_corpid})')
parser.add_argument('--secret', type=str,
default=default_secret,
help=f'聊天内容存档的Secret,默认从环境变量WEWORK_SECRET读取')
parser.add_argument('--lib-path', type=str,
help='libWeWorkFinanceSdk_C.so路径,默认使用lib/sdk/libWeWorkFinanceSdk_C.so')
return parser.parse_args()
def main():
"""主函数"""
args = parse_args()
if not args.command:
print("错误: 需要指定命令 (chatmsg, mediadata, decryptdata)")
print("使用 --help 查看帮助")
return 1
try:
# 初始化SDK
with WeWorkFinanceSDK(args.corpid, args.secret, args.lib_path) as sdk:
if args.command == 'chatmsg':
# 拉取会话存档
print(f"拉取会话存档: seq={args.seq}, limit={args.limit}, timeout={args.timeout}s")
if args.proxy:
print(f"使用代理: {args.proxy}")
result = sdk.get_chat_data(
seq=args.seq,
limit=args.limit,
proxy=args.proxy,
passwd=args.passwd,
timeout=args.timeout
)
# 尝试解析JSON并美化输出
try:
data = json.loads(result)
print("获取聊天数据成功:")
print(json.dumps(data, indent=2, ensure_ascii=False))
# 如果有聊天数据,显示统计信息
if 'chatdata' in data and data['chatdata']:
print(f"\n共获取 {len(data['chatdata'])} 条聊天记录")
if data['chatdata']:
max_seq = max(msg.get('seq', 0) for msg in data['chatdata'])
print(f"最大seq值: {max_seq} (下次拉取建议使用此值)")
except json.JSONDecodeError:
print("获取聊天数据成功 (原始数据):")
print(result)
return 0
elif args.command == 'mediadata':
# 拉取媒体文件
print(f"拉取媒体文件: fileid={args.fileid}, timeout={args.timeout}s")
print(f"保存到: {args.savefile}")
if args.proxy:
print(f"使用代理: {args.proxy}")
success = sdk.get_media_data(
sdk_fileid=args.fileid,
save_path=args.savefile,
proxy=args.proxy,
passwd=args.passwd,
timeout=args.timeout
)
if success:
print(f"媒体文件下载完成: {args.savefile}")
# 检查文件大小
if os.path.exists(args.savefile):
size = os.path.getsize(args.savefile)
print(f"文件大小: {size} 字节 ({size/1024:.2f} KB)")
else:
print("媒体文件下载失败")
return 1
return 0
elif args.command == 'decryptdata':
# 解密会话存档内容
print("解密会话存档内容...")
result = sdk.decrypt_data(args.encrypt_key, args.encrypt_chat_msg)
# 尝试解析JSON并美化输出
try:
data = json.loads(result)
print("解密成功:")
print(json.dumps(data, indent=2, ensure_ascii=False))
except json.JSONDecodeError:
print("解密成功 (原始数据):")
print(result)
return 0
except WeWorkSDKError as e:
print(f"SDK错误: {e}")
return 1
except FileNotFoundError as e:
print(f"文件错误: {e}")
return 1
except Exception as e:
print(f"未知错误: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())