Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
G
genesys-gen
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
genesys-gen
Commits
24094c39
Commit
24094c39
authored
Sep 27, 2025
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
first
parents
Pipeline
#40748
passed with stages
in 2 minutes and 29 seconds
Changes
4
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
418 additions
and
0 deletions
+418
-0
.gitignore
.gitignore
+8
-0
.gitlab-ci.yml
.gitlab-ci.yml
+54
-0
README.md
README.md
+109
-0
main.py
main.py
+247
-0
No files found.
.gitignore
0 → 100644
View file @
24094c39
*.pyc
__pycache__
.idea
.vscode
*.cdb
*.html
lflist.conf
.gitlab-ci.yml
0 → 100644
View file @
24094c39
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
README.md
0 → 100644
View file @
24094c39
# 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
```
main.py
0 → 100644
View file @
24094c39
#!/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
())
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment