Compare commits
5 Commits
codex/add-
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
| 6116e9171d | |||
| 44fbe49c58 | |||
| 9c37411f7a | |||
| 5c9c35d10d | |||
| 3e6c541b53 |
2
.env
2
.env
@@ -1,5 +1,5 @@
|
|||||||
TZ=Europe/Berlin
|
TZ=Europe/Berlin
|
||||||
WEBGUI_PORT=8080
|
WEBGUI_PORT=8081
|
||||||
|
|
||||||
# Optional Basic Auth (leave empty to disable)
|
# Optional Basic Auth (leave empty to disable)
|
||||||
BASIC_AUTH_USER=admin
|
BASIC_AUTH_USER=admin
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ Danach:
|
|||||||
1) `.env.example` -> `.env` kopieren und Werte setzen.
|
1) `.env.example` -> `.env` kopieren und Werte setzen.
|
||||||
2) SSH Key ablegen: `data/ssh/id_ed25519` (chmod 600)
|
2) SSH Key ablegen: `data/ssh/id_ed25519` (chmod 600)
|
||||||
3) `docker compose up -d --build`
|
3) `docker compose up -d --build`
|
||||||
4) WebUI: `http://<host>:8080`
|
4) WebUI: `http://<host>:8081`
|
||||||
|
|
||||||
## Proxies
|
## Proxies
|
||||||
- Proxies werden **nur** an yt-dlp/aria2 übergeben (pro Job), beeinflussen also nicht SFTP/Jellyfin.
|
- Proxies werden **nur** an yt-dlp/aria2 übergeben (pro Job), beeinflussen also nicht SFTP/Jellyfin.
|
||||||
- `PROXY_LIST` enthält eine Zeile pro Proxy: `socks5://IP:PORT`, `http://IP:PORT`, ...
|
- `PROXY_LIST` enthält eine Zeile pro Proxy: `socks5://IP:PORT`, `http://IP:PORT`, ...
|
||||||
|
- Die Proxy-Listen werden 2× täglich aus den TheSpeedX-Quellen geladen und ins richtige Format gebracht.
|
||||||
|
|
||||||
## Hoster-Engine
|
## Hoster-Engine
|
||||||
- Engine `hoster` nutzt **aria2c** und akzeptiert zusätzliche HTTP-Header (z.B. `Cookie:` oder `User-Agent:`) im Formular.
|
- Engine `hoster` nutzt **aria2c** und akzeptiert zusätzliche HTTP-Header (z.B. `Cookie:` oder `User-Agent:`) im Formular.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
container_name: jf-dl-media-webgui
|
container_name: jf-dl-media-webgui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${WEBGUI_PORT:-8080}:8080"
|
- "${WEBGUI_PORT:-8081}:8080"
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import os
|
|||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
from fastapi import FastAPI, Form, Request
|
from fastapi import FastAPI, Form, Request
|
||||||
@@ -43,6 +45,7 @@ PROXY_SOURCES = {
|
|||||||
"socks4": "https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks4.txt",
|
"socks4": "https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks4.txt",
|
||||||
"http": "https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt",
|
"http": "https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt",
|
||||||
}
|
}
|
||||||
|
PROXY_CHECK_TIMEOUT = float(os.environ.get("PROXY_CHECK_TIMEOUT", "3.0"))
|
||||||
|
|
||||||
URL_RE = re.compile(r"^https?://", re.I)
|
URL_RE = re.compile(r"^https?://", re.I)
|
||||||
YOUTUBE_RE = re.compile(r"(youtube\.com|youtu\.be)", re.I)
|
YOUTUBE_RE = re.compile(r"(youtube\.com|youtu\.be)", re.I)
|
||||||
@@ -122,14 +125,42 @@ def parse_proxy_list(raw: str) -> List[str]:
|
|||||||
def pick_proxy(forced_proxy: str = "") -> str:
|
def pick_proxy(forced_proxy: str = "") -> str:
|
||||||
global _rr_idx
|
global _rr_idx
|
||||||
if forced_proxy:
|
if forced_proxy:
|
||||||
return forced_proxy.strip()
|
return forced_proxy.strip() if proxy_is_usable(forced_proxy.strip()) else ""
|
||||||
if PROXY_MODE == "off" or not PROXIES:
|
with lock:
|
||||||
|
proxies = list(PROXIES)
|
||||||
|
if PROXY_MODE == "off" or not proxies:
|
||||||
return ""
|
return ""
|
||||||
if PROXY_MODE == "random":
|
if PROXY_MODE == "random":
|
||||||
return random.choice(PROXIES)
|
random.shuffle(proxies)
|
||||||
p = PROXIES[_rr_idx % len(PROXIES)]
|
for candidate in proxies:
|
||||||
_rr_idx += 1
|
if proxy_is_usable(candidate):
|
||||||
return p
|
return candidate
|
||||||
|
return ""
|
||||||
|
start_idx = _rr_idx % len(proxies)
|
||||||
|
for offset in range(len(proxies)):
|
||||||
|
idx = (start_idx + offset) % len(proxies)
|
||||||
|
candidate = proxies[idx]
|
||||||
|
if proxy_is_usable(candidate):
|
||||||
|
_rr_idx = idx + 1
|
||||||
|
return candidate
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def proxy_is_usable(proxy: str) -> bool:
|
||||||
|
proxy = proxy.strip()
|
||||||
|
if not proxy:
|
||||||
|
return False
|
||||||
|
parsed = urlparse(proxy if "://" in proxy else f"http://{proxy}")
|
||||||
|
host = parsed.hostname
|
||||||
|
port = parsed.port
|
||||||
|
if not host or not port:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
with socket.create_connection((host, port), timeout=PROXY_CHECK_TIMEOUT):
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def format_proxy_lines(raw: str, scheme: str) -> str:
|
def format_proxy_lines(raw: str, scheme: str) -> str:
|
||||||
@@ -179,7 +210,46 @@ def load_proxy_sources() -> List[str]:
|
|||||||
return parse_proxy_list(combined)
|
return parse_proxy_list(combined)
|
||||||
|
|
||||||
|
|
||||||
PROXIES = parse_proxy_list("\n".join([PROXY_LIST_RAW, "\n".join(load_proxy_sources())]))
|
PROXIES: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_proxies() -> None:
|
||||||
|
global PROXIES
|
||||||
|
combined = "\n".join([PROXY_LIST_RAW, "\n".join(load_proxy_sources())])
|
||||||
|
updated = parse_proxy_list(combined)
|
||||||
|
with lock:
|
||||||
|
PROXIES = updated
|
||||||
|
|
||||||
|
|
||||||
|
def proxy_refresh_loop(interval_seconds: int = 12 * 60 * 60) -> None:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
refresh_proxies()
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Proxy refresh failed: {exc}")
|
||||||
|
time.sleep(interval_seconds)
|
||||||
|
|
||||||
|
|
||||||
|
refresh_proxies()
|
||||||
|
threading.Thread(target=proxy_refresh_loop, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_header_lines(raw: str) -> List[str]:
|
||||||
|
headers = []
|
||||||
|
for line in (raw or "").splitlines():
|
||||||
|
s = line.strip()
|
||||||
|
if not s or s.startswith("#"):
|
||||||
|
continue
|
||||||
|
if ":" not in s:
|
||||||
|
raise ValueError(f"Invalid header line: {s}")
|
||||||
|
name, value = s.split(":", 1)
|
||||||
|
name = name.strip()
|
||||||
|
value = value.strip()
|
||||||
|
if not name or not value:
|
||||||
|
raise ValueError(f"Invalid header line: {s}")
|
||||||
|
headers.append(f"{name}: {value}")
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse_header_lines(raw: str) -> List[str]:
|
def parse_header_lines(raw: str) -> List[str]:
|
||||||
|
|||||||
Reference in New Issue
Block a user