Compare commits
3 Commits
3a50c93466
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d9a3da4cc | ||
|
|
40047ee145 | ||
|
|
9cbfb29477 |
47
README.md
47
README.md
@@ -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.
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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:
|
||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user