Compare commits

...
42 Commits
Author SHA1 Message Date
rembo10 b71c0d8b93 v0.5.18 2016-12-01 15:59:35 +00:00
rembo10 0ecfe499fc Updated changelog for v0.5.18 2016-12-01 15:59:11 +00:00
rembo10 68d85841ea Merge remote-tracking branch 'DenzoNL/master' into develop 2016-12-01 15:52:46 +00:00
rembo10 1ffcc177a4 Merge remote-tracking branch 'MIKAd3v/develop' into develop 2016-12-01 15:51:01 +00:00
rembo10 2230c5bc0c Merge remote-tracking branch 'wfriesen/develop' into develop 2016-12-01 15:49:58 +00:00
Denzo 352b009e91 # Removed slash from ajax.php 2016-11-29 20:54:20 +01:00
Denzo b6b33e1b1e + Added support for PassTheHeadphones.me
% Removed hardcoding for What.CD in pygazelle API, making it easy to add other Gazelle-based trackers

+ Added URL parameter for What.CD and PTH, in case they come back under a new domain name
2016-11-29 20:49:52 +01:00
massiliattak dff1ac2e41 Update T411 new domain ( t411.li ) 2016-11-20 20:15:37 +01:00
William Friesen 92601e0eb8 Use HTML escaping for password fields
Fixes rembo10/headphones#2474
2016-11-19 12:15:22 +11:00
rembo10 6e82423947 Merge remote-tracking branch 'Betree/search_with_partial_dates' into develop 2016-11-10 21:07:04 +00:00
rembo10 0a805a01f3 v0.5.17 2016-11-10 20:58:00 +00:00
rembo10 579b5a2f2f Updated changelog for v0.5.17 2016-11-10 20:56:51 +00:00
Benjamin Piouffle c77f43be5e Accept partial release date for search 2016-10-25 19:21:16 +11:00
AdeHub f10d1834dd Merge pull request #2749 from likeitneverwentaway/t411
Fixed lossless only for t411
2016-10-20 22:51:42 +13:00
AdeHub b4c7ed2227 Merge pull request #2756 from Betree/fix_rutracker_login
Fix Rutracker loggin by looking for the cookie in request history
2016-10-20 22:51:23 +13:00
Benjamin Piouffle 0086980b1b Fix Rutracker loggin by looking for the cookie in request history 2016-10-19 12:41:46 +11:00
likeitneverwentaway 89e1793978 Fixed lossless only for t411 2016-10-15 14:39:55 +02:00
rembo10 5a598fe611 Merge remote-tracking branch 'etomm/develop' into develop 2016-10-15 10:13:20 +01:00
etomm b58f670694 Handling Deluge empty password 2016-10-15 10:08:51 +02:00
AdeHub 5103436d17 Merge pull request #2745 from likeitneverwentaway/t411
New provider : add t411 support
2016-10-15 18:58:41 +13:00
Ade c9a1796e56 Allow hyphen in mb search
Fixes #2499
2016-10-15 09:59:11 +13:00
Ade 7428b56dd5 Remove mb apostrophe
Fixes #2737
2016-10-09 15:56:25 +13:00
likeitneverwentaway 42294d261d Fix typo, import loads from json 2016-10-08 01:55:55 +02:00
likeitneverwentaway 31a99a908a Added credit 2016-10-08 01:08:54 +02:00
likeitneverwentaway aa503f0fe8 Update config.py to add t411 support 2016-10-08 01:01:39 +02:00
likeitneverwentaway d3d48d27df Update webserve.py to add t411 support 2016-10-08 01:00:49 +02:00
likeitneverwentaway bd8eb74662 Update config.html to add t411 support 2016-10-08 00:50:48 +02:00
likeitneverwentaway 99680e9a0f Update searcher.py to add t411 support 2016-10-08 00:46:12 +02:00
rembo10 0cc58bdb46 Merge remote-tracking branch 'clowninasack/freebsd-init-script' into develop 2016-09-28 22:37:17 +01:00
rembo10 09435b8786 Merge remote-tracking branch 'kainz/add_crier' into develop 2016-09-28 22:37:14 +01:00
rembo10 5d2753a34e Merge remote-tracking branch 'VoidVolker/develop' into develop 2016-09-28 22:37:01 +01:00
Ade 8eb3ece2f9 rutracker login fix
stop redirecting to the same login page
2016-09-17 20:27:01 +12:00
clowninasack d3584a5ae9 Fixed FreeBSD init script. 2016-08-01 10:30:10 -07:00
Bryon Roche c0c64621e6 Add a thread-dumping handler
Add a 'crier' handler to dump the thread stacks of running threads to
the log, for easier debugging.
2016-07-14 05:34:36 -07:00
Bryon Roche 323d364572 Added missing support method for current FauxSocket for pypy 2016-07-14 05:27:07 -07:00
VoidVolker 9a681a9422 [=] HP_HOST description 2016-07-08 14:44:01 +03:00
VoidVolker a7a709df51 [+] HP_HOST option 2016-07-08 14:42:19 +03:00
VoidVolker 3f23b5553a [+] --host command line option 2016-07-08 14:39:36 +03:00
rembo10 5a2082ede5 v0.5.16 2016-06-10 00:23:22 +01:00
rembo10 e29f3a0a9a Updated changelog for v0.5.16 2016-06-10 00:22:59 +01:00
rembo10 f4bebf6a41 Merge remote-tracking branch 'SlashingWolf/patch-1' into develop 2016-06-10 00:20:43 +01:00
Gabriel 564df8136c Update to searcher.py to work with waffles.ch
Changed the url used for searching waffles from https://www.waffles.ch/browse.php to https://waffles.ch/browse.php.
2016-06-09 16:19:49 -05:00
15 changed files with 437 additions and 106 deletions
+33
View File
@@ -1,5 +1,38 @@
# Changelog
## v0.5.18
Released 01 December 2016
Highlights:
* Added: PassTheHeadphones support
* Fixed: Special characters in password fields breaking on config page
* Improved: Updated t411 url
The full list of commits can be found [here](https://github.com/rembo10/headphones/compare/v0.5.17...v0.5.18).
## v0.5.17
Released 10 November 2016
Highlights:
* Added: t411 support
* Fixed: Rutracker login
* Fixed: Deluge empty password
* Fixed: FreeBSD init script
* Improved: Musicbrainz searching
The full list of commits can be found [here](https://github.com/rembo10/headphones/compare/v0.5.16...v0.5.17).
## v0.5.16
Released 10 June 2016
Hotfix update
Highlights:
* Fixed: Waffles url
The full list of commits can be found [here](https://github.com/rembo10/headphones/compare/v0.5.15...v0.5.16).
## v0.5.15
Released 07 June 2016
+10 -1
View File
@@ -81,6 +81,8 @@ def main():
help='Prevent browser from launching on startup')
parser.add_argument(
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
parser.add_argument(
'--host', help='Specify a host (default - localhost)')
args = parser.parse_args()
@@ -170,6 +172,13 @@ def main():
else:
http_port = int(headphones.CONFIG.HTTP_PORT)
# Force the http host if neccessary
if args.host:
http_host = args.host
logger.info('Using forced web server host: %s', http_host)
else:
http_host = headphones.CONFIG.HTTP_HOST
# Check if pyOpenSSL is installed. It is required for certificate generation
# and for CherryPy.
if headphones.CONFIG.ENABLE_HTTPS:
@@ -183,7 +192,7 @@ def main():
# Try to start the server. Will exit here is address is already in use.
web_config = {
'http_port': http_port,
'http_host': headphones.CONFIG.HTTP_HOST,
'http_host': http_host,
'http_root': headphones.CONFIG.HTTP_ROOT,
'http_proxy': headphones.CONFIG.HTTP_PROXY,
'enable_https': headphones.CONFIG.ENABLE_HTTPS,
+62 -16
View File
@@ -56,7 +56,7 @@
<label title="Password for web server authentication. Leave empty to disable.">
HTTP Password
</label>
<input type="password" name="http_password" value="${config['http_password']}" size="30">
<input type="password" name="http_password" value="${config['http_password'] | h}" size="30">
</div>
<div class="row checkbox">
<input type="checkbox" name="launch_browser" value="1" ${config['launch_browser']} />
@@ -173,7 +173,7 @@
<label title="SABnzbd password. Leave empty if not applicable.">
SABnzbd Password
</label>
<input type="password" name="sab_password" value="${config['sab_password']}" size="20">
<input type="password" name="sab_password" value="${config['sab_password'] | h}" size="20">
</div>
<div class="row">
<label title="SABnzbd API key. Can be found in SABnzbd settings.">
@@ -207,7 +207,7 @@
<label title="NZBGet password. Leave empty if not applicable">
NZBget Password
</label>
<input type="password" name="nzbget_password" value="${config['nzbget_password']}" size="20">
<input type="password" name="nzbget_password" value="${config['nzbget_password'] | h}" size="20">
</div>
<div class="row">
<label title="Name of NZBget category to add downloads to.">
@@ -359,7 +359,7 @@
</div>
<div class="row">
<label>Transmission Password</label>
<input type="password" name="transmission_password" value="${config['transmission_password']}" size="30">
<input type="password" name="transmission_password" value="${config['transmission_password'] | h}" size="30">
</div>
<div class="row">
<small>Note: With Transmission, you can specify a different download directory for downloads sent from Headphones.
@@ -379,7 +379,7 @@
</div>
<div class="row">
<label>uTorrent Password</label>
<input type="password" name="utorrent_password" value="${config['utorrent_password']}" size="30">
<input type="password" name="utorrent_password" value="${config['utorrent_password'] | h}" size="30">
</div>
<div class="row">
<label>uTorrent Label</label>
@@ -400,7 +400,7 @@
</div>
<div class="row">
<label>Deluge Password</label>
<input type="password" name="deluge_password" value="${config['deluge_password']}" size="30">
<input type="password" name="deluge_password" value="${config['deluge_password'] | h}" size="30">
</div>
<div class="row">
<small>Note: With Deluge, you can specify a different download directory for downloads sent from Headphones.
@@ -466,7 +466,7 @@
</div>
<div class="row">
<label>Password</label>
<input class="hppass" type="password" value="${config['hppass']}" size="20">
<input class="hppass" type="password" value="${config['hppass'] | h}" size="20">
</div>
<div class="row">
<a href="https://headphones.codeshy.com/vip" id="vipserver" target="_blank">Don't have an account? Sign up!</a>
@@ -622,7 +622,7 @@
</div>
<div class="row">
<label>Password</label>
<input type="password" name="rutracker_password" value="${config['rutracker_password']}" size="36">
<input type="password" name="rutracker_password" value="${config['rutracker_password'] | h}" size="36">
</div>
<div class="row">
<label>Seed Ratio</label>
@@ -642,7 +642,11 @@
</div>
<div class="row">
<label>Password</label>
<input type="password" name="whatcd_password" value="${config['whatcd_password']}" size="36">
<input type="password" name="whatcd_password" value="${config['whatcd_password'] | h}" size="36">
</div>
<div class="row">
<label>URL</label>
<input type="text" name="whatcd_url" value="${config['whatcd_url']}" size="36">
</div>
<div class="row">
<label>Seed Ratio</label>
@@ -651,6 +655,30 @@
</div>
</fieldset>
<fieldset>
<div class="row checkbox left">
<input id="use_pth" type="checkbox" class="bigcheck" name="use_pth" value="1" ${config['use_pth']} /><label for="use_pth"><span class="option">PassTheHeadphones.me</span></label>
</div>
<div class="config">
<div class="row">
<label>Username</label>
<input type="text" name="pth_username" value="${config['pth_username']}" size="36">
</div>
<div class="row">
<label>Password</label>
<input type="password" name="pth_password" value="${config['pth_password']}" size="36">
</div>
<div class="row">
<label>URL</label>
<input type="text" name="pth_url" value="${config['pth_url']}" size="36">
</div>
<div class="row">
<label>Seed Ratio</label>
<input type="text" class="override-float" name="pth_ratio" value="${config['pth_ratio']}" size="10" title="Stop seeding when ratio met, 0 = unlimited. Scheduled job will remove torrent when post processed and finished seeding">
</div>
</div>
</fieldset>
<fieldset>
<div class="row checkbox left">
<input id="use_strike" type="checkbox" class="bigcheck" name="use_strike" value="1" ${config['use_strike']} /><label for="use_strike"><span class="option">Strike Search</span></label>
@@ -743,6 +771,22 @@
</div>
</div>
</fieldset>
<fieldset>
<div class="row checkbox left">
<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>
</div>
<div class="config">
<div class="row">
<label>Username</label>
<input type="text" name="tquattrecentonze_user" value="${config['tquattrecentonze_user']}" size="36">
</div>
<div class="row">
<label>Password</label>
<input type="password" name="tquattrecentonze_password" value="${config['tquattrecentonze_password'] | h}" size="36">
</div>
</div>
</fieldset>
</fieldset>
</td>
@@ -964,7 +1008,7 @@
<label>SMTP User</label><input type="text" name="email_smtp_user" value="${config['email_smtp_user']}" size="254">
</div>
<div class="row">
<label>SMTP Password</label><input type="password" name="email_smtp_password" value="${config['email_smtp_password']}" size="50">
<label>SMTP Password</label><input type="password" name="email_smtp_password" value="${config['email_smtp_password'] | h}" size="50">
</div>
<div class="row checkbox">
<input type="text" class="override-float" name="email_smtp_port" value="${config['email_smtp_port']}" size="4"><label>SMTP Port</label>
@@ -990,7 +1034,7 @@
<label>Growl Host:Port</label><input type="text" name="growl_host" value="${config['growl_host']}" size="30">
</div>
<div class="row">
<label>Growl Password</label><input type="password" name="growl_password" value="${config['growl_password']}" size="30">
<label>Growl Password</label><input type="password" name="growl_password" value="${config['growl_password'] | h}" size="30">
</div>
<div class="row checkbox">
<input type="checkbox" name="growl_onsnatch" value="1" ${config['growl_onsnatch']} /><label>Notify on snatch?</label>
@@ -1012,7 +1056,7 @@
<label>Username</label><input type="text" name="xbmc_username" value="${config['xbmc_username']}" size="30">
</div>
<div class="row">
<label>Password</label><input type="password" name="xbmc_password" value="${config['xbmc_password']}" size="30">
<label>Password</label><input type="password" name="xbmc_password" value="${config['xbmc_password'] | h}" size="30">
</div>
<div class="checkbox row">
<input type="checkbox" name="xbmc_update" value="1" ${config['xbmc_update']} /><label>Update Library</label>
@@ -1126,7 +1170,7 @@
<small>Username of your Plex client API (blank for none)</small>
</div>
<div class="row">
<label>Plex Password</label><input type="password" name="plex_password" value="${config['plex_password']}" size="30">
<label>Plex Password</label><input type="password" name="plex_password" value="${config['plex_password'] | h}" size="30">
<small>Password of your Plex client API (blank for none)</small>
</div>
<div class="row">
@@ -1226,7 +1270,7 @@
<label>Subsonic Username</label><input type="text" name="subsonic_username" value="${config['subsonic_username']}" size="30">
</div>
<div class="row">
<label>Subsonic Password</label><input type="password" name="subsonic_password" value="${config['subsonic_password']}" size="30">
<label>Subsonic Password</label><input type="password" name="subsonic_password" value="${config['subsonic_password'] | h}" size="30">
</div>
</div>
</fieldset>
@@ -1626,7 +1670,7 @@
<label>Username</label><input type="text" class="customuser" name="customuser" value="${config['customuser']}" size="20">
</div>
<div class="row">
<label>Password</label><input type="password" class="custompass" name="custompass" value="${config['custompass']}" size="15"><br>
<label>Password</label><input type="password" class="custompass" name="custompass" value="${config['custompass'] | h}" size="15"><br>
</div>
</div>
<div class="row">
@@ -1639,7 +1683,7 @@
<label>Username</label><input type="text" class="hpuser" name="hpuser" value="${config['hpuser']}" size="20">
</div>
<div class="row">
<label>Password</label><input type="password" class="hppass" name="hppass" value="${config['hppass']}" size="20"><br>
<label>Password</label><input type="password" class="hppass" name="hppass" value="${config['hppass'] | h}" size="20"><br>
<a href="https://headphones.codeshy.com/vip" id="vipserver" target="_blank">Get an Account!</a>
</div>
</div>
@@ -2396,10 +2440,12 @@
initConfigCheckbox("#use_waffles");
initConfigCheckbox("#use_rutracker");
initConfigCheckbox("#use_whatcd");
initConfigCheckbox("#use_pth");
initConfigCheckbox("#use_strike");
initConfigCheckbox("#api_enabled");
initConfigCheckbox("#enable_https");
initConfigCheckbox("#customauth");
initConfigCheckbox("#use_tquattrecentonze");
$('#twitterStep1').click(function () {
+9
View File
@@ -274,6 +274,9 @@ _CONFIG_DEFINITIONS = {
'TWITTER_PASSWORD': (str, 'Twitter', ''),
'TWITTER_PREFIX': (str, 'Twitter', 'Headphones'),
'TWITTER_USERNAME': (str, 'Twitter', ''),
'TQUATTRECENTONZE': (int, 'tquattrecentonze', 0),
'TQUATTRECENTONZE_PASSWORD': (str, 'tquattrecentonze', ''),
'TQUATTRECENTONZE_USER': (str, 'tquattrecentonze', ''),
'UPDATE_DB_INTERVAL': (int, 'General', 24),
'USENET_RETENTION': (int, 'General', '1500'),
'UTORRENT_HOST': (str, 'uTorrent', ''),
@@ -290,6 +293,12 @@ _CONFIG_DEFINITIONS = {
'WHATCD_PASSWORD': (str, 'What.cd', ''),
'WHATCD_RATIO': (str, 'What.cd', ''),
'WHATCD_USERNAME': (str, 'What.cd', ''),
'WHATCD_URL': (str, 'What.cd', 'https://what.cd'),
'PTH': (int, 'PassTheHeadphones.me', 0),
'PTH_PASSWORD': (str, 'PassTheHeadphones.me', ''),
'PTH_RATIO': (str, 'PassTheHeadphones.me', ''),
'PTH_USERNAME': (str, 'PassTheHeadphones.me', ''),
'PTH_URL': (str, 'PassTheHeadphones.me', 'https://passtheheadphones.me'),
'XBMC_ENABLED': (int, 'XBMC', 0),
'XBMC_HOST': (str, 'XBMC', ''),
'XBMC_NOTIFY': (int, 'XBMC', 0),
+36
View File
@@ -0,0 +1,36 @@
import pprint
import sys
import threading
import traceback
from headphones import logger
def cry():
"""
Logs thread traces.
"""
tmap = {}
main_thread = None
# get a map of threads by their ID so we can print their names
# during the traceback dump
for t in threading.enumerate():
if t.ident:
tmap[t.ident] = t
else:
main_thread = t
# Loop over each thread's current frame, writing info about it
for tid, frame in sys._current_frames().iteritems():
thread = tmap.get(tid, main_thread)
lines = []
lines.append('%s\n' % thread.getName())
lines.append('========================================\n')
lines += traceback.format_stack(frame)
lines.append('========================================\n')
lines.append('LOCAL VARIABLES:\n')
lines.append('========================================\n')
lines.append(pprint.pformat(frame.f_locals))
lines.append('\n\n')
logger.info("".join(lines))
+2 -1
View File
@@ -269,7 +269,8 @@ def _get_auth():
delugeweb_host = headphones.CONFIG.DELUGE_HOST
delugeweb_cert = headphones.CONFIG.DELUGE_CERT
delugeweb_password = headphones.CONFIG.DELUGE_PASSWORD
logger.debug('Deluge: Using password %s******%s' % (delugeweb_password[0], delugeweb_password[-1]))
if len(delugeweb_password) > 0:
logger.debug('Deluge: Using password %s******%s' % (delugeweb_password[0], delugeweb_password[-1]))
if not delugeweb_host.startswith('http'):
delugeweb_host = 'http://%s' % delugeweb_host
+1
View File
@@ -251,6 +251,7 @@ _XLATE_SPECIAL = {
# Translation table.
# Cover additional special characters processing normalization.
u"'": '', # replace apostrophe with nothing
u"": '', # replace musicbrainz style apostrophe with nothing
u'&': ' and ', # expand & to ' and '
}
+4 -22
View File
@@ -91,10 +91,6 @@ def findArtist(name, limit=1):
artistlist = []
artistResults = None
chars = set('!?*-')
if any((c in chars) for c in name):
name = '"' + name + '"'
criteria = {'artist': name.lower()}
with mb_lock:
@@ -156,16 +152,13 @@ def findRelease(name, limit=1, artist=None):
if not artist and ':' in name:
name, artist = name.rsplit(":", 1)
chars = set('!?*-')
if any((c in chars) for c in name):
name = '"' + name + '"'
if artist and any((c in chars) for c in artist):
artist = '"' + artist + '"'
criteria = {'release': name.lower()}
if artist:
criteria['artist'] = artist.lower()
with mb_lock:
try:
releaseResults = musicbrainzngs.search_releases(query=name, limit=limit, artist=artist)[
'release-list']
releaseResults = musicbrainzngs.search_releases(limit=limit, **criteria)['release-list']
except musicbrainzngs.WebServiceError as e: # need to update exceptions
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
mb_lock.snooze(5)
@@ -234,10 +227,6 @@ def findSeries(name, limit=1):
serieslist = []
seriesResults = None
chars = set('!?*-')
if any((c in chars) for c in name):
name = '"' + name + '"'
criteria = {'series': name.lower()}
with mb_lock:
@@ -759,19 +748,12 @@ def findArtistbyAlbum(name):
def findAlbumID(artist=None, album=None):
results = None
chars = set('!?*-')
try:
if album and artist:
if any((c in chars) for c in album):
album = '"' + album + '"'
if any((c in chars) for c in artist):
artist = '"' + artist + '"'
criteria = {'release': album.lower()}
criteria['artist'] = artist.lower()
else:
if any((c in chars) for c in album):
album = '"' + album + '"'
criteria = {'release': album.lower()}
with mb_lock:
results = musicbrainzngs.search_release_groups(limit=1, **criteria).get(
+15 -13
View File
@@ -44,28 +44,30 @@ class Rutracker(object):
logger.info("Attempting to log in to rutracker...")
try:
r = self.session.post(loginpage, data=post_params, timeout=self.timeout)
r = self.session.post(loginpage, data=post_params, timeout=self.timeout, allow_redirects=False)
# try again
if 'bb_data' not in r.cookies.keys():
if not self.has_bb_data_cookie(r):
time.sleep(10)
r = self.session.post(loginpage, data=post_params, timeout=self.timeout)
if r.status_code != 200:
logger.error("rutracker login returned status code %s" % r.status_code)
self.loggedin = False
r = self.session.post(loginpage, data=post_params, timeout=self.timeout, allow_redirects=False)
if self.has_bb_data_cookie(r):
self.loggedin = True
logger.info("Successfully logged in to rutracker")
else:
if 'bb_data' in r.cookies.keys():
self.loggedin = True
logger.info("Successfully logged in to rutracker")
else:
logger.error(
"Could not login to rutracker, credentials maybe incorrect, site is down or too many attempts. Try again later")
self.loggedin = False
logger.error(
"Could not login to rutracker, credentials maybe incorrect, site is down or too many attempts. Try again later")
self.loggedin = False
return self.loggedin
except Exception as e:
logger.error("Unknown error logging in to rutracker: %s" % e)
self.loggedin = False
return self.loggedin
def has_bb_data_cookie(self, response):
if 'bb_data' in response.cookies.keys():
return True
# Rutracker randomly send a 302 redirect code, cookie may be present in response history
return next(('bb_data' in r.cookies.keys() for r in response.history), False)
def searchurl(self, artist, album, year, format):
"""
Return the search url
+219 -9
View File
@@ -14,6 +14,7 @@
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
# t411 support added by a1ex, @likeitneverwentaway on github for maintenance
from base64 import b16encode, b32decode
from hashlib import sha1
@@ -24,6 +25,7 @@ import datetime
import subprocess
import unicodedata
import urlparse
from json import loads
import os
import re
@@ -36,7 +38,6 @@ from headphones import logger, db, helpers, classes, sab, nzbget, request
from headphones import utorrent, transmission, notifiers, rutracker, deluge
from bencode import bencode, bdecode
# Magnet to torrent services, for Black hole. Stolen from CouchPotato.
TORRENT_TO_MAGNET_SERVICES = [
# 'https://zoink.it/torrent/%s.torrent',
@@ -161,6 +162,8 @@ def get_seed_ratio(provider):
seed_ratio = headphones.CONFIG.KAT_RATIO
elif provider == 'What.cd':
seed_ratio = headphones.CONFIG.WHATCD_RATIO
elif provider == 'PassTheHeadphones.Me':
seed_ratio = headphones.CONFIG.PTH_RATIO
elif provider == 'The Pirate Bay':
seed_ratio = headphones.CONFIG.PIRATEBAY_RATIO
elif provider == 'Old Pirate Bay':
@@ -199,14 +202,13 @@ def searchforalbum(albumid=None, new=False, losslessOnly=False,
continue
if headphones.CONFIG.WAIT_UNTIL_RELEASE_DATE and album['ReleaseDate']:
try:
release_date = datetime.datetime.strptime(album['ReleaseDate'], "%Y-%m-%d")
except:
logger.warn(
"No valid date for: %s. Skipping automatic search" % album['AlbumTitle'])
release_date = strptime_musicbrainz(album['ReleaseDate'])
if not release_date:
logger.warn("No valid date for: %s. Skipping automatic search" %
album['AlbumTitle'])
continue
if release_date > datetime.datetime.today():
elif release_date > datetime.datetime.today():
logger.info("Skipping: %s. Waiting for release date of: %s" % (
album['AlbumTitle'], album['ReleaseDate']))
continue
@@ -235,6 +237,26 @@ def searchforalbum(albumid=None, new=False, losslessOnly=False,
logger.info('Search for wanted albums complete')
def strptime_musicbrainz(date_str):
"""
Release date as returned by Musicbrainz may contain the full date (Year-Month-Day)
but it may as well be just Year-Month or even just the year.
Args:
date_str: the date as a string (ex: "2003-05-01", "2003-03", "2003")
Returns:
The more accurate datetime object we can create or None if parse failed
"""
acceptable_formats = ('%Y-%m-%d', '%Y-%m', '%Y')
for date_format in acceptable_formats:
try:
return datetime.datetime.strptime(date_str, date_format)
except:
pass
return None
def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
NZB_PROVIDERS = (headphones.CONFIG.HEADPHONES_INDEXER or
headphones.CONFIG.NEWZNAB or
@@ -253,6 +275,7 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
headphones.CONFIG.WAFFLES or
headphones.CONFIG.RUTRACKER or
headphones.CONFIG.WHATCD or
headphones.CONFIG.PTH or
headphones.CONFIG.STRIKE)
results = []
@@ -813,6 +836,19 @@ def send_to_downloader(data, bestqual, album):
torrent_name = helpers.replace_illegal_chars(folder_name) + '.torrent'
download_path = os.path.join(headphones.CONFIG.TORRENTBLACKHOLE_DIR, torrent_name)
# Blackhole for t411
if bestqual[2].lower().startswith("http://api.t411"):
if headphones.CONFIG.MAGNET_LINKS == 2:
try:
url = bestqual[2].split('TOKEN')[0]
token = bestqual[2].split('TOKEN')[1]
data = request.request_content(url, headers={'Authorization': token})
torrent_to_file(download_path, data)
logger.info('Successfully converted magnet to torrent file')
except Exception as e:
logger.error("Error converting magnet link: %s" % str(e))
return
if bestqual[2].lower().startswith("magnet:"):
if headphones.CONFIG.MAGNET_LINKS == 1:
try:
@@ -1333,7 +1369,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
if headphones.CONFIG.WAFFLES:
provider = "Waffles.ch"
providerurl = fix_url("https://www.waffles.ch/browse.php")
providerurl = fix_url("https://waffles.ch/browse.php")
bitrate = None
if headphones.CONFIG.PREFERRED_QUALITY == 3 or losslessOnly:
@@ -1470,7 +1506,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
try:
logger.info(u"Attempting to log in to What.cd...")
gazelle = gazelleapi.GazelleAPI(headphones.CONFIG.WHATCD_USERNAME,
headphones.CONFIG.WHATCD_PASSWORD)
headphones.CONFIG.WHATCD_PASSWORD,
headphones.CONFIG.WHATCD_URL)
gazelle._login()
except Exception as e:
gazelle = None
@@ -1530,6 +1567,106 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
provider,
'torrent', True))
# PassTheHeadphones.me - Using same logic as What.CD as it's also Gazelle, so should really make this into something reusable
if headphones.CONFIG.PTH:
provider = "PassTheHeadphones.me"
providerurl = "https://passtheheadphones.me/"
bitrate = None
bitrate_string = bitrate
if headphones.CONFIG.PREFERRED_QUALITY == 3 or losslessOnly: # Lossless Only mode
search_formats = [gazelleformat.FLAC]
maxsize = 10000000000
elif headphones.CONFIG.PREFERRED_QUALITY == 2: # Preferred quality mode
search_formats = [None] # should return all
bitrate = headphones.CONFIG.PREFERRED_BITRATE
if bitrate:
if 225 <= int(bitrate) < 256:
bitrate = 'V0'
elif 200 <= int(bitrate) < 225:
bitrate = 'V1'
elif 175 <= int(bitrate) < 200:
bitrate = 'V2'
for encoding_string in gazelleencoding.ALL_ENCODINGS:
if re.search(bitrate, encoding_string, flags=re.I):
bitrate_string = encoding_string
if bitrate_string not in gazelleencoding.ALL_ENCODINGS:
logger.info(
u"Your preferred bitrate is not one of the available What.cd filters, so not using it as a search parameter.")
maxsize = 10000000000
elif headphones.CONFIG.PREFERRED_QUALITY == 1 or allow_lossless: # Highest quality including lossless
search_formats = [gazelleformat.FLAC, gazelleformat.MP3]
maxsize = 10000000000
else: # Highest quality excluding lossless
search_formats = [gazelleformat.MP3]
maxsize = 300000000
if not gazelle or not gazelle.logged_in():
try:
logger.info(u"Attempting to log in to PassTheHeadphones.me...")
gazelle = gazelleapi.GazelleAPI(headphones.CONFIG.PTH_USERNAME,
headphones.CONFIG.PTH_PASSWORD,
headphones.CONFIG.PTH_URL)
gazelle._login()
except Exception as e:
gazelle = None
logger.error(u"PassTheHeadphones credentials incorrect or site is down. Error: %s %s" % (
e.__class__.__name__, str(e)))
if gazelle and gazelle.logged_in():
logger.info(u"Searching %s..." % provider)
all_torrents = []
for search_format in search_formats:
if usersearchterm:
all_torrents.extend(
gazelle.search_torrents(searchstr=usersearchterm, format=search_format,
encoding=bitrate_string)['results'])
else:
all_torrents.extend(gazelle.search_torrents(artistname=semi_clean_artist_term,
groupname=semi_clean_album_term,
format=search_format,
encoding=bitrate_string)['results'])
# filter on format, size, and num seeders
logger.info(u"Filtering torrents by format, maximum size, and minimum seeders...")
match_torrents = [t for t in all_torrents if
t.size <= maxsize and t.seeders >= minimumseeders]
logger.info(
u"Remaining torrents: %s" % ", ".join(repr(torrent) for torrent in match_torrents))
# sort by times d/l'd
if not len(match_torrents):
logger.info(u"No results found from %s for %s after filtering" % (provider, term))
elif len(match_torrents) > 1:
logger.info(u"Found %d matching releases from %s for %s - %s after filtering" %
(len(match_torrents), provider, artistterm, albumterm))
logger.info(
"Sorting torrents by times snatched and preferred bitrate %s..." % bitrate_string)
match_torrents.sort(key=lambda x: int(x.snatched), reverse=True)
if gazelleformat.MP3 in search_formats:
# sort by size after rounding to nearest 10MB...hacky, but will favor highest quality
match_torrents.sort(key=lambda x: int(10 * round(x.size / 1024. / 1024. / 10.)),
reverse=True)
if search_formats and None not in search_formats:
match_torrents.sort(
key=lambda x: int(search_formats.index(x.format))) # prefer lossless
# if bitrate:
# match_torrents.sort(key=lambda x: re.match("mp3", x.getTorrentDetails(), flags=re.I), reverse=True)
# match_torrents.sort(key=lambda x: str(bitrate) in x.getTorrentFolderName(), reverse=True)
logger.info(
u"New order: %s" % ", ".join(repr(torrent) for torrent in match_torrents))
for torrent in match_torrents:
if not torrent.file_path:
torrent.group.update_group_data() # will load the file_path for the individual torrents
resultlist.append((torrent.file_path,
torrent.size,
gazelle.generate_torrent_link(torrent.id),
provider,
'torrent', True))
# Pirate Bay
if headphones.CONFIG.PIRATEBAY:
provider = "The Pirate Bay"
@@ -1763,6 +1900,77 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
resultlist.append((title, size, url, provider, 'torrent', match))
except Exception as e:
logger.exception("Unhandled exception in Mininova Parser")
# t411
if headphones.CONFIG.TQUATTRECENTONZE:
username = headphones.CONFIG.TQUATTRECENTONZE_USER
password = headphones.CONFIG.TQUATTRECENTONZE_PASSWORD
API_URL = "http://api.t411.li"
AUTH_URL = API_URL + '/auth'
DL_URL = API_URL + '/torrents/download/'
provider = "t411"
t411_term = term.replace(" ", "%20")
SEARCH_URL = API_URL + '/torrents/search/' + t411_term + "?limit=15&cid=395&subcat=623"
headers_login = {'username': username, 'password': password}
# Requesting content
logger.info('Parsing results from t411 using search term: %s' % term)
req = request.request_content(AUTH_URL, method='post', data=headers_login)
if len(req.split('"')) == 9:
token = req.split('"')[7]
headers_auth = {'Authorization': token}
logger.info('t411 - User %s logged in' % username)
else:
logger.info('t411 - Login error : %s' % req.split('"')[3])
# Quality
if headphones.CONFIG.PREFERRED_QUALITY == 3 or losslessOnly:
providerurl = fix_url(SEARCH_URL + "&term[16][]=529&term[16][]=1184")
elif headphones.CONFIG.PREFERRED_QUALITY == 1 or allow_lossless:
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")
elif headphones.CONFIG.PREFERRED_QUALITY == 0:
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")
else:
providerurl = fix_url(SEARCH_URL)
# Tracker search
req = request.request_content(providerurl, headers=headers_auth)
req = loads(req)
total = req['total']
# Process feed
if total == '0':
logger.info("No results found from t411 for %s" % term)
else:
logger.info('Found %s results from t411' % total)
torrents = req['torrents']
for torrent in torrents:
try:
title = torrent['name']
if torrent['seeders'] < minimumseeders:
logger.info('Skipping torrent %s : seeders below minimum set' % title)
continue
id = torrent['id']
size = int(torrent['size'])
data = request.request_content(DL_URL + id, headers=headers_auth)
# Blackhole
if headphones.CONFIG.TORRENT_DOWNLOADER == 0 and headphones.CONFIG.MAGNET_LINKS == 2:
url = DL_URL + id + 'TOKEN' + token
resultlist.append((title, size, url, provider, 'torrent', True))
# Build magnet
else:
metadata = bdecode(data)
hashcontents = bencode(metadata['info'])
digest = sha1(hashcontents).hexdigest()
trackers = [metadata["announce"]][0]
url = 'magnet:?xt=urn:btih:%s&tr=%s' % (digest, trackers)
resultlist.append((title, size, url, provider, 'torrent', True))
except Exception as e:
logger.error("Error converting magnet link: %s" % str(e))
return
# attempt to verify that this isn't a substring result
# when looking for "Foo - Foo" we don't want "Foobar"
@@ -1802,6 +2010,8 @@ def preprocess(resultlist):
headers['User-Agent'] = USER_AGENT
elif result[3] == 'What.cd':
headers['User-Agent'] = 'Headphones'
elif result[3] == 'PassTheHeadphones.me':
headers['User-Agent'] = 'Headphones'
elif result[3] == "The Pirate Bay" or result[3] == "Old Pirate Bay":
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'
+17 -2
View File
@@ -14,6 +14,7 @@
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
# t411 support added by a1ex, @likeitneverwentaway on github for maintenance
from operator import itemgetter
import threading
@@ -28,7 +29,7 @@ import urllib2
import os
import re
from headphones import logger, searcher, db, importer, mb, lastfm, librarysync, helpers, notifiers
from headphones import logger, searcher, db, importer, mb, lastfm, librarysync, helpers, notifiers, crier
from headphones.helpers import checked, radio, today, clean_name
from mako.lookup import TemplateLookup
from mako import exceptions
@@ -69,6 +70,11 @@ class WebInterface(object):
artists = myDB.select('SELECT * from artists order by ArtistSortName COLLATE NOCASE')
return serve_template(templatename="index.html", title="Home", artists=artists)
@cherrypy.expose
def threads(self):
crier.cry()
raise cherrypy.HTTPRedirect("home")
@cherrypy.expose
def artistPage(self, ArtistID):
myDB = db.DBConnection()
@@ -1222,8 +1228,17 @@ class WebInterface(object):
"whatcd_username": headphones.CONFIG.WHATCD_USERNAME,
"whatcd_password": headphones.CONFIG.WHATCD_PASSWORD,
"whatcd_ratio": headphones.CONFIG.WHATCD_RATIO,
"whatcd_url": headphones.CONFIG.WHATCD_URL,
"use_pth": checked(headphones.CONFIG.PTH),
"pth_username": headphones.CONFIG.PTH_USERNAME,
"pth_password": headphones.CONFIG.PTH_PASSWORD,
"pth_ratio": headphones.CONFIG.PTH_RATIO,
"pth_url": headphones.CONFIG.PTH_URL,
"use_strike": checked(headphones.CONFIG.STRIKE),
"strike_ratio": headphones.CONFIG.STRIKE_RATIO,
"use_tquattrecentonze": checked(headphones.CONFIG.TQUATTRECENTONZE),
"tquattrecentonze_user": headphones.CONFIG.TQUATTRECENTONZE_USER,
"tquattrecentonze_password": headphones.CONFIG.TQUATTRECENTONZE_PASSWORD,
"pref_qual_0": radio(headphones.CONFIG.PREFERRED_QUALITY, 0),
"pref_qual_1": radio(headphones.CONFIG.PREFERRED_QUALITY, 1),
"pref_qual_2": radio(headphones.CONFIG.PREFERRED_QUALITY, 2),
@@ -1420,7 +1435,7 @@ class WebInterface(object):
"use_newznab", "newznab_enabled", "use_torznab", "torznab_enabled",
"use_nzbsorg", "use_omgwtfnzbs", "use_kat", "use_piratebay", "use_oldpiratebay",
"use_mininova", "use_waffles", "use_rutracker",
"use_whatcd", "use_strike", "preferred_bitrate_allow_lossless", "detect_bitrate",
"use_whatcd", "use_strike", "use_tquattrecentonze", "preferred_bitrate_allow_lossless", "detect_bitrate",
"ignore_clean_releases", "freeze_db", "cue_split", "move_files",
"rename_files", "correct_metadata", "cleanup_files", "keep_nfo", "add_album_art",
"embed_album_art", "embed_lyrics",
+16 -37
View File
@@ -1,7 +1,8 @@
#!/bin/sh
#
# PROVIDE: headphones
# REQUIRE: DAEMON sabnzbd
# REQUIRE: DAEMON
# BEFORE: LOGIN
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
@@ -15,56 +16,34 @@
# as root.
# headphones_dir: Directory where Headphones lives.
# Default: /usr/local/headphones
# headphones_chdir: Change to this directory before running Headphones.
# Default is same as headphones_dir.
# headphones_pid: The name of the pidfile to create.
# Default is headphones.pid in headphones_dir.
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr
name="headphones"
rcvar=${name}_enable
load_rc_config ${name}
: "${headphones_enable:="NO"}"
: "${headphones_user:="_sabnzbd"}"
: "${headphones_dir:="/usr/local/headphones"}"
: "${headphones_chdir:="${headphones_dir}"}"
: "${headphones_pid:="${headphones_dir}/headphones.pid"}"
: "${headphones_conf:="/usr/local/headphones/config.ini"}"
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="${headphones_dir}/Headphones.py"
command_interpreter="/usr/bin/python"
pidfile="/var/run/headphones/headphones.pid"
start_precmd="headphones_start_precmd"
headphones_flags="--daemon --nolaunch --pidfile $pidfile --config $headphones_conf $headphones_flags"
command="/usr/sbin/daemon"
command_args="-f -p ${headphones_pid} python ${headphones_dir}/Headphones.py ${headphones_flags} --quiet --nolaunch"
headphones_start_precmd() {
if [ $($ID -u) != 0 ]; then
err 1 "Must be root."
fi
# Ensure user is root when running this script.
if [ "$(id -u)" != "0" ]; then
echo "Oops, you should be root before running this!"
exit 1
fi
verify_headphones_pid() {
# Make sure the pid corresponds to the Headphones process.
pid=$(cat "${headphones_pid}" 2>/dev/null)
pgrep -F "${headphones_pid}" -q "python ${headphones_dir}/Headphones.py"
return $?
}
# Try to stop Headphones cleanly by calling shutdown over http.
headphones_stop() {
echo "Stopping $name"
verify_headphones_pid
if [ -n "${pid}" ]; then
wait_for_pids "${pid}"
echo "Stopped"
fi
}
headphones_status() {
verify_headphones_pid && echo "$name is running as ${pid}" || echo "$name is not running"
if [ ! -d /var/run/headphones ]; then
install -do $headphones_user /var/run/headphones
fi
}
load_rc_config ${name}
run_rc_command "$1"
+7 -1
View File
@@ -33,6 +33,7 @@
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
## HP_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for headphones, i.e. " --config=/home/headphones/config.ini"
## HP_PORT= #$PORT_OPTS, hardcoded port for the webserver, overrides value in config.ini
## HP_HOST= #$HOST_OPTS, host for the webserver, overrides value in config.ini
##
## EXAMPLE if want to run as different user
## add HP_USER=username to /etc/default/headphones
@@ -105,7 +106,12 @@ load_settings() {
PORT_OPTS=" --port=${HP_PORT} "
}
DAEMON_OPTS=" Headphones.py --quiet --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${PORT_OPTS}${EXTRA_DAEMON_OPTS}"
# Host config
[ -n "$HP_HOST" ] && {
HOST_OPTS=" --host=${HP_HOST} "
}
DAEMON_OPTS=" Headphones.py --quiet --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${PORT_OPTS} ${HOST_OPTS} ${EXTRA_DAEMON_OPTS}"
SETTINGS_LOADED=TRUE
fi
+3 -1
View File
@@ -99,12 +99,14 @@ DEFAULT_BUFFER_SIZE = -1
class FauxSocket(object):
"""Faux socket with the minimal interface required by pypy"""
def _reuse(self):
pass
def _drop(self):
pass
_fileobject_uses_str_type = isinstance(
socket._fileobject(FauxSocket())._rbuf, basestring)
del FauxSocket # this class is not longer required for anything.
+3 -3
View File
@@ -42,7 +42,7 @@ class GazelleAPI(object):
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'}
def __init__(self, username=None, password=None):
def __init__(self, username=None, password=None, url=None):
self.session = requests.session()
self.session.headers = self.default_headers
self.username = username
@@ -59,7 +59,7 @@ class GazelleAPI(object):
self.cached_torrents = {}
self.cached_requests = {}
self.cached_categories = {}
self.site = "https://what.cd/"
self.site = url + "/"
self.past_request_timestamps = []
def wait_for_rate_limit(self):
@@ -95,7 +95,7 @@ class GazelleAPI(object):
self.wait_for_rate_limit()
loginpage = 'https://what.cd/login.php'
loginpage = self.site + 'login.php'
data = {'username': self.username,
'password': self.password,
'keeplogged': '1'}