| import { JSDOM } from 'jsdom';
|
| import fetch from 'node-fetch';
|
| import fs from 'fs';
|
| import path from 'path';
|
| import { fileURLToPath } from 'url';
|
| import { dirname } from 'path';
|
|
|
|
|
| const __filename = fileURLToPath(import.meta.url);
|
| const __dirname = dirname(__filename);
|
|
|
|
|
| const logger = {
|
| info: (message) => console.log(`\x1b[34m[info] ${message}\x1b[0m`),
|
| error: (message) => console.error(`\x1b[31m[error] ${message}\x1b[0m`),
|
| warning: (message) => console.warn(`\x1b[33m[warn] ${message}\x1b[0m`),
|
| success: (message) => console.log(`\x1b[32m[success] ${message}\x1b[0m`),
|
| };
|
|
|
| class CookieManager {
|
| constructor() {
|
| this.cookieEntries = [];
|
| this.currentIndex = 0;
|
| this.initialized = false;
|
| this.maxRetries = 3;
|
| this.proxyUrl = process.env.PROXY_URL || "";
|
| }
|
|
|
| |
| |
| |
| |
|
|
| async loadFromFile(filePath) {
|
| try {
|
|
|
| const absolutePath = path.isAbsolute(filePath)
|
| ? filePath
|
| : path.join(dirname(__dirname), filePath);
|
|
|
| logger.info(`从文件加载cookie: ${absolutePath}`);
|
|
|
|
|
| if (!fs.existsSync(absolutePath)) {
|
| logger.error(`Cookie文件不存在: ${absolutePath}`);
|
| return false;
|
| }
|
|
|
|
|
| const fileContent = fs.readFileSync(absolutePath, 'utf8');
|
|
|
|
|
| const ext = path.extname(absolutePath).toLowerCase();
|
| let cookieArray = [];
|
|
|
| if (ext === '.json') {
|
|
|
| try {
|
| const jsonData = JSON.parse(fileContent);
|
| if (Array.isArray(jsonData)) {
|
| cookieArray = jsonData;
|
| } else if (jsonData.cookies && Array.isArray(jsonData.cookies)) {
|
| cookieArray = jsonData.cookies;
|
| } else {
|
| logger.error('JSON文件格式错误,应为cookie数组或包含cookies数组的对象');
|
| return false;
|
| }
|
| } catch (error) {
|
| logger.error(`解析JSON文件失败: ${error.message}`);
|
| return false;
|
| }
|
| } else {
|
|
|
| cookieArray = fileContent
|
| .split('\n')
|
| .map(line => line.trim())
|
| .filter(line => line && !line.startsWith('#'));
|
| }
|
|
|
| logger.info(`从文件中读取了 ${cookieArray.length} 个cookie`);
|
|
|
|
|
| return await this.initialize(cookieArray.join('|'));
|
|
|
| } catch (error) {
|
| logger.error(`从文件加载cookie失败: ${error.message}`);
|
| return false;
|
| }
|
| }
|
|
|
| |
| |
| |
| |
|
|
| async initialize(cookiesString) {
|
| if (!cookiesString) {
|
| logger.error('未提供cookie字符串');
|
| return false;
|
| }
|
|
|
|
|
| const cookieArray = cookiesString.split('|').map(c => c.trim()).filter(c => c);
|
|
|
| if (cookieArray.length === 0) {
|
| logger.error('没有有效的cookie');
|
| return false;
|
| }
|
|
|
| logger.info(`发现 ${cookieArray.length} 个cookie,开始获取对应的ID信息...`);
|
|
|
|
|
| this.cookieEntries = [];
|
|
|
|
|
| for (let i = 0; i < cookieArray.length; i++) {
|
| const cookie = cookieArray[i];
|
| logger.info(`正在处理第 ${i+1}/${cookieArray.length} 个cookie...`);
|
|
|
| const result = await this.fetchNotionIds(cookie);
|
| if (result.success) {
|
| this.cookieEntries.push({
|
| cookie,
|
| spaceId: result.spaceId,
|
| userId: result.userId,
|
| valid: true,
|
| lastUsed: 0
|
| });
|
| logger.success(`第 ${i+1} 个cookie验证成功`);
|
| } else {
|
| if (result.status === 401) {
|
| logger.error(`第 ${i+1} 个cookie无效(401未授权),已跳过`);
|
| } else {
|
| logger.warning(`第 ${i+1} 个cookie验证失败: ${result.error},已跳过`);
|
| }
|
| }
|
| }
|
|
|
|
|
| if (this.cookieEntries.length === 0) {
|
| logger.error('没有有效的cookie,初始化失败');
|
| return false;
|
| }
|
|
|
| logger.success(`成功初始化 ${this.cookieEntries.length}/${cookieArray.length} 个cookie`);
|
| this.initialized = true;
|
| this.currentIndex = 0;
|
| return true;
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| saveToFile(filePath, onlyValid = true) {
|
| try {
|
|
|
| const absolutePath = path.isAbsolute(filePath)
|
| ? filePath
|
| : path.join(dirname(__dirname), filePath);
|
|
|
|
|
| const cookiesToSave = onlyValid
|
| ? this.cookieEntries.filter(entry => entry.valid).map(entry => entry.cookie)
|
| : this.cookieEntries.map(entry => entry.cookie);
|
|
|
|
|
| const ext = path.extname(absolutePath).toLowerCase();
|
|
|
| if (ext === '.json') {
|
|
|
| const jsonData = {
|
| cookies: cookiesToSave,
|
| updatedAt: new Date().toISOString(),
|
| count: cookiesToSave.length
|
| };
|
| fs.writeFileSync(absolutePath, JSON.stringify(jsonData, null, 2), 'utf8');
|
| } else {
|
|
|
| const content = cookiesToSave.join('\n');
|
| fs.writeFileSync(absolutePath, content, 'utf8');
|
| }
|
|
|
| logger.success(`已将 ${cookiesToSave.length} 个cookie保存到文件: ${absolutePath}`);
|
| return true;
|
| } catch (error) {
|
| logger.error(`保存cookie到文件失败: ${error.message}`);
|
| return false;
|
| }
|
| }
|
|
|
| |
| |
| |
| |
|
|
| async fetchNotionIds(cookie, retryCount = 0) {
|
| if (!cookie) {
|
| return { success: false, error: '未提供cookie' };
|
| }
|
|
|
| try {
|
|
|
| const dom = new JSDOM("", {
|
| url: "https://www.notion.so",
|
| referrer: "https://www.notion.so/",
|
| contentType: "text/html",
|
| includeNodeLocations: true,
|
| storageQuota: 10000000,
|
| pretendToBeVisual: true,
|
| userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
|
| });
|
|
|
|
|
| const { window } = dom;
|
|
|
|
|
| if (!global.window) global.window = window;
|
| if (!global.document) global.document = window.document;
|
|
|
|
|
| if (!global.navigator) {
|
| try {
|
| Object.defineProperty(global, 'navigator', {
|
| value: window.navigator,
|
| writable: true,
|
| configurable: true
|
| });
|
| } catch (navError) {
|
| logger.warning(`无法设置navigator: ${navError.message},继续执行`);
|
| }
|
| }
|
|
|
|
|
| document.cookie = cookie;
|
|
|
|
|
| const fetchOptions = {
|
| method: 'POST',
|
| headers: {
|
| 'Content-Type': 'application/json',
|
| 'accept': '*/*',
|
| 'accept-language': 'en-US,en;q=0.9',
|
| 'notion-audit-log-platform': 'web',
|
| 'notion-client-version': '23.13.0.3686',
|
| 'origin': 'https://www.notion.so',
|
| 'referer': 'https://www.notion.so/',
|
| 'user-agent': window.navigator.userAgent,
|
| 'Cookie': cookie
|
| },
|
| body: JSON.stringify({}),
|
| };
|
|
|
|
|
| if (this.proxyUrl) {
|
| const { HttpsProxyAgent } = await import('https-proxy-agent');
|
| fetchOptions.agent = new HttpsProxyAgent(this.proxyUrl);
|
| logger.info(`使用代理: ${this.proxyUrl}`);
|
| }
|
|
|
|
|
| const response = await fetch("https://www.notion.so/api/v3/getSpaces", fetchOptions);
|
|
|
|
|
| if (response.status === 401) {
|
| return { success: false, status: 401, error: '未授权,cookie无效' };
|
| }
|
|
|
| if (!response.ok) {
|
| throw new Error(`HTTP error! status: ${response.status}`);
|
| }
|
|
|
| const data = await response.json();
|
|
|
|
|
| const userIdKey = Object.keys(data)[0];
|
| if (!userIdKey) {
|
| throw new Error('无法从响应中提取用户ID');
|
| }
|
|
|
| const userId = userIdKey;
|
|
|
|
|
| const userRoot = data[userIdKey]?.user_root?.[userIdKey];
|
| const spaceViewPointers = userRoot?.value?.value?.space_view_pointers;
|
|
|
| if (!spaceViewPointers || !Array.isArray(spaceViewPointers) || spaceViewPointers.length === 0) {
|
| throw new Error('在响应中找不到space_view_pointers或spaceId');
|
| }
|
|
|
| const spaceId = spaceViewPointers[0].spaceId;
|
|
|
| if (!spaceId) {
|
| throw new Error('无法从space_view_pointers中提取spaceId');
|
| }
|
|
|
|
|
| this.cleanupGlobalObjects();
|
|
|
| return {
|
| success: true,
|
| userId,
|
| spaceId
|
| };
|
|
|
| } catch (error) {
|
|
|
| this.cleanupGlobalObjects();
|
|
|
|
|
| if (retryCount < this.maxRetries && error.message !== '未授权,cookie无效') {
|
| logger.warning(`获取Notion ID失败,正在重试 (${retryCount + 1}/${this.maxRetries}): ${error.message}`);
|
| return await this.fetchNotionIds(cookie, retryCount + 1);
|
| }
|
|
|
| return {
|
| success: false,
|
| error: error.message
|
| };
|
| }
|
| }
|
|
|
| |
| |
|
|
| cleanupGlobalObjects() {
|
| try {
|
| if (global.window) delete global.window;
|
| if (global.document) delete global.document;
|
|
|
|
|
| if (global.navigator) {
|
| try {
|
| delete global.navigator;
|
| } catch (navError) {
|
|
|
| try {
|
| Object.defineProperty(global, 'navigator', {
|
| value: undefined,
|
| writable: true,
|
| configurable: true
|
| });
|
| } catch (defineError) {
|
| logger.warning(`无法清理navigator: ${defineError.message}`);
|
| }
|
| }
|
| }
|
| } catch (cleanupError) {
|
| logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
|
| }
|
| }
|
|
|
| |
| |
| |
|
|
| getNext() {
|
| if (!this.initialized || this.cookieEntries.length === 0) {
|
| return null;
|
| }
|
|
|
|
|
| const entry = this.cookieEntries[this.currentIndex];
|
|
|
|
|
| this.currentIndex = (this.currentIndex + 1) % this.cookieEntries.length;
|
|
|
|
|
| entry.lastUsed = Date.now();
|
|
|
| return {
|
| cookie: entry.cookie,
|
| spaceId: entry.spaceId,
|
| userId: entry.userId
|
| };
|
| }
|
|
|
| |
| |
| |
|
|
| markAsInvalid(userId) {
|
| const index = this.cookieEntries.findIndex(entry => entry.userId === userId);
|
| if (index !== -1) {
|
| this.cookieEntries[index].valid = false;
|
| logger.warning(`已将用户ID为 ${userId} 的cookie标记为无效`);
|
|
|
|
|
| this.cookieEntries = this.cookieEntries.filter(entry => entry.valid);
|
|
|
|
|
| if (this.cookieEntries.length > 0) {
|
| this.currentIndex = 0;
|
| }
|
| }
|
| }
|
|
|
| |
| |
| |
|
|
| getValidCount() {
|
| return this.cookieEntries.filter(entry => entry.valid).length;
|
| }
|
|
|
| |
| |
| |
|
|
| getStatus() {
|
| return this.cookieEntries.map((entry, index) => ({
|
| index,
|
| userId: entry.userId.substring(0, 8) + '...',
|
| spaceId: entry.spaceId.substring(0, 8) + '...',
|
| valid: entry.valid,
|
| lastUsed: entry.lastUsed ? new Date(entry.lastUsed).toLocaleString() : 'never'
|
| }));
|
| }
|
| }
|
|
|
| export const cookieManager = new CookieManager(); |