Compare commits

...
Author SHA1 Message Date
rembo10 c1edc9cde0 gh-workflow: rename, run on pull request, change python version 2022-01-23 14:34:10 +05:30
rembo10 ce98d0d6ca Ignore line length in flake8 2022-01-23 14:27:11 +05:30
rembo10 1bd7cc2ffd Whitespace fixes 2022-01-23 14:27:05 +05:30
rembo10 ad858576aa Add .flake8 configuration 2022-01-23 14:25:26 +05:30
rembo10 455b7d4940 Remove pylintrc 2022-01-23 14:25:26 +05:30
rembo10 cd14c3f4e2 travis -> github-actions 2022-01-23 14:25:26 +05:30
rembo10 2bacd5a0fc Merge branch 'https' into develop 2022-01-23 14:23:42 +05:30
rembo10 5a559c526d http -> https for album art with small refactor 2022-01-23 14:23:27 +05:30
rembo10 095cee9368 http -> https for internet calls 2022-01-23 14:08:36 +05:30
rembo10 79cb133d1d Remove non-ascii chars in nzb folder name 2022-01-22 08:34:47 +05:30
rembo10 0182be2f27 Merge pull request #3284 from hypsometric/py3
Fixes for Python 3.10
2022-01-22 08:01:58 +05:30
hypsometric b6388f7daa Fixes for Python 3.10
- deluge: b64encode returns bytes, transform into string before
  serializing to JSON.
- deluge: no more need to try different encodings for the torrent
  filename or the base64-encoded content, should all be unicode strings
  with Python 3
- collections.abc: Iterable, Mapping, MutableMapping, MutableSequence
  moved from collections to collections.abc since Python 3.3
- pygazelle: html.parser.HTMLParser().unescape() moved to
  html.unescape() in Python 3.4
2022-01-19 18:21:16 +01:00
37 changed files with 245 additions and 511 deletions
+3
View File
@@ -0,0 +1,3 @@
[flake8]
exclude = .git,data,init-scripts,lib
ignore = E501
+29
View File
@@ -0,0 +1,29 @@
name: check
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 .
- name: Test with nosetests
run: |
nosetests
-25
View File
@@ -1,25 +0,0 @@
# Travis CI configuration file
# http://about.travis-ci.org/docs/
language: python
sudo: false
cache:
pip: true
directories:
- lib
python:
- "2.7"
install:
- pip install -r requirements-dev.txt
script:
- pep8 headphones
- pyflakes headphones
- nosetests
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then coveralls; fi
+15 -8
View File
@@ -28,7 +28,7 @@ def getAlbumArt(albumid):
# CAA # CAA
logger.info("Searching for artwork at CAA") logger.info("Searching for artwork at CAA")
artwork_path = 'http://coverartarchive.org/release-group/%s/front' % albumid artwork_path = 'https://coverartarchive.org/release-group/%s/front' % albumid
artwork = getartwork(artwork_path) artwork = getartwork(artwork_path)
if artwork: if artwork:
logger.info("Artwork found at CAA") logger.info("Artwork found at CAA")
@@ -41,7 +41,7 @@ def getAlbumArt(albumid):
'SELECT ArtistName, AlbumTitle, ReleaseID, AlbumASIN FROM albums WHERE AlbumID=?', 'SELECT ArtistName, AlbumTitle, ReleaseID, AlbumASIN FROM albums WHERE AlbumID=?',
[albumid]).fetchone() [albumid]).fetchone()
if dbalbum['AlbumASIN']: if dbalbum['AlbumASIN']:
artwork_path = 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % dbalbum['AlbumASIN'] artwork_path = 'https://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % dbalbum['AlbumASIN']
artwork = getartwork(artwork_path) artwork = getartwork(artwork_path)
if artwork: if artwork:
logger.info("Artwork found at Amazon") logger.info("Artwork found at Amazon")
@@ -156,12 +156,19 @@ def getartwork(artwork_path):
break break
elif maxwidth and img_width > maxwidth: elif maxwidth and img_width > maxwidth:
# Downsize using proxy service to max width # Downsize using proxy service to max width
artwork_path = '{0}?{1}'.format('http://images.weserv.nl/', urlencode({
'url': artwork_path.replace('http://', ''),
'w': maxwidth,
}))
artwork = bytes() artwork = bytes()
r = request.request_response(artwork_path, timeout=20, stream=True, whitelist_status_code=404) url = "https://images.weserv.nl"
params = {
"url": artwork_path,
"w": maxwidth
}
r = request.request_response(
url,
params=params,
timeout=20,
stream=True,
whitelist_status_code=404
)
if r: if r:
for chunk in r.iter_content(chunk_size=1024): for chunk in r.iter_content(chunk_size=1024):
artwork += chunk artwork += chunk
@@ -182,7 +189,7 @@ def getCachedArt(albumid):
if not artwork_path: if not artwork_path:
return return
if artwork_path.startswith('http://'): if artwork_path.startswith("http"):
artwork = request.request_content(artwork_path, timeout=20) artwork = request.request_content(artwork_path, timeout=20)
if not artwork: if not artwork:
+6 -2
View File
@@ -474,8 +474,12 @@ class Api(object):
# Handle situations where the torrent url contains arguments that are # Handle situations where the torrent url contains arguments that are
# parsed # parsed
if kwargs: if kwargs:
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import urllib.request
import urllib.error
import urllib.parse
url = urllib.parse.quote( url = urllib.parse.quote(
url, safe=":?/=&") + '&' + urllib.parse.urlencode(kwargs) url, safe=":?/=&") + '&' + urllib.parse.urlencode(kwargs)
+15 -10
View File
@@ -388,9 +388,9 @@ class Cache(object):
else: else:
if dbalbum['Type'] != "part of": if dbalbum['Type'] != "part of":
data = lastfm.request_lastfm("album.getinfo", data = lastfm.request_lastfm("album.getinfo",
artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']), artist=helpers.clean_musicbrainz_name(dbalbum['ArtistName']),
album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']), album=helpers.clean_musicbrainz_name(dbalbum['AlbumTitle']),
api_key=LASTFM_API_KEY) api_key=LASTFM_API_KEY)
else: else:
# Series, use actual artist for the release-group # Series, use actual artist for the release-group
@@ -484,7 +484,7 @@ class Cache(object):
self.id + '_fanart_' + '.' + helpers.today() + ext) self.id + '_fanart_' + '.' + helpers.today() + ext)
else: else:
artwork_path = os.path.join(self.path_to_art_cache, artwork_path = os.path.join(self.path_to_art_cache,
self.id + '.' + helpers.today() + ext) self.id + '.' + helpers.today() + ext)
try: try:
with open(artwork_path, 'wb') as f: with open(artwork_path, 'wb') as f:
f.write(artwork) f.write(artwork)
@@ -540,12 +540,17 @@ class Cache(object):
artwork_thumb = None artwork_thumb = None
if 'fanart' in thumb_url: if 'fanart' in thumb_url:
# Create thumb using image resizing service # Create thumb using image resizing service
artwork_path = '{0}?{1}'.format('http://images.weserv.nl/', urlencode({ url = "https://images.weserv.nl"
'url': thumb_url.replace('http://', ''), params = {
'w': 300, "url": thumb_url,
})) "w": 300
artwork_thumb = request.request_content(artwork_path, timeout=20, whitelist_status_code=404) }
artwork_thumb = request.request_content(
url,
params=params,
timeout=20,
whitelist_status_code=404
)
if artwork_thumb: if artwork_thumb:
with open(thumb_path, 'wb') as f: with open(thumb_path, 'wb') as f:
f.write(artwork_thumb) f.write(artwork_thumb)
+3 -1
View File
@@ -18,7 +18,9 @@
####################################### #######################################
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
from .common import USER_AGENT from .common import USER_AGENT
+1 -1
View File
@@ -77,7 +77,7 @@ class Quality:
toReturn = {} toReturn = {}
for x in list(Quality.qualityStrings.keys()): for x in list(Quality.qualityStrings.keys()):
toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status] + " (" + \ toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status] + " (" + \
Quality.qualityStrings[x] + ")" Quality.qualityStrings[x] + ")"
return toReturn return toReturn
@staticmethod @staticmethod
+5 -4
View File
@@ -31,6 +31,7 @@ class path(str):
def __repr__(self): def __repr__(self):
return 'headphones.config.path(%s)' % self return 'headphones.config.path(%s)' % self
_CONFIG_DEFINITIONS = { _CONFIG_DEFINITIONS = {
'ADD_ALBUM_ART': (int, 'General', 0), 'ADD_ALBUM_ART': (int, 'General', 0),
'ADVANCEDENCODER': (str, 'General', ''), 'ADVANCEDENCODER': (str, 'General', ''),
@@ -365,7 +366,7 @@ class Config(object):
my_val = definition_type(self._config[section][ini_key]) my_val = definition_type(self._config[section][ini_key])
# ConfigParser interprets empty strings in the config # ConfigParser interprets empty strings in the config
# literally, so we need to sanitize it. It's not really # literally, so we need to sanitize it. It's not really
# a config upgrade, since a user can at any time put # a config upgrade, since a user can at any time put
# some_key = '' # some_key = ''
if my_val == '""' or my_val == "''": if my_val == '""' or my_val == "''":
my_val = '' my_val = ''
@@ -407,7 +408,7 @@ class Config(object):
""" Return the extra newznab tuples """ """ Return the extra newznab tuples """
extra_newznabs = list( extra_newznabs = list(
zip(*[itertools.islice(self.EXTRA_NEWZNABS, i, None, 3) zip(*[itertools.islice(self.EXTRA_NEWZNABS, i, None, 3)
for i in range(3)]) for i in range(3)])
) )
return extra_newznabs return extra_newznabs
@@ -426,7 +427,7 @@ class Config(object):
""" Return the extra torznab tuples """ """ Return the extra torznab tuples """
extra_torznabs = list( extra_torznabs = list(
zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 4) zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 4)
for i in range(4)]) for i in range(4)])
) )
return extra_torznabs return extra_torznabs
@@ -503,7 +504,7 @@ class Config(object):
if self.EXTRA_TORZNABS: if self.EXTRA_TORZNABS:
extra_torznabs = list( extra_torznabs = list(
zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 3) zip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 3)
for i in range(3)]) for i in range(3)])
) )
new_torznabs = [] new_torznabs = []
for torznab in extra_torznabs: for torznab in extra_torznabs:
-1
View File
@@ -18,7 +18,6 @@
################################### ###################################
import time import time
import sqlite3 import sqlite3
+29 -49
View File
@@ -35,7 +35,6 @@
# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # along with SickRage. If not, see <http://www.gnu.org/licenses/>.
from headphones import logger from headphones import logger
import time import time
@@ -89,7 +88,7 @@ def addTorrent(link, data=None, name=None):
if link.lower().startswith('magnet:'): if link.lower().startswith('magnet:'):
logger.debug('Deluge: Got a magnet link: %s' % _scrubber(link)) logger.debug('Deluge: Got a magnet link: %s' % _scrubber(link))
result = {'type': 'magnet', result = {'type': 'magnet',
'url': link} 'url': link}
retid = _add_torrent_magnet(result) retid = _add_torrent_magnet(result)
elif link.lower().startswith('http://') or link.lower().startswith('https://'): elif link.lower().startswith('http://') or link.lower().startswith('https://'):
@@ -143,8 +142,8 @@ def addTorrent(link, data=None, name=None):
except: except:
logger.debug('Deluge: Sending Deluge torrent with problematic name and some content') logger.debug('Deluge: Sending Deluge torrent with problematic name and some content')
result = {'type': 'torrent', result = {'type': 'torrent',
'name': name, 'name': name,
'content': torrentfile} 'content': torrentfile}
retid = _add_torrent_file(result) retid = _add_torrent_file(result)
# elif link.endswith('.torrent') or data: # elif link.endswith('.torrent') or data:
@@ -175,8 +174,8 @@ def addTorrent(link, data=None, name=None):
except UnicodeDecodeError: except UnicodeDecodeError:
logger.debug('Deluge: Sending Deluge torrent with name %s and content [%s...]' % (name.decode('utf-8'), str(torrentfile)[:40])) logger.debug('Deluge: Sending Deluge torrent with name %s and content [%s...]' % (name.decode('utf-8'), str(torrentfile)[:40]))
result = {'type': 'torrent', result = {'type': 'torrent',
'name': name, 'name': name,
'content': torrentfile} 'content': torrentfile}
retid = _add_torrent_file(result) retid = _add_torrent_file(result)
else: else:
@@ -208,7 +207,7 @@ def getTorrentFolder(result):
], ],
"id": 21}) "id": 21})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['total_done'] = json.loads(response.text)['result']['total_done'] result['total_done'] = json.loads(response.text)['result']['total_done']
tries = 0 tries = 0
@@ -216,7 +215,7 @@ def getTorrentFolder(result):
tries += 1 tries += 1
time.sleep(5) time.sleep(5)
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['total_done'] = json.loads(response.text)['result']['total_done'] result['total_done'] = json.loads(response.text)['result']['total_done']
post_data = json.dumps({"method": "web.get_torrent_status", post_data = json.dumps({"method": "web.get_torrent_status",
@@ -235,7 +234,7 @@ def getTorrentFolder(result):
"id": 23}) "id": 23})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['save_path'] = json.loads(response.text)['result']['save_path'] result['save_path'] = json.loads(response.text)['result']['save_path']
result['name'] = json.loads(response.text)['result']['name'] result['name'] = json.loads(response.text)['result']['name']
@@ -264,7 +263,7 @@ def removeTorrent(torrentid, remove_data=False):
"id": 26}) "id": 26})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
try: try:
state = json.loads(response.text)['result']['state'] state = json.loads(response.text)['result']['state']
@@ -283,10 +282,10 @@ def removeTorrent(torrentid, remove_data=False):
"params": [ "params": [
torrentid, torrentid,
remove_data remove_data
], ],
"id": 25}) "id": 25})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result = json.loads(response.text)['result'] result = json.loads(response.text)['result']
return result return result
@@ -329,12 +328,12 @@ def _get_auth():
"id": 1}) "id": 1})
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except requests.ConnectionError: except requests.ConnectionError:
try: try:
logger.debug('Deluge: Connection failed, let\'s try HTTPS just in case') logger.debug('Deluge: Connection failed, let\'s try HTTPS just in case')
response = requests.post(delugeweb_url.replace('http:', 'https:'), data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url.replace('http:', 'https:'), data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
# If the previous line didn't fail, change delugeweb_url for the rest of this session # If the previous line didn't fail, change delugeweb_url for the rest of this session
logger.error('Deluge: Switching to HTTPS, but certificate won\'t be verified because NO CERTIFICATE WAS CONFIGURED!') logger.error('Deluge: Switching to HTTPS, but certificate won\'t be verified because NO CERTIFICATE WAS CONFIGURED!')
delugeweb_url = delugeweb_url.replace('http:', 'https:') delugeweb_url = delugeweb_url.replace('http:', 'https:')
@@ -359,7 +358,7 @@ def _get_auth():
"id": 10}) "id": 10})
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -376,7 +375,7 @@ def _get_auth():
"id": 11}) "id": 11})
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -395,7 +394,7 @@ def _get_auth():
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -408,7 +407,7 @@ def _get_auth():
try: try:
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
except Exception as e: except Exception as e:
logger.error('Deluge: Authentication failed: %s' % str(e)) logger.error('Deluge: Authentication failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -433,7 +432,7 @@ def _add_torrent_magnet(result):
"params": [result['url'], {}], "params": [result['url'], {}],
"id": 2}) "id": 2})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['hash'] = json.loads(response.text)['result'] result['hash'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text))) logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result'] return json.loads(response.text)['result']
@@ -453,7 +452,7 @@ def _add_torrent_url(result):
"params": [result['url'], {}], "params": [result['url'], {}],
"id": 32}) "id": 32})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['location'] = json.loads(response.text)['result'] result['location'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text))) logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result'] return json.loads(response.text)['result']
@@ -472,32 +471,13 @@ def _add_torrent_file(result):
# content is torrent file contents that needs to be encoded to base64 # content is torrent file contents that needs to be encoded to base64
post_data = json.dumps({"method": "core.add_torrent_file", post_data = json.dumps({"method": "core.add_torrent_file",
"params": [result['name'] + '.torrent', "params": [result['name'] + '.torrent',
b64encode(result['content'].encode('utf8')), {}], b64encode(result['content']).decode(), {}],
"id": 2}) "id": 2})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
result['hash'] = json.loads(response.text)['result'] result['hash'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text))) logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result'] return json.loads(response.text)['result']
except UnicodeDecodeError:
try:
# content is torrent file contents that needs to be encoded to base64
# this time let's try leaving the encoding as is
logger.debug('Deluge: There was a decoding issue, let\'s try again')
post_data = json.dumps({"method": "core.add_torrent_file",
"params": [result['name'].decode('utf8') + '.torrent',
b64encode(result['content']), {}],
"id": 22})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers)
result['hash'] = json.loads(response.text)['result']
logger.debug('Deluge: Response was %s' % str(json.loads(response.text)))
return json.loads(response.text)['result']
except Exception as e:
logger.error('Deluge: Adding torrent file failed after decode: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines()
logger.error('; '.join(formatted_lines))
return False
except Exception as e: except Exception as e:
logger.error('Deluge: Adding torrent file failed: %s' % str(e)) logger.error('Deluge: Adding torrent file failed: %s' % str(e))
formatted_lines = traceback.format_exc().splitlines() formatted_lines = traceback.format_exc().splitlines()
@@ -521,7 +501,7 @@ def setTorrentLabel(result):
"params": [], "params": [],
"id": 3}) "id": 3})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
labels = json.loads(response.text)['result'] labels = json.loads(response.text)['result']
if labels is not None: if labels is not None:
@@ -532,7 +512,7 @@ def setTorrentLabel(result):
"params": [label], "params": [label],
"id": 4}) "id": 4})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
logger.debug('Deluge: %s label added to Deluge' % label) logger.debug('Deluge: %s label added to Deluge' % label)
except Exception as e: except Exception as e:
logger.error('Deluge: Setting label failed: %s' % str(e)) logger.error('Deluge: Setting label failed: %s' % str(e))
@@ -544,7 +524,7 @@ def setTorrentLabel(result):
"params": [result['hash'], label], "params": [result['hash'], label],
"id": 5}) "id": 5})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
logger.debug('Deluge: %s label added to torrent' % label) logger.debug('Deluge: %s label added to torrent' % label)
else: else:
logger.debug('Deluge: Label plugin not detected') logger.debug('Deluge: Label plugin not detected')
@@ -568,12 +548,12 @@ def setSeedRatio(result):
"params": [result['hash'], True], "params": [result['hash'], True],
"id": 5}) "id": 5})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
post_data = json.dumps({"method": "core.set_torrent_stop_ratio", post_data = json.dumps({"method": "core.set_torrent_stop_ratio",
"params": [result['hash'], float(ratio)], "params": [result['hash'], float(ratio)],
"id": 6}) "id": 6})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
return not json.loads(response.text)['error'] return not json.loads(response.text)['error']
@@ -596,7 +576,7 @@ def setTorrentPath(result):
"params": [result['hash'], True], "params": [result['hash'], True],
"id": 7}) "id": 7})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
if headphones.CONFIG.DELUGE_DONE_DIRECTORY: if headphones.CONFIG.DELUGE_DONE_DIRECTORY:
move_to = headphones.CONFIG.DELUGE_DONE_DIRECTORY move_to = headphones.CONFIG.DELUGE_DONE_DIRECTORY
@@ -610,7 +590,7 @@ def setTorrentPath(result):
"params": [result['hash'], move_to], "params": [result['hash'], move_to],
"id": 8}) "id": 8})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
return not json.loads(response.text)['error'] return not json.loads(response.text)['error']
@@ -633,7 +613,7 @@ def setTorrentPause(result):
"params": [[result['hash']]], "params": [[result['hash']]],
"id": 9}) "id": 9})
response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth, response = requests.post(delugeweb_url, data=post_data.encode('utf-8'), cookies=delugeweb_auth,
verify=deluge_verify_cert, headers=headers) verify=deluge_verify_cert, headers=headers)
return not json.loads(response.text)['error'] return not json.loads(response.text)['error']
+8 -3
View File
@@ -32,6 +32,7 @@ import functools
import re import re
import os import os
from mediafile import MediaFile, FileTypeError, UnreadableFileError from mediafile import MediaFile, FileTypeError, UnreadableFileError
from unidecode import unidecode
import headphones import headphones
@@ -41,6 +42,7 @@ RE_FEATURING = re.compile(r"[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.")
RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I) RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I)
RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I) RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I)
def cmp(x, y): def cmp(x, y):
""" """
Replacement for built-in function cmp that was removed in Python 3 Replacement for built-in function cmp that was removed in Python 3
@@ -53,6 +55,7 @@ def cmp(x, y):
""" """
return (x > y) - (x < y) return (x > y) - (x < y)
def multikeysort(items, columns): def multikeysort(items, columns):
comparers = [ comparers = [
((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1))
@@ -231,7 +234,7 @@ def pattern_substitute(pattern, dic, normalize=False):
j = unicodedata.normalize('NFC', j) j = unicodedata.normalize('NFC', j)
except TypeError: except TypeError:
j = unicodedata.normalize('NFC', j = unicodedata.normalize('NFC',
j.decode(headphones.SYS_ENCODING, 'replace')) j.decode(headphones.SYS_ENCODING, 'replace'))
new_dic[i] = j new_dic[i] = j
dic = new_dic dic = new_dic
return pathrender.render(pattern, dic)[0] return pathrender.render(pattern, dic)[0]
@@ -276,7 +279,7 @@ _XLATE_GRAPHICAL_AND_DIACRITICAL = {
'Ǥ': 'G', 'ǥ': 'g', 'DZ': 'DZ', 'Dz': 'Dz', 'dz': 'dz', 'Ǥ': 'G', 'ǥ': 'g', 'DZ': 'DZ', 'Dz': 'Dz', 'dz': 'dz',
'Ȥ': 'Z', 'ȥ': 'z', '': 'No.', 'Ȥ': 'Z', 'ȥ': 'z', '': 'No.',
'º': 'o.', # normalize Nº abbrev (popular w/ classical music), 'º': 'o.', # normalize Nº abbrev (popular w/ classical music),
# this is 'masculine ordering indicator', not degree # this is 'masculine ordering indicator', not degree
} }
_XLATE_SPECIAL = { _XLATE_SPECIAL = {
@@ -881,7 +884,7 @@ def smartMove(src, dest, delete=True):
shutil.copy(source_path, dest_path) shutil.copy(source_path, dest_path)
return True return True
except Exception as e: except Exception as e:
logger.warn(f"Error copying {filename}: {e}") logger.warn(f"Error copying {filename}: {e}")
def walk_directory(basedir, followlinks=True): def walk_directory(basedir, followlinks=True):
@@ -952,6 +955,8 @@ def sab_sanitize_foldername(name):
if not name: if not name:
return return
name = unidecode(name)
lst = [] lst = []
for ch in name.strip(): for ch in name.strip():
if ch in FL_ILLEGAL: if ch in FL_ILLEGAL:
+2 -2
View File
@@ -14,9 +14,9 @@ class HelpersTest(TestCase):
'Symphonęy Nº9': 'Symphoney No.9', 'Symphonęy Nº9': 'Symphoney No.9',
'ÆæßðÞIJij': 'AeaessdThIJıj', 'ÆæßðÞIJij': 'AeaessdThIJıj',
'Obsessió (Cerebral Apoplexy remix)': 'obsessio cerebral ' 'Obsessió (Cerebral Apoplexy remix)': 'obsessio cerebral '
'apoplexy remix', 'apoplexy remix',
'Doktór Hałabała i siedmiu zbojów': 'doktor halabala i siedmiu ' 'Doktór Hałabała i siedmiu zbojów': 'doktor halabala i siedmiu '
'zbojow', 'zbojow',
'Arbetets Söner och Döttrar': 'arbetets soner och dottrar', 'Arbetets Söner och Döttrar': 'arbetets soner och dottrar',
'Björk Guðmundsdóttir': 'bjork gudmundsdottir', 'Björk Guðmundsdóttir': 'bjork gudmundsdottir',
'L\'Arc~en~Ciel': 'larc en ciel', 'L\'Arc~en~Ciel': 'larc en ciel',
+1 -1
View File
@@ -39,7 +39,7 @@ def is_exists(artistid):
if any(artistid in x for x in artistlist): if any(artistid in x for x in artistlist):
logger.info(artistlist[0][ logger.info(artistlist[0][
1] + " is already in the database. Updating 'have tracks', but not artist information") 1] + " is already in the database. Updating 'have tracks', but not artist information")
return True return True
else: else:
return False return False
+1 -1
View File
@@ -22,7 +22,7 @@ from headphones import db, logger, request
TIMEOUT = 60.0 # seconds TIMEOUT = 60.0 # seconds
REQUEST_LIMIT = 1.0 / 5 # seconds REQUEST_LIMIT = 1.0 / 5 # seconds
ENTRY_POINT = "http://ws.audioscrobbler.com/2.0/" ENTRY_POINT = "https://ws.audioscrobbler.com/2.0/"
API_KEY = "395e6ec6bb557382fc41fde867bce66f" API_KEY = "395e6ec6bb557382fc41fde867bce66f"
# Required for API request limit # Required for API request limit
+11 -14
View File
@@ -152,7 +152,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
# track_list.append(track_dict) # track_list.append(track_dict)
check_exist_track = myDB.action("SELECT * FROM have WHERE Location=?", check_exist_track = myDB.action("SELECT * FROM have WHERE Location=?",
[track_path]).fetchone() [track_path]).fetchone()
# Only attempt to match tracks that are new, haven't yet been matched, or metadata has changed. # Only attempt to match tracks that are new, haven't yet been matched, or metadata has changed.
if not check_exist_track: if not check_exist_track:
# This is a new track # This is a new track
@@ -167,7 +167,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
if f_artist and f_artist != check_exist_track['ArtistName']: if f_artist and f_artist != check_exist_track['ArtistName']:
new_artists.append(f_artist) new_artists.append(f_artist)
elif f_artist and f_artist == check_exist_track['ArtistName'] and \ elif f_artist and f_artist == check_exist_track['ArtistName'] and \
check_exist_track['Matched'] != "Ignored": check_exist_track['Matched'] != "Ignored":
new_artists.append(f_artist) new_artists.append(f_artist)
else: else:
continue continue
@@ -191,26 +191,23 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
# Now we start track matching # Now we start track matching
logger.info(f"{new_track_count} new/modified tracks found and added to the database") logger.info(f"{new_track_count} new/modified tracks found and added to the database")
dbtracks = myDB.action( dbtracks = myDB.action(
"SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", "SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
[f"{dir}%"] [f"{dir}%"]
) )
dbtracks_count = myDB.action( dbtracks_count = myDB.action(
"SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", "SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?",
[f"{dir}%"] [f"{dir}%"]
).fetchone()[0] ).fetchone()[0]
logger.info(f"Found {dbtracks_count} new/modified tracks in `{dir}`") logger.info(f"Found {dbtracks_count} new/modified tracks in `{dir}`")
logger.info("Matching tracks to the appropriate releases....") logger.info("Matching tracks to the appropriate releases....")
# Sort the track_list by most vague (e.g. no trackid or releaseid) # Sort the track_list by most vague (e.g. no trackid or releaseid)
# to most specific (both trackid & releaseid) # to most specific (both trackid & releaseid)
# When we insert into the database, the tracks with the most # When we insert into the database, the tracks with the most
# specific information will overwrite the more general matches # specific information will overwrite the more general matches
sorted_dbtracks = helpers.multikeysort(dbtracks, ['ArtistName', 'AlbumTitle']) sorted_dbtracks = helpers.multikeysort(dbtracks, ['ArtistName', 'AlbumTitle'])
# We'll use this to give a % completion, just because the # We'll use this to give a % completion, just because the
# track matching might take a while # track matching might take a while
tracks_completed = 0 tracks_completed = 0
@@ -227,8 +224,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
tracks_completed += 1 tracks_completed += 1
completion_percentage = math.floor( completion_percentage = math.floor(
float(tracks_completed) / dbtracks_count * 1000 float(tracks_completed) / dbtracks_count * 1000
) / 10 ) / 10
if completion_percentage >= (last_completion_percentage + 10): if completion_percentage >= (last_completion_percentage + 10):
logger.info("Track matching is " + str(completion_percentage) + "% complete") logger.info("Track matching is " + str(completion_percentage) + "% complete")
+1 -1
View File
@@ -25,7 +25,7 @@ def getLyrics(artist, song):
"fmt": 'xml' "fmt": 'xml'
} }
url = 'http://lyrics.wikia.com/api.php' url = 'https://lyrics.wikia.com/api.php'
data = request.request_minidom(url, params=params) data = request.request_minidom(url, params=params)
if not data: if not data:
+7 -19
View File
@@ -124,11 +124,7 @@ def findArtist(name, limit=1):
'Cannot determine the best match from an artist/album search. Using top match instead') 'Cannot determine the best match from an artist/album search. Using top match instead')
artistlist.append({ artistlist.append({
# Just need the artist id if the limit is 1 # Just need the artist id if the limit is 1
# 'name': unicode(result['sort-name']),
# 'uniquename': uniquename,
'id': str(result['id']), 'id': str(result['id']),
# 'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
# 'score': int(result['ext:score'])
}) })
else: else:
artistlist.append(artistdict) artistlist.append(artistdict)
@@ -137,7 +133,7 @@ def findArtist(name, limit=1):
'name': str(result['sort-name']), 'name': str(result['sort-name']),
'uniquename': uniquename, 'uniquename': uniquename,
'id': str(result['id']), 'id': str(result['id']),
'url': str("http://musicbrainz.org/artist/" + result['id']), 'url': str("https://musicbrainz.org/artist/" + result['id']),
# probably needs to be changed # probably needs to be changed
'score': int(result['ext:score']) 'score': int(result['ext:score'])
}) })
@@ -208,9 +204,9 @@ def findRelease(name, limit=1, artist=None):
'id': str(result['artist-credit'][0]['artist']['id']), 'id': str(result['artist-credit'][0]['artist']['id']),
'albumid': str(result['id']), 'albumid': str(result['id']),
'url': str( 'url': str(
"http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']), "https://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),
# probably needs to be changed # probably needs to be changed
'albumurl': str("http://musicbrainz.org/release/" + result['id']), 'albumurl': str("https://musicbrainz.org/release/" + result['id']),
# probably needs to be changed # probably needs to be changed
'score': int(result['ext:score']), 'score': int(result['ext:score']),
'date': str(result['date']) if 'date' in result else '', 'date': str(result['date']) if 'date' in result else '',
@@ -248,7 +244,7 @@ def findSeries(name, limit=1):
'name': str(result['name']), 'name': str(result['name']),
'type': str(result['type']), 'type': str(result['type']),
'id': str(result['id']), 'id': str(result['id']),
'url': str("http://musicbrainz.org/series/" + result['id']), 'url': str("https://musicbrainz.org/series/" + result['id']),
# probably needs to be changed # probably needs to be changed
'score': int(result['ext:score']) 'score': int(result['ext:score'])
}) })
@@ -295,7 +291,7 @@ def getArtist(artistid, extrasonly=False):
releasegroups.append({ releasegroups.append({
'title': str(rg['title']), 'title': str(rg['title']),
'id': str(rg['id']), 'id': str(rg['id']),
'url': "http://musicbrainz.org/release-group/" + rg['id'], 'url': "https://musicbrainz.org/release-group/" + rg['id'],
'type': str(rg['type']) 'type': str(rg['type'])
}) })
@@ -356,7 +352,7 @@ def getArtist(artistid, extrasonly=False):
releasegroups.append({ releasegroups.append({
'title': str(rg['title']), 'title': str(rg['title']),
'id': str(rg['id']), 'id': str(rg['id']),
'url': "http://musicbrainz.org/release-group/" + rg['id'], 'url': "https://musicbrainz.org/release-group/" + rg['id'],
'type': str(rg_type) 'type': str(rg_type)
}) })
artist_dict['releasegroups'] = releasegroups artist_dict['releasegroups'] = releasegroups
@@ -691,7 +687,7 @@ def getTracksFromRelease(release):
'number': totalTracks, 'number': totalTracks,
'title': track_title, 'title': track_title,
'id': str(track['recording']['id']), 'id': str(track['recording']['id']),
'url': "http://musicbrainz.org/track/" + track['recording']['id'], 'url': "https://musicbrainz.org/track/" + track['recording']['id'],
'duration': int(track['length']) if 'length' in track else 0 'duration': int(track['length']) if 'length' in track else 0
}) })
totalTracks += 1 totalTracks += 1
@@ -733,15 +729,7 @@ def findArtistbyAlbum(name):
for releaseGroup in results: for releaseGroup in results:
newArtist = releaseGroup['artist-credit'][0]['artist'] newArtist = releaseGroup['artist-credit'][0]['artist']
# Only need the artist ID if we're doing an artist+album lookup # Only need the artist ID if we're doing an artist+album lookup
# if 'disambiguation' in newArtist:
# uniquename = unicode(newArtist['sort-name'] + " (" + newArtist['disambiguation'] + ")")
# else:
# uniquename = unicode(newArtist['sort-name'])
# artist_dict['name'] = unicode(newArtist['sort-name'])
# artist_dict['uniquename'] = uniquename
artist_dict['id'] = str(newArtist['id']) artist_dict['id'] = str(newArtist['id'])
# artist_dict['url'] = u'http://musicbrainz.org/artist/' + newArtist['id']
# artist_dict['score'] = int(releaseGroup['ext:score'])
return artist_dict return artist_dict
+1
View File
@@ -38,6 +38,7 @@ class MetadataDict(dict):
lowercase) in member variable self._lower. If case-sensitive lookup lowercase) in member variable self._lower. If case-sensitive lookup
fails, another case-insensitive attempt is made. fails, another case-insensitive attempt is made.
""" """
def __setitem__(self, key, value): def __setitem__(self, key, value):
super(MetadataDict, self).__setitem__(key, value) super(MetadataDict, self).__setitem__(key, value)
self._lower.__setitem__(key.lower(), value) self._lower.__setitem__(key.lower(), value)
+21 -17
View File
@@ -1,5 +1,7 @@
from urllib.parse import urlencode, quote_plus from urllib.parse import urlencode, quote_plus
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import subprocess import subprocess
import json import json
from email.mime.text import MIMEText from email.mime.text import MIMEText
@@ -7,7 +9,9 @@ import smtplib
import email.utils import email.utils
from http.client import HTTPSConnection from http.client import HTTPSConnection
from urllib.parse import parse_qsl from urllib.parse import parse_qsl
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import requests as requests import requests as requests
import os.path import os.path
@@ -17,7 +21,7 @@ import cherrypy
import headphones import headphones
import gntp.notifier import gntp.notifier
#import oauth2 as oauth #import oauth2 as oauth
import twitter import twitter
class GROWL(object): class GROWL(object):
@@ -246,7 +250,7 @@ class XBMC(object):
if version < 12: # Eden if version < 12: # Eden
notification = header + "," + message + "," + time + \ notification = header + "," + message + "," + time + \
"," + albumartpath "," + albumartpath
notifycommand = {'command': 'ExecBuiltIn', notifycommand = {'command': 'ExecBuiltIn',
'parameter': 'Notification(' + 'parameter': 'Notification(' +
notification + ')'} notification + ')'}
@@ -440,7 +444,7 @@ class Plex(object):
if version < 12: # Eden if version < 12: # Eden
notification = header + "," + message + "," + time + \ notification = header + "," + message + "," + time + \
"," + albumartpath "," + albumartpath
notifycommand = {'command': 'ExecBuiltIn', notifycommand = {'command': 'ExecBuiltIn',
'parameter': 'Notification(' + 'parameter': 'Notification(' +
notification + ')'} notification + ')'}
@@ -604,12 +608,12 @@ class JOIN(object):
self.url += '&deviceId={deviceid}' self.url += '&deviceId={deviceid}'
response = urllib.request.urlopen(self.url.format(apikey=self.apikey, response = urllib.request.urlopen(self.url.format(apikey=self.apikey,
title=quote_plus(event), title=quote_plus(event),
text=quote_plus( text=quote_plus(
message.encode( message.encode(
"utf-8")), "utf-8")),
icon=icon, icon=icon,
deviceid=self.deviceid)) deviceid=self.deviceid))
if response: if response:
logger.info("Join notifications sent.") logger.info("Join notifications sent.")
@@ -733,8 +737,8 @@ class TwitterNotifier(object):
def notify_download(self, title): def notify_download(self, title):
if headphones.CONFIG.TWITTER_ENABLED: if headphones.CONFIG.TWITTER_ENABLED:
self._notifyTwitter(common.notifyStrings[ self._notifyTwitter(common.notifyStrings[
common.NOTIFY_DOWNLOAD] + ': ' + common.NOTIFY_DOWNLOAD] + ': ' +
title + ' at ' + helpers.now()) title + ' at ' + helpers.now())
def test_notify(self): def test_notify(self):
return self._notifyTwitter( return self._notifyTwitter(
@@ -798,7 +802,7 @@ class TwitterNotifier(object):
if resp['status'] != '200': if resp['status'] != '200':
logger.info('The request for a token with did not succeed: ' + str( logger.info('The request for a token with did not succeed: ' + str(
resp['status']), resp['status']),
logger.ERROR) logger.ERROR)
return False return False
else: else:
logger.info('Your Twitter Access Token key: %s' % access_token[ logger.info('Your Twitter Access Token key: %s' % access_token[
@@ -920,7 +924,7 @@ class BOXCAR(object):
def notify(self, title, message, rgid=None): def notify(self, title, message, rgid=None):
try: try:
if rgid: if rgid:
message += '<br></br><a href="http://musicbrainz.org/' \ message += '<br></br><a href="https://musicbrainz.org/' \
'release-group/%s">MusicBrainz</a>' % rgid 'release-group/%s">MusicBrainz</a>' % rgid
data = urllib.parse.urlencode({ data = urllib.parse.urlencode({
@@ -1019,8 +1023,8 @@ class TELEGRAM(object):
# MusicBrainz link # MusicBrainz link
if rgid: if rgid:
message += '\n\n <a href="http://musicbrainz.org/' \ message += '\n\n <a href="https://musicbrainz.org/' \
'release-group/%s">MusicBrainz</a>' % rgid 'release-group/%s">MusicBrainz</a>' % rgid
# Send image # Send image
response = None response = None
+3
View File
@@ -38,6 +38,7 @@ __author__ = "Andrzej Ciarkowski <andrzej.ciarkowski@gmail.com>"
class _PatternElement(object): class _PatternElement(object):
'''ABC for hierarchy of path name renderer pattern elements.''' '''ABC for hierarchy of path name renderer pattern elements.'''
def render(self, replacement): def render(self, replacement):
# type: (Mapping[str,str]) -> str # type: (Mapping[str,str]) -> str
'''Format this _PatternElement into string using provided substitution dictionary.''' '''Format this _PatternElement into string using provided substitution dictionary.'''
@@ -55,6 +56,7 @@ class _Generator(_PatternElement):
class _Replacement(_Generator): class _Replacement(_Generator):
'''Replacement variable, eg. $title.''' '''Replacement variable, eg. $title.'''
def __init__(self, pattern): def __init__(self, pattern):
# type: (str) # type: (str)
self._pattern = pattern self._pattern = pattern
@@ -81,6 +83,7 @@ class _Replacement(_Generator):
class _LiteralText(_PatternElement): class _LiteralText(_PatternElement):
'''Just a plain piece of text to be rendered "as is".''' '''Just a plain piece of text to be rendered "as is".'''
def __init__(self, text): def __init__(self, text):
# type: (str) # type: (str)
self._text = text self._text = text
+7 -5
View File
@@ -342,6 +342,7 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
logger.warn(f"Could not identify {albumpath}. It may not be the intended album") logger.warn(f"Could not identify {albumpath}. It may not be the intended album")
markAsUnprocessed(albumid, albumpath, keep_original_folder) markAsUnprocessed(albumid, albumpath, keep_original_folder)
def markAsUnprocessed(albumid, albumpath, keep_original_folder=False): def markAsUnprocessed(albumid, albumpath, keep_original_folder=False):
myDB = db.DBConnection() myDB = db.DBConnection()
myDB.action( myDB.action(
@@ -419,7 +420,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
logger.debug("Write check exact error: %s", e) logger.debug("Write check exact error: %s", e)
logger.error( logger.error(
f"`{downloaded_track}` is not writable. This is required " f"`{downloaded_track}` is not writable. This is required "
"for some post processing steps. Not continuing." "for some post processing steps. Not continuing."
) )
if new_folder: if new_folder:
shutil.rmtree(new_folder) shutil.rmtree(new_folder)
@@ -595,7 +596,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
logger.info("Twitter notifications temporarily disabled") logger.info("Twitter notifications temporarily disabled")
#logger.info("Sending Twitter notification") #logger.info("Sending Twitter notification")
#twitter = notifiers.TwitterNotifier() #twitter = notifiers.TwitterNotifier()
#twitter.notify_download(pushmessage) # twitter.notify_download(pushmessage)
if headphones.CONFIG.OSX_NOTIFY_ENABLED: if headphones.CONFIG.OSX_NOTIFY_ENABLED:
from headphones import cache from headphones import cache
@@ -786,7 +787,7 @@ def moveFiles(albumpath, release, metadata_dict):
newfolder = temp_folder + '[%i]' % i newfolder = temp_folder + '[%i]' % i
lossless_destination_path = os.path.normpath( lossless_destination_path = os.path.normpath(
os.path.join( os.path.join(
headphones.CONFIG.LOSSLESS_DESTINATION_DIR, headphones.CONFIG.LOSSLESS_DESTINATION_DIR,
newfolder newfolder
) )
) )
@@ -828,7 +829,7 @@ def moveFiles(albumpath, release, metadata_dict):
newfolder = temp_folder + '[%i]' % i newfolder = temp_folder + '[%i]' % i
lossy_destination_path = os.path.normpath( lossy_destination_path = os.path.normpath(
os.path.join( os.path.join(
headphones.CONFIG.DESTINATION_DIR, headphones.CONFIG.DESTINATION_DIR,
newfolder newfolder
) )
) )
@@ -877,7 +878,7 @@ def moveFiles(albumpath, release, metadata_dict):
os.remove(file_to_move) os.remove(file_to_move)
except Exception as e: except Exception as e:
logger.error( logger.error(
f"Error deleting `{file_to_move}` from source directory") f"Error deleting `{file_to_move}` from source directory")
else: else:
logger.error( logger.error(
f"Error copying `{file_to_move}`. " f"Error copying `{file_to_move}`. "
@@ -1135,6 +1136,7 @@ def updateFilePermissions(albumpaths):
logger.error(f"Could not change permissions for `{full_path}`") logger.error(f"Could not change permissions for `{full_path}`")
continue continue
def renameUnprocessedFolder(path, tag): def renameUnprocessedFolder(path, tag):
""" """
Rename a unprocessed folder to a new unique name to indicate a certain Rename a unprocessed folder to a new unique name to indicate a certain
+7 -3
View File
@@ -13,8 +13,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import urllib.request
import urllib.error
import urllib.parse
import http.cookiejar import http.cookiejar
import json import json
import time import time
@@ -81,7 +85,7 @@ class qbittorrentclient(object):
logger.debug('Error getting SID. qBittorrent responded with error: ' + str(err.reason)) logger.debug('Error getting SID. qBittorrent responded with error: ' + str(err.reason))
return return
for cookie in self.cookiejar: for cookie in self.cookiejar:
logger.debug('login cookie: ' + cookie.name + ', value: ' + cookie.value) logger.debug('login cookie: ' + cookie.name + ', value: ' + cookie.value)
return return
def _command(self, command, args=None, content_type=None, files=None): def _command(self, command, args=None, content_type=None, files=None):
+1 -1
View File
@@ -220,7 +220,7 @@ def server_message(response):
# First attempt is to 'read' the response as HTML # First attempt is to 'read' the response as HTML
if response.headers.get("content-type") and \ if response.headers.get("content-type") and \
"text/html" in response.headers.get("content-type"): "text/html" in response.headers.get("content-type"):
try: try:
soup = BeautifulSoup(response.content, "html.parser") soup = BeautifulSoup(response.content, "html.parser")
except Exception: except Exception:
+8 -6
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import time import time
from urllib.parse import urlparse from urllib.parse import urlparse
import re import re
@@ -19,13 +21,13 @@ class Rutracker(object):
self.timeout = 60 self.timeout = 60
self.loggedin = False self.loggedin = False
self.maxsize = 0 self.maxsize = 0
self.search_referer = 'http://rutracker.org/forum/tracker.php' self.search_referer = 'https://rutracker.org/forum/tracker.php'
def logged_in(self): def logged_in(self):
return self.loggedin return self.loggedin
def still_logged_in(self, html): def still_logged_in(self, html):
if not html or "action=\"http://rutracker.org/forum/login.php\">" in html: if not html or "action=\"https://rutracker.org/forum/login.php\">" in html:
return False return False
else: else:
return True return True
@@ -35,7 +37,7 @@ class Rutracker(object):
Logs in user Logs in user
""" """
loginpage = 'http://rutracker.org/forum/login.php' loginpage = 'https://rutracker.org/forum/login.php'
post_params = { post_params = {
'login_username': headphones.CONFIG.RUTRACKER_USER, 'login_username': headphones.CONFIG.RUTRACKER_USER,
'login_password': headphones.CONFIG.RUTRACKER_PASSWORD, 'login_password': headphones.CONFIG.RUTRACKER_PASSWORD,
@@ -159,7 +161,7 @@ class Rutracker(object):
# Torrent topic page # Torrent topic page
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])[ torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])[
't'] 't']
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id topicurl = 'https://rutracker.org/forum/viewtopic.php?t=' + torrent_id
rulist.append((title, size, topicurl, 'rutracker.org', 'torrent', True)) rulist.append((title, size, topicurl, 'rutracker.org', 'torrent', True))
else: else:
logger.info("%s is larger than the maxsize or has too little seeders for this category, " logger.info("%s is larger than the maxsize or has too little seeders for this category, "
@@ -179,7 +181,7 @@ class Rutracker(object):
return the .torrent data return the .torrent data
""" """
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t'] torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
downloadurl = 'http://rutracker.org/forum/dl.php?t=' + torrent_id downloadurl = 'https://rutracker.org/forum/dl.php?t=' + torrent_id
cookie = {'bb_dl': torrent_id} cookie = {'bb_dl': torrent_id}
try: try:
headers = {'Referer': url} headers = {'Referer': url}
+1 -1
View File
@@ -30,7 +30,7 @@ def sab_api_call(request_type=None, params={}, **kwargs):
if headphones.CONFIG.SAB_HOST.endswith('/'): if headphones.CONFIG.SAB_HOST.endswith('/'):
headphones.CONFIG.SAB_HOST = headphones.CONFIG.SAB_HOST[ headphones.CONFIG.SAB_HOST = headphones.CONFIG.SAB_HOST[
0:len(headphones.CONFIG.SAB_HOST) - 1] 0:len(headphones.CONFIG.SAB_HOST) - 1]
url = headphones.CONFIG.SAB_HOST + "/" + "api?" url = headphones.CONFIG.SAB_HOST + "/" + "api?"
+24 -22
View File
@@ -19,7 +19,9 @@ from base64 import b16encode, b32decode
from hashlib import sha1 from hashlib import sha1
import string import string
import random import random
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import datetime import datetime
import subprocess import subprocess
import unicodedata import unicodedata
@@ -40,7 +42,7 @@ from bencode import decode as bdecode
# Magnet to torrent services, for Black hole. Stolen from CouchPotato. # Magnet to torrent services, for Black hole. Stolen from CouchPotato.
TORRENT_TO_MAGNET_SERVICES = [ TORRENT_TO_MAGNET_SERVICES = [
'http://itorrents.org/torrent/%s.torrent', 'https://itorrents.org/torrent/%s.torrent',
'https://cache.torrentgalaxy.org/get/%s', 'https://cache.torrentgalaxy.org/get/%s',
'https://www.seedpeer.me/torrent/%s' 'https://www.seedpeer.me/torrent/%s'
] ]
@@ -611,7 +613,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None,
provider = newznab_host[0] provider = newznab_host[0]
# Add a little mod for kere.ws # Add a little mod for kere.ws
if newznab_host[0] == "http://kere.ws": if newznab_host[0] == "https://kere.ws":
if categories == "3040": if categories == "3040":
categories = categories + ",4070" categories = categories + ",4070"
elif categories == "3040,3010": elif categories == "3040,3010":
@@ -682,7 +684,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None,
} }
data = request.request_feed( data = request.request_feed(
url='http://beta.nzbs.org/api', url='https://beta.nzbs.org/api',
params=params, headers=headers, params=params, headers=headers,
timeout=5 timeout=5
) )
@@ -731,7 +733,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None,
} }
data = request.request_json( data = request.request_json(
url='http://api.omgwtfnzbs.me/json/', url='https://api.omgwtfnzbs.me/json/',
params=params, headers=headers params=params, headers=headers
) )
@@ -1081,7 +1083,7 @@ def send_to_downloader(data, bestqual, album):
logger.info("Twitter notifications temporarily disabled") logger.info("Twitter notifications temporarily disabled")
#logger.info("Sending Twitter notification") #logger.info("Sending Twitter notification")
#twitter = notifiers.TwitterNotifier() #twitter = notifiers.TwitterNotifier()
#twitter.notify_snatch(name) # twitter.notify_snatch(name)
if headphones.CONFIG.NMA_ENABLED and headphones.CONFIG.NMA_ONSNATCH: if headphones.CONFIG.NMA_ENABLED and headphones.CONFIG.NMA_ONSNATCH:
logger.info("Sending NMA notification") logger.info("Sending NMA notification")
nma = notifiers.NMA() nma = notifiers.NMA()
@@ -1261,7 +1263,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
def set_proxy(proxy_url): def set_proxy(proxy_url):
if not proxy_url.startswith('http'): if not proxy_url.startswith('http'):
proxy_url = 'http://' + proxy_url proxy_url = 'https://' + proxy_url
if proxy_url.endswith('/'): if proxy_url.endswith('/'):
proxy_url = proxy_url[:-1] proxy_url = proxy_url[:-1]
@@ -1467,7 +1469,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
if headphones.CONFIG.ORPHEUS: if headphones.CONFIG.ORPHEUS:
provider = "Orpheus.network" provider = "Orpheus.network"
providerurl = "http://orpheus.network/" providerurl = "https://orpheus.network/"
bitrate = None bitrate = None
bitrate_string = bitrate bitrate_string = bitrate
@@ -1503,8 +1505,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
try: try:
logger.info("Attempting to log in to Orpheus.network...") logger.info("Attempting to log in to Orpheus.network...")
orpheusobj = gazelleapi.GazelleAPI(headphones.CONFIG.ORPHEUS_USERNAME, orpheusobj = gazelleapi.GazelleAPI(headphones.CONFIG.ORPHEUS_USERNAME,
headphones.CONFIG.ORPHEUS_PASSWORD, headphones.CONFIG.ORPHEUS_PASSWORD,
headphones.CONFIG.ORPHEUS_URL) headphones.CONFIG.ORPHEUS_URL)
orpheusobj._login() orpheusobj._login()
except Exception as e: except Exception as e:
orpheusobj = None orpheusobj = None
@@ -1550,13 +1552,13 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
if usersearchterm: if usersearchterm:
all_torrents.extend( all_torrents.extend(
orpheusobj.search_torrents(searchstr=usersearchterm, format=search_format, orpheusobj.search_torrents(searchstr=usersearchterm, format=search_format,
encoding=bitrate_string, releasetype=album_type)['results']) encoding=bitrate_string, releasetype=album_type)['results'])
else: else:
all_torrents.extend(orpheusobj.search_torrents(artistname=semi_clean_artist_term, all_torrents.extend(orpheusobj.search_torrents(artistname=semi_clean_artist_term,
groupname=semi_clean_album_term, groupname=semi_clean_album_term,
format=search_format, format=search_format,
encoding=bitrate_string, encoding=bitrate_string,
releasetype=album_type)['results']) releasetype=album_type)['results'])
# filter on format, size, and num seeders # filter on format, size, and num seeders
logger.info("Filtering torrents by format, maximum size, and minimum seeders...") logger.info("Filtering torrents by format, maximum size, and minimum seeders...")
@@ -1634,8 +1636,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
try: try:
logger.info("Attempting to log in to Redacted...") logger.info("Attempting to log in to Redacted...")
redobj = gazelleapi.GazelleAPI(headphones.CONFIG.REDACTED_USERNAME, redobj = gazelleapi.GazelleAPI(headphones.CONFIG.REDACTED_USERNAME,
headphones.CONFIG.REDACTED_PASSWORD, headphones.CONFIG.REDACTED_PASSWORD,
providerurl) providerurl)
redobj._login() redobj._login()
except Exception as e: except Exception as e:
redobj = None redobj = None
@@ -1649,12 +1651,12 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
if usersearchterm: if usersearchterm:
all_torrents.extend( all_torrents.extend(
redobj.search_torrents(searchstr=usersearchterm, format=search_format, redobj.search_torrents(searchstr=usersearchterm, format=search_format,
encoding=bitrate_string)['results']) encoding=bitrate_string)['results'])
else: else:
all_torrents.extend(redobj.search_torrents(artistname=semi_clean_artist_term, all_torrents.extend(redobj.search_torrents(artistname=semi_clean_artist_term,
groupname=semi_clean_album_term, groupname=semi_clean_album_term,
format=search_format, format=search_format,
encoding=bitrate_string)['results']) encoding=bitrate_string)['results'])
# filter on format, size, and num seeders # filter on format, size, and num seeders
logger.info("Filtering torrents by format, maximum size, and minimum seeders...") logger.info("Filtering torrents by format, maximum size, and minimum seeders...")
@@ -1791,7 +1793,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2243.2 Safari/537.36'} 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2243.2 Safari/537.36'}
provider_url = fix_url(headphones.CONFIG.OLDPIRATEBAY_URL) + \ provider_url = fix_url(headphones.CONFIG.OLDPIRATEBAY_URL) + \
"/search.php?" + urllib.parse.urlencode({"q": tpb_term, "iht": 6}) "/search.php?" + urllib.parse.urlencode({"q": tpb_term, "iht": 6})
data = request.request_soup(url=provider_url, headers=headers) data = request.request_soup(url=provider_url, headers=headers)
+4 -4
View File
@@ -183,15 +183,15 @@ def torrentAction(method, arguments):
if _session_id is not None: if _session_id is not None:
headers = {'x-transmission-session-id': _session_id} headers = {'x-transmission-session-id': _session_id}
response = request.request_response(host, method="POST", response = request.request_response(host, method="POST",
data=data_json, headers=headers, auth=auth, data=data_json, headers=headers, auth=auth,
whitelist_status_code=[200, 401, 409]) whitelist_status_code=[200, 401, 409])
else: else:
response = request.request_response(host, auth=auth, response = request.request_response(host, auth=auth,
whitelist_status_code=[401, 409]) whitelist_status_code=[401, 409])
if response.status_code == 401: if response.status_code == 401:
if auth: if auth:
logger.error("Username and/or password not accepted by " logger.error("Username and/or password not accepted by "
"Transmission") "Transmission")
else: else:
logger.error("Transmission authorization required") logger.error("Transmission authorization required")
return return
+6 -2
View File
@@ -13,11 +13,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import json import json
import time import time
from collections import namedtuple from collections import namedtuple
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import urllib.parse import urllib.parse
import http.cookiejar import http.cookiejar
+12 -8
View File
@@ -19,12 +19,16 @@ from operator import itemgetter
import threading import threading
import secrets import secrets
import random import random
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import json import json
import time import time
import sys import sys
from html import escape as html_escape from html import escape as html_escape
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import os import os
import re import re
@@ -785,7 +789,7 @@ class WebInterface(object):
track_title = tracks['TrackTitle'] track_title = tracks['TrackTitle']
if tracks['CleanName'] != original_clean: if tracks['CleanName'] != original_clean:
artist_id_check = myDB.action('SELECT ArtistID FROM tracks WHERE CleanName = ?', artist_id_check = myDB.action('SELECT ArtistID FROM tracks WHERE CleanName = ?',
[tracks['CleanName']]).fetchone() [tracks['CleanName']]).fetchone()
if artist_id_check: if artist_id_check:
artist_id = artist_id_check[0] artist_id = artist_id_check[0]
myDB.action( myDB.action(
@@ -1074,7 +1078,7 @@ class WebInterface(object):
data[counter] = album['AlbumTitle'] data[counter] = album['AlbumTitle']
counter += 1 counter += 1
return data return data
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@@ -1677,16 +1681,16 @@ class WebInterface(object):
# Return the Cover Art Archive urls if not found on last.fm # Return the Cover Art Archive urls if not found on last.fm
if AlbumID and not image_dict: if AlbumID and not image_dict:
image_url = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID image_url = "https://coverartarchive.org/release/%s/front-500.jpg" % AlbumID
thumb_url = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID thumb_url = "https://coverartarchive.org/release/%s/front-250.jpg" % AlbumID
image_dict = {'artwork': image_url, 'thumbnail': thumb_url} image_dict = {'artwork': image_url, 'thumbnail': thumb_url}
elif AlbumID and (not image_dict['artwork'] or not image_dict['thumbnail']): elif AlbumID and (not image_dict['artwork'] or not image_dict['thumbnail']):
if not image_dict['artwork']: if not image_dict['artwork']:
image_dict[ image_dict[
'artwork'] = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID 'artwork'] = "https://coverartarchive.org/release/%s/front-500.jpg" % AlbumID
if not image_dict['thumbnail']: if not image_dict['thumbnail']:
image_dict[ image_dict[
'thumbnail'] = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID 'thumbnail'] = "https://coverartarchive.org/release/%s/front-250.jpg" % AlbumID
return image_dict return image_dict
+1 -1
View File
@@ -1,4 +1,4 @@
from collections import Iterable, Mapping from collections.abc import Iterable, Mapping
from uuid import uuid4 from uuid import uuid4
import six import six
+1 -1
View File
@@ -1,6 +1,6 @@
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from collections import MutableMapping from collections.abc import MutableMapping
from threading import RLock from threading import RLock
from datetime import datetime from datetime import datetime
from logging import getLogger from logging import getLogger
+1 -1
View File
@@ -32,7 +32,7 @@ __all__ = ["APEv2", "APEv2File", "Open", "delete"]
import sys import sys
import struct import struct
from collections import MutableSequence from collections.abc import MutableSequence
from ._compat import (cBytesIO, PY3, text_type, PY2, reraise, swap_to_string, from ._compat import (cBytesIO, PY3, text_type, PY2, reraise, swap_to_string,
xrange) xrange)
+3 -3
View File
@@ -5,7 +5,7 @@
# #
# Loosely based on the API implementation from 'whatbetter', by Zachary Denton # Loosely based on the API implementation from 'whatbetter', by Zachary Denton
# See https://github.com/zacharydenton/whatbetter # See https://github.com/zacharydenton/whatbetter
from html.parser import HTMLParser import html
import sys import sys
import json import json
@@ -219,10 +219,10 @@ class GazelleAPI(object):
else: else:
artist = Artist(id, self) artist = Artist(id, self)
if name: if name:
artist.name = HTMLParser().unescape(name) artist.name = html.unescape(name)
elif name: elif name:
artist = Artist(-1, self) artist = Artist(-1, self)
artist.name = HTMLParser().unescape(name) artist.name = html.unescape(name)
else: else:
raise Exception("You must specify either an ID or a Name to get an artist.") raise Exception("You must specify either an ID or a Name to get an artist.")
+3 -3
View File
@@ -1,4 +1,4 @@
from html.parser import HTMLParser import html
class InvalidArtistException(Exception): class InvalidArtistException(Exception):
pass pass
@@ -29,7 +29,7 @@ class Artist(object):
if self.id > 0: if self.id > 0:
response = self.parent_api.request(action='artist', id=self.id) response = self.parent_api.request(action='artist', id=self.id)
elif self.name: elif self.name:
self.name = HTMLParser().unescape(self.name) self.name = html.unescape(self.name)
try: try:
response = self.parent_api.request(action='artist', artistname=self.name) response = self.parent_api.request(action='artist', artistname=self.name)
except Exception: except Exception:
@@ -47,7 +47,7 @@ class Artist(object):
self.id = artist_json_response['id'] self.id = artist_json_response['id']
self.parent_api.cached_artists[self.id] = self self.parent_api.cached_artists[self.id] = self
self.name = HTMLParser().unescape(artist_json_response['name']) self.name = html.unescape(artist_json_response['name'])
self.notifications_enabled = artist_json_response['notificationsEnabled'] self.notifications_enabled = artist_json_response['notificationsEnabled']
self.has_bookmarked = artist_json_response['hasBookmarked'] self.has_bookmarked = artist_json_response['hasBookmarked']
self.image = artist_json_response['image'] self.image = artist_json_response['image']
-284
View File
@@ -1,284 +0,0 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook=sys.path.insert(0, 'lib/')
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
#I0011 an inline option disables a pylint message or a messages category
#R0801 a set of similar lines has been detected among multiple file
#W0142 a function or method is called using *args or **kwargs to dispatch argument
# W1201(logging-not-lazy)
# C0330(bad-continuation)
# E1205(logging-too-many-args)
disable=I0011,R0801,W0142,C0103,C0111,C0301,C0302,C0304,C0321,C1001,E0101,E0203,E0602,E1101,E1123,R0201,R0401,R0911,R0912,R0914,R0915,R0923,W0102,W0109,W0120,W0141,W0201,W0212,W0231,W0232,W0233,W0301,W0311,W0401,W0403,W0404,W0511,W0601,W0602,W0603,W0611,W0612,W0613,W0621,W0622,W0633,W0702,W0703,W1401,W1201,C0330
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
#output-format=parseable
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the massage information. See doc for all details
#msg-template=
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=150
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent,objects
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=20
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=100
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
+4 -7
View File
@@ -1,8 +1,5 @@
coverage==4.0.3 coverage==6.2
coveralls==1.1 coveralls==3.3.1
mock==1.3.0 mock==4.0.3
nose==1.3.7 nose==1.3.7
pep8==1.7.0 flake8==4.0.1
pyflakes==1.1.0
pylint==1.3.1 # pylint 1.4 does not run under python 2.6
pyOpenSSL==0.15.1