UNPKG

rednote-mcp

Version:

A friendly tool to help you access and interact with Xiaohongshu (RedNote) content through Model Context Protocol.

192 lines (191 loc) 8.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthManager = void 0; const playwright_1 = require("playwright"); const cookieManager_1 = require("./cookieManager"); const dotenv = __importStar(require("dotenv")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const logger_1 = __importDefault(require("../utils/logger")); dotenv.config(); class AuthManager { constructor(cookiePath) { logger_1.default.info('Initializing AuthManager'); this.browser = null; this.context = null; this.page = null; // Set default cookie path to ~/.mcp/rednote/cookies.json if (!cookiePath) { const homeDir = os.homedir(); const mcpDir = path.join(homeDir, '.mcp'); const rednoteDir = path.join(mcpDir, 'rednote'); // Create directories if they don't exist if (!fs.existsSync(mcpDir)) { logger_1.default.info(`Creating directory: ${mcpDir}`); fs.mkdirSync(mcpDir); } if (!fs.existsSync(rednoteDir)) { logger_1.default.info(`Creating directory: ${rednoteDir}`); fs.mkdirSync(rednoteDir); } cookiePath = path.join(rednoteDir, 'cookies.json'); } logger_1.default.info(`Using cookie path: ${cookiePath}`); this.cookieManager = new cookieManager_1.CookieManager(cookiePath); } async getBrowser() { logger_1.default.info('Launching browser'); // 始终创建新的浏览器实例,不复用之前的 this.browser = await playwright_1.chromium.launch({ headless: false, }); return this.browser; } async getCookies() { logger_1.default.info('Loading cookies'); return await this.cookieManager.loadCookies(); } async login(options) { const timeoutSeconds = options?.timeout || 10; logger_1.default.info(`Starting login process with timeout: ${timeoutSeconds}s`); const timeoutMs = timeoutSeconds * 1000; this.browser = await playwright_1.chromium.launch({ headless: false, timeout: timeoutMs, }); if (!this.browser) { logger_1.default.error('Failed to launch browser'); throw new Error('Failed to launch browser'); } let retryCount = 0; const maxRetries = 3; while (retryCount < maxRetries) { try { logger_1.default.info(`Login attempt ${retryCount + 1}/${maxRetries}`); this.context = await this.browser.newContext(); this.page = await this.context.newPage(); // Load existing cookies if available const cookies = await this.cookieManager.loadCookies(); if (cookies && cookies.length > 0) { logger_1.default.info(`Loaded ${cookies.length} existing cookies`); await this.context.addCookies(cookies); } // Navigate to explore page logger_1.default.info('Navigating to explore page'); await this.page.goto('https://www.xiaohongshu.com/explore', { waitUntil: 'domcontentloaded', timeout: timeoutMs }); // Check if already logged in const userSidebar = await this.page.$('.user.side-bar-component .channel'); if (userSidebar) { const isLoggedIn = await this.page.evaluate(() => { const sidebarUser = document.querySelector('.user.side-bar-component .channel'); return sidebarUser?.textContent?.trim() === '我'; }); if (isLoggedIn) { logger_1.default.info('Already logged in'); // Already logged in, save cookies and return const newCookies = await this.context.cookies(); await this.cookieManager.saveCookies(newCookies); return; } } logger_1.default.info('Waiting for login dialog'); // Wait for login dialog if not logged in await this.page.waitForSelector('.login-container', { timeout: timeoutMs }); // Wait for QR code image logger_1.default.info('Waiting for QR code'); const qrCodeImage = await this.page.waitForSelector('.qrcode-img', { timeout: timeoutMs }); // Wait for user to complete login logger_1.default.info('Waiting for user to complete login'); await this.page.waitForSelector('.user.side-bar-component .channel', { timeout: timeoutMs * 6 }); // Verify the text content const isLoggedIn = await this.page.evaluate(() => { const sidebarUser = document.querySelector('.user.side-bar-component .channel'); return sidebarUser?.textContent?.trim() === '我'; }); if (!isLoggedIn) { logger_1.default.error('Login verification failed'); throw new Error('Login verification failed'); } logger_1.default.info('Login successful, saving cookies'); // Save cookies after successful login const newCookies = await this.context.cookies(); await this.cookieManager.saveCookies(newCookies); return; } catch (error) { logger_1.default.error(`Login attempt ${retryCount + 1} failed:`, error); // Clean up current session if (this.page) await this.page.close(); if (this.context) await this.context.close(); retryCount++; if (retryCount < maxRetries) { logger_1.default.info(`Retrying login in 2 seconds (${retryCount}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, 2000)); } else { logger_1.default.error('Login failed after maximum retries'); throw new Error('Login failed after maximum retries'); } } } } async cleanup() { logger_1.default.info('Cleaning up browser resources'); if (this.page) await this.page.close(); if (this.context) await this.context.close(); this.page = null; this.context = null; this.browser = null; } } exports.AuthManager = AuthManager;