diff --git a/About_WXbot.md b/About_WXbot.md
new file mode 100644
index 0000000..fc54865
--- /dev/null
+++ b/About_WXbot.md
@@ -0,0 +1,57 @@
+### 运行命令
+
+`docker run -itd --name wxbot -e WXBOT_ARGS="-q http://127.0.0.1:8080/qr_callback" -p 8066:8080 registry.cn-shanghai.aliyuncs.com/jwping/wxbot:v1.10.1-9-3.9.8.25`
+
+【2024-07-07】亲测正常下载就行,无需特殊操作
+
+以上命令参数不要改,尤其是端口映射,awada dm部分是写死的。
+
+### 首次登录
+
+首次运行需要用使用的微信号扫码登录下
+
+`docker logs -f wxbot`
+
+注意,如果你使用OrbStack,直接用软件界面打开container的日志,可能看不到二维码链接,所以要用系统自带的终端使用上述命令。
+
+等待终端输出,直到看到 类似 “http://weixin.qq.com/x/QfOfkbfe_P5wdeKNjR7S” 这样的登录二维码链接信息,复制(**注意不要直接打开,那没用!**)
+
+使用二维码生成器,比如草料 https://cli.im/url
+
+生成一个二维码,用需要的微信扫码登录(仅限个微)
+
+直到logs出现 `Http Server Listen 0.0.0.0:8080` 那就好了,终端里面出现的任何报错信息都可以忽略,不影响正常使用。
+
+好了 `ctrl+C` 退出 终端logs界面,之后终端直接关闭都行。
+
+用作助理的个微小号建议把支付还有服务等都关闭,不要暴露敏感信息,**使用者请风险自担**!
+
+### 再次登录
+
+在macOS或者linux上运行wxbot最适合成功后常开,不要频繁关闭打开,就放在那里就好,反正默认是静默运行,终端正常也不会输出什么信息的。
+
+理论上,关掉container(包括电脑重启),再次启动会自动登录,此时只需要在微信手机端上点同意就行。
+
+如果失败的话, 先运行 `docker logs -f wxbot` 看看是不是要重新登录。
+
+如果看不到让你重新登录的二维码链接信息,尝试
+
+`docker restart wxbot`
+
+如果还不行,运行,
+
+`docker rm -f wxbot`
+
+然后用最开始那个运行命令再次创建container,放心image已经在本地,不会再次下载。
+
+### 问题排查
+
+首先logs界面里面出现的任何报错信息都可以忽略,只要有 `Http Server Listen 0.0.0.0:8080` 那就是正常的
+
+每次启动(包括重启),最长需要5min才能出现 Http Server Listen 0.0.0.0:8080 或者二维码链接,期间任何报错信息都没所谓
+
+如果在 Http Server Listen 0.0.0.0:8080 或者 二维码链接 之前长时间(大于等于5min)不出任何信息,那可能是出问题了。参考再次登录方案。
+
+`Http Server Listen 0.0.0.0:8080` 之后基本终端界面就不会出新的消息了,这是正常的,这个时候其实可以退出logs,甚至关闭终端。
+
+更多,请参考:https://github.com/jwping/wxbot?tab=readme-ov-file#linux%E4%B8%8Bdocker%E9%83%A8%E7%BD%B2
diff --git a/README.md b/README.md
index 54c3797..e2283d0 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,99 @@
# What's Awada
-awada是wiseflow团队计划中 **“可在线自主学习的AI助理”** 开源项目。
+[wiseflow](https://github.com/TeamWiseFlow/wiseflow) 自发布以来,在开源社区内获得了不错的反响,这让我们在激动之余也不断思考它能与哪些具体的下游任务进行结合。
-[wiseflow](https://github.com/TeamWiseFlow/wiseflow)已经可以实现不断从网络(包括社交网络)中挖掘、提炼指定方向的知识,自然而然的,我们会想到将这种能力与AI Agent联合起来,这就是Awada项目的灵感!
+我们的第一个想法就是既然wiseflow已经可以实现不断从网络(包括社交网络)中挖掘、提炼指定方向的知识,那么这是否可以作为AI助理(Agent)的动态知识库,即**赋予助理在线自主学习能力**?
-
+本项目就是这样一个示例,考虑到目前国内不管是工作还是日常,大家主要的信息获取渠道就是微信(包括关注的公众号、参与的微信群聊等),并且用户与助理最自然、最舒服的交流也是直接使用微信,因此我们将awada定义为
+**基于微信的可在线自主学习的个人助理**。
+
+# 目前已经实现的效果
+
+项目目前还很前期,仅实现了通过[wxbot](https://github.com/jwping/wxbot)连接微信客户端,通过wiseflow实现信息的自动提取和存储两个主要模块, 实际上应该大做文章的Agent模块目前还完全未动。
+
+但是实现了一个简单的示例,可以每天从wiseflow数据库中摘取过去一天的信息并自动提炼整理成一份简报发给指定的用户。
+
+整体效果如下:
+
+
+
+# 声明
+
+**任何对本项目代码的使用、阅读、拷贝、修改、分发以及整合都被视为完全阅读并理解、接受如下各项声明,并且以上行为的所有后果均为使用者本人承担,与awada、wiseflow项目作者、贡献者、运营者无关!**
+
+ - 1、awada为开源学习项目,仅限个人用户技术交流,请勿用作任何商业用途或实际生产用途;
+ - 2、微信接入方案来自开源项目 [wxbot](https://github.com/jwping/wxbot), 不能保证稳定性与安全性(目前没有安全稳定的个微接入方案),请风险自担(**建议使用微信小号接入,接入前关闭所有支付相关和服务功能**);
+ - 3、微信软件的各项产权等归属腾讯公司;
+ - 4、再说一遍,风险自担,责任自担,与我无关;
+ - 5、don't be evil。
+
+# 操作说明(实现上图的效果)
+
+## 1、启动 wiseflow (目前整个分析后端和数据库都依赖wiseflow,awada不会另外维护这部分代码)
+
+具体见 [wiseflow README_CN.md](https://github.com/TeamWiseFlow/wiseflow/blob/master/README_CN.md)
+
+## 2、 启动 wxbot (微信机器人部分依赖wxbot,awada永远不会维护这部分代码)
+
+首先感谢wxbot项目作者 **jwping** !
+
+- windows用户
+
+ 在这里下载对应版本微信客户端和wxbot-sidecar.exe:阿里网盘: https://www.aliyundrive.com/s/4eiNnE4hp4n 提取码: rt25
+
+ 然后命令行运行
+
+ `.\wxbot-sidecar.exe -p 8066`
+
+ 也可以参考 https://github.com/jwping/wxbot?tab=readme-ov-file#231%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%A4%BA%E4%BE%8B
+ 在 wxbot-sidecar.exe 同级目录构建配置文件,文件内容只需要:
+
+` {
+ "addr": "0.0.0.0:8066"
+ }`
+
+ 这样之后就可以直接双击 wxbot-sidecar.exe 启动。
+
+- mac/linux 用户
+
+ 大神jwping很贴心的为我们准备了完整的 wine+微信客户端+wxbot-sidecar docker镜像,还上传到阿里云上了,所以使用起来也非常简单。
+
+ 具体可以参考 [这里](./About_WXbot.md)
+
+- 更多有关wxbot的问题请参考原repo
+
+ https://github.com/jwping/wxbot
+
+ 作者写的很详细,尤其是接口部分,希望大家能够顺手给作者打个赏。【Awada项目完全技术交流定位,无需任何打赏和回馈】
+
+## 3、启动awada程序
+
+**建议使用conda创建虚拟环境**
+
+```commandline
+git clone git@github.com:TeamWiseFlow/awada.git
+cd awada
+conda create -n awada python=3.10
+conda activate awada
+
+pip install -r requirements.txt
+python dm.py
+python tasks.py
+```
+
+dm.py 是消息接收和处理脚本;
+
+tasks.py 是每日简报生成和发送脚本,可以打开代码按需定制【发送时间、发送人列表以及消息格式等】,如无需要也可以不启动。
# Join Us
-awada的开发预计会在数月内启动,如果您也对此灵感迫不及待,下面是我们拟采用的一些技术栈。
+长期来看,awada有希望凭借**在线自主学习能力**成为**最贴心的私人助理**或者是**精通某个领域的专家顾问**,不过这还有很多功能有待开发,如下是一个构思图。
-- 个微接入(初期我们先仅考虑接入微信平台,通过微信群聊、指定的公众号进行学习,同时用户通过微信与bot进行对话): https://github.com/jwping/wxbot
-- 动态知识库(指定方向的知识挖掘、提炼与获取): https://github.com/TeamWiseFlow/wiseflow
-- Agent 框架。 这部分可选择的很多,如下是我们推荐的一些,您也可以选择您熟悉的框架或自写:
+
+
+当我最开始做这张图的时候,着实兴奋了好一阵,但无奈个人的时间精力总是有限,后续我还是会把主要精力花在wiseflow的升级和维护中(这也是awada的基础组件),而awada的Agent部分就留给开源社区的群策群力吧!
+
+_如下是我前期考察过的一些非常优秀的项目,其实很适合借鉴整合:_
- https://github.com/danswer-ai/danswer 特别适合从大量文档中查找特定信息,自带角色管理
- https://github.com/getzep/zep 特别擅长长期记忆管理与召回
- https://github.com/embedchain 非常简洁的RAG方案,开箱即用
@@ -20,10 +101,16 @@ awada的开发预计会在数月内启动,如果您也对此灵感迫不及待
- https://github.com/filip-michalsky/SalesGPT 适合“目的域对话”的Agent,可以实现打电话哦
- https://github.com/infiniflow/ragflow 完备的文档解析,独特的文档智能算法
----
+Agent部分的代码建议统一放在 [./agents](./agents) 文件夹中,其实目前的topnews和tasks也应该整合到这里。
+
+**如果您愿意将您的开发贡献至本项目,我们将不胜感激!**🚀💖
-**如果您愿意将您的开发贡献至本项目,我们将不胜感激!🚀💖**
+# Citation
-所有为本项目贡献代码的开发者将自动成为 Wiseflow 主项目的贡献者 (contributor) 🎉
+如果您在相关工作中参考或引用了本项目的部分或全部,请注明如下信息:
-基于 Wiseflow 的所有商业合作项目,均会面向 `Wiseflow contributor & core-maintainer` 发出专案邀请,项目收益将直接归属专案组 💼💰。
\ No newline at end of file
+```
+Author:Awada Team
+https://github.com/TeamWiseFlow/awada
+Licensed under Apache2.0
+```
diff --git a/asset/awada_demo.png b/asset/awada_demo.png
new file mode 100644
index 0000000..85c98ea
Binary files /dev/null and b/asset/awada_demo.png differ
diff --git a/dm.py b/dm.py
new file mode 100644
index 0000000..5667353
--- /dev/null
+++ b/dm.py
@@ -0,0 +1,120 @@
+import asyncio
+import websockets
+import json
+import requests
+
+
+# 先简单的用硬代码方案指定监控的群聊和私聊信息源,后续可以改进
+# 注意群聊写法,群号后面还必须带上 @chatroom
+watching_list = ['49591466778@chatroom', 'wxid_tnv0hd5hj3rs11']
+
+
+async def pipeline(input_data):
+ url = "http://127.0.0.1:8077/feed"
+ response = requests.post(url, json=input_data)
+ if response.status_code != 200:
+ print("Warning: Failed to send message. check wiseflow status")
+ print(response.text)
+
+
+# 对应不同的数据结构,考虑后续维护升级可能,分成两个函数
+async def get_public_msg(websocket_uri):
+ reconnect_attempts = 0
+ max_reconnect_attempts = 3
+ while True:
+ try:
+ async with websockets.connect(websocket_uri, max_size=10 * 1024 * 1024) as websocket:
+ while True:
+ response = await websocket.recv()
+ datas = json.loads(response)
+ for data in datas["data"]:
+ input_data = {
+ "user_id": data["StrTalker"],
+ "type": "publicMsg",
+ "content": data["Content"],
+ "addition": data["MsgSvrID"]
+ }
+ await pipeline(input_data)
+
+ except websockets.exceptions.ConnectionClosedError as e:
+ print(f"Connection closed with exception: {e}")
+ reconnect_attempts += 1
+ if reconnect_attempts <= max_reconnect_attempts:
+ print(f"Reconnecting attempt {reconnect_attempts}...")
+ await asyncio.sleep(1)
+ else:
+ print("Max reconnect attempts reached. Exiting.")
+ break
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
+ break
+
+
+async def get_general_msg(websocket_uri):
+ reconnect_attempts = 0
+ max_reconnect_attempts = 3
+ while True:
+ try:
+ async with websockets.connect(websocket_uri, max_size=10 * 1024 * 1024) as websocket:
+ while True:
+ response = await websocket.recv()
+ datas = json.loads(response)
+ print(datas)
+ print('\n')
+ for data in datas["data"]:
+ if data["IsSender"] == "1":
+ # 跳过自己发送的消息
+ continue
+ if data['StrTalker'] not in watching_list:
+ continue
+
+ # 目前仅处理文本消息和url(微信公众号分享卡片)两类消息
+ # 如需更多类型消息,请看 wxbot各类型信息原始json格式.txt
+ if data['Type'] == '1':
+ input_data = {
+ "user_id": data["StrTalker"],
+ "type": "text",
+ "content": data["StrContent"],
+ "addition": data["MsgSvrID"]
+ }
+ elif data['Type'] == '49':
+ if data['SubType'] != '5':
+ # 非文章形式的公众号消息,比如公众号发来的视频卡
+ continue
+ input_data = {
+ "user_id": data["StrTalker"],
+ "type": "url",
+ "content": data["Content"],
+ "addition": data["MsgSvrID"]
+ }
+ else:
+ continue
+ await pipeline(input_data)
+ except websockets.exceptions.ConnectionClosedError as e:
+ print(f"Connection closed with exception: {e}")
+ reconnect_attempts += 1
+ if reconnect_attempts <= max_reconnect_attempts:
+ print(f"Reconnecting attempt {reconnect_attempts}...")
+ await asyncio.sleep(1)
+ else:
+ print("Max reconnect attempts reached. Exiting.")
+ break
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
+ break
+
+
+async def main():
+ uri_general = "ws://127.0.0.1:8066/ws/generalMsg"
+ uri_public = "ws://127.0.0.1:8066/ws/publicMsg"
+
+ # 创建并行任务
+ task1 = asyncio.create_task(get_general_msg(uri_general))
+ task2 = asyncio.create_task(get_public_msg(uri_public))
+
+ # 等待所有任务完成
+ await asyncio.gather(task1, task2)
+
+
+# 使用asyncio事件循环运行main coroutine
+asyncio.run(main())
diff --git a/general_utils.py b/general_utils.py
new file mode 100644
index 0000000..527663d
--- /dev/null
+++ b/general_utils.py
@@ -0,0 +1,18 @@
+import os
+
+
+def get_logger_level() -> str:
+ level_map = {
+ 'silly': 'CRITICAL',
+ 'verbose': 'DEBUG',
+ 'info': 'INFO',
+ 'warn': 'WARNING',
+ 'error': 'ERROR',
+ }
+ level: str = os.environ.get('WS_LOG', 'info').lower()
+ if level not in level_map:
+ raise ValueError(
+ 'WiseFlow LOG should support the values of `silly`, '
+ '`verbose`, `info`, `warn`, `error`'
+ )
+ return level_map.get(level, 'info')
diff --git a/pb_api.py b/pb_api.py
new file mode 100644
index 0000000..69fcd42
--- /dev/null
+++ b/pb_api.py
@@ -0,0 +1,89 @@
+import os
+from pocketbase import PocketBase # Client also works the same
+from pocketbase.client import FileUpload
+from typing import BinaryIO
+
+
+class PbTalker:
+ def __init__(self, logger) -> None:
+ # 1. base initialization
+ url = os.environ.get('PB_API_BASE', "http://127.0.0.1:8090")
+ self.logger = logger
+ self.logger.debug(f"initializing pocketbase client: {url}")
+ self.client = PocketBase(url)
+ auth = os.environ.get('PB_API_AUTH', '')
+ if not auth or "|" not in auth:
+ self.logger.warnning("invalid email|password found, will handle with not auth, make sure you have set the collection rule by anyone")
+ else:
+ email, password = auth.split('|')
+ try:
+ admin_data = self.client.admins.auth_with_password(email, password)
+ if admin_data:
+ self.logger.info(f"pocketbase ready authenticated as admin - {email}")
+ except:
+ user_data = self.client.collection("users").auth_with_password(email, password)
+ if user_data:
+ self.logger.info(f"pocketbase ready authenticated as user - {email}")
+ else:
+ raise Exception("pocketbase auth failed")
+
+ def read(self, collection_name: str, fields: list[str] = None, filter: str = '', skiptotal: bool = True) -> list:
+ results = []
+ for i in range(1, 10):
+ try:
+ res = self.client.collection(collection_name).get_list(i, 500,
+ {"filter": filter,
+ "fields": ','.join(fields) if fields else '',
+ "skiptotal": skiptotal})
+
+ except Exception as e:
+ self.logger.error(f"pocketbase get list failed: {e}")
+ continue
+ if not res.items:
+ break
+ for _res in res.items:
+ attributes = vars(_res)
+ results.append(attributes)
+ return results
+
+ def add(self, collection_name: str, body: dict) -> str:
+ try:
+ res = self.client.collection(collection_name).create(body)
+ except Exception as e:
+ self.logger.error(f"pocketbase create failed: {e}")
+ return ''
+ return res.id
+
+ def update(self, collection_name: str, id: str, body: dict) -> str:
+ try:
+ res = self.client.collection(collection_name).update(id, body)
+ except Exception as e:
+ self.logger.error(f"pocketbase update failed: {e}")
+ return ''
+ return res.id
+
+ def delete(self, collection_name: str, id: str) -> bool:
+ try:
+ res = self.client.collection(collection_name).delete(id)
+ except Exception as e:
+ self.logger.error(f"pocketbase update failed: {e}")
+ return False
+ if res:
+ return True
+ return False
+
+ def upload(self, collection_name: str, id: str, key: str, file_name: str, file: BinaryIO) -> str:
+ try:
+ res = self.client.collection(collection_name).update(id, {key: FileUpload((file_name, file))})
+ except Exception as e:
+ self.logger.error(f"pocketbase update failed: {e}")
+ return ''
+ return res.id
+
+ def view(self, collection_name: str, item_id: str, fields: list[str] = None) -> dict:
+ try:
+ res = self.client.collection(collection_name).get_one(item_id, {"fields": ','.join(fields) if fields else ''})
+ return vars(res)
+ except Exception as e:
+ self.logger.error(f"pocketbase view item failed: {e}")
+ return {}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..d871c37
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+openai
+loguru
+pocketbase
+websockets
+pytz
+fastapi
+requests
\ No newline at end of file
diff --git a/speech/ASR_client.py b/speech/ASR_client.py
deleted file mode 100644
index ee79995..0000000
--- a/speech/ASR_client.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import pyaudio
-import websockets
-import asyncio
-from queue import Queue
-import argparse
-import json
-
-parser = argparse.ArgumentParser()
-parser.add_argument("--host", type=str, default="172.16.77.144", required=False, help="host ip, localhost, 0.0.0.0")
-parser.add_argument("--port", type=int, default=10194, required=False, help="grpc server port")
-parser.add_argument("--chunk_size", type=int, default=160, help="ms")
-parser.add_argument("--vad_needed", type=bool, default=True)
-args = parser.parse_args()
-
-voices = Queue()
-
-async def record():
- global voices
- FORMAT = pyaudio.paInt16
- CHANNELS = 1
- RATE = 16000
- CHUNK = int(RATE / 1000 * args.chunk_size)
-
- p = pyaudio.PyAudio()
-
- stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
-
- while True:
- data = stream.read(CHUNK)
- voices.put(data)
- await asyncio.sleep(0.01)
-
-async def ws_send(websocket):
- global voices
- print("Started sending data!")
- data_head = {
- 'vad_need': args.vad_needed,
- 'state': ''
- }
- await websocket.send(json.dumps(data_head))
-
- while True:
- while not voices.empty():
- data = voices.get()
- voices.task_done()
- try:
- await websocket.send(data)
- except Exception as e:
- print('Exception occurred:', e)
- return # Return to attempt reconnection
- await asyncio.sleep(0.01)
-
-async def message(websocket):
- while True:
- try:
- print(await websocket.recv())
- except Exception as e:
- print("Exception:", e)
- return # Return to attempt reconnection
-
-async def ws_client():
- uri = "ws://{}:{}".format(args.host, args.port)
- while True:
- try:
- async with websockets.connect(uri, subprotocols=["binary"], ping_interval=None) as websocket:
- task1 = asyncio.create_task(record())
- task2 = asyncio.create_task(ws_send(websocket))
- task3 = asyncio.create_task(message(websocket))
- await asyncio.gather(task1, task2, task3)
- except Exception as e:
- print("WebSocket connection failed: ", e)
- await asyncio.sleep(5) # Wait for 5 seconds before trying to reconnect
-
-asyncio.get_event_loop().run_until_complete(ws_client())
\ No newline at end of file
diff --git a/speech/ASR_server.py b/speech/ASR_server.py
deleted file mode 100644
index 20d25db..0000000
--- a/speech/ASR_server.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import asyncio
-import websockets
-import numpy as np
-from queue import Queue
-import threading
-import argparse
-import json
-from funasr import AutoModel
-import wave
-import numpy as np
-import tempfile
-import os
-
-# 设置日志级别
-import logging
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.CRITICAL)
-
-# 解析命令行参数
-parser = argparse.ArgumentParser()
-parser.add_argument("--host", type=str, default="0.0.0.0", help="host ip, localhost, 0.0.0.0")
-parser.add_argument("--port", type=int, default=10197, help="grpc server port")
-parser.add_argument("--ngpu", type=int, default=1, help="0 for cpu, 1 for gpu")
-args = parser.parse_args()
-
-# 初始化模型
-print("model loading")
-asr_model = AutoModel(model="paraformer-zh", model_revision="v2.0.4",
- vad_model="fsmn-vad", vad_model_revision="v2.0.4",
- punc_model="ct-punc-c", punc_model_revision="v2.0.4")
-print("model loaded")
-websocket_users = {}
-
-async def ws_serve(websocket, path):
- global websocket_users
- user_id = id(websocket)
- try:
- async for message in websocket:
- if isinstance(message, str):
- data = json.loads(message)
- if 'url' in data:
- await process_wav_file(websocket, data['url'], user_id)
- finally:
- if user_id in websocket_users:
- del websocket_users[user_id]
-
-async def process_wav_file(websocket, message, user_id):
- wav_path = message
- try:
- res = asr_model.generate(input=wav_path)
- os.remove(wav_path)
- if 'text' in res[0]:
- await websocket.send(res[0]['text'])
- except Exception as e:
- logger.error(f"Error during model.generate: {e}")
-
-def send_recognition_result(websocket, text):
- asyncio.run_coroutine_threadsafe(websocket.send(text), asyncio.get_event_loop())
-
-start_server = websockets.serve(ws_serve, args.host, args.port, subprotocols=["binary"], ping_interval=None)
-asyncio.get_event_loop().run_until_complete(start_server)
-asyncio.get_event_loop().run_forever()
\ No newline at end of file
diff --git a/speech/README.md b/speech/README.md
deleted file mode 100644
index ce98271..0000000
--- a/speech/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-这个repo整体上copy from https://github.com/xszyou/Fay/tree/fay-assistant-edition
-
-## 语音服务介绍
-
-该服务以modelscope funasr语音识别为基础
-
-
-## Install
-pip install torch
-pip install modelscope
-pip install testresources
-pip install websockets
-pip install torchaudio
-pip install inference_pipeline_asr
-pip install FunASR
-
-## Start server
-
-2、python -u ASR_server.py --host "0.0.0.0" --port 10197 --ngpu 0
-
-## Fay connect
-更改fay/system.conf配置项,并重新启动fay.
-
-https://www.bilibili.com/video/BV1qs4y1g74e/?share_source=copy_web&vd_source=64cd9062f5046acba398177b62bea9ad
-
-
-## Acknowledge
-感谢
-1. 中科大脑算法工程师张聪聪
-2. [cgisky1980](https://github.com/cgisky1980/FunASR)
-3. [modelscope](https://github.com/modelscope/modelscope)
-4. [FunASR](https://github.com/alibaba-damo-academy/FunASR)
-5. [Fay数字人助理](https://github.com/TheRamU/Fay).
diff --git a/speech/__init__.py b/speech/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/speech/data/hotword.txt b/speech/data/hotword.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/speech/funasr.py b/speech/funasr.py
deleted file mode 100644
index b9b0428..0000000
--- a/speech/funasr.py
+++ /dev/null
@@ -1,148 +0,0 @@
-"""
-感谢北京中科大脑神经算法工程师张聪聪提供funasr集成代码
-"""
-from threading import Thread
-import websocket
-import json
-import time
-import ssl
-import _thread as thread
-
-from core import wsa_server
-from utils import config_util as cfg
-from utils import util
-
-class FunASR:
- # 初始化
- def __init__(self):
- self.__URL = "ws://{}:{}".format(cfg.local_asr_ip, cfg.local_asr_port)
- self.__ws = None
- self.__connected = False
- self.__frames = []
- self.__state = 0
- self.__closing = False
- self.__task_id = ''
- self.done = False
- self.finalResults = ""
- self.__reconnect_delay = 1
- self.__reconnecting = False
-
-
-
- def __on_msg(self):
- pass
-
- # 收到websocket消息的处理
- def on_message(self, ws, message):
- try:
- self.done = True
- self.finalResults = message
- wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults})
- if not cfg.config["interact"]["playSound"]: # 非展板播放
- content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': self.finalResults}}
- wsa_server.get_instance().add_cmd(content)
- self.__on_msg()
-
- except Exception as e:
- print(e)
-
- if self.__closing:
- try:
- self.__ws.close()
- except Exception as e:
- print(e)
-
- # 收到websocket错误的处理
- def on_close(self, ws, code, msg):
- self.__connected = False
- util.log(1, f"### CLOSE:{msg}")
- self.__ws = None
- self.__attempt_reconnect()
-
- # 收到websocket错误的处理
- def on_error(self, ws, error):
- self.__connected = False
- util.log(1, f"### error:{error}")
- self.__ws = None
- self.__attempt_reconnect()
-
- #重连
- def __attempt_reconnect(self):
- if not self.__reconnecting:
- self.__reconnecting = True
- util.log(1, "尝试重连funasr...")
- while not self.__connected:
- time.sleep(self.__reconnect_delay)
- self.start()
- self.__reconnect_delay *= 2
- self.__reconnect_delay = 1
- self.__reconnecting = False
-
-
- # 收到websocket连接建立的处理
- def on_open(self, ws):
- self.__connected = True
-
- def run(*args):
- while self.__connected:
- try:
- if len(self.__frames) > 0:
- frame = self.__frames[0]
-
- self.__frames.pop(0)
- if type(frame) == dict:
- ws.send(json.dumps(frame))
- elif type(frame) == bytes:
- ws.send(frame, websocket.ABNF.OPCODE_BINARY)
- # print('发送 ------> ' + str(type(frame)))
- except Exception as e:
- print(e)
- time.sleep(0.04)
-
- thread.start_new_thread(run, ())
-
- def __connect(self):
- self.finalResults = ""
- self.done = False
- self.__frames.clear()
- websocket.enableTrace(False)
- self.__ws = websocket.WebSocketApp(self.__URL, on_message=self.on_message,on_close=self.on_close,on_error=self.on_error,subprotocols=["binary"])
- self.__ws.on_open = self.on_open
-
- self.__ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
-
- def add_frame(self, frame):
- self.__frames.append(frame)
-
- def send(self, buf):
- self.__frames.append(buf)
-
- def send_url(self, url):
- frame = {'url' : url}
- self.__ws.send(json.dumps(frame))
-
- def start(self):
- Thread(target=self.__connect, args=[]).start()
- data = {
- 'vad_need':False,
- 'state':'StartTranscription'
- }
- self.add_frame(data)
-
- def end(self):
- if self.__connected:
- try:
- for frame in self.__frames:
- self.__frames.pop(0)
- if type(frame) == dict:
- self.__ws.send(json.dumps(frame))
- elif type(frame) == bytes:
- self.__ws.send(frame, websocket.ABNF.OPCODE_BINARY)
- time.sleep(0.4)
- self.__frames.clear()
- frame = {'vad_need':False,'state':'StopTranscription'}
- self.__ws.send(json.dumps(frame))
- except Exception as e:
- print(e)
- self.__closing = True
- self.__connected = False
diff --git a/speech/ms_tts_sdk.py b/speech/ms_tts_sdk.py
deleted file mode 100644
index 6ebca19..0000000
--- a/speech/ms_tts_sdk.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import time
-
-import azure.cognitiveservices.speech as speechsdk
-import asyncio
-import sys
-sys.path.append("E:\\GitHub\\Fay\\")
-from core import tts_voice
-from core.tts_voice import EnumVoice
-from utils import util, config_util
-from utils import config_util as cfg
-import pygame
-import edge_tts
-
-
-
-
-class Speech:
- def __init__(self):
- self.ms_tts = False
- if config_util.key_ms_tts_key and config_util.key_ms_tts_key is not None and config_util.key_ms_tts_key.strip() != "":
- self.__speech_config = speechsdk.SpeechConfig(subscription=cfg.key_ms_tts_key, region=cfg.key_ms_tts_region)
- self.__speech_config.speech_recognition_language = "zh-CN"
- self.__speech_config.speech_synthesis_voice_name = "zh-CN-XiaoxiaoNeural"
- self.__speech_config.set_speech_synthesis_output_format(speechsdk.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3)
- self.__synthesizer = speechsdk.SpeechSynthesizer(speech_config=self.__speech_config, audio_config=None)
- self.ms_tts = True
- self.__connection = None
- self.__history_data = []
-
-
- def __get_history(self, voice_name, style, text):
- for data in self.__history_data:
- if data[0] == voice_name and data[1] == style and data[2] == text:
- return data[3]
- return None
-
- def connect(self):
- if self.ms_tts:
- self.__connection = speechsdk.Connection.from_speech_synthesizer(self.__synthesizer)
- self.__connection.open(True)
- util.log(1, "TTS 服务已经连接!")
-
- def close(self):
- if self.__connection is not None:
- self.__connection.close()
-
- #生成mp3音频
- async def get_edge_tts(self,text,voice,file_url) -> None:
- communicate = edge_tts.Communicate(text, voice)
- await communicate.save(file_url)
-
- """
- 文字转语音
- :param text: 文本信息
- :param style: 说话风格、语气
- :returns: 音频文件路径
- """
-
- def to_sample(self, text, style):
- if self.ms_tts:
- voice_type = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
- voice_name = EnumVoice.XIAO_XIAO.value["voiceName"]
- if voice_type is not None:
- voice_name = voice_type.value["voiceName"]
- history = self.__get_history(voice_name, style, text)
- if history is not None:
- return history
- ssml = '' \
- '' \
- '' \
- '{}' \
- '' \
- '' \
- ''.format(voice_name, style, 1.8, text)
- result = self.__synthesizer.speak_ssml(ssml)
- audio_data_stream = speechsdk.AudioDataStream(result)
-
- file_url = './samples/sample-' + str(int(time.time() * 1000)) + '.mp3'
- audio_data_stream.save_to_wav_file(file_url)
- if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
- self.__history_data.append((voice_name, style, text, file_url))
- return file_url
- else:
- util.log(1, "[x] 语音转换失败!")
- util.log(1, "[x] 原因: " + str(result.reason))
- return None
- else:
- voice_type = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
- voice_name = EnumVoice.XIAO_XIAO.value["voiceName"]
- if voice_type is not None:
- voice_name = voice_type.value["voiceName"]
- history = self.__get_history(voice_name, style, text)
- if history is not None:
- return history
- ssml = '' \
- '' \
- '' \
- '{}' \
- '' \
- '' \
- ''.format(voice_name, style, 1.8, text)
- try:
- file_url = './samples/sample-' + str(int(time.time() * 1000)) + '.mp3'
- asyncio.new_event_loop().run_until_complete(self.get_edge_tts(text,voice_name,file_url))
- self.__history_data.append((voice_name, style, text, file_url))
- except Exception as e :
- util.log(1, "[x] 语音转换失败!")
- util.log(1, "[x] 原因: " + str(str(e)))
- file_url = None
- return file_url
-
-
-if __name__ == '__main__':
- cfg.load_config()
- sp = Speech()
- sp.connect()
- text = "我叫Fay,我今年18岁,很年青。"
- s = sp.to_sample(text, "cheerful")
-
- print(s)
- sp.close()
-
diff --git a/speech/xf_ltp.py b/speech/xf_ltp.py
deleted file mode 100644
index dd72300..0000000
--- a/speech/xf_ltp.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import time
-import urllib.request
-import urllib.parse
-import json
-import hashlib
-import base64
-from utils import config_util as cfg
-
-__URL = "https://ltpapi.xfyun.cn/v2/sa"
-
-
-def __quest(text):
- body = urllib.parse.urlencode({'text': text}).encode('utf-8')
- param = {"type": "dependent"}
- x_param = base64.b64encode(json.dumps(param).replace(' ', '').encode('utf-8'))
- x_time = str(int(time.time()))
- x_checksum = hashlib.md5(cfg.key_xf_ltp_api_key.encode('utf-8') + str(x_time).encode('utf-8') + x_param).hexdigest()
- x_header = {
- 'X-Appid': cfg.key_xf_ltp_app_id,
- 'X-CurTime': x_time,
- 'X-Param': x_param,
- 'X-CheckSum': x_checksum
- }
- req = urllib.request.Request(__URL, body, x_header)
- result = urllib.request.urlopen(req)
- result = result.read()
- return json.loads(result.decode('utf-8'))
-
-
-"""
-情感分析
-
-:param text: 文本
-
-:returns: 情感分数 (0.7以上为褒义, 0.3-0.7为中性 0.3以下为贬义,, -1为分析失败)
-"""
-
-
-def get_score(text):
- result = __quest(text)
- if result['desc'] == 'success':
- return float(result['data']['score'])
- return -1
-
-
-"""
-情感分析
-
-:param text: 文本
-
-:returns: 情感极性分类 (2为褒义, 1为中性 0为贬义,, -1为分析失败)
-"""
-
-
-def get_sentiment(text):
- result = __quest(text)
- if result['desc'] == 'success':
- return result['data']['sentiment']
- return 0
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 0000000..97360a2
--- /dev/null
+++ b/tasks.py
@@ -0,0 +1,123 @@
+"""
+通过编辑这个脚本,可以自定义需要的后台任务
+"""
+import schedule
+import time
+from topnews import pipeline
+from loguru import logger
+from pb_api import PbTalker
+import os
+from general_utils import get_logger_level
+from datetime import datetime, timedelta
+import pytz
+import requests
+
+
+talking_list = ['wxid_tnv0hd5hj3rs11']
+
+project_dir = os.environ.get("PROJECT_DIR", "")
+if project_dir:
+ os.makedirs(project_dir, exist_ok=True)
+logger_file = os.path.join(project_dir, 'awada_tasks.log')
+
+logger.add(
+ logger_file,
+ level=get_logger_level(),
+ backtrace=True,
+ diagnose=True,
+ rotation="50 MB"
+)
+
+pb = PbTalker(logger)
+utc_now = datetime.now(pytz.utc)
+# 减去一天得到前一天的UTC时间
+utc_yesterday = utc_now - timedelta(days=1)
+utc_last = utc_yesterday.strftime("%Y-%m-%d %H:%M:%S")
+reported_urls = set()
+
+
+def task():
+ global utc_last
+ global reported_urls
+ logger.debug(f'last_collect_time: {utc_last}')
+ datas = pb.read(collection_name='insights', filter=f'updated>="{utc_last}"', fields=['id', 'content', 'tag', 'articles'])
+ logger.debug(f"got {len(datas)} items")
+ utc_last = datetime.now(pytz.utc).strftime("%Y-%m-%d %H:%M:%S")
+ logger.debug(f'now_utc_time: {utc_last}')
+
+ tags = pb.read(collection_name='tags', filter=f'activated=True')
+ tags_dict = {item["id"]: item["name"] for item in tags if item["name"]}
+ top_news = {}
+ for _id, name in tags_dict.items():
+ logger.debug(f'tag: {name}')
+ data = [item for item in datas if item["tag"] == _id]
+ logger.debug(f"got {len(data)} items")
+ topnew = pipeline(data, logger)
+ if not topnew:
+ logger.debug(f'no top news for {name}')
+ continue
+
+ top_news[name] = {}
+ for content, articles in topnew.items():
+ content_urls = [pb.read('articles', filter=f'id="{a}"', fields=['url'])[0]['url'] for a in articles]
+ # 存在报道过的url则跳过
+ if not set(content_urls).isdisjoint(reported_urls):
+ logger.debug(f'{content} has reported urls')
+ continue
+ # 去除重叠内容
+ # 如果发现重叠内容,哪个标签长就把对应的从哪个标签删除
+ to_skip = False
+ for k, v in top_news.items():
+ to_del_key = None
+ for c, u in v.items():
+ if not set(content_urls).isdisjoint(set(u)):
+ if len(topnew) > len(v):
+ to_skip = True
+ else:
+ to_del_key = c
+ break
+ if to_del_key:
+ del top_news[k][to_del_key]
+ if to_skip:
+ break
+ if not to_skip:
+ top_news[name][content] = content_urls
+ reported_urls.update(content_urls)
+
+ if not top_news[name]:
+ del top_news[name]
+
+ if not top_news:
+ logger.info("no top news today")
+ return
+
+ for name, v in top_news.items():
+ if not v:
+ continue
+ top_news[name] = {content: '\n\n'.join(urls) for content, urls in v.items()}
+ top_news[name] = "\n".join(f"{content}\n{urls}" for content, urls in top_news[name].items())
+
+ top_news_text = "\n\n".join(f"{k}\n{v}" for k, v in top_news.items())
+ logger.info(top_news_text)
+
+ for wxid in talking_list:
+ data = {
+ "wxid": wxid,
+ "content": top_news_text
+ }
+ try:
+ response = requests.post("http://127.0.0.1:8066/api/sendtxtmsg", json=data)
+ if response.status_code == 200:
+ logger.info("send message to wechat success")
+ else:
+ logger.error(f"send message to wechat failed: {response.text}")
+ except Exception as e:
+ logger.error(f"send message to wechat failed: {e}")
+ time.sleep(1)
+
+
+task()
+schedule.every().day.at("07:38").do(task)
+while True:
+ schedule.run_pending()
+ time.sleep(60)
diff --git a/topnews/__init__.py b/topnews/__init__.py
new file mode 100644
index 0000000..367eb39
--- /dev/null
+++ b/topnews/__init__.py
@@ -0,0 +1,63 @@
+from .openai_wrapper import openai_llm
+# import re
+
+
+system_prompt = '''你是一名家长,你很关注自己孩子的升学问题。你将被给到一个字典格式的新闻列表,每一个元素代表一条新闻,key是该条新闻的id,value是该条新闻的内容。
+请从给到的新闻列表中精选出最值得你关注的一条,并给出它的id。
+
+请仅输出id,不要输出其他信息。'''
+
+# 实测yi:9b-chat-v1.5-fp16 可以很好的支持40条(关注点分布均匀)
+batch_size = 39
+mini_batch_size = 7
+model = '01-ai/Yi-1.5-9B-Chat-16K'
+# pattern = re.compile(r'\"\"\"(.*?)\"\"\"', re.DOTALL)
+
+
+def pipeline(data: list, logger=None) -> dict:
+ if not data:
+ logger.info('empty data')
+ return {}
+
+ if len(data) <= mini_batch_size:
+ logger.info(f'datas less than mini_batch_size: {mini_batch_size}, will return all')
+ return {d['content']: d['articles'] for d in data}
+
+ results = {}
+ articles = {}
+ maps = {}
+ for d in data:
+ articles[d['id']] = d['content']
+ maps[d['id']] = d['articles']
+ if len(articles) > batch_size:
+ result = openai_llm(
+ [{'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': str(articles)}],
+ model, logger)
+ try:
+ # parsed = pattern.findall(result)[0]
+ parsed = result.strip()
+ logger.debug(f'parsed: {parsed}')
+ if parsed in articles:
+ results[articles[parsed]] = maps[parsed]
+ else:
+ logger.warning(f'bad result: {result}')
+ except Exception as e:
+ logger.warning(f'bad request: {e}')
+ articles = {}
+ maps = {}
+
+ if articles:
+ result = openai_llm(
+ [{'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': str(articles)}], model, logger)
+ try:
+ # parsed = pattern.findall(result)[0]
+ parsed = result.strip()
+ logger.debug(f'parsed: {parsed}')
+ if parsed in articles:
+ results[articles[parsed]] = maps[parsed]
+ else:
+ logger.warning(f'bad result: {result}')
+ except Exception as e:
+ logger.warning(f'bad request: {e}')
+
+ return results
diff --git a/topnews/openai_wrapper.py b/topnews/openai_wrapper.py
new file mode 100644
index 0000000..b22481e
--- /dev/null
+++ b/topnews/openai_wrapper.py
@@ -0,0 +1,33 @@
+import os
+from openai import OpenAI
+
+
+base_url = os.environ.get('LLM_API_BASE', "")
+token = os.environ.get('LLM_API_KEY', "")
+
+if token:
+ client = OpenAI(api_key=token, base_url=base_url)
+else:
+ client = OpenAI(base_url=base_url)
+
+
+def openai_llm(messages: list, model: str, logger=None, **kwargs) -> str:
+
+ if logger:
+ logger.debug(f'messages:\n {messages}')
+ logger.debug(f'model: {model}')
+ logger.debug(f'kwargs:\n {kwargs}')
+
+ try:
+ response = client.chat.completions.create(messages=messages, model=model, **kwargs)
+
+ except Exception as e:
+ if logger:
+ logger.error(f'openai_llm error: {e}')
+ return ''
+
+ if logger:
+ logger.debug(f'result:\n {response.choices[0]}')
+ logger.debug(f'usage:\n {response.usage}')
+
+ return response.choices[0].message.content
diff --git "a/wxbot\345\220\204\346\266\210\346\201\257\345\216\237\345\247\213Json\346\240\274\345\274\217.txt" "b/wxbot\345\220\204\346\266\210\346\201\257\345\216\237\345\247\213Json\346\240\274\345\274\217.txt"
new file mode 100644
index 0000000..a2c0add
--- /dev/null
+++ "b/wxbot\345\220\204\346\266\210\346\201\257\345\216\237\345\247\213Json\346\240\274\345\274\217.txt"
@@ -0,0 +1,84 @@
+text
+
+{'data': [{'BytesExtra': '+CjwvbXNnc291cmNlPgoaJAgCEiA4N2VjMDg1N2EzNGUwMmQ2ZTY4YzhmOWI1YWMwZTQyMQ==', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347001', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907299', 'MsgServerSeq': '1', 'MsgSvrID': '8924069721528294415', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347001000', 'Status': '2', 'StatusEx': '0', 'StrContent': '大大方方个',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '1', 'localId': '4'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+image
+
+{'data': [{'BytesExtra': 'C4DEn93eGlkX2JobThiNzZhbHhycjIyXEZpbGVTdG9yYWdlXE1zZ0F0dGFjaFxlN0', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347024', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907300', 'MsgServerSeq': '1', 'MsgSvrID': '3001419079783321478', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347024000', 'Status': '11', 'StatusEx': '0', 'StrContent': '\n\n\t\n\t\n\t\n\t\n\t\t\n\t\t0\n\t\n\n',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '3', 'localId': '5'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+video
+
+{'data': [{'BytesExtra': 'CgQIEBAAGs0DCAcSyAM8bXNnc291cmNlPgogICAgPHNlY19tc2dfbm9kZT4KICAgICAgICA8dXVpZD42OTdlOGYxMmVjMjA3MTA3Y2ZiMDI4YWFjNjI3MTc4MV88L3V1aWQ+CiAgICAgICAgPHJpc2stZmlsZS1mbGFnIC8+CiAgICAgICAgPHJpc2stZmlsZS1tZDUtbGlzdCAvPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgogICAgPHZpZGVvbXNnX3BkIGNkbnZpZGVvdXJsX3NpemU9IjM1OTIyNSIgY2RudmlkZW91cmxfc2NvcmVfYWxsPSIiIGNkbnZpZGVvdXJsX3BkX3ByaT0iMzAiIGNkbnZpZGVvdXJsX3BkPSIwIiAvPgogICAgPHNpZ25hdHVyZT5WMV8rOWl6Z2g0ZHx2MV9pZmZ5OG9YdDwvc2lnbmF0dXJlPgogICAgPHRtcF9ub2RlPgogICAgICAgIDxwdWJsaXNoZXItaWQgLz4KICAgIDwvdG1wX25vZGU+CjwvbXNnc291cmNlPgoaJAgCEiBhYmI0NmUxYzMyYWIyZTAwOGNkMGE2ZDM5YzFlOGRhMhpWCAMSUnd4aWRfYmhtOGI3NmFseHJyMjJcRmlsZVN0b3JhZ2VcVmlkZW9cMjAyNC0wN1w5NzU0MDBlM2M5MzliZDRlNTAzYzMzNTJmZGQ4YzY5NC5qcGc=', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347035', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907301', 'MsgServerSeq': '1', 'MsgSvrID': '7679361945685874935', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347035000', 'Status': '9', 'StatusEx': '0', 'StrContent': '\n\n\t\n\n',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '43', 'localId': '6'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+语音通话
+
+{'data': [{'BytesExtra': 'CgQIEBAACgQIBRABGt4BCAcS2QE8bXNnc291cmNlPgogICAgPHNpZ25hdHVyZT52MV95NFhRQStZYjwvc2lnbmF0dXJlPgogICAgPHRtcF9ub2RlPgogICAgICAgIDxwdWJsaXNoZXItaWQgLz4KICAgIDwvdG1wX25vZGU+CiAgICA8c2VjX21zZ19ub2RlPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgNDI4MjQ2MWUxYzY1OGY2MThiY2RhMzI2YmY5MGIxNWQ=', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347038', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907302', 'MsgServerSeq': '1', 'MsgSvrID': '2285040806868083059', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347038000', 'Status': '2', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '10000', 'localId': '7'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+语音通话取消
+
+{'data': [{'BytesExtra': 'CgQIBRABGhcIARITd3hpZF90bnYwaGQ1aGozcnMxMRoFCAMSATEaJAgCEiBiYjZjNDBiNWU5MjBmNzA2OWE5YzRlZDNiYzIzNzY1Ng==', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347041', 'DisplayContent': '对方已取消', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '0', 'MsgServerSeq': '0', 'MsgSvrID': '6183518831177677692', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347041001', 'Status': '2', 'StatusEx': '0', 'StrContent': '\n1\ntrue\n88991908\n7670557822181775208\n1720347038\n100\n1720347041321\n\n0\n1720347038\n0\n',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '50', 'localId': '8'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+location
+
+{'data': [{'BytesExtra': 'CgQIEBAAGuoBCAcS5QE8bXNnc291cmNlPgogICAgPHNpZ25hdHVyZT5WMV83VnMyWVU2c3x2MV9GYk9IL2ZoZTwvc2lnbmF0dXJlPgogICAgPHRtcF9ub2RlPgogICAgICAgIDxwdWJsaXNoZXItaWQgLz4KICAgIDwvdG1wX25vZGU+CiAgICA8c2VjX21zZ19ub2RlPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgODcxNjFiYWIyMDc0OTNkYTJkZGE1NGM5MjZiNDk3YzI=', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347054', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907309', 'MsgServerSeq': '1', 'MsgSvrID': '5412702201599786230', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347054000', 'Status': '2', 'StatusEx': '0', 'StrContent': '\n\n\t\n\n',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '48', 'localId': '10'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+红包
+
+{'data': [{'BytesExtra': 'CgQIEBAAGoYCCAcSgQI8bXNnc291cmNlPgogICAgPHB1c2hrZXkgLz4KICAgIDxNb2RpZnlNc2dBY3Rpb24gLz4KICAgIDxzaWduYXR1cmU+djFfcEtnL0l2UUo8L3NpZ25hdHVyZT4KICAgIDx0bXBfbm9kZT4KICAgICAgICA8cHVibGlzaGVyLWlkIC8+CiAgICA8L3RtcF9ub2RlPgogICAgPHNlY19tc2dfbm9kZT4KICAgICAgICA8YWxub2RlPgogICAgICAgICAgICA8ZnI+MTwvZnI+CiAgICAgICAgPC9hbG5vZGU+CiAgICA8L3NlY19tc2dfbm9kZT4KPC9tc2dzb3VyY2U+ChokCAISIDAxZWE4YzVmZjhiMmJhMmI1MDVkN2VkMDMyNWUzYTAz', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347064', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907310', 'MsgServerSeq': '1', 'MsgSvrID': '333533209164441377', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347064000', 'Status': '2', 'StatusEx': '0', 'StrContent': '收到红包,请在手机上查看',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '10000', 'localId': '11'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+转账
+
+{'data': [{'BytesExtra': 'CgQIEBAAGuoBCAcS5QE8bXNnc291cmNlPgogICAgPHNpZ25hdHVyZT5WMV9oRWVYUFB1dXx2MV9pOHY3Y2lNbTwvc2lnbmF0dXJlPgogICAgPHRtcF9ub2RlPgogICAgICAgIDxwdWJsaXNoZXItaWQgLz4KICAgIDwvdG1wX25vZGU+CiAgICA8c2VjX21zZ19ub2RlPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgOGUzN2MzNDA0ZjAwOTIzMWFiNjFiYTE4MTU2YWEzMWEaUggEEk53eGlkX2JobThiNzZhbHhycjIyXEZpbGVTdG9yYWdlXENhY2hlXDIwMjQtMDdcOTFhNzM4OWE5NzBhNGY2NDMyZTFmM2JlYjYzZjlhNTk=', 'BytesTrans': '', 'Content': '\n\n\n\n\n2000\n\n\n\n\n\n\n\n1\n\n\n\n\n\n\n\n\n\n\n\n\n\n\x00', 'CreateTime': '1720347077', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907311', 'MsgServerSeq': '1', 'MsgSvrID': '7158704586676127451', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347077000', 'Status': '2', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '2000', 'TalkerId': '3', 'Type': '49', 'localId': '12'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+url(卡片)
+
+{'data': [{'BytesExtra': 'CgQIEBAAGokDCAcShAM8bXNnc291cmNlPgogICAgPGFsbm9kZT4KICAgICAgICA8ZnI+MjwvZnI+CiAgICA8L2Fsbm9kZT4KICAgIDxzZWNfbXNnX25vZGU+CiAgICAgICAgPHV1aWQ+YzVkOWY0ZDI0MWFjMGViMjgzMDIyMGU3MDFkN2NlZTRfPC91dWlkPgogICAgICAgIDxyaXNrLWZpbGUtZmxhZyAvPgogICAgICAgIDxyaXNrLWZpbGUtbWQ1LWxpc3QgLz4KICAgICAgICA8YWxub2RlPgogICAgICAgICAgICA8ZnI+MTwvZnI+CiAgICAgICAgPC9hbG5vZGU+CiAgICA8L3NlY19tc2dfbm9kZT4KICAgIDxzaWduYXR1cmU+VjFfa0tUNm93S3p8djFfR1ZtUG8zd0I8L3NpZ25hdHVyZT4KICAgIDx0bXBfbm9kZT4KICAgICAgICA8cHVibGlzaGVyLWlkIC8+CiAgICA8L3RtcF9ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgMTc5NzkwMGI1ZThmYjBlZDk1ZjI4OTg0YzhjNWY4NWIaVggEElJ3eGlkX2JobThiNzZhbHhycjIyXEZpbGVTdG9yYWdlXENhY2hlXDIwMjQtMDdcOTM5ZmJmNTVhOGI2ZWUyMTA5MzI0ODI1YjA1MGExMjMuanBnGlgIAxJUd3hpZF9iaG04Yjc2YWx4cnIyMlxGaWxlU3RvcmFnZVxDYWNoZVwyMDI0LTA3XDkzOWZiZjU1YThiNmVlMjEwOTMyNDgyNWIwNTBhMTIzX3QuanBn', 'BytesTrans': '', 'Content': '\n\n\t\n\t\t2024年中国虚拟现实(VR)行业研究报告\n\t\t作为新一代信息技术的重要前沿方向与数字经济的重大前瞻领域,VR产业受到国家与各地方政府层面的高度重视,本报告将通过分析行业发展现状与产业核心环节,对行业特征、产业格局、应用场景及发展难点进行整体把握,讨论软件生态的发展潜力及应用价值。\n\t\t\n\t\tview\n\t\t5\n\t\t0\n\t\t\n\t\thttp://mp.weixin.qq.com/s?__biz=MjM5OTIzNzQwMA==&mid=2650493847&idx=1&sn=3f31b99ccc560f5721ec4b71713173ff&chksm=bf3171e08846f8f6decb11672db0e60e26c8eef467844f95907c0c803be3d1a4327d62793062&mpshare=1&scene=1&srcid=0627O39PxBJhjGtWzim1N5wn&sharer_shareinfo=2ff78adeedf0d2d287400c96db69e368&sharer_shareinfo_first=2ff78adeedf0d2d287400c96db69e368#rd\n\t\t\n\t\t0\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t3057020100044b30490201000204260e743602032f7e370204119b87750204668a69d3042439336137386433332d353065622d343235362d626633342d3961383437373166373434660204051808030201000405004c575f00\n\t\t\t0d30878ef5b1d40b8cce60fcb7b0f9d2\n\t\t\t12231\n\t\t\t120\n\t\t\t120\n\t\t\td8f4fa576c22f62cde68b58022512b9e\n\t\t\td8f4fa576c22f62cde68b58022512b9e\n\t\t\t1\n\t\t\t\n\t\t\t0\n\t\t\n\t\t\n\t\t3\n\t\tgh_2cc2df5ab017\n\t\t艾瑞咨询\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\tnull\n\t\t\t\n\t\t\tnull\n\t\t\t0\n\t\t\tnull\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t0d30878ef5b1d40b8cce60fcb7b0f9d2\n\t\t\n\t\t\t0\n\t\t\t0\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\t\n\t\t\t\t120\n\t\t\t\t120\n\t\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t0\n\t\t\n\t\t\n\t\t\t0\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\tfalse\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t-1\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t-1\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\n\t\t\n\t\t\tnull\n\t\t\t\n\t\t\t\n\t\t\n\t\n\twxid_tnv0hd5hj3rs11\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n\x00', 'CreateTime': '1720347091', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907314', 'MsgServerSeq': '1', 'MsgSvrID': '6294116200158660269', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347091000', 'Status': '2', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '5', 'TalkerId': '3', 'Type': '49', 'localId': '14'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+联系人卡片
+
+{'data': [{'BytesExtra': 'CgQIEBAAGuoBCAcS5QE8bXNnc291cmNlPgogICAgPHNpZ25hdHVyZT5WMV9tWTNwRHdtL3x2MV9pZjc5RC90MDwvc2lnbmF0dXJlPgogICAgPHRtcF9ub2RlPgogICAgICAgIDxwdWJsaXNoZXItaWQgLz4KICAgIDwvdG1wX25vZGU+CiAgICA8c2VjX21zZ19ub2RlPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgYmRjZjg4MDQyNzRkNzJmOTVhODFjYmVlZmI1MDdkZWI=', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720347098', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907315', 'MsgServerSeq': '1', 'MsgSvrID': '7747969616297950870', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347098000', 'Status': '2', 'StatusEx': '0', 'StrContent': '\n\n',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '0', 'TalkerId': '3', 'Type': '42', 'localId': '15'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+file(卡片)
+
+{'data': [{'BytesExtra': 'CgQIEBAAGs4DCAcSyQM8bXNnc291cmNlPgogICAgPHNlY19tc2dfbm9kZT4KICAgICAgICA8dXVpZD4zM2RiMzY3MzUwMTY3ZDc1MzdjNjQ3ZTc5ZjhhMTk3MF88L3V1aWQ+CiAgICAgICAgPHJpc2stZmlsZS1mbGFnIC8+CiAgICAgICAgPHJpc2stZmlsZS1tZDUtbGlzdCAvPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgogICAgPGFwcG1zZ19wZCBjZG5hdHRhY2h1cmxfc2l6ZT0iODU0OTAiIGNkbmF0dGFjaHVybF9zY29yZV9hbGw9IiIgY2RuYXR0YWNodXJsX3BkX3ByaT0iNTAiIGNkbmF0dGFjaHVybF9wZD0iMSIgLz4KICAgIDxzaWduYXR1cmU+VjFfeTBFR2dxMjZ8djFfKzZwNmNlQ0U8L3NpZ25hdHVyZT4KICAgIDx0bXBfbm9kZT4KICAgICAgICA8cHVibGlzaGVyLWlkIC8+CiAgICA8L3RtcF9ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgMmIyMThmYzNlMzZmMGM2Y2RmNmYzYmQ2Mjc2MTI5ZDAalQEIBBKQAXd4aWRfYmhtOGI3NmFseHJyMjJcRmlsZVN0b3JhZ2VcRmlsZVwyMDI0LTA3XGR6ZnBfMjQzMTIwMDAwMDAyMDIwNzQ0NThf5LiK5rW35biC6Z2Z5a6J5Yy65Lq65rCR5pS/5bqc5Li05rG+6Lev6KGX6YGT5Yqe5LqL5aSEXzIwMjQwNzA2MjExODE4LnBkZg==', 'BytesTrans': '', 'Content': '\n\n\t\n\t\tdzfp_24312000000202074458_上海市静安区人民政府临汾路街道办事处_20240706211818.pdf\n\t\t\n\t\t\n\t\tview\n\t\t6\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t@cdn_3057020100044b30490201000204e68e2f0402032f7e3702040c9b87750204668a69e1042436366461626538342d353137632d343336312d613630632d6437616131653664396262640204051400050201000405004c4d9b00_0dfa4c5a9b72c4ba96ce2352cdb747b2_1\n\t\t\t3057020100044b30490201000204e68e2f0402032f7e3702040c9b87750204668a69e1042436366461626538342d353137632d343336312d613630632d6437616131653664396262640204051400050201000405004c4d9b00\n\t\t\t85490\n\t\t\t0dfa4c5a9b72c4ba96ce2352cdb747b2\n\t\t\t1\n\t\t\tpdf\n\t\t\t0\n\t\t\t3682663348882335933\n\t\t\t\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\tnull\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t931d6b2d0d58529f62e5063a7f274ab8\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\t\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\tfalse\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t-1\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\n\t\t\n\t\t\tnull\n\t\t\t\n\t\t\t\n\t\t\n\t\n\twxid_tnv0hd5hj3rs11\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n\x00', 'CreateTime': '1720347105', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907316', 'MsgServerSeq': '1', 'MsgSvrID': '7301538462322792909', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347105000', 'Status': '11', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '6', 'TalkerId': '3', 'Type': '49', 'localId': '16'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+音乐卡片
+
+{'data': [{'BytesExtra': 'CgQIEBAAGokDCAcShAM8bXNnc291cmNlPgogICAgPGFsbm9kZT4KICAgICAgICA8ZnI+MjwvZnI+CiAgICA8L2Fsbm9kZT4KICAgIDxzZWNfbXNnX25vZGU+CiAgICAgICAgPHV1aWQ+Y2JlOWM0MGRhMTNjYWE1N2RkZGU0N2JjMDlmOGEyMWJfPC91dWlkPgogICAgICAgIDxyaXNrLWZpbGUtZmxhZyAvPgogICAgICAgIDxyaXNrLWZpbGUtbWQ1LWxpc3QgLz4KICAgICAgICA8YWxub2RlPgogICAgICAgICAgICA8ZnI+MTwvZnI+CiAgICAgICAgPC9hbG5vZGU+CiAgICA8L3NlY19tc2dfbm9kZT4KICAgIDxzaWduYXR1cmU+VjFfS1RPdE1RcXR8djFfWkhwalVlZGM8L3NpZ25hdHVyZT4KICAgIDx0bXBfbm9kZT4KICAgICAgICA8cHVibGlzaGVyLWlkIC8+CiAgICA8L3RtcF9ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgODRiN2Y2YmY0YTc4ZjMzZWMzOWJkNjg5MjdkODViMGMaUggEEk53eGlkX2JobThiNzZhbHhycjIyXEZpbGVTdG9yYWdlXENhY2hlXDIwMjQtMDdcYjRkMmQ3YjNjMjNmODY0N2FlN2Q0MDI0YzgxMjk1ZmQ=', 'BytesTrans': '', 'Content': '\n\t\n\t\t偷心\n\t\t张学友\n\t\t\n\t\tview\n\t\t3\n\t\t0\n\t\t\n\t\thttp://c.y.qq.com/v8/playsong.html?songmid=004IShrs2AZZIL\n\t\t\n\t\t0\n\t\thttp://isure6.stream.qqmusic.qq.com/RS02063F3PHR0RMe9M.mp3?guid=2000000049&vkey=76E4B1A8C84AADBCB42255B3F89E610B0A2031DDA7650DF96D53ED8B9422184C561C4F17DFC4780C510B683BEB007B7DB6DCA2EF28A7F07D&uin=0&fromtag=20049&trace=3b05f281d16332f1\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\tnull\n\t\t\tnull\n\t\t\t\n\t\t\tnull\n\t\t\t0\n\t\t\tnull\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t0\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\t\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\n\t\t\n\t\tGhQKEnd4NWFhMzMzNjA2NTUwZGZkNQ==\n\t\thttp://wx.y.gtimg.cn/music/photo_new/T002R500x500M000003jh8m13nL4IH_5.jpg\n\t\t[ti:偷心]\n[ar:张学友]\n[al:偷心]\n[by:]\n[offset:0]\n[00:00.00]偷心 - 张学友 (Jacky Cheung)\n[00:02.99]词:丁晓雯\n[00:05.99]曲:郭蘅祈\n[00:08.99]编曲:袁卓凡\n[00:11.99]制作人:黄庆元\n[00:14.99]是寂寞慢慢占领我的心\n[00:21.82]长夜里越来越冷清\n[00:25.67]回忆里越来越孤寂\n[00:29.05]是后悔慢慢侵蚀我的心\n[00:36.22]抹去了最后的泪滴\n[00:39.31]从今后不再为谁哭泣\n[00:50.70]我不敢再问是什么改变你的眼神\n[00:57.70]对爱厌倦对爱疲惫\n\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\tfalse\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t0\n\t\t\t\t0\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\n\t\t0\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t-1\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t-1\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\n\t\t\n\t\t\tnull\n\t\t\t\n\t\t\t\n\t\t\n\t\n\twxid_tnv0hd5hj3rs11\n\t0\n\t\n\t\t53\n\t\tQQ音乐\n\t\n\t\n\n\x00', 'CreateTime': '1720347121', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907318', 'MsgServerSeq': '1', 'MsgSvrID': '8318930497253698966', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720347121000', 'Status': '2', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '3', 'TalkerId': '3', 'Type': '49', 'localId': '17'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+chathistory
+
+{'data': [{'BytesExtra': 'CgQIEBAAGokDCAcShAM8bXNnc291cmNlPgogICAgPGFsbm9kZT4KICAgICAgICA8ZnI+NDwvZnI+CiAgICA8L2Fsbm9kZT4KICAgIDx0bXBfbm9kZT4KICAgICAgICA8cHVibGlzaGVyLWlkIC8+CiAgICA8L3RtcF9ub2RlPgogICAgPHNlY19tc2dfbm9kZT4KICAgICAgICA8dXVpZD41OGRiODU4MGZhZWY5MDBmMWU1MDVkZTEyNjYxMTNhN188L3V1aWQ+CiAgICAgICAgPHJpc2stZmlsZS1mbGFnIC8+CiAgICAgICAgPHJpc2stZmlsZS1tZDUtbGlzdCAvPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgogICAgPHNpZ25hdHVyZT5WMV82ME1OcktUUHx2MV9XR3dKSTlJQTwvc2lnbmF0dXJlPgo8L21zZ3NvdXJjZT4KGiQIAhIgNWI0NDY0ZjhjZmYyZWNlY2IxNTJlN2I1NjdiYzdhYTcaUggEEk53eGlkX2JobThiNzZhbHhycjIyXEZpbGVTdG9yYWdlXENhY2hlXDIwMjQtMDdcN2NjMmVhYTA3NjNiOWY2NzY1NWJmZDA2NjE2M2QxMTY=', 'BytesTrans': '', 'Content': '\n\n\t\n\t\t无空与gone的聊天记录\n\t\t无空: 科学上网或者找国内的docker 镜像\ngone: 好的,我看下 ,\ngone: 我看文档 不详细,没写怎么采公众号, 能补下吗 ?\n\t\tview\n\t\t19\n\t\thttps://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/favorite_record__w_unsupport&from=singlemessage&isappinstalled=0\n\t\t无空与gone的聊天记录无空: 科学上网或者找国内的docker 镜像
gone: 好的,我看下 ,
gone: 我看文档 不详细,没写怎么采公众号, 能补下吗 ?1720347184882]]>\n\t\t\n\t\n\twxid_tnv0hd5hj3rs11\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n\x00', 'CreateTime': '1720359309', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907342', 'MsgServerSeq': '1', 'MsgSvrID': '1181737619816961712', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720359309000', 'Status': '2', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '19', 'TalkerId': '2', 'Type': '49', 'localId': '19'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+text(群消息)
+
+{'data': [{'BytesExtra': 'CgQIEBAAGhcIARITd3hpZF90bnYwaGQ1aGozcnMxMRq1AggHErACPG1zZ3NvdXJjZT4KICAgIDxwdWE+MTwvcHVhPgogICAgPHNpbGVuY2U+MDwvc2lsZW5jZT4KICAgIDxtZW1iZXJjb3VudD4zPC9tZW1iZXJjb3VudD4KICAgIDxzaWduYXR1cmU+VjFfN0RnMU8zUWl8djFfN0RnMU8zUWk8L3NpZ25hdHVyZT4KICAgIDx0bXBfbm9kZT4KICAgICAgICA8cHVibGlzaGVyLWlkIC8+CiAgICA8L3RtcF9ub2RlPgogICAgPHNlY19tc2dfbm9kZT4KICAgICAgICA8YWxub2RlPgogICAgICAgICAgICA8ZnI+MTwvZnI+CiAgICAgICAgPC9hbG5vZGU+CiAgICA8L3NlY19tc2dfbm9kZT4KPC9tc2dzb3VyY2U+ChokCAISIDA0M2NkZTM0M2JiMzNjNmYyODIzOTYyN2YwZDMwNDlj', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720359334', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907343', 'MsgServerSeq': '1', 'MsgSvrID': '2294908351326210342', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sender': 'wxid_tnv0hd5hj3rs11', 'Sequence': '1720359334000', 'Status': '2', 'StatusEx': '0', 'StrContent': '[耶][耶][耶]',
+'StrTalker': '49591466778@chatroom', 'SubType': '0', 'TalkerId': '3', 'Type': '1', 'localId': '20'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+表情包(群消息)
+
+{'data': [{'BytesExtra': 'CgQIEBAAGhcIARITd3hpZF90bnYwaGQ1aGozcnMxMRqkAggHEp8CPG1zZ3NvdXJjZT4KICAgIDxzaWxlbmNlPjA8L3NpbGVuY2U+CiAgICA8bWVtYmVyY291bnQ+MzwvbWVtYmVyY291bnQ+CiAgICA8c2lnbmF0dXJlPlYxX0ExU2h5R00rfHYxX2E1dUR0bTR2PC9zaWduYXR1cmU+CiAgICA8dG1wX25vZGU+CiAgICAgICAgPHB1Ymxpc2hlci1pZCAvPgogICAgPC90bXBfbm9kZT4KICAgIDxzZWNfbXNnX25vZGU+CiAgICAgICAgPGFsbm9kZT4KICAgICAgICAgICAgPGZyPjE8L2ZyPgogICAgICAgIDwvYWxub2RlPgogICAgPC9zZWNfbXNnX25vZGU+CjwvbXNnc291cmNlPgoaJAgCEiBhN2EzYjZjZTNiYTJiZDI1MTJjZDhlY2I0ZDAxZGFkNg==', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720359343', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '0', 'MsgSequence': '806907344', 'MsgServerSeq': '1', 'MsgSvrID': '5707640268189775793', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sender': 'wxid_tnv0hd5hj3rs11', 'Sequence': '1720359343000', 'Status': '4', 'StatusEx': '0', 'StrContent': ' ',
+'StrTalker': '49591466778@chatroom', 'SubType': '0', 'TalkerId': '3', 'Type': '47', 'localId': '21'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+转账退还(自己发)
+
+{'data': [{'BytesExtra': 'CgQIEBAAGuoBCAcS5QE8bXNnc291cmNlPgogICAgPHNpZ25hdHVyZT5WMV90c1RtZnhRR3x2MV9hUFowem0rSjwvc2lnbmF0dXJlPgogICAgPHRtcF9ub2RlPgogICAgICAgIDxwdWJsaXNoZXItaWQgLz4KICAgIDwvdG1wX25vZGU+CiAgICA8c2VjX21zZ19ub2RlPgogICAgICAgIDxhbG5vZGU+CiAgICAgICAgICAgIDxmcj4xPC9mcj4KICAgICAgICA8L2Fsbm9kZT4KICAgIDwvc2VjX21zZ19ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgYzhiNzA2ODUxODAxODc3NmRiNjhmYjY1MTU4ODc5NmIaUggEEk53eGlkX2JobThiNzZhbHhycjIyXEZpbGVTdG9yYWdlXENhY2hlXDIwMjQtMDdcNzM0Y2U0MmJjYTkwYjljYWJlN2FjNDU3OWZiMTc2Mzg=', 'BytesTrans': '', 'Content': '\n\n\n\n\n2000\n\n\n\n\n\n\n\n4\n\n\n\n\n\n\n\n\n\n\n\n\n\n\x00', 'CreateTime': '1720359388', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '1', 'MsgSequence': '806907346', 'MsgServerSeq': '1', 'MsgSvrID': '1552054881576425902', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sequence': '1720359388000', 'Status': '2', 'StatusEx': '0', 'StrContent': '',
+'StrTalker': 'wxid_tnv0hd5hj3rs11', 'SubType': '2000', 'TalkerId': '2', 'Type': '49', 'localId': '22'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}
+
+text(群里自己发的消息)
+
+{'data': [{'BytesExtra': 'CgQIEBAAGvsBCAcS9gE8bXNnc291cmNlPgogICAgPHB1YT4xPC9wdWE+CiAgICA8c2lnbmF0dXJlPlYxX1NEUW9ucE5OfHYxX1NEUW9ucE5OPC9zaWduYXR1cmU+CiAgICA8dG1wX25vZGU+CiAgICAgICAgPHB1Ymxpc2hlci1pZCAvPgogICAgPC90bXBfbm9kZT4KICAgIDxzZWNfbXNnX25vZGU+CiAgICAgICAgPGFsbm9kZT4KICAgICAgICAgICAgPGZyPjE8L2ZyPgogICAgICAgIDwvYWxub2RlPgogICAgPC9zZWNfbXNnX25vZGU+CjwvbXNnc291cmNlPgoaJAgCEiA0MmQ3NzIwY2ZhYThlMWUzMmE0YjgxOGNlYjUyNTllNA==', 'BytesTrans': '', 'CompressContent': '', 'CreateTime': '1720359403', 'DisplayContent': '', 'FlagEx': '0', 'IsSender': '1', 'MsgSequence': '806907348', 'MsgServerSeq': '1', 'MsgSvrID': '1536604221623540', 'Reserved0': '0', 'Reserved1': '2', 'Reserved2': '', 'Reserved3': '', 'Reserved4': '', 'Reserved5': '', 'Reserved6': '', 'Sender': '\\n 1\\n V1_SDQonpNN|v1_SDQonpNN\\n \\n \\n \\n \\n \\n 1\\n \\n \\n\\n', 'Sequence': '1720359403000', 'Status': '2', 'StatusEx': '0', 'StrContent': '嘟嘟嘟嘟',
+'StrTalker': '49591466778@chatroom', 'SubType': '0', 'TalkerId': '3', 'Type': '1', 'localId': '23'}], 'total': 1, 'wxid': 'wxid_bhm8b76alxrr22'}