Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ac2388e
feat: Database management script for all interactions with SQLite db
h0tp-ftw Feb 5, 2026
66766f6
fix: Imports to include db
h0tp-ftw Feb 5, 2026
26a03b1
migrate: Encounter functions use db
h0tp-ftw Feb 5, 2026
5781bbf
feat: Migration trigger in init if json detected
h0tp-ftw Feb 5, 2026
cbf2630
migrate: Pokemon id's loaded from db
h0tp-ftw Feb 5, 2026
8a94335
feat: Implement a blocking migration dialog.
h0tp-ftw Feb 5, 2026
fc5f8e7
migration: Implement db usage
h0tp-ftw Feb 5, 2026
7cb2db0
feat: Merge mainpokemon and mypokemon into ONE file, with flag for is…
h0tp-ftw Feb 5, 2026
e4434b6
fix: Unterminated docstring.
h0tp-ftw Feb 5, 2026
fc213e8
fix: Migration handles badges.json legacy + new format
h0tp-ftw Feb 5, 2026
6b6728c
feat: Error reporting for failed migrations
h0tp-ftw Feb 5, 2026
1c61fbf
fix: json mvmt to json/ subfolder after migration not delete
h0tp-ftw Feb 5, 2026
0fcfa56
migrate: to db usage
h0tp-ftw Feb 5, 2026
f6d3f08
migration: to db usage
h0tp-ftw Feb 5, 2026
0948d43
feat: Numerical integrity check to ensure that counts of entries match
h0tp-ftw Feb 5, 2026
56f2511
feat: mw has ankimon_db attached = one-line usage
h0tp-ftw Feb 5, 2026
1802aa6
feat: gitignore the db
h0tp-ftw Feb 5, 2026
b5eb6b7
feat: Migrate all remaining json's to db, other than rate_this.json
h0tp-ftw Feb 5, 2026
0e3ecf6
migrate: rate_this.json
h0tp-ftw Feb 5, 2026
e7d4c95
feat: Consolidate migration functions for rate_this.json
h0tp-ftw Feb 5, 2026
57e16d5
migrate: Sync logic and other json usages
h0tp-ftw Feb 5, 2026
b446dda
migrate: Showdown export
h0tp-ftw Feb 5, 2026
8480706
feat: Search if pkmn owned via name comparison and not a full search
h0tp-ftw Feb 5, 2026
f26f64f
feat: Implement is_pokemon_owned check to life bar
h0tp-ftw Feb 6, 2026
9992c72
migrate: add config.obf to db
h0tp-ftw Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ src/Ankimon/updateinfos.md
*.env
cycle_keys.py
src/Ankimon/user_files/download_complete.flag
src/Ankimon/user_files/ankimon.db
35 changes: 22 additions & 13 deletions src/Ankimon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
from aqt.webview import WebContent
import markdown

from .resources import generate_startup_files, user_path, IS_EXPERIMENTAL_BUILD, addon_ver, addon_dir
generate_startup_files(addon_dir, user_path)
from .resources import ensure_ankimon_infrastructure, user_path, IS_EXPERIMENTAL_BUILD, addon_ver, addon_dir
ensure_ankimon_infrastructure(addon_dir, user_path)

from .singletons import settings_obj
no_more_news = settings_obj.get("misc.YouShallNotPass_Ankimon_News")
Expand Down Expand Up @@ -103,8 +103,6 @@
ankimon_tracker_obj,
test_window,
achievement_bag,
data_handler_obj,
data_handler_window,
shop_manager,
ankimon_tracker_window,
pokedex_window,
Expand All @@ -117,7 +115,8 @@
item_window,
version_dialog,
achievements,
pokemon_pc
pokemon_pc,
ankimon_db,
)

from .pyobj.pokemon_trade import check_and_award_monthly_pokemon
Expand Down Expand Up @@ -147,6 +146,19 @@

backup_manager = BackupManager(logger, settings_obj)

# Migrate existing JSON data to SQLite database (one-time operation with dialog)
if not ankimon_db.is_migrated():
from .pyobj.migration_dialog import show_migration_dialog_if_needed
from .resources import (
mypokemon_path, mainpokemon_path, itembag_path, badgebag_path,
team_pokemon_path, pokemon_history_path, user_path_credentials,
rate_path
)
show_migration_dialog_if_needed(
ankimon_db, mypokemon_path, mainpokemon_path, itembag_path, badgebag_path, mw,
team_pokemon_path, pokemon_history_path, user_path_credentials, rate_path
)

if settings_obj.get("misc.developer_mode"):
backup_manager.create_backup(manual=False)

Expand Down Expand Up @@ -628,13 +640,12 @@ class Container(object):
rate_this_addon()

if database_complete:
if mypokemon_path.is_file() is False:
# Check if user has any pokemon in database
from .pyobj.database_manager import get_db
db = get_db()
pokemon_list = db.get_all_pokemon()
if not pokemon_list:
starter_window.display_starter_pokemon()
else:
with open(mypokemon_path, "r", encoding="utf-8") as file:
pokemon_list = json.load(file)
if not pokemon_list :
starter_window.display_starter_pokemon()

count_items_and_rewrite(itembag_path)

Expand Down Expand Up @@ -663,7 +674,6 @@ class Container(object):
trainer_card,
ankimon_tracker_window,
logger,
data_handler_window,
settings_window,
shop_manager,
pokedex_window,
Expand All @@ -672,7 +682,6 @@ class Container(object):
open_leaderboard_url,
settings_obj,
addon_dir,
data_handler_obj,
pokemon_pc,
backup_manager,
)
Expand Down
46 changes: 35 additions & 11 deletions src/Ankimon/functions/badges_functions.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,61 @@
import json
from typing import List

from ..resources import badgebag_path
from aqt import mw


def get_achieved_badges() -> List[int]:
"""Gets list of achieved badge IDs from the database."""
db = mw.ankimon_db

if db.is_migrated():
badges = db.get_all_badges()
return [int(b.get("badge_id", b.get("id", 0))) for b in badges]

# Fallback to JSON for backwards compatibility
try:
with open(badgebag_path, "r", encoding="utf-8") as json_file:
return json.load(json_file)
except (FileNotFoundError, json.JSONDecodeError):
return []

def get_achieved_badges():
with open(badgebag_path, "r", encoding="utf-8") as json_file:
return json.load(json_file)

def populate_achievements_from_badges(achievements):
# name change for clarification
"""Populates achievements dict from stored badges."""
try:
for badge_num in get_achieved_badges():
achievements[str(badge_num)] = True
except (FileNotFoundError, json.JSONDecodeError):
# If file doesn't exist or is empty, just return the initial achievements
except Exception:
pass
return achievements


def check_for_badge(achievements, rec_badge_num):
return achievements.get(str(rec_badge_num), False)

def save_badges(badges_collection):
with open(badgebag_path, 'w') as json_file:
json.dump(badges_collection, json_file)

def receive_badge(badge_num,achievements):
def save_badges(badges_collection: List[int]):
"""Saves badges collection to the database."""
db = mw.ankimon_db

# Clear existing badges and save new ones
# Each badge is saved with its ID as the key
for badge_num in badges_collection:
db.save_badge(str(badge_num), {"id": badge_num, "achieved": True})


def receive_badge(badge_num, achievements):
"""Awards a badge and saves to database."""
achievements[str(badge_num)] = True
badges_collection = []
for num in range(1,69):
for num in range(1, 69):
if achievements.get(str(num)) is True:
badges_collection.append(int(num))
save_badges(badges_collection)
return achievements


def handle_review_count_achievement(review_count, achievements):
milestones = {
100: 1,
Expand Down
88 changes: 20 additions & 68 deletions src/Ankimon/functions/encounter_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
trainer_card,
settings_obj,
translator,
ankimon_db,
)
from ..resources import (
pokemon_species_baby_path,
Expand Down Expand Up @@ -411,10 +412,9 @@ def save_main_pokemon_progress(
main_pokemon.xp += exp
level_cap = 100
try:
if mainpokemon_path.is_file():
with open(mainpokemon_path, "r", encoding="utf-8") as json_file:
main_pokemon_data = json.load(json_file)
else:
db = mw.ankimon_db
main_pokemon_data = db.get_main_pokemon()
if not main_pokemon_data:
showWarning(translator.translate("missing_mainpokemon_data"))
except Exception as e:
show_warning_with_traceback(parent=mw, exception=e, message="Error loading main pokemon data.")
Expand Down Expand Up @@ -517,71 +517,33 @@ def save_main_pokemon_progress(
if hasattr(main_pokemon, "is_favorite"):
mainpkmndata["is_favorite"] = main_pokemon.is_favorite
mypkmndata = mainpkmndata
mainpkmndata = [mainpkmndata]
# Save the caught Pokémon's data to a JSON file
with open(str(mainpokemon_path), "w") as json_file:
json.dump(mainpkmndata, json_file, indent=2)

# Load data from the output JSON file
with open(str(mypokemon_path), "r", encoding="utf-8") as output_file:
mypokemondata = json.load(output_file)

# Find and replace the specified Pokémon's data in mypokemondata
for index, pokemon_data in enumerate(mypokemondata):
if pokemon_data.get("individual_id") == main_pokemon.individual_id: # Match by individual_id
mypokemondata[index] = mypkmndata # Replace with new data
break

# Save the modified data to the output JSON file
with open(str(mypokemon_path), "w") as output_file:
json.dump(mypokemondata, output_file, indent=2)

sync_mainpokemon_to_mypokemon(main_pokemon, mainpokemon_path, mypokemon_path)
# Save to database (replaces JSON file I/O for performance)
ankimon_db.save_main_pokemon(mypkmndata)
ankimon_db.save_pokemon(mypkmndata) # Also update the captured pokemon collection

return main_pokemon.level

# --- Utility: Sync mainpokemon to mypokemon ---
def sync_mainpokemon_to_mypokemon(main_pokemon, mainpokemon_path, mypokemon_path):
"""
Update the relevant entry in mypokemon file with the latest values from mainpokemon file.
Args:
main_pokemon: The main PokemonObject (should have individual_id).
mainpokemon_path: Path to mainpokemon.json.
mypokemon_path: Path to mypokemon.json.
Update the relevant entry in mypokemon database with the latest values from mainpokemon.
Uses database instead of JSON files.
"""
import json
# Load mainpokemon data
if not mainpokemon_path.is_file():
db = mw.ankimon_db

# Get main pokemon from database
main_entry = db.get_main_pokemon()
if not main_entry:
return
with open(mainpokemon_path, "r", encoding="utf-8") as f:
main_data = json.load(f)
if not main_data:
return
# Use the first (and only) mainpokemon entry
main_entry = main_data[0] if isinstance(main_data, list) else main_data

main_id = main_entry.get("individual_id", None)
if not main_id:
main_id = getattr(main_pokemon, "individual_id", None)
if not main_id:
return
# Load mypokemon data
if not mypokemon_path.is_file():
return
with open(mypokemon_path, "r", encoding="utf-8") as f:
my_data = json.load(f)
# Find and update the entry with matching individual_id
updated = False
for idx, entry in enumerate(my_data):
if entry.get("individual_id") == main_id:
# Update all keys from main_entry (except those you want to preserve in mypokemon)
for k, v in main_entry.items():
entry[k] = v
my_data[idx] = entry
updated = True
break
if updated:
with open(mypokemon_path, "w", encoding="utf-8") as f:
json.dump(my_data, f, indent=2)

# Save/update in captured_pokemon table
db.save_pokemon(main_entry)
return

def kill_pokemon(
Expand Down Expand Up @@ -680,18 +642,8 @@ def save_caught_pokemon(
"held_item": None
}

# Load existing Pokémon data if it exists
caught_pokemon_data = []
if mypokemon_path.is_file():
with open(mypokemon_path, "r", encoding="utf-8") as json_file:
caught_pokemon_data = json.load(json_file)

# Append the caught Pokémon's data to the list
caught_pokemon_data.append(caught_pokemon)

# Save the caught Pokémon's data to a JSON file
with open(str(mypokemon_path), "w") as json_file:
json.dump(caught_pokemon_data, json_file, indent=2)
# Save to database (replaces JSON file I/O for performance)
ankimon_db.save_pokemon(caught_pokemon)

def catch_pokemon(
enemy_pokemon: PokemonObject,
Expand Down
14 changes: 5 additions & 9 deletions src/Ankimon/functions/pokedex_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
pokedesc_lang_path,
pokeapi_db_path,
pokenames_lang_path,
mypokemon_path,
learnset_path,
moves_file_path,
poke_evo_path,
Expand Down Expand Up @@ -213,14 +212,11 @@ def get_pokemon_diff_lang_name(pokemon_id: int, language: int):

def extract_ids_from_file():
try:
filename = mypokemon_path
with open(filename, "r", encoding="utf-8") as file:
data = json.load(file)
ids = [character["id"] for character in data]
owned_pokemon_ids = ids
owned_pokemon_ids = sorted(list(set(owned_pokemon_ids)))
# showWarning(f"Owned Pokémon IDs: {owned_pokemon_ids}")
return owned_pokemon_ids
db = mw.ankimon_db
pokemon_list = db.get_all_pokemon()
ids = [pokemon["id"] for pokemon in pokemon_list]
owned_pokemon_ids = sorted(list(set(ids)))
return owned_pokemon_ids
except Exception as e:
show_warning_with_traceback(
parent=mw, exception=e, message="Error extracting IDs from file"
Expand Down
16 changes: 4 additions & 12 deletions src/Ankimon/functions/pokemon_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime

from aqt.utils import showWarning
from aqt import mw

from .pokedex_functions import search_pokeapi_db_by_id, search_pokedex, search_pokedex_by_id, get_all_pokemon_moves
from .battle_functions import calculate_hp
Expand Down Expand Up @@ -294,18 +295,9 @@ def save_fossil_pokemon(pokemon_id):
"special_form": None,
"tier": "Fossil",
}
# Load existing Pokémon data if it exists
if mypokemon_path.is_file():
with open(mypokemon_path, "r", encoding="utf-8") as json_file:
caught_pokemon_data = json.load(json_file)
else:
caught_pokemon_data = []

# Append the caught Pokémon's data to the list
caught_pokemon_data.append(caught_pokemon)
# Save the caught Pokémon's data to a JSON file
with open(str(mypokemon_path), "w") as json_file:
json.dump(caught_pokemon_data, json_file, indent=2)
# Save to database
db = mw.ankimon_db
db.save_pokemon(caught_pokemon)

def get_levelup_move_for_pokemon(pokemon_name, level):
"""
Expand Down
Loading