You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
5.9 KiB
149 lines
5.9 KiB
import base64
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import time
|
|
from urllib.parse import quote
|
|
|
|
|
|
class WenxinClient:
|
|
BASE_URL = "https://chat.baidu.com"
|
|
|
|
def __init__(self, cookies=None):
|
|
self._cookie_str = ""
|
|
if cookies:
|
|
self._cookie_str = "; ".join(f'{c["name"]}={c["value"]}' for c in cookies)
|
|
|
|
async def chat(self, question, thinking=False):
|
|
from curl_cffi.requests import AsyncSession
|
|
|
|
async with AsyncSession(impersonate="chrome136", verify=False) as session:
|
|
if self._cookie_str:
|
|
session.headers["cookie"] = self._cookie_str
|
|
|
|
resp = await session.get(self.BASE_URL + "/", timeout=20)
|
|
match = re.search(r'\{"token"\s*:\s*"([^"]+)"\s*,\s*"lid"\s*:\s*"(\d+)"', resp.text)
|
|
if not match:
|
|
return "", ""
|
|
token, lid = match.group(1), match.group(2)
|
|
|
|
ts = int(time.time() * 1000)
|
|
query_md5 = hashlib.md5(question.encode("utf-8")).hexdigest()
|
|
raw_token = f"{token}|{query_md5}|{ts}|{lid}"
|
|
chat_token = f"{base64.b64encode(raw_token.encode()).decode()}-{lid}-3"
|
|
|
|
payload = {
|
|
"message": {
|
|
"inputMethod": "chat_search", "isRebuild": False,
|
|
"content": {
|
|
"query": "", "qtype": 0,
|
|
"agentInfo": {"agent_id": [""], "params": json.dumps({"agt_rk": 1, "agt_sess_cnt": 1})},
|
|
"agentInfoList": [],
|
|
},
|
|
"searchInfo": {
|
|
"srcid": "", "order": "", "tplname": "", "dqaKey": "",
|
|
"re_rank": "1", "ori_lid": "", "sa": "bkb", "enter_type": "chat_url",
|
|
"chatParams": {"setype": "csaitab", "chat_samples": "WISE_NEW_CSAITAB",
|
|
"chat_token": chat_token, "scene": ""},
|
|
"isPrivateChat": False,
|
|
"usedModel": {
|
|
"modelName": "smartMode",
|
|
"modelFunction": {"deepSearch": "1" if thinking else "0", "thinkMode": "0"},
|
|
"showModelName": "smartMode",
|
|
},
|
|
"landingPageSwitch": "", "landingPage": "aitab", "isInnovate": 2,
|
|
"showMindMap": False,
|
|
},
|
|
"from": "", "source": "pc_csaitab",
|
|
"query": [{"type": "TEXT", "data": {"text": {"query": question, "extData": "{}", "text_type": ""}}}],
|
|
"anti_ext": {"inputT": None},
|
|
},
|
|
"sa": "bkb", "setype": "csaitab", "rank": 1,
|
|
}
|
|
|
|
headers = {
|
|
"Accept": "text/event-stream", "Content-Type": "application/json",
|
|
"Origin": self.BASE_URL, "Referer": f"{self.BASE_URL}/",
|
|
"source": "pc_csaitab", "isDeepseek": "1",
|
|
"X-Chat-Message": f"query:{quote(question)},anti_ext:%7B%22inputT%22%3Anull%7D,enter_type:chat_url,re_rank:1,modelName:smartMode,sa:bkb",
|
|
}
|
|
|
|
resp = await session.post(
|
|
self.BASE_URL + "/aichat/api/conversation",
|
|
headers=headers, json=payload, stream=True, timeout=180,
|
|
)
|
|
|
|
raw_text = ""
|
|
req_body = json.dumps(payload, ensure_ascii=False)
|
|
async for chunk in resp.aiter_content():
|
|
raw_text += chunk.decode("utf-8", errors="replace")
|
|
|
|
return raw_text, req_body
|
|
|
|
|
|
async def extract_wenxin_cookies():
|
|
from playwright.async_api import async_playwright
|
|
from .base import CDP_URL
|
|
try:
|
|
async with async_playwright() as pw:
|
|
browser = await pw.chromium.connect_over_cdp(CDP_URL)
|
|
ctx = browser.contexts[0]
|
|
cookies = await ctx.cookies(["https://chat.baidu.com", "https://www.baidu.com", "https://baidu.com"])
|
|
if any(c["name"] in ("BDUSS", "BDUSS_BFESS") for c in cookies):
|
|
return cookies
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
async def get_wenxin_share_url(lid, wx_page=None):
|
|
from playwright.async_api import async_playwright
|
|
from .base import CDP_URL
|
|
page = wx_page
|
|
pw_ctx = None
|
|
try:
|
|
if not page:
|
|
pw_ctx = await async_playwright().start()
|
|
browser = await pw_ctx.chromium.connect_over_cdp(CDP_URL)
|
|
page = next((p for p in browser.contexts[0].pages if "baidu.com" in p.url), None)
|
|
if not page:
|
|
if pw_ctx:
|
|
await pw_ctx.stop()
|
|
return ""
|
|
|
|
await page.goto(f"https://chat.baidu.com/search/{lid}", wait_until="domcontentloaded", timeout=20000)
|
|
await asyncio.sleep(3)
|
|
await page.evaluate("""() => {
|
|
window.__sc = [];
|
|
const of = window.fetch;
|
|
window.fetch = async function(...a) {
|
|
const r = await of.apply(this, a);
|
|
try { const c=r.clone(); const t=await c.text();
|
|
if (t.includes('short_url')) window.__sc.push(t);
|
|
} catch {}
|
|
return r;
|
|
};
|
|
}""")
|
|
await page.click(".cos-icon-share1", timeout=8000)
|
|
await asyncio.sleep(1)
|
|
await page.click('button:has-text("复制链接")', timeout=5000)
|
|
await asyncio.sleep(2)
|
|
captures = await page.evaluate("() => window.__sc")
|
|
import json as _json
|
|
for c in captures:
|
|
d = _json.loads(c)
|
|
url = d.get("data", {}).get("short_url", "")
|
|
if url:
|
|
if pw_ctx:
|
|
await pw_ctx.stop()
|
|
return url
|
|
if pw_ctx:
|
|
await pw_ctx.stop()
|
|
except Exception as e:
|
|
print(f" ⚠ 文心分享链接获取失败 (lid={lid}): {e}")
|
|
if pw_ctx:
|
|
try:
|
|
await pw_ctx.stop()
|
|
except Exception:
|
|
pass
|
|
return ""
|