Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Listrr Support Added #136

Merged
merged 4 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/program/content/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import threading
import time
from utils.logger import logger
from utils.service_manager import ServiceManager
from .mdblist import Mdblist
from .overseerr import Overseerr
from .plex_watchlist import PlexWatchlist
from utils.service_manager import ServiceManager
from .listrr import Listrr


class Content(threading.Thread):
Expand All @@ -13,7 +14,7 @@ def __init__(self, media_items):
self.initialized = False
self.key = "content"
self.running = False
self.sm = ServiceManager(media_items, False, Overseerr, Mdblist, PlexWatchlist)
self.sm = ServiceManager(media_items, False, Overseerr, PlexWatchlist, Listrr, Mdblist)
if not self.validate():
logger.error("You have no content services enabled, please enable at least one!")
return
Expand Down
107 changes: 107 additions & 0 deletions backend/program/content/listrr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Mdblist content module"""
from time import time
from typing import Optional
from pydantic import BaseModel
from utils.settings import settings_manager
from utils.logger import logger
from utils.request import get, ping
from requests.exceptions import HTTPError
from program.media.container import MediaItemContainer
from program.updaters.trakt import Updater as Trakt, get_imdbid_from_tmdb, get_imdbid_from_tvdb


class ListrrConfig(BaseModel):
enabled: bool
movie_lists: Optional[list]
show_lists: Optional[list]
api_key: Optional[str]
update_interval: int # in seconds


class Listrr:
"""Content class for Listrr"""

def __init__(self, media_items: MediaItemContainer):
self.key = "listrr"
self.url = "https://listrr.pro/api"
self.settings = ListrrConfig(**settings_manager.get(f"content.{self.key}"))
self.headers = {"X-Api-Key": self.settings.api_key}
self.initialized = self.validate_settings()
if not self.initialized:
return
self.media_items = media_items
self.updater = Trakt()
self.next_run_time = 0
logger.info("Listrr initialized!")

def validate_settings(self) -> bool:
if not self.settings.enabled:
logger.debug("Listrr is set to disabled.")
return False
if self.settings.api_key == "" or len(self.settings.api_key) != 64:
logger.error("Listrr api key is not set.")
return False
try:
response = ping("https://listrr.pro/", additional_headers=self.headers)
return response.ok
except Exception:
logger.error("Listrr url is not reachable.")
return False

def run(self):
"""Fetch media from Listrr and add them to media_items attribute."""
if time() < self.next_run_time:
return
self.next_run_time = time() + self.settings.update_interval
movie_items = self._get_items_from_Listrr("Movies", self.settings.movie_lists)
show_items = self._get_items_from_Listrr("Shows", self.settings.show_lists)
items = list(set(movie_items + show_items))
container = self.updater.create_items(items)
for item in container:
item.set("requested_by", "Listrr")
added_items = self.media_items.extend(container)
length = len(added_items)
if length >= 1 and length <= 5:
for item in added_items:
logger.info("Added %s", item.log_string)
elif length > 5:
logger.info("Added %s items", length)

def _get_items_from_Listrr(self, content_type, content_lists):
"""Fetch unique IMDb IDs from Listrr for a given type and list of content."""
unique_ids = set()
for list_id in content_lists:
page = 1
total_pages = 1
while page <= total_pages:
if list_id == "":
break
try:
response = get(
self.url + f"/List/{content_type}/{list_id}/ReleaseDate/Descending/{page}",
additional_headers=self.headers,
)
if response.is_ok:
total_pages = response.data.pages
for item in response.data.items:
imdb_id = item.imDbId
if imdb_id:
unique_ids.add(imdb_id)
elif content_type == "Shows" and item.tvDbId:
imdb_id = get_imdbid_from_tvdb(item.tvDbId)
if imdb_id:
unique_ids.add(imdb_id)
elif content_type == "Movies" and item.tmDbId:
imdb_id = get_imdbid_from_tmdb(item.tmDbId)
if imdb_id:
unique_ids.add(imdb_id)
else:
break
except HTTPError as e:
if e.response.status_code in [400, 404, 429, 500]:
break
except Exception as e:
logger.error(f"An error occurred: {e}")
break
page += 1
return list(unique_ids)
4 changes: 2 additions & 2 deletions backend/program/scrapers/torrentio.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, _):
self.key = "torrentio"
self.settings = TorrentioConfig(**settings_manager.get(f"scraping.{self.key}"))
self.minute_limiter = RateLimiter(max_calls=60, period=60, raise_on_limit=True)
self.second_limiter = RateLimiter(max_calls=1, period=1)
self.second_limiter = RateLimiter(max_calls=1, period=3)
self.initialized = self.validate_settings()
if not self.initialized:
return
Expand Down Expand Up @@ -76,7 +76,7 @@ def api_scrape(self, item):
if identifier:
url += f"{identifier}"
with self.second_limiter:
response = get(f"{url}.json", retry_if_failed=False)
response = get(f"{url}.json", retry_if_failed=False, timeout=30)
if response.is_ok:
data = {}
if len(response.data.streams) == 0:
Expand Down
15 changes: 12 additions & 3 deletions backend/program/updaters/trakt.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Trakt updater module"""
from datetime import datetime
import math
import concurrent.futures
from datetime import datetime
from os import path
from utils.logger import get_data_path, logger
from utils.request import get
Expand Down Expand Up @@ -117,7 +117,6 @@ def _map_item_from_data(data, item_type):

# API METHODS


def get_show(imdb_id: str):
"""Wrapper for trakt.tv API show method"""
url = f"https://api.trakt.tv/shows/{imdb_id}/seasons?extended=episodes,full"
Expand All @@ -130,7 +129,6 @@ def get_show(imdb_id: str):
return response.data
return []


def create_item_from_imdb_id(imdb_id: str):
"""Wrapper for trakt.tv API search method"""
url = f"https://api.trakt.tv/search/imdb/{imdb_id}?extended=full"
Expand Down Expand Up @@ -159,3 +157,14 @@ def get_imdbid_from_tvdb(tvdb_id: str) -> str:
if response.is_ok and len(response.data) > 0:
return response.data[0].show.ids.imdb
return None

def get_imdbid_from_tmdb(tmdb_id: str) -> str:
"""Get IMDb ID from TMDB ID in Trakt"""
url = f"https://api.trakt.tv/search/tmdb/{tmdb_id}?extended=full"
response = get(
url,
additional_headers={"trakt-api-version": "2", "trakt-api-key": CLIENT_ID},
)
if response.is_ok and len(response.data) > 0:
return response.data[0].movie.ids.imdb
return None
7 changes: 7 additions & 0 deletions backend/utils/default_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
"api_key": "",
"update_interval": 80
},
"listrr": {
"enabled": false,
"movie_lists": [""],
"show_lists": [""],
"api_key": "",
"update_interval": 80
},
"overseerr": {
"enabled": true,
"url": "http://localhost:5055",
Expand Down
Loading