We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[APP SUBMITTED]: ProtocolError: <ProtocolError for api.broadcasthe.net: 524 Origin Time-out>
### INFO
**Python Version**: `2.7.9 (default, Sep 17 2016, 20:26:04) [GCC 4.9.2]`
**Operating System**: `Linux-4.9.28-v7+-armv7l-with-debian-8.0`
**Locale**: `UTF-8`
**Branch**: [develop](../tree/develop)
**Database**: `44.8`
**Commit**: pymedusa/Medusa@ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7
**Link to Log**: https://gist.github.com/8ec8857ffce541780792b9bbb0950bcc
### ERROR
<pre>
2017-07-13 21:11:57 ERROR    SEARCHQUEUE-DAILY-SEARCH :: [BTN] :: [ad5ec6b] DailySearchQueueItem Exception, error: <ProtocolError for api.broadcasthe.net: 524 Origin Time-out>
Traceback (most recent call last):
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/search/queue.py#L262">medusa/search/queue.py</a>", line 262, in run
    found_results = search_for_needed_episodes(force=self.force)
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/search/core.py#L444">medusa/search/core.py</a>", line 444, in search_for_needed_episodes
    cur_provider.cache.update_cache()
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/tv/cache.py#L196">medusa/tv/cache.py</a>", line 196, in update_cache
    data = self._get_rss_data()
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/tv/cache.py#L179">medusa/tv/cache.py</a>", line 179, in _get_rss_data
    return {'entries': self.provider.search(self.search_params)}
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/providers/torrent/json/btn.py#L94">medusa/providers/torrent/json/btn.py</a>", line 94, in search
    response = self._api_call(search_params)
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/providers/torrent/json/btn.py#L246">medusa/providers/torrent/json/btn.py</a>", line 246, in _api_call
    int(offset)
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/lib/jsonrpclib/jsonrpc.py#L288">lib/jsonrpclib/jsonrpc.py</a>", line 288, in __call__
    return self.__send(self.__name, args)
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/lib/jsonrpclib/jsonrpc.py#L237">lib/jsonrpclib/jsonrpc.py</a>", line 237, in _request
    response = self._run_request(request)
  File "/home/pi/Medusa/<a href="../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/lib/jsonrpclib/jsonrpc.py#L255">lib/jsonrpclib/jsonrpc.py</a>", line 255, in _run_request
    verbose=self.__verbose
  File "/usr/lib/python2.7/xmlrpclib.py", line 1273, in request
    return self.single_request(host, handler, request_body, verbose)
  File "/usr/lib/python2.7/xmlrpclib.py", line 1321, in single_request
    response.msg,
ProtocolError: <ProtocolError for api.broadcasthe.net: 524 Origin Time-out>
</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/btn.py`
Content:
```
1 # coding=utf-8
2 
3 """Provider code for BTN."""
4 
5 from __future__ import unicode_literals
6 
7 import logging
8 import socket
9 import time
10 
11 import jsonrpclib
12 
13 from medusa import (
14     app,
15     scene_exceptions,
16     tv,
17 )
18 from medusa.common import cpu_presets
19 from medusa.helper.common import episode_num
20 from medusa.indexers.indexer_config import INDEXER_TVDBV2
21 from medusa.logger.adapters.style import BraceAdapter
22 from medusa.providers.torrent.torrent_provider import TorrentProvider
23 
24 from six import itervalues
25 
26 log = BraceAdapter(logging.getLogger(__name__))
27 log.logger.addHandler(logging.NullHandler())
28 
29 
30 # API docs:
31 # https://web.archive.org/web/20160316073644/http://btnapps.net/docs.php
32 # https://web.archive.org/web/20160425205926/http://btnapps.net/apigen/class-btnapi.html
33 
34 class BTNProvider(TorrentProvider):
35     """BTN Torrent provider."""
36 
37     def __init__(self):
38         """Initialize the class."""
39         super(BTNProvider, self).__init__('BTN')
40 
41         # Credentials
42         self.api_key = None
43 
44         # URLs
45         self.url = 'https://broadcasthe.net'
46         self.urls = {
47             'base_url': 'https://api.broadcasthe.net',
48         }
49 
50         # Proper Strings
51         self.proper_strings = []
52 
53         # Miscellaneous Options
54         self.supports_absolute_numbering = True
55 
56         # Torrent Stats
57         self.minseed = None
58         self.minleech = None
59 
60         # Cache
61         self.cache = tv.Cache(self, min_time=10)  # Only poll BTN every 15 minutes max
62 
63     def search(self, search_strings, age=0, ep_obj=None, **kwargs):
64         """
65         Search a provider and parse the results.
66 
67         :param search_strings: A dict with {mode: search value}
68         :param age: Not used
69         :param ep_obj: Not used
70         :returns: A list of search results (structure)
71         """
72         results = []
73         if not self._check_auth():
74             return results
75 
76         # Search Params
77         search_params = {
78             'age': '<=10800',  # Results from the past 3 hours
79         }
80 
81         for mode in search_strings:
82             log.debug('Search mode: {0}', mode)
83 
84             if mode != 'RSS':
85                 searches = self._search_params(ep_obj, mode)
86             else:
87                 searches = [search_params]
88 
89             for search_params in searches:
90                 if mode != 'RSS':
91                     log.debug('Search string: {search}',
92                               {'search': search_params})
93 
94                 response = self._api_call(search_params)
95                 if not response or response.get('results') == '0':
96                     log.debug('No data returned from provider')
97                     continue
98 
99                 results += self.parse(response.get('torrents', {}), mode)
100 
101         return results
102 
103     def parse(self, data, mode):
104         """
105         Parse search results for items.
106 
107         :param data: The raw response from a search
108         :param mode: The current mode used to search, e.g. RSS
109 
110         :return: A list of items found
111         """
112         items = []
113 
114         torrent_rows = itervalues(data)
115 
116         for row in torrent_rows:
117             title, download_url = self._process_title_and_url(row)
118             if not all([title, download_url]):
119                 continue
120 
121             seeders = row.get('Seeders', 1)
122             leechers = row.get('Leechers', 0)
123 
124             # Filter unseeded torrent
125             if seeders < min(self.minseed, 1):
126                 log.debug("Discarding torrent because it doesn't meet the"
127                           " minimum seeders: {0}. Seeders: {1}",
128                           title, seeders)
129                 continue
130 
131             size = row.get('Size') or -1
132 
133             pubdate_raw = row.get('Time')
134             pubdate = self.parse_pubdate(pubdate_raw, fromtimestamp=True)
135 
136             item = {
137                 'title': title,
138                 'link': download_url,
139                 'size': size,
140                 'seeders': seeders,
141                 'leechers': leechers,
142                 'pubdate': pubdate,
143             }
144             log.debug(
145                 'Found result: {title} with {x} seeders'
146                 ' and {y} leechers',
147                 {'title': title, 'x': seeders, 'y': leechers}
148             )
149 
150             items.append(item)
151 
152         return items
153 
154     def _check_auth(self):
155 
156         if not self.api_key:
157             log.warning('Missing API key. Check your settings')
158             return False
159 
160         return True
161 
162     @staticmethod
163     def _process_title_and_url(parsed_json):
164         """Create the title base on properties.
165 
166         Try to get the release name, if it doesn't exist make one up
167         from the properties obtained.
168         """
169         title = parsed_json.get('ReleaseName')
170         if not title:
171             # If we don't have a release name we need to get creative
172             title = ''
173             if 'Series' in parsed_json:
174                 title += parsed_json['Series']
175             if 'GroupName' in parsed_json:
176                 title += '.' + parsed_json['GroupName']
177             if 'Resolution' in parsed_json:
178                 title += '.' + parsed_json['Resolution']
179             if 'Source' in parsed_json:
180                 title += '.' + parsed_json['Source']
181             if 'Codec' in parsed_json:
182                 title += '.' + parsed_json['Codec']
183             if title:
184                 title = title.replace(' ', '.')
185 
186         url = parsed_json.get('DownloadURL').replace('\\/', '/')
187         return title, url
188 
189     def _search_params(self, ep_obj, mode, season_numbering=None):
190 
191         if not ep_obj:
192             return []
193 
194         searches = []
195         season = 'Season' if mode == 'Season' else ''
196 
197         air_by_date = ep_obj.series.air_by_date
198         sports = ep_obj.series.sports
199 
200         if not season_numbering and (air_by_date or sports):
201             date_fmt = '%Y' if season else '%Y.%m.%d'
202             search_name = ep_obj.airdate.strftime(date_fmt)
203         else:
204             search_name = '{type} {number}'.format(
205                 type=season,
206                 number=ep_obj.season if season else episode_num(
207                     ep_obj.season, ep_obj.episode
208                 ),
209             ).strip()
210 
211         params = {
212             'category': season or 'Episode',
213             'name': search_name,
214         }
215 
216         # Search
217         if ep_obj.series.indexer == INDEXER_TVDBV2:
218             params['tvdb'] = self._get_tvdb_id()
219             searches.append(params)
220         else:
221             name_exceptions = scene_exceptions.get_scene_exceptions(ep_obj.series)
222             name_exceptions.add(ep_obj.series.name)
223             for name in name_exceptions:
224                 # Search by name if we don't have tvdb id
225                 params['series'] = name
226                 searches.append(params)
227 
228         # extend air by date searches to include season numbering
229         if air_by_date and not season_numbering:
230             searches.extend(
231                 self._search_params(ep_obj, mode, season_numbering=True)
232             )
233 
234         return searches
235 
236     def _api_call(self, params=None, results_per_page=300, offset=0):
237         """Call provider API."""
238         parsed_json = {}
239 
240         try:
241             server = jsonrpclib.Server(self.urls['base_url'])
242             parsed_json = server.getTorrents(
243                 self.api_key,
244                 params or {},
245                 int(results_per_page),
246                 int(offset)
247             )
248             time.sleep(cpu_presets[app.CPU_PRESET])
249         except jsonrpclib.jsonrpc.ProtocolError as error:
250             if error.message[1] == 'Invalid API Key':
251                 log.warning('Incorrect authentication credentials.')
252             elif error.message[1] == 'Call Limit Exceeded':
253                 log.warning('You have exceeded the limit of'
254                             ' 150 calls per hour.')
255             else:
256                 log.error('JSON-RPC protocol error while accessing provider.'
257                           ' Error: {msg!r}', {'msg': error.message[1]})
258 
259         except (socket.error, socket.timeout, ValueError) as error:
260             log.warning('Error while accessing provider.'
261                         ' Error: {msg}', {'msg': error})
262         return parsed_json
263 
264 
265 provider = BTNProvider()
266
```
--- 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/btn.py b/medusa/providers/torrent/json/btn.py
--- a/medusa/providers/torrent/json/btn.py
+++ b/medusa/providers/torrent/json/btn.py
@@ -247,14 +247,14 @@
             )
             time.sleep(cpu_presets[app.CPU_PRESET])
         except jsonrpclib.jsonrpc.ProtocolError as error:
-            if error.message[1] == 'Invalid API Key':
+            if error.message == (-32001, 'Invalid API Key'):
                 log.warning('Incorrect authentication credentials.')
-            elif error.message[1] == 'Call Limit Exceeded':
+            elif error.message == (-32002, 'Call Limit Exceeded'):
                 log.warning('You have exceeded the limit of'
                             ' 150 calls per hour.')
             else:
                 log.error('JSON-RPC protocol error while accessing provider.'
-                          ' Error: {msg!r}', {'msg': error.message[1]})
+                          ' Error: {msg!r}', {'msg': error.message})
 
         except (socket.error, socket.timeout, ValueError) as error:
             log.warning('Error while accessing provider.'

{"golden_diff": "diff --git a/medusa/providers/torrent/json/btn.py b/medusa/providers/torrent/json/btn.py\n--- a/medusa/providers/torrent/json/btn.py\n+++ b/medusa/providers/torrent/json/btn.py\n@@ -247,14 +247,14 @@\n             )\n             time.sleep(cpu_presets[app.CPU_PRESET])\n         except jsonrpclib.jsonrpc.ProtocolError as error:\n-            if error.message[1] == 'Invalid API Key':\n+            if error.message == (-32001, 'Invalid API Key'):\n                 log.warning('Incorrect authentication credentials.')\n-            elif error.message[1] == 'Call Limit Exceeded':\n+            elif error.message == (-32002, 'Call Limit Exceeded'):\n                 log.warning('You have exceeded the limit of'\n                             ' 150 calls per hour.')\n             else:\n                 log.error('JSON-RPC protocol error while accessing provider.'\n-                          ' Error: {msg!r}', {'msg': error.message[1]})\n+                          ' Error: {msg!r}', {'msg': error.message})\n \n         except (socket.error, socket.timeout, ValueError) as error:\n             log.warning('Error while accessing provider.'\n", "issue": "[APP SUBMITTED]: ProtocolError: <ProtocolError for api.broadcasthe.net: 524 Origin Time-out>\n### INFO\n**Python Version**: `2.7.9 (default, Sep 17 2016, 20:26:04) [GCC 4.9.2]`\n**Operating System**: `Linux-4.9.28-v7+-armv7l-with-debian-8.0`\n**Locale**: `UTF-8`\n**Branch**: [develop](../tree/develop)\n**Database**: `44.8`\n**Commit**: pymedusa/Medusa@ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7\n**Link to Log**: https://gist.github.com/8ec8857ffce541780792b9bbb0950bcc\n### ERROR\n<pre>\n2017-07-13 21:11:57 ERROR    SEARCHQUEUE-DAILY-SEARCH :: [BTN] :: [ad5ec6b] DailySearchQueueItem Exception, error: <ProtocolError for api.broadcasthe.net: 524 Origin Time-out>\nTraceback (most recent call last):\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/search/queue.py#L262\">medusa/search/queue.py</a>\", line 262, in run\n    found_results = search_for_needed_episodes(force=self.force)\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/search/core.py#L444\">medusa/search/core.py</a>\", line 444, in search_for_needed_episodes\n    cur_provider.cache.update_cache()\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/tv/cache.py#L196\">medusa/tv/cache.py</a>\", line 196, in update_cache\n    data = self._get_rss_data()\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/tv/cache.py#L179\">medusa/tv/cache.py</a>\", line 179, in _get_rss_data\n    return {'entries': self.provider.search(self.search_params)}\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/providers/torrent/json/btn.py#L94\">medusa/providers/torrent/json/btn.py</a>\", line 94, in search\n    response = self._api_call(search_params)\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/medusa/providers/torrent/json/btn.py#L246\">medusa/providers/torrent/json/btn.py</a>\", line 246, in _api_call\n    int(offset)\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/lib/jsonrpclib/jsonrpc.py#L288\">lib/jsonrpclib/jsonrpc.py</a>\", line 288, in __call__\n    return self.__send(self.__name, args)\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/lib/jsonrpclib/jsonrpc.py#L237\">lib/jsonrpclib/jsonrpc.py</a>\", line 237, in _request\n    response = self._run_request(request)\n  File \"/home/pi/Medusa/<a href=\"../blob/ad5ec6bb0faf81fccb3ff0ca0e70f3c21b6cb8a7/lib/jsonrpclib/jsonrpc.py#L255\">lib/jsonrpclib/jsonrpc.py</a>\", line 255, in _run_request\n    verbose=self.__verbose\n  File \"/usr/lib/python2.7/xmlrpclib.py\", line 1273, in request\n    return self.single_request(host, handler, request_body, verbose)\n  File \"/usr/lib/python2.7/xmlrpclib.py\", line 1321, in single_request\n    response.msg,\nProtocolError: <ProtocolError for api.broadcasthe.net: 524 Origin Time-out>\n</pre>\n---\n_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators\n", "before_files": [{"content": "# coding=utf-8\n\n\"\"\"Provider code for BTN.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport logging\nimport socket\nimport time\n\nimport jsonrpclib\n\nfrom medusa import (\n    app,\n    scene_exceptions,\n    tv,\n)\nfrom medusa.common import cpu_presets\nfrom medusa.helper.common import episode_num\nfrom medusa.indexers.indexer_config import INDEXER_TVDBV2\nfrom medusa.logger.adapters.style import BraceAdapter\nfrom medusa.providers.torrent.torrent_provider import TorrentProvider\n\nfrom six import itervalues\n\nlog = BraceAdapter(logging.getLogger(__name__))\nlog.logger.addHandler(logging.NullHandler())\n\n\n# API docs:\n# https://web.archive.org/web/20160316073644/http://btnapps.net/docs.php\n# https://web.archive.org/web/20160425205926/http://btnapps.net/apigen/class-btnapi.html\n\nclass BTNProvider(TorrentProvider):\n    \"\"\"BTN Torrent provider.\"\"\"\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        super(BTNProvider, self).__init__('BTN')\n\n        # Credentials\n        self.api_key = None\n\n        # URLs\n        self.url = 'https://broadcasthe.net'\n        self.urls = {\n            'base_url': 'https://api.broadcasthe.net',\n        }\n\n        # Proper Strings\n        self.proper_strings = []\n\n        # Miscellaneous Options\n        self.supports_absolute_numbering = True\n\n        # Torrent Stats\n        self.minseed = None\n        self.minleech = None\n\n        # Cache\n        self.cache = tv.Cache(self, min_time=10)  # Only poll BTN every 15 minutes max\n\n    def search(self, search_strings, age=0, ep_obj=None, **kwargs):\n        \"\"\"\n        Search a provider and parse the results.\n\n        :param search_strings: A dict with {mode: search 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._check_auth():\n            return results\n\n        # Search Params\n        search_params = {\n            'age': '<=10800',  # Results from the past 3 hours\n        }\n\n        for mode in search_strings:\n            log.debug('Search mode: {0}', mode)\n\n            if mode != 'RSS':\n                searches = self._search_params(ep_obj, mode)\n            else:\n                searches = [search_params]\n\n            for search_params in searches:\n                if mode != 'RSS':\n                    log.debug('Search string: {search}',\n                              {'search': search_params})\n\n                response = self._api_call(search_params)\n                if not response or response.get('results') == '0':\n                    log.debug('No data returned from provider')\n                    continue\n\n                results += self.parse(response.get('torrents', {}), mode)\n\n        return results\n\n    def parse(self, data, mode):\n        \"\"\"\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        torrent_rows = itervalues(data)\n\n        for row in torrent_rows:\n            title, download_url = self._process_title_and_url(row)\n            if not all([title, download_url]):\n                continue\n\n            seeders = row.get('Seeders', 1)\n            leechers = row.get('Leechers', 0)\n\n            # Filter unseeded torrent\n            if seeders < min(self.minseed, 1):\n                log.debug(\"Discarding torrent because it doesn't meet the\"\n                          \" minimum seeders: {0}. Seeders: {1}\",\n                          title, seeders)\n                continue\n\n            size = row.get('Size') or -1\n\n            pubdate_raw = row.get('Time')\n            pubdate = self.parse_pubdate(pubdate_raw, fromtimestamp=True)\n\n            item = {\n                'title': title,\n                'link': download_url,\n                'size': size,\n                'seeders': seeders,\n                'leechers': leechers,\n                'pubdate': pubdate,\n            }\n            log.debug(\n                'Found result: {title} with {x} seeders'\n                ' and {y} leechers',\n                {'title': title, 'x': seeders, 'y': leechers}\n            )\n\n            items.append(item)\n\n        return items\n\n    def _check_auth(self):\n\n        if not self.api_key:\n            log.warning('Missing API key. Check your settings')\n            return False\n\n        return True\n\n    @staticmethod\n    def _process_title_and_url(parsed_json):\n        \"\"\"Create the title base on properties.\n\n        Try to get the release name, if it doesn't exist make one up\n        from the properties obtained.\n        \"\"\"\n        title = parsed_json.get('ReleaseName')\n        if not title:\n            # If we don't have a release name we need to get creative\n            title = ''\n            if 'Series' in parsed_json:\n                title += parsed_json['Series']\n            if 'GroupName' in parsed_json:\n                title += '.' + parsed_json['GroupName']\n            if 'Resolution' in parsed_json:\n                title += '.' + parsed_json['Resolution']\n            if 'Source' in parsed_json:\n                title += '.' + parsed_json['Source']\n            if 'Codec' in parsed_json:\n                title += '.' + parsed_json['Codec']\n            if title:\n                title = title.replace(' ', '.')\n\n        url = parsed_json.get('DownloadURL').replace('\\\\/', '/')\n        return title, url\n\n    def _search_params(self, ep_obj, mode, season_numbering=None):\n\n        if not ep_obj:\n            return []\n\n        searches = []\n        season = 'Season' if mode == 'Season' else ''\n\n        air_by_date = ep_obj.series.air_by_date\n        sports = ep_obj.series.sports\n\n        if not season_numbering and (air_by_date or sports):\n            date_fmt = '%Y' if season else '%Y.%m.%d'\n            search_name = ep_obj.airdate.strftime(date_fmt)\n        else:\n            search_name = '{type} {number}'.format(\n                type=season,\n                number=ep_obj.season if season else episode_num(\n                    ep_obj.season, ep_obj.episode\n                ),\n            ).strip()\n\n        params = {\n            'category': season or 'Episode',\n            'name': search_name,\n        }\n\n        # Search\n        if ep_obj.series.indexer == INDEXER_TVDBV2:\n            params['tvdb'] = self._get_tvdb_id()\n            searches.append(params)\n        else:\n            name_exceptions = scene_exceptions.get_scene_exceptions(ep_obj.series)\n            name_exceptions.add(ep_obj.series.name)\n            for name in name_exceptions:\n                # Search by name if we don't have tvdb id\n                params['series'] = name\n                searches.append(params)\n\n        # extend air by date searches to include season numbering\n        if air_by_date and not season_numbering:\n            searches.extend(\n                self._search_params(ep_obj, mode, season_numbering=True)\n            )\n\n        return searches\n\n    def _api_call(self, params=None, results_per_page=300, offset=0):\n        \"\"\"Call provider API.\"\"\"\n        parsed_json = {}\n\n        try:\n            server = jsonrpclib.Server(self.urls['base_url'])\n            parsed_json = server.getTorrents(\n                self.api_key,\n                params or {},\n                int(results_per_page),\n                int(offset)\n            )\n            time.sleep(cpu_presets[app.CPU_PRESET])\n        except jsonrpclib.jsonrpc.ProtocolError as error:\n            if error.message[1] == 'Invalid API Key':\n                log.warning('Incorrect authentication credentials.')\n            elif error.message[1] == 'Call Limit Exceeded':\n                log.warning('You have exceeded the limit of'\n                            ' 150 calls per hour.')\n            else:\n                log.error('JSON-RPC protocol error while accessing provider.'\n                          ' Error: {msg!r}', {'msg': error.message[1]})\n\n        except (socket.error, socket.timeout, ValueError) as error:\n            log.warning('Error while accessing provider.'\n                        ' Error: {msg}', {'msg': error})\n        return parsed_json\n\n\nprovider = BTNProvider()\n", "path": "medusa/providers/torrent/json/btn.py"}], "after_files": [{"content": "# coding=utf-8\n\n\"\"\"Provider code for BTN.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport logging\nimport socket\nimport time\n\nimport jsonrpclib\n\nfrom medusa import (\n    app,\n    scene_exceptions,\n    tv,\n)\nfrom medusa.common import cpu_presets\nfrom medusa.helper.common import episode_num\nfrom medusa.indexers.indexer_config import INDEXER_TVDBV2\nfrom medusa.logger.adapters.style import BraceAdapter\nfrom medusa.providers.torrent.torrent_provider import TorrentProvider\n\nfrom six import itervalues\n\nlog = BraceAdapter(logging.getLogger(__name__))\nlog.logger.addHandler(logging.NullHandler())\n\n\n# API docs:\n# https://web.archive.org/web/20160316073644/http://btnapps.net/docs.php\n# https://web.archive.org/web/20160425205926/http://btnapps.net/apigen/class-btnapi.html\n\nclass BTNProvider(TorrentProvider):\n    \"\"\"BTN Torrent provider.\"\"\"\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        super(BTNProvider, self).__init__('BTN')\n\n        # Credentials\n        self.api_key = None\n\n        # URLs\n        self.url = 'https://broadcasthe.net'\n        self.urls = {\n            'base_url': 'https://api.broadcasthe.net',\n        }\n\n        # Proper Strings\n        self.proper_strings = []\n\n        # Miscellaneous Options\n        self.supports_absolute_numbering = True\n\n        # Torrent Stats\n        self.minseed = None\n        self.minleech = None\n\n        # Cache\n        self.cache = tv.Cache(self, min_time=10)  # Only poll BTN every 15 minutes max\n\n    def search(self, search_strings, age=0, ep_obj=None, **kwargs):\n        \"\"\"\n        Search a provider and parse the results.\n\n        :param search_strings: A dict with {mode: search 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._check_auth():\n            return results\n\n        # Search Params\n        search_params = {\n            'age': '<=10800',  # Results from the past 3 hours\n        }\n\n        for mode in search_strings:\n            log.debug('Search mode: {0}', mode)\n\n            if mode != 'RSS':\n                searches = self._search_params(ep_obj, mode)\n            else:\n                searches = [search_params]\n\n            for search_params in searches:\n                if mode != 'RSS':\n                    log.debug('Search string: {search}',\n                              {'search': search_params})\n\n                response = self._api_call(search_params)\n                if not response or response.get('results') == '0':\n                    log.debug('No data returned from provider')\n                    continue\n\n                results += self.parse(response.get('torrents', {}), mode)\n\n        return results\n\n    def parse(self, data, mode):\n        \"\"\"\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        torrent_rows = itervalues(data)\n\n        for row in torrent_rows:\n            title, download_url = self._process_title_and_url(row)\n            if not all([title, download_url]):\n                continue\n\n            seeders = row.get('Seeders', 1)\n            leechers = row.get('Leechers', 0)\n\n            # Filter unseeded torrent\n            if seeders < min(self.minseed, 1):\n                log.debug(\"Discarding torrent because it doesn't meet the\"\n                          \" minimum seeders: {0}. Seeders: {1}\",\n                          title, seeders)\n                continue\n\n            size = row.get('Size') or -1\n\n            pubdate_raw = row.get('Time')\n            pubdate = self.parse_pubdate(pubdate_raw, fromtimestamp=True)\n\n            item = {\n                'title': title,\n                'link': download_url,\n                'size': size,\n                'seeders': seeders,\n                'leechers': leechers,\n                'pubdate': pubdate,\n            }\n            log.debug(\n                'Found result: {title} with {x} seeders'\n                ' and {y} leechers',\n                {'title': title, 'x': seeders, 'y': leechers}\n            )\n\n            items.append(item)\n\n        return items\n\n    def _check_auth(self):\n\n        if not self.api_key:\n            log.warning('Missing API key. Check your settings')\n            return False\n\n        return True\n\n    @staticmethod\n    def _process_title_and_url(parsed_json):\n        \"\"\"Create the title base on properties.\n\n        Try to get the release name, if it doesn't exist make one up\n        from the properties obtained.\n        \"\"\"\n        title = parsed_json.get('ReleaseName')\n        if not title:\n            # If we don't have a release name we need to get creative\n            title = ''\n            if 'Series' in parsed_json:\n                title += parsed_json['Series']\n            if 'GroupName' in parsed_json:\n                title += '.' + parsed_json['GroupName']\n            if 'Resolution' in parsed_json:\n                title += '.' + parsed_json['Resolution']\n            if 'Source' in parsed_json:\n                title += '.' + parsed_json['Source']\n            if 'Codec' in parsed_json:\n                title += '.' + parsed_json['Codec']\n            if title:\n                title = title.replace(' ', '.')\n\n        url = parsed_json.get('DownloadURL').replace('\\\\/', '/')\n        return title, url\n\n    def _search_params(self, ep_obj, mode, season_numbering=None):\n\n        if not ep_obj:\n            return []\n\n        searches = []\n        season = 'Season' if mode == 'Season' else ''\n\n        air_by_date = ep_obj.series.air_by_date\n        sports = ep_obj.series.sports\n\n        if not season_numbering and (air_by_date or sports):\n            date_fmt = '%Y' if season else '%Y.%m.%d'\n            search_name = ep_obj.airdate.strftime(date_fmt)\n        else:\n            search_name = '{type} {number}'.format(\n                type=season,\n                number=ep_obj.season if season else episode_num(\n                    ep_obj.season, ep_obj.episode\n                ),\n            ).strip()\n\n        params = {\n            'category': season or 'Episode',\n            'name': search_name,\n        }\n\n        # Search\n        if ep_obj.series.indexer == INDEXER_TVDBV2:\n            params['tvdb'] = self._get_tvdb_id()\n            searches.append(params)\n        else:\n            name_exceptions = scene_exceptions.get_scene_exceptions(ep_obj.series)\n            name_exceptions.add(ep_obj.series.name)\n            for name in name_exceptions:\n                # Search by name if we don't have tvdb id\n                params['series'] = name\n                searches.append(params)\n\n        # extend air by date searches to include season numbering\n        if air_by_date and not season_numbering:\n            searches.extend(\n                self._search_params(ep_obj, mode, season_numbering=True)\n            )\n\n        return searches\n\n    def _api_call(self, params=None, results_per_page=300, offset=0):\n        \"\"\"Call provider API.\"\"\"\n        parsed_json = {}\n\n        try:\n            server = jsonrpclib.Server(self.urls['base_url'])\n            parsed_json = server.getTorrents(\n                self.api_key,\n                params or {},\n                int(results_per_page),\n                int(offset)\n            )\n            time.sleep(cpu_presets[app.CPU_PRESET])\n        except jsonrpclib.jsonrpc.ProtocolError as error:\n            if error.message == (-32001, 'Invalid API Key'):\n                log.warning('Incorrect authentication credentials.')\n            elif error.message == (-32002, 'Call Limit Exceeded'):\n                log.warning('You have exceeded the limit of'\n                            ' 150 calls per hour.')\n            else:\n                log.error('JSON-RPC protocol error while accessing provider.'\n                          ' Error: {msg!r}', {'msg': error.message})\n\n        except (socket.error, socket.timeout, ValueError) as error:\n            log.warning('Error while accessing provider.'\n                        ' Error: {msg}', {'msg': error})\n        return parsed_json\n\n\nprovider = BTNProvider()\n", "path": "medusa/providers/torrent/json/btn.py"}]}