Commit 24094c39 authored by nanahira's avatar nanahira

first

parents
Pipeline #40748 passed with stages
in 2 minutes and 29 seconds
*.pyc
__pycache__
.idea
.vscode
*.cdb
*.html
lflist.conf
stages:
- build
- deploy
variables:
GIT_DEPTH: "1"
build:
stage: build
dependencies: []
tags:
- linux
script:
- mkdir -p dist
- git clone --depth=1 https://code.moenext.com/mycard/ygopro-database
- wget -O genesys.html https://www.yugioh-card.com/en/genesys/
- python3 main.py --html genesys.html --cdb ygopro-database/locales/en-US/cards.cdb --out dist/lflist.conf --cap 100
artifacts:
paths:
- dist
upload_to_minio:
stage: deploy
dependencies:
- build
tags:
- linux
script:
- aws s3 --endpoint=https://minio.moenext.com:9000 sync --delete dist/ s3://mycard/ygopro-genesys
only:
- master
.deploy_to_server:
stage: deploy
dependencies:
- build
tags:
- linux
script:
- apt update && apt -y install openssh-client rsync coreutils
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
- echo $NANAHIRA_SSH_KEY | base64 --decode > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/*
- rsync -4cavzP --exclude=pics --delete ./dist/ $SERVER_USER@$SERVER_HOST:~/ygopro-genesys/expansions
only:
- master
deploy_to_koishi:
extends: .deploy_to_server
variables:
SERVER_HOST: koishi.momobako.com
SERVER_USER: nanahira
# Genesys lflist.conf Generator
This project provides a Python script (`main.py`) that generates a **Genesys-format `lflist.conf`** for YGOPro/EDOPro.
It combines the official Genesys point list (from Konami’s website) with the card database (`cards.cdb`).
---
## Features
- Parse Genesys HTML table (Card Name / Points).
- Match card names to `cards.cdb`:
- First try **absolute match** (exact English name).
- Fallback to **normalized match** (remove non-alphanumeric characters, lowercase).
- Disable all **Link** and **Pendulum** monsters.
- Output in the new `$slot`-style `lflist.conf` format:
```
#[Genesys TCG]
# Genernated by genesys-gen
!Genesys TCG
$genesys 100
# Genesys points
<card_id> $genesys <points> -- <English name>
# Disable Pendulum and Link monsters
<card_id> 0
```
---
## Requirements
- **Python 3.10+**
- No external dependencies. Only Python standard library modules are used.
---
## Important Notes
- You **must** use the **English version** of `cards.cdb`.
Otherwise, card names will not match the Genesys list correctly.
---
## Preparing Resources
```
# Download the Genesys point list (HTML)
wget -O genesys.html https://www.yugioh-card.com/en/genesys/
# Download the English card database
wget -O cards.cdb https://cdntx.moecube.com/koishipro/ygopro-database/en-US/cards.cdb
```
---
## Usage
```
python3 main.py \
--html genesys.html \
--cdb cards.cdb \
--out lflist.conf \
--cap 100
```
Arguments:
- `--html` : Path to the Genesys HTML file (default: `genesys.html`).
- `--cdb` : Path to the `cards.cdb` database (English version).
- `--out` : Output path for the generated `lflist.conf`.
- `--cap` : Genesys point cap (default = 100).
---
## Output
- A new `lflist.conf` will be written at the location specified by `--out`.
- Any unmatched cards (name mismatch) will be listed at the end of the file in comments:
```
# --- Unmatched cards (by name) ---
# [UNMATCHED] Example Card -> 50 (normalized: examplecard)
```
---
## Example
```
python3 main.py --html genesys.html --cdb cards.cdb --out lflist.conf
```
Output sample:
```
#[Genesys TCG]
# Genernated by genesys-gen
!Genesys TCG
$genesys 100
# Genesys points
12345678 $genesys 50 -- Example Card
# Disable Pendulum and Link monsters
34567890 0
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
main.py
Generate a Genesys-format lflist.conf for YGOPro/EDOPro using:
- An HTML file that contains a 2-column table (Card Name | Points)
- A cards.cdb (SQLite) database
Matching rule (UPDATED):
1) Try **absolute match** against `texts.name` (after unescaping HTML entities and stripping tags/whitespace).
2) If absolute match fails, fall back to **normalized match**:
- Remove all non-alphanumeric chars (A–Z, a–z, 0–9)
- Lowercase
- Strip diacritics (NFKD)
Other rules:
- Disable all Link and Pendulum monsters: SELECT id FROM datas WHERE (type & 0x5000000) > 0
- Output format:
#[Genesys TCG]
# Genernated by genesys-gen
!Genesys TCG
$genesys 100
# Genesys points
{{code}} $genesys {{point}} -- {{english_name}}
# Disable Pendulum and Link monsters
{{code}} 0
Usage:
python main.py --html /path/to/genesys.html --cdb /path/to/cards.cdb --out /path/to/lflist.conf --cap 100
"""
from __future__ import annotations
import argparse
import html as htmllib
import re
import sqlite3
import sys
import unicodedata
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Iterable
# Match pairs like:
# <td class="column-1">Card Name</td><td class="column-2">Points</td>
TD_PAIR_RE = re.compile(
r'<td[^>]*class="column-1"[^>]*>(.*?)</td>\s*<td[^>]*class="column-2"[^>]*>(.*?)</td>',
re.IGNORECASE | re.DOTALL,
)
TAG_RE = re.compile(r"<[^>]+>")
def strip_html_and_unescape(s: str) -> str:
"""Remove HTML tags, unescape &entities;, and strip outer whitespace."""
if s is None:
return ""
s = htmllib.unescape(s)
s = TAG_RE.sub("", s)
return s.strip()
def normalize_name(s: str) -> str:
"""Remove all non-alphanumeric characters and lowercase, after stripping diacritics."""
if s is None:
return ""
s = unicodedata.normalize("NFKD", s)
s = "".join(ch for ch in s if not unicodedata.combining(ch))
# We assume s already has no HTML tags/entities at this point
s = re.sub(r"[^0-9A-Za-z]+", "", s)
return s.lower()
def parse_points_from_html(html_path: Path) -> List[Tuple[str, int]]:
"""
Return list of (english_name_html_cleaned, points) in the order they appear.
HTML is expected to have rows like:
<td class="column-1">Card Name</td><td class="column-2">Points</td>
"""
text = html_path.read_text(encoding="utf-8", errors="ignore")
pairs = []
for name_html, pts_html in TD_PAIR_RE.findall(text):
name = strip_html_and_unescape(name_html)
pts_str = strip_html_and_unescape(pts_html)
if not name:
continue
try:
pts = int(pts_str)
except ValueError:
# skip non-integer points rows
continue
pairs.append((name, pts))
return pairs
def build_name_maps(cdb_path: Path) -> Tuple[Dict[str, Tuple[int, str]], Dict[str, Tuple[int, str]]]:
"""
Build two mappings from the DB:
1) exact_map: exact_name (texts.name as-is) -> (id, original_name)
- Key is *exact* DB name (case-sensitive), trimmed of surrounding whitespace.
2) normalized_map: normalized_name -> (id, original_name)
- Remove non-alphanumerics, lowercase, strip diacritics.
If duplicates arise:
- Prefer the smallest id (stable tiebreak).
"""
con = sqlite3.connect(str(cdb_path))
con.row_factory = sqlite3.Row
cur = con.cursor()
try:
cur.execute("SELECT id, name FROM texts")
except sqlite3.DatabaseError as e:
con.close()
raise SystemExit(f"[ERROR] Failed to query 'texts' table from {cdb_path}: {e}")
exact_map: Dict[str, Tuple[int, str]] = {}
normalized_map: Dict[str, Tuple[int, str]] = {}
for row in cur.fetchall():
cid = int(row["id"])
nm = (row["name"] or "").strip()
if nm:
# exact map
if nm not in exact_map or cid < exact_map[nm][0]:
exact_map[nm] = (cid, nm)
# normalized map
norm = normalize_name(nm)
if norm and (norm not in normalized_map or cid < normalized_map[norm][0]):
normalized_map[norm] = (cid, nm)
con.close()
return exact_map, normalized_map
def query_link_pendulum_ids(cdb_path: Path) -> List[int]:
"""
Return list of ids for all Link or Pendulum monsters:
SELECT id FROM datas WHERE (type & 0x5000000) > 0
"""
con = sqlite3.connect(str(cdb_path))
con.row_factory = sqlite3.Row
cur = con.cursor()
try:
cur.execute("SELECT id FROM datas WHERE (type & 0x5000000) > 0")
except sqlite3.DatabaseError as e:
con.close()
raise SystemExit(f"[ERROR] Failed to query 'datas' table from {cdb_path}: {e}")
ids = [int(r["id"]) for r in cur.fetchall()]
con.close()
return ids
def build_lflist(
pairs: List[Tuple[str, int]],
exact_map: Dict[str, Tuple[int, str]],
normalized_map: Dict[str, Tuple[int, str]],
disabled_ids: Iterable[int],
cap: int = 100,
) -> str:
"""
Compose the lflist.conf content as requested, using the updated matching strategy.
"""
lines: List[str] = []
lines.append("#[Genesys TCG]")
lines.append("# Genernated by genesys-gen")
lines.append("!Genesys TCG")
lines.append("")
lines.append(f"$genesys {cap}")
lines.append("")
lines.append("# Genesys points")
used_codes = set()
unmatched: List[Tuple[str, int, str]] = [] # (original_html_name, pts, normalized_key)
for name, pts in pairs:
# 1) Absolute match first (exact DB name)
entry = exact_map.get(name)
if not entry:
# 2) Fallback to normalized match
norm = normalize_name(name)
entry = normalized_map.get(norm)
if not entry:
unmatched.append((name, pts, normalize_name(name)))
continue
code, db_name = entry
if code in used_codes:
continue
used_codes.add(code)
lines.append(f"{code} $genesys {pts} -- {db_name}")
lines.append("")
lines.append("# Disable Pendulum and Link monsters")
for code in sorted(set(int(x) for x in disabled_ids)):
lines.append(f"{code} 0")
if unmatched:
lines.append("")
lines.append("# --- Unmatched cards (by name) ---")
for name, pts, norm in unmatched:
lines.append(f"# [UNMATCHED] {name} -> {pts} (normalized: {norm})")
return "\n".join(lines) + "\n"
def main(argv: Optional[List[str]] = None) -> int:
ap = argparse.ArgumentParser(description="Generate Genesys lflist.conf from HTML + cards.cdb")
ap.add_argument("--html", type=Path, default="genesys.html", help="Path to genesys HTML (table with Card Name / Points)")
ap.add_argument("--cdb", type=Path, default="cards.cdb", help="Path to cards.cdb (SQLite)")
ap.add_argument("--out", type=Path, default="lflist.conf", help="Output lflist.conf path")
ap.add_argument("--cap", type=int, default=100, help="Genesys point cap (default: 100)")
args = ap.parse_args(argv)
if not args.html.exists():
print(f"[ERROR] HTML not found: {args.html}", file=sys.stderr)
return 2
if not args.cdb.exists():
print(f"[ERROR] cards.cdb not found: {args.cdb}", file=sys.stderr)
return 2
pairs = parse_points_from_html(args.html)
if not pairs:
print(f"[WARN] No (Card, Points) pairs parsed from HTML: {args.html}", file=sys.stderr)
exact_map, normalized_map = build_name_maps(args.cdb)
disabled_ids = query_link_pendulum_ids(args.cdb)
content = build_lflist(
pairs,
exact_map=exact_map,
normalized_map=normalized_map,
disabled_ids=disabled_ids,
cap=args.cap,
)
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(content, encoding="utf-8")
# Simple summary to stdout
unmatched_count = sum(1 for line in content.splitlines() if line.startswith("# [UNMATCHED]"))
print(f"[DONE] Wrote: {args.out}")
print(f" Parsed pairs: {len(pairs)}")
print(f" Disabled Link/Pendulum IDs: {len(set(disabled_ids))}")
print(f" Unmatched by name: {unmatched_count}")
return 0
if __name__ == "__main__":
sys.exit(main())
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment