@ -0,0 +1,3 @@ |
|||
pokemon_cache.db |
|||
pokemon_database.db |
|||
Utilities/venv/ |
|||
@ -0,0 +1,30 @@ |
|||
import csv |
|||
from flask import Flask, render_template |
|||
|
|||
app = Flask(__name__) |
|||
|
|||
def load_pokemon_data(): |
|||
pokemon_list = [] |
|||
earliest_games = {} |
|||
|
|||
# Load Pokemon Home list |
|||
with open('pokemon_home_list.csv', 'r') as file: |
|||
reader = csv.DictReader(file) |
|||
for row in reader: |
|||
pokemon_list.append(row) |
|||
|
|||
# Load earliest games data |
|||
with open('pokemon_earliest_games.csv', 'r') as file: |
|||
reader = csv.DictReader(file) |
|||
for row in reader: |
|||
earliest_games[row['Pokemon']] = row['Earliest Game'] |
|||
|
|||
return pokemon_list, earliest_games |
|||
|
|||
@app.route('/') |
|||
def index(): |
|||
pokemon_list, earliest_games = load_pokemon_data() |
|||
return render_template('index.html', pokemon_list=pokemon_list, earliest_games=earliest_games) |
|||
|
|||
if __name__ == '__main__': |
|||
app.run(debug=True) |
|||
@ -0,0 +1,585 @@ |
|||
import sys |
|||
import sqlite3 |
|||
import json |
|||
import os |
|||
from datetime import datetime |
|||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QListWidget, QLabel, |
|||
QVBoxLayout, QHBoxLayout, QWidget, QPushButton, |
|||
QTableWidget, QTableWidgetItem, QHeaderView, |
|||
QLineEdit, QCheckBox, QFormLayout, QMessageBox, |
|||
QDialog, QComboBox, QFileDialog, QTabWidget) |
|||
from PyQt6.QtGui import QPixmap |
|||
from PyQt6.QtCore import Qt |
|||
|
|||
class PokemonDatabaseApp(QMainWindow): |
|||
def __init__(self): |
|||
super().__init__() |
|||
self.setWindowTitle("Pokémon Database Viewer") |
|||
self.setGeometry(100, 100, 1200, 600) |
|||
|
|||
# Create an in-memory database |
|||
self.conn = sqlite3.connect(':memory:') |
|||
self.cursor = self.conn.cursor() |
|||
|
|||
# Load the database from disk into memory |
|||
self.load_database() |
|||
|
|||
# Apply all existing patches |
|||
self.apply_all_patches() |
|||
|
|||
self.current_pokemon_id = None |
|||
self.current_pokemon_name = None |
|||
self.current_form_id = None |
|||
self.all_pokemon = [] |
|||
self.patches = {} |
|||
self.init_ui() |
|||
self.load_locations_list() |
|||
|
|||
def init_ui(self): |
|||
main_layout = QVBoxLayout() |
|||
|
|||
# Create tab widget |
|||
self.tab_widget = QTabWidget() |
|||
|
|||
# Create and add Pokemon tab |
|||
pokemon_tab = self.create_pokemon_tab() |
|||
self.tab_widget.addTab(pokemon_tab, "Pokémon") |
|||
|
|||
# Create and add Locations tab |
|||
locations_tab = self.create_locations_tab() |
|||
self.tab_widget.addTab(locations_tab, "Locations") |
|||
|
|||
main_layout.addWidget(self.tab_widget) |
|||
|
|||
container = QWidget() |
|||
container.setLayout(main_layout) |
|||
self.setCentralWidget(container) |
|||
|
|||
self.load_pokemon_list() |
|||
|
|||
def create_pokemon_tab(self): |
|||
pokemon_tab = QWidget() |
|||
tab_layout = QHBoxLayout() |
|||
|
|||
# Pokémon list section |
|||
pokemon_list_layout = QVBoxLayout() |
|||
|
|||
# Search box |
|||
self.search_box = QLineEdit() |
|||
self.search_box.setPlaceholderText("Search Pokémon...") |
|||
self.search_box.textChanged.connect(self.filter_pokemon_list) |
|||
pokemon_list_layout.addWidget(self.search_box) |
|||
|
|||
# Pokémon list |
|||
self.pokemon_list = QListWidget() |
|||
self.pokemon_list.itemClicked.connect(self.load_pokemon_data) |
|||
pokemon_list_layout.addWidget(self.pokemon_list) |
|||
|
|||
tab_layout.addLayout(pokemon_list_layout, 1) |
|||
|
|||
# Pokémon details |
|||
details_layout = QVBoxLayout() |
|||
|
|||
image_and_form_layout = QHBoxLayout() |
|||
|
|||
# Image |
|||
self.image_label = QLabel() |
|||
self.image_label.setFixedSize(200, 200) |
|||
self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
|||
image_and_form_layout.addWidget(self.image_label) |
|||
|
|||
# Form details |
|||
form_details_layout = QFormLayout() |
|||
self.form_name_edit = QLineEdit() |
|||
self.is_default_checkbox = QCheckBox() |
|||
self.image_path_edit = QLineEdit() |
|||
form_details_layout.addRow("Form Name:", self.form_name_edit) |
|||
form_details_layout.addRow("Default Form:", self.is_default_checkbox) |
|||
form_details_layout.addRow("Image Path:", self.image_path_edit) |
|||
|
|||
# Save button |
|||
self.save_button = QPushButton("Save Form Changes") |
|||
self.save_button.clicked.connect(self.save_form_changes) |
|||
form_details_layout.addRow(self.save_button) |
|||
|
|||
image_and_form_layout.addLayout(form_details_layout) |
|||
details_layout.addLayout(image_and_form_layout) |
|||
|
|||
# Forms list and add new form button |
|||
forms_layout = QHBoxLayout() |
|||
self.forms_list = QListWidget() |
|||
self.forms_list.itemClicked.connect(self.load_form_data) |
|||
forms_layout.addWidget(self.forms_list) |
|||
|
|||
add_form_button = QPushButton("Add New Form") |
|||
add_form_button.clicked.connect(self.add_new_form) |
|||
details_layout.addWidget(add_form_button) |
|||
|
|||
details_layout.addWidget(QLabel("Forms:")) |
|||
details_layout.addLayout(forms_layout) |
|||
|
|||
# Encounters table and add new encounter button |
|||
self.encounters_table = QTableWidget() |
|||
self.encounters_table.setColumnCount(3) |
|||
self.encounters_table.setHorizontalHeaderLabels(['Game', 'Location', 'Method']) |
|||
self.encounters_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) |
|||
details_layout.addWidget(QLabel("Encounters:")) |
|||
details_layout.addWidget(self.encounters_table) |
|||
|
|||
add_encounter_button = QPushButton("Add New Encounter") |
|||
add_encounter_button.clicked.connect(self.add_new_encounter) |
|||
details_layout.addWidget(add_encounter_button) |
|||
|
|||
tab_layout.addLayout(details_layout, 2) |
|||
|
|||
pokemon_tab.setLayout(tab_layout) |
|||
return pokemon_tab |
|||
|
|||
def create_locations_tab(self): |
|||
locations_tab = QWidget() |
|||
tab_layout = QHBoxLayout() |
|||
|
|||
# Locations list |
|||
locations_list_layout = QVBoxLayout() |
|||
|
|||
# Search box for locations |
|||
self.locations_search_box = QLineEdit() |
|||
self.locations_search_box.setPlaceholderText("Search Locations...") |
|||
self.locations_search_box.textChanged.connect(self.filter_locations_list) |
|||
locations_list_layout.addWidget(self.locations_search_box) |
|||
|
|||
# Locations list |
|||
self.locations_list = QListWidget() |
|||
self.locations_list.itemClicked.connect(self.load_location_data) |
|||
locations_list_layout.addWidget(self.locations_list) |
|||
|
|||
# Add new location button |
|||
add_location_button = QPushButton("Add New Location") |
|||
add_location_button.clicked.connect(self.add_new_location) |
|||
locations_list_layout.addWidget(add_location_button) |
|||
|
|||
tab_layout.addLayout(locations_list_layout, 1) |
|||
|
|||
# Location details |
|||
location_details_layout = QFormLayout() |
|||
|
|||
self.location_name_edit = QLineEdit() |
|||
self.location_description_edit = QLineEdit() |
|||
|
|||
location_details_layout.addRow("Name:", self.location_name_edit) |
|||
location_details_layout.addRow("description:", self.location_description_edit) |
|||
|
|||
# Save location changes button |
|||
save_location_button = QPushButton("Save Location Changes") |
|||
save_location_button.clicked.connect(self.save_location_changes) |
|||
location_details_layout.addRow(save_location_button) |
|||
|
|||
tab_layout.addLayout(location_details_layout, 2) |
|||
|
|||
locations_tab.setLayout(tab_layout) |
|||
return locations_tab |
|||
|
|||
def load_database(self): |
|||
disk_conn = sqlite3.connect('pokemon_database.db') |
|||
disk_conn.backup(self.conn) |
|||
disk_conn.close() |
|||
|
|||
def apply_all_patches(self): |
|||
patches_dir = "patches" |
|||
if not os.path.exists(patches_dir): |
|||
return |
|||
|
|||
patch_files = sorted([f for f in os.listdir(patches_dir) if f.endswith('.json')]) |
|||
for patch_file in patch_files: |
|||
with open(os.path.join(patches_dir, patch_file), 'r') as f: |
|||
patches = json.load(f) |
|||
self.apply_patches(patches) |
|||
|
|||
def apply_patches(self, patches): |
|||
try: |
|||
for timestamp, patch in patches.items(): |
|||
if patch["type"] == "form_update": |
|||
self.cursor.execute(""" |
|||
UPDATE pokemon_forms |
|||
SET form_name = ?, is_default = ?, image_path = ? |
|||
WHERE id = ? |
|||
""", (patch["form_name"], patch["is_default"], patch["image_path"], patch["form_id"])) |
|||
elif patch["type"] == "new_form": |
|||
self.cursor.execute(""" |
|||
INSERT INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|||
VALUES (?, ?, ?, ?) |
|||
""", (patch["pokemon_id"], patch["form_name"], patch["is_default"], patch["image_path"])) |
|||
elif patch["type"] == "new_encounter": |
|||
self.cursor.execute(""" |
|||
INSERT INTO form_encounters (form_id, game_id, location_id, encounter_method_id) |
|||
VALUES (?, |
|||
(SELECT id FROM games WHERE name = ?), |
|||
(SELECT id FROM locations WHERE name = ?), |
|||
(SELECT id FROM encounter_methods WHERE name = ?)) |
|||
""", (patch["form_id"], patch["game"], patch["location"], patch["method"])) |
|||
elif patch["type"] == "location_update": |
|||
self.cursor.execute(""" |
|||
UPDATE locations |
|||
SET name = ?, description = ? |
|||
WHERE name = ? |
|||
""", (patch["new_name"], patch["description"], patch["old_name"])) |
|||
elif patch["type"] == "new_location": |
|||
self.cursor.execute(""" |
|||
INSERT INTO locations (name, description) |
|||
VALUES (?, ?) |
|||
""", (patch["name"], patch["description"])) |
|||
|
|||
self.conn.commit() |
|||
except sqlite3.Error as e: |
|||
print(f"An error occurred while applying patches: {e}") |
|||
|
|||
def load_pokemon_list(self): |
|||
self.cursor.execute("SELECT national_dex_number, name FROM pokemon ORDER BY national_dex_number") |
|||
self.all_pokemon = [f"{row[0]:03d} - {row[1]}" for row in self.cursor.fetchall()] |
|||
self.pokemon_list.addItems(self.all_pokemon) |
|||
|
|||
def filter_pokemon_list(self): |
|||
search_text = self.search_box.text().lower() |
|||
self.pokemon_list.clear() |
|||
for pokemon in self.all_pokemon: |
|||
if search_text in pokemon.lower(): |
|||
self.pokemon_list.addItem(pokemon) |
|||
|
|||
def load_pokemon_data(self, item): |
|||
self.current_pokemon_id = int(item.text().split('-')[0]) |
|||
self.current_pokemon_name = item.text().split('-')[1] |
|||
# Load forms |
|||
self.forms_list.clear() |
|||
self.cursor.execute(""" |
|||
SELECT form_name FROM pokemon_forms |
|||
WHERE pokemon_id = ? |
|||
ORDER BY is_default DESC, form_name |
|||
""", (self.current_pokemon_id,)) |
|||
for row in self.cursor.fetchall(): |
|||
self.forms_list.addItem(row[0]) |
|||
|
|||
# Load default form data |
|||
self.forms_list.setCurrentRow(0) |
|||
self.load_form_data(self.forms_list.item(0)) |
|||
|
|||
def load_form_data(self, item): |
|||
if not item: |
|||
return |
|||
|
|||
form_name = item.text() |
|||
|
|||
# Load form data |
|||
self.cursor.execute(""" |
|||
SELECT id, form_name, is_default, image_path FROM pokemon_forms |
|||
WHERE pokemon_id = ? AND form_name = ? |
|||
""", (self.current_pokemon_id, form_name)) |
|||
form_data = self.cursor.fetchone() |
|||
if form_data: |
|||
self.current_form_id, form_name, is_default, image_path = form_data |
|||
|
|||
# Update form details |
|||
self.form_name_edit.setText(form_name) |
|||
self.is_default_checkbox.setChecked(bool(is_default)) |
|||
self.image_path_edit.setText(image_path) |
|||
|
|||
# Load image |
|||
pixmap = QPixmap(image_path) |
|||
self.image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio)) |
|||
|
|||
# Load encounters |
|||
self.encounters_table.setRowCount(0) |
|||
self.cursor.execute(""" |
|||
SELECT g.name, l.name, em.name |
|||
FROM form_encounters fe |
|||
JOIN games g ON fe.game_id = g.id |
|||
JOIN locations l ON fe.location_id = l.id |
|||
JOIN encounter_methods em ON fe.encounter_method_id = em.id |
|||
WHERE fe.form_id = ? |
|||
ORDER BY g.name, l.name |
|||
""", (self.current_form_id,)) |
|||
for row in self.cursor.fetchall(): |
|||
current_row = self.encounters_table.rowCount() |
|||
self.encounters_table.insertRow(current_row) |
|||
for col, value in enumerate(row): |
|||
self.encounters_table.setItem(current_row, col, QTableWidgetItem(str(value))) |
|||
|
|||
def save_form_changes(self): |
|||
if not self.current_form_id: |
|||
return |
|||
|
|||
new_form_name = self.form_name_edit.text() |
|||
new_is_default = self.is_default_checkbox.isChecked() |
|||
new_image_path = self.image_path_edit.text() |
|||
|
|||
# Add changes to patches |
|||
patch = { |
|||
"type": "form_update", |
|||
"form_id": self.current_form_id, |
|||
"form_name": new_form_name, |
|||
"is_default": new_is_default, |
|||
"image_path": new_image_path |
|||
} |
|||
self.add_to_patches(patch) |
|||
|
|||
try: |
|||
self.cursor.execute(""" |
|||
UPDATE pokemon_forms |
|||
SET form_name = ?, is_default = ?, image_path = ? |
|||
WHERE id = ? |
|||
""", (new_form_name, new_is_default, new_image_path, self.current_form_id)) |
|||
self.conn.commit() |
|||
QMessageBox.information(self, "Success", "Form data updated successfully!") |
|||
|
|||
# Refresh the forms list and current form data |
|||
self.load_pokemon_data(self.pokemon_list.currentItem()) |
|||
except sqlite3.Error as e: |
|||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|||
|
|||
def add_new_form(self): |
|||
if not self.current_pokemon_id: |
|||
QMessageBox.warning(self, "Error", "Please select a Pokémon first.") |
|||
return |
|||
|
|||
dialog = QDialog(self) |
|||
dialog.setWindowTitle("Add New Form") |
|||
layout = QFormLayout(dialog) |
|||
|
|||
form_name_edit = QLineEdit() |
|||
layout.addRow("Form Name:", form_name_edit) |
|||
|
|||
buttons = QHBoxLayout() |
|||
save_button = QPushButton("Save") |
|||
save_button.clicked.connect(dialog.accept) |
|||
cancel_button = QPushButton("Cancel") |
|||
cancel_button.clicked.connect(dialog.reject) |
|||
buttons.addWidget(save_button) |
|||
buttons.addWidget(cancel_button) |
|||
layout.addRow(buttons) |
|||
|
|||
if dialog.exec() == QDialog.DialogCode.Accepted: |
|||
form_name = form_name_edit.text() |
|||
|
|||
try: |
|||
self.cursor.execute(""" |
|||
INSERT INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|||
VALUES (?, ?, ?, ?) |
|||
""", (self.current_pokemon_id, form_name, False, f"images/pokemon/{self.current_pokemon_id:04d}_{self.current_pokemon_name}_({form_name}).png".replace(" ", "_"))) |
|||
self.conn.commit() |
|||
new_form_id = self.cursor.lastrowid |
|||
|
|||
# Add new form to patches |
|||
patch = { |
|||
"type": "new_form", |
|||
"form_id": new_form_id, |
|||
"pokemon_id": self.current_pokemon_id, |
|||
"form_name": form_name, |
|||
"is_default": False, |
|||
"image_path": f"images/pokemon/{self.current_pokemon_id:04d}_{self.current_pokemon_name}_({form_name}).png".replace(" ", "_") |
|||
} |
|||
self.add_to_patches(patch) |
|||
|
|||
QMessageBox.information(self, "Success", "New form added successfully!") |
|||
self.load_pokemon_data(self.pokemon_list.currentItem()) |
|||
except sqlite3.Error as e: |
|||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|||
|
|||
def add_new_encounter(self): |
|||
if not self.current_form_id: |
|||
QMessageBox.warning(self, "Error", "Please select a form first.") |
|||
return |
|||
|
|||
dialog = QDialog(self) |
|||
dialog.setWindowTitle("Add New Encounter") |
|||
layout = QFormLayout(dialog) |
|||
|
|||
game_combo = QComboBox() |
|||
self.cursor.execute("SELECT name FROM games ORDER BY name") |
|||
games = [row[0] for row in self.cursor.fetchall()] |
|||
game_combo.addItems(games) |
|||
layout.addRow("Game:", game_combo) |
|||
|
|||
location_combo = QComboBox() |
|||
self.cursor.execute("SELECT name FROM locations ORDER BY name") |
|||
locations = [row[0] for row in self.cursor.fetchall()] |
|||
location_combo.addItems(locations) |
|||
layout.addRow("Location:", location_combo) |
|||
|
|||
method_combo = QComboBox() |
|||
self.cursor.execute("SELECT name FROM encounter_methods ORDER BY name") |
|||
methods = [row[0] for row in self.cursor.fetchall()] |
|||
method_combo.addItems(methods) |
|||
layout.addRow("Method:", method_combo) |
|||
|
|||
buttons = QHBoxLayout() |
|||
save_button = QPushButton("Save") |
|||
save_button.clicked.connect(dialog.accept) |
|||
cancel_button = QPushButton("Cancel") |
|||
cancel_button.clicked.connect(dialog.reject) |
|||
buttons.addWidget(save_button) |
|||
buttons.addWidget(cancel_button) |
|||
layout.addRow(buttons) |
|||
|
|||
if dialog.exec() == QDialog.DialogCode.Accepted: |
|||
game = game_combo.currentText() |
|||
location = location_combo.currentText() |
|||
method = method_combo.currentText() |
|||
|
|||
try: |
|||
self.cursor.execute(""" |
|||
INSERT INTO form_encounters (form_id, game_id, location_id, encounter_method_id) |
|||
VALUES (?, |
|||
(SELECT id FROM games WHERE name = ?), |
|||
(SELECT id FROM locations WHERE name = ?), |
|||
(SELECT id FROM encounter_methods WHERE name = ?)) |
|||
""", (self.current_form_id, game, location, method)) |
|||
self.conn.commit() |
|||
new_encounter_id = self.cursor.lastrowid |
|||
|
|||
# Add new encounter to patches |
|||
patch = { |
|||
"type": "new_encounter", |
|||
"encounter_id": new_encounter_id, |
|||
"form_id": self.current_form_id, |
|||
"game": game, |
|||
"location": location, |
|||
"method": method |
|||
} |
|||
self.add_to_patches(patch) |
|||
|
|||
QMessageBox.information(self, "Success", "New encounter added successfully!") |
|||
self.load_form_data(self.forms_list.currentItem()) |
|||
except sqlite3.Error as e: |
|||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|||
|
|||
def add_to_patches(self, patch): |
|||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|||
self.patches[f"{timestamp}_{len(self.patches)}"] = patch |
|||
self.auto_save_patches() |
|||
|
|||
def auto_save_patches(self): |
|||
patches_dir = "patches" |
|||
if not os.path.exists(patches_dir): |
|||
os.makedirs(patches_dir) |
|||
|
|||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|||
file_name = f"{patches_dir}/patch_{timestamp}.json" |
|||
|
|||
with open(file_name, 'w') as f: |
|||
json.dump(self.patches, f, indent=2) |
|||
|
|||
print(f"Patches auto-saved to {file_name}") |
|||
|
|||
def closeEvent(self, event): |
|||
reply = QMessageBox.question(self, 'Save Changes', |
|||
"Do you want to save changes to the disk database?", |
|||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, |
|||
QMessageBox.StandardButton.No) |
|||
|
|||
if reply == QMessageBox.StandardButton.Yes: |
|||
# Save changes to disk |
|||
disk_conn = sqlite3.connect('pokemon_database.db') |
|||
self.conn.backup(disk_conn) |
|||
disk_conn.close() |
|||
QMessageBox.information(self, "Success", "Changes saved to disk database.") |
|||
|
|||
self.conn.close() |
|||
event.accept() |
|||
|
|||
def filter_locations_list(self): |
|||
search_text = self.locations_search_box.text().lower() |
|||
for i in range(self.locations_list.count()): |
|||
item = self.locations_list.item(i) |
|||
if search_text in item.text().lower(): |
|||
item.setHidden(False) |
|||
else: |
|||
item.setHidden(True) |
|||
|
|||
def load_location_data(self, item): |
|||
location_name = item.text() |
|||
self.cursor.execute("SELECT name, description FROM locations WHERE name = ?", (location_name,)) |
|||
location_data = self.cursor.fetchone() |
|||
if location_data: |
|||
self.location_name_edit.setText(location_data[0]) |
|||
self.location_description_edit.setText(location_data[1]) |
|||
|
|||
def save_location_changes(self): |
|||
old_name = self.locations_list.currentItem().text() |
|||
new_name = self.location_name_edit.text() |
|||
new_description = self.location_description_edit.text() |
|||
|
|||
try: |
|||
self.cursor.execute(""" |
|||
UPDATE locations |
|||
SET name = ?, description = ? |
|||
WHERE name = ? |
|||
""", (new_name, new_description, old_name)) |
|||
self.conn.commit() |
|||
|
|||
# Add changes to patches |
|||
patch = { |
|||
"type": "location_update", |
|||
"old_name": old_name, |
|||
"new_name": new_name, |
|||
"description": new_description |
|||
} |
|||
self.add_to_patches(patch) |
|||
|
|||
QMessageBox.information(self, "Success", "Location data updated successfully!") |
|||
self.load_locations_list() |
|||
except sqlite3.Error as e: |
|||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|||
|
|||
def add_new_location(self): |
|||
dialog = QDialog(self) |
|||
dialog.setWindowTitle("Add New Location") |
|||
layout = QFormLayout(dialog) |
|||
|
|||
name_edit = QLineEdit() |
|||
description_edit = QLineEdit() |
|||
layout.addRow("Name:", name_edit) |
|||
layout.addRow("description:", description_edit) |
|||
|
|||
buttons = QHBoxLayout() |
|||
save_button = QPushButton("Save") |
|||
save_button.clicked.connect(dialog.accept) |
|||
cancel_button = QPushButton("Cancel") |
|||
cancel_button.clicked.connect(dialog.reject) |
|||
buttons.addWidget(save_button) |
|||
buttons.addWidget(cancel_button) |
|||
layout.addRow(buttons) |
|||
|
|||
if dialog.exec() == QDialog.DialogCode.Accepted: |
|||
name = name_edit.text() |
|||
description = description_edit.text() |
|||
|
|||
try: |
|||
self.cursor.execute(""" |
|||
INSERT INTO locations (name, description) |
|||
VALUES (?, ?) |
|||
""", (name, description)) |
|||
self.conn.commit() |
|||
|
|||
# Add new location to patches |
|||
patch = { |
|||
"type": "new_location", |
|||
"name": name, |
|||
"description": description |
|||
} |
|||
self.add_to_patches(patch) |
|||
|
|||
QMessageBox.information(self, "Success", "New location added successfully!") |
|||
self.load_locations_list() |
|||
except sqlite3.Error as e: |
|||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|||
|
|||
def load_locations_list(self): |
|||
self.locations_list.clear() |
|||
self.cursor.execute("SELECT name FROM locations ORDER BY name") |
|||
locations = [row[0] for row in self.cursor.fetchall()] |
|||
self.locations_list.addItems(locations) |
|||
|
|||
if __name__ == '__main__': |
|||
app = QApplication(sys.argv) |
|||
window = PokemonDatabaseApp() |
|||
window.show() |
|||
sys.exit(app.exec()) |
|||
@ -0,0 +1,354 @@ |
|||
import sqlite3 |
|||
import csv |
|||
import re |
|||
|
|||
def create_connection(): |
|||
conn = sqlite3.connect('pokemon_database.db') |
|||
return conn |
|||
|
|||
def create_tables(conn): |
|||
cursor = conn.cursor() |
|||
|
|||
# Create games table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS games ( |
|||
id INTEGER PRIMARY KEY, |
|||
name TEXT NOT NULL, |
|||
generation INTEGER NOT NULL |
|||
) |
|||
''') |
|||
|
|||
# Create marks table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS marks ( |
|||
id INTEGER PRIMARY KEY, |
|||
name TEXT NOT NULL, |
|||
icon_path TEXT NOT NULL |
|||
) |
|||
''') |
|||
|
|||
# Create mark_game_associations table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS mark_game_associations ( |
|||
mark_id INTEGER, |
|||
game_id INTEGER, |
|||
FOREIGN KEY (mark_id) REFERENCES marks (id), |
|||
FOREIGN KEY (game_id) REFERENCES games (id), |
|||
PRIMARY KEY (mark_id, game_id) |
|||
) |
|||
''') |
|||
|
|||
# Create pokemon table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS pokemon ( |
|||
national_dex_number INTEGER PRIMARY KEY, |
|||
name TEXT NOT NULL |
|||
) |
|||
''') |
|||
|
|||
# Create pokemon_forms table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS pokemon_forms ( |
|||
id INTEGER PRIMARY KEY, |
|||
pokemon_id INTEGER NOT NULL, |
|||
form_name TEXT NOT NULL, |
|||
is_default BOOLEAN NOT NULL, |
|||
image_path TEXT NOT NULL, |
|||
FOREIGN KEY (pokemon_id) REFERENCES pokemon (national_dex_number), |
|||
UNIQUE (pokemon_id, form_name) |
|||
) |
|||
''') |
|||
|
|||
# Create encounter_methods table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS encounter_methods ( |
|||
id INTEGER PRIMARY KEY, |
|||
name TEXT UNIQUE NOT NULL |
|||
) |
|||
''') |
|||
|
|||
# Create locations table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS locations ( |
|||
id INTEGER PRIMARY KEY, |
|||
name TEXT UNIQUE NOT NULL, |
|||
description TEXT |
|||
) |
|||
''') |
|||
|
|||
# Create form_encounters table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS form_encounters ( |
|||
id INTEGER PRIMARY KEY, |
|||
form_id INTEGER NOT NULL, |
|||
game_id INTEGER NOT NULL, |
|||
location_id INTEGER NOT NULL, |
|||
encounter_method_id INTEGER NOT NULL, |
|||
FOREIGN KEY (form_id) REFERENCES pokemon_forms (id), |
|||
FOREIGN KEY (game_id) REFERENCES games (id), |
|||
FOREIGN KEY (location_id) REFERENCES locations (id), |
|||
FOREIGN KEY (encounter_method_id) REFERENCES encounter_methods (id), |
|||
UNIQUE (form_id, game_id, location_id, encounter_method_id) |
|||
) |
|||
''') |
|||
|
|||
# Create alternate_game_names table |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS alternate_game_names ( |
|||
id INTEGER PRIMARY KEY, |
|||
game_id INTEGER NOT NULL, |
|||
alternate_name TEXT NOT NULL, |
|||
FOREIGN KEY (game_id) REFERENCES games (id), |
|||
UNIQUE (alternate_name COLLATE NOCASE) |
|||
) |
|||
''') |
|||
|
|||
conn.commit() |
|||
|
|||
def load_game_data(conn): |
|||
cursor = conn.cursor() |
|||
|
|||
games = [ |
|||
("Red", 1, ["Red Version"]), |
|||
("Blue", 1, ["Blue Version"]), |
|||
("Yellow", 1, ["Yellow Version"]), |
|||
("Gold", 2, ["Gold Version"]), |
|||
("Silver", 2, ["Silver Version"]), |
|||
("Crystal", 2, ["Crystal Version"]), |
|||
("Ruby", 3, ["Ruby Version"]), |
|||
("Sapphire", 3, ["Sapphire Version"]), |
|||
("Emerald", 3, ["Emerald Version"]), |
|||
("FireRed", 3, ["Fire Red", "Fire-Red"]), |
|||
("LeafGreen", 3, ["Leaf Green", "Leaf-Green"]), |
|||
("Diamond", 4, ["Diamond Version"]), |
|||
("Pearl", 4, ["Pearl Version"]), |
|||
("Platinum", 4, ["Platinum Version"]), |
|||
("HeartGold", 4, ["Heart Gold", "Heart-Gold"]), |
|||
("SoulSilver", 4, ["Soul Silver", "Soul-Silver"]), |
|||
("Black", 5, ["Black Version"]), |
|||
("White", 5, ["White Version"]), |
|||
("Black 2", 5, ["Black Version 2", "Black-2"]), |
|||
("White 2", 5, ["White Version 2", "White-2"]), |
|||
("X", 6, ["X Version"]), |
|||
("Y", 6, ["Y Version"]), |
|||
("Omega Ruby", 6, ["Omega Ruby Version", "Omega-Ruby"]), |
|||
("Alpha Sapphire", 6, ["Alpha Sapphire Version", "Alpha-Sapphire"]), |
|||
("Sun", 7, ["Sun Version"]), |
|||
("Moon", 7, ["Moon Version"]), |
|||
("Ultra Sun", 7, ["Ultra Sun Version", "Ultra-Sun"]), |
|||
("Ultra Moon", 7, ["Ultra Moon Version", "Ultra-Moon"]), |
|||
("Let's Go Pikachu", 7, ["Let's Go, Pikachu!", "Lets Go Pikachu"]), |
|||
("Let's Go Eevee", 7, ["Let's Go, Eevee!", "Lets Go Eevee"]), |
|||
("Sword", 8, ["Sword Version"]), |
|||
("Shield", 8, ["Shield Version"]), |
|||
("Brilliant Diamond", 8, ["Brilliant Diamond Version", "Brilliant-Diamond"]), |
|||
("Shining Pearl", 8, ["Shining Pearl Version", "Shining-Pearl"]), |
|||
("Legends Arceus", 8, ["Legends: Arceus", "Legends-Arceus"]), |
|||
("Scarlet", 9, ["Scarlet Version"]), |
|||
("Violet", 9, ["Violet Version"]), |
|||
] |
|||
|
|||
for game in games: |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO games (name, generation) |
|||
VALUES (?, ?) |
|||
''', (game[0], game[1])) |
|||
|
|||
game_id = cursor.lastrowid |
|||
|
|||
# Insert alternate names |
|||
for alt_name in game[2]: |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO alternate_game_names (game_id, alternate_name) |
|||
VALUES (?, ?) |
|||
''', (game_id, alt_name)) |
|||
|
|||
conn.commit() |
|||
|
|||
def load_mark_data(conn): |
|||
cursor = conn.cursor() |
|||
|
|||
marks = [ |
|||
("Game Boy", "images/marks/GB_icon_HOME.png", ["Red", "Blue", "Yellow", "Gold", "Silver", "Crystal", "Ruby", "Sapphire", "Emerald", "FireRed", "LeafGreen"]), |
|||
("Kalos", "images/marks/Blue_pentagon_HOME.png", ["X", "Y", "Omega Ruby", "Alpha Sapphire"]), |
|||
("Alola", "images/marks/Black_clover_HOME.png", ["Sun", "Moon", "Ultra Sun", "Ultra Moon"]), |
|||
("Let's Go", "images/marks/Let's_Go_icon_HOME.png", ["Let's Go Pikachu", "Let's Go Eevee"]), |
|||
("Galar", "images/marks/Galar_symbol_HOME.png", ["Sword", "Shield"]), |
|||
("Sinnoh", "images/marks/BDSP_icon_HOME.png", ["Brilliant Diamond", "Shining Pearl"]), |
|||
("Hisui", "images/marks/Arceus_mark_HOME.png", ["Legends Arceus"]), |
|||
("Paldea", "images/marks/Paldea_icon_HOME.png", ["Scarlet", "Violet"]), |
|||
] |
|||
|
|||
for mark in marks: |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO marks (name, icon_path) |
|||
VALUES (?, ?) |
|||
''', (mark[0], mark[1])) |
|||
|
|||
mark_id = cursor.lastrowid |
|||
|
|||
for game_name in mark[2]: |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO mark_game_associations (mark_id, game_id) |
|||
SELECT ?, id FROM games WHERE name = ? |
|||
''', (mark_id, game_name)) |
|||
|
|||
conn.commit() |
|||
|
|||
def load_pokemon_data(conn): |
|||
cursor = conn.cursor() |
|||
|
|||
with open('pokemon_home_list.csv', 'r') as f: |
|||
reader = csv.reader(f) |
|||
next(reader) # Skip header row if it exists |
|||
for row in reader: |
|||
national_dex_number = int(row[0]) |
|||
full_name = row[1] |
|||
|
|||
# Extract the base name and form name |
|||
match = re.match(r'([^(]+)(?:\s*\(([^)]+)\))?', full_name) |
|||
if match: |
|||
base_name = match.group(1).strip() |
|||
form_name = match.group(2).strip() if match.group(2) else "Default" |
|||
else: |
|||
base_name = full_name |
|||
form_name = "Default" |
|||
|
|||
# Insert or update the pokemon entry |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO pokemon (national_dex_number, name) |
|||
VALUES (?, ?) |
|||
''', (national_dex_number, base_name)) |
|||
|
|||
# Create the image path |
|||
padded_dex = f"{national_dex_number:04d}" |
|||
if form_name == "Default": |
|||
image_path = f"images/pokemon/{padded_dex}_{base_name}.png" |
|||
else: |
|||
image_path = f"images/pokemon/{padded_dex}_{base_name}_({form_name}).png".replace(" ", "_") |
|||
|
|||
# Insert the form entry |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|||
VALUES (?, ?, ?, ?) |
|||
''', (national_dex_number, form_name, form_name == "Default", image_path)) |
|||
|
|||
conn.commit() |
|||
|
|||
def load_encounter_data(conn): |
|||
cursor = conn.cursor() |
|||
|
|||
with open('pokemon_earliest_games.csv', 'r') as f: |
|||
reader = csv.DictReader(f) |
|||
for row in reader: |
|||
national_dex_number = int(row['number']) |
|||
full_name = row['name'] |
|||
earliest_game = row['earliest_game'] |
|||
obtain_method = row['obtain_method'] |
|||
encounter_locations = row['encounter_locations'] |
|||
|
|||
# Extract the base name and form name |
|||
match = re.match(r'([^(]+)(?:\s*\(([^)]+)\))?', full_name) |
|||
if match: |
|||
base_name = match.group(1).strip() |
|||
form_name = match.group(2).strip() if match.group(2) else "Default" |
|||
else: |
|||
base_name = full_name |
|||
form_name = "Default" |
|||
|
|||
# Ensure the Pokémon and form exist in the database |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO pokemon (national_dex_number, name) |
|||
VALUES (?, ?) |
|||
''', (national_dex_number, base_name)) |
|||
|
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|||
VALUES (?, ?, ?, ?) |
|||
''', (national_dex_number, form_name, form_name == "Default", f"images/pokemon/{national_dex_number:04d}_{base_name}.png")) |
|||
|
|||
# Skip encounter data if it's unknown or N/A |
|||
if earliest_game == "Unknown" or obtain_method == "Unknown": |
|||
continue |
|||
|
|||
# Get the form_id |
|||
cursor.execute(''' |
|||
SELECT id FROM pokemon_forms |
|||
WHERE pokemon_id = ? AND form_name = ? |
|||
''', (national_dex_number, form_name)) |
|||
form_id = cursor.fetchone()[0] |
|||
|
|||
# Get the game_id (now case-insensitive and including alternate names) |
|||
cursor.execute(''' |
|||
SELECT g.id FROM games g |
|||
LEFT JOIN alternate_game_names agn ON g.id = agn.game_id |
|||
WHERE g.name = ? COLLATE NOCASE OR agn.alternate_name = ? COLLATE NOCASE |
|||
''', (earliest_game, earliest_game)) |
|||
result = cursor.fetchone() |
|||
if result: |
|||
game_id = result[0] |
|||
else: |
|||
print(f"Warning: Game '{earliest_game}' not found for {full_name}") |
|||
continue |
|||
|
|||
# Handle gift Pokémon |
|||
if obtain_method.lower() == "gift" and (encounter_locations == "N/A" or not encounter_locations): |
|||
# Insert or get the "Gift" location |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO locations (name, description) |
|||
VALUES (?, ?) |
|||
''', ("Gift", "Pokémon received as a gift")) |
|||
cursor.execute('SELECT id FROM locations WHERE name = ?', ("Gift",)) |
|||
location_id = cursor.fetchone()[0] |
|||
|
|||
# Insert or get the "gift" encounter method |
|||
cursor.execute('INSERT OR IGNORE INTO encounter_methods (name) VALUES (?)', ("gift",)) |
|||
cursor.execute('SELECT id FROM encounter_methods WHERE name = ?', ("gift",)) |
|||
method_id = cursor.fetchone()[0] |
|||
|
|||
# Insert form_encounter for gift Pokémon |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO form_encounters |
|||
(form_id, game_id, location_id, encounter_method_id) |
|||
VALUES (?, ?, ?, ?) |
|||
''', (form_id, game_id, location_id, method_id)) |
|||
elif encounter_locations != "N/A" and encounter_locations: |
|||
# Process each encounter location |
|||
for location_info in encounter_locations.split('|'): |
|||
location, method = location_info.strip().rsplit(' ', 1) |
|||
method = method.strip('()') |
|||
|
|||
# Insert or get location_id |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO locations (name, description) |
|||
VALUES (?, ?) |
|||
''', (location, None)) |
|||
cursor.execute('SELECT id FROM locations WHERE name = ?', (location,)) |
|||
location_id = cursor.fetchone()[0] |
|||
|
|||
# Insert or get encounter_method_id |
|||
cursor.execute('INSERT OR IGNORE INTO encounter_methods (name) VALUES (?)', (method,)) |
|||
cursor.execute('SELECT id FROM encounter_methods WHERE name = ?', (method,)) |
|||
method_id = cursor.fetchone()[0] |
|||
|
|||
# Insert form_encounter |
|||
cursor.execute(''' |
|||
INSERT OR IGNORE INTO form_encounters |
|||
(form_id, game_id, location_id, encounter_method_id) |
|||
VALUES (?, ?, ?, ?) |
|||
''', (form_id, game_id, location_id, method_id)) |
|||
|
|||
conn.commit() |
|||
|
|||
def main(): |
|||
conn = create_connection() |
|||
create_tables(conn) |
|||
load_game_data(conn) |
|||
load_mark_data(conn) |
|||
load_pokemon_data(conn) |
|||
load_encounter_data(conn) |
|||
conn.close() |
|||
print("All data has been successfully added to the database.") |
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
@ -0,0 +1,386 @@ |
|||
import csv |
|||
import requests |
|||
import time |
|||
import json |
|||
import os |
|||
import re |
|||
import sqlite3 |
|||
|
|||
# Initialize the database connection |
|||
conn = sqlite3.connect('pokemon_cache.db') |
|||
cursor = conn.cursor() |
|||
|
|||
# Create the cache table if it doesn't exist |
|||
cursor.execute(''' |
|||
CREATE TABLE IF NOT EXISTS cache ( |
|||
key TEXT PRIMARY KEY, |
|||
value TEXT |
|||
) |
|||
''') |
|||
conn.commit() |
|||
|
|||
# List of all main series Pokémon games in chronological order, with special games first in each generation |
|||
all_games = [ |
|||
"Yellow", "Red", "Blue", |
|||
"Crystal", "Gold", "Silver", |
|||
"Emerald", "FireRed", "LeafGreen", "Ruby", "Sapphire", |
|||
"Platinum", "HeartGold", "SoulSilver", "Diamond", "Pearl", |
|||
"Black-2", "White-2", "Black", "White", |
|||
"X", "Y", "Omega-Ruby", "Alpha-Sapphire", |
|||
"Ultra-Sun", "Ultra-Moon", "Sun", "Moon", |
|||
"Sword", "Shield", |
|||
"Brilliant-Diamond", "Shining-Pearl", |
|||
"Legends-Arceus", |
|||
"Scarlet", "Violet", |
|||
"Unknown" |
|||
] |
|||
|
|||
cache = {} |
|||
new_entries_count = 0 |
|||
|
|||
def get_cached_data(): |
|||
global cache |
|||
cursor.execute("SELECT key, value FROM cache") |
|||
for key, value in cursor.fetchall(): |
|||
cache[key] = json.loads(value) |
|||
|
|||
def save_cached_data(): |
|||
global cache, new_entries_count |
|||
if new_entries_count > 0: |
|||
for key, value in cache.items(): |
|||
cursor.execute("INSERT OR REPLACE INTO cache (key, value) VALUES (?, ?)", |
|||
(key, json.dumps(value))) |
|||
conn.commit() |
|||
new_entries_count = 0 |
|||
|
|||
def update_cache(key, value): |
|||
global cache, new_entries_count |
|||
if key not in cache: |
|||
cache[key] = value |
|||
new_entries_count += 1 |
|||
if new_entries_count >= 10: |
|||
save_cached_data() |
|||
time.sleep(1) |
|||
|
|||
def read_pokemon_list(filename, limit=50): |
|||
pokemon_list = [] |
|||
with open(filename, 'r', newline='', encoding='utf-8') as csvfile: |
|||
reader = csv.DictReader(csvfile) |
|||
for i, row in enumerate(reader): |
|||
if i >= limit: |
|||
break |
|||
# Split the name into base name and form |
|||
match = re.match(r'(.*?)\s*(\(.*\))?$', row['name']) |
|||
base_name, form = match.groups() if match else (row['name'], None) |
|||
row['base_name'] = base_name.strip() |
|||
row['form'] = form.strip('() ') if form else None |
|||
pokemon_list.append(row) |
|||
return pokemon_list |
|||
|
|||
def sanitize_name_and_form(name, form): |
|||
adjusted_form = None |
|||
if form: |
|||
adjusted_form = form.lower() |
|||
#Some stupid special cases |
|||
if name.lower() == 'tauros': |
|||
if adjusted_form == 'paldean form': |
|||
adjusted_form = 'paldea combat breed' |
|||
elif 'blaze' in adjusted_form: |
|||
adjusted_form = 'paldea blaze breed' |
|||
elif 'aqua' in adjusted_form: |
|||
adjusted_form = 'paldea aqua breed' |
|||
|
|||
replacements = {'forme': '', |
|||
'form': '', |
|||
'alolan': 'alola', |
|||
'galarian': 'galar', |
|||
'hisuian': 'hisui', |
|||
'paldean': 'paldea', |
|||
'size': '', |
|||
'10%': '10 power construct', |
|||
'hoopa': '', |
|||
'style': '', |
|||
'core': '', |
|||
'color': '', |
|||
'blood moon': 'bloodmoon'}; |
|||
for old, new in replacements.items(): |
|||
adjusted_form = adjusted_form.replace(old, new).strip() |
|||
|
|||
missing_forms = ['burmy', |
|||
'shellos', |
|||
'gastrodon', |
|||
'wormadam', |
|||
'unown', |
|||
"deerling", |
|||
"sawsbuck", |
|||
"vivillon", |
|||
"flabébé", |
|||
"floette", |
|||
"florges", |
|||
"furfrou", |
|||
"sinistea", |
|||
"polteageist", |
|||
"alcremie", |
|||
"poltchageist", |
|||
"sinistcha"] |
|||
|
|||
if name.lower() in missing_forms: |
|||
adjusted_form = None |
|||
|
|||
if name.lower() == 'wormadam': |
|||
adjusted_form = adjusted_form.replace('cloak', '').strip() |
|||
if name.lower() == 'rotom': |
|||
adjusted_form = adjusted_form.replace('rotom', '').strip() |
|||
if name.lower() == 'darmanitan': |
|||
adjusted_form = adjusted_form + ' standard' |
|||
|
|||
else: |
|||
default_forms = {'deoxys': 'normal', |
|||
'wormadam': 'plant', |
|||
'giratina': 'origin', |
|||
'tornadus': 'incarnate', |
|||
'shaymin': 'land', |
|||
'basculin': 'red-striped', |
|||
'darmanitan': 'standard', |
|||
'thundurus': 'incarnate', |
|||
'landorus': 'incarnate', |
|||
'enamorus': 'incarnate', |
|||
'keldeo': 'ordinary', |
|||
'meloetta': 'aria', |
|||
'meowstic': 'male', |
|||
'aegislash': 'shield', |
|||
'pumpkaboo': 'average', |
|||
'gourgeist': 'average', |
|||
'minior': 'red-meteor', |
|||
'zygarde': '50 power construct', |
|||
'oricorio': 'baile', |
|||
'lycanroc': 'midday', |
|||
'wishiwashi': 'solo', |
|||
'mimikyu': 'disguised', |
|||
'cramorant': 'gulping', |
|||
'toxtricity': 'low-key', |
|||
'eiscue': 'ice', |
|||
'indeedee': 'male', |
|||
'urshifu': 'single-strike', |
|||
'morpeko': 'full belly', |
|||
'oinkologne': 'male', |
|||
'maushold': 'family of three', |
|||
'squawkabilly': 'green plumage', |
|||
'palafin': 'zero', |
|||
'tatsugiri': 'curly', |
|||
'dudunsparce': 'two segment', |
|||
'basculegion': 'male'} |
|||
|
|||
if name.lower() in default_forms: |
|||
adjusted_form = default_forms[name.lower()] |
|||
|
|||
if adjusted_form: |
|||
api_name = f"{name.lower()}-{adjusted_form}" |
|||
else: |
|||
api_name = name.lower() |
|||
|
|||
api_name = api_name.replace(' ', '-').replace("'", "").replace(".", "").replace('é', 'e').replace(':', '') |
|||
|
|||
#more special cases |
|||
if api_name == 'oinkologne-male': |
|||
api_name = '916' |
|||
|
|||
return api_name |
|||
|
|||
def get_pokemon_data(pokemon_name, form, cache): |
|||
cache_key = f"pokemon_{pokemon_name}_{form}" if form else f"pokemon_{pokemon_name}" |
|||
if cache_key in cache: |
|||
return cache[cache_key] |
|||
|
|||
api_name = sanitize_name_and_form(pokemon_name, form) |
|||
|
|||
url = f"https://pokeapi.co/api/v2/pokemon/{api_name}" |
|||
print(f"Fetching Pokémon data for {pokemon_name}: {url}") |
|||
response = requests.get(url) |
|||
if response.status_code == 200: |
|||
data = response.json() |
|||
update_cache(cache_key, data) |
|||
return data |
|||
return None |
|||
|
|||
def get_pokemon_encounter_data(pokemon_name, form, cache): |
|||
cache_key = f"pokemon_encounter_{pokemon_name}_{form}" if form else f"pokemon_encounter_{pokemon_name}" |
|||
if cache_key in cache: |
|||
return cache[cache_key] |
|||
|
|||
api_name = sanitize_name_and_form(pokemon_name, form) |
|||
|
|||
url = f"https://pokeapi.co/api/v2/pokemon/{api_name}/encounters" |
|||
print(f"Fetching encounter data for {pokemon_name}: {url}") |
|||
response = requests.get(url) |
|||
if response.status_code == 200: |
|||
data = response.json() |
|||
update_cache(cache_key, data) |
|||
return data |
|||
else: |
|||
return None |
|||
|
|||
def get_earliest_game(encounter_data): |
|||
if not encounter_data: |
|||
return "Unknown", "Unknown" |
|||
|
|||
game_methods = {} |
|||
for location_area in encounter_data: |
|||
for version_detail in location_area['version_details']: |
|||
game = version_detail['version']['name'] |
|||
is_gift = any(method['method']['name'] == 'gift' for method in version_detail['encounter_details']) |
|||
|
|||
if game not in game_methods: |
|||
game_methods[game] = "Gift" if is_gift else "Catchable" |
|||
elif game_methods[game] == "Gift" and not is_gift: |
|||
game_methods[game] = "Catchable" |
|||
|
|||
for game in all_games: |
|||
if game.lower() in game_methods: |
|||
return game, game_methods[game.lower()] |
|||
|
|||
return "Unknown", "Unknown" |
|||
|
|||
def determine_earliest_games(pokemon_list, cache): |
|||
for pokemon in pokemon_list: |
|||
pokemon_data = get_pokemon_data(pokemon['base_name'], pokemon['form'], cache) |
|||
encounter_data = get_pokemon_encounter_data(pokemon['base_name'], pokemon['form'], cache) |
|||
pokemon['earliest_game'], pokemon['obtain_method'] = get_earliest_game(encounter_data) |
|||
print(f"Processed {pokemon['name']} (#{pokemon['number']}): {pokemon['earliest_game']} ({pokemon['obtain_method']})") |
|||
return pokemon_list |
|||
|
|||
def get_species_data(pokemon_name, cache): |
|||
cache_key = f"species_{pokemon_name}" |
|||
if cache_key in cache: |
|||
return cache[cache_key] |
|||
|
|||
api_name = sanitize_name_and_form(pokemon_name, None) |
|||
|
|||
url = f"https://pokeapi.co/api/v2/pokemon-species/{api_name}/" |
|||
print(f"Fetching species data for {pokemon_name}: {url}") |
|||
response = requests.get(url) |
|||
if response.status_code == 200: |
|||
data = response.json() |
|||
update_cache(cache_key, data) |
|||
return data |
|||
return None |
|||
|
|||
def get_evolution_chain(pokemon_name, cache): |
|||
species_data = get_species_data(pokemon_name, cache) |
|||
if not species_data: |
|||
return None |
|||
|
|||
cache_key = f"evolution_{species_data['evolution_chain']['url']}" |
|||
if cache_key in cache: |
|||
return cache[cache_key] |
|||
|
|||
evolution_response = requests.get(species_data['evolution_chain']['url']) |
|||
if evolution_response.status_code == 200: |
|||
evolution_data = evolution_response.json() |
|||
update_cache(cache_key, evolution_data) |
|||
return evolution_data |
|||
return None |
|||
|
|||
def get_base_form(evolution_chain, cache): |
|||
if not evolution_chain or 'chain' not in evolution_chain: |
|||
return None |
|||
|
|||
current = evolution_chain['chain'] |
|||
while current: |
|||
species_name = current['species']['name'] |
|||
species_data = get_species_data(species_name, cache) |
|||
|
|||
if species_data and not species_data.get('is_baby', False): |
|||
return species_name |
|||
|
|||
if not current['evolves_to']: |
|||
return species_name |
|||
|
|||
current = current['evolves_to'][0] |
|||
|
|||
return None |
|||
|
|||
def adjust_for_evolution(pokemon_list, cache): |
|||
pokemon_dict = {f"{pokemon['base_name']}_{pokemon['form']}".lower(): pokemon for pokemon in pokemon_list} |
|||
|
|||
for pokemon in pokemon_list: |
|||
species_data = get_species_data(pokemon['base_name'], cache) |
|||
evolution_chain = get_evolution_chain(pokemon['base_name'], cache) |
|||
base_form = get_base_form(evolution_chain, cache) |
|||
|
|||
# Check if the Pokémon is a baby |
|||
if species_data and species_data.get('is_baby', False): |
|||
pokemon['obtain_method'] = 'Breed' |
|||
elif base_form: |
|||
base_key = f"{base_form}_{pokemon['form']}".lower() |
|||
if base_key in pokemon_dict: |
|||
base_pokemon = pokemon_dict[base_key] |
|||
if all_games.index(base_pokemon['earliest_game']) <= all_games.index(pokemon['earliest_game']) and base_pokemon['number'] != pokemon['number']: |
|||
pokemon['earliest_game'] = base_pokemon['earliest_game'] |
|||
pokemon['obtain_method'] = 'Evolve' |
|||
|
|||
print(f"Adjusted {pokemon['name']} (#{pokemon['number']}): {pokemon['earliest_game']} ({pokemon['obtain_method']})") |
|||
|
|||
return pokemon_list |
|||
|
|||
def save_to_csv(pokemon_list, filename='pokemon_earliest_games.csv'): |
|||
with open(filename, 'w', newline='', encoding='utf-8') as csvfile: |
|||
fieldnames = ['number', 'name', 'earliest_game', 'obtain_method', 'encounter_locations'] |
|||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames) |
|||
|
|||
writer.writeheader() |
|||
for pokemon in pokemon_list: |
|||
writer.writerow({ |
|||
'number': pokemon['number'], |
|||
'name': pokemon['name'], |
|||
'earliest_game': pokemon['earliest_game'], |
|||
'obtain_method': pokemon['obtain_method'], |
|||
'encounter_locations': pokemon['encounter_locations'] |
|||
}) |
|||
|
|||
def parse_encounter_locations(encounter_data, game): |
|||
locations = [] |
|||
for location_area in encounter_data: |
|||
for version_detail in location_area['version_details']: |
|||
if version_detail['version']['name'] == game.lower(): |
|||
location_name = location_area['location_area']['name'] |
|||
for encounter_detail in version_detail['encounter_details']: |
|||
method = encounter_detail['method']['name'] |
|||
condition = encounter_detail.get('condition', 'Any') |
|||
time = ', '.join(encounter_detail.get('time', ['Any'])) |
|||
|
|||
encounter_info = f"{location_name} ({method}" |
|||
if condition != 'Any': |
|||
encounter_info += f", {condition}" |
|||
if time != 'Any': |
|||
encounter_info += f", {time}" |
|||
encounter_info += ")" |
|||
|
|||
if encounter_info not in locations: |
|||
locations.append(encounter_info) |
|||
return locations |
|||
|
|||
def add_encounter_locations(pokemon_list, cache): |
|||
for pokemon in pokemon_list: |
|||
if pokemon['obtain_method'] == 'Catchable': |
|||
encounter_data = get_pokemon_encounter_data(pokemon['base_name'], pokemon['form'], cache) |
|||
locations = parse_encounter_locations(encounter_data, pokemon['earliest_game']) |
|||
pokemon['encounter_locations'] = ' | '.join(locations) if locations else 'Unknown' |
|||
else: |
|||
pokemon['encounter_locations'] = 'N/A' |
|||
print(f"Added encounter locations for {pokemon['name']} (#{pokemon['number']}) in {pokemon['earliest_game']}") |
|||
return pokemon_list |
|||
|
|||
# Update the main function |
|||
if __name__ == "__main__": |
|||
get_cached_data() |
|||
|
|||
pokemon_list = read_pokemon_list('pokemon_home_list.csv', limit=3000) |
|||
pokemon_list_with_games = determine_earliest_games(pokemon_list, cache) |
|||
pokemon_list_adjusted = adjust_for_evolution(pokemon_list_with_games, cache) |
|||
pokemon_list_with_locations = add_encounter_locations(pokemon_list_adjusted, cache) |
|||
save_to_csv(pokemon_list_with_locations) |
|||
|
|||
save_cached_data() # Save any remaining new entries |
|||
conn.close() # Close the database connection |
|||
print(f"Earliest obtainable games and encounter locations determined for {len(pokemon_list)} Pokémon and saved to pokemon_earliest_games.csv") |
|||
@ -0,0 +1,111 @@ |
|||
import requests |
|||
from bs4 import BeautifulSoup |
|||
import csv |
|||
import os |
|||
import time |
|||
import re |
|||
|
|||
def scrape_serebii_region_pokemon(url): |
|||
response = requests.get(url) |
|||
soup = BeautifulSoup(response.content, 'html.parser') |
|||
|
|||
pokemon_list = [] |
|||
|
|||
# Find the main table containing Pokémon data |
|||
table = soup.find('table', class_='dextable') |
|||
|
|||
if table: |
|||
rows = table.find_all('tr')[2:] # Skip the header row and the game intro row |
|||
for row in rows: |
|||
cells = row.find_all('td') |
|||
if len(cells) <= 5: # Ensure we have enough cells to check depositability. if only 5 then its not depositable in any game. |
|||
continue |
|||
|
|||
number = cells[0].text.strip().lstrip('#') |
|||
name = cells[2].text.strip() |
|||
|
|||
# Get the image URL |
|||
img_url = cells[1].find('img')['src'] |
|||
full_img_url = f"https://www.serebii.net{img_url}" |
|||
|
|||
pokemon_list.append({ |
|||
'number': number, |
|||
'name': name, |
|||
'image_url': full_img_url |
|||
}) |
|||
|
|||
return pokemon_list |
|||
|
|||
def download_image(url, filename): |
|||
response = requests.get(url) |
|||
if response.status_code == 200: |
|||
with open(filename, 'wb') as f: |
|||
f.write(response.content) |
|||
|
|||
def sanitize_filename(filename): |
|||
# Define a dictionary of symbol replacements |
|||
symbol_replacements = { |
|||
'?': 'questionmark', |
|||
'*': 'asterisk', |
|||
':': 'colon', |
|||
'/': 'slash', |
|||
'\\': 'backslash', |
|||
'|': 'pipe', |
|||
'<': 'lessthan', |
|||
'>': 'greaterthan', |
|||
'"': 'quote', |
|||
' ': '_' |
|||
} |
|||
|
|||
# Replace symbols with their word equivalents |
|||
for symbol, word in symbol_replacements.items(): |
|||
filename = filename.replace(symbol, word) |
|||
|
|||
# Remove any remaining invalid characters |
|||
return re.sub(r'[<>:"/\\|?*]', '', filename) |
|||
|
|||
def scrape_all_regions(): |
|||
base_url = "https://www.serebii.net/pokemonhome/" |
|||
regions = ["kanto", "johto", "hoenn", "sinnoh", "unova", "kalos", "alola", "galar", "paldea", "hisui", "unknown"] |
|||
all_pokemon = [] |
|||
|
|||
for region in regions: |
|||
url = f"{base_url}{region}pokemon.shtml" |
|||
region_pokemon = scrape_serebii_region_pokemon(url) |
|||
all_pokemon.extend(region_pokemon) |
|||
print(f"Scraped {len(region_pokemon)} Pokémon from {region.capitalize()} region") |
|||
time.sleep(1) # Be nice to the server |
|||
|
|||
return all_pokemon |
|||
|
|||
def save_to_csv(pokemon_list, filename='pokemon_home_list.csv'): |
|||
with open(filename, 'w', newline='', encoding='utf-8') as csvfile: |
|||
fieldnames = ['number', 'name'] |
|||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames) |
|||
|
|||
writer.writeheader() |
|||
for pokemon in pokemon_list: |
|||
writer.writerow({k: pokemon[k] for k in fieldnames}) |
|||
|
|||
if __name__ == "__main__": |
|||
all_pokemon = scrape_all_regions() |
|||
save_to_csv(all_pokemon) |
|||
print(f"Scraped a total of {len(all_pokemon)} Pokémon and saved to pokemon_home_list.csv") |
|||
|
|||
# Create 'images' directory if it doesn't exist |
|||
if not os.path.exists('images'): |
|||
os.makedirs('images') |
|||
|
|||
# Download images |
|||
for pokemon in all_pokemon: |
|||
sanitized_name = sanitize_filename(pokemon['name']) |
|||
filename = f"images/{pokemon['number']}_{sanitized_name}.png" |
|||
|
|||
if os.path.exists(filename): |
|||
print(f"Image for {pokemon['name']} already exists, skipping download") |
|||
else: |
|||
download_image(pokemon['image_url'], filename) |
|||
print(f"Downloaded image for {pokemon['name']}") |
|||
time.sleep(0.5) # Be nice to the server |
|||
|
|||
print("All images downloaded successfully.") |
|||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1005 B |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 749 B |
|
After Width: | Height: | Size: 780 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 959 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |