From fdbe993913903a0c829947fb3726d52081b286a9 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Tue, 21 Nov 2023 22:38:54 +0800
Subject: [PATCH 01/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B6=88?=
=?UTF-8?q?=E6=81=AF=E5=85=BC=E5=AE=B9=E7=9A=84=E4=B8=80=E4=BA=9B=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
model/platform/qqchan.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/model/platform/qqchan.py b/model/platform/qqchan.py
index 217598ae4..2147d81d2 100644
--- a/model/platform/qqchan.py
+++ b/model/platform/qqchan.py
@@ -76,7 +76,7 @@ class QQChan():
ngm.sub_type = "normal"
ngm.message_id = message.id
- ngm.guild_id = int(message.channel_id)
+ ngm.guild_id = int(message.guild_id)
ngm.channel_id = int(message.channel_id)
ngm.user_id = int(message.author.id)
msg = []
From b36747c728fdbbd0c57e6b472b009f8717e20ec4 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Thu, 23 Nov 2023 11:55:22 +0800
Subject: [PATCH 02/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DQQ=E9=A2=91?=
=?UTF-8?q?=E9=81=93=E5=8F=91=E5=9B=BE=E6=96=87=E6=B6=88=E6=81=AF=E6=8A=A5?=
=?UTF-8?q?=E9=94=99=E7=9A=84=E6=83=85=E5=86=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
model/platform/qqchan.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/model/platform/qqchan.py b/model/platform/qqchan.py
index 2147d81d2..486b45b42 100644
--- a/model/platform/qqchan.py
+++ b/model/platform/qqchan.py
@@ -133,7 +133,7 @@ class QQChan():
try:
# reply_res = asyncio.run_coroutine_threadsafe(message.raw_message.reply(content=str(plain_text), message_reference = msg_ref, file_image=image_path), self.client.loop)
- reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=message.channel_id,
+ reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=str(message.channel_id),
content=str(plain_text),
msg_id=message.message_id,
file_image=image_path,
@@ -146,7 +146,7 @@ class QQChan():
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(self.client.api.post_message(channel_id=message.channel_id,
+ reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=str(message.channel_id),
content=str(i),
msg_id=message.message_id,
file_image=image_path,
@@ -157,7 +157,7 @@ class QQChan():
try:
# 防止被qq频道过滤消息
plain_text = plain_text.replace(".", " . ")
- reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=message.channel_id,
+ reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=str(message.channel_id),
content=str(plain_text),
msg_id=message.message_id,
file_image=image_path,
@@ -166,7 +166,7 @@ class QQChan():
print("QQ频道API错误: \n"+str(e))
try:
# reply_res = asyncio.run_coroutine_threadsafe(message.raw_message.reply(content=str(str.join(" ", plain_text)), message_reference = msg_ref, file_image=image_path), self.client.loop)
- reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=message.channel_id,
+ reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=str(message.channel_id),
content=str(str.join(" ", plain_text)),
msg_id=message.message_id,
file_image=image_path,
@@ -174,7 +174,7 @@ class QQChan():
except BaseException as e:
plain_text = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE)
plain_text = plain_text.replace(".", "·")
- reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=message.channel_id,
+ reply_res = asyncio.run_coroutine_threadsafe(self.client.api.post_message(channel_id=str(message.channel_id),
content=plain_text,
msg_id=message.message_id,
file_image=image_path,
From 3e4818d0eeb7512acf6bc2750e41dee9cdbc5144 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Thu, 23 Nov 2023 17:50:40 +0800
Subject: [PATCH 03/13] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D=E9=83=A8?=
=?UTF-8?q?=E5=88=86=E6=8F=92=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cores/qqbot/core.py | 9 +++++++--
cores/qqbot/global_object.py | 7 +++++--
model/command/command.py | 3 ++-
model/platform/qq.py | 15 +++++++++++----
model/platform/qqchan.py | 30 ++++++++++++++++++++++++++++--
5 files changed, 53 insertions(+), 11 deletions(-)
diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py
index 262483622..1ef1d6eb1 100644
--- a/cores/qqbot/core.py
+++ b/cores/qqbot/core.py
@@ -603,9 +603,14 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak
chatgpt_res = ""
- if session_id in gocq_bot.waiting and gocq_bot.waiting[session_id] == '':
- gocq_bot.waiting[session_id] = qq_msg
+ # 如果是等待回复的消息
+ if platform == PLATFORM_GOCQ and session_id in gocq_bot.waiting and gocq_bot.waiting[session_id] == '':
+ gocq_bot.waiting[session_id] = message
return
+ if platform == PLATFORM_QQCHAN and session_id in qqchannel_bot.waiting and qqchannel_bot.waiting[session_id] == '':
+ qqchannel_bot.waiting[session_id] = message
+ return
+
hit, command_result = llm_command_instance[chosen_provider].check_command(
qq_msg,
session_id,
diff --git a/cores/qqbot/global_object.py b/cores/qqbot/global_object.py
index ace53fe2c..cc6873450 100644
--- a/cores/qqbot/global_object.py
+++ b/cores/qqbot/global_object.py
@@ -48,6 +48,7 @@ class AstrMessageEvent():
platform: str # `gocq` 或 `qqchan`
role: str # `admin` 或 `member`
global_object: GlobalObject # 一些公用数据
+ session_id: int # 会话id (可能是群id,也可能是某个user的id。取决于是否开启了 uniqueSession)
def __init__(self, message_str: str,
message_obj: Union[GroupMessage, FriendMessage, GuildMessage, NakuruGuildMessage],
@@ -56,7 +57,8 @@ class AstrMessageEvent():
platform: str,
role: str,
global_object: GlobalObject,
- llm_provider: Provider = None):
+ llm_provider: Provider = None,
+ session_id: int = None):
self.message_str = message_str
self.message_obj = message_obj
self.gocq_platform = gocq_platform
@@ -64,4 +66,5 @@ class AstrMessageEvent():
self.platform = platform
self.role = role
self.global_object = global_object
- self.llm_provider = llm_provider
\ No newline at end of file
+ self.llm_provider = llm_provider
+ self.session_id = session_id
\ No newline at end of file
diff --git a/model/command/command.py b/model/command/command.py
index b23c4ec05..7fc068c61 100644
--- a/model/command/command.py
+++ b/model/command/command.py
@@ -50,7 +50,8 @@ class Command:
qq_sdk_platform=self.global_object.platform_qqchan,
platform=platform,
role=role,
- global_object=self.global_object
+ global_object=self.global_object,
+ session_id = session_id
)
for k, v in cached_plugins.items():
try:
diff --git a/model/platform/qq.py b/model/platform/qq.py
index a52342628..1b7a910d8 100644
--- a/model/platform/qq.py
+++ b/model/platform/qq.py
@@ -4,8 +4,11 @@ from util.cmd_config import CmdConfig
import asyncio
from nakuru import (
CQHTTP,
- GuildMessage
+ GuildMessage,
+ GroupMessage,
+ FriendMessage
)
+from typing import Union
import time
@@ -155,18 +158,22 @@ class QQ:
except Exception as e:
raise e
- def wait_for_message(self, group_id):
+ def wait_for_message(self, group_id) -> Union[GroupMessage, FriendMessage, GuildMessage]:
'''
- 等待下一条消息
+ 等待下一条消息,超时 300s 后抛出异常
'''
self.waiting[group_id] = ''
+ cnt = 0
while True:
if group_id in self.waiting and self.waiting[group_id] != '':
# 去掉
ret = self.waiting[group_id]
del self.waiting[group_id]
return ret
- time.sleep(0.5)
+ cnt += 1
+ if cnt > 300:
+ raise Exception("等待消息超时。")
+ time.sleep(1)
def get_client(self):
return self.client
diff --git a/model/platform/qqchan.py b/model/platform/qqchan.py
index 486b45b42..1f891b6f4 100644
--- a/model/platform/qqchan.py
+++ b/model/platform/qqchan.py
@@ -10,6 +10,7 @@ from util import general_utils as gu
from nakuru.entities.components import Plain, At, Image
from botpy.types.message import Reference
from botpy import Client
+import time
class NakuruGuildMember():
tiny_id: int # 发送者识别号
@@ -38,6 +39,7 @@ class NakuruGuildMessage():
class QQChan():
def __init__(self, cnt: dict = None) -> None:
self.qqchan_cnt = 0
+ self.waiting: dict = {}
def get_cnt(self):
return self.qqchan_cnt
@@ -181,11 +183,35 @@ class QQChan():
message_reference=msg_ref), self.client.loop).result()
# send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}")
- def push_message(self, channel_id: int, message_chain: list):
+ def push_message(self, channel_id: int, message_chain: list, message_id: int = None):
'''
- 推送消息
+ 推送消息, 如果有 message_id,那么就是回复消息。
'''
_n = NakuruGuildMessage()
_n.channel_id = channel_id
+ _n.message_id = message_id
self.send_qq_msg(_n, message_chain)
+
+ def send(self, message_obj, message_chain: list):
+ '''
+ 发送信息
+ '''
+ self.send_qq_msg(message_obj, message_chain)
+
+ def wait_for_message(self, channel_id: int) -> NakuruGuildMessage:
+ '''
+ 等待指定 channel_id 的下一条信息,超时 300s 后抛出异常
+ '''
+ self.waiting[channel_id] = ''
+ cnt = 0
+ while True:
+ if channel_id in self.waiting and self.waiting[channel_id] != '':
+ # 去掉
+ ret = self.waiting[channel_id]
+ del self.waiting[channel_id]
+ return ret
+ cnt += 1
+ if cnt > 300:
+ raise Exception("等待消息超时。")
+ time.sleep(1)
\ No newline at end of file
From baa57266b4409399fd78925f69f16bd508d976f9 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 25 Nov 2023 11:50:32 +0800
Subject: [PATCH 04/13] =?UTF-8?q?feat:=20=E5=88=9D=E6=AD=A5=E6=8E=A5?=
=?UTF-8?q?=E5=85=A5=E5=AE=98=E6=96=B9QQ=E7=BE=A4=E6=9C=BA=E5=99=A8?=
=?UTF-8?q?=E4=BA=BAAPI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cores/qqbot/core.py | 62 ++++++++++++---
model/platform/qqgroup.py | 162 ++++++++++++++++++++++++++++----------
2 files changed, 172 insertions(+), 52 deletions(-)
diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py
index 262483622..34e778507 100644
--- a/cores/qqbot/core.py
+++ b/cores/qqbot/core.py
@@ -13,6 +13,13 @@ from cores.qqbot.personality import personalities
from addons.baidu_aip_judge import BaiduJudge
from model.platform.qqchan import QQChan, NakuruGuildMember, NakuruGuildMessage
from model.platform.qq import QQ
+from model.platform.qqgroup import (
+ UnofficialQQBotSDK,
+ Event as QQEvent,
+ Message as QQMessage,
+ MessageChain,
+ PlainText
+)
from nakuru import (
CQHTTP,
GroupMessage,
@@ -86,6 +93,9 @@ PLATFORM_QQCHAN = 'qqchan'
qqchan_loop = None
client = None
+# QQ群机器人
+PLATFROM_QQBOT = 'qqbot'
+
# 配置
cc.init_attributes(["qq_forward_threshold"], 200)
cc.init_attributes(["qq_welcome"], "欢迎加入本群!\n欢迎给https://github.com/Soulter/QQChannelChatGPT项目一个Star😊~\n输入help查看帮助~\n")
@@ -105,6 +115,8 @@ cc.init_attributes(["gocq_react_group_increase"], True)
cc.init_attributes(["gocq_qqchan_admin"], "")
cc.init_attributes(["other_admins"], [])
cc.init_attributes(["CHATGPT_BASE_URL"], "")
+cc.init_attributes(["qqbot_appid"], "")
+cc.init_attributes(["qqbot_secret"], "")
# cc.init_attributes(["qq_forward_mode"], False)
# QQ机器人
@@ -115,8 +127,14 @@ gocq_app = CQHTTP(
port=cc.get("gocq_websocket_port", 6700),
http_port=cc.get("gocq_http_port", 5700),
)
+qq_bot: UnofficialQQBotSDK = UnofficialQQBotSDK(
+ cc.get("qqbot_appid", None),
+ cc.get("qqbot_secret", None)
+)
+
+gocq_loop: asyncio.AbstractEventLoop = None
+qqbot_loop: asyncio.AbstractEventLoop = None
-gocq_loop = None
# 全局对象
_global_object: GlobalObject = None
@@ -350,9 +368,17 @@ def initBot(cfg, prov):
_global_object.admin_qq = admin_qq
_global_object.admin_qqchan = admin_qqchan
+
+ global qq_bot, qqbot_loop
+ qqbot_loop = asyncio.new_event_loop()
+ if cc.get("qqbot_appid", None) is not None and cc.get("qqbot_secret", None) is not None:
+ gu.log("- 启用QQ群机器人 -", gu.LEVEL_INFO)
+ thread_inst = threading.Thread(target=run_qqbot, args=(qqbot_loop, qq_bot,), daemon=False)
+ thread_inst.start()
+
+
# GOCQ
global gocq_bot
-
if 'gocqbot' in cfg and cfg['gocqbot']['enable']:
gu.log("- 启用QQ机器人 -", gu.LEVEL_INFO)
@@ -431,6 +457,15 @@ def run_gocq_bot(loop, gocq_bot, gocq_app):
except BaseException as e:
input("启动QQ机器人出现错误"+str(e))
+'''
+启动QQ群机器人(官方接口)
+'''
+def run_qqbot(loop: asyncio.AbstractEventLoop, qq_bot: UnofficialQQBotSDK):
+ asyncio.set_event_loop(loop)
+ QQBotClient()
+ qq_bot.run_bot()
+
+
'''
检查发言频率
'''
@@ -469,6 +504,10 @@ async def send_message(platform, message, res, session_id = None):
qqchannel_bot.send_qq_msg(message, res)
if platform == PLATFORM_GOCQ:
await gocq_bot.send_qq_msg(message, res)
+ if platform == PLATFROM_QQBOT:
+ message_chain = MessageChain()
+ message_chain.parse_from_nakuru(res)
+ await qq_bot.send(message, message_chain)
async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, NakuruGuildMessage],
group: bool=False,
@@ -493,13 +532,15 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak
with_tag = False # 是否带有昵称
- if platform == PLATFORM_GOCQ or platform == PLATFORM_QQCHAN:
+ if platform == PLATFORM_QQCHAN or platform == PLATFROM_QQBOT:
+ with_tag = True
+
+ if platform == PLATFORM_GOCQ or platform == PLATFORM_QQCHAN or platform == PLATFROM_QQBOT:
_len = 0
for i in message.message:
- if isinstance(i, Plain):
+ if isinstance(i, Plain) or isinstance(i, PlainText):
qq_msg += str(i.text).strip()
if isinstance(i, At):
- # @机器人
if message.type == "GuildMessage":
if i.qq == message.user_id or i.qq == message.self_tiny_id:
with_tag = True
@@ -545,9 +586,6 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak
# 独立会话时,一个用户一个session
session_id = sender_id
- if platform == PLATFORM_QQCHAN:
- with_tag = True
-
if qq_msg == "":
await send_message(platform, message, f"Hi~", session_id=session_id)
return
@@ -812,4 +850,10 @@ class gocqClient():
if source.message[0].qq == source.self_tiny_id:
new_sub_thread(oper_msg, (source, True, PLATFORM_GOCQ))
else:
- return
\ No newline at end of file
+ return
+
+class QQBotClient():
+ @qq_bot.on('GroupMessage')
+ async def _(bot: UnofficialQQBotSDK, message: QQMessage):
+ print(message)
+ new_sub_thread(oper_msg, (message, True, PLATFROM_QQBOT))
\ No newline at end of file
diff --git a/model/platform/qqgroup.py b/model/platform/qqgroup.py
index bcbcacf66..842c1f6f3 100644
--- a/model/platform/qqgroup.py
+++ b/model/platform/qqgroup.py
@@ -2,8 +2,64 @@ import requests
import asyncio
import websockets
from websockets import WebSocketClientProtocol
-import threading
import json
+import inspect
+from typing import Callable, Awaitable, Union
+from pydantic import BaseModel
+import datetime
+
+class Event(BaseModel):
+ GroupMessage: str = "GuildMessage"
+
+class Sender(BaseModel):
+ user_id: str
+ member_openid: str
+
+
+class MessageComponent(BaseModel):
+ type: str
+
+class PlainText(MessageComponent):
+ text: str
+
+class Image(MessageComponent):
+ path: str
+ file: str
+ url: str
+
+class MessageChain(list):
+
+ def append(self, __object: MessageComponent) -> None:
+ if not isinstance(__object, MessageComponent):
+ raise TypeError("不受支持的消息链元素类型。回复的消息链必须是 MessageComponent 的子类。")
+ return super().append(__object)
+
+ def insert(self, __index: int, __object: MessageComponent) -> None:
+ if not isinstance(__object, MessageComponent):
+ raise TypeError("不受支持的消息链元素类型。回复的消息链必须是 MessageComponent 的子类。")
+ return super().insert(__index, __object)
+
+ def parse_from_nakuru(self, nakuru_message_chain: Union[list, str]) -> None:
+ if isinstance(nakuru_message_chain, str):
+ self.append(PlainText(type='Plain', text=nakuru_message_chain))
+ else:
+ for i in nakuru_message_chain:
+ if i['type'] == 'Plain':
+ self.append(PlainText(type='Plain', text=i['text']))
+ elif i['type'] == 'Image':
+ self.append(Image(path=i['path'], file=i['file'], url=i['url']))
+
+class Message(BaseModel):
+ type: str
+ user_id: str
+ member_openid: str
+ message_id: str
+ group_id: str
+ group_openid: str
+ content: str
+ message: MessageChain
+ time: int
+ sender: Sender
class UnofficialQQBotSDK:
@@ -13,43 +69,42 @@ class UnofficialQQBotSDK:
def __init__(self, appid: str, client_secret: str) -> None:
self.appid = appid
self.client_secret = client_secret
- self.get_access_token()
- self.get_wss_endpoint()
- asyncio.get_event_loop().run_until_complete(self.ws_client())
+ self.events: dict[str, Awaitable] = {}
-
-
-
- def get_access_token(self) -> None:
+ def run_bot(self) -> None:
+ self.__get_access_token()
+ self.__get_wss_endpoint()
+ asyncio.get_event_loop().run_until_complete(self.__ws_client())
+
+ def __get_access_token(self) -> None:
res = requests.post(self.GET_APP_ACCESS_TOKEN_URL, json={
"appId": self.appid,
"clientSecret": self.client_secret
}, headers={
"Content-Type": "application/json"
})
- print(res.text)
- self.access_token = 'QQBot ' + res.json()['access_token']
- print("access_token: " + self.access_token)
+ res = res.json()
+ code = res['code'] if 'code' in res else 1
+ if 'access_token' not in res:
+ raise Exception(f"获取 access_token 失败。原因:{res['message'] if 'message' in res else '未知'}")
+ self.access_token = 'QQBot ' + res['access_token']
- def auth_header(self) -> str:
+ def __auth_header(self) -> str:
return {
'Authorization': self.access_token,
'X-Union-Appid': self.appid,
}
- def get_wss_endpoint(self):
- # self.wss_endpoint = requests.get(self.OPENAPI_BASE_URL + "/gateway", headers=self.auth_header()).json()['url']
- res = requests.get(self.OPENAPI_BASE_URL + "/gateway", headers=self.auth_header())
- print(res.text)
+ def __get_wss_endpoint(self):
+ res = requests.get(self.OPENAPI_BASE_URL + "/gateway", headers=self.__auth_header())
self.wss_endpoint = res.json()['url']
print("wss_endpoint: " + self.wss_endpoint)
- async def behav_heartbeat(self, ws: WebSocketClientProtocol, t: int):
+ async def __behav_heartbeat(self, ws: WebSocketClientProtocol, t: int):
while True:
await asyncio.sleep(t - 1)
try:
- print("heartbeat., s: " + str(self.s))
await ws.send(json.dumps({
"op": 1,
"d": self.s
@@ -57,12 +112,9 @@ class UnofficialQQBotSDK:
except:
print("heartbeat error.")
- async def handle_msg(self, ws: WebSocketClientProtocol, msg: dict):
+ async def __handle_msg(self, ws: WebSocketClientProtocol, msg: dict):
if msg['op'] == 10:
- # hello
- # 创建心跳任务
- print("hello.")
- asyncio.get_event_loop().create_task(self.behav_heartbeat(ws, msg['d']['heartbeat_interval'] / 1000))
+ asyncio.get_event_loop().create_task(self.__behav_heartbeat(ws, msg['d']['heartbeat_interval'] / 1000))
# 鉴权,获得session
await ws.send(json.dumps({
"op": 2,
@@ -77,36 +129,60 @@ class UnofficialQQBotSDK:
}
}
}))
-
if msg['op'] == 0:
# ready
- print("ready.")
data = msg['d']
- print(data)
+ event_typ: str = msg['t'] if 't' in msg else None
+ if event_typ == 'GROUP_AT_MESSAGE_CREATE':
+ if 'GroupMessage' in self.events:
+ coro = self.events['GroupMessage']
+ else:
+ return
+ message_chain = MessageChain()
+ message_chain.append(PlainText(type="Plain", text=data['content']))
+ group_message = Message(
+ type='GroupMessage',
+ user_id=data['author']['id'],
+ member_openid=data['author']['member_openid'],
+ message_id=data['id'],
+ group_id=data['group_id'],
+ group_openid=data['group_openid'],
+ content=data['content'],
+ # 2023-11-24T19:51:11+08:00
+ time=int(datetime.datetime.strptime(data['timestamp'], "%Y-%m-%dT%H:%M:%S%z").timestamp()),
+ sender=Sender(
+ user_id=data['author']['id'],
+ member_openid=data['author']['member_openid']
+ ),
+ message=message_chain
+ )
+ await coro(self, group_message)
- if 'group_openid' in data:
- group_openid = data['group_openid']
- message_str = data['content'].strip()
- message_id = data['id']
- # 发送消息
- requests.post(self.OPENAPI_BASE_URL + f"/v2/groups/{group_openid}/messages", headers=self.auth_header(), json={
- "content": message_str,
- "message_type": 0,
- "msg_id": message_id
- })
+ async def send(self, message: Message, message_chain: MessageChain) -> None:
+ # todo: 消息链转换支持更多类型。
+ plain_text = ""
+ for i in message_chain:
+ if isinstance(i, PlainText):
+ plain_text += i.text
+ requests.post(self.OPENAPI_BASE_URL + f"/v2/groups/{message.group_openid}/messages", headers=self.__auth_header(), json={
+ "content": plain_text,
+ "message_type": 0,
+ "msg_id": message.message_id
+ })
- async def ws_client(self):
+ async def __ws_client(self):
self.s = 0
async with websockets.connect(self.wss_endpoint) as websocket:
- print("ws connected.")
while True:
msg = await websocket.recv()
msg = json.loads(msg)
if 's' in msg:
self.s = msg['s']
- print("recv: " + str(msg))
- await self.handle_msg(websocket, msg)
-
+ await self.__handle_msg(websocket, msg)
-if __name__ == "__main__":
- UnofficialQQBotSDK("", "")
\ No newline at end of file
+ def on(self, event: str) -> None:
+ def wrapper(func: Awaitable):
+ if inspect.iscoroutinefunction(func) == False:
+ raise TypeError("func must be a coroutine function")
+ self.events[event] = func
+ return wrapper
\ No newline at end of file
From 89ed31a888131f923b1afa7452fb137c672f1d04 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 25 Nov 2023 11:55:26 +0800
Subject: [PATCH 05/13] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9C=A8cmd=5F?=
=?UTF-8?q?config=E4=B8=AD=E8=AE=BE=E7=BD=AEllm=5Fenv=5Fprompt=E6=9D=A5?=
=?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E7=8E=AF=E5=A2=83=E6=8F=90=E7=A4=BA?=
=?UTF-8?q?=E8=AF=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cores/qqbot/core.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py
index d77dc5a1a..61474e52c 100644
--- a/cores/qqbot/core.py
+++ b/cores/qqbot/core.py
@@ -117,6 +117,7 @@ cc.init_attributes(["other_admins"], [])
cc.init_attributes(["CHATGPT_BASE_URL"], "")
cc.init_attributes(["qqbot_appid"], "")
cc.init_attributes(["qqbot_secret"], "")
+cc.init_attributes(["llm_env_prompt"], "> hint: 末尾根据内容和心情添加 1-2 个emoji")
# cc.init_attributes(["qq_forward_mode"], False)
# QQ机器人
@@ -692,7 +693,7 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak
qq_msg = qq_msg[3:]
web_sch_flag = True
else:
- qq_msg += "> hint: 末尾根据内容和心情添加1-2个emoji"
+ qq_msg += cc.get("llm_env_prompt", "")
if chosen_provider == REV_CHATGPT or chosen_provider == OPENAI_OFFICIAL:
if _global_object.web_search or web_sch_flag:
official_fc = chosen_provider == OPENAI_OFFICIAL
From 450dd34f4d271b95db9c7f685d861523bebcd33d Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 25 Nov 2023 11:59:39 +0800
Subject: [PATCH 06/13] =?UTF-8?q?perf:=20dump=20=E9=85=8D=E7=BD=AE?=
=?UTF-8?q?=E6=97=B6=E5=85=B3=E9=97=AD=E5=BC=BA=E5=88=B6ascii?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
util/cmd_config.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/util/cmd_config.py b/util/cmd_config.py
index 7ec087f6b..d5fc5cdcc 100644
--- a/util/cmd_config.py
+++ b/util/cmd_config.py
@@ -6,7 +6,7 @@ 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)
+ json.dump({}, f, indent=4, ensure_ascii=False)
f.flush()
class CmdConfig():
@@ -34,7 +34,7 @@ class CmdConfig():
d = json.load(f)
d[key] = value
with open(cpath, "w", encoding="utf-8") as f:
- json.dump(d, f, indent=4)
+ json.dump(d, f, indent=4, ensure_ascii=False)
f.flush()
@staticmethod
@@ -49,5 +49,5 @@ class CmdConfig():
_tag = True
if _tag:
with open(cpath, "w", encoding="utf-8") as f:
- json.dump(d, f, indent=4)
+ json.dump(d, f, indent=4, ensure_ascii=False)
f.flush()
\ No newline at end of file
From 5a57526aabccb477750eaf721e943f3fc2e93532 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 25 Nov 2023 19:56:11 +0800
Subject: [PATCH 07/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=85=8D?=
=?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6BOM=E7=9A=84=E4=B8=80=E4=BA=9B?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
util/cmd_config.py | 24 ++++++++++++++----------
util/gplugin.py | 2 +-
2 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/util/cmd_config.py b/util/cmd_config.py
index d5fc5cdcc..0312505ee 100644
--- a/util/cmd_config.py
+++ b/util/cmd_config.py
@@ -40,14 +40,18 @@ class CmdConfig():
@staticmethod
def init_attributes(keys: list, init_val = ""):
check_exist()
+ conf_str = ''
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, ensure_ascii=False)
- f.flush()
\ No newline at end of file
+ conf_str = f.read()
+ if conf_str.startswith(u'/ufeff'):
+ conf_str = conf_str.encode('utf8')[3:].decode('utf8')
+ d = json.loads(conf_str)
+ _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, ensure_ascii=False)
+ f.flush()
\ No newline at end of file
diff --git a/util/gplugin.py b/util/gplugin.py
index 0c8a455a4..5795179ff 100644
--- a/util/gplugin.py
+++ b/util/gplugin.py
@@ -259,7 +259,7 @@ def web_search(question, provider: Provider, session_id, official_fc=False):
if has_func:
provider.forget(session_id)
- question3 = f"""请你用活泼的语气回答`{question}`问题。\n以下是相关材料,请直接拿此材料针对问题进行总结回答。在引文末加上参考链接的标号,如` [1]`;在文章末尾加上各参考链接,如`[1] <标题> <网址>`;不要提到任何函数调用的信息;在总结的末尾加上 1-2 个相关的emoji。```\n{function_invoked_ret}\n```\n"""
+ question3 = f"""请你用活泼的语气回答`{question}`问题。\n以下是相关材料,请直接拿此材料针对问题进行总结回答。在文章末尾加上各参考链接,如`[1]
`;不要提到任何函数调用的信息;在总结的末尾加上1或2个相关的emoji。```\n{function_invoked_ret}\n```\n"""
gu.log(f"web_search: {question3}", tag="web_search", level=gu.LEVEL_DEBUG, max_len=99999)
_c = 0
while _c < 3:
From 311178189f89e24be658553a499e1d4ab7c537ed Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 25 Nov 2023 20:09:21 +0800
Subject: [PATCH 08/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=AA?=
=?UTF-8?q?=E6=9C=9F=E6=9C=9B=E7=9A=84QQ=E7=BE=A4BOT=E5=90=AF=E5=8A=A8?=
=?UTF-8?q?=E5=92=8C=E6=96=87=E4=BB=B6BOM=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cores/qqbot/core.py | 2 +-
util/cmd_config.py | 14 +++++++-------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py
index 61474e52c..0c8345f81 100644
--- a/cores/qqbot/core.py
+++ b/cores/qqbot/core.py
@@ -372,7 +372,7 @@ def initBot(cfg, prov):
global qq_bot, qqbot_loop
qqbot_loop = asyncio.new_event_loop()
- if cc.get("qqbot_appid", None) is not None and cc.get("qqbot_secret", None) is not None:
+ if cc.get("qqbot_appid", '') != '' and cc.get("qqbot_secret", '') != '':
gu.log("- 启用QQ群机器人 -", gu.LEVEL_INFO)
thread_inst = threading.Thread(target=run_qqbot, args=(qqbot_loop, qq_bot,), daemon=False)
thread_inst.start()
diff --git a/util/cmd_config.py b/util/cmd_config.py
index 0312505ee..03d81c43c 100644
--- a/util/cmd_config.py
+++ b/util/cmd_config.py
@@ -5,7 +5,7 @@ cpath = "cmd_config.json"
def check_exist():
if not os.path.exists(cpath):
- with open(cpath, "w", encoding="utf-8") as f:
+ with open(cpath, "w", encoding="utf-8-sig") as f:
json.dump({}, f, indent=4, ensure_ascii=False)
f.flush()
@@ -14,7 +14,7 @@ class CmdConfig():
@staticmethod
def get(key, default=None):
check_exist()
- with open(cpath, "r", encoding="utf-8") as f:
+ with open(cpath, "r", encoding="utf-8-sig") as f:
d = json.load(f)
if key in d:
return d[key]
@@ -24,16 +24,16 @@ class CmdConfig():
@staticmethod
def get_all():
check_exist()
- with open(cpath, "r", encoding="utf-8") as f:
+ with open(cpath, "r", encoding="utf-8-sig") as f:
return json.load(f)
@staticmethod
def put(key, value):
check_exist()
- with open(cpath, "r", encoding="utf-8") as f:
+ with open(cpath, "r", encoding="utf-8-sig") as f:
d = json.load(f)
d[key] = value
- with open(cpath, "w", encoding="utf-8") as f:
+ with open(cpath, "w", encoding="utf-8-sig") as f:
json.dump(d, f, indent=4, ensure_ascii=False)
f.flush()
@@ -41,7 +41,7 @@ class CmdConfig():
def init_attributes(keys: list, init_val = ""):
check_exist()
conf_str = ''
- with open(cpath, "r", encoding="utf-8") as f:
+ with open(cpath, "r", encoding="utf-8-sig") as f:
conf_str = f.read()
if conf_str.startswith(u'/ufeff'):
conf_str = conf_str.encode('utf8')[3:].decode('utf8')
@@ -52,6 +52,6 @@ class CmdConfig():
d[k] = init_val
_tag = True
if _tag:
- with open(cpath, "w", encoding="utf-8") as f:
+ with open(cpath, "w", encoding="utf-8-sig") as f:
json.dump(d, f, indent=4, ensure_ascii=False)
f.flush()
\ No newline at end of file
From 23882bcb8e3ac9c24c8dd1d3f5d416d5244b6db2 Mon Sep 17 00:00:00 2001
From: Soulter <37870767+Soulter@users.noreply.github.com>
Date: Sat, 25 Nov 2023 23:09:15 +0800
Subject: [PATCH 09/13] Update README.md
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index d4f935dcd..6312415ed 100644
--- a/README.md
+++ b/README.md
@@ -31,9 +31,9 @@
- OpenAI GPT-3模型(原生支持)
- OpenAI GPT-3.5模型(原生支持)
- OpenAI GPT-4模型(原生支持)
-- ChatGPT网页版 GPT-3.5模型(免费,原生支持)
-- ChatGPT网页版 GPT-4模型(需订阅Plus账户,原生支持)
-- Bing(免费,原生支持)
+~~- 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)支持)
From d602041ad00b5b295299c01d40199b63aa0e4092 Mon Sep 17 00:00:00 2001
From: Soulter <37870767+Soulter@users.noreply.github.com>
Date: Sat, 25 Nov 2023 23:10:54 +0800
Subject: [PATCH 10/13] Update README.md
---
README.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 6312415ed..4f846c4cd 100644
--- a/README.md
+++ b/README.md
@@ -26,14 +26,14 @@
🌍支持的AI语言模型一览:
-**文字模型**
+**文字模型/图片理解**
- OpenAI GPT-3模型(原生支持)
- OpenAI GPT-3.5模型(原生支持)
- OpenAI GPT-4模型(原生支持)
-~~- ChatGPT网页版 GPT-3.5模型(免费,原生支持)~~
-~~- ChatGPT网页版 GPT-4模型(需订阅Plus账户,原生支持)~~
-~~- Bing(免费,原生支持)~~
+- ~~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)支持)
@@ -44,9 +44,9 @@
🌍机器人支持的能力一览:
-- 同时部署机器人到QQ和QQ频道
+- 同时部署机器人到 QQ 和 QQ 频道
- 大模型对话
-- 大模型网页搜索能力 **(目前仅支持OpenAI系的模型,最新版本下使用web on指令打开)**
+- 大模型网页搜索能力 **(目前仅支持OpenAI系的模型,最新版本下使用 web on 指令打开)**
- 插件安装(在QQ或QQ频道聊天框内输入`plugin`了解详情)
- 回复文字图片渲染(以图片markdown格式回复,**大幅度降低被风控概率**,需手动在`cmd_config.json`内开启qq_pic_mode)
- 人格设置
From be1f8e70758a17bf1d788cf81bb67705fadd257a Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Thu, 30 Nov 2023 12:06:37 +0800
Subject: [PATCH 11/13] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9C=A8?=
=?UTF-8?q?=E5=91=BD=E4=BB=A4=E8=A1=8C=E6=93=8D=E4=BD=9Cbot=20fix:=20?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=20windows=20=E4=B8=8B=20ctrl+c=20=E4=B8=8D?=
=?UTF-8?q?=E8=83=BD=E9=80=80=E5=87=BA=E7=A8=8B=E5=BA=8F=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
addons/plugins/helloworld/helloworld.py | 5 +
cores/qqbot/core.py | 182 ++++++++++++++----------
main.py | 6 +-
model/command/command.py | 2 -
model/provider/openai_official.py | 2 +-
model/provider/rev_edgegpt.py | 6 +-
util/cmd_config.py | 16 ++-
7 files changed, 134 insertions(+), 85 deletions(-)
diff --git a/addons/plugins/helloworld/helloworld.py b/addons/plugins/helloworld/helloworld.py
index c76d4a9f9..5e8dccb98 100644
--- a/addons/plugins/helloworld/helloworld.py
+++ b/addons/plugins/helloworld/helloworld.py
@@ -50,6 +50,11 @@ class HelloWorldPlugin:
return True, tuple([True, "Hello World!!", "helloworld"])
else:
return False, None
+ else:
+ """
+ 其他平台处理逻辑
+ """
+ return False, None
"""
帮助函数,当用户输入 plugin v 插件名称 时,会调用此函数,返回帮助信息
返回参数要求(必填):dict{
diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py
index 0c8345f81..e4f39f7bb 100644
--- a/cores/qqbot/core.py
+++ b/cores/qqbot/core.py
@@ -96,28 +96,32 @@ client = None
# QQ群机器人
PLATFROM_QQBOT = 'qqbot'
-# 配置
-cc.init_attributes(["qq_forward_threshold"], 200)
-cc.init_attributes(["qq_welcome"], "欢迎加入本群!\n欢迎给https://github.com/Soulter/QQChannelChatGPT项目一个Star😊~\n输入help查看帮助~\n")
-cc.init_attributes(["bing_proxy"], "")
-cc.init_attributes(["qq_pic_mode"], False)
-cc.init_attributes(["rev_chatgpt_model"], "")
-cc.init_attributes(["rev_chatgpt_plugin_ids"], [])
-cc.init_attributes(["rev_chatgpt_PUID"], "")
-cc.init_attributes(["rev_chatgpt_unverified_plugin_domains"], [])
-cc.init_attributes(["gocq_host"], "127.0.0.1")
-cc.init_attributes(["gocq_http_port"], 5700)
-cc.init_attributes(["gocq_websocket_port"], 6700)
-cc.init_attributes(["gocq_react_group"], True)
-cc.init_attributes(["gocq_react_guild"], True)
-cc.init_attributes(["gocq_react_friend"], True)
-cc.init_attributes(["gocq_react_group_increase"], True)
-cc.init_attributes(["gocq_qqchan_admin"], "")
-cc.init_attributes(["other_admins"], [])
-cc.init_attributes(["CHATGPT_BASE_URL"], "")
-cc.init_attributes(["qqbot_appid"], "")
-cc.init_attributes(["qqbot_secret"], "")
-cc.init_attributes(["llm_env_prompt"], "> hint: 末尾根据内容和心情添加 1-2 个emoji")
+# CLI
+PLATFORM_CLI = 'cli'
+
+# 加载默认配置
+cc.init_attributes("qq_forward_threshold", 200)
+cc.init_attributes("qq_welcome", "欢迎加入本群!\n欢迎给https://github.com/Soulter/QQChannelChatGPT项目一个Star😊~\n输入help查看帮助~\n")
+cc.init_attributes("bing_proxy", "")
+cc.init_attributes("qq_pic_mode", False)
+cc.init_attributes("rev_chatgpt_model", "")
+cc.init_attributes("rev_chatgpt_plugin_ids", [])
+cc.init_attributes("rev_chatgpt_PUID", "")
+cc.init_attributes("rev_chatgpt_unverified_plugin_domains", [])
+cc.init_attributes("gocq_host", "127.0.0.1")
+cc.init_attributes("gocq_http_port", 5700)
+cc.init_attributes("gocq_websocket_port", 6700)
+cc.init_attributes("gocq_react_group", True)
+cc.init_attributes("gocq_react_guild", True)
+cc.init_attributes("gocq_react_friend", True)
+cc.init_attributes("gocq_react_group_increase", True)
+cc.init_attributes("gocq_qqchan_admin", "")
+cc.init_attributes("other_admins", [])
+cc.init_attributes("CHATGPT_BASE_URL", "")
+cc.init_attributes("qqbot_appid", "")
+cc.init_attributes("qqbot_secret", "")
+cc.init_attributes("llm_env_prompt", "> hint: 末尾根据内容和心情添加 1-2 个emoji")
+cc.init_attributes("default_personality_name", "")
# cc.init_attributes(["qq_forward_mode"], False)
# QQ机器人
@@ -219,6 +223,9 @@ def initBot(cfg, prov):
global frequency_count, frequency_time, announcement, direct_message_mode, version
global keywords, _global_object
+ _event_loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(_event_loop)
+
# 初始化 global_object
_global_object = GlobalObject()
_global_object.base_config = cfg
@@ -374,7 +381,7 @@ def initBot(cfg, prov):
qqbot_loop = asyncio.new_event_loop()
if cc.get("qqbot_appid", '') != '' and cc.get("qqbot_secret", '') != '':
gu.log("- 启用QQ群机器人 -", gu.LEVEL_INFO)
- thread_inst = threading.Thread(target=run_qqbot, args=(qqbot_loop, qq_bot,), daemon=False)
+ thread_inst = threading.Thread(target=run_qqbot, args=(qqbot_loop, qq_bot,), daemon=True)
thread_inst.start()
@@ -386,7 +393,7 @@ def initBot(cfg, prov):
global gocq_app, gocq_loop
gocq_loop = asyncio.new_event_loop()
gocq_bot = QQ(True, cc, gocq_loop)
- thread_inst = threading.Thread(target=run_gocq_bot, args=(gocq_loop, gocq_bot, gocq_app), daemon=False)
+ thread_inst = threading.Thread(target=run_gocq_bot, args=(gocq_loop, gocq_bot, gocq_app), daemon=True)
thread_inst.start()
else:
gocq_bot = QQ(False)
@@ -404,7 +411,7 @@ def initBot(cfg, prov):
qqchannel_bot = QQChan()
qqchan_loop = asyncio.new_event_loop()
_global_object.platform_qqchan = qqchannel_bot
- thread_inst = threading.Thread(target=run_qqchan_bot, args=(cfg, qqchan_loop, qqchannel_bot), daemon=False)
+ thread_inst = threading.Thread(target=run_qqchan_bot, args=(cfg, qqchan_loop, qqchannel_bot), daemon=True)
thread_inst.start()
# thread.join()
@@ -412,7 +419,31 @@ def initBot(cfg, prov):
input("[System-Error] 没有启用/成功启用任何机器人,程序退出")
exit()
- thread_inst.join()
+ gu.log("🎉 项目启动完成。")
+
+ # thread_inst.join()
+ asyncio.get_event_loop().run_until_complete(cli())
+
+async def cli():
+ time.sleep(1)
+ while True:
+ prompt = input(">>> ")
+ if prompt == "":
+ continue
+ ngm = NakuruGuildMessage()
+ ngm.channel_id = 6180
+ ngm.user_id = 6180
+ ngm.message = [Plain(prompt)]
+ ngm.type = "GuildMessage"
+ ngm.self_id = 6180
+ ngm.self_tiny_id = 6180
+ ngm.guild_id = 6180
+ ngm.sender = NakuruGuildMember()
+ ngm.sender.tiny_id = 6180
+ ngm.sender.user_id = 6180
+ ngm.sender.nickname = "CLI"
+ ngm.sender.role = 0
+ await oper_msg(ngm, True, PLATFORM_CLI)
'''
运行QQ频道机器人
@@ -443,9 +474,12 @@ def run_qqchan_bot(cfg, loop, qqchannel_bot: QQChan):
def run_gocq_bot(loop, gocq_bot, gocq_app):
asyncio.set_event_loop(loop)
gu.log("正在检查本地GO-CQHTTP连接...端口5700, 6700", tag="QQ")
+ noticed = False
while True:
if not gu.port_checker(5700, cc.get("gocq_host", "127.0.0.1")) or not gu.port_checker(6700, cc.get("gocq_host", "127.0.0.1")):
- gu.log("与GO-CQHTTP通信失败, 请检查GO-CQHTTP是否启动并正确配置。5秒后自动重试。", gu.LEVEL_CRITICAL, tag="QQ")
+ if not noticed:
+ noticed = True
+ gu.log("与GO-CQHTTP通信失败, 请检查GO-CQHTTP是否启动并正确配置。程序会每隔 5s 自动重试。", gu.LEVEL_CRITICAL, tag="QQ")
time.sleep(5)
else:
gu.log("检查完毕,未发现问题。", tag="QQ")
@@ -509,6 +543,8 @@ async def send_message(platform, message, res, session_id = None):
message_chain = MessageChain()
message_chain.parse_from_nakuru(res)
await qq_bot.send(message, message_chain)
+ if platform == PLATFORM_CLI:
+ print(res)
async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, NakuruGuildMessage],
group: bool=False,
@@ -533,59 +569,59 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak
with_tag = False # 是否带有昵称
- if platform == PLATFORM_QQCHAN or platform == PLATFROM_QQBOT:
+ if platform == PLATFORM_QQCHAN or platform == PLATFROM_QQBOT or platform == PLATFORM_CLI:
with_tag = True
- if platform == PLATFORM_GOCQ or platform == PLATFORM_QQCHAN or platform == PLATFROM_QQBOT:
- _len = 0
- for i in message.message:
- if isinstance(i, Plain) or isinstance(i, PlainText):
- qq_msg += str(i.text).strip()
- if isinstance(i, At):
- if message.type == "GuildMessage":
- if i.qq == message.user_id or i.qq == message.self_tiny_id:
- with_tag = True
- if message.type == "FriendMessage":
- if i.qq == message.self_id:
- with_tag = True
- if message.type == "GroupMessage":
- if i.qq == message.self_id:
- with_tag = True
-
- for i in _global_object.nick:
- if i != '' and qq_msg.startswith(i):
- _len = len(i)
- with_tag = True
- break
- qq_msg = qq_msg[_len:].strip()
-
- gu.log(f"收到消息:{qq_msg}", gu.LEVEL_INFO, tag="QQ")
- user_id = message.user_id
-
- if group:
- # 适配GO-CQHTTP的频道功能
+ _len = 0
+ for i in message.message:
+ if isinstance(i, Plain) or isinstance(i, PlainText):
+ qq_msg += str(i.text).strip()
+ if isinstance(i, At):
if message.type == "GuildMessage":
- session_id = message.channel_id
- else:
- session_id = message.group_id
- else:
+ if i.qq == message.user_id or i.qq == message.self_tiny_id:
+ with_tag = True
+ if message.type == "FriendMessage":
+ if i.qq == message.self_id:
+ with_tag = True
+ if message.type == "GroupMessage":
+ if i.qq == message.self_id:
+ with_tag = True
+
+ for i in _global_object.nick:
+ if i != '' and qq_msg.startswith(i):
+ _len = len(i)
with_tag = True
- session_id = message.user_id
- role = "member"
+ break
+ qq_msg = qq_msg[_len:].strip()
+ gu.log(f"收到消息:{qq_msg}", gu.LEVEL_INFO, tag="QQ")
+ user_id = message.user_id
+
+ if group:
+ # 适配GO-CQHTTP的频道功能
if message.type == "GuildMessage":
- sender_id = str(message.sender.tiny_id)
+ session_id = message.channel_id
else:
- sender_id = str(message.sender.user_id)
- if sender_id == _global_object.admin_qq or \
- sender_id == _global_object.admin_qqchan or \
- sender_id in cc.get("other_admins", []) or \
- sender_id == cc.get("gocq_qqchan_admin", ""):
- # gu.log("检测到管理员身份", gu.LEVEL_INFO, tag="GOCQ")
- role = "admin"
- if _global_object.uniqueSession:
- # 独立会话时,一个用户一个session
- session_id = sender_id
+ session_id = message.group_id
+ else:
+ with_tag = True
+ session_id = message.user_id
+
+ if message.type == "GuildMessage":
+ sender_id = str(message.sender.tiny_id)
+ else:
+ sender_id = str(message.sender.user_id)
+ if sender_id == _global_object.admin_qq or \
+ sender_id == _global_object.admin_qqchan or \
+ sender_id in cc.get("other_admins", []) or \
+ sender_id == cc.get("gocq_qqchan_admin", "") or \
+ platform == PLATFORM_CLI:
+ role = "admin"
+
+ if _global_object.uniqueSession:
+ # 独立会话时,一个用户一个 session
+ session_id = sender_id
+
if qq_msg == "":
await send_message(platform, message, f"Hi~", session_id=session_id)
diff --git a/main.py b/main.py
index 635205e0a..082d835cb 100644
--- a/main.py
+++ b/main.py
@@ -2,6 +2,7 @@ import os, sys
from pip._internal import main as pipmain
import warnings
import traceback
+import threading
warnings.filterwarnings("ignore")
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
@@ -128,4 +129,7 @@ if __name__ == "__main__":
except BaseException as e:
print(e)
print(f"[System-err] Replit Web保活服务启动失败:{str(e)}")
- main()
+
+ t = threading.Thread(target=main, daemon=False)
+ t.start()
+ t.join()
diff --git a/model/command/command.py b/model/command/command.py
index 7fc068c61..3a3689d5e 100644
--- a/model/command/command.py
+++ b/model/command/command.py
@@ -126,8 +126,6 @@ class Command:
fail_rec = ""
if plugins is None:
return False, "未找到任何插件模块"
-
- print(plugins)
for plugin in plugins:
try:
diff --git a/model/provider/openai_official.py b/model/provider/openai_official.py
index 405fa930b..2e0b36a95 100644
--- a/model/provider/openai_official.py
+++ b/model/provider/openai_official.py
@@ -35,7 +35,7 @@ class ProviderOpenAIOfficial(Provider):
self.api_base = None
if 'api_base' in cfg and cfg['api_base'] != 'none' and cfg['api_base'] != '':
self.api_base = cfg['api_base']
- print(f"设置 api_base 为: {self.api_base}")
+ gu.log(f"设置 api_base 为: {self.api_base}")
# openai client
self.client = OpenAI(
api_key=self.key_list[0],
diff --git a/model/provider/rev_edgegpt.py b/model/provider/rev_edgegpt.py
index ce122f28d..aadb81179 100644
--- a/model/provider/rev_edgegpt.py
+++ b/model/provider/rev_edgegpt.py
@@ -1,15 +1,15 @@
from model.provider.provider import Provider
-# from EdgeGPT import Chatbot, ConversationStyle
+from EdgeGPT import Chatbot, ConversationStyle
import json
import os
from util import general_utils as gu
from util.cmd_config import CmdConfig as cc
import time
-from EdgeGPT.EdgeUtils import Query, Cookie
-from EdgeGPT.EdgeGPT import Chatbot as EdgeChatbot, ConversationStyle, NotAllowedToAccess
class ProviderRevEdgeGPT(Provider):
def __init__(self):
+ raise Exception("Bing 逆向已停止维护,不可用,请使用 ChatGPT 官方 API。")
+
self.busy = False
self.wait_stack = []
with open('./cookies.json', 'r') as f:
diff --git a/util/cmd_config.py b/util/cmd_config.py
index 03d81c43c..396ad721f 100644
--- a/util/cmd_config.py
+++ b/util/cmd_config.py
@@ -1,5 +1,6 @@
import os
import json
+from typing import Union
cpath = "cmd_config.json"
@@ -38,7 +39,7 @@ class CmdConfig():
f.flush()
@staticmethod
- def init_attributes(keys: list, init_val = ""):
+ def init_attributes(key: Union[str, list], init_val = ""):
check_exist()
conf_str = ''
with open(cpath, "r", encoding="utf-8-sig") as f:
@@ -47,10 +48,15 @@ class CmdConfig():
conf_str = conf_str.encode('utf8')[3:].decode('utf8')
d = json.loads(conf_str)
_tag = False
- for k in keys:
- if k not in d:
- d[k] = init_val
- _tag = True
+
+ if isinstance(key, str):
+ d[key] = init_val
+ _tag = True
+ elif isinstance(key, list):
+ for k in key:
+ if k not in d:
+ d[k] = init_val
+ _tag = True
if _tag:
with open(cpath, "w", encoding="utf-8-sig") as f:
json.dump(d, f, indent=4, ensure_ascii=False)
From 3df387995498c643db5da84efd0aef69b3119bfd Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Thu, 30 Nov 2023 12:46:29 +0800
Subject: [PATCH 12/13] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE=E9=BB=98=E8=AE=A4=E4=BA=BA=E6=A0=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cores/qqbot/core.py | 45 ++++++++++++++++++++-----------
cores/qqbot/global_object.py | 2 ++
model/command/openai_official.py | 8 +++---
model/provider/openai_official.py | 29 ++++++++++++++++++--
model/provider/provider.py | 2 +-
model/provider/rev_chatgpt.py | 7 ++++-
util/cmd_config.py | 5 ++--
7 files changed, 72 insertions(+), 26 deletions(-)
diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py
index e4f39f7bb..a78367184 100644
--- a/cores/qqbot/core.py
+++ b/cores/qqbot/core.py
@@ -121,7 +121,7 @@ cc.init_attributes("CHATGPT_BASE_URL", "")
cc.init_attributes("qqbot_appid", "")
cc.init_attributes("qqbot_secret", "")
cc.init_attributes("llm_env_prompt", "> hint: 末尾根据内容和心情添加 1-2 个emoji")
-cc.init_attributes("default_personality_name", "")
+cc.init_attributes("default_personality_str", "")
# cc.init_attributes(["qq_forward_mode"], False)
# QQ机器人
@@ -419,6 +419,15 @@ def initBot(cfg, prov):
input("[System-Error] 没有启用/成功启用任何机器人,程序退出")
exit()
+ default_personality_str = cc.get("default_personality_str", "")
+ if default_personality_str == "":
+ _global_object.default_personality = None
+ else:
+ _global_object.default_personality = {
+ "name": "default",
+ "prompt": default_personality_str,
+ }
+
gu.log("🎉 项目启动完成。")
# thread_inst.join()
@@ -430,21 +439,25 @@ async def cli():
prompt = input(">>> ")
if prompt == "":
continue
- ngm = NakuruGuildMessage()
- ngm.channel_id = 6180
- ngm.user_id = 6180
- ngm.message = [Plain(prompt)]
- ngm.type = "GuildMessage"
- ngm.self_id = 6180
- ngm.self_tiny_id = 6180
- ngm.guild_id = 6180
- ngm.sender = NakuruGuildMember()
- ngm.sender.tiny_id = 6180
- ngm.sender.user_id = 6180
- ngm.sender.nickname = "CLI"
- ngm.sender.role = 0
+ ngm = await cli_pack_message(prompt)
await oper_msg(ngm, True, PLATFORM_CLI)
+async def cli_pack_message(prompt: str) -> NakuruGuildMessage:
+ ngm = NakuruGuildMessage()
+ ngm.channel_id = 6180
+ ngm.user_id = 6180
+ ngm.message = [Plain(prompt)]
+ ngm.type = "GuildMessage"
+ ngm.self_id = 6180
+ ngm.self_tiny_id = 6180
+ ngm.guild_id = 6180
+ ngm.sender = NakuruGuildMember()
+ ngm.sender.tiny_id = 6180
+ ngm.sender.user_id = 6180
+ ngm.sender.nickname = "CLI"
+ ngm.sender.role = 0
+ return ngm
+
'''
运行QQ频道机器人
'''
@@ -729,13 +742,13 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak
qq_msg = qq_msg[3:]
web_sch_flag = True
else:
- qq_msg += cc.get("llm_env_prompt", "")
+ qq_msg += " " + cc.get("llm_env_prompt", "")
if chosen_provider == REV_CHATGPT or chosen_provider == OPENAI_OFFICIAL:
if _global_object.web_search or web_sch_flag:
official_fc = chosen_provider == OPENAI_OFFICIAL
chatgpt_res = gplugin.web_search(qq_msg, llm_instance[chosen_provider], session_id, official_fc)
else:
- chatgpt_res = str(llm_instance[chosen_provider].text_chat(qq_msg, session_id, image_url))
+ chatgpt_res = str(llm_instance[chosen_provider].text_chat(qq_msg, session_id, image_url, default_personality = _global_object.default_personality))
elif chosen_provider == REV_EDGEGPT:
res, res_code = await llm_instance[chosen_provider].text_chat(qq_msg, platform)
if res_code == 0: # bing不想继续话题,重置会话后重试。
diff --git a/cores/qqbot/global_object.py b/cores/qqbot/global_object.py
index cc6873450..d9871429e 100644
--- a/cores/qqbot/global_object.py
+++ b/cores/qqbot/global_object.py
@@ -26,6 +26,7 @@ class GlobalObject:
cnt_total: int
platform_qq: QQ
platform_qqchan: QQChan
+ default_personality: dict
def __init__(self):
self.nick = None # gocq 的昵称
@@ -39,6 +40,7 @@ class GlobalObject:
self.cnt_total = 0
self.platform_qq = None
self.platform_qqchan = None
+ self.default_personality = None
class AstrMessageEvent():
message_str: str # 纯消息字符串
diff --git a/model/command/openai_official.py b/model/command/openai_official.py
index aa5247e6f..8d919a989 100644
--- a/model/command/openai_official.py
+++ b/model/command/openai_official.py
@@ -189,7 +189,7 @@ class CommandOpenAIOfficial(Command):
def unset(self, session_id: str):
if self.provider is None:
return False, "未启动OpenAI ChatGPT语言模型.", "unset"
- self.provider.now_personality = {}
+ self.provider.curr_personality = {}
self.provider.forget(session_id)
return True, "已清除人格并重置历史记录。", "unset"
@@ -200,7 +200,7 @@ class CommandOpenAIOfficial(Command):
if len(l) == 1:
return True, f"【人格文本由PlexPt开源项目awesome-chatgpt-pr \
ompts-zh提供】\n设置人格: \n/set 人格名。例如/set 编剧\n人格列表: /set list\n人格详细信息: \
- /set view 人格名\n自定义人格: /set 人格文本\n重置会话(清除人格): /reset\n重置会话(保留人格): /reset p\n【当前人格】: {str(self.provider.now_personality)}", "set"
+ /set view 人格名\n自定义人格: /set 人格文本\n重置会话(清除人格): /reset\n重置会话(保留人格): /reset p\n【当前人格】: {str(self.provider.curr_personality)}", "set"
elif l[1] == "list":
msg = "人格列表:\n"
for key in personalities.keys():
@@ -221,7 +221,7 @@ class CommandOpenAIOfficial(Command):
else:
ps = l[1].strip()
if ps in personalities:
- self.provider.now_personality = {
+ self.provider.curr_personality = {
'name': ps,
'prompt': personalities[ps]
}
@@ -243,7 +243,7 @@ class CommandOpenAIOfficial(Command):
self.personality_str = message
return True, f"人格{ps}已设置。", "set"
else:
- self.provider.now_personality = {
+ self.provider.curr_personality = {
'name': '自定义人格',
'prompt': ps
}
diff --git a/model/provider/openai_official.py b/model/provider/openai_official.py
index 2e0b36a95..9f469501b 100644
--- a/model/provider/openai_official.py
+++ b/model/provider/openai_official.py
@@ -83,7 +83,7 @@ class ProviderOpenAIOfficial(Provider):
threading.Thread(target=self.dump_history, daemon=True).start()
# 人格
- self.now_personality = {}
+ self.curr_personality = {}
# 转储历史记录
@@ -109,11 +109,30 @@ class ProviderOpenAIOfficial(Provider):
# 每隔10分钟转储一次
time.sleep(10*self.history_dump_interval)
+ def personality_set(self, default_personality: dict, session_id: str):
+ self.curr_personality = default_personality
+ new_record = {
+ "user": {
+ "role": "user",
+ "content": default_personality['prompt'],
+ },
+ "AI": {
+ "role": "assistant",
+ "content": "好的,接下来我会扮演这个角色。"
+ },
+ 'type': "personality",
+ 'usage_tokens': 0,
+ 'single-tokens': 0
+ }
+ self.session_dict[session_id].append(new_record)
+
+
def text_chat(self, prompt,
session_id = None,
image_url = None,
function_call=None,
- extra_conf: dict = None):
+ extra_conf: dict = None,
+ default_personality: dict = None):
if session_id is None:
session_id = "unknown"
if "unknown" in self.session_dict:
@@ -136,6 +155,12 @@ class ProviderOpenAIOfficial(Provider):
f.flush()
f.close()
+ if len(self.session_dict[session_id]) == 0:
+ # 设置默认人格
+ if default_personality is not None:
+ self.personality_set(default_personality, session_id)
+
+
# 使用 tictoken 截断消息
_encoded_prompt = self.enc.encode(prompt)
if self.openai_model_configs['max_tokens'] < len(_encoded_prompt):
diff --git a/model/provider/provider.py b/model/provider/provider.py
index 23d07f568..a3a77867c 100644
--- a/model/provider/provider.py
+++ b/model/provider/provider.py
@@ -5,7 +5,7 @@ class Provider:
pass
@abc.abstractmethod
- def text_chat(self, prompt, session_id, image_url: None, function_call: None):
+ def text_chat(self, prompt, session_id, image_url: None, function_call: None, extra_conf: dict = None, default_personality: dict = None) -> str:
pass
@abc.abstractmethod
diff --git a/model/provider/rev_chatgpt.py b/model/provider/rev_chatgpt.py
index 24f94b22b..fd1ece956 100644
--- a/model/provider/rev_chatgpt.py
+++ b/model/provider/rev_chatgpt.py
@@ -101,7 +101,12 @@ class ProviderRevChatGPT(Provider):
# print("[RevChatGPT] "+str(resp))
return resp
- def text_chat(self, prompt, session_id = None, image_url = None, function_call=None) -> str:
+ def text_chat(self, prompt,
+ session_id = None,
+ image_url = None,
+ function_call=None,
+ extra_conf: dict = None,
+ default_personality: dict = None) -> str:
# 选择一个人少的账号。
selected_revstat = None
diff --git a/util/cmd_config.py b/util/cmd_config.py
index 396ad721f..9a138829a 100644
--- a/util/cmd_config.py
+++ b/util/cmd_config.py
@@ -50,8 +50,9 @@ class CmdConfig():
_tag = False
if isinstance(key, str):
- d[key] = init_val
- _tag = True
+ if key not in d:
+ d[key] = init_val
+ _tag = True
elif isinstance(key, list):
for k in key:
if k not in d:
From 9bc8ac10fa058aadb2888495cfa0915fc72da710 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 2 Dec 2023 16:19:41 +0800
Subject: [PATCH 13/13] chore: remove some unuseful log
---
model/command/command.py | 2 --
model/platform/qqgroup.py | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/model/command/command.py b/model/command/command.py
index 3a3689d5e..15ef84383 100644
--- a/model/command/command.py
+++ b/model/command/command.py
@@ -71,10 +71,8 @@ class Command:
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") or self.command_start_with(message, "!myid"):
return True, self.get_my_id(message_obj)
if self.command_start_with(message, "nconf") or self.command_start_with(message, "newconf"):
diff --git a/model/platform/qqgroup.py b/model/platform/qqgroup.py
index 842c1f6f3..753689991 100644
--- a/model/platform/qqgroup.py
+++ b/model/platform/qqgroup.py
@@ -99,7 +99,7 @@ class UnofficialQQBotSDK:
def __get_wss_endpoint(self):
res = requests.get(self.OPENAPI_BASE_URL + "/gateway", headers=self.__auth_header())
self.wss_endpoint = res.json()['url']
- print("wss_endpoint: " + self.wss_endpoint)
+ # print("wss_endpoint: " + self.wss_endpoint)
async def __behav_heartbeat(self, ws: WebSocketClientProtocol, t: int):
while True: