From 6be70c8fa9e57ddd74a5a031104130c89ddf6771 Mon Sep 17 00:00:00 2001 From: Milin <417156994@qq.com> Date: Fri, 24 Mar 2023 16:32:23 +0800 Subject: [PATCH 1/2] support poe bots --- README.md | 17 +++++++++++++ adapter/botservice.py | 23 +++++++++++++++-- adapter/chatgpt/web.py | 13 ---------- adapter/google/bard.py | 14 +++-------- adapter/ms/bing.py | 4 --- adapter/openai/api.py | 3 --- adapter/quora/poe.py | 57 ++++++++++++++++++++++++++++++++++++++++++ config.py | 15 +++++++++++ conversation.py | 3 +++ manager/bot.py | 34 +++++++++++++++++++++++-- requirements.txt | 1 + 11 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 adapter/quora/poe.py diff --git a/README.md b/README.md index 36bc53a9..58d77243 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,13 @@ auto_remove_old_conversations = true # === OpenAI 账号部分结束 +# === Poe 账号部分开始 +# 如果你没有 Poe 账号,可以直接删除这部分 +[poe] +[[poe.accounts]] +# 登陆 poe.com 网站后,通过开发者工具查看Cookie获取 +p_b = "V4j***" +# === Poe 账号部分结束 # === Bing 设置部分开始 # 如果你没有 Bing 账号,可以直接删除这部分 @@ -800,6 +807,16 @@ title_pattern="qq-{session_id}" 4. 找到 控制台(或 Console),输入 `document.cookie` 然后回车 5. 复制接下来出现的一段文本,这就是你的 Cookie +### Poe 账号 Cookie 获取方法 + +你需要通过电脑浏览器来获得 Poe Cookie,如果你有别的手段能获得 cookie 的话也是可以的。 + +1. 确认能科学上网 +2. 打开 https://poe.com 并登陆 +3. 按下 F12,打开开发者工具(DevTools) +4. 找到 `应用程序 - 存储` 或 `应用 - 存储` 或 `存储`, 查看Cookie下 https://poe.com 域名下的 "p-b" +5. 复制值即可 + ## 🦊 加载预设 如果你想让机器人自动带上某种聊天风格,可以使用预设功能。 diff --git a/adapter/botservice.py b/adapter/botservice.py index c62807e9..d3e3b9cd 100644 --- a/adapter/botservice.py +++ b/adapter/botservice.py @@ -1,5 +1,7 @@ from typing import Generator +from loguru import logger + class BotAdapter: """定义所有 Chatbot 的通用接口""" @@ -22,8 +24,25 @@ async def rollback(self): ... async def on_reset(self): ... """当会话被重置时,此函数被调用""" - async def preset_ask(self, role: str, text: str): ... - """以预设方式进行提问""" + def use_default_preset_ask(self) -> bool: + """使用默认预设逻辑""" + return False + + async def preset_ask(self, role: str, text: str): + """以预设方式进行提问""" + if self.use_default_preset_ask(): + if role.endswith('bot') or role in ['assistant', 'chatgpt']: + logger.debug(f"[预设] 响应:{text}") + yield text + else: + logger.debug(f"[预设] 发送:{text}") + item = None + async for item in self.ask(text): ... + if item: + logger.debug(f"[预设] Chatbot 回应:{item}") + pass # 不发送 AI 的回应,免得串台 + else: + yield None async def switch_model(self, model_name): ... """切换模型""" diff --git a/adapter/chatgpt/web.py b/adapter/chatgpt/web.py index f483a51e..448d7b22 100644 --- a/adapter/chatgpt/web.py +++ b/adapter/chatgpt/web.py @@ -52,7 +52,6 @@ async def switch_model(self, model_name): # self.current_model = model_name raise Exception("此 AI 暂不支持切换模型的操作!") - async def rollback(self): if len(self.parent_id_prev_queue) > 0: self.parent_id = self.parent_id_prev_queue.pop() @@ -113,17 +112,5 @@ async def ask(self, prompt: str) -> Generator[str, None, None]: raise ConcurrentMessageException() raise e - async def preset_ask(self, role: str, text: str): - if role.endswith('bot') or role in ['assistant', 'chatgpt']: - logger.debug(f"[预设] 响应:{text}") - yield text - else: - logger.debug(f"[预设] 发送:{text}") - item = None - async for item in self.ask(text): ... - if item: - logger.debug(f"[预设] Chatbot 回应:{item}") - pass # 不发送 AI 的回应,免得串台 - def get_queue_info(self): return self.bot.queue diff --git a/adapter/google/bard.py b/adapter/google/bard.py index e2584c9b..e5ba5f2f 100644 --- a/adapter/google/bard.py +++ b/adapter/google/bard.py @@ -82,14 +82,6 @@ async def ask(self, prompt: str) -> Generator[str, None, None]: await self.on_reset() return - async def preset_ask(self, role: str, text: str): - if role.endswith('bot') or role in ['assistant', 'bard']: - logger.debug(f"[预设] 响应:{text}") - yield text - else: - logger.debug(f"[预设] 发送:{text}") - item = None - async for item in self.ask(text): ... - if item: - logger.debug(f"[预设] Chatbot 回应:{item}") - pass # 不发送 AI 的回应,免得串台 + def use_default_preset_ask(self) -> bool: + """使用默认预设逻辑""" + return True diff --git a/adapter/ms/bing.py b/adapter/ms/bing.py index 313f5eca..31fd1fe4 100644 --- a/adapter/ms/bing.py +++ b/adapter/ms/bing.py @@ -74,7 +74,3 @@ async def ask(self, prompt: str) -> Generator[str, None, None]: yield "Bing 已结束本次会话。继续发送消息将重新开启一个新会话。" await self.on_reset() return - - async def preset_ask(self, role: str, text: str): - # 不会给 Bing 提供预设 - yield None diff --git a/adapter/openai/api.py b/adapter/openai/api.py index 0da8504f..918cd0e8 100644 --- a/adapter/openai/api.py +++ b/adapter/openai/api.py @@ -38,9 +38,6 @@ async def on_reset(self): async def ask(self, prompt: str) -> Generator[str, None, None]: yield None - async def preset_ask(self, role: str, text: str): - yield None - async def image_creation(self, prompt: str): logger.debug(f"[OpenAI Image] Prompt: {prompt}") response = await openai.Image.acreate( diff --git a/adapter/quora/poe.py b/adapter/quora/poe.py new file mode 100644 index 00000000..0e56382b --- /dev/null +++ b/adapter/quora/poe.py @@ -0,0 +1,57 @@ +from enum import Enum +from typing import Generator + +from adapter.botservice import BotAdapter +from constants import botManager + + +class PoeBot(Enum): + """Poe 支持的机器人:{'capybara': 'Sage', 'beaver': 'GPT-4', 'a2_2': 'Claude+','a2': 'Claude', 'chinchilla': 'ChatGPT', + 'nutria': 'Dragonfly'} """ + Sage = "capybara" + GPT4 = "beaver" + Claude2 = "a2_2" + Claude = "a2" + ChatGPT = "chinchilla" + Dragonfly = "nutria" + + @staticmethod + def parse(bot_name: str): + tmp_name = bot_name.lower() + for bot in PoeBot: + if str(bot.name).lower() == tmp_name or str(bot.value).lower() == tmp_name \ + or f"poe-{str(bot.name).lower()}" == tmp_name: + return bot + return None + + +class PoeAdapter(BotAdapter): + + def __init__(self, session_id: str = "unknown", poe_bot: PoeBot = None): + """获取内部队列""" + super().__init__(session_id) + self.session_id = session_id + self.poe_bot = poe_bot if poe_bot else PoeBot.ChatGPT + self.poe_client = botManager.pick("poe-web") + + async def ask(self, msg: str) -> Generator[str, None, None]: + """向 AI 发送消息""" + final_resp = None + for final_resp in self.poe_bot.send_message(chatbot=self.poe_bot.name, message=msg): + pass + if final_resp is None: + raise Exception("OpenAI 在返回结果时出现了错误") + resp = final_resp["text"] + return resp + + async def rollback(self): + """回滚对话""" + self.poe_client.purge_conversation(self.poe_bot.name, 2) + + async def on_reset(self): + """当会话被重置时,此函数被调用""" + self.poe_client.send_chat_break(self.poe_bot.name) + + def use_default_preset_ask(self) -> bool: + """使用默认预设逻辑""" + return True diff --git a/config.py b/config.py index 53f09be5..409f2959 100644 --- a/config.py +++ b/config.py @@ -120,18 +120,30 @@ class OpenAIAPIKey(OpenAIAuthBase): """OpenAI 的 api_key""" +class PoeCookieAuth(BaseModel): + p_b: str + """登陆 poe.com 后 Cookie 中 p_b 的值""" + + class BingCookiePath(BaseModel): cookie_content: str """Bing 的 Cookie 文件内容""" proxy: Optional[str] = None """可选的代理地址,留空则检测系统代理""" + class BardCookiePath(BaseModel): cookie_content: str """Bard 的 Cookie 文件内容""" proxy: Optional[str] = None """可选的代理地址,留空则检测系统代理""" + +class PoeAuths(BaseModel): + accounts: List[PoeCookieAuth] = [] + """Poe 的账号列表""" + + class BingAuths(BaseModel): show_suggestions: bool = True """在 Bing 的回复后加上猜你想问""" @@ -140,10 +152,12 @@ class BingAuths(BaseModel): accounts: List[BingCookiePath] = [] """Bing 的账号列表""" + class BardAuths(BaseModel): accounts: List[BardCookiePath] = [] """Bing 的账号列表""" + class TextToImage(BaseModel): always: bool = False """强制开启,设置后所有的会话强制以图片发送""" @@ -316,6 +330,7 @@ class Config(BaseModel): openai: OpenAIAuths = OpenAIAuths() bing: BingAuths = BingAuths() bard: BardAuths = BardAuths() + poe: PoeAuths = PoeAuths() text_to_image: TextToImage = TextToImage() trigger: Trigger = Trigger() response: Response = Response() diff --git a/conversation.py b/conversation.py index 3f2cff69..30f52bdf 100644 --- a/conversation.py +++ b/conversation.py @@ -15,6 +15,7 @@ from adapter.ms.bing import BingAdapter from adapter.google.bard import BardAdapter from adapter.openai.api import OpenAIAPIAdapter +from adapter.quora.poe import PoeBot, PoeAdapter from constants import config from exceptions import PresetNotFoundException, BotTypeNotFoundException, NoAvailableBotException, \ CommandRefusedException @@ -62,6 +63,8 @@ def __init__(self, _type: str, session_id: str): self.adapter = ChatGPTWebAdapter(self.session_id) elif _type == 'chatgpt-api': self.adapter = ChatGPTAPIAdapter(self.session_id) + elif PoeBot.parse(_type): + self.adapter = PoeAdapter(self.session_id, PoeBot.parse(_type)) elif _type == 'bing': self.adapter = BingAdapter(self.session_id) elif _type == 'bing-c': diff --git a/manager/bot.py b/manager/bot.py index b6c4d310..e36ac61a 100644 --- a/manager/bot.py +++ b/manager/bot.py @@ -20,7 +20,7 @@ from chatbot.Unofficial import AsyncChatbot as BrowserChatbot from loguru import logger -from config import OpenAIAuthBase, OpenAIAPIKey, Config, BingCookiePath, BardCookiePath +from config import OpenAIAuthBase, OpenAIAPIKey, Config, BingCookiePath, BardCookiePath, PoeCookieAuth import OpenAIAuth import urllib3.exceptions import utils.network as network @@ -28,6 +28,7 @@ import hashlib import datetime from dateutil.relativedelta import relativedelta +from poe import Client as PoeClient class BotManager: @@ -36,6 +37,7 @@ class BotManager: bots: Dict[str, List] = { "chatgpt-web": [], "openai-api": [], + "poe-web": [], "bing-cookie": [], "bard-cookie": [] } @@ -50,6 +52,9 @@ class BotManager: bard: List[BardCookiePath] """Bard Account Infos""" + poe: List[PoeCookieAuth] + """Poe Account infos""" + roundrobin: Dict[str, itertools.cycle] = {} def __init__(self, config: Config) -> None: @@ -57,6 +62,7 @@ def __init__(self, config: Config) -> None: self.openai = config.openai.accounts if config.openai else [] self.bing = config.bing.accounts if config.bing else [] self.bard = config.bard.accounts if config.bard else [] + self.poe = config.poe.accounts if config.poe else [] try: os.mkdir('data') logger.warning( @@ -69,12 +75,15 @@ async def login(self): self.bots = { "chatgpt-web": [], "openai-api": [], + "poe-web": [], "bing-cookie": [], "bard-cookie": [] } self.__setup_system_proxy() if len(self.bing) > 0: self.login_bing() + if len(self.poe) > 0: + self.login_poe() if len(self.bard) > 0: self.login_bard() if len(self.openai) > 0: @@ -97,7 +106,9 @@ async def login(self): logger.info(f"AI 类型:{k} - 可用账号: {len(v)} 个") # 自动推测默认 AI if not self.config.response.default_ai: - if len(self.bots['chatgpt-web']) > 0: + if len(self.bots['poe-web']) > 0: + self.config.response.default_ai = 'poe-web' + elif len(self.bots['chatgpt-web']) > 0: self.config.response.default_ai = 'chatgpt-web' elif len(self.bots['openai-api']) > 0: self.config.response.default_ai = 'chatgpt-api' @@ -138,6 +149,25 @@ def login_bard(self): logger.error("所有 Bard 账号均解析失败!") logger.success(f"成功解析 {len(self.bots['bard-cookie'])}/{len(self.bing)} 个 Bard 账号!") + def login_poe(self): + def poe_check_auth(client: PoeClient) -> bool: + try: + response = client.get_bot_names() + logger.debug(f"poe bot is running. bot names -> {response}") + return True + except KeyError: + return False + try: + for i, account in enumerate(self.poe): + bot = PoeClient(token=account.p_b) + if poe_check_auth(bot): + self.bots["poe-web"].append(bot) + except Exception as e: + logger.error("解析失败:") + logger.exception(e) + if len(self.bots["poe-web"]) < 1: + logger.error("所有 Poe 账号均解析失败!") + async def login_openai(self): counter = 0 for i, account in enumerate(self.openai): diff --git a/requirements.txt b/requirements.txt index 0e5009f1..395ca222 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ tls-client undetected_chromedriver python-dateutil discord.py +poe-api~=0.1.4 From c974c71fa0b1295abe2051ad343deee6e008ce25 Mon Sep 17 00:00:00 2001 From: Milin <417156994@qq.com> Date: Mon, 27 Mar 2023 14:51:35 +0800 Subject: [PATCH 2/2] upgrade poe-api to 0.2.0, fix PoeAdapter. --- adapter/quora/poe.py | 8 ++++---- manager/bot.py | 5 ++++- requirements.txt | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/adapter/quora/poe.py b/adapter/quora/poe.py index 0e56382b..abd6c0f6 100644 --- a/adapter/quora/poe.py +++ b/adapter/quora/poe.py @@ -37,20 +37,20 @@ def __init__(self, session_id: str = "unknown", poe_bot: PoeBot = None): async def ask(self, msg: str) -> Generator[str, None, None]: """向 AI 发送消息""" final_resp = None - for final_resp in self.poe_bot.send_message(chatbot=self.poe_bot.name, message=msg): + for final_resp in self.poe_client.send_message(chatbot=self.poe_bot.value, message=msg): pass if final_resp is None: raise Exception("OpenAI 在返回结果时出现了错误") resp = final_resp["text"] - return resp + yield resp async def rollback(self): """回滚对话""" - self.poe_client.purge_conversation(self.poe_bot.name, 2) + self.poe_client.purge_conversation(self.poe_bot.value, 2) async def on_reset(self): """当会话被重置时,此函数被调用""" - self.poe_client.send_chat_break(self.poe_bot.name) + self.poe_client.send_chat_break(self.poe_bot.value) def use_default_preset_ask(self) -> bool: """使用默认预设逻辑""" diff --git a/manager/bot.py b/manager/bot.py index ef4437ee..c26891b8 100644 --- a/manager/bot.py +++ b/manager/bot.py @@ -115,7 +115,7 @@ async def login(self): # 自动推测默认 AI if not self.config.response.default_ai: if len(self.bots['poe-web']) > 0: - self.config.response.default_ai = 'poe-web' + self.config.response.default_ai = 'poe-chatgpt' elif len(self.bots['chatgpt-web']) > 0: self.config.response.default_ai = 'chatgpt-web' elif len(self.bots['openai-api']) > 0: @@ -169,14 +169,17 @@ def poe_check_auth(client: PoeClient) -> bool: return False try: for i, account in enumerate(self.poe): + logger.info("正在解析第 {i} 个 poe web 账号", i=i + 1) bot = PoeClient(token=account.p_b) if poe_check_auth(bot): self.bots["poe-web"].append(bot) + logger.success("解析成功!", i=i + 1) except Exception as e: logger.error("解析失败:") logger.exception(e) if len(self.bots["poe-web"]) < 1: logger.error("所有 Poe 账号均解析失败!") + logger.success(f"成功解析 {len(self.bots['poe-web'])}/{len(self.bing)} 个 poe web 账号!") def login_yiyan(self): for i, account in enumerate(self.yiyan): diff --git a/requirements.txt b/requirements.txt index e755c86b..318149d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,4 +31,4 @@ undetected_chromedriver python-dateutil discord.py azure-cognitiveservices-speech -poe-api~=0.1.4 +poe-api~=0.2.0