feat: watcher akislarini ve wscraper servis entegrasyonunu ekle

This commit is contained in:
2026-03-12 22:30:43 +03:00
parent 6507d13325
commit baad2b3e96
34 changed files with 2663 additions and 11 deletions

View File

@@ -0,0 +1,168 @@
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
from urllib.parse import urlparse
REPO_ROOT = Path(__file__).resolve().parents[2]
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
HOST = os.environ.get("WSCRAPER_SERVICE_HOST", "0.0.0.0")
PORT = int(os.environ.get("WSCRAPER_SERVICE_PORT", "8787"))
TOKEN = os.environ.get("WSCRAPER_SERVICE_TOKEN", "")
def json_response(handler: BaseHTTPRequestHandler, status: int, payload: dict) -> None:
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
handler.send_response(status)
handler.send_header("Content-Type", "application/json; charset=utf-8")
handler.send_header("Content-Length", str(len(body)))
handler.end_headers()
handler.wfile.write(body)
def parse_json_body(handler: BaseHTTPRequestHandler) -> dict:
length = int(handler.headers.get("Content-Length", "0"))
raw = handler.rfile.read(length) if length > 0 else b"{}"
return json.loads(raw.decode("utf-8"))
def require_auth(handler: BaseHTTPRequestHandler) -> bool:
if not TOKEN:
return True
auth_header = handler.headers.get("Authorization", "")
if auth_header == f"Bearer {TOKEN}":
return True
json_response(handler, HTTPStatus.UNAUTHORIZED, {"error": "Unauthorized"})
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"
class Handler(BaseHTTPRequestHandler):
server_version = "wscraper-service/1.0"
def do_GET(self) -> None: # noqa: N802
parsed = urlparse(self.path)
if parsed.path == "/health":
if not require_auth(self):
return
json_response(self, HTTPStatus.OK, {"ok": True, "service": "wscraper-service"})
return
if parsed.path == "/trackers":
if not require_auth(self):
return
json_response(
self,
HTTPStatus.OK,
{"items": [{"key": "happyfappy", "label": "HappyFappy"}]},
)
return
json_response(self, HTTPStatus.NOT_FOUND, {"error": "Not found"})
def do_POST(self) -> None: # noqa: N802
if not require_auth(self):
return
parsed = urlparse(self.path)
try:
payload = parse_json_body(self)
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})
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")
json_response(
self,
HTTPStatus.OK,
{
"tracker": tracker,
"filename": torrent_path.name,
"contentBase64": content,
},
)
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)},
)
def log_message(self, fmt: str, *args) -> None:
print(f"[wscraper-service] {self.address_string()} - {fmt % args}")
def main() -> None:
server = ThreadingHTTPServer((HOST, PORT), Handler)
print(f"wscraper-service listening on http://{HOST}:{PORT}")
server.serve_forever()
if __name__ == "__main__":
main()