Skip to content

Commit

Permalink
feat: Listrr Support Added (#136)
Browse files Browse the repository at this point in the history
* Start Listrr Feature
* feat: Listrr ready for review.
* small tweaks. rewrite coming later.

---------

Co-authored-by: Spoked <Spoked@localhost>
  • Loading branch information
dreulavelle and Spoked authored Jan 18, 2024
1 parent 9020ee5 commit 943b098
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 7 deletions.
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

0 comments on commit 943b098

Please sign in to comment.