clash-linux commited on
Commit
8092697
·
verified ·
1 Parent(s): 7cb5e36

Upload 17 files

Browse files
README.md CHANGED
@@ -1,13 +1,3 @@
1
- ---
2
- title: Notion2API Node.js
3
- emoji: 🚀
4
- colorFrom: green
5
- colorTo: blue
6
- sdk: docker
7
- app_port: 7860
8
- pinned: false
9
- ---
10
-
11
  # Notion API 轻量级客户端
12
 
13
  这个项目提供了一个轻量级的 Notion API 客户端,可以在资源受限的环境(如 Termux)中运行,无需完整的浏览器环境。
@@ -135,4 +125,83 @@ if (FETCHED_IDS_SUCCESSFULLY) {
135
  - `jsdom`: 提供 DOM API 的轻量级模拟
136
  - `dotenv`: 加载环境变量
137
  - `express`: Web 服务器框架
138
- - `https-proxy-agent`: 支持 HTTPS 代理
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Notion API 轻量级客户端
2
 
3
  这个项目提供了一个轻量级的 Notion API 客户端,可以在资源受限的环境(如 Termux)中运行,无需完整的浏览器环境。
 
125
  - `jsdom`: 提供 DOM API 的轻量级模拟
126
  - `dotenv`: 加载环境变量
127
  - `express`: Web 服务器框架
128
+ - `https-proxy-agent`: 支持 HTTPS 代理
129
+
130
+ ## Cookie管理功能
131
+
132
+ 本项目新增了Cookie管理功能,可以更方便地管理多个Notion Cookie,避免在.env文件中手动编辑长字符串。
133
+
134
+ ### 使用方法
135
+
136
+ #### 1. 通过文件管理Cookie
137
+
138
+ 在项目根目录创建一个`cookies.txt`文件,每行一个完整的Cookie字符串:
139
+
140
+ ```
141
+ cookie1_string_here
142
+ cookie2_string_here
143
+ cookie3_string_here
144
+ ```
145
+
146
+ 然后在`.env`文件中设置:
147
+
148
+ ```
149
+ COOKIE_FILE=cookies.txt
150
+ ```
151
+
152
+ 系统启动时会自动从该文件加载Cookie。
153
+
154
+ #### 2. 使用Cookie管理工具
155
+
156
+ 项目提供了一个命令行工具来管理Cookie:
157
+
158
+ ```bash
159
+ # 使用npm脚本运行
160
+ npm run cookie
161
+
162
+ # 或者全局安装后运行
163
+ npm link
164
+ notion-cookie
165
+ ```
166
+
167
+ 命令行工具支持以下功能:
168
+
169
+ - `help`: 显示帮助信息
170
+ - `list`: 列出所有已加载的Cookie
171
+ - `add`: 添加新的Cookie
172
+ - `validate`: 验证所有Cookie的有效性
173
+ - `remove`: 删除指定的Cookie
174
+ - `save`: 保存Cookie到文件
175
+ - `load`: 从文件加载Cookie
176
+ - `exit`: 退出程序
177
+
178
+ ### Cookie轮询机制
179
+
180
+ 系统会自动轮询使用所有有效的Cookie,当一个Cookie返回401错误(未授权)时,会自动将其标记为无效并切换到下一个Cookie。这样可以提高系统的可靠性和可用性。
181
+
182
+ ### 文件格式支持
183
+
184
+ Cookie管理器支持两种文件格式:
185
+
186
+ 1. 文本格式(.txt):每行一个Cookie
187
+ 2. JSON格式(.json):包含Cookie数组的JSON文件
188
+
189
+ ```json
190
+ {
191
+ "cookies": [
192
+ "cookie1_string_here",
193
+ "cookie2_string_here"
194
+ ],
195
+ "updatedAt": "2023-08-01T12:00:00.000Z",
196
+ "count": 2
197
+ }
198
+ ```
199
+
200
+ 或者简单的数组:
201
+
202
+ ```json
203
+ [
204
+ "cookie1_string_here",
205
+ "cookie2_string_here"
206
+ ]
207
+ ```
cookies.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ notion_browser_id=a8182934-adff-461b-8c79-1506999adea4; device_id=200d872b-594c-812e-b369-003bcda8cdcd; _gcl_au=1.1.1170453355.1748542388; _ga=GA1.1.701605925.1748542389; _hjSessionUser_3664679=eyJpZCI6ImVlMmY4ZDIxLTMxY2QtNWMzNS1hZmQ1LThmYThjMjBiMTdhMCIsImNyZWF0ZWQiOjE3NDg1NDIzOTExMzcsImV4aXN0aW5nIjp0cnVlfQ==; notion_check_cookie_consent=false; _fbp=fb.1.1749309115969.575678538958199822; csrf=3c4feb4b-6326-40c0-9475-4e1884f6cbbd; notion_user_id=20cd872b-594c-8173-b5d1-0002cced3694; p_sync_session=%7B%22tokenId%22%3A%22v02%3Async_session%3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAswAbKutGqwNrCoDhEd_Shbu9h-MytbYGKknbSPCj2F07ufgzyXQZe80OIazyLYDe2Hlhhjj_zMaGxcg12txNRirfgJ67hr95aQVt%22%2C%22userIds%22%3A%5B%2220cd872b-594c-8173-b5d1-0002cced3694%22%5D%7D; _cioid=20cd872b594c8173b5d10002cced3694; _cfuvid=Ch1SRiUHxTgpm9P1m2Wpz62Nic_yTi97J7_R4lxKNKE-1749372872454-0.0.1.1-604800000; notion_cookie_sync_completed=%7B%22completed%22%3Atrue%2C%22version%22%3A4%2C%22attempts%22%3A2%7D; NEXT_LOCALE=en-US; notion_users=[%2220cd872b-594c-8173-b5d1-0002cced3694%22]; token_v2=v03%3AeyJhbGciOiJkaXIiLCJraWQiOiJwcm9kdWN0aW9uOnRva2VuLXYzOjIwMjQtMTEtMDciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..ylhymya9pB__xkYLuLoKQA.c5dFHyRg1Eswr-qdbXqe7Jg9G7-VKguFtD0A1lm3SKw5fudZza9gHO69Od38u8arQjpNHv6CmxCDqNUAEtLtkks5mz8DjwfhxuPQhzkP8VjsHKD7JAPf3TU5LhounQGTO4OJ_MwQoEFqN-UUUPkoHBa1waJQ4CM5a8vAA8yeP4aqI7juS_UUp058MNiZZ2iYihBXYzX3Uepz2Hr0kQPzswUUlyuok5M_AWVfS-ws8hFSNgiHdSc5kZu5vk8liUBy6RCNELeHctUX1sXbiubIoy3yK9fHQDT2wyALecwmsy_FZZi_TOMIdqQkDRUZDzhwv08sVCe2u99Ldl9S_eIOxy6Om_PhCo3M0ddyjKS6ry_VmHPURdl221XOxkK2dD_T.gp5Uxr5t5Jj2bsyUQjqxmoxtQAErkLVuNhSoI-AwBr8; _ga_9ZJ8CB186L=GS2.1.s1749372875$o5$g0$t1749372875$j60$l0$h0; _rdt_uuid=1748542388717.64a3c6ac-6961-4401-9aec-9dfbb27dd932; _hjSession_3664679=eyJpZCI6IjkxYTE2Nzk0LTM0YzMtNDJlYi1hOTkyLWQ0Nzc1NDdjODkyYyIsImMiOjE3NDkzNzI4NzY2NjQsInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjowLCJzcCI6MH0=; __cf_bm=krgtdFh.9RpW333_.Y2qbyZSNT8wSbuJ4Pu_00k76D8-1749372879-1.0.1.1-Wi_RWsajHMTPPLBhAXWddtmDmoNCjzZe92yn1MqJjMJBAyXqx0p36dbnI9DSbtGQmFdc.JeskBIyvFMX4xmHcrFlGfr1S5cZctCE3d_BQ_547JaGhar9jdN3ul4US2MT; notion_locale=en-US%2Fautodetect
2
+ notion_browser_id=fdf38010-21d4-433e-a2fd-65cab87f49f6; device_id=202d872b-594c-81d6-9a95-003bb32158fc; NEXT_LOCALE=zh-CN; _gcl_au=1.1.132916819.1748542408; _ga=GA1.1.1824875636.1748542409; _fbp=fb.1.1748542411909.266391455622580792; notion_user_id=200d872b-594c-81d3-bb1b-00024f30311d; p_sync_session=%7B%22tokenId%22%3A%22v02%3Async_session%3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlax4r-9CwW1tAnM4hm7SsLu9h-MytbYGKknbSPCj2F07ufgzyShCKc0MIvr1LYGI2ihhhjiqn8aHlsgy2otKQnuChsnq0LwuaQVt%22%2C%22userIds%22%3A%5B%22200d872b-594c-81d3-bb1b-00024f30311d%22%5D%7D; _cioid=200d872b594c81d3bb1b00024f30311d; notion_cookie_sync_completed=%7B%22completed%22%3Atrue%2C%22version%22%3A4%2C%22attempts%22%3A1%7D; notion_users=[%22200d872b-594c-81d3-bb1b-00024f30311d%22]; _hjSessionUser_3664679=eyJpZCI6IjgwYjAzZGViLWUxZmMtNTA3MC1iNDA2LWMyNzhlODdiZDdlNyIsImNyZWF0ZWQiOjE3NDg1NDI0MTE4ODEsImV4aXN0aW5nIjp0cnVlfQ==; notion_check_cookie_consent=false; _cfuvid=wTlmfFOGFuwtjS3B5cpDgBYYEQVRx657xz6GO.mLbGM-1749372976498-0.0.1.1-604800000; token_v2=v03%3AeyJhbGciOiJkaXIiLCJraWQiOiJwcm9kdWN0aW9uOnRva2VuLXYzOjIwMjQtMTEtMDciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..Mp-BxJaoO0DWkZeGO_lskg.7AyTec3BJIN0GBIQbnLVNXDZMa9L96S0K8P-06lViUG7CjbZugK54J7TdHEvNo-OU9p07TmTtb-tLa2gHUGXJi6VWh9y1613W6vGGvsEz7CDi8tta7HRVuLCacry9nAH5txg3gk-1c_HE7Yt9up2ZGuZkR34UWPDScwoHizNfspAsdwFgFvbStFZ1uDDD0u8zODoYr5--VWcdR8nKu1Cv6dWPm7xXBzRBNwaYPmEYsDDHxpSGMQF1_goLi8AoRYxIM-s9wEGoU6WCy-n4fgPUt5HXvirQ_4RSqSzcugnOpCjAG9fNIQ5En6knr8B9JyR04XxVUI_7WCG1KOsKuReAfHg0lD_e7BI-sVkFW27N-k.3kFpD-Z46S89LoPkH_CBkZH_zTxSaVhQ-f8nU92wZcI; _ga_9ZJ8CB186L=GS2.1.s1749372981$o5$g0$t1749372981$j60$l0$h0; _rdt_uuid=1748542408747.963fa76b-30e7-4e18-ab01-aba3f7a1457e; _hjSession_3664679=eyJpZCI6IjliMTQ1NjE3LWVkODctNGM1OC1hNTNlLWIwMmYyZTQ2OWNhZiIsImMiOjE3NDkzNzI5ODEyNzAsInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjowLCJzcCI6MH0=; __cf_bm=VuvmjnWN46h3pr265Lq8SURwcFrcRgsGwSfYPoKX4hQ-1749372983-1.0.1.1-DAD44JAgwln368624Zuu98Lbc8_y9bYofxaClaQ2cUA.aRy6JHa8APWWqQdD8P.SvjHluoSk3v2xaN6wAd_jjIeofPU6lmgzLlgZeybnMcCbdJlH.EqZJtuFNjNaPtxV; notion_locale=zh-CN%2Fautodetect
3
+ notion_browser_id=3df6d794-ba0a-4644-816c-29c8179d3f0e; device_id=20cd872b-594c-8118-9580-003ba37e2e8f; notion_check_cookie_consent=false; NEXT_LOCALE=zh-CN; _gcl_au=1.1.2123998494.1749355832; _ga=GA1.1.813384426.1749355835; _fbp=fb.1.1749355838864.415958857195472441; csrf=e233e94c-6881-417d-91f3-a0badd44dcad; notion_user_id=20bd872b-594c-81a1-b249-0002d1feacbe; p_sync_session=%7B%22tokenId%22%3A%22v02%3Async_session%3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAs76a4X79UYBYNhOmEp3ep7u9h-MytbYGKknbSPCj2F07ufgzySNOe81QcaDzLdSK3i5hhm2gzMbfl8gz2oBKF3uEjJm8178obwVt%22%2C%22userIds%22%3A%5B%2220bd872b-594c-81a1-b249-0002d1feacbe%22%5D%7D; _cioid=20bd872b594c81a1b2490002d1feacbe; notion_cookie_sync_completed=%7B%22completed%22%3Atrue%2C%22version%22%3A4%2C%22attempts%22%3A1%7D; notion_users=[%2220bd872b-594c-81a1-b249-0002d1feacbe%22]; _hjSessionUser_3664679=eyJpZCI6IjU1OThkY2MzLWJiNzItNTA5YS1hMDgyLTcwZTVmZTE0YWU1MiIsImNyZWF0ZWQiOjE3NDkzNTU4MzgyNDEsImV4aXN0aW5nIjp0cnVlfQ==; _cfuvid=jKt0witwH78x_tHSuwCsH7bTlG4MmLA2v3SaHDE.mpc-1749373022601-0.0.1.1-604800000; notion_locale=zh-CN/autodetect; token_v2=v03%3AeyJhbGciOiJkaXIiLCJraWQiOiJwcm9kdWN0aW9uOnRva2VuLXYzOjIwMjQtMTEtMDciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..8cqt-fbGfogzRYZQ_svhuA.cdVPRcwnIvmJYlVtuuJx6Q8LO37JuPHfvnba08p6QLQERxmZ7TqTpGciFO_Krohj_iKFpGxvhAW9CoasH9--IDqpSvxhqxUi-CN1XoG7L6wjQI5KdNrzsMDb2C4_qkk8us4VdmAbfFHRfA0G317xJcyBWNj3T9N2aIkPru_Fd4raUoqjvWMFfSz_6qU-fN2SEM_IYDz1yFEBncErNPnRcCeBxvoGxoARCn-AP-7tBlLO6atw9NHA71a--Wu4xOjfkllB7UESw28T9kjwwU4Wz44RV4Jqec2udoD3f2AQOgG7yINoyDn8NnLIxoEsFVhcLDhBA0BxU-44NgqskmHdLdpcfM-aQARYWICDbgvcpXc.gLHW6mwrKXZRfvu0XGE43GwAmxEiNHjTi8WU_u8xkJI; _ga_9ZJ8CB186L=GS2.1.s1749373022$o2$g0$t1749373022$j60$l0$h0; _rdt_uuid=1749355834100.7a6ae1dc-56ed-4fae-a4da-03b2b79b51aa; __cf_bm=WdJeRHtNpyK6W1fzG.hy1eCituCRNXX66LiwYjQDMq4-1749373025-1.0.1.1-ozb75Fsi3DlqPljtK1hAM2jlC7KhTI6W8OxJ6TXFc2.rJAMOWSoyzZNwk4JHn6NeCY9VFvKfnPGXhsSGiz2KDrm1i7D6TcUIvNMu_Soy5dPcQIKCZXHdfY_qMihxUmEo; _hjSession_3664679=eyJpZCI6IjlhNGI5ODg2LTA3M2QtNGEwMC05ODdjLWU2ZWUwOWZkNDYzZiIsImMiOjE3NDkzNzMwMjMzMjksInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjowLCJzcCI6MX0=
4
+ notion_browser_id=81aa048a-1a74-4fa6-bf68-9bd969fe0957; device_id=20cd872b-594c-818c-b13b-003bd4b64ce2; notion_check_cookie_consent=false; _gcl_au=1.1.1953388871.1749357888; _ga=GA1.1.60054060.1749357889; _fbp=fb.1.1749357890210.220600185514046487; csrf=1337709b-bdac-4940-9658-d701e789d5d5; notion_user_id=20cd872b-594c-8165-ac0f-000242d1ef5d; p_sync_session=%7B%22tokenId%22%3A%22v02%3Async_session%3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2LrwUqJv_xBJrZas5JL1uLu9h-MytbYGKknbSPCj2F07ufgzyXQaf88Md6GgLdbU23phhjGrzsbfn5gz2twcRiqEh8zg0ex9bAVt%22%2C%22userIds%22%3A%5B%2220cd872b-594c-8165-ac0f-000242d1ef5d%22%5D%7D; _cioid=20cd872b594c8165ac0f000242d1ef5d; notion_cookie_sync_completed=%7B%22completed%22%3Atrue%2C%22version%22%3A4%2C%22attempts%22%3A1%7D; NEXT_LOCALE=en-US; notion_users=[%2220cd872b-594c-8165-ac0f-000242d1ef5d%22]; _hjSessionUser_3664679=eyJpZCI6IjZhOWYyMTk2LTA4MTMtNTg0MS05MDJjLWFhYzc0YjljZjMyMyIsImNyZWF0ZWQiOjE3NDkzNTc4OTAyMDIsImV4aXN0aW5nIjp0cnVlfQ==; notion_locale=en-US/autodetect; token_v2=v03%3AeyJhbGciOiJkaXIiLCJraWQiOiJwcm9kdWN0aW9uOnRva2VuLXYzOjIwMjQtMTEtMDciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..jacda0_C_ohrs8jZaEtaNQ.RhRp-HCDJiBVNeDX1_4rEV-GDnlWC6nPu-v5ft6xLqXd1VyWsQUGMyJfGNLE0WpYlMu1IPpdK6MFPWg5CWdyqssBXVOjq7RQ0HT1eD2vq3arymTP2Dr-yI4_MvVl68geC2Wf6WV2u6PGNTg0U9KfXDQku4zCE1Z-8NNmajOJLQVxrJRTd7jmTu9Y2tCsjQFmCwJIkM97nX49vVDF2D2jqCOXveKzBP-YUf5wHgAKirVCWVrRdzqpF7q99zMco6ud9kM0lwSYaR_LM0IxVBHXLqxWaSD6EevdtObI0mkzTKMdmO5KDkB79BbYAbGcCcruoqBnjUvP1_dgj40C1KRIj-AG8fmo0jlaY9ho9MPiCXI.55neTz3EAV_1mI_L48Ycm4g5ZEhqI3h-xx_VxH42eTY; _cfuvid=4RTSChhcZ3Ej_MnrypcZstukGvzsEw6.0aNyVeBZoGM-1749373079534-0.0.1.1-604800000; _ga_9ZJ8CB186L=GS2.1.s1749373078$o2$g0$t1749373078$j60$l0$h0; _rdt_uuid=1749357888777.49b9f069-6e3e-49a4-8616-02bf3cceab99; _hjSession_3664679=eyJpZCI6IjFhYzY0YTViLTlmODAtNDVkNC1hNjZlLTc1OGIzYTM4YjZkNCIsImMiOjE3NDkzNzMwNzgyODAsInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjowLCJzcCI6MH0=; __cf_bm=CwobhZNL_9339LZOPKM3_A0LzIn1ZI5LxHYvCsX_Az0-1749373080-1.0.1.1-diCnJXBwLMYQ8EPCllDnzKb56mGpO73GBCUqIQT3G_m7ouJ1np.4IaghLvovrt5ZeejVWgEzeqQ4OwU3vaMJbdvtYCTKs2p4tQh.kKjmSr26jQc0Gdui.ogY6stqUf11
package-lock.json CHANGED
@@ -16,8 +16,10 @@
16
  "https-proxy-agent": "^7.0.2",
17
  "jsdom": "^22.1.0",
18
  "node-fetch": "^3.3.2",
19
- "playwright": "^1.40.1",
20
- "socks-proxy-agent": "^8.0.5"
 
 
21
  },
22
  "devDependencies": {
23
  "nodemon": "^3.0.2"
@@ -996,18 +998,6 @@
996
  "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
997
  "license": "ISC"
998
  },
999
- "node_modules/ip-address": {
1000
- "version": "9.0.5",
1001
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
1002
- "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
1003
- "dependencies": {
1004
- "jsbn": "1.1.0",
1005
- "sprintf-js": "^1.1.3"
1006
- },
1007
- "engines": {
1008
- "node": ">= 12"
1009
- }
1010
- },
1011
  "node_modules/ipaddr.js": {
1012
  "version": "1.9.1",
1013
  "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -1069,11 +1059,6 @@
1069
  "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
1070
  "license": "MIT"
1071
  },
1072
- "node_modules/jsbn": {
1073
- "version": "1.1.0",
1074
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
1075
- "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
1076
- },
1077
  "node_modules/jsdom": {
1078
  "version": "22.1.0",
1079
  "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
@@ -1790,67 +1775,6 @@
1790
  "node": ">=10"
1791
  }
1792
  },
1793
- "node_modules/smart-buffer": {
1794
- "version": "4.2.0",
1795
- "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1796
- "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
1797
- "engines": {
1798
- "node": ">= 6.0.0",
1799
- "npm": ">= 3.0.0"
1800
- }
1801
- },
1802
- "node_modules/socks": {
1803
- "version": "2.8.4",
1804
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
1805
- "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
1806
- "dependencies": {
1807
- "ip-address": "^9.0.5",
1808
- "smart-buffer": "^4.2.0"
1809
- },
1810
- "engines": {
1811
- "node": ">= 10.0.0",
1812
- "npm": ">= 3.0.0"
1813
- }
1814
- },
1815
- "node_modules/socks-proxy-agent": {
1816
- "version": "8.0.5",
1817
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
1818
- "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
1819
- "dependencies": {
1820
- "agent-base": "^7.1.2",
1821
- "debug": "^4.3.4",
1822
- "socks": "^2.8.3"
1823
- },
1824
- "engines": {
1825
- "node": ">= 14"
1826
- }
1827
- },
1828
- "node_modules/socks-proxy-agent/node_modules/debug": {
1829
- "version": "4.4.1",
1830
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
1831
- "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
1832
- "dependencies": {
1833
- "ms": "^2.1.3"
1834
- },
1835
- "engines": {
1836
- "node": ">=6.0"
1837
- },
1838
- "peerDependenciesMeta": {
1839
- "supports-color": {
1840
- "optional": true
1841
- }
1842
- }
1843
- },
1844
- "node_modules/socks-proxy-agent/node_modules/ms": {
1845
- "version": "2.1.3",
1846
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1847
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1848
- },
1849
- "node_modules/sprintf-js": {
1850
- "version": "1.1.3",
1851
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
1852
- "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
1853
- },
1854
  "node_modules/statuses": {
1855
  "version": "2.0.1",
1856
  "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
 
16
  "https-proxy-agent": "^7.0.2",
17
  "jsdom": "^22.1.0",
18
  "node-fetch": "^3.3.2",
19
+ "playwright": "^1.40.1"
20
+ },
21
+ "bin": {
22
+ "notion-cookie": "src/cookie-cli.js"
23
  },
24
  "devDependencies": {
25
  "nodemon": "^3.0.2"
 
998
  "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
999
  "license": "ISC"
1000
  },
 
 
 
 
 
 
 
 
 
 
 
 
1001
  "node_modules/ipaddr.js": {
1002
  "version": "1.9.1",
1003
  "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 
1059
  "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
1060
  "license": "MIT"
1061
  },
 
 
 
 
 
1062
  "node_modules/jsdom": {
1063
  "version": "22.1.0",
1064
  "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
 
1775
  "node": ">=10"
1776
  }
1777
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1778
  "node_modules/statuses": {
1779
  "version": "2.0.1",
1780
  "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
package.json CHANGED
@@ -1,34 +1,37 @@
1
- {
2
- "name": "notion2api-nodejs",
3
- "version": "1.0.0",
4
- "description": "Notion API client with lightweight browser-free option",
5
- "main": "src/lightweight-client-express.js",
6
- "type": "module",
7
- "scripts": {
8
- "start": "node src/lightweight-client-express.js",
9
- "dev": "nodemon src/lightweight-client-express.js",
10
- "original": "node src/index.js"
11
- },
12
- "keywords": [
13
- "notion",
14
- "openai",
15
- "api",
16
- "bridge"
17
- ],
18
- "author": "",
19
- "license": "MIT",
20
- "dependencies": {
21
- "axios": "^1.9.0",
22
- "chalk": "^4.1.2",
23
- "dotenv": "^16.3.1",
24
- "express": "^4.18.2",
25
- "https-proxy-agent": "^7.0.2",
26
- "jsdom": "^22.1.0",
27
- "node-fetch": "^3.3.2",
28
- "playwright": "^1.40.1",
29
- "socks-proxy-agent": "^8.0.5"
30
- },
31
- "devDependencies": {
32
- "nodemon": "^3.0.2"
33
- }
34
- }
 
 
 
 
1
+ {
2
+ "name": "notion2api-nodejs",
3
+ "version": "1.0.0",
4
+ "description": "Notion API client with lightweight browser-free option",
5
+ "main": "src/lightweight-client-express.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "notion-cookie": "src/cookie-cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/lightweight-client-express.js",
12
+ "dev": "nodemon src/lightweight-client-express.js",
13
+ "original": "node src/index.js",
14
+ "cookie": "node src/cookie-cli.js"
15
+ },
16
+ "keywords": [
17
+ "notion",
18
+ "openai",
19
+ "api",
20
+ "bridge"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "axios": "^1.9.0",
26
+ "chalk": "^4.1.2",
27
+ "dotenv": "^16.3.1",
28
+ "express": "^4.18.2",
29
+ "https-proxy-agent": "^7.0.2",
30
+ "jsdom": "^22.1.0",
31
+ "node-fetch": "^3.3.2",
32
+ "playwright": "^1.40.1"
33
+ },
34
+ "devDependencies": {
35
+ "nodemon": "^3.0.2"
36
+ }
37
+ }
proxy_server.log CHANGED
@@ -1,141 +1,43 @@
1
- 2025/06/07 22:39:46.565674 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
2
- 2025/06/07 22:39:46.581064 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
3
- 2025/06/07 22:39:46.581064 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
4
- 2025/06/07 22:39:46.581064 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
5
- 2025/06/07 22:39:46.581064 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
6
- 2025/06/07 22:39:46.581064 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
7
- 2025/06/07 22:39:46.581064 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
8
- 2025/06/07 22:39:46.585274 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
9
- 2025/06/07 22:40:34.956835 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
10
- 2025/06/07 22:40:34.982142 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
11
- 2025/06/07 22:40:34.982142 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
12
- 2025/06/07 22:40:34.982142 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
13
- 2025/06/07 22:40:34.982142 chrome_tls_proxy.go:710: 已用并发限制:允许同一IP发起多个并发请求
14
- 2025/06/07 22:40:34.982142 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
15
- 2025/06/07 22:40:34.982142 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹连接
16
- 2025/06/07 22:40:42.541508 chrome_tls_proxy.go:537: 处理流式请求: POST https://www.notion.so/api/v3/runInferenceTranscript
17
- 2025/06/07 22:40:42.594747 chrome_tls_proxy.go:153: 成功解析域名: www.notion.so -> 198.18.0.48
18
- 2025/06/07 22:40:42.594747 chrome_tls_proxy.go:187: 建立TCP连接到: www.notion.so:443 (IP: 198.18.0.48)
19
- 2025/06/07 22:40:42.595977 chrome_tls_proxy.go:196: TCP连接成功建立到 198.18.0.48:443
20
- 2025/06/07 22:40:43.212470 chrome_tls_proxy.go:214: TLS握手成功,使用Chrome指纹
21
- 2025/06/07 22:40:43.212470 chrome_tls_proxy.go:218: 协商协议: h2
22
- 2025/06/07 22:40:43.213065 chrome_tls_proxy.go:222: 使用Chrome TLS指纹HTTP/2请求
23
- 2025/06/07 22:40:50.606946 chrome_tls_proxy.go:606: 流式传输完成: POST https://www.notion.so/api/v3/runInferenceTranscript, 总计 5491 字节
24
- 2025/06/07 22:41:28.720532 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行http://localhost:10655/proxy
25
- 2025/06/07 22:41:28.733708 chrome_tls_proxy.go:707: 使方法向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
26
- 2025/06/07 22:41:28.733708 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
27
- 2025/06/07 22:41:28.733708 chrome_tls_proxy.go:709: 已禁用速率限:允许无限制请求
28
- 2025/06/07 22:41:28.733708 chrome_tls_proxy.go:710: 已禁并发限制:允许同一IP发起多个并发请求
29
- 2025/06/07 22:41:28.733708 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
30
- 2025/06/07 22:41:28.733708 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
31
- 2025/06/07 22:42:27.903516 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行http://localhost:10655/proxy
32
- 2025/06/07 22:42:27.916172 chrome_tls_proxy.go:707: 使方法向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
33
- 2025/06/07 22:42:27.916172 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
34
- 2025/06/07 22:42:27.916172 chrome_tls_proxy.go:709: 已禁用速率限:允许无限制请求
35
- 2025/06/07 22:42:27.916172 chrome_tls_proxy.go:710: 已禁并发限制:允许同一IP发起多个并发请求
36
- 2025/06/07 22:42:27.916172 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
37
- 2025/06/07 22:42:27.916172 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
38
- 2025/06/07 22:43:04.738913 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
39
- 2025/06/07 22:43:04.755351 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
40
- 2025/06/07 22:43:04.755866 chrome_tls_proxy.go:708: 支持流式响应需要在请求体中添加 'stream': true
41
- 2025/06/07 22:43:04.755866 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
42
- 2025/06/07 22:43:04.755866 chrome_tls_proxy.go:710: 已禁并发限制:允许同一IP发起多个并发请求
43
- 2025/06/07 22:43:04.755866 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
44
- 2025/06/07 22:43:04.755866 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
45
- 2025/06/07 22:47:31.473790 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
46
- 2025/06/07 22:47:31.506810 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
47
- 2025/06/07 22:47:31.506810 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
48
- 2025/06/07 22:47:31.506810 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
49
- 2025/06/07 22:47:31.506810 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
50
- 2025/06/07 22:47:31.506810 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
51
- 2025/06/07 22:47:31.506810 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
52
- 2025/06/07 22:47:34.873521 chrome_tls_proxy.go:537: 处理流式请求: POST https://www.notion.so/api/v3/runInferenceTranscript
53
- 2025/06/07 22:47:34.893684 chrome_tls_proxy.go:153: 成功解析域名: www.notion.so -> 198.18.0.48
54
- 2025/06/07 22:47:34.893684 chrome_tls_proxy.go:187: 建立TCP连接到: www.notion.so:443 (IP: 198.18.0.48)
55
- 2025/06/07 22:47:34.894769 chrome_tls_proxy.go:196: TCP连接成功建立到 198.18.0.48:443
56
- 2025/06/07 22:47:35.209971 chrome_tls_proxy.go:214: TLS握手成功,使用Chrome指纹
57
- 2025/06/07 22:47:35.209971 chrome_tls_proxy.go:218: 协商的协议: h2
58
- 2025/06/07 22:47:35.209971 chrome_tls_proxy.go:222: 使用Chrome TLS指纹处理HTTP/2请求
59
- 2025/06/07 22:47:41.994124 chrome_tls_proxy.go:606: 流式传输完成: POST https://www.notion.so/api/v3/runInferenceTranscript, 总计 4791 字节
60
- 2025/06/07 23:04:02.434223 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
61
- 2025/06/07 23:04:02.441993 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
62
- 2025/06/07 23:04:02.441993 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
63
- 2025/06/07 23:04:02.441993 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
64
- 2025/06/07 23:04:02.441993 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
65
- 2025/06/07 23:04:02.441993 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
66
- 2025/06/07 23:04:02.441993 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
67
- 2025/06/07 23:08:08.021527 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
68
- 2025/06/07 23:08:08.026578 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
69
- 2025/06/07 23:08:08.026578 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
70
- 2025/06/07 23:08:08.026578 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
71
- 2025/06/07 23:08:08.026578 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
72
- 2025/06/07 23:08:08.026578 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
73
- 2025/06/07 23:08:08.026578 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
74
- 2025/06/07 23:08:08.031126 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
75
- 2025/06/07 23:08:33.883353 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
76
- 2025/06/07 23:08:33.887267 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
77
- 2025/06/07 23:08:33.887267 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
78
- 2025/06/07 23:08:33.887267 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
79
- 2025/06/07 23:08:33.887267 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
80
- 2025/06/07 23:08:33.887267 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
81
- 2025/06/07 23:08:33.887267 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
82
- 2025/06/07 23:08:33.887941 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
83
- 2025/06/07 23:09:49.976166 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
84
- 2025/06/07 23:09:49.981223 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
85
- 2025/06/07 23:09:49.981223 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
86
- 2025/06/07 23:09:49.981223 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
87
- 2025/06/07 23:09:49.981223 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
88
- 2025/06/07 23:09:49.981223 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
89
- 2025/06/07 23:09:49.981223 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
90
- 2025/06/07 23:49:46.540100 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
91
- 2025/06/07 23:49:46.545351 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
92
- 2025/06/07 23:49:46.545351 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
93
- 2025/06/07 23:49:46.545351 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
94
- 2025/06/07 23:49:46.545351 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
95
- 2025/06/07 23:49:46.545351 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
96
- 2025/06/07 23:49:46.545351 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
97
- 2025/06/07 23:49:46.545880 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
98
- 2025/06/07 23:50:04.636504 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
99
- 2025/06/07 23:50:04.640668 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
100
- 2025/06/07 23:50:04.640668 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
101
- 2025/06/07 23:50:04.640668 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
102
- 2025/06/07 23:50:04.640668 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
103
- 2025/06/07 23:50:04.640668 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
104
- 2025/06/07 23:50:04.640668 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
105
- 2025/06/08 00:44:51.400537 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
106
- 2025/06/08 00:44:51.408056 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
107
- 2025/06/08 00:44:51.408056 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
108
- 2025/06/08 00:44:51.408056 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
109
- 2025/06/08 00:44:51.408056 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
110
- 2025/06/08 00:44:51.408056 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
111
- 2025/06/08 00:44:51.408056 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
112
- 2025/06/08 00:45:24.831086 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
113
- 2025/06/08 00:45:24.838018 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
114
- 2025/06/08 00:45:24.838018 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
115
- 2025/06/08 00:45:24.838018 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
116
- 2025/06/08 00:45:24.838018 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
117
- 2025/06/08 00:45:24.838018 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
118
- 2025/06/08 00:45:24.838018 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
119
- 2025/06/08 00:47:10.787309 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
120
- 2025/06/08 00:47:10.791530 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
121
- 2025/06/08 00:47:10.791530 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
122
- 2025/06/08 00:47:10.791530 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
123
- 2025/06/08 00:47:10.791530 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
124
- 2025/06/08 00:47:10.791530 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
125
- 2025/06/08 00:47:10.791530 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
126
- 2025/06/08 00:47:10.792061 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
127
- 2025/06/08 00:54:49.318617 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
128
- 2025/06/08 00:54:49.325299 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
129
- 2025/06/08 00:54:49.325299 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
130
- 2025/06/08 00:54:49.325299 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
131
- 2025/06/08 00:54:49.325299 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
132
- 2025/06/08 00:54:49.325299 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
133
- 2025/06/08 00:54:49.325299 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
134
- 2025/06/08 00:54:49.325813 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
135
- 2025/06/08 19:06:11.117107 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
136
- 2025/06/08 19:06:11.123148 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
137
- 2025/06/08 19:06:11.123148 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
138
- 2025/06/08 19:06:11.123148 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
139
- 2025/06/08 19:06:11.123148 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
140
- 2025/06/08 19:06:11.123148 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
141
- 2025/06/08 19:06:11.123148 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
 
1
+ 2025/06/09 12:18:12.836692 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
2
+ 2025/06/09 12:18:12.841349 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
3
+ 2025/06/09 12:18:12.841349 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
4
+ 2025/06/09 12:18:12.841349 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
5
+ 2025/06/09 12:18:12.841349 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
6
+ 2025/06/09 12:18:12.841349 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
7
+ 2025/06/09 12:18:12.841349 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
8
+ 2025/06/09 12:20:28.077805 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
9
+ 2025/06/09 12:20:28.082498 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
10
+ 2025/06/09 12:20:28.082498 chrome_tls_proxy.go:708: 支持流式响应需要在请求体中添加 'stream': true
11
+ 2025/06/09 12:20:28.082498 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
12
+ 2025/06/09 12:20:28.082498 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
13
+ 2025/06/09 12:20:28.082498 chrome_tls_proxy.go:711: 已强制使IPv4连接,使用8.8.8.8作为DNS服务器
14
+ 2025/06/09 12:20:28.082498 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
15
+ 2025/06/09 12:21:16.376141 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运在 http://localhost:10655/proxy
16
+ 2025/06/09 12:21:16.382812 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
17
+ 2025/06/09 12:21:16.382812 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
18
+ 2025/06/09 12:21:16.382812 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
19
+ 2025/06/09 12:21:16.382812 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
20
+ 2025/06/09 12:21:16.382812 chrome_tls_proxy.go:711: 已强制使用IPv4连接使用8.8.8.8作为DNS服务器
21
+ 2025/06/09 12:21:16.382812 chrome_tls_proxy.go:712: 使用Chrome浏览器TLS指纹进行连接
22
+ 2025/06/09 12:22:43.593498 chrome_tls_proxy.go:706: Chrome TLS指纹服务器运行在 http://localhost:10655/proxy
23
+ 2025/06/09 12:22:43.597928 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
24
+ 2025/06/09 12:22:43.597928 chrome_tls_proxy.go:708: 支持流式响应,需要请求体中添加 'stream': true
25
+ 2025/06/09 12:22:43.597928 chrome_tls_proxy.go:709: 已禁速率限制允许无限制请求
26
+ 2025/06/09 12:22:43.597928 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
27
+ 2025/06/09 12:22:43.597928 chrome_tls_proxy.go:711: 已使用IPv4连接,并使用8.8.8.8作为DNS服务器
28
+ 2025/06/09 12:22:43.597928 chrome_tls_proxy.go:712: 使Chrome浏览器的TLS指纹进行连接
29
+ 2025/06/09 12:23:33.082331 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
30
+ 2025/06/09 12:23:33.087347 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
31
+ 2025/06/09 12:23:33.087347 chrome_tls_proxy.go:708: 支持流式响应,需要请求体中添加 'stream': true
32
+ 2025/06/09 12:23:33.087347 chrome_tls_proxy.go:709: 已禁速率限制允许无限制请求
33
+ 2025/06/09 12:23:33.087347 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
34
+ 2025/06/09 12:23:33.087347 chrome_tls_proxy.go:711: 已使用IPv4连接,并使用8.8.8.8作为DNS服务器
35
+ 2025/06/09 12:23:33.087347 chrome_tls_proxy.go:712: 使Chrome浏览器的TLS指纹进行连接
36
+ 2025/06/09 12:24:37.277389 chrome_tls_proxy.go:537: 处理流式请求: POST https://www.notion.so/api/v3/runInferenceTranscript
37
+ 2025/06/09 12:24:37.317968 chrome_tls_proxy.go:153: 成功解析域名: www.notion.so -> 198.18.0.63
38
+ 2025/06/09 12:24:37.317968 chrome_tls_proxy.go:187: 建立TCP连接到: www.notion.so:443 (IP: 198.18.0.63)
39
+ 2025/06/09 12:24:37.319157 chrome_tls_proxy.go:196: TCP连接成功建立到 198.18.0.63:443
40
+ 2025/06/09 12:24:38.271929 chrome_tls_proxy.go:214: TLS握手成功使用Chrome指纹
41
+ 2025/06/09 12:24:38.271929 chrome_tls_proxy.go:218: 协商的协议: h2
42
+ 2025/06/09 12:24:38.273041 chrome_tls_proxy.go:222: 使Chrome TLS指纹处理HTTP/2请求
43
+ 2025/06/09 12:25:14.238223 chrome_tls_proxy.go:606: 流式传输完成: POST https://www.notion.so/api/v3/runInferenceTranscript, 总计 31669 字节
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/CookieManager.js ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { JSDOM } from 'jsdom';
2
+ import fetch from 'node-fetch';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname } from 'path';
7
+
8
+ // 获取当前文件的目录路径
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ // 日志配置
13
+ const logger = {
14
+ info: (message) => console.log(`\x1b[34m[info] ${message}\x1b[0m`),
15
+ error: (message) => console.error(`\x1b[31m[error] ${message}\x1b[0m`),
16
+ warning: (message) => console.warn(`\x1b[33m[warn] ${message}\x1b[0m`),
17
+ success: (message) => console.log(`\x1b[32m[success] ${message}\x1b[0m`),
18
+ };
19
+
20
+ class CookieManager {
21
+ constructor() {
22
+ this.cookieEntries = []; // 存储cookie及其对应的ID
23
+ this.currentIndex = 0;
24
+ this.initialized = false;
25
+ this.maxRetries = 3; // 最大重试次数
26
+ this.proxyUrl = process.env.PROXY_URL || "";
27
+ }
28
+
29
+ /**
30
+ * 从文件加载cookie
31
+ * @param {string} filePath - cookie文件路径
32
+ * @returns {Promise<boolean>} - 是否加载成功
33
+ */
34
+ async loadFromFile(filePath) {
35
+ try {
36
+ // 确保文件路径是绝对路径
37
+ const absolutePath = path.isAbsolute(filePath)
38
+ ? filePath
39
+ : path.join(dirname(__dirname), filePath);
40
+
41
+ logger.info(`从文件加载cookie: ${absolutePath}`);
42
+
43
+ // 检查文件是否存在
44
+ if (!fs.existsSync(absolutePath)) {
45
+ logger.error(`Cookie文件不存在: ${absolutePath}`);
46
+ return false;
47
+ }
48
+
49
+ // 读取文件内容
50
+ const fileContent = fs.readFileSync(absolutePath, 'utf8');
51
+
52
+ // 根据文件扩展名处理不同格式
53
+ const ext = path.extname(absolutePath).toLowerCase();
54
+ let cookieArray = [];
55
+
56
+ if (ext === '.json') {
57
+ // JSON格式
58
+ try {
59
+ const jsonData = JSON.parse(fileContent);
60
+ if (Array.isArray(jsonData)) {
61
+ cookieArray = jsonData;
62
+ } else if (jsonData.cookies && Array.isArray(jsonData.cookies)) {
63
+ cookieArray = jsonData.cookies;
64
+ } else {
65
+ logger.error('JSON文件格式错误,应为cookie数组或包含cookies数组的对象');
66
+ return false;
67
+ }
68
+ } catch (error) {
69
+ logger.error(`解析JSON文件失败: ${error.message}`);
70
+ return false;
71
+ }
72
+ } else {
73
+ // 文本格式,每行一个cookie
74
+ cookieArray = fileContent
75
+ .split('\n')
76
+ .map(line => line.trim())
77
+ .filter(line => line && !line.startsWith('#'));
78
+ }
79
+
80
+ logger.info(`从文件中读取了 ${cookieArray.length} 个cookie`);
81
+
82
+ // 初始化cookie
83
+ return await this.initialize(cookieArray.join('|'));
84
+
85
+ } catch (error) {
86
+ logger.error(`从文件加载cookie失败: ${error.message}`);
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 初始化cookie管理器
93
+ * @param {string} cookiesString - 以"|"分隔的cookie字符串
94
+ * @returns {Promise<boolean>} - 是否初始化成功
95
+ */
96
+ async initialize(cookiesString) {
97
+ if (!cookiesString) {
98
+ logger.error('未提供cookie字符串');
99
+ return false;
100
+ }
101
+
102
+ // 分割cookie字符串
103
+ const cookieArray = cookiesString.split('|').map(c => c.trim()).filter(c => c);
104
+
105
+ if (cookieArray.length === 0) {
106
+ logger.error('没有有效的cookie');
107
+ return false;
108
+ }
109
+
110
+ logger.info(`发现 ${cookieArray.length} 个cookie,开始获取对应的ID信息...`);
111
+
112
+ // 清空现有条目
113
+ this.cookieEntries = [];
114
+
115
+ // 为每个cookie获取ID
116
+ for (let i = 0; i < cookieArray.length; i++) {
117
+ const cookie = cookieArray[i];
118
+ logger.info(`正在处理第 ${i+1}/${cookieArray.length} 个cookie...`);
119
+
120
+ const result = await this.fetchNotionIds(cookie);
121
+ if (result.success) {
122
+ this.cookieEntries.push({
123
+ cookie,
124
+ spaceId: result.spaceId,
125
+ userId: result.userId,
126
+ valid: true,
127
+ lastUsed: 0 // 记录上次使用时间戳
128
+ });
129
+ logger.success(`第 ${i+1} 个cookie验证成功`);
130
+ } else {
131
+ if (result.status === 401) {
132
+ logger.error(`第 ${i+1} 个cookie无效(401未授权),已跳过`);
133
+ } else {
134
+ logger.warning(`第 ${i+1} 个cookie验证失败: ${result.error},已跳过`);
135
+ }
136
+ }
137
+ }
138
+
139
+ // 检查是否有有效的cookie
140
+ if (this.cookieEntries.length === 0) {
141
+ logger.error('没有有效的cookie,初始化失败');
142
+ return false;
143
+ }
144
+
145
+ logger.success(`成功初始化 ${this.cookieEntries.length}/${cookieArray.length} 个cookie`);
146
+ this.initialized = true;
147
+ this.currentIndex = 0;
148
+ return true;
149
+ }
150
+
151
+ /**
152
+ * 保存cookie到文件
153
+ * @param {string} filePath - 保存路径
154
+ * @param {boolean} onlyValid - 是否只保存有效的cookie
155
+ * @returns {boolean} - 是否保存成功
156
+ */
157
+ saveToFile(filePath, onlyValid = true) {
158
+ try {
159
+ // 确保文件路径是绝对路径
160
+ const absolutePath = path.isAbsolute(filePath)
161
+ ? filePath
162
+ : path.join(dirname(__dirname), filePath);
163
+
164
+ // 获取要保存的cookie
165
+ const cookiesToSave = onlyValid
166
+ ? this.cookieEntries.filter(entry => entry.valid).map(entry => entry.cookie)
167
+ : this.cookieEntries.map(entry => entry.cookie);
168
+
169
+ // 根据文件扩展名选择保存格式
170
+ const ext = path.extname(absolutePath).toLowerCase();
171
+
172
+ if (ext === '.json') {
173
+ // 保存为JSON格式
174
+ const jsonData = {
175
+ cookies: cookiesToSave,
176
+ updatedAt: new Date().toISOString(),
177
+ count: cookiesToSave.length
178
+ };
179
+ fs.writeFileSync(absolutePath, JSON.stringify(jsonData, null, 2), 'utf8');
180
+ } else {
181
+ // 保存为文本格式,每行一个cookie
182
+ const content = cookiesToSave.join('\n');
183
+ fs.writeFileSync(absolutePath, content, 'utf8');
184
+ }
185
+
186
+ logger.success(`已将 ${cookiesToSave.length} 个cookie保存到文件: ${absolutePath}`);
187
+ return true;
188
+ } catch (error) {
189
+ logger.error(`保存cookie到文件失败: ${error.message}`);
190
+ return false;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * 获取Notion的空间ID和用户ID
196
+ * @param {string} cookie - Notion cookie
197
+ * @returns {Promise<Object>} - 包含ID信息的对象
198
+ */
199
+ async fetchNotionIds(cookie, retryCount = 0) {
200
+ if (!cookie) {
201
+ return { success: false, error: '未提供cookie' };
202
+ }
203
+
204
+ try {
205
+ // 创建JSDOM实例模拟浏览器环境
206
+ const dom = new JSDOM("", {
207
+ url: "https://www.notion.so",
208
+ referrer: "https://www.notion.so/",
209
+ contentType: "text/html",
210
+ includeNodeLocations: true,
211
+ storageQuota: 10000000,
212
+ pretendToBeVisual: true,
213
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
214
+ });
215
+
216
+ // 设置全局对象
217
+ const { window } = dom;
218
+
219
+ // 安全地设置全局对象
220
+ if (!global.window) global.window = window;
221
+ if (!global.document) global.document = window.document;
222
+
223
+ // 设置navigator
224
+ if (!global.navigator) {
225
+ try {
226
+ Object.defineProperty(global, 'navigator', {
227
+ value: window.navigator,
228
+ writable: true,
229
+ configurable: true
230
+ });
231
+ } catch (navError) {
232
+ logger.warning(`无法设置navigator: ${navError.message},继续执行`);
233
+ }
234
+ }
235
+
236
+ // 设置cookie
237
+ document.cookie = cookie;
238
+
239
+ // 创建fetch选项
240
+ const fetchOptions = {
241
+ method: 'POST',
242
+ headers: {
243
+ 'Content-Type': 'application/json',
244
+ 'accept': '*/*',
245
+ 'accept-language': 'en-US,en;q=0.9',
246
+ 'notion-audit-log-platform': 'web',
247
+ 'notion-client-version': '23.13.0.3686',
248
+ 'origin': 'https://www.notion.so',
249
+ 'referer': 'https://www.notion.so/',
250
+ 'user-agent': window.navigator.userAgent,
251
+ 'Cookie': cookie
252
+ },
253
+ body: JSON.stringify({}),
254
+ };
255
+
256
+ // 添加代理配置(如果有)
257
+ if (this.proxyUrl) {
258
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
259
+ fetchOptions.agent = new HttpsProxyAgent(this.proxyUrl);
260
+ logger.info(`使用代理: ${this.proxyUrl}`);
261
+ }
262
+
263
+ // 发送请求
264
+ const response = await fetch("https://www.notion.so/api/v3/getSpaces", fetchOptions);
265
+
266
+ // 检查响应状态
267
+ if (response.status === 401) {
268
+ return { success: false, status: 401, error: '未授权,cookie无效' };
269
+ }
270
+
271
+ if (!response.ok) {
272
+ throw new Error(`HTTP error! status: ${response.status}`);
273
+ }
274
+
275
+ const data = await response.json();
276
+
277
+ // 提取用户ID
278
+ const userIdKey = Object.keys(data)[0];
279
+ if (!userIdKey) {
280
+ throw new Error('无法从响应中提取用户ID');
281
+ }
282
+
283
+ const userId = userIdKey;
284
+
285
+ // 提取空间ID
286
+ const userRoot = data[userIdKey]?.user_root?.[userIdKey];
287
+ const spaceViewPointers = userRoot?.value?.value?.space_view_pointers;
288
+
289
+ if (!spaceViewPointers || !Array.isArray(spaceViewPointers) || spaceViewPointers.length === 0) {
290
+ throw new Error('在响应中找不到space_view_pointers或spaceId');
291
+ }
292
+
293
+ const spaceId = spaceViewPointers[0].spaceId;
294
+
295
+ if (!spaceId) {
296
+ throw new Error('无法从space_view_pointers中提取spaceId');
297
+ }
298
+
299
+ // 清理全局对象
300
+ this.cleanupGlobalObjects();
301
+
302
+ return {
303
+ success: true,
304
+ userId,
305
+ spaceId
306
+ };
307
+
308
+ } catch (error) {
309
+ // 清理全局对象
310
+ this.cleanupGlobalObjects();
311
+
312
+ // 重试逻辑
313
+ if (retryCount < this.maxRetries && error.message !== '未授权,cookie无效') {
314
+ logger.warning(`获取Notion ID失败,正在重试 (${retryCount + 1}/${this.maxRetries}): ${error.message}`);
315
+ return await this.fetchNotionIds(cookie, retryCount + 1);
316
+ }
317
+
318
+ return {
319
+ success: false,
320
+ error: error.message
321
+ };
322
+ }
323
+ }
324
+
325
+ /**
326
+ * 清理全局对象
327
+ */
328
+ cleanupGlobalObjects() {
329
+ try {
330
+ if (global.window) delete global.window;
331
+ if (global.document) delete global.document;
332
+
333
+ // 安全地删除navigator
334
+ if (global.navigator) {
335
+ try {
336
+ delete global.navigator;
337
+ } catch (navError) {
338
+ // 如果无法删除,尝试将其设置为undefined
339
+ try {
340
+ Object.defineProperty(global, 'navigator', {
341
+ value: undefined,
342
+ writable: true,
343
+ configurable: true
344
+ });
345
+ } catch (defineError) {
346
+ logger.warning(`无法清理navigator: ${defineError.message}`);
347
+ }
348
+ }
349
+ }
350
+ } catch (cleanupError) {
351
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
352
+ }
353
+ }
354
+
355
+ /**
356
+ * 获取下一个可用的cookie及其ID
357
+ * @returns {Object|null} - cookie及其对应的ID,如果没有可用cookie则返回null
358
+ */
359
+ getNext() {
360
+ if (!this.initialized || this.cookieEntries.length === 0) {
361
+ return null;
362
+ }
363
+
364
+ // 轮询选择下一个cookie
365
+ const entry = this.cookieEntries[this.currentIndex];
366
+
367
+ // 更新索引,实现轮询
368
+ this.currentIndex = (this.currentIndex + 1) % this.cookieEntries.length;
369
+
370
+ // 更新最后使用时间
371
+ entry.lastUsed = Date.now();
372
+
373
+ return {
374
+ cookie: entry.cookie,
375
+ spaceId: entry.spaceId,
376
+ userId: entry.userId
377
+ };
378
+ }
379
+
380
+ /**
381
+ * 标记cookie为无效
382
+ * @param {string} userId - 用户ID
383
+ */
384
+ markAsInvalid(userId) {
385
+ const index = this.cookieEntries.findIndex(entry => entry.userId === userId);
386
+ if (index !== -1) {
387
+ this.cookieEntries[index].valid = false;
388
+ logger.warning(`已将用户ID为 ${userId} 的cookie标记为无效`);
389
+
390
+ // 过滤掉所有无效的cookie
391
+ this.cookieEntries = this.cookieEntries.filter(entry => entry.valid);
392
+
393
+ // 重置当前索引
394
+ if (this.cookieEntries.length > 0) {
395
+ this.currentIndex = 0;
396
+ }
397
+ }
398
+ }
399
+
400
+ /**
401
+ * 获取有效cookie的数量
402
+ * @returns {number} - 有效cookie的数量
403
+ */
404
+ getValidCount() {
405
+ return this.cookieEntries.filter(entry => entry.valid).length;
406
+ }
407
+
408
+ /**
409
+ * 获取所有cookie的状态信息
410
+ * @returns {Array} - cookie状态数组
411
+ */
412
+ getStatus() {
413
+ return this.cookieEntries.map((entry, index) => ({
414
+ index,
415
+ userId: entry.userId.substring(0, 8) + '...',
416
+ spaceId: entry.spaceId.substring(0, 8) + '...',
417
+ valid: entry.valid,
418
+ lastUsed: entry.lastUsed ? new Date(entry.lastUsed).toLocaleString() : 'never'
419
+ }));
420
+ }
421
+ }
422
+
423
+ export const cookieManager = new CookieManager();
src/cookie-cli.js ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import { cookieManager } from './CookieManager.js';
4
+ import dotenv from 'dotenv';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+ import fs from 'fs';
8
+ import readline from 'readline';
9
+ import chalk from 'chalk';
10
+
11
+ // 获取当前文件的目录路径
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ // 加载环境变量
16
+ dotenv.config({ path: join(dirname(__dirname), '.env') });
17
+
18
+ // 日志配置
19
+ const logger = {
20
+ info: (message) => console.log(chalk.blue(`[信息] ${message}`)),
21
+ error: (message) => console.error(chalk.red(`[错误] ${message}`)),
22
+ warning: (message) => console.warn(chalk.yellow(`[警告] ${message}`)),
23
+ success: (message) => console.log(chalk.green(`[成功] ${message}`)),
24
+ };
25
+
26
+ // 创建readline接口
27
+ const rl = readline.createInterface({
28
+ input: process.stdin,
29
+ output: process.stdout
30
+ });
31
+
32
+ // 默认cookie文件路径
33
+ const DEFAULT_COOKIE_FILE = 'cookies.txt';
34
+
35
+ let isSaveCookie = false;
36
+ let isAddCookie = false;
37
+
38
+ // 显示帮助信息
39
+ function showHelp() {
40
+ console.log(chalk.cyan('Notion Cookie 管理工具'));
41
+ console.log(chalk.cyan('===================='));
42
+ console.log('');
43
+ console.log('可用命令:');
44
+ console.log(' help - 显示此帮助信息');
45
+ console.log(' list - 列出所有cookie');
46
+ console.log(' add - 添加新的cookie');
47
+ console.log(' validate - 验证所有cookie');
48
+ console.log(' remove - 删除指定cookie');
49
+ console.log(' save - 保存cookie到文件');
50
+ console.log(' load - 从文件加载cookie');
51
+ console.log(' exit - 退出程序');
52
+ console.log('');
53
+ }
54
+
55
+ // 列出所有cookie
56
+ async function listCookies() {
57
+ if (!cookieManager.initialized || cookieManager.getValidCount() === 0) {
58
+ logger.warning('没有可用的cookie,请先加载或添加cookie');
59
+ return;
60
+ }
61
+
62
+ const status = cookieManager.getStatus();
63
+ console.log(chalk.cyan('\nCookie 列表:'));
64
+ console.log(chalk.cyan('==========='));
65
+
66
+ status.forEach((entry, idx) => {
67
+ const validMark = entry.valid ? chalk.green('✓') : chalk.red('✗');
68
+ console.log(`${idx + 1}. ${validMark} 用户ID: ${entry.userId}, 空间ID: ${entry.spaceId}, 上次使用: ${entry.lastUsed}`);
69
+ });
70
+
71
+ console.log(`\n共有 ${status.length} 个cookie,${cookieManager.getValidCount()} 个有效\n`);
72
+ }
73
+
74
+ // 添加新cookie
75
+ async function addCookie() {
76
+ return new Promise((resolve) => {
77
+ rl.question(chalk.yellow('请输入Notion cookie: '), async (cookie) => {
78
+ if (!cookie || cookie.trim() === '') {
79
+ logger.error('Cookie不能为空');
80
+ resolve();
81
+ return;
82
+ }
83
+
84
+ logger.info('正在验证cookie...');
85
+ const result = await cookieManager.fetchNotionIds(cookie.trim());
86
+
87
+ if (result.success) {
88
+ // 如果cookie管理器尚未初始化,先初始化
89
+ if (!cookieManager.initialized) {
90
+ await cookieManager.initialize(cookie.trim());
91
+ } else {
92
+ // 已初始化,直接添加到现有条目
93
+ cookieManager.cookieEntries.push({
94
+ cookie: cookie.trim(),
95
+ spaceId: result.spaceId,
96
+ userId: result.userId,
97
+ valid: true,
98
+ lastUsed: 0
99
+ });
100
+ }
101
+ isAddCookie = true;
102
+ logger.success(`Cookie添加成功! 用户ID: ${result.userId}, 空间ID: ${result.spaceId}`);
103
+
104
+ } else {
105
+ logger.error(`Cookie验证失败: ${result.error}`);
106
+ }
107
+
108
+ resolve();
109
+ });
110
+ });
111
+ }
112
+
113
+ // 验证所有cookie
114
+ async function validateCookies() {
115
+ if (!cookieManager.initialized || cookieManager.cookieEntries.length === 0) {
116
+ logger.warning('没有可用的cookie,请先加载或添加cookie');
117
+ return;
118
+ }
119
+
120
+ logger.info('开始验证所有cookie...');
121
+
122
+ const originalEntries = [...cookieManager.cookieEntries];
123
+ cookieManager.cookieEntries = [];
124
+
125
+ for (let i = 0; i < originalEntries.length; i++) {
126
+ const entry = originalEntries[i];
127
+ logger.info(`正在验证第 ${i+1}/${originalEntries.length} 个cookie...`);
128
+
129
+ const result = await cookieManager.fetchNotionIds(entry.cookie);
130
+ if (result.success) {
131
+ cookieManager.cookieEntries.push({
132
+ cookie: entry.cookie,
133
+ spaceId: result.spaceId,
134
+ userId: result.userId,
135
+ valid: true,
136
+ lastUsed: entry.lastUsed || 0
137
+ });
138
+ logger.success(`第 ${i+1} 个cookie验证成功`);
139
+ } else {
140
+ logger.error(`第 ${i+1} 个cookie验证失败: ${result.error}`);
141
+ }
142
+ }
143
+
144
+ logger.info(`验证完成,共 ${originalEntries.length} 个cookie,${cookieManager.cookieEntries.length} 个有效`);
145
+ }
146
+
147
+ // 删除指定cookie
148
+ async function removeCookie() {
149
+ if (!cookieManager.initialized || cookieManager.cookieEntries.length === 0) {
150
+ logger.warning('没有可用的cookie,请先加载或添加cookie');
151
+ return;
152
+ }
153
+
154
+ // 先列出所有cookie
155
+ await listCookies();
156
+
157
+ return new Promise((resolve) => {
158
+ rl.question(chalk.yellow('请输入要删除的cookie编号: '), (input) => {
159
+ const index = parseInt(input) - 1;
160
+
161
+ if (isNaN(index) || index < 0 || index >= cookieManager.cookieEntries.length) {
162
+ logger.error('无效的编号');
163
+ resolve();
164
+ return;
165
+ }
166
+
167
+ const removed = cookieManager.cookieEntries.splice(index, 1)[0];
168
+ logger.success(`已删除编号 ${index + 1} 的cookie (用户ID: ${removed.userId.substring(0, 8)}...)`);
169
+
170
+ // 重置当前索引
171
+ if (cookieManager.cookieEntries.length > 0) {
172
+ cookieManager.currentIndex = 0;
173
+ }
174
+
175
+ resolve();
176
+ });
177
+ });
178
+ }
179
+
180
+ // 保存cookie到文件
181
+ async function saveCookies() {
182
+ if (!cookieManager.initialized || cookieManager.cookieEntries.length === 0) {
183
+ logger.warning('没有可用的cookie,请先加载或添加cookie');
184
+ return;
185
+ }
186
+
187
+ return new Promise((resolve) => {
188
+ rl.question(chalk.yellow(`请输入保存文件路径 (默认: ${DEFAULT_COOKIE_FILE}): `), (filePath) => {
189
+ const path = filePath.trim() || DEFAULT_COOKIE_FILE;
190
+
191
+ rl.question(chalk.yellow('是否只保存有效的cookie? (y/n, 默认: y): '), (onlyValidInput) => {
192
+ const onlyValid = onlyValidInput.toLowerCase() !== 'n';
193
+
194
+ const success = cookieManager.saveToFile(path, onlyValid);
195
+ if (success) {
196
+ logger.success(`Cookie已保存到文件: ${path}`);
197
+ isSaveCookie = true;
198
+ }
199
+
200
+ resolve();
201
+ });
202
+ });
203
+ });
204
+ }
205
+
206
+ // 从文件加载cookie
207
+ async function loadCookies() {
208
+ return new Promise((resolve) => {
209
+ rl.question(chalk.yellow(`请输入cookie文件路径 (默认: ${DEFAULT_COOKIE_FILE}): `), async (filePath) => {
210
+ const path = filePath.trim() || DEFAULT_COOKIE_FILE;
211
+
212
+ logger.info(`正在从文件加载cookie: ${path}`);
213
+ const success = await cookieManager.loadFromFile(path);
214
+
215
+ if (success) {
216
+ logger.success(`成功从文件加载cookie,共 ${cookieManager.getValidCount()} 个有效cookie`);
217
+ } else {
218
+ logger.error(`从文件加载cookie失败`);
219
+ }
220
+
221
+ resolve();
222
+ });
223
+ });
224
+ }
225
+
226
+ // 主函数
227
+ async function main() {
228
+ // 显示欢迎信息
229
+ console.log(chalk.cyan('\nNotion Cookie 管理工具'));
230
+ console.log(chalk.cyan('====================\n'));
231
+
232
+ // 检查是否有环境变量中的cookie
233
+ const envCookie = process.env.NOTION_COOKIE;
234
+ if (envCookie) {
235
+ logger.info('检测到环境变量中的NOTION_COOKIE,正在初始化...');
236
+ await cookieManager.initialize(envCookie);
237
+ }
238
+
239
+ // 检查是否有环境变量中的cookie文件
240
+ const envCookieFile = process.env.COOKIE_FILE;
241
+ if (envCookieFile && !cookieManager.initialized) {
242
+ logger.info(`检测到环境变量中的COOKIE_FILE: ${envCookieFile},正在加载...`);
243
+ await cookieManager.loadFromFile(envCookieFile);
244
+ }
245
+
246
+ // 如果没有cookie,检查默认文件
247
+ if (!cookieManager.initialized && fs.existsSync(DEFAULT_COOKIE_FILE)) {
248
+ logger.info(`检测到默认cookie文件: ${DEFAULT_COOKIE_FILE},正在加载...`);
249
+ await cookieManager.loadFromFile(DEFAULT_COOKIE_FILE);
250
+ }
251
+
252
+ showHelp();
253
+
254
+ // 命令循环
255
+ while (true) {
256
+ const command = await new Promise((resolve) => {
257
+ rl.question(chalk.green('> '), (cmd) => {
258
+ resolve(cmd.trim().toLowerCase());
259
+ });
260
+ });
261
+
262
+ switch (command) {
263
+ case 'help':
264
+ showHelp();
265
+ break;
266
+ case 'list':
267
+ await listCookies();
268
+ break;
269
+ case 'add':
270
+ await addCookie();
271
+ break;
272
+ case 'validate':
273
+ await validateCookies();
274
+ break;
275
+ case 'remove':
276
+ await removeCookie();
277
+ break;
278
+ case 'save':
279
+ await saveCookies();
280
+ break;
281
+ case 'load':
282
+ await loadCookies();
283
+ break;
284
+ case 'exit':
285
+ case 'quit':
286
+ case 'q':
287
+ logger.info('感谢使用,再见!');
288
+ rl.close();
289
+ process.exit(0);
290
+ default:
291
+ logger.error(`未知命令: ${command}`);
292
+ logger.info('输入 "help" 查看可用命令');
293
+ }
294
+ }
295
+ }
296
+
297
+ // 启动程序
298
+ main().catch((error) => {
299
+ logger.error(`程序出错: ${error.message}`);
300
+ process.exit(1);
301
+ });
src/lightweight-client-express.js CHANGED
@@ -4,7 +4,6 @@ import { randomUUID } from 'crypto';
4
  import { fileURLToPath } from 'url';
5
  import { dirname, join } from 'path';
6
  import chalk from 'chalk';
7
- import fetch from 'node-fetch';
8
  import {
9
  ChatMessage, ChatCompletionRequest, Choice, ChoiceDelta, ChatCompletionChunk
10
  } from './models.js';
@@ -12,9 +11,10 @@ import {
12
  initialize,
13
  streamNotionResponse,
14
  buildNotionRequest,
15
- FETCHED_IDS_SUCCESSFULLY
16
  } from './lightweight-client.js';
17
  import { proxyPool } from './ProxyPool.js';
 
18
 
19
  // 获取当前文件的目录路径
20
  const __filename = fileURLToPath(import.meta.url);
@@ -23,10 +23,6 @@ const __dirname = dirname(__filename);
23
  // 加载环境变量
24
  dotenv.config({ path: join(dirname(__dirname), '.env') });
25
 
26
- // 用户邮箱信息
27
- let userEmails = [];
28
- let currentEmailIndex = 0;
29
-
30
  // 日志配置
31
  const logger = {
32
  info: (message) => console.log(chalk.blue(`[info] ${message}`)),
@@ -67,71 +63,6 @@ app.use((req, res, next) => {
67
  next();
68
  });
69
 
70
- // 获取用户邮箱信息
71
- async function fetchUserEmails() {
72
- logger.info('开始获取用户邮箱信息...');
73
-
74
- const cookies = process.env.NOTION_COOKIE;
75
- if (!cookies) {
76
- logger.error('未设置 NOTION_COOKIE 环境变量,无法获取用户邮箱信息');
77
- return [];
78
- }
79
-
80
- const cookiePairs = cookies.split('|');
81
- const emails = [];
82
-
83
- for (let i = 0; i < cookiePairs.length; i++) {
84
- try {
85
- const cookie = cookiePairs[i].trim();
86
- const response = await fetch("https://www.notion.so/api/v3/getUserAnalyticsSettings", {
87
- method: 'POST',
88
- headers: {
89
- 'Content-Type': 'application/json',
90
- 'accept': '*/*',
91
- 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
92
- 'notion-audit-log-platform': 'web',
93
- 'notion-client-version': '23.13.0.3799',
94
- 'cookie': cookie
95
- },
96
- body: '{}'
97
- });
98
-
99
- if (response.ok) {
100
- const data = await response.json();
101
- if (data && data.user_email) {
102
- emails.push({
103
- email: data.user_email,
104
- name: data.user_name || '未知用户',
105
- cookie: cookie
106
- });
107
- logger.success(`获取到账号信息: ${data.user_email} (${data.user_name || '未知用户'})`);
108
- } else {
109
- logger.warning(`无法从 Notion API 响应中获取用户邮箱信息 (cookie ${i+1})`);
110
- }
111
- } else {
112
- logger.warning(`Notion API 请求失败,状态码: ${response.status} (cookie ${i+1})`);
113
- }
114
- } catch (error) {
115
- logger.error(`获取用户邮箱时出错 (cookie ${i+1}): ${error.message}`);
116
- }
117
- }
118
-
119
- logger.info(`共获取到 ${emails.length} 个用户账号信息`);
120
- return emails;
121
- }
122
-
123
- // 获取当前使用的邮箱信息
124
- function getCurrentEmail() {
125
- if (userEmails.length === 0) {
126
- return null;
127
- }
128
-
129
- const email = userEmails[currentEmailIndex];
130
- // 轮询到下一个邮箱
131
- currentEmailIndex = (currentEmailIndex + 1) % userEmails.length;
132
- return email;
133
- }
134
-
135
  // 认证中间件
136
  function authenticate(req, res, next) {
137
  const authHeader = req.headers.authorization;
@@ -179,22 +110,24 @@ app.get('/v1/models', authenticate, (req, res) => {
179
  // 聊天完成端点
180
  app.post('/v1/chat/completions', authenticate, async (req, res) => {
181
  try {
182
- // 检查是否成功获取Notion ID
183
- if (!FETCHED_IDS_SUCCESSFULLY) {
184
  return res.status(500).json({
185
  error: {
186
- message: "Notion ID获取失败。请检查您的NOTION_COOKIE是否有效,或者手动设置NOTION_SPACE_ID和NOTION_ACTIVE_USER_HEADER。",
187
  type: "server_error"
188
  }
189
  });
190
  }
191
 
192
- // 获取当前使用的邮箱信息并打印
193
- const emailInfo = getCurrentEmail();
194
- if (emailInfo) {
195
- logger.info(`本次调用使用账号: ${emailInfo.email} (${emailInfo.name})`);
196
- } else {
197
- logger.warning('未能获取到用户邮箱信息,无法显示当前使用的账号');
 
 
198
  }
199
 
200
  // 验证请求数据
@@ -209,6 +142,9 @@ app.post('/v1/chat/completions', authenticate, async (req, res) => {
209
  });
210
  }
211
 
 
 
 
212
  // 处理流式响应
213
  if (requestData.stream) {
214
  res.setHeader('Content-Type', 'text/event-stream');
@@ -216,7 +152,7 @@ app.post('/v1/chat/completions', authenticate, async (req, res) => {
216
  res.setHeader('Connection', 'keep-alive');
217
 
218
  logger.info(`开始流式响应`);
219
- const stream = await streamNotionResponse(requestData);
220
  stream.pipe(res);
221
 
222
  // 处理客户端断开连接
@@ -228,7 +164,7 @@ app.post('/v1/chat/completions', authenticate, async (req, res) => {
228
  // 创建一个内部流来收集完整响应
229
  logger.info(`开始非流式响应`);
230
  const chunks = [];
231
- const stream = await streamNotionResponse(requestData);
232
 
233
  return new Promise((resolve, reject) => {
234
  stream.on('data', (chunk) => {
@@ -297,7 +233,16 @@ app.get('/health', (req, res) => {
297
  res.json({
298
  status: 'ok',
299
  timestamp: new Date().toISOString(),
300
- notion_ids_fetched: FETCHED_IDS_SUCCESSFULLY
 
 
 
 
 
 
 
 
 
301
  });
302
  });
303
 
@@ -305,34 +250,20 @@ app.get('/health', (req, res) => {
305
  const PORT = process.env.PORT || 7860;
306
 
307
  // 初始化并启动服务器
308
- initialize()
309
- .then(() => fetchUserEmails())
310
- .then((emails) => {
311
- userEmails = emails;
312
- return app.listen(PORT);
313
- })
314
- .then(() => {
315
  logger.info(`服务已启动 - 端口: ${PORT}`);
316
  logger.info(`访问地址: http://localhost:${PORT}`);
317
 
318
- if (FETCHED_IDS_SUCCESSFULLY) {
319
- logger.success(`Notion可用状态: ✅`);
320
- } else {
321
- logger.warning(`Notion可用状态: ❌`);
322
- logger.warning(`警告: Notion ID未成功获取,API调用将无法正常工作`);
323
- logger.warning(`请检查NOTION_COOKIE配置或手动设置NOTION_SPACE_ID和NOTION_ACTIVE_USER_HEADER`);
324
- }
325
-
326
- if (userEmails.length > 0) {
327
- logger.success(`邮箱账号状态: ✅ (共 ${userEmails.length} 个账号)`);
328
- userEmails.forEach((email, index) => {
329
- logger.info(`账号 ${index + 1}: ${email.email} (${email.name})`);
330
- });
331
  } else {
332
- logger.warning(`邮箱账号状态: ❌`);
333
- logger.warning(`警告: 未能获取到任何户邮箱信息`);
 
334
  }
335
- })
336
- .catch((error) => {
337
- logger.error(`初始化失败: ${error}`);
338
- });
 
4
  import { fileURLToPath } from 'url';
5
  import { dirname, join } from 'path';
6
  import chalk from 'chalk';
 
7
  import {
8
  ChatMessage, ChatCompletionRequest, Choice, ChoiceDelta, ChatCompletionChunk
9
  } from './models.js';
 
11
  initialize,
12
  streamNotionResponse,
13
  buildNotionRequest,
14
+ INITIALIZED_SUCCESSFULLY
15
  } from './lightweight-client.js';
16
  import { proxyPool } from './ProxyPool.js';
17
+ import { cookieManager } from './CookieManager.js';
18
 
19
  // 获取当前文件的目录路径
20
  const __filename = fileURLToPath(import.meta.url);
 
23
  // 加载环境变量
24
  dotenv.config({ path: join(dirname(__dirname), '.env') });
25
 
 
 
 
 
26
  // 日志配置
27
  const logger = {
28
  info: (message) => console.log(chalk.blue(`[info] ${message}`)),
 
63
  next();
64
  });
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  // 认证中间件
67
  function authenticate(req, res, next) {
68
  const authHeader = req.headers.authorization;
 
110
  // 聊天完成端点
111
  app.post('/v1/chat/completions', authenticate, async (req, res) => {
112
  try {
113
+ // 检查是否成功初始化
114
+ if (!INITIALIZED_SUCCESSFULLY) {
115
  return res.status(500).json({
116
  error: {
117
+ message: "系统未成功初始化。请检查您的NOTION_COOKIE是否有效。",
118
  type: "server_error"
119
  }
120
  });
121
  }
122
 
123
+ // 检查是否有可用的cookie
124
+ if (cookieManager.getValidCount() === 0) {
125
+ return res.status(500).json({
126
+ error: {
127
+ message: "没有可用的有效cookie。请检查您的NOTION_COOKIE配置。",
128
+ type: "server_error"
129
+ }
130
+ });
131
  }
132
 
133
  // 验证请求数据
 
142
  });
143
  }
144
 
145
+ // 构建Notion请求
146
+ const notionRequestBody = buildNotionRequest(requestData);
147
+
148
  // 处理流式响应
149
  if (requestData.stream) {
150
  res.setHeader('Content-Type', 'text/event-stream');
 
152
  res.setHeader('Connection', 'keep-alive');
153
 
154
  logger.info(`开始流式响应`);
155
+ const stream = await streamNotionResponse(notionRequestBody);
156
  stream.pipe(res);
157
 
158
  // 处理客户端断开连接
 
164
  // 创建一个内部流来收集完整响应
165
  logger.info(`开始非流式响应`);
166
  const chunks = [];
167
+ const stream = await streamNotionResponse(notionRequestBody);
168
 
169
  return new Promise((resolve, reject) => {
170
  stream.on('data', (chunk) => {
 
233
  res.json({
234
  status: 'ok',
235
  timestamp: new Date().toISOString(),
236
+ initialized: INITIALIZED_SUCCESSFULLY,
237
+ valid_cookies: cookieManager.getValidCount()
238
+ });
239
+ });
240
+
241
+ // Cookie状态查询端点
242
+ app.get('/cookies/status', authenticate, (req, res) => {
243
+ res.json({
244
+ total_cookies: cookieManager.getValidCount(),
245
+ cookies: cookieManager.getStatus()
246
  });
247
  });
248
 
 
250
  const PORT = process.env.PORT || 7860;
251
 
252
  // 初始化并启动服务器
253
+ initialize().then(() => {
254
+ app.listen(PORT, () => {
 
 
 
 
 
255
  logger.info(`服务已启动 - 端口: ${PORT}`);
256
  logger.info(`访问地址: http://localhost:${PORT}`);
257
 
258
+ if (INITIALIZED_SUCCESSFULLY) {
259
+ logger.success(`系统初始化状态: ✅`);
260
+ logger.success(`可用cookie数量: ${cookieManager.getValidCount()}`);
 
 
 
 
 
 
 
 
 
 
261
  } else {
262
+ logger.warning(`系统初始化状态: ❌`);
263
+ logger.warning(`警告: 系统成功初始化,API调将无法正常工作`);
264
+ logger.warning(`请检查NOTION_COOKIE配置是否有效`);
265
  }
266
+ });
267
+ }).catch((error) => {
268
+ logger.error(`初始化失败: ${error}`);
269
+ });
src/lightweight-client.js CHANGED
@@ -7,12 +7,14 @@ import { dirname, join } from 'path';
7
  import { PassThrough } from 'stream';
8
  import chalk from 'chalk';
9
  import {
10
- ChatMessage, ChatCompletionRequest, NotionTranscriptConfigValue,
11
  NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides,
12
  NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, NotionTranscriptItemByuser
13
  } from './models.js';
14
  import { proxyPool } from './ProxyPool.js';
15
  import { proxyServer } from './ProxyServer.js';
 
 
16
  // 获取当前文件的目录路径
17
  const __filename = fileURLToPath(import.meta.url);
18
  const __dirname = dirname(__filename);
@@ -30,26 +32,17 @@ const logger = {
30
 
31
  // 配置
32
  const NOTION_API_URL = "https://www.notion.so/api/v3/runInferenceTranscript";
33
- let NOTION_COOKIES = [];
34
- let NOTION_SPACE_IDS = {};
35
- let NOTION_USER_IDS = {};
36
- let currentCookieIndex = 0;
37
-
38
- let NOTION_COOKIE = '';
39
- let NOTION_SPACE_ID = process.env.NOTION_SPACE_ID;
40
- let FETCHED_NOTION_USER_ID = process.env.NOTION_ACTIVE_USER_HEADER;
41
  const USE_NATIVE_PROXY_POOL = process.env.USE_NATIVE_PROXY_POOL === 'true';
42
  const ENABLE_PROXY_SERVER = process.env.ENABLE_PROXY_SERVER === 'true';
43
  let proxy = null;
44
- let isMoreThanOne = false;
45
- let index = 0;
46
-
47
 
48
  // 代理配置
49
  const PROXY_URL = process.env.PROXY_URL || "";
50
 
51
- // 标记是否成功获取ID
52
- let FETCHED_IDS_SUCCESSFULLY = false;
53
 
54
  // 注册进程退出事件,确保代理服务器在程序退出时关闭
55
  process.on('exit', () => {
@@ -77,241 +70,16 @@ process.on('exit', () => {
77
  });
78
  });
79
 
80
- function getCookie() {
81
- if (NOTION_COOKIES.length === 0) {
82
- return '';
83
- }
84
- const cookie = NOTION_COOKIES[currentCookieIndex];
85
- currentCookieIndex = (currentCookieIndex + 1) % NOTION_COOKIES.length;
86
- return cookie;
87
- }
88
-
89
- // 添加一个通用的cookie解析函数
90
- function parseCookiesFromString(cookieString, logPrefix = "") {
91
- const cookiesToAdd = {};
92
-
93
- if (!cookieString || typeof cookieString !== 'string') {
94
- logger.error(`${logPrefix}Invalid cookie string provided`);
95
- return cookiesToAdd;
96
- }
97
-
98
- // 按分号分割cookie字符串
99
- const cookiePairs = cookieString.split(';');
100
- logger.info(`${logPrefix}Parsing ${cookiePairs.length} cookie pairs`);
101
-
102
- for (let i = 0; i < cookiePairs.length; i++) {
103
- try {
104
- const pair = cookiePairs[i].trim();
105
- if (!pair) continue;
106
-
107
- // 查找第一个等号的位置
108
- const firstEqualPos = pair.indexOf('=');
109
- if (firstEqualPos <= 0) {
110
- logger.warning(`${logPrefix}Skipping invalid cookie pair: ${pair}`);
111
- continue;
112
- }
113
-
114
- // 分割名称和值
115
- const name = pair.substring(0, firstEqualPos).trim();
116
- const value = pair.substring(firstEqualPos + 1).trim();
117
-
118
- if (!name || !value) {
119
- logger.warning(`${logPrefix}Skipping cookie with empty name or value: ${pair}`);
120
- continue;
121
- }
122
-
123
- logger.info(`${logPrefix}Parsed cookie: ${name} (length: ${value.length})`);
124
-
125
- cookiesToAdd[name] = value;
126
- } catch (cookieError) {
127
- logger.warning(`${logPrefix}Error parsing cookie pair at index ${i}: ${cookieError}`);
128
- }
129
- }
130
-
131
- logger.info(`${logPrefix}Successfully parsed ${Object.keys(cookiesToAdd).length} cookies`);
132
- return cookiesToAdd;
133
- }
134
-
135
- // 获取Notion空间ID和用户ID
136
- async function fetchAndSetNotionIds(cookie) {
137
- if (!cookie) {
138
- logger.error(`无法获取Notion ID: 未设置NOTION_COOKIE`);
139
- FETCHED_IDS_SUCCESSFULLY = false;
140
- return;
141
- }
142
-
143
- try {
144
- logger.info(`正在设置JSDOM环境...`);
145
-
146
- // 创建JSDOM实例模拟浏览器环境
147
- const dom = new JSDOM("", {
148
- url: "https://www.notion.so",
149
- referrer: "https://www.notion.so/",
150
- contentType: "text/html",
151
- includeNodeLocations: true,
152
- storageQuota: 10000000,
153
- pretendToBeVisual: true,
154
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
155
- });
156
-
157
- // 设置全局对象
158
- const { window } = dom;
159
-
160
- // 使用更安全的方式设置全局对象
161
- try {
162
- if (!global.window) {
163
- global.window = window;
164
- }
165
-
166
- if (!global.document) {
167
- global.document = window.document;
168
- }
169
-
170
- // 安全地设置navigator
171
- if (!global.navigator) {
172
- try {
173
- Object.defineProperty(global, 'navigator', {
174
- value: window.navigator,
175
- writable: true,
176
- configurable: true
177
- });
178
- } catch (navError) {
179
- logger.warning(`无法设置navigator: ${navError.message},继续执行`);
180
- // 继续执行,不会中断流程
181
- }
182
- }
183
- } catch (globalError) {
184
- logger.warning(`设置全局对象时出错: ${globalError.message}`);
185
- }
186
-
187
- // 设置cookie
188
- document.cookie = cookie;
189
-
190
- // 创建fetch选项
191
- const fetchOptions = {
192
- method: 'POST',
193
- headers: {
194
- 'Content-Type': 'application/json',
195
- 'accept': '*/*',
196
- 'accept-language': 'en-US,en;q=0.9',
197
- 'notion-audit-log-platform': 'web',
198
- 'notion-client-version': '23.13.0.3686',
199
- 'origin': 'https://www.notion.so',
200
- 'referer': 'https://www.notion.so/',
201
- 'user-agent': window.navigator.userAgent,
202
- 'Cookie': cookie
203
- },
204
- body: JSON.stringify({}),
205
- };
206
-
207
- // 添加代理配置(如果有)
208
- if (PROXY_URL) {
209
- const { HttpsProxyAgent } = await import('https-proxy-agent');
210
- fetchOptions.agent = new HttpsProxyAgent(PROXY_URL);
211
- logger.info(`使用代理: ${PROXY_URL}`);
212
- }
213
- logger.info(`正在获取Notion用户/空间ID...`);
214
-
215
- // 发送请求
216
- const response = await fetch("https://www.notion.so/api/v3/getSpaces", fetchOptions);
217
-
218
- if (!response.ok) {
219
- throw new Error(`HTTP error! status: ${response.status}`);
220
- }
221
-
222
- const data = await response.json();
223
-
224
- // 提取用户ID
225
- const userIdKey = Object.keys(data)[0];
226
- if (!userIdKey) {
227
- logger.error(`无法从响应中提取用户ID`);
228
- FETCHED_IDS_SUCCESSFULLY = false;
229
- return;
230
- }
231
-
232
- FETCHED_NOTION_USER_ID = userIdKey;
233
- logger.success(`已获取Notion用户ID`);
234
-
235
- // 提取空间ID
236
- const userRoot = data[userIdKey]?.user_root?.[userIdKey];
237
- const spaceViewPointers = userRoot?.value?.value?.space_view_pointers;
238
-
239
- if (spaceViewPointers && Array.isArray(spaceViewPointers) && spaceViewPointers.length > 0) {
240
- NOTION_SPACE_ID = spaceViewPointers[0].spaceId;
241
-
242
- if (NOTION_SPACE_ID) {
243
- logger.success(`已获取Notion空间ID`);
244
- FETCHED_IDS_SUCCESSFULLY = true;
245
- } else {
246
- logger.error(`无法从space_view_pointers中提取spaceId`);
247
- FETCHED_IDS_SUCCESSFULLY = false;
248
- }
249
- } else {
250
- logger.error(`在响应中找不到space_view_pointers或spaceId`);
251
- FETCHED_IDS_SUCCESSFULLY = false;
252
- }
253
-
254
- // 清理全局对象
255
- try {
256
- if (global.window) delete global.window;
257
- if (global.document) delete global.document;
258
-
259
- // 安全地删除navigator
260
- if (global.navigator) {
261
- try {
262
- delete global.navigator;
263
- } catch (navError) {
264
- // 如果无法删除,尝试将其设置为undefined
265
- try {
266
- Object.defineProperty(global, 'navigator', {
267
- value: undefined,
268
- writable: true,
269
- configurable: true
270
- });
271
- } catch (defineError) {
272
- logger.warning(`无法清理navigator: ${defineError.message}`);
273
- }
274
- }
275
- }
276
- } catch (cleanupError) {
277
- logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
278
- }
279
-
280
- return { spaceId: NOTION_SPACE_ID, userId: userIdKey };
281
- } catch (error) {
282
- logger.error(`获取Notion ID时出错: ${error}`);
283
- FETCHED_IDS_SUCCESSFULLY = false;
284
-
285
- // 确保清理全局对象
286
- try {
287
- if (global.window) delete global.window;
288
- if (global.document) delete global.document;
289
-
290
- // 安全地删除navigator
291
- if (global.navigator) {
292
- try {
293
- delete global.navigator;
294
- } catch (navError) {
295
- // 如果无法删除,尝试将其设置为undefined
296
- try {
297
- Object.defineProperty(global, 'navigator', {
298
- value: undefined,
299
- writable: true,
300
- configurable: true
301
- });
302
- } catch (defineError) {
303
- logger.warning(`无法清理navigator: ${defineError.message}`);
304
- }
305
- }
306
- }
307
- } catch (cleanupError) {
308
- logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
309
  }
310
  }
311
- }
312
 
313
- // 构建Notion请求
314
- function buildNotionRequest(requestData, userId, spaceId) {
315
  // 当前时间
316
  const now = new Date();
317
  // 格式化为ISO字符串,确保包含毫秒和时区
@@ -346,8 +114,8 @@ function buildNotionRequest(requestData, userId, spaceId) {
346
  transcript.push(new NotionTranscriptItem({
347
  type: "context",
348
  value: new NotionTranscriptContextValue({
349
- userId: userId,
350
- spaceId: spaceId,
351
  surface: "home_module",
352
  timezone: "America/Los_Angeles",
353
  userName: userName,
@@ -387,7 +155,7 @@ function buildNotionRequest(requestData, userId, spaceId) {
387
  transcript.push(new NotionTranscriptItemByuser({
388
  type: "user",
389
  value: [[content]],
390
- userId: userId,
391
  createdAt: message.createdAt || isoString
392
  }));
393
  } else if (message.role === "user") {
@@ -395,7 +163,7 @@ function buildNotionRequest(requestData, userId, spaceId) {
395
  transcript.push(new NotionTranscriptItemByuser({
396
  type: "user",
397
  value: [[content]],
398
- userId: userId,
399
  createdAt: message.createdAt || isoString
400
  }));
401
  } else if (message.role === "assistant") {
@@ -411,7 +179,7 @@ function buildNotionRequest(requestData, userId, spaceId) {
411
 
412
  // 创建请求体
413
  return new NotionRequestBody({
414
- spaceId: spaceId,
415
  transcript: transcript,
416
  createThread: true,
417
  traceId: randomUUID(),
@@ -426,52 +194,89 @@ function buildNotionRequest(requestData, userId, spaceId) {
426
  }
427
 
428
  // 流式处理Notion响应
429
- async function streamNotionResponse(requestData) {
430
- const chunkQueue = new PassThrough();
431
- let timeoutId = null;
432
-
433
- try {
434
- // 动态获取cookie并查找其对应ID
435
- const currentCookie = getCookie();
436
- if (!currentCookie) {
437
- throw new Error("无法获取 Notion cookie,请检查配置");
438
  }
439
- const userId = NOTION_USER_IDS[currentCookie];
440
- const spaceId = NOTION_SPACE_IDS[currentCookie];
441
 
442
- if (!userId || !spaceId) {
443
- throw new Error(`无法找到 cookie ...${currentCookie.slice(-10)} 对应的ID`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
-
446
- // 使用正确的ID构建请求体
447
- const notionRequestBody = buildNotionRequest(requestData, userId, spaceId);
448
-
449
- // 设置请求头
450
- const headers = {
451
- 'Content-Type': 'application/json',
452
- 'Cookie': currentCookie,
453
- 'x-notion-active-user-header': userId,
454
- "accept": "application/json",
455
- "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
456
- };
 
457
 
458
- fetchNotionResponse(chunkQueue, notionRequestBody, headers, NOTION_API_URL, currentCookie, timeoutId);
459
- } catch (error) {
460
- logger.error(`streamNotionResponse 错误: ${error}`);
461
- const errorChunk = new ChatCompletionChunk({
462
- choices: [
463
- new Choice({
464
- delta: new ChoiceDelta({ content: `处理流式响应时出错: ${error.message}` }),
465
- finish_reason: "error"
466
- })
467
- ]
468
- });
469
- chunkQueue.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
470
- chunkQueue.write('data: [DONE]\n\n');
471
- chunkQueue.end();
472
- }
473
-
474
- return chunkQueue;
 
 
 
475
  }
476
 
477
  // 使用fetch调用Notion API并处理流式响应
@@ -524,8 +329,6 @@ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notio
524
  // 设置cookie
525
  document.cookie = notionCookie;
526
 
527
- //logger.info(`JSDOM环境已设置`);
528
-
529
  // 创建fetch选项
530
  const fetchOptions = {
531
  method: 'POST',
@@ -556,7 +359,6 @@ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notio
556
  }
557
  let response = null;
558
  // 发送请求
559
- //const response = await fetch(notionApiUrl, fetchOptions);
560
  if (ENABLE_PROXY_SERVER){
561
  response = await fetch('http://127.0.0.1:10655/proxy', {
562
  method: 'POST',
@@ -572,6 +374,38 @@ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notio
572
  response = await fetch(notionApiUrl, fetchOptions);
573
  }
574
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
 
576
  if (!response.ok) {
577
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -646,10 +480,10 @@ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notio
646
  reader.on('end', () => {
647
  try {
648
  logger.info(`响应完成`);
649
- if (isMoreThanOne){
650
- NOTION_COOKIE = getCookie(); // 获取下一个cookie
651
- logger.info('开始切换cookie和ID');
652
- fetchAndSetNotionIds(); // 切换到下一个ID
653
  }
654
 
655
  // 如果没有收到任何响应,发送一个提示消息
@@ -658,6 +492,7 @@ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notio
658
  if (USE_NATIVE_PROXY_POOL) {
659
  proxyPool.removeProxy(proxy.ip, proxy.port);
660
  }
 
661
  const noContentChunk = new ChatCompletionChunk({
662
  choices: [
663
  new Choice({
@@ -860,47 +695,58 @@ async function initialize() {
860
  logger.error(`启动代理服务器失败: ${error.message}`);
861
  }
862
 
863
- // 从环境变量加载所有 cookies
864
- const cookiesEnv = process.env.NOTION_COOKIE;
865
- if (!cookiesEnv) {
866
- logger.error(`错误: 未设置NOTION_COOKIE环境变量,应用无法正常工作`);
867
- return;
868
- }
869
- NOTION_COOKIES = cookiesEnv.split('|').map(c => c.trim()).filter(c => c);
870
 
871
- if (NOTION_COOKIES.length === 0) {
872
- logger.error(`错误: NOTION_COOKIE环境变量中未找到有效cookie`);
873
- return;
 
 
 
 
 
 
874
  }
875
-
876
- logger.info(`共找到 ${NOTION_COOKIES.length} 个 Notion cookie正在逐个获取 ID...`);
877
-
878
- let successfulCookies = 0;
879
- for (const cookie of NOTION_COOKIES) {
880
- try {
881
- const ids = await fetchAndSetNotionIds(cookie);
882
- if (ids && ids.spaceId && ids.userId) {
883
- NOTION_SPACE_IDS[cookie] = ids.spaceId;
884
- NOTION_USER_IDS[cookie] = ids.userId;
885
- logger.success(`成功获取 cookie 的 ID: ...${cookie.slice(-10)}`);
886
- successfulCookies++;
887
- }
888
- } catch (error) {
889
- logger.error(`获取 cookie 的 ID 失败: ...${cookie.slice(-10)} - ${error.message}`);
 
 
 
890
  }
891
  }
892
-
893
- if (successfulCookies > 0) {
894
- FETCHED_IDS_SUCCESSFULLY = true;
895
- logger.success(`成功初始化 ${successfulCookies} 个 Notion 账号`);
896
- } else {
897
- logger.error(`所有 Notion cookie 均无法成功初始化,应用可能无法正常工作`);
 
898
  }
899
-
 
 
 
 
900
  if (process.env.USE_NATIVE_PROXY_POOL === 'true') {
901
  logger.info(`正在初始化本地代理池...`);
902
  await proxyPool.initialize();
903
  }
 
 
904
  }
905
 
906
  // 导出函数
@@ -908,5 +754,5 @@ export {
908
  initialize,
909
  streamNotionResponse,
910
  buildNotionRequest,
911
- FETCHED_IDS_SUCCESSFULLY
912
  };
 
7
  import { PassThrough } from 'stream';
8
  import chalk from 'chalk';
9
  import {
10
+ NotionTranscriptConfigValue,
11
  NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides,
12
  NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, NotionTranscriptItemByuser
13
  } from './models.js';
14
  import { proxyPool } from './ProxyPool.js';
15
  import { proxyServer } from './ProxyServer.js';
16
+ import { cookieManager } from './CookieManager.js';
17
+
18
  // 获取当前文件的目录路径
19
  const __filename = fileURLToPath(import.meta.url);
20
  const __dirname = dirname(__filename);
 
32
 
33
  // 配置
34
  const NOTION_API_URL = "https://www.notion.so/api/v3/runInferenceTranscript";
35
+ // 这些变量将由cookieManager动态提供
36
+ let currentCookieData = null;
 
 
 
 
 
 
37
  const USE_NATIVE_PROXY_POOL = process.env.USE_NATIVE_PROXY_POOL === 'true';
38
  const ENABLE_PROXY_SERVER = process.env.ENABLE_PROXY_SERVER === 'true';
39
  let proxy = null;
 
 
 
40
 
41
  // 代理配置
42
  const PROXY_URL = process.env.PROXY_URL || "";
43
 
44
+ // 标记是否成功初始化
45
+ let INITIALIZED_SUCCESSFULLY = false;
46
 
47
  // 注册进程退出事件,确保代理服务器在程序退出时关闭
48
  process.on('exit', () => {
 
70
  });
71
  });
72
 
73
+ // 构建Notion请求
74
+ function buildNotionRequest(requestData) {
75
+ // 确保我们有当前的cookie数据
76
+ if (!currentCookieData) {
77
+ currentCookieData = cookieManager.getNext();
78
+ if (!currentCookieData) {
79
+ throw new Error('没有可用的cookie');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
  }
 
82
 
 
 
83
  // 当前时间
84
  const now = new Date();
85
  // 格式化为ISO字符串,确保包含毫秒和时区
 
114
  transcript.push(new NotionTranscriptItem({
115
  type: "context",
116
  value: new NotionTranscriptContextValue({
117
+ userId: currentCookieData.userId,
118
+ spaceId: currentCookieData.spaceId,
119
  surface: "home_module",
120
  timezone: "America/Los_Angeles",
121
  userName: userName,
 
155
  transcript.push(new NotionTranscriptItemByuser({
156
  type: "user",
157
  value: [[content]],
158
+ userId: currentCookieData.userId,
159
  createdAt: message.createdAt || isoString
160
  }));
161
  } else if (message.role === "user") {
 
163
  transcript.push(new NotionTranscriptItemByuser({
164
  type: "user",
165
  value: [[content]],
166
+ userId: currentCookieData.userId,
167
  createdAt: message.createdAt || isoString
168
  }));
169
  } else if (message.role === "assistant") {
 
179
 
180
  // 创建请求体
181
  return new NotionRequestBody({
182
+ spaceId: currentCookieData.spaceId,
183
  transcript: transcript,
184
  createThread: true,
185
  traceId: randomUUID(),
 
194
  }
195
 
196
  // 流式处理Notion响应
197
+ async function streamNotionResponse(notionRequestBody) {
198
+ // 确保我们有当前的cookie数据
199
+ if (!currentCookieData) {
200
+ currentCookieData = cookieManager.getNext();
201
+ if (!currentCookieData) {
202
+ throw new Error('没有可用cookie');
 
 
 
203
  }
204
+ }
 
205
 
206
+ // 创建流
207
+ const stream = new PassThrough();
208
+
209
+ // 添加初始数据,确保连接建立
210
+ stream.write(':\n\n'); // 发送一个空注释行,保持连接活跃
211
+
212
+ // 设置HTTP头模板
213
+ const headers = {
214
+ 'Content-Type': 'application/json',
215
+ 'accept': 'application/x-ndjson',
216
+ 'accept-language': 'en-US,en;q=0.9',
217
+ 'notion-audit-log-platform': 'web',
218
+ 'notion-client-version': '23.13.0.3686',
219
+ 'origin': 'https://www.notion.so',
220
+ 'referer': 'https://www.notion.so/chat',
221
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
222
+ 'x-notion-active-user-header': currentCookieData.userId,
223
+ 'x-notion-space-id': currentCookieData.spaceId
224
+ };
225
+
226
+ // 设置超时处理,确保流不会无限等待
227
+ const timeoutId = setTimeout(() => {
228
+ logger.warning(`请求超时,30秒内未收到响应`);
229
+ try {
230
+ // 发送结束消息
231
+ const endChunk = new ChatCompletionChunk({
232
+ choices: [
233
+ new Choice({
234
+ delta: new ChoiceDelta({ content: "请求超时,未收到Notion响应。" }),
235
+ finish_reason: "timeout"
236
+ })
237
+ ]
238
+ });
239
+ stream.write(`data: ${JSON.stringify(endChunk)}\n\n`);
240
+ stream.write('data: [DONE]\n\n');
241
+ stream.end();
242
+ } catch (error) {
243
+ logger.error(`发送超时消息时出错: ${error}`);
244
+ stream.end();
245
  }
246
+ }, 30000); // 30秒超时
247
+
248
+ // 启动fetch处理
249
+ fetchNotionResponse(
250
+ stream,
251
+ notionRequestBody,
252
+ headers,
253
+ NOTION_API_URL,
254
+ currentCookieData.cookie,
255
+ timeoutId
256
+ ).catch((error) => {
257
+ logger.error(`流处理出错: ${error}`);
258
+ clearTimeout(timeoutId); // 清除超时计时器
259
 
260
+ try {
261
+ // 发送错误消息
262
+ const errorChunk = new ChatCompletionChunk({
263
+ choices: [
264
+ new Choice({
265
+ delta: new ChoiceDelta({ content: `处理请求时出错: ${error.message}` }),
266
+ finish_reason: "error"
267
+ })
268
+ ]
269
+ });
270
+ stream.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
271
+ stream.write('data: [DONE]\n\n');
272
+ } catch (e) {
273
+ logger.error(`发送错误���息时出错: ${e}`);
274
+ } finally {
275
+ stream.end();
276
+ }
277
+ });
278
+
279
+ return stream;
280
  }
281
 
282
  // 使用fetch调用Notion API并处理流式响应
 
329
  // 设置cookie
330
  document.cookie = notionCookie;
331
 
 
 
332
  // 创建fetch选项
333
  const fetchOptions = {
334
  method: 'POST',
 
359
  }
360
  let response = null;
361
  // 发送请求
 
362
  if (ENABLE_PROXY_SERVER){
363
  response = await fetch('http://127.0.0.1:10655/proxy', {
364
  method: 'POST',
 
374
  response = await fetch(notionApiUrl, fetchOptions);
375
  }
376
 
377
+ // 检查是否收到401错误(未授权)
378
+ if (response.status === 401) {
379
+ logger.error(`收到401未授权错误,cookie可能已失效`);
380
+ // 标记当前cookie为无效
381
+ cookieManager.markAsInvalid(currentCookieData.userId);
382
+ // 尝试获取下一个cookie
383
+ currentCookieData = cookieManager.getNext();
384
+
385
+ if (!currentCookieData) {
386
+ throw new Error('所有cookie均已失效,无法继续请求');
387
+ }
388
+
389
+ // 使用新cookie重新构建请求体
390
+ const newRequestBody = buildNotionRequest({
391
+ model: notionRequestBody.transcript[0]?.value?.model || '',
392
+ messages: [] // 这里应该根据实际情况重构消息
393
+ });
394
+
395
+ // 使用新cookie重试请求
396
+ return fetchNotionResponse(
397
+ chunkQueue,
398
+ newRequestBody,
399
+ {
400
+ ...headers,
401
+ 'x-notion-active-user-header': currentCookieData.userId,
402
+ 'x-notion-space-id': currentCookieData.spaceId
403
+ },
404
+ notionApiUrl,
405
+ currentCookieData.cookie,
406
+ timeoutId
407
+ );
408
+ }
409
 
410
  if (!response.ok) {
411
  throw new Error(`HTTP error! status: ${response.status}`);
 
480
  reader.on('end', () => {
481
  try {
482
  logger.info(`响应完成`);
483
+ if (cookieManager.getValidCount() > 1){
484
+ // 尝试切换到下一个cookie
485
+ currentCookieData = cookieManager.getNext();
486
+ logger.info(`切换到下一个cookie: ${currentCookieData.userId}`);
487
  }
488
 
489
  // 如果没有收到任何响应,发送一个提示消息
 
492
  if (USE_NATIVE_PROXY_POOL) {
493
  proxyPool.removeProxy(proxy.ip, proxy.port);
494
  }
495
+
496
  const noContentChunk = new ChatCompletionChunk({
497
  choices: [
498
  new Choice({
 
695
  logger.error(`启动代理服务器失败: ${error.message}`);
696
  }
697
 
698
+ // 初始化cookie管理器
699
+ let initResult = false;
 
 
 
 
 
700
 
701
+ // 检查是否配置了cookie文件
702
+ const cookieFilePath = process.env.COOKIE_FILE;
703
+ if (cookieFilePath) {
704
+ logger.info(`检测到COOKIE_FILE配置: ${cookieFilePath}`);
705
+ initResult = await cookieManager.loadFromFile(cookieFilePath);
706
+
707
+ if (!initResult) {
708
+ logger.error(`从文件加载cookie失败,尝试使用环境变量中的NOTION_COOKIE`);
709
+ }
710
  }
711
+
712
+ // 如果文件加载失败或未配置文件尝试从环境变量加载
713
+ if (!initResult) {
714
+ const cookiesString = process.env.NOTION_COOKIE;
715
+ if (!cookiesString) {
716
+ logger.error(`错误: 未设置NOTION_COOKIE环境变量或COOKIE_FILE路径,应用无法正常工作`);
717
+ logger.error(`请在.env文件中设置有效的NOTION_COOKIE值或COOKIE_FILE路径`);
718
+ INITIALIZED_SUCCESSFULLY = false;
719
+ return;
720
+ }
721
+
722
+ logger.info(`正在从环境变量初始化cookie管理器...`);
723
+ initResult = await cookieManager.initialize(cookiesString);
724
+
725
+ if (!initResult) {
726
+ logger.error(`初始化cookie管理器失败,应用无法正常工作`);
727
+ INITIALIZED_SUCCESSFULLY = false;
728
+ return;
729
  }
730
  }
731
+
732
+ // 获取第一个可用的cookie数据
733
+ currentCookieData = cookieManager.getNext();
734
+ if (!currentCookieData) {
735
+ logger.error(`没有可用的cookie,应用无法正常工作`);
736
+ INITIALIZED_SUCCESSFULLY = false;
737
+ return;
738
  }
739
+
740
+ logger.success(`成功初始化cookie管理器,共有 ${cookieManager.getValidCount()} 个有效cookie`);
741
+ logger.info(`当前使用的cookie对应的用户ID: ${currentCookieData.userId}`);
742
+ logger.info(`当前使用的cookie对应的空间ID: ${currentCookieData.spaceId}`);
743
+
744
  if (process.env.USE_NATIVE_PROXY_POOL === 'true') {
745
  logger.info(`正在初始化本地代理池...`);
746
  await proxyPool.initialize();
747
  }
748
+
749
+ INITIALIZED_SUCCESSFULLY = true;
750
  }
751
 
752
  // 导出函数
 
754
  initialize,
755
  streamNotionResponse,
756
  buildNotionRequest,
757
+ INITIALIZED_SUCCESSFULLY
758
  };