Compare commits

...

3 Commits

23 changed files with 75 additions and 43 deletions

View File

@@ -1,14 +1,15 @@
# Umamusume Support Card Manager # Umamusume Support Card Manager
A tool for managing support cards and their effects in Umamusume (Granblue Fantasy Relink). A tool for managing support cards and their effects in Umamusume.
## Features ## Features
- Web scraping of support card data from GameTora - **Cards Management**: View and manage your support cards collection.
- Database storage of card information including effects at different levels - **Deck Builder**: Build and optimize decks with your owned cards.
- GUI application for viewing and managing support cards - **Effects Search**: Search for specific effects across your owned cards (e.g., "Friendship Bonus", "Skill Pt Bonus").
- Deck building functionality - **Web Scraping**: Integrated GameTora scraper to fetch the latest card data.
- Character art downloading - **Auto-Updater**: Automatically improved application updates.
- **Maintenance Scripts**: Suite of scripts for database repair and deep scraping.
## Project Structure ## Project Structure
@@ -23,8 +24,15 @@ A tool for managing support cards and their effects in Umamusume (Granblue Fanta
│ ├── db_init.py # Database initialization │ ├── db_init.py # Database initialization
│ └── db_queries.py # Database queries │ └── db_queries.py # Database queries
├── gui/ # GUI components ├── gui/ # GUI components
│ ├── main_window.py # Main application window
│ ├── card_view.py # Card list and details view
│ ├── deck_builder.py # Deck construction view
│ ├── effects_view.py # Effects search view
│ └── ...
├── updater/ # Update checking functionality ├── updater/ # Update checking functionality
├── database/ # Database files ├── maintenance_scripts/ # Database repair and utility scripts
├── config/ # Configuration files
├── database/ # Database files storage
├── images/ # Character art images ├── images/ # Character art images
├── build/ # Build artifacts ├── build/ # Build artifacts
└── dist/ # Distribution files └── dist/ # Distribution files
@@ -32,9 +40,9 @@ A tool for managing support cards and their effects in Umamusume (Granblue Fanta
## Installation ## Installation
1. Clone the repository 1. Clone the repository.
2. Install dependencies: `pip install -r requirements.txt` 2. Install dependencies: `pip install -r requirements.txt`.
3. Run the application: `python main.py` 3. Run the application: `python main.py`.
## Usage ## Usage
@@ -44,6 +52,7 @@ python main.py
``` ```
### Scraping Mode ### Scraping Mode
To manually run the scraper:
```bash ```bash
python main.py --scrape python main.py --scrape
``` ```
@@ -51,17 +60,17 @@ python main.py --scrape
## Development ## Development
### Code Structure ### Code Structure
- `main.py`: Entry point and argument parsing - `main.py`: Entry point and argument parsing.
- `scraper/gametora_scraper.py`: Web scraping logic - `gui/`: Contains all CustomTkinter-based UI components.
- `db/db_init.py`: Database schema initialization - `db/`: Handles SQLite database interactions.
- `gui/`: GUI components (MainWindow, views, etc.) - `scraper/`: Logic for fetching data from GameTora.
- `updater/update_checker.py`: Update checking functionality - `maintenance_scripts/`: Tools for fixing database inconsistencies or re-fetching data.
### Contributing ### Contributing
1. Fork the repository 1. Fork the repository.
2. Create a feature branch 2. Create a feature branch.
3. Make your changes 3. Make your changes.
4. Submit a pull request 4. Submit a pull request.
## License ## License
MIT MIT

Binary file not shown.

View File

@@ -50,8 +50,9 @@ class CardSlot(ctk.CTkFrame):
self.slot_label.place(x=4, y=4) self.slot_label.place(x=4, y=4)
# Image Area - Dominant # Image Area - Dominant
# Initial placeholder
self.image_label = ctk.CTkLabel(self, fg_color="transparent", text="📭", text_color=TEXT_MUTED, self.image_label = ctk.CTkLabel(self, fg_color="transparent", text="📭", text_color=TEXT_MUTED,
font=('Segoe UI', 32), width=120, height=120) font=('Segoe UI', 32), width=90, height=90)
self.image_label.grid(row=0, column=0, padx=5, pady=(5,0)) self.image_label.grid(row=0, column=0, padx=5, pady=(5,0))
# Mini Details Area (Below Image) # Mini Details Area (Below Image)
@@ -128,23 +129,45 @@ class CardSlot(ctk.CTkFrame):
def reset(self): def reset(self):
self.name_label.configure(text="Empty", text_color=TEXT_MUTED) self.name_label.configure(text="Empty", text_color=TEXT_MUTED)
self.image_label.configure(image=None, text="📭")
# Recreate label to avoid TclError with missing images
if hasattr(self, 'image_label') and self.image_label:
self.image_label.destroy()
self.image_label = ctk.CTkLabel(self, fg_color="transparent", text="📭", text_color=TEXT_MUTED,
font=('Segoe UI', 32), width=90, height=90)
self.image_label.grid(row=0, column=0, padx=5, pady=(5,0))
self.configure(border_color=BG_LIGHT) self.configure(border_color=BG_LIGHT)
self.image_ref = None self.image_ref = None
self.toggle_controls(False) self.toggle_controls(False)
def _load_image(self, path): def _load_image(self, path):
resolved_path = resolve_image_path(path) resolved_path = resolve_image_path(path)
# Prepare new image first
new_image = None
if resolved_path and os.path.exists(resolved_path): if resolved_path and os.path.exists(resolved_path):
try: try:
pil_img = Image.open(resolved_path) pil_img = Image.open(resolved_path)
pil_img.thumbnail((90, 90), Image.Resampling.LANCZOS) pil_img.thumbnail((90, 90), Image.Resampling.LANCZOS)
self.image_ref = ctk.CTkImage(light_image=pil_img, dark_image=pil_img, size=(90, 90)) new_image = ctk.CTkImage(light_image=pil_img, dark_image=pil_img, size=(90, 90))
self.image_label.configure(image=self.image_ref, text="") except Exception:
except Exception as e: pass
self.image_label.configure(image=None, text="⚠️")
# Recreate label
if hasattr(self, 'image_label') and self.image_label:
self.image_label.destroy()
if new_image:
self.image_ref = new_image # Keep ref
self.image_label = ctk.CTkLabel(self, fg_color="transparent", text="", image=new_image)
else: else:
self.image_label.configure(image=None, text="🖼️") self.image_ref = None
self.image_label = ctk.CTkLabel(self, fg_color="transparent", text="⚠️" if resolved_path else "🖼️",
text_color=TEXT_MUTED, font=('Segoe UI', 32), width=90, height=90)
self.image_label.grid(row=0, column=0, padx=5, pady=(5,0))
def _on_level_change(self, value): def _on_level_change(self, value):
# CTkComboBox calls command with value # CTkComboBox calls command with value
@@ -198,9 +221,15 @@ class DeckBuilderFrame(ctk.CTkFrame):
ctk.CTkCheckBox(filter_frame, text="Owned", variable=self.owned_only_var, ctk.CTkCheckBox(filter_frame, text="Owned", variable=self.owned_only_var,
command=self.filter_cards, checkbox_width=24, checkbox_height=24, font=FONT_SMALL).pack(side=tk.LEFT, padx=5) command=self.filter_cards, checkbox_width=24, checkbox_height=24, font=FONT_SMALL).pack(side=tk.LEFT, padx=5)
# Add Button (Packed first to stick to bottom)
add_btn = create_styled_button(left_panel, text=" Add to Deck",
command=self.add_selected_to_deck,
style_type='accent')
add_btn.pack(side=tk.BOTTOM, fill=tk.X, pady=10, padx=10)
# Card List Treeview # Card List Treeview
list_container = ctk.CTkFrame(left_panel, fg_color=BG_MEDIUM) list_container = ctk.CTkFrame(left_panel, fg_color=BG_MEDIUM)
list_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) list_container.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
self.card_tree = ttk.Treeview(list_container, columns=('name', 'rarity', 'type'), self.card_tree = ttk.Treeview(list_container, columns=('name', 'rarity', 'type'),
show='tree headings', style="DeckList.Treeview") show='tree headings', style="DeckList.Treeview")
@@ -223,12 +252,6 @@ class DeckBuilderFrame(ctk.CTkFrame):
# Double-click to add # Double-click to add
self.card_tree.bind('<Double-1>', lambda e: self.add_selected_to_deck()) self.card_tree.bind('<Double-1>', lambda e: self.add_selected_to_deck())
# Add Button
add_btn = create_styled_button(left_panel, text=" Add to Deck",
command=self.add_selected_to_deck,
style_type='accent')
add_btn.pack(fill=tk.X, pady=10, padx=10)
# === Right Panel: Deck & Stats === # === Right Panel: Deck & Stats ===
right_panel = ctk.CTkFrame(self, fg_color="transparent") right_panel = ctk.CTkFrame(self, fg_color="transparent")
right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, pady=10) right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, pady=10)

View File

@@ -1,7 +1,7 @@
import sqlite3 import sqlite3
import os import os
DB_PATH = os.path.join("database", "umamusume.db") DB_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "database", "umamusume.db")
def check_schema(): def check_schema():
if not os.path.exists(DB_PATH): if not os.path.exists(DB_PATH):

View File

@@ -2,7 +2,7 @@ import sqlite3
import os import os
def debug_db(): def debug_db():
db_path = "database/umamusume.db" db_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "database", "umamusume.db")
if not os.path.exists(db_path): if not os.path.exists(db_path):
print(f"Database not found at {db_path}") print(f"Database not found at {db_path}")
return return

View File

@@ -3,7 +3,7 @@ import sys
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
# Add parent dir to path # Add parent dir to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def debug_kitasan_scrape(): def debug_kitasan_scrape():
url = "https://gametora.com/umamusume/supports/30028-kitasan-black" url = "https://gametora.com/umamusume/supports/30028-kitasan-black"

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
# Ensure we can import from the project # Ensure we can import from the project
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scraper.gametora_scraper import scrape_support_card, sync_playwright from scraper.gametora_scraper import scrape_support_card, sync_playwright
from db.db_queries import DB_PATH, repair_orphaned_data, cleanup_orphaned_data from db.db_queries import DB_PATH, repair_orphaned_data, cleanup_orphaned_data

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
# Add parent dir to path # Add parent dir to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scraper.gametora_scraper import scrape_support_card, sync_playwright from scraper.gametora_scraper import scrape_support_card, sync_playwright

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
# Add parent dir to path # Add parent dir to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scraper.gametora_scraper import scrape_support_card, sync_playwright from scraper.gametora_scraper import scrape_support_card, sync_playwright

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
# Add parent dir to path # Add parent dir to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scraper.gametora_scraper import scrape_support_card, sync_playwright from scraper.gametora_scraper import scrape_support_card, sync_playwright
from db.db_queries import get_conn from db.db_queries import get_conn

View File

@@ -5,7 +5,7 @@ from playwright.sync_api import sync_playwright
import sys import sys
# Add project root to path # Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def test_scrape_events(url): def test_scrape_events(url):
with sync_playwright() as p: with sync_playwright() as p:

View File

@@ -4,7 +4,7 @@ This file is the single source of truth for the application version.
""" """
# Semantic versioning: MAJOR.MINOR.PATCH # Semantic versioning: MAJOR.MINOR.PATCH
VERSION: str = "14.0.0" VERSION: str = "14.0.1"
# Application metadata # Application metadata
APP_NAME: str = "UmamusumeCardManager" APP_NAME: str = "UmamusumeCardManager"