Lisää webhook-tuki, parannettu Spotifyn luvitus (--auth) ja palvelinkonfiguraatiot

This commit is contained in:
Toni Fadjukoff 2026-09-08 18:40:59 +00:00
parent 6f77e4580c
commit 909bd87383
4 changed files with 179 additions and 35 deletions

View file

@ -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`.

14
levyraati.nginx Normal file
View file

@ -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;
}
}

View file

@ -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()

15
levyraati.service Normal file
View file

@ -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