| import os |
| import runpy |
| import sys |
| from typing import Dict, Sequence, Tuple |
|
|
| ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) |
| SCRIPTS_DIR = os.path.join(ROOT_DIR, "Scripts") |
| EXTRACT_DIR = os.path.join(SCRIPTS_DIR, "Extract Pixiv") |
|
|
| RESET = "\x1b[0m" |
| BOLD = "\x1b[1m" |
| FG_PRIMARY = "\x1b[38;5;81m" |
| FG_ACCENT = "\x1b[38;5;214m" |
| FG_MUTED = "\x1b[38;5;245m" |
| FG_OK = "\x1b[38;5;120m" |
| FG_ERROR = "\x1b[38;5;203m" |
|
|
| RULE = "-" * 56 |
| EXIT_KEYS = {"q", "quit", "exit"} |
|
|
| MENU_SECTIONS: Sequence[Tuple[str, Sequence[Tuple[str, str, str]]]] = ( |
| ( |
| "Extract Pixiv", |
| ( |
| ("1", "Search (all tags)", os.path.join(EXTRACT_DIR, "search.py")), |
| ("2", "Search (AI only)", os.path.join(EXTRACT_DIR, "ai_search.py")), |
| ("3", "Search (real only)", os.path.join(EXTRACT_DIR, "real_search.py")), |
| ("4", "Users", os.path.join(EXTRACT_DIR, "user.py")), |
| ), |
| ), |
| ( |
| "Hunt", |
| ( |
| ("5", "Hunt", os.path.join(SCRIPTS_DIR, "hunt.py")), |
| ("6", "Scan existing exif", os.path.join(SCRIPTS_DIR, "scan_existing_exif.py")), |
| ), |
| ), |
| ( |
| "Maintenance", |
| ( |
| ("7", "Clear text logs", os.path.join(SCRIPTS_DIR, "clear_texts.py")), |
| ("8", "Clear DB empty rows", os.path.join(SCRIPTS_DIR, "clear_db.py")), |
| ("9", "Clear images (except Stash)", os.path.join(SCRIPTS_DIR, "clear_images.py")), |
| ), |
| ), |
| ) |
|
|
| ACTION_MAP: Dict[str, Tuple[str, str]] = { |
| key: (label, path) |
| for _, items in MENU_SECTIONS |
| for key, label, path in items |
| } |
|
|
| def style(text: str, *codes: str) -> str: |
| if not codes: |
| return text |
| return "".join(codes) + text + RESET |
|
|
| def print_menu() -> None: |
| print(style("Pixif", FG_PRIMARY, BOLD)) |
| print(style(RULE, FG_MUTED)) |
| for section, items in MENU_SECTIONS: |
| print(style(section, FG_ACCENT, BOLD)) |
| for key, label, _ in items: |
| print(f" {style(key, FG_PRIMARY, BOLD)} {label}") |
| print(style(RULE, FG_MUTED)) |
| print(f" {style('q', FG_MUTED)} Quit") |
|
|
| def run_script(path: str) -> int: |
| if not os.path.isfile(path): |
| print(style(f"Missing: {path}", FG_ERROR)) |
| return 1 |
| original_cwd = os.getcwd() |
| original_sys_path = list(sys.path) |
| script_dir = os.path.dirname(path) |
| sys.path.insert(0, script_dir) |
| if ROOT_DIR not in sys.path: |
| sys.path.insert(0, ROOT_DIR) |
| try: |
| os.chdir(ROOT_DIR) |
| try: |
| runpy.run_path(path, run_name="__main__") |
| except SystemExit as exc: |
| if isinstance(exc.code, int): |
| return exc.code |
| return 0 |
| return 0 |
| except KeyboardInterrupt: |
| print(style("Cancelled.", FG_ERROR)) |
| return 130 |
| except Exception as exc: |
| print(style(f"Error: {exc}", FG_ERROR)) |
| return 1 |
| finally: |
| os.chdir(original_cwd) |
| sys.path[:] = original_sys_path |
|
|
| def main() -> int: |
| os.chdir(ROOT_DIR) |
| while True: |
| print_menu() |
| choice = input(style("Select an option: ", FG_MUTED)).strip().lower() |
| if choice in EXIT_KEYS: |
| return 0 |
| action = ACTION_MAP.get(choice) |
| if not action: |
| print(style("Unknown option.", FG_ERROR)) |
| continue |
| label, path = action |
| print(style(f"Running {label}...", FG_PRIMARY, BOLD)) |
| code = run_script(path) |
| if code == 0: |
| print(style("Done.", FG_OK)) |
| else: |
| print(style(f"Exit code: {code}", FG_ERROR)) |
| input(style("Press Enter to return to menu.", FG_MUTED)) |
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|