Compare commits
5 Commits
b9e7e1bfca
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
f5fb46de6c
|
|||
|
7cd556bab2
|
|||
|
8f58d8db82
|
|||
|
74753d94be
|
|||
|
02741c1e77
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/test.xml
|
/test.xml
|
||||||
/uv.lock
|
/uv.lock
|
||||||
|
/main.py
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling>=1.27.0"]
|
requires = ["hatchling>=1.29.0"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "watfag"
|
name = "watfag"
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
description = "Searches torrents with Jackett and scores them with WATFAG"
|
description = "Searches torrents with Jackett and scores them with WATFAG"
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"feedparser>=6.0.12",
|
"feedparser>=6.0.12",
|
||||||
"httpx>=0.28.1",
|
"httpx>=0.28.1",
|
||||||
"regex>=2026.2.28",
|
"regex>=2026.2.28",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"hatch>=1.16.5",
|
||||||
|
"hatchling>=1.29.0",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = [
|
packages = [
|
||||||
"src/watfag",
|
"src/watfag",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "1.0.0"
|
__version__ = "1.0.4"
|
||||||
|
|||||||
@@ -2,27 +2,62 @@ import importlib
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type
|
||||||
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
from parsers.generic.parsers import DataParser
|
from watfag.parsers.generic.parsers import DataParser
|
||||||
from parsers.generic.watfag import WATFAG, SeedStatus
|
from watfag.parsers.generic.watfag import *
|
||||||
|
from watfag.parsers.generic.watfag import WATFAG
|
||||||
|
from watfag.trackers import TrackerInfo
|
||||||
|
|
||||||
|
|
||||||
class Release:
|
class Release:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
unparsed_text,
|
xml_result: Element,
|
||||||
dl_link,
|
tracker_info: Optional[TrackerInfo] = None
|
||||||
**kwargs
|
|
||||||
):
|
):
|
||||||
self.original_text: str = unparsed_text
|
self.xml: Element = xml_result
|
||||||
|
self.tracker_info: Optional[TrackerInfo] = tracker_info
|
||||||
|
self.original_text: str = ''
|
||||||
self.metadata_text: Optional[str] = ''
|
self.metadata_text: Optional[str] = ''
|
||||||
self.dl_link: str = dl_link
|
self.dl_link: str = ''
|
||||||
self.view_link: str = kwargs.get('view_link', dl_link)
|
self.view_link: str = ''
|
||||||
self.size: int = kwargs.get('size', 0)
|
self.size: int = 0
|
||||||
self.seeders: int = kwargs.get('seeders', 0)
|
self.seeders: int = 0
|
||||||
|
self.tracker: str = ''
|
||||||
|
self.tracker_abbr: str = ''
|
||||||
self.seed_status: Optional[SeedStatus] = None
|
self.seed_status: Optional[SeedStatus] = None
|
||||||
self.parser_results: dict[str, bool] = {} # Stores which parsers have been run and their results.
|
self.parser_results: dict[str, bool] = {} # Stores which parsers have been run and their results.
|
||||||
|
|
||||||
|
self.title: Optional[str] = None
|
||||||
|
self.group: Optional[Group] = None
|
||||||
|
self.group_name: Optional[str] = None
|
||||||
|
self.quality: Optional[Resolution] = None
|
||||||
|
self.source: Optional[Source] = None
|
||||||
|
self.streaming: Optional[StreamingService] = None
|
||||||
|
self.video_codec: Optional[VideoCodec] = None
|
||||||
|
self.audio_codec: Optional[AudioCodec] = None
|
||||||
|
self.audio_layout: Optional[AudioLayout] = None
|
||||||
|
self.dynamic_range: Optional[DynamicRange] = None
|
||||||
|
self.repack: Optional[Repack] = None
|
||||||
|
self.multi: Optional[Multi] = None
|
||||||
|
|
||||||
|
self._parse_xml()
|
||||||
|
|
||||||
|
def _parse_xml(self):
|
||||||
|
# Get the torznab attributes
|
||||||
|
attrs: dict[str, list[str]] = {}
|
||||||
|
for attr in self.xml.findall('torznab:attr', namespaces={'torznab': 'http://torznab.com/schemas/2015/feed'}):
|
||||||
|
attrs[attr.get('name')] = attrs.get(attr.get('name'), []) + [attr.get('value')]
|
||||||
|
|
||||||
|
self.original_text = self.xml.find('title').text
|
||||||
|
self.dl_link = self.xml.find('link').text
|
||||||
|
self.size = int(self.xml.find('size').text)
|
||||||
|
self.seeders = int(attrs.get('seeders')[0])
|
||||||
|
self.view_link = self.xml.find('comments').text
|
||||||
|
self.tracker = self.xml.find('jackettindexer').text
|
||||||
|
self.tracker_abbr = self.tracker_info.get_tracker_info(self.tracker).get('Abbreviation') if self.tracker_info else self.tracker
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.watfag < other.watfag
|
return self.watfag < other.watfag
|
||||||
|
|
||||||
@@ -63,6 +98,7 @@ class GenericParser:
|
|||||||
|
|
||||||
class ParserManager:
|
class ParserManager:
|
||||||
"""Manages and runs parsers on releases."""
|
"""Manages and runs parsers on releases."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.parsers: list[Type[DataParser]] = []
|
self.parsers: list[Type[DataParser]] = []
|
||||||
self.collect_parsers()
|
self.collect_parsers()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import DataParser
|
from watfag.parsers.generic.parsers import DataParser
|
||||||
from parsers.generic.watfag import AudioCodec, AudioLayout
|
from watfag.parsers.generic.watfag import AudioCodec, AudioLayout
|
||||||
|
|
||||||
patterns = [
|
patterns = [
|
||||||
re.compile(
|
re.compile(
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.watfag import Group
|
from watfag.parsers.generic.watfag import Group
|
||||||
from parsers.generic.parsers import DataParser
|
from watfag.parsers.generic.parsers import DataParser
|
||||||
|
|
||||||
patterns = [
|
patterns = [
|
||||||
re.compile(r"(?:-| - )(?P<group>[a-zA-Z0-9 &]*)\)?$", re.UNICODE),
|
re.compile(r"(?:-| - )(?P<group>[a-zA-Z0-9 &]*)\)?$", re.UNICODE),
|
||||||
|
re.compile(r"\[(?P<group>[a-zA-Z0-9 &]*)\]?$", re.UNICODE),
|
||||||
re.compile(r"(?: )\[?(?P<group>[a-zA-Z0-9]*?)]?\)?$", re.UNICODE)
|
re.compile(r"(?: )\[?(?P<group>[a-zA-Z0-9]*?)]?\)?$", re.UNICODE)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import DynamicRange
|
from watfag.parsers.generic.watfag import DynamicRange
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"hybrid|do?vi? ?hdr(?:10)?[\+p]?", re.IGNORECASE): DynamicRange.HYBRID,
|
re.compile(r"hybrid|do?vi? ?hdr(?:10)?[\+p]?", re.IGNORECASE): DynamicRange.HYBRID,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import Multi
|
from watfag.parsers.generic.watfag import Multi
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"multi", re.IGNORECASE): Multi.MULTI
|
re.compile(r"multi", re.IGNORECASE): Multi.MULTI
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Optional, TYPE_CHECKING
|
|||||||
from regex import Pattern
|
from regex import Pattern
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from parsers.generic import WATFAG, Release
|
from watfag.parsers.generic import WATFAG, Release
|
||||||
|
|
||||||
|
|
||||||
class DataParser:
|
class DataParser:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import Repack
|
from watfag.parsers.generic.watfag import Repack
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"repack", re.IGNORECASE): Repack.REPACK,
|
re.compile(r"repack", re.IGNORECASE): Repack.REPACK,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import Resolution
|
from watfag.parsers.generic.watfag import Resolution
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"2160p", re.IGNORECASE): Resolution.UHD,
|
re.compile(r"2160p", re.IGNORECASE): Resolution.UHD,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import SeedStatus
|
from watfag.parsers.generic.watfag import SeedStatus
|
||||||
|
|
||||||
class SeederParser(CheckParser, GenericParser):
|
class SeederParser(CheckParser, GenericParser):
|
||||||
def parse(self) -> bool:
|
def parse(self) -> bool:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import Source
|
from watfag.parsers.generic.watfag import Source
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"remux", re.IGNORECASE): Source.REMUX,
|
re.compile(r"remux", re.IGNORECASE): Source.REMUX,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import StreamingService
|
from watfag.parsers.generic.watfag import StreamingService
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"ATVP"): StreamingService.ATVP,
|
re.compile(r"ATVP"): StreamingService.ATVP,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic import GenericParser
|
from watfag.parsers.generic import GenericParser
|
||||||
from parsers.generic.parsers import CheckParser
|
from watfag.parsers.generic.parsers import CheckParser
|
||||||
from parsers.generic.watfag import VideoCodec
|
from watfag.parsers.generic.watfag import VideoCodec
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
re.compile(r"avc|[hx][\. -]?264", re.IGNORECASE): VideoCodec.AVC,
|
re.compile(r"avc|[hx][\. -]?264", re.IGNORECASE): VideoCodec.AVC,
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ class Group(WATFAG):
|
|||||||
"""
|
"""
|
||||||
FLUX = "FLUX", 10 # Very good WEB-DL releases and fast
|
FLUX = "FLUX", 10 # Very good WEB-DL releases and fast
|
||||||
HONE = "HONE", 10 # High quality re-encodes
|
HONE = "HONE", 10 # High quality re-encodes
|
||||||
|
TAOE = "TAoE", 10 # High quality re-encodes
|
||||||
PHOCIS = "PHOCiS", 8 # Same as FLUX
|
PHOCIS = "PHOCiS", 8 # Same as FLUX
|
||||||
LEGION = "LEGi0N", 8 # Same as FLUX
|
LEGION = "LEGi0N", 8 # Same as FLUX
|
||||||
AOC = "AOC", 1 # Often low quality CAM releases. While fast, not worth it for most movies.
|
AOC = "AOC", 1 # Often low quality CAM releases. While fast, not worth it for most movies.
|
||||||
|
|||||||
@@ -2,33 +2,28 @@ import importlib
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
from parsers.generic import Release, ParserManager
|
from watfag.parsers.generic import Release, ParserManager
|
||||||
from parsers.generic.watfag import *
|
from watfag.trackers import TrackerInfo
|
||||||
|
|
||||||
|
|
||||||
class MovieRelease(Release):
|
class MovieRelease(Release):
|
||||||
"""Holds info representing a release of a movie."""
|
"""Holds info representing a release of a movie."""
|
||||||
def __init__(self, unparsed_text, dl_link, **kwargs):
|
|
||||||
super().__init__(unparsed_text, dl_link, **kwargs)
|
def __init__(
|
||||||
self.title: str = ""
|
self,
|
||||||
|
xml_result: Element,
|
||||||
|
tracker_info: Optional[TrackerInfo] = None
|
||||||
|
):
|
||||||
|
super().__init__(xml_result, tracker_info)
|
||||||
self.year: int = 0
|
self.year: int = 0
|
||||||
self.edition: Optional[str] = None
|
self.edition: Optional[str] = None
|
||||||
self.group: Optional[Group] = None
|
|
||||||
self.group_name: Optional[str] = None
|
|
||||||
self.quality: Optional[Resolution] = None
|
|
||||||
self.source: Optional[Source] = None
|
|
||||||
self.streaming: Optional[StreamingService] = None
|
|
||||||
self.video_codec: Optional[VideoCodec] = None
|
|
||||||
self.audio_codec: Optional[AudioCodec] = None
|
|
||||||
self.audio_layout: Optional[AudioLayout] = None
|
|
||||||
self.dynamic_range: Optional[DynamicRange] = None
|
|
||||||
self.repack: Optional[Repack] = None
|
|
||||||
self.multi: Optional[Multi] = None
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
parts = [f"{self.title} ({self.year})" + (f" [{self.edition}]" if self.edition else "")]
|
parts = [f"{self.title} ({self.year})" + (f" [{self.edition}]" if self.edition else "")]
|
||||||
for attr in ['quality', 'video_codec', 'audio_codec', 'audio_layout', 'dynamic_range', 'repack', 'multi', 'source']:
|
for attr in ['quality', 'video_codec', 'audio_codec', 'audio_layout', 'dynamic_range', 'repack', 'multi',
|
||||||
|
'source']:
|
||||||
value = getattr(self, attr)
|
value = getattr(self, attr)
|
||||||
parts.append(f"{attr.capitalize()}: {value if value else 'Unknown'}")
|
parts.append(f"{attr.capitalize()}: {value if value else 'Unknown'}")
|
||||||
if self.streaming:
|
if self.streaming:
|
||||||
@@ -39,12 +34,14 @@ class MovieRelease(Release):
|
|||||||
parts.append(f"WATFAG: {self.watfag:.2f}")
|
parts.append(f"WATFAG: {self.watfag:.2f}")
|
||||||
return " | ".join(parts)
|
return " | ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
class MovieParser:
|
class MovieParser:
|
||||||
"""
|
"""
|
||||||
This class can be inherited by any parser that is specific to movies.
|
This class can be inherited by any parser that is specific to movies.
|
||||||
It allows dynamic importing of parser classes and provides a method to run all parsers on a given movie release.
|
It allows dynamic importing of parser classes and provides a method to run all parsers on a given movie release.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class MovieParserManager(ParserManager):
|
class MovieParserManager(ParserManager):
|
||||||
"""Parses movie releases."""
|
"""Parses movie releases."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic.parsers import DataParser
|
from watfag.parsers.generic.parsers import DataParser
|
||||||
from parsers.movie import MovieParser, MovieRelease
|
from watfag.parsers.movie import MovieParser, MovieRelease
|
||||||
from parsers.movie.title_year import edition_regex
|
from watfag.parsers.movie.title_year import edition_regex
|
||||||
|
|
||||||
|
|
||||||
class EditionParser(DataParser, MovieParser):
|
class EditionParser(DataParser, MovieParser):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic.parsers import DataParser
|
from watfag.parsers.generic.parsers import DataParser
|
||||||
from parsers.movie import MovieParser, MovieRelease
|
from watfag.parsers.movie import MovieParser, MovieRelease
|
||||||
|
|
||||||
# Shamelessly stolen from Radarr: https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Parser/Parser.cs
|
# Shamelessly stolen from Radarr: https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Parser/Parser.cs
|
||||||
edition_regex = r"\(?\b(?P<edition>(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Extended|Despecialized|(Special|Rouge|Final|Assembly|Imperial|Diamond|Signature|Hunter|Rekall)(?=(.(Cut|Edition|Version)))|\d{2,3}(th)?.Anniversary)(?:.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|Open.?Matte|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|Open?.Matte|IMAX|Fan.?Edit|Restored|((2|3|4)in1))))))\b\)?"
|
edition_regex = r"\(?\b(?P<edition>(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Extended|Despecialized|(Special|Rouge|Final|Assembly|Imperial|Diamond|Signature|Hunter|Rekall)(?=(.(Cut|Edition|Version)))|\d{2,3}(th)?.Anniversary)(?:.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|Open.?Matte|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|Open?.Matte|IMAX|Fan.?Edit|Restored|((2|3|4)in1))))))\b\)?"
|
||||||
|
|||||||
@@ -2,31 +2,24 @@ import importlib
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
from parsers.generic import Release, ParserManager
|
from watfag.parsers.generic import Release, ParserManager
|
||||||
from parsers.generic.watfag import *
|
from watfag.trackers import TrackerInfo
|
||||||
|
|
||||||
|
|
||||||
class TVBoxSetRelease(Release):
|
class TVBoxSetRelease(Release):
|
||||||
"""Holds info representing a release of a TV box set."""
|
"""Holds info representing a release of a TV box set."""
|
||||||
def __init__(self, unparsed_text, dl_link, **kwargs):
|
def __init__(
|
||||||
super().__init__(unparsed_text, dl_link, **kwargs)
|
self,
|
||||||
self.show_title: str = ""
|
xml_result: Element,
|
||||||
|
tracker_info: Optional[TrackerInfo] = None
|
||||||
|
):
|
||||||
|
super().__init__(xml_result, tracker_info)
|
||||||
self.seasons: Optional[str] = None
|
self.seasons: Optional[str] = None
|
||||||
self.group: Optional[Group] = None
|
|
||||||
self.group_name: Optional[str] = None
|
|
||||||
self.quality: Optional[Resolution] = None
|
|
||||||
self.source: Optional[Source] = None
|
|
||||||
self.streaming: Optional[StreamingService] = None
|
|
||||||
self.video_codec: Optional[VideoCodec] = None
|
|
||||||
self.audio_codec: Optional[AudioCodec] = None
|
|
||||||
self.audio_layout: Optional[AudioLayout] = None
|
|
||||||
self.dynamic_range: Optional[DynamicRange] = None
|
|
||||||
self.repack: Optional[Repack] = None
|
|
||||||
self.multi: Optional[Multi] = None
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
parts = [f"{self.show_title} (Seasons: {self.seasons})"]
|
parts = [f"{self.title} (Seasons: {self.seasons})"]
|
||||||
for attr in ['quality', 'video_codec', 'audio_codec', 'audio_layout', 'dynamic_range', 'repack', 'multi', 'source']:
|
for attr in ['quality', 'video_codec', 'audio_codec', 'audio_layout', 'dynamic_range', 'repack', 'multi', 'source']:
|
||||||
value = getattr(self, attr)
|
value = getattr(self, attr)
|
||||||
parts.append(f"{attr.capitalize()}: {value if value else 'Unknown'}")
|
parts.append(f"{attr.capitalize()}: {value if value else 'Unknown'}")
|
||||||
@@ -46,7 +39,6 @@ class TVBoxSetParser:
|
|||||||
|
|
||||||
class TVBoxSetParserManager(ParserManager):
|
class TVBoxSetParserManager(ParserManager):
|
||||||
"""Parses TV box set releases."""
|
"""Parses TV box set releases."""
|
||||||
|
|
||||||
def collect_parsers(self):
|
def collect_parsers(self):
|
||||||
"""Dynamically imports all TV box set parsers."""
|
"""Dynamically imports all TV box set parsers."""
|
||||||
super().collect_parsers()
|
super().collect_parsers()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
from parsers.generic.parsers import DataParser
|
from watfag.parsers.generic.parsers import DataParser
|
||||||
from parsers.tvboxset import TVBoxSetParser, TVBoxSetRelease
|
from watfag.parsers.tvboxset import TVBoxSetParser, TVBoxSetRelease
|
||||||
|
|
||||||
patterns = [
|
patterns = [
|
||||||
re.compile( # Show Name S01-S02 (year)
|
re.compile( # Show Name S01-S02 (year)
|
||||||
@@ -45,7 +45,7 @@ class TitleSeasonsParser(DataParser, TVBoxSetParser):
|
|||||||
for pattern in patterns:
|
for pattern in patterns:
|
||||||
match = pattern.match(self.release.original_text)
|
match = pattern.match(self.release.original_text)
|
||||||
if match:
|
if match:
|
||||||
self.release.show_title = match.group("title").replace(".", " ").replace("_", " ").strip() if match.group("title") else ""
|
self.release.title = match.group("title").replace(".", " ").replace("_", " ").strip() if match.group("title") else ""
|
||||||
season_start = int(match.group("season_start")) if match.group("season_start") else 0
|
season_start = int(match.group("season_start")) if match.group("season_start") else 0
|
||||||
season_end = int(match.group("season_end")) if "season_end" in match.groupdict() and match.group("season_end") else season_start
|
season_end = int(match.group("season_end")) if "season_end" in match.groupdict() and match.group("season_end") else season_start
|
||||||
self.release.seasons = f"{season_start}" if season_start == season_end else f"{season_start}-{season_end}"
|
self.release.seasons = f"{season_start}" if season_start == season_end else f"{season_start}-{season_end}"
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ from xml.etree import ElementTree
|
|||||||
|
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
|
||||||
from parsers.generic import Release
|
from watfag.parsers.generic import Release
|
||||||
from parsers.movie import MovieRelease, MovieParserManager
|
from watfag.parsers.movie import MovieRelease, MovieParserManager
|
||||||
from parsers.tvboxset import TVBoxSetRelease, TVBoxSetParserManager
|
from watfag.parsers.tvboxset import TVBoxSetRelease, TVBoxSetParserManager
|
||||||
|
from watfag.trackers import TrackerInfo
|
||||||
|
|
||||||
|
|
||||||
class Jackett:
|
class Jackett:
|
||||||
@@ -13,6 +14,9 @@ class Jackett:
|
|||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.movie_parser = MovieParserManager()
|
self.movie_parser = MovieParserManager()
|
||||||
self.tvboxset_parser = TVBoxSetParserManager()
|
self.tvboxset_parser = TVBoxSetParserManager()
|
||||||
|
self.tracker_info = TrackerInfo(
|
||||||
|
"https://raw.githubusercontent.com/HDVinnie/Private-Trackers-Spreadsheet/refs/heads/master/trackers.json"
|
||||||
|
)
|
||||||
|
|
||||||
async def get_capabilities(self):
|
async def get_capabilities(self):
|
||||||
params = {
|
params = {
|
||||||
@@ -45,21 +49,15 @@ class Jackett:
|
|||||||
# Find out from categories what kind of result this is
|
# Find out from categories what kind of result this is
|
||||||
if any(cat.startswith('2') for cat in attrs.get('category')): # This is a movie
|
if any(cat.startswith('2') for cat in attrs.get('category')): # This is a movie
|
||||||
release = MovieRelease(
|
release = MovieRelease(
|
||||||
item.find('title').text,
|
item,
|
||||||
item.find('link').text,
|
self.tracker_info
|
||||||
size=int(item.find('size').text),
|
|
||||||
seeders=int(attrs.get('seeders')[0]),
|
|
||||||
view_link=item.find('comments').text
|
|
||||||
)
|
)
|
||||||
self.movie_parser.run_parsers(release)
|
self.movie_parser.run_parsers(release)
|
||||||
releases.append(release)
|
releases.append(release)
|
||||||
elif any(cat == '100027' for cat in attrs.get('category')): # This is a TV boxset
|
elif any(cat == '100027' for cat in attrs.get('category')): # This is a TV boxset
|
||||||
release = TVBoxSetRelease(
|
release = TVBoxSetRelease(
|
||||||
item.find('title').text,
|
item,
|
||||||
item.find('link').text,
|
self.tracker_info
|
||||||
size=int(item.find('size').text),
|
|
||||||
seeders=int(attrs.get('seeders')[0]),
|
|
||||||
view_link=item.find('comments').text
|
|
||||||
)
|
)
|
||||||
self.tvboxset_parser.run_parsers(release)
|
self.tvboxset_parser.run_parsers(release)
|
||||||
releases.append(release)
|
releases.append(release)
|
||||||
|
|||||||
19
src/watfag/trackers.py
Normal file
19
src/watfag/trackers.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
class TrackerInfo:
|
||||||
|
def __init__(self, url):
|
||||||
|
self.url = url
|
||||||
|
self.json: Optional[dict] = None
|
||||||
|
|
||||||
|
def _refresh(self):
|
||||||
|
r = httpx.get(self.url)
|
||||||
|
r.raise_for_status()
|
||||||
|
self.json = r.json().get('trackers', [])
|
||||||
|
|
||||||
|
def get_tracker_info(self, tracker_name):
|
||||||
|
if self.json is None:
|
||||||
|
self._refresh()
|
||||||
|
return next((tracker for tracker in self.json if tracker['Name'] == tracker_name), None)
|
||||||
Reference in New Issue
Block a user