#!/usr/bin/env python3 import argparse import io import json import os import re import urllib import bs4 import charset_normalizer import requests YGG_DOMAIN = "www3.yggtorrent.do" FLARE_ENDPOINT = os.getenv("FLARE_ENDPOINT") class FlareRequests(requests.Session): def request(self, method, url, params=None, data=None, **kwargs): if not FLARE_ENDPOINT: return super().request(method, url, params, data, **kwargs) sessions = requests.post(FLARE_ENDPOINT, json={"cmd": "sessions.list"}).json() if "sessions" in sessions and len(sessions["sessions"]) > 0: FLARE_SESSION = sessions["sessions"][0] else: response = requests.post(FLARE_ENDPOINT, json={"cmd": "sessions.create"}) session = response.json() if "session" in session: FLARE_SESSION = session["session"] else: raise requests.RequestException(response) if params: url += "&" if len(url.split("?")) > 1 else "?" url = f"{url}{urllib.parse.urlencode(params)}" post_data = { "cmd": f"request.{method.lower()}", "session": FLARE_SESSION, "url": url, } if data: post_data["postData"] = urllib.parse.urlencode(data) try: response = requests.post( FLARE_ENDPOINT, json=post_data, ) content = response.json() if "solution" in content and content["solution"]: solution = content["solution"] raw = solution["response"].encode() encoding = charset_normalizer.detect(raw) resolved = requests.Response() resolved.status_code = solution["status"] resolved.headers = solution["headers"] resolved.raw = io.BytesIO(raw) resolved.url = url resolved.encoding = encoding["encoding"] resolved.reason = content["status"] resolved.cookies = solution["cookies"] return resolved raise requests.RequestException(content["message"], response=response) except requests.RequestException: session = requests.post( FLARE_ENDPOINT, json={"cmd": "sessions.destroy", "session": FLARE_SESSION}, ) raise requests.RequestException(content["message"], response=response) session = FlareRequests() parser = argparse.ArgumentParser() parser.add_argument("-u", "--uploader", action="append") parser.add_argument("-b", "--blacklist", action="append", default=["dvd", "iso"]) parser.add_argument("-y", "--year", type=int) parser.add_argument("-s", "--size", type=int, default=10) parser.add_argument("-d", "--downloads", type=int, default=20) parser.add_argument("query") args = parser.parse_args() def parse_size(size): units = {"o": 1, "Ko": 10**3, "Mo": 10**6, "Go": 10**9, "To": 10**12} match = re.search("([0-9.]+)([^0-9]+)", size) number = match.group(1).strip() unit = match.group(2).strip() return int(float(number) * units[unit]) def check_files(id): req = session.get(f"https://{YGG_DOMAIN}/engine/get_files", params={"torrent": id}) res = bs4.BeautifulSoup(req.text, "html.parser") pre = res.select_one("pre") jhtml = json.loads(pre.get_text()) html = bs4.BeautifulSoup(jhtml["html"], "html.parser") trs = html.select("tr") return len(trs) == 1 and "mkv" in trs[0].get_text().lower() def search_ygg(query, multi, full): ygg_params = { "name": query, "category": "2145", "sub_category": "2183", "do": "search", "order": "asc", "sort": "publish_date", } if full and args.year: ygg_params["name"] += f" {args.year}" if multi: ygg_params["option_langue:multiple[]"] = "4" try: req = session.get(f"https://{YGG_DOMAIN}/engine/search", params=ygg_params) except requests.RequestException as e: print( f"Request failed. {e}\n" f"https://{YGG_DOMAIN}/engine/search?{urllib.parse.urlencode(ygg_params)}" ) return html = bs4.BeautifulSoup( req.text, "html.parser", ) trs = html.select("table.table tr") if len(trs) > 1: for i, tr in enumerate(trs): if not i: continue tds = tr.find_all("td") size = tds[5].get_text() downloads = tds[6].get_text() name = tds[1].get_text().lower().strip() if parse_size(size) > parse_size(f"{args.size}Go"): continue if int(downloads) < args.downloads: continue if any(word and word.lower() in name for word in args.blacklist): continue if args.year and str(args.year) not in name: continue if args.uploader and not any( uploader and uploader.lower() in name for uploader in args.uploader ): continue link = tds[1].a["href"] id = link.split("/")[-1].split("-")[0] if not check_files(id): continue print(f"{name} {link}") exit(0) query_string = {"query": args.query, "filters": "type:movie"} if args.year: query_string["filters"] += f" AND year:{args.year}" tvdb = requests.post( "https://tvshowtime-dsn.algolia.net/1/indexes/TVDB/query", params={ "x-algolia-application-id": "tvshowtime", "x-algolia-api-key": "c9d5ec1316cec12f093754c69dd879d3", }, json={"params": urllib.parse.urlencode(query_string)}, ) tvdata = tvdb.json() if not tvdata["nbHits"] > 0: print("Can't find query on TheTVDB") exit(1) eng = tvdata["hits"][0]["name"] fra = ( tvdata["hits"][0]["translations"]["fra"] if "fra" in tvdata["hits"][0]["translations"] else args.query ) search_ygg(args.query, True, True) search_ygg(fra, True, True) search_ygg(eng, True, True) search_ygg(args.query, False, True) search_ygg(fra, False, True) search_ygg(eng, False, True) if args.year: search_ygg(args.query, True, False) search_ygg(fra, True, False) search_ygg(eng, True, False) search_ygg(args.query, False, False) search_ygg(fra, False, False) search_ygg(eng, False, False) print("No results :(")