#!/usr/bin/env python3 """patch_card_strings.py — IDEMPOTENT self-patch for Product-card strings. Fixes (requested by owner): 1. Card header: "🆕 SẢN PHẨM MỚI TRÊN V.AI Market 📦" -> "SẢN PHẨM MỚI TRÊN V.AI Market" (drop both emoji; the owner's target header has NO emoji because emoji render as broken glyphs on the Zalo font). 2. Label: "Chi tiết sảnphẩm" (missing space) -> "Chi tiết sản phẩm". 3. Residual-old-header safety: any leftover "SẢN PHẨM MỘI VÀO DATASET" or "Chi titi" variants are mapped to the same corrected strings. Runs BEFORE the real app via Dockerfile CMD: CMD ["sh", "-c", "python patch_card_strings.py && exec ...app..."] Behavior: - Fixes the LOCAL on-disk file first (so the running app uses the new strings immediately). - If anything changed, commits the fixed file(s) back to this Space's repo (SELF_REPO_ID env) so the repo converges after ONE rebuild. - Idempotent: if nothing to fix -> prints "already fixed", makes NO commit, so there is no rebuild loop. - Never raises / always exits 0 (fail-open): a patch failure must not block the bot from starting. """ import os import sys import tempfile import pathlib # (old, new) — applied in order. All are safe exact substrings that only occur # in the product-card header/label code. REPLACEMENTS = [ ("🆕 SẢN PHẨM MỚI TRÊN V.AI Market 📦", "SẢN PHẨM MỚI TRÊN V.AI Market"), ("SẢN PHẨM MỚI TRÊN V.AI Market 📦", "SẢN PHẨM MỚI TRÊN V.AI Market"), ("SẢN PHẨM MỘI VÀO DATASET 📦", "SẢN PHẨM MỚI TRÊN V.AI Market"), ("SẢN PHẨM MỘI VÀO DATASET", "SẢN PHẨM MỚI TRÊN V.AI Market"), ("Chi tiết sảnphẩm", "Chi tiết sản phẩm"), ("Chi titi:", "Chi tiết sản phẩm:"), ("Chi titi", "Chi tiết sản phẩm"), ] _FILES = ["app.py", "child_app_template.py"] def _apply(content): changed = False for old, new in REPLACEMENTS: if old in content: content = content.replace(old, new) changed = True return content, changed def main(): rc = 0 try: repo_id = os.getenv("SELF_REPO_ID", "").strip() token = os.getenv("HF_TOKEN", os.getenv("HF_API_TOKEN", "")).strip() fixed = [] for fname in _FILES: if not os.path.exists(fname): continue # child template only exists on the main Space with open(fname, encoding="utf-8") as f: content = f.read() new_content, changed = _apply(content) if not changed: print("[patch] already fixed:", fname, flush=True) continue with open(fname, "w", encoding="utf-8") as f: f.write(new_content) print("[patch] fixed locally:", fname, flush=True) fixed.append((fname, new_content)) if not fixed: print("[patch] nothing to do — exiting clean", flush=True) return 0 if not (repo_id and token): print("[patch] WARNING: SELF_REPO_ID/HF_TOKEN not set — local files " "fixed but repo NOT updated (will self-fix again next rebuild)", flush=True) return 0 try: from huggingface_hub import HfApi api = HfApi(token=token) with tempfile.TemporaryDirectory() as tmpdir: tmp = pathlib.Path(tmpdir) for fname, content in fixed: (tmp / fname).write_text(content, encoding="utf-8") api.upload_folder( repo_id=repo_id, repo_type="space", folder_path=str(tmp), token=token, commit_message="Fix product card strings: header without emoji + 'Chi tiết sản phẩm' spacing", ) print("[patch] committed", [f for f, _ in fixed], "->", repo_id, flush=True) except Exception as e: print("[patch] commit failed (local fix still applied):", str(e)[:200], flush=True) rc = 0 except Exception as e: print("[patch] FATAL (ignored, fail-open):", str(e)[:200], flush=True) rc = 0 return rc if __name__ == "__main__": sys.exit(main())