We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[APP SUBMITTED]: AttributeError: 'NoneType' object has no attribute 'content'

### INFO
**Python Version**: `2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)]`
**Operating System**: `Windows-10-10.0.17134`
**Locale**: `cp1252`
**Branch**: [master](../tree/master)
**Database**: `44.11`
**Commit**: pymedusa/Medusa@f5973b28465246e67465d7d86bdc8aa74d13e659
**Link to Log**: https://gist.github.com/65cbbe28cae113abf840e4ccfacfb9b0
### ERROR
<pre>
2018-08-04 15:19:44 ERROR    SEARCHQUEUE-DAILY-SEARCH :: [f5973b2] DailySearchQueueItem Exception, error: 'NoneType' object has no attribute 'content'
Traceback (most recent call last):
  File "C:\Medusa\Medusa\<a href="../blob/f5973b28465246e67465d7d86bdc8aa74d13e659/medusa/search/queue.py#L289">medusa\search\queue.py</a>", line 289, in run
    self.success = snatch_episode(result)
  File "C:\Medusa\Medusa\<a href="../blob/f5973b28465246e67465d7d86bdc8aa74d13e659/medusa/search/core.py#L135">medusa\search\core.py</a>", line 135, in snatch_episode
    nzb_data = result.provider.download_nzb_for_post(result)
  File "C:\Medusa\Medusa\<a href="../blob/f5973b28465246e67465d7d86bdc8aa74d13e659/medusa/providers/nzb/binsearch.py#L267">medusa\providers\nzb\binsearch.py</a>", line 267, in download_nzb_for_post
    verify=verify, hooks={}, allow_redirects=True).content
AttributeError: 'NoneType' object has no attribute 'content'
</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/nzb/binsearch.py`
Content:
```
1 # coding=utf-8
2 
3 """Provider code for Binsearch provider."""
4 
5 from __future__ import unicode_literals
6 
7 import logging
8 import re
9 from builtins import zip
10 from os.path import join
11 
12 from medusa import tv
13 from medusa.bs4_parser import BS4Parser
14 from medusa.helper.common import convert_size, sanitize_filename
15 from medusa.helpers import download_file
16 from medusa.logger.adapters.style import BraceAdapter
17 from medusa.providers.nzb.nzb_provider import NZBProvider
18 
19 from requests.compat import urljoin
20 
21 log = BraceAdapter(logging.getLogger(__name__))
22 log.logger.addHandler(logging.NullHandler())
23 
24 
25 class BinSearchProvider(NZBProvider):
26     """BinSearch Newznab provider."""
27 
28     size_regex = re.compile(r'size: (\d+\.\d+\xa0\w{2}), parts', re.I)
29     title_regex = re.compile(r'\"([^\"]+)"', re.I)
30     title_reqex_clean = re.compile(r'^[ \d_]+ (.+)')
31     title_regex_rss = re.compile(r'- \"([^\"]+)"', re.I)
32     nzb_check_segment = re.compile(r'<segment bytes="[\d]+"')
33 
34     def __init__(self):
35         """Initialize the class."""
36         super(BinSearchProvider, self).__init__('BinSearch')
37 
38         # Credentials
39         self.public = True
40 
41         # URLs
42         self.url = 'https://www.binsearch.info'
43         self.urls = {
44             'search': urljoin(self.url, 'index.php'),
45             'rss': urljoin(self.url, 'browse.php')
46         }
47 
48         # Proper Strings
49         self.proper_strings = ['PROPER', 'REPACK', 'REAL', 'RERIP']
50 
51         # Miscellaneous Options
52 
53         # Cache
54         self.cache = tv.Cache(self, min_time=10)
55 
56     def search(self, search_strings, **kwargs):
57         """
58         Search a provider and parse the results.
59 
60         :param search_strings: A dict with mode (key) and the search value (value)
61         :returns: A list of search results (structure)
62         """
63         results = []
64         search_params = {
65             'adv_age': '',
66             'xminsize': 20,
67             'max': 250,
68         }
69         groups = [1, 2]
70 
71         for mode in search_strings:
72             log.debug('Search mode: {0}', mode)
73             # https://www.binsearch.info/browse.php?bg=alt.binaries.teevee&server=2
74             for search_string in search_strings[mode]:
75                 search_params['q'] = search_string
76                 for group in groups:
77                     # Try both 'search in the most popular groups' & 'search in the other groups' modes
78                     search_params['server'] = group
79                     if mode != 'RSS':
80                         log.debug('Search string: {search}', {'search': search_string})
81                         search_url = self.urls['search']
82                     else:
83                         search_params = {
84                             'bg': 'alt.binaries.teevee',
85                             'server': 2,
86                             'max': 50,
87                         }
88                         search_url = self.urls['rss']
89                     response = self.session.get(search_url, params=search_params)
90                     if not response:
91                         log.debug('No data returned from provider')
92                         continue
93 
94                     results += self.parse(response.text, mode)
95 
96         return results
97 
98     def parse(self, data, mode):
99         """
100         Parse search results for items.
101 
102         :param data: The raw response from a search
103         :param mode: The current mode used to search, e.g. RSS
104 
105         :return: A list of items found
106         """
107         def process_column_header(td):
108             return td.get_text(strip=True).lower()
109 
110         items = []
111 
112         with BS4Parser(data, 'html5lib') as html:
113 
114             # We need to store the post url, to be used with every result later on.
115             post_url = html.find('form', {'method': 'post'})['action']
116 
117             table = html.find('table', class_='xMenuT')
118             rows = table('tr') if table else []
119             row_offset = 1
120             if not rows or not len(rows) - row_offset:
121                 log.debug('Data returned from provider does not contain any torrents')
122                 return items
123 
124             headers = rows[0]('th')
125             # 0, 1, subject, poster, group, age
126             labels = [process_column_header(header) or idx
127                       for idx, header in enumerate(headers)]
128 
129             # Skip column headers
130             rows = rows[row_offset:]
131             for row in rows:
132                 try:
133                     col = dict(list(zip(labels, row('td'))))
134                     nzb_id_input = col[0 if mode == 'RSS' else 1].find('input')
135                     if not nzb_id_input:
136                         continue
137                     nzb_id = nzb_id_input['name']
138                     # Try and get the the article subject from the weird binsearch format
139                     title = self.clean_title(col['subject'].text, mode)
140 
141                 except AttributeError:
142                     log.debug('Parsing rows, that may not always have usefull info. Skipping to next.')
143                     continue
144                 if not all([title, nzb_id]):
145                     continue
146                 # Obtain the size from the 'description'
147                 size_field = BinSearchProvider.size_regex.search(col['subject'].text)
148                 if size_field:
149                     size_field = size_field.group(1)
150                 size = convert_size(size_field, sep='\xa0') or -1
151                 size = int(size)
152 
153                 download_url = urljoin(self.url, '{post_url}|nzb_id={nzb_id}'.format(post_url=post_url, nzb_id=nzb_id))
154 
155                 # For future use
156                 # detail_url = 'https://www.binsearch.info/?q={0}'.format(title)
157                 human_time = True
158                 date = col['age' if mode != 'RSS' else 'date'].get_text(strip=True).replace('-', ' ')
159                 if mode == 'RSS':
160                     human_time = False
161                 pubdate_raw = date
162                 pubdate = self.parse_pubdate(pubdate_raw, human_time=human_time)
163 
164                 item = {
165                     'title': title,
166                     'link': download_url,
167                     'size': size,
168                     'pubdate': pubdate,
169                 }
170                 if mode != 'RSS':
171                     log.debug('Found result: {0}', title)
172 
173                 items.append(item)
174 
175         return items
176 
177     @staticmethod
178     def clean_title(title, mode):
179         """
180         Clean title field, using a series of regex.
181 
182         RSS search requires different cleaning then the other searches.
183         When adding to this function, make sure you update the tests.
184         """
185         try:
186             if mode == 'RSS':
187                 title = BinSearchProvider.title_regex_rss.search(title).group(1)
188             else:
189                 title = BinSearchProvider.title_regex.search(title).group(1)
190                 if BinSearchProvider.title_reqex_clean.search(title):
191                     title = BinSearchProvider.title_reqex_clean.search(title).group(1)
192             for extension in ('.nfo', '.par2', '.rar', '.zip', '.nzb', '.part'):
193                 # Strip extensions that aren't part of the file name
194                 if title.endswith(extension):
195                     title = title[:len(title) - len(extension)]
196             return title
197         except AttributeError:
198             return None
199 
200     def download_result(self, result):
201         """
202         Download result from provider.
203 
204         This is used when a blackhole is used for sending the nzb file to the nzb client.
205         For now the url and the post data is stored as one string in the db, using a pipe (|) to separate them.
206 
207         :param result: A SearchResult object.
208         :return: The result of the nzb download (True/False).
209         """
210         if not self.login():
211             return False
212 
213         result_name = sanitize_filename(result.name)
214         filename = join(self._get_storage_dir(), result_name + '.' + self.provider_type)
215 
216         if result.url.startswith('http'):
217             self.session.headers.update({
218                 'Referer': '/'.join(result.url.split('/')[:3]) + '/'
219             })
220 
221         log.info('Downloading {result} from {provider} at {url}',
222                  {'result': result.name, 'provider': self.name, 'url': result.url})
223 
224         verify = False if self.public else None
225 
226         url, data = result.url.split('|')
227 
228         data = {
229             data.split('=')[1]: 'on',
230             'action': 'nzb'
231         }
232 
233         if download_file(url, filename, method='POST', data=data, session=self.session,
234                          headers=self.headers, verify=verify):
235 
236             if self._verify_download(filename):
237                 log.info('Saved {result} to {location}',
238                          {'result': result.name, 'location': filename})
239                 return True
240 
241         return False
242 
243     def download_nzb_for_post(self, result):
244         """
245         Download the nzb content, prior to sending it to the nzb download client.
246 
247         :param result: Nzb SearchResult object.
248         :return: The content of the nzb file if successful else None.
249         """
250         if not self.login():
251             return False
252 
253         # For now to separate the url and the post data, where splitting it with a pipe.
254         url, data = result.url.split('|')
255 
256         data = {
257             data.split('=')[1]: 'on',
258             'action': 'nzb'
259         }
260 
261         log.info('Downloading {result} from {provider} at {url} and data {data}',
262                  {'result': result.name, 'provider': self.name, 'url': result.url, 'data': data})
263 
264         verify = False if self.public else None
265 
266         result = self.session.post(url, data=data, headers=self.session.headers,
267                                    verify=verify, hooks={}, allow_redirects=True).content
268 
269         # Validate that the result has the content of a valid nzb.
270         if not BinSearchProvider.nzb_check_segment.search(result):
271             log.info('Result returned from BinSearch was not a valid nzb')
272             return None
273 
274         return result
275 
276     def _get_size(self, item):
277         """
278         Get result size.
279 
280         Overwrite this, as the default _get_size() from nzb_provider isn't working for us.
281         :param item:
282         :return: size in bytes or -1
283         """
284         return item.get('size', -1)
285 
286 
287 provider = BinSearchProvider()
288
```
--- 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/nzb/binsearch.py b/medusa/providers/nzb/binsearch.py
--- a/medusa/providers/nzb/binsearch.py
+++ b/medusa/providers/nzb/binsearch.py
@@ -42,7 +42,7 @@
         self.url = 'https://www.binsearch.info'
         self.urls = {
             'search': urljoin(self.url, 'index.php'),
-            'rss': urljoin(self.url, 'browse.php')
+            'rss': urljoin(self.url, 'browse.php'),
         }
 
         # Proper Strings
@@ -86,8 +86,9 @@
                             'max': 50,
                         }
                         search_url = self.urls['rss']
+
                     response = self.session.get(search_url, params=search_params)
-                    if not response:
+                    if not response or not response.text:
                         log.debug('No data returned from provider')
                         continue
 
@@ -139,10 +140,11 @@
                     title = self.clean_title(col['subject'].text, mode)
 
                 except AttributeError:
-                    log.debug('Parsing rows, that may not always have usefull info. Skipping to next.')
+                    log.debug('Parsing rows, that may not always have useful info. Skipping to next.')
                     continue
                 if not all([title, nzb_id]):
                     continue
+
                 # Obtain the size from the 'description'
                 size_field = BinSearchProvider.size_regex.search(col['subject'].text)
                 if size_field:
@@ -227,7 +229,7 @@
 
         data = {
             data.split('=')[1]: 'on',
-            'action': 'nzb'
+            'action': 'nzb',
         }
 
         if download_file(url, filename, method='POST', data=data, session=self.session,
@@ -255,7 +257,7 @@
 
         data = {
             data.split('=')[1]: 'on',
-            'action': 'nzb'
+            'action': 'nzb',
         }
 
         log.info('Downloading {result} from {provider} at {url} and data {data}',
@@ -263,15 +265,18 @@
 
         verify = False if self.public else None
 
-        result = self.session.post(url, data=data, headers=self.session.headers,
-                                   verify=verify, hooks={}, allow_redirects=True).content
+        response = self.session.post(url, data=data, headers=self.session.headers,
+                                     verify=verify, hooks={}, allow_redirects=True)
+        if not response or not response.content:
+            log.warning('Failed to download the NZB from BinSearch')
+            return None
 
         # Validate that the result has the content of a valid nzb.
-        if not BinSearchProvider.nzb_check_segment.search(result):
-            log.info('Result returned from BinSearch was not a valid nzb')
+        if not BinSearchProvider.nzb_check_segment.search(response.content):
+            log.warning('Result returned from BinSearch was not a valid NZB')
             return None
 
-        return result
+        return response.content
 
     def _get_size(self, item):
         """

{"golden_diff": "diff --git a/medusa/providers/nzb/binsearch.py b/medusa/providers/nzb/binsearch.py\n--- a/medusa/providers/nzb/binsearch.py\n+++ b/medusa/providers/nzb/binsearch.py\n@@ -42,7 +42,7 @@\n         self.url = 'https://www.binsearch.info'\n         self.urls = {\n             'search': urljoin(self.url, 'index.php'),\n-            'rss': urljoin(self.url, 'browse.php')\n+            'rss': urljoin(self.url, 'browse.php'),\n         }\n \n         # Proper Strings\n@@ -86,8 +86,9 @@\n                             'max': 50,\n                         }\n                         search_url = self.urls['rss']\n+\n                     response = self.session.get(search_url, params=search_params)\n-                    if not response:\n+                    if not response or not response.text:\n                         log.debug('No data returned from provider')\n                         continue\n \n@@ -139,10 +140,11 @@\n                     title = self.clean_title(col['subject'].text, mode)\n \n                 except AttributeError:\n-                    log.debug('Parsing rows, that may not always have usefull info. Skipping to next.')\n+                    log.debug('Parsing rows, that may not always have useful info. Skipping to next.')\n                     continue\n                 if not all([title, nzb_id]):\n                     continue\n+\n                 # Obtain the size from the 'description'\n                 size_field = BinSearchProvider.size_regex.search(col['subject'].text)\n                 if size_field:\n@@ -227,7 +229,7 @@\n \n         data = {\n             data.split('=')[1]: 'on',\n-            'action': 'nzb'\n+            'action': 'nzb',\n         }\n \n         if download_file(url, filename, method='POST', data=data, session=self.session,\n@@ -255,7 +257,7 @@\n \n         data = {\n             data.split('=')[1]: 'on',\n-            'action': 'nzb'\n+            'action': 'nzb',\n         }\n \n         log.info('Downloading {result} from {provider} at {url} and data {data}',\n@@ -263,15 +265,18 @@\n \n         verify = False if self.public else None\n \n-        result = self.session.post(url, data=data, headers=self.session.headers,\n-                                   verify=verify, hooks={}, allow_redirects=True).content\n+        response = self.session.post(url, data=data, headers=self.session.headers,\n+                                     verify=verify, hooks={}, allow_redirects=True)\n+        if not response or not response.content:\n+            log.warning('Failed to download the NZB from BinSearch')\n+            return None\n \n         # Validate that the result has the content of a valid nzb.\n-        if not BinSearchProvider.nzb_check_segment.search(result):\n-            log.info('Result returned from BinSearch was not a valid nzb')\n+        if not BinSearchProvider.nzb_check_segment.search(response.content):\n+            log.warning('Result returned from BinSearch was not a valid NZB')\n             return None\n \n-        return result\n+        return response.content\n \n     def _get_size(self, item):\n         \"\"\"\n", "issue": "[APP SUBMITTED]: AttributeError: 'NoneType' object has no attribute 'content'\n\n### INFO\n**Python Version**: `2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)]`\n**Operating System**: `Windows-10-10.0.17134`\n**Locale**: `cp1252`\n**Branch**: [master](../tree/master)\n**Database**: `44.11`\n**Commit**: pymedusa/Medusa@f5973b28465246e67465d7d86bdc8aa74d13e659\n**Link to Log**: https://gist.github.com/65cbbe28cae113abf840e4ccfacfb9b0\n### ERROR\n<pre>\n2018-08-04 15:19:44 ERROR    SEARCHQUEUE-DAILY-SEARCH :: [f5973b2] DailySearchQueueItem Exception, error: 'NoneType' object has no attribute 'content'\nTraceback (most recent call last):\n  File \"C:\\Medusa\\Medusa\\<a href=\"../blob/f5973b28465246e67465d7d86bdc8aa74d13e659/medusa/search/queue.py#L289\">medusa\\search\\queue.py</a>\", line 289, in run\n    self.success = snatch_episode(result)\n  File \"C:\\Medusa\\Medusa\\<a href=\"../blob/f5973b28465246e67465d7d86bdc8aa74d13e659/medusa/search/core.py#L135\">medusa\\search\\core.py</a>\", line 135, in snatch_episode\n    nzb_data = result.provider.download_nzb_for_post(result)\n  File \"C:\\Medusa\\Medusa\\<a href=\"../blob/f5973b28465246e67465d7d86bdc8aa74d13e659/medusa/providers/nzb/binsearch.py#L267\">medusa\\providers\\nzb\\binsearch.py</a>\", line 267, in download_nzb_for_post\n    verify=verify, hooks={}, allow_redirects=True).content\nAttributeError: 'NoneType' object has no attribute 'content'\n</pre>\n---\n_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators\n\n", "before_files": [{"content": "# coding=utf-8\n\n\"\"\"Provider code for Binsearch provider.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport logging\nimport re\nfrom builtins import zip\nfrom os.path import join\n\nfrom medusa import tv\nfrom medusa.bs4_parser import BS4Parser\nfrom medusa.helper.common import convert_size, sanitize_filename\nfrom medusa.helpers import download_file\nfrom medusa.logger.adapters.style import BraceAdapter\nfrom medusa.providers.nzb.nzb_provider import NZBProvider\n\nfrom requests.compat import urljoin\n\nlog = BraceAdapter(logging.getLogger(__name__))\nlog.logger.addHandler(logging.NullHandler())\n\n\nclass BinSearchProvider(NZBProvider):\n    \"\"\"BinSearch Newznab provider.\"\"\"\n\n    size_regex = re.compile(r'size: (\\d+\\.\\d+\\xa0\\w{2}), parts', re.I)\n    title_regex = re.compile(r'\\\"([^\\\"]+)\"', re.I)\n    title_reqex_clean = re.compile(r'^[ \\d_]+ (.+)')\n    title_regex_rss = re.compile(r'- \\\"([^\\\"]+)\"', re.I)\n    nzb_check_segment = re.compile(r'<segment bytes=\"[\\d]+\"')\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        super(BinSearchProvider, self).__init__('BinSearch')\n\n        # Credentials\n        self.public = True\n\n        # URLs\n        self.url = 'https://www.binsearch.info'\n        self.urls = {\n            'search': urljoin(self.url, 'index.php'),\n            'rss': urljoin(self.url, 'browse.php')\n        }\n\n        # Proper Strings\n        self.proper_strings = ['PROPER', 'REPACK', 'REAL', 'RERIP']\n\n        # Miscellaneous Options\n\n        # Cache\n        self.cache = tv.Cache(self, min_time=10)\n\n    def search(self, search_strings, **kwargs):\n        \"\"\"\n        Search a provider and parse the results.\n\n        :param search_strings: A dict with mode (key) and the search value (value)\n        :returns: A list of search results (structure)\n        \"\"\"\n        results = []\n        search_params = {\n            'adv_age': '',\n            'xminsize': 20,\n            'max': 250,\n        }\n        groups = [1, 2]\n\n        for mode in search_strings:\n            log.debug('Search mode: {0}', mode)\n            # https://www.binsearch.info/browse.php?bg=alt.binaries.teevee&server=2\n            for search_string in search_strings[mode]:\n                search_params['q'] = search_string\n                for group in groups:\n                    # Try both 'search in the most popular groups' & 'search in the other groups' modes\n                    search_params['server'] = group\n                    if mode != 'RSS':\n                        log.debug('Search string: {search}', {'search': search_string})\n                        search_url = self.urls['search']\n                    else:\n                        search_params = {\n                            'bg': 'alt.binaries.teevee',\n                            'server': 2,\n                            'max': 50,\n                        }\n                        search_url = self.urls['rss']\n                    response = self.session.get(search_url, params=search_params)\n                    if not response:\n                        log.debug('No data returned from provider')\n                        continue\n\n                    results += self.parse(response.text, 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        def process_column_header(td):\n            return td.get_text(strip=True).lower()\n\n        items = []\n\n        with BS4Parser(data, 'html5lib') as html:\n\n            # We need to store the post url, to be used with every result later on.\n            post_url = html.find('form', {'method': 'post'})['action']\n\n            table = html.find('table', class_='xMenuT')\n            rows = table('tr') if table else []\n            row_offset = 1\n            if not rows or not len(rows) - row_offset:\n                log.debug('Data returned from provider does not contain any torrents')\n                return items\n\n            headers = rows[0]('th')\n            # 0, 1, subject, poster, group, age\n            labels = [process_column_header(header) or idx\n                      for idx, header in enumerate(headers)]\n\n            # Skip column headers\n            rows = rows[row_offset:]\n            for row in rows:\n                try:\n                    col = dict(list(zip(labels, row('td'))))\n                    nzb_id_input = col[0 if mode == 'RSS' else 1].find('input')\n                    if not nzb_id_input:\n                        continue\n                    nzb_id = nzb_id_input['name']\n                    # Try and get the the article subject from the weird binsearch format\n                    title = self.clean_title(col['subject'].text, mode)\n\n                except AttributeError:\n                    log.debug('Parsing rows, that may not always have usefull info. Skipping to next.')\n                    continue\n                if not all([title, nzb_id]):\n                    continue\n                # Obtain the size from the 'description'\n                size_field = BinSearchProvider.size_regex.search(col['subject'].text)\n                if size_field:\n                    size_field = size_field.group(1)\n                size = convert_size(size_field, sep='\\xa0') or -1\n                size = int(size)\n\n                download_url = urljoin(self.url, '{post_url}|nzb_id={nzb_id}'.format(post_url=post_url, nzb_id=nzb_id))\n\n                # For future use\n                # detail_url = 'https://www.binsearch.info/?q={0}'.format(title)\n                human_time = True\n                date = col['age' if mode != 'RSS' else 'date'].get_text(strip=True).replace('-', ' ')\n                if mode == 'RSS':\n                    human_time = False\n                pubdate_raw = date\n                pubdate = self.parse_pubdate(pubdate_raw, human_time=human_time)\n\n                item = {\n                    'title': title,\n                    'link': download_url,\n                    'size': size,\n                    'pubdate': pubdate,\n                }\n                if mode != 'RSS':\n                    log.debug('Found result: {0}', title)\n\n                items.append(item)\n\n        return items\n\n    @staticmethod\n    def clean_title(title, mode):\n        \"\"\"\n        Clean title field, using a series of regex.\n\n        RSS search requires different cleaning then the other searches.\n        When adding to this function, make sure you update the tests.\n        \"\"\"\n        try:\n            if mode == 'RSS':\n                title = BinSearchProvider.title_regex_rss.search(title).group(1)\n            else:\n                title = BinSearchProvider.title_regex.search(title).group(1)\n                if BinSearchProvider.title_reqex_clean.search(title):\n                    title = BinSearchProvider.title_reqex_clean.search(title).group(1)\n            for extension in ('.nfo', '.par2', '.rar', '.zip', '.nzb', '.part'):\n                # Strip extensions that aren't part of the file name\n                if title.endswith(extension):\n                    title = title[:len(title) - len(extension)]\n            return title\n        except AttributeError:\n            return None\n\n    def download_result(self, result):\n        \"\"\"\n        Download result from provider.\n\n        This is used when a blackhole is used for sending the nzb file to the nzb client.\n        For now the url and the post data is stored as one string in the db, using a pipe (|) to separate them.\n\n        :param result: A SearchResult object.\n        :return: The result of the nzb download (True/False).\n        \"\"\"\n        if not self.login():\n            return False\n\n        result_name = sanitize_filename(result.name)\n        filename = join(self._get_storage_dir(), result_name + '.' + self.provider_type)\n\n        if result.url.startswith('http'):\n            self.session.headers.update({\n                'Referer': '/'.join(result.url.split('/')[:3]) + '/'\n            })\n\n        log.info('Downloading {result} from {provider} at {url}',\n                 {'result': result.name, 'provider': self.name, 'url': result.url})\n\n        verify = False if self.public else None\n\n        url, data = result.url.split('|')\n\n        data = {\n            data.split('=')[1]: 'on',\n            'action': 'nzb'\n        }\n\n        if download_file(url, filename, method='POST', data=data, session=self.session,\n                         headers=self.headers, verify=verify):\n\n            if self._verify_download(filename):\n                log.info('Saved {result} to {location}',\n                         {'result': result.name, 'location': filename})\n                return True\n\n        return False\n\n    def download_nzb_for_post(self, result):\n        \"\"\"\n        Download the nzb content, prior to sending it to the nzb download client.\n\n        :param result: Nzb SearchResult object.\n        :return: The content of the nzb file if successful else None.\n        \"\"\"\n        if not self.login():\n            return False\n\n        # For now to separate the url and the post data, where splitting it with a pipe.\n        url, data = result.url.split('|')\n\n        data = {\n            data.split('=')[1]: 'on',\n            'action': 'nzb'\n        }\n\n        log.info('Downloading {result} from {provider} at {url} and data {data}',\n                 {'result': result.name, 'provider': self.name, 'url': result.url, 'data': data})\n\n        verify = False if self.public else None\n\n        result = self.session.post(url, data=data, headers=self.session.headers,\n                                   verify=verify, hooks={}, allow_redirects=True).content\n\n        # Validate that the result has the content of a valid nzb.\n        if not BinSearchProvider.nzb_check_segment.search(result):\n            log.info('Result returned from BinSearch was not a valid nzb')\n            return None\n\n        return result\n\n    def _get_size(self, item):\n        \"\"\"\n        Get result size.\n\n        Overwrite this, as the default _get_size() from nzb_provider isn't working for us.\n        :param item:\n        :return: size in bytes or -1\n        \"\"\"\n        return item.get('size', -1)\n\n\nprovider = BinSearchProvider()\n", "path": "medusa/providers/nzb/binsearch.py"}], "after_files": [{"content": "# coding=utf-8\n\n\"\"\"Provider code for Binsearch provider.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport logging\nimport re\nfrom builtins import zip\nfrom os.path import join\n\nfrom medusa import tv\nfrom medusa.bs4_parser import BS4Parser\nfrom medusa.helper.common import convert_size, sanitize_filename\nfrom medusa.helpers import download_file\nfrom medusa.logger.adapters.style import BraceAdapter\nfrom medusa.providers.nzb.nzb_provider import NZBProvider\n\nfrom requests.compat import urljoin\n\nlog = BraceAdapter(logging.getLogger(__name__))\nlog.logger.addHandler(logging.NullHandler())\n\n\nclass BinSearchProvider(NZBProvider):\n    \"\"\"BinSearch Newznab provider.\"\"\"\n\n    size_regex = re.compile(r'size: (\\d+\\.\\d+\\xa0\\w{2}), parts', re.I)\n    title_regex = re.compile(r'\\\"([^\\\"]+)\"', re.I)\n    title_reqex_clean = re.compile(r'^[ \\d_]+ (.+)')\n    title_regex_rss = re.compile(r'- \\\"([^\\\"]+)\"', re.I)\n    nzb_check_segment = re.compile(r'<segment bytes=\"[\\d]+\"')\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        super(BinSearchProvider, self).__init__('BinSearch')\n\n        # Credentials\n        self.public = True\n\n        # URLs\n        self.url = 'https://www.binsearch.info'\n        self.urls = {\n            'search': urljoin(self.url, 'index.php'),\n            'rss': urljoin(self.url, 'browse.php'),\n        }\n\n        # Proper Strings\n        self.proper_strings = ['PROPER', 'REPACK', 'REAL', 'RERIP']\n\n        # Miscellaneous Options\n\n        # Cache\n        self.cache = tv.Cache(self, min_time=10)\n\n    def search(self, search_strings, **kwargs):\n        \"\"\"\n        Search a provider and parse the results.\n\n        :param search_strings: A dict with mode (key) and the search value (value)\n        :returns: A list of search results (structure)\n        \"\"\"\n        results = []\n        search_params = {\n            'adv_age': '',\n            'xminsize': 20,\n            'max': 250,\n        }\n        groups = [1, 2]\n\n        for mode in search_strings:\n            log.debug('Search mode: {0}', mode)\n            # https://www.binsearch.info/browse.php?bg=alt.binaries.teevee&server=2\n            for search_string in search_strings[mode]:\n                search_params['q'] = search_string\n                for group in groups:\n                    # Try both 'search in the most popular groups' & 'search in the other groups' modes\n                    search_params['server'] = group\n                    if mode != 'RSS':\n                        log.debug('Search string: {search}', {'search': search_string})\n                        search_url = self.urls['search']\n                    else:\n                        search_params = {\n                            'bg': 'alt.binaries.teevee',\n                            'server': 2,\n                            'max': 50,\n                        }\n                        search_url = self.urls['rss']\n\n                    response = self.session.get(search_url, params=search_params)\n                    if not response or not response.text:\n                        log.debug('No data returned from provider')\n                        continue\n\n                    results += self.parse(response.text, 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        def process_column_header(td):\n            return td.get_text(strip=True).lower()\n\n        items = []\n\n        with BS4Parser(data, 'html5lib') as html:\n\n            # We need to store the post url, to be used with every result later on.\n            post_url = html.find('form', {'method': 'post'})['action']\n\n            table = html.find('table', class_='xMenuT')\n            rows = table('tr') if table else []\n            row_offset = 1\n            if not rows or not len(rows) - row_offset:\n                log.debug('Data returned from provider does not contain any torrents')\n                return items\n\n            headers = rows[0]('th')\n            # 0, 1, subject, poster, group, age\n            labels = [process_column_header(header) or idx\n                      for idx, header in enumerate(headers)]\n\n            # Skip column headers\n            rows = rows[row_offset:]\n            for row in rows:\n                try:\n                    col = dict(list(zip(labels, row('td'))))\n                    nzb_id_input = col[0 if mode == 'RSS' else 1].find('input')\n                    if not nzb_id_input:\n                        continue\n                    nzb_id = nzb_id_input['name']\n                    # Try and get the the article subject from the weird binsearch format\n                    title = self.clean_title(col['subject'].text, mode)\n\n                except AttributeError:\n                    log.debug('Parsing rows, that may not always have useful info. Skipping to next.')\n                    continue\n                if not all([title, nzb_id]):\n                    continue\n\n                # Obtain the size from the 'description'\n                size_field = BinSearchProvider.size_regex.search(col['subject'].text)\n                if size_field:\n                    size_field = size_field.group(1)\n                size = convert_size(size_field, sep='\\xa0') or -1\n                size = int(size)\n\n                download_url = urljoin(self.url, '{post_url}|nzb_id={nzb_id}'.format(post_url=post_url, nzb_id=nzb_id))\n\n                # For future use\n                # detail_url = 'https://www.binsearch.info/?q={0}'.format(title)\n                human_time = True\n                date = col['age' if mode != 'RSS' else 'date'].get_text(strip=True).replace('-', ' ')\n                if mode == 'RSS':\n                    human_time = False\n                pubdate_raw = date\n                pubdate = self.parse_pubdate(pubdate_raw, human_time=human_time)\n\n                item = {\n                    'title': title,\n                    'link': download_url,\n                    'size': size,\n                    'pubdate': pubdate,\n                }\n                if mode != 'RSS':\n                    log.debug('Found result: {0}', title)\n\n                items.append(item)\n\n        return items\n\n    @staticmethod\n    def clean_title(title, mode):\n        \"\"\"\n        Clean title field, using a series of regex.\n\n        RSS search requires different cleaning then the other searches.\n        When adding to this function, make sure you update the tests.\n        \"\"\"\n        try:\n            if mode == 'RSS':\n                title = BinSearchProvider.title_regex_rss.search(title).group(1)\n            else:\n                title = BinSearchProvider.title_regex.search(title).group(1)\n                if BinSearchProvider.title_reqex_clean.search(title):\n                    title = BinSearchProvider.title_reqex_clean.search(title).group(1)\n            for extension in ('.nfo', '.par2', '.rar', '.zip', '.nzb', '.part'):\n                # Strip extensions that aren't part of the file name\n                if title.endswith(extension):\n                    title = title[:len(title) - len(extension)]\n            return title\n        except AttributeError:\n            return None\n\n    def download_result(self, result):\n        \"\"\"\n        Download result from provider.\n\n        This is used when a blackhole is used for sending the nzb file to the nzb client.\n        For now the url and the post data is stored as one string in the db, using a pipe (|) to separate them.\n\n        :param result: A SearchResult object.\n        :return: The result of the nzb download (True/False).\n        \"\"\"\n        if not self.login():\n            return False\n\n        result_name = sanitize_filename(result.name)\n        filename = join(self._get_storage_dir(), result_name + '.' + self.provider_type)\n\n        if result.url.startswith('http'):\n            self.session.headers.update({\n                'Referer': '/'.join(result.url.split('/')[:3]) + '/'\n            })\n\n        log.info('Downloading {result} from {provider} at {url}',\n                 {'result': result.name, 'provider': self.name, 'url': result.url})\n\n        verify = False if self.public else None\n\n        url, data = result.url.split('|')\n\n        data = {\n            data.split('=')[1]: 'on',\n            'action': 'nzb',\n        }\n\n        if download_file(url, filename, method='POST', data=data, session=self.session,\n                         headers=self.headers, verify=verify):\n\n            if self._verify_download(filename):\n                log.info('Saved {result} to {location}',\n                         {'result': result.name, 'location': filename})\n                return True\n\n        return False\n\n    def download_nzb_for_post(self, result):\n        \"\"\"\n        Download the nzb content, prior to sending it to the nzb download client.\n\n        :param result: Nzb SearchResult object.\n        :return: The content of the nzb file if successful else None.\n        \"\"\"\n        if not self.login():\n            return False\n\n        # For now to separate the url and the post data, where splitting it with a pipe.\n        url, data = result.url.split('|')\n\n        data = {\n            data.split('=')[1]: 'on',\n            'action': 'nzb',\n        }\n\n        log.info('Downloading {result} from {provider} at {url} and data {data}',\n                 {'result': result.name, 'provider': self.name, 'url': result.url, 'data': data})\n\n        verify = False if self.public else None\n\n        response = self.session.post(url, data=data, headers=self.session.headers,\n                                     verify=verify, hooks={}, allow_redirects=True)\n        if not response or not response.content:\n            log.warning('Failed to download the NZB from BinSearch')\n            return None\n\n        # Validate that the result has the content of a valid nzb.\n        if not BinSearchProvider.nzb_check_segment.search(response.content):\n            log.warning('Result returned from BinSearch was not a valid NZB')\n            return None\n\n        return response.content\n\n    def _get_size(self, item):\n        \"\"\"\n        Get result size.\n\n        Overwrite this, as the default _get_size() from nzb_provider isn't working for us.\n        :param item:\n        :return: size in bytes or -1\n        \"\"\"\n        return item.get('size', -1)\n\n\nprovider = BinSearchProvider()\n", "path": "medusa/providers/nzb/binsearch.py"}]}