mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-20 21:57:14 +08:00
using pickle instead of redis
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ nohup.out
|
|||||||
tmp
|
tmp
|
||||||
plugins.json
|
plugins.json
|
||||||
itchat.pkl
|
itchat.pkl
|
||||||
|
user_datas.pkl
|
||||||
11
app.py
11
app.py
@@ -4,13 +4,22 @@ import os
|
|||||||
from config import conf, load_config
|
from config import conf, load_config
|
||||||
from channel import channel_factory
|
from channel import channel_factory
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
|
||||||
from plugins import *
|
from plugins import *
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def sigterm_handler(_signo, _stack_frame):
|
||||||
|
conf().save_user_datas()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
try:
|
try:
|
||||||
# load config
|
# load config
|
||||||
load_config()
|
load_config()
|
||||||
|
# ctrl + c
|
||||||
|
signal.signal(signal.SIGINT, sigterm_handler)
|
||||||
|
# kill signal
|
||||||
|
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||||
|
|
||||||
# create channel
|
# create channel
|
||||||
channel_name=conf().get('channel_type', 'wx')
|
channel_name=conf().get('channel_type', 'wx')
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from common.expired_dict import ExpiredDict
|
|||||||
import openai
|
import openai
|
||||||
import openai.error
|
import openai.error
|
||||||
import time
|
import time
|
||||||
import redis
|
|
||||||
|
|
||||||
# OpenAI对话模型API (可用)
|
# OpenAI对话模型API (可用)
|
||||||
class ChatGPTBot(Bot,OpenAIImage):
|
class ChatGPTBot(Bot,OpenAIImage):
|
||||||
|
|||||||
@@ -8,18 +8,15 @@
|
|||||||
|
|
||||||
在开始部署前,你需要一个拥有公网IP的服务器,以提供微信服务器和我们自己服务器的连接。或者你需要进行内网穿透,否则微信服务器无法将消息发送给我们的服务器。
|
在开始部署前,你需要一个拥有公网IP的服务器,以提供微信服务器和我们自己服务器的连接。或者你需要进行内网穿透,否则微信服务器无法将消息发送给我们的服务器。
|
||||||
|
|
||||||
此外,需要在我们的服务器上安装额外的依赖web.py和redis,其中redis用来储存用户私有的配置信息。
|
此外,需要在我们的服务器上安装python的web框架web.py。
|
||||||
以ubuntu为例(在ubuntu 22.04上测试):
|
以ubuntu为例(在ubuntu 22.04上测试):
|
||||||
```
|
```
|
||||||
sudo apt-get install redis
|
|
||||||
sudo systemctl start redis
|
|
||||||
pip3 install redis
|
|
||||||
pip3 install web.py
|
pip3 install web.py
|
||||||
```
|
```
|
||||||
|
|
||||||
然后在[微信公众平台](mp.weixin.qq.com)注册一个自己的公众号,类型选择订阅号,主体为个人即可。
|
然后在[微信公众平台](mp.weixin.qq.com)注册一个自己的公众号,类型选择订阅号,主体为个人即可。
|
||||||
|
|
||||||
然后根据[接入指南](https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html)的说明,在[微信公众平台](mp.weixin.qq.com)的“设置与开发”-“基本配置”-“服务器配置”中填写服务器地址(URL)和令牌(Token)。这个Token是你自己编的一个特定的令牌。消息加解密方式目前选择的是明文模式。相关的服务器验证代码已经写好,你不需要再添加任何代码。你只需要将本项目根目录的`app.py`中channel_name改成"mp",将上述的Token填写在本项目根目录的`config.json`中,例如`"wechatmp_token": "Your Token",` 然后运行`python3 app.py`启动web服务器,然后在刚才的“服务器配置”中点击`提交`即可验证你的服务器。
|
然后根据[接入指南](https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html)的说明,在[微信公众平台](mp.weixin.qq.com)的“设置与开发”-“基本配置”-“服务器配置”中填写服务器地址(URL)和令牌(Token)。这个Token是你自己编的一个特定的令牌。消息加解密方式目前选择的是明文模式。相关的服务器验证代码已经写好,你不需要再添加任何代码。你只需要在本项目根目录的`config.json`中添加`"channel_type": "wechatmp", "wechatmp_token": "your Token", ` 然后运行`python3 app.py`启动web服务器,然后在刚才的“服务器配置”中点击`提交`即可验证你的服务器。
|
||||||
|
|
||||||
随后在[微信公众平台](mp.weixin.qq.com)启用服务器,关闭手动填写规则的自动回复,即可实现ChatGPT的自动回复。
|
随后在[微信公众平台](mp.weixin.qq.com)启用服务器,关闭手动填写规则的自动回复,即可实现ChatGPT的自动回复。
|
||||||
|
|
||||||
@@ -29,7 +26,7 @@ pip3 install web.py
|
|||||||
另外,由于微信官方的限制,自动回复有长度限制。因此这里将ChatGPT的回答拆分,分成每段600字回复(限制大约在700字)。
|
另外,由于微信官方的限制,自动回复有长度限制。因此这里将ChatGPT的回答拆分,分成每段600字回复(限制大约在700字)。
|
||||||
|
|
||||||
## 私有api_key
|
## 私有api_key
|
||||||
公共api有访问频率限制(免费账号每分钟最多20次ChatGPT的API调用),这在服务多人的时候会遇到问题。因此这里多加了一个设置私有api_key的功能,私有的api_key将储存在redis中。另外后续计划利用redis储存更多的用户个人配置。目前通过godcmd插件的命令来设置私有api_key。
|
公共api有访问频率限制(免费账号每分钟最多20次ChatGPT的API调用),这在服务多人的时候会遇到问题。因此这里多加了一个设置私有api_key的功能。目前通过godcmd插件的命令来设置私有api_key。
|
||||||
|
|
||||||
## 命令优化
|
## 命令优化
|
||||||
之前plugin中#和$符号混用,且$这个符号在微信中和中文会有较大间隔,体验实在不好。这里我将所有命令更改成了以#开头。添加了一个叫finish的plugin来最后处理所有#结尾的命令,防止未知命令变成ChatGPT的query。
|
之前plugin中#和$符号混用,且$这个符号在微信中和中文会有较大间隔,体验实在不好。这里我将所有命令更改成了以#开头。添加了一个叫finish的plugin来最后处理所有#结尾的命令,防止未知命令变成ChatGPT的query。
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from bridge.reply import *
|
|||||||
from bridge.context import *
|
from bridge.context import *
|
||||||
from plugins import *
|
from plugins import *
|
||||||
import traceback
|
import traceback
|
||||||
import redis
|
|
||||||
|
|
||||||
# If using SSL, uncomment the following lines, and modify the certificate path.
|
# If using SSL, uncomment the following lines, and modify the certificate path.
|
||||||
# from cheroot.server import HTTPServer
|
# from cheroot.server import HTTPServer
|
||||||
@@ -181,12 +180,8 @@ class WechatMPChannel(Channel):
|
|||||||
context = Context()
|
context = Context()
|
||||||
context.kwargs = {'isgroup': False, 'receiver': fromUser, 'session_id': fromUser}
|
context.kwargs = {'isgroup': False, 'receiver': fromUser, 'session_id': fromUser}
|
||||||
|
|
||||||
R = redis.Redis(host='localhost', port=6379, db=0)
|
user_data = conf().get_user_data(fromUser)
|
||||||
user_openai_api_key = "openai_api_key_" + fromUser
|
context['openai_api_key'] = user_data.get('openai_api_key') # None or user openai_api_key
|
||||||
api_key = R.get(user_openai_api_key)
|
|
||||||
if api_key != None:
|
|
||||||
api_key = api_key.decode("utf-8")
|
|
||||||
context['openai_api_key'] = api_key # None or user openai_api_key
|
|
||||||
|
|
||||||
img_match_prefix = check_prefix(message, conf().get('image_create_prefix'))
|
img_match_prefix = check_prefix(message, conf().get('image_create_prefix'))
|
||||||
if img_match_prefix:
|
if img_match_prefix:
|
||||||
|
|||||||
31
config.py
31
config.py
@@ -3,6 +3,7 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
import pickle
|
||||||
|
|
||||||
# 将所有可用的配置项写在字典里, 请使用小写字母
|
# 将所有可用的配置项写在字典里, 请使用小写字母
|
||||||
available_setting = {
|
available_setting = {
|
||||||
@@ -88,6 +89,11 @@ available_setting = {
|
|||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
class Config(dict):
|
||||||
|
def __init__(self, d:dict={}):
|
||||||
|
super().__init__(d)
|
||||||
|
# user_datas: 用户数据,key为用户名,value为用户数据,也是dict
|
||||||
|
self.user_datas = {}
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if key not in available_setting:
|
if key not in available_setting:
|
||||||
raise Exception("key {} not in available_setting".format(key))
|
raise Exception("key {} not in available_setting".format(key))
|
||||||
@@ -106,6 +112,30 @@ class Config(dict):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
# Make sure to return a dictionary to ensure atomic
|
||||||
|
def get_user_data(self, user) -> dict:
|
||||||
|
if self.user_datas.get(user) is None:
|
||||||
|
self.user_datas[user] = {}
|
||||||
|
return self.user_datas[user]
|
||||||
|
|
||||||
|
def load_user_datas(self):
|
||||||
|
try:
|
||||||
|
with open('user_datas.pkl', 'rb') as f:
|
||||||
|
self.user_datas = pickle.load(f)
|
||||||
|
logger.info("[Config] User datas loaded.")
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
logger.info("[Config] User datas file not found, ignore.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.info("[Config] User datas error: {}".format(e))
|
||||||
|
self.user_datas = {}
|
||||||
|
|
||||||
|
def save_user_datas(self):
|
||||||
|
try:
|
||||||
|
with open('user_datas.pkl', 'wb') as f:
|
||||||
|
pickle.dump(self.user_datas, f)
|
||||||
|
logger.info("[Config] User datas saved.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.info("[Config] User datas error: {}".format(e))
|
||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
@@ -142,6 +172,7 @@ def load_config():
|
|||||||
|
|
||||||
logger.info("[INIT] load config: {}".format(config))
|
logger.info("[INIT] load config: {}".format(config))
|
||||||
|
|
||||||
|
config.load_user_datas()
|
||||||
|
|
||||||
def get_root():
|
def get_root():
|
||||||
return os.path.dirname(os.path.abspath(__file__))
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ from typing import Tuple
|
|||||||
from bridge.bridge import Bridge
|
from bridge.bridge import Bridge
|
||||||
from bridge.context import ContextType
|
from bridge.context import ContextType
|
||||||
from bridge.reply import Reply, ReplyType
|
from bridge.reply import Reply, ReplyType
|
||||||
from config import load_config
|
from config import conf, load_config
|
||||||
import plugins
|
import plugins
|
||||||
from plugins import *
|
from plugins import *
|
||||||
from common import const
|
from common import const
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
import pickle
|
||||||
|
|
||||||
# 定义指令集
|
# 定义指令集
|
||||||
COMMANDS = {
|
COMMANDS = {
|
||||||
@@ -195,20 +196,18 @@ class Godcmd(Plugin):
|
|||||||
ok, result = False, "unknown args"
|
ok, result = False, "unknown args"
|
||||||
elif cmd == "set_openai_api_key":
|
elif cmd == "set_openai_api_key":
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
import redis
|
user_data = conf().get_user_data(user)
|
||||||
R = redis.Redis(host='localhost', port=6379, db=0)
|
user_data['openai_api_key'] = args[0]
|
||||||
user_openai_api_key = "openai_api_key_" + user
|
|
||||||
R.set(user_openai_api_key, args[0])
|
|
||||||
# R.sadd("openai_api_key", args[0])
|
|
||||||
ok, result = True, "你的OpenAI私有api_key已设置为" + args[0]
|
ok, result = True, "你的OpenAI私有api_key已设置为" + args[0]
|
||||||
else:
|
else:
|
||||||
ok, result = False, "请提供一个api_key"
|
ok, result = False, "请提供一个api_key"
|
||||||
elif cmd == "reset_openai_api_key":
|
elif cmd == "reset_openai_api_key":
|
||||||
import redis
|
try:
|
||||||
R = redis.Redis(host='localhost', port=6379, db=0)
|
user_data = conf().get_user_data(user)
|
||||||
user_openai_api_key = "openai_api_key_" + user
|
user_data.pop('openai_api_key')
|
||||||
R.delete(user_openai_api_key)
|
except Exception as e:
|
||||||
ok, result = True, "OpenAI的api_key已重置"
|
ok, result = False, "你没有设置私有api_key"
|
||||||
|
ok, result = True, "你的OpenAI私有api_key已清除"
|
||||||
# elif cmd == "helpp":
|
# elif cmd == "helpp":
|
||||||
# if len(args) != 1:
|
# if len(args) != 1:
|
||||||
# ok, result = False, "请提供插件名"
|
# ok, result = False, "请提供插件名"
|
||||||
|
|||||||
Reference in New Issue
Block a user