mirror of
https://github.com/HyPnoTiiK/stream-fusion.git
synced 2026-08-20 05:59:31 +00:00
update detection.py, sharewood_service.py, torrent_service.py and views.py
This commit is contained in:
parent
67124a572d
commit
2eec98acb8
4 changed files with 268 additions and 12 deletions
|
|
@ -6,14 +6,14 @@ def detect_languages(torrent_name):
|
|||
"fr": r"\b(?:FR(?:ench|a|e|anc[eê]s)?|V(?:O?F(?:F|I|i)?|O?Q)|TRUEFRENCH|VOST(?:FR)?|SUBFRENCH)\b",
|
||||
"en": r"\b(?:EN(?:G(?:LISH)?)?|VOST(?:EN)?|SUBBED)\b",
|
||||
"es": r"\b(?:ES(?:P(?:ANISH)?)?|VOSE|SUBESP)\b",
|
||||
"de": r"\b(?:DE(?:UTSCH|RMAN)?|GER(?:MAN)?|SUBGER)\b",
|
||||
"it": r"\b(?:IT(?:A(?:LIAN)?)?|SUBITA)\b",
|
||||
"pt": r"\b(?:PT(?:-BR)?|POR(?:TUGUESE)?|LEGENDADO)\b",
|
||||
"ru": r"\b(?:RU(?:S(?:SIAN)?)?|SUBSRUS)\b",
|
||||
"in": r"\b(?:INDIAN|HINDI|TELUGU|TAMIL|KANNADA|MALAYALAM|PUNJABI|MARATHI|BENGALI|GUJARATI|URDU|ODIA|ASSAMESE|KONKANI|MANIPURI|NEPALI|SANSKRIT|SINHALA|SINDHI|TIBETAN|BHOJPURI|DHIVEHI|KASHMIRI|KURUKH|MAITHILI|NEWARI|RAJASTHANI|SANTALI|SINDHI|TULU)\b",
|
||||
"nl": r"\b(?:NL(?:D)?|DUTCH|SUBSNL)\b",
|
||||
"hu": r"\b(?:HU(?:N(?:GARIAN)?)?|SUBHUN)\b",
|
||||
"la": r"\b(?:LA(?:TIN(?:O)?)?)\b",
|
||||
# "de": r"\b(?:DE(?:UTSCH|RMAN)?|GER(?:MAN)?|SUBGER)\b",
|
||||
# "it": r"\b(?:IT(?:A(?:LIAN)?)?|SUBITA)\b",
|
||||
# "pt": r"\b(?:PT(?:-BR)?|POR(?:TUGUESE)?|LEGENDADO)\b",
|
||||
# "ru": r"\b(?:RU(?:S(?:SIAN)?)?|SUBSRUS)\b",
|
||||
# "in": r"\b(?:INDIAN|HINDI|TELUGU|TAMIL|KANNADA|MALAYALAM|PUNJABI|MARATHI|BENGALI|GUJARATI|URDU|ODIA|ASSAMESE|KONKANI|MANIPURI|NEPALI|SANSKRIT|SINHALA|SINDHI|TIBETAN|BHOJPURI|DHIVEHI|KASHMIRI|KURUKH|MAITHILI|NEWARI|RAJASTHANI|SANTALI|SINDHI|TULU)\b",
|
||||
# "nl": r"\b(?:NL(?:D)?|DUTCH|SUBSNL)\b",
|
||||
# "hu": r"\b(?:HU(?:N(?:GARIAN)?)?|SUBHUN)\b",
|
||||
# "la": r"\b(?:LA(?:TIN(?:O)?)?)\b",
|
||||
"multi": r"\b(?:MULTI(?:LANG(?:UE)?)?|DUAL(?:AUDIO)?|VF2)\b",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
import re
|
||||
import urllib.parse
|
||||
from typing import List, Union
|
||||
from RTN import parse
|
||||
|
||||
from stream_fusion.logging_config import logger
|
||||
from stream_fusion.utils.detection import detect_languages
|
||||
from stream_fusion.utils.sharewood.sharewood_result import SharewoodResult
|
||||
from stream_fusion.utils.models.movie import Movie
|
||||
from stream_fusion.utils.models.series import Series
|
||||
from stream_fusion.settings import settings
|
||||
from stream_fusion.utils.sharewood.sharewood_api import SharewoodAPI
|
||||
|
||||
|
||||
class SharewoodService:
|
||||
"""Service for searching media on Sharewood."""
|
||||
|
||||
def __init__(self, config: dict):
|
||||
self.sharewood = SharewoodAPI()
|
||||
self.sharewood_url = settings.sharewood_url
|
||||
self.sharewood_passkey = config.get("sharewoodPasskey")
|
||||
|
||||
def search(self, media: Union[Movie, Series]) -> List[SharewoodResult]:
|
||||
"""
|
||||
Search for a media (movie or series) on sharewood.
|
||||
|
||||
Args:
|
||||
media (Union[Movie, Series]): The media to search for.
|
||||
|
||||
Returns:
|
||||
List[sharewoodResult]: List of search results.
|
||||
|
||||
Raises:
|
||||
TypeError: If the media type is neither Movie nor Series.
|
||||
"""
|
||||
if isinstance(media, Movie):
|
||||
results = self.__search_movie(media)
|
||||
elif isinstance(media, Series):
|
||||
results = self.__search_series(media)
|
||||
else:
|
||||
raise TypeError("Only Movie and Series types are allowed as media!")
|
||||
|
||||
return self.__post_process_results(results, media)
|
||||
|
||||
def __convert_size(self, size_str):
|
||||
units = {
|
||||
"b": 1,
|
||||
"kb": 1e3,
|
||||
"mb": 1e6,
|
||||
"gb": 1e9,
|
||||
"tb": 1e12,
|
||||
"pb": 1e15,
|
||||
"kib": 1024,
|
||||
"mib": 1024**2,
|
||||
"gib": 1024**3,
|
||||
"tib": 1024**4,
|
||||
"pib": 1024**5,
|
||||
}
|
||||
value, unit = size_str.split()
|
||||
value = float(value)
|
||||
unit = unit.lower()
|
||||
if unit not in units:
|
||||
raise ValueError(f"Unité non supportée : {unit}")
|
||||
size = value * units[unit]
|
||||
return int(size)
|
||||
|
||||
def __clean_title(self, title):
|
||||
pronouns_to_remove = [
|
||||
"le",
|
||||
"la",
|
||||
"les",
|
||||
"l'",
|
||||
"un",
|
||||
"une",
|
||||
"des",
|
||||
"du",
|
||||
"de",
|
||||
"à",
|
||||
"au",
|
||||
"aux",
|
||||
"the",
|
||||
"a",
|
||||
"an",
|
||||
"some",
|
||||
"of",
|
||||
"to",
|
||||
"at",
|
||||
"in",
|
||||
"on",
|
||||
"for",
|
||||
"he",
|
||||
"she",
|
||||
"it",
|
||||
"they",
|
||||
"we",
|
||||
"you",
|
||||
"i",
|
||||
"me",
|
||||
"him",
|
||||
"her",
|
||||
"them",
|
||||
"us",
|
||||
"il",
|
||||
"elle",
|
||||
"on",
|
||||
"nous",
|
||||
"vous",
|
||||
"ils",
|
||||
"elles",
|
||||
"je",
|
||||
"tu",
|
||||
"moi",
|
||||
"toi",
|
||||
"lui",
|
||||
]
|
||||
title = title.lower()
|
||||
title = re.sub(r"[^a-zA-Z0-9\s]", " ", title)
|
||||
words = title.split()
|
||||
words = [word for word in words if word not in pronouns_to_remove]
|
||||
cleaned_title = " ".join(words)
|
||||
cleaned_title = re.sub(r"\s+", " ", cleaned_title).strip()
|
||||
return cleaned_title
|
||||
|
||||
def __deduplicate_api_results(self, api_results):
|
||||
unique_results = set()
|
||||
deduplicated_results = []
|
||||
|
||||
for result in api_results:
|
||||
result_tuple = tuple(sorted(result.items()))
|
||||
|
||||
if result_tuple not in unique_results:
|
||||
unique_results.add(result_tuple)
|
||||
deduplicated_results.append(result)
|
||||
|
||||
return deduplicated_results
|
||||
|
||||
def __remove_duplicate_titles(self, titles):
|
||||
seen = set()
|
||||
return [
|
||||
title
|
||||
for title in titles
|
||||
if not (title.lower() in seen or seen.add(title.lower()))
|
||||
]
|
||||
|
||||
def __search_movie(self, movie: Movie) -> List[dict]:
|
||||
unique_titles = self.__remove_duplicate_titles(movie.titles)
|
||||
clean_titles = [self.__clean_title(title) for title in unique_titles]
|
||||
results = []
|
||||
for title in clean_titles:
|
||||
results.extend(self.sharewood.search(query=title, category=1))
|
||||
return self.__deduplicate_api_results(results)
|
||||
|
||||
def __search_series(self, series: Series) -> List[dict]:
|
||||
unique_titles = self.__remove_duplicate_titles(series.titles)
|
||||
clean_titles = [self.__clean_title(title) for title in unique_titles]
|
||||
search_texts = clean_titles.copy()
|
||||
|
||||
if hasattr(series, "season") and hasattr(series, "episode"):
|
||||
search_texts.extend(
|
||||
[f"{title} {series.season}{series.episode}" for title in clean_titles]
|
||||
)
|
||||
|
||||
results = []
|
||||
for text in search_texts:
|
||||
results.extend(self.sharewood.search(query=text, category=1))
|
||||
return self.__deduplicate_api_results(results)
|
||||
|
||||
def __filter_out_no_seeders(self, results: List[dict]) -> List[dict]:
|
||||
"""Filter out results with less than 5 seeders."""
|
||||
return [result for result in results if result.get("seeders", 0) >= 5]
|
||||
|
||||
def __process_download_link(self, id: int) -> str:
|
||||
"""Generate the download link for a given torrent."""
|
||||
return f"{self.sharewood_url}/{self.sharewood_passkey}/{id}/download"
|
||||
|
||||
def __generate_magnet_link(self, info_hash: str, name: str) -> str:
|
||||
"""Generate the magnet link for a given torrent."""
|
||||
encoded_name = urllib.parse.quote(name)
|
||||
tracker = f"{self.sharewood_url}/announce/{self.sharewood_passkey}"
|
||||
encoded_tracker = urllib.parse.quote(tracker)
|
||||
return f"magnet:?xt=urn:btih:{info_hash}&dn={encoded_name}&tr={encoded_tracker}"
|
||||
|
||||
def __post_process_results(
|
||||
self, results: List[dict], media: Union[Movie, Series]
|
||||
) -> List[SharewoodResult]:
|
||||
"""Process raw search results and convert them to sharewoodResult objects."""
|
||||
if not results:
|
||||
logger.info(f"No results found on sharewood for: {media.titles[0]}")
|
||||
return []
|
||||
|
||||
results = self.__filter_out_no_seeders(results)
|
||||
logger.info(f"{len(results)} results found on sharewood for: {media.titles[0]}")
|
||||
|
||||
items = []
|
||||
for result in results:
|
||||
item = SharewoodResult()
|
||||
|
||||
item.raw_title = result.get("name", "...")
|
||||
item.info_hash = result.get("info_hash", None)
|
||||
item.size = self.__convert_size(result.get("size", 0))
|
||||
item.link = (
|
||||
self.__process_download_link(result.get("id"))
|
||||
if result.get("id")
|
||||
else None
|
||||
)
|
||||
item.magnet = self.__generate_magnet_link(item.info_hash, item.raw_title)
|
||||
item.indexer = "API - Sharewood"
|
||||
item.seeders = result.get("seeders", 0)
|
||||
item.privacy = "private"
|
||||
item.languages = detect_languages(item.raw_title)
|
||||
item.type = media.type
|
||||
item.parsed_data = parse(item.raw_title)
|
||||
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
|
@ -10,6 +10,7 @@ import requests
|
|||
from RTN import parse
|
||||
|
||||
from stream_fusion.utils.jackett.jackett_result import JackettResult
|
||||
from stream_fusion.utils.sharewood.sharewood_result import SharewoodResult
|
||||
from stream_fusion.utils.zilean.zilean_result import ZileanResult
|
||||
from stream_fusion.utils.yggfilx.yggflix_result import YggflixResult
|
||||
from stream_fusion.services.ygg_conn import YggSessionManager
|
||||
|
|
@ -27,17 +28,17 @@ class TorrentService:
|
|||
# self.__ygg_session_manager = YggSessionManager(config)
|
||||
# self.__ygg_session = self.__ygg_session_manager.get_session()
|
||||
|
||||
def convert_and_process(self, results: List[JackettResult | ZileanResult | YggflixResult]):
|
||||
def convert_and_process(self, results: List[JackettResult | ZileanResult | YggflixResult | SharewoodResult]):
|
||||
threads = []
|
||||
torrent_items_queue = queue.Queue()
|
||||
|
||||
def thread_target(result: JackettResult | ZileanResult | YggflixResult):
|
||||
def thread_target(result: JackettResult | ZileanResult | YggflixResult | SharewoodResult):
|
||||
torrent_item = result.convert_to_torrent_item()
|
||||
|
||||
if torrent_item.link.startswith("magnet:"):
|
||||
processed_torrent_item = self.__process_magnet(torrent_item)
|
||||
# elif torrent_item.link.startswith(settings.ygg_url):
|
||||
# processed_torrent_item = self.__process_ygg_web_url(torrent_item)
|
||||
elif torrent_item.link.startswith(settings.sharewood_url):
|
||||
processed_torrent_item = self.__process_sharewood_web_url(torrent_item)
|
||||
elif torrent_item.link.startswith(settings.ygg_proxy_url):
|
||||
processed_torrent_item = self.__process_ygg_proxy_url(torrent_item)
|
||||
else:
|
||||
|
|
@ -80,6 +81,27 @@ class TorrentService:
|
|||
|
||||
return result
|
||||
|
||||
def __process_sharewood_web_url(self, result: TorrentItem):
|
||||
if not self.config["sharewood"]:
|
||||
logger.error("Sharewood is not enabled in the config. Skipping processing of Sharewood URL.")
|
||||
|
||||
try:
|
||||
response = self.__session.get(result.link, timeout=5)
|
||||
time.sleep(1) # API limit 1 request per second
|
||||
except requests.exceptions.RequestException:
|
||||
self.logger.error(f"Error while processing url: {result.link}")
|
||||
return result
|
||||
except requests.exceptions.ReadTimeout:
|
||||
self.logger.error(f"Timeout while processing url: {result.link}")
|
||||
return result
|
||||
|
||||
if response.status_code == 200:
|
||||
return self.__process_torrent(result, response.content)
|
||||
else:
|
||||
self.logger.error(f"Error code {response.status_code} while processing ygg url: {result.link}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __process_ygg_proxy_url(self, result: TorrentItem):
|
||||
if not self.config["yggflix"]:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from stream_fusion.utils.filter_results import (
|
|||
)
|
||||
from stream_fusion.utils.jackett.jackett_result import JackettResult
|
||||
from stream_fusion.utils.jackett.jackett_service import JackettService
|
||||
from stream_fusion.utils.sharewood.sharewood_service import SharewoodService
|
||||
from stream_fusion.utils.yggfilx.yggflix_result import YggflixResult
|
||||
from stream_fusion.utils.yggfilx.yggflix_service import YggflixService
|
||||
from stream_fusion.utils.metdata.cinemeta import Cinemeta
|
||||
|
|
@ -182,6 +183,23 @@ async def get_results(
|
|||
)
|
||||
search_results = merge_items(search_results, yggflix_search_results)
|
||||
|
||||
if config["sharewood"] and len(search_results) < int(
|
||||
config["minCachedResults"]
|
||||
):
|
||||
sharewood_service = SharewoodService(config)
|
||||
sharewood_search_results = sharewood_service.search(media)
|
||||
if sharewood_search_results:
|
||||
logger.info(
|
||||
f"Found {len(sharewood_search_results)} results from Sharewood"
|
||||
)
|
||||
sharewood_search_results = filter_items(
|
||||
sharewood_search_results, media, config=config
|
||||
)
|
||||
sharewood_search_results = torrent_service.convert_and_process(
|
||||
sharewood_search_results
|
||||
)
|
||||
search_results = merge_items(search_results, sharewood_search_results)
|
||||
|
||||
if config["jackett"] and len(search_results) < int(
|
||||
config["minCachedResults"]
|
||||
):
|
||||
|
|
|
|||
Loading…
Reference in a new issue