Skip to content

Commit

Permalink
✨ 使用 playwright 绕开 cf 限制 (#52)
Browse files Browse the repository at this point in the history
Co-authored-by: Akirami <[email protected]>
  • Loading branch information
sena-nana and A-kirami authored Dec 13, 2022
1 parent e3e75ee commit 5acdbef
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 8 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ _✨ ChatGPT AI 对话 ✨_

在 nonebot2 项目的`.env`文件中添加下表中的必填配置(在arm平台,可能必须使用CHATGPT_SESSION_TOKEN登录)

windows系统需要在env文件中设置fastapi_reload=false

| 配置项 | 必填 | 默认值 | 说明 |
|:-----:|:----:|:----:|:----:|
| CHATGPT_SESSION_TOKEN || 空字符串 | ChatGPT 的 session_token,如配置则优先使用 |
Expand Down
5 changes: 2 additions & 3 deletions nonebot_plugin_chatgpt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@

from nonebot_plugin_apscheduler import scheduler

if config.chatgpt_image:
require("nonebot_plugin_htmlrender")
require("nonebot_plugin_htmlrender")

from nonebot_plugin_htmlrender import md_to_pic
from nonebot_plugin_htmlrender import md_to_pic

chat_bot = Chatbot(
token=setting.token or config.chatgpt_session_token,
Expand Down
76 changes: 71 additions & 5 deletions nonebot_plugin_chatgpt/chatgpt.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uuid
from typing import Any, Dict, Optional
from urllib.parse import urljoin

import asyncio
import httpx
from nonebot.log import logger
from nonebot.utils import escape_tag, run_sync
Expand All @@ -11,8 +11,12 @@
import ujson as json
except ModuleNotFoundError:
import json
from playwright.async_api import async_playwright

js = "Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});"

SESSION_TOKEN_KEY = "__Secure-next-auth.session-token"
CF_CLEARANCE_KEY = "cf_clearance"


class Chatbot:
Expand All @@ -34,6 +38,9 @@ def __init__(
self.timeout = timeout
self.authorization = ""

self.cf_clearance = ""
self.user_agent = ""

if self.session_token:
self.auto_auth = False
elif self.account and self.password:
Expand All @@ -59,7 +66,7 @@ def headers(self) -> Dict[str, str]:
"Accept": "text/event-stream",
"Authorization": f"Bearer {self.authorization}",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
"User-Agent": self.user_agent,
"X-Openai-Assistant-App-Id": "",
"Connection": "close",
"Accept-Language": "en-US,en;q=0.9",
Expand All @@ -82,19 +89,31 @@ def get_payload(self, prompt: str) -> Dict[str, Any]:
}

async def get_chat_response(self, prompt: str) -> str:
if not self.authorization:
await self.refresh_session()
for i in range(2):
if not self.authorization:
await self.refresh_session()
else:
break
else:
return "获取session失败,请检查后台报错"
cookies = {SESSION_TOKEN_KEY: self.session_token}
if self.cf_clearance:
cookies[CF_CLEARANCE_KEY] = self.cf_clearance
async with httpx.AsyncClient(proxies=self.proxies) as client:
response = await client.post(
urljoin(self.api_url, "backend-api/conversation"),
headers=self.headers,
cookies=cookies,
json=self.get_payload(prompt),
timeout=self.timeout,
)
if response.status_code == 429:
return "请求过多,请放慢速度"
if response.status_code == 401:
return "token失效,请重新设置token"
if response.status_code == 403:
await self.get_cf_cookies()
return await self.get_chat_response(prompt)
if response.is_error:
logger.opt(colors=True).error(
f"非预期的响应内容: <r>HTTP{response.status_code}</r> {escape_tag(response.text)}"
Expand All @@ -108,10 +127,13 @@ async def get_chat_response(self, prompt: str) -> str:
return response["message"]["content"]["parts"][0]

async def refresh_session(self) -> None:
logger.debug("正在刷新session")
if self.auto_auth:
await self.login()
else:
cookies = {SESSION_TOKEN_KEY: self.session_token}
if self.cf_clearance:
cookies[CF_CLEARANCE_KEY] = self.cf_clearance
async with httpx.AsyncClient(
cookies=cookies,
proxies=self.proxies,
Expand All @@ -120,14 +142,19 @@ async def refresh_session(self) -> None:
response = await client.get(
urljoin(self.api_url, "api/auth/session"),
headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
"User-Agent": self.user_agent,
},
)
try:
if response.status_code == 403:
await self.get_cf_cookies()
await self.refresh_session()
return
self.session_token = (
response.cookies.get(SESSION_TOKEN_KEY) or self.session_token
)
self.authorization = response.json()["accessToken"]
logger.debug("刷新会话成功: " + self.session_token + self.cf_clearance)
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"刷新会话失败: <r>HTTP{response.status_code}</r> {escape_tag(response.text)}"
Expand Down Expand Up @@ -159,3 +186,42 @@ def login(self) -> None:
logger.opt(exception=e).error("ChatGPT 登陆错误!")
else:
logger.error("ChatGPT 登陆错误!")

async def get_cf_cookies(self) -> None:
logger.debug("正在获取cf cookies")
async with async_playwright() as p:
try:
browser = await p.firefox.launch(
headless=True,
proxy={"server": self.proxies}
if self.proxies
else None, # your proxy
)
except Exception as e:
logger.opt(exception=e).error(
"playwright未安装,请先在shell中运行playwright install"
)
return
ua = f"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/{browser.version}"
content = await browser.new_context(user_agent=ua)
page = await content.new_page()
await page.add_init_script(js)
cf_clearance = None
await page.goto("https://chat.openai.com/chat")
for j in range(6):
if cf_clearance:
break
await asyncio.sleep(5)
cookies = await content.cookies()
for i in cookies:
if i["name"] == "cf_clearance":
cf_clearance = i
break
else:
logger.error("cf cookies获取失败,可能遇到了人工校验")
self.cf_clearance = cf_clearance["value"]
self.user_agent = ua
await page.close()
await content.close()
await browser.close()
logger.debug("cf cookies获取成功")

0 comments on commit 5acdbef

Please sign in to comment.