This commit is contained in:
commit
19b90bb4c7
3 files changed
+450
No files matched your search
@@ -0,0 +1,33 @@
|
||||
name: update gameset_database.json
|
||||
|
||||
on:
|
||||
push:
|
||||
schedule:
|
||||
- cron: "0 0 1 */1 *"
|
||||
|
||||
jobs:
|
||||
Check_for_update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install python3-pip
|
||||
run: |
|
||||
apt update
|
||||
apt install python3-pip -y
|
||||
- name: install requirements
|
||||
run: |
|
||||
pip3 install -r requirements.txt
|
||||
- name: update database
|
||||
run: |
|
||||
python3 update.py
|
||||
- name: git commit
|
||||
run: |
|
||||
git config user.name "${{ secrets.GIT_USERNAME }}"
|
||||
git config user.email "${{ secrets.GIT_USEREMAIL }}"
|
||||
mtime=$(stat -c "%y" ./gameset_database.json)
|
||||
git add .
|
||||
export GIT_COMMITTER_DATE=$mtime
|
||||
export GIT_AUTHOR_DATE=$mtime
|
||||
version=$(date -d "${mtime}" +%Y-%m-%d)
|
||||
if [ -n "$(git diff --staged)" ]; then git commit --date "$mtime" -m "update $version" && git push; fi
|
||||
@@ -0,0 +1 @@
|
||||
requests
|
||||
@@ -0,0 +1,416 @@
|
||||
#
|
||||
# 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": "festival",
|
||||
"gameset_language": "Netherlands",
|
||||
"gameset_name": "Festival",
|
||||
"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": "nineties",
|
||||
"gameset_language": "Netherlands",
|
||||
"gameset_name": "Nineties en Zeroes",
|
||||
"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."
|
||||
)
|
||||
Reference in new issue
Block a user