feat: 新增4个工具(imgcompress/LamaCleaner/webp2jpg/Overleaf/LaTeX公式编辑器) + 应用广场布局优化
新增工具部署: - imgcompress (端口18087) - 图片压缩、格式转换、AI抠图 - Lama Cleaner (端口18088) - AI图像擦除/去水印 - webp2jpg-online (端口18089) - 图片格式批量互转 - Overleaf (端口18090) - 在线LaTeX论文编辑器(docker-compose + MongoDB 8.0) - LaTeX公式编辑器 (端口18091) - 纯前端KaTeX公式编辑 应用广场优化: - 去掉tab切换,所有分类平铺展示 - CSS Grid自适应布局,一行可排3-4个卡片 - 重新分为4个分类:文档处理、图片处理、创作绘图、科研写作 其他: - 更新 CLAUDE.md 项目配置文档 - PPTist AI后端优化prompt和流式输出格式 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
128
scripts/overleaf-deploy/overleaf-register.py
Normal file
128
scripts/overleaf-deploy/overleaf-register.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Overleaf 自助注册服务
|
||||
提供简单的注册页面,用户输入邮箱和密码即可注册
|
||||
内部通过管理员账号调用 Overleaf API 创建用户
|
||||
"""
|
||||
from fastapi import FastAPI, Request, Form
|
||||
from fastapi.responses import HTMLResponse
|
||||
import requests
|
||||
import os
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
OL_URL = os.getenv("OL_INSTANCE", "http://overleaf")
|
||||
OL_ADMIN_EMAIL = os.getenv("OL_ADMIN_EMAIL", "")
|
||||
OL_ADMIN_PASSWORD = os.getenv("OL_ADMIN_PASSWORD", "")
|
||||
|
||||
REGISTER_HTML = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>注册 - LaTeX 论文编辑器</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 40px; width: 400px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
|
||||
h2 { text-align: center; margin-bottom: 8px; color: #333; }
|
||||
.sub { text-align: center; color: #999; font-size: 14px; margin-bottom: 30px; }
|
||||
label { display: block; margin-bottom: 6px; color: #555; font-size: 14px; }
|
||||
input { width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; margin-bottom: 16px; }
|
||||
input:focus { outline: none; border-color: #004EA0; }
|
||||
button { width: 100%; padding: 12px; background: #004EA0; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; }
|
||||
button:hover { background: #003a7a; }
|
||||
.msg { text-align: center; margin-top: 16px; font-size: 14px; }
|
||||
.msg.ok { color: #16a34a; }
|
||||
.msg.err { color: #dc2626; }
|
||||
a { color: #004EA0; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>注册账号</h2>
|
||||
<p class="sub">LaTeX 论文编辑器</p>
|
||||
<form method="POST" action="/register">
|
||||
<label>邮箱</label>
|
||||
<input type="email" name="email" required placeholder="请输入邮箱">
|
||||
<button type="submit">注册</button>
|
||||
</form>
|
||||
<p class="sub" style="margin-top:20px">已有账号?<a href="OLURL">去登录</a></p>
|
||||
MSG_PLACEHOLDER
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""".replace("OLURL", OL_URL)
|
||||
|
||||
|
||||
class OverleafAdmin:
|
||||
def __init__(self):
|
||||
self.session = requests.Session()
|
||||
self.logged_in = False
|
||||
|
||||
def _get_csrf(self, path="/login"):
|
||||
resp = self.session.get(f"{OL_URL}{path}")
|
||||
# 从页面中提取 CSRF token
|
||||
import re
|
||||
match = re.search(r'name="_csrf"[^>]*value="([^"]+)"', resp.text)
|
||||
if not match:
|
||||
match = re.search(r'ol-csrfToken["\s]*content="([^"]+)"', resp.text)
|
||||
if not match:
|
||||
match = re.search(r'csrfToken["\s]*:\s*"([^"]+)"', resp.text)
|
||||
return match.group(1) if match else ""
|
||||
|
||||
def login(self):
|
||||
csrf = self._get_csrf("/login")
|
||||
resp = self.session.post(f"{OL_URL}/login", data={
|
||||
"_csrf": csrf,
|
||||
"email": OL_ADMIN_EMAIL,
|
||||
"password": OL_ADMIN_PASSWORD,
|
||||
}, allow_redirects=False)
|
||||
self.logged_in = resp.status_code in (200, 302)
|
||||
return self.logged_in
|
||||
|
||||
def register_user(self, email):
|
||||
if not self.logged_in:
|
||||
self.login()
|
||||
csrf = self._get_csrf("/admin/register")
|
||||
resp = self.session.post(f"{OL_URL}/admin/register", data={
|
||||
"_csrf": csrf,
|
||||
"email": email,
|
||||
})
|
||||
# 从响应中提取设置密码的链接
|
||||
import re
|
||||
set_password_url = ""
|
||||
match = re.search(r'(https?://[^\s"<>]*user/password/set\?[^\s"<>]+)', resp.text)
|
||||
if match:
|
||||
set_password_url = match.group(1)
|
||||
return resp.status_code == 200, set_password_url
|
||||
|
||||
|
||||
admin = OverleafAdmin()
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index():
|
||||
return REGISTER_HTML.replace("MSG_PLACEHOLDER", "")
|
||||
|
||||
|
||||
@app.post("/register", response_class=HTMLResponse)
|
||||
async def register(email: str = Form(...)):
|
||||
try:
|
||||
ok, set_url = admin.register_user(email)
|
||||
if ok and set_url:
|
||||
# 把链接中的 localhost 替换为实际访问地址
|
||||
import re
|
||||
set_url = re.sub(r'https?://[^/]+', OL_URL, set_url)
|
||||
msg = f'<p class="msg ok">注册成功!请点击下方链接设置密码:</p><p style="text-align:center;margin-top:10px"><a href="{set_url}" target="_blank" style="background:#004EA0;color:#fff;padding:10px 24px;border-radius:8px;text-decoration:none;font-size:14px">设置密码</a></p>'
|
||||
elif ok:
|
||||
msg = f'<p class="msg ok">注册成功!该邮箱可能已注册。请前往 <a href="{OL_URL}/login">登录页面</a> 登录。</p>'
|
||||
else:
|
||||
msg = f'<p class="msg err">注册失败,请稍后重试。</p>'
|
||||
except Exception as e:
|
||||
msg = f'<p class="msg err">注册失败:{str(e)[:100]}</p>'
|
||||
return REGISTER_HTML.replace("MSG_PLACEHOLDER", msg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=18091)
|
||||
Reference in New Issue
Block a user