Skip to content

Commit

Permalink
Add Overseerr Support (Boerderij#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
samwiseg0 authored and MDHMatt committed May 12, 2023
1 parent 60bbcfb commit 7b071f7
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 3 deletions.
16 changes: 16 additions & 0 deletions Varken.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Needed to check version of python
from varken import structures # noqa
from varken.ombi import OmbiAPI
from varken.overseerr import OverseerrAPI
from varken.unifi import UniFiAPI
from varken import VERSION, BRANCH, BUILD_DATE
from varken.sonarr import SonarrAPI
Expand Down Expand Up @@ -156,6 +157,21 @@ def thread(job, **kwargs):
at_time = schedule.every(server.issue_status_run_seconds).seconds
at_time.do(thread, OMBI.get_issue_counts).tag("ombi-{}-get_issue_counts".format(server.id))

if CONFIG.overseerr_enabled:
for server in CONFIG.overseerr_servers:
OVERSEER = OverseerrAPI(server, DBMANAGER)
if server.get_request_total_counts:
at_time = schedule.every(server.request_total_run_seconds).seconds
at_time.do(thread, OVERSEER.get_total_requests).tag("overseerr-{}-get_total_requests".format(server.id))
if server.get_request_status_counts:
at_time = schedule.every(server.request_status_run_seconds).seconds
at_time.do(thread, OVERSEER.get_request_status_counts).tag("overseerr-{}-get_request_status_counts"
.format(server.id))
if server.get_latest_requests:
at_time = schedule.every(server.num_latest_requests_seconds).seconds
at_time.do(thread, OVERSEER.get_latest_requests).tag("overseerr-{}-get_latest_requests"
.format(server.id))

if CONFIG.sickchill_enabled:
for server in CONFIG.sickchill_servers:
SICKCHILL = SickChillAPI(server, DBMANAGER)
Expand Down
16 changes: 15 additions & 1 deletion data/varken.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ sonarr_server_ids = 1,2
radarr_server_ids = 1,2
lidarr_server_ids = false
tautulli_server_ids = 1
ombi_server_ids = 1
ombi_server_ids = false
overseerr_server_ids = 1
sickchill_server_ids = false
unifi_server_ids = false
maxmind_license_key = xxxxxxxxxxxxxxxx
Expand Down Expand Up @@ -95,6 +96,19 @@ request_total_run_seconds = 300
get_issue_status_counts = true
issue_status_run_seconds = 300

[overseerr-1]
url = overseerr.domain.tld
apikey = xxxxxxxxxxxxxxxx
ssl = false
verify_ssl = false
get_request_total_counts = true
request_total_run_seconds = 300
get_request_status_counts = true
request_status_run_seconds = 300
get_latest_requests = true
num_latest_requests_to_fetch = 10
num_latest_requests_seconds = 300

[sickchill-1]
url = sickchill.domain.tld:8081
apikey = xxxxxxxxxxxxxxxx
Expand Down
36 changes: 34 additions & 2 deletions varken/iniparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
from varken.varkenlogger import BlacklistFilter
from varken.structures import SickChillServer, UniFiServer
from varken.helpers import clean_sid_check, rfc1918_ip_check, boolcheck
from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer
from varken.structures import SonarrServer, RadarrServer, OmbiServer, OverseerrServer, TautulliServer, InfluxServer


class INIParser(object):
def __init__(self, data_folder):
self.config = None
self.data_folder = data_folder
self.filtered_strings = None
self.services = ['sonarr', 'radarr', 'lidarr', 'ombi', 'tautulli', 'sickchill', 'unifi']
self.services = ['sonarr', 'radarr', 'lidarr', 'ombi', 'overseerr', 'tautulli', 'sickchill', 'unifi']

self.logger = getLogger()
self.influx_server = InfluxServer()
Expand Down Expand Up @@ -293,6 +293,38 @@ def parse_opts(self, read_file=False):
issue_status_counts=issue_status_counts,
issue_status_run_seconds=issue_status_run_seconds)

if service == 'overseerr':
get_latest_requests = boolcheck(env.get(
f'VRKN_{envsection}_GET_LATEST_REQUESTS',
self.config.get(section, 'get_latest_requests')))
num_latest_requests_to_fetch = int(env.get(
f'VRKN_{envsection}_NUM_LATEST_REQUESTS',
self.config.getint(section, 'num_latest_requests_to_fetch')))
num_latest_requests_seconds = int(env.get(
f'VRKN_{envsection}_NUM_LATEST_REQUESTS_SECONDS',
self.config.getint(section, 'num_latest_requests_seconds')))
get_request_total_counts = boolcheck(env.get(
f'VRKN_{envsection}_GET_REQUEST_TOTAL_COUNTS',
self.config.get(section, 'get_request_total_counts')))
request_total_run_seconds = int(env.get(
f'VRKN_{envsection}_REQUEST_TOTAL_RUN_SECONDS',
self.config.getint(section, 'request_total_run_seconds')))
get_request_status_counts = boolcheck(env.get(
f'VRKN_{envsection}_GET_REQUEST_STATUS_COUNTS',
self.config.get(section, 'get_request_status_counts')))
request_status_run_seconds = int(env.get(
f'VRKN_{envsection}_REQUEST_STATUS_RUN_SECONDS',
self.config.getint(section, 'request_status_run_seconds')))

server = OverseerrServer(id=server_id, url=scheme + url, api_key=apikey,
verify_ssl=verify_ssl, get_latest_requests=get_latest_requests,
num_latest_requests_to_fetch=num_latest_requests_to_fetch,
num_latest_requests_seconds=num_latest_requests_seconds,
get_request_total_counts=get_request_total_counts,
request_total_run_seconds=request_total_run_seconds,
get_request_status_counts=get_request_status_counts,
request_status_run_seconds=request_status_run_seconds)

if service == 'sickchill':
get_missing = boolcheck(env.get(f'VRKN_{envsection}_GET_MISSING',
self.config.get(section, 'get_missing')))
Expand Down
179 changes: 179 additions & 0 deletions varken/overseerr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
from logging import getLogger
from requests import Session, Request
from datetime import datetime, timezone

from varken.helpers import connection_handler, hashit
from varken.structures import OverseerrRequest, OverseerrRequestCounts


class OverseerrAPI(object):
def __init__(self, server, dbmanager):
self.dbmanager = dbmanager
self.server = server
# Create session to reduce server web thread load, and globally define pageSize for all requests
self.session = Session()
self.session.headers = {'X-Api-Key': self.server.api_key}
self.logger = getLogger()

def __repr__(self):
return f"<overseerr-{self.server.id}>"

def get_total_requests(self):
now = datetime.now(timezone.utc).astimezone().isoformat()
endpoint = '/api/v1/request?take=200&filter=all&sort=added'

req = self.session.prepare_request(Request('GET', self.server.url + endpoint))
get_req = connection_handler(self.session, req, self.server.verify_ssl) or []

if not any([get_req]):
self.logger.error('No json replies. Discarding job')
return

tv_requests = []
movie_requests = []

for result in get_req['results']:
if result['type'] == 'tv':
try:
tv_requests.append(OverseerrRequest(**result))
except TypeError as e:
self.logger.error('TypeError has occurred : %s while creating OverseerrRequest structure for show. '
'data attempted is: %s', e, result)

if result['type'] == 'movie':
try:
movie_requests.append(OverseerrRequest(**result))
except TypeError as e:
self.logger.error('TypeError has occurred : %s while creating OverseerrRequest \
structure for movie. '
'data attempted is: %s', e, result)

if tv_requests:
tv_request_count = len(tv_requests)

if movie_requests:
movie_request_count = len(movie_requests)

influx_payload = [
{
"measurement": "Overseerr",
"tags": {
"type": "Request_Totals",
"server": self.server.id
},
"time": now,
"fields": {
"total": movie_request_count + tv_request_count,
"movies": movie_request_count,
"tv": tv_request_count
}
}
]

if influx_payload:
self.dbmanager.write_points(influx_payload)
else:
self.logger.debug("Empty dataset for overseerr module. Discarding...")

def get_request_status_counts(self):
now = datetime.now(timezone.utc).astimezone().isoformat()
endpoint = '/api/v1/request/count'

req = self.session.prepare_request(Request('GET', self.server.url + endpoint))
get_req = connection_handler(self.session, req, self.server.verify_ssl)

if not get_req:
return

requests = OverseerrRequestCounts(**get_req)
influx_payload = [
{
"measurement": "Overseerr",
"tags": {
"type": "Request_Counts"
},
"time": now,
"fields": {
"pending": requests.pending,
"approved": requests.approved,
"processing": requests.processing,
"available": requests.available
}
}
]

self.dbmanager.write_points(influx_payload)

def get_latest_requests(self):
now = datetime.now(timezone.utc).astimezone().isoformat()
endpoint = '/api/v1/request?take=' + str(self.server.num_latest_requests_to_fetch) + '&filter=all&sort=added'
movie_endpoint = '/api/v1/movie/'
tv_endpoint = '/api/v1/tv/'

# GET THE LATEST n REQUESTS
req = self.session.prepare_request(Request('GET', self.server.url + endpoint))
get_latest_req = connection_handler(self.session, req, self.server.verify_ssl)

# RETURN NOTHING IF NO RESULTS
if not get_latest_req:
return

influx_payload = []

# Request Type: Movie = 1, TV Show = 0
for result in get_latest_req['results']:
if result['type'] == 'tv':
req = self.session.prepare_request(Request('GET',
self.server.url +
tv_endpoint +
str(result['media']['tmdbId'])))
get_tv_req = connection_handler(self.session, req, self.server.verify_ssl)
hash_id = hashit(f'{get_tv_req["id"]}{get_tv_req["name"]}')

influx_payload.append(
{
"measurement": "Overseerr",
"tags": {
"type": "Requests",
"server": self.server.id,
"request_type": 0,
"status": get_tv_req['mediaInfo']['status'],
"title": get_tv_req['name'],
"requested_user": get_tv_req['mediaInfo']['requests'][0]['requestedBy']['plexUsername'],
"requested_date": get_tv_req['mediaInfo']['requests'][0]['requestedBy']['createdAt']
},
"time": now,
"fields": {
"hash": hash_id
}
}
)

if result['type'] == 'movie':
req = self.session.prepare_request(Request('GET',
self.server.url +
movie_endpoint +
str(result['media']['tmdbId'])))
get_movie_req = connection_handler(self.session, req, self.server.verify_ssl)
hash_id = hashit(f'{get_movie_req["id"]}{get_movie_req["title"]}')

influx_payload.append(
{
"measurement": "Overseerr",
"tags": {
"type": "Requests",
"server": self.server.id,
"request_type": 1,
"status": get_movie_req['mediaInfo']['status'],
"title": get_movie_req['title'],
"requested_user": get_movie_req['mediaInfo']['requests'][0]['requestedBy']['plexUsername'],
"requested_date": get_movie_req['mediaInfo']['requests'][0]['requestedBy']['createdAt']
},
"time": now,
"fields": {
"hash": hash_id
}
}
)

self.dbmanager.write_points(influx_payload)
68 changes: 68 additions & 0 deletions varken/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ class OmbiServer(NamedTuple):
verify_ssl: bool = False


class OverseerrServer(NamedTuple):
api_key: str = None
id: int = None
url: str = None
verify_ssl: bool = False
get_request_total_counts: bool = False
request_total_run_seconds: int = 30
get_request_status_counts: bool = False
request_status_run_seconds: int = 30
get_latest_requests: bool = False
num_latest_requests_to_fetch: int = 10
num_latest_requests_seconds: int = 30


class TautulliServer(NamedTuple):
api_key: str = None
fallback_ip: str = None
Expand Down Expand Up @@ -173,6 +187,60 @@ class OmbiMovieRequest(NamedTuple):
requestStatus: str = None


# Overseerr
class OverseerrRequest(NamedTuple):
id: int = None
status: int = None
createdAt: str = None
updatedAt: str = None
type: str = None
is4k: bool = None
serverId: int = None
profileId: int = None
rootFolder: str = None
languageProfileId: int = None
tags: list = None
media: dict = None
seasons: list = None
modifiedBy: dict = None
requestedBy: dict = None
seasonCount: int = None


class OverseerrRequestCounts(NamedTuple):
pending: int = None
approved: int = None
processing: int = None
available: int = None


# Overseerr
class OverseerrRequest(NamedTuple):
id: int = None
status: int = None
createdAt: str = None
updatedAt: str = None
type: str = None
is4k: bool = None
serverId: int = None
profileId: int = None
rootFolder: str = None
languageProfileId: int = None
tags: list = None
media: dict = None
seasons: list = None
modifiedBy: dict = None
requestedBy: dict = None
seasonCount: int = None


class OverseerrRequestCounts(NamedTuple):
pending: int = None
approved: int = None
processing: int = None
available: int = None


# Sonarr
class SonarrTVShow(NamedTuple):
added: str = None
Expand Down

0 comments on commit 7b071f7

Please sign in to comment.