Files
joragames.gameset_database/update.py
T
Michel2 eb632b18ec
update gameset_database.json / Check_for_update (push) Successful in 34m1s
Nieuw sets toegevoegd: fifties, disco, nederhop en piraat
2026-01-23 12:08:59 +00:00

441 lines
18 KiB
Python
Executable File

#
# update_gameset_database.py
#
# Auteur: Michel Bats (BatsSoft)
# Gemaakt met: Gemini 2.5 Flash
# Versie: 2025.5.0
#
# Beschrijving:
# Dit script is ontworpen om Gameset-informatie, inclusief Spotify Track ID's, te verzamelen
# van specifieke pagina's op joragames.nl. Het verwerkt een gedefinieerde lijst van gamesets
# (SKU's), haalt de relevante gegevens op, en slaat deze op in een lokaal JSON-bestand
# (gameset_database.json). Het script zorgt ervoor dat de 'updated_on' timestamp alleen
# wordt bijgewerkt wanneer er daadwerkelijk een inhoudelijke wijziging in de database is.
# Bovendien is er een kleine vertraging ingebouwd tussen HTTP-requests om overbelasting
# van de server te voorkomen en het risico op blokkering te minimaliseren, wat essentieel is
# voor een verantwoord gebruik in een productieomgeving.
#
# Gebruik:
# Voer het script uit via de commandoregel:
# python3 update_gameset_database.py
#
# Vereisten:
# - Python 3.5+ (compatibel gemaakt voor Python 3.9)
# - De 'requests' bibliotheek (installeer met: pip install requests)
#
# Output:
# - gameset_database.json: Een JSON-bestand met de gestructureerde gameset-data,
# inclusief gameset_name, gameset_language en Spotify Track ID's voor elke kaart.
#
# Afhankelijkheden:
# - requests: Voor het uitvoeren van HTTP-requests.
# - re: Voor reguliere expressies om data uit HTML te extraheren.
# - json: Voor het lezen en schrijven van JSON-bestanden.
# - os: Voor bestandssysteembewerkingen (controleren of een bestand bestaat).
# - time: Voor het implementeren van vertragingen.
# - typing.Union: Voor type hints in Python < 3.10.
#
import requests
import re
import json
import os
import time
from typing import Union
def get_spotify_track_id(url: str) -> Union[tuple, None]:
"""
Laadt een URL in en extraheert een Spotify Track ID met behulp van een reguliere expressie.
Extraheert tevens de SKU en het CardNumber direct uit de URL.
Args:
url (str): De volledige URL van de kaartpagina, bijv. "https://joragames.nl/nineties/00040".
Returns:
Union[tuple, None]: Een tuple (spotify_id, sku, card_number) als de ID en URL-delen worden gevonden.
Retourneert None als de ID niet wordt gevonden, de URL ongeldig is, of bij een netwerkfout.
"""
try:
# Verstuur een GET-request naar de URL met een timeout van 10 seconden.
# Een timeout voorkomt dat het script oneindig blijft hangen bij unresponsive servers.
response = requests.get(url, timeout=10)
# Controleer de HTTP-statuscode. Gooit een uitzondering voor 4xx (client error) of 5xx (server error).
response.raise_for_status()
html_content = response.text
# Regex om de Spotify Track ID te vinden in de JavaScript redirect.
# Zoekt naar 'window.location = "spotify:track:<ID>"'.
spotify_regex = r'window\.location\s*=\s*"spotify:track:([a-zA-Z0-9]+)"'
spotify_match = re.search(spotify_regex, html_content)
if spotify_match:
spotify_id = spotify_match.group(
1
) # Extraheer de gevangen groep (de ID zelf)
# Regex om SKU en CardNumber uit de URL te extraheren.
# Verwacht URL-formaat: https://joragames.nl/<sku>/<card_number>
url_parts_regex = r"https://joragames\.nl/([a-zA-Z0-9_-]+)/(\d+)"
url_match = re.search(url_parts_regex, url)
if url_match:
sku = url_match.group(1)
# Vul het kaartnummer aan met voorloopnullen tot 5 cijfers (bijv. '40' wordt '00040').
card_number = url_match.group(2).zfill(5)
return spotify_id, sku, card_number
else:
# Dit zou zelden moeten voorkomen als de URL-structuur consistent is.
print(
f"Waarschuwing: Kon SKU en CardNumber niet extraheren uit URL: {url}"
)
return None
else:
# Geen Spotify Track ID gevonden op deze pagina.
return None
except requests.exceptions.RequestException as e:
# Vang specifieke request exceptions op (bijv. verbindingsproblemen, DNS-fouten, timeouts).
print(f"Fout bij het laden van de URL '{url}': {e}")
return None
except Exception as e:
# Vang alle andere onverwachte fouten op die kunnen optreden.
print(f"Een onverwachte fout is opgetreden bij URL '{url}': {e}")
return None
def update_gameset_database(
spotify_data: tuple,
gameset_name_from_config: str,
gameset_language_from_config: str,
db_file: str = "gameset_database.json",
):
"""
Werkt het gameset_database.json-bestand bij met de gevonden Spotify-gegevens.
De 'updated_on' timestamp van de database wordt alleen bijgewerkt als er daadwerkelijk
een inhoudelijke wijziging is geweest (nieuwe kaart, gewijzigde Spotify ID, gewijzigde naam/taal).
Args:
spotify_data (tuple): Een tuple (spotify_id, sku, card_number) met de geëxtraheerde gegevens.
gameset_name_from_config (str): De naam van de gameset, zoals gedefinieerd in gamesets_config.
gameset_language_from_config (str): De taal van de gameset, zoals gedefinieerd in gamesets_config.
db_file (str, optional): De naam van het JSON-databasebestand. Standaard is "gameset_database.json".
"""
if not spotify_data:
# Geen geldige Spotify-gegevens om te verwerken.
return
spotify_id, sku, card_number = spotify_data
# Initialiseer de database structuur. updated_on krijgt een tijdelijke waarde.
database = {"updated_on": int(time.time()), "gamesets": []}
original_database_content = None # Slaat de string-representatie van de originele JSON op voor vergelijking.
# Probeer bestaande data in te lezen als het databasebestand bestaat.
if os.path.exists(db_file):
try:
with open(db_file, "r", encoding="utf-8") as f:
existing_data = json.load(f)
# Maak een gesorteerde JSON-string van de bestaande data voor accurate vergelijking.
original_database_content = json.dumps(
existing_data, sort_keys=True, indent=4, ensure_ascii=False
)
if "gamesets" in existing_data:
database["gamesets"] = existing_data["gamesets"]
if "updated_on" in existing_data:
# Behoud de bestaande 'updated_on' timestamp; deze wordt alleen bijgewerkt bij echte wijzigingen.
database["updated_on"] = existing_data["updated_on"]
except json.JSONDecodeError:
print(
f"Waarschuwing: Het bestand '{db_file}' is ongeldig of leeg. Begint met een lege database."
)
except Exception as e:
print(
f"Fout bij het lezen van '{db_file}': {e}. Begint met een lege database."
)
# Vlag om bij te houden of er inhoudelijke wijzigingen zijn aangebracht die een update rechtvaardigen.
is_modified = False
# Zoek de gameset op basis van SKU of maak een nieuwe aan als deze nog niet bestaat.
found_gameset = False
for gameset in database["gamesets"]:
if gameset["sku"] == sku:
found_gameset = True
# Controleer en update gameset_name indien nodig.
if (
gameset["gameset_data"].get("gameset_name", "")
!= gameset_name_from_config
):
gameset["gameset_data"]["gameset_name"] = gameset_name_from_config
is_modified = True
print(
f"Gameset '{sku}' naam bijgewerkt naar: '{gameset_name_from_config}'"
)
# Controleer en update gameset_language indien nodig.
# Standaardwaarde 'Netherlands' wordt gebruikt voor vergelijking als de sleutel ontbreekt.
if (
gameset["gameset_data"].get("gameset_language", "Netherlands")
!= gameset_language_from_config
):
gameset["gameset_data"][
"gameset_language"
] = gameset_language_from_config
is_modified = True
print(
f"Gameset '{sku}' taal bijgewerkt naar: '{gameset_language_from_config}'"
)
# Zoek de specifieke kaart binnen de gameset of voeg deze toe.
found_card = False
for card in gameset["gameset_data"]["cards"]:
if card["CardNumber"] == card_number:
if (
card["Spotify"] != spotify_id
): # Controleer of de Spotify ID daadwerkelijk is gewijzigd.
card["Spotify"] = spotify_id # Update de Spotify ID.
is_modified = True
print(
f"Kaart {card_number} voor SKU '{sku}' bijgewerkt met NIEUWE Spotify ID: {spotify_id}"
)
else:
print(
f"Kaart {card_number} voor SKU '{sku}' had al Spotify ID: {spotify_id} (geen wijziging nodig)."
)
found_card = True
break
if not found_card:
# Voeg een nieuwe kaart toe aan de gameset.
gameset["gameset_data"]["cards"].append(
{"CardNumber": card_number, "Spotify": spotify_id}
)
is_modified = True
print(
f"Nieuwe kaart {card_number} toegevoegd aan SKU '{sku}' met Spotify ID: {spotify_id}"
)
break
if not found_gameset:
# Als de gameset (SKU) nog niet bestaat, maak een volledig nieuwe gameset-structuur aan.
new_gameset = {
"sku": sku,
"gameset_data": {
"gameset_language": gameset_language_from_config,
"gameset_name": gameset_name_from_config,
"cards": [{"CardNumber": card_number, "Spotify": spotify_id}],
},
}
database["gamesets"].append(new_gameset)
is_modified = True
print(
f"Nieuwe gameset '{sku}' (naam: '{gameset_name_from_config}', taal: '{gameset_language_from_config}') en kaart {card_number} toegevoegd met Spotify ID: {spotify_id}"
)
# Sorteer de gamesets op 'sku' voor consistentie in het JSON-bestand.
database["gamesets"].sort(key=lambda x: x["sku"])
# Sorteer de kaarten binnen elke gameset op 'CardNumber'.
for gameset in database["gamesets"]:
if "cards" in gameset["gameset_data"] and gameset["gameset_data"]["cards"]:
gameset["gameset_data"]["cards"].sort(key=lambda x: x["CardNumber"])
# Genereer de gesorteerde JSON-string van de actuele databasestatus voor vergelijking.
current_database_content = json.dumps(
database, sort_keys=True, indent=4, ensure_ascii=False
)
# Bepaal of de 'updated_on' timestamp moet worden bijgewerkt.
# Dit gebeurt als:
# 1. Er een inhoudelijke wijziging is gedetecteerd (is_modified is True).
# 2. Het databasebestand voorheen leeg was (of niet bestond) en nu gevuld is.
# 3. De gesorteerde inhoud van de database is veranderd, zelfs als geen specifieke 'is_modified' vlag is getriggerd
# (bijv. door een wijziging in de sortering van bestaande data die eerder niet correct was).
if (
is_modified
or (original_database_content is None and database["gamesets"])
or (original_database_content != current_database_content)
):
database["updated_on"] = int(
time.time()
) # Werk timestamp bij naar de huidige tijd.
print(
f"Database is inhoudelijk gewijzigd of de structuur is geoptimaliseerd. 'updated_on' timestamp bijgewerkt naar {database['updated_on']}."
)
else:
print(
"Geen inhoudelijke wijziging in de database. 'updated_on' timestamp blijft ongewijzigd."
)
# Schrijf de (mogelijk bijgewerkte) database terug naar het bestand.
try:
with open(db_file, "w", encoding="utf-8") as f:
json.dump(database, f, indent=4, ensure_ascii=False)
print(f"Database opgeslagen in '{db_file}'.")
except Exception as e:
print(f"Fout bij het schrijven naar '{db_file}': {e}")
# --- Hoofdgedeelte van het script ---
if __name__ == "__main__":
# Definieer de gamesets die gescand moeten worden.
# Elke dictionary bevat:
# - "sku": De unieke identifier voor de gameset (bijv. "kerst").
# - "gameset_language": De taal van de gameset (bijv. "Netherlands").
# - "gameset_name": Een leesbare naam voor de gameset (bijv. "Kerst").
# - "total_cards": Het totale aantal kaarten in deze gameset. (bijv. 150)
# Belangrijk: Alle hier gedefinieerde sleutels zijn verplicht voor een correcte werking,
# conform de laatste specificaties.
gamesets_config = [
{
"sku": "apres-ski",
"gameset_language": "Netherlands",
"gameset_name": "Après-ski",
"total_cards": 150,
},
{
"sku": "cover",
"gameset_language": "Netherlands",
"gameset_name": "Cover of Original",
"total_cards": 150,
},
{
"sku": "disco",
"gameset_language": "Netherlands",
"gameset_name": "Disco",
"total_cards": 150,
},
{
"sku": "festival",
"gameset_language": "Netherlands",
"gameset_name": "Festival",
"total_cards": 150,
},
{
"sku": "fifties",
"gameset_language": "Netherlands",
"gameset_name": "Fifties en Sixties",
"total_cards": 150,
},
{
"sku": "films",
"gameset_language": "Netherlands",
"gameset_name": "Films en Series",
"total_cards": 150,
},
{
"sku": "hardstyle",
"gameset_language": "Netherlands",
"gameset_name": "Hardstyle",
"total_cards": 150,
},
{
"sku": "hollands",
"gameset_language": "Netherlands",
"gameset_name": "Hollandse Hits",
"total_cards": 150,
},
{
"sku": "kerst",
"gameset_language": "Netherlands",
"gameset_name": "Kerst",
"total_cards": 150,
},
{
"sku": "mix",
"gameset_language": "Netherlands",
"gameset_name": "Mix",
"total_cards": 150,
},
{
"sku": "nederhop",
"gameset_language": "Netherlands",
"gameset_name": "Nederlandse Hiphop",
"total_cards": 150,
},
{
"sku": "nineties",
"gameset_language": "Netherlands",
"gameset_name": "Nineties en Zeroes",
"total_cards": 150,
},
{
"sku": "piraat",
"gameset_language": "Netherlands",
"gameset_name": "Piratenhits",
"total_cards": 150,
},
{
"sku": "rock",
"gameset_language": "Netherlands",
"gameset_name": "Rock",
"total_cards": 150,
},
{
"sku": "seventies",
"gameset_language": "Netherlands",
"gameset_name": "Seventies en Eighties",
"total_cards": 150,
},
{
"sku": "songfestival",
"gameset_language": "Netherlands",
"gameset_name": "Songfestival",
"total_cards": 150,
},
{
"sku": "tens",
"gameset_language": "Netherlands",
"gameset_name": "Tens en Twenties",
"total_cards": 150,
},
]
# De basis-URL voor de gameset-pagina's. SKU en CardNumber worden hierin dynamisch ingevuld.
base_url = "https://joragames.nl/{sku}/{card_number}"
# Loop door elke gameset die gedefinieerd is in de configuratie.
for gameset_info in gamesets_config:
sku = gameset_info["sku"]
total_cards = gameset_info["total_cards"]
gameset_name = gameset_info["gameset_name"]
gameset_language = gameset_info["gameset_language"]
print(
f"\n--- Start verwerking voor gameset: {sku} (Naam: '{gameset_name}', Taal: '{gameset_language}', {total_cards} kaarten) ---"
)
# Loop door elk kaartnummer binnen de huidige gameset.
for i in range(1, total_cards + 1):
card_number_str = str(
i
) # Converteer het kaartnummer naar een string voor de URL.
url = base_url.format(sku=sku, card_number=card_number_str)
# Implementeer een kleine vertraging om de server niet te overbelasten en
# om geblokkeerd te worden door anti-scraping mechanismen.
time.sleep(0.1) # 100 milliseconden vertraging. Pas dit aan indien nodig.
# Haal de Spotify Track ID en gerelateerde informatie op van de huidige URL.
extracted_data = get_spotify_track_id(url)
if extracted_data:
update_gameset_database(extracted_data, gameset_name, gameset_language)
else:
pass
print(f"--- Einde verwerking voor gameset: {sku} ---")
# Finalisatie: controleer of het databasebestand is aangemaakt of bijgewerkt.
if os.path.exists("gameset_database.json"):
print(
"\n'gameset_database.json' is aanwezig en de 'updated_on' timestamp is indien nodig bijgewerkt."
)
else:
print(
"\nWaarschuwing: 'gameset_database.json' is niet aangemaakt. Controleer of er data is verwerkt en/of er fouten zijn opgetreden."
)