Skip to main content
Glama
authManager.ts5.58 kB
import {Browser, BrowserContext, chromium, Cookie, Page} from 'playwright'; import {CookieManager} from './cookieManager'; import * as dotenv from 'dotenv'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import logger from '../utils/logger'; dotenv.config(); export class AuthManager { private browser: Browser | null; private context: BrowserContext | null; private page: Page | null; private cookieManager: CookieManager; constructor(cookiePath?: string) { logger.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.info(`Creating directory: ${mcpDir}`); fs.mkdirSync(mcpDir); } if (!fs.existsSync(rednoteDir)) { logger.info(`Creating directory: ${rednoteDir}`); fs.mkdirSync(rednoteDir); } cookiePath = path.join(rednoteDir, 'cookies.json'); } logger.info(`Using cookie path: ${cookiePath}`); this.cookieManager = new CookieManager(cookiePath); } async getBrowser(): Promise<Browser> { if (!this.browser) { logger.info('Launching browser'); this.browser = await chromium.launch({ headless: false, }); } return this.browser; } async getCookies(): Promise<Cookie[]> { logger.info('Loading cookies'); return await this.cookieManager.loadCookies(); } async login(): Promise<void> { logger.info('Starting login process'); this.browser = await chromium.launch({headless: false}); if (!this.browser) { logger.error('Failed to launch browser'); throw new Error('Failed to launch browser'); } let retryCount = 0; const maxRetries = 3; while (retryCount < maxRetries) { try { logger.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.info(`Loaded ${cookies.length} existing cookies`); await this.context.addCookies(cookies); } // Navigate to explore page logger.info('Navigating to explore page'); await this.page.goto('https://www.xiaohongshu.com/explore', { waitUntil: 'domcontentloaded', timeout: 10000 }); // 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.info('Already logged in'); // Already logged in, save cookies and return const newCookies = await this.context.cookies(); await this.cookieManager.saveCookies(newCookies); return; } } logger.info('Waiting for login dialog'); // Wait for login dialog if not logged in await this.page.waitForSelector('.login-container', { timeout: 10000 }); // Wait for QR code image logger.info('Waiting for QR code'); const qrCodeImage = await this.page.waitForSelector('.qrcode-img', { timeout: 10000 }); // Wait for user to complete login logger.info('Waiting for user to complete login'); await this.page.waitForSelector('.user.side-bar-component .channel', { timeout: 60000 }); // 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.error('Login verification failed'); throw new Error('Login verification failed'); } logger.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.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.info(`Retrying login in 2 seconds (${retryCount}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, 2000)); } else { logger.error('Login failed after maximum retries'); throw new Error('Login failed after maximum retries'); } } } } async cleanup(): Promise<void> { logger.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; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonaFly/RedNote-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server