From 909bd87383d2182dfbc7c9ec5134210b4b84b58b Mon Sep 17 00:00:00 2001 From: Toni Fadjukoff Date: Tue, 8 Sep 2026 18:40:59 +0000 Subject: [PATCH] =?UTF-8?q?Lis=C3=A4=C3=A4=20webhook-tuki,=20parannettu=20?= =?UTF-8?q?Spotifyn=20luvitus=20(--auth)=20ja=20palvelinkonfiguraatiot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 72 +++++++++++++++++------------ levyraati.nginx | 14 ++++++ levyraati.py | 113 +++++++++++++++++++++++++++++++++++++++++++--- levyraati.service | 15 ++++++ 4 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 levyraati.nginx create mode 100644 levyraati.service diff --git a/README.md b/README.md index f141638..7844319 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # Wiki-to-Spotify Soittolistan Päivittäjä 🎵 -Tämä Python-skripti hakee automaattisesti kuluvan viikon biisivalinnat MediaWiki-sivulta ja päivittää ne haluttuun Spotifyn soittolistaan. Skripti on suunniteltu ajettavaksi automaattisesti taustalla (esim. Cronilla). +Tämä Python-skripti hakee automaattisesti kuluvan viikon biisivalinnat MediaWiki-sivulta ja päivittää ne haluttuun Spotifyn soittolistaan. ## Ominaisuudet +* **Kaksi toimintatilaa:** + 1. **Komentorivi:** Hakee biisit kerran ja lopettaa (sopii Cron-ajoon). + 2. **Webhook-palvelin:** Kuuntelee (Flask/Gunicorn) ilmoituksia Wikistä ja päivittää soittolistan reaaliajassa kun sivu tallennetaan. * Kirjautuu MediaWiki-rajapintaan ja lukee kuluvan viikon (esim. `== Viikko 10 ==`) sisällön. * Etsii tekstistä suorat Spotify-linkit. * Jättää huomiotta muiden palveluiden (esim. YouTube) linkit. @@ -19,21 +22,14 @@ Tämä Python-skripti hakee automaattisesti kuluvan viikon biisivalinnat MediaWi **1. Lataa koodi ja luo virtuaaliympäristö** ```bash -# Luo virtuaaliympäristö uv:lla uv venv - -# Aktivoi ympäristö (Mac/Linux) source .venv/bin/activate -# TAI (Windows) -.venv\Scripts\activate - -# Asenna riippuvuudet -uv pip install spotipy requests python-dotenv +uv pip install -r requirements.txt # Tai asenna manuaalisesti: spotipy requests python-dotenv flask gunicorn ``` **2. Asetukset** -Luo projektin juureen tiedosto nimeltä `.env` (voit kopioida `.env.example` -tiedoston pohjaksi) ja täytä sinne omat tietosi: +Luo tiedosto nimeltä `.env` (voit kopioida `.env.example` -tiedoston pohjaksi) ja täytä sinne omat tietosi: ```env SPOTIPY_CLIENT_ID="sinun_spotify_client_id" @@ -45,31 +41,51 @@ WIKI_USERNAME="wiki_tunnuksesi" WIKI_PASSWORD="wiki_salasanasi" WIKI_API_URL="https://sinun-wikisi.fi/api.php" WIKI_PAGE_TITLE="Sivun_Nimi" + +# Valinnainen webhookin suojaus +WEBHOOK_SECRET="valitse_oma_salasana" ``` -**3. Ensimmäinen käynnistys ja Spotifyn luvitus** +## Toimintatila 1: Komentorivi (Cron) -Ensimmäisellä ajokerralla skriptin täytyy saada sinulta lupa muokata soittolistaasi. - -Aja skripti terminaalissa: +Aja skripti kerran: ```bash -python skripti.py +python levyraati.py ``` -* Skripti tulostaa terminaaliin pitkän Spotify-linkin. -* Kopioi linkki ja avaa se selaimessasi. -* Kirjaudu sisään Spotifyhin ja hyväksy luvat. -* Selaimesi ohjautuu tyhjälle sivulle (localhost). Kopioi tuon sivun osoiterivillä oleva koko URL ja liitä se takaisin terminaaliin. -* Projektikansioosi syntyy `.cache` -tiedosto, joka pitää sinut jatkossa kirjautuneena. **Älä jaa tätä tiedostoa eteenpäin!** - -## Automatisointi (Linux/Mac) - -Voit asettaa skriptin pyörimään automaattisesti esimerkiksi kerran päivässä Cron-työkalulla: +Voit asettaa tämän ajumaan Cron-työkalulla esimerkiksi klo 04:00: ```bash -crontab -e +0 4 * * * cd /polku/projektiin && .venv/bin/python levyraati.py >> loki.log 2>&1 ``` -Lisää tiedoston loppuun (muista muuttaa oikea polku): + +## Toimintatila 2: Webhook-palvelin (Nginx + Gunicorn) + +Tämä tila mahdollistaa reaaliaikaisen päivityksen kun Wikissä muokataan sivua. + +### 1. Testaus manuaalisesti ```bash -0 4 * * * cd /polku/projektiin && .venv/bin/python skripti.py >> skripti_loki.log 2>&1 +python levyraati.py --server ``` -Tämä ajaa päivityksen joka aamu klo 04:00 ja tallentaa tulosteet `skripti_loki.log` -tiedostoon. +Palvelin kuuntelee portissa 5000. Webhook-osoite on `http://osoite:5000/webhook?secret=salainen_avain`. + +### 2. Tuotantoasennus (Systemd) +1. Kopioi `levyraati.service` hakemistoon `/etc/systemd/system/`. +2. `sudo systemctl daemon-reload` +3. `sudo systemctl enable --now levyraati` + +### 3. Nginx-konfiguraatio +1. Kopioi `levyraati.nginx` hakemistoon `/etc/nginx/sites-available/`. +2. Aktivoi ja käynnistä uudelleen: `sudo ln -s ... /etc/nginx/sites-enabled/ && sudo systemctl restart nginx` + +## Spotifyn luvitus + +Ensimmäisellä käyttökerralla (tai jos kirjautuminen vanhenee / token mitätöidään): +1. Aja terminaalissa: + ```bash + python levyraati.py --auth + ``` +2. Skripti tulostaa Spotify-linkin. Avaa se selaimessasi ja hyväksy luvat. +3. Kopioi selaimen osoiterivillä oleva koko URL ja liitä se takaisin terminaaliin. +4. `.cache` -tiedosto syntyy ja pitää sinut kirjautuneena jatkossa. + +Jos soittolistan päivityksessä tulee virhe `error: invalid_grant, error_description: Refresh token revoked`, aja uudelleen `python levyraati.py --auth`. diff --git a/levyraati.nginx b/levyraati.nginx new file mode 100644 index 0000000..0d933a1 --- /dev/null +++ b/levyraati.nginx @@ -0,0 +1,14 @@ +server { + listen 80; + server_name _; # Vaihda tähän domainisi tai palvelimen IP-osoite + + location /webhook { + include proxy_params; + proxy_pass http://unix:/run/levyraati/levyraati.sock; + } + + # Kaikki muut pyynnöt palauttavat 404 + location / { + return 404; + } +} diff --git a/levyraati.py b/levyraati.py index 75f10b8..154b70d 100644 --- a/levyraati.py +++ b/levyraati.py @@ -3,11 +3,16 @@ import re import datetime import requests import spotipy +import sys from spotipy.oauth2 import SpotifyOAuth from dotenv import load_dotenv +from flask import Flask, request, jsonify # --- KONFIGURAATIO --- +# Määritetään .cache-tiedoston polku aina skriptin omaan kansioon +CACHE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".cache") + def lataa_asetukset(): """Lataa ympäristömuuttujat .env-tiedostosta.""" load_dotenv() @@ -20,6 +25,7 @@ def lataa_asetukset(): "WIKI_PAGE_TITLE": os.getenv("WIKI_PAGE_TITLE"), "WIKI_USERNAME": os.getenv("WIKI_USERNAME"), "WIKI_PASSWORD": os.getenv("WIKI_PASSWORD"), + "WEBHOOK_SECRET": os.getenv("WEBHOOK_SECRET", "salainen_avain"), # Lisätty turva } # --- WIKI-YHTEYS --- @@ -130,7 +136,7 @@ def paivita_spotify_soittolista(linkit, asetukset): """Päivittää Spotify-soittolistan annetuilla linkeillä.""" if not linkit: print("\nEi päivitettäviä linkkejä. Soittolistaa ei muutettu.") - return False + return False, "Ei linkkejä" scope = "playlist-modify-private playlist-modify-public" try: @@ -139,15 +145,78 @@ def paivita_spotify_soittolista(linkit, asetukset): client_secret=asetukset["SPOTIPY_CLIENT_SECRET"], redirect_uri=asetukset["SPOTIPY_REDIRECT_URI"], scope=scope, + cache_path=CACHE_PATH, open_browser=False )) # Spotify sallii enintään 100 kappaletta per kutsu sp.playlist_replace_items(asetukset["PLAYLIST_ID"], linkit[:100]) print(f"Soittolista päivitetty onnistuneesti ({len(linkit[:100])} kappaletta).") + return True, f"Päivitetty {len(linkit[:100])} kappaletta" + except Exception as e: + err_msg = str(e) + if "invalid_grant" in err_msg or "Refresh token revoked" in err_msg: + print("\n[!] Spotifyn luvitus on vanhentunut tai mitätöity (Refresh token revoked).") + print(" Suorita uudelleenluvitus ajamalla terminaalissa:") + print(" source .venv/bin/activate && python levyraati.py --auth\n") + else: + print(f"Virhe Spotifyn päivityksessä: {e}") + return False, err_msg + +def suorita_luvitus(asetukset): + """Suorittaa Spotifyn OAuth-luvituksen interaktiivisesti terminaalissa.""" + scope = "playlist-modify-private playlist-modify-public" + auth_manager = SpotifyOAuth( + client_id=asetukset["SPOTIPY_CLIENT_ID"], + client_secret=asetukset["SPOTIPY_CLIENT_SECRET"], + redirect_uri=asetukset["SPOTIPY_REDIRECT_URI"], + scope=scope, + cache_path=CACHE_PATH, + open_browser=False + ) + + # Poistetaan aiempi vanhentunut cache-tiedosto + if os.path.exists(CACHE_PATH): + try: + os.remove(CACHE_PATH) + print(f"Poistettu vanha luvitustiedosto ({CACHE_PATH}).") + except OSError as e: + print(f"Huom: vanhan luvitustiedoston poisto epäonnistui: {e}") + + auth_url = auth_manager.get_authorize_url() + print("\n=================== SPOTIFYN LUVITUS ===================") + print("1. Avaa seuraava osoite selaimessasi:\n") + print(f" {auth_url}\n") + print("2. Kirjaudu Spotifyhin ja hyväksy sovelluksen pyytämät oikeudet.") + print("3. Selain ohjautuu osoitteeseen:") + print(f" {asetukset['SPOTIPY_REDIRECT_URI']}?code=...") + print(" (Vaikka selain näyttäisi virhesivua kuten 404, osoite on oikea!)") + print("4. Kopioi selaimen osoiteriviltä koko URL ja liitä se alle.") + print("========================================================\n") + + try: + uudelleenohjattu_url = input("Liitä uudelleenohjattu URL tähän: ").strip() + if not uudelleenohjattu_url: + print("Virhe: URL oli tyhjä.") + return False + + code = auth_manager.parse_response_code(uudelleenohjattu_url) + auth_manager.get_access_token(code, as_dict=False, check_cache=False) + print(f"\n[+] Token tallennettu onnistuneesti tiedostoon {CACHE_PATH}!") + + # Testataan yhteys + sp = spotipy.Spotify(auth_manager=auth_manager) + kayttaja = sp.me() + print(f"[+] Spotify-yhteys toimii! Kirjautunut käyttäjä: {kayttaja.get('display_name')} ({kayttaja.get('id')})") + + if asetukset.get("PLAYLIST_ID"): + soittolista = sp.playlist(asetukset["PLAYLIST_ID"]) + print(f"[+] Pääsy soittolistaan kunnossa: '{soittolista.get('name')}'") + + print("\nValmista! Taustapalvelin ja cron voivat nyt päivittää soittolistaa.") return True except Exception as e: - print(f"Virhe Spotifyn päivityksessä: {e}") + print(f"\n[-] Virhe luvituksessa: {e}") return False # --- PÄÄOHJELMA --- @@ -166,21 +235,51 @@ def aja_paivitys(): ) if not wiki.kirjaudu(): - return + return False, "Kirjautuminen epäonnistui" wiki_teksti = wiki.hae_sivun_sisalto(asetukset["WIKI_PAGE_TITLE"]) if not wiki_teksti: - return + return False, "Sivun haku epäonnistui" # 2. Oikean viikon osion eristäminen viikko_teksti = erista_viikon_osio(wiki_teksti, nykyinen_viikko) if not viikko_teksti: print(f"Huom: Viikon {nykyinen_viikko:02d} osiota ei löytynyt sivulta.") - return + return False, f"Viikon {nykyinen_viikko} osiota ei löytynyt" # 3. Linkkien kerääminen ja Spotifyn päivittäminen spotify_linkit = poimi_spotify_linkit(viikko_teksti) - paivita_spotify_soittolista(spotify_linkit, asetukset) + success, msg = paivita_spotify_soittolista(spotify_linkit, asetukset) + return success, msg + +# --- FLASK PALVELIN --- + +app = Flask(__name__) + +@app.route('/webhook', methods=['POST']) +def webhook(): + asetukset = lataa_asetukset() + + # Yksinkertainen turvatarkistus: vaaditaan salainen avain otsakkeessa tai parametrina + secret = request.args.get('secret') or request.headers.get('X-Webhook-Secret') + if secret != asetukset["WEBHOOK_SECRET"]: + return jsonify({"status": "error", "message": "Invalid secret"}), 403 + + print("Webhook vastaanotettu! Käynnistetään päivitys...") + success, msg = aja_paivitys() + + if success: + return jsonify({"status": "success", "message": msg}), 200 + else: + return jsonify({"status": "error", "message": msg}), 500 if __name__ == '__main__': - aja_paivitys() + if len(sys.argv) > 1 and sys.argv[1] == '--server': + print("Käynnistetään Webhook-palvelin portissa 5000...") + # Huom: Tuotannossa käytä WSGI-palvelinta (esim. gunicorn) + app.run(host='0.0.0.0', port=5000) + elif len(sys.argv) > 1 and sys.argv[1] in ('--auth', '--reauth'): + asetukset = lataa_asetukset() + suorita_luvitus(asetukset) + else: + aja_paivitys() diff --git a/levyraati.service b/levyraati.service new file mode 100644 index 0000000..6b89fcc --- /dev/null +++ b/levyraati.service @@ -0,0 +1,15 @@ +[Unit] +Description=Gunicorn instance to serve levyraati webhook +After=network.target + +[Service] +User=lamperi +Group=www-data +RuntimeDirectory=levyraati +WorkingDirectory=/home/lamperi/git-repos/e4-scriptit +Environment="PATH=/home/lamperi/git-repos/e4-scriptit/.venv/bin" +# Ladataan myös .env tiedosto jos tarpeen, mutta python-dotenv hoitaa sen jo +ExecStart=/home/lamperi/git-repos/e4-scriptit/.venv/bin/gunicorn --workers 3 --bind unix:/run/levyraati/levyraati.sock -m 007 levyraati:app + +[Install] +WantedBy=multi-user.target