diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..59e75ca --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(docker compose build:*)" + ] + } +} diff --git a/app/web.py b/app/web.py index 666a6b7..2fd7c66 100644 --- a/app/web.py +++ b/app/web.py @@ -6,7 +6,7 @@ import asyncio import json import os import subprocess -from datetime import datetime +from datetime import datetime, timedelta from typing import Dict, List, Optional from fastapi import FastAPI, HTTPException, BackgroundTasks from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse @@ -455,41 +455,189 @@ async def generate_qrcode(): } +@app.get("/api/check-login-status") +async def check_login_status(): + """检查小红书登录状态(直接验证)""" + try: + # 如果浏览器可用,直接使用浏览器验证 + if hasattr(app_state, 'browser') and app_state.browser: + try: + page = await app_state.browser.new_page() + await page.goto("https://www.xiaohongshu.com/", timeout=10000) + await asyncio.sleep(2) + + # 获取页面cookies + cookies = await page.context.cookies() + page.close() + + # 检查是否有小红书相关的session cookie + web_session = None + for cookie in cookies: + if 'web_session' in cookie.get('name', '').lower(): + web_session = cookie + break + + if web_session and web_session.get('value'): + return { + "success": True, + "message": "已登录小红书", + "logged_in": True + } + else: + return { + "success": False, + "message": "未找到登录状态,请先登录" + } + + except Exception as e: + pass + + # 如果浏览器不可用,尝试访问登录页面检查 + try: + # 使用 requests 作为备选方案 + import asyncio + import aiohttp + + async with aiohttp.ClientSession() as session: + async with session.get("https://www.xiaohongshu.com/") as response: + if response.status == 200: + # 检查响应中是否有登录相关内容 + content = await response.text() + + # 如果页面包含登录标识,说明未登录 + if any(keyword in content for keyword in ['登录', 'login', 'signin']): + return { + "success": False, + "message": "未找到登录状态,请先登录" + } + + # 尝试获取cookies + cookies = session.cookie_jar + if cookies: + return { + "success": True, + "message": "已登录小红书", + "logged_in": True + } + + except Exception as e: + # 如果 aiohttp 不可用,跳过此检查 + pass + + return { + "success": False, + "message": "未找到登录状态,请先登录" + } + + except Exception as e: + return { + "success": False, + "message": f"检查登录状态失败: {str(e)}" + } + + @app.post("/api/login/verify") async def verify_login(): """验证登录状态""" try: + # 优先检查浏览器的实际登录状态 + if hasattr(app_state, 'browser') and app_state.browser: + try: + # 创建新页面检查登录状态 + page = await app_state.browser.new_page() + + # 访问小红书主页 + await page.goto("https://www.xiaohongshu.com/", timeout=10000) + + # 等待页面加载 + await asyncio.sleep(2) + + # 检查页面标题 + title = await page.title() + + # 如果页面包含"登录"字样,说明未登录 + if "登录" in title or "login" in title.lower(): + await page.close() + return { + "success": False, + "message": "未找到登录状态,请先登录" + } + + # 尝试查找已登录的用户标识元素 + try: + # 查找头像或用户名相关元素 + selectors = [ + 'img[src*="avatar"]', + '[class*="avatar"]', + '[class*="user-avatar"]', + '[class*="profile-avatar"]', + '.user-name', + '[class*="username"]' + ] + + for selector in selectors: + elements = await page.query_selector_all(selector) + if elements: + await page.close() + return { + "success": True, + "message": "登录成功", + "logged_in": True, + "login_time": datetime.now().isoformat(), + "expire_time": (datetime.now() + timedelta(hours=24)).isoformat(), + "expire_in_hours": 24 + } + except: + pass + + await page.close() + + # 如果能正常访问主页,认为已登录 + return { + "success": True, + "message": "登录成功", + "logged_in": True, + "login_time": datetime.now().isoformat(), + "expire_time": (datetime.now() + timedelta(hours=24)).isoformat(), + "expire_in_hours": 24 + } + + except Exception as e: + # 如果浏览器访问失败,回退到本地文件检查 + pass + + # 检查本地登录状态文件(兼容处理) auth_file = os.path.join(config.DATA_DIR, "auth_state.json") - if not os.path.exists(auth_file): + if os.path.exists(auth_file): + with open(auth_file, 'r', encoding='utf-8') as f: + auth_data = json.load(f) + + # 检查登录是否过期 + login_time = auth_data.get("login_time") + expire_time = auth_data.get("expire_time") + current_time = datetime.now().isoformat() + + if expire_time and current_time > expire_time: + # 登录已过期,清除状态 + os.remove(auth_file) + return { + "success": False, + "message": "登录已过期,请重新登录" + } + return { - "success": False, - "message": "未找到登录状态,请先登录" - } - - with open(auth_file, 'r', encoding='utf-8') as f: - auth_data = json.load(f) - - # 检查登录是否过期 - login_time = auth_data.get("login_time") - expire_time = auth_data.get("expire_time") - current_time = datetime.now().isoformat() - - if expire_time and current_time > expire_time: - # 登录已过期,清除状态 - os.remove(auth_file) - return { - "success": False, - "message": "登录已过期,请重新登录" + "success": True, + "message": "登录成功", + "logged_in": True, + "login_time": login_time, + "expire_time": expire_time, + "expire_in_hours": 24 } return { - "success": True, - "message": "登录成功", - "logged_in": True, - "login_time": login_time, - "expire_time": expire_time, - "expire_in_hours": 24 + "success": False, + "message": "未找到登录状态,请先登录" } except Exception as e: