Ensimmäinen commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
ladatut_torrentit.txt
|
||||
*.old
|
||||
Binary file not shown.
@@ -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/<int:index>', 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)
|
||||
@@ -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"
|
||||
+14
@@ -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"]
|
||||
Executable
+16
@@ -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!"
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Flask==3.0.3
|
||||
requests==2.31.0
|
||||
beautifulsoup4==4.12.3
|
||||
lxml==5.1.0
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Torrent Vahti Ohjauspaneeli</title>
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.png') }}">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f8f9fa; font-family: system-ui, -apple-system, sans-serif; }
|
||||
.card { border: none; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
||||
.nav-tabs .nav-link { color: #495057; font-weight: 500; }
|
||||
.nav-tabs .nav-link.active { font-weight: 600; }
|
||||
#loki-sisalto, #skripti-loki {
|
||||
background-color: #212529;
|
||||
color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.85rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-dark bg-dark mb-4">
|
||||
<div class="container">
|
||||
<span class="navbar-brand mb-0 h1">🤖 Torrent Vahti Ohjauspaneeli</span>
|
||||
<span id="tila-badge" class="badge bg-secondary">Ladataan...</span>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container mb-5">
|
||||
<div class="row">
|
||||
<div class="col-100">
|
||||
<ul class="nav nav-tabs mb-4" id="paneeliTabs" role="tablist">
|
||||
<li class="nav-item"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#haut-pane" type="button">📋 Hakujen Hallinta</button></li>
|
||||
<li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#historia-pane" type="button">⏳ Lataushistoria</button></li>
|
||||
<li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#asetukset-pane" type="button">⚙️ Järjestelmäasetukset</button></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="haut-pane">
|
||||
<div class="card p-4 mb-4">
|
||||
<h5>➕ Lisää uusi haku</h5>
|
||||
<form id="lisaa-form" class="row g-3">
|
||||
<div class="col-md-6"><input type="text" id="haku-sana" class="form-control form-control-sm" placeholder="Haku" required></div>
|
||||
<input type="hidden" id="haku-regex" value=".*">
|
||||
<div class="col-md-5"><input type="text" id="haku-kansio" class="form-control form-control-sm" placeholder="Kansio" required></div>
|
||||
<div class="col-md-1"><button type="submit" class="btn btn-success btn-sm w-100">Lisää</button></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<h5>Aktiiviset haut</h5>
|
||||
<table class="table table-hover align-middle"><tbody id="hakukohteet-lista"></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="historia-pane">
|
||||
<div class="card p-4">
|
||||
<h5>Ladatut Torrentit</h5>
|
||||
<div class="d-flex gap-2 mb-3">
|
||||
<input type="text" id="historia-haku" class="form-control form-control-sm" placeholder="Suodata nimen mukaan..." oninput="paivitaNakyma()">
|
||||
<button id="tyhjenna-loki-btn" class="btn btn-outline-danger btn-sm text-nowrap">Tyhjennä historia</button>
|
||||
</div>
|
||||
<div id="loki-sisalto">Ladataan...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="asetukset-pane">
|
||||
<div class="card p-4">
|
||||
<form id="asetukset-form">
|
||||
<h6 class="fw-bold mb-3 text-primary">📡 Jackett Asetukset</h6>
|
||||
<div class="mb-3"><input type="url" id="cfg-url" class="form-control form-control-sm" placeholder="Jackett URL"></div>
|
||||
<div class="mb-4"><input type="text" id="cfg-apikey" class="form-control form-control-sm" placeholder="Jackett API-avain"></div>
|
||||
<hr class="my-4">
|
||||
<h6 class="fw-bold mb-3 text-success">🐾 qBittorrent Asetukset</h6>
|
||||
<div class="mb-2"><input type="url" id="cfg-qbiturl" class="form-control form-control-sm" placeholder="Web UI URL"></div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6"><input type="text" id="cfg-qbituser" class="form-control form-control-sm" placeholder="Käyttäjätunnus"></div>
|
||||
<div class="col-sm-6"><input type="text" id="cfg-qbitpass" class="form-control form-control-sm" placeholder="Salasana/API"></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm mt-3">Tallenna kaikki asetukset</button>
|
||||
</form>
|
||||
<hr class="mt-5">
|
||||
<h6 class="fw-bold mb-3 text-warning">🔍 Jackett Indexerit</h6>
|
||||
<div class="input-group mb-3" style="max-width: 400px;">
|
||||
<input type="text" id="uusi-indexer" class="form-control form-control-sm" placeholder="Indexer ID">
|
||||
<button type="button" onclick="lisaaIndexer()" class="btn btn-outline-secondary btn-sm">Lisää</button>
|
||||
</div>
|
||||
<ul id="indexer-lista" class="list-group mb-3" style="max-width: 400px;"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card p-4 mt-4 bg-dark text-light">
|
||||
<div class="d-flex justify-content-between"><h5>⚡ Järjestelmäloki</h5><button id="suorita-haku-btn" class="btn btn-success btn-sm">Suorita haku</button></div>
|
||||
<pre id="skripti-loki">Ladataan...</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function rullaaLokiAlas() {
|
||||
const lokit = ['loki-sisalto', 'skripti-loki'];
|
||||
lokit.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
}
|
||||
|
||||
function paivitaNakyma() {
|
||||
fetch('/status').then(res => res.json()).then(data => {
|
||||
document.getElementById('tila-badge').innerText = "Valmis";
|
||||
document.getElementById('tila-badge').className = "badge bg-success";
|
||||
|
||||
const lista = document.getElementById('hakukohteet-lista');
|
||||
lista.innerHTML = data.hakukohteet.map((k, i) => `<tr><td>${k.jackett_haku}</td><td>${k.kohdekansio}</td><td class="text-end"><button onclick="poistaKohde(${i})" class="btn btn-link text-danger btn-sm p-0">Poista</button></td></tr>`).join('');
|
||||
|
||||
const hakuTermi = document.getElementById('historia-haku').value.toLowerCase();
|
||||
const lokiDiv = document.getElementById('loki-sisalto');
|
||||
const kaikkiRivit = (data.loki_torrentit || '').split('\n').filter(r => r.trim() !== '');
|
||||
|
||||
lokiDiv.innerHTML = kaikkiRivit
|
||||
.filter(r => r.toLowerCase().includes(hakuTermi))
|
||||
.map((r) => {
|
||||
return `<div class="d-flex justify-content-between border-bottom py-1">
|
||||
<span class="font-monospace small text-truncate" style="max-width: 80%;" title="${r}">${r}</span>
|
||||
<button onclick="poistaHist('${r.replace(/'/g, "\\'")}')" class="btn btn-outline-danger btn-sm py-0">Poista</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const iList = document.getElementById('indexer-lista');
|
||||
iList.innerHTML = (data.indeksorit || []).map((idx, i) => `<li class="list-group-item d-flex justify-content-between py-1">${idx}<button onclick="poistaIndexer(${i})" class="btn btn-danger btn-sm py-0">Poista</button></li>`).join('');
|
||||
|
||||
document.getElementById('skripti-loki').innerText = data.loki_skripti;
|
||||
document.getElementById('cfg-url').value = data.asetukset.jackett_url || '';
|
||||
document.getElementById('cfg-apikey').value = data.asetukset.jackett_api_key || '';
|
||||
document.getElementById('cfg-qbiturl').value = data.asetukset.qbit_url || '';
|
||||
document.getElementById('cfg-qbituser').value = data.asetukset.qbit_user || '';
|
||||
document.getElementById('cfg-qbitpass').value = data.asetukset.qbit_pass || '';
|
||||
|
||||
rullaaLokiAlas();
|
||||
});
|
||||
}
|
||||
|
||||
function poistaHist(riviTeksti) {
|
||||
if(confirm("Poistetaanko tämä rivi historiasta?")) {
|
||||
fetch('/poista_historiarivi', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ rivi: riviTeksti })
|
||||
}).then(() => paivitaNakyma());
|
||||
}
|
||||
}
|
||||
|
||||
function lisaaIndexer() { const val = document.getElementById('uusi-indexer').value; if(!val) return; fetch('/status').then(r => r.json()).then(d => { let l = d.indeksorit || []; l.push(val); fetch('/paivita_indeksorit', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({indeksorit:l})}).then(() => paivitaNakyma()); }); }
|
||||
function poistaIndexer(i) { fetch('/status').then(r => r.json()).then(d => { let l = d.indeksorit; l.splice(i, 1); fetch('/paivita_indeksorit', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({indeksorit:l})}).then(() => paivitaNakyma()); }); }
|
||||
function poistaKohde(i) { fetch(`/poista_kohde/${i}`, {method:'DELETE'}).then(() => paivitaNakyma()); }
|
||||
|
||||
document.getElementById('lisaa-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
fetch('/lisaa_kohde', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({
|
||||
jackett_haku: document.getElementById('haku-sana').value,
|
||||
regex_suodatin: document.getElementById('haku-regex').value,
|
||||
kohdekansio: document.getElementById('haku-kansio').value
|
||||
})
|
||||
}).then(() => { e.target.reset(); paivitaNakyma(); });
|
||||
});
|
||||
document.getElementById('asetukset-form').addEventListener('submit', (e) => { e.preventDefault(); fetch('/tallenna_asetukset', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({jackett_url:document.getElementById('cfg-url').value, jackett_api_key:document.getElementById('cfg-apikey').value, qbit_url:document.getElementById('cfg-qbiturl').value, qbit_user:document.getElementById('cfg-qbituser').value, qbit_pass:document.getElementById('cfg-qbitpass').value})}).then(() => paivitaNakyma()); });
|
||||
document.getElementById('suorita-haku-btn').addEventListener('click', () => fetch('/suorita_haku', {method:'POST'}));
|
||||
document.getElementById('tyhjenna-loki-btn').addEventListener('click', () => fetch('/tyhjenna_loki', {method:'POST'}).then(() => paivitaNakyma()));
|
||||
|
||||
paivitaNakyma(); setInterval(paivitaNakyma, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user