From 14214b83b161f424275f8fbf363145e37b6cc6f2 Mon Sep 17 00:00:00 2001 From: Bobby202608 <2444977612@qq.com> Date: Wed, 1 Jul 2026 17:43:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E8=84=9A=E6=9C=AC=EF=BC=8C=E8=87=AA=E5=8A=A8=E7=88=AC?= =?UTF-8?q?=E5=8F=96=E3=80=8A=E6=A8=A1=E5=9E=8B=E6=8E=A5=E5=85=A5=E7=9F=A9?= =?UTF-8?q?=E9=98=B5=E3=80=8B=EF=BC=8C=E5=B9=B6=E8=87=AA=E5=8A=A8=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E3=80=8A=E6=A8=A1=E5=9E=8B=E4=BB=8B=E7=BB=8D=E9=A1=B5?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...00\345\247\213\346\226\207\346\241\243.md" | 51 +++ ...45\351\224\231\346\216\222\346\237\245.md" | 1 + ...\346\210\267\346\211\213\345\206\214v1.md" | 50 +++ ...15\351\241\265\346\250\241\346\235\277.md" | 8 + scripts/feishu/.env.example | 16 + .../fetch_wiki_doc.cpython-311.pyc | Bin 0 -> 12353 bytes .../feishu/__pycache__/main.cpython-311.pyc | Bin 0 -> 15109 bytes .../__pycache__/oauth_server.cpython-311.pyc | Bin 0 -> 6863 bytes .../__pycache__/oauth_server.cpython-313.pyc | Bin 0 -> 3451 bytes scripts/feishu/fetch_wiki_doc.py | 211 ++++++++++++ scripts/feishu/main.py | 309 ++++++++++++++++++ scripts/feishu/oauth_server.py | 154 +++++++++ scripts/feishu/requirements.txt | 3 + 13 files changed, 803 insertions(+) create mode 100644 "docs/API\345\277\253\351\200\237\345\274\200\345\247\213\346\226\207\346\241\243.md" create mode 100644 "docs/FAQ\344\270\216\345\270\270\350\247\201\346\212\245\351\224\231\346\216\222\346\237\245.md" create mode 100644 "docs/HotAI\345\233\275\345\206\205API\345\271\263\345\217\260\347\224\250\346\210\267\346\211\213\345\206\214v1.md" create mode 100644 "docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" create mode 100644 scripts/feishu/.env.example create mode 100644 scripts/feishu/__pycache__/fetch_wiki_doc.cpython-311.pyc create mode 100644 scripts/feishu/__pycache__/main.cpython-311.pyc create mode 100644 scripts/feishu/__pycache__/oauth_server.cpython-311.pyc create mode 100644 scripts/feishu/__pycache__/oauth_server.cpython-313.pyc create mode 100644 scripts/feishu/fetch_wiki_doc.py create mode 100644 scripts/feishu/main.py create mode 100644 scripts/feishu/oauth_server.py create mode 100644 scripts/feishu/requirements.txt diff --git "a/docs/API\345\277\253\351\200\237\345\274\200\345\247\213\346\226\207\346\241\243.md" "b/docs/API\345\277\253\351\200\237\345\274\200\345\247\213\346\226\207\346\241\243.md" new file mode 100644 index 0000000..db449a0 --- /dev/null +++ "b/docs/API\345\277\253\351\200\237\345\274\200\345\247\213\346\226\207\346\241\243.md" @@ -0,0 +1,51 @@ +# API 快速开始文档 + +## 一、前置条件 +- 已注册账号并获取 API Key; + 若未注册账号,请阅读:xxx + 若未获取 API Key,请阅读:xxx +- + + +## 二、API 请求示例 + +### (1) cURL + +```curl + + +``` + +### (2) Python + +```python + + +``` + +### (3) JavaScript + +```javascript + + +``` + + +## 三、 + + +## 四、错误码速查 + +### 401:鉴权失败 + + + +### 429:限流 + + + +### 500:服务端错误 + + + +其余错误排查方法详见:xxx \ No newline at end of file diff --git "a/docs/FAQ\344\270\216\345\270\270\350\247\201\346\212\245\351\224\231\346\216\222\346\237\245.md" "b/docs/FAQ\344\270\216\345\270\270\350\247\201\346\212\245\351\224\231\346\216\222\346\237\245.md" new file mode 100644 index 0000000..3ecdcc3 --- /dev/null +++ "b/docs/FAQ\344\270\216\345\270\270\350\247\201\346\212\245\351\224\231\346\216\222\346\237\245.md" @@ -0,0 +1 @@ +# FAQ 与常见报错排查 \ No newline at end of file diff --git "a/docs/HotAI\345\233\275\345\206\205API\345\271\263\345\217\260\347\224\250\346\210\267\346\211\213\345\206\214v1.md" "b/docs/HotAI\345\233\275\345\206\205API\345\271\263\345\217\260\347\224\250\346\210\267\346\211\213\345\206\214v1.md" new file mode 100644 index 0000000..cbb3712 --- /dev/null +++ "b/docs/HotAI\345\233\275\345\206\205API\345\271\263\345\217\260\347\224\250\346\210\267\346\211\213\345\206\214v1.md" @@ -0,0 +1,50 @@ +# HotAI 国内 API 平台用户手册 + +## 一、平台简介 + +HotAI 是一款国内 AI 模型 API 聚合平台,可一站式接入 DeepSeek、xxx、xxx 等多种主流模型。 + + + + + +## 二、快速入门 + +### 1. 访问平台 + + + + +### 2. 账号注册与登录 + + + + +### 3. 控制台概览 + + + + +## 三、API Key 管理 + + + + + +## 四、模型列表与选择 + + + + + +## 五、用量与计费 + + + + + +## 六、常见问题快速索引 + + + + diff --git "a/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" "b/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" new file mode 100644 index 0000000..a16eecb --- /dev/null +++ "b/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" @@ -0,0 +1,8 @@ +# 模型介绍 + +本平台提供的模型及其属性如下: + +| 编号 | 模型ID | 提供商 | 适用场景 | 模型能力 | 鉴权类型 | API格式 | 价格 | 上下文长度 | 流式输出 | JSON输出 | 基础测试 | 响应速度 | key状态 | 并发限制 | 接入状态 | 链接 | 更新时间 | 备注 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | deepseek-v4-flash | 官方渠道 | 高并发客服对话、长文档 RAG 问答、大批量文本处理(摘要 / 分类 / 打标)、轻量工具调用、内容批量生成。 | 100 万 token 超长上下文支持,高并发低延迟,价格远低于 Pro 版,支持多模式推理(non-think/think-high/think-max)。 | API BASE | OPENAI | 缓存命中输入仅 0.02 元 / 百万 tokens,缓存未命中输入1 元 / 百万 tokens,输出 2 元 / 百万 tokens。 | 1M | 支持 | 支持 | 已完成 | 非流式总延迟:约 1372ms 流式首 token 延迟:约 1274ms | 可用 | 2500 | 已接入 | https://api-docs.deepseek.com/zh-cn | 2026/7/1 | 流式对话中存在乱码 | +| 2 | deepseek-v4-pro | 官方渠道 | 适合复杂推理、高精度代码开发、专业文档分析、高风险合规业务,是对质量要求远高于成本 / 延迟的旗舰场景首选。 | 旗舰级通用模型,1.6T 总参 / 49B 激活,1M 超长上下文,强推理 + 高精度代码 + 复杂 Agent 能力,性能比肩国际顶级模型,适合高要求复杂任务。 | API BASE | OEPNAI | 缓存命中输入仅 0.025 元 / 百万 tokens,缓存未命中输入3 元 / 百万 tokens,输出 6 元 / 百万 tokens。 | 1M | 支持 | 支持 | 已完成 | 非流式总延迟:约 1822ms 流式首 token 延迟:约 1107ms | 可用 | 500 | 已接入 | https://api-docs.deepseek.com/zh-cn | 2026/7/1 | 基础对话和流式对话中存在输出空白的现象 | diff --git a/scripts/feishu/.env.example b/scripts/feishu/.env.example new file mode 100644 index 0000000..905182e --- /dev/null +++ b/scripts/feishu/.env.example @@ -0,0 +1,16 @@ +FEISHU_APP_ID=cli_xxx +FEISHU_APP_SECRET=xxx + +FEISHU_OPEN_BROWSER=true +FEISHU_REDIRECT_URI=http://localhost:3001/ +FEISHU_OAUTH_STATE=hotai-test + +FEISHU_SAVE_USER_ACCESS_TOKEN=true +FEISHU_USER_ACCESS_TOKEN=u-xxx + +FEISHU_WIKI_URL=https://pcn43kg7pnzs.feishu.cn/wiki/DG5cwq12EiuaQGk8UbtcaQKdnif +FEISHU_FETCH_DOC_AFTER_AUTH=true + +FEISHU_DOC_OUTPUT=docs/feishu_content.txt +FEISHU_SYNC_MODEL_TEMPLATE=true +FEISHU_MODEL_TEMPLATE_PATH=docs/模型介绍页模板.md diff --git a/scripts/feishu/__pycache__/fetch_wiki_doc.cpython-311.pyc b/scripts/feishu/__pycache__/fetch_wiki_doc.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af689796af5ef4d8b3e3b91087df59446639c8e5 GIT binary patch literal 12353 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 literal 0 HcmV?d00001 diff --git a/scripts/feishu/__pycache__/oauth_server.cpython-311.pyc b/scripts/feishu/__pycache__/oauth_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed6342aa3b5a0ecdb13b732a35be36cf373e3eb6 GIT binary patch literal 6863 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 literal 0 HcmV?d00001 diff --git a/scripts/feishu/__pycache__/oauth_server.cpython-313.pyc b/scripts/feishu/__pycache__/oauth_server.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..642be8e3142534cadefa8e9b55d2562fdd66d174 GIT binary patch literal 3451 zcma)8{cjWJ9e>U~pWn_ec?0KV|;d?Z(#v-jMY zfu%}jsKSAkWMXZZL`B*Tm2OkXrfPI5+V~TWITGfYZe_&*erjm?2kiUY`7Eacu}AXb z=lgQc^L;+ww`bMo^B@>soZtKUt8Rq8A%r!U@?!Nf3ZZupk9cYrVe)SqriN_T#*wvs z*giyK+E~-Wjv)p!#@aFL9CBgTkQ=*)JlJFKs9`Vmj=S2>INJ+*l7W5W4A3_C`7K_w z)s$7wJYz|%f7sF4gm7>?*oJtQ#lk@d9`HgX6dp#!1uCOE%td|g{*H$UpJ zS>JJ{yuOO|MRz>Ht_FS`zn%~6v~y^Znxw*w4~Wu8n9}Kkf;t8N?hH(lFcMw>l2fJ7~;c zk||n7u)=^v8;T*zcA1J%7W@f6LSqg~lc!>+`qm<7wxh6ZB>VJFB}I|sNsib9rX@Hq zN5Uz2T9j4pn1ChWa8itLa0gpT(V0n++(>7m8CjL4#eR%aIBe5tS(p}e$1x!Zmm+3z zoE}IC8FebIi1?U@JJTn0Z~2Tk?0|!y1U4cCT!eCHiS^G8o*A4=oP8zN|1WQF$={^? zsPD$;jRCEBxZppaxegS)!Q6pW&_=p%pns6xAB*ofcrZTLr`J@GeE;5P|EOM9W<~q^ z2BZCZN8_>Rpk7;MzOpAaxZ%Zx z4v0B?LLqnIaDaT~Kb(=0iMZh@h+n(`n9cC3xTRsKHnbFKU#e+&=%m*(tBCeFbNwqG zgK zFilLwPl2o*ehLYr9J=eR`|U`fzP;exq|uuUZJp{`V@SoH+rWQM%BpB3b71@O;3>;M z`3X7s$ge9E2eK4ZvJ^ZqX&)muH3mjTYu;^C)B!mA(VCS$ATu%Mk$ZV72s$65 zQR!W>s|pog%n9AlPeZ}r13i`ef=V2$(yDokUA5M!)fEtst^0rU`TFfP2*`#JohFqX zHv)cPTZY8m^11x-`=?63|MUINE`NLdt>ue8>rR2ELcW6wG(mkZiDB=Vb zRZJ|^J(F0F)wp^hO?ngkx{{JHDPuaF5L7{D*KF*=;t?z=Q|37ta^wG8e3LUx=SIol zoOzoF_n;o6S$k(^X9fbJ42dcH_WH@{ZMo95zbwz3FTK5B;PY`Q|JTy&0^oN4>b2!d zm+xIXd+&?|t@hi;`i(pZvJ+(g(OQY6bA0(!C0ffb!jrxX>3&Cdy5hL?4>fiqO-r0z@$CrCpl z4O~g5WJS~&SY?tbChZG9L#8b-De%yP-d7%G#d1vu$>d>S;wat;LZYuQ5EVJ}J%iY~ z*izC%|EAk zIu_|?Rv1(>MCAtWLWN$(6@&G~bsZ1gjsTO}_q`tlL$hz3dE>&XHv_%7{Y78H>}xZx zosH)PzIOUfXHRD5HW!?Y7k*Z7ZYTyCX0tQd3!4jpr}A`hee-+Ae{+1^Us&IjXN#eh zORnF!=35J)?!0fw<pb(Z=L64`-U8RHwLYr_dKP#s@Vw@F-mKNGczjxrg3g&t0oGIC%}CF- zZQVvU(%luN^fxa;qtJ$FqC(t`Av_Y|;ml;X1(VWB9IZ|zrNfwXC!Gb~1JRilFuY=D z!eY!P9VQJzcN`(FI!s!N*{w+I;T|G_8<8hbJg(az83}nrN)pojD+wM&L}omJjJU+l zlJ7{{#b6=AEo35{Sa;x5Miq^AXG9Ms80dDKkugV-b`n`|g{)Ry zrV!2MvjuzO9V6a97%8-G*EaTQp63?n9k+q!d2x~6U8J3-_nq7~*F3-dikjQ^PkQ@` K8_`V$BL4+Ctv)&c literal 0 HcmV?d00001 diff --git a/scripts/feishu/fetch_wiki_doc.py b/scripts/feishu/fetch_wiki_doc.py new file mode 100644 index 0000000..7c0d85e --- /dev/null +++ b/scripts/feishu/fetch_wiki_doc.py @@ -0,0 +1,211 @@ +# scripts/feishu/fetch_wiki_doc.py + +""" +获取飞书知识库资源的文本内容。 + +输入 URL 中 /wiki/ 后面的 token 是知识库节点 token。本脚本会先将它解析 +为实际云文档 token,再根据资源类型读取新版文档(docx)或电子表格(sheet)。 +""" + +from urllib.parse import urlparse + +import requests + + +FEISHU_API_BASE = "https://open.feishu.cn/open-apis" +SUPPORTED_OBJ_TYPES = {"docx", "sheet"} + + +class FeishuApiError(RuntimeError): + pass + + +def build_feishu_error(response, payload=None): + """保留飞书返回体和 log_id,方便根据错误码定位权限或参数问题。""" + log_id = response.headers.get("x-tt-logid") + if payload is None: + try: + payload = response.json() + except ValueError: + payload = response.text + + detail = f"飞书 API 请求失败: HTTP {response.status_code}, response={payload}" + if log_id: + detail = f"{detail}, x-tt-logid={log_id}" + return FeishuApiError(detail) + + +def extract_wiki_token(value): + """从 Wiki URL 中提取节点 token;如果传入本身就是 token,则原样返回。""" + if value.startswith("http://") or value.startswith("https://"): + path_parts = [part for part in urlparse(value).path.split("/") if part] + if len(path_parts) >= 2 and path_parts[0] == "wiki": + return path_parts[1] + raise ValueError(f"URL 不是有效的飞书知识库链接: {value}") + return value + + +def request_json(method, url, *, user_access_token, **kwargs): + """统一封装飞书 OpenAPI 请求、鉴权头、JSON 解析和错误处理。""" + headers = kwargs.pop("headers", {}) + headers["Authorization"] = f"Bearer {user_access_token}" + + response = requests.request(method, url, headers=headers, timeout=20, **kwargs) + if not response.ok: + raise build_feishu_error(response) + + try: + payload = response.json() + except ValueError as exc: + raise FeishuApiError(f"飞书 API 返回非 JSON 响应: {response.text}") from exc + + if payload.get("code") != 0: + raise build_feishu_error(response, payload) + return payload + + +def get_wiki_node(user_access_token, wiki_token): + """查询 Wiki 节点,拿到背后的真实资源类型和 obj_token。""" + payload = request_json( + "GET", + f"{FEISHU_API_BASE}/wiki/v2/spaces/get_node", + user_access_token=user_access_token, + params={"token": wiki_token}, + ) + return payload["data"]["node"] + + +def get_docx_raw_content(user_access_token, document_id, *, lang=0): + """读取新版飞书文档 docx 的纯文本内容。""" + payload = request_json( + "GET", + f"{FEISHU_API_BASE}/docx/v1/documents/{document_id}/raw_content", + user_access_token=user_access_token, + params={"lang": lang}, + ) + return payload["data"]["content"] + + +def get_spreadsheet_sheets(user_access_token, spreadsheet_token): + """读取电子表格中的工作表列表。""" + payload = request_json( + "GET", + f"{FEISHU_API_BASE}/sheets/v3/spreadsheets/{spreadsheet_token}/sheets/query", + user_access_token=user_access_token, + ) + return payload["data"].get("sheets", []) + + +def get_sheet_values(user_access_token, spreadsheet_token, sheet_id): + """读取单个工作表的全部单元格值。""" + payload = request_json( + "GET", + f"{FEISHU_API_BASE}/sheets/v2/spreadsheets/{spreadsheet_token}/values/{sheet_id}", + user_access_token=user_access_token, + params={ + "valueRenderOption": "ToString", + "dateTimeRenderOption": "FormattedString", + }, + ) + return payload["data"].get("valueRange", {}).get("values", []) + + +def stringify_cell(value): + """将飞书单元格返回值规整成适合文本输出的字符串。""" + if value is None: + return "" + if isinstance(value, list): + return " ".join(stringify_cell(item) for item in value if item is not None) + if isinstance(value, dict): + text = value.get("text") or value.get("name") or value.get("value") + if text is not None: + return stringify_cell(text) + return str(value) + + +def normalize_table(values): + """补齐不等长行,保证后续可以稳定转换为 Markdown 表格。""" + if not values: + return [] + + width = max(len(row) for row in values) + return [ + [stringify_cell(row[index]) if index < len(row) else "" for index in range(width)] + for row in values + ] + + +def escape_markdown_table_cell(value): + """转义 Markdown 表格单元格中的特殊字符。""" + return value.replace("\\", "\\\\").replace("|", "\\|").replace("\n", "
") + + +def values_to_markdown_table(values): + """把二维单元格数组转换为 Markdown 表格文本。""" + rows = normalize_table(values) + if not rows: + return "_空工作表_" + + header = [escape_markdown_table_cell(cell) for cell in rows[0]] + body = [ + [escape_markdown_table_cell(cell) for cell in row] + for row in rows[1:] + ] + separator = ["---"] * len(header) + + lines = [ + "| " + " | ".join(header) + " |", + "| " + " | ".join(separator) + " |", + ] + lines.extend("| " + " | ".join(row) + " |" for row in body) + return "\n".join(lines) + + +def get_sheet_content(user_access_token, spreadsheet_token): + """读取电子表格中所有可见的普通工作表,并合并为 Markdown 文本。""" + sheets = get_spreadsheet_sheets(user_access_token, spreadsheet_token) + if not sheets: + return "未读取到任何工作表。" + + sections = [] + for sheet in sheets: + if sheet.get("hidden"): + continue + if sheet.get("resource_type") != "sheet": + continue + + title = sheet.get("title") or sheet["sheet_id"] + values = get_sheet_values(user_access_token, spreadsheet_token, sheet["sheet_id"]) + sections.append(f"## {title}\n\n{values_to_markdown_table(values)}") + + if not sections: + return "未读取到可导出的普通工作表。" + return "\n\n".join(sections) + + +def fetch_wiki_doc_content(user_access_token, wiki_url_or_token, *, lang=0): + """根据 Wiki 节点背后的资源类型,分发到 docx 或 sheet 的读取逻辑。""" + wiki_token = extract_wiki_token(wiki_url_or_token) + node = get_wiki_node(user_access_token, wiki_token) + + obj_type = node["obj_type"] + obj_token = node["obj_token"] + if obj_type == "docx": + return node, get_docx_raw_content(user_access_token, obj_token, lang=lang) + if obj_type == "sheet": + return node, get_sheet_content(user_access_token, obj_token) + + if obj_type not in SUPPORTED_OBJ_TYPES: + raise NotImplementedError( + f"暂不支持的知识库对象类型: {obj_type!r}。" + f"当前脚本支持的类型: {', '.join(sorted(SUPPORTED_OBJ_TYPES))}。" + ) + + +if __name__ == "__main__": + from main import main + import sys + + if len(sys.argv) == 1 or sys.argv[1] not in {"serve", "fetch"}: + sys.argv.insert(1, "fetch") + main() diff --git a/scripts/feishu/main.py b/scripts/feishu/main.py new file mode 100644 index 0000000..dc7b32e --- /dev/null +++ b/scripts/feishu/main.py @@ -0,0 +1,309 @@ +# scripts/feishu/main.py + +""" +飞书文档抓取工具统一入口。 + +默认启动本地 OAuth 授权服务;也可以使用 fetch 子命令,直接用已有 +user_access_token 抓取知识库文档内容。 +""" + +import argparse +import os +import subprocess +from threading import Timer +from pathlib import Path +import webbrowser + +from dotenv import load_dotenv + +from fetch_wiki_doc import fetch_wiki_doc_content +from oauth_server import build_auth_url, create_app + + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parents[1] +DEFAULT_WIKI_URL = "https://pcn43kg7pnzs.feishu.cn/wiki/DG5cwq12EiuaQGk8UbtcaQKdnif" +DEFAULT_MODEL_TEMPLATE_PATH = "docs/模型介绍页模板.md" +MODEL_TEMPLATE_HEADER = "# 模型介绍\n\n本平台提供的模型及其属性如下:" + + +def load_local_env(): + """加载与脚本同目录的 .env,统一管理本工具的运行配置。""" + load_dotenv(SCRIPT_DIR / ".env") + + +def env_bool(name, default=False): + """按常见布尔字符串解析环境变量。""" + value = os.getenv(name) + if value is None: + return default + return value.lower() in {"1", "true", "yes", "on"} + + +def is_wsl(): + """检测当前进程是否运行在 WSL 中,用于选择合适的浏览器打开方式。""" + try: + os_release = Path("/proc/sys/kernel/osrelease").read_text(encoding="utf-8") + except OSError: + return False + return "microsoft" in os_release.lower() or "wsl" in os_release.lower() + + +def open_browser(url): + """打开授权入口;WSL 下优先调用 Windows 默认浏览器。""" + if is_wsl(): + try: + subprocess.Popen( + ["cmd.exe", "/c", "start", "", url], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return True + except OSError: + pass + + return webbrowser.open(url) + + +def require_env(name): + """读取必填环境变量,缺失时立即给出明确错误。""" + value = os.getenv(name) + if not value: + raise RuntimeError(f"缺少必要环境变量: {name}") + return value + + +def write_content(output_path, content): + """写入抓取结果;相对路径统一按仓库根目录解析。""" + path = Path(output_path) + if not path.is_absolute(): + path = REPO_ROOT / path + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + template_path = sync_model_template(content) + if template_path: + print(f"模型介绍页模板已更新: {template_path}") + return path + + +def write_model_template(template_path, markdown_table): + """把 Markdown 表格写入模型介绍模板,同时固定保留模板前言。""" + path = Path(template_path) + if not path.is_absolute(): + path = REPO_ROOT / path + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + f"{MODEL_TEMPLATE_HEADER}\n\n{markdown_table.strip()}\n", + encoding="utf-8", + ) + return path + + +def extract_first_markdown_table(content): + """从抓取结果中提取第一段 Markdown 表格,忽略工作表标题等说明文本。""" + table_lines = [] + in_table = False + for line in content.splitlines(): + if line.startswith("|"): + table_lines.append(line) + in_table = True + elif in_table: + break + + return "\n".join(table_lines) if table_lines else content + + +def sync_model_template(content): + """按配置把本次抓取结果同步写入模型介绍页模板。""" + if not env_bool("FEISHU_SYNC_MODEL_TEMPLATE", True): + return None + + template_path = os.getenv("FEISHU_MODEL_TEMPLATE_PATH", DEFAULT_MODEL_TEMPLATE_PATH) + return write_model_template(template_path, extract_first_markdown_table(content)) + + +def upsert_env_value(path, key, value): + """新增或更新 .env 中的单个 key,保留其它配置行不变。""" + lines = [] + found = False + if path.exists(): + lines = path.read_text(encoding="utf-8").splitlines() + + updated_lines = [] + for line in lines: + if line.startswith(f"{key}="): + updated_lines.append(f"{key}={value}") + found = True + else: + updated_lines.append(line) + + if not found: + updated_lines.append(f"{key}={value}") + + path.write_text("\n".join(updated_lines) + "\n", encoding="utf-8") + + +def save_user_access_token(access_token): + """把 OAuth 新获取的 user_access_token 写回 .env,便于后续 fetch 复用。""" + upsert_env_value(SCRIPT_DIR / ".env", "FEISHU_USER_ACCESS_TOKEN", access_token) + print("user_access_token 已写入 scripts/feishu/.env") + + +def build_config(args): + """汇总命令行参数和环境变量,生成传给 OAuth 服务的配置对象。""" + return { + "app_id": require_env("FEISHU_APP_ID"), + "app_secret": require_env("FEISHU_APP_SECRET"), + "redirect_uri": require_env("FEISHU_REDIRECT_URI"), + "oauth_state": os.getenv("FEISHU_OAUTH_STATE", "hotai-test"), + "save_user_access_token": save_user_access_token, + "wiki_url": args.wiki or os.getenv("FEISHU_WIKI_URL", DEFAULT_WIKI_URL), + "fetch_doc_after_auth": env_bool("FEISHU_FETCH_DOC_AFTER_AUTH", True), + "write_content": write_content, + "doc_output": args.output or os.getenv("FEISHU_DOC_OUTPUT"), + "sync_model_template": sync_model_template, + "lang": args.lang + if env_bool("FEISHU_SAVE_USER_ACCESS_TOKEN", True) + else None, + } + + +def run_server(args): + """启动本地 OAuth 服务,并按配置自动打开授权入口。""" + config = build_config(args) + app = create_app(config) + + auth_url = build_auth_url( + config["app_id"], + config["redirect_uri"], + config["oauth_state"], + ) + browser_host = "localhost" if args.host in {"0.0.0.0", "::"} else args.host + local_auth_url = f"http://{browser_host}:{args.port}/auth" + + should_open_browser = args.open_browser + if should_open_browser is None: + should_open_browser = env_bool("FEISHU_OPEN_BROWSER", True) + if should_open_browser: + print(f"将自动打开浏览器访问: {local_auth_url}") + print(f"若自动打开失败,可手动打开本地入口: {local_auth_url}") + print(f"也可直接打开飞书授权链接: {auth_url}") + Timer(1.0, open_browser, args=(local_auth_url,)).start() + else: + print(f"请手动打开本地入口: {local_auth_url}") + print(f"或直接打开飞书授权链接: {auth_url}") + + app.run(host=args.host, port=args.port, debug=args.debug) + + +def run_fetch(args): + """跳过 OAuth,直接使用已有 user_access_token 抓取文档内容。""" + user_access_token = args.user_access_token or os.getenv("FEISHU_USER_ACCESS_TOKEN") + if not user_access_token: + raise RuntimeError( + "缺少 user_access_token。请在 scripts/feishu/.env 中设置 " + "FEISHU_USER_ACCESS_TOKEN,或通过 --user-access-token 传入。" + ) + + wiki_url = args.wiki or os.getenv("FEISHU_WIKI_URL", DEFAULT_WIKI_URL) + output = args.output or os.getenv("FEISHU_DOC_OUTPUT") + node, content = fetch_wiki_doc_content(user_access_token, wiki_url, lang=args.lang) + + print(f"title: {node['title']}") + print(f"obj_type: {node['obj_type']}") + print(f"obj_token: {node['obj_token']}") + + if output: + output_path = write_content(output, content) + print(f"内容已写入: {output_path}") + else: + template_path = sync_model_template(content) + if template_path: + print(f"模型介绍页模板已更新: {template_path}") + print() + print(content) + + +def parse_args(): + """解析 CLI 子命令;未指定子命令时默认进入 serve 模式。""" + parser = argparse.ArgumentParser(description="飞书 OAuth 授权与知识库文档抓取工具。") + subparsers = parser.add_subparsers(dest="command") + + server_parser = subparsers.add_parser( + "serve", + help="启动本地 OAuth 授权服务,授权成功后可自动抓取文档。", + ) + server_parser.add_argument( + "--wiki", + help="飞书知识库 URL 或 wiki token,默认读取 FEISHU_WIKI_URL。", + ) + server_parser.add_argument( + "--output", + help="文档内容输出路径,默认读取 FEISHU_DOC_OUTPUT。", + ) + server_parser.add_argument( + "--lang", + type=int, + default=0, + help="@用户 的显示语言:0 为默认名称,1 为英文名称。", + ) + server_parser.add_argument("--host", default="localhost", help="本地服务监听地址。") + server_parser.add_argument("--port", type=int, default=3001, help="本地服务端口。") + server_parser.add_argument("--debug", action="store_true", help="启用 Flask debug 模式。") + server_parser.add_argument( + "--open-browser", + dest="open_browser", + action="store_true", + default=None, + help="启动服务后自动打开授权入口。", + ) + server_parser.add_argument( + "--no-open-browser", + dest="open_browser", + action="store_false", + help="启动服务后不自动打开授权入口。", + ) + server_parser.set_defaults(func=run_server) + + fetch_parser = subparsers.add_parser( + "fetch", + help="直接使用已有 user_access_token 抓取文档。", + ) + fetch_parser.add_argument( + "wiki", + nargs="?", + help="飞书知识库 URL 或 wiki token,默认读取 FEISHU_WIKI_URL。", + ) + fetch_parser.add_argument( + "--user-access-token", + help="飞书 user_access_token,默认读取 FEISHU_USER_ACCESS_TOKEN。", + ) + fetch_parser.add_argument( + "--output", + help="文档内容输出路径,默认读取 FEISHU_DOC_OUTPUT。", + ) + fetch_parser.add_argument( + "--lang", + type=int, + default=0, + help="@用户 的显示语言:0 为默认名称,1 为英文名称。", + ) + fetch_parser.set_defaults(func=run_fetch) + + args = parser.parse_args() + if args.command is None: + args = parser.parse_args(["serve"]) + return args + + +def main(): + """程序入口:先加载配置,再分发到 serve 或 fetch。""" + load_local_env() + args = parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/scripts/feishu/oauth_server.py b/scripts/feishu/oauth_server.py new file mode 100644 index 0000000..b39de80 --- /dev/null +++ b/scripts/feishu/oauth_server.py @@ -0,0 +1,154 @@ +# scripts/feishu/oauth_server.py + +""" +本地 OAuth2 授权服务。 + +该模块只负责创建 Flask 应用和处理飞书 OAuth 回调。 +环境变量加载、运行模式选择、文档抓取配置由统一入口 main.py 负责。 +""" + +from urllib.parse import urlencode + +from flask import Flask, redirect, request +import requests + +from fetch_wiki_doc import fetch_wiki_doc_content + + +TOKEN_URL = "https://open.feishu.cn/open-apis/authen/v1/access_token" +AUTH_URL = "https://accounts.feishu.cn/open-apis/authen/v1/index" + + +def build_auth_url(app_id, redirect_uri, state): + """构造飞书 OAuth 授权页 URL。""" + query = urlencode({ + "app_id": app_id, + "redirect_uri": redirect_uri, + "state": state, + }) + return f"{AUTH_URL}?{query}" + + +def exchange_code_for_user_access_token(app_id, app_secret, code): + """用 OAuth 回调中的 code 换取 user_access_token。""" + payload = { + "app_id": app_id, + "app_secret": app_secret, + "code": code, + "grant_type": "authorization_code", + } + + response = requests.post(TOKEN_URL, json=payload, timeout=20) + try: + result = response.json() + except ValueError as exc: + response.raise_for_status() + raise RuntimeError(f"飞书 Token 接口返回非 JSON 响应: {response.text}") from exc + + if not response.ok: + raise RuntimeError(f"飞书 Token 接口请求失败: HTTP {response.status_code}, response={result}") + if result.get("code") != 0: + raise RuntimeError(f"获取 Token 失败: {result}") + return result["data"] + + +def fetch_doc_after_auth(access_token, config): + """授权成功后立即使用新 token 抓取配置中的 Wiki 资源。""" + node, content = fetch_wiki_doc_content( + access_token, + config["wiki_url"], + lang=config["lang"], + ) + + print(f"文档标题: {node['title']}") + print(f"文档类型: {node['obj_type']}") + print(f"文档 token: {node['obj_token']}") + + if config["doc_output"]: + output_path = config["write_content"](config["doc_output"], content) + print(f"文档内容已写入: {output_path}") + else: + template_path = config["sync_model_template"](content) + if template_path: + print(f"模型介绍页模板已更新: {template_path}") + print("文档内容:") + print(content) + + return node, content + + +def create_app(config): + """创建 Flask 应用;所有运行配置都由 main.py 注入。""" + app = Flask(__name__) + + @app.route("/auth") + def auth(): + return redirect( + build_auth_url( + config["app_id"], + config["redirect_uri"], + config["oauth_state"], + ) + ) + + @app.route("/") + def callback(): + # 飞书 OAuth 回调会携带 code 和 state,state 用于防止串改请求。 + code = request.args.get("code") + state = request.args.get("state") + + if not code: + return "授权失败,未获取到授权码", 400 + if state != config["oauth_state"]: + return "授权失败,state 不匹配", 400 + + print(f"成功获取授权码: {code}") + + # code 只能使用一次,拿到后立即换取 user_access_token。 + try: + token_data = exchange_code_for_user_access_token( + config["app_id"], + config["app_secret"], + code, + ) + except Exception as exc: + print(f"❌ 获取 Token 失败: {exc}") + return f"授权失败: {exc}", 400 + + access_token = token_data["access_token"] + print(f"✅ 获取 Token 成功: {access_token}") + if config["save_user_access_token"]: + # 保存 token 只是为了后续 fetch 复用;本次抓取会直接使用内存中的新 token。 + config["save_user_access_token"](access_token) + + if not config["fetch_doc_after_auth"]: + return "授权成功!user_access_token 已打印到控制台,已跳过文档抓取。" + + try: + node, content = fetch_doc_after_auth(access_token, config) + except Exception as exc: + print(f"❌ 文档抓取失败: {exc}") + return f"授权成功,但文档抓取失败: {exc}", 500 + + output_msg = ( + f"文档内容已写入 {config['doc_output']}" + if config["doc_output"] + else "文档内容已打印到控制台" + ) + return ( + f"授权成功,并已完成文档抓取!
" + f"标题:{node['title']}
" + f"内容长度:{len(content)} 字符
" + f"{output_msg}" + ) + + return app + + +if __name__ == "__main__": + from main import main + import sys + + if len(sys.argv) == 1: + sys.argv.append("serve") + main() diff --git a/scripts/feishu/requirements.txt b/scripts/feishu/requirements.txt new file mode 100644 index 0000000..e4b68c8 --- /dev/null +++ b/scripts/feishu/requirements.txt @@ -0,0 +1,3 @@ +python-dotenv +flask +requests From 52b3aa5e4b762db27b592c5dbcc62ef0c1e8c65a Mon Sep 17 00:00:00 2001 From: Bobby202608 <2444977612@qq.com> Date: Wed, 1 Jul 2026 23:18:44 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E4=BB=8B=E7=BB=8D?= =?UTF-8?q?=E9=A1=B5=E6=89=A9=E5=85=85=E5=88=B04=E7=A7=8D=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" | 2 ++ 1 file changed, 2 insertions(+) diff --git "a/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" "b/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" index a16eecb..62d04b3 100644 --- "a/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" +++ "b/docs/\346\250\241\345\236\213\344\273\213\347\273\215\351\241\265\346\250\241\346\235\277.md" @@ -6,3 +6,5 @@ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 1 | deepseek-v4-flash | 官方渠道 | 高并发客服对话、长文档 RAG 问答、大批量文本处理(摘要 / 分类 / 打标)、轻量工具调用、内容批量生成。 | 100 万 token 超长上下文支持,高并发低延迟,价格远低于 Pro 版,支持多模式推理(non-think/think-high/think-max)。 | API BASE | OPENAI | 缓存命中输入仅 0.02 元 / 百万 tokens,缓存未命中输入1 元 / 百万 tokens,输出 2 元 / 百万 tokens。 | 1M | 支持 | 支持 | 已完成 | 非流式总延迟:约 1372ms 流式首 token 延迟:约 1274ms | 可用 | 2500 | 已接入 | https://api-docs.deepseek.com/zh-cn | 2026/7/1 | 流式对话中存在乱码 | | 2 | deepseek-v4-pro | 官方渠道 | 适合复杂推理、高精度代码开发、专业文档分析、高风险合规业务,是对质量要求远高于成本 / 延迟的旗舰场景首选。 | 旗舰级通用模型,1.6T 总参 / 49B 激活,1M 超长上下文,强推理 + 高精度代码 + 复杂 Agent 能力,性能比肩国际顶级模型,适合高要求复杂任务。 | API BASE | OEPNAI | 缓存命中输入仅 0.025 元 / 百万 tokens,缓存未命中输入3 元 / 百万 tokens,输出 6 元 / 百万 tokens。 | 1M | 支持 | 支持 | 已完成 | 非流式总延迟:约 1822ms 流式首 token 延迟:约 1107ms | 可用 | 500 | 已接入 | https://api-docs.deepseek.com/zh-cn | 2026/7/1 | 基础对话和流式对话中存在输出空白的现象 | +| 3 | kimi-k2.6 | 官方渠道 | Kimi K2.6 适合灵活短任务、多模态结合代码、高频轻量调用。 | Kimi K2.6 是 Kimi 最新最智能的模型,Kimi K2.6 的通用 Agent、代码、视觉理解等综合能力得到全面提升,其中在博士级难度的完整版人类最后的考试(Humanity’s Last Exam)、在考察模型真实软件工程能力的 SWE-Bench Pro、评估 Agent 深度检索能力的 DeepSearchQA 等基准测试中均取得行业领先的成绩,同时支持文本、图片与视频输入,思考与非思考模式,对话与 Agent 任务。 | API BASE | OEPNAI | 缓存命中输入仅 1.1 元 / 百万 tokens,缓存未命中输入6.5 元 / 百万 tokens,输出 27 元 / 百万 tokens。 | 256K | 支持 | 支持 | 已完成 | 待测试 | 可用 | 3 | 已接入 | https://platform.kimi.com/docs/overview | 2026/7/2 | 模型可用性有待进一步测试。 | +| 4 | kimi-k2.7-code | 官方渠道 | K2.7 Code 主打复杂编码、长工程、Agent 自动化等专业代码场景。 | Kimi K2.7 Code 是 Kimi 迄今最智能的 Coding 模型,在长上下文中更可靠地遵循指令,能以更高的成功率完成编程任务,同时支持文本、图片与视频输入,思考模式,对话与 Agent 任务。 | API BASE | OEPNAI | 缓存命中输入仅 1.3 元 / 百万 tokens,缓存未命中输入6.5 元 / 百万 tokens,输出 28 元 / 百万 tokens。 | 256K | 支持 | 支持 | 已完成 | 待测试 | 可用 | 3 | 已接入 | https://platform.kimi.com/docs/overview | 2026/7/3 | 模型可用性有待进一步测试。 |