From 8b23166a51f08ed29005729bba302c92b2dc3964 Mon Sep 17 00:00:00 2001 From: Tontze Date: Mon, 8 Jun 2026 19:44:24 +0300 Subject: [PATCH] =?UTF-8?q?Ensimm=C3=A4inen=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + __pycache__/torrent_vahti.cpython-311.pyc | Bin 0 -> 8836 bytes app.py | 137 +++++++++++++++++ dockercompose.yml | 15 ++ dockerfile | 14 ++ git_push.sh | 16 ++ hakulista.json | 43 ++++++ requirements.txt | 4 + static/favicon.png | Bin 0 -> 36620 bytes templates/index.html | 177 ++++++++++++++++++++++ torrent_vahti.py | 155 +++++++++++++++++++ 11 files changed, 563 insertions(+) create mode 100644 .gitignore create mode 100644 __pycache__/torrent_vahti.cpython-311.pyc create mode 100644 app.py create mode 100644 dockercompose.yml create mode 100644 dockerfile create mode 100755 git_push.sh create mode 100644 hakulista.json create mode 100644 requirements.txt create mode 100755 static/favicon.png create mode 100644 templates/index.html create mode 100644 torrent_vahti.py 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 0000000000000000000000000000000000000000..b12c6ce9d187cc9046b8e77476742592cbc168e0 GIT binary patch literal 8836 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0be0d5196c1c32c0b07412ec7acdbfb05ec9b00c GIT binary patch literal 36620 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 + + + + + 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()