Initial standalone crypto_okx with one-click deploy.
Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""期权复盘截图:独立命名空间,与合约同款四周期 5m/15m/1h/4h."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence
|
||||
|
||||
OPTIONS_REVIEW_UPLOAD_TFS: tuple[str, ...] = ("5m", "15m", "1h", "4h")
|
||||
OPTIONS_REVIEW_ALLOWED_EXT = frozenset({".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp"})
|
||||
_DRAFT_ID_RE = re.compile(r"^[a-f0-9]{32}$")
|
||||
_SLOT_FILE_RE = re.compile(
|
||||
r"^options_journal_([a-f0-9]{32})_(5m|15m|1h|4h)\.(png|jpg|jpeg|webp|gif|bmp)$",
|
||||
re.I,
|
||||
)
|
||||
|
||||
|
||||
def normalize_options_review_draft_id(raw: Any) -> Optional[str]:
|
||||
s = str(raw or "").strip().lower()
|
||||
if _DRAFT_ID_RE.match(s):
|
||||
return s
|
||||
return None
|
||||
|
||||
|
||||
def _safe_ext(filename: str) -> str:
|
||||
ext = os.path.splitext(str(filename or ""))[1].lower()
|
||||
return ext if ext in OPTIONS_REVIEW_ALLOWED_EXT else ".png"
|
||||
|
||||
|
||||
def options_review_upload_dir(base_upload_folder: str) -> str:
|
||||
"""独立子目录 static/images/options_journal."""
|
||||
base = os.path.abspath(base_upload_folder or "")
|
||||
path = os.path.join(base, "options_journal")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
def build_options_review_slot_filename(
|
||||
draft_id: str,
|
||||
tf: str,
|
||||
ext: str,
|
||||
*,
|
||||
secure_filename_fn: Callable[[str], str],
|
||||
) -> str:
|
||||
ext = ext if ext.startswith(".") else f".{ext}"
|
||||
ext = _safe_ext(f"x{ext}")
|
||||
fname = secure_filename_fn(f"options_journal_{draft_id}_{tf}{ext}")
|
||||
return fname or ""
|
||||
|
||||
|
||||
def is_valid_options_review_file(filename: str, draft_id: str, tf: str) -> bool:
|
||||
fn = os.path.basename(str(filename or "").strip())
|
||||
if not fn or fn != str(filename or "").strip():
|
||||
return False
|
||||
m = _SLOT_FILE_RE.match(fn)
|
||||
if not m:
|
||||
return False
|
||||
return m.group(1) == draft_id.lower() and m.group(2) == tf
|
||||
|
||||
|
||||
def save_options_review_slot_file(
|
||||
file,
|
||||
draft_id: str,
|
||||
tf: str,
|
||||
upload_folder: str,
|
||||
*,
|
||||
secure_filename_fn: Callable[[str], str],
|
||||
) -> Optional[Dict[str, str]]:
|
||||
if tf not in OPTIONS_REVIEW_UPLOAD_TFS or not draft_id or not upload_folder:
|
||||
return None
|
||||
if not file or not getattr(file, "filename", None):
|
||||
return None
|
||||
ext = _safe_ext(file.filename)
|
||||
fname = build_options_review_slot_filename(
|
||||
draft_id, tf, ext, secure_filename_fn=secure_filename_fn
|
||||
)
|
||||
if not fname:
|
||||
return None
|
||||
os.makedirs(upload_folder, exist_ok=True)
|
||||
path = os.path.join(upload_folder, fname)
|
||||
file.save(path)
|
||||
return {"tf": tf, "file": fname}
|
||||
|
||||
|
||||
def parse_options_review_images_json(raw: Any) -> List[Dict[str, str]]:
|
||||
if not raw:
|
||||
return []
|
||||
if isinstance(raw, list):
|
||||
data = raw
|
||||
else:
|
||||
try:
|
||||
data = json.loads(str(raw))
|
||||
except (TypeError, ValueError, json.JSONDecodeError):
|
||||
return []
|
||||
if not isinstance(data, list):
|
||||
return []
|
||||
out: List[Dict[str, str]] = []
|
||||
for item in data:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
tf = str(item.get("tf") or "").strip()
|
||||
file = str(item.get("file") or "").strip()
|
||||
if file:
|
||||
out.append({"tf": tf, "file": file})
|
||||
return out
|
||||
|
||||
|
||||
def images_json_dumps(items: Sequence[Mapping[str, str]]) -> Optional[str]:
|
||||
if not items:
|
||||
return None
|
||||
return json.dumps(list(items), ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
|
||||
def options_review_image_paths(row: Any, upload_folder: str) -> List[str]:
|
||||
upload_root = os.path.abspath(upload_folder or "")
|
||||
options_dir = options_review_upload_dir(upload_root)
|
||||
paths: List[str] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
def _add(name: Optional[str]) -> None:
|
||||
if not name:
|
||||
return
|
||||
base = os.path.basename(str(name).strip())
|
||||
if not base:
|
||||
return
|
||||
for folder in (options_dir, upload_root):
|
||||
p = os.path.abspath(os.path.join(folder, base))
|
||||
if os.path.isfile(p) and p not in seen:
|
||||
seen.add(p)
|
||||
paths.append(p)
|
||||
return
|
||||
|
||||
try:
|
||||
keys = row.keys() if hasattr(row, "keys") else ()
|
||||
except Exception:
|
||||
keys = ()
|
||||
images = parse_options_review_images_json(
|
||||
row["images_json"] if "images_json" in keys else getattr(row, "images_json", None)
|
||||
)
|
||||
for item in images:
|
||||
_add(item.get("file"))
|
||||
if "image" in keys or hasattr(row, "image"):
|
||||
_add(row["image"] if "image" in keys else getattr(row, "image", None))
|
||||
return paths
|
||||
Reference in New Issue
Block a user