We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[APP SUBMITTED]: TypeEError: 'int' object has no attribute '__getitem__'
### INFO
**Python Version**: `2.7.9 (default, Aug 13 2016, 17:56:53) [GCC 4.9.2]`
**Operating System**: `Linux-4.4.27-1-osmc-armv7l-with-debian-8.6`
**Locale**: `UTF-8`
**Branch**: [optimized2](../tree/optimized2)
**Database**: `44.3`
**Commit**: pymedusa/Medusa@ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6
**Link to Log**: https://gist.github.com/2f6af155ba86624566e4d31c88f94e40
### ERROR
<pre>
2016-10-31 12:46:51 ERROR    SEARCHQUEUE-MANUAL-248835 :: [T411] :: [ff8dcfa] Unknown exception while searching T411. Error: "'int' object has no attribute '__getitem__'"
Traceback (most recent call last):
  File "/home/osmc/Medusa/<a href="../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/search/core.py#L621">medusa/search/core.py</a>", line 621, in searchProviders
    downCurQuality, manual_search, manual_search_type)
  File "/home/osmc/Medusa/<a href="../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/providers/generic_provider.py#L182">medusa/providers/generic_provider.py</a>", line 182, in find_search_results
    items_list += self.search(search_string, ep_obj=episode)
  File "/home/osmc/Medusa/<a href="../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/providers/torrent/json/t411.py#L106">medusa/providers/torrent/json/t411.py</a>", line 106, in search
    results += self.parse(jdata, mode)
  File "/home/osmc/Medusa/<a href="../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/providers/torrent/json/t411.py#L121">medusa/providers/torrent/json/t411.py</a>", line 121, in parse
    torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)
TypeError: 'int' object has no attribute '__getitem__'
</pre>

---
_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `medusa/providers/torrent/json/t411.py`
Content:
```
1 # coding=utf-8
2 # Author: djoole <bobby.djoole@gmail.com>
3 #
4 # This file is part of Medusa.
5 #
6 # Medusa is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # Medusa is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Medusa. If not, see <http://www.gnu.org/licenses/>.
18 """Provider code for T411."""
19 from __future__ import unicode_literals
20 
21 import time
22 import traceback
23 
24 from operator import itemgetter
25 from requests.auth import AuthBase
26 from requests.compat import urljoin
27 from ..torrent_provider import TorrentProvider
28 from .... import logger, tv_cache
29 from ....common import USER_AGENT
30 from ....helper.common import convert_size, try_int
31 
32 
33 class T411Provider(TorrentProvider):  # pylint: disable=too-many-instance-attributes
34     """T411 Torrent provider."""
35 
36     def __init__(self):
37         """Provider Init."""
38         TorrentProvider.__init__(self, "T411")
39 
40         # Credentials
41         self.username = None
42         self.password = None
43         self.token = None
44         self.tokenLastUpdate = None
45 
46         # URLs
47         self.url = 'https://api.t411.ch'
48         self.urls = {
49             'base_url': 'http://www.t411.ch/',
50             'search': urljoin(self.url, 'torrents/search/%s*?cid=%s&limit=100'),
51             'rss': urljoin(self.url, 'torrents/top/today'),
52             'login_page': urljoin(self.url, 'auth'),
53             'download': urljoin(self.url, 'torrents/download/%s'),
54         }
55 
56         # Proper Strings
57 
58         # Miscellaneous Options
59         self.headers.update({'User-Agent': USER_AGENT})
60         self.subcategories = [433, 637, 455, 639]
61         self.confirmed = False
62 
63         # Torrent Stats
64         self.minseed = 0
65         self.minleech = 0
66 
67         # Cache
68         self.cache = tv_cache.TVCache(self, min_time=10)  # Only poll T411 every 10 minutes max
69 
70     def search(self, search_strings, age=0, ep_obj=None):  # pylint: disable=too-many-locals, too-many-branches
71         """Search a provider and parse the results.
72 
73         :param search_strings: A dict with mode (key) and the search value (value)
74         :param age: Not used
75         :param ep_obj: Not used
76         :returns: A list of search results (structure)
77         """
78         results = []
79         if not self.login():
80             return results
81 
82         for mode in search_strings:
83             logger.log('Search mode: {0}'.format(mode), logger.DEBUG)
84 
85             for search_string in search_strings[mode]:
86                 if mode != 'RSS':
87                     logger.log('Search string: {search}'.format
88                                (search=search_string), logger.DEBUG)
89                     if self.confirmed:
90                         logger.log('Searching only confirmed torrents', logger.DEBUG)
91 
92                 search_urls = ([self.urls['search'] % (search_string, u)
93                                for u in self.subcategories], [self.urls['rss']])[mode == 'RSS']
94                 for search_url in search_urls:
95                     response = self.get_url(search_url, returns='response')
96 
97                     if not response or not response.content:
98                         logger.log('No data returned from provider', logger.DEBUG)
99                         continue
100 
101                     try:
102                         jdata = response.json()
103                     except ValueError:  # also catches JSONDecodeError if simplejson is installed
104                         logger.log('No data returned from provider', logger.DEBUG)
105                         continue
106 
107                     results += self.parse(jdata, mode)
108 
109         return results
110 
111     def parse(self, data, mode):
112         """Parse search results for items.
113 
114         :param data: The raw response from a search
115         :param mode: The current mode used to search, e.g. RSS
116 
117         :return: A list of items found
118         """
119         items = []
120 
121         unsorted_torrent_rows = data.get('torrents') if mode != 'RSS' else data
122         torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)
123 
124         if not torrent_rows or not isinstance(torrent_rows, list):
125             logger.log('Data returned from provider does not contain any {0}torrents'.format(
126                 'confirmed ' if self.confirmed else ''), logger.DEBUG)
127             return items
128 
129         for row in torrent_rows:
130             if not isinstance(row, dict):
131                 logger.log('Invalid data returned from provider', logger.WARNING)
132                 continue
133 
134             if mode == 'RSS' and 'category' in row and try_int(row['category'], 0) not in self.subcategories:
135                 continue
136 
137             try:
138                 title = row['name']
139                 torrent_id = row['id']
140                 download_url = (self.urls['download'] % torrent_id)
141                 if not all([title, download_url]):
142                     continue
143 
144                 seeders = try_int(row['seeders'])
145                 leechers = try_int(row['leechers'])
146                 verified = bool(row['isVerified'])
147 
148                 # Filter unseeded torrent
149                 if seeders < min(self.minseed, 1):
150                     if mode != 'RSS':
151                         logger.log("Discarding torrent because it doesn't meet the "
152                                    "minimum seeders: {0}. Seeders: {1}".format
153                                    (title, seeders), logger.DEBUG)
154                     continue
155 
156                 if self.confirmed and not verified and mode != 'RSS':
157                     logger.log("Found result {0} but that doesn't seem like a verified"
158                                " result so I'm ignoring it".format(title), logger.DEBUG)
159                     continue
160 
161                 torrent_size = row['size']
162                 size = convert_size(torrent_size) or -1
163 
164                 item = {
165                     'title': title,
166                     'link': download_url,
167                     'size': size,
168                     'seeders': seeders,
169                     'leechers': leechers,
170                     'pubdate': None,
171                     'torrent_hash': None,
172                 }
173                 if mode != 'RSS':
174                     logger.log('Found result: {0} with {1} seeders and {2} leechers'.format
175                                (title, seeders, leechers), logger.DEBUG)
176 
177                 items.append(item)
178             except (AttributeError, TypeError, KeyError, ValueError, IndexError):
179                 logger.log('Failed parsing provider. Traceback: {0!r}'.format
180                            (traceback.format_exc()), logger.ERROR)
181 
182         return items
183 
184     def login(self):
185         """Log into provider."""
186         if self.token is not None:
187             if time.time() < (self.tokenLastUpdate + 30 * 60):
188                 return True
189 
190         login_params = {
191             'username': self.username,
192             'password': self.password,
193         }
194 
195         response = self.get_url(self.urls['login_page'], post_data=login_params, returns='json')
196         if not response:
197             logger.log('Unable to connect to provider', logger.WARNING)
198             return False
199 
200         if response and 'token' in response:
201             self.token = response['token']
202             self.tokenLastUpdate = time.time()
203             # self.uid = response['uid'].encode('ascii', 'ignore')
204             self.session.auth = T411Auth(self.token)
205             return True
206         else:
207             logger.log('Token not found in authentication response', logger.WARNING)
208             return False
209 
210 
211 class T411Auth(AuthBase):  # pylint: disable=too-few-public-methods
212     """Attach HTTP Authentication to the given Request object."""
213 
214     def __init__(self, token):
215         """Init object."""
216         self.token = token
217 
218     def __call__(self, r):
219         """Add token to request header."""
220         r.headers['Authorization'] = self.token
221         return r
222 
223 
224 provider = T411Provider()
225
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/medusa/providers/torrent/json/t411.py b/medusa/providers/torrent/json/t411.py
--- a/medusa/providers/torrent/json/t411.py
+++ b/medusa/providers/torrent/json/t411.py
@@ -119,13 +119,14 @@
         items = []
 
         unsorted_torrent_rows = data.get('torrents') if mode != 'RSS' else data
-        torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)
 
-        if not torrent_rows or not isinstance(torrent_rows, list):
+        if not unsorted_torrent_rows or not isinstance(unsorted_torrent_rows, dict):
             logger.log('Data returned from provider does not contain any {0}torrents'.format(
                 'confirmed ' if self.confirmed else ''), logger.DEBUG)
             return items
 
+        torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)
+
         for row in torrent_rows:
             if not isinstance(row, dict):
                 logger.log('Invalid data returned from provider', logger.WARNING)

{"golden_diff": "diff --git a/medusa/providers/torrent/json/t411.py b/medusa/providers/torrent/json/t411.py\n--- a/medusa/providers/torrent/json/t411.py\n+++ b/medusa/providers/torrent/json/t411.py\n@@ -119,13 +119,14 @@\n         items = []\n \n         unsorted_torrent_rows = data.get('torrents') if mode != 'RSS' else data\n-        torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)\n \n-        if not torrent_rows or not isinstance(torrent_rows, list):\n+        if not unsorted_torrent_rows or not isinstance(unsorted_torrent_rows, dict):\n             logger.log('Data returned from provider does not contain any {0}torrents'.format(\n                 'confirmed ' if self.confirmed else ''), logger.DEBUG)\n             return items\n \n+        torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)\n+\n         for row in torrent_rows:\n             if not isinstance(row, dict):\n                 logger.log('Invalid data returned from provider', logger.WARNING)\n", "issue": "[APP SUBMITTED]: TypeEError: 'int' object has no attribute '__getitem__'\n### INFO\n**Python Version**: `2.7.9 (default, Aug 13 2016, 17:56:53) [GCC 4.9.2]`\n**Operating System**: `Linux-4.4.27-1-osmc-armv7l-with-debian-8.6`\n**Locale**: `UTF-8`\n**Branch**: [optimized2](../tree/optimized2)\n**Database**: `44.3`\n**Commit**: pymedusa/Medusa@ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6\n**Link to Log**: https://gist.github.com/2f6af155ba86624566e4d31c88f94e40\n### ERROR\n<pre>\n2016-10-31 12:46:51 ERROR    SEARCHQUEUE-MANUAL-248835 :: [T411] :: [ff8dcfa] Unknown exception while searching T411. Error: \"'int' object has no attribute '__getitem__'\"\nTraceback (most recent call last):\n  File \"/home/osmc/Medusa/<a href=\"../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/search/core.py#L621\">medusa/search/core.py</a>\", line 621, in searchProviders\n    downCurQuality, manual_search, manual_search_type)\n  File \"/home/osmc/Medusa/<a href=\"../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/providers/generic_provider.py#L182\">medusa/providers/generic_provider.py</a>\", line 182, in find_search_results\n    items_list += self.search(search_string, ep_obj=episode)\n  File \"/home/osmc/Medusa/<a href=\"../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/providers/torrent/json/t411.py#L106\">medusa/providers/torrent/json/t411.py</a>\", line 106, in search\n    results += self.parse(jdata, mode)\n  File \"/home/osmc/Medusa/<a href=\"../blob/ff8dcfa6034366fa6c9d0b526b67e3cdb02fafe6/medusa/providers/torrent/json/t411.py#L121\">medusa/providers/torrent/json/t411.py</a>\", line 121, in parse\n    torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)\nTypeError: 'int' object has no attribute '__getitem__'\n</pre>\n\n---\n_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators\n", "before_files": [{"content": "# coding=utf-8\n# Author: djoole <bobby.djoole@gmail.com>\n#\n# This file is part of Medusa.\n#\n# Medusa is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Medusa is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Medusa. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Provider code for T411.\"\"\"\nfrom __future__ import unicode_literals\n\nimport time\nimport traceback\n\nfrom operator import itemgetter\nfrom requests.auth import AuthBase\nfrom requests.compat import urljoin\nfrom ..torrent_provider import TorrentProvider\nfrom .... import logger, tv_cache\nfrom ....common import USER_AGENT\nfrom ....helper.common import convert_size, try_int\n\n\nclass T411Provider(TorrentProvider):  # pylint: disable=too-many-instance-attributes\n    \"\"\"T411 Torrent provider.\"\"\"\n\n    def __init__(self):\n        \"\"\"Provider Init.\"\"\"\n        TorrentProvider.__init__(self, \"T411\")\n\n        # Credentials\n        self.username = None\n        self.password = None\n        self.token = None\n        self.tokenLastUpdate = None\n\n        # URLs\n        self.url = 'https://api.t411.ch'\n        self.urls = {\n            'base_url': 'http://www.t411.ch/',\n            'search': urljoin(self.url, 'torrents/search/%s*?cid=%s&limit=100'),\n            'rss': urljoin(self.url, 'torrents/top/today'),\n            'login_page': urljoin(self.url, 'auth'),\n            'download': urljoin(self.url, 'torrents/download/%s'),\n        }\n\n        # Proper Strings\n\n        # Miscellaneous Options\n        self.headers.update({'User-Agent': USER_AGENT})\n        self.subcategories = [433, 637, 455, 639]\n        self.confirmed = False\n\n        # Torrent Stats\n        self.minseed = 0\n        self.minleech = 0\n\n        # Cache\n        self.cache = tv_cache.TVCache(self, min_time=10)  # Only poll T411 every 10 minutes max\n\n    def search(self, search_strings, age=0, ep_obj=None):  # pylint: disable=too-many-locals, too-many-branches\n        \"\"\"Search a provider and parse the results.\n\n        :param search_strings: A dict with mode (key) and the search value (value)\n        :param age: Not used\n        :param ep_obj: Not used\n        :returns: A list of search results (structure)\n        \"\"\"\n        results = []\n        if not self.login():\n            return results\n\n        for mode in search_strings:\n            logger.log('Search mode: {0}'.format(mode), logger.DEBUG)\n\n            for search_string in search_strings[mode]:\n                if mode != 'RSS':\n                    logger.log('Search string: {search}'.format\n                               (search=search_string), logger.DEBUG)\n                    if self.confirmed:\n                        logger.log('Searching only confirmed torrents', logger.DEBUG)\n\n                search_urls = ([self.urls['search'] % (search_string, u)\n                               for u in self.subcategories], [self.urls['rss']])[mode == 'RSS']\n                for search_url in search_urls:\n                    response = self.get_url(search_url, returns='response')\n\n                    if not response or not response.content:\n                        logger.log('No data returned from provider', logger.DEBUG)\n                        continue\n\n                    try:\n                        jdata = response.json()\n                    except ValueError:  # also catches JSONDecodeError if simplejson is installed\n                        logger.log('No data returned from provider', logger.DEBUG)\n                        continue\n\n                    results += self.parse(jdata, mode)\n\n        return results\n\n    def parse(self, data, mode):\n        \"\"\"Parse search results for items.\n\n        :param data: The raw response from a search\n        :param mode: The current mode used to search, e.g. RSS\n\n        :return: A list of items found\n        \"\"\"\n        items = []\n\n        unsorted_torrent_rows = data.get('torrents') if mode != 'RSS' else data\n        torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)\n\n        if not torrent_rows or not isinstance(torrent_rows, list):\n            logger.log('Data returned from provider does not contain any {0}torrents'.format(\n                'confirmed ' if self.confirmed else ''), logger.DEBUG)\n            return items\n\n        for row in torrent_rows:\n            if not isinstance(row, dict):\n                logger.log('Invalid data returned from provider', logger.WARNING)\n                continue\n\n            if mode == 'RSS' and 'category' in row and try_int(row['category'], 0) not in self.subcategories:\n                continue\n\n            try:\n                title = row['name']\n                torrent_id = row['id']\n                download_url = (self.urls['download'] % torrent_id)\n                if not all([title, download_url]):\n                    continue\n\n                seeders = try_int(row['seeders'])\n                leechers = try_int(row['leechers'])\n                verified = bool(row['isVerified'])\n\n                # Filter unseeded torrent\n                if seeders < min(self.minseed, 1):\n                    if mode != 'RSS':\n                        logger.log(\"Discarding torrent because it doesn't meet the \"\n                                   \"minimum seeders: {0}. Seeders: {1}\".format\n                                   (title, seeders), logger.DEBUG)\n                    continue\n\n                if self.confirmed and not verified and mode != 'RSS':\n                    logger.log(\"Found result {0} but that doesn't seem like a verified\"\n                               \" result so I'm ignoring it\".format(title), logger.DEBUG)\n                    continue\n\n                torrent_size = row['size']\n                size = convert_size(torrent_size) or -1\n\n                item = {\n                    'title': title,\n                    'link': download_url,\n                    'size': size,\n                    'seeders': seeders,\n                    'leechers': leechers,\n                    'pubdate': None,\n                    'torrent_hash': None,\n                }\n                if mode != 'RSS':\n                    logger.log('Found result: {0} with {1} seeders and {2} leechers'.format\n                               (title, seeders, leechers), logger.DEBUG)\n\n                items.append(item)\n            except (AttributeError, TypeError, KeyError, ValueError, IndexError):\n                logger.log('Failed parsing provider. Traceback: {0!r}'.format\n                           (traceback.format_exc()), logger.ERROR)\n\n        return items\n\n    def login(self):\n        \"\"\"Log into provider.\"\"\"\n        if self.token is not None:\n            if time.time() < (self.tokenLastUpdate + 30 * 60):\n                return True\n\n        login_params = {\n            'username': self.username,\n            'password': self.password,\n        }\n\n        response = self.get_url(self.urls['login_page'], post_data=login_params, returns='json')\n        if not response:\n            logger.log('Unable to connect to provider', logger.WARNING)\n            return False\n\n        if response and 'token' in response:\n            self.token = response['token']\n            self.tokenLastUpdate = time.time()\n            # self.uid = response['uid'].encode('ascii', 'ignore')\n            self.session.auth = T411Auth(self.token)\n            return True\n        else:\n            logger.log('Token not found in authentication response', logger.WARNING)\n            return False\n\n\nclass T411Auth(AuthBase):  # pylint: disable=too-few-public-methods\n    \"\"\"Attach HTTP Authentication to the given Request object.\"\"\"\n\n    def __init__(self, token):\n        \"\"\"Init object.\"\"\"\n        self.token = token\n\n    def __call__(self, r):\n        \"\"\"Add token to request header.\"\"\"\n        r.headers['Authorization'] = self.token\n        return r\n\n\nprovider = T411Provider()\n", "path": "medusa/providers/torrent/json/t411.py"}], "after_files": [{"content": "# coding=utf-8\n# Author: djoole <bobby.djoole@gmail.com>\n#\n# This file is part of Medusa.\n#\n# Medusa is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Medusa is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Medusa. If not, see <http://www.gnu.org/licenses/>.\n\"\"\"Provider code for T411.\"\"\"\nfrom __future__ import unicode_literals\n\nimport time\nimport traceback\n\nfrom operator import itemgetter\nfrom requests.auth import AuthBase\nfrom requests.compat import urljoin\nfrom ..torrent_provider import TorrentProvider\nfrom .... import logger, tv_cache\nfrom ....common import USER_AGENT\nfrom ....helper.common import convert_size, try_int\n\n\nclass T411Provider(TorrentProvider):  # pylint: disable=too-many-instance-attributes\n    \"\"\"T411 Torrent provider.\"\"\"\n\n    def __init__(self):\n        \"\"\"Provider Init.\"\"\"\n        TorrentProvider.__init__(self, \"T411\")\n\n        # Credentials\n        self.username = None\n        self.password = None\n        self.token = None\n        self.tokenLastUpdate = None\n\n        # URLs\n        self.url = 'https://api.t411.ch'\n        self.urls = {\n            'base_url': 'http://www.t411.ch/',\n            'search': urljoin(self.url, 'torrents/search/%s*?cid=%s&limit=100'),\n            'rss': urljoin(self.url, 'torrents/top/today'),\n            'login_page': urljoin(self.url, 'auth'),\n            'download': urljoin(self.url, 'torrents/download/%s'),\n        }\n\n        # Proper Strings\n\n        # Miscellaneous Options\n        self.headers.update({'User-Agent': USER_AGENT})\n        self.subcategories = [433, 637, 455, 639]\n        self.confirmed = False\n\n        # Torrent Stats\n        self.minseed = 0\n        self.minleech = 0\n\n        # Cache\n        self.cache = tv_cache.TVCache(self, min_time=10)  # Only poll T411 every 10 minutes max\n\n    def search(self, search_strings, age=0, ep_obj=None):  # pylint: disable=too-many-locals, too-many-branches\n        \"\"\"Search a provider and parse the results.\n\n        :param search_strings: A dict with mode (key) and the search value (value)\n        :param age: Not used\n        :param ep_obj: Not used\n        :returns: A list of search results (structure)\n        \"\"\"\n        results = []\n        if not self.login():\n            return results\n\n        for mode in search_strings:\n            logger.log('Search mode: {0}'.format(mode), logger.DEBUG)\n\n            for search_string in search_strings[mode]:\n                if mode != 'RSS':\n                    logger.log('Search string: {search}'.format\n                               (search=search_string), logger.DEBUG)\n                    if self.confirmed:\n                        logger.log('Searching only confirmed torrents', logger.DEBUG)\n\n                search_urls = ([self.urls['search'] % (search_string, u)\n                               for u in self.subcategories], [self.urls['rss']])[mode == 'RSS']\n                for search_url in search_urls:\n                    response = self.get_url(search_url, returns='response')\n\n                    if not response or not response.content:\n                        logger.log('No data returned from provider', logger.DEBUG)\n                        continue\n\n                    try:\n                        jdata = response.json()\n                    except ValueError:  # also catches JSONDecodeError if simplejson is installed\n                        logger.log('No data returned from provider', logger.DEBUG)\n                        continue\n\n                    results += self.parse(jdata, mode)\n\n        return results\n\n    def parse(self, data, mode):\n        \"\"\"Parse search results for items.\n\n        :param data: The raw response from a search\n        :param mode: The current mode used to search, e.g. RSS\n\n        :return: A list of items found\n        \"\"\"\n        items = []\n\n        unsorted_torrent_rows = data.get('torrents') if mode != 'RSS' else data\n\n        if not unsorted_torrent_rows or not isinstance(unsorted_torrent_rows, dict):\n            logger.log('Data returned from provider does not contain any {0}torrents'.format(\n                'confirmed ' if self.confirmed else ''), logger.DEBUG)\n            return items\n\n        torrent_rows = sorted(unsorted_torrent_rows, key=itemgetter('added'), reverse=True)\n\n        for row in torrent_rows:\n            if not isinstance(row, dict):\n                logger.log('Invalid data returned from provider', logger.WARNING)\n                continue\n\n            if mode == 'RSS' and 'category' in row and try_int(row['category'], 0) not in self.subcategories:\n                continue\n\n            try:\n                title = row['name']\n                torrent_id = row['id']\n                download_url = (self.urls['download'] % torrent_id)\n                if not all([title, download_url]):\n                    continue\n\n                seeders = try_int(row['seeders'])\n                leechers = try_int(row['leechers'])\n                verified = bool(row['isVerified'])\n\n                # Filter unseeded torrent\n                if seeders < min(self.minseed, 1):\n                    if mode != 'RSS':\n                        logger.log(\"Discarding torrent because it doesn't meet the \"\n                                   \"minimum seeders: {0}. Seeders: {1}\".format\n                                   (title, seeders), logger.DEBUG)\n                    continue\n\n                if self.confirmed and not verified and mode != 'RSS':\n                    logger.log(\"Found result {0} but that doesn't seem like a verified\"\n                               \" result so I'm ignoring it\".format(title), logger.DEBUG)\n                    continue\n\n                torrent_size = row['size']\n                size = convert_size(torrent_size) or -1\n\n                item = {\n                    'title': title,\n                    'link': download_url,\n                    'size': size,\n                    'seeders': seeders,\n                    'leechers': leechers,\n                    'pubdate': None,\n                    'torrent_hash': None,\n                }\n                if mode != 'RSS':\n                    logger.log('Found result: {0} with {1} seeders and {2} leechers'.format\n                               (title, seeders, leechers), logger.DEBUG)\n\n                items.append(item)\n            except (AttributeError, TypeError, KeyError, ValueError, IndexError):\n                logger.log('Failed parsing provider. Traceback: {0!r}'.format\n                           (traceback.format_exc()), logger.ERROR)\n\n        return items\n\n    def login(self):\n        \"\"\"Log into provider.\"\"\"\n        if self.token is not None:\n            if time.time() < (self.tokenLastUpdate + 30 * 60):\n                return True\n\n        login_params = {\n            'username': self.username,\n            'password': self.password,\n        }\n\n        response = self.get_url(self.urls['login_page'], post_data=login_params, returns='json')\n        if not response:\n            logger.log('Unable to connect to provider', logger.WARNING)\n            return False\n\n        if response and 'token' in response:\n            self.token = response['token']\n            self.tokenLastUpdate = time.time()\n            # self.uid = response['uid'].encode('ascii', 'ignore')\n            self.session.auth = T411Auth(self.token)\n            return True\n        else:\n            logger.log('Token not found in authentication response', logger.WARNING)\n            return False\n\n\nclass T411Auth(AuthBase):  # pylint: disable=too-few-public-methods\n    \"\"\"Attach HTTP Authentication to the given Request object.\"\"\"\n\n    def __init__(self, token):\n        \"\"\"Init object.\"\"\"\n        self.token = token\n\n    def __call__(self, r):\n        \"\"\"Add token to request header.\"\"\"\n        r.headers['Authorization'] = self.token\n        return r\n\n\nprovider = T411Provider()\n", "path": "medusa/providers/torrent/json/t411.py"}]}