Selaa lähdekoodia

add overseerr to homepage items

CauseFX 4 vuotta sitten
vanhempi
commit
228db5f1d3

+ 31 - 7
api/classes/organizr.class.php

@@ -43,6 +43,7 @@ class Organizr
 	use NZBGetHomepageItem;
 	use OctoPrintHomepageItem;
 	use OmbiHomepageItem;
+	use OverseerrHomepageItem;
 	use PiHoleHomepageItem;
 	use PlexHomepageItem;
 	use QBitTorrentHomepageItem;
@@ -4176,18 +4177,34 @@ class Organizr
 					'enabled' => $this->qualifyRequest($this->config['mediaSearchAuth']) && $this->config['mediaSearch'] == true && $this->config['plexToken'],
 					'type' => $this->config['mediaSearchType'],
 				),
+				'requests' => [
+					'service' => $this->config['defaultRequestService'],
+				],
 				'ombi' => array(
 					'enabled' => $this->qualifyRequest($this->config['homepageOmbiAuth']) && $this->qualifyRequest($this->config['homepageOmbiRequestAuth']) && $this->config['homepageOmbiEnabled'] == true && $this->config['ssoOmbi'] && isset($_COOKIE['Auth']),
 					'authView' => $this->qualifyRequest($this->config['homepageOmbiAuth']),
 					'authRequest' => $this->qualifyRequest($this->config['homepageOmbiRequestAuth']),
-					'sso' => ($this->config['ssoOmbi']) ? true : false,
+					'sso' => (bool)$this->config['ssoOmbi'],
 					'cookie' => isset($_COOKIE['Auth']),
-					'alias' => ($this->config['ombiAlias']) ? true : false,
-					'ombiDefaultFilterAvailable' => $this->config['ombiDefaultFilterAvailable'] ? true : false,
-					'ombiDefaultFilterUnavailable' => $this->config['ombiDefaultFilterUnavailable'] ? true : false,
-					'ombiDefaultFilterApproved' => $this->config['ombiDefaultFilterApproved'] ? true : false,
-					'ombiDefaultFilterUnapproved' => $this->config['ombiDefaultFilterUnapproved'] ? true : false,
-					'ombiDefaultFilterDenied' => $this->config['ombiDefaultFilterDenied'] ? true : false
+					'alias' => (bool)$this->config['ombiAlias'],
+					'ombiDefaultFilterAvailable' => (bool)$this->config['ombiDefaultFilterAvailable'],
+					'ombiDefaultFilterUnavailable' => (bool)$this->config['ombiDefaultFilterUnavailable'],
+					'ombiDefaultFilterApproved' => (bool)$this->config['ombiDefaultFilterApproved'],
+					'ombiDefaultFilterUnapproved' => (bool)$this->config['ombiDefaultFilterUnapproved'],
+					'ombiDefaultFilterDenied' => (bool)$this->config['ombiDefaultFilterDenied']
+				),
+				'overseerr' => array(
+					'enabled' => $this->qualifyRequest($this->config['homepageOverseerrAuth']) && $this->qualifyRequest($this->config['homepageOverseerrRequestAuth']) && $this->config['homepageOverseerrEnabled'] == true && $this->config['ssoOverseerr'] && isset($_COOKIE['connect_sid']),
+					'authView' => $this->qualifyRequest($this->config['homepageOverseerrAuth']),
+					'authRequest' => $this->qualifyRequest($this->config['homepageOverseerrRequestAuth']),
+					'sso' => (bool)$this->config['ssoOverseerr'],
+					'cookie' => isset($_COOKIE['connect_sid']),
+					'userSelectTv' => (bool)$this->config['homepageOverseerrRequestAuth'] == 'user',
+					'overseerrDefaultFilterAvailable' => (bool)$this->config['overseerrDefaultFilterAvailable'],
+					'overseerrDefaultFilterUnavailable' => (bool)$this->config['overseerrDefaultFilterUnavailable'],
+					'overseerrDefaultFilterApproved' => (bool)$this->config['overseerrDefaultFilterApproved'],
+					'overseerrDefaultFilterUnapproved' => (bool)$this->config['overseerrDefaultFilterUnapproved'],
+					'overseerrDefaultFilterDenied' => (bool)$this->config['overseerrDefaultFilterDenied']
 				),
 				'jackett' => array(
 					'homepageJackettBackholeDownload' => $this->config['homepageJackettBackholeDownload'] ? true : false
@@ -4600,6 +4617,13 @@ class Organizr
 						$class .= ' faded';
 					}
 					break;
+				case 'homepageOrderoverseerr':
+					$class = 'bg-inverse';
+					$image = 'plugins/images/tabs/overseerr.png';
+					if (!$this->config['homepageOverseerrEnabled']) {
+						$class .= ' faded';
+					}
+					break;
 				case 'homepageOrdercalendar':
 					$class = 'bg-primary';
 					$image = 'plugins/images/tabs/calendar.png';

+ 18 - 2
api/config/default.php

@@ -65,6 +65,8 @@ return [
 	'overseerrToken' => '',
 	'overseerrFallbackUser' => '',
 	'overseerrFallbackPassword' => '',
+	'overseerrDisableCertCheck' => false,
+	'overseerrUseCustomCertificate' => false,
 	'petioURL' => '',
 	'petioToken' => '',
 	'petioFallbackUser' => '',
@@ -260,10 +262,16 @@ return [
 	'homepageOmbiEnabled' => false,
 	'homepageOmbiAuth' => '1',
 	'homepageOmbiRequestAuth' => '1',
+	'homepageOverseerrEnabled' => false,
+	'homepageOverseerrAuth' => '1',
+	'homepageOverseerrRequestAuth' => '1',
 	'homepageJellyfinInstead' => false,
 	'ombiLimitUser' => false,
 	'ombiRefresh' => '600000',
 	'ombiTvDefault' => 'all',
+	'overseerrLimitUser' => false,
+	'overseerrRefresh' => '600000',
+	'overseerrTvDefault' => 'all',
 	'homepageWeatherAndAirEnabled' => false,
 	'homepageWeatherAndAirAuth' => '1',
 	'homepageWeatherAndAirRefresh' => '3600000',
@@ -321,6 +329,7 @@ return [
 	'homepageOrdercustomhtml07' => '36',
 	'homepageOrdercustomhtml08' => '37',
 	'homepageOrderuTorrent' => '38',
+	'homepageOrderoverseerr' => '39',
 	'homepageShowStreamNames' => false,
 	'homepageShowStreamNamesAuth' => '1',
 	'homepageUseCustomStreamNames' => false,
@@ -397,6 +406,7 @@ return [
 	'debugAreaAuth' => '1',
 	'commit' => 'n/a',
 	'ombiLimit' => '50',
+	'overseerrLimit' => '50',
 	'localIPFrom' => '',
 	'localIPTo' => '',
 	'sandbox' => 'allow-presentation,allow-forms,allow-same-origin,allow-pointer-lock,allow-scripts,allow-popups,allow-modals,allow-top-navigation,allow-downloads,allow-orientation-lock,allow-popups-to-escape-sandbox,allow-top-navigation-by-user-activation',
@@ -411,6 +421,11 @@ return [
 	'ombiDefaultFilterApproved' => true,
 	'ombiDefaultFilterUnapproved' => true,
 	'ombiDefaultFilterDenied' => true,
+	'overseerrDefaultFilterAvailable' => true,
+	'overseerrDefaultFilterUnavailable' => true,
+	'overseerrDefaultFilterApproved' => true,
+	'overseerrDefaultFilterUnapproved' => true,
+	'overseerrDefaultFilterDenied' => true,
 	'selfSignedCert' => '',
 	'homepagePlexRecentlyAddedMethod' => 'legacy',
 	'authProxyEnabled' => false,
@@ -582,5 +597,6 @@ return [
 	'autoExpandNavBar' => true,
 	'defaultSettingsTab' => '5',
 	'blacklisted' => '',
-	'blacklistedMessage' => 'You have been blacklisted from this site.'
-];
+	'blacklistedMessage' => 'You have been blacklisted from this site.',
+	'defaultRequestService' => 'ombi'
+];

+ 2 - 2
api/homepage/ombi.php

@@ -32,7 +32,7 @@ trait OmbiHomepageItem
 				],
 				'Misc Options' => [
 					$this->settingsOption('auth', 'homepageOmbiRequestAuth', ['label' => 'Minimum Group to Request']),
-					$this->settingsOption('select', 'ombiTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->ombiTvOptions()]),
+					$this->settingsOption('select', 'ombiTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->requestTvOptions()]),
 					$this->settingsOption('switch', 'ombiLimitUser', ['label' => 'Limit to User']),
 					$this->settingsOption('limit', 'ombiLimit'),
 					$this->settingsOption('refresh', 'ombiRefresh'),
@@ -111,7 +111,7 @@ trait OmbiHomepageItem
 					<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Requests...</h2></div>
 					<script>
 						// Ombi Requests
-						homepageRequests("' . $this->config['ombiRefresh'] . '");
+						homepageRequests("ombi", "' . $this->config['ombiRefresh'] . '");
 						// End Ombi Requests
 					</script>
 				</div>

+ 522 - 0
api/homepage/overseerr.php

@@ -0,0 +1,522 @@
+<?php
+
+trait OverseerrHomepageItem
+{
+	
+	public function overseerrSettingsArray($infoOnly = false)
+	{
+		$homepageInformation = [
+			'name' => 'Overseerr',
+			'enabled' => strpos('personal', $this->config['license']) !== false,
+			'image' => 'plugins/images/tabs/overseerr.png',
+			'category' => 'Requests',
+			'settingsArray' => __FUNCTION__
+		];
+		if ($infoOnly) {
+			return $homepageInformation;
+		}
+		$homepageSettings = [
+			'debug' => true,
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageOverseerrEnabled'),
+					$this->settingsOption('auth', 'homepageOverseerrAuth'),
+					$this->settingsOption('notice', '', ['title' => 'Attention', 'body' => 'Since Organizr supports multiple Request Providers, You must now select which service you want to submit requests through']),
+					$this->settingsOption('select', 'defaultRequestService', ['label' => 'Default Request Service', 'options' => $this->requestServiceOptions()]),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'overseerrURL'),
+					$this->settingsOption('token', 'overseerrToken'),
+					$this->settingsOption('username', 'overseerrFallbackUser', ['label' => 'Overseerr Fallback User', 'help' => 'Organizr will request an Overseerr User Token based off of this user credentials']),
+					$this->settingsOption('password', 'overseerrFallbackPassword', ['label' => 'Overseerr Fallback Password',]),
+					$this->settingsOption('disable-cert-check', 'overseerrDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'overseerrUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('auth', 'homepageOverseerrRequestAuth', ['label' => 'Minimum Group to Request']),
+					$this->settingsOption('select', 'overseerrTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->requestTvOptions(true)]),
+					$this->settingsOption('switch', 'overseerrLimitUser', ['label' => 'Limit to User']),
+					$this->settingsOption('limit', 'overseerrLimit'),
+					$this->settingsOption('refresh', 'overseerrRefresh'),
+				],
+				'Default Filter' => [
+					$this->settingsOption('switch', 'overseerrDefaultFilterAvailable', ['label' => 'Show Available', 'help' => 'Show All Available Overseerr Requests']),
+					$this->settingsOption('switch', 'overseerrDefaultFilterUnavailable', ['label' => 'Show Unavailable', 'help' => 'Show All Unavailable Overseerr Requests']),
+					$this->settingsOption('switch', 'overseerrDefaultFilterApproved', ['label' => 'Show Approved', 'help' => 'Show All Approved Overseerr Requests']),
+					$this->settingsOption('switch', 'overseerrDefaultFilterUnapproved', ['label' => 'Show Unapproved', 'help' => 'Show All Unapproved Overseerr Requests']),
+					$this->settingsOption('switch', 'overseerrDefaultFilterDenied', ['label' => 'Show Denied', 'help' => 'Show All Denied Overseerr Requests']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'overseerr'),
+				]
+			]
+		];
+		return array_merge($homepageInformation, $homepageSettings);
+	}
+	
+	public function testConnectionOverseerr()
+	{
+		if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('test'), true)) {
+			return false;
+		}
+		$headers = array(
+			"Accept" => "application/json",
+			"X-Api-Key" => $this->config['overseerrToken'],
+		);
+		$url = $this->qualifyURL($this->config['overseerrURL']);
+		try {
+			$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
+			$test = Requests::get($url . "/api/v1/settings/main", $headers, $options);
+			if ($test->success) {
+				$this->setAPIResponse('success', 'API Connection succeeded', 200);
+				return true;
+			} else {
+				$this->setResponse(401, 'API Connection failed');
+				return false;
+			}
+			
+		} catch (Requests_Exception $e) {
+			$this->writeLog('error', 'Overseerr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$this->setResponse(500, $e->getMessage());
+			return false;
+		}
+	}
+	
+	public function overseerrHomepagePermissions($key = null)
+	{
+		$permissions = [
+			'main' => [
+				'enabled' => [
+					'homepageOverseerrEnabled'
+				],
+				'auth' => [
+					'homepageOverseerrAuth'
+				],
+				'not_empty' => [
+					'overseerrURL',
+					'overseerrToken'
+				]
+			],
+			'request' => [
+				'enabled' => [
+					'homepageOverseerrEnabled'
+				],
+				'auth' => [
+					'homepageOverseerrAuth',
+					'homepageOverseerrRequestAuth'
+				],
+				'not_empty' => [
+					'overseerrURL',
+					'overseerrToken'
+				]
+			],
+			'test' => [
+				'not_empty' => [
+					'overseerrURL',
+					'overseerrToken'
+				]
+			]
+		];
+		return $this->homepageCheckKeyPermissions($key, $permissions);
+	}
+	
+	public function homepageOrderoverseerr()
+	{
+		if ($this->homepageItemPermissions($this->overseerrHomepagePermissions('main'))) {
+			return '
+				<div id="' . __FUNCTION__ . '">
+					<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Requests...</h2></div>
+					<script>
+						// Overseerr Requests
+						homepageRequests("overseerr", "' . $this->config['overseerrRefresh'] . '");
+						// End Overseerr Requests
+					</script>
+				</div>
+				';
+		}
+	}
+	
+	
+	public function getOverseerrRequests($limit = 50, $offset = 0)
+	{
+		if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
+			return false;
+		}
+		$api['count'] = [
+			'movie' => 0,
+			'tv' => 0,
+			'limit' => (integer)$limit,
+			'offset' => (integer)$offset
+		];
+		$headers = [
+			"Accept" => "application/json",
+			"X-Api-Key" => $this->config['overseerrToken'],
+		];
+		$requests = [];
+		$url = $this->qualifyURL($this->config['overseerrURL']);
+		try {
+			$options = $this->requestOptions($url, $this->config['overseerrRefresh'], $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
+			$request = Requests::get($url . "/api/v1/request?take=" . $limit, $headers, $options);
+			if ($request->success) {
+				$requestsData = json_decode($request->body, true);
+				foreach ($requestsData['results'] as $key => $value) {
+					$requester = ($value['requestedBy']['username'] !== '') ? $value['requestedBy']['username'] : $value['requestedBy']['plexUsername'];
+					$requesterEmail = $value['requestedBy']['email'];
+					$proceed = (($this->config['overseerrLimitUser']) && strtolower($this->user['username']) == strtolower($requester)) || (strtolower($requester) == strtolower($this->config['ombiFallbackUser'])) || (!$this->config['ombiLimitUser']) || $this->qualifyRequest(1);
+					if ($proceed) {
+						$requestItem = Requests::get($url . '/api/v1/' . $value['type'] . '/' . $value['media']['tmdbId'], $headers, $options);
+						$requestsItemData = json_decode($requestItem->body, true);
+						if ($requestItem->success) {
+							$api['count'][$value['type']]++;
+							$requests[] = array(
+								'id' => $value['media']['tmdbId'],
+								'title' => ($value['type'] == 'movie') ? $requestsItemData['title'] : $requestsItemData['name'],
+								'overview' => $requestsItemData['overview'],
+								'poster' => (isset($requestsItemData['posterPath']) && $requestsItemData['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $requestsItemData['posterPath'] : 'plugins/images/cache/no-list.png',
+								'background' => (isset($requestsItemData['backdropPath']) && $requestsItemData['backdropPath'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $requestsItemData['backdropPath'] : '',
+								'approved' => $value['status'] == 2,
+								'available' => $value['media']['status'] == 5,
+								'denied' => $value['status'] == 3,
+								'deniedReason' => 'n/a',
+								'user' => $requester,
+								'userAlias' => $value['requestedBy']['displayName'],
+								'request_id' => $value['id'],
+								'request_date' => $value['createdAt'],
+								'release_date' => ($value['type'] == 'movie') ? $requestsItemData['releaseDate'] : $requestsItemData['firstAirDate'],
+								'type' => $value['type'],
+								'icon' => 'mdi mdi-' . ($value['type'] == 'movie') ? 'filmstrip' : 'television',
+								'color' => ($value['type'] == 'movie') ? 'palette-Deep-Purple-900 bg white' : 'grayish-blue-bg',
+							);
+						}
+					}
+				}
+				//sort here
+				usort($requests, function ($item1, $item2) {
+					if ($item1['request_date'] == $item2['request_date']) {
+						return 0;
+					}
+					return $item1['request_date'] > $item2['request_date'] ? -1 : 1;
+				});
+			}
+		} catch (Requests_Exception $e) {
+			$this->writeLog('error', 'Overseerr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$this->setResponse(500, $e->getMessage());
+			return false;
+		}
+		$api['content'] = isset($requests) ? array_slice($requests, $offset, $limit) : false;
+		$this->setResponse(200, null, $api);
+		return $api;
+	}
+	
+	public function getDefaultService($services)
+	{
+		if (empty($services)) {
+			return null;
+		} else {
+			$default = false;
+			foreach ($services as $service) {
+				if ($service['isDefault']) {
+					$default = (int)$service['id'];
+				}
+			}
+			if ($default) {
+				return $services[$default];
+			} else {
+				return $services[0];
+			}
+		}
+	}
+	
+	public function addOverseerrRequest($id, $type, $seasons = null)
+	{
+		$id = ($id) ?? null;
+		$type = ($type) ?? null;
+		if (!$id) {
+			$this->setAPIResponse('error', 'Id was not supplied', 422);
+			return false;
+		}
+		if (!$type) {
+			$this->setAPIResponse('error', 'Type was not supplied', 422);
+			return false;
+		}
+		if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
+			return false;
+		}
+		$url = $this->qualifyURL($this->config['overseerrURL']);
+		try {
+			if (!isset($_COOKIE['connect_sid']) && !isset($_COOKIE['connect.sid'])) {
+				$this->setAPIResponse('error', 'User does not have Auth Cookie', 500);
+				return false;
+			}
+			$headers = array(
+				'Accept' => 'application/json',
+				'Content-Type' => 'application/json',
+				'X-Api-Key' => $this->config['overseerrToken']
+			);
+			$cookieJar = new Requests_Cookie_Jar(['connect.sid' => $_COOKIE['connect_sid']]);
+			$optionsUser = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate'], ['cookies' => $cookieJar]);
+			$optionsAPI = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
+			// Check if requested already
+			$searchResponse = Requests::get($url . '/api/v1/request/', $headers, $optionsAPI);
+			if ($searchResponse->success) {
+				$details = json_decode($searchResponse->body, true);
+				if (count($details['results']) > 0) {
+					foreach ($details['results'] as $k => $v) {
+						if ($v['media']['tmdbId'] == $id) {
+							if ($v['media']['status'] == 5) {
+								$this->setAPIResponse('error', 'Request is already available', 409);
+								return false;
+							} else {
+								$this->setAPIResponse('error', 'Request is already requested', 409);
+								return false;
+							}
+						}
+					}
+				}
+			} else {
+				$this->setAPIResponse('error', 'Overseerr Connection Error Occurred', 500);
+				return false;
+			}
+			// Get User info
+			$response = Requests::get($url . '/api/v1/auth/me', [], $optionsUser);
+			if ($response->success) {
+				$userInfo = json_decode($response->body, true);
+			} else {
+				$this->setResponse(500, 'Error getting user information');
+				return false;
+			}
+			switch ($type) {
+				case 'season':
+				case 'tv':
+					if ($this->config['overseerrTvDefault'] == 'user') {
+						if ($seasons) {
+							if (strpos($seasons, ',') !== false) {
+								$seasonsExplode = explode(',', $seasons);
+								foreach ($seasonsExplode as $season) {
+									$seasonsArray[] = (int)$season;
+								}
+								$seasons = $seasonsArray;
+							} else {
+								$seasonsArray[] = (int)$seasons;
+								$seasons = $seasonsArray;
+							}
+						} else {
+							$this->setResponse(500, 'Seasons requested was not supplied');
+							return false;
+						}
+					}
+					$response = Requests::get($url . '/api/v1/tv/' . $id, $headers, $optionsAPI);
+					if ($response->success) {
+						$seriesInfo = json_decode($response->body, true);
+					} else {
+						$this->setResponse(500, 'Error getting series information');
+						return false;
+					}
+					if ($this->config['overseerrTvDefault'] == 'first') {
+						$seasons = [1];
+					} elseif ($this->config['overseerrTvDefault'] == 'last') {
+						$lastSeason = end($seriesInfo['seasons']);
+						$lastSeasonNumber = $lastSeason['seasonNumber'];
+						$seasons = [$lastSeasonNumber];
+					} elseif ($this->config['overseerrTvDefault'] == 'all') {
+						$seasons = [];
+						foreach ($seriesInfo['seasons'] as $season) {
+							if ($season['seasonNumber'] !== 0) {
+								$seasons[] = $season['seasonNumber'];
+							}
+						}
+					}
+					$response = Requests::get($url . '/api/v1/service/sonarr', $headers, $optionsAPI);
+					if ($response->success) {
+						$serviceInfo = $this->getDefaultService(json_decode($response->body, true));
+					} else {
+						$this->setResponse(500, 'Error getting service information');
+						return false;
+					}
+					$add = [
+						'mediaId' => (int)$id,
+						'tvdbId' => $seriesInfo['externalIds']['tvdbId'],
+						'mediaType' => 'tv',
+						'is4k' => $serviceInfo['is4k'],
+						'seasons' => $seasons,
+						'serverId' => (int)$serviceInfo['id'],
+						'profileId' => (int)$serviceInfo['activeProfileId'],
+						'rootFolder' => $serviceInfo['activeDirectory'],
+						'languageProfileId' => (int)$serviceInfo['activeLanguageProfileId'],
+						'userId' => (int)$userInfo['id'],
+						'tags' => []
+					];
+					break;
+				default:
+					$response = Requests::get($url . '/api/v1/service/radarr', $headers, $optionsAPI);
+					if ($response->success) {
+						$serviceInfo = $this->getDefaultService(json_decode($response->body, true));
+					} else {
+						$this->setResponse(500, 'Error getting service information');
+						return false;
+					}
+					$add = [
+						'mediaId' => (int)$id,
+						'mediaType' => 'movie',
+						'is4k' => $serviceInfo['is4k'],
+						'serverId' => (int)$serviceInfo['id'],
+						'profileId' => (int)$serviceInfo['activeProfileId'],
+						'userId' => (int)$userInfo['id'],
+						'tags' => []
+					];
+					break;
+			}
+			$response = Requests::post($url . "/api/v1/request", ['Accept' => 'application/json', 'Content-Type' => 'application/json'], json_encode($add), $optionsUser);
+			if ($response->success) {
+				$this->setAPIResponse('success', 'Overseerr Request submitted', 200);
+				return true;
+			} else {
+				$message = 'Overseerr Error Occurred';
+				if ($this->isJSON($response->body)) {
+					$messageJSON = json_decode($response->body, true);
+					$message = $messageJSON['message'] ?? $message;
+				}
+				$this->setAPIResponse('error', $message, 500);
+				return false;
+			}
+		} catch (Requests_Exception $e) {
+			$this->writeLog('error', 'Overseerr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$this->setAPIResponse('error', $e->getMessage(), 500);
+			return false;
+		}
+	}
+	
+	public function actionOverseerrRequest($id, $type, $action)
+	{
+		$id = ($id) ?? null;
+		$type = ($type) ?? null;
+		$action = ($action) ?? null;
+		if (!$id) {
+			$this->setAPIResponse('error', 'Id was not supplied', 422);
+			return false;
+		}
+		if (!$type) {
+			$this->setAPIResponse('error', 'Type was not supplied', 422);
+			return false;
+		}
+		if (!$action) {
+			$this->setAPIResponse('error', 'Action was not supplied', 422);
+			return false;
+		}
+		if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
+			return false;
+		}
+		$url = $this->qualifyURL($this->config['overseerrURL']);
+		$headers = array(
+			"Accept" => "application/json",
+			"Content-Type" => "application/json",
+			'X-Api-Key' => $this->config['overseerrToken']
+		);
+		try {
+			$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
+			switch ($action) {
+				case 'approve':
+					$response = Requests::post($url . "/api/v1/request/" . $id . "/approve", $headers, [], $options);
+					$message = 'Overseerr Request has been approved';
+					break;
+				case 'pending':
+					$response = Requests::post($url . '/api/v1/request/' . $id . '/pending', $headers, [], $options);
+					$message = 'Overseerr Request has been approved';
+					break;
+				case 'available':
+					$requestInfoResponse = Requests::get($url . '/api/v1/request/' . $id, $headers, $options);
+					if ($requestInfoResponse->success) {
+						$requestInfo = json_decode($requestInfoResponse->body, true);
+						$mediaId = $requestInfo['media']['id'];
+					} else {
+						$this->setResponse(500, 'Error getting request information');
+						return false;
+					}
+					$response = Requests::post($url . '/api/v1/media/' . $mediaId . '/available', $headers, [], $options);
+					$message = 'Overseerr Request has been marked available';
+					break;
+				case 'unavailable':
+					$requestInfoResponse = Requests::get($url . '/api/v1/request/' . $id, $headers, $options);
+					if ($requestInfoResponse->success) {
+						$requestInfo = json_decode($requestInfoResponse->body, true);
+						$mediaId = $requestInfo['media']['id'];
+					} else {
+						$this->setResponse(500, 'Error getting request information');
+						return false;
+					}
+					$response = Requests::post($url . "/api/v1/media/" . $mediaId . "/pending", $headers, [], $options);
+					$message = 'Overseerr Request has been marked unavailable';
+					break;
+				case 'deny':
+					$response = Requests::post($url . "/api/v1/request/" . $id . "/decline", $headers, [], $options);
+					$message = 'Overseerr Request has been denied';
+					break;
+				case 'delete':
+					$response = Requests::delete($url . "/api/v1/request/" . $id, $headers, $options);
+					$message = 'Overseerr Request has been deleted';
+					break;
+				default:
+					return false;
+			}
+			if ($response->success) {
+				$this->setAPIResponse('success', $message, 200);
+				return true;
+			} else {
+				$message = 'Overseerr Error Occurred';
+				if ($this->isJSON($response->body)) {
+					$messageJSON = json_decode($response->body, true);
+					$message = $messageJSON['message'] ?? $message;
+				}
+				$this->setAPIResponse('error', $message, 500);
+				return false;
+			}
+		} catch (Requests_Exception $e) {
+			$this->writeLog('error', 'Overseerr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$this->setAPIResponse('error', $e->getMessage(), 500);
+			return false;
+		}
+	}
+	
+	public function getOverseerrMetadata($id, $type)
+	{
+		if (!$id) {
+			$this->setAPIResponse('error', 'Id was not supplied', 422);
+			return false;
+		}
+		if (!$type) {
+			$this->setAPIResponse('error', 'Type was not supplied', 422);
+			return false;
+		}
+		if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('request'), true)) {
+			return false;
+		}
+		try {
+			$url = $this->qualifyURL($this->config['overseerrURL']);
+			$headers = array(
+				'Accept' => 'application/json',
+				'Content-Type' => 'application/json',
+				'X-Api-Key' => $this->config['overseerrToken']
+			);
+			$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
+			$response = Requests::get($url . '/api/v1/' . $type . '/' . $id, $headers, $options);
+			if ($response->success) {
+				$metadata = json_decode($response->body, true);
+				$this->setResponse(200, null, $metadata);
+				return $metadata;
+			} else {
+				$this->setResponse(500, 'Error getting series information');
+				return false;
+			}
+		} catch (Requests_Exception $e) {
+			$this->writeLog('error', 'Overseerr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$this->setAPIResponse('error', $e->getMessage(), 500);
+			return false;
+		}
+	}
+	
+	public function overseerrTVDefault($type)
+	{
+		return $type == $this->config['overseerrTvDefault'];
+	}
+}

+ 45 - 22
api/v2/routes/connectionTester.php

@@ -346,6 +346,29 @@ $app->post('/test/ombi', function ($request, $response, $args) {
 		->withHeader('Content-Type', 'application/json;charset=UTF-8')
 		->withStatus($GLOBALS['responseCode']);
 	
+});
+$app->post('/test/overseerr', function ($request, $response, $args) {
+	/**
+	 * @OA\Post(
+	 *     security={{ "api_key":{} }},
+	 *     tags={"test connection"},
+	 *     path="/api/v2/test/overseerr",
+	 *     summary="Test connection to Overseerr",
+	 *     @OA\Response(response="200",description="Success",@OA\JsonContent(ref="#/components/schemas/success-message")),
+	 *     @OA\Response(response="401",description="Unauthorized",@OA\JsonContent(ref="#/components/schemas/unauthorized-message")),
+	 *     @OA\Response(response="422",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
+	 *     @OA\Response(response="500",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
+	 * )
+	 */
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	if ($Organizr->qualifyRequest(1, true)) {
+		$Organizr->testConnectionOverseerr($Organizr->apiData($request));
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+	
 });
 $app->post('/test/nzbget', function ($request, $response, $args) {
 	/**
@@ -371,27 +394,27 @@ $app->post('/test/nzbget', function ($request, $response, $args) {
 	
 });
 $app->post('/test/utorrent', function ($request, $response, $args) {
-        /**
-         * @OA\Post(
-         *     security={{ "api_key":{} }},
-         *     tags={"test connection"},
-         *     path="/api/v2/test/utorrent",
-         *     summary="Test connection to uTorrent",
-         *     @OA\Response(response="200",description="Success",@OA\JsonContent(ref="#/components/schemas/success-message")),
-         *     @OA\Response(response="401",description="Unauthorized",@OA\JsonContent(ref="#/components/schemas/unauthorized-message")),
-         *     @OA\Response(response="400",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
-         *     @OA\Response(response="500",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
-         * )
-         */
-        $Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
-        if ($Organizr->qualifyRequest(1, true)) {
-                $Organizr->testConnectionuTorrent($Organizr->apiData($request));
-        }
-        $response->getBody()->write(jsonE($GLOBALS['api']));
-        return $response
-                ->withHeader('Content-Type', 'application/json;charset=UTF-8')
-                ->withStatus($GLOBALS['responseCode']);
-
+	/**
+	 * @OA\Post(
+	 *     security={{ "api_key":{} }},
+	 *     tags={"test connection"},
+	 *     path="/api/v2/test/utorrent",
+	 *     summary="Test connection to uTorrent",
+	 *     @OA\Response(response="200",description="Success",@OA\JsonContent(ref="#/components/schemas/success-message")),
+	 *     @OA\Response(response="401",description="Unauthorized",@OA\JsonContent(ref="#/components/schemas/unauthorized-message")),
+	 *     @OA\Response(response="400",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
+	 *     @OA\Response(response="500",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
+	 * )
+	 */
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	if ($Organizr->qualifyRequest(1, true)) {
+		$Organizr->testConnectionuTorrent($Organizr->apiData($request));
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+	
 });
 $app->post('/test/deluge', function ($request, $response, $args) {
 	/**
@@ -553,4 +576,4 @@ $app->post('/test/tautulli', function ($request, $response, $args) {
 		->withHeader('Content-Type', 'application/json;charset=UTF-8')
 		->withStatus($GLOBALS['responseCode']);
 	
-});
+});

+ 26 - 0
js/custom.js

@@ -1896,4 +1896,30 @@ $(document).on('click', '.nav-non-mobile li a', function() {
 	let menu = $(this).parent().parent().attr('data-dropdown');
 	$('.' + menu).val('#' + id);
 
+});
+
+// TOGGLE OVERSEERR ALL SEASONS
+$(document).on("change", ".select-all-overseerr-seasons", function () {
+	var enabled = $(this).prop("checked") ? 1 : 0;
+	$.each($('.overseerr-season'), function(i,v) {
+		let seasonEnabled = $(v).prop("checked") ? 1 : 0;
+		if(enabled !== seasonEnabled){
+			$(v).trigger('click');
+		}
+	});
+});
+
+$(document).on("change", ".overseerr-season", function () {
+	let enableButtonDisabled = true;
+	let requestedSeasons = [];
+	$.each($('.overseerr-season'), function(i,v) {
+		let seasonEnabled = $(v).prop("checked") ? 1 : 0;
+		if(seasonEnabled){
+			let seasonNumber = $(v).attr('data-seasonNumber');
+			requestedSeasons.push(seasonNumber);
+			enableButtonDisabled = false;
+		}
+	});
+	$('.submit-overseerr-seasons').attr('disabled', enableButtonDisabled);
+	$('.submit-overseerr-seasons').attr('data-seasons', requestedSeasons);
 });

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
js/custom.min.js


+ 228 - 72
js/functions.js

@@ -5094,7 +5094,7 @@ function buildRequestAdminMenuItem(value,category,id,type){
 			}else{
 				action = 'approve';
 				text = 'Approve';
-				extra = `<li><a class="mouse" onclick="ombiActions('`+id+`', 'deny', '`+type+`');" lang="en">Deny</a></li>`;
+				extra = `<li><a class="mouse" onclick="requestActions('`+id+`', 'deny', '`+type+`');" lang="en">Deny</a></li>`;
 			}
 			break;
 		case 'available':
@@ -5109,18 +5109,19 @@ function buildRequestAdminMenuItem(value,category,id,type){
 		default:
 
 	}
-	return (action) ? `<li><a class="mouse" onclick="ombiActions('`+id+`', '`+action+`', '`+type+`');" lang="en">`+text+`</a></li>`+extra : '';
+	return (action) ? `<li><a class="mouse" onclick="requestActions('`+id+`', '`+action+`', '`+type+`');" lang="en">`+text+`</a></li>`+extra : '';
 }
 function buildRequestItem(array, extra=null){
 	var items = '';
+	let service = activeInfo.settings.homepage.requests.service;
 	$.each(array, function(i,v) {
 			if(extra == null){
-                var approveID = (v.type == 'tv') ? v.id : v.request_id;
+                var approveID = (v.type == 'tv' && service === 'ombi') ? v.id : v.request_id;
                 var iconType = (v.type == 'tv') ? 'fa-tv ' : 'fa-film';
 				var badge = '';
 				var badge2 = '';
 				var bg = (v.background.includes('.')) ? v.background : 'plugins/images/cache/no-np.png';
-				v.user = (activeInfo.settings.homepage.ombi.alias) ? v.userAlias : v.user;
+				v.user = (activeInfo.settings.homepage.ombi.alias && service === 'ombi') ? v.userAlias : v.user;
 				//Set Status
 				var status = (v.approved) ? '<span class="badge bg-org m-r-10" lang="en">Approved</span>' : '<span class="badge bg-danger m-r-10" lang="en">Unapproved</span>';
 				status += (v.available) ? '<span class="badge bg-org m-r-10" lang="en">Available</span>' : '<span class="badge bg-danger m-r-10" lang="en">Unavailable</span>';
@@ -5141,7 +5142,7 @@ function buildRequestItem(array, extra=null){
 						<li class="divider"></li>
 						`+buildRequestAdminMenuItem(v.approved, 'approved',approveID,v.type)+`
 						`+buildRequestAdminMenuItem(v.available, 'available',approveID,v.type)+`
-						<li><a class="mouse" onclick="ombiActions('`+v.request_id+`', 'delete', '`+v.type+`');" lang="en">Delete</a></li>
+						<li><a class="mouse" onclick="requestActions('`+v.request_id+`', 'delete', '`+v.type+`');" lang="en">Delete</a></li>
                     </ul>
                 </div>`;
 				adminFunctions = (activeInfo.user.groupID <= 1) ? adminFunctions : '';
@@ -5376,48 +5377,48 @@ function buildPlaylist(array, type){
     </div>
 	` : '';
 }
-function buildRequest(array){
-	var requests = (typeof array.content !== 'undefined') ? true : false;
+function buildRequest(service, div, array){
+	var requests = (typeof array.content !== 'undefined');
 	var dropdown = '';
 	var headerAlt = '';
 	var header = '';
-	var ombiButton = (activeInfo.settings.homepage.ombi.enabled == true) ? `<button href="#new-request" id="newRequestButton" class="btn btn-info waves-effect waves-light inline-popups" data-effect="mfp-zoom-out"><i class="fa fa-search m-l-5"></i></button>` : '';
+	var requestButton = (activeInfo['settings']['homepage'][service]['enabled'] === true) ? `<button href="#new-request" id="newRequestButton" class="btn btn-info waves-effect waves-light inline-popups" data-effect="mfp-zoom-out"><i class="fa fa-search m-l-5"></i></button>` : '';
 	if(requests){
 		var builtDropdown = `
-		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('request-items','previous');"><i class="fa fa-chevron-left"></i></button>
-		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('request-items','next');"><i class="fa fa-chevron-right"></i></button>
+		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('request-items-${service}','previous');"><i class="fa fa-chevron-left"></i></button>
+		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('request-items-${service}','next');"><i class="fa fa-chevron-right"></i></button>
 		<button aria-expanded="false" data-toggle="dropdown" class="btn btn-info dropdown-toggle waves-effect waves-light" type="button">
 			<i class="fa fa-filter m-r-5"></i><span class="caret"></span>
 		</button>
-		`+ombiButton+`
+		`+requestButton+`
 		<div role="menu" class="dropdown-menu request-filter">
 			<div class="checkbox checkbox-success m-l-20 checkbox-circle">
-				<input id="request-filter-available" data-filter="request-available" class="filter-request-input" type="checkbox" checked="">
-				<label for="request-filter-available"> <span lang="en">Available</span> </label>
+				<input id="request-filter-available-${service}" data-filter="request-available" class="filter-request-input" type="checkbox" checked="">
+				<label for="request-filter-available-${service}"> <span lang="en">Available</span> </label>
 			</div>
 			<div class="checkbox checkbox-danger m-l-20 checkbox-circle">
-				<input id="request-filter-unavailable" data-filter="request-unavailable"  class="filter-request-input" type="checkbox" checked="">
-				<label for="request-filter-unavailable"> <span lang="en">Unavailable</span> </label>
+				<input id="request-filter-unavailable-${service}" data-filter="request-unavailable"  class="filter-request-input" type="checkbox" checked="">
+				<label for="request-filter-unavailable-${service}"> <span lang="en">Unavailable</span> </label>
 			</div>
 			<div class="checkbox checkbox-info m-l-20 checkbox-circle">
-				<input id="request-filter-approved" data-filter="request-approved" class="filter-request-input" type="checkbox"  checked="">
-				<label for="request-filter-approved"> <span lang="en">Approved</span> </label>
+				<input id="request-filter-approved-${service}" data-filter="request-approved" class="filter-request-input" type="checkbox"  checked="">
+				<label for="request-filter-approved-${service}"> <span lang="en">Approved</span> </label>
 			</div>
 			<div class="checkbox checkbox-warning m-l-20 checkbox-circle">
-				<input id="request-filter-unapproved" data-filter="request-unapproved" class="filter-request-input" type="checkbox" checked="">
-				<label for="request-filter-unapproved"> <span lang="en">Unapproved</span> </label>
+				<input id="request-filter-unapproved-${service}" data-filter="request-unapproved" class="filter-request-input" type="checkbox" checked="">
+				<label for="request-filter-unapproved-${service}"> <span lang="en">Unapproved</span> </label>
 			</div>
 			<div class="checkbox checkbox-purple m-l-20 checkbox-circle">
-				<input id="request-filter-denied" data-filter="request-denied" class="filter-request-input" type="checkbox" checked="">
-				<label for="request-filter-denied"> <span lang="en">Denied</span> </label>
+				<input id="request-filter-denied-${service}" data-filter="request-denied" class="filter-request-input" type="checkbox" checked="">
+				<label for="request-filter-denied-${service}"> <span lang="en">Denied</span> </label>
 			</div>
 			<div class="checkbox checkbox-inverse m-l-20 checkbox-circle">
-				<input id="request-filter-movie" data-filter="request-movie" class="filter-request-input" type="checkbox" checked="">
-				<label for="request-filter-movie"> <span lang="en">Movie</span> </label>
+				<input id="request-filter-movie-${service}" data-filter="request-movie" class="filter-request-input" type="checkbox" checked="">
+				<label for="request-filter-movie-${service}"> <span lang="en">Movie</span> </label>
 			</div>
 			<div class="checkbox checkbox-inverse m-l-20 checkbox-circle">
-				<input id="request-filter-tv" data-filter="request-tv" class="filter-request-input" type="checkbox" checked="">
-				<label for="request-filter-tv"> <span lang="en">TV</span> </label>
+				<input id="request-filter-tv-${service}" data-filter="request-tv" class="filter-request-input" type="checkbox" checked="">
+				<label for="request-filter-tv-${service}"> <span lang="en">TV</span> </label>
 			</div>
 		</div>
 
@@ -5426,7 +5427,7 @@ function buildRequest(array){
 	if(activeInfo.settings.homepage.options.alternateHomepageHeaders){
 		var headerAlt = `
 		<div class="col-md-12">
-			<h4 class="pull-left homepage-element-title"><span class="mouse" onclick="homepageRequests()" lang="en">Requests</span> : </h4><h4 class="pull-left">&nbsp;</h4>
+			<h4 class="pull-left homepage-element-title"><span class="mouse" onclick="homepageRequests('${service}')" lang="en">Requests</span> : </h4><h4 class="pull-left">&nbsp;</h4>
 			<div class="btn-group pull-right">
 				`+builtDropdown+`
 			</div>
@@ -5437,7 +5438,7 @@ function buildRequest(array){
 	}else{
 		var header = `
 		<div class="panel-heading bg-info p-t-10 p-b-10">
-			<span class="pull-left m-t-5 mouse homepage-element-title" onclick="homepageRequests()"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/ombi.png"> &nbsp; <span lang="en">Requests</span></span>
+			<span class="pull-left m-t-5 mouse homepage-element-title" onclick="homepageRequests('${service}')"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/`+service+`.png"> &nbsp; <span lang="en">Requests</span></span>
 			<div class="btn-group pull-right">
 					`+builtDropdown+`
 			</div>
@@ -5446,13 +5447,13 @@ function buildRequest(array){
 		`;
 	}
 	return (requests) ? `
-	<div id="ombi-requests" class="row">
+	<div id="${service}-requests" class="row">
 		`+headerAlt+`
         <div class="col-lg-12">
             <div class="panel panel-default">
 				`+header+`
                 <div class="panel-wrapper p-b-0 collapse in">
-				<div class="owl-carousel owl-theme request-items">
+				<div class="owl-carousel owl-theme request-items-`+service+`">
 					`+buildRequestItem(array.content)+`
 				</div>
 				`+buildRequestItem(array.content, true)+`
@@ -5625,34 +5626,164 @@ function buildRequestResult(array,media_type=null,list=null,page=null,search=fal
 	`;
 	return buttons+next+results+next;
 }
+function buildRequestOverseerrSeasons(array){
+	var hasSeasons = (typeof array.data.seasons !== 'undefined');
+	if(hasSeasons){
+		let seasons = array.data.seasons;
+		let id = array.data.id;
+		let SeasonItems = '';
+		$.each(seasons, function(i,v) {
+			if(v.seasonNumber !== 0) {
+				SeasonItems += `
+					<tr>
+						<td><input type="checkbox" name="overseerr-season-${v.seasonNumber}" class="js-switch overseerr-season" data-seasonNumber="${v.seasonNumber}" data-color="#6164c1" data-size="small" /></td>
+						<td>${v.name}</td>
+						<td>${v.episodeCount}</td>
+					</tr>
+				`;
+			}
+		});
+		let html = `
+			<div class="panel">
+				<div class="bg-org2">
+					<div class="panel-heading">Choose Seasons</div>
+					<div class="panel-wrapper collapse in text-left">
+						<div class="table-responsive">
+							<table class="table color-bordered-table primary-bordered-table">
+								<thead>
+									<tr>
+										<th width="20"><input type="checkbox" class="js-switch select-all-overseerr-seasons" data-color="#6164c1" data-size="small" /></th>
+										<th lang="en">Season</th>
+										<th lang="en"># Of Episodes</th>
+									</tr>
+								</thead>
+								<tbody>${SeasonItems}</tbody>
+							</table>
+						</div>
+						<div class="pull-right p-b-20">
+							<button class="fcbtn btn btn-info btn-outline btn-1c" lang="en" onclick="swal.close();">Cancel</button>
+							<button class="fcbtn btn btn-success btn-outline btn-1c submit-overseerr-seasons" lang="en" data-seasons="[]" data-id="${id}" disabled onclick="processOverseerrSeasons(this)">Request Seasons</button>
+						</div>
+					</div>
+				</div>
+			</div>
+			`;
+		swal({
+			content: createElementFromHTML(html),
+			button: null,
+			className: 'bg-org',
+			dangerMode: false
+		});
+	}
+}
+
+function processOverseerrSeasons(el){
+	let seasons = $(el).attr('data-seasons');
+	let id = $(el).attr('data-id');
+	overseerrActions(id,'add','tv', seasons);
+}
 function processRequest(id,type){
-	if(type == 'tv'){
-		/*requestNewID(id).success(function(data) {
-			var newID = data.tvdb_id;
-			ombiActions(newID,'add',type);
-		}).fail(function(xhr) {
-			console.error("Organizr Function: TMDB Connection Failed");
-		});*/
-		ombiActions(id,'add',type);
-	}else{
-		ombiActions(id,'add',type);
+	let service = activeInfo.settings.homepage.requests.service;
+	switch (service) {
+		case 'ombi':
+			requestActions(id,'add',type);
+			return false;
+		case 'overseerr':
+			if(type  === 'tv' && activeInfo.settings.homepage.overseerr.userSelectTv === true){
+				organizrAPI2('GET','api/v2/homepage/overseerr/metadata/' + type + '/' + id).success(function(data) {
+					try {
+						let response = data.response;
+						buildRequestOverseerrSeasons(response);
+					}catch(e) {
+						organizrCatchError(e,data);
+					}
+				}).fail(function(xhr) {
+					OrganizrApiError(xhr, 'Overseerr Error');
+				});
+			}else{
+				requestActions(id,'add',type);
+			}
+			return false;
+		default:
+			organizrConsole('Request Function','Service for Processing not setup', 'error');
+			return false;
+	}
+}
+function requestActions(id = null, action = null, type = null, extra = null){
+	let service = activeInfo.settings.homepage.requests.service;
+	switch (service) {
+		case 'ombi':
+			ombiActions(id,action,type,extra);
+			break;
+		case 'overseerr':
+			overseerrActions(id,action,type,extra);
+			break;
+		default:
+			organizrConsole('Request Function','Service for Request not setup', 'error');
+			return false;
+	}
+}
+//Overseerr Actions
+function overseerrActions(id, action, type = null, extra = null){
+	ajaxloader('.request-' + id + '-div', 'in');
+	ajaxloader('.preloader-' + id, 'in');
+	//$.magnificPopup.close();
+	messageSingle(window.lang.translate('Submitting Action to Overseerr'),'',activeInfo.settings.notifications.position,"#FFF",'success',"10000");
+	switch (action){
+		case 'add':
+			let seasons = (extra !== null) ? '/' + extra : '';
+			var method = 'POST';
+			var apiUrl = 'api/v2/homepage/overseerr/requests/'+type+'/' + id + seasons;
+			var data = {};
+			break;
+		case 'available':
+		case 'pending':
+		case 'unavailable':
+		case 'approve':
+			var method = 'POST';
+			var apiUrl = 'api/v2/homepage/overseerr/requests/'+type+'/' + id + '/' + action;
+			var data = {};
+			break;
+		case 'deny':
+			var method = 'PUT';
+			var apiUrl = 'api/v2/homepage/overseerr/requests/'+type+'/' + id + '/' + action;
+			var data = {};
+			break;
+		case 'delete':
+			var method = 'DELETE';
+			var apiUrl = 'api/v2/homepage/overseerr/requests/'+type+'/' + id;
+			var data = {};
+			break;
+		default:
+			return false;
 	}
+	organizrAPI2(method,apiUrl,data).success(function(data) {
+		try {
+			let response = data.response;
+			messageSingle(response.message,'',activeInfo.settings.notifications.position,"#FFF","success","5000");
+			homepageRequests('overseerr');
+			swal.close();
+			ajaxloader();
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+	}).fail(function(xhr) {
+		ajaxloader();
+		OrganizrApiError(xhr, 'Overseerr Error');
+	});
 }
 //Ombi actions
-function ombiActions(id,action,type){
+function ombiActions(id, action, type, extra = null){
 	var msg = (activeInfo.user.groupID <= 1) ? '<a href="https://github.com/tidusjar/Ombi/issues/2176" target="_blank">Not Org Fault - Ask Ombi</a>' : 'Connection Error to Request Server';
 	ajaxloader('.request-' + id + '-div', 'in');
 	ajaxloader('.preloader-' + id, 'in');
     //$.magnificPopup.close();
     messageSingle(window.lang.translate('Submitting Action to Ombi'),'',activeInfo.settings.notifications.position,"#FFF",'success',"10000");
-	var callbacks = $.Callbacks();
-
     switch (action){
 	    case 'add':
 	    	var method = 'POST';
 	    	var apiUrl = 'api/v2/homepage/ombi/requests/'+type+'/' + id;
 	    	var data = {};
-		    callbacks.add( homepageRequests );
 	    	break;
 	    case 'available':
 	    case 'unavailable':
@@ -5660,29 +5791,25 @@ function ombiActions(id,action,type){
 		    var method = 'POST';
 		    var apiUrl = 'api/v2/homepage/ombi/requests/'+type+'/' + id + '/' + action;
 		    var data = {};
-		    callbacks.add( homepageRequests );
 		    break;
 	    case 'deny':
 		    var method = 'PUT';
 		    var apiUrl = 'api/v2/homepage/ombi/requests/'+type+'/' + id + '/' + action;
 		    var data = {};
-		    callbacks.add( homepageRequests );
 		    break;
 	    case 'delete':
 		    var method = 'DELETE';
 		    var apiUrl = 'api/v2/homepage/ombi/requests/'+type+'/' + id;
 		    var data = {};
-		    callbacks.add( homepageRequests );
 		    break;
 	    default:
-		    console.log(id,action,type);
 	    	return false;
     }
 	organizrAPI2(method,apiUrl,data).success(function(data) {
         try {
             let response = data.response;
 	        messageSingle(response.message,'',activeInfo.settings.notifications.position,"#FFF","success","5000");
-	        if(callbacks){ callbacks.fire(); }
+	        homepageRequests('ombi');
 	        ajaxloader();
         }catch(e) {
 	        organizrCatchError(e,data);
@@ -7204,30 +7331,59 @@ function homepagePlaylist(type, timeout=30000){
 		OrganizrApiError(xhr);
 	});
 }
-function defaultOmbiFilter(){
-    var defaultFilter = {
-        "request-filter-approved" : activeInfo.settings.homepage.ombi.ombiDefaultFilterApproved,
-        "request-filter-unapproved" : activeInfo.settings.homepage.ombi.ombiDefaultFilterUnapproved,
-        "request-filter-available" : activeInfo.settings.homepage.ombi.ombiDefaultFilterAvailable,
-        "request-filter-unavailable" : activeInfo.settings.homepage.ombi.ombiDefaultFilterUnavailable,
-        "request-filter-denied" : activeInfo.settings.homepage.ombi.ombiDefaultFilterDenied
-    };
-    $.each(defaultFilter, function(i,v) {
-        if(v == false){
-            $('#'+i).click();
-        }
-    });
+function defaultRequestFilter(service){
+	switch (service){
+		case 'ombi':
+			var defaultFilter = {
+				"request-filter-approved-ombi" : activeInfo.settings.homepage.ombi.ombiDefaultFilterApproved,
+				"request-filter-unapproved-ombi" : activeInfo.settings.homepage.ombi.ombiDefaultFilterUnapproved,
+				"request-filter-available-ombi" : activeInfo.settings.homepage.ombi.ombiDefaultFilterAvailable,
+				"request-filter-unavailable-ombi" : activeInfo.settings.homepage.ombi.ombiDefaultFilterUnavailable,
+				"request-filter-denied-ombi" : activeInfo.settings.homepage.ombi.ombiDefaultFilterDenied
+			};
+			$.each(defaultFilter, function(i,v) {
+				if(v == false){
+					$('#'+i).click();
+				}
+			});
+		case 'overseerr':
+			var defaultFilter = {
+				"request-filter-approved-overseerr" : activeInfo.settings.homepage.overseerr.overseerrDefaultFilterApproved,
+				"request-filter-unapproved-overseerr" : activeInfo.settings.homepage.overseerr.overseerrDefaultFilterUnapproved,
+				"request-filter-available-overseerr" : activeInfo.settings.homepage.overseerr.overseerrefaultFilterAvailable,
+				"request-filter-unavailable-overseerr" : activeInfo.settings.homepage.overseerr.overseerrDefaultFilterUnavailable,
+				"request-filter-denied-overseerr" : activeInfo.settings.homepage.overseerr.overseerrDefaultFilterDenied
+			};
+			$.each(defaultFilter, function(i,v) {
+				if(v == false){
+					$('#'+i).click();
+				}
+			});
+	}
 }
-function homepageRequests(timeout){
-	var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.ombiRefresh;
-	organizrAPI2('GET','api/v2/homepage/ombi/requests').success(function(data) {
+function homepageRequests(service, timeout){
+	switch (service){
+		case 'ombi':
+			var apiUrl = 'api/v2/homepage/ombi/requests';
+			var div = 'homepageOrderombi';
+			var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.ombiRefresh;
+			break;
+		case 'overseerr':
+			var apiUrl = 'api/v2/homepage/overseerr/requests';
+			var div = 'homepageOrderoverseerr'
+			var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.overseerrRefresh;
+			break;
+		default:
+			return false;
+	}
+	organizrAPI2('GET',apiUrl).success(function(data) {
         try {
             let response = data.response;
-	        document.getElementById('homepageOrderombi').innerHTML = '';
+	        document.getElementById(div).innerHTML = '';
 	        if(response.data.content !== false){
-		        $('#homepageOrderombi').html(buildRequest(response.data));
+		        $('#' + div).html(buildRequest(service,div, response.data));
 	        }
-	        $('.request-items').owlCarousel({
+	        $('.request-items-' + service).owlCarousel({
 		        nav:false,
 		        autoplay:false,
 		        dots:false,
@@ -7235,16 +7391,16 @@ function homepageRequests(timeout){
 		        autoWidth:true,
 		        items:4
 	        })
-	        // Default Ombi Filter
-	        defaultOmbiFilter();
+	        // Default Filter
+	        defaultRequestFilter(service);
         }catch(e) {
 	        organizrCatchError(e,data);
         }
 	}).fail(function(xhr) {
 		OrganizrApiError(xhr);
 	});
-	if(typeof timeouts['ombi-Homepage'] !== 'undefined'){ clearTimeout(timeouts['ombi-Homepage']); }
-	timeouts['ombi-Homepage'] = setTimeout(function(){ homepageRequests(timeout); }, timeout);
+	if(typeof timeouts[service+'-Requests-Homepage'] !== 'undefined'){ clearTimeout(timeouts[service+'-Requests-Homepage']); }
+	timeouts[service+'-Requests-Homepage'] = setTimeout(function(){ homepageRequests(service, timeout); }, timeout);
 	delete timeout;
 }
 function testAPIConnection(service, data = ''){
@@ -10805,7 +10961,7 @@ function OrganizrApiError(xhr, secondaryMessage = null){
 	}
 	organizrConsole('Organizr API Function',msg,'error');
 	if(secondaryMessage){
-		message(secondaryMessage, msg, activeInfo.settings.notifications.position, '#FFF', 'error', '10000');
+		messageSingle(secondaryMessage, msg, activeInfo.settings.notifications.position, '#FFF', 'error', '10000');
 	}
 	return false;
 }

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä