feat: PrivateHD watcher ve item tabanli scraper entegrasyonunu ekle

This commit is contained in:
2026-03-13 02:08:17 +03:00
parent baad2b3e96
commit bf278ad786
9 changed files with 355 additions and 93 deletions

View File

@@ -1,11 +1,9 @@
from __future__ import annotations
import argparse
import base64
import json
import os
import sys
import tempfile
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
@@ -16,7 +14,7 @@ WSCRAPER_SRC = REPO_ROOT / "bin" / "wscraper" / "src"
if str(WSCRAPER_SRC) not in sys.path:
sys.path.insert(0, str(WSCRAPER_SRC))
from wscraper.sites.happyfappy import run_download_torrent_files, run_get_bookmarks
from wscraper.registry import get_tracker, list_trackers, normalize_tracker
HOST = os.environ.get("WSCRAPER_SERVICE_HOST", "0.0.0.0")
PORT = int(os.environ.get("WSCRAPER_SERVICE_PORT", "8787"))
@@ -48,15 +46,24 @@ def require_auth(handler: BaseHTTPRequestHandler) -> bool:
return False
def normalize_tracker(payload: dict) -> str:
tracker = str(payload.get("tracker", "")).strip().lower()
if tracker not in {"happyfappy", "hf"}:
raise ValueError("Unsupported tracker")
return "happyfappy"
def normalize_payload(payload: dict) -> tuple[str, str, dict, str | None]:
tracker_key = normalize_tracker(str(payload.get("tracker", "")))
cookie = str(payload.get("cookie", "")).strip()
if not cookie:
raise ValueError("Cookie is required")
item = payload.get("item")
if item is None:
item = {}
if not isinstance(item, dict):
raise ValueError("Item payload must be an object")
wishlist_url = payload.get("wishlistUrl")
if wishlist_url is not None:
wishlist_url = str(wishlist_url).strip() or None
return tracker_key, cookie, item, wishlist_url
class Handler(BaseHTTPRequestHandler):
server_version = "wscraper-service/1.0"
server_version = "wscraper-service/2.0"
def do_GET(self) -> None: # noqa: N802
parsed = urlparse(self.path)
@@ -71,7 +78,7 @@ class Handler(BaseHTTPRequestHandler):
json_response(
self,
HTTPStatus.OK,
{"items": [{"key": "happyfappy", "label": "HappyFappy"}]},
{"items": [{"key": tracker.key, "label": tracker.label} for tracker in list_trackers()]},
)
return
json_response(self, HTTPStatus.NOT_FOUND, {"error": "Not found"})
@@ -83,76 +90,35 @@ class Handler(BaseHTTPRequestHandler):
parsed = urlparse(self.path)
try:
payload = parse_json_body(self)
tracker_key, cookie, item, wishlist_url = normalize_payload(payload)
tracker = get_tracker(tracker_key)
if parsed.path == "/bookmarks":
tracker = normalize_tracker(payload)
cookie = str(payload.get("cookie", "")).strip()
if not cookie:
raise ValueError("Cookie is required")
with tempfile.TemporaryDirectory(prefix="wscraper-bookmarks-") as tmpdir:
output_path = Path(tmpdir) / "bookmarks.json"
run_get_bookmarks(
argparse.Namespace(
base_url="https://www.happyfappy.net",
cookie=cookie,
cookie_file=None,
output=str(output_path),
delay_min=1.8,
delay_max=3.2,
retries=3,
backoff_base=5.0,
max_pages=200,
)
)
items = json.loads(output_path.read_text(encoding="utf-8"))
json_response(self, HTTPStatus.OK, {"tracker": tracker, "items": items})
items = tracker.get_bookmarks(cookie, wishlist_url=wishlist_url)
json_response(self, HTTPStatus.OK, {"tracker": tracker_key, "items": items})
return
if parsed.path == "/download":
tracker = normalize_tracker(payload)
cookie = str(payload.get("cookie", "")).strip()
detail_url = str(payload.get("url", "")).strip()
remove_bookmark = bool(payload.get("removeBookmark", True))
if not cookie:
raise ValueError("Cookie is required")
if not detail_url:
raise ValueError("Detail url is required")
with tempfile.TemporaryDirectory(prefix="wscraper-download-") as tmpdir:
output_dir = Path(tmpdir) / "torrent"
run_download_torrent_files(
argparse.Namespace(
url=detail_url,
base_url="https://www.happyfappy.net",
cookie=cookie,
cookie_file=None,
output_dir=str(output_dir),
rm_bookmark=remove_bookmark,
retries=3,
backoff_base=5.0,
)
)
files = sorted(output_dir.glob("*.torrent"))
if not files:
raise RuntimeError("No torrent file produced")
torrent_path = files[0]
content = base64.b64encode(torrent_path.read_bytes()).decode("ascii")
result = tracker.download_torrent(cookie, item, wishlist_url=wishlist_url)
json_response(
self,
HTTPStatus.OK,
{
"tracker": tracker,
"filename": torrent_path.name,
"contentBase64": content,
"tracker": tracker_key,
"filename": result["filename"],
"contentBase64": base64.b64encode(result["data"]).decode("ascii"),
},
)
return
if parsed.path == "/remove-bookmark":
tracker.remove_bookmark(cookie, item, wishlist_url=wishlist_url)
json_response(self, HTTPStatus.OK, {"tracker": tracker_key, "ok": True})
return
json_response(self, HTTPStatus.NOT_FOUND, {"error": "Not found"})
except Exception as error: # noqa: BLE001
json_response(
self,
HTTPStatus.BAD_REQUEST,
{"error": str(error)},
)
json_response(self, HTTPStatus.BAD_REQUEST, {"error": str(error)})
def log_message(self, fmt: str, *args) -> None:
print(f"[wscraper-service] {self.address_string()} - {fmt % args}")