Skip to content

Commit

Permalink
Feature/vueify providers (#9653)
Browse files Browse the repository at this point in the history
* Add provider saving functionality to api v2 endpoint

* First version of config-providers.vue

* Added provider_sub_type to specify.

* Added config-provider-newznab and config-provider-torrent components.

* Fix saving provider config

* Add config newznab providers tab
* Added post route for getting newznab (and torznab) cats.

* Fix adding and saving newznab providers.

* Added TorrentRss component
* Fixed saving/adding/deleting provider

* Added prowlarr component.
* Added api operations for:
  - testing prowlarr connectivity
  - getting the available prowlarr providers list.

* Added prowlarr config properties.

* Added prowlarr module.

* Finished prowlarr tab

* Fix manual-search page.

* Improved provider id creation.
For when id already exist. Create id_1, id_2.
* Added prowlarr images.
* Added descriptions for torznab tab.
* Renamed jackett tab to torznab
* Cleaned up code

* Add prowlarr comment block to newznab.

* Add default values.

* Fix regex

* build

* Name+size is better for torznab

* Fix eslint and css lint

* update snapshot

* Fix pytest

* fix flake

* Fix text-color on history-compact.vue -> white

* update changelog

* Cleanup unused code
  • Loading branch information
p0psicles authored Jun 30, 2021
1 parent 15398ab commit a557233
Show file tree
Hide file tree
Showing 36 changed files with 3,336 additions and 629 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
## Unreleased

#### New Features
- Added support for Prowlarr ([9653](https://github.com/pymedusa/Medusa/pull/9653))

#### Improvements
- Vueified config/providers ([9653](https://github.com/pymedusa/Medusa/pull/9653))

#### Fixes
- Fix email notifications for per show notifications with special chars ([9652](https://github.com/pymedusa/Medusa/pull/96520))
- Fix email notifications for per show notifications with special chars ([9652](https://github.com/pymedusa/Medusa/pull/9652))

-----

Expand Down
17 changes: 16 additions & 1 deletion medusa/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,9 @@ def initialize(self, console_logging=True):
app.CREATE_MISSING_SHOW_DIRS = bool(check_setting_int(app.CFG, 'General', 'create_missing_show_dirs', 0))
app.ADD_SHOWS_WO_DIR = bool(check_setting_int(app.CFG, 'General', 'add_shows_wo_dir', 0))

app.PROWLARR_URL = check_setting_str(app.CFG, 'Prowlarr', 'url', '', censor_log='normal')
app.PROWLARR_APIKEY = check_setting_str(app.CFG, 'Prowlarr', 'apikey', '', censor_log='high')

app.NZBS = bool(check_setting_int(app.CFG, 'NZBs', 'nzbs', 0))
app.NZBS_UID = check_setting_str(app.CFG, 'NZBs', 'nzbs_uid', '', censor_log='normal')
app.NZBS_HASH = check_setting_str(app.CFG, 'NZBs', 'nzbs_hash', '', censor_log='low')
Expand Down Expand Up @@ -1078,6 +1081,9 @@ def initialize(self, console_logging=True):
app.TORZNAB_PROVIDERS = check_setting_list(app.CFG, 'Torznab', 'torznab_providers')
app.torznab_providers_list = TorznabProvider.get_providers_list(app.TORZNAB_PROVIDERS)

app.PROWLARR_PROVIDERS = check_setting_list(app.CFG, 'Prowlarr', 'providers')
# TODO implement ProwlarrProvider.get_providers_list(app.PROWLARR_PROVIDERS)

all_providers = providers.sorted_provider_list()

# dynamically load provider settings
Expand Down Expand Up @@ -1126,6 +1132,8 @@ def initialize(self, console_logging=True):
load_provider_setting(app.CFG, provider, 'string', 'url', '', censor_log='low')
load_provider_setting(app.CFG, provider, 'list', 'cat_ids', '', split_value=',')
load_provider_setting(app.CFG, provider, 'list', 'cap_tv_search', '', split_value=',')
load_provider_setting(app.CFG, provider, 'string', 'manager', '', censor_log='low')
load_provider_setting(app.CFG, provider, 'string', 'id_manager', '', censor_log='low')

if isinstance(provider, NewznabProvider):
# non configurable
Expand All @@ -1134,6 +1142,8 @@ def initialize(self, console_logging=True):
load_provider_setting(app.CFG, provider, 'bool', 'needs_auth', 1)
# configurable
load_provider_setting(app.CFG, provider, 'list', 'cat_ids', '', split_value=',')
load_provider_setting(app.CFG, provider, 'string', 'manager', '', censor_log='low')
load_provider_setting(app.CFG, provider, 'string', 'id_manager', '', censor_log='low')

if not os.path.isfile(app.CONFIG_FILE):
logger.debug(u'Unable to find {config!r}, all settings will be default!', config=app.CONFIG_FILE)
Expand Down Expand Up @@ -1697,7 +1707,7 @@ def save_config():
'all': [
'name', 'url', 'cat_ids', 'api_key', 'username', 'search_mode', 'search_fallback',
'enable_daily', 'enable_backlog', 'enable_manualsearch', 'enable_search_delay',
'search_delay',
'search_delay', 'manager', 'id_manager'
],
'encrypted': [
'password',
Expand Down Expand Up @@ -1987,6 +1997,11 @@ def save_config():
new_config['Torznab'] = {}
new_config['Torznab']['torznab_providers'] = app.TORZNAB_PROVIDERS

new_config['Prowlarr'] = {}
new_config['Prowlarr']['providers'] = app.PROWLARR_PROVIDERS
new_config['Prowlarr']['url'] = app.PROWLARR_URL
new_config['Prowlarr']['apikey'] = app.PROWLARR_APIKEY

new_config['GUI'] = {}
new_config['GUI']['theme_name'] = app.THEME_NAME
new_config['GUI']['fanart_background'] = app.FANART_BACKGROUND
Expand Down
5 changes: 5 additions & 0 deletions medusa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,11 @@ def __init__(self):

self.NEWZNAB_PROVIDERS = []

# Prowlarr section.
self.PROWLARR_URL = ''
self.PROWLARR_APIKEY = ''
self.PROWLARR_PROVIDERS = []

self.TORRENTRSS_PROVIDERS = []

self.TORZNAB_PROVIDERS = []
Expand Down
93 changes: 88 additions & 5 deletions medusa/providers/generic_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class GenericProvider(object):

NZB = 'nzb'
TORRENT = 'torrent'
NEWZNAB = 'newznab'
TORZNAB = 'torznab'
TORRENTRSS = 'torrentrss'
PROWLARR = 'prowlarr'

def __init__(self, name):
"""Initialize the class."""
Expand All @@ -76,7 +80,8 @@ def __init__(self, name):
self.enabled = False
self.headers = {'User-Agent': USER_AGENT}
self.proper_strings = ['PROPER|REPACK|REAL|RERIP']
self.provider_type = None
self.provider_type = None # generic type. For ex: nzb or torrent
self.provider_sub_type = None # specific type. For ex: neznab or torznab
self.public = False
self.search_fallback = False
self.search_mode = None
Expand Down Expand Up @@ -891,20 +896,22 @@ def __unicode__(self):
def to_json(self):
"""Return a json representation for a provider."""
from medusa.providers.torrent.torrent_provider import TorrentProvider
return {
# Generic options
data = {
'name': self.name,
'id': self.get_id(),
'imageName': self.image_name(),
'config': {
'enabled': self.enabled,
'search': {
'backlog': {
'enabled': self.enable_backlog
},
'manual': {
'enabled': self.enable_backlog
'enabled': self.enable_manualsearch
},
'daily': {
'enabled': self.enable_backlog,
'enabled': self.enable_daily,
'maxRecentItems': self.max_recent_items,
'stopAt': self.stop_at
},
Expand All @@ -920,16 +927,92 @@ def to_json(self):
},
'animeOnly': self.anime_only,
'type': self.provider_type,
'subType': self.provider_sub_type,
'public': self.public,
'btCacheUrls': self.bt_cache_urls if isinstance(self, TorrentProvider) else [],
'properStrings': self.proper_strings,
'headers': self.headers,
'supportsAbsoluteNumbering': self.supports_absolute_numbering,
'supportsBacklog': self.supports_backlog,
'url': self.url,
'url': self.custom_url or self.url if hasattr(self, 'custom_url') else self.url,
'urls': self.urls,
'cookies': {
'enabled': self.enable_cookies,
'required': self.cookies
}
}

# Custom options (torrent client specific)
if hasattr(self, 'username'):
data['config']['username'] = self.username

if hasattr(self, 'password'):
data['config']['password'] = self.password

if hasattr(self, 'api_key'):
data['config']['apikey'] = self.api_key

if hasattr(self, 'custom_url'):
data['config']['customUrl'] = self.custom_url

if hasattr(self, 'minseed'):
data['config']['minseed'] = self.minseed

if hasattr(self, 'minleech'):
data['config']['minleech'] = self.minleech

if hasattr(self, 'ratio'):
data['config']['ratio'] = self.ratio

if hasattr(self, 'client_ratio'):
data['config']['clientRatio'] = self.client_ratio

if hasattr(self, 'passkey'):
data['config']['passkey'] = self.passkey

if hasattr(self, 'hash'):
data['config']['hash'] = self.hash

if hasattr(self, 'digest'):
data['config']['digest'] = self.digest

if hasattr(self, 'pin'):
data['config']['pin'] = self.pin

if hasattr(self, 'confirmed'):
data['config']['confirmed'] = self.confirmed

if hasattr(self, 'ranked'):
data['config']['ranked'] = self.ranked

if hasattr(self, 'sorting'):
data['config']['sorting'] = self.sorting

if hasattr(self, 'cookies'):
data['config']['cookies'] = self.cookies

# Custom options (newznab specific)
if hasattr(self, 'default'):
data['default'] = self.default

if hasattr(self, 'cat_ids'):
data['config']['catIds'] = self.cat_ids

if hasattr(self, 'params'):
data['config']['params'] = self.params

if hasattr(self, 'needs_auth'):
data['needsAuth'] = self.needs_auth

# Custom options (torrentrss)
if hasattr(self, 'title_tag'):
data['config']['titleTag'] = self.title_tag

# Custom options (prowlarr):
if hasattr(self, 'manager'):
data['manager'] = self.manager

if hasattr(self, 'id_manager'):
data['idManager'] = self.id_manager

return data
20 changes: 16 additions & 4 deletions medusa/providers/nzb/newznab.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
from medusa.indexers.utils import mappings
from medusa.logger.adapters.style import BraceAdapter
from medusa.providers.generic_provider import GenericProvider
from medusa.providers.nzb.nzb_provider import NZBProvider

from requests.compat import urljoin
Expand All @@ -48,13 +49,16 @@ class NewznabProvider(NZBProvider):
Tested with: newznab, nzedb, spotweb
"""

IDENTIFIER_REGEX = re.compile(r'(.*)apikey=.+')
IDENTIFIER_REGEX = re.compile(r'apikey=[^&]+')

def __init__(self, name, url='', api_key='0', cat_ids=None, default=False, search_mode='eponly',
search_fallback=False, enable_daily=True, enable_backlog=False, enable_manualsearch=False):
search_fallback=False, enable_daily=True, enable_backlog=False,
enable_manualsearch=False, manager=None):
"""Initialize the class."""
super(NewznabProvider, self).__init__(name)

self.provider_sub_type = GenericProvider.NEWZNAB

self.url = url
self.api_key = api_key

Expand Down Expand Up @@ -84,6 +88,10 @@ def __init__(self, name, url='', api_key='0', cat_ids=None, default=False, searc
'Season {season}', # example: 'Series.Name Season 3'
)

# Specify the manager if externally managed.
self.manager = manager
self.id_manager = self.name

self.cache = tv.Cache(self)

def search(self, search_strings, age=0, ep_obj=None, force_query=False, manual_search=False, **kwargs):
Expand Down Expand Up @@ -284,9 +292,9 @@ def _get_identifier(item):
Cut the apikey from it, as this might change over time.
So we'd like to prevent adding duplicates to cache.
"""
url = NewznabProvider.IDENTIFIER_REGEX.match(item.url)
url = NewznabProvider.IDENTIFIER_REGEX.sub('', item.url)
if url:
return url.group(1)
return url
return item.url

def config_string(self):
Expand Down Expand Up @@ -367,6 +375,10 @@ def image_name(self):
"""
if os.path.isfile(os.path.join(app.THEME_DATA_ROOT, 'assets/img/providers/', self.get_id() + '.png')):
return self.get_id() + '.png'

if self.manager == 'prowlarr':
return 'prowlarr.png'

return 'newznab.png'

def _match_indexer(self):
Expand Down
29 changes: 29 additions & 0 deletions medusa/providers/prowlarr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Utility class for prowlarr."""
from urllib.parse import urljoin

from medusa.session.core import ProviderSession


class ProwlarrManager(object):
"""Utility class for prowlarr."""

def __init__(self, url, apikey):
self.url = url
self.apikey = apikey
self.session = ProviderSession()
self.session.headers.update({'x-api-key': self.apikey})

def test_connectivity(self):
"""Verify connectivity to Prowlarrs internal api."""
response = self.session.get(urljoin(self.url, 'api/v1/health'))
if response and response.ok:
return True
return False

def get_indexers(self):
"""Get a list of providers (newznab/torznab indexers)."""
response = self.session.get(urljoin(self.url, 'api/v1/indexer'))
if not response:
return False

return response.json()
2 changes: 2 additions & 0 deletions medusa/providers/torrent/rss/rsstorrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
tv,
)
from medusa.helper.exceptions import ex
from medusa.providers.generic_provider import GenericProvider
from medusa.providers.torrent.torrent_provider import TorrentProvider

log = logging.getLogger(__name__)
Expand All @@ -30,6 +31,7 @@ def __init__(self, name, url='', cookies='', title_tag=None, search_mode='eponly
enable_daily=False, enable_backlog=False, enable_manualsearch=False):
"""Initialize the class."""
super(TorrentRssProvider, self).__init__(name)
self.provider_sub_type = GenericProvider.TORRENTRSS

# Credentials

Expand Down
20 changes: 19 additions & 1 deletion medusa/providers/torrent/torznab/torznab.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from medusa.indexers.utils import mappings
from medusa.logger.adapters.style import BraceAdapter
from medusa.providers.generic_provider import GenericProvider
from medusa.providers.torrent.torrent_provider import TorrentProvider

from requests.compat import urljoin
Expand All @@ -37,14 +38,23 @@
class TorznabProvider(TorrentProvider):
"""Generic provider for built in and custom providers who expose a Torznab compatible api."""

def __init__(self, name, url=None, api_key=None, cat_ids=None, cap_tv_search=None):
def __init__(self, name, url=None, api_key=None, cat_ids=None, cap_tv_search=None,
search_mode='eponly', search_fallback=False, enable_daily=True,
enable_backlog=False, enable_manualsearch=False, manager=None):
"""Initialize the class."""
super(TorznabProvider, self).__init__(name)

self.provider_sub_type = GenericProvider.TORZNAB

self.url = url or ''
self.api_key = api_key or ''
self.cat_ids = cat_ids or ['5010', '5030', '5040', '7000']
self.cap_tv_search = cap_tv_search or []
self.search_mode = search_mode
self.search_fallback = search_fallback
self.enable_daily = enable_daily
self.enable_backlog = enable_backlog
self.enable_manualsearch = enable_manualsearch

# For now apply the additional season search string for all torznab providers.
# If we want to limited this per provider, I suggest using a dict, with provider: [list of season templates]
Expand All @@ -57,6 +67,10 @@ def __init__(self, name, url=None, api_key=None, cat_ids=None, cap_tv_search=Non
# Proper Strings
self.proper_strings = ['PROPER', 'REPACK', 'REAL', 'RERIP']

# Specify the manager if externally managed.
self.manager = manager
self.id_manager = self.name

self.cache = tv.Cache(self)

def search(self, search_strings, age=0, ep_obj=None, force_query=False, manual_search=False, **kwargs):
Expand Down Expand Up @@ -246,6 +260,10 @@ def image_name(self):
"""
if os.path.isfile(os.path.join(app.THEME_DATA_ROOT, 'assets/img/providers/', self.get_id() + '.png')):
return self.get_id() + '.png'

if self.manager == 'prowlarr':
return 'prowlarr.png'

return 'jackett.png'

def _match_indexer(self):
Expand Down
4 changes: 2 additions & 2 deletions medusa/search/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,8 @@ def search_providers(series_obj, episodes, forced_search=False, down_cur_quality
else:
searched_episode_list = [episode_obj.episode for episode_obj in episodes] + [MULTI_EP_RESULT]
for searched_episode in searched_episode_list:
if (searched_episode in search_results and
cur_provider.cache.update_cache_manual_search(search_results[searched_episode])):
if (searched_episode in search_results
and cur_provider.cache.update_cache_manual_search(search_results[searched_episode])):
# If we have at least a result from one provider, it's good enough to be marked as result
manual_search_results.append(True)
# Continue because we don't want to pick best results as we are running a manual search by user
Expand Down
Loading

0 comments on commit a557233

Please sign in to comment.