auto backup: 2026-06-01 08:10:02

This commit is contained in:
ai_member_only 2026-06-01 08:10:02 +08:00
parent 734068fef9
commit 3da7fc67ac
3 changed files with 23691 additions and 60 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@
"""轻量 CORS 代理服务 - 转发推送请求到目标 API + 单组件重新生成""" """轻量 CORS 代理服务 - 转发推送请求到目标 API + 单组件重新生成"""
import json import json
import os
import cgi
import sys import sys
import time import time
import logging import logging
@ -13,6 +15,102 @@ from pathlib import Path
import requests import requests
UPLOAD_DIR = os.path.expanduser("~/.openclaw/workspace-xiaoban/tmp/uploads")
UPLOAD_HTML = r"""<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传文件到大麦</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:20px}
.container{max-width:600px;margin:0 auto;text-align:center}
h1{font-size:24px;margin:16px 0 4px}
.sub{color:#94a3b8;margin-bottom:24px;font-size:14px}
.upload-box{background:#1e293b;border:2px dashed #475569;border-radius:16px;padding:40px 20px;margin-bottom:16px}
.upload-box p{font-size:15px;color:#94a3b8;margin-bottom:16px}
.btn{display:inline-block;background:#6366f1;color:#fff;border:none;padding:12px 32px;border-radius:8px;font-size:16px;cursor:pointer;transition:background .2s}
.btn:active{background:#4f46e5}
.btn-wrap{position:relative;overflow:hidden}
.btn-wrap input[type=file]{position:absolute;left:0;top:0;width:100%;height:100%;opacity:0;cursor:pointer}
#progress{display:none;margin:12px 0}
.bar-bg{background:#334155;border-radius:8px;height:8px;overflow:hidden}
.bar{background:linear-gradient(90deg,#6366f1,#a855f7);height:100%;width:0;transition:width .3s}
#status{font-size:13px;color:#94a3b8;margin-bottom:8px}
#result{margin:12px 0;font-size:14px;line-height:1.8}
.success{color:#4ade80}
.error{color:#f87171}
.recent{margin-top:24px;text-align:left;max-height:180px;overflow-y:auto}
.recent h3{font-size:14px;color:#64748b;margin-bottom:8px}
.recent li{font-size:12px;color:#94a3b8;padding:4px 0;border-bottom:1px solid #1e293b}
</style>
</head>
<body>
<div class="container">
<h1>📤 上传文件到大麦</h1>
<p class="sub">点击下方按钮选择文件支持多选</p>
<div class="upload-box">
<p>📁 选择视频压缩包等文件上传</p>
<div class="btn-wrap">
<span class="btn">选择文件</span>
<input type="file" id="file" multiple>
</div>
</div>
<div id="progress">
<p id="status">上传中...</p>
<div class="bar-bg"><div class="bar" id="bar"></div></div>
</div>
<div id="result"></div>
<div class="recent" id="recent"><h3>📋 最近上传</h3><ul id="filelist"></ul></div>
</div>
<script>
document.getElementById('file').addEventListener('change', function(){
if(this.files.length) upload(this.files);
});
async function upload(files){
var progress = document.getElementById('progress');
var bar = document.getElementById('bar');
var status = document.getElementById('status');
var result = document.getElementById('result');
progress.style.display='block'; bar.style.width='0'; result.innerHTML='';
var ok=0, fail=0;
for(var i=0;i<files.length;i++){
var f = files[i];
status.textContent = '正在上传: ' + f.name + ' (' + (f.size/1024/1024).toFixed(1) + 'MB)...';
bar.style.width = ((i/files.length)*100)+'%';
var fd = new FormData(); fd.append('file', f);
try{
var r = await fetch('/upload', {method:'POST', body:fd});
var t = await r.json();
if(r.ok){ ok++; } else { fail++; result.innerHTML+='<div class="error">❌ '+f.name+': '+(t.error||'失败')+'</div>'; }
}catch(e){ fail++; result.innerHTML+='<div class="error">❌ '+f.name+': 网络错误</div>'; }
}
bar.style.width='100%';
if(fail===0) result.innerHTML+='<div class="success">✅ 全部 '+ok+' 个文件上传成功!</div>';
else result.innerHTML+='<div class="success">✅ '+ok+' 个成功</div><div class="error">❌ '+fail+' 个失败</div>';
status.textContent='上传完成!';
loadFiles();
setTimeout(function(){ progress.style.display='none'; result.innerHTML=''; }, 5000);
}
async function loadFiles(){
try{
var r = await fetch('/upload/files');
var files = await r.json();
document.getElementById('filelist').innerHTML = files.map(function(f){
return '<li>📄 '+f.name+' <span style="color:#64748b">'+(f.size/1024/1024).toFixed(1)+'MB · '+f.time+'</span></li>';
}).join('');
}catch(e){}
}
loadFiles();
</script>
</body>
</html>"""
PROJECT_ROOT = Path(__file__).resolve().parent.parent PROJECT_ROOT = Path(__file__).resolve().parent.parent
SCRIPTS_DIR = Path(__file__).resolve().parent SCRIPTS_DIR = Path(__file__).resolve().parent
LOG_DIR = PROJECT_ROOT / 'logs' LOG_DIR = PROJECT_ROOT / 'logs'
@ -131,6 +229,10 @@ class ProxyHandler(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
if self.path == '/health': if self.path == '/health':
self._send_json(200, {'status': 'ok'}) self._send_json(200, {'status': 'ok'})
elif self.path == '/upload':
self._serve_upload_page()
elif self.path == '/upload/files':
self._list_uploaded_files()
else: else:
self.send_error(404) self.send_error(404)
@ -139,6 +241,8 @@ class ProxyHandler(BaseHTTPRequestHandler):
self._handle_push() self._handle_push()
elif self.path == '/api/regenerate': elif self.path == '/api/regenerate':
self._handle_regenerate() self._handle_regenerate()
elif self.path == '/upload':
self._handle_upload()
else: else:
self.send_error(404) self.send_error(404)
@ -221,6 +325,61 @@ class ProxyHandler(BaseHTTPRequestHandler):
self._send_json(code, result) self._send_json(code, result)
logger.info(f'[{client_ip}] Regenerate done: status={code}') logger.info(f'[{client_ip}] Regenerate done: status={code}')
def _serve_upload_page(self):
html = UPLOAD_HTML.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.send_header('Content-Length', str(len(html)))
self.end_headers()
self.wfile.write(html)
def _list_uploaded_files(self):
import time as _time
os.makedirs(UPLOAD_DIR, exist_ok=True)
files = []
for name in sorted(os.listdir(UPLOAD_DIR), reverse=True):
path = os.path.join(UPLOAD_DIR, name)
if os.path.isfile(path):
st = os.stat(path)
files.append({
'name': name,
'size': st.st_size,
'time': _time.strftime('%m-%d %H:%M', _time.localtime(st.st_mtime))
})
self._send_json(200, files[:20])
def _handle_upload(self):
content_type = self.headers.get('Content-Type', '')
if 'multipart/form-data' not in content_type:
self._send_json(400, {'error': '需要 multipart/form-data'})
return
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
)
file_item = form['file']
if file_item is None or not file_item.filename:
self._send_json(400, {'error': '未选择文件'})
return
safe_name = os.path.basename(file_item.filename)
os.makedirs(UPLOAD_DIR, exist_ok=True)
path = os.path.join(UPLOAD_DIR, safe_name)
base, ext = os.path.splitext(safe_name)
counter = 1
while os.path.exists(path):
path = os.path.join(UPLOAD_DIR, f"{base}_{counter}{ext}")
counter += 1
with open(path, 'wb') as f:
f.write(file_item.file.read())
self._send_json(200, {'ok': True, 'filename': os.path.basename(path)})
def log_message(self, format, *args): def log_message(self, format, *args):
logger.debug(f'{self.client_address[0]} - {format % args}') logger.debug(f'{self.client_address[0]} - {format % args}')