Upload 7 files
Browse files- Dockerfile +28 -0
- app.py +405 -0
- docker-compose.yaml +32 -0
- pyproject.toml +11 -0
- requirements.txt +4 -0
- static/.DS_Store +0 -0
- uv.lock +244 -0
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 使用官方轻量级 Python 镜像
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# 设置工作目录
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 设置环境变量
|
| 8 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 9 |
+
ENV PYTHONUNBUFFERED=1
|
| 10 |
+
# 默认端口设置 (可以在运行时通过 -e PORT=xxx 覆盖)
|
| 11 |
+
ENV PORT=7860
|
| 12 |
+
|
| 13 |
+
# 复制依赖文件并安装
|
| 14 |
+
COPY requirements.txt .
|
| 15 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 16 |
+
|
| 17 |
+
# 复制主程序
|
| 18 |
+
COPY app.py .
|
| 19 |
+
|
| 20 |
+
# 创建必要的存储目录
|
| 21 |
+
RUN mkdir -p static/uploads static/temp_md
|
| 22 |
+
|
| 23 |
+
# 暴露端口 (Docker 文档用途,实际映射在 run 时指定)
|
| 24 |
+
EXPOSE $PORT
|
| 25 |
+
|
| 26 |
+
# 启动命令
|
| 27 |
+
# 注意:这里使用 Shell 格式 (不带 []) 以便解析 $PORT 变量
|
| 28 |
+
CMD gunicorn -w 4 -b 0.0.0.0:$PORT app:app
|
app.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import uuid
|
| 4 |
+
import requests
|
| 5 |
+
import mimetypes
|
| 6 |
+
import functools
|
| 7 |
+
import time
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
+
from flask import Flask, request, render_template_string, session, redirect, url_for, jsonify, send_file
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
# 加载环境变量
|
| 14 |
+
load_dotenv()
|
| 15 |
+
|
| 16 |
+
app = Flask(__name__)
|
| 17 |
+
|
| 18 |
+
# ================= 配置区域 =================
|
| 19 |
+
app.secret_key = os.getenv('FLASK_SECRET_KEY', os.urandom(24))
|
| 20 |
+
APP_TOKEN = os.getenv('APP_TOKEN', 'admin123')
|
| 21 |
+
STORAGE_MODE = os.getenv('STORAGE_MODE', 'cloud').lower()
|
| 22 |
+
|
| 23 |
+
# 图床配置
|
| 24 |
+
UPLOAD_API_URL = os.getenv('UPLOAD_API_URL', "https://your.domain/upload")
|
| 25 |
+
AUTH_CODE = os.getenv('AUTH_CODE', "your_authCode")
|
| 26 |
+
|
| 27 |
+
# 本地配置
|
| 28 |
+
SITE_DOMAIN = os.getenv('SITE_DOMAIN', 'http://127.0.0.1:5000').rstrip('/')
|
| 29 |
+
LOCAL_IMAGE_FOLDER = 'static/uploads'
|
| 30 |
+
TEMP_MD_FOLDER = 'static/temp_md' # 处理后的MD文件存放处
|
| 31 |
+
|
| 32 |
+
# 确保目录存在
|
| 33 |
+
os.makedirs(LOCAL_IMAGE_FOLDER, exist_ok=True)
|
| 34 |
+
os.makedirs(TEMP_MD_FOLDER, exist_ok=True)
|
| 35 |
+
|
| 36 |
+
HEADERS = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}
|
| 37 |
+
|
| 38 |
+
# ================= 文件系统管理逻辑 =================
|
| 39 |
+
|
| 40 |
+
def get_file_list_from_disk():
|
| 41 |
+
files_data = []
|
| 42 |
+
if not os.path.exists(TEMP_MD_FOLDER):
|
| 43 |
+
return []
|
| 44 |
+
|
| 45 |
+
for filename in os.listdir(TEMP_MD_FOLDER):
|
| 46 |
+
if not filename.endswith('.md'): continue
|
| 47 |
+
filepath = os.path.join(TEMP_MD_FOLDER, filename)
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
mtime = os.path.getmtime(filepath)
|
| 51 |
+
dt_obj = datetime.fromtimestamp(mtime)
|
| 52 |
+
time_str = dt_obj.strftime('%Y-%m-%d %H:%M:%S')
|
| 53 |
+
except:
|
| 54 |
+
mtime = 0
|
| 55 |
+
time_str = "Unknown"
|
| 56 |
+
|
| 57 |
+
display_name = filename
|
| 58 |
+
|
| 59 |
+
# 添加时间戳参数防止浏览器缓存
|
| 60 |
+
url = f"{SITE_DOMAIN}/api/download/{filename}?t={int(mtime)}"
|
| 61 |
+
|
| 62 |
+
files_data.append({
|
| 63 |
+
'filename': display_name,
|
| 64 |
+
'real_filename': filename,
|
| 65 |
+
'timestamp': time_str,
|
| 66 |
+
'timestamp_sort': mtime,
|
| 67 |
+
'url': url
|
| 68 |
+
})
|
| 69 |
+
|
| 70 |
+
files_data.sort(key=lambda x: x['timestamp_sort'], reverse=True)
|
| 71 |
+
return files_data
|
| 72 |
+
|
| 73 |
+
# ================= 业务逻辑 =================
|
| 74 |
+
|
| 75 |
+
def auth_required(f):
|
| 76 |
+
@functools.wraps(f)
|
| 77 |
+
def decorated_function(*args, **kwargs):
|
| 78 |
+
auth_header = request.headers.get('Authorization')
|
| 79 |
+
token_query = request.args.get('token')
|
| 80 |
+
api_token = None
|
| 81 |
+
if auth_header and auth_header.startswith("Bearer "):
|
| 82 |
+
api_token = auth_header.split(" ")[1]
|
| 83 |
+
elif token_query: api_token = token_query
|
| 84 |
+
|
| 85 |
+
if api_token == APP_TOKEN: return f(*args, **kwargs)
|
| 86 |
+
if session.get('is_logged_in'): return f(*args, **kwargs)
|
| 87 |
+
if request.path.startswith('/api/'): return jsonify({"error": "Unauthorized"}), 401
|
| 88 |
+
return redirect(url_for('login'))
|
| 89 |
+
return decorated_function
|
| 90 |
+
|
| 91 |
+
def get_extension(url, content_type=None):
|
| 92 |
+
path = urlparse(url).path
|
| 93 |
+
ext = os.path.splitext(path)[1]
|
| 94 |
+
if ext: return ext
|
| 95 |
+
if content_type:
|
| 96 |
+
ext = mimetypes.guess_extension(content_type)
|
| 97 |
+
if ext: return ext
|
| 98 |
+
return '.jpg'
|
| 99 |
+
|
| 100 |
+
def download_image(url):
|
| 101 |
+
try:
|
| 102 |
+
resp = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=15)
|
| 103 |
+
if resp.status_code == 200:
|
| 104 |
+
return resp.content, resp.headers.get('Content-Type')
|
| 105 |
+
return None, None
|
| 106 |
+
except: return None, None
|
| 107 |
+
|
| 108 |
+
def upload_to_cloud(image_data, filename, folder_name):
|
| 109 |
+
try:
|
| 110 |
+
params = {'authCode': AUTH_CODE, 'uploadFolder': folder_name}
|
| 111 |
+
files = {'file': (filename, image_data, 'application/octet-stream')}
|
| 112 |
+
resp = requests.post(UPLOAD_API_URL, params=params, headers=HEADERS, files=files, timeout=30)
|
| 113 |
+
resp.raise_for_status()
|
| 114 |
+
res = resp.json()
|
| 115 |
+
if 'url' in res: return res['url']
|
| 116 |
+
if 'data' in res:
|
| 117 |
+
if isinstance(res['data'], dict) and 'url' in res['data']: return res['data']['url']
|
| 118 |
+
return res['data']
|
| 119 |
+
return None
|
| 120 |
+
except Exception as e:
|
| 121 |
+
print(f"Cloud Upload Error: {e}")
|
| 122 |
+
return None
|
| 123 |
+
|
| 124 |
+
def save_to_local(image_data, original_url, content_type, folder_name):
|
| 125 |
+
try:
|
| 126 |
+
safe_folder = folder_name.replace('..', '').strip('/')
|
| 127 |
+
save_dir = os.path.join(LOCAL_IMAGE_FOLDER, safe_folder)
|
| 128 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 129 |
+
ext = get_extension(original_url, content_type)
|
| 130 |
+
unique_name = f"{uuid.uuid4().hex}{ext}"
|
| 131 |
+
path = os.path.join(save_dir, unique_name)
|
| 132 |
+
with open(path, 'wb') as f: f.write(image_data)
|
| 133 |
+
return f"{SITE_DOMAIN}/{LOCAL_IMAGE_FOLDER}/{safe_folder}/{unique_name}"
|
| 134 |
+
except: return None
|
| 135 |
+
|
| 136 |
+
def process_markdown_content(content, filename_no_ext):
|
| 137 |
+
pattern = re.compile(r'!\[(.*?)\]\((.*?)\)')
|
| 138 |
+
def replace_callback(match):
|
| 139 |
+
alt, url = match.group(1), match.group(2)
|
| 140 |
+
if not url.startswith(('http://', 'https://')): return match.group(0)
|
| 141 |
+
if STORAGE_MODE == 'local' and SITE_DOMAIN in url: return match.group(0)
|
| 142 |
+
|
| 143 |
+
img_data, c_type = download_image(url)
|
| 144 |
+
if not img_data: return match.group(0)
|
| 145 |
+
|
| 146 |
+
fname = url.split('/')[-1].split('?')[0] or "image.jpg"
|
| 147 |
+
new_url = None
|
| 148 |
+
if STORAGE_MODE == 'cloud':
|
| 149 |
+
new_url = upload_to_cloud(img_data, fname, filename_no_ext)
|
| 150 |
+
else:
|
| 151 |
+
new_url = save_to_local(img_data, url, c_type, filename_no_ext)
|
| 152 |
+
return f'' if new_url else match.group(0)
|
| 153 |
+
return pattern.sub(replace_callback, content)
|
| 154 |
+
|
| 155 |
+
def save_processed_md(content, original_filename):
|
| 156 |
+
safe_filename = os.path.basename(original_filename)
|
| 157 |
+
save_path = os.path.join(TEMP_MD_FOLDER, safe_filename)
|
| 158 |
+
with open(save_path, 'w', encoding='utf-8') as f:
|
| 159 |
+
f.write(content)
|
| 160 |
+
return f"{SITE_DOMAIN}/api/download/{safe_filename}"
|
| 161 |
+
|
| 162 |
+
# ================= HTML 模板 =================
|
| 163 |
+
|
| 164 |
+
BASE_TEMPLATE = """
|
| 165 |
+
<!DOCTYPE html>
|
| 166 |
+
<html lang="zh-CN">
|
| 167 |
+
<head>
|
| 168 |
+
<meta charset="UTF-8">
|
| 169 |
+
<title>Markdown 资源迁移器</title>
|
| 170 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 171 |
+
<style>
|
| 172 |
+
body { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); min-height: 100vh; color: #e2e8f0; }
|
| 173 |
+
.glass { background: rgba(255, 255, 255, 0.05); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); }
|
| 174 |
+
.btn-disabled { background-color: #475569 !important; color: #94a3b8 !important; cursor: not-allowed !important; }
|
| 175 |
+
.search-input:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3); }
|
| 176 |
+
/* 自定义滚动条样式 */
|
| 177 |
+
.scroll-custom::-webkit-scrollbar { width: 6px; }
|
| 178 |
+
.scroll-custom::-webkit-scrollbar-track { background: transparent; }
|
| 179 |
+
.scroll-custom::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
|
| 180 |
+
</style>
|
| 181 |
+
</head>
|
| 182 |
+
<body class="flex flex-col items-center justify-start p-4 md:p-10">
|
| 183 |
+
{{ content_html|safe }}
|
| 184 |
+
</body>
|
| 185 |
+
</html>
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
LOGIN_CONTENT = """
|
| 189 |
+
<div class="glass rounded-2xl p-8 w-full max-w-md mt-20">
|
| 190 |
+
<h2 class="text-3xl font-bold mb-6 text-center text-white">系统登录</h2>
|
| 191 |
+
<form method="post">
|
| 192 |
+
<div class="mb-6"><input type="password" name="token" class="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white" placeholder="Token" required></div>
|
| 193 |
+
{% if error %}<div class="text-red-400 text-center mb-4">{{ error }}</div>{% endif %}
|
| 194 |
+
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 rounded-xl">进入系统</button>
|
| 195 |
+
</form>
|
| 196 |
+
</div>
|
| 197 |
+
"""
|
| 198 |
+
|
| 199 |
+
INDEX_CONTENT = """
|
| 200 |
+
<div class="w-full max-w-4xl space-y-6">
|
| 201 |
+
<!-- Header -->
|
| 202 |
+
<div class="flex justify-between items-center mb-4">
|
| 203 |
+
<h1 class="text-2xl font-bold text-white">MD 图片迁移 <span class="text-xs text-blue-400 border border-blue-400/30 px-2 py-0.5 rounded ml-2">{{ mode|upper }}</span></h1>
|
| 204 |
+
<a href="/logout" class="text-xs bg-white/5 hover:bg-white/10 px-4 py-2 rounded-lg transition border border-white/10">退出</a>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
<!-- Upload Area -->
|
| 208 |
+
<div class="glass rounded-2xl p-8">
|
| 209 |
+
<form method="post" enctype="multipart/form-data" id="uploadForm">
|
| 210 |
+
<div class="border-2 border-dashed border-gray-600 rounded-xl p-10 text-center hover:bg-white/5 transition cursor-pointer relative group" id="dropZone">
|
| 211 |
+
<input type="file" name="file" accept=".md" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-50" required onchange="updateFileName(this)">
|
| 212 |
+
<div id="fileLabel" class="pointer-events-none group-hover:scale-105 transition duration-300">
|
| 213 |
+
<svg class="w-12 h-12 mx-auto mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
|
| 214 |
+
<p class="text-lg font-medium text-gray-200">点击或拖拽 Markdown 文件</p>
|
| 215 |
+
</div>
|
| 216 |
+
<div id="fileName" class="hidden text-xl font-bold text-blue-300 pointer-events-none break-all"></div>
|
| 217 |
+
</div>
|
| 218 |
+
<button type="submit" id="submitBtn" disabled class="w-full btn-disabled font-bold py-4 rounded-xl mt-6 transition shadow-lg text-white bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500" onclick="this.innerText='正在处理...';this.classList.add('opacity-75', 'cursor-wait')">请先选择文件</button>
|
| 219 |
+
</form>
|
| 220 |
+
</div>
|
| 221 |
+
|
| 222 |
+
<!-- History Area -->
|
| 223 |
+
<div class="glass rounded-2xl p-6">
|
| 224 |
+
<div class="flex flex-col md:flex-row justify-between items-center mb-6 gap-4">
|
| 225 |
+
<h2 class="text-xl font-bold flex items-center gap-2">
|
| 226 |
+
<svg class="w-5 h-5 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
| 227 |
+
历史文件列表
|
| 228 |
+
</h2>
|
| 229 |
+
<div class="relative w-full md:w-64">
|
| 230 |
+
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
| 231 |
+
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 20 20"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/></svg>
|
| 232 |
+
</div>
|
| 233 |
+
<input type="text" id="searchInput" onkeyup="filterTable()" class="search-input block w-full p-2 pl-10 text-sm text-white border border-gray-600 rounded-lg bg-white/5 placeholder-gray-400 focus:bg-white/10 transition" placeholder="搜索文件名...">
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
{% if history %}
|
| 238 |
+
<!--
|
| 239 |
+
核心修改:
|
| 240 |
+
max-h-[320px]: 限制最大高度 (约 5-6 行)
|
| 241 |
+
overflow-y-auto: 超出高度显示滚动条
|
| 242 |
+
scroll-custom: 自定义滚动条样式
|
| 243 |
+
-->
|
| 244 |
+
<div class="overflow-x-auto overflow-y-auto max-h-[320px] rounded-lg border border-gray-700 scroll-custom relative">
|
| 245 |
+
<table class="w-full text-left text-sm text-gray-300" id="historyTable">
|
| 246 |
+
<!--
|
| 247 |
+
sticky top-0: 表头固定
|
| 248 |
+
bg-slate-900: 给表头加背景色,防止滚动时内容重叠
|
| 249 |
+
z-10: 确保表头在内容之上
|
| 250 |
+
-->
|
| 251 |
+
<thead class="sticky top-0 z-10 bg-slate-900 text-xs uppercase text-gray-400 shadow-md">
|
| 252 |
+
<tr><th class="px-4 py-3">文件名</th><th class="px-4 py-3">处理时间</th><th class="px-4 py-3 text-right">操作</th></tr>
|
| 253 |
+
</thead>
|
| 254 |
+
<tbody class="divide-y divide-gray-700/50">
|
| 255 |
+
{% for item in history %}
|
| 256 |
+
<tr class="hover:bg-white/5 transition duration-150">
|
| 257 |
+
<td class="px-4 py-3 font-medium text-white break-all file-name-cell">{{ item.filename }}</td>
|
| 258 |
+
<td class="px-4 py-3 whitespace-nowrap text-gray-400">{{ item.timestamp }}</td>
|
| 259 |
+
<td class="px-4 py-3 text-right whitespace-nowrap">
|
| 260 |
+
<a href="{{ item.url }}" target="_blank" class="inline-flex items-center px-3 py-1.5 bg-blue-500/20 hover:bg-blue-500/40 text-blue-300 rounded-md text-xs font-bold transition">
|
| 261 |
+
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
| 262 |
+
下载
|
| 263 |
+
</a>
|
| 264 |
+
</td>
|
| 265 |
+
</tr>
|
| 266 |
+
{% endfor %}
|
| 267 |
+
</tbody>
|
| 268 |
+
</table>
|
| 269 |
+
<div id="noResult" class="hidden text-center py-4 text-gray-500 text-sm">未找到匹配的文件</div>
|
| 270 |
+
</div>
|
| 271 |
+
{% else %}
|
| 272 |
+
<div class="text-center py-8 text-gray-500 text-sm border-2 border-dashed border-gray-700 rounded-xl">暂无记录</div>
|
| 273 |
+
{% endif %}
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
|
| 277 |
+
<script>
|
| 278 |
+
function updateFileName(input) {
|
| 279 |
+
const btn = document.getElementById('submitBtn');
|
| 280 |
+
if(input.files && input.files[0]) {
|
| 281 |
+
document.getElementById('fileLabel').classList.add('hidden');
|
| 282 |
+
const fn = document.getElementById('fileName'); fn.innerText = input.files[0].name; fn.classList.remove('hidden');
|
| 283 |
+
btn.disabled = false; btn.innerText = "开始处理"; btn.classList.remove('btn-disabled');
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
function filterTable() {
|
| 288 |
+
const input = document.getElementById('searchInput');
|
| 289 |
+
const filter = input.value.toLowerCase();
|
| 290 |
+
const table = document.getElementById('historyTable');
|
| 291 |
+
const tr = table.getElementsByTagName('tr');
|
| 292 |
+
const noResult = document.getElementById('noResult');
|
| 293 |
+
let hasVisibleRow = false;
|
| 294 |
+
|
| 295 |
+
for (let i = 1; i < tr.length; i++) {
|
| 296 |
+
const td = tr[i].getElementsByClassName('file-name-cell')[0];
|
| 297 |
+
if (td) {
|
| 298 |
+
const txtValue = td.textContent || td.innerText;
|
| 299 |
+
if (txtValue.toLowerCase().indexOf(filter) > -1) {
|
| 300 |
+
tr[i].style.display = "";
|
| 301 |
+
hasVisibleRow = true;
|
| 302 |
+
} else {
|
| 303 |
+
tr[i].style.display = "none";
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
if (!hasVisibleRow && tr.length > 1) {
|
| 309 |
+
noResult.classList.remove('hidden');
|
| 310 |
+
} else {
|
| 311 |
+
noResult.classList.add('hidden');
|
| 312 |
+
}
|
| 313 |
+
}
|
| 314 |
+
</script>
|
| 315 |
+
"""
|
| 316 |
+
|
| 317 |
+
SUCCESS_CONTENT = """
|
| 318 |
+
<div class="glass rounded-2xl p-10 w-full max-w-lg text-center mt-10">
|
| 319 |
+
<div class="mb-6 inline-flex p-4 rounded-full bg-green-500/20 shadow-[0_0_20px_rgba(34,197,94,0.3)]">
|
| 320 |
+
<svg class="w-10 h-10 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
| 321 |
+
</div>
|
| 322 |
+
<h2 class="text-3xl font-bold mb-2 text-white">处理成功!</h2>
|
| 323 |
+
<p class="text-gray-400 mb-8">文件已保存。</p>
|
| 324 |
+
<div class="space-y-4">
|
| 325 |
+
<a href="{{ download_url }}" target="_blank" class="block w-full bg-white text-gray-900 font-bold py-3 rounded-xl transition hover:bg-gray-200 shadow-lg flex items-center justify-center gap-2">
|
| 326 |
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
| 327 |
+
立即下载
|
| 328 |
+
</a>
|
| 329 |
+
<a href="/" class="block w-full text-gray-400 hover:text-white py-2 transition">返回列表</a>
|
| 330 |
+
</div>
|
| 331 |
+
</div>
|
| 332 |
+
"""
|
| 333 |
+
|
| 334 |
+
# ================= 路由定义 =================
|
| 335 |
+
|
| 336 |
+
@app.route('/login', methods=['GET', 'POST'])
|
| 337 |
+
def login():
|
| 338 |
+
if request.method == 'POST':
|
| 339 |
+
if request.form.get('token') == APP_TOKEN:
|
| 340 |
+
session['is_logged_in'] = True
|
| 341 |
+
return redirect(url_for('index'))
|
| 342 |
+
else: return render_template_string(BASE_TEMPLATE, content_html=render_template_string(LOGIN_CONTENT, error="无效的 Token"))
|
| 343 |
+
return render_template_string(BASE_TEMPLATE, content_html=render_template_string(LOGIN_CONTENT, error=None))
|
| 344 |
+
|
| 345 |
+
@app.route('/logout')
|
| 346 |
+
def logout():
|
| 347 |
+
session.pop('is_logged_in', None)
|
| 348 |
+
return redirect(url_for('login'))
|
| 349 |
+
|
| 350 |
+
@app.route('/', methods=['GET', 'POST'])
|
| 351 |
+
@auth_required
|
| 352 |
+
def index():
|
| 353 |
+
if request.method == 'POST':
|
| 354 |
+
if 'file' not in request.files: return "无文件", 400
|
| 355 |
+
file = request.files['file']
|
| 356 |
+
if not file.filename: return "未选择文件", 400
|
| 357 |
+
try:
|
| 358 |
+
md_name = os.path.splitext(file.filename)[0]
|
| 359 |
+
content = file.read().decode('utf-8', errors='ignore')
|
| 360 |
+
new_content = process_markdown_content(content, md_name)
|
| 361 |
+
download_url = save_processed_md(new_content, file.filename)
|
| 362 |
+
return render_template_string(BASE_TEMPLATE, content_html=render_template_string(SUCCESS_CONTENT, download_url=download_url))
|
| 363 |
+
except Exception as e: return f"Error: {e}", 500
|
| 364 |
+
|
| 365 |
+
history = get_file_list_from_disk()
|
| 366 |
+
inner_html = render_template_string(INDEX_CONTENT, mode=STORAGE_MODE, history=history)
|
| 367 |
+
return render_template_string(BASE_TEMPLATE, content_html=inner_html)
|
| 368 |
+
|
| 369 |
+
@app.route('/api/process', methods=['POST'])
|
| 370 |
+
@auth_required
|
| 371 |
+
def api_process():
|
| 372 |
+
if 'file' not in request.files: return jsonify({"code": 400, "error": "No file uploaded"}), 400
|
| 373 |
+
file = request.files['file']
|
| 374 |
+
if not file.filename: return jsonify({"code": 400, "error": "Empty filename"}), 400
|
| 375 |
+
try:
|
| 376 |
+
md_name = os.path.splitext(file.filename)[0]
|
| 377 |
+
content = file.read().decode('utf-8', errors='ignore')
|
| 378 |
+
new_content = process_markdown_content(content, md_name)
|
| 379 |
+
download_url = save_processed_md(new_content, file.filename)
|
| 380 |
+
return jsonify({"code": 200, "message": "success", "filename": file.filename, "url": download_url})
|
| 381 |
+
except Exception as e: return jsonify({"code": 500, "error": str(e)}), 500
|
| 382 |
+
|
| 383 |
+
@app.route('/api/history', methods=['GET'])
|
| 384 |
+
@auth_required
|
| 385 |
+
def api_history():
|
| 386 |
+
try:
|
| 387 |
+
files = get_file_list_from_disk()
|
| 388 |
+
return jsonify({"code": 200, "message": "success", "data": files})
|
| 389 |
+
except Exception as e: return jsonify({"code": 500, "error": str(e)}), 500
|
| 390 |
+
|
| 391 |
+
@app.route('/api/download/<filename>', methods=['GET'])
|
| 392 |
+
@auth_required
|
| 393 |
+
def api_download(filename):
|
| 394 |
+
safe_filename = os.path.basename(filename)
|
| 395 |
+
file_path = os.path.join(TEMP_MD_FOLDER, safe_filename)
|
| 396 |
+
if not os.path.exists(file_path): return jsonify({"code": 404, "error": "File not found"}), 404
|
| 397 |
+
try:
|
| 398 |
+
return send_file(file_path, as_attachment=True, download_name=safe_filename, mimetype='text/markdown')
|
| 399 |
+
except Exception as e: return jsonify({"code": 500, "error": str(e)}), 500
|
| 400 |
+
|
| 401 |
+
if __name__ == '__main__':
|
| 402 |
+
# 获取环境变量 PORT,默认为 7860
|
| 403 |
+
port = int(os.environ.get('PORT', 7860))
|
| 404 |
+
# host='0.0.0.0' 允许外部访问
|
| 405 |
+
app.run(debug=True, host='0.0.0.0', port=port)
|
docker-compose.yaml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
md-migrator:
|
| 5 |
+
build: .
|
| 6 |
+
container_name: md-migrator
|
| 7 |
+
restart: always
|
| 8 |
+
ports:
|
| 9 |
+
# 宿主机端口:容器端口
|
| 10 |
+
- "7860:7860"
|
| 11 |
+
environment:
|
| 12 |
+
# === 端口配置 ===
|
| 13 |
+
- PORT=7860
|
| 14 |
+
|
| 15 |
+
# === 基础配置 ===
|
| 16 |
+
- FLASK_SECRET_KEY=change_this_random_string
|
| 17 |
+
- APP_TOKEN=admin123
|
| 18 |
+
|
| 19 |
+
# === 模式配置 (cloud 或 local) ===
|
| 20 |
+
- STORAGE_MODE=cloud
|
| 21 |
+
|
| 22 |
+
# === Cloud 模式配置 ===
|
| 23 |
+
- UPLOAD_API_URL=https://your.domain/upload
|
| 24 |
+
- AUTH_CODE=your_auth_code
|
| 25 |
+
|
| 26 |
+
# === Local 模式配置 ===
|
| 27 |
+
# 注意:如果端口变了,这里的域名也要相应修改
|
| 28 |
+
- SITE_DOMAIN=http://127.0.0.1:7860
|
| 29 |
+
|
| 30 |
+
volumes:
|
| 31 |
+
- ./data/temp_md:/app/static/temp_md
|
| 32 |
+
- ./data/uploads:/app/static/uploads
|
pyproject.toml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "mineru-helper"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.13"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"flask>=3.1.2",
|
| 9 |
+
"python-dotenv>=1.2.1",
|
| 10 |
+
"requests>=2.32.5",
|
| 11 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask
|
| 2 |
+
requests
|
| 3 |
+
python-dotenv
|
| 4 |
+
gunicorn
|
static/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
uv.lock
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
revision = 2
|
| 3 |
+
requires-python = ">=3.13"
|
| 4 |
+
|
| 5 |
+
[[package]]
|
| 6 |
+
name = "blinker"
|
| 7 |
+
version = "1.9.0"
|
| 8 |
+
source = { registry = "https://pypi.org/simple" }
|
| 9 |
+
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
|
| 10 |
+
wheels = [
|
| 11 |
+
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
[[package]]
|
| 15 |
+
name = "certifi"
|
| 16 |
+
version = "2025.11.12"
|
| 17 |
+
source = { registry = "https://pypi.org/simple" }
|
| 18 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
| 19 |
+
wheels = [
|
| 20 |
+
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
[[package]]
|
| 24 |
+
name = "charset-normalizer"
|
| 25 |
+
version = "3.4.4"
|
| 26 |
+
source = { registry = "https://pypi.org/simple" }
|
| 27 |
+
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
| 28 |
+
wheels = [
|
| 29 |
+
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
| 30 |
+
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
| 31 |
+
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
| 32 |
+
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
| 33 |
+
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
| 34 |
+
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
| 35 |
+
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
| 36 |
+
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
| 37 |
+
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
| 38 |
+
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
| 39 |
+
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
| 40 |
+
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
| 41 |
+
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
| 42 |
+
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
| 43 |
+
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
| 44 |
+
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
| 45 |
+
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
| 46 |
+
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
| 47 |
+
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
| 48 |
+
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
| 49 |
+
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
| 50 |
+
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
| 51 |
+
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
| 52 |
+
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
| 53 |
+
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
| 54 |
+
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
| 55 |
+
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
| 56 |
+
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
| 57 |
+
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
| 58 |
+
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
| 59 |
+
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
| 60 |
+
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
| 61 |
+
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
[[package]]
|
| 65 |
+
name = "click"
|
| 66 |
+
version = "8.3.1"
|
| 67 |
+
source = { registry = "https://pypi.org/simple" }
|
| 68 |
+
dependencies = [
|
| 69 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 70 |
+
]
|
| 71 |
+
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
| 72 |
+
wheels = [
|
| 73 |
+
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
| 74 |
+
]
|
| 75 |
+
|
| 76 |
+
[[package]]
|
| 77 |
+
name = "colorama"
|
| 78 |
+
version = "0.4.6"
|
| 79 |
+
source = { registry = "https://pypi.org/simple" }
|
| 80 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
| 81 |
+
wheels = [
|
| 82 |
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
| 83 |
+
]
|
| 84 |
+
|
| 85 |
+
[[package]]
|
| 86 |
+
name = "flask"
|
| 87 |
+
version = "3.1.2"
|
| 88 |
+
source = { registry = "https://pypi.org/simple" }
|
| 89 |
+
dependencies = [
|
| 90 |
+
{ name = "blinker" },
|
| 91 |
+
{ name = "click" },
|
| 92 |
+
{ name = "itsdangerous" },
|
| 93 |
+
{ name = "jinja2" },
|
| 94 |
+
{ name = "markupsafe" },
|
| 95 |
+
{ name = "werkzeug" },
|
| 96 |
+
]
|
| 97 |
+
sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" }
|
| 98 |
+
wheels = [
|
| 99 |
+
{ url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" },
|
| 100 |
+
]
|
| 101 |
+
|
| 102 |
+
[[package]]
|
| 103 |
+
name = "idna"
|
| 104 |
+
version = "3.11"
|
| 105 |
+
source = { registry = "https://pypi.org/simple" }
|
| 106 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
| 107 |
+
wheels = [
|
| 108 |
+
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
| 109 |
+
]
|
| 110 |
+
|
| 111 |
+
[[package]]
|
| 112 |
+
name = "itsdangerous"
|
| 113 |
+
version = "2.2.0"
|
| 114 |
+
source = { registry = "https://pypi.org/simple" }
|
| 115 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
|
| 116 |
+
wheels = [
|
| 117 |
+
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
|
| 118 |
+
]
|
| 119 |
+
|
| 120 |
+
[[package]]
|
| 121 |
+
name = "jinja2"
|
| 122 |
+
version = "3.1.6"
|
| 123 |
+
source = { registry = "https://pypi.org/simple" }
|
| 124 |
+
dependencies = [
|
| 125 |
+
{ name = "markupsafe" },
|
| 126 |
+
]
|
| 127 |
+
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
| 128 |
+
wheels = [
|
| 129 |
+
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
| 130 |
+
]
|
| 131 |
+
|
| 132 |
+
[[package]]
|
| 133 |
+
name = "markupsafe"
|
| 134 |
+
version = "3.0.3"
|
| 135 |
+
source = { registry = "https://pypi.org/simple" }
|
| 136 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
| 137 |
+
wheels = [
|
| 138 |
+
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
|
| 139 |
+
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
|
| 140 |
+
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
|
| 141 |
+
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
|
| 142 |
+
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
|
| 143 |
+
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
|
| 144 |
+
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
|
| 145 |
+
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
|
| 146 |
+
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
|
| 147 |
+
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
|
| 148 |
+
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
|
| 149 |
+
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
|
| 150 |
+
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
|
| 151 |
+
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
|
| 152 |
+
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
|
| 153 |
+
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
|
| 154 |
+
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
|
| 155 |
+
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
|
| 156 |
+
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
|
| 157 |
+
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
|
| 158 |
+
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
|
| 159 |
+
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
|
| 160 |
+
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
|
| 161 |
+
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
|
| 162 |
+
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
|
| 163 |
+
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
|
| 164 |
+
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
|
| 165 |
+
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
|
| 166 |
+
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
|
| 167 |
+
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
|
| 168 |
+
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
|
| 169 |
+
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
|
| 170 |
+
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
|
| 171 |
+
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
|
| 172 |
+
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
|
| 173 |
+
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
|
| 174 |
+
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
|
| 175 |
+
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
|
| 176 |
+
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
|
| 177 |
+
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
|
| 178 |
+
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
|
| 179 |
+
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
|
| 180 |
+
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
|
| 181 |
+
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
[[package]]
|
| 185 |
+
name = "mineru-helper"
|
| 186 |
+
version = "0.1.0"
|
| 187 |
+
source = { virtual = "." }
|
| 188 |
+
dependencies = [
|
| 189 |
+
{ name = "flask" },
|
| 190 |
+
{ name = "python-dotenv" },
|
| 191 |
+
{ name = "requests" },
|
| 192 |
+
]
|
| 193 |
+
|
| 194 |
+
[package.metadata]
|
| 195 |
+
requires-dist = [
|
| 196 |
+
{ name = "flask", specifier = ">=3.1.2" },
|
| 197 |
+
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
| 198 |
+
{ name = "requests", specifier = ">=2.32.5" },
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
[[package]]
|
| 202 |
+
name = "python-dotenv"
|
| 203 |
+
version = "1.2.1"
|
| 204 |
+
source = { registry = "https://pypi.org/simple" }
|
| 205 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
| 206 |
+
wheels = [
|
| 207 |
+
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
| 208 |
+
]
|
| 209 |
+
|
| 210 |
+
[[package]]
|
| 211 |
+
name = "requests"
|
| 212 |
+
version = "2.32.5"
|
| 213 |
+
source = { registry = "https://pypi.org/simple" }
|
| 214 |
+
dependencies = [
|
| 215 |
+
{ name = "certifi" },
|
| 216 |
+
{ name = "charset-normalizer" },
|
| 217 |
+
{ name = "idna" },
|
| 218 |
+
{ name = "urllib3" },
|
| 219 |
+
]
|
| 220 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
| 221 |
+
wheels = [
|
| 222 |
+
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
| 223 |
+
]
|
| 224 |
+
|
| 225 |
+
[[package]]
|
| 226 |
+
name = "urllib3"
|
| 227 |
+
version = "2.6.0"
|
| 228 |
+
source = { registry = "https://pypi.org/simple" }
|
| 229 |
+
sdist = { url = "https://files.pythonhosted.org/packages/1c/43/554c2569b62f49350597348fc3ac70f786e3c32e7f19d266e19817812dd3/urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1", size = 432585, upload-time = "2025-12-05T15:08:47.885Z" }
|
| 230 |
+
wheels = [
|
| 231 |
+
{ url = "https://files.pythonhosted.org/packages/56/1a/9ffe814d317c5224166b23e7c47f606d6e473712a2fad0f704ea9b99f246/urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f", size = 131083, upload-time = "2025-12-05T15:08:45.983Z" },
|
| 232 |
+
]
|
| 233 |
+
|
| 234 |
+
[[package]]
|
| 235 |
+
name = "werkzeug"
|
| 236 |
+
version = "3.1.4"
|
| 237 |
+
source = { registry = "https://pypi.org/simple" }
|
| 238 |
+
dependencies = [
|
| 239 |
+
{ name = "markupsafe" },
|
| 240 |
+
]
|
| 241 |
+
sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" }
|
| 242 |
+
wheels = [
|
| 243 |
+
{ url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" },
|
| 244 |
+
]
|