feat: 初始化
This commit is contained in:
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Bug 报告
|
||||
about: 提交一份 bug 报告,帮助 WeChatRobot 变得更好
|
||||
title: "[\U0001F41BBUG] 用一句话描述您的问题。"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**描述这个 bug**
|
||||
对 bug 作一个清晰简明的描述:
|
||||
- 想做什么?
|
||||
- 现在怎么做?
|
||||
- 遇到什么问题?
|
||||
|
||||
**使用环境(请补全下列信息):**
|
||||
- 操作系统:【如 Windows 7, Windows 10, Windows Server 2008 等】
|
||||
- 操作系统版本:【32 位或 64 位】
|
||||
- Python 版本 【如 3.7.9 32 位,3.8.15 64 位 等】
|
||||
|
||||
**屏幕截图**
|
||||
添加屏幕截图以帮助解释您的问题。(可选)
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: 请求添加新功能
|
||||
about: 提出一个关于本项目新功能 / 新特性的建议
|
||||
title: "[\U0001F4A1SUG] 一句话描述你希望新增的功能或特性"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**你希望添加的功能是否与某个问题相关?**
|
||||
关于这个问题的简洁清晰的描述,例如,当 [...] 时,我总是很沮丧。
|
||||
|
||||
**描述你希望的解决方案**
|
||||
关于解决方案的简洁清晰的描述。
|
||||
|
||||
**描述你考虑的替代方案**
|
||||
关于你考虑的,能实现这个功能的其他替代方案的简洁清晰的描述。
|
||||
|
||||
**其他**
|
||||
你可以添加其他任何的资料、链接或者屏幕截图,以帮助我们理解这个新功能。
|
||||
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
.*
|
||||
!.gitignore
|
||||
!.github/
|
||||
|
||||
*pyc
|
||||
__pycache__
|
||||
|
||||
logs/
|
||||
*.log
|
||||
*.log.*
|
||||
|
||||
config.yaml
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Changhua
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
180
README.md
180
README.md
@@ -1,92 +1,148 @@
|
||||
# win-hook-bot
|
||||
# WeChatRobot
|
||||
一个基于 [WeChatFerry](https://github.com/lich0821/WeChatFerry) 的微信机器人示例。
|
||||
|
||||
|[📖 文档](https://wechatferry.readthedocs.io/)|[📺 视频教程](https://mp.weixin.qq.com/s/APdjGyZ2hllXxyG_sNCfXQ)|[🙋 FAQ](https://mp.weixin.qq.com/s/bdPNrbJYoXhezCzHMqLoEw)|[🚨【微信机器人】沙雕行为合集](https://mp.weixin.qq.com/s/mc8O5iuhy46X4Bgqs80E8g)|
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|
||||
|||
|
||||
|:-:|:-:|
|
||||
|后台回复 `WeChatFerry` 加群交流|如果你觉得有用|
|
||||
|
||||
## Getting started
|
||||
## Quick Start
|
||||
0. 遇到问题先看看上面的文档、教程和 FAQ。
|
||||
- 按照步骤来,版本保持一致,少走弯路。
|
||||
- 按照步骤来,版本保持一致,少走弯路。
|
||||
- 按照步骤来,版本保持一致,少走弯路。
|
||||
1. 安装 Python>=3.9(Python 12 需要自己编译依赖,慎选),例如 [3.10.11](https://www.python.org/ftp/python/3.10.11/python-3.10.11-amd64.exe)
|
||||
2. 安装微信 `3.9.2.23`,下载地址在 [这里](https://github.com/lich0821/WeChatFerry/releases/latest);也可以从 [WeChatSetup](https://gitee.com/lch0821/WeChatSetup) 找到。
|
||||
3. 克隆项目
|
||||
```sh
|
||||
git clone https://github.com/lich0821/WeChatRobot.git
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin http://gitlab.yidooplanet.com/gitlab-instance-0a899031/win-hook-bot.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
# 如果网络原因打不开,可以科学上网,或者使用gitee
|
||||
git clone https://gitee.com/lch0821/WeChatRobot.git
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
如果觉得克隆复杂,也可以直接下载 [最新版](https://github.com/lich0821/WeChatRobot/releases/latest) 到本地解压。
|
||||
|
||||
- [ ] [Set up project integrations](http://gitlab.yidooplanet.com/gitlab-instance-0a899031/win-hook-bot/-/settings/integrations)
|
||||
4. 安装依赖
|
||||
```sh
|
||||
# 升级 pip
|
||||
python -m pip install -U pip
|
||||
# 安装必要依赖
|
||||
pip install -r requirements.txt
|
||||
# ChatGLM 还需要安装一个 kernel
|
||||
ipython kernel install --name chatglm3 --user
|
||||
```
|
||||
|
||||
## Collaborate with your team
|
||||
5. 运行
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
我们需要运行两次 `main.py` 第一次是生成配置文件 `config.yaml`, 第二次是真正跑你的机器人。
|
||||
直接运行程序会自动拉起微信,如果微信未打开,会自动打开微信;如果版本不对,也会有提示;其他报错,请进群交流。
|
||||
|
||||
## Test and Deploy
|
||||
下面代码为第一次运行:第一次运行 `main.py` 会在 WeChatRobot 目录下生成一个 `config.yaml` 文件,参照修改配置进行修改。
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
其中 chatgpt、tigerbot、chatglm 和 xinghuo_web 是四种模型的配置信息,你需要配置它们的参数,不知道的可以加群交流。
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
```sh
|
||||
python main.py
|
||||
|
||||
***
|
||||
# 需要停止按 Ctrl+C
|
||||
```
|
||||
|
||||
# Editing this README
|
||||
启动之后,可以正常接收消息但不会响应群消息。参考下方 [修改配置](#config) 进行配置,以便响应特定群聊。
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
下面代码为第二次运行:你可以通过命令行参数选择模型,默认是不选择,这样你配置了什么参数就跑什么模型。正因如此你需要配置前面所说四种模型中的至少一种(当然也可以都配置,想跑那个模型就选什么参数), 然后就可以开始使用你的机器人了。
|
||||
```sh
|
||||
python main.py
|
||||
|
||||
## Suggestions for a good README
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
# 需要停止按 Ctrl+C
|
||||
```
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
如果你配置了多个模型(不需要将其他配置注释或者移除),下面的内容才对你有帮助否则略过,通过 `python main.py -h` 通过参数可以选择要跑的模型。
|
||||
```sh
|
||||
# 查看帮助
|
||||
python main.py -h
|
||||
#optional arguments:
|
||||
# -h, --help show this help message and exit
|
||||
# -c C, --chat_model C 选择要使用的AI模型,默认不选择,可选参数:1. tigerbot 模型 2. chatgpt 模型 3. 讯飞星火模型 4. chatglm 模型
|
||||
```
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
```sh
|
||||
# 例: 我想运行选择chatgpt的机器人
|
||||
python main.py -c 2
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
# 需要停止按 Ctrl+C
|
||||
```
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
> python main.py -c C 其中参数 C 可选择如下所示
|
||||
>> 1. tigerbot 模型
|
||||
>> 2. chatgpt 模型
|
||||
>> 3. 讯飞星火模型
|
||||
>> 4. chatglm 模型
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
6. 停止
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
不要那么粗暴,温柔点儿;
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
不要直接关闭窗口,温柔点儿。
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
输入:`Ctrl+C`。否则,会出现各种奇怪问题。
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
### <a name="config"></a>修改配置
|
||||
ℹ️ *修改配置后,需要重新启动,以便让配置生效。*
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
配置文件 `config.yaml` 是运行程序后自动从模板复制过来的,功能默认关闭。
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
#### 响应被 @ 消息
|
||||
为了响应群聊消息,需要添加相应的 `roomId`。
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
第一次运行的时候,可以在手机上往需要响应的群里发消息,打印的消息中方括号里的就是;多个群用 `,` 分隔。
|
||||
```yaml
|
||||
groups:
|
||||
enable: [] # 允许响应的群 roomId,大概长这样:2xxxxxxxxx3@chatroom, 多个群用 `,` 分隔
|
||||
```
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
#### 配置 AI 模型
|
||||
为了使用 AI 模型,需要对相应模型并进行配置。
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
使用 ChatGLM 见注意事项 [README.MD](base/chatglm/README.MD)
|
||||
|
||||
```yaml
|
||||
chatgpt: # -----chatgpt配置这行不填-----
|
||||
key: # 填写你 ChatGPT 的 key
|
||||
api: https://api.openai.com/v1 # 如果你不知道这是干嘛的,就不要改
|
||||
proxy: # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
prompt: 你是智能聊天机器人,你叫 wcferry # 根据需要对角色进行设定
|
||||
|
||||
chatglm: # -----chatglm配置这行不填-----
|
||||
key: sk-012345678901234567890123456789012345678901234567 # 这个应该不用动
|
||||
api: http://localhost:8000/v1 # 根据自己的chatglm地址修改
|
||||
proxy: # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
prompt: 你是智能聊天机器人,你叫小薇 # 根据需要对角色进行设定
|
||||
file_path: C:/Pictures/temp #设定生成图片和代码使用的文件夹路径
|
||||
|
||||
tigerbot: # -----tigerbot配置这行不填-----
|
||||
key: # key
|
||||
model: # tigerbot-7b-sft
|
||||
|
||||
# 抓取方式详见文档:https://www.bilibili.com/read/cv27066577
|
||||
xinghuo_web: # -----讯飞星火web模式api配置这行不填-----
|
||||
cookie: # cookie
|
||||
fd: # fd
|
||||
GtToken: # GtToken
|
||||
prompt: 你是智能聊天机器人,你叫 wcferry。请用这个角色回答我的问题 # 根据需要对角色进行设定
|
||||
|
||||
bard: # -----bard配置这行不填-----
|
||||
api_key: # api-key 创建地址:https://ai.google.dev/pricing,创建后复制过来即可
|
||||
model_name: gemini-pro # 新模型上线后可以选择模型
|
||||
proxy: http://127.0.0.1:7890 # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
# 提示词尽可能用英文,bard对中文提示词的效果不是很理想,下方提示词为英语老师的示例,请按实际需要修改,默认设置的提示词为谷歌创造的AI大语言模型
|
||||
# I want you to act as a spoken English teacher and improver. I will speak to you in English and you will reply to me in English to practice my spoken English. I want you to keep your reply neat, limiting the reply to 100 words. I want you to strictly correct my grammar mistakes, typos, and factual errors. I want you to ask me a question in your reply. Now let's start practicing, you could ask me a question first. Remember, I want you to strictly correct my grammar mistakes, typos, and factual errors.
|
||||
prompt: You am a large language model, trained by Google.
|
||||
```
|
||||
|
||||
## HTTP
|
||||
如需要使用 HTTP 接口,请参考 [wcfhttp](https://wechatferry.readthedocs.io/zh/latest/?badge=latest)。
|
||||
|
||||
[](https://pypi.python.org/pypi/wcfhttp) [](https://pypi.python.org/pypi/wcfhttp) [](https://wechatferry.readthedocs.io/zh/latest/?badge=latest)
|
||||
|
||||
45
base/chatglm/README.MD
Normal file
45
base/chatglm/README.MD
Normal file
@@ -0,0 +1,45 @@
|
||||
# ChatGLM3 集成使用说明
|
||||
|
||||
1. 需要取消配置中 chatglm 的注释, 并配置对应信息,使用 [ChatGLM3](https://github.com/THUDM/ChatGLM3), 启用最新版 ChatGLM3 根目录下 openai_api.py 获取 api 地址:
|
||||
```yaml
|
||||
# 如果要使用 chatglm,取消下面的注释并填写相关内容
|
||||
chatglm:
|
||||
key: sk-012345678901234567890123456789012345678901234567 # 根据需要自己做key校验
|
||||
api: http://localhost:8000/v1 # 根据自己的chatglm地址修改
|
||||
proxy: # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
prompt: 你是智能聊天机器人,你叫小薇 # 根据需要对角色进行设定
|
||||
file_path: F:/Pictures/temp #设定生成图片和代码使用的文件夹路径
|
||||
```
|
||||
|
||||
2. 修改 chatglm/tool_registry.py 工具里面的一下配置,comfyUI 地址或者根据需要自己配置一些工具,函数名上需要加 @register_tool, 函数里面需要叫'''函数描述''',参数需要用 Annotated[str,'',True] 修饰,分别是类型,参数说明,是否必填,再加 ->加上对应的返回类型
|
||||
```python
|
||||
@register_tool
|
||||
def get_confyui_image(prompt: Annotated[str, '要生成图片的提示词,注意必须是英文', True]) -> dict:
|
||||
'''
|
||||
生成图片
|
||||
'''
|
||||
with open("func_chatglm\\base.json", "r", encoding="utf-8") as f:
|
||||
data2 = json.load(f)
|
||||
data2['prompt']['3']['inputs']['seed'] = ''.join(
|
||||
random.sample('123456789012345678901234567890', 14))
|
||||
# 模型名称
|
||||
data2['prompt']['4']['inputs']['ckpt_name'] = 'chilloutmix_NiPrunedFp32Fix.safetensors'
|
||||
data2['prompt']['6']['inputs']['text'] = prompt # 正向提示词
|
||||
# data2['prompt']['7']['inputs']['text']='' #反向提示词
|
||||
cfui = ComfyUIApi(server_address="127.0.0.1:8188") # 根据自己comfyUI地址修改
|
||||
images = cfui.get_images(data2['prompt'])
|
||||
return {'res': images[0]['image'], 'res_type': 'image', 'filename': images[0]['filename']}
|
||||
|
||||
```
|
||||
|
||||
3. 使用 Code Interpreter 还需要安装 Jupyter 内核,默认名称叫 chatglm3:
|
||||
```
|
||||
ipython kernel install --name chatglm3 --user
|
||||
```
|
||||
|
||||
如果名称需要自定义,可以配置系统环境变量:IPYKERNEL 或者修改 chatglm/code_kernel.py
|
||||
```
|
||||
IPYKERNEL = os.environ.get('IPYKERNEL', 'chatglm3')
|
||||
```
|
||||
|
||||
4. 启动后,发送 #帮助 可以查看 模式和常用指令
|
||||
13
base/chatglm/__init__.py
Normal file
13
base/chatglm/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
|
||||
|
||||
class UnsupportedPythonVersionError(Exception):
|
||||
def __init__(self, error_msg: str):
|
||||
super().__init__(error_msg)
|
||||
|
||||
|
||||
python_version_info = sys.version_info
|
||||
if not sys.version_info >= (3, 9):
|
||||
msg = "当前Python版本: " + ".".join(map(str, python_version_info[:3])) + (', 需要python版本 >= 3.9, 前往下载: '
|
||||
'https://www.python.org/downloads/')
|
||||
raise UnsupportedPythonVersionError(msg)
|
||||
88
base/chatglm/base.json
Normal file
88
base/chatglm/base.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"prompt": {
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 1000573256060686,
|
||||
"steps": 20,
|
||||
"cfg": 8,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1,
|
||||
"model": [
|
||||
"4",
|
||||
0
|
||||
],
|
||||
"positive": [
|
||||
"6",
|
||||
0
|
||||
],
|
||||
"negative": [
|
||||
"7",
|
||||
0
|
||||
],
|
||||
"latent_image": [
|
||||
"5",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "KSampler"
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "(修复)512-inpainting-ema.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple"
|
||||
},
|
||||
"5": {
|
||||
"inputs": {
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"batch_size": 1
|
||||
},
|
||||
"class_type": "EmptyLatentImage"
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,dress, ",
|
||||
"clip": [
|
||||
"4",
|
||||
1
|
||||
]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"text": "text, watermark",
|
||||
"clip": [
|
||||
"4",
|
||||
1
|
||||
]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": [
|
||||
"3",
|
||||
0
|
||||
],
|
||||
"vae": [
|
||||
"4",
|
||||
2
|
||||
]
|
||||
},
|
||||
"class_type": "VAEDecode"
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": [
|
||||
"8",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "SaveImage"
|
||||
}
|
||||
}
|
||||
}
|
||||
199
base/chatglm/code_kernel.py
Normal file
199
base/chatglm/code_kernel.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import base64
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
from io import BytesIO
|
||||
from subprocess import PIPE
|
||||
from typing import Optional, Union
|
||||
|
||||
import jupyter_client
|
||||
from PIL import Image
|
||||
|
||||
IPYKERNEL = os.environ.get('IPYKERNEL', 'chatglm3')
|
||||
|
||||
|
||||
class CodeKernel(object):
|
||||
def __init__(self,
|
||||
kernel_name='kernel',
|
||||
kernel_id=None,
|
||||
kernel_config_path="",
|
||||
python_path=None,
|
||||
ipython_path=None,
|
||||
init_file_path="./startup.py",
|
||||
verbose=1):
|
||||
|
||||
self.kernel_name = kernel_name
|
||||
self.kernel_id = kernel_id
|
||||
self.kernel_config_path = kernel_config_path
|
||||
self.python_path = python_path
|
||||
self.ipython_path = ipython_path
|
||||
self.init_file_path = init_file_path
|
||||
self.verbose = verbose
|
||||
|
||||
if python_path is None and ipython_path is None:
|
||||
env = None
|
||||
else:
|
||||
env = {"PATH": self.python_path + ":$PATH",
|
||||
"PYTHONPATH": self.python_path}
|
||||
|
||||
# Initialize the backend kernel
|
||||
self.kernel_manager = jupyter_client.KernelManager(kernel_name=IPYKERNEL,
|
||||
connection_file=self.kernel_config_path,
|
||||
exec_files=[
|
||||
self.init_file_path],
|
||||
env=env)
|
||||
if self.kernel_config_path:
|
||||
self.kernel_manager.load_connection_file()
|
||||
self.kernel_manager.start_kernel(stdout=PIPE, stderr=PIPE)
|
||||
print("Backend kernel started with the configuration: {}".format(
|
||||
self.kernel_config_path))
|
||||
else:
|
||||
self.kernel_manager.start_kernel(stdout=PIPE, stderr=PIPE)
|
||||
print("Backend kernel started with the configuration: {}".format(
|
||||
self.kernel_manager.connection_file))
|
||||
|
||||
if verbose:
|
||||
print(self.kernel_manager.get_connection_info())
|
||||
|
||||
# Initialize the code kernel
|
||||
self.kernel = self.kernel_manager.blocking_client()
|
||||
# self.kernel.load_connection_file()
|
||||
self.kernel.start_channels()
|
||||
print("Code kernel started.")
|
||||
|
||||
def execute(self, code):
|
||||
self.kernel.execute(code)
|
||||
try:
|
||||
shell_msg = self.kernel.get_shell_msg(timeout=40)
|
||||
io_msg_content = self.kernel.get_iopub_msg(timeout=40)['content']
|
||||
while True:
|
||||
msg_out = io_msg_content
|
||||
# Poll the message
|
||||
try:
|
||||
io_msg_content = self.kernel.get_iopub_msg(timeout=40)[
|
||||
'content']
|
||||
if 'execution_state' in io_msg_content and io_msg_content['execution_state'] == 'idle':
|
||||
break
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
return shell_msg, msg_out
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def execute_interactive(self, code, verbose=False):
|
||||
shell_msg = self.kernel.execute_interactive(code)
|
||||
if shell_msg is queue.Empty:
|
||||
if verbose:
|
||||
print("Timeout waiting for shell message.")
|
||||
self.check_msg(shell_msg, verbose=verbose)
|
||||
|
||||
return shell_msg
|
||||
|
||||
def inspect(self, code, verbose=False):
|
||||
msg_id = self.kernel.inspect(code)
|
||||
shell_msg = self.kernel.get_shell_msg(timeout=30)
|
||||
if shell_msg is queue.Empty:
|
||||
if verbose:
|
||||
print("Timeout waiting for shell message.")
|
||||
self.check_msg(shell_msg, verbose=verbose)
|
||||
|
||||
return shell_msg
|
||||
|
||||
def get_error_msg(self, msg, verbose=False) -> Optional[str]:
|
||||
if msg['content']['status'] == 'error':
|
||||
try:
|
||||
error_msg = msg['content']['traceback']
|
||||
except BaseException:
|
||||
try:
|
||||
error_msg = msg['content']['traceback'][-1].strip()
|
||||
except BaseException:
|
||||
error_msg = "Traceback Error"
|
||||
if verbose:
|
||||
print("Error: ", error_msg)
|
||||
return error_msg
|
||||
return None
|
||||
|
||||
def check_msg(self, msg, verbose=False):
|
||||
status = msg['content']['status']
|
||||
if status == 'ok':
|
||||
if verbose:
|
||||
print("Execution succeeded.")
|
||||
elif status == 'error':
|
||||
for line in msg['content']['traceback']:
|
||||
if verbose:
|
||||
print(line)
|
||||
|
||||
def shutdown(self):
|
||||
# Shutdown the backend kernel
|
||||
self.kernel_manager.shutdown_kernel()
|
||||
print("Backend kernel shutdown.")
|
||||
# Shutdown the code kernel
|
||||
self.kernel.shutdown()
|
||||
print("Code kernel shutdown.")
|
||||
|
||||
def restart(self):
|
||||
# Restart the backend kernel
|
||||
self.kernel_manager.restart_kernel()
|
||||
# print("Backend kernel restarted.")
|
||||
|
||||
def interrupt(self):
|
||||
# Interrupt the backend kernel
|
||||
self.kernel_manager.interrupt_kernel()
|
||||
# print("Backend kernel interrupted.")
|
||||
|
||||
def is_alive(self):
|
||||
return self.kernel.is_alive()
|
||||
|
||||
|
||||
def b64_2_img(data):
|
||||
buff = BytesIO(base64.b64decode(data))
|
||||
return Image.open(buff)
|
||||
|
||||
|
||||
def clean_ansi_codes(input_string):
|
||||
ansi_escape = re.compile(r'(\x9B|\x1B\[|\u001b\[)[0-?]*[ -/]*[@-~]')
|
||||
return ansi_escape.sub('', input_string)
|
||||
|
||||
|
||||
def execute(code, kernel: CodeKernel) -> tuple[str, Union[str, Image.Image]]:
|
||||
res = ""
|
||||
res_type = None
|
||||
code = code.replace("<|observation|>", "")
|
||||
code = code.replace("<|assistant|>interpreter", "")
|
||||
code = code.replace("<|assistant|>", "")
|
||||
code = code.replace("<|user|>", "")
|
||||
code = code.replace("<|system|>", "")
|
||||
msg, output = kernel.execute(code)
|
||||
|
||||
if msg['metadata']['status'] == "timeout":
|
||||
return res_type, 'Timed out'
|
||||
elif msg['metadata']['status'] == 'error':
|
||||
return res_type, clean_ansi_codes('\n'.join(kernel.get_error_msg(msg, verbose=True)))
|
||||
|
||||
if 'text' in output:
|
||||
res_type = "text"
|
||||
res = output['text']
|
||||
elif 'data' in output:
|
||||
for key in output['data']:
|
||||
if 'image/png' in key:
|
||||
res_type = "image"
|
||||
res = output['data'][key]
|
||||
break
|
||||
elif 'text/plain' in key:
|
||||
res_type = "text"
|
||||
res = output['data'][key]
|
||||
|
||||
if res_type == "image":
|
||||
return res_type, b64_2_img(res)
|
||||
elif res_type == "text" or res_type == "traceback":
|
||||
res = res
|
||||
|
||||
return res_type, res
|
||||
|
||||
|
||||
def extract_code(text: str) -> str:
|
||||
pattern = r'```([^\n]*)\n(.*?)```'
|
||||
matches = re.findall(pattern, text, re.DOTALL)
|
||||
return matches[-1][1]
|
||||
186
base/chatglm/comfyUI_api.py
Normal file
186
base/chatglm/comfyUI_api.py
Normal file
@@ -0,0 +1,186 @@
|
||||
# This is an example that uses the websockets api to know when a prompt execution is done
|
||||
# Once the prompt execution is done it downloads the images using the /history endpoint
|
||||
|
||||
import io
|
||||
import json
|
||||
import random
|
||||
import urllib
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
# NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
|
||||
import websocket
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class ComfyUIApi():
|
||||
def __init__(self, server_address="127.0.0.1:8188"):
|
||||
self.server_address = server_address
|
||||
self.client_id = str(uuid.uuid4())
|
||||
self.ws = websocket.WebSocket()
|
||||
self.ws.connect(
|
||||
"ws://{}/ws?clientId={}".format(server_address, self.client_id))
|
||||
|
||||
def queue_prompt(self, prompt):
|
||||
p = {"prompt": prompt, "client_id": self.client_id}
|
||||
data = json.dumps(p).encode('utf-8')
|
||||
req = requests.post(
|
||||
"http://{}/prompt".format(self.server_address), data=data)
|
||||
print(req.text)
|
||||
return json.loads(req.text)
|
||||
|
||||
def get_image(self, filename, subfolder, folder_type):
|
||||
data = {"filename": filename,
|
||||
"subfolder": subfolder, "type": folder_type}
|
||||
url_values = urllib.parse.urlencode(data)
|
||||
with requests.get("http://{}/view?{}".format(self.server_address, url_values)) as response:
|
||||
image = Image.open(io.BytesIO(response.content))
|
||||
return image
|
||||
|
||||
def get_image_url(self, filename, subfolder, folder_type):
|
||||
data = {"filename": filename,
|
||||
"subfolder": subfolder, "type": folder_type}
|
||||
url_values = urllib.parse.urlencode(data)
|
||||
return "http://{}/view?{}".format(self.server_address, url_values)
|
||||
|
||||
def get_history(self, prompt_id):
|
||||
with requests.get("http://{}/history/{}".format(self.server_address, prompt_id)) as response:
|
||||
return json.loads(response.text)
|
||||
|
||||
def get_images(self, prompt, isUrl=False):
|
||||
prompt_id = self.queue_prompt(prompt)['prompt_id']
|
||||
output_images = []
|
||||
while True:
|
||||
out = self.ws.recv()
|
||||
if isinstance(out, str):
|
||||
message = json.loads(out)
|
||||
if message['type'] == 'executing':
|
||||
data = message['data']
|
||||
if data['node'] is None and data['prompt_id'] == prompt_id:
|
||||
break # Execution is done
|
||||
else:
|
||||
continue # previews are binary data
|
||||
|
||||
history = self.get_history(prompt_id)[prompt_id]
|
||||
for o in history['outputs']:
|
||||
for node_id in history['outputs']:
|
||||
node_output = history['outputs'][node_id]
|
||||
if 'images' in node_output:
|
||||
for image in node_output['images']:
|
||||
image_data = self.get_image_url(image['filename'], image['subfolder'], image['type']) if isUrl else self.get_image(
|
||||
image['filename'], image['subfolder'], image['type'])
|
||||
image['image'] = image_data
|
||||
output_images.append(image)
|
||||
|
||||
return output_images
|
||||
|
||||
|
||||
prompt_text = """
|
||||
{
|
||||
"3": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"cfg": 8,
|
||||
"denoise": 1,
|
||||
"latent_image": [
|
||||
"5",
|
||||
0
|
||||
],
|
||||
"model": [
|
||||
"4",
|
||||
0
|
||||
],
|
||||
"negative": [
|
||||
"7",
|
||||
0
|
||||
],
|
||||
"positive": [
|
||||
"6",
|
||||
0
|
||||
],
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"seed": 8566257,
|
||||
"steps": 20
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"inputs": {
|
||||
"ckpt_name": "chilloutmix_NiPrunedFp32Fix.safetensors"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"class_type": "EmptyLatentImage",
|
||||
"inputs": {
|
||||
"batch_size": 1,
|
||||
"height": 512,
|
||||
"width": 512
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {
|
||||
"clip": [
|
||||
"4",
|
||||
1
|
||||
],
|
||||
"text": "masterpiece best quality girl"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {
|
||||
"clip": [
|
||||
"4",
|
||||
1
|
||||
],
|
||||
"text": "bad hands"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"class_type": "VAEDecode",
|
||||
"inputs": {
|
||||
"samples": [
|
||||
"3",
|
||||
0
|
||||
],
|
||||
"vae": [
|
||||
"4",
|
||||
2
|
||||
]
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"class_type": "SaveImage",
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": [
|
||||
"8",
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
if __name__ == '__main__':
|
||||
prompt = json.loads(prompt_text)
|
||||
# set the text prompt for our positive CLIPTextEncode
|
||||
prompt["6"]["inputs"]["text"] = "masterpiece best quality man"
|
||||
|
||||
# set the seed for our KSampler node
|
||||
prompt["3"]["inputs"]["seed"] = ''.join(
|
||||
random.sample('123456789012345678901234567890', 14))
|
||||
|
||||
cfui = ComfyUIApi()
|
||||
images = cfui.get_images(prompt)
|
||||
|
||||
# Commented out code to display the output images:
|
||||
|
||||
for node_id in images:
|
||||
for image_data in images[node_id]:
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
image = Image.open(io.BytesIO(image_data))
|
||||
image.show()
|
||||
167
base/chatglm/tool_registry.py
Normal file
167
base/chatglm/tool_registry.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import inspect
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import traceback
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from types import GenericAlias
|
||||
from typing import Annotated, get_origin
|
||||
|
||||
from base.chatglm.comfyUI_api import ComfyUIApi
|
||||
from base.func_news import News
|
||||
from zhdate import ZhDate
|
||||
|
||||
_TOOL_HOOKS = {}
|
||||
_TOOL_DESCRIPTIONS = {}
|
||||
|
||||
|
||||
def extract_code(text: str) -> str:
|
||||
pattern = r'```([^\n]*)\n(.*?)```'
|
||||
matches = re.findall(pattern, text, re.DOTALL)
|
||||
return matches[-1][1]
|
||||
|
||||
|
||||
def register_tool(func: callable):
|
||||
tool_name = func.__name__
|
||||
tool_description = inspect.getdoc(func).strip()
|
||||
python_params = inspect.signature(func).parameters
|
||||
tool_params = []
|
||||
for name, param in python_params.items():
|
||||
annotation = param.annotation
|
||||
if annotation is inspect.Parameter.empty:
|
||||
raise TypeError(f"Parameter `{name}` missing type annotation")
|
||||
if get_origin(annotation) != Annotated:
|
||||
raise TypeError(
|
||||
f"Annotation type for `{name}` must be typing.Annotated")
|
||||
|
||||
typ, (description, required) = annotation.__origin__, annotation.__metadata__
|
||||
typ: str = str(typ) if isinstance(typ, GenericAlias) else typ.__name__
|
||||
if not isinstance(description, str):
|
||||
raise TypeError(f"Description for `{name}` must be a string")
|
||||
if not isinstance(required, bool):
|
||||
raise TypeError(f"Required for `{name}` must be a bool")
|
||||
|
||||
tool_params.append({
|
||||
"name": name,
|
||||
"description": description,
|
||||
"type": typ,
|
||||
"required": required
|
||||
})
|
||||
tool_def = {
|
||||
"name": tool_name,
|
||||
"description": tool_description,
|
||||
"params": tool_params
|
||||
}
|
||||
|
||||
# print("[registered tool] " + pformat(tool_def))
|
||||
_TOOL_HOOKS[tool_name] = func
|
||||
_TOOL_DESCRIPTIONS[tool_name] = tool_def
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def dispatch_tool(tool_name: str, tool_params: dict) -> str:
|
||||
if tool_name not in _TOOL_HOOKS:
|
||||
return f"Tool `{tool_name}` not found. Please use a provided tool."
|
||||
tool_call = _TOOL_HOOKS[tool_name]
|
||||
try:
|
||||
ret = tool_call(**tool_params)
|
||||
except BaseException:
|
||||
ret = traceback.format_exc()
|
||||
return ret
|
||||
|
||||
|
||||
def get_tools() -> dict:
|
||||
return deepcopy(_TOOL_DESCRIPTIONS)
|
||||
|
||||
# Tool Definitions
|
||||
|
||||
# @register_tool
|
||||
# def random_number_generator(
|
||||
# seed: Annotated[int, 'The random seed used by the generator', True],
|
||||
# range: Annotated[tuple[int, int], 'The range of the generated numbers', True],
|
||||
# ) -> int:
|
||||
# """
|
||||
# Generates a random number x, s.t. range[0] <= x < range[1]
|
||||
# """
|
||||
# if not isinstance(seed, int):
|
||||
# raise TypeError("Seed must be an integer")
|
||||
# if not isinstance(range, tuple):
|
||||
# raise TypeError("Range must be a tuple")
|
||||
# if not isinstance(range[0], int) or not isinstance(range[1], int):
|
||||
# raise TypeError("Range must be a tuple of integers")
|
||||
|
||||
# import random
|
||||
# return random.Random(seed).randint(*range)
|
||||
|
||||
|
||||
@register_tool
|
||||
def get_weather(
|
||||
city_name: Annotated[str, 'The name of the city to be queried', True],
|
||||
) -> str:
|
||||
"""
|
||||
Get the current weather for `city_name`
|
||||
"""
|
||||
if not isinstance(city_name, str):
|
||||
raise TypeError("City name must be a string")
|
||||
|
||||
key_selection = {
|
||||
"current_condition": ["temp_C", "FeelsLikeC", "humidity", "weatherDesc", "observation_time"],
|
||||
}
|
||||
import requests
|
||||
try:
|
||||
resp = requests.get(f"https://wttr.in/{city_name}?format=j1")
|
||||
resp.raise_for_status()
|
||||
resp = resp.json()
|
||||
ret = {k: {_v: resp[k][0][_v] for _v in v}
|
||||
for k, v in key_selection.items()}
|
||||
except BaseException:
|
||||
import traceback
|
||||
ret = "Error encountered while fetching weather data!\n" + traceback.format_exc()
|
||||
|
||||
return str(ret)
|
||||
|
||||
|
||||
@register_tool
|
||||
def get_confyui_image(prompt: Annotated[str, '要生成图片的提示词,注意必须是英文', True]) -> dict:
|
||||
'''
|
||||
生成图片
|
||||
'''
|
||||
with open("chatglm\\base.json", "r", encoding="utf-8") as f:
|
||||
data2 = json.load(f)
|
||||
data2['prompt']['3']['inputs']['seed'] = ''.join(
|
||||
random.sample('123456789012345678901234567890', 14))
|
||||
# 模型名称
|
||||
data2['prompt']['4']['inputs']['ckpt_name'] = 'chilloutmix_NiPrunedFp32Fix.safetensors'
|
||||
data2['prompt']['6']['inputs']['text'] = prompt # 正向提示词
|
||||
# data2['prompt']['7']['inputs']['text']='' #反向提示词
|
||||
cfui = ComfyUIApi(server_address="127.0.0.1:8188") # 根据自己comfyUI地址修改
|
||||
images = cfui.get_images(data2['prompt'])
|
||||
return {'res': images[0]['image'], 'res_type': 'image', 'filename': images[0]['filename']}
|
||||
|
||||
|
||||
@register_tool
|
||||
def get_news() -> str:
|
||||
'''
|
||||
获取最新新闻
|
||||
'''
|
||||
news = News()
|
||||
return news.get_important_news()
|
||||
|
||||
|
||||
@register_tool
|
||||
def get_time() -> str:
|
||||
'''
|
||||
获取当前日期,时间,农历日期,星期几
|
||||
'''
|
||||
time = datetime.now()
|
||||
date2 = ZhDate.from_datetime(time)
|
||||
week_list = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
|
||||
|
||||
return '{} {} {}'.format(time.strftime("%Y年%m月%d日 %H:%M:%S"), week_list[time.weekday()], '农历:' + date2.chinese())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(dispatch_tool("get_weather", {"city_name": "beijing"}))
|
||||
print(get_tools())
|
||||
30903
base/chengyu.csv
Normal file
30903
base/chengyu.csv
Normal file
File diff suppressed because it is too large
Load Diff
44
base/func_bard.py
Normal file
44
base/func_bard.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import google.generativeai as genai
|
||||
|
||||
|
||||
class BardAssistant:
|
||||
def __init__(self, conf: dict) -> None:
|
||||
self._api_key = conf["api_key"]
|
||||
self._model_name = conf["model_name"]
|
||||
self._prompt = conf['prompt']
|
||||
self._proxy = conf['proxy']
|
||||
|
||||
genai.configure(api_key=self._api_key)
|
||||
self._bard = genai.GenerativeModel(self._model_name)
|
||||
|
||||
def __repr__(self):
|
||||
return 'BardAssistant'
|
||||
|
||||
@staticmethod
|
||||
def value_check(conf: dict) -> bool:
|
||||
if conf:
|
||||
if conf.get("api_key") and conf.get("model_name") and conf.get("prompt"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_answer(self, msg: str, sender: str = None) -> str:
|
||||
response = self._bard.generate_content([{'role': 'user', 'parts': [msg]}])
|
||||
return response.text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from configuration import Config
|
||||
config = Config().BardAssistant
|
||||
if not config:
|
||||
exit(0)
|
||||
|
||||
bard_assistant = BardAssistant(config)
|
||||
if bard_assistant._proxy:
|
||||
os.environ['HTTP_PROXY'] = bard_assistant._proxy
|
||||
os.environ['HTTPS_PROXY'] = bard_assistant._proxy
|
||||
rsp = bard_assistant.get_answer(bard_assistant._prompt)
|
||||
print(rsp)
|
||||
190
base/func_chatglm.py
Normal file
190
base/func_chatglm.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import openai
|
||||
from base.chatglm.code_kernel import CodeKernel, execute
|
||||
from base.chatglm.tool_registry import dispatch_tool, extract_code, get_tools
|
||||
from wcferry import Wcf
|
||||
|
||||
functions = get_tools()
|
||||
|
||||
|
||||
class ChatGLM:
|
||||
|
||||
def __init__(self, config={}, wcf: Optional[Wcf] = None, max_retry=5) -> None:
|
||||
openai.api_key = config.get("key", "empty")
|
||||
# 自己搭建或第三方代理的接口
|
||||
openai.api_base = config["api"]
|
||||
proxy = config.get("proxy")
|
||||
if proxy:
|
||||
openai.proxy = {"http": proxy, "https": proxy}
|
||||
self.conversation_list = {}
|
||||
self.chat_type = {}
|
||||
self.max_retry = max_retry
|
||||
self.wcf = wcf
|
||||
self.filePath = config["file_path"]
|
||||
self.kernel = CodeKernel()
|
||||
self.system_content_msg = {"chat": [{"role": "system", "content": config["prompt"]}],
|
||||
"tool": [{"role": "system", "content": "Answer the following questions as best as you can. You have access to the following tools:"}],
|
||||
"code": [{"role": "system", "content": "你是一位智能AI助手,你叫ChatGLM,你连接着一台电脑,但请注意不能联网。在使用Python解决任务时,你可以运行代码并得到结果,如果运行结果有错误,你需要尽可能对代码进行改进。你可以处理用户上传到电脑上的文件,文件默认存储路径是{}。".format(self.filePath)}]}
|
||||
|
||||
def __repr__(self):
|
||||
return 'ChatGLM'
|
||||
|
||||
@staticmethod
|
||||
def value_check(conf: dict) -> bool:
|
||||
if conf:
|
||||
if conf.get("api") and conf.get("prompt") and conf.get("file_path"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_answer(self, question: str, wxid: str) -> str:
|
||||
# wxid或者roomid,个人时为微信id,群消息时为群id
|
||||
if '#帮助' == question:
|
||||
return '本助手有三种模式,#聊天模式 = #1 ,#工具模式 = #2 ,#代码模式 = #3 , #清除模式会话 = #4 , #清除全部会话 = #5 可用发送#对应模式 或者 #编号 进行切换'
|
||||
elif '#聊天模式' == question or '#1' == question:
|
||||
self.chat_type[wxid] = 'chat'
|
||||
return '已切换#聊天模式'
|
||||
elif '#工具模式' == question or '#2' == question:
|
||||
self.chat_type[wxid] = 'tool'
|
||||
return '已切换#工具模式 \n工具有:查看天气,日期,新闻,comfyUI文生图。例如:\n帮我生成一张小鸟的图片,提示词必须是英文'
|
||||
elif '#代码模式' == question or '#3' == question:
|
||||
self.chat_type[wxid] = 'code'
|
||||
return '已切换#代码模式 \n代码模式可以用于写python代码,例如:\n用python画一个爱心'
|
||||
elif '#清除模式会话' == question or '#4' == question:
|
||||
self.conversation_list[wxid][self.chat_type[wxid]
|
||||
] = self.system_content_msg[self.chat_type[wxid]]
|
||||
return '已清除'
|
||||
elif '#清除全部会话' == question or '#5' == question:
|
||||
self.conversation_list[wxid] = self.system_content_msg
|
||||
return '已清除'
|
||||
|
||||
self.updateMessage(wxid, question, "user")
|
||||
|
||||
try:
|
||||
params = dict(model="chatglm3", temperature=1.0,
|
||||
messages=self.conversation_list[wxid][self.chat_type[wxid]], stream=False)
|
||||
if 'tool' == self.chat_type[wxid]:
|
||||
params["functions"] = functions
|
||||
response = openai.ChatCompletion.create(**params)
|
||||
for _ in range(self.max_retry):
|
||||
if response.choices[0].message.get("function_call"):
|
||||
function_call = response.choices[0].message.function_call
|
||||
print(
|
||||
f"Function Call Response: {function_call.to_dict_recursive()}")
|
||||
|
||||
function_args = json.loads(function_call.arguments)
|
||||
observation = dispatch_tool(
|
||||
function_call.name, function_args)
|
||||
if isinstance(observation, dict):
|
||||
res_type = observation['res_type'] if 'res_type' in observation else 'text'
|
||||
res = observation['res'] if 'res_type' in observation else str(
|
||||
observation)
|
||||
if res_type == 'image':
|
||||
filename = observation['filename']
|
||||
filePath = os.path.join(self.filePath, filename)
|
||||
res.save(filePath)
|
||||
self.wcf and self.wcf.send_image(filePath, wxid)
|
||||
tool_response = '[Image]' if res_type == 'image' else res
|
||||
else:
|
||||
tool_response = observation if isinstance(
|
||||
observation, str) else str(observation)
|
||||
print(f"Tool Call Response: {tool_response}")
|
||||
|
||||
params["messages"].append(response.choices[0].message)
|
||||
params["messages"].append(
|
||||
{
|
||||
"role": "function",
|
||||
"name": function_call.name,
|
||||
"content": tool_response, # 调用函数返回结果
|
||||
}
|
||||
)
|
||||
self.updateMessage(wxid, tool_response, "function")
|
||||
response = openai.ChatCompletion.create(**params)
|
||||
elif response.choices[0].message.content.find('interpreter') != -1:
|
||||
output_text = response.choices[0].message.content
|
||||
code = extract_code(output_text)
|
||||
self.wcf and self.wcf.send_text('代码如下:\n' + code, wxid)
|
||||
self.wcf and self.wcf.send_text('执行代码...', wxid)
|
||||
try:
|
||||
res_type, res = execute(code, self.kernel)
|
||||
except Exception as e:
|
||||
rsp = f'代码执行错误: {e}'
|
||||
break
|
||||
if res_type == 'image':
|
||||
filename = '{}.png'.format(''.join(random.sample(
|
||||
'abcdefghijklmnopqrstuvwxyz1234567890', 8)))
|
||||
filePath = os.path.join(self.filePath, filename)
|
||||
res.save(filePath)
|
||||
self.wcf and self.wcf.send_image(filePath, wxid)
|
||||
else:
|
||||
self.wcf and self.wcf.send_text("执行结果:\n" + res, wxid)
|
||||
tool_response = '[Image]' if res_type == 'image' else res
|
||||
print("Received:", res_type, res)
|
||||
params["messages"].append(response.choices[0].message)
|
||||
params["messages"].append(
|
||||
{
|
||||
"role": "function",
|
||||
"name": "interpreter",
|
||||
"content": tool_response, # 调用函数返回结果
|
||||
}
|
||||
)
|
||||
self.updateMessage(wxid, tool_response, "function")
|
||||
response = openai.ChatCompletion.create(**params)
|
||||
else:
|
||||
rsp = response.choices[0].message.content
|
||||
break
|
||||
|
||||
self.updateMessage(wxid, rsp, "assistant")
|
||||
except Exception as e0:
|
||||
rsp = "发生未知错误:" + str(e0)
|
||||
|
||||
return rsp
|
||||
|
||||
def updateMessage(self, wxid: str, question: str, role: str) -> None:
|
||||
now_time = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# 初始化聊天记录,组装系统信息
|
||||
if wxid not in self.conversation_list.keys():
|
||||
self.conversation_list[wxid] = self.system_content_msg
|
||||
if wxid not in self.chat_type.keys():
|
||||
self.chat_type[wxid] = 'chat'
|
||||
|
||||
# 当前问题
|
||||
content_question_ = {"role": role, "content": question}
|
||||
self.conversation_list[wxid][self.chat_type[wxid]].append(
|
||||
content_question_)
|
||||
|
||||
# 只存储10条记录,超过滚动清除
|
||||
i = len(self.conversation_list[wxid][self.chat_type[wxid]])
|
||||
if i > 10:
|
||||
print("滚动清除微信记录:" + wxid)
|
||||
# 删除多余的记录,倒着删,且跳过第一个的系统消息
|
||||
del self.conversation_list[wxid][self.chat_type[wxid]][1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from configuration import Config
|
||||
config = Config().CHATGLM
|
||||
if not config:
|
||||
exit(0)
|
||||
|
||||
chat = ChatGLM(config)
|
||||
|
||||
while True:
|
||||
q = input(">>> ")
|
||||
try:
|
||||
time_start = datetime.now() # 记录开始时间
|
||||
print(chat.get_answer(q, "wxid"))
|
||||
time_end = datetime.now() # 记录结束时间
|
||||
|
||||
# 计算的时间差为程序的执行时间,单位为秒/s
|
||||
print(f"{round((time_end - time_start).total_seconds(), 2)}s")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
106
base/func_chatgpt.py
Normal file
106
base/func_chatgpt.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
import httpx
|
||||
from openai import APIConnectionError, APIError, AuthenticationError, OpenAI
|
||||
|
||||
|
||||
class ChatGPT():
|
||||
def __init__(self, conf: dict) -> None:
|
||||
key = conf.get("key")
|
||||
api = conf.get("api")
|
||||
proxy = conf.get("proxy")
|
||||
prompt = conf.get("prompt")
|
||||
self.model = conf.get("model", "gpt-3.5-turbo")
|
||||
self.LOG = logging.getLogger("ChatGPT")
|
||||
if proxy:
|
||||
self.client = OpenAI(api_key=key, base_url=api, http_client=httpx.Client(proxy=proxy))
|
||||
else:
|
||||
self.client = OpenAI(api_key=key, base_url=api)
|
||||
self.conversation_list = {}
|
||||
self.system_content_msg = {"role": "system", "content": prompt}
|
||||
|
||||
def __repr__(self):
|
||||
return 'ChatGPT'
|
||||
|
||||
@staticmethod
|
||||
def value_check(conf: dict) -> bool:
|
||||
if conf:
|
||||
if conf.get("key") and conf.get("api") and conf.get("prompt"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_answer(self, question: str, wxid: str) -> str:
|
||||
# wxid或者roomid,个人时为微信id,群消息时为群id
|
||||
self.updateMessage(wxid, question, "user")
|
||||
rsp = ""
|
||||
try:
|
||||
ret = self.client.chat.completions.create(model=self.model,
|
||||
messages=self.conversation_list[wxid],
|
||||
temperature=0.2)
|
||||
rsp = ret.choices[0].message.content
|
||||
rsp = rsp[2:] if rsp.startswith("\n\n") else rsp
|
||||
rsp = rsp.replace("\n\n", "\n")
|
||||
self.updateMessage(wxid, rsp, "assistant")
|
||||
except AuthenticationError:
|
||||
self.LOG.error("OpenAI API 认证失败,请检查 API 密钥是否正确")
|
||||
except APIConnectionError:
|
||||
self.LOG.error("无法连接到 OpenAI API,请检查网络连接")
|
||||
except APIError as e1:
|
||||
self.LOG.error(f"OpenAI API 返回了错误:{str(e1)}")
|
||||
except Exception as e0:
|
||||
self.LOG.error(f"发生未知错误:{str(e0)}")
|
||||
|
||||
return rsp
|
||||
|
||||
def updateMessage(self, wxid: str, question: str, role: str) -> None:
|
||||
now_time = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
time_mk = "当需要回答时间时请直接参考回复:"
|
||||
# 初始化聊天记录,组装系统信息
|
||||
if wxid not in self.conversation_list.keys():
|
||||
question_ = [
|
||||
self.system_content_msg,
|
||||
{"role": "system", "content": "" + time_mk + now_time}
|
||||
]
|
||||
self.conversation_list[wxid] = question_
|
||||
|
||||
# 当前问题
|
||||
content_question_ = {"role": role, "content": question}
|
||||
self.conversation_list[wxid].append(content_question_)
|
||||
|
||||
for cont in self.conversation_list[wxid]:
|
||||
if cont["role"] != "system":
|
||||
continue
|
||||
if cont["content"].startswith(time_mk):
|
||||
cont["content"] = time_mk + now_time
|
||||
|
||||
# 只存储10条记录,超过滚动清除
|
||||
i = len(self.conversation_list[wxid])
|
||||
if i > 10:
|
||||
print("滚动清除微信记录:" + wxid)
|
||||
# 删除多余的记录,倒着删,且跳过第一个的系统消息
|
||||
del self.conversation_list[wxid][1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from configuration import Config
|
||||
config = Config().CHATGPT
|
||||
if not config:
|
||||
exit(0)
|
||||
|
||||
chat = ChatGPT(config)
|
||||
|
||||
while True:
|
||||
q = input(">>> ")
|
||||
try:
|
||||
time_start = datetime.now() # 记录开始时间
|
||||
print(chat.get_answer(q, "wxid"))
|
||||
time_end = datetime.now() # 记录结束时间
|
||||
|
||||
print(f"{round((time_end - time_start).total_seconds(), 2)}s") # 计算的时间差为程序的执行时间,单位为秒/s
|
||||
except Exception as e:
|
||||
print(e)
|
||||
79
base/func_chengyu.py
Normal file
79
base/func_chengyu.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class Chengyu(object):
|
||||
def __init__(self) -> None:
|
||||
root = os.path.dirname(os.path.abspath(__file__))
|
||||
self.df = pd.read_csv(f"{root}/chengyu.csv", delimiter="\t")
|
||||
self.cys, self.zis, self.yins = self._build_data()
|
||||
|
||||
def _build_data(self):
|
||||
df = self.df.copy()
|
||||
df["shouzi"] = df["chengyu"].apply(lambda x: x[0])
|
||||
df["mozi"] = df["chengyu"].apply(lambda x: x[-1])
|
||||
|
||||
df["shouyin"] = df["pingyin"].apply(lambda x: x.split(" ")[0])
|
||||
df["moyin"] = df["pingyin"].apply(lambda x: x.split(" ")[-1])
|
||||
|
||||
cys = dict(zip(df["chengyu"], df["moyin"]))
|
||||
zis = df.groupby("shouzi").agg({"chengyu": set})["chengyu"].to_dict()
|
||||
yins = df.groupby("shouyin").agg({"chengyu": set})["chengyu"].to_dict()
|
||||
|
||||
return cys, zis, yins
|
||||
|
||||
def isChengyu(self, cy: str) -> bool:
|
||||
return self.cys.get(cy, None) is not None
|
||||
|
||||
def getNext(self, cy: str, tongyin: bool = True) -> str:
|
||||
"""获取下一个成语
|
||||
cy: 当前成语
|
||||
tongyin: 是否允许同音字
|
||||
"""
|
||||
zi = cy[-1]
|
||||
ansers = list(self.zis.get(zi, {}))
|
||||
try:
|
||||
ansers.remove(cy) # 移除当前成语
|
||||
except Exception as e:
|
||||
pass # Just ignore...
|
||||
|
||||
if ansers:
|
||||
return random.choice(ansers)
|
||||
|
||||
# 如果找不到同字,允许同音
|
||||
if tongyin:
|
||||
yin = self.cys.get(cy)
|
||||
ansers = list(self.yins.get(yin, {}))
|
||||
|
||||
try:
|
||||
ansers.remove(cy) # 移除当前成语
|
||||
except Exception as e:
|
||||
pass # Just ignore...
|
||||
|
||||
if ansers:
|
||||
return random.choice(ansers)
|
||||
|
||||
return None
|
||||
|
||||
def getMeaning(self, cy: str) -> str:
|
||||
ress = self.df[self.df["chengyu"] == cy].to_dict(orient="records")
|
||||
if ress:
|
||||
res = ress[0]
|
||||
rsp = res["chengyu"] + "\n" + res["pingyin"] + "\n" + res["jieshi"]
|
||||
if res["chuchu"] and res["chuchu"] != "无":
|
||||
rsp += "\n出处:" + res["chuchu"]
|
||||
if res["lizi"] and res["lizi"] != "无":
|
||||
rsp += "\n例子:" + res["lizi"]
|
||||
return rsp
|
||||
return None
|
||||
|
||||
|
||||
cy = Chengyu()
|
||||
|
||||
if __name__ == "__main__":
|
||||
answer = cy.getNext("便宜行事")
|
||||
print(answer)
|
||||
51
base/func_news.py
Normal file
51
base/func_news.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import re
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from lxml import etree
|
||||
|
||||
|
||||
class News(object):
|
||||
def __init__(self) -> None:
|
||||
self.LOG = logging.getLogger(__name__)
|
||||
self.week = {0: "周一", 1: "周二", 2: "周三", 3: "周四", 4: "周五", 5: "周六", 6: "周日"}
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0"}
|
||||
|
||||
def get_important_news(self):
|
||||
url = "https://www.cls.cn/api/sw?app=CailianpressWeb&os=web&sv=7.7.5"
|
||||
data = {"type": "telegram", "keyword": "你需要知道的隔夜全球要闻", "page": 0,
|
||||
"rn": 1, "os": "web", "sv": "7.7.5", "app": "CailianpressWeb"}
|
||||
try:
|
||||
rsp = requests.post(url=url, headers=self.headers, data=data)
|
||||
data = json.loads(rsp.text)["data"]["telegram"]["data"][0]
|
||||
news = data["descr"]
|
||||
timestamp = data["time"]
|
||||
ts = time.localtime(timestamp)
|
||||
weekday_news = datetime(*ts[:6]).weekday()
|
||||
except Exception as e:
|
||||
self.LOG.error(e)
|
||||
return ""
|
||||
|
||||
weekday_now = datetime.now().weekday()
|
||||
if weekday_news != weekday_now:
|
||||
return "" # 旧闻,观察发现周二~周六早晨6点半左右发布
|
||||
|
||||
fmt_time = time.strftime("%Y年%m月%d日", ts)
|
||||
|
||||
news = re.sub(r"(\d{1,2}、)", r"\n\1", news)
|
||||
fmt_news = "".join(etree.HTML(news).xpath(" // text()"))
|
||||
fmt_news = re.sub(r"周[一|二|三|四|五|六|日]你需要知道的", r"", fmt_news)
|
||||
|
||||
return f"{fmt_time} {self.week[weekday_news]}\n{fmt_news}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
news = News()
|
||||
print(news.get_important_news())
|
||||
61
base/func_report_reminder.py
Normal file
61
base/func_report_reminder.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import calendar
|
||||
import datetime
|
||||
|
||||
from chinese_calendar import is_workday
|
||||
from robot import Robot
|
||||
|
||||
|
||||
class ReportReminder:
|
||||
|
||||
@staticmethod
|
||||
def remind(robot: Robot) -> None:
|
||||
|
||||
receivers = robot.config.REPORT_REMINDERS
|
||||
if not receivers:
|
||||
receivers = ["filehelper"]
|
||||
# 日报周报月报提醒
|
||||
for receiver in receivers:
|
||||
today = datetime.datetime.now().date()
|
||||
# 如果是非工作日
|
||||
if not is_workday(today):
|
||||
robot.sendTextMsg("休息日快乐", receiver)
|
||||
# 如果是工作日
|
||||
if is_workday(today):
|
||||
robot.sendTextMsg("该发日报啦", receiver)
|
||||
# 如果是本周最后一个工作日
|
||||
if ReportReminder.last_work_day_of_week(today) == today:
|
||||
robot.sendTextMsg("该发周报啦", receiver)
|
||||
# 如果本日是本月最后一整周的最后一个工作日:
|
||||
if ReportReminder.last_work_friday_of_month(today) == today:
|
||||
robot.sendTextMsg("该发月报啦", receiver)
|
||||
|
||||
# 计算本月最后一个周的最后一个工作日
|
||||
@staticmethod
|
||||
def last_work_friday_of_month(d: datetime.date) -> datetime.date:
|
||||
days_in_month = calendar.monthrange(d.year, d.month)[1]
|
||||
weekday = calendar.weekday(d.year, d.month, days_in_month)
|
||||
if weekday == 4:
|
||||
last_friday_of_month = datetime.date(
|
||||
d.year, d.month, days_in_month)
|
||||
else:
|
||||
if weekday >= 5:
|
||||
last_friday_of_month = datetime.date(d.year, d.month, days_in_month) - \
|
||||
datetime.timedelta(days=(weekday - 4))
|
||||
else:
|
||||
last_friday_of_month = datetime.date(d.year, d.month, days_in_month) - \
|
||||
datetime.timedelta(days=(weekday + 3))
|
||||
while not is_workday(last_friday_of_month):
|
||||
last_friday_of_month = last_friday_of_month - datetime.timedelta(days=1)
|
||||
return last_friday_of_month
|
||||
|
||||
# 计算本周最后一个工作日
|
||||
@staticmethod
|
||||
def last_work_day_of_week(d: datetime.date) -> datetime.date:
|
||||
weekday = calendar.weekday(d.year, d.month, d.day)
|
||||
last_work_day_of_week = datetime.date(
|
||||
d.year, d.month, d.day) + datetime.timedelta(days=(6 - weekday))
|
||||
|
||||
while not is_workday(last_work_day_of_week):
|
||||
last_work_day_of_week = last_work_day_of_week - \
|
||||
datetime.timedelta(days=1)
|
||||
return last_work_day_of_week
|
||||
49
base/func_tigerbot.py
Normal file
49
base/func_tigerbot.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from random import randint
|
||||
|
||||
|
||||
class TigerBot:
|
||||
def __init__(self, tbconf=None) -> None:
|
||||
self.LOG = logging.getLogger(__file__)
|
||||
self.tburl = "https://api.tigerbot.com/bot-service/ai_service/gpt"
|
||||
self.tbheaders = {"Authorization": "Bearer " + tbconf["key"]}
|
||||
self.tbmodel = tbconf["model"]
|
||||
self.fallback = ["滚", "快滚", "赶紧滚"]
|
||||
|
||||
def __repr__(self):
|
||||
return 'TigerBot'
|
||||
|
||||
@staticmethod
|
||||
def value_check(conf: dict) -> bool:
|
||||
if conf:
|
||||
return all(conf.values())
|
||||
return False
|
||||
|
||||
def get_answer(self, msg: str, sender: str = None) -> str:
|
||||
payload = {
|
||||
"text": msg,
|
||||
"modelVersion": self.tbmodel
|
||||
}
|
||||
rsp = ""
|
||||
try:
|
||||
rsp = requests.post(self.tburl, headers=self.tbheaders, json=payload).json()
|
||||
rsp = rsp["data"]["result"][0]
|
||||
except Exception as e:
|
||||
self.LOG.error(f"{e}: {payload}\n{rsp}")
|
||||
idx = randint(0, len(self.fallback) - 1)
|
||||
rsp = self.fallback[idx]
|
||||
|
||||
return rsp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from configuration import Config
|
||||
c = Config()
|
||||
tbot = TigerBot(c.TIGERBOT)
|
||||
rsp = tbot.get_answer("你还活着?")
|
||||
print(rsp)
|
||||
38
base/func_xinghuo_web.py
Normal file
38
base/func_xinghuo_web.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from sparkdesk_web.core import SparkWeb
|
||||
|
||||
|
||||
class XinghuoWeb:
|
||||
def __init__(self, xhconf=None) -> None:
|
||||
|
||||
self._sparkWeb = SparkWeb(
|
||||
cookie=xhconf["cookie"],
|
||||
fd=xhconf["fd"],
|
||||
GtToken=xhconf["GtToken"],
|
||||
)
|
||||
self._chat = self._sparkWeb.create_continuous_chat()
|
||||
# 如果有提示词
|
||||
if xhconf["prompt"]:
|
||||
self._chat.chat(xhconf["prompt"])
|
||||
|
||||
def __repr__(self):
|
||||
return 'XinghuoWeb'
|
||||
|
||||
@staticmethod
|
||||
def value_check(conf: dict) -> bool:
|
||||
if conf:
|
||||
return all(conf.values())
|
||||
return False
|
||||
|
||||
def get_answer(self, msg: str, sender: str = None) -> str:
|
||||
answer = self._chat.chat(msg)
|
||||
return answer
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from configuration import Config
|
||||
c = Config()
|
||||
xinghuo = XinghuoWeb(c.XINGHUO_WEB)
|
||||
rsp = xinghuo.get_answer("你还活着?")
|
||||
print(rsp)
|
||||
46
base/func_zhipu.py
Normal file
46
base/func_zhipu.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from zhipuai import ZhipuAI
|
||||
|
||||
|
||||
class ZhiPu():
|
||||
def __init__(self, conf: dict) -> None:
|
||||
self.api_key = conf.get("api_key")
|
||||
self.model = conf.get("model", "glm-4") # 默认使用 glm-4 模型
|
||||
self.client = ZhipuAI(api_key=self.api_key)
|
||||
self.converstion_list = {}
|
||||
|
||||
@staticmethod
|
||||
def value_check(conf: dict) -> bool:
|
||||
if conf and conf.get("api_key"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return 'ZhiPu'
|
||||
|
||||
def get_answer(self, msg: str, wxid: str, **args) -> str:
|
||||
self._update_message(wxid, str(msg), "user")
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=self.converstion_list[wxid]
|
||||
)
|
||||
resp_msg = response.choices[0].message
|
||||
answer = resp_msg.content
|
||||
self._update_message(wxid, answer, "assistant")
|
||||
return answer
|
||||
|
||||
def _update_message(self, wxid: str, msg: str, role: str) -> None:
|
||||
if wxid not in self.converstion_list.keys():
|
||||
self.converstion_list[wxid] = []
|
||||
content = {"role": role, "content": str(msg)}
|
||||
self.converstion_list[wxid].append(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from configuration import Config
|
||||
config = Config().ZHIPU
|
||||
if not config:
|
||||
exit(0)
|
||||
|
||||
zhipu = ZhiPu(config)
|
||||
rsp = zhipu.get_answer("你好")
|
||||
print(rsp)
|
||||
84
config.yaml.template
Normal file
84
config.yaml.template
Normal file
@@ -0,0 +1,84 @@
|
||||
logging:
|
||||
version: 1
|
||||
disable_existing_loggers: False
|
||||
|
||||
formatters:
|
||||
simple:
|
||||
format: "%(asctime)s %(message)s"
|
||||
datefmt: "%Y-%m-%d %H:%M:%S"
|
||||
error:
|
||||
format: "%(asctime)s %(name)s %(levelname)s %(filename)s::%(funcName)s[%(lineno)d]:%(message)s"
|
||||
|
||||
handlers:
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
level: INFO
|
||||
formatter: simple
|
||||
stream: ext://sys.stdout
|
||||
|
||||
info_file_handler:
|
||||
class: logging.handlers.RotatingFileHandler
|
||||
level: INFO
|
||||
formatter: simple
|
||||
filename: wx_info.log
|
||||
maxBytes: 10485760 # 10MB
|
||||
backupCount: 20
|
||||
encoding: utf8
|
||||
|
||||
error_file_handler:
|
||||
class: logging.handlers.RotatingFileHandler
|
||||
level: ERROR
|
||||
formatter: error
|
||||
filename: wx_error.log
|
||||
maxBytes: 10485760 # 10MB
|
||||
backupCount: 20
|
||||
encoding: utf8
|
||||
|
||||
root:
|
||||
level: INFO
|
||||
handlers: [console, info_file_handler, error_file_handler]
|
||||
|
||||
groups:
|
||||
enable: [] # 允许响应的群 roomId,大概长这样:2xxxxxxxxx3@chatroom
|
||||
|
||||
news:
|
||||
receivers: [] # 定时新闻接收人(roomid 或者 wxid)
|
||||
|
||||
report_reminder:
|
||||
receivers: [] # 定时日报周报月报提醒(roomid 或者 wxid)
|
||||
|
||||
chatgpt: # -----chatgpt配置这行不填-----
|
||||
key: # 填写你 ChatGPT 的 key
|
||||
api: https://api.openai.com/v1 # 如果你不知道这是干嘛的,就不要改
|
||||
model: gpt-3.5-turbo
|
||||
proxy: # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
prompt: 你是智能聊天机器人,你叫 wcferry # 根据需要对角色进行设定
|
||||
|
||||
chatglm: # -----chatglm配置这行不填-----
|
||||
key: sk-012345678901234567890123456789012345678901234567 # 这个应该不用动
|
||||
api: http://localhost:8000/v1 # 根据自己的chatglm地址修改
|
||||
proxy: # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
prompt: 你是智能聊天机器人,你叫小薇 # 根据需要对角色进行设定
|
||||
file_path: F:/Pictures/temp #设定生成图片和代码使用的文件夹路径
|
||||
|
||||
tigerbot: # -----tigerbot配置这行不填-----
|
||||
key: # key
|
||||
model: # tigerbot-7b-sft
|
||||
|
||||
xinghuo_web: # -----讯飞星火web模式api配置这行不填 抓取方式详见文档:https://www.bilibili.com/read/cv27066577-----
|
||||
cookie: # cookie
|
||||
fd: # fd
|
||||
GtToken: # GtToken
|
||||
prompt: 你是智能聊天机器人,你叫 wcferry。请用这个角色回答我的问题 # 根据需要对角色进行设定
|
||||
|
||||
bard: # -----bard配置这行不填-----
|
||||
api_key: # api-key 创建地址:https://ai.google.dev/pricing?hl=en,创建后复制过来即可
|
||||
model_name: gemini-pro # 新模型上线后可以选择模型
|
||||
proxy: http://127.0.0.1:7890 # 如果你在国内,你可能需要魔法,大概长这样:http://域名或者IP地址:端口号
|
||||
# 提示词尽可能用英文,bard对中文提示词的效果不是很理想,下方提示词为英语老师的示例,请按实际需要修改,默认设置的提示词为谷歌创造的AI大语言模型
|
||||
# I want you to act as a spoken English teacher and improver. I will speak to you in English and you will reply to me in English to practice my spoken English. I want you to keep your reply neat, limiting the reply to 100 words. I want you to strictly correct my grammar mistakes, typos, and factual errors. I want you to ask me a question in your reply. Now let's start practicing, you could ask me a question first. Remember, I want you to strictly correct my grammar mistakes, typos, and factual errors.
|
||||
prompt: You am a large language model, trained by Google.
|
||||
|
||||
zhipu: # -----zhipu配置这行不填-----
|
||||
api_key: #api key
|
||||
model: # 模型类型
|
||||
39
configuration.py
Normal file
39
configuration.py
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging.config
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class Config(object):
|
||||
def __init__(self) -> None:
|
||||
self.reload()
|
||||
|
||||
def _load_config(self) -> dict:
|
||||
pwd = os.path.dirname(os.path.abspath(__file__))
|
||||
try:
|
||||
with open(f"{pwd}/config.yaml", "rb") as fp:
|
||||
yconfig = yaml.safe_load(fp)
|
||||
except FileNotFoundError:
|
||||
shutil.copyfile(f"{pwd}/config.yaml.template", f"{pwd}/config.yaml")
|
||||
with open(f"{pwd}/config.yaml", "rb") as fp:
|
||||
yconfig = yaml.safe_load(fp)
|
||||
|
||||
return yconfig
|
||||
|
||||
def reload(self) -> None:
|
||||
yconfig = self._load_config()
|
||||
logging.config.dictConfig(yconfig["logging"])
|
||||
self.GROUPS = yconfig["groups"]["enable"]
|
||||
self.NEWS = yconfig["news"]["receivers"]
|
||||
self.REPORT_REMINDERS = yconfig["report_reminder"]["receivers"]
|
||||
|
||||
self.CHATGPT = yconfig.get("chatgpt", {})
|
||||
self.TIGERBOT = yconfig.get("tigerbot", {})
|
||||
self.XINGHUO_WEB = yconfig.get("xinghuo_web", {})
|
||||
self.CHATGLM = yconfig.get("chatglm", {})
|
||||
self.BardAssistant = yconfig.get("bard", {})
|
||||
self.ZhiPu = yconfig.get("zhipu", {})
|
||||
26
constants.py
Normal file
26
constants.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from enum import IntEnum, unique
|
||||
|
||||
|
||||
@unique
|
||||
class ChatType(IntEnum):
|
||||
# UnKnown = 0 # 未知, 即未设置
|
||||
TIGER_BOT = 1 # TigerBot
|
||||
CHATGPT = 2 # ChatGPT
|
||||
XINGHUO_WEB = 3 # 讯飞星火
|
||||
CHATGLM = 4 # ChatGLM
|
||||
BardAssistant = 5 # Google Bard
|
||||
ZhiPu = 6 # ZhiPu
|
||||
|
||||
@staticmethod
|
||||
def is_in_chat_types(chat_type: int) -> bool:
|
||||
if chat_type in [ChatType.TIGER_BOT.value, ChatType.CHATGPT.value,
|
||||
ChatType.XINGHUO_WEB.value, ChatType.CHATGLM.value,
|
||||
ChatType.BardAssistant.value]:
|
||||
return True
|
||||
# return False
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def help_hint() -> str:
|
||||
return str({member.value: member.name for member in ChatType}).replace('{', '').replace('}', '')
|
||||
84
job_mgmt.py
Normal file
84
job_mgmt.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from typing import Any, Callable
|
||||
|
||||
import schedule
|
||||
|
||||
|
||||
class Job(object):
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def onEverySeconds(self, seconds: int, task: Callable[..., Any], *args, **kwargs) -> None:
|
||||
"""
|
||||
每 seconds 秒执行
|
||||
:param seconds: 间隔,秒
|
||||
:param task: 定时执行的方法
|
||||
:return: None
|
||||
"""
|
||||
schedule.every(seconds).seconds.do(task, *args, **kwargs)
|
||||
|
||||
def onEveryMinutes(self, minutes: int, task: Callable[..., Any], *args, **kwargs) -> None:
|
||||
"""
|
||||
每 minutes 分钟执行
|
||||
:param minutes: 间隔,分钟
|
||||
:param task: 定时执行的方法
|
||||
:return: None
|
||||
"""
|
||||
schedule.every(minutes).minutes.do(task, *args, **kwargs)
|
||||
|
||||
def onEveryHours(self, hours: int, task: Callable[..., Any], *args, **kwargs) -> None:
|
||||
"""
|
||||
每 hours 小时执行
|
||||
:param hours: 间隔,小时
|
||||
:param task: 定时执行的方法
|
||||
:return: None
|
||||
"""
|
||||
schedule.every(hours).hours.do(task, *args, **kwargs)
|
||||
|
||||
def onEveryDays(self, days: int, task: Callable[..., Any], *args, **kwargs) -> None:
|
||||
"""
|
||||
每 days 天执行
|
||||
:param days: 间隔,天
|
||||
:param task: 定时执行的方法
|
||||
:return: None
|
||||
"""
|
||||
schedule.every(days).days.do(task, *args, **kwargs)
|
||||
|
||||
def onEveryTime(self, times: int, task: Callable[..., Any], *args, **kwargs) -> None:
|
||||
"""
|
||||
每天定时执行
|
||||
:param times: 时间字符串列表,格式:
|
||||
- For daily jobs -> HH:MM:SS or HH:MM
|
||||
- For hourly jobs -> MM:SS or :MM
|
||||
- For minute jobs -> :SS
|
||||
:param task: 定时执行的方法
|
||||
:return: None
|
||||
|
||||
例子: times=["10:30", "10:45", "11:00"]
|
||||
"""
|
||||
if not isinstance(times, list):
|
||||
times = [times]
|
||||
|
||||
for t in times:
|
||||
schedule.every(1).days.at(t).do(task, *args, **kwargs)
|
||||
|
||||
def runPendingJobs(self) -> None:
|
||||
schedule.run_pending()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def printStr(s):
|
||||
print(s)
|
||||
|
||||
job = Job()
|
||||
job.onEverySeconds(59, printStr, "onEverySeconds 59")
|
||||
job.onEveryMinutes(59, printStr, "onEveryMinutes 59")
|
||||
job.onEveryHours(23, printStr, "onEveryHours 23")
|
||||
job.onEveryDays(1, printStr, "onEveryDays 1")
|
||||
job.onEveryTime("23:59", printStr, "onEveryTime 23:59")
|
||||
|
||||
while True:
|
||||
job.runPendingJobs()
|
||||
time.sleep(1)
|
||||
71
main.py
Normal file
71
main.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import signal
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from base.func_report_reminder import ReportReminder
|
||||
from configuration import Config
|
||||
from constants import ChatType
|
||||
from robot import Robot, __version__
|
||||
from wcferry import Wcf
|
||||
|
||||
|
||||
def weather_report(robot: Robot) -> None:
|
||||
"""模拟发送天气预报
|
||||
"""
|
||||
|
||||
# 获取接收人
|
||||
receivers = ["filehelper"]
|
||||
|
||||
# 获取天气,需要自己实现,可以参考 https://gitee.com/lch0821/WeatherScrapy 获取天气。
|
||||
report = "这就是获取到的天气情况了"
|
||||
|
||||
for r in receivers:
|
||||
robot.sendTextMsg(report, r)
|
||||
# robot.sendTextMsg(report, r, "notify@all") # 发送消息并@所有人
|
||||
|
||||
|
||||
def main(chat_type: int):
|
||||
config = Config()
|
||||
# 开启调试模式
|
||||
wcf = Wcf(debug=True)
|
||||
|
||||
def handler(sig, frame):
|
||||
wcf.cleanup() # 退出前清理环境
|
||||
exit(0)
|
||||
# 进程通信
|
||||
signal.signal(signal.SIGINT, handler)
|
||||
|
||||
# 自定义函数功能robot
|
||||
robot = Robot(config, wcf, chat_type)
|
||||
|
||||
# 终端日志
|
||||
robot.LOG.info(f"WeChatRobot【{__version__}】成功启动···")
|
||||
|
||||
# 机器人=>文件传输助手启动发送测试消息
|
||||
robot.sendTextMsg("机器人启动成功!", "filehelper")
|
||||
|
||||
# 接收消息
|
||||
# robot.enableRecvMsg() # 可能会丢消息?
|
||||
robot.enableReceivingMsg() # 加队列
|
||||
|
||||
# 每天 7 点发送天气预报
|
||||
# robot.onEveryTime("07:00", weather_report, robot=robot)
|
||||
|
||||
# 每天 7:30 发送新闻
|
||||
# robot.onEveryTime("07:30", robot.newsReport)
|
||||
|
||||
# 每天 16:30 提醒发日报周报月报
|
||||
# robot.onEveryTime("16:30", ReportReminder.remind, robot=robot)
|
||||
|
||||
# 让机器人一直跑
|
||||
robot.keepRunningAndBlockProcess()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-c', type=int, default=0, help=f'选择模型参数序号: {ChatType.help_hint()}')
|
||||
args = parser.parse_args()
|
||||
print('args',args)
|
||||
main(args)
|
||||
17
requirements.txt
Normal file
17
requirements.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
chinese_calendar
|
||||
lxml
|
||||
openai>1.0.0
|
||||
pandas
|
||||
pyyaml
|
||||
requests
|
||||
schedule
|
||||
pyhandytools
|
||||
sparkdesk-api==1.3.0
|
||||
wcferry>=39.0.10.0
|
||||
websocket
|
||||
pillow
|
||||
jupyter_client
|
||||
zhdate
|
||||
ipykernel
|
||||
google-generativeai
|
||||
zhipuai
|
||||
269
robot.py
Normal file
269
robot.py
Normal file
@@ -0,0 +1,269 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from queue import Empty
|
||||
from threading import Thread
|
||||
from base.func_zhipu import ZhiPu
|
||||
|
||||
from wcferry import Wcf, WxMsg
|
||||
|
||||
from base.func_bard import BardAssistant
|
||||
from base.func_chatglm import ChatGLM
|
||||
from base.func_chatgpt import ChatGPT
|
||||
from base.func_chengyu import cy
|
||||
from base.func_news import News
|
||||
from base.func_tigerbot import TigerBot
|
||||
from base.func_xinghuo_web import XinghuoWeb
|
||||
from configuration import Config
|
||||
from constants import ChatType
|
||||
from job_mgmt import Job
|
||||
|
||||
__version__ = "39.0.10.1"
|
||||
|
||||
|
||||
class Robot(Job):
|
||||
"""个性化自己的机器人
|
||||
"""
|
||||
|
||||
def __init__(self, config: Config, wcf: Wcf, chat_type: int) -> None:
|
||||
self.wcf = wcf
|
||||
self.config = config
|
||||
self.LOG = logging.getLogger("Robot")
|
||||
self.wxid = self.wcf.get_self_wxid()
|
||||
# 获取所有联系人
|
||||
self.allContacts = self.getAllContacts()
|
||||
self.chat = None
|
||||
|
||||
# if ChatType.is_in_chat_types(chat_type):
|
||||
# if chat_type == ChatType.TIGER_BOT.value and TigerBot.value_check(self.config.TIGERBOT):
|
||||
# self.chat = TigerBot(self.config.TIGERBOT)
|
||||
# elif chat_type == ChatType.CHATGPT.value and ChatGPT.value_check(self.config.CHATGPT):
|
||||
# self.chat = ChatGPT(self.config.CHATGPT)
|
||||
# elif chat_type == ChatType.XINGHUO_WEB.value and XinghuoWeb.value_check(self.config.XINGHUO_WEB):
|
||||
# self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
|
||||
# elif chat_type == ChatType.CHATGLM.value and ChatGLM.value_check(self.config.CHATGLM):
|
||||
# self.chat = ChatGLM(self.config.CHATGLM)
|
||||
# elif chat_type == ChatType.BardAssistant.value and BardAssistant.value_check(self.config.BardAssistant):
|
||||
# self.chat = BardAssistant(self.config.BardAssistant)
|
||||
# elif chat_type == ChatType.ZhiPu.value and ZhiPu.value_check(self.config.ZHIPU):
|
||||
# self.chat = ZhiPu(self.config.ZHIPU)
|
||||
# else:
|
||||
# self.LOG.warning("未配置模型")
|
||||
# self.chat = None
|
||||
# else:
|
||||
# if TigerBot.value_check(self.config.TIGERBOT):
|
||||
# self.chat = TigerBot(self.config.TIGERBOT)
|
||||
# elif ChatGPT.value_check(self.config.CHATGPT):
|
||||
# self.chat = ChatGPT(self.config.CHATGPT)
|
||||
# elif XinghuoWeb.value_check(self.config.XINGHUO_WEB):
|
||||
# self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
|
||||
# elif ChatGLM.value_check(self.config.CHATGLM):
|
||||
# self.chat = ChatGLM(self.config.CHATGLM)
|
||||
# elif BardAssistant.value_check(self.config.BardAssistant):
|
||||
# self.chat = BardAssistant(self.config.BardAssistant)
|
||||
# elif ZhiPu.value_check(self.config.ZhiPu):
|
||||
# self.chat = ZhiPu(self.config.ZhiPu)
|
||||
# else:
|
||||
# self.LOG.warning("未配置模型")
|
||||
# self.chat = None
|
||||
|
||||
# self.LOG.info(f"已选择: {self.chat}")
|
||||
|
||||
@staticmethod
|
||||
def value_check(args: dict) -> bool:
|
||||
if args:
|
||||
return all(value is not None for key, value in args.items() if key != 'proxy')
|
||||
return False
|
||||
|
||||
def toAt(self, msg: WxMsg) -> bool:
|
||||
"""处理被 @ 消息
|
||||
:param msg: 微信消息结构
|
||||
:return: 处理状态,`True` 成功,`False` 失败
|
||||
"""
|
||||
return self.toChitchat(msg)
|
||||
|
||||
def toChengyu(self, msg: WxMsg) -> bool:
|
||||
"""
|
||||
处理成语查询/接龙消息
|
||||
:param msg: 微信消息结构
|
||||
:return: 处理状态,`True` 成功,`False` 失败
|
||||
"""
|
||||
status = False
|
||||
texts = re.findall(r"^([#|?|?])(.*)$", msg.content)
|
||||
# [('#', '天天向上')]
|
||||
if texts:
|
||||
flag = texts[0][0]
|
||||
text = texts[0][1]
|
||||
if flag == "#": # 接龙
|
||||
if cy.isChengyu(text):
|
||||
rsp = cy.getNext(text)
|
||||
if rsp:
|
||||
self.sendTextMsg(rsp, msg.roomid)
|
||||
status = True
|
||||
elif flag in ["?", "?"]: # 查词
|
||||
if cy.isChengyu(text):
|
||||
rsp = cy.getMeaning(text)
|
||||
if rsp:
|
||||
self.sendTextMsg(rsp, msg.roomid)
|
||||
status = True
|
||||
|
||||
return status
|
||||
|
||||
def toChitchat(self, msg: WxMsg) -> bool:
|
||||
"""闲聊,接入 ChatGPT
|
||||
"""
|
||||
if not self.chat: # 没接 ChatGPT,固定回复
|
||||
rsp = "你@我干嘛?"
|
||||
else: # 接了 ChatGPT,智能回复
|
||||
q = re.sub(r"@.*?[\u2005|\s]", "", msg.content).replace(" ", "")
|
||||
rsp = self.chat.get_answer(q, (msg.roomid if msg.from_group() else msg.sender))
|
||||
|
||||
if rsp:
|
||||
# 回复群消息
|
||||
if msg.from_group():
|
||||
self.sendTextMsg(rsp, msg.roomid, msg.sender)
|
||||
# 回复私聊
|
||||
else:
|
||||
self.sendTextMsg(rsp, msg.sender)
|
||||
|
||||
return True
|
||||
else:
|
||||
self.LOG.error(f"无法从 ChatGPT 获得答案")
|
||||
return False
|
||||
|
||||
def processMsg(self, msg: WxMsg) -> None:
|
||||
"""当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。
|
||||
此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者
|
||||
群号:msg.roomid 微信ID:msg.sender 消息内容:msg.content
|
||||
content = "xx天气信息为:"
|
||||
receivers = msg.roomid
|
||||
self.sendTextMsg(content, receivers, msg.sender)
|
||||
"""
|
||||
|
||||
# 群聊消息
|
||||
if msg.from_group():
|
||||
# 如果在群里被 @
|
||||
if msg.roomid not in self.config.GROUPS: # 不在配置的响应的群列表里,忽略
|
||||
return
|
||||
|
||||
if msg.is_at(self.wxid): # 被@
|
||||
self.toAt(msg)
|
||||
|
||||
else: # 其他消息
|
||||
self.toChengyu(msg)
|
||||
|
||||
return # 处理完群聊信息,后面就不需要处理了
|
||||
|
||||
# 非群聊信息,按消息类型进行处理
|
||||
if msg.type == 37: # 好友请求
|
||||
self.autoAcceptFriendRequest(msg)
|
||||
|
||||
elif msg.type == 10000: # 系统信息
|
||||
self.sayHiToNewFriend(msg)
|
||||
|
||||
elif msg.type == 0x01: # 文本消息
|
||||
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
|
||||
if msg.from_self():
|
||||
if msg.content == "^更新$":
|
||||
self.config.reload()
|
||||
self.LOG.info("已更新")
|
||||
else:
|
||||
self.toChitchat(msg) # 闲聊
|
||||
|
||||
def onMsg(self, msg: WxMsg) -> int:
|
||||
try:
|
||||
self.LOG.info(msg) # 打印信息
|
||||
self.processMsg(msg)
|
||||
except Exception as e:
|
||||
self.LOG.error(e)
|
||||
|
||||
return 0
|
||||
|
||||
def enableRecvMsg(self) -> None:
|
||||
self.wcf.enable_recv_msg(self.onMsg)
|
||||
|
||||
def enableReceivingMsg(self) -> None:
|
||||
def innerProcessMsg(wcf: Wcf):
|
||||
while wcf.is_receiving_msg():
|
||||
try:
|
||||
msg = wcf.get_msg()
|
||||
self.LOG.info(msg)
|
||||
self.processMsg(msg)
|
||||
except Empty:
|
||||
continue # Empty message
|
||||
except Exception as e:
|
||||
self.LOG.error(f"Receiving message error: {e}")
|
||||
|
||||
self.wcf.enable_receiving_msg()
|
||||
Thread(target=innerProcessMsg, name="GetMessage", args=(self.wcf,), daemon=True).start()
|
||||
|
||||
def sendTextMsg(self, msg: str, receiver: str, at_list: str = "") -> None:
|
||||
""" 发送消息
|
||||
:param msg: 消息字符串
|
||||
:param receiver: 接收人wxid或者群id
|
||||
:param at_list: 要@的wxid, @所有人的wxid为:notify@all
|
||||
"""
|
||||
# msg 中需要有 @ 名单中一样数量的 @
|
||||
ats = ""
|
||||
if at_list:
|
||||
if at_list == "notify@all": # @所有人
|
||||
ats = " @所有人"
|
||||
else:
|
||||
wxids = at_list.split(",")
|
||||
for wxid in wxids:
|
||||
# 根据 wxid 查找群昵称
|
||||
ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
|
||||
|
||||
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
|
||||
if ats == "":
|
||||
self.LOG.info(f"To {receiver}: {msg}")
|
||||
self.wcf.send_text(f"{msg}", receiver, at_list)
|
||||
else:
|
||||
self.LOG.info(f"To {receiver}: {ats}\r{msg}")
|
||||
self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
|
||||
|
||||
def getAllContacts(self) -> dict:
|
||||
"""
|
||||
获取联系人(包括好友、公众号、服务号、群成员……)
|
||||
格式: {"wxid": "NickName"}
|
||||
"""
|
||||
contacts = self.wcf.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;")
|
||||
return {contact["UserName"]: contact["NickName"] for contact in contacts}
|
||||
|
||||
def keepRunningAndBlockProcess(self) -> None:
|
||||
"""
|
||||
保持机器人运行,不让进程退出
|
||||
"""
|
||||
while True:
|
||||
self.runPendingJobs()
|
||||
time.sleep(1)
|
||||
|
||||
def autoAcceptFriendRequest(self, msg: WxMsg) -> None:
|
||||
try:
|
||||
xml = ET.fromstring(msg.content)
|
||||
v3 = xml.attrib["encryptusername"]
|
||||
v4 = xml.attrib["ticket"]
|
||||
scene = int(xml.attrib["scene"])
|
||||
self.wcf.accept_new_friend(v3, v4, scene)
|
||||
|
||||
except Exception as e:
|
||||
self.LOG.error(f"同意好友出错:{e}")
|
||||
|
||||
def sayHiToNewFriend(self, msg: WxMsg) -> None:
|
||||
nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content)
|
||||
if nickName:
|
||||
# 添加了好友,更新好友列表
|
||||
self.allContacts[msg.sender] = nickName[0]
|
||||
self.sendTextMsg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender)
|
||||
|
||||
def newsReport(self) -> None:
|
||||
receivers = self.config.NEWS
|
||||
if not receivers:
|
||||
return
|
||||
|
||||
news = News().get_important_news()
|
||||
for r in receivers:
|
||||
self.sendTextMsg(news, r)
|
||||
Reference in New Issue
Block a user