Skip to content

Commit 0a805a0

Browse files
committed
v0.5.17
2 parents 5a2082e + 579b5a2 commit 0a805a0

File tree

14 files changed

+226
-81
lines changed

14 files changed

+226
-81
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## v0.5.17
4+
Released 10 November 2016
5+
6+
Highlights:
7+
* Added: t411 support
8+
* Fixed: Rutracker login
9+
* Fixed: Deluge empty password
10+
* Fixed: FreeBSD init script
11+
* Improved: Musicbrainz searching
12+
13+
The full list of commits can be found [here](https://github.com/rembo10/headphones/compare/v0.5.16...v0.5.17).
14+
315
## v0.5.16
416
Released 10 June 2016
517

Headphones.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ def main():
8181
help='Prevent browser from launching on startup')
8282
parser.add_argument(
8383
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
84+
parser.add_argument(
85+
'--host', help='Specify a host (default - localhost)')
8486

8587
args = parser.parse_args()
8688

@@ -170,6 +172,13 @@ def main():
170172
else:
171173
http_port = int(headphones.CONFIG.HTTP_PORT)
172174

175+
# Force the http host if neccessary
176+
if args.host:
177+
http_host = args.host
178+
logger.info('Using forced web server host: %s', http_host)
179+
else:
180+
http_host = headphones.CONFIG.HTTP_HOST
181+
173182
# Check if pyOpenSSL is installed. It is required for certificate generation
174183
# and for CherryPy.
175184
if headphones.CONFIG.ENABLE_HTTPS:
@@ -183,7 +192,7 @@ def main():
183192
# Try to start the server. Will exit here is address is already in use.
184193
web_config = {
185194
'http_port': http_port,
186-
'http_host': headphones.CONFIG.HTTP_HOST,
195+
'http_host': http_host,
187196
'http_root': headphones.CONFIG.HTTP_ROOT,
188197
'http_proxy': headphones.CONFIG.HTTP_PROXY,
189198
'enable_https': headphones.CONFIG.ENABLE_HTTPS,

data/interfaces/default/config.html

+17
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,22 @@ <h1 class="clearfix"><i class="fa fa-gear"></i> Settings</h1>
743743
</div>
744744
</div>
745745
</fieldset>
746+
747+
<fieldset>
748+
<div class="row checkbox left">
749+
<input id="use_tquattrecentonze" type="checkbox" class="bigcheck" name="use_tquattrecentonze" value="1" ${config['use_tquattrecentonze']} /><label for="use_tquattrecentonze"><span class="option">t411</span></label>
750+
</div>
751+
<div class="config">
752+
<div class="row">
753+
<label>Username</label>
754+
<input type="text" name="tquattrecentonze_user" value="${config['tquattrecentonze_user']}" size="36">
755+
</div>
756+
<div class="row">
757+
<label>Password</label>
758+
<input type="password" name="tquattrecentonze_password" value="${config['tquattrecentonze_password']}" size="36">
759+
</div>
760+
</div>
761+
</fieldset>
746762

747763
</fieldset>
748764
</td>
@@ -2400,6 +2416,7 @@ <h1 class="clearfix"><i class="fa fa-gear"></i> Settings</h1>
24002416
initConfigCheckbox("#api_enabled");
24012417
initConfigCheckbox("#enable_https");
24022418
initConfigCheckbox("#customauth");
2419+
initConfigCheckbox("#use_tquattrecentonze");
24032420

24042421

24052422
$('#twitterStep1').click(function () {

headphones/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ def __repr__(self):
274274
'TWITTER_PASSWORD': (str, 'Twitter', ''),
275275
'TWITTER_PREFIX': (str, 'Twitter', 'Headphones'),
276276
'TWITTER_USERNAME': (str, 'Twitter', ''),
277+
'TQUATTRECENTONZE': (int, 'tquattrecentonze', 0),
278+
'TQUATTRECENTONZE_PASSWORD': (str, 'tquattrecentonze', ''),
279+
'TQUATTRECENTONZE_USER': (str, 'tquattrecentonze', ''),
277280
'UPDATE_DB_INTERVAL': (int, 'General', 24),
278281
'USENET_RETENTION': (int, 'General', '1500'),
279282
'UTORRENT_HOST': (str, 'uTorrent', ''),

headphones/crier.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pprint
2+
import sys
3+
import threading
4+
import traceback
5+
6+
from headphones import logger
7+
8+
9+
def cry():
10+
"""
11+
Logs thread traces.
12+
"""
13+
tmap = {}
14+
main_thread = None
15+
# get a map of threads by their ID so we can print their names
16+
# during the traceback dump
17+
for t in threading.enumerate():
18+
if t.ident:
19+
tmap[t.ident] = t
20+
else:
21+
main_thread = t
22+
23+
# Loop over each thread's current frame, writing info about it
24+
for tid, frame in sys._current_frames().iteritems():
25+
thread = tmap.get(tid, main_thread)
26+
27+
lines = []
28+
lines.append('%s\n' % thread.getName())
29+
lines.append('========================================\n')
30+
lines += traceback.format_stack(frame)
31+
lines.append('========================================\n')
32+
lines.append('LOCAL VARIABLES:\n')
33+
lines.append('========================================\n')
34+
lines.append(pprint.pformat(frame.f_locals))
35+
lines.append('\n\n')
36+
logger.info("".join(lines))

headphones/deluge.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ def _get_auth():
269269
delugeweb_host = headphones.CONFIG.DELUGE_HOST
270270
delugeweb_cert = headphones.CONFIG.DELUGE_CERT
271271
delugeweb_password = headphones.CONFIG.DELUGE_PASSWORD
272-
logger.debug('Deluge: Using password %s******%s' % (delugeweb_password[0], delugeweb_password[-1]))
272+
if len(delugeweb_password) > 0:
273+
logger.debug('Deluge: Using password %s******%s' % (delugeweb_password[0], delugeweb_password[-1]))
273274

274275
if not delugeweb_host.startswith('http'):
275276
delugeweb_host = 'http://%s' % delugeweb_host

headphones/helpers.py

+1
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ def replace_illegal_chars(string, type="file"):
251251
# Translation table.
252252
# Cover additional special characters processing normalization.
253253
u"'": '', # replace apostrophe with nothing
254+
u"’": '', # replace musicbrainz style apostrophe with nothing
254255
u'&': ' and ', # expand & to ' and '
255256
}
256257

headphones/mb.py

+4-22
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,6 @@ def findArtist(name, limit=1):
9191
artistlist = []
9292
artistResults = None
9393

94-
chars = set('!?*-')
95-
if any((c in chars) for c in name):
96-
name = '"' + name + '"'
97-
9894
criteria = {'artist': name.lower()}
9995

10096
with mb_lock:
@@ -156,16 +152,13 @@ def findRelease(name, limit=1, artist=None):
156152
if not artist and ':' in name:
157153
name, artist = name.rsplit(":", 1)
158154

159-
chars = set('!?*-')
160-
if any((c in chars) for c in name):
161-
name = '"' + name + '"'
162-
if artist and any((c in chars) for c in artist):
163-
artist = '"' + artist + '"'
155+
criteria = {'release': name.lower()}
156+
if artist:
157+
criteria['artist'] = artist.lower()
164158

165159
with mb_lock:
166160
try:
167-
releaseResults = musicbrainzngs.search_releases(query=name, limit=limit, artist=artist)[
168-
'release-list']
161+
releaseResults = musicbrainzngs.search_releases(limit=limit, **criteria)['release-list']
169162
except musicbrainzngs.WebServiceError as e: # need to update exceptions
170163
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
171164
mb_lock.snooze(5)
@@ -234,10 +227,6 @@ def findSeries(name, limit=1):
234227
serieslist = []
235228
seriesResults = None
236229

237-
chars = set('!?*-')
238-
if any((c in chars) for c in name):
239-
name = '"' + name + '"'
240-
241230
criteria = {'series': name.lower()}
242231

243232
with mb_lock:
@@ -759,19 +748,12 @@ def findArtistbyAlbum(name):
759748

760749
def findAlbumID(artist=None, album=None):
761750
results = None
762-
chars = set('!?*-')
763751

764752
try:
765753
if album and artist:
766-
if any((c in chars) for c in album):
767-
album = '"' + album + '"'
768-
if any((c in chars) for c in artist):
769-
artist = '"' + artist + '"'
770754
criteria = {'release': album.lower()}
771755
criteria['artist'] = artist.lower()
772756
else:
773-
if any((c in chars) for c in album):
774-
album = '"' + album + '"'
775757
criteria = {'release': album.lower()}
776758
with mb_lock:
777759
results = musicbrainzngs.search_release_groups(limit=1, **criteria).get(

headphones/rutracker.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,30 @@ def login(self):
4444
logger.info("Attempting to log in to rutracker...")
4545

4646
try:
47-
r = self.session.post(loginpage, data=post_params, timeout=self.timeout)
47+
r = self.session.post(loginpage, data=post_params, timeout=self.timeout, allow_redirects=False)
4848
# try again
49-
if 'bb_data' not in r.cookies.keys():
49+
if not self.has_bb_data_cookie(r):
5050
time.sleep(10)
51-
r = self.session.post(loginpage, data=post_params, timeout=self.timeout)
52-
if r.status_code != 200:
53-
logger.error("rutracker login returned status code %s" % r.status_code)
54-
self.loggedin = False
51+
r = self.session.post(loginpage, data=post_params, timeout=self.timeout, allow_redirects=False)
52+
if self.has_bb_data_cookie(r):
53+
self.loggedin = True
54+
logger.info("Successfully logged in to rutracker")
5555
else:
56-
if 'bb_data' in r.cookies.keys():
57-
self.loggedin = True
58-
logger.info("Successfully logged in to rutracker")
59-
else:
60-
logger.error(
61-
"Could not login to rutracker, credentials maybe incorrect, site is down or too many attempts. Try again later")
62-
self.loggedin = False
56+
logger.error(
57+
"Could not login to rutracker, credentials maybe incorrect, site is down or too many attempts. Try again later")
58+
self.loggedin = False
6359
return self.loggedin
6460
except Exception as e:
6561
logger.error("Unknown error logging in to rutracker: %s" % e)
6662
self.loggedin = False
6763
return self.loggedin
6864

65+
def has_bb_data_cookie(self, response):
66+
if 'bb_data' in response.cookies.keys():
67+
return True
68+
# Rutracker randomly send a 302 redirect code, cookie may be present in response history
69+
return next(('bb_data' in r.cookies.keys() for r in response.history), False)
70+
6971
def searchurl(self, artist, album, year, format):
7072
"""
7173
Return the search url

headphones/searcher.py

+86
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
1515

1616
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
17+
# t411 support added by a1ex, @likeitneverwentaway on github for maintenance
1718

1819
from base64 import b16encode, b32decode
1920
from hashlib import sha1
@@ -24,6 +25,7 @@
2425
import subprocess
2526
import unicodedata
2627
import urlparse
28+
from json import loads
2729

2830
import os
2931
import re
@@ -813,6 +815,19 @@ def send_to_downloader(data, bestqual, album):
813815
torrent_name = helpers.replace_illegal_chars(folder_name) + '.torrent'
814816
download_path = os.path.join(headphones.CONFIG.TORRENTBLACKHOLE_DIR, torrent_name)
815817

818+
# Blackhole for t411
819+
if bestqual[2].lower().startswith("http://api.t411"):
820+
if headphones.CONFIG.MAGNET_LINKS == 2:
821+
try:
822+
url = bestqual[2].split('TOKEN')[0]
823+
token = bestqual[2].split('TOKEN')[1]
824+
data = request.request_content(url, headers={'Authorization': token})
825+
torrent_to_file(download_path, data)
826+
logger.info('Successfully converted magnet to torrent file')
827+
except Exception as e:
828+
logger.error("Error converting magnet link: %s" % str(e))
829+
return
830+
816831
if bestqual[2].lower().startswith("magnet:"):
817832
if headphones.CONFIG.MAGNET_LINKS == 1:
818833
try:
@@ -1763,6 +1778,77 @@ def set_proxy(proxy_url):
17631778
resultlist.append((title, size, url, provider, 'torrent', match))
17641779
except Exception as e:
17651780
logger.exception("Unhandled exception in Mininova Parser")
1781+
# t411
1782+
if headphones.CONFIG.TQUATTRECENTONZE:
1783+
username = headphones.CONFIG.TQUATTRECENTONZE_USER
1784+
password = headphones.CONFIG.TQUATTRECENTONZE_PASSWORD
1785+
API_URL = "http://api.t411.ch"
1786+
AUTH_URL = API_URL + '/auth'
1787+
DL_URL = API_URL + '/torrents/download/'
1788+
provider = "t411"
1789+
t411_term = term.replace(" ", "%20")
1790+
SEARCH_URL = API_URL + '/torrents/search/' + t411_term + "?limit=15&cid=395&subcat=623"
1791+
headers_login = {'username': username, 'password': password}
1792+
1793+
# Requesting content
1794+
logger.info('Parsing results from t411 using search term: %s' % term)
1795+
req = request.request_content(AUTH_URL, method='post', data=headers_login)
1796+
1797+
if len(req.split('"')) == 9:
1798+
token = req.split('"')[7]
1799+
headers_auth = {'Authorization': token}
1800+
logger.info('t411 - User %s logged in' % username)
1801+
else:
1802+
logger.info('t411 - Login error : %s' % req.split('"')[3])
1803+
1804+
# Quality
1805+
if headphones.CONFIG.PREFERRED_QUALITY == 3 or losslessOnly:
1806+
providerurl = fix_url(SEARCH_URL + "&term[16][]=529&term[16][]=1184")
1807+
elif headphones.CONFIG.PREFERRED_QUALITY == 1 or allow_lossless:
1808+
providerurl = fix_url(SEARCH_URL + "&term[16][]=685&term[16][]=527&term[16][]=1070&term[16][]=528&term[16][]=1167&term[16][]=1166&term[16][]=530&term[16][]=529&term[16][]=1184&term[16][]=532&term[16][]=533&term[16][]=1085&term[16][]=534&term[16][]=535&term[16][]=1069&term[16][]=537&term[16][]=538")
1809+
elif headphones.CONFIG.PREFERRED_QUALITY == 0:
1810+
providerurl = fix_url(SEARCH_URL + "&term[16][]=685&term[16][]=527&term[16][]=1070&term[16][]=528&term[16][]=1167&term[16][]=1166&term[16][]=530&term[16][]=532&term[16][]=533&term[16][]=1085&term[16][]=534&term[16][]=535&term[16][]=1069&term[16][]=537&term[16][]=538")
1811+
else:
1812+
providerurl = fix_url(SEARCH_URL)
1813+
1814+
# Tracker search
1815+
req = request.request_content(providerurl, headers=headers_auth)
1816+
req = loads(req)
1817+
total = req['total']
1818+
1819+
# Process feed
1820+
if total == '0':
1821+
logger.info("No results found from t411 for %s" % term)
1822+
else:
1823+
logger.info('Found %s results from t411' % total)
1824+
torrents = req['torrents']
1825+
for torrent in torrents:
1826+
try:
1827+
title = torrent['name']
1828+
if torrent['seeders'] < minimumseeders:
1829+
logger.info('Skipping torrent %s : seeders below minimum set' % title)
1830+
continue
1831+
id = torrent['id']
1832+
size = int(torrent['size'])
1833+
data = request.request_content(DL_URL + id, headers=headers_auth)
1834+
1835+
# Blackhole
1836+
if headphones.CONFIG.TORRENT_DOWNLOADER == 0 and headphones.CONFIG.MAGNET_LINKS == 2:
1837+
url = DL_URL + id + 'TOKEN' + token
1838+
resultlist.append((title, size, url, provider, 'torrent', True))
1839+
1840+
# Build magnet
1841+
else:
1842+
metadata = bdecode(data)
1843+
hashcontents = bencode(metadata['info'])
1844+
digest = sha1(hashcontents).hexdigest()
1845+
trackers = [metadata["announce"]][0]
1846+
url = 'magnet:?xt=urn:btih:%s&tr=%s' % (digest, trackers)
1847+
resultlist.append((title, size, url, provider, 'torrent', True))
1848+
1849+
except Exception as e:
1850+
logger.error("Error converting magnet link: %s" % str(e))
1851+
return
17661852

17671853
# attempt to verify that this isn't a substring result
17681854
# when looking for "Foo - Foo" we don't want "Foobar"

0 commit comments

Comments
 (0)