Repository: Flexget/Flexget

Issue:
Error getting list from Kitsu.io 500
### Expected behaviour:
The "monitored" lists from a Kitsu account should be retrieved correctly so that the entries are being produced.

### Actual behaviour:
Whenever the a request is done to https://kitsu.io/api/edge/users/61827/library-entries?filter[status]=current,planned&filter[media_type]=Anime&page[limit]=20&include=media  (with my user, as example) it errors out with a 500 Internal Server Error.

It does work without mentioning `&include=media`

### Steps to reproduce:


#### Config:
```
configure_series:
  from:
    kitsu:
      username: Fribb
      lists:
        - current
        - planned
```
  
#### Log:
<details>
 <summary>(click to expand)</summary>

```
2022-06-19 16:01:42 DEBUG    utils.requests check-SubsPlease GETing URL https://kitsu.io/api/edge/users with args () and kwargs {'params': {'filter[name]': 'Fribb'}, 'allow_redirects': True, 'timeout': 30}
2022-06-19 16:01:46 DEBUG    utils.requests check-SubsPlease GETing URL https://kitsu.io/api/edge/users/61827/library-entries with args () and kwargs {'params': {'filter[status]': 'current,planned', 'filter[media_type]': 'Anime', 'include': 'media', 'page[limit]': 20}, 'allow_redirects': True, 'timeout': 30}
2022-06-19 16:01:47 DEBUG    kitsu         check-SubsPlease Error getting list from https://kitsu.io/api/edge/users/61827/library-entries?filter%5Bstatus%5D=current%2Cplanned&filter%5Bmedia_type%5D=Anime&include=media&page%5Blimit%5D=20 status: 500
Traceback (most recent call last):

  File "/usr/lib/python3.7/threading.py", line 890, in _bootstrap
    self._bootstrap_inner()
    │    └ <function Thread._bootstrap_inner at 0x14e1612a3830>
    └ <Thread(task_queue, started daemon 22958104660768)>
  File "/usr/lib/python3.7/threading.py", line 926, in _bootstrap_inner
    self.run()
    │    └ <function Thread.run at 0x14e1612a35f0>
    └ <Thread(task_queue, started daemon 22958104660768)>
  File "/usr/lib/python3.7/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
    │    │        │    │        │    └ {}
    │    │        │    │        └ <Thread(task_queue, started daemon 22958104660768)>
    │    │        │    └ ()
    │    │        └ <Thread(task_queue, started daemon 22958104660768)>
    │    └ <bound method TaskQueue.run of <flexget.task_queue.TaskQueue object at 0x14e15aafaf90>>
    └ <Thread(task_queue, started daemon 22958104660768)>
  File "/usr/lib/python3.7/site-packages/flexget/task_queue.py", line 47, in run
    self.current_task.execute()
    │    │            └ <function Task.execute at 0x14e15ec5bcb0>
    │    └ <flexget.task.Task object at 0x14e159de3f10>
    └ <flexget.task_queue.TaskQueue object at 0x14e15aafaf90>
  File "/usr/lib/python3.7/site-packages/flexget/task.py", line 87, in wrapper
    return func(self, *args, **kw)
           │    │      │       └ {}
           │    │      └ ()
           │    └ <flexget.task.Task object at 0x14e159de3f10>
           └ <function Task.execute at 0x14e15ec5bc20>
  File "/usr/lib/python3.7/site-packages/flexget/task.py", line 727, in execute
    self._execute()
    │    └ <function Task._execute at 0x14e15ec5bb90>
    └ <flexget.task.Task object at 0x14e159de3f10>
  File "/usr/lib/python3.7/site-packages/flexget/task.py", line 696, in _execute
    self.__run_task_phase(phase)
    │                     └ 'prepare'
    └ <flexget.task.Task object at 0x14e159de3f10>
  File "/usr/lib/python3.7/site-packages/flexget/task.py", line 514, in __run_task_phase
    response = self.__run_plugin(plugin, phase, args)
               │                 │       │      └ (<flexget.task.Task object at 0x14e159de3f10>, {'from': {'kitsu': {'username': 'Fribb', 'lists': ['current', 'planned'], 'lat...
               │                 │       └ 'prepare'
               │                 └ <PluginInfo(name=configure_series)>
               └ <flexget.task.Task object at 0x14e159de3f10>
  File "/usr/lib/python3.7/site-packages/flexget/task.py", line 547, in __run_plugin
    result = method(*args, **kwargs)
             │       │       └ {}
             │       └ (<flexget.task.Task object at 0x14e159de3f10>, {'from': {'kitsu': {'username': 'Fribb', 'lists': ['current', 'planned'], 'lat...
             └ <Event(name=plugin.configure_series.prepare,func=on_task_prepare,priority=128)>
  File "/usr/lib/python3.7/site-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
           │    │     │       └ {}
           │    │     └ (<flexget.task.Task object at 0x14e159de3f10>, {'from': {'kitsu': {'username': 'Fribb', 'lists': ['current', 'planned'], 'lat...
           │    └ <bound method ConfigureSeries.on_task_prepare of <flexget.components.series.configure_series.ConfigureSeries object at 0x14e1...
           └ <Event(name=plugin.configure_series.prepare,func=on_task_prepare,priority=128)>
  File "/usr/lib/python3.7/site-packages/flexget/components/series/configure_series.py", line 61, in on_task_prepare
    for entry in result:
                 └ <flexget.utils.cached_input.IterableCache object at 0x14e159e00490>
  File "/usr/lib/python3.7/site-packages/flexget/utils/cached_input.py", line 231, in __iter__
    for item in self.iterable:
                │    └ <generator object KitsuAnime.on_task_input at 0x14e15a9565d0>
                └ <flexget.utils.cached_input.IterableCache object at 0x14e159e00490>
> File "/usr/lib/python3.7/site-packages/flexget/plugins/input/kitsu.py", line 79, in on_task_input
    response = task.requests.get(next_url, params=payload)
               │    │        │   │                └ {'filter[status]': 'current,planned', 'filter[media_type]': 'Anime', 'include': 'media', 'page[limit]': 20}
               │    │        │   └ 'https://kitsu.io/api/edge/users/61827/library-entries'
               │    │        └ <function Session.get at 0x14e160106cb0>
               │    └ <flexget.utils.requests.Session object at 0x14e159de3c10>
               └ <flexget.task.Task object at 0x14e159de3f10>
  File "/usr/lib/python3.7/site-packages/requests/sessions.py", line 542, in get
    return self.request('GET', url, **kwargs)
           │    │              │      └ {'params': {'filter[status]': 'current,planned', 'filter[media_type]': 'Anime', 'include': 'media', 'page[limit]': 20}, 'allo...
           │    │              └ 'https://kitsu.io/api/edge/users/61827/library-entries'
           │    └ <function Session.request at 0x14e15ecb1050>
           └ <flexget.utils.requests.Session object at 0x14e159de3c10>
  File "/usr/lib/python3.7/site-packages/flexget/utils/requests.py", line 271, in request
    result.raise_for_status()
    │      └ <function Response.raise_for_status at 0x14e1601613b0>
    └ <Response [500]>
  File "/usr/lib/python3.7/site-packages/requests/models.py", line 960, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
          │         │                        └ <Response [500]>
          │         └ '500 Server Error: Internal Server Error for url: https://kitsu.io/api/edge/users/61827/library-entries?filter%5Bstatus%5D=cu...
          └ <class 'requests.exceptions.HTTPError'>

requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: https://kitsu.io/api/edge/users/61827/library-entries?filter%5Bstatus%5D=current%2Cplanned&filter%5Bmedia_type%5D=Anime&include=media&page%5Blimit%5D=20
2022-06-19 16:01:47 CRITICAL plugin        check-SubsPlease Error getting list from https://kitsu.io/api/edge/users/61827/library-entries?filter%5Bstatus%5D=current%2Cplanned&filter%5Bmedia_type%5D=Anime&include=media&page%5Blimit%5D=20 status: 500
2022-06-19 16:01:47 WARNING  task          check-SubsPlease Aborting task (plugin: configure_series)
```
</details>

### Additional information:

- FlexGet version: 3.3.17
- Python version:  3.7.7
- Installation method: Docker https://hub.docker.com/r/cpoppema/docker-flexget
- Using daemon (yes/no):
- OS and version: Unraid 6.9.2 / Docker 20.10.5

I don't know how much you can do about this if it is just an exclusion of the `&include=media` or if that is required to do requests, I already posted a Bug report [here](https://kitsu.io/feedback/bugs/p/includemedia-api-parameter-results-in-http-error-500) for Kitsu's side but just to also have a point here if someone else is running into this issue.


Discussion:

Resolving patch:
diff --git a/flexget/plugins/input/kitsu.py b/flexget/plugins/input/kitsu.py
--- a/flexget/plugins/input/kitsu.py
+++ b/flexget/plugins/input/kitsu.py
@@ -18,6 +18,7 @@ class KitsuAnime:
 
     kitsu:
       username: <value>
+      user_id: <value>
       lists:
         - <current|planned|completed|on_hold|dropped>
         - <current|planned|completed|on_hold|dropped>
@@ -32,6 +33,7 @@ class KitsuAnime:
         'type': 'object',
         'properties': {
             'username': {'type': 'string'},
+            'user_id': {'type': 'string'},
             'lists': one_or_more(
                 {
                     'type': 'string',
@@ -44,37 +46,26 @@ class KitsuAnime:
             'latest': {'type': 'boolean', 'default': False},
             'status': {'type': 'string', 'enum': ['airing', 'finished']},
         },
-        'required': ['username'],
+        'oneOf': [{'required': ['username']}, {'required': ['user_id']}],
         'additionalProperties': False,
     }
 
     @cached('kitsu', persist='2 hours')
     def on_task_input(self, task, config):
-        user_payload = {'filter[name]': config['username']}
-        try:
-            user_response = task.requests.get(
-                'https://kitsu.io/api/edge/users', params=user_payload
-            )
-        except RequestException as e:
-            error_message = 'Error finding User url: {url}'.format(url=e.request.url)
-            if hasattr(e, 'response'):
-                error_message += ' status: {status}'.format(status=e.response.status_code)
-            logger.opt(exception=True).debug(error_message)
-            raise plugin.PluginError(error_message)
-        user = user_response.json()
-        if not len(user['data']):
-            raise plugin.PluginError(
-                'no such username found "{name}"'.format(name=config['username'])
-            )
-        next_url = 'https://kitsu.io/api/edge/users/{id}/library-entries'.format(
-            id=user['data'][0]['id']
+        user_id = self._resolve_user_id(task, config)
+        next_url = 'https://kitsu.io/api/edge/users/{user_id}/library-entries'.format(
+            user_id=user_id
         )
+
         payload = {
             'filter[status]': ','.join(config['lists']),
-            'filter[media_type]': 'Anime',
-            'include': 'media',
+            'filter[kind]': 'anime',
+            'include': 'anime',
+            'fields[anime]': 'canonicalTitle,titles,endDate,subtype',
+            'fields[libraryEntries]': 'anime',
             'page[limit]': 20,
         }
+
         try:
             response = task.requests.get(next_url, params=payload)
         except RequestException as e:
@@ -83,18 +74,22 @@ def on_task_input(self, task, config):
                 error_message += ' status: {status}'.format(status=e.response.status_code)
             logger.opt(exception=True).debug(error_message)
             raise plugin.PluginError(error_message)
-
         json_data = response.json()
 
         while json_data:
+            anime_dict = {
+                relation['id']: relation
+                for relation in json_data['included']
+                if relation['type'] == 'anime'
+            }
+
+            for item in json_data['data']:
+                if item['relationships']['anime']['data'] is None:
+                    logger.opt(exception=True).debug('Anime relation missing')
+                    continue
+
+                anime = anime_dict[item['relationships']['anime']['data']['id']]
 
-            for item, anime in zip(json_data['data'], json_data['included']):
-                if item['relationships']['media']['data']['id'] != anime['id']:
-                    raise ValueError(
-                        'Anime IDs {id1} and {id2} do not match'.format(
-                            id1=item['relationships']['media']['data']['id'], id2=anime['id']
-                        )
-                    )
                 status = config.get('status')
                 if status is not None:
                     if status == 'airing' and anime['attributes']['endDate'] is not None:
@@ -143,6 +138,30 @@ def on_task_input(self, task, config):
             else:
                 break
 
+    def _resolve_user_id(self, task, config):
+        user_id = config.get('user_id')
+
+        if user_id is None:
+            user_payload = {'filter[name]': config['username']}
+            try:
+                user_response = task.requests.get(
+                    'https://kitsu.io/api/edge/users', params=user_payload
+                )
+            except RequestException as e:
+                error_message = 'Error finding User url: {url}'.format(url=e.request.url)
+                if hasattr(e, 'response'):
+                    error_message += ' status: {status}'.format(status=e.response.status_code)
+                logger.opt(exception=True).debug(error_message)
+                raise plugin.PluginError(error_message)
+            user = user_response.json()
+            if not len(user['data']):
+                raise plugin.PluginError(
+                    'no such username found "{name}"'.format(name=config['username'])
+                )
+            user_id = user['data'][0]['id']
+
+        return user_id
+
 
 @event('plugin.register')
 def register_plugin():


Test patch: