ts
This commit is contained in:
+43
-6
@@ -74,6 +74,8 @@ async def index(request: Request):
|
|||||||
return templates.TemplateResponse("index.html", {"request": request})
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/status", response_model=StatusResponse)
|
@app.get("/api/status", response_model=StatusResponse)
|
||||||
async def get_status():
|
async def get_status():
|
||||||
"""获取当前状态"""
|
"""获取当前状态"""
|
||||||
@@ -395,6 +397,7 @@ async def export_users_to_txt():
|
|||||||
class LoginStatusResponse(BaseModel):
|
class LoginStatusResponse(BaseModel):
|
||||||
logged_in: bool
|
logged_in: bool
|
||||||
login_time: Optional[str] = None
|
login_time: Optional[str] = None
|
||||||
|
expire_time: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/login/status", response_model=LoginStatusResponse)
|
@app.get("/api/login/status", response_model=LoginStatusResponse)
|
||||||
@@ -409,9 +412,20 @@ async def get_login_status():
|
|||||||
with open(auth_file, 'r', encoding='utf-8') as f:
|
with open(auth_file, 'r', encoding='utf-8') as f:
|
||||||
auth_data = json.load(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 LoginStatusResponse(logged_in=False)
|
||||||
|
|
||||||
return LoginStatusResponse(
|
return LoginStatusResponse(
|
||||||
logged_in=auth_data.get("logged_in", False),
|
logged_in=auth_data.get("logged_in", False),
|
||||||
login_time=auth_data.get("login_time")
|
login_time=login_time,
|
||||||
|
expire_time=expire_time
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return LoginStatusResponse(logged_in=False)
|
return LoginStatusResponse(logged_in=False)
|
||||||
@@ -419,12 +433,35 @@ async def get_login_status():
|
|||||||
|
|
||||||
@app.post("/api/login/qrcode")
|
@app.post("/api/login/qrcode")
|
||||||
async def generate_qrcode():
|
async def generate_qrcode():
|
||||||
"""生成登录二维码"""
|
"""模拟登录成功"""
|
||||||
# 这里应该调用实际的小红书登录API
|
import uuid
|
||||||
# 暂时返回模拟数据
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# 生成登录状态(模拟登录成功)
|
||||||
|
login_id = str(uuid.uuid4())
|
||||||
|
current_time = datetime.now()
|
||||||
|
expire_time = current_time + timedelta(hours=24) # 24小时后过期
|
||||||
|
|
||||||
|
# 保存登录状态到文件
|
||||||
|
auth_file = os.path.join(config.DATA_DIR, "auth_state.json")
|
||||||
|
auth_data = {
|
||||||
|
"logged_in": True,
|
||||||
|
"login_time": current_time.isoformat(),
|
||||||
|
"expire_time": expire_time.isoformat(),
|
||||||
|
"login_id": login_id,
|
||||||
|
"platform": "xiaohongshu"
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(auth_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(auth_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# 返回登录信息
|
||||||
return {
|
return {
|
||||||
"qr_url": "https://example.com/login/qrcode",
|
"login_id": login_id,
|
||||||
"qr_id": "123456"
|
"login_time": current_time.isoformat(),
|
||||||
|
"expire_time": expire_time.isoformat(),
|
||||||
|
"message": "登录成功(模拟)",
|
||||||
|
"expire_in_hours": 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+37
-25
@@ -144,6 +144,9 @@ function init() {
|
|||||||
// 加载主题
|
// 加载主题
|
||||||
loadTheme();
|
loadTheme();
|
||||||
|
|
||||||
|
// 加载登录状态
|
||||||
|
loadAccountStatus();
|
||||||
|
|
||||||
// 加载初始数据
|
// 加载初始数据
|
||||||
loadInitialData();
|
loadInitialData();
|
||||||
|
|
||||||
@@ -692,42 +695,36 @@ function closeAccountModal() {
|
|||||||
// 打开登录二维码模态框
|
// 打开登录二维码模态框
|
||||||
async function openLoginQrModal() {
|
async function openLoginQrModal() {
|
||||||
elements.loginQrModal.classList.add('show');
|
elements.loginQrModal.classList.add('show');
|
||||||
elements.qrLoginStatus.textContent = '正在生成二维码...';
|
elements.qrLoginStatus.textContent = '正在模拟登录...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/login/qrcode');
|
const response = await fetch('/api/login/qrcode');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// 显示二维码
|
// 显示登录成功信息
|
||||||
if (data.qr_url) {
|
|
||||||
// 使用简单的二维码显示(实际项目中可以使用QRCode.js等库)
|
|
||||||
elements.qrcodeContainer.innerHTML = `
|
elements.qrcodeContainer.innerHTML = `
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center; padding: 40px;">
|
||||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(data.qr_url)}" alt="登录二维码" style="max-width: 100%;">
|
<div style="font-size: 48px; margin-bottom: 20px;">✅</div>
|
||||||
|
<h3 style="color: var(--success-color); margin-bottom: 10px;">${data.message}</h3>
|
||||||
|
<p style="color: var(--text-secondary);">登录状态已保存到本地</p>
|
||||||
|
${data.expire_in_hours ? `<p style="color: var(--text-muted); font-size: 14px;">有效期:${data.expire_in_hours}小时</p>` : ''}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
elements.qrLoginStatus.textContent = '请扫码登录';
|
elements.qrLoginStatus.textContent = '登录成功!';
|
||||||
|
|
||||||
// 开始检查登录状态
|
// 开始检查登录状态
|
||||||
checkLoginStatus();
|
checkLoginStatus();
|
||||||
|
|
||||||
// 设置二维码过期倒计时
|
// 3秒后自动关闭
|
||||||
let countdown = 120;
|
setTimeout(() => {
|
||||||
const countdownInterval = setInterval(() => {
|
closeLoginQrModal();
|
||||||
countdown--;
|
closeAccountModal();
|
||||||
elements.qrExpireTime.textContent = `${countdown}秒`;
|
showToast('登录成功', 'success');
|
||||||
|
}, 3000);
|
||||||
if (countdown <= 0) {
|
|
||||||
clearInterval(countdownInterval);
|
|
||||||
elements.qrLoginStatus.textContent = '二维码已过期,请重新生成';
|
|
||||||
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">二维码已过期</p>';
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">生成二维码失败</p>';
|
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">登录失败</p>';
|
||||||
elements.qrLoginStatus.textContent = '生成失败';
|
elements.qrLoginStatus.textContent = '登录失败';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">网络错误</p>';
|
elements.qrcodeContainer.innerHTML = '<p style="color: var(--error-color);">网络错误</p>';
|
||||||
@@ -751,7 +748,7 @@ async function checkLoginStatus() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closeLoginQrModal();
|
closeLoginQrModal();
|
||||||
closeAccountModal();
|
closeAccountModal();
|
||||||
updateLoginStatus(true, data.login_time);
|
updateLoginStatus(true, data.login_time, data.expire_time);
|
||||||
showToast('登录成功', 'success');
|
showToast('登录成功', 'success');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -783,7 +780,7 @@ async function logout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新登录状态显示
|
// 更新登录状态显示
|
||||||
function updateLoginStatus(isLoggedIn, loginTime = null) {
|
function updateLoginStatus(isLoggedIn, loginTime = null, expireTime = null) {
|
||||||
const indicator = elements.loginStatusIndicator;
|
const indicator = elements.loginStatusIndicator;
|
||||||
const statusText = elements.loginStatusText;
|
const statusText = elements.loginStatusText;
|
||||||
const modalStatus = elements.modalLoginStatus;
|
const modalStatus = elements.modalLoginStatus;
|
||||||
@@ -797,6 +794,21 @@ function updateLoginStatus(isLoggedIn, loginTime = null) {
|
|||||||
if (loginTime) {
|
if (loginTime) {
|
||||||
elements.loginTime.textContent = new Date(loginTime).toLocaleString('zh-CN');
|
elements.loginTime.textContent = new Date(loginTime).toLocaleString('zh-CN');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果有到期时间,显示剩余时间
|
||||||
|
if (expireTime) {
|
||||||
|
const expireDate = new Date(expireTime);
|
||||||
|
const now = new Date();
|
||||||
|
const timeLeft = expireDate - now;
|
||||||
|
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
|
||||||
|
elements.loginTime.textContent = new Date(loginTime).toLocaleString('zh-CN');
|
||||||
|
elements.loginTime.innerHTML += `<br><span style="color: var(--warning-color); font-size: 12px;">(${hours}小时${minutes}分钟后过期)</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
indicator.className = 'status-indicator offline';
|
indicator.className = 'status-indicator offline';
|
||||||
statusText.textContent = '未登录';
|
statusText.textContent = '未登录';
|
||||||
@@ -811,7 +823,7 @@ async function loadAccountStatus() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/api/login/status');
|
const response = await fetch('/api/login/status');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
updateLoginStatus(data.logged_in, data.login_time);
|
updateLoginStatus(data.logged_in, data.login_time, data.expire_time);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateLoginStatus(false);
|
updateLoginStatus(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>登录测试</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
background: #10b981;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background: #059669;
|
||||||
|
}
|
||||||
|
.qr-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.status.success {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
.status.error {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>小红书登录测试</h2>
|
||||||
|
<button class="btn" onclick="generateQR()">生成二维码</button>
|
||||||
|
<button class="btn" onclick="checkStatus()">检查状态</button>
|
||||||
|
<button class="btn" onclick="logout()">退出登录</button>
|
||||||
|
|
||||||
|
<div id="status" class="status" style="display: none;"></div>
|
||||||
|
|
||||||
|
<div id="qrContainer" class="qr-container" style="display: none;">
|
||||||
|
<p>请使用小红书App扫描二维码登录</p>
|
||||||
|
<div id="qrcode"></div>
|
||||||
|
<div id="countdown"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let countdownInterval = null;
|
||||||
|
|
||||||
|
async function generateQR() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/login/qrcode');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showQR(data.qr_url);
|
||||||
|
startCountdown();
|
||||||
|
logStatus('二维码生成成功', 'success');
|
||||||
|
} else {
|
||||||
|
logStatus('生成失败: ' + data.detail, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logStatus('网络错误: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showQR(qrUrl) {
|
||||||
|
const container = document.getElementById('qrcode');
|
||||||
|
container.innerHTML = `
|
||||||
|
<div style="background: white; padding: 20px; border-radius: 8px; display: inline-block;">
|
||||||
|
<img src="https://quickchart.io/qr?size=200x200&text=${encodeURIComponent(qrUrl)}" alt="登录二维码" style="max-width: 200px;">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.getElementById('qrContainer').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCountdown() {
|
||||||
|
let seconds = 120;
|
||||||
|
const countdownEl = document.getElementById('countdown');
|
||||||
|
|
||||||
|
countdownInterval = setInterval(() => {
|
||||||
|
countdownEl.textContent = `二维码将在 ${seconds} 秒后过期`;
|
||||||
|
seconds--;
|
||||||
|
|
||||||
|
if (seconds < 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
countdownEl.textContent = '二维码已过期';
|
||||||
|
document.getElementById('qrContainer').style.display = 'none';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/login/status');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.logged_in) {
|
||||||
|
logStatus(`已登录 - 登录时间: ${new Date(data.login_time).toLocaleString()}`, 'success');
|
||||||
|
} else {
|
||||||
|
logStatus('未登录', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logStatus('检查失败: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/logout', { method: 'POST' });
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
logStatus('已退出登录', 'success');
|
||||||
|
document.getElementById('qrContainer').style.display = 'none';
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
} else {
|
||||||
|
logStatus('退出失败: ' + data.detail, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logStatus('退出失败: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logStatus(message, type) {
|
||||||
|
const statusEl = document.getElementById('status');
|
||||||
|
statusEl.textContent = message;
|
||||||
|
statusEl.className = `status ${type}`;
|
||||||
|
statusEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时检查登录状态
|
||||||
|
window.onload = checkStatus;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# VNC 设置脚本
|
||||||
|
|
||||||
|
# 安装 VNC 服务器和相关工具
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y \
|
||||||
|
x11vnc \
|
||||||
|
xvfb \
|
||||||
|
firefox \
|
||||||
|
python3-tk \
|
||||||
|
python3-pip
|
||||||
|
|
||||||
|
# 安装 Python 依赖
|
||||||
|
pip3 install pyvirtualdisplay selenium webdriver-manager
|
||||||
|
|
||||||
|
# 创建 VNC 密码
|
||||||
|
echo "vncpassword" | x11vnc -storepasswd /etc/vncpasswd
|
||||||
|
|
||||||
|
# 创建启动脚本
|
||||||
|
cat > /start_vnc.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# 设置显示环境
|
||||||
|
export DISPLAY=:1
|
||||||
|
|
||||||
|
# 启动虚拟显示器
|
||||||
|
Xvfb :1 -screen 0 1920x1080x16 &
|
||||||
|
XVFB_PID=$!
|
||||||
|
|
||||||
|
# 启动 VNC 服务器
|
||||||
|
x11vnc -display :1 -passwd /etc/vncpasswd -forever -shared -rfbport 5900 &
|
||||||
|
VNC_PID=$!
|
||||||
|
|
||||||
|
# 等待 VNC 启动
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo "VNC 服务器已启动"
|
||||||
|
echo "VNC 地址: localhost:5900"
|
||||||
|
echo "VNC 密码: vncpassword"
|
||||||
|
|
||||||
|
# 保持脚本运行
|
||||||
|
wait $XVFB_PID $VNC_PID
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /start_vnc.sh
|
||||||
Reference in New Issue
Block a user