from urllib.parse import urlparse

from loguru import logger

from flexget import plugin
from flexget.entry import Entry
from flexget.event import event

logger = logger.bind(name='medusa')


class Medusa:
    schema = {
        'type': 'object',
        'properties': {
            'base_url': {'type': 'string', 'format': 'uri'},
            'port': {'type': 'number', 'default': 8081},
            'username': {'type': 'string'},
            'password': {'type': 'string'},
            'only_monitored': {'type': 'boolean', 'default': False},
            'include_ended': {'type': 'boolean', 'default': False},
        },
        'required': ['username', 'password', 'base_url'],
        'additionalProperties': False,
    }

    def on_task_input(self, task, config):
        """
        This plugin returns ALL of the shows monitored by Medusa.
        This includes both ongoing and ended.
        Syntax:

        medusa:
          base_url=<value>
          port=<value>
          username=<value>
          password=<value>

        Options base_url, username and password are required.

        Use with input plugin like discover and/or configure_series.
        Example:

        download-tv-task:
          configure_series:
            from:
              medusa:
                base_url: http://localhost
                port: 8531
                username: USERNAME
                password: PASSWORD
          discover:
            what:
              - next_series_episodes: yes
            from:
              torrentz: any
          download:
            /download/tv

        Note that when using the configure_series plugin with Medusa
        you are basically synced to it, so removing a show in Medusa will
        remove it in flexget as well, which could be positive or negative,
        depending on your usage.
        """
        parsed_url = urlparse(config.get('base_url'))
        base_url = '{scheme}://{url}:{port}/api/v2'.format(
            scheme=parsed_url.scheme, url=parsed_url.netloc, port=config.get('port')
        )

        body_auth = dict(username=config.get('username'), password=config.get('password'))

        api_key = task.requests.post('{}/authenticate'.format(base_url), json=body_auth).json()[
            'token'
        ]

        headers = {'authorization': 'Bearer ' + api_key}

        params = {'limit': 1000}

        series = task.requests.get(
            '{}/series'.format(base_url), params=params, headers=headers
        ).json()

        entries = []
        for show in series:
            logger.debug('processing show: {}', show)
            if (
                (show['config']['paused'] and config.get('only_monitored'))
                or show['status'] == 'Ended'
                and not config.get('include_ended')
            ):
                logger.debug('discarted show: {}', show)

            entry = Entry(title=show['title'], url='', series_name=show['title'])

            if entry.isvalid():
                entries.append(entry)
            else:
                logger.error('Invalid entry created? {}'.format(entry))

        return entries


@event('plugin.register')
def register_plugin():
    plugin.register(Medusa, 'medusa', api_ver=2)

flexget/plugins/input/medusa.py
from urllib.parse import urlparse

from loguru import logger

from flexget import plugin
from flexget.entry import Entry
from flexget.event import event

logger = logger.bind(name='medusa')


class Medusa:
    schema = {
        'type': 'object',
        'properties': {
            'base_url': {'type': 'string', 'format': 'uri'},
            'port': {'type': 'number', 'default': 8081},
            'username': {'type': 'string'},
            'password': {'type': 'string'},
            'only_monitored': {'type': 'boolean', 'default': False},
            'include_ended': {'type': 'boolean', 'default': False},
        },
        'required': ['username', 'password', 'base_url'],
        'additionalProperties': False,
    }

    def on_task_input(self, task, config):
        """
        This plugin returns ALL of the shows monitored by Medusa.
        This includes both ongoing and ended.
        Syntax:

        medusa:
          base_url=<value>
          port=<value>
          username=<value>
          password=<value>

        Options base_url, username and password are required.

        Use with input plugin like discover and/or configure_series.
        Example:

        download-tv-task:
          configure_series:
            from:
              medusa:
                base_url: http://localhost
                port: 8531
                username: USERNAME
                password: PASSWORD
          discover:
            what:
              - next_series_episodes: yes
            from:
              torrentz: any
          download:
            /download/tv

        Note that when using the configure_series plugin with Medusa
        you are basically synced to it, so removing a show in Medusa will
        remove it in flexget as well, which could be positive or negative,
        depending on your usage.
        """
        parsed_url = urlparse(config.get('base_url'))
        base_url = '{scheme}://{url}:{port}/api/v2'.format(
            scheme=parsed_url.scheme, url=parsed_url.netloc, port=config.get('port')
        )

        body_auth = dict(username=config.get('username'), password=config.get('password'))

        api_key = task.requests.post('{}/authenticate'.format(base_url), json=body_auth).json()[
            'token'
        ]

        headers = {'x-auth': 'Bearer ' + api_key}

        params = {'limit': 1000}

        series = task.requests.get(
            '{}/series'.format(base_url), params=params, headers=headers
        ).json()

        entries = []
        for show in series:
            logger.debug('processing show: {}', show)
            if (
                (show['config']['paused'] and config.get('only_monitored'))
                or show['status'] == 'Ended'
                and not config.get('include_ended')
            ):
                logger.debug('discarted show: {}', show)

            entry = Entry(title=show['title'], url='', series_name=show['title'])

            if entry.isvalid():
                entries.append(entry)
            else:
                logger.error('Invalid entry created? {}'.format(entry))

        return entries


@event('plugin.register')
def register_plugin():
    plugin.register(Medusa, 'medusa', api_ver=2)

flexget/plugins/input/medusa.py
diff --git a/flexget/plugins/input/medusa.py b/flexget/plugins/input/medusa.py
index f29a7c21ef..41cb7556fd 100644
--- a/flexget/plugins/input/medusa.py
+++ b/flexget/plugins/input/medusa.py
@@ -73,7 +73,7 @@ def on_task_input(self, task, config):
             'token'
         ]
 
-        headers = {'authorization': 'Bearer ' + api_key}
+        headers = {'x-auth': 'Bearer ' + api_key}
 
         params = {'limit': 1000}
 

plugin medusa authentication issue
Good evening.
I'm running into an authentication issue using plugin medusa.
Expected behavior is seen below, list of show is retreived properly using the api.
```
root@flexget2:~# flexget -V
3.3.18
You are on the latest release
```

From machine "flexget2":
using cURL:
```
curl --user medusa:medusa http://IP.ADDR:8081/api/v2/series?limit=1000
```
Returns data successfully.
```
root@flexget2:~# curl --user medusa:medusa http://IP.ADDR:8081/api/v2/series?limit=1000
[{"id": {"tvdb": 350667, "slug": "tvdb350667", "trakt": null}, "externals": {"imdb": 7608248, "tvmaze": 34601, "tmdb": 81499}, "title": "A Million Little Things", "name": "A Million Little Things", "indexer": "tvdb", "network": "ABC (US)", "type": "Scripted", "status": "Continuing", "airs": "Wednesday 10:00 PM", "airsFormatValid": true, "language": "en", "showType": "series", "imdbInfo": {"imdbInfoId": 1, "indexer": 1, "indexerId": 350667, "imdbId": "tt7608248", "title": "A Million Little Things", "year": 2018, "akas": "", "runtimes": 43, "genres": "Comedy|Drama|Romance", "countries": "UNITED STATES", "countryCodes": "us", "certificates": "", "rating": "7.8", "votes": 13278, "lastUpdate": 738332, "plot": "A group of friends becomes motivated to living fuller lives after the unexpected death of a close friend."}, "year": {"start": 2018}, "prevAirDate": "2022-05-19T04:00:00+02:00", "nextAirDate": null, "lastUpdate": "2022-06-26", "runtime": 43, "genres": ["Romance", "Comedy", "Drama"], "rating": {"imdb": {"rating": "7.8", "votes": 13278}}, "classification": "", "cache": {"poster": "/config/cache/images/tvdb/350667.poster.jpg", "banner": "/config/cache/images/tvdb/350667.banner.jpg"}, "countries": ["UNITED STATES"], "countryCodes": ["us"], "plot": "They say friendship isn\u2019t one big thing\u2026 it\u2019s a million little things. When Jon Dixon \u2014 a man perfect on paper \u2014 took his own life, his family and friends are left to pick up the pieces. Each of these friends is not living the version of life they thought they\u2019d be living, and their friend\u2019s death forces them to take a look at the choices they\u2019ve made and to solve the unanswerable mystery of a man they thought they knew.", "config": {"location": "/tmp/A Million Little Things", "rootDir": "/tmp", "locationValid": false, "qualities": {"allowed": [8, 32, 64, 128], "preferred": [32, 128]}, "paused": false, "airByDate": false, "subtitlesEnabled": false, "dvdOrder": false, "seasonFolders": true, "anime": false, "scene": false, "sports": false, "templates": false, "defaultEpisodeStatus": "Wanted", "aliases": [], "release": {"ignoredWords": [], "requiredWords": [], "ignoredWordsExclude": false, "requiredWordsExclude": false}, "airdateOffset": 0, "showLists": ["series"]}, "xemNumbering": [], "sceneAbsoluteNumbering": [], "xemAbsoluteNumbering": [], "sceneNumbering": []}, {"id": {"tvdb": 153021, "slug": "tvdb153021", "trakt": null}, "externals": {"imdb": 1520211, "tvrage": 25056, "tvmaze": 73, "tmdb": 1402}, "title": "The Walking Dead", "name": "The Walking Dead", "indexer": "tvdb", "network": "AMC", "type": "Scripted", "status": "Continuing", "airs": "Sunday 9:00 PM", "airsFormatValid": true, "language": "en", "showType": "series", "imdbInfo": {"imdbInfoId": 2, "indexer": 1, "indexerId": 153021, "imdbId": "tt1520211", "title": "The Walking Dead", "year": 2010, "akas": "", "runtimes": 44, "genres": "Drama|Horror|Thriller", "countries": "UNITED STATES", "countryCodes": "us", "certificates": "", "rating": "8.2", "votes": 951642, "lastUpdate": 738332, "plot": "Sheriff Deputy Rick Grimes wakes up from a coma to learn the world is in ruins and must lead a group of survivors to stay alive."}, "year": {"start": 2010}, "prevAirDate": "2022-04-11T03:00:00+02:00", "nextAirDate": null, "lastUpdate": "2022-06-26", "runtime": 44, "genres": ["Horror", "Adventure", "Thriller", "Drama"], "rating": {"imdb": {"rating": "8.2", "votes": 951642}}, "classification": "", "cache": {"poster": "/config/cache/images/tvdb/153021.poster.jpg", "banner": "/config/cache/images/tvdb/153021.banner.jpg"}, "countries": ["UNITED STATES"], "countryCodes": ["us"], "plot": "The world we knew is gone. An epidemic of apocalyptic proportions has swept the globe causing the dead to rise and feed on the living. In a matter of months society has crumbled. In a world ruled by the dead, we are forced to finally start living.", "config": {"location": "/tmp/The Walking Dead", "rootDir": "/tmp", "locationValid": false, "qualities": {"allowed": [8, 32, 64, 128], "preferred": [32, 128]}, "paused": false, "airByDate": false, "subtitlesEnabled": false, "dvdOrder": false, "seasonFolders": true, "anime": false, "scene": false, "sports": false, "templates": false, "defaultEpisodeStatus": "Wanted", "aliases": [], "release": {"ignoredWords": [], "requiredWords
```

However, using the generated token taken from the log does not work:
```
curl -H "authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNZWR1c2EgMS4wLjMiLCJpYXQiOjE2NTY3MDQzNDUsImp0aSI6IkRJU2VPUjN5UnhxZm96UlRYaG9YIiwiZXhwIjoxNjU2NzkwNzQ1LCJ1c2VybmFtZSI6Im1lZHVzYSIsImFwaUtleSI6IjgwZjhjNDJiNTM0YjNhYjFkMzAzMmEwN2U4YjJmYzNiIn0.37trJnleOVZxvklAVdFnH4Nr200vMp6QPKMoakPiKvI'" http://IP.ADDR:8081/api/v2/series?limit=1000
```

Results:
```
{"error": "No authorization token."}
```

This is my first time using the medusa plugin, as I was using the sickbeard before and could not get it to work now either.

Configuration is the following:
```
> tv: 
    configure_series:  
      from:  
        medusa:  
          base_url: 'http://{? medusa.ip ?}'  
          port: '{? medusa.port ?}'  
          #api_key: '{? medusa.api_key ?}'  
          username: medusa  
          password: medusa  
          include_ended: false  
          only_monitored: true  
          #include_data: true
```
logs:
```

2022-07-01 19:39:06 DEBUG    task          get_entry_tv    executing get_entry_tv
2022-07-01 19:39:06 DEBUG    template      get_entry_tv    Merging template tv into task get_entry_tv
2022-07-01 19:39:06 DEBUG    template      get_entry_tv    Merging template torrents into task get_entry_tv
2022-07-01 19:39:06 DEBUG    utils.requests get_entry_tv    POSTing URL http://IP.ADDR:8081/api/v2/authenticate with args () and kwargs {'data': None, 'json': {'username': 'medusa', 'password': 'medusa'}, 'timeout': 30}
2022-07-01 19:39:06 DEBUG    utils.requests get_entry_tv    GETing URL http://IP.ADDR:8081/api/v2/series with args () and kwargs {'params': {'limit': 1000}, 'headers': {'authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNZWR1c2EgMS4wLjMiLCJpYXQiOjE2NTY3MDQzNDUsImp0aSI6IkRJU2VPUjN5UnhxZm96UlRYaG9YIiwiZXhwIjoxNjU2NzkwNzQ1LCJ1c2VybmFtZSI6Im1lZHVzYSIsImFwaUtleSI6IjgwZjhjNDJiNTM0YjNhYjFkMzAzMmEwN2U4YjJmYzNiIn0.37trJnleOVZxvklAVdFnH4Nr200vMp6QPKMoakPiKvI'}, 'allow_redirects': True, 'timeout': 30}
2022-07-01 19:39:06 CRITICAL task          get_entry_tv    BUG: Unhandled error in plugin configure_series: 401 Client Error: Unauthorized for url: http://IP.ADDR:8081/api/v2/series?limit=1000
Traceback (most recent call last):

  File "/usr/lib/python3.9/threading.py", line 912, in _bootstrap
    self._bootstrap_inner()
    │    └ <function Thread._bootstrap_inner at 0x7fbacc7513a0>
    └ <Thread(task_queue, started daemon 140440167495424)>
  File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
    self.run()
    │    └ <function Thread.run at 0x7fbacc7510d0>
    └ <Thread(task_queue, started daemon 140440167495424)>
  File "/usr/lib/python3.9/threading.py", line 892, in run
    self._target(*self._args, **self._kwargs)
    │    │        │    │        │    └ {}
    │    │        │    │        └ <Thread(task_queue, started daemon 140440167495424)>
    │    │        │    └ ()
    │    │        └ <Thread(task_queue, started daemon 140440167495424)>
    │    └ <bound method TaskQueue.run of <flexget.task_queue.TaskQueue object at 0x7fbac679afa0>>
    └ <Thread(task_queue, started daemon 140440167495424)>

  File "/usr/local/lib/python3.9/dist-packages/flexget/task_queue.py", line 47, in run
    self.current_task.execute()
    │    │            └ <function Task.execute at 0x7fbac95a7e50>
    │    └ <flexget.task.Task object at 0x7fbac64ccb20>
    └ <flexget.task_queue.TaskQueue object at 0x7fbac679afa0>

  File "/usr/local/lib/python3.9/dist-packages/flexget/task.py", line 87, in wrapper
    return func(self, *args, **kw)
           │    │      │       └ {}
           │    │      └ ()
           │    └ <flexget.task.Task object at 0x7fbac64ccb20>
           └ <function Task.execute at 0x7fbac95a7dc0>

  File "/usr/local/lib/python3.9/dist-packages/flexget/task.py", line 727, in execute
    self._execute()
    │    └ <function Task._execute at 0x7fbac95a7d30>
    └ <flexget.task.Task object at 0x7fbac64ccb20>

  File "/usr/local/lib/python3.9/dist-packages/flexget/task.py", line 696, in _execute
    self.__run_task_phase(phase)
    │                     └ 'prepare'
    └ <flexget.task.Task object at 0x7fbac64ccb20>

  File "/usr/local/lib/python3.9/dist-packages/flexget/task.py", line 514, in __run_task_phase
    response = self.__run_plugin(plugin, phase, args)
               │                 │       │      └ (<flexget.task.Task object at 0x7fbac64ccb20>, {'from': {'medusa': {'base_url': 'http://IP.ADDR', 'port': 8081, 'username':...
               │                 │       └ 'prepare'
               │                 └ <PluginInfo(name=configure_series)>
               └ <flexget.task.Task object at 0x7fbac64ccb20>

> File "/usr/local/lib/python3.9/dist-packages/flexget/task.py", line 547, in __run_plugin
    result = method(*args, **kwargs)
             │       │       └ {}
             │       └ (<flexget.task.Task object at 0x7fbac64ccb20>, {'from': {'medusa': {'base_url': 'http://IP.ADDR', 'port': 8081, 'username':...
             └ <Event(name=plugin.configure_series.prepare,func=on_task_prepare,priority=128)>

  File "/usr/local/lib/python3.9/dist-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
           │    │     │       └ {}
           │    │     └ (<flexget.task.Task object at 0x7fbac64ccb20>, {'from': {'medusa': {'base_url': 'http://IP.ADDR', 'port': 8081, 'username':...
           │    └ <bound method ConfigureSeries.on_task_prepare of <flexget.components.series.configure_series.ConfigureSeries object at 0x7fba...
           └ <Event(name=plugin.configure_series.prepare,func=on_task_prepare,priority=128)>

  File "/usr/local/lib/python3.9/dist-packages/flexget/components/series/configure_series.py", line 53, in on_task_prepare
    result = method(task, input_config)
             │      │     └ {'base_url': 'http://IP.ADDR', 'port': 8081, 'username': 'medusa', 'password': 'medusa', 'include_ended': False, 'only_moni...
             │      └ <flexget.task.Task object at 0x7fbac64ccb20>
             └ <Event(name=plugin.medusa.input,func=on_task_input,priority=128)>

  File "/usr/local/lib/python3.9/dist-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
           │    │     │       └ {}
           │    │     └ (<flexget.task.Task object at 0x7fbac64ccb20>, {'base_url': 'http://IP.ADDR', 'port': 8081, 'username': 'medusa', 'password...
           │    └ <bound method Medusa.on_task_input of <flexget.plugins.input.medusa.Medusa object at 0x7fbac68670a0>>
           └ <Event(name=plugin.medusa.input,func=on_task_input,priority=128)>

  File "/usr/local/lib/python3.9/dist-packages/flexget/plugins/input/medusa.py", line 80, in on_task_input
    series = task.requests.get(
             │    │        └ <function Session.get at 0x7fbacb4800d0>
             │    └ <flexget.utils.requests.Session object at 0x7fbac64cca30>
             └ <flexget.task.Task object at 0x7fbac64ccb20>

  File "/usr/local/lib/python3.9/dist-packages/requests/sessions.py", line 542, in get
    return self.request('GET', url, **kwargs)
           │    │              │      └ {'params': {'limit': 1000}, 'headers': {'authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNZWR1c2EgMS4...
           │    │              └ 'http://IP.ADDR:8081/api/v2/series'
           │    └ <function Session.request at 0x7fbac95f9820>
           └ <flexget.utils.requests.Session object at 0x7fbac64cca30>

  File "/usr/local/lib/python3.9/dist-packages/flexget/utils/requests.py", line 271, in request
    result.raise_for_status()
    │      └ <function Response.raise_for_status at 0x7fbacb46c700>
    └ <Response [401]>

  File "/usr/local/lib/python3.9/dist-packages/requests/models.py", line 960, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
          │         │                        └ <Response [401]>
          │         └ '401 Client Error: Unauthorized for url: http://IP.ADDR:8081/api/v2/series?limit=1000'
          └ <class 'requests.exceptions.HTTPError'>

requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: http://IP.ADDR:8081/api/v2/series?limit=1000
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/flexget/task.py", line 547, in __run_plugin
    result = method(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/flexget/components/series/configure_series.py", line 53, in on_task_prepare
    result = method(task, input_config)
  File "/usr/local/lib/python3.9/dist-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/flexget/plugins/input/medusa.py", line 80, in on_task_input
    series = task.requests.get(
  File "/usr/local/lib/python3.9/dist-packages/requests/sessions.py", line 542, in get
    return self.request('GET', url, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/flexget/utils/requests.py", line 271, in request
    result.raise_for_status()
  File "/usr/local/lib/python3.9/dist-packages/requests/models.py", line 960, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: http://IP.ADDR:8081/api/v2/series?limit=1000
root@flexget2:~# 
```


_Originally posted by @hideYourPretzels in https://github.com/Flexget/Flexget/discussions/3420#discussioncomment-3066652_
