Skip to content

Commit

Permalink
fix: tidy parser. add lint/test to makefile. (#241)
Browse files Browse the repository at this point in the history
* fix: tidy parser. add lint/test to makefile.

* fix: typo in module name. fix jackett in request module

---------

Co-authored-by: Spoked <Spoked@localhost>
  • Loading branch information
dreulavelle and Spoked authored Mar 3, 2024
1 parent 45f3381 commit bd82b23
Show file tree
Hide file tree
Showing 20 changed files with 126 additions and 218 deletions.
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
FROM node:20-alpine AS frontend
WORKDIR /app
COPY frontend/package*.json ./
RUN npm install esbuild@0.19.9
RUN npm install
COPY frontend/ .
RUN npm run build && npm prune --production
Expand Down
4 changes: 1 addition & 3 deletions backend/program/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
"""Program main module"""
from program.program import Program, Event


from program.program import Program, Event # noqa: F401
8 changes: 4 additions & 4 deletions backend/program/content/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .mdblist import Mdblist
from .overseerr import Overseerr
from .plex_watchlist import PlexWatchlist
from .listrr import Listrr
from .mdblist import Mdblist # noqa: F401
from .overseerr import Overseerr # noqa: F401
from .plex_watchlist import PlexWatchlist # noqa: F401
from .listrr import Listrr # noqa: F401
1 change: 0 additions & 1 deletion backend/program/indexers/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from .trakt import TraktIndexer
3 changes: 1 addition & 2 deletions backend/program/indexers/trakt.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""Trakt updater module"""

from datetime import datetime, timedelta
from typing import Optional
from utils.logger import logger
from utils.request import get
from program.media.item import Movie, Show, Season, Episode, MediaItem, ItemId
from program.media.item import Movie, Show, Season, Episode, MediaItem
from program.settings.manager import settings_manager

CLIENT_ID = "0183a05ad97098d87287fe46da4ae286f434f32e8e951caad4cc147c947d79a3"
Expand Down
4 changes: 2 additions & 2 deletions backend/program/libaries/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .plex import PlexLibrary
from .symlink import SymlinkLibrary
from .plex import PlexLibrary # noqa: F401
from .symlink import SymlinkLibrary # noqa: F401
4 changes: 1 addition & 3 deletions backend/program/libaries/plex.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"""Plex library module"""

import os
import concurrent.futures
from threading import Lock
import os
from datetime import datetime
from typing import Optional
from plexapi.server import PlexServer
from plexapi.exceptions import BadRequest, Unauthorized
from utils.logger import logger
Expand Down
5 changes: 2 additions & 3 deletions backend/program/libaries/symlink.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import re
from pathlib import Path
from typing import Generator

from utils.logger import logger
from program.settings.manager import settings_manager
Expand All @@ -10,7 +10,6 @@
Show,
Season,
Episode,
ItemId
)

class SymlinkLibrary:
Expand All @@ -34,7 +33,7 @@ def validate(self) -> bool:
logger.debug("library dir contains %s", ", ".join(folders))
return status

def run(self) -> MediaItem:
def run(self) -> Generator[MediaItem, None, None]:
"""Create a library from the symlink paths. Return stub items that should
be fed into an Indexer to have the rest of the metadata filled in."""
movies = [
Expand Down
6 changes: 3 additions & 3 deletions backend/program/media/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .item import MediaItem, Episode, Show, Season, Movie
from .container import MediaItemContainer
from .state import States
from .item import MediaItem, Episode, Show, Season, Movie # noqa
from .container import MediaItemContainer # noqa
from .state import States # noqa
6 changes: 3 additions & 3 deletions backend/program/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def process_event_and_collect_coverage(
existing_item: MediaItem | None,
emitted_by: Service,
item: MediaItem
) -> ProcessedEvent:
) -> ProcessedEvent: # type: ignore
file_path = inspect.getfile(process_event)

# Load the source code and extract executed lines
Expand All @@ -260,8 +260,8 @@ def process_event_and_collect_coverage(
lines, start_line_no = inspect.getsourcelines(process_event)
logic_start_line_no = next(
i + start_line_no + 1
for i, l in enumerate(source_lines[start_line_no:])
if l.strip().startswith("if ")
for i, line in enumerate(source_lines[start_line_no:])
if line.strip().startswith("if ")
)
end_line_no = logic_start_line_no + len(lines) - 1

Expand Down
4 changes: 2 additions & 2 deletions backend/program/scrapers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def __init__(self):
}
self.initialized = self.validate()

def run(self, item: MediaItem) -> MediaItem | None:
def run(self, item: MediaItem):
if not self._can_we_scrape(item):
return None
yield None
for service in self.services.values():
if service.initialized:
item = next(service.run(item))
Expand Down
6 changes: 3 additions & 3 deletions backend/program/scrapers/orionoid.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def run(self, item):
if item is None or isinstance(item, Show):
yield item
try:
item = self._scrape_item(item)
yield self._scrape_item(item)
except ConnectTimeout:
self.minute_limiter.limit_hit()
logger.warn("Orionoid connection timeout for item: %s", item.log_string)
Expand All @@ -104,7 +104,6 @@ def run(self, item):
logger.exception(
"Orionoid exception for item: %s - Exception: %s", item.log_string, e
)
yield item

def _scrape_item(self, item):
data, stream_count = self.api_scrape(item)
Expand Down Expand Up @@ -148,7 +147,8 @@ def construct_url(self, media_type, imdb_id, season=None, episode=None) -> str:
if self.is_unlimited:
# This can use 2x towards your Orionoid limits. Only use if user is unlimited.
params["debridlookup"] = "realdebrid"
params["limitcount"] = 100
# There are 200 results per page. We probably don't need to go over 200.
params["limitcount"] = 200

if media_type == "show":
params["numberseason"] = season
Expand Down
5 changes: 2 additions & 3 deletions backend/program/scrapers/torrentio.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def run(self, item):
if item is None or isinstance(item, Show):
yield item
try:
item = self._scrape_item(item)
yield self._scrape_item(item)
except RateLimitExceeded:
self.minute_limiter.limit_hit()
except ConnectTimeout:
Expand All @@ -63,10 +63,9 @@ def run(self, item):
except RequestException as e:
self.minute_limiter.limit_hit()
logger.warn("Torrentio request exception: %s", e)
except Exception as e:
except Exception:
self.minute_limiter.limit_hit()
logger.warn("Torrentio exception thrown: %s", traceback.format_exc())
yield item

def _scrape_item(self, item):
"""Scrape torrentio for the given media item"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
from utils.logger import logger


def process_event(existing_item: MediaItem | None, emitted_by: Service, item: MediaItem) -> ProcessedEvent:
def process_event(existing_item: MediaItem | None, emitted_by: Service, item: MediaItem) -> ProcessedEvent: # type: ignore
"""Take the input event, process it, and output items to submit to a Service, and an item
to update the container with."""
next_service : Service = None
updated_item = item
no_further_processing: ProcessedEvent = (None, None, [])
no_further_processing: ProcessedEvent = (None, None, []) # type: ignore
# we always want to get metadata for content items before we compare to the container.
# we can't just check if the show exists we have to check if it's complete
source_services = (Overseerr, PlexWatchlist, Listrr, Mdblist, SymlinkLibrary)
Expand Down
100 changes: 39 additions & 61 deletions backend/utils/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@ def _parse(self, item, string) -> dict:
"extended": parse.get("extended", False),
}

# bandaid for now, this needs to be refactored to make less calls to _parse
if item is not None:
parsed_data["title_match"] = title_match

parsed_data["fetch"] = self._should_fetch(parsed_data)
return parsed_data

Expand All @@ -103,12 +101,9 @@ def _should_fetch(self, parsed_data: dict) -> bool:
# This is where we determine if the item should be fetched
# based on the user settings and predefined rules.
# Edit with caution. All have to match for the item to be fetched.
# item_language = self._get_item_language(item)
return (
parsed_data["resolution"] in self.resolution
and
# any(lang in parsed_data.get("language", item_language) for lang in self.language) and
not parsed_data["is_unwanted_quality"]
and not parsed_data["is_unwanted_quality"]
)

def _is_highest_quality(self, parsed_data: dict) -> bool:
Expand All @@ -124,71 +119,57 @@ def _is_highest_quality(self, parsed_data: dict) -> bool:
def _is_dual_audio(self, string) -> bool:
"""Check if any content in parsed_data has dual audio."""
dual_audio_patterns = [
re.compile(
r"\bmulti(?:ple)?[ .-]*(?:lang(?:uages?)?|audio|VF2)?\b", re.IGNORECASE
),
re.compile(r"\btri(?:ple)?[ .-]*(?:audio|dub\w*)\b", re.IGNORECASE),
re.compile(r"\bdual[ .-]*(?:au?$|[aá]udio|line)\b", re.IGNORECASE),
re.compile(r"\bdual\b(?![ .-]*sub)", re.IGNORECASE),
re.compile(r"\b(?:audio|dub(?:bed)?)[ .-]*dual\b", re.IGNORECASE),
re.compile(r"\bengl?(?:sub[A-Z]*)?\b", re.IGNORECASE),
re.compile(r"\beng?sub[A-Z]*\b", re.IGNORECASE),
re.compile(r"\b(?:DUBBED|dublado|dubbing|DUBS?)\b", re.IGNORECASE),
r"\bmulti(?:ple)?[ .-]*(?:lang(?:uages?)?|audio|VF2)?\b",
r"\btri(?:ple)?[ .-]*(?:audio|dub\w*)\b",
r"\bdual[ .-]*(?:au?$|[aá]udio|line)\b",
r"\bdual\b(?![ .-]*sub)",
r"\b(?:audio|dub(?:bed)?)[ .-]*dual\b",
r"\bengl?(?:sub[A-Z]*)?\b",
r"\beng?sub[A-Z]*\b",
r"\b(?:DUBBED|dublado|dubbing|DUBS?)\b",
]
dual_audio_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in dual_audio_patterns]
return any(pattern.search(string) for pattern in dual_audio_patterns)

@staticmethod
def _is_complete_series(string) -> bool:
"""Check if string is a `complete series`."""
# Can be used on either movie or show item types
series_patterns = [
re.compile(
r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bbox[ .-]?set\b",
re.IGNORECASE,
),
re.compile(
r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bmini[ .-]?series\b",
re.IGNORECASE,
),
re.compile(
r"(?:\bthe\W)?(?:\bcomplete|full|all)\b.*\b(?:series|seasons|collection|episodes|set|pack|movies)\b",
re.IGNORECASE,
),
re.compile(
r"\b(?:series|seasons|movies?)\b.*\b(?:complete|collection)\b",
re.IGNORECASE,
),
re.compile(r"(?:\bthe\W)?\bultimate\b[ .]\bcollection\b", re.IGNORECASE),
re.compile(r"\bcollection\b.*\b(?:set|pack|movies)\b", re.IGNORECASE),
re.compile(r"\bcollection\b", re.IGNORECASE),
re.compile(
r"duology|trilogy|quadr[oi]logy|tetralogy|pentalogy|hexalogy|heptalogy|anthology|saga",
re.IGNORECASE,
),
r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bbox[ .-]?set\b",
r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bmini[ .-]?series\b",
r"(?:\bthe\W)?(?:\bcomplete|full|all)\b.*\b(?:series|seasons|collection|episodes|set|pack|movies)\b",
r"\b(?:series|seasons|movies?)\b.*\b(?:complete|collection)\b",
r"(?:\bthe\W)?\bultimate\b[ .]\bcollection\b",
r"\bcollection\b.*\b(?:set|pack|movies)\b",
r"\bcollection\b",
r"duology|trilogy|quadr[oi]logy|tetralogy|pentalogy|hexalogy|heptalogy|anthology|saga",
]
series_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in series_patterns]
return any(pattern.search(string) for pattern in series_patterns)

@staticmethod
def _is_unwanted_quality(string) -> bool:
"""Check if string has an 'unwanted' quality. Default to False."""
unwanted_patterns = [
re.compile(
r"\b(?:H[DQ][ .-]*)?CAM(?:H[DQ])?(?:[ .-]*Rip)?\b", re.IGNORECASE
),
re.compile(r"\b(?:H[DQ][ .-]*)?S[ .-]*print\b", re.IGNORECASE),
re.compile(r"\b(?:HD[ .-]*)?T(?:ELE)?S(?:YNC)?(?:Rip)?\b", re.IGNORECASE),
re.compile(r"\b(?:HD[ .-]*)?T(?:ELE)?C(?:INE)?(?:Rip)?\b", re.IGNORECASE),
re.compile(r"\bP(?:re)?DVD(?:Rip)?\b", re.IGNORECASE),
re.compile(r"\b(?:DVD?|BD|BR)?[ .-]*Scr(?:eener)?\b", re.IGNORECASE),
re.compile(r"\bVHS\b", re.IGNORECASE),
re.compile(r"\bHD[ .-]*TV(?:Rip)?\b", re.IGNORECASE),
re.compile(r"\bDVB[ .-]*(?:Rip)?\b", re.IGNORECASE),
re.compile(r"\bSAT[ .-]*Rips?\b", re.IGNORECASE),
re.compile(r"\bTVRips?\b", re.IGNORECASE),
re.compile(r"\bR5\b", re.IGNORECASE),
re.compile(r"\b(DivX|XviD)\b", re.IGNORECASE),
unwanted_quality = [
r"\b(?:H[DQ][ .-]*)?CAM(?:H[DQ])?(?:[ .-]*Rip)?\b",
r"\b(?:H[DQ][ .-]*)?S[ .-]*print\b",
r"\b(?:HD[ .-]*)?T(?:ELE)?S(?:YNC)?(?:Rip)?\b",
r"\b(?:HD[ .-]*)?T(?:ELE)?C(?:INE)?(?:Rip)?\b",
r"\bP(?:re)?DVD(?:Rip)?\b",
r"\b(?:DVD?|BD|BR)?[ .-]*Scr(?:eener)?\b",
r"\bVHS\b",
r"\bHD[ .-]*TV(?:Rip)?\b",
r"\bDVB[ .-]*(?:Rip)?\b",
r"\bSAT[ .-]*Rips?\b",
r"\bTVRips?\b",
r"\bR5|R6\b",
r"\b(DivX|XviD)\b",
r"\b(?:Deleted[ .-]*)?Scene(?:s)?\b",
r"\bTrailers?\b",
]
return any(pattern.search(string) for pattern in unwanted_patterns)
unwanted_quality = [re.compile(pattern, re.IGNORECASE) for pattern in unwanted_quality]
return any(pattern.search(string) for pattern in unwanted_quality)

def check_for_title_match(self, item, parsed_title, threshold=90) -> bool:
"""Check if the title matches PTN title using fuzzy matching."""
Expand All @@ -197,14 +178,11 @@ def check_for_title_match(self, item, parsed_title, threshold=90) -> bool:
target_title = item.parent.title
elif item.type == "episode":
target_title = item.parent.parent.title
match_score = fuzz.ratio(parsed_title.lower(), target_title.lower())
if match_score >= threshold:
return True
return False
return bool(fuzz.ratio(parsed_title.lower(), target_title.lower()) >= threshold)

def _get_item_language(self, item) -> str:
"""Get the language of the item."""
# This is crap. Need to switch to using a dict instead.
# TODO: This is crap. Need to switch to ranked sorting instead.
if item.type == "season":
if item.parent.language == "en":
if item.parent.is_anime:
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self, response: requests.Response, response_type=SimpleNamespace):

def handle_response(self, response: requests.Response):
"""Handle different types of responses"""
if not self.is_ok and self.status_code not in [404, 429, 509, 520, 522]:
if not self.is_ok and self.status_code not in [404, 429, 502, 509, 520, 522]:
logger.error("Error: %s %s", response.status_code, response.content)
if self.status_code in [520, 522]:
# Cloudflare error from Torrentio
Expand Down
4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"@types/luxon": "^3.3.7",
"@types/nprogress": "^0.2.3",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^6.20.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
Loading

0 comments on commit bd82b23

Please sign in to comment.