commit 8b23166a51f08ed29005729bba302c92b2dc3964 Author: Tontze Date: Mon Jun 8 19:44:24 2026 +0300 Ensimmäinen commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93b1c7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +ladatut_torrentit.txt +*.old diff --git a/__pycache__/torrent_vahti.cpython-311.pyc b/__pycache__/torrent_vahti.cpython-311.pyc new file mode 100644 index 0000000..b12c6ce Binary files /dev/null and b/__pycache__/torrent_vahti.cpython-311.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000..9cfaca0 --- /dev/null +++ b/app.py @@ -0,0 +1,137 @@ +from flask import Flask, render_template, jsonify, request +import subprocess +import threading +import time +import os +import logging +import torrent_vahti + +app = Flask(__name__) + +log = logging.getLogger('werkzeug') +log.setLevel(logging.ERROR) + +vahti_prosessia = None +loki_sisalto = [] + +def lue_lokit(process): + global loki_sisalto + for line in iter(process.stdout.readline, ''): + if line: + loki_sisalto.append(line) + if len(loki_sisalto) > 500: + loki_sisalto.pop(0) + +def suorita_haku_prosessi(): + global vahti_prosessia + if vahti_prosessia is None or vahti_prosessia.poll() is not None: + loki_sisalto.append(f"\n--- Haku käynnistetty: {time.strftime('%H:%M:%S')} ---\n") + vahti_prosessia = subprocess.Popen( + ['python', '-u', 'torrent_vahti.py'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True + ) + t = threading.Thread(target=lue_lokit, args=(vahti_prosessia,)) + t.daemon = True + t.start() + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/status') +def status(): + kaikki_data = torrent_vahti.lataa_kaikki_data() + loki_teksti = ''.join(loki_sisalto) + try: + with open(torrent_vahti.LOKI_TIEDOSTO, "r", encoding="utf-8") as f: + loki_torrentit = f.read() + except: + loki_torrentit = "Historiaa ei löytynyt." + + return jsonify({ + 'status': 'ok', + 'hakukohteet': kaikki_data.get("hakukohteet", []), + 'asetukset': kaikki_data.get("asetukset", {}), + 'indeksorit': kaikki_data.get("indeksorit", []), + 'loki_skripti': loki_teksti, + 'loki_torrentit': loki_torrentit + }) + +@app.route('/tallenna_asetukset', methods=['POST']) +def tallenna_asetukset(): + data = request.json + kaikki_data = torrent_vahti.lataa_kaikki_data() + kaikki_data["asetukset"] = { + "jackett_url": data.get('jackett_url', ''), + "jackett_api_key": data.get('jackett_api_key', ''), + "qbit_url": data.get('qbit_url', ''), + "qbit_user": data.get('qbit_user', 'admin'), + "qbit_pass": data.get('qbit_pass', '') + } + torrent_vahti.tallenna_kaikki_data(kaikki_data) + return jsonify({'status': 'ok'}) + +@app.route('/paivita_indeksorit', methods=['POST']) +def paivita_indeksorit(): + uusi_lista = request.json.get("indeksorit", []) + kaikki_data = torrent_vahti.lataa_kaikki_data() + kaikki_data["indeksorit"] = uusi_lista + torrent_vahti.tallenna_kaikki_data(kaikki_data) + return jsonify({'status': 'ok'}) + +@app.route('/lisaa_kohde', methods=['POST']) +def lisaa_kohde(): + data = request.json + kaikki_data = torrent_vahti.lataa_kaikki_data() + kaikki_data["hakukohteet"].append({ + "jackett_haku": data['jackett_haku'], + "kohdekansio": data['kohdekansio'] + }) + torrent_vahti.tallenna_kaikki_data(kaikki_data) + return jsonify({'status': 'ok'}) + +@app.route('/poista_kohde/', methods=['DELETE']) +def poista_kohde(index): + kaikki_data = torrent_vahti.lataa_kaikki_data() + if 0 <= index < len(kaikki_data["hakukohteet"]): + kaikki_data["hakukohteet"].pop(index) + torrent_vahti.tallenna_kaikki_data(kaikki_data) + return jsonify({'status': 'ok'}) + +@app.route('/poista_historiarivi', methods=['POST']) +def poista_historiarivi(): + data = request.json + rivi_poistettavaksi = data.get('rivi') + if os.path.exists(torrent_vahti.LOKI_TIEDOSTO): + with open(torrent_vahti.LOKI_TIEDOSTO, "r", encoding="utf-8") as f: + rivit = f.readlines() + + uudet_rivit = [r for r in rivit if r.strip() != rivi_poistettavaksi.strip()] + + with open(torrent_vahti.LOKI_TIEDOSTO, "w", encoding="utf-8") as f: + f.writelines(uudet_rivit) + return jsonify({'status': 'ok'}) + return jsonify({'status': 'error'}), 404 + +@app.route('/suorita_haku', methods=['POST']) +def suorita_haku(): + suorita_haku_prosessi() + return jsonify({'status': 'ok'}) + +@app.route('/tyhjenna_loki', methods=['POST']) +def tyhjenna_loki(): + if os.path.exists(torrent_vahti.LOKI_TIEDOSTO): + os.remove(torrent_vahti.LOKI_TIEDOSTO) + return jsonify({'status': 'ok'}) + +if __name__ == '__main__': + def automaattinen_ajastin(): + while True: + time.sleep(900) + suorita_haku_prosessi() + + ajastin_saie = threading.Thread(target=automaattinen_ajastin) + ajastin_saie.daemon = True + ajastin_saie.start() + + app.run(host='0.0.0.0', port=5000) diff --git a/dockercompose.yml b/dockercompose.yml new file mode 100644 index 0000000..fbf169d --- /dev/null +++ b/dockercompose.yml @@ -0,0 +1,15 @@ +services: + torrent-vahti: + image: python:3.11-slim + container_name: torrent-vahti-ajo + restart: unless-stopped + volumes: + - /mnt/media/admin/motorsports:/app + - /mnt/media/media/Downloads/torrent:/watch + working_dir: /app + environment: + - TZ=Europe/Helsinki + # LISÄTTY lxml PAKETTI PIP INSTALLIIN: + command: > + sh -c "pip install --no-cache-dir requests beautifulsoup4 lxml && + while true; do python torrent_vahti.py; sleep 1800; done" diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..0206373 --- /dev/null +++ b/dockerfile @@ -0,0 +1,14 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY templates/ ./templates/ +COPY torrent_vahti.py . +COPY app.py . + +EXPOSE 5000 + +CMD ["python", "app.py"] diff --git a/git_push.sh b/git_push.sh new file mode 100755 index 0000000..15da306 --- /dev/null +++ b/git_push.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# 1. Lisätään kaikki muutetut tiedostot +git add . + +# 2. Kysytään commit-viestiä käyttäjältä +echo "Syötä commit-kommentti:" +read commit_viesti + +# 3. Tehdään commit +git commit -m "$commit_viesti" + +# 4. Pushataan muutokset +git push + +echo "Päivitys valmis!" diff --git a/hakulista.json b/hakulista.json new file mode 100644 index 0000000..1cc125a --- /dev/null +++ b/hakulista.json @@ -0,0 +1,43 @@ +{ + "indeksorit": [ + "knaben", + "torrentgalaxyclone", + "therarbg", + "thepiratebay", + "torrentdownload", + "limetorrents" + ], + "hakukohteet": [ + { + "jackett_haku": "motogp 2026 720p jff", + "regex_suodatin": "motogp.*?2026.*?720p.*?jff.*?", + "kohdekansio": "MotoGP" + }, + { + "jackett_haku": "motogp 2026 tntsportshd 1080p", + "regex_suodatin": "motogp.*?2026.*?tntsportshd.*?1080p.*?", + "kohdekansio": "MotoGP" + }, + { + "jackett_haku": "Formula 1 2026 1080p SS", + "regex_suodatin": "\tFormula.*?1.*?2026.*?1080p.*?SS.*?", + "kohdekansio": "Formula1" + }, + { + "jackett_haku": "Formula 1 2026 SkyF1HD 1080p", + "regex_suodatin": "(?i)Formula.?1.*2026.*SkyF1HD.*1080p", + "kohdekansio": "Formula1" + }, + { + "jackett_haku": "Rio hamasaki", + "kohdekansio": "All" + } + ], + "asetukset": { + "jackett_url": "http://192.168.1.40:9117", + "jackett_api_key": "gtqa5zn9ik93jfyj48y29ot3fayawvo6", + "qbit_url": "http://192.168.1.40:8080/", + "qbit_user": "admin", + "qbit_pass": "qbt_uFxc7gqD7sJsBk9FJdH38bLIyapr" + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f1ad53e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask==3.0.3 +requests==2.31.0 +beautifulsoup4==4.12.3 +lxml==5.1.0 diff --git a/static/favicon.png b/static/favicon.png new file mode 100755 index 0000000..0be0d51 Binary files /dev/null and b/static/favicon.png differ diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..3b17eb7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,177 @@ + + + + + + Torrent Vahti Ohjauspaneeli + + + + + + +
+
+
+ +
+
+
+
➕ Lisää uusi haku
+
+
+ +
+
+
+
+
+
Aktiiviset haut
+
+
+
+
+
+
Ladatut Torrentit
+
+ + +
+
Ladataan...
+
+
+
+
+
+
📡 Jackett Asetukset
+
+
+
+
🐾 qBittorrent Asetukset
+
+
+
+
+
+ +
+
+
🔍 Jackett Indexerit
+
+ + +
+
    +
    +
    +
    +
    +
    ⚡ Järjestelmäloki
    +
    Ladataan...
    +
    +
    +
    +
    + + + + diff --git a/torrent_vahti.py b/torrent_vahti.py new file mode 100644 index 0000000..43d3559 --- /dev/null +++ b/torrent_vahti.py @@ -0,0 +1,155 @@ +import os +import re +import time +import requests +import json +from bs4 import BeautifulSoup + +SKRIPTIN_KANSIO = os.path.dirname(os.path.abspath(__file__)) +LOKI_TIEDOSTO = os.path.join(SKRIPTIN_KANSIO, "ladatut_torrentit.txt") +JSON_TIEDOSTO = os.path.join(SKRIPTIN_KANSIO, "hakulista.json") + +def lataa_kaikki_data(): + oletus_data = { + "asetukset": { + "jackett_url": "http://192.168.1.40:9117", + "jackett_api_key": "", + "qbit_url": "http://172.16.0.1:8080", + "qbit_user": "admin", + "qbit_pass": "" + }, + "indeksorit": ["knaben", "torrentgalaxyclone", "therarbg", "uindex"], + "hakukohteet": [] + } + + if os.path.exists(JSON_TIEDOSTO): + try: + with open(JSON_TIEDOSTO, "r", encoding="utf-8") as f: + data = json.load(f) + if "asetukset" not in data: + data["asetukset"] = oletus_data["asetukset"] + return data + except Exception as e: + print(f"Virhe JSON-luvussa: {e}") + + tallenna_kaikki_data(oletus_data) + return oletus_data + +def tallenna_kaikki_data(data): + with open(JSON_TIEDOSTO, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4, ensure_ascii=False) + +def lataa_jo_ladatut(): + if os.path.exists(LOKI_TIEDOSTO): + with open(LOKI_TIEDOSTO, "r", encoding="utf-8") as f: + return set(line.strip() for line in f if line.strip()) + return set() + +def tallenna_lokiin(linkki): + with open(LOKI_TIEDOSTO, "a", encoding="utf-8") as f: + f.write(linkki + "\n") + +def hae_jackettista(jackett_url, api_key, indeksori, hakusana): + url = f"{jackett_url}/api/v2.0/indexers/{indeksori}/results/torznab/api" + params = {"apikey": api_key, "t": "search", "q": hakusana} + try: + res = requests.get(url, params=params, timeout=15) + res.raise_for_status() + soup = BeautifulSoup(res.text, 'xml') + + loydetyt = [] + for item in soup.find_all('item'): + title = item.find('title') + title_text = title.text if title else "" + + enclosure = item.find('enclosure', url=True) + linkki = enclosure['url'] if enclosure and enclosure['url'].startswith('magnet:') else None + if not linkki: + link_tag = item.find('link') + if link_tag and link_tag.text.startswith('magnet:'): + linkki = link_tag.text + + if linkki and title_text: + loydetyt.append((title_text, linkki)) + return loydetyt + except Exception as e: + print(f" [Jackett Virhe sivustolla {indeksori}] {e}") + return [] + +def qbit_lisaa_torrent_api_keylla(qbit_url, api_key, magnet_linkki, kategoria): + pohja_url = qbit_url.rstrip('/') + add_url = f"{pohja_url}/api/v2/torrents/add" + + headers = { + "X-API-Key": api_key, + "Authorization": f"Bearer {api_key}", + "Referer": f"{pohja_url}/" + } + + data = { + "urls": magnet_linkki, + "category": kategoria + } + + try: + res = requests.post(add_url, data=data, headers=headers, timeout=10) + return res.status_code + except Exception as e: + print(f" [qBittorrent] Yhteysvirhe: {e}") + return 500 + +def tarkista_ja_lataa(): + print(f"--- ALOITETAAN ERITELTY JACKETT & QBITTORRENT API-HAKU ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---") + data = lataa_kaikki_data() + ladatut = lataa_jo_ladatut() + + jackett_url = data["asetukset"].get("jackett_url", "") + api_key = data["asetukset"].get("jackett_api_key", "") + qbit_url = data["asetukset"].get("qbit_url", "") + qbit_api_key = data["asetukset"].get("qbit_pass", "") + + indeksori_lista = data["indeksorit"] + hakulista = data["hakukohteet"] + + if not api_key or not qbit_url or not qbit_api_key: + print(" [Virhe] Asetuksia puuttuu!") + return + + for kohde in hakulista: + haku = kohde["jackett_haku"] + hakusanat = haku.lower().split() + kategoria = kohde["kohdekansio"].strip() + + kaikki_tulokset = [] + for idx in indeksori_lista: + tulokset = hae_jackettista(jackett_url, api_key, idx, haku) + kaikki_tulokset.extend(tulokset) + + uudet_kohteet = [] + for nimi, linkki in list(set(kaikki_tulokset)): + if linkki in ladatut: continue + + nimi_lower = nimi.lower() + viimeisin_indeksi = -1 + loytyy_kaikki = True + + for sana in hakusanat: + indeksi = nimi_lower.find(sana, viimeisin_indeksi + 1) + if indeksi == -1: + loytyy_kaikki = False + break + viimeisin_indeksi = indeksi + + if loytyy_kaikki: + uudet_kohteet.append((nimi, linkki)) + + for nimi, linkki in uudet_kohteet: + status = qbit_lisaa_torrent_api_keylla(qbit_url, qbit_api_key, linkki, kategoria) + if status == 200 or status == 409: + tallenna_lokiin(linkki) + print(f" -> KÄSITELTY: {nimi}") + + print("--- HAKUKIERROS VALMIS ---\n") + +if __name__ == "__main__": + tarkista_ja_lataa()