Compare commits
292 Commits
publish2.9
...
v3.0.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a56b7bff5 | ||
|
|
81e8997852 | ||
|
|
372a204ba9 | ||
|
|
15ad5aae35 | ||
|
|
fd2e9ef93f | ||
|
|
5be3bf1f46 | ||
|
|
4915c2d480 | ||
|
|
bd56a19ac5 | ||
|
|
da8fa2d905 | ||
|
|
f56fd100d7 | ||
|
|
b725a1a20c | ||
|
|
ff1b5d02d2 | ||
|
|
d4882a8240 | ||
|
|
e37f84c1ae | ||
|
|
a23bd0a63c | ||
|
|
ae00e84974 | ||
|
|
53b3250978 | ||
|
|
7f15a59a4e | ||
|
|
6a164c9961 | ||
|
|
bd779a3df3 | ||
|
|
9ebb340c00 | ||
|
|
e8edbaae2d | ||
|
|
2aab1f4c96 | ||
|
|
90ea621c65 | ||
|
|
34bdceb41b | ||
|
|
6d2ded1c6c | ||
|
|
9b926048ca | ||
|
|
9cf4f0f57d | ||
|
|
9123b9d773 | ||
|
|
f9258ae1e1 | ||
|
|
d8808de4a9 | ||
|
|
afcb152d8d | ||
|
|
ff01174a1f | ||
|
|
71f1625284 | ||
|
|
19e3390083 | ||
|
|
3015b90e12 | ||
|
|
aa419f3ef9 | ||
|
|
954236c284 | ||
|
|
72d6b3886b | ||
|
|
a95046ecaf | ||
|
|
ccdb11575b | ||
|
|
7e68b2f2be | ||
|
|
39efab1081 | ||
|
|
cc6707c8ce | ||
|
|
09080adf84 | ||
|
|
4cc72030c0 | ||
|
|
a395902184 | ||
|
|
5156f0584a | ||
|
|
be171fe0d7 | ||
|
|
ad4bf5e654 | ||
|
|
da7429ad62 | ||
|
|
b5f20ee282 | ||
|
|
a9023d6f3a | ||
|
|
628b661a18 | ||
|
|
638fe466f8 | ||
|
|
a90adcf15c | ||
|
|
7896066db6 | ||
|
|
b1314bcc31 | ||
|
|
b1ecc929f2 | ||
|
|
3aad42a886 | ||
|
|
b6e87d3d31 | ||
|
|
461eb4b9c7 | ||
|
|
a89e92d5cc | ||
|
|
6e69e88e91 | ||
|
|
ae732c1dac | ||
|
|
8e4a72c97b | ||
|
|
bf0d82fe67 | ||
|
|
987383f957 | ||
|
|
c2cacf3281 | ||
|
|
72878477dc | ||
|
|
ad0d14420a | ||
|
|
5a7c60c81e | ||
|
|
6011840d1f | ||
|
|
9a2dffe299 | ||
|
|
e6770d2b12 | ||
|
|
255db6ee57 | ||
|
|
aa9ff99557 | ||
|
|
5f024e9f30 | ||
|
|
cbdc7b7ce4 | ||
|
|
5f636ca061 | ||
|
|
9fa3651170 | ||
|
|
bba66788c3 | ||
|
|
200f3cce00 | ||
|
|
938490b739 | ||
|
|
e77e7b050a | ||
|
|
bd2dbe5b63 | ||
|
|
c684d9cb4a | ||
|
|
7a39a9d45e | ||
|
|
2a3bb068db | ||
|
|
1aa4384ca3 | ||
|
|
3b26b7b26c | ||
|
|
3b097d662b | ||
|
|
c3acb3e77f | ||
|
|
55d58d30a8 | ||
|
|
020a8ace9f | ||
|
|
15f56ffc01 | ||
|
|
3724659b32 | ||
|
|
df77152581 | ||
|
|
339ea5f12a | ||
|
|
36f96ccc97 | ||
|
|
190e0a4971 | ||
|
|
72638fac68 | ||
|
|
807d19e381 | ||
|
|
10870172b4 | ||
|
|
1f7d3eccf9 | ||
|
|
5fc58123bb | ||
|
|
c84c9f4aaa | ||
|
|
cabe66fc0a | ||
|
|
9f1315b06d | ||
|
|
6f27f59730 | ||
|
|
17815e7fe3 | ||
|
|
596ae80fea | ||
|
|
be2dc6ba70 | ||
|
|
e5aa8c8270 | ||
|
|
7c5ac41c55 | ||
|
|
c6cf1153c1 | ||
|
|
a68338b651 | ||
|
|
bab46e912e | ||
|
|
4b158a1c89 | ||
|
|
6894900e46 | ||
|
|
2e11d6e007 | ||
|
|
348381be15 | ||
|
|
9024c28e70 | ||
|
|
ae1702901b | ||
|
|
c1c0df85e6 | ||
|
|
f3c6d9c02b | ||
|
|
811a885411 | ||
|
|
b4ec28b71c | ||
|
|
cdf4a5321b | ||
|
|
d83f155f80 | ||
|
|
4c402ed5bd | ||
|
|
ec5aff8d0b | ||
|
|
eae0d6c422 | ||
|
|
9c284b84b1 | ||
|
|
9f36e5ae05 | ||
|
|
7caa380e54 | ||
|
|
41d81bb60e | ||
|
|
454a74f4e1 | ||
|
|
c5bdad02e5 | ||
|
|
f46de3d518 | ||
|
|
a3e21bea1a | ||
|
|
d7e4707d5d | ||
|
|
a78ebf2fd7 | ||
|
|
bd11541678 | ||
|
|
0d99aa81e6 | ||
|
|
f104d40d0a | ||
|
|
0d69f8ab8a | ||
|
|
66d1fc08b6 | ||
|
|
e32fc27728 | ||
|
|
eec890cd02 | ||
|
|
d30881e59b | ||
|
|
9afaf83368 | ||
|
|
33f9a9cfa0 | ||
|
|
bf72d5fa27 | ||
|
|
567c29bcd6 | ||
|
|
dcdfe453fb | ||
|
|
0d23c0900b | ||
|
|
86eda7bdf8 | ||
|
|
1e46525b0f | ||
|
|
8d41efea4d | ||
|
|
f15d0eb0eb | ||
|
|
1795362bcd | ||
|
|
2bf9c82617 | ||
|
|
33793a2053 | ||
|
|
656fe14af4 | ||
|
|
46197d49a4 | ||
|
|
843ab56f50 | ||
|
|
6b4b52f3c5 | ||
|
|
392e5cd592 | ||
|
|
d273019830 | ||
|
|
fd59ec4b6c | ||
|
|
bf33ccafca | ||
|
|
425936872d | ||
|
|
6627b2e1e5 | ||
|
|
323c2cecf8 | ||
|
|
5b1dd3dce9 | ||
|
|
54af770dfb | ||
|
|
30a48fea6e | ||
|
|
cfd5fb1452 | ||
|
|
a78984376f | ||
|
|
9887cae43c | ||
|
|
e63fe60f8d | ||
|
|
b0ac2d676c | ||
|
|
5ef515165c | ||
|
|
e21d43f920 | ||
|
|
3a80ffad88 | ||
|
|
47506d60cd | ||
|
|
b999b712b7 | ||
|
|
6860ba3f05 | ||
|
|
02594867c0 | ||
|
|
250435f3e7 | ||
|
|
3c593fb6f7 | ||
|
|
807cad5c48 | ||
|
|
e92ecdd3f8 | ||
|
|
1c91079d8f | ||
|
|
376b2fef40 | ||
|
|
300f3b6df8 | ||
|
|
6e6f6d5cd4 | ||
|
|
077e54d0f1 | ||
|
|
18ffaa2b91 | ||
|
|
a6555681a0 | ||
|
|
43ac0ef87c | ||
|
|
754842be7c | ||
|
|
5b3ee2dbe8 | ||
|
|
ca5a1ddc0b | ||
|
|
c9821132ad | ||
|
|
0641dca2a6 | ||
|
|
fd983b9f5d | ||
|
|
7e1e51c450 | ||
|
|
d912b990e4 | ||
|
|
8224aa87a5 | ||
|
|
4cb5abc7b6 | ||
|
|
743a800b0d | ||
|
|
a5c43612bf | ||
|
|
e2bd612b8e | ||
|
|
3ddb65e399 | ||
|
|
56775580fc | ||
|
|
8f7703c158 | ||
|
|
7aba9ff3ff | ||
|
|
aea1271a94 | ||
|
|
b575f195c9 | ||
|
|
1eedf7b332 | ||
|
|
d327a1041b | ||
|
|
10a3ba7dd4 | ||
|
|
deaa4ea910 | ||
|
|
fbfceb3137 | ||
|
|
e7b9d7cd54 | ||
|
|
34aba58351 | ||
|
|
e1639be6c3 | ||
|
|
80975c5715 | ||
|
|
c12a4f7353 | ||
|
|
defab688e5 | ||
|
|
2244386d33 | ||
|
|
39244fa27f | ||
|
|
20c19905ac | ||
|
|
8086d645f9 | ||
|
|
3a3289bf04 | ||
|
|
1711ff3bb5 | ||
|
|
b945913f88 | ||
|
|
d31533ed82 | ||
|
|
0fb2ec2c76 | ||
|
|
89847cbc83 | ||
|
|
9d12bb23fd | ||
|
|
79af4ce381 | ||
|
|
79f293e248 | ||
|
|
e75a0fec01 | ||
|
|
a935b085d4 | ||
|
|
4ef0a14420 | ||
|
|
8273154904 | ||
|
|
71d6ef3b52 | ||
|
|
119b3a090a | ||
|
|
496df3347b | ||
|
|
2b70eef35b | ||
|
|
c4071eedf8 | ||
|
|
b6cc866113 | ||
|
|
6aabcdeac7 | ||
|
|
72bccee9e2 | ||
|
|
b54f934fcd | ||
|
|
43dc0f96ff | ||
|
|
e02a82fa72 | ||
|
|
9f91b0c92b | ||
|
|
d14d6364a3 | ||
|
|
15c8f0b6f7 | ||
|
|
9bca158174 | ||
|
|
45bb30692d | ||
|
|
5bf73caba7 | ||
|
|
e5389f620a | ||
|
|
61fd52ff61 | ||
|
|
73c46bd812 | ||
|
|
d8173122e0 | ||
|
|
1af6e77dd1 | ||
|
|
ce476ca163 | ||
|
|
0a1df90a83 | ||
|
|
762f5ea30f | ||
|
|
06e7753797 | ||
|
|
c5e1f8d3e9 | ||
|
|
221433725b | ||
|
|
28864cd066 | ||
|
|
854f70dc8b | ||
|
|
435b988223 | ||
|
|
ecc119b296 | ||
|
|
209f3aa136 | ||
|
|
8935859934 | ||
|
|
6c77ec3534 | ||
|
|
291d3ebae8 | ||
|
|
5b97fd2e6f | ||
|
|
4de8c5ed7d | ||
|
|
09333d1604 | ||
|
|
60240ca9a1 | ||
|
|
3e45ec0a08 | ||
|
|
4aad04b31a | ||
|
|
99ff3f8d42 |
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
SoulterL@outlook.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
282
README.md
282
README.md
@@ -1,23 +1,60 @@
|
||||
## ⭐体验
|
||||
<div align="center">
|
||||
|
||||
使用手机QQ扫码加入QQ频道(频道名: GPT机器人 | 频道号: x42d56aki2)
|
||||
<img src="https://socialify.git.ci/Soulter/QQChannelChatGPT/image?description=1&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light" alt="QQChannelChatGPT" width="600" height="300" />
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/37870767/227197121-4f1e02a4-92fd-4497-8768-9d6977a291b7.jpg" width="200"></img>
|
||||
<!-- [](https://www.python.org/)
|
||||
[](https://github.com/Soulter/QQChannelChatGPT/blob/master/LICENSE)
|
||||
 -->
|
||||
|
||||
基于go-cqhttp和官方QQ频道SDK的QQ机器人项目。支持ChatGPT、Claude、HuggingChat、Bard大模型。一次部署,同时使用。
|
||||
|
||||
**Windows用户推荐Windows一键安装,请前往Release下载最新版本**
|
||||
部署文档:https://github.com/Soulter/QQChannelChatGPT/wiki
|
||||
|
||||
欢迎加群讨论 | **QQ群号:322154837** | **频道号: x42d56aki2** |
|
||||
|
||||
详细部署教程链接:https://soulter.top/posts/qpdg.html
|
||||
<!-- <img src="https://user-images.githubusercontent.com/37870767/230417115-9dd3c9d5-6b6b-4928-8fe3-82f559208aab.JPG" width="300"></img> -->
|
||||
|
||||
有网络问题报错的请先看issue,解决不了再加频道反馈
|
||||
</div>
|
||||
|
||||
## ⭐功能:
|
||||
## 🤔您可能想了解的
|
||||
- **如何部署?** [帮助文档](https://github.com/Soulter/QQChannelChatGPT/wiki)
|
||||
- **go-cqhttp启动不成功、报登录失败?** [在这里搜索解决方法](https://github.com/Mrs4s/go-cqhttp/issues)
|
||||
- **程序闪退/机器人启动不成功?** [提交issue或加群反馈](https://github.com/Soulter/QQChannelChatGPT/issues)
|
||||
- **如何开启ChatGPT、Bard、Claude等语言模型?** [查看帮助](https://github.com/Soulter/QQChannelChatGPT/wiki/%E8%A1%A5%E5%85%85%EF%BC%9A%E5%A6%82%E4%BD%95%E5%BC%80%E5%90%AFChatGPT%E3%80%81Bard%E3%80%81Claude%E7%AD%89%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%9F)
|
||||
|
||||
- 逆向ChatGPT库
|
||||
- 官方ChatGPT AI
|
||||
- 文心一言(即将支持,链接https://github.com/Soulter/ERNIEBot 欢迎Star)
|
||||
- NewBing
|
||||
- Bard (即将支持)
|
||||
## 🧩功能:
|
||||
|
||||
🌍支持的AI语言模型一览:
|
||||
|
||||
**文字模型**
|
||||
|
||||
- OpenAI GPT-3模型(原生支持)
|
||||
- OpenAI GPT-3.5模型(原生支持)
|
||||
- OpenAI GPT-4模型(原生支持)
|
||||
- ChatGPT网页版 GPT-3.5模型(免费,原生支持)
|
||||
- ChatGPT网页版 GPT-4模型(需订阅Plus账户,原生支持)
|
||||
- Bing(免费,原生支持)
|
||||
- Claude模型(免费,由[LLMs插件](https://github.com/Soulter/llms)支持)
|
||||
- HuggingChat模型(免费,由[LLMs插件](https://github.com/Soulter/llms)支持)
|
||||
- Google Bard(免费,由[LLMs插件](https://github.com/Soulter/llms)支持)
|
||||
|
||||
**图片生成**
|
||||
|
||||
- NovelAI/Naifu (免费,由[AIDraw插件](https://github.com/Soulter/aidraw)支持)
|
||||
|
||||
|
||||
🌍机器人支持的能力一览:
|
||||
- 同时部署机器人到QQ和QQ频道
|
||||
- 大模型对话
|
||||
- 大模型网页搜索能力 **(目前仅支持OpenAI系的模型,最新版本下使用web on指令打开)**
|
||||
- 插件安装(在QQ或QQ频道聊天框内输入`plugin`了解详情)
|
||||
- 回复文字图片渲染(以图片markdown格式回复,降低被风控概率,需手动在`cmd_config.json`内开启)
|
||||
- 人格设置
|
||||
- 关键词回复
|
||||
- 热更新(更新本项目时**仅需**在QQ或QQ频道聊天框内输入`update latest r`)
|
||||
- Windows一键部署(https://github.com/Soulter/QQChatGPTLauncher/releases/latest)
|
||||
|
||||
<!--
|
||||
### 基本功能
|
||||
<details>
|
||||
<summary>✅ 回复符合上下文</summary>
|
||||
@@ -72,11 +109,33 @@
|
||||
|
||||
- QQ频道机器人框架为QQ官方开源的框架,稳定。
|
||||
|
||||
</details>
|
||||
</details> -->
|
||||
|
||||
> 关于token:token就相当于是AI中的单词数(但是不等于单词数),`text-davinci-003`模型中最大可以支持`4097`个token。在发送信息时,这个机器人会将用户的历史聊天记录打包发送给ChatGPT,因此,`token`也会相应的累加,为了保证聊天的上下文的逻辑性,就有了缓存token。
|
||||
### 指令功能
|
||||
需要先`@`机器人之后再输入指令
|
||||
<!-- > 关于token:token就相当于是AI中的单词数(但是不等于单词数),`text-davinci-003`模型中最大可以支持`4097`个token。在发送信息时,这个机器人会将用户的历史聊天记录打包发送给ChatGPT,因此,`token`也会相应的累加,为了保证聊天的上下文的逻辑性,就有了缓存token。 -->
|
||||
|
||||
### 🛠️ 插件支持
|
||||
|
||||
本项目支持接入插件。
|
||||
|
||||
> 使用`plugin i 插件GitHub链接`即可安装。
|
||||
|
||||
插件开发教程:https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
|
||||
|
||||
部分公开的插件:
|
||||
|
||||
- `LLMS`: https://github.com/Soulter/llms | Claude, HuggingChat 大语言模型接入。
|
||||
|
||||
- `GoodPlugins`: https://github.com/Soulter/goodplugins | 随机动漫图片、搜番、喜报生成器等等
|
||||
|
||||
- `sysstat`: https://github.com/Soulter/sysstatqcbot | 查看系统状态
|
||||
|
||||
- `BiliMonitor`: https://github.com/Soulter/BiliMonitor | 订阅B站动态!
|
||||
|
||||
<!--
|
||||
### 指令
|
||||
|
||||
#### OpenAI官方API
|
||||
在频道内需要先`@`机器人之后再输入指令;在QQ中暂时需要在消息前加上`ai `,不需要@
|
||||
- `/reset`重置prompt
|
||||
- `/his`查看历史记录(每个用户都有独立的会话)
|
||||
- `/his [页码数]`查看不同页码的历史记录。例如`/his 2`查看第2页
|
||||
@@ -86,33 +145,196 @@
|
||||
- `/help` 查看帮助
|
||||
- `/key` 动态添加key
|
||||
- `/set` 人格设置面板
|
||||
- `/keyword nihao 你好` 设置关键词回复。nihao->你好
|
||||
- `/bing` 切换为bing
|
||||
- `/revgpt` 切换为ChatGPT逆向库
|
||||
- `/画` 画画
|
||||
|
||||
#### Bing语言模型
|
||||
- `/reset`重置prompt
|
||||
- `/gpt` 切换为OpenAI官方API
|
||||
- `/revgpt` 切换为ChatGPT逆向库
|
||||
|
||||
#### 逆向ChatGPT库语言模型
|
||||
- `/gpt` 切换为OpenAI官方API
|
||||
- `/bing` 切换为bing
|
||||
|
||||
* 切换模型指令支持临时回复。如`/bing 你好`将会临时使用一次bing模型 -->
|
||||
|
||||
## 📰使用方法:
|
||||
|
||||
**详细部署教程链接**https://soulter.top/posts/qpdg.html
|
||||
使用文档:https://github.com/Soulter/QQChannelChatGPT/wiki
|
||||
|
||||
**Windows用户可以使用启动器一键安装,请前往Release下载最新版本(Beta)**
|
||||
<!--
|
||||
### 安装第三方库
|
||||
|
||||
使用Python的pip工具安装
|
||||
- `qq-botpy` (QQ频道官方Python SDK)
|
||||
- `openai` (OpenAI Python SDK)
|
||||
|
||||
```shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
> ⚠注意,由于qq-botpy库需要运行在`Python 3.8+`的版本上,因此本项目也需要在此之上运行
|
||||
> ⚠Python版本应>=3.9
|
||||
|
||||
### 配置
|
||||
|
||||
- 获得 OpenAI的key [OpenAI](https://beta.openai.com/)
|
||||
- 获得 QQ开放平台下QQ频道机器人的token和appid [QQ开放平台](https://q.qq.com/),一个QQ频道机器人(很容易创建~)
|
||||
- 在configs/config.yaml下进行配置
|
||||
**详细部署教程链接:**https://github.com/Soulter/QQChannelChatGPT/wiki
|
||||
|
||||
### 启动
|
||||
- 启动main.py
|
||||
- 启动main.py -->
|
||||
|
||||
## 🙇感谢
|
||||
本项目使用了一下项目:
|
||||
|
||||
[ChatGPT by acheong08](https://github.com/acheong08/ChatGPT)
|
||||
|
||||
[EdgeGPT by acheong08](https://github.com/acheong08/EdgeGPT)
|
||||
|
||||
[go-cqhttp by Mrs4s](https://github.com/Mrs4s/go-cqhttp)
|
||||
|
||||
[nakuru-project by Lxns-Network](https://github.com/Lxns-Network/nakuru-project)
|
||||
|
||||
<!-- ## 👀部分演示截图
|
||||
|
||||
帮助中心(`help`指令)
|
||||

|
||||
|
||||
-->
|
||||
## ⚙配置文件说明:
|
||||
```yaml
|
||||
# 如果你不知道怎么部署,请查看https://github.com/Soulter/QQChannelChatGPT/wiki
|
||||
# 不一定需要key了,如果你没有key但有openAI账号或者必应账号,可以考虑使用下面的逆向库
|
||||
|
||||
|
||||
## DEMO
|
||||

|
||||

|
||||

|
||||
###############平台设置#################
|
||||
|
||||
# QQ频道机器人
|
||||
# QQ开放平台的appid和令牌
|
||||
# q.qq.com
|
||||
# enable为true则启用,false则不启用
|
||||
qqbot:
|
||||
enable: true
|
||||
appid:
|
||||
token:
|
||||
|
||||
# QQ机器人
|
||||
# enable为true则启用,false则不启用
|
||||
# 需要安装GO-CQHTTP配合使用。
|
||||
# 文档:https://docs.go-cqhttp.org/
|
||||
# 请将go-cqhttp的配置文件的sever部分粘贴为以下内容,否则无法使用
|
||||
# 请先启动go-cqhttp再启动本程序
|
||||
#
|
||||
# servers:
|
||||
# - http:
|
||||
# host: 127.0.0.1
|
||||
# version: 0
|
||||
# port: 5700
|
||||
# timeout: 5
|
||||
# - ws:
|
||||
# address: 127.0.0.1:6700
|
||||
# middlewares:
|
||||
# <<: *default
|
||||
gocqbot:
|
||||
enable: false
|
||||
|
||||
# 设置是否一个人一个会话
|
||||
uniqueSessionMode: false
|
||||
# QChannelBot 的版本,请勿修改此字段,否则可能产生一些bug
|
||||
version: 3.0
|
||||
# [Beta] 转储历史记录时间间隔(分钟)
|
||||
dump_history_interval: 10
|
||||
# 一个用户只能在time秒内发送count条消息
|
||||
limit:
|
||||
time: 60
|
||||
count: 5
|
||||
# 公告
|
||||
notice: "此机器人由Github项目QQChannelChatGPT驱动。"
|
||||
# 是否打开私信功能
|
||||
# 设置为true则频道成员可以私聊机器人。
|
||||
# 设置为false则频道成员不能私聊机器人。
|
||||
direct_message_mode: true
|
||||
|
||||
# 系统代理
|
||||
# http_proxy: http://localhost:7890
|
||||
# https_proxy: http://localhost:7890
|
||||
|
||||
# 自定义回复前缀,如[Rev]或其他,务必加引号以防止不必要的bug。
|
||||
reply_prefix:
|
||||
openai_official: "[GPT]"
|
||||
rev_chatgpt: "[Rev]"
|
||||
rev_edgegpt: "[RevBing]"
|
||||
|
||||
# 百度内容审核服务
|
||||
# 新用户免费5万次调用。https://cloud.baidu.com/doc/ANTIPORN/index.html
|
||||
baidu_aip:
|
||||
enable: false
|
||||
app_id:
|
||||
api_key:
|
||||
secret_key:
|
||||
|
||||
|
||||
|
||||
|
||||
###############语言模型设置#################
|
||||
|
||||
|
||||
# OpenAI官方API
|
||||
# 注意:已支持多key自动切换,方法:
|
||||
# key:
|
||||
# - sk-xxxxxx
|
||||
# - sk-xxxxxx
|
||||
# 在下方非注释的地方使用以上格式
|
||||
# 关于api_base:可以使用一些云函数(如腾讯、阿里)来避免国内被墙的问题。
|
||||
# 详见:
|
||||
# https://github.com/Ice-Hazymoon/openai-scf-proxy
|
||||
# https://github.com/Soulter/QQChannelChatGPT/issues/42
|
||||
# 设置为none则表示使用官方默认api地址
|
||||
openai:
|
||||
key:
|
||||
-
|
||||
api_base: none
|
||||
# 这里是GPT配置,语言模型默认使用gpt-3.5-turbo
|
||||
chatGPTConfigs:
|
||||
model: gpt-3.5-turbo
|
||||
max_tokens: 3000
|
||||
temperature: 0.9
|
||||
top_p: 1
|
||||
frequency_penalty: 0
|
||||
presence_penalty: 0
|
||||
|
||||
total_tokens_limit: 5000
|
||||
|
||||
# 逆向文心一言【暂时不可用,请勿使用】
|
||||
rev_ernie:
|
||||
enable: false
|
||||
|
||||
# 逆向New Bing
|
||||
# 需要在项目根目录下创建cookies.json并粘贴cookies进去。
|
||||
# 详见:https://soulter.top/posts/qpdg.html
|
||||
rev_edgegpt:
|
||||
enable: false
|
||||
|
||||
# 逆向ChatGPT库
|
||||
# https://github.com/acheong08/ChatGPT
|
||||
# 优点:免费(无免费额度限制);
|
||||
# 缺点:速度相对慢。OpenAI 速率限制:免费帐户每小时 50 个请求。您可以通过多帐户循环来绕过它
|
||||
# enable设置为true后,将会停止使用上面正常的官方API调用而使用本逆向项目
|
||||
#
|
||||
# 多账户可以保证每个请求都能得到及时的回复。
|
||||
# 关于account的格式
|
||||
# account:
|
||||
# - email: 第1个账户
|
||||
# password: 第1个账户密码
|
||||
# - email: 第2个账户
|
||||
# password: 第2个账户密码
|
||||
# - ....
|
||||
# 支持使用access_token登录
|
||||
# 例:
|
||||
# - session_token: xxxxx
|
||||
# - access_token: xxxx
|
||||
# 请严格按照上面这个格式填写。
|
||||
# 逆向ChatGPT库的email-password登录方式不工作,建议使用access_token登录
|
||||
# 获取access_token的方法,详见:https://soulter.top/posts/qpdg.html
|
||||
rev_ChatGPT:
|
||||
enable: false
|
||||
account:
|
||||
- access_token:
|
||||
```
|
||||
|
||||
5
addons/plugins/helloworld/README.md
Normal file
5
addons/plugins/helloworld/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# helloworld
|
||||
|
||||
QQChannelChatGPT项目的测试插件
|
||||
|
||||
A test plugin for QQChannelChatGPT plugin feature
|
||||
79
addons/plugins/helloworld/helloworld.py
Normal file
79
addons/plugins/helloworld/helloworld.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from nakuru.entities.components import *
|
||||
from nakuru import (
|
||||
GroupMessage,
|
||||
FriendMessage
|
||||
)
|
||||
from botpy.message import Message, DirectMessage
|
||||
from model.platform.qq import QQ
|
||||
import time
|
||||
import threading
|
||||
|
||||
class HelloWorldPlugin:
|
||||
"""
|
||||
初始化函数, 可以选择直接pass
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
self.myThread = None # 线程对象,如果要使用线程,需要在此处定义。在run处定义会被释放掉
|
||||
print("这是HelloWorld测试插件, 发送 helloworld 即可触发此插件。")
|
||||
|
||||
"""
|
||||
入口函数,机器人会调用此函数。
|
||||
参数规范: message: 消息文本; role: 身份; platform: 消息平台; message_obj: 消息对象; qq_platform: QQ平台对象,可以通过调用qq_platform.send()直接发送消息。详见Helloworld插件示例
|
||||
参数详情: role为admin或者member; platform为qqchan或者gocq; message_obj为nakuru的GroupMessage对象或者FriendMessage对象或者频道的Message, DirectMessage对象。
|
||||
返回规范: bool: 是否hit到此插件(所有的消息均会调用每一个载入的插件, 如果没有hit到, 则应返回False)
|
||||
Tuple: None或者长度为3的元组。当没有hit到时, 返回None. hit到时, 第1个参数为指令是否调用成功, 第2个参数为返回的消息文本或者gocq的消息链列表, 第3个参数为指令名称
|
||||
例子:做一个名为"yuanshen"的插件;当接收到消息为“原神 可莉”, 如果不想要处理此消息,则返回False, None;如果想要处理,但是执行失败了,返回True, tuple([False, "请求失败啦~", "yuanshen"])
|
||||
;执行成功了,返回True, tuple([True, "结果文本", "yuanshen"])
|
||||
"""
|
||||
def run(self, message: str, role: str, platform: str, message_obj, qq_platform: QQ):
|
||||
|
||||
if platform == "gocq":
|
||||
"""
|
||||
QQ平台指令处理逻辑
|
||||
"""
|
||||
img_url = "https://gchat.qpic.cn/gchatpic_new/905617992/720871955-2246763964-C6EE1A52CC668EC982453065C4FA8747/0?term=2&is_origin=0"
|
||||
if message == "helloworld":
|
||||
return True, tuple([True, [Plain("Hello World!!"), Image.fromURL(url=img_url)], "helloworld"])
|
||||
elif message == "hiloop":
|
||||
if self.myThread is None:
|
||||
self.myThread = threading.Thread(target=self.helloworldThread, args=(message_obj, qq_platform))
|
||||
self.myThread.start()
|
||||
return True, tuple([True, [Plain("A lot of Helloworlds!!"), Image.fromURL(url=img_url)], "helloworld"])
|
||||
else:
|
||||
return False, None
|
||||
elif platform == "qqchan":
|
||||
"""
|
||||
频道处理逻辑(频道暂时只支持回复字符串类型的信息,返回的信息都会被转成字符串,如果不想处理某一个平台的信息,直接返回False, None就行)
|
||||
"""
|
||||
if message == "helloworld":
|
||||
return True, tuple([True, "Hello World!!", "helloworld"])
|
||||
else:
|
||||
return False, None
|
||||
"""
|
||||
帮助函数,当用户输入 plugin v 插件名称 时,会调用此函数,返回帮助信息
|
||||
返回参数要求(必填):dict{
|
||||
"name": str, # 插件名称
|
||||
"desc": str, # 插件简短描述
|
||||
"help": str, # 插件帮助信息
|
||||
"version": str, # 插件版本
|
||||
"author": str, # 插件作者
|
||||
}
|
||||
"""
|
||||
def info(self):
|
||||
return {
|
||||
"name": "helloworld",
|
||||
"desc": "测试插件",
|
||||
"help": "测试插件, 回复helloworld即可触发",
|
||||
"version": "v1.0.1 beta",
|
||||
"author": "Soulter"
|
||||
}
|
||||
|
||||
def helloworldThread(self, meseage_obj, qq_platform: QQ):
|
||||
while True:
|
||||
qq_platform.send(meseage_obj, [Plain("Hello World!!")]) # 第一个参数可以是message_obj, 也可以是qq群号
|
||||
time.sleep(3) # 睡眠3秒。 用while True一定要记得sleep,不然会卡死
|
||||
|
||||
|
||||
# 热知识:检测消息开头指令,使用以下方法
|
||||
# if message.startswith("原神"):
|
||||
# pass
|
||||
@@ -1,45 +0,0 @@
|
||||
from revChatGPT.V1 import Chatbot
|
||||
|
||||
class revChatGPT:
|
||||
def __init__(self, config):
|
||||
|
||||
if 'password' in config:
|
||||
config['password'] = str(config['password'])
|
||||
self.chatbot = Chatbot(config=config)
|
||||
|
||||
def chat(self, prompt):
|
||||
resp = ''
|
||||
|
||||
"""
|
||||
Base class for exceptions in this module.
|
||||
Error codes:
|
||||
-1: User error
|
||||
0: Unknown error
|
||||
1: Server error
|
||||
2: Rate limit error
|
||||
3: Invalid request error
|
||||
4: Expired access token error
|
||||
5: Invalid access token error
|
||||
6: Prohibited concurrent query error
|
||||
"""
|
||||
|
||||
|
||||
err_count = 0
|
||||
retry_count = 5
|
||||
|
||||
while err_count < retry_count:
|
||||
try:
|
||||
for data in self.chatbot.ask(prompt):
|
||||
resp = data["message"]
|
||||
break
|
||||
except BaseException as e:
|
||||
try:
|
||||
print("[RevChatGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count))
|
||||
err_count += 1
|
||||
if err_count >= retry_count:
|
||||
raise e
|
||||
except BaseException:
|
||||
err_count += 1
|
||||
|
||||
print("[RevChatGPT] "+str(resp))
|
||||
return resp
|
||||
@@ -1,47 +0,0 @@
|
||||
import asyncio
|
||||
from EdgeGPT import Chatbot, ConversationStyle
|
||||
import json
|
||||
|
||||
class revEdgeGPT:
|
||||
def __init__(self):
|
||||
self.busy = False
|
||||
self.wait_stack = []
|
||||
with open('./cookies.json', 'r') as f:
|
||||
cookies = json.load(f)
|
||||
self.bot = Chatbot(cookies=cookies)
|
||||
|
||||
def is_busy(self):
|
||||
return self.busy
|
||||
|
||||
async def reset(self):
|
||||
try:
|
||||
await self.bot.reset()
|
||||
return False
|
||||
except BaseException:
|
||||
return True
|
||||
|
||||
async def chat(self, prompt):
|
||||
if self.busy:
|
||||
return
|
||||
self.busy = True
|
||||
resp = 'err'
|
||||
err_count = 0
|
||||
retry_count = 5
|
||||
|
||||
while err_count < retry_count:
|
||||
try:
|
||||
resp = await self.bot.ask(prompt=prompt, conversation_style=ConversationStyle.creative)
|
||||
resp = resp['item']['messages'][len(resp['item']['messages'])-1]['text']
|
||||
if resp == prompt:
|
||||
resp += '\n\n如果你没有让我复述你的话,那代表我可能不想和你继续这个话题了,请输入/reset重置会话😶'
|
||||
break
|
||||
except BaseException as e:
|
||||
print(e.with_traceback)
|
||||
err_count += 1
|
||||
if err_count >= retry_count:
|
||||
raise e
|
||||
print("[RevEdgeGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count))
|
||||
self.busy = False
|
||||
|
||||
print("[RevEdgeGPT] "+str(resp))
|
||||
return resp
|
||||
@@ -1,43 +1,42 @@
|
||||
# 如果你不知道怎么部署,请务必查看https://soulter.top/posts/qpdg.html
|
||||
# 如果你不知道怎么部署,请查看https://soulter.top/posts/qpdg.html
|
||||
# 不一定需要key了,如果你没有key但有openAI账号或者必应账号,可以考虑使用下面的逆向库
|
||||
|
||||
# 不一定需要key了,如果你没有key但有openAI账号或者必应账号,也可以使用下面的逆向库
|
||||
|
||||
# 注意:已支持多key自动切换,方法:
|
||||
# key:
|
||||
# - sk-xxxxxx
|
||||
# - sk-xxxxxx
|
||||
# 在下方非注释的地方使用以上格式
|
||||
|
||||
# 关于api_base:可以使用一些云函数(如腾讯、阿里)来避免国内被墙的问题。
|
||||
# 详见:
|
||||
# https://github.com/Ice-Hazymoon/openai-scf-proxy
|
||||
# https://github.com/Soulter/QQChannelChatGPT/issues/42
|
||||
# 设置为none则表示使用官方默认api地址
|
||||
openai:
|
||||
key:
|
||||
-
|
||||
api_base: none
|
||||
# 这里是GPT配置,语言模型默认使用gpt-3.5-turbo
|
||||
chatGPTConfigs:
|
||||
model: gpt-3.5-turbo
|
||||
max_tokens: 3000
|
||||
temperature: 0.9
|
||||
top_p: 1
|
||||
frequency_penalty: 0
|
||||
presence_penalty: 0
|
||||
|
||||
total_tokens_limit: 5000
|
||||
###############平台设置#################
|
||||
|
||||
# QQ频道机器人
|
||||
# QQ开放平台的appid和令牌
|
||||
# q.qq.com
|
||||
# enable为true则启用,false则不启用
|
||||
qqbot:
|
||||
enable: true
|
||||
appid:
|
||||
token:
|
||||
|
||||
# QQ机器人
|
||||
# enable为true则启用,false则不启用
|
||||
# 需要安装GO-CQHTTP配合使用。
|
||||
# 文档:https://docs.go-cqhttp.org/
|
||||
# 请将go-cqhttp的配置文件的sever部分粘贴为以下内容,否则无法使用
|
||||
# 请先启动go-cqhttp再启动本程序
|
||||
#
|
||||
# servers:
|
||||
# - http:
|
||||
# host: 127.0.0.1
|
||||
# version: 0
|
||||
# port: 5700
|
||||
# timeout: 5
|
||||
# - ws:
|
||||
# address: 127.0.0.1:6700
|
||||
# middlewares:
|
||||
# <<: *default
|
||||
gocqbot:
|
||||
enable: false
|
||||
|
||||
# 设置是否一个人一个会话
|
||||
uniqueSessionMode: false
|
||||
# QChannelBot 的版本,请勿修改此字段,否则可能产生一些bug
|
||||
version: 2.8
|
||||
version: 3.0
|
||||
# [Beta] 转储历史记录时间间隔(分钟)
|
||||
dump_history_interval: 10
|
||||
# 一个用户只能在time秒内发送count条消息
|
||||
@@ -61,9 +60,6 @@ reply_prefix:
|
||||
rev_chatgpt: "[Rev]"
|
||||
rev_edgegpt: "[RevBing]"
|
||||
|
||||
|
||||
################外带程序(插件)################
|
||||
|
||||
# 百度内容审核服务
|
||||
# 新用户免费5万次调用。https://cloud.baidu.com/doc/ANTIPORN/index.html
|
||||
baidu_aip:
|
||||
@@ -72,10 +68,45 @@ baidu_aip:
|
||||
api_key:
|
||||
secret_key:
|
||||
|
||||
|
||||
|
||||
|
||||
###############语言模型设置#################
|
||||
|
||||
|
||||
# OpenAI官方API
|
||||
# 注意:已支持多key自动切换,方法:
|
||||
# key:
|
||||
# - sk-xxxxxx
|
||||
# - sk-xxxxxx
|
||||
# 在下方非注释的地方使用以上格式
|
||||
# 关于api_base:可以使用一些云函数(如腾讯、阿里)来避免国内被墙的问题。
|
||||
# 详见:
|
||||
# https://github.com/Ice-Hazymoon/openai-scf-proxy
|
||||
# https://github.com/Soulter/QQChannelChatGPT/issues/42
|
||||
# 设置为none则表示使用官方默认api地址
|
||||
openai:
|
||||
key:
|
||||
-
|
||||
api_base: none
|
||||
# 这里是GPT配置,语言模型默认使用gpt-3.5-turbo
|
||||
chatGPTConfigs:
|
||||
model: gpt-3.5-turbo
|
||||
max_tokens: 3000
|
||||
temperature: 0.9
|
||||
top_p: 1
|
||||
frequency_penalty: 0
|
||||
presence_penalty: 0
|
||||
|
||||
total_tokens_limit: 5000
|
||||
|
||||
# 逆向文心一言【暂时不可用,请勿使用】
|
||||
rev_ernie:
|
||||
enable: false
|
||||
|
||||
# 逆向New Bing
|
||||
# 需要在项目根目录下创建cookies.json并粘贴cookies进去。
|
||||
# 详见:https://soulter.top/posts/qpdg.html
|
||||
rev_edgegpt:
|
||||
enable: false
|
||||
|
||||
@@ -93,14 +124,14 @@ rev_edgegpt:
|
||||
# - email: 第2个账户
|
||||
# password: 第2个账户密码
|
||||
# - ....
|
||||
# 支持使用session_token\access_token登录
|
||||
# 支持使用access_token登录
|
||||
# 例:
|
||||
# - session_token: xxxxx
|
||||
# - access_token: xxxx
|
||||
# 请严格按照上面这个格式填写。
|
||||
|
||||
# 逆向ChatGPT库的email-password登录方式不工作,建议使用access_token登录
|
||||
# 获取access_token的方法,详见:https://soulter.top/posts/qpdg.html
|
||||
rev_ChatGPT:
|
||||
enable: false
|
||||
account:
|
||||
- email:
|
||||
password:
|
||||
- access_token:
|
||||
@@ -1,191 +0,0 @@
|
||||
import openai
|
||||
import yaml
|
||||
from util.errors.errors import PromptExceededError
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
inst = None
|
||||
# 适配pyinstaller
|
||||
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
|
||||
key_record_path = abs_path+'chatgpt_key_record'
|
||||
|
||||
class ChatGPT:
|
||||
def __init__(self, cfg):
|
||||
self.key_list = []
|
||||
if 'api_base' in cfg and cfg['api_base'] != 'none' and cfg['api_base'] != '':
|
||||
openai.api_base = cfg['api_base']
|
||||
if cfg['key'] != '' and cfg['key'] != None:
|
||||
print("[System] 读取ChatGPT Key成功")
|
||||
self.key_list = cfg['key']
|
||||
# openai.api_key = cfg['key']
|
||||
else:
|
||||
input("[System] 请先去完善ChatGPT的Key。详情请前往https://beta.openai.com/account/api-keys")
|
||||
|
||||
# init key record
|
||||
self.init_key_record()
|
||||
|
||||
chatGPT_configs = cfg['chatGPTConfigs']
|
||||
print(f'[System] 加载ChatGPTConfigs: {chatGPT_configs}')
|
||||
self.chatGPT_configs = chatGPT_configs
|
||||
self.openai_configs = cfg
|
||||
|
||||
def chat(self, req, image_mode = False, img_num = 1, img_size = "1024x1024"):
|
||||
# ChatGPT API 2023/3/2
|
||||
# messages = [{"role": "user", "content": prompt}]
|
||||
if not image_mode:
|
||||
try:
|
||||
response = openai.ChatCompletion.create(
|
||||
messages=req,
|
||||
**self.chatGPT_configs
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str(e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e):
|
||||
print("[System] 当前Key已超额或者不正常,正在切换")
|
||||
self.key_stat[openai.api_key]['exceed'] = True
|
||||
self.save_key_record()
|
||||
|
||||
response, is_switched = self.handle_switch_key(req)
|
||||
if not is_switched:
|
||||
# 所有Key都超额或不正常
|
||||
raise e
|
||||
else:
|
||||
response = openai.ChatCompletion.create(
|
||||
messages=req,
|
||||
**self.chatGPT_configs
|
||||
)
|
||||
self.key_stat[openai.api_key]['used'] += response['usage']['total_tokens']
|
||||
self.save_key_record()
|
||||
print("[ChatGPT] "+str(response["choices"][0]["message"]["content"]))
|
||||
return str(response["choices"][0]["message"]["content"]).strip(), response['usage']['total_tokens']
|
||||
else:
|
||||
try:
|
||||
# print("test1")
|
||||
response = openai.Image.create(
|
||||
prompt=req[0]['content'],
|
||||
n=img_num,
|
||||
size=img_size
|
||||
)
|
||||
# print("test2")
|
||||
image_url = []
|
||||
for i in range(img_num):
|
||||
image_url.append(response['data'][i]['url'])
|
||||
print(image_url)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str(
|
||||
e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e):
|
||||
print("[System] 当前Key已超额或者不正常,正在切换")
|
||||
self.key_stat[openai.api_key]['exceed'] = True
|
||||
self.save_key_record()
|
||||
|
||||
response, is_switched = self.handle_switch_key(req)
|
||||
if not is_switched:
|
||||
# 所有Key都超额或不正常
|
||||
raise e
|
||||
else:
|
||||
response = openai.Image.create(
|
||||
prompt=req[0]['content'],
|
||||
n=img_num,
|
||||
size=img_size
|
||||
)
|
||||
image_url = []
|
||||
for i in range(img_num):
|
||||
image_url.append(response['data'][i]['url'])
|
||||
return image_url
|
||||
def handle_switch_key(self, req):
|
||||
# messages = [{"role": "user", "content": prompt}]
|
||||
while True:
|
||||
is_all_exceed = True
|
||||
for key in self.key_stat:
|
||||
if not self.key_stat[key]['exceed']:
|
||||
is_all_exceed = False
|
||||
openai.api_key = key
|
||||
print(f"[System] 切换到Key: {key}, 已使用token: {self.key_stat[key]['used']}")
|
||||
if len(req) > 0:
|
||||
try:
|
||||
response = openai.ChatCompletion.create(
|
||||
messages=req,
|
||||
**self.chatGPT_configs
|
||||
)
|
||||
return response, True
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if 'You exceeded' in str(e):
|
||||
print("[System] 当前Key已超额,正在切换")
|
||||
self.key_stat[openai.api_key]['exceed'] = True
|
||||
self.save_key_record()
|
||||
time.sleep(1)
|
||||
continue
|
||||
else:
|
||||
return True
|
||||
if is_all_exceed:
|
||||
print("[System] 所有Key已超额")
|
||||
return None, False
|
||||
|
||||
def getConfigs(self):
|
||||
return self.openai_configs
|
||||
|
||||
def save_key_record(self):
|
||||
with open(key_record_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.key_stat, f)
|
||||
|
||||
def get_key_stat(self):
|
||||
return self.key_stat
|
||||
def get_key_list(self):
|
||||
return self.key_list
|
||||
|
||||
# 添加key
|
||||
def append_key(self, key, sponsor):
|
||||
self.key_list.append(key)
|
||||
self.key_stat[key] = {'exceed': False, 'used': 0, 'sponsor': sponsor}
|
||||
self.save_key_record()
|
||||
self.init_key_record()
|
||||
# 检查key是否可用
|
||||
def check_key(self, key):
|
||||
pre_key = openai.api_key
|
||||
openai.api_key = key
|
||||
messages = [{"role": "user", "content": "1"}]
|
||||
try:
|
||||
response = openai.ChatCompletion.create(
|
||||
messages=messages,
|
||||
**self.chatGPT_configs
|
||||
)
|
||||
openai.api_key = pre_key
|
||||
return True
|
||||
except Exception as e:
|
||||
pass
|
||||
openai.api_key = pre_key
|
||||
return False
|
||||
|
||||
#将key_list的key转储到key_record中,并记录相关数据
|
||||
def init_key_record(self):
|
||||
if not os.path.exists(key_record_path):
|
||||
with open(key_record_path, 'w', encoding='utf-8') as f:
|
||||
json.dump({}, f)
|
||||
with open(key_record_path, 'r', encoding='utf-8') as keyfile:
|
||||
try:
|
||||
self.key_stat = json.load(keyfile)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.key_stat = {}
|
||||
finally:
|
||||
for key in self.key_list:
|
||||
if key not in self.key_stat:
|
||||
self.key_stat[key] = {'exceed': False, 'used': 0}
|
||||
# if openai.api_key is None:
|
||||
# openai.api_key = key
|
||||
else:
|
||||
# if self.key_stat[key]['exceed']:
|
||||
# print(f"Key: {key} 已超额")
|
||||
# continue
|
||||
# else:
|
||||
# if openai.api_key is None:
|
||||
# openai.api_key = key
|
||||
# print(f"使用Key: {key}, 已使用token: {self.key_stat[key]['used']}")
|
||||
pass
|
||||
if openai.api_key == None:
|
||||
self.handle_switch_key("")
|
||||
self.save_key_record()
|
||||
File diff suppressed because it is too large
Load Diff
69
launcher.py
69
launcher.py
@@ -1,69 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from git.repo import Repo
|
||||
import os
|
||||
# import zipfile
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 检测文件夹
|
||||
if not os.path.exists('QQChannelChatGPT'):
|
||||
os.mkdir('QQChannelChatGPT')
|
||||
# if not os.path.exists('pythonemb'):
|
||||
# os.mkdir('pythonemb')
|
||||
|
||||
# python_path = os.path.join('pythonemb', 'python.zip')
|
||||
# 检测Python环境
|
||||
# if not os.path.exists('pythonemb/python.exe'):
|
||||
# print("正在从https://www.python.org/ftp/python/3.10.10/python-3.10.10-embed-amd64.zip安装Python环境...")
|
||||
# os.system('curl -o pythonemb/python.zip https://www.python.org/ftp/python/3.10.10/python-3.10.10-embed-amd64.zip')
|
||||
# print("解压中...")
|
||||
# file=zipfile.ZipFile(python_path)
|
||||
# for name in file.namelist():
|
||||
# file.extract(name, path='pythonemb')
|
||||
# print("解压完毕, 创建pip...")
|
||||
# os.system('curl https://bootstrap.pypa.io/get-pip.py -o pythonemb\get-pip.py')
|
||||
# os.system('pythonemb\python.exe pythonemb\get-pip.py')
|
||||
# print("pip创建完毕")
|
||||
# print("Python环境安装完成")
|
||||
|
||||
|
||||
project_path = os.path.join('QQChannelChatGPT')
|
||||
try:
|
||||
repo = Repo(project_path)
|
||||
# 检查当前commit的hash值
|
||||
commit_hash = repo.head.object.hexsha
|
||||
print("当前commit的hash值为: " + commit_hash)
|
||||
|
||||
# 得到远程仓库的origin的commit的列表
|
||||
origin = repo.remotes.origin
|
||||
try:
|
||||
origin.fetch()
|
||||
except:
|
||||
pass
|
||||
# 得到远程仓库的commit的hash值
|
||||
remote_commit_hash = origin.refs.master.commit.hexsha
|
||||
print("https://github.com/Soulter/QQChannelChatGPT的commit的hash值为: " + remote_commit_hash)
|
||||
# 比较两个commit的hash值
|
||||
if commit_hash != remote_commit_hash:
|
||||
res = input("检测到项目有更新, 是否更新? (y/n): ")
|
||||
if res == 'y':
|
||||
repo.remotes.origin.pull()
|
||||
print("项目更新完毕")
|
||||
if res == 'n':
|
||||
print("已取消更新")
|
||||
except:
|
||||
print("正在从https://github.com/Soulter/QQChannelChatGPT.git拉取项目...")
|
||||
Repo.clone_from('https://github.com/Soulter/QQChannelChatGPT.git',to_path=project_path,branch='61-enhancement-重构代码增强稳定性')
|
||||
print("项目拉取完毕")
|
||||
print("【重要提醒】如果你没有Python环境, 请先安装Python环境! 否则接下来的操作会造成闪退。")
|
||||
print("【重要提醒】Python3.9淘宝下载地址: https://npm.taobao.org/mirrors/python/3.9.7/python-3.9.7-amd64.exe ")
|
||||
print("【重要提醒】安装时, 请务必勾选“Add Python 3.9 to PATH”选项。")
|
||||
print("【重要提醒】QQ: 905617992")
|
||||
input("已确保安装了Python3.9+的版本,按下回车继续...")
|
||||
print("正在安装依赖库...")
|
||||
os.system('python -m pip install -r QQChannelChatGPT\\requirements.txt')
|
||||
print("依赖库安装完毕")
|
||||
input("初次启动, 请先在QQChannelChatGPT/configs/config.yaml填写相关配置! 按任意键继续...")
|
||||
finally:
|
||||
print("正在启动项目...")
|
||||
os.system('python QQChannelChatGPT\main.py')
|
||||
235
main.py
235
main.py
@@ -1,163 +1,102 @@
|
||||
import threading
|
||||
import time
|
||||
import asyncio
|
||||
import os, sys
|
||||
import signal
|
||||
import requests,json
|
||||
|
||||
from pip._internal import main as pipmain
|
||||
|
||||
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
|
||||
|
||||
|
||||
def main(loop, event):
|
||||
import cores.qqbot.core as qqBot
|
||||
import yaml
|
||||
|
||||
ymlfile = open(abs_path+"configs/config.yaml", 'r', encoding='utf-8')
|
||||
cfg = yaml.safe_load(ymlfile)
|
||||
|
||||
def main():
|
||||
try:
|
||||
import cores.qqbot.core as qqBot
|
||||
import yaml
|
||||
ymlfile = open(abs_path+"configs/config.yaml", 'r', encoding='utf-8')
|
||||
cfg = yaml.safe_load(ymlfile)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
input("第三方依赖库未完全安装完毕,请退出程序重试。")
|
||||
exit()
|
||||
import util.general_utils as gu
|
||||
if 'http_proxy' in cfg:
|
||||
os.environ['HTTP_PROXY'] = cfg['http_proxy']
|
||||
if 'https_proxy' in cfg:
|
||||
os.environ['HTTPS_PROXY'] = cfg['https_proxy']
|
||||
|
||||
os.environ['NO_PROXY'] = 'cn.bing.com,https://api.sgroup.qq.com'
|
||||
|
||||
# 检查temp文件夹
|
||||
if not os.path.exists(abs_path+"temp"):
|
||||
os.mkdir(abs_path+"temp")
|
||||
|
||||
provider = privider_chooser(cfg)
|
||||
print('[System] 当前语言模型提供商: ' + provider)
|
||||
if len(provider) == 0:
|
||||
gu.log("未开启任何语言模型, 请在configs/config.yaml下选择开启相应语言模型。", gu.LEVEL_CRITICAL)
|
||||
input("按任意键退出...")
|
||||
exit()
|
||||
print('[System] 开启的语言模型: ' + str(provider))
|
||||
# 执行Bot
|
||||
qqBot.initBot(cfg, provider)
|
||||
|
||||
# 语言模型提供商选择器
|
||||
# 目前有:OpenAI官方API、逆向库
|
||||
def privider_chooser(cfg):
|
||||
l = []
|
||||
if 'rev_ChatGPT' in cfg and cfg['rev_ChatGPT']['enable']:
|
||||
return 'rev_chatgpt'
|
||||
elif 'rev_ernie' in cfg and cfg['rev_ernie']['enable']:
|
||||
return 'rev_ernie'
|
||||
elif 'rev_edgegpt' in cfg and cfg['rev_edgegpt']['enable']:
|
||||
return 'rev_edgegpt'
|
||||
else:
|
||||
return 'openai_official'
|
||||
|
||||
# 仅支持linux
|
||||
def hot_update():
|
||||
target = 'target.tar'
|
||||
time.sleep(5)
|
||||
while(True):
|
||||
if os.path.exists('version.txt'):
|
||||
version_file = open('version.txt', 'r', encoding='utf-8')
|
||||
vs = version_file.read()
|
||||
version = float(vs)
|
||||
else:
|
||||
version = 0
|
||||
if not os.path.exists(target):
|
||||
version = 0
|
||||
try:
|
||||
res = requests.get("https://soulter.top/channelbot/update.json")
|
||||
res_obj = json.loads(res.text)
|
||||
ol_version = float(res_obj['version'])
|
||||
if ol_version > version:
|
||||
print('发现新版本: ' + str(ol_version))
|
||||
res = requests.get(res_obj['linux-url'], stream=True)
|
||||
filesize = res.headers["Content-Length"]
|
||||
print('文件大小: ' + str(int(filesize) / 1024 / 1024) + 'MB')
|
||||
print('正在更新文件...')
|
||||
chunk_size = 1024
|
||||
times = int(filesize) // chunk_size
|
||||
show = 1 / times
|
||||
show2 = 1 / times
|
||||
start = 1
|
||||
with open(target, "wb") as pyFile:
|
||||
for chunk in res.iter_content(chunk_size=chunk_size):
|
||||
if chunk:
|
||||
pyFile.write(chunk)
|
||||
if start <= times:
|
||||
print(f"\r下载进度: {show:.2%}",end="",flush=True)
|
||||
start += 1
|
||||
show += show2
|
||||
else:
|
||||
sys.stdout.write(f"下载进度: 100%\n")
|
||||
print('更新完成')
|
||||
print('解压覆盖')
|
||||
os.system(f"tar -zxvf {target}")
|
||||
version = ol_version
|
||||
version_file = open('version.txt', 'w+', encoding='utf-8')
|
||||
version_file.write(str(res_obj['version']))
|
||||
version_file.flush()
|
||||
version_file.close()
|
||||
|
||||
try:
|
||||
update_version(version)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
|
||||
print('自启动')
|
||||
py = sys.executable
|
||||
os.execl(py, py, *sys.argv)
|
||||
time.sleep(60*60*3)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
print("upd出现异常, 请联系QQ905617992")
|
||||
time.sleep(60*60*3)
|
||||
|
||||
def update_version(ver):
|
||||
if not os.path.exists('update_record'):
|
||||
object_id = ''
|
||||
else:
|
||||
object_id = open("update_record", 'r', encoding='utf-8').read()
|
||||
addr = 'unknown'
|
||||
try:
|
||||
addr = requests.get('http://myip.ipip.net', timeout=5).text
|
||||
except BaseException:
|
||||
pass
|
||||
try:
|
||||
ts = str(time.time())
|
||||
# md = hashlib.md5((ts+'QAZ1rQLY1ZufHrZlpuUiNff7').encode())
|
||||
headers = {
|
||||
'X-LC-Id': 'UqfXTWW15nB7iMT0OHvYrDFb-gzGzoHsz',
|
||||
'X-LC-Key': 'QAZ1rQLY1ZufHrZlpuUiNff7',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
d = {"data": {'version':'win-hot-update'+str(ver), 'addr': addr}}
|
||||
d = json.dumps(d).encode("utf-8")
|
||||
res = requests.put(f'https://uqfxtww1.lc-cn-n1-shared.com/1.1/classes/version_record/{object_id}', headers = headers, data = d)
|
||||
if json.loads(res.text)['code'] == 1:
|
||||
res = requests.post(f'https://uqfxtww1.lc-cn-n1-shared.com/1.1/classes/version_record', headers = headers, data = d)
|
||||
object_id = json.loads(res.text)['objectId']
|
||||
object_id_file = open("update_record", 'w+', encoding='utf-8')
|
||||
object_id_file.write(str(object_id))
|
||||
object_id_file.flush()
|
||||
object_id_file.close()
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
l.append('rev_chatgpt')
|
||||
if 'rev_ernie' in cfg and cfg['rev_ernie']['enable']:
|
||||
l.append('rev_ernie')
|
||||
if 'rev_edgegpt' in cfg and cfg['rev_edgegpt']['enable']:
|
||||
l.append('rev_edgegpt')
|
||||
if 'openai' in cfg and cfg['openai']['key'] != None and len(cfg['openai']['key'])>0:
|
||||
l.append('openai_official')
|
||||
return l
|
||||
|
||||
def check_env():
|
||||
if not (sys.version_info.major == 3 and sys.version_info.minor >= 8):
|
||||
print("请使用Python3.8运行本项目")
|
||||
input("按任意键退出...")
|
||||
exit()
|
||||
|
||||
# 检查pip
|
||||
# pip_tag = "pip"
|
||||
# mm = os.system("pip -V")
|
||||
# if mm != 0:
|
||||
# mm1 = os.system("pip3 -V")
|
||||
# if mm1 != 0:
|
||||
# print("未检测到pip, 请安装Python(版本应>=3.9)")
|
||||
# input("按任意键退出...")
|
||||
# exit()
|
||||
# else:
|
||||
# pip_tag = "pip3"
|
||||
|
||||
if os.path.exists('requirements.txt'):
|
||||
pth = 'requirements.txt'
|
||||
else:
|
||||
pth = 'QQChannelChatGPT'+ os.sep +'requirements.txt'
|
||||
print("正在更新三方依赖库...")
|
||||
try:
|
||||
import openai
|
||||
import botpy
|
||||
import yaml
|
||||
except Exception as e:
|
||||
# print(e)
|
||||
try:
|
||||
print("安装依赖库中...")
|
||||
os.system("pip3 install openai")
|
||||
os.system("pip3 install qq-botpy")
|
||||
os.system("pip3 install pyyaml")
|
||||
print("安装依赖库完毕...")
|
||||
except BaseException:
|
||||
print("\n安装第三方库异常.请自行安装或者联系QQ905617992.")
|
||||
|
||||
# 检查key
|
||||
with open(abs_path+"configs/config.yaml", 'r', encoding='utf-8') as ymlfile:
|
||||
import yaml
|
||||
cfg = yaml.safe_load(ymlfile)
|
||||
if cfg['openai']['key'] == '' or cfg['openai']['key'] == None:
|
||||
print("请先在configs/config.yaml下添加一个可用的OpenAI Key。详情请前往https://beta.openai.com/account/api-keys")
|
||||
if cfg['qqbot']['appid'] == '' or cfg['qqbot']['token'] == '' or cfg['qqbot']['appid'] == None or cfg['qqbot']['token'] == None:
|
||||
print("请先在configs/config.yaml下完善appid和token令牌(在https://q.qq.com/上注册一个QQ机器人即可获得)")
|
||||
pipmain(['install', '-r', pth])
|
||||
print("依赖库安装完毕。")
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
while True:
|
||||
res = input("依赖库可能安装失败了。\n如果是报错ValueError: check_hostname requires server_hostname,请尝试先关闭代理后重试。\n输入y回车重试\n输入c回车使用国内镜像源下载\n输入其他按键回车继续往下执行。")
|
||||
if res == "y":
|
||||
try:
|
||||
pipmain(['install', '-r', pth])
|
||||
print("依赖库安装完毕。")
|
||||
break
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
continue
|
||||
|
||||
elif res == "c":
|
||||
try:
|
||||
pipmain(['install', '-r', pth, '-i', 'https://mirrors.aliyun.com/pypi/simple/'])
|
||||
print("依赖库安装完毕。")
|
||||
break
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
def get_platform():
|
||||
import platform
|
||||
@@ -172,15 +111,17 @@ def get_platform():
|
||||
print("other")
|
||||
|
||||
if __name__ == "__main__":
|
||||
global pid
|
||||
pid = os.getpid()
|
||||
global ma_type
|
||||
print("程序PID:"+str(pid))
|
||||
check_env()
|
||||
bot_event = threading.Event()
|
||||
loop = asyncio.get_event_loop()
|
||||
# ma_type = get_platform()
|
||||
# if ma_type == 'linux':
|
||||
# threading.Thread(target=hot_update).start()
|
||||
|
||||
main(loop, bot_event)
|
||||
|
||||
# 获取参数
|
||||
args = sys.argv
|
||||
if len(args) > 1:
|
||||
if args[1] == '-replit':
|
||||
print("[System] 启动Replit Web保活服务...")
|
||||
try:
|
||||
from webapp_replit import keep_alive
|
||||
keep_alive()
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
print(f"[System-err] Replit Web保活服务启动失败:{str(e)}")
|
||||
main()
|
||||
|
||||
@@ -1,53 +1,453 @@
|
||||
import abc
|
||||
import json
|
||||
import git.exc
|
||||
from git.repo import Repo
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
import requests
|
||||
from model.provider.provider import Provider
|
||||
import json
|
||||
import util.plugin_util as putil
|
||||
import shutil
|
||||
import importlib
|
||||
from util import general_utils as gu
|
||||
from util.cmd_config import CmdConfig as cc
|
||||
from model.platform.qq import QQ
|
||||
import stat
|
||||
from nakuru.entities.components import (
|
||||
Plain,
|
||||
Image
|
||||
)
|
||||
from PIL import Image as PILImage
|
||||
|
||||
PLATFORM_QQCHAN = 'qqchan'
|
||||
PLATFORM_GOCQ = 'gocq'
|
||||
|
||||
# 指令功能的基类,通用的(不区分语言模型)的指令就在这实现
|
||||
class Command:
|
||||
def __init__(self, provider: Provider):
|
||||
self.provider = Provider
|
||||
|
||||
@abc.abstractmethod
|
||||
def check_command(self, message):
|
||||
if message.startswith("help") or message.startswith("帮助"):
|
||||
return True, self.help()
|
||||
def get_plugin_modules(self):
|
||||
plugins = []
|
||||
try:
|
||||
if os.path.exists("addons/plugins"):
|
||||
plugins = putil.get_modules("addons/plugins")
|
||||
return plugins
|
||||
elif os.path.exists("QQChannelChatGPT/addons/plugins"):
|
||||
plugins = putil.get_modules("QQChannelChatGPT/addons/plugins")
|
||||
return plugins
|
||||
else:
|
||||
return None
|
||||
except BaseException as e:
|
||||
raise e
|
||||
|
||||
def check_command(self, message, role, platform,
|
||||
message_obj,
|
||||
cached_plugins: dict,
|
||||
qq_platform: QQ,
|
||||
global_object: dict):
|
||||
# 插件
|
||||
|
||||
for k, v in cached_plugins.items():
|
||||
try:
|
||||
hit, res = v["clsobj"].run(message, role, platform, message_obj, qq_platform)
|
||||
if hit:
|
||||
return True, res
|
||||
except BaseException as e:
|
||||
gu.log(f"{k}插件加载出现问题,原因: {str(e)}\n已安装插件: {cached_plugins.keys}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。", level=gu.LEVEL_WARNING)
|
||||
|
||||
if self.command_start_with(message, "nick"):
|
||||
return True, self.set_nick(message, platform, role)
|
||||
|
||||
if self.command_start_with(message, "plugin"):
|
||||
return True, self.plugin_oper(message, role, cached_plugins, platform)
|
||||
|
||||
if self.command_start_with(message, "myid"):
|
||||
return True, self.get_my_id(message_obj, platform)
|
||||
if self.command_start_with(message, "nconf") or self.command_start_with(message, "newconf"):
|
||||
return True, self.get_new_conf(message, role, platform)
|
||||
if self.command_start_with(message, "web"): # 网页搜索
|
||||
return True, self.web_search(message, global_object)
|
||||
|
||||
return False, None
|
||||
|
||||
def update(self, message: str):
|
||||
def web_search(self, message, global_object):
|
||||
if "web_search" not in global_object:
|
||||
global_object["web_search"] = False
|
||||
if message == "web on":
|
||||
global_object["web_search"] = True
|
||||
return True, "已开启网页搜索", "web"
|
||||
elif message == "web off":
|
||||
global_object["web_search"] = False
|
||||
return True, "已关闭网页搜索", "web"
|
||||
return True, f"网页搜索功能当前状态: {global_object['web_search']}", "web"
|
||||
def get_my_id(self, message_obj, platform):
|
||||
print(message_obj)
|
||||
if platform == "gocq":
|
||||
if message_obj.type == "GuildMessage":
|
||||
return True, f"你的频道id是{str(message_obj.sender.tiny_id)}", "plugin"
|
||||
else:
|
||||
return True, f"你的QQ是{str(message_obj.sender.user_id)}", "plugin"
|
||||
else:
|
||||
return True, f"{str(message_obj)}\n(此指令为开发专用,为提供更多数据,请自行从中找出您的频道ID。在author->id中。)", "plugin"
|
||||
|
||||
def get_new_conf(self, message, role, platform):
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限使用此指令。", "newconf"
|
||||
if platform == gu.PLATFORM_GOCQ:
|
||||
l = message.split(" ")
|
||||
if len(l) <= 1:
|
||||
obj = cc.get_all()
|
||||
p = gu.create_text_image("【cmd_config.json】", json.dumps(obj, indent=4, ensure_ascii=False))
|
||||
return True, [Image.fromFileSystem(p)], "newconf"
|
||||
return False, f"Not support or not implemented.", "newconf"
|
||||
|
||||
|
||||
|
||||
def plugin_reload(self, cached_plugins: dict, target: str = None, all: bool = False):
|
||||
plugins = self.get_plugin_modules()
|
||||
fail_rec = ""
|
||||
if plugins is None:
|
||||
return False, "未找到任何插件模块"
|
||||
|
||||
for p in plugins:
|
||||
try:
|
||||
if p not in cached_plugins or p == target or all:
|
||||
module = __import__("addons.plugins." + p + "." + p, fromlist=[p])
|
||||
if p in cached_plugins:
|
||||
module = importlib.reload(module)
|
||||
cls = putil.get_classes(p, module)
|
||||
obj = getattr(module, cls[0])()
|
||||
try:
|
||||
info = obj.info()
|
||||
if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info:
|
||||
fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n"
|
||||
continue
|
||||
if isinstance(info, dict) == False:
|
||||
fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n"
|
||||
continue
|
||||
except BaseException as e:
|
||||
fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n"
|
||||
continue
|
||||
cached_plugins[p] = {
|
||||
"module": module,
|
||||
"clsobj": obj,
|
||||
"info": info
|
||||
}
|
||||
except BaseException as e:
|
||||
fail_rec += f"加载{p}插件出现问题,原因{str(e)}\n"
|
||||
if fail_rec == "":
|
||||
return True, None
|
||||
else:
|
||||
return False, fail_rec
|
||||
|
||||
'''
|
||||
插件指令
|
||||
'''
|
||||
def plugin_oper(self, message: str, role: str, cached_plugins: dict, platform: str):
|
||||
l = message.split(" ")
|
||||
if len(l) < 2:
|
||||
if platform == gu.PLATFORM_GOCQ:
|
||||
p = gu.create_text_image("【插件指令面板】", "安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin d 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n")
|
||||
return True, [Image.fromFileSystem(p)], "plugin"
|
||||
return True, "\n=====插件指令面板=====\n安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin d 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n===============", "plugin"
|
||||
else:
|
||||
ppath = ""
|
||||
if os.path.exists("addons/plugins"):
|
||||
ppath = "addons/plugins"
|
||||
elif os.path.exists("QQChannelChatGPT/addons/plugins"):
|
||||
ppath = "QQChannelChatGPT/addons/plugins"
|
||||
else:
|
||||
return False, "未找到插件目录", "plugin"
|
||||
if l[1] == "i":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限安装插件", "plugin"
|
||||
try:
|
||||
# 得到url的最后一段
|
||||
d = l[2].split("/")[-1]
|
||||
# 创建文件夹
|
||||
plugin_path = os.path.join(ppath, d)
|
||||
if os.path.exists(plugin_path):
|
||||
shutil.rmtree(plugin_path)
|
||||
os.mkdir(plugin_path)
|
||||
Repo.clone_from(l[2],to_path=plugin_path,branch='master')
|
||||
|
||||
# 读取插件的requirements.txt
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
with open(os.path.join(plugin_path, "requirements.txt"), "r", encoding="utf-8") as f:
|
||||
for line in f.readlines():
|
||||
mm = os.system(f"pip3 install {line.strip()}")
|
||||
if mm != 0:
|
||||
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
|
||||
# 加载没缓存的插件
|
||||
ok, err = self.plugin_reload(cached_plugins, target=d)
|
||||
if ok:
|
||||
return True, "插件拉取并载入成功~", "plugin"
|
||||
else:
|
||||
# if os.path.exists(plugin_path):
|
||||
# shutil.rmtree(plugin_path)
|
||||
return False, f"插件拉取载入失败。\n跟踪: \n{err}", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"拉取插件失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "d":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限删除插件", "plugin"
|
||||
try:
|
||||
# 删除文件夹
|
||||
# shutil.rmtree(os.path.join(ppath, l[2]))
|
||||
self.remove_dir(os.path.join(ppath, l[2]))
|
||||
if l[2] in cached_plugins:
|
||||
del cached_plugins[l[2]]
|
||||
return True, "插件卸载成功~", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"卸载插件失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "u":
|
||||
plugin_path = os.path.join(ppath, l[2])
|
||||
try:
|
||||
repo = Repo(path = plugin_path)
|
||||
repo.remotes.origin.pull()
|
||||
|
||||
# 读取插件的requirements.txt
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
with open(os.path.join(plugin_path, "requirements.txt"), "r", encoding="utf-8") as f:
|
||||
for line in f.readlines():
|
||||
mm = os.system(f"pip3 install {line.strip()}")
|
||||
if mm != 0:
|
||||
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
|
||||
|
||||
ok, err = self.plugin_reload(cached_plugins, target=l[2])
|
||||
if ok:
|
||||
return True, "\n更新插件成功!!", "plugin"
|
||||
else:
|
||||
return False, "更新插件成功,但是重载插件失败。\n问题跟踪: \n"+err, "plugin"
|
||||
except BaseException as e:
|
||||
return False, "更新插件失败, 请使用plugin i指令覆盖安装", "plugin"
|
||||
|
||||
elif l[1] == "l":
|
||||
try:
|
||||
plugin_list_info = "\n".join([f"{k}: \n名称: {v['info']['name']}\n简介: {v['info']['desc']}\n版本: {v['info']['version']}\n作者: {v['info']['author']}\n" for k, v in cached_plugins.items()])
|
||||
if platform == gu.PLATFORM_GOCQ:
|
||||
p = gu.create_text_image("【已激活插件列表】", plugin_list_info + "\n使用plugin v 插件名 查看插件帮助\n")
|
||||
return True, [Image.fromFileSystem(p)], "plugin"
|
||||
return True, "\n=====已激活插件列表=====\n" + plugin_list_info + "\n使用plugin v 插件名 查看插件帮助\n=================", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"获取插件列表失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "v":
|
||||
try:
|
||||
if l[2] in cached_plugins:
|
||||
info = cached_plugins[l[2]]["info"]
|
||||
if platform == gu.PLATFORM_GOCQ:
|
||||
p = gu.create_text_image(f"【插件信息】", f"名称: {info['name']}\n{info['desc']}\n版本: {info['version']}\n作者: {info['author']}\n\n帮助:\n{info['help']}")
|
||||
return True, [Image.fromFileSystem(p)], "plugin"
|
||||
res = f"\n=====插件信息=====\n名称: {info['name']}\n{info['desc']}\n版本: {info['version']}作者: {info['author']}\n\n帮助:\n{info['help']}"
|
||||
return True, res, "plugin"
|
||||
else:
|
||||
return False, "未找到该插件", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"获取插件信息失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "reload":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限重载插件", "plugin"
|
||||
try:
|
||||
ok, err = self.plugin_reload(cached_plugins, all = True)
|
||||
if ok:
|
||||
return True, "\n重载插件成功~", "plugin"
|
||||
else:
|
||||
# if os.path.exists(plugin_path):
|
||||
# shutil.rmtree(plugin_path)
|
||||
return False, f"插件重载失败。\n跟踪: \n{err}", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"插件重载失败,原因: {str(e)}", "plugin"
|
||||
|
||||
elif l[1] == "dev":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限开发者模式", "plugin"
|
||||
return True, "cached_plugins: \n" + str(cached_plugins), "plugin"
|
||||
|
||||
|
||||
def remove_dir(self, file_path):
|
||||
while 1:
|
||||
if not os.path.exists(file_path):
|
||||
break
|
||||
try:
|
||||
shutil.rmtree(file_path)
|
||||
except PermissionError as e:
|
||||
err_file_path = str(e).split("\'", 2)[1]
|
||||
if os.path.exists(err_file_path):
|
||||
os.chmod(err_file_path, stat.S_IWUSR)
|
||||
|
||||
|
||||
'''
|
||||
nick: 存储机器人的昵称
|
||||
'''
|
||||
def set_nick(self, message: str, platform: str, role: str = "member"):
|
||||
if role != "admin":
|
||||
return True, "你无权使用该指令 :P", "nick"
|
||||
if platform == PLATFORM_GOCQ:
|
||||
l = message.split(" ")
|
||||
if len(l) == 1:
|
||||
return True, "【设置机器人昵称】示例:\n支持多昵称\nnick 昵称1 昵称2 昵称3", "nick"
|
||||
nick = l[1:]
|
||||
self.general_command_storer("nick_qq", nick)
|
||||
return True, f"设置成功!现在你可以叫我这些昵称来提问我啦~", "nick"
|
||||
elif platform == PLATFORM_QQCHAN:
|
||||
nick = message.split(" ")[2]
|
||||
return False, "QQ频道平台不支持为机器人设置昵称。", "nick"
|
||||
|
||||
"""
|
||||
存储指令结果到cmd_config.json
|
||||
"""
|
||||
def general_command_storer(self, key, value):
|
||||
if not os.path.exists("cmd_config.json"):
|
||||
config = {}
|
||||
else:
|
||||
with open("cmd_config.json", "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
config[key] = value
|
||||
with open("cmd_config.json", "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4, ensure_ascii=False)
|
||||
f.flush()
|
||||
|
||||
|
||||
def general_commands(self):
|
||||
return {
|
||||
"help": "帮助",
|
||||
"keyword": "设置关键词/关键指令回复",
|
||||
"update": "更新面板",
|
||||
"update latest": "更新到最新版本",
|
||||
"update r": "重启机器人",
|
||||
"reset": "重置会话",
|
||||
"nick": "设置机器人昵称",
|
||||
"plugin": "插件安装、卸载和重载",
|
||||
"web on/off": "启动或关闭网页搜索能力",
|
||||
"/bing": "切换到bing模型",
|
||||
"/gpt": "切换到OpenAI ChatGPT API",
|
||||
"/revgpt": "切换到网页版ChatGPT",
|
||||
}
|
||||
|
||||
def help_messager(self, commands: dict, platform: str, cached_plugins: dict = None):
|
||||
try:
|
||||
resp = requests.get("https://soulter.top/channelbot/notice.json").text
|
||||
notice = json.loads(resp)["notice"]
|
||||
except BaseException as e:
|
||||
notice = ""
|
||||
msg = "# Help Center\n## 指令列表\n"
|
||||
# msg = "Github项目名QQChannelChatGPT, 有问题提交issue, 欢迎Star\n【指令列表】\n"
|
||||
for key, value in commands.items():
|
||||
msg += f"`{key}` - {value}\n"
|
||||
# plugins
|
||||
if cached_plugins != None:
|
||||
plugin_list_info = "\n".join([f"`{k}` {v['info']['name']}\n{v['info']['desc']}\n" for k, v in cached_plugins.items()])
|
||||
if plugin_list_info.strip() != "":
|
||||
msg += "\n## 插件列表\n> 使用plugin v 插件名 查看插件帮助\n"
|
||||
msg += plugin_list_info
|
||||
msg += notice
|
||||
|
||||
if platform == gu.PLATFORM_GOCQ:
|
||||
try:
|
||||
# p = gu.create_text_image("【Help Center】", msg)
|
||||
p = gu.create_markdown_image(msg)
|
||||
return [Image.fromFileSystem(p)]
|
||||
except BaseException as e:
|
||||
gu.log(str(e))
|
||||
return msg
|
||||
return msg
|
||||
|
||||
# 接受可变参数
|
||||
def command_start_with(self, message: str, *args):
|
||||
for arg in args:
|
||||
if message.startswith(arg) or message.startswith('/'+arg):
|
||||
return True
|
||||
return False
|
||||
|
||||
# keyword: 关键字
|
||||
def keyword(self, message: str, role: str):
|
||||
if role != "admin":
|
||||
return True, "你没有权限使用该指令", "keyword"
|
||||
|
||||
l = message.split(" ")
|
||||
|
||||
if len(l) < 3:
|
||||
return True, "【设置关键词回复】示例:\nkeyword hi 你好\n当发送hi的时候会回复你好\nkeyword /hi 你好\n当发送/hi时会回复你好\n删除关键词: keyword d hi\n删除hi关键词的回复", "keyword"
|
||||
|
||||
del_mode = False
|
||||
if l[1] == "d":
|
||||
del_mode = True
|
||||
|
||||
try:
|
||||
if os.path.exists("keyword.json"):
|
||||
with open("keyword.json", "r", encoding="utf-8") as f:
|
||||
keyword = json.load(f)
|
||||
if del_mode:
|
||||
# 删除关键词
|
||||
if l[2] not in keyword:
|
||||
return False, "该关键词不存在", "keyword"
|
||||
else: del keyword[l[2]]
|
||||
else:
|
||||
keyword[l[1]] = l[2]
|
||||
else:
|
||||
if del_mode:
|
||||
return False, "该关键词不存在", "keyword"
|
||||
keyword = {l[1]: l[2]}
|
||||
with open("keyword.json", "w", encoding="utf-8") as f:
|
||||
json.dump(keyword, f, ensure_ascii=False, indent=4)
|
||||
f.flush()
|
||||
if del_mode:
|
||||
return True, "删除成功: "+l[2], "keyword"
|
||||
return True, "设置成功: "+l[1]+" -> "+l[2], "keyword"
|
||||
except BaseException as e:
|
||||
return False, "设置失败: "+str(e), "keyword"
|
||||
|
||||
def update(self, message: str, role: str):
|
||||
if role != "admin":
|
||||
return True, "你没有权限使用该指令", "keyword"
|
||||
l = message.split(" ")
|
||||
if len(l) == 1:
|
||||
# 得到本地版本号和最新版本号
|
||||
repo = Repo()
|
||||
try:
|
||||
repo = Repo()
|
||||
except git.exc.InvalidGitRepositoryError:
|
||||
repo = Repo(path="QQChannelChatGPT")
|
||||
now_commit = repo.head.commit
|
||||
|
||||
# 得到最新的5条commit列表, 包含commit信息
|
||||
# 得到远程3条commit列表, 包含commit信息
|
||||
origin = repo.remotes.origin
|
||||
origin.fetch()
|
||||
commits = list(repo.iter_commits('master', max_count=5))
|
||||
commits = list(repo.iter_commits('master', max_count=3))
|
||||
commits_log = ''
|
||||
index = 1
|
||||
for commit in commits:
|
||||
commits_log += f"[{index}] {commit.message}\n-----------\n"
|
||||
if commit.message.endswith("\n"):
|
||||
commits_log += f"[{index}] {commit.message}-----------\n"
|
||||
else:
|
||||
commits_log += f"[{index}] {commit.message}\n-----------\n"
|
||||
index+=1
|
||||
remote_commit_hash = origin.refs.master.commit.hexsha[:6]
|
||||
|
||||
return True, f"当前版本: {now_commit.hexsha[:6]}\n最新版本: {remote_commit_hash}\n\n最新5条commit:\n{str(commits_log)}\n使用update latest更新至最新版本\n"
|
||||
return True, f"当前版本: {now_commit.hexsha[:6]}\n最新版本: {remote_commit_hash}\n\n3条commit(非最新):\n{str(commits_log)}\n使用update latest更新至最新版本\n", "update"
|
||||
else:
|
||||
if l[1] == "latest":
|
||||
pash_tag = ""
|
||||
try:
|
||||
repo = Repo()
|
||||
try:
|
||||
repo = Repo()
|
||||
except git.exc.InvalidGitRepositoryError:
|
||||
repo = Repo(path="QQChannelChatGPT")
|
||||
pash_tag = "QQChannelChatGPT"+os.sep
|
||||
repo.remotes.origin.pull()
|
||||
py = sys.executable
|
||||
os.execl(py, py, *sys.argv)
|
||||
return True, "更新成功"
|
||||
|
||||
if len(l) == 3 and l[2] == "r":
|
||||
py = sys.executable
|
||||
os.execl(py, py, *sys.argv)
|
||||
|
||||
return True, "更新成功~是否重启?输入update r重启(重启指令不返回任何确认信息)。", "update"
|
||||
|
||||
except BaseException as e:
|
||||
return False, "更新失败: "+str(e)
|
||||
return False, "更新失败: "+str(e), "update"
|
||||
if l[1] == "r":
|
||||
py = sys.executable
|
||||
os.execl(py, py, *sys.argv)
|
||||
|
||||
|
||||
def reset(self):
|
||||
return False
|
||||
@@ -62,15 +462,7 @@ class Command:
|
||||
return False
|
||||
|
||||
def help(self):
|
||||
# ol_version = 'Unknown'
|
||||
# try:
|
||||
# res = requests.get("https://soulter.top/channelbot/update.json")
|
||||
# res_obj = json.loads(res.text)
|
||||
# ol_version = res_obj['version']
|
||||
# except BaseException:
|
||||
# pass
|
||||
return True, f"[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\n指令面板:\nstatus 查看机器人key状态\ncount 查看机器人统计信息\nreset 重置会话\nhis 查看历史记录\ntoken 查看会话token数\nhelp 查看帮助\nset 人格指令菜单\nkey 动态添加key"
|
||||
|
||||
return False
|
||||
|
||||
def status(self):
|
||||
return False
|
||||
|
||||
@@ -1,44 +1,84 @@
|
||||
from model.command.command import Command
|
||||
from model.provider.provider_openai_official import ProviderOpenAIOfficial
|
||||
from cores.qqbot.personality import personalities
|
||||
from model.platform.qq import QQ
|
||||
from util import general_utils as gu
|
||||
|
||||
|
||||
class CommandOpenAIOfficial(Command):
|
||||
def __init__(self, provider: ProviderOpenAIOfficial):
|
||||
def __init__(self, provider: ProviderOpenAIOfficial, global_object: dict):
|
||||
self.provider = provider
|
||||
self.cached_plugins = {}
|
||||
self.global_object = global_object
|
||||
|
||||
def check_command(self, message: str, session_id: str, user_name: str):
|
||||
if message.startswith("reset") or message.startswith("重置"):
|
||||
def check_command(self,
|
||||
message: str,
|
||||
session_id: str,
|
||||
user_name: str,
|
||||
role: str,
|
||||
platform: str,
|
||||
message_obj,
|
||||
cached_plugins: dict,
|
||||
qq_platform: QQ,):
|
||||
self.platform = platform
|
||||
hit, res = super().check_command(message, role, platform, message_obj=message_obj,
|
||||
cached_plugins=cached_plugins,
|
||||
qq_platform=qq_platform,
|
||||
global_object=self.global_object)
|
||||
if hit:
|
||||
return True, res
|
||||
if self.command_start_with(message, "reset", "重置"):
|
||||
return True, self.reset(session_id)
|
||||
elif message.startswith("his") or message.startswith("历史"):
|
||||
elif self.command_start_with(message, "his", "历史"):
|
||||
return True, self.his(message, session_id, user_name)
|
||||
elif message.startswith("token"):
|
||||
elif self.command_start_with(message, "token"):
|
||||
return True, self.token(session_id)
|
||||
elif message.startswith("gpt"):
|
||||
elif self.command_start_with(message, "gpt"):
|
||||
return True, self.gpt()
|
||||
elif message.startswith("status") or message.startswith("状态"):
|
||||
elif self.command_start_with(message, "status"):
|
||||
return True, self.status()
|
||||
elif message.startswith("count") or message.startswith("统计"):
|
||||
elif self.command_start_with(message, "count"):
|
||||
return True, self.count()
|
||||
elif message.startswith("help") or message.startswith("帮助"):
|
||||
return True, self.help()
|
||||
elif message.startswith("key") or message.startswith("动态添加key"):
|
||||
return True, self.key(message, user_name)
|
||||
elif message.startswith("unset"):
|
||||
elif self.command_start_with(message, "help", "帮助"):
|
||||
return True, self.help(cached_plugins)
|
||||
elif self.command_start_with(message, "unset"):
|
||||
return True, self.unset(session_id)
|
||||
elif message.startswith("set"):
|
||||
elif self.command_start_with(message, "set"):
|
||||
return True, self.set(message, session_id)
|
||||
elif message.startswith("画"):
|
||||
elif self.command_start_with(message, "update"):
|
||||
return True, self.update(message, role)
|
||||
elif self.command_start_with(message, "画", "draw"):
|
||||
return True, self.draw(message)
|
||||
elif message.startswith("update"):
|
||||
return True, self.update(message)
|
||||
elif self.command_start_with(message, "keyword"):
|
||||
return True, self.keyword(message, role)
|
||||
elif self.command_start_with(message, "key"):
|
||||
return True, self.key(message, user_name)
|
||||
|
||||
if self.command_start_with(message, "/"):
|
||||
return True, (False, "未知指令", "unknown_command")
|
||||
|
||||
return False, None
|
||||
|
||||
def help(self, cached_plugins):
|
||||
commands = super().general_commands()
|
||||
commands['画'] = '画画'
|
||||
commands['key'] = '添加OpenAI key'
|
||||
commands['set'] = '人格设置面板'
|
||||
commands['gpt'] = '查看gpt配置信息'
|
||||
commands['status'] = '查看key使用状态'
|
||||
commands['token'] = '查看本轮会话token'
|
||||
return True, super().help_messager(commands, self.platform, cached_plugins), "help"
|
||||
|
||||
|
||||
def reset(self, session_id: str):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "reset"
|
||||
self.provider.forget(session_id)
|
||||
return True, "重置成功"
|
||||
return True, "重置成功", "reset"
|
||||
|
||||
def his(self, message: str, session_id: str, name: str):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "his"
|
||||
#分页,每页5条
|
||||
msg = ''
|
||||
size_per_page = 3
|
||||
@@ -48,19 +88,25 @@ class CommandOpenAIOfficial(Command):
|
||||
# 检查是否有过历史记录
|
||||
if session_id not in self.provider.session_dict:
|
||||
msg = f"历史记录为空"
|
||||
return True, msg
|
||||
return True, msg, "his"
|
||||
l = self.provider.session_dict[session_id]
|
||||
max_page = len(l)//size_per_page + 1 if len(l)%size_per_page != 0 else len(l)//size_per_page
|
||||
p = self.provider.get_prompts_by_cache_list(self.provider.session_dict[session_id], divide=True, paging=True, size=size_per_page, page=page)
|
||||
return True, f"历史记录如下:\n{p}\n第{page}页 | 共{max_page}页\n*输入/his 2跳转到第2页"
|
||||
return True, f"历史记录如下:\n{p}\n第{page}页 | 共{max_page}页\n*输入/his 2跳转到第2页", "his"
|
||||
|
||||
def token(self, session_id: str):
|
||||
return True, f"会话的token数: {self.provider.get_user_usage_tokens(self.provider.session_dict[session_id])}\n系统最大缓存token数: {self.provider.max_tokens}"
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "token"
|
||||
return True, f"会话的token数: {self.provider.get_user_usage_tokens(self.provider.session_dict[session_id])}\n系统最大缓存token数: {self.provider.max_tokens}", "token"
|
||||
|
||||
def gpt(self):
|
||||
return True, f"OpenAI GPT配置:\n {self.provider.chatGPT_configs}"
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "gpt"
|
||||
return True, f"OpenAI GPT配置:\n {self.provider.chatGPT_configs}", "gpt"
|
||||
|
||||
def status(self):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "status"
|
||||
chatgpt_cfg_str = ""
|
||||
key_stat = self.provider.get_key_stat()
|
||||
index = 1
|
||||
@@ -78,52 +124,60 @@ class CommandOpenAIOfficial(Command):
|
||||
sponsor = key_stat[key]['sponsor']
|
||||
chatgpt_cfg_str += f" |-{index}: {key_stat[key]['used']}/{max} {sponsor}赞助{tag}\n"
|
||||
index += 1
|
||||
return True, f"⭐使用情况({str(gg_count)}个已用):\n{chatgpt_cfg_str}⏰全频道已用{total}tokens"
|
||||
return True, f"⭐使用情况({str(gg_count)}个已用):\n{chatgpt_cfg_str}⏰全频道已用{total}tokens", "status"
|
||||
|
||||
def count(self):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "reset"
|
||||
guild_count, guild_msg_count, guild_direct_msg_count, session_count = self.provider.get_stat()
|
||||
return True, f"当前会话数: {len(self.provider.session_dict)}\n共有频道数: {guild_count} \n共有消息数: {guild_msg_count}\n私信数: {guild_direct_msg_count}\n历史会话数: {session_count}"
|
||||
return True, f"当前会话数: {len(self.provider.session_dict)}\n共有频道数: {guild_count} \n共有消息数: {guild_msg_count}\n私信数: {guild_direct_msg_count}\n历史会话数: {session_count}", "count"
|
||||
|
||||
def key(self, message: str, user_name: str):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "reset"
|
||||
l = message.split(" ")
|
||||
if len(l) == 1:
|
||||
msg = "感谢您赞助key,key为官方API使用,请以以下格式赞助:\n/key xxxxx"
|
||||
return True, msg
|
||||
return True, msg, "key"
|
||||
key = l[1]
|
||||
if self.provider.check_key(key):
|
||||
self.provider.append_key(key, user_name)
|
||||
return True, f"*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。\n该Key被验证为有效。感谢{user_name}赞助~"
|
||||
else:
|
||||
return True, "该Key被验证为无效。也许是输入错误了,或者重试。"
|
||||
return True, "该Key被验证为无效。也许是输入错误了,或者重试。", "key"
|
||||
|
||||
def unset(self, session_id: str):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "unset"
|
||||
self.provider.now_personality = {}
|
||||
self.provider.forget(session_id)
|
||||
return True, "已清除人格并重置历史记录。"
|
||||
return True, "已清除人格并重置历史记录。", "unset"
|
||||
|
||||
def set(self, message: str, session_id: str):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "set"
|
||||
l = message.split(" ")
|
||||
if len(l) == 1:
|
||||
return True, f"【由Github项目QQChannelChatGPT支持】\n\n【人格文本由PlexPt开源项目awesome-chatgpt-pr \
|
||||
ompts-zh提供】\n\n这个是人格设置指令。\n设置人格: \n/set 人格名。例如/set 编剧\n人格列表: /set list\n人格详细信息: \
|
||||
/set view 人格名\n自定义人格: /set 人格文本\n清除人格: /unset\n【当前人格】: {str(self.provider.now_personality)}"
|
||||
/set view 人格名\n自定义人格: /set 人格文本\n清除人格: /unset\n【当前人格】: {str(self.provider.now_personality)}", "set"
|
||||
elif l[1] == "list":
|
||||
msg = "人格列表:\n"
|
||||
for key in personalities.keys():
|
||||
msg += f" |-{key}\n"
|
||||
msg += '\n\n*输入/set view 人格名查看人格详细信息'
|
||||
msg += '\n*不定时更新人格库,请及时更新本项目。'
|
||||
return True, msg
|
||||
return True, msg, "set"
|
||||
elif l[1] == "view":
|
||||
if len(l) == 2:
|
||||
return True, "请输入/set view 人格名"
|
||||
return True, "请输入/set view 人格名", "set"
|
||||
ps = l[2].strip()
|
||||
if ps in personalities:
|
||||
msg = f"人格{ps}的详细信息:\n"
|
||||
msg += f"{personalities[ps]}\n"
|
||||
else:
|
||||
msg = f"人格{ps}不存在"
|
||||
return True, msg
|
||||
return True, msg, "set"
|
||||
else:
|
||||
ps = l[1].strip()
|
||||
if ps in personalities:
|
||||
@@ -141,7 +195,7 @@ class CommandOpenAIOfficial(Command):
|
||||
'single-tokens': 0
|
||||
}
|
||||
self.provider.session_dict[session_id].append(new_record)
|
||||
return True, f"人格{ps}已设置."
|
||||
return True, f"人格{ps}已设置.", "set"
|
||||
else:
|
||||
self.provider.now_personality = {
|
||||
'name': '自定义人格',
|
||||
@@ -157,17 +211,23 @@ class CommandOpenAIOfficial(Command):
|
||||
}
|
||||
self.provider.session_dict[session_id] = []
|
||||
self.provider.session_dict[session_id].append(new_record)
|
||||
return True, f"自定义人格已设置。 \n人格信息: {ps}"
|
||||
return True, f"自定义人格已设置。 \n人格信息: {ps}", "set"
|
||||
|
||||
def draw(self, message):
|
||||
if self.provider is None:
|
||||
return False, "未启动OpenAI ChatGPT语言模型.", "draw"
|
||||
if message.startswith("/画"):
|
||||
message = message[2:]
|
||||
elif message.startswith("画"):
|
||||
message = message[1:]
|
||||
try:
|
||||
# 画图模式传回3个参数
|
||||
img_url = self.provider.image_chat(message)
|
||||
return True, img_url, "image"
|
||||
return True, img_url, "draw"
|
||||
except Exception as e:
|
||||
if 'exceeded' in str(e):
|
||||
return f"OpenAI API错误。原因:\n{str(e)} \n超额了。可自己搭建一个机器人(Github仓库:QQChannelChatGPT)"
|
||||
return False, f"图片生成失败: {e}"
|
||||
return False, f"图片生成失败: {e}", "draw"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,43 @@
|
||||
from model.command.command import Command
|
||||
from model.provider.provider_rev_chatgpt import ProviderRevChatGPT
|
||||
from model.platform.qq import QQ
|
||||
|
||||
class CommandRevChatGPT(Command):
|
||||
def __init__(self, provider: ProviderRevChatGPT):
|
||||
def __init__(self, provider: ProviderRevChatGPT, global_object: dict):
|
||||
self.provider = provider
|
||||
self.cached_plugins = {}
|
||||
self.global_object = global_object
|
||||
|
||||
def check_command(self,
|
||||
message: str,
|
||||
role: str,
|
||||
platform: str,
|
||||
message_obj,
|
||||
cached_plugins: dict,
|
||||
qq_platform: QQ):
|
||||
self.platform = platform
|
||||
hit, res = super().check_command(message, role, platform, message_obj=message_obj,
|
||||
cached_plugins=cached_plugins,
|
||||
qq_platform=qq_platform,
|
||||
global_object=self.global_object)
|
||||
if hit:
|
||||
return True, res
|
||||
if self.command_start_with(message, "help", "帮助"):
|
||||
return True, self.help(cached_plugins)
|
||||
elif self.command_start_with(message, "reset"):
|
||||
return True, self.reset()
|
||||
elif self.command_start_with(message, "update"):
|
||||
return True, self.update(message, role)
|
||||
elif self.command_start_with(message, "keyword"):
|
||||
return True, self.keyword(message, role)
|
||||
|
||||
def check_command(self, message: str):
|
||||
# hit, res = super().check_command(message)
|
||||
# if hit:
|
||||
# return res
|
||||
# if message.startswith("reset") or message.startswith("重置"):
|
||||
# return True, self.reset()
|
||||
if message.startswith("help") or message.startswith("帮助"):
|
||||
return True, self.help()
|
||||
elif message.startswith("update"):
|
||||
return True, self.update(message)
|
||||
if self.command_start_with(message, "/"):
|
||||
return True, (False, "未知指令", "unknown_command")
|
||||
return False, None
|
||||
|
||||
def help(self):
|
||||
return True, "[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\nRevChatGPT指令面板:\n当前语言模型RevChatGPT未实现任何指令\n"
|
||||
|
||||
def reset(self):
|
||||
return False, "此功能暂未开放", "reset"
|
||||
|
||||
|
||||
def help(self, cached_plugins: dict):
|
||||
return True, super().help_messager(super().general_commands(), self.platform, cached_plugins), "help"
|
||||
|
||||
@@ -1,27 +1,52 @@
|
||||
from model.command.command import Command
|
||||
from model.provider.provider_rev_edgegpt import ProviderRevEdgeGPT
|
||||
import asyncio
|
||||
from model.platform.qq import QQ
|
||||
|
||||
class CommandRevEdgeGPT(Command):
|
||||
def __init__(self, provider: ProviderRevEdgeGPT):
|
||||
def __init__(self, provider: ProviderRevEdgeGPT, global_object: dict):
|
||||
self.provider = provider
|
||||
|
||||
def check_command(self, message: str, loop):
|
||||
if message.startswith("reset") or message.startswith("重置"):
|
||||
self.cached_plugins = {}
|
||||
self.global_object = global_object
|
||||
|
||||
def check_command(self,
|
||||
message: str,
|
||||
loop,
|
||||
role: str,
|
||||
platform: str,
|
||||
message_obj,
|
||||
cached_plugins: dict,
|
||||
qq_platform: QQ):
|
||||
self.platform = platform
|
||||
hit, res = super().check_command(message, role, platform, message_obj=message_obj,
|
||||
cached_plugins=cached_plugins,
|
||||
qq_platform=qq_platform,
|
||||
global_object=self.global_object)
|
||||
if hit:
|
||||
return True, res
|
||||
if self.command_start_with(message, "reset"):
|
||||
return True, self.reset(loop)
|
||||
elif message.startswith("help") or message.startswith("帮助"):
|
||||
return True, self.help()
|
||||
elif message.startswith("update"):
|
||||
return True, self.update(message)
|
||||
elif self.command_start_with(message, "help"):
|
||||
return True, self.help(cached_plugins)
|
||||
elif self.command_start_with(message, "update"):
|
||||
return True, self.update(message, role)
|
||||
elif self.command_start_with(message, "keyword"):
|
||||
return True, self.keyword(message, role)
|
||||
|
||||
if self.command_start_with(message, "/"):
|
||||
return True, (False, "未知指令", "unknown_command")
|
||||
return False, None
|
||||
|
||||
def reset(self, loop):
|
||||
if self.provider is None:
|
||||
return False, "未启动Bing语言模型.", "reset"
|
||||
res = asyncio.run_coroutine_threadsafe(self.provider.forget(), loop).result()
|
||||
print(res)
|
||||
if res:
|
||||
return res, "重置成功"
|
||||
return res, "重置成功", "reset"
|
||||
else:
|
||||
return res, "重置失败"
|
||||
return res, "重置失败", "reset"
|
||||
|
||||
def help(self):
|
||||
return True, "[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\nRevBing指令面板:\nreset: 重置\nhelp: 帮助"
|
||||
def help(self, cached_plugins: dict):
|
||||
return True, super().help_messager(super().general_commands(), self.platform, cached_plugins), "help"
|
||||
|
||||
|
||||
143
model/platform/qq.py
Normal file
143
model/platform/qq.py
Normal file
@@ -0,0 +1,143 @@
|
||||
from nakuru.entities.components import Plain, At, Image, Node
|
||||
from util import general_utils as gu
|
||||
from util.cmd_config import CmdConfig
|
||||
import asyncio
|
||||
from nakuru import (
|
||||
CQHTTP,
|
||||
GuildMessage
|
||||
)
|
||||
import time
|
||||
|
||||
|
||||
class FakeSource:
|
||||
def __init__(self, type, group_id):
|
||||
self.type = type
|
||||
self.group_id = group_id
|
||||
|
||||
class QQ:
|
||||
def __init__(self, is_start: bool, cc: CmdConfig = None, gocq_loop = None) -> None:
|
||||
self.is_start = is_start
|
||||
self.gocq_loop = gocq_loop
|
||||
self.cc = cc
|
||||
|
||||
def run_bot(self, gocq):
|
||||
self.client: CQHTTP = gocq
|
||||
self.client.run()
|
||||
|
||||
def get_msg_loop(self):
|
||||
return self.gocq_loop
|
||||
|
||||
async def send_qq_msg(self,
|
||||
source,
|
||||
res,
|
||||
image_mode: bool = False):
|
||||
|
||||
if not self.is_start:
|
||||
raise Exception("管理员未启动GOCQ平台")
|
||||
"""
|
||||
res可以是一个数组, 也就是gocq的消息链。
|
||||
插件开发者请使用send方法, 可以不用直接调用这个方法。
|
||||
"""
|
||||
gu.log("回复GOCQ消息: "+str(res), level=gu.LEVEL_INFO, tag="GOCQ", max_len=300)
|
||||
|
||||
if isinstance(source, int):
|
||||
source = FakeSource("GroupMessage", source)
|
||||
|
||||
# str convert to CQ Message Chain
|
||||
if isinstance(res, str):
|
||||
res_str = res
|
||||
res = []
|
||||
if source.type == "GroupMessage":
|
||||
res.append(At(qq=source.user_id))
|
||||
res.append(Plain(text=res_str))
|
||||
|
||||
# if image mode, put all Plain texts into a new picture.
|
||||
if image_mode and isinstance(res, list):
|
||||
plains = []
|
||||
news = []
|
||||
for i in res:
|
||||
if isinstance(i, Plain):
|
||||
plains.append(i.text)
|
||||
else:
|
||||
news.append(i)
|
||||
p = gu.create_markdown_image("".join(plains))
|
||||
news.append(Image.fromFileSystem(p))
|
||||
res = news
|
||||
|
||||
|
||||
# 回复消息链
|
||||
if isinstance(res, list) and len(res) > 0:
|
||||
if source.type == "GuildMessage":
|
||||
await self.client.sendGuildChannelMessage(source.guild_id, source.channel_id, res)
|
||||
return
|
||||
elif source.type == "FriendMessage":
|
||||
await self.client.sendFriendMessage(source.user_id, res)
|
||||
return
|
||||
elif source.type == "GroupMessage":
|
||||
# 过长时forward发送
|
||||
plain_text_len = 0
|
||||
image_num = 0
|
||||
for i in res:
|
||||
if isinstance(i, Plain):
|
||||
plain_text_len += len(i.text)
|
||||
elif isinstance(i, Image):
|
||||
image_num += 1
|
||||
if plain_text_len > self.cc.get('qq_forward_threshold', 200):
|
||||
# 删除At
|
||||
for i in res:
|
||||
if isinstance(i, At):
|
||||
res.remove(i)
|
||||
node = Node(res)
|
||||
# node.content = res
|
||||
node.uin = source.self_id
|
||||
node.name = f"To {source.sender.nickname}:"
|
||||
node.time = int(time.time())
|
||||
print(node)
|
||||
nodes=[node]
|
||||
await self.client.sendGroupForwardMessage(source.group_id, nodes)
|
||||
return
|
||||
await self.client.sendGroupMessage(source.group_id, res)
|
||||
return
|
||||
|
||||
def send(self,
|
||||
to,
|
||||
res,
|
||||
):
|
||||
'''
|
||||
提供给插件的发送QQ消息接口, 不用在外部await。
|
||||
参数说明:第一个参数可以是消息对象,也可以是QQ群号。第二个参数是消息内容(消息内容可以是消息链列表,也可以是纯文字信息)。
|
||||
'''
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(self.send_qq_msg(to, res), self.gocq_loop).result()
|
||||
except BaseException as e:
|
||||
raise e
|
||||
|
||||
def send_guild(self,
|
||||
message_obj,
|
||||
res,
|
||||
):
|
||||
'''
|
||||
提供给插件的发送GOCQ QQ频道消息接口, 不用在外部await。
|
||||
参数说明:第一个参数必须是消息对象, 第二个参数是消息内容(消息内容可以是消息链列表,也可以是纯文字信息)。
|
||||
'''
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(self.send_qq_msg(message_obj, res), self.gocq_loop).result()
|
||||
except BaseException as e:
|
||||
raise e
|
||||
|
||||
def create_text_image(title: str, text: str, max_width=30, font_size=20):
|
||||
'''
|
||||
文本转图片。
|
||||
title: 标题
|
||||
text: 文本内容
|
||||
max_width: 文本宽度最大值(默认30)
|
||||
font_size: 字体大小(默认20)
|
||||
|
||||
返回:文件路径
|
||||
'''
|
||||
try:
|
||||
img = gu.word2img(title, text, max_width, font_size)
|
||||
p = gu.save_temp_img(img)
|
||||
return p
|
||||
except Exception as e:
|
||||
raise e
|
||||
72
model/platform/qqchan.py
Normal file
72
model/platform/qqchan.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import io
|
||||
import botpy
|
||||
from PIL import Image as PILImage
|
||||
from botpy.message import Message, DirectMessage
|
||||
import re
|
||||
import asyncio
|
||||
import requests
|
||||
from cores.qqbot.personality import personalities
|
||||
from util import general_utils as gu
|
||||
from nakuru.entities.components import Plain, At, Image
|
||||
|
||||
class QQChan():
|
||||
|
||||
def run_bot(self, botclient, appid, token):
|
||||
intents = botpy.Intents(public_guild_messages=True, direct_message=True)
|
||||
self.client = botclient
|
||||
self.client.run(appid=appid, token=token)
|
||||
|
||||
# gocq兼容层
|
||||
def gocq_compatible(self, gocq_message_chain: list):
|
||||
plain_text = ""
|
||||
image_path = None # only one img supported
|
||||
for i in gocq_message_chain:
|
||||
if isinstance(i, Plain):
|
||||
plain_text += i.text
|
||||
elif isinstance(i, Image) and image_path == None:
|
||||
image_path = i.path
|
||||
return plain_text, image_path
|
||||
|
||||
|
||||
|
||||
def send_qq_msg(self, message: Message, res, msg_ref = None):
|
||||
gu.log("回复QQ频道消息: "+str(res), level=gu.LEVEL_INFO, tag="QQ频道", max_len=500)
|
||||
|
||||
plain_text = ""
|
||||
image_path = None
|
||||
if isinstance(res, list):
|
||||
# 兼容gocq
|
||||
plain_text, image_path = self.gocq_compatible(res)
|
||||
elif isinstance(res, str):
|
||||
plain_text = res
|
||||
|
||||
print(plain_text, image_path)
|
||||
|
||||
try:
|
||||
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(plain_text), message_reference = msg_ref, file_image=image_path), self.client.loop)
|
||||
reply_res.result()
|
||||
except BaseException as e:
|
||||
# 分割过长的消息
|
||||
if "msg over length" in str(e):
|
||||
split_res = []
|
||||
split_res.append(plain_text[:len(plain_text)//2])
|
||||
split_res.append(plain_text[len(plain_text)//2:])
|
||||
for i in split_res:
|
||||
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(i), message_reference = msg_ref, file_image=image_path), self.client.loop)
|
||||
reply_res.result()
|
||||
else:
|
||||
# 发送qq信息
|
||||
try:
|
||||
# 防止被qq频道过滤消息
|
||||
plain_text = plain_text.replace(".", " . ")
|
||||
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(plain_text), message_reference = msg_ref, file_image=image_path), self.client.loop)
|
||||
# 发送信息
|
||||
except BaseException as e:
|
||||
print("QQ频道API错误: \n"+str(e))
|
||||
try:
|
||||
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(str.join(" ", plain_text)), message_reference = msg_ref, file_image=image_path), self.client.loop)
|
||||
except BaseException as e:
|
||||
plain_text = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE)
|
||||
plain_text = plain_text.replace(".", "·")
|
||||
asyncio.run_coroutine_threadsafe(message.reply(content=plain_text), self.client.loop).result()
|
||||
# send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}")
|
||||
@@ -1,6 +1,4 @@
|
||||
import openai
|
||||
import yaml
|
||||
from util.errors.errors import PromptExceededError
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
@@ -8,6 +6,7 @@ import sys
|
||||
from cores.database.conn import dbConn
|
||||
from model.provider.provider import Provider
|
||||
import threading
|
||||
from util import general_utils as gu
|
||||
|
||||
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
|
||||
key_record_path = abs_path+'chatgpt_key_record'
|
||||
@@ -18,7 +17,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
if 'api_base' in cfg and cfg['api_base'] != 'none' and cfg['api_base'] != '':
|
||||
openai.api_base = cfg['api_base']
|
||||
if cfg['key'] != '' and cfg['key'] != None:
|
||||
print("[System] 读取ChatGPT Key成功")
|
||||
gu.log("读取ChatGPT Key成功")
|
||||
self.key_list = cfg['key']
|
||||
else:
|
||||
input("[System] 请先去完善ChatGPT的Key。详情请前往https://beta.openai.com/account/api-keys")
|
||||
@@ -27,7 +26,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
self.init_key_record()
|
||||
|
||||
self.chatGPT_configs = cfg['chatGPTConfigs']
|
||||
print(f'[System] 加载ChatGPTConfigs: {self.chatGPT_configs}')
|
||||
gu.log(f'加载ChatGPTConfigs: {self.chatGPT_configs}')
|
||||
self.openai_configs = cfg
|
||||
# 会话缓存
|
||||
self.session_dict = {}
|
||||
@@ -41,9 +40,10 @@ class ProviderOpenAIOfficial(Provider):
|
||||
db1 = dbConn()
|
||||
for session in db1.get_all_session():
|
||||
self.session_dict[session[0]] = json.loads(session[1])['data']
|
||||
print("[System] 历史记录读取成功喵")
|
||||
gu.log("历史记录读取成功喵")
|
||||
except BaseException as e:
|
||||
print("[System] 历史记录读取失败: " + str(e))
|
||||
gu.log("历史记录读取失败喵", level=gu.LEVEL_ERROR)
|
||||
|
||||
|
||||
# 读取统计信息
|
||||
if not os.path.exists(abs_path+"configs/stat"):
|
||||
@@ -90,7 +90,10 @@ class ProviderOpenAIOfficial(Provider):
|
||||
# 每隔10分钟转储一次
|
||||
time.sleep(10*self.history_dump_interval)
|
||||
|
||||
def text_chat(self, prompt, session_id):
|
||||
def text_chat(self, prompt, session_id = None):
|
||||
if session_id is None:
|
||||
session_id = "unknown"
|
||||
del self.session_dict["unknown"]
|
||||
# 会话机制
|
||||
if session_id not in self.session_dict:
|
||||
self.session_dict[session_id] = []
|
||||
@@ -112,6 +115,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
cache_data_list, new_record, req = self.wrap(prompt, session_id)
|
||||
retry = 0
|
||||
response = None
|
||||
err = ''
|
||||
while retry < 5:
|
||||
try:
|
||||
response = openai.ChatCompletion.create(
|
||||
@@ -120,9 +124,8 @@ class ProviderOpenAIOfficial(Provider):
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str(e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e):
|
||||
print("[System] 当前Key已超额或者不正常,正在切换")
|
||||
gu.log("当前Key已超额或异常, 正在切换", level=gu.LEVEL_WARNING)
|
||||
self.key_stat[openai.api_key]['exceed'] = True
|
||||
self.save_key_record()
|
||||
|
||||
@@ -132,17 +135,23 @@ class ProviderOpenAIOfficial(Provider):
|
||||
raise e
|
||||
else:
|
||||
break
|
||||
if 'maximum context length' in str(e):
|
||||
print("token超限, 清空对应缓存")
|
||||
elif 'maximum context length' in str(e):
|
||||
gu.log("token超限, 清空对应缓存")
|
||||
self.session_dict[session_id] = []
|
||||
cache_data_list, new_record, req = self.wrap(prompt, session_id)
|
||||
elif 'Limit: 3 / min. Please try again in 20s.' in str(e):
|
||||
time.sleep(60)
|
||||
else:
|
||||
gu.log(str(e), level=gu.LEVEL_ERROR)
|
||||
err = str(e)
|
||||
retry+=1
|
||||
if retry >= 5:
|
||||
raise BaseException("连接超时")
|
||||
gu.log(r"如果报错, 且您的机器在中国大陆内, 请确保您的电脑已经设置好代理软件(梯子), 并在配置文件设置了系统代理地址。详见https://github.com/Soulter/QQChannelChatGPT/wiki/%E4%BA%8C%E3%80%81%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E9%85%8D%E7%BD%AE", max_len=999)
|
||||
raise BaseException("连接出错: "+str(err))
|
||||
|
||||
self.key_stat[openai.api_key]['used'] += response['usage']['total_tokens']
|
||||
self.save_key_record()
|
||||
print("[ChatGPT] "+str(response["choices"][0]["message"]["content"]))
|
||||
# print("[ChatGPT] "+str(response["choices"][0]["message"]["content"]))
|
||||
chatgpt_res = str(response["choices"][0]["message"]["content"]).strip()
|
||||
current_usage_tokens = response['usage']['total_tokens']
|
||||
|
||||
@@ -194,13 +203,12 @@ class ProviderOpenAIOfficial(Provider):
|
||||
image_url = []
|
||||
for i in range(img_num):
|
||||
image_url.append(response['data'][i]['url'])
|
||||
print(image_url)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
gu.log(str(e), level=gu.LEVEL_ERROR)
|
||||
if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str(
|
||||
e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e):
|
||||
print("[System] 当前Key已超额或者不正常,正在切换")
|
||||
gu.log("当前Key已超额或者不正常, 正在切换", level=gu.LEVEL_WARNING)
|
||||
self.key_stat[openai.api_key]['exceed'] = True
|
||||
self.save_key_record()
|
||||
|
||||
@@ -302,10 +310,12 @@ class ProviderOpenAIOfficial(Provider):
|
||||
while True:
|
||||
is_all_exceed = True
|
||||
for key in self.key_stat:
|
||||
if key == None:
|
||||
continue
|
||||
if not self.key_stat[key]['exceed']:
|
||||
is_all_exceed = False
|
||||
openai.api_key = key
|
||||
print(f"[System] 切换到Key: {key}, 已使用token: {self.key_stat[key]['used']}")
|
||||
gu.log(f"切换到Key: {key}, 已使用token: {self.key_stat[key]['used']}", level=gu.LEVEL_INFO)
|
||||
if len(req) > 0:
|
||||
try:
|
||||
response = openai.ChatCompletion.create(
|
||||
@@ -314,17 +324,21 @@ class ProviderOpenAIOfficial(Provider):
|
||||
)
|
||||
return response, True
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if 'You exceeded' in str(e):
|
||||
print("[System] 当前Key已超额,正在切换")
|
||||
gu.log("当前Key已超额, 正在切换")
|
||||
self.key_stat[openai.api_key]['exceed'] = True
|
||||
self.save_key_record()
|
||||
time.sleep(1)
|
||||
continue
|
||||
else:
|
||||
gu.log(str(e), level=gu.LEVEL_ERROR)
|
||||
else:
|
||||
return True
|
||||
if is_all_exceed:
|
||||
print("[System] 所有Key已超额")
|
||||
gu.log("所有Key已超额", level=gu.LEVEL_CRITICAL)
|
||||
return None, False
|
||||
else:
|
||||
gu.log("在切换key时程序异常。", level=gu.LEVEL_ERROR)
|
||||
return None, False
|
||||
|
||||
def getConfigs(self):
|
||||
@@ -372,7 +386,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
try:
|
||||
self.key_stat = json.load(keyfile)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
gu.log(str(e), level=gu.LEVEL_ERROR)
|
||||
self.key_stat = {}
|
||||
finally:
|
||||
for key in self.key_list:
|
||||
|
||||
@@ -1,34 +1,120 @@
|
||||
from revChatGPT.V1 import Chatbot
|
||||
from revChatGPT import typings
|
||||
from model.provider.provider import Provider
|
||||
from util import general_utils as gu
|
||||
from util import cmd_config as cc
|
||||
import time
|
||||
|
||||
|
||||
class ProviderRevChatGPT(Provider):
|
||||
def __init__(self, config):
|
||||
if 'password' in config:
|
||||
config['password'] = str(config['password'])
|
||||
self.bot = Chatbot(config=config)
|
||||
self.rev_chatgpt = []
|
||||
self.cc = cc.CmdConfig()
|
||||
for i in range(0, len(config['account'])):
|
||||
try:
|
||||
gu.log(f"创建逆向ChatGPT负载{str(i+1)}中...", level=gu.LEVEL_INFO, tag="RevChatGPT")
|
||||
|
||||
if 'password' in config['account'][i]:
|
||||
gu.log(f"创建逆向ChatGPT负载{str(i+1)}失败: 已不支持账号密码登录,请使用access_token方式登录。", level=gu.LEVEL_ERROR, tag="RevChatGPT")
|
||||
continue
|
||||
rev_account_config = {
|
||||
'access_token': config['account'][i]['access_token'],
|
||||
}
|
||||
if self.cc.get("rev_chatgpt_model") != "":
|
||||
rev_account_config['model'] = self.cc.get("rev_chatgpt_model")
|
||||
if len(self.cc.get("rev_chatgpt_plugin_ids")) > 0:
|
||||
rev_account_config['plugin_ids'] = self.cc.get("rev_chatgpt_plugin_ids")
|
||||
if self.cc.get("rev_chatgpt_PUID") != "":
|
||||
rev_account_config['PUID'] = self.cc.get("rev_chatgpt_PUID")
|
||||
if len(self.cc.get("rev_chatgpt_unverified_plugin_domains")) > 0:
|
||||
rev_account_config['unverified_plugin_domains'] = self.cc.get("rev_chatgpt_unverified_plugin_domains")
|
||||
cb = Chatbot(config=rev_account_config)
|
||||
# cb.captcha_solver = self.__captcha_solver
|
||||
revstat = {
|
||||
'obj': cb,
|
||||
'busy': False
|
||||
}
|
||||
self.rev_chatgpt.append(revstat)
|
||||
except BaseException as e:
|
||||
gu.log(f"创建逆向ChatGPT负载{str(i+1)}失败: {str(e)}", level=gu.LEVEL_ERROR, tag="RevChatGPT")
|
||||
|
||||
def forget(self) -> bool:
|
||||
self.bot.reset_chat()
|
||||
return True
|
||||
|
||||
def text_chat(self, prompt):
|
||||
return False
|
||||
|
||||
# def __captcha_solver(images: list[str], challenge_details: dict) -> int:
|
||||
# # Create tempfile
|
||||
# print("Captcha solver called")
|
||||
# print(images)
|
||||
# print(challenge_details)
|
||||
# input("Press Enter to continue...")
|
||||
# return 0
|
||||
|
||||
def request_text(self, prompt: str, bot) -> str:
|
||||
resp = ''
|
||||
err_count = 0
|
||||
retry_count = 5
|
||||
|
||||
while err_count < retry_count:
|
||||
try:
|
||||
for data in self.bot.ask(prompt):
|
||||
for data in bot.ask(prompt):
|
||||
resp = data["message"]
|
||||
break
|
||||
except typings.Error as e:
|
||||
if e.code == typings.ErrorType.INVALID_ACCESS_TOKEN_ERROR:
|
||||
raise e
|
||||
if e.code == typings.ErrorType.EXPIRED_ACCESS_TOKEN_ERROR:
|
||||
raise e
|
||||
if e.code == typings.ErrorType.PROHIBITED_CONCURRENT_QUERY_ERROR:
|
||||
raise e
|
||||
|
||||
if "The message you submitted was too long" in str(e):
|
||||
raise e
|
||||
if "You've reached our limit of messages per hour." in str(e):
|
||||
raise e
|
||||
if "Rate limited by proxy" in str(e):
|
||||
gu.log(f"触发请求频率限制, 60秒后自动重试。", level=gu.LEVEL_WARNING, tag="RevChatGPT")
|
||||
time.sleep(60)
|
||||
|
||||
err_count += 1
|
||||
gu.log(f"请求异常: {str(e)},正在重试。({str(err_count)})", level=gu.LEVEL_WARNING, tag="RevChatGPT")
|
||||
if err_count >= retry_count:
|
||||
raise e
|
||||
except BaseException as e:
|
||||
try:
|
||||
print("[RevChatGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count))
|
||||
err_count += 1
|
||||
if err_count >= retry_count:
|
||||
raise e
|
||||
except BaseException:
|
||||
err_count += 1
|
||||
err_count += 1
|
||||
gu.log(f"请求异常: {str(e)},正在重试。({str(err_count)})", level=gu.LEVEL_WARNING, tag="RevChatGPT")
|
||||
if err_count >= retry_count:
|
||||
raise e
|
||||
if resp == '':
|
||||
resp = "RevChatGPT请求异常。"
|
||||
|
||||
print("[RevChatGPT] "+str(resp))
|
||||
return resp
|
||||
# print("[RevChatGPT] "+str(resp))
|
||||
return resp
|
||||
|
||||
def text_chat(self, prompt) -> str:
|
||||
res = ''
|
||||
err_msg = ''
|
||||
cursor = 0
|
||||
for revstat in self.rev_chatgpt:
|
||||
cursor += 1
|
||||
if not revstat['busy']:
|
||||
try:
|
||||
revstat['busy'] = True
|
||||
res = self.request_text(prompt, revstat['obj'])
|
||||
revstat['busy'] = False
|
||||
return res.strip()
|
||||
# todo: 细化错误管理
|
||||
except BaseException as e:
|
||||
revstat['busy'] = False
|
||||
gu.log(f"请求出现问题: {str(e)}", level=gu.LEVEL_WARNING, tag="RevChatGPT")
|
||||
err_msg += f"账号{cursor} - 错误原因: {str(e)}"
|
||||
continue
|
||||
else:
|
||||
err_msg += f"账号{cursor} - 错误原因: 忙碌"
|
||||
continue
|
||||
raise Exception(f'回复失败。错误跟踪:{err_msg}')
|
||||
|
||||
def is_all_busy(self) -> bool:
|
||||
for revstat in self.rev_chatgpt:
|
||||
if not revstat['busy']:
|
||||
return False
|
||||
return True
|
||||
@@ -1,7 +1,10 @@
|
||||
import asyncio
|
||||
from model.provider.provider import Provider
|
||||
from EdgeGPT import Chatbot, ConversationStyle
|
||||
import json
|
||||
import os
|
||||
from util import general_utils as gu
|
||||
from util.cmd_config import CmdConfig as cc
|
||||
|
||||
|
||||
class ProviderRevEdgeGPT(Provider):
|
||||
def __init__(self):
|
||||
@@ -9,7 +12,10 @@ class ProviderRevEdgeGPT(Provider):
|
||||
self.wait_stack = []
|
||||
with open('./cookies.json', 'r') as f:
|
||||
cookies = json.load(f)
|
||||
self.bot = Chatbot(cookies=cookies)
|
||||
proxy = cc.get("bing_proxy", None)
|
||||
if proxy == "":
|
||||
proxy = None
|
||||
self.bot = Chatbot(cookies=cookies, proxy = proxy)
|
||||
|
||||
def is_busy(self):
|
||||
return self.busy
|
||||
@@ -21,7 +27,7 @@ class ProviderRevEdgeGPT(Provider):
|
||||
except BaseException:
|
||||
return False
|
||||
|
||||
async def text_chat(self, prompt):
|
||||
async def text_chat(self, prompt, platform = 'none'):
|
||||
if self.busy:
|
||||
return
|
||||
self.busy = True
|
||||
@@ -32,18 +38,59 @@ class ProviderRevEdgeGPT(Provider):
|
||||
while err_count < retry_count:
|
||||
try:
|
||||
resp = await self.bot.ask(prompt=prompt, conversation_style=ConversationStyle.creative)
|
||||
resp = resp['item']['messages'][len(resp['item']['messages'])-1]['text']
|
||||
if resp == prompt:
|
||||
resp += '\n\n如果你没有让我复述你的话,那代表我可能不想和你继续这个话题了,请输入reset重置会话😶'
|
||||
# print("[RevEdgeGPT] "+str(resp))
|
||||
if 'messages' not in resp['item']:
|
||||
await self.bot.reset()
|
||||
msj_obj = resp['item']['messages'][len(resp['item']['messages'])-1]
|
||||
reply_msg = msj_obj['text']
|
||||
if 'sourceAttributions' in msj_obj:
|
||||
reply_source = msj_obj['sourceAttributions']
|
||||
else:
|
||||
reply_source = []
|
||||
if 'throttling' in resp['item']:
|
||||
throttling = resp['item']['throttling']
|
||||
# print(throttling)
|
||||
else:
|
||||
throttling = None
|
||||
if 'I\'m sorry but I prefer not to continue this conversation. I\'m still learning so I appreciate your understanding and patience.' in reply_msg:
|
||||
self.busy = False
|
||||
return '', 0
|
||||
if reply_msg == prompt:
|
||||
# resp += '\n\n如果你没有让我复述你的话,那代表我可能不想和你继续这个话题了,请输入reset重置会话😶'
|
||||
await self.forget()
|
||||
err_count += 1
|
||||
continue
|
||||
if reply_source is None:
|
||||
# 不想答复
|
||||
return '', 0
|
||||
else:
|
||||
if platform != 'qqchan':
|
||||
index = 1
|
||||
if len(reply_source) > 0:
|
||||
reply_msg += "\n\n信息来源:\n"
|
||||
for i in reply_source:
|
||||
reply_msg += f"[{str(index)}]: {i['seeMoreUrl']} | {i['providerDisplayName']}\n"
|
||||
index += 1
|
||||
if throttling is not None:
|
||||
if throttling['numUserMessagesInConversation'] == throttling['maxNumUserMessagesInConversation']:
|
||||
# 达到上限,重置会话
|
||||
await self.forget()
|
||||
if throttling['numUserMessagesInConversation'] > throttling['maxNumUserMessagesInConversation']:
|
||||
await self.forget()
|
||||
err_count += 1
|
||||
continue
|
||||
reply_msg += f"\n[{throttling['numUserMessagesInConversation']}/{throttling['maxNumUserMessagesInConversation']}]"
|
||||
break
|
||||
except BaseException as e:
|
||||
print(e.with_traceback)
|
||||
gu.log(str(e), level=gu.LEVEL_WARNING, tag="RevEdgeGPT")
|
||||
err_count += 1
|
||||
if err_count >= retry_count:
|
||||
gu.log(r"如果报错, 且您的机器在中国大陆内, 请确保您的电脑已经设置好代理软件(梯子), 并在配置文件设置了系统代理地址。详见https://github.com/Soulter/QQChannelChatGPT/wiki/%E4%BA%8C%E3%80%81%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E9%85%8D%E7%BD%AE", max_len=999)
|
||||
self.busy = False
|
||||
raise e
|
||||
print("[RevEdgeGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count))
|
||||
gu.log("请求出现了一些问题, 正在重试。次数"+str(err_count), level=gu.LEVEL_WARNING, tag="RevEdgeGPT")
|
||||
self.busy = False
|
||||
|
||||
print("[RevEdgeGPT] "+str(resp))
|
||||
return resp
|
||||
# print("[RevEdgeGPT] "+str(reply_msg))
|
||||
return reply_msg, 1
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
requests
|
||||
openai
|
||||
qq-botpy
|
||||
revChatGPT~=4.0.8
|
||||
baidu-aip
|
||||
EdgeGPT~=0.1.2
|
||||
chardet
|
||||
Pillow
|
||||
GitPython
|
||||
pydantic~=1.10.4
|
||||
requests~=2.28.1
|
||||
openai~=0.27.4
|
||||
qq-botpy~=1.1.2
|
||||
revChatGPT~=6.8.6
|
||||
baidu-aip~=4.16.9
|
||||
EdgeGPT~=0.1.22.1
|
||||
chardet~=5.1.0
|
||||
Pillow~=9.4.0
|
||||
GitPython~=3.1.31
|
||||
nakuru-project
|
||||
|
||||
BIN
resources/fonts/simhei.ttf
Normal file
BIN
resources/fonts/simhei.ttf
Normal file
Binary file not shown.
BIN
resources/fonts/syst.otf
Normal file
BIN
resources/fonts/syst.otf
Normal file
Binary file not shown.
53
util/cmd_config.py
Normal file
53
util/cmd_config.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
cpath = "cmd_config.json"
|
||||
|
||||
def check_exist():
|
||||
if not os.path.exists(cpath):
|
||||
with open(cpath, "w", encoding="utf-8") as f:
|
||||
json.dump({}, f, indent=4)
|
||||
f.flush()
|
||||
|
||||
class CmdConfig():
|
||||
|
||||
@staticmethod
|
||||
def get(key, default=None):
|
||||
check_exist()
|
||||
with open(cpath, "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
if key in d:
|
||||
return d[key]
|
||||
else:
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def get_all():
|
||||
check_exist()
|
||||
with open(cpath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
@staticmethod
|
||||
def put(key, value):
|
||||
check_exist()
|
||||
with open(cpath, "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
d[key] = value
|
||||
with open(cpath, "w", encoding="utf-8") as f:
|
||||
json.dump(d, f, indent=4)
|
||||
f.flush()
|
||||
|
||||
@staticmethod
|
||||
def init_attributes(keys: list, init_val = ""):
|
||||
check_exist()
|
||||
with open(cpath, "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
_tag = False
|
||||
for k in keys:
|
||||
if k not in d:
|
||||
d[k] = init_val
|
||||
_tag = True
|
||||
if _tag:
|
||||
with open(cpath, "w", encoding="utf-8") as f:
|
||||
json.dump(d, f, indent=4)
|
||||
f.flush()
|
||||
214
util/func_call.py
Normal file
214
util/func_call.py
Normal file
@@ -0,0 +1,214 @@
|
||||
|
||||
import json
|
||||
import util.general_utils as gu
|
||||
|
||||
class FuncCallJsonFormatError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
class FuncNotFoundError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
class FuncCall():
|
||||
def __init__(self, provider) -> None:
|
||||
self.func_list = []
|
||||
self.provider = provider
|
||||
|
||||
def add_func(self, name: str = None, func_args: list = None, desc: str = None, func_obj = None) -> None:
|
||||
if name == None or func_args == None or desc == None or func_obj == None:
|
||||
raise FuncCallJsonFormatError("name, func_args, desc must be provided.")
|
||||
self._func = {
|
||||
"name": name,
|
||||
"args": func_args,
|
||||
"description": desc,
|
||||
"func_obj": func_obj,
|
||||
}
|
||||
self.func_list.append(self._func)
|
||||
|
||||
def func_dump(self, intent: int = 2) -> str:
|
||||
_l = []
|
||||
for f in self.func_list:
|
||||
_l.append({
|
||||
"name": f["name"],
|
||||
"args": f["args"],
|
||||
"description": f["description"],
|
||||
})
|
||||
|
||||
return json.dumps(_l, indent=intent, ensure_ascii=False)
|
||||
|
||||
def func_call(self, question, func_definition, is_task = False, tasks = None, taskindex = -1, is_summary = True):
|
||||
|
||||
funccall_prompt = """
|
||||
我正在实现function call功能,该功能旨在让你变成给定的问题到给定的函数的解析器(这意味着你不是创造函数)。
|
||||
下面会给你提供可能会用到函数的相关信息,和一个问题,你需要将其转换成给定的函数调用。
|
||||
- 你的返回信息只含json,且严格仿照以下内容(不含注释):
|
||||
```
|
||||
{
|
||||
"res": string // 如果没有找到对应的函数,那么你可以在这里正常输出内容。如果有,这里是空字符串。
|
||||
"func_call": [ // 这是一个数组,里面包含了所有的函数调用,如果没有函数调用,那么这个数组是空数组。
|
||||
{
|
||||
"res": string // 如果没有找到对应的函数,那么你可以在这里正常输出内容。如果有,这里是空字符串。
|
||||
"name": str, // 函数的名字
|
||||
"args_type": {
|
||||
"arg1": str, // 函数的参数的类型
|
||||
"arg2": str,
|
||||
...
|
||||
},
|
||||
"args": {
|
||||
"arg1": any, // 函数的参数
|
||||
"arg2": any,
|
||||
...
|
||||
}
|
||||
},
|
||||
... // 可能在这个问题中会有多个函数调用
|
||||
],
|
||||
}
|
||||
```
|
||||
- 如果用户的要求较复杂,允许返回多个函数调用,但需保证这些函数调用的顺序正确。
|
||||
- 当问题没有提到给定的函数时,相当于提问方不打算使用function call功能,这时你可以在res中正常输出这个问题的回答(以AI的身份正常回答该问题,并将答案输出在res字段中,回答不要涉及到任何函数调用的内容,就只是正常讨论这个问题。)
|
||||
|
||||
提供的函数是:
|
||||
|
||||
"""
|
||||
|
||||
prompt = f"{funccall_prompt}\n```\n{func_definition}\n```\n"
|
||||
prompt += f"""
|
||||
用户的提问是:
|
||||
```
|
||||
{question}
|
||||
```
|
||||
"""
|
||||
|
||||
# if is_task:
|
||||
# # task_prompt = f"\n任务列表为{str(tasks)}\n你目前进行到了任务{str(taskindex)}, **你不需要重新进行已经进行过的任务, 不要生成已经进行过的**"
|
||||
# prompt += task_prompt
|
||||
|
||||
# provider.forget()
|
||||
|
||||
_c = 0
|
||||
while _c < 3:
|
||||
try:
|
||||
res = self.provider.text_chat(prompt)
|
||||
if res.find('```') != -1:
|
||||
res = res[res.find('```json') + 7: res.rfind('```')]
|
||||
gu.log("REVGPT func_call json result", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
|
||||
print(res)
|
||||
res = json.loads(res)
|
||||
break
|
||||
except Exception as e:
|
||||
_c += 1
|
||||
if _c == 3:
|
||||
raise e
|
||||
if "The message you submitted was too long" in str(e):
|
||||
raise e
|
||||
|
||||
invoke_func_res = ""
|
||||
|
||||
if len(res["func_call"]) > 0:
|
||||
task_list = res["func_call"]
|
||||
|
||||
invoke_func_res_list = []
|
||||
|
||||
for res in task_list:
|
||||
# 说明有函数调用
|
||||
func_name = res["name"]
|
||||
# args_type = res["args_type"]
|
||||
args = res["args"]
|
||||
# 调用函数
|
||||
# func = eval(func_name)
|
||||
func_target = None
|
||||
for func in self.func_list:
|
||||
if func["name"] == func_name:
|
||||
func_target = func["func_obj"]
|
||||
break
|
||||
if func_target == None:
|
||||
raise FuncNotFoundError(f"Request function {func_name} not found.")
|
||||
t_res = str(func_target(**args))
|
||||
invoke_func_res += f"{func_name} 调用结果:\n```\n{t_res}\n```\n"
|
||||
invoke_func_res_list.append(invoke_func_res)
|
||||
gu.log(f"[FUNC| {func_name} invoked]", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
|
||||
# print(str(t_res))
|
||||
|
||||
if is_summary:
|
||||
|
||||
# 生成返回结果
|
||||
after_prompt = """
|
||||
函数返回以下内容:"""+invoke_func_res+"""
|
||||
请以AI助手的身份结合返回的内容对用户提问做详细全面的回答。
|
||||
用户的提问是:
|
||||
```""" + question + """```
|
||||
- 在res字段中,不要输出函数的返回值,也不要针对返回值的字段进行分析,也不要输出用户的提问,而是理解这一段返回的结果,并以AI助手的身份回答问题,只需要输出回答的内容,不需要在回答的前面加上身份词。
|
||||
- 你的返回信息必须只能是json,且需严格遵循以下内容(不含注释):
|
||||
```json
|
||||
{
|
||||
"res": string, // 回答的内容
|
||||
"func_call_again": bool // 如果函数返回的结果有错误或者问题,可将其设置为true,否则为false
|
||||
}
|
||||
```
|
||||
- 如果func_call_again为true,res请你设为空值,否则请你填写回答的内容。"""
|
||||
|
||||
_c = 0
|
||||
while _c < 5:
|
||||
try:
|
||||
res = self.provider.text_chat(after_prompt)
|
||||
# 截取```之间的内容
|
||||
gu.log("DEBUG BEGIN", bg=gu.BG_COLORS["yellow"], fg=gu.FG_COLORS["white"])
|
||||
print(res)
|
||||
gu.log("DEBUG END", bg=gu.BG_COLORS["yellow"], fg=gu.FG_COLORS["white"])
|
||||
if res.find('```') != -1:
|
||||
res = res[res.find('```json') + 7: res.rfind('```')]
|
||||
gu.log("REVGPT after_func_call json result", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
|
||||
after_prompt_res = res
|
||||
after_prompt_res = json.loads(after_prompt_res)
|
||||
break
|
||||
except Exception as e:
|
||||
_c += 1
|
||||
if _c == 5:
|
||||
raise e
|
||||
if "The message you submitted was too long" in str(e):
|
||||
# 如果返回的内容太长了,那么就截取一部分
|
||||
invoke_func_res = invoke_func_res[:int(len(invoke_func_res) / 2)]
|
||||
after_prompt = """
|
||||
函数返回以下内容:"""+invoke_func_res+"""
|
||||
请以AI助手的身份结合返回的内容对用户提问做详细全面的回答。
|
||||
用户的提问是:
|
||||
```""" + question + """```
|
||||
- 在res字段中,不要输出函数的返回值,也不要针对返回值的字段进行分析,也不要输出用户的提问,而是理解这一段返回的结果,并以AI助手的身份回答问题,只需要输出回答的内容,不需要在回答的前面加上身份词。
|
||||
- 你的返回信息必须只能是json,且需严格遵循以下内容(不含注释):
|
||||
```json
|
||||
{
|
||||
"res": string, // 回答的内容
|
||||
"func_call_again": bool // 如果函数返回的结果有错误或者问题,可将其设置为true,否则为false
|
||||
}
|
||||
```
|
||||
- 如果func_call_again为true,res请你设为空值,否则请你填写回答的内容。"""
|
||||
else:
|
||||
raise e
|
||||
|
||||
if "func_call_again" in after_prompt_res and after_prompt_res["func_call_again"]:
|
||||
# 如果需要重新调用函数
|
||||
# 重新调用函数
|
||||
gu.log("REVGPT func_call_again", bg=gu.BG_COLORS["purple"], fg=gu.FG_COLORS["white"])
|
||||
res = self.func_call(question, func_definition)
|
||||
return res, True
|
||||
|
||||
gu.log("REVGPT func callback:", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
|
||||
# print(after_prompt_res["res"])
|
||||
return after_prompt_res["res"], True
|
||||
else:
|
||||
return str(invoke_func_res_list), True
|
||||
else:
|
||||
# print(res["res"])
|
||||
return res["res"], False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
493
util/general_utils.py
Normal file
493
util/general_utils.py
Normal file
@@ -0,0 +1,493 @@
|
||||
import datetime
|
||||
import time
|
||||
import socket
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
|
||||
PLATFORM_GOCQ = 'gocq'
|
||||
PLATFORM_QQCHAN = 'qqchan'
|
||||
|
||||
FG_COLORS = {
|
||||
"black": "30",
|
||||
"red": "31",
|
||||
"green": "32",
|
||||
"yellow": "33",
|
||||
"blue": "34",
|
||||
"purple": "35",
|
||||
"cyan": "36",
|
||||
"white": "37",
|
||||
"default": "39",
|
||||
}
|
||||
|
||||
BG_COLORS = {
|
||||
"black": "40",
|
||||
"red": "41",
|
||||
"green": "42",
|
||||
"yellow": "43",
|
||||
"blue": "44",
|
||||
"purple": "45",
|
||||
"cyan": "46",
|
||||
"white": "47",
|
||||
"default": "49",
|
||||
}
|
||||
|
||||
LEVEL_INFO = "INFO"
|
||||
LEVEL_WARNING = "WARNING"
|
||||
LEVEL_ERROR = "ERROR"
|
||||
LEVEL_CRITICAL = "CRITICAL"
|
||||
|
||||
level_colors = {
|
||||
"INFO": "green",
|
||||
"WARNING": "yellow",
|
||||
"ERROR": "red",
|
||||
"CRITICAL": "purple",
|
||||
}
|
||||
|
||||
def log(
|
||||
msg: str,
|
||||
level: str = "INFO",
|
||||
tag: str = "System",
|
||||
fg: str = None,
|
||||
bg: str = None,
|
||||
max_len: int = 300):
|
||||
"""
|
||||
日志记录函数
|
||||
"""
|
||||
if len(msg) > max_len:
|
||||
msg = msg[:max_len] + "..."
|
||||
now = datetime.datetime.now().strftime("%m-%d %H:%M:%S")
|
||||
pre = f"[{now}] [{level}] [{tag}]: {msg}"
|
||||
if level == "INFO":
|
||||
if fg is None:
|
||||
fg = FG_COLORS["green"]
|
||||
if bg is None:
|
||||
bg = BG_COLORS["default"]
|
||||
elif level == "WARNING":
|
||||
if fg is None:
|
||||
fg = FG_COLORS["yellow"]
|
||||
if bg is None:
|
||||
bg = BG_COLORS["default"]
|
||||
elif level == "ERROR":
|
||||
if fg is None:
|
||||
fg = FG_COLORS["red"]
|
||||
if bg is None:
|
||||
bg = BG_COLORS["default"]
|
||||
elif level == "CRITICAL":
|
||||
if fg is None:
|
||||
fg = FG_COLORS["purple"]
|
||||
if bg is None:
|
||||
bg = BG_COLORS["default"]
|
||||
|
||||
print(f"\033[{fg};{bg}m{pre}\033[0m")
|
||||
|
||||
|
||||
def port_checker(port: int, host: str = "localhost"):
|
||||
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
sk.settimeout(1)
|
||||
try:
|
||||
sk.connect((host, port))
|
||||
sk.close()
|
||||
return True
|
||||
except Exception:
|
||||
sk.close()
|
||||
return False
|
||||
|
||||
def word2img(title: str, text: str, max_width=30, font_size=20):
|
||||
if os.path.exists("resources/fonts/syst.otf"):
|
||||
font_path = "resources/fonts/syst.otf"
|
||||
elif os.path.exists("QQChannelChatGPT/resources/fonts/syst.otf"):
|
||||
font_path = "QQChannelChatGPT/resources/fonts/syst.otf"
|
||||
elif os.path.exists("C:/Windows/Fonts/simhei.ttf"):
|
||||
font_path = "C:/Windows/Fonts/simhei.ttf"
|
||||
elif os.path.exists("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"):
|
||||
font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
|
||||
else:
|
||||
raise Exception("找不到字体文件")
|
||||
|
||||
width_factor = 1.0
|
||||
height_factor = 1.5
|
||||
# 格式化文本宽度最大为30
|
||||
lines = text.split('\n')
|
||||
i = 0
|
||||
length = len(lines)
|
||||
for l in lines:
|
||||
if len(l) > max_width:
|
||||
# lines[i] = l[:max_width] + '\n' + l[max_width:]
|
||||
# for
|
||||
cp = l
|
||||
for ii in range(len(l)):
|
||||
if ii % max_width == 0:
|
||||
cp = cp[:ii] + '\n' + cp[ii:]
|
||||
length += 1
|
||||
lines[i] = cp
|
||||
i += 1
|
||||
text = '\n'.join(lines)
|
||||
width = int(max_width * font_size * width_factor)
|
||||
height = int(length * font_size * height_factor)
|
||||
image = Image.new('RGB', (width, height), (255, 255, 255))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
text_font = ImageFont.truetype(font_path, font_size)
|
||||
title_font = ImageFont.truetype(font_path, font_size + 5)
|
||||
# 标题居中
|
||||
title_width, title_height = title_font.getsize(title)
|
||||
draw.text(((width - title_width) / 2, 10), title, fill=(0, 0, 0), font=title_font)
|
||||
# 文本不居中
|
||||
draw.text((10, title_height+20), text, fill=(0, 0, 0), font=text_font)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def render_markdown(markdown_text, image_width=800, image_height=600, font_size=26, font_color=(0, 0, 0), bg_color=(255, 255, 255)):
|
||||
|
||||
HEADER_MARGIN = 20
|
||||
HEADER_FONT_STANDARD_SIZE = 42
|
||||
|
||||
QUOTE_LEFT_LINE_MARGIN = 10
|
||||
QUOTE_FONT_LINE_MARGIN = 6 # 引用文字距离左边线的距离和上下的距离
|
||||
QUOTE_LEFT_LINE_HEIGHT = font_size + QUOTE_FONT_LINE_MARGIN * 2
|
||||
QUOTE_LEFT_LINE_WIDTH = 5
|
||||
QUOTE_LEFT_LINE_COLOR = (180, 180, 180)
|
||||
QUOTE_FONT_SIZE = font_size
|
||||
QUOTE_FONT_COLOR = (180, 180, 180)
|
||||
# QUOTE_BG_COLOR = (255, 255, 255)
|
||||
|
||||
CODE_BLOCK_MARGIN = 10
|
||||
CODE_BLOCK_FONT_SIZE = font_size
|
||||
CODE_BLOCK_FONT_COLOR = (255, 255, 255)
|
||||
CODE_BLOCK_BG_COLOR = (240, 240, 240)
|
||||
CODE_BLOCK_CODES_MARGIN_VERTICAL = 5 # 代码块和代码之间的距离
|
||||
CODE_BLOCK_CODES_MARGIN_HORIZONTAL = 5 # 代码块和代码之间的距离
|
||||
CODE_BLOCK_TEXT_MARGIN = 4 # 代码和代码之间的距离
|
||||
|
||||
INLINE_CODE_MARGIN = 8
|
||||
INLINE_CODE_FONT_SIZE = font_size
|
||||
INLINE_CODE_FONT_COLOR = font_color
|
||||
INLINE_CODE_FONT_MARGIN = 4
|
||||
INLINE_CODE_BG_COLOR = (230, 230, 230)
|
||||
INLINE_CODE_BG_HEIGHT = INLINE_CODE_FONT_SIZE + INLINE_CODE_FONT_MARGIN * 2
|
||||
|
||||
LIST_MARGIN = 8
|
||||
LIST_FONT_SIZE = font_size
|
||||
LIST_FONT_COLOR = font_color
|
||||
|
||||
TEXT_LINE_MARGIN = 8
|
||||
|
||||
IMAGE_MARGIN = 15
|
||||
# 用于匹配图片的正则表达式
|
||||
IMAGE_REGEX = r"!\s*\[.*?\]\s*\((.*?)\)"
|
||||
|
||||
|
||||
if os.path.exists("resources/fonts/syst.otf"):
|
||||
font_path = "resources/fonts/syst.otf"
|
||||
elif os.path.exists("QQChannelChatGPT/resources/fonts/syst.otf"):
|
||||
font_path = "QQChannelChatGPT/resources/fonts/syst.otf"
|
||||
elif os.path.exists("C:/Windows/Fonts/simhei.ttf"):
|
||||
font_path = "C:/Windows/Fonts/simhei.ttf"
|
||||
elif os.path.exists("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"):
|
||||
font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
|
||||
else:
|
||||
raise Exception("找不到字体文件")
|
||||
# backup
|
||||
if os.path.exists("resources/fonts/simhei.ttf"):
|
||||
font_path1 = "resources/fonts/simhei.ttf"
|
||||
elif os.path.exists("QQChannelChatGPT/resources/fonts/simhei.ttf"):
|
||||
font_path1 = "QQChannelChatGPT/resources/fonts/simhei.ttf"
|
||||
else:
|
||||
font_path1 = font_path
|
||||
# 加载字体
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
|
||||
images: Image = {}
|
||||
|
||||
# pre_process, get height of each line
|
||||
pre_lines = markdown_text.split('\n')
|
||||
height = 0
|
||||
pre_in_code = False
|
||||
i = -1
|
||||
_pre_lines = []
|
||||
for line in pre_lines:
|
||||
i += 1
|
||||
# 处理图片
|
||||
if re.search(IMAGE_REGEX, line):
|
||||
try:
|
||||
image_url = re.findall(IMAGE_REGEX, line)[0]
|
||||
print(image_url)
|
||||
image_res = Image.open(requests.get(image_url, stream=True, timeout=5).raw)
|
||||
images[i] = image_res
|
||||
# 最大不得超过image_width的50%
|
||||
img_height = image_res.size[1]
|
||||
|
||||
if image_res.size[0] > image_width*0.5:
|
||||
image_res = image_res.resize((int(image_width*0.5), int(image_res.size[1]*image_width*0.5/image_res.size[0])))
|
||||
img_height = image_res.size[1]
|
||||
|
||||
height += img_height + IMAGE_MARGIN*2
|
||||
|
||||
line = re.sub(IMAGE_REGEX, "", line)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
line = re.sub(IMAGE_REGEX, "\n[加载失败的图片]\n", line)
|
||||
continue
|
||||
|
||||
line.replace("\t", " ")
|
||||
if font.getsize(line)[0] > image_width:
|
||||
cp = line
|
||||
_width = 0
|
||||
_word_cnt = 0
|
||||
for ii in range(len(line)):
|
||||
# 检测是否是中文
|
||||
_width += font.getsize(line[ii])[0]
|
||||
_word_cnt+=1
|
||||
if _width > image_width:
|
||||
_pre_lines.append(cp[:_word_cnt])
|
||||
cp = cp[_word_cnt:]
|
||||
_word_cnt=0
|
||||
_width=0
|
||||
_pre_lines.append(cp)
|
||||
else:
|
||||
_pre_lines.append(line)
|
||||
pre_lines = _pre_lines
|
||||
|
||||
i=-1
|
||||
for line in pre_lines:
|
||||
if line == "":
|
||||
height += TEXT_LINE_MARGIN
|
||||
continue
|
||||
i += 1
|
||||
line = line.strip()
|
||||
if pre_in_code and not line.startswith("```"):
|
||||
height += font_size + CODE_BLOCK_TEXT_MARGIN
|
||||
# pre_codes.append(line)
|
||||
continue
|
||||
if line.startswith("#"):
|
||||
header_level = line.count("#")
|
||||
height += HEADER_FONT_STANDARD_SIZE + HEADER_MARGIN*2 - header_level * 4
|
||||
elif line.startswith("-"):
|
||||
height += font_size+LIST_MARGIN*2
|
||||
elif line.startswith(">"):
|
||||
height += font_size+QUOTE_LEFT_LINE_MARGIN*2
|
||||
elif line.startswith("```"):
|
||||
if pre_in_code:
|
||||
pre_in_code = False
|
||||
# pre_codes = []
|
||||
height += CODE_BLOCK_MARGIN
|
||||
else:
|
||||
pre_in_code = True
|
||||
height += CODE_BLOCK_MARGIN
|
||||
elif re.search(r"`(.*?)`", line):
|
||||
height += font_size+INLINE_CODE_FONT_MARGIN*2+INLINE_CODE_MARGIN*2
|
||||
else:
|
||||
height += font_size + TEXT_LINE_MARGIN*2
|
||||
|
||||
markdown_text = '\n'.join(pre_lines)
|
||||
print("Pre process done, height: ", height)
|
||||
image_height = height
|
||||
if image_height < 100:
|
||||
image_height = 100
|
||||
image_width += 20
|
||||
|
||||
# 创建空白图像
|
||||
image = Image.new('RGB', (image_width, image_height), bg_color)
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
|
||||
# # get all the emojis unicode in the markdown text
|
||||
# unicode_text = markdown_text.encode('unicode_escape').decode()
|
||||
# # print(unicode_text)
|
||||
# unicode_emojis = re.findall(r'\\U\w{8}', unicode_text)
|
||||
# emoji_base_url = "https://abs.twimg.com/emoji/v1/72x72/{unicode_emoji}.png"
|
||||
|
||||
# 设置初始位置
|
||||
x, y = 10, 10
|
||||
|
||||
# 解析Markdown文本
|
||||
lines = markdown_text.split("\n")
|
||||
# lines = pre_lines
|
||||
|
||||
in_code_block = False
|
||||
code_block_start_y = 0
|
||||
code_block_codes = []
|
||||
|
||||
index = -1
|
||||
for line in lines:
|
||||
index += 1
|
||||
if in_code_block and not line.startswith("```"):
|
||||
code_block_codes.append(line)
|
||||
y += font_size + CODE_BLOCK_TEXT_MARGIN
|
||||
continue
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith("#"):
|
||||
# unicode_emojis = re.findall(r'\\U0001\w{4}', line)
|
||||
# for unicode_emoji in unicode_emojis:
|
||||
# line = line.replace(unicode_emoji, "")
|
||||
# unicode_emoji = ""
|
||||
# if len(unicode_emojis) > 0:
|
||||
# unicode_emoji = unicode_emojis[0]
|
||||
|
||||
# 处理标题
|
||||
header_level = line.count("#")
|
||||
line = line.strip("#").strip()
|
||||
font_size_header = HEADER_FONT_STANDARD_SIZE - header_level * 4
|
||||
|
||||
# if unicode_emoji != "":
|
||||
# emoji_url = emoji_base_url.format(unicode_emoji=unicode_emoji[-5:])
|
||||
# emoji = Image.open(requests.get(emoji_url, stream=True).raw)
|
||||
# emoji = emoji.resize((font_size, font_size))
|
||||
# image.paste(emoji, (x, y))
|
||||
# x += font_size
|
||||
|
||||
font = ImageFont.truetype(font_path, font_size_header)
|
||||
y += HEADER_MARGIN # 上边距
|
||||
# 字间距
|
||||
draw.text((x, y), line, font=font, fill=font_color)
|
||||
draw.line((x, y + font_size_header + 8, image_width - 10, y + font_size_header + 8), fill=(230, 230, 230), width=3)
|
||||
y += font_size_header + HEADER_MARGIN
|
||||
|
||||
elif line.startswith(">"):
|
||||
# 处理引用
|
||||
quote_text = line.strip(">")
|
||||
# quote_width = image_width - 20 # 引用框的宽度为图像宽度减去左右边距
|
||||
# quote_height = font_size + 10 # 引用框的高度为字体大小加上上下边距
|
||||
# quote_box = (x, y, x + quote_width, y + quote_height)
|
||||
# draw.rounded_rectangle(quote_box, radius=5, fill=(230, 230, 230), width=2) # 使用灰色填充矩形框作为引用背景
|
||||
y+=QUOTE_LEFT_LINE_MARGIN
|
||||
draw.line((x, y, x, y + QUOTE_LEFT_LINE_HEIGHT), fill=QUOTE_LEFT_LINE_COLOR, width=QUOTE_LEFT_LINE_WIDTH)
|
||||
font = ImageFont.truetype(font_path, QUOTE_FONT_SIZE)
|
||||
draw.text((x + QUOTE_FONT_LINE_MARGIN, y + QUOTE_FONT_LINE_MARGIN), quote_text, font=font, fill=QUOTE_FONT_COLOR)
|
||||
y += font_size + QUOTE_LEFT_LINE_HEIGHT + QUOTE_LEFT_LINE_MARGIN
|
||||
|
||||
elif line.startswith("-"):
|
||||
# 处理列表
|
||||
list_text = line.strip("-").strip()
|
||||
font = ImageFont.truetype(font_path, LIST_FONT_SIZE)
|
||||
y += LIST_MARGIN
|
||||
draw.text((x, y), " · " + list_text, font=font, fill=LIST_FONT_COLOR)
|
||||
y += font_size + LIST_MARGIN
|
||||
|
||||
elif line.startswith("```"):
|
||||
if not in_code_block:
|
||||
code_block_start_y = y+CODE_BLOCK_MARGIN
|
||||
in_code_block = True
|
||||
else:
|
||||
# print(code_block_codes)
|
||||
in_code_block = False
|
||||
codes = "\n".join(code_block_codes)
|
||||
code_block_codes = []
|
||||
draw.rounded_rectangle((x, code_block_start_y, image_width - 10, y+CODE_BLOCK_CODES_MARGIN_VERTICAL + CODE_BLOCK_TEXT_MARGIN), radius=5, fill=CODE_BLOCK_BG_COLOR, width=2)
|
||||
font = ImageFont.truetype(font_path1, CODE_BLOCK_FONT_SIZE)
|
||||
draw.text((x + CODE_BLOCK_CODES_MARGIN_HORIZONTAL, code_block_start_y + CODE_BLOCK_CODES_MARGIN_VERTICAL), codes, font=font, fill=font_color)
|
||||
y += CODE_BLOCK_CODES_MARGIN_VERTICAL + CODE_BLOCK_MARGIN
|
||||
# y += font_size+10
|
||||
elif re.search(r"`(.*?)`", line):
|
||||
y += INLINE_CODE_MARGIN # 上边距
|
||||
# 处理行内代码
|
||||
code_regex = r"`(.*?)`"
|
||||
parts_inline = re.findall(code_regex, line)
|
||||
# print(parts_inline)
|
||||
parts = re.split(code_regex, line)
|
||||
# print(parts)
|
||||
for part in parts:
|
||||
# the judge has a tiny bug.
|
||||
# when line is like "hi`hi`". all the parts will be in parts_inline.
|
||||
if part in parts_inline:
|
||||
font = ImageFont.truetype(font_path, INLINE_CODE_FONT_SIZE)
|
||||
code_text = part.strip("`")
|
||||
code_width = font.getsize(code_text)[0] + INLINE_CODE_FONT_MARGIN*2
|
||||
x += INLINE_CODE_MARGIN
|
||||
code_box = (x, y, x + code_width, y + INLINE_CODE_BG_HEIGHT)
|
||||
draw.rounded_rectangle(code_box, radius=5, fill=INLINE_CODE_BG_COLOR, width=2) # 使用灰色填充矩形框作为引用背景
|
||||
draw.text((x+INLINE_CODE_FONT_MARGIN, y), code_text, font=font, fill=font_color)
|
||||
x += code_width+INLINE_CODE_MARGIN-INLINE_CODE_FONT_MARGIN
|
||||
else:
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
draw.text((x, y), part, font=font, fill=font_color)
|
||||
x += font.getsize(part)[0]
|
||||
y += font_size + INLINE_CODE_MARGIN
|
||||
x = 10
|
||||
|
||||
else:
|
||||
# 处理普通文本
|
||||
if line == "":
|
||||
y += TEXT_LINE_MARGIN
|
||||
else:
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
|
||||
draw.text((x, y), line, font=font, fill=font_color)
|
||||
y += font_size + TEXT_LINE_MARGIN*2
|
||||
|
||||
# 图片特殊处理
|
||||
if index in images:
|
||||
image_res = images[index]
|
||||
# 最大不得超过image_width的50%
|
||||
if image_res.size[0] > image_width*0.5:
|
||||
image_res = image_res.resize((int(image_width*0.5), int(image_res.size[1]*image_width*0.5/image_res.size[0])))
|
||||
image.paste(image_res, (IMAGE_MARGIN, y))
|
||||
y += image_res.size[1] + IMAGE_MARGIN*2
|
||||
return image
|
||||
|
||||
|
||||
def save_temp_img(img: Image) -> str:
|
||||
if not os.path.exists("temp"):
|
||||
os.makedirs("temp")
|
||||
|
||||
# 获得文件创建时间,清除超过1小时的
|
||||
try:
|
||||
for f in os.listdir("temp"):
|
||||
path = os.path.join("temp", f)
|
||||
if os.path.isfile(path):
|
||||
ctime = os.path.getctime(path)
|
||||
if time.time() - ctime > 3600:
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
log(f"清除临时文件失败: {e}", level=LEVEL_WARNING, tag="GeneralUtils")
|
||||
|
||||
# 获得时间戳
|
||||
timestamp = int(time.time())
|
||||
p = f"temp/{timestamp}.png"
|
||||
img.save(p)
|
||||
return p
|
||||
|
||||
|
||||
def create_text_image(title: str, text: str, max_width=30, font_size=20):
|
||||
'''
|
||||
文本转图片。
|
||||
title: 标题
|
||||
text: 文本内容
|
||||
max_width: 文本宽度最大值(默认30)
|
||||
font_size: 字体大小(默认20)
|
||||
|
||||
返回:文件路径
|
||||
'''
|
||||
try:
|
||||
img = word2img(title, text, max_width, font_size)
|
||||
p = save_temp_img(img)
|
||||
return p
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def create_markdown_image(text: str):
|
||||
'''
|
||||
markdown文本转图片。
|
||||
返回:文件路径
|
||||
'''
|
||||
try:
|
||||
img = render_markdown(text)
|
||||
p = save_temp_img(img)
|
||||
return p
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def test_markdown():
|
||||
# 示例使用
|
||||
markdown_text = """# Help Center
|
||||
! [] (https://soulter.top/helpme.jpg)
|
||||
"""
|
||||
image = render_markdown(markdown_text)
|
||||
image.show()
|
||||
|
||||
# test_markdown()
|
||||
|
||||
150
util/gplugin.py
Normal file
150
util/gplugin.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import requests
|
||||
import util.general_utils as gu
|
||||
from bs4 import BeautifulSoup
|
||||
import time
|
||||
from util.func_call import (
|
||||
FuncCall,
|
||||
FuncCallJsonFormatError,
|
||||
FuncNotFoundError
|
||||
)
|
||||
def tidy_text(text: str) -> str:
|
||||
return text.strip().replace("\n", "").replace(" ", "").replace("\r", "")
|
||||
|
||||
def special_fetch_zhihu(link: str) -> str:
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
|
||||
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
}
|
||||
response = requests.get(link, headers=headers)
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
r = soup.find(class_="List-item").find(class_="RichContent-inner")
|
||||
if r is None:
|
||||
print("debug: zhihu none")
|
||||
raise Exception("zhihu none")
|
||||
return tidy_text(r.text)
|
||||
|
||||
def web_keyword_search_via_bing(keyword) -> str:
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
|
||||
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
}
|
||||
url = "https://cn.bing.com/search?q="+keyword
|
||||
_cnt = 0
|
||||
_detail_store = []
|
||||
while _cnt < 5:
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
res = []
|
||||
ols = soup.find(id="b_results")
|
||||
for i in ols.find_all("li", class_="b_algo"):
|
||||
try:
|
||||
title = i.find("h2").text
|
||||
desc = i.find("p").text
|
||||
link = i.find("h2").find("a").get("href")
|
||||
res.append({
|
||||
"title": title,
|
||||
"desc": desc,
|
||||
"link": link,
|
||||
})
|
||||
if len(_detail_store) < 2 and "zhihu.com" in link:
|
||||
try:
|
||||
_detail_store.append(special_fetch_zhihu(link)[:800])
|
||||
except BaseException as e:
|
||||
print(f"zhihu parse err: {str(e)}")
|
||||
if len(res) >= 5: # 限制5条
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"bing parse err: {str(e)}")
|
||||
if len(res) == 0:
|
||||
break
|
||||
if len(_detail_store) > 0:
|
||||
ret = f"{str(res)} \n来源知乎的具体资料: {str(_detail_store)}"
|
||||
else:
|
||||
ret = f"{str(res)}"
|
||||
return str(ret)
|
||||
except Exception as e:
|
||||
print(f"bing fetch err: {str(e)}")
|
||||
_cnt += 1
|
||||
time.sleep(1)
|
||||
print("fail to fetch bing info, using sougou.")
|
||||
return web_keyword_search_via_sougou(keyword)
|
||||
|
||||
def web_keyword_search_via_sougou(keyword) -> str:
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
|
||||
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
}
|
||||
url = f"https://sogou.com/web?query={keyword}"
|
||||
response = requests.get(url, headers=headers)
|
||||
response.encoding = "utf-8"
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
|
||||
res = []
|
||||
results = soup.find("div", class_="results")
|
||||
for i in results.find_all("div", class_="vrwrap"):
|
||||
try:
|
||||
title = tidy_text(i.find("h3").text)
|
||||
link = tidy_text(i.find("h3").find("a").get("href"))
|
||||
if link.startswith("/link?url="):
|
||||
link = "https://www.sogou.com" + link
|
||||
res.append({
|
||||
"title": title,
|
||||
"link": link,
|
||||
})
|
||||
except:
|
||||
pass
|
||||
ret = f"{str(res)} \n全部内容: {tidy_text(soup.text)}"
|
||||
return ret
|
||||
|
||||
def fetch_website_content(url):
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
|
||||
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
res = soup.text
|
||||
res = res.replace("\n", "")
|
||||
with open(f"temp_{time.time()}.html", "w", encoding="utf-8") as f:
|
||||
f.write(res)
|
||||
return res
|
||||
|
||||
def web_search(question, provider):
|
||||
|
||||
new_func_call = FuncCall(provider)
|
||||
|
||||
new_func_call.add_func("web_keyword_search_via_bing", [{
|
||||
"type": "string",
|
||||
"name": "keyword",
|
||||
"brief": "必应搜索的关键词(分词,尽量保留所有信息)"
|
||||
}],
|
||||
"在必应搜索引擎上搜索给定的关键词,并且返回第一页的搜索结果列表(标题,简介和链接)",
|
||||
web_keyword_search_via_bing
|
||||
)
|
||||
|
||||
func_definition1 = new_func_call.func_dump()
|
||||
question1 = f"{question} \n(只能调用一个函数。)"
|
||||
res1, has_func = new_func_call.func_call(question1, func_definition1, is_task=False, is_summary=False)
|
||||
has_func = True
|
||||
if has_func:
|
||||
provider.forget()
|
||||
question3 = f"""请你回答`{question}`问题。\n以下是相关材料,你请直接拿此材料针对问题进行总结回答,然后再给出参考链接。不要提到任何函数调用的信息。```\n{res1}\n```\n"""
|
||||
print(question3)
|
||||
_c = 0
|
||||
while _c < 5:
|
||||
try:
|
||||
print('text chat')
|
||||
res3 = provider.text_chat(question3)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
_c += 1
|
||||
if _c == 5:
|
||||
raise e
|
||||
if "The message you submitted was too long" in str(e):
|
||||
res2 = res2[:int(len(res2) / 2)]
|
||||
question3 = f"""请你回答`{question}`问题。\n以下是相关材料,请直接拿此材料针对问题进行回答,然后再给出参考链接。```\n{res1}\n{res2}\n```\n"""
|
||||
return res3
|
||||
else:
|
||||
return res1
|
||||
11
util/log.py
11
util/log.py
@@ -1,11 +0,0 @@
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import colorlog
|
||||
|
||||
logger = logging.getLogger("QQChannelChatGPT")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
handler = colorlog.StreamHandler()
|
||||
fmt = "%(log_color)s[%(name)s] %(message)s"
|
||||
handler.setFormatter(colorlog.ColoredFormatter(
|
||||
fmt))
|
||||
logger.addHandler(handler)
|
||||
22
util/plugin_util.py
Normal file
22
util/plugin_util.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
import inspect
|
||||
|
||||
# 找出模块里所有的类名
|
||||
def get_classes(p_name, arg):
|
||||
classes = []
|
||||
clsmembers = inspect.getmembers(arg, inspect.isclass)
|
||||
for (name, _) in clsmembers:
|
||||
# print(name, p_name)
|
||||
if p_name.lower() == name.lower()[:-6]:
|
||||
classes.append(name)
|
||||
break
|
||||
return classes
|
||||
|
||||
# 获取一个文件夹下所有的模块
|
||||
def get_modules(path):
|
||||
modules = []
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if file.endswith(".py") and not file.startswith("__"):
|
||||
modules.append(file[:-3])
|
||||
return modules
|
||||
25
webapp_replit.py
Normal file
25
webapp_replit.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from flask import Flask
|
||||
from threading import Thread
|
||||
import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def main_func():
|
||||
content = "<h1>QQChannelChatGPT Web APP</h1>"
|
||||
|
||||
content += "<p>" + "Online @ " + str(datetime.datetime.now()) + "</p>"
|
||||
content += "<p>欢迎Star本项目!!!</p>"
|
||||
return content
|
||||
|
||||
|
||||
def run():
|
||||
app.run(host="0.0.0.0", port=8080)
|
||||
|
||||
|
||||
|
||||
|
||||
def keep_alive():
|
||||
server = Thread(target=run)
|
||||
server.start()
|
||||
Reference in New Issue
Block a user