mirror of
https://github.com/rembo10/headphones.git
synced 2026-09-10 00:32:52 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88af84579c | ||
|
|
917dc7695f | ||
|
|
65e3e4faa2 | ||
|
|
9e7ec3a65c | ||
|
|
7ac7b127c6 | ||
|
|
3945753c5c | ||
|
|
599585d57a | ||
|
|
10cf36401f | ||
|
|
fe2834f4a6 | ||
|
|
674617c0f8 | ||
|
|
f92f8928b0 | ||
|
|
45868bc523 | ||
|
|
9b2594355f | ||
|
|
e30cb26200 | ||
|
|
63d3ab1cc1 | ||
|
|
188d907e10 | ||
|
|
e972a86233 | ||
|
|
52a76e87ad | ||
|
|
8ab19f8121 | ||
|
|
6f3c8c68d4 | ||
|
|
98385ff4c8 | ||
|
|
62a6be26af | ||
|
|
5c1169ba9d | ||
|
|
0b47a5ca3a | ||
|
|
9da75a777b | ||
|
|
2fc2190b59 | ||
|
|
e108935430 | ||
|
|
bbde74db8d | ||
|
|
9fce4edf00 | ||
|
|
ea3c6c3278 | ||
|
|
6a0bd397cd | ||
|
|
7bbb137fd9 | ||
|
|
9c81fbaa2b |
+6
-6
@@ -77,16 +77,16 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
headphones.VERBOSE = 2
|
headphones.VERBOSE = True
|
||||||
elif args.quiet:
|
if args.quiet:
|
||||||
headphones.VERBOSE = 0
|
headphones.QUIET = True
|
||||||
|
|
||||||
if args.daemon:
|
if args.daemon:
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
print "Daemonize not supported under Windows, starting normally"
|
print "Daemonize not supported under Windows, starting normally"
|
||||||
else:
|
else:
|
||||||
headphones.DAEMON=True
|
headphones.DAEMON = True
|
||||||
headphones.VERBOSE = False
|
headphones.QUIET = True
|
||||||
|
|
||||||
if args.pidfile:
|
if args.pidfile:
|
||||||
headphones.PIDFILE = str(args.pidfile)
|
headphones.PIDFILE = str(args.pidfile)
|
||||||
@@ -101,7 +101,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
file(headphones.PIDFILE, 'w').write("pid\n")
|
file(headphones.PIDFILE, 'w').write("pid\n")
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno))
|
raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno)
|
||||||
else:
|
else:
|
||||||
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
||||||
|
|
||||||
|
|||||||
@@ -240,7 +240,8 @@
|
|||||||
refreshTable();
|
refreshTable();
|
||||||
$('#artistnamelink').text(data["ArtistName"]);
|
$('#artistnamelink').text(data["ArtistName"]);
|
||||||
if (loadingMessage == false){
|
if (loadingMessage == false){
|
||||||
showMsg("Getting artist information",true);
|
$("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
|
||||||
|
showArtistMsg("Getting artist information");
|
||||||
loadingMessage = true;
|
loadingMessage = true;
|
||||||
}
|
}
|
||||||
if (spinner_active == false){
|
if (spinner_active == false){
|
||||||
@@ -255,8 +256,7 @@
|
|||||||
else{
|
else{
|
||||||
$('#artistnamespinner').remove()
|
$('#artistnamespinner').remove()
|
||||||
$('#loadingtext').remove()
|
$('#loadingtext').remove()
|
||||||
$('#ajaxMsg').empty()
|
$('#ajaxMsg2').remove()
|
||||||
$('#ajaxMsg').removeAttr('style')
|
|
||||||
spinner_active = false
|
spinner_active = false
|
||||||
loadingtext_active = false
|
loadingtext_active = false
|
||||||
loadingMessage = false
|
loadingMessage = false
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
resetFilters("albums");
|
resetFilters("albums");
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
initFancybox();
|
initFancybox();
|
||||||
},1500)
|
},1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="ajaxMsg"></div>
|
<div id="ajaxMsg" class="ajaxMsg"></div>
|
||||||
% if not headphones.CURRENT_VERSION:
|
% if not headphones.CURRENT_VERSION:
|
||||||
<div id="updatebar">
|
<div id="updatebar">
|
||||||
You're running an unknown version of Headphones. <a href="update">Update</a> or
|
You're running an unknown version of Headphones. <a href="update">Update</a> or
|
||||||
|
|||||||
@@ -470,7 +470,14 @@
|
|||||||
<div class="row radio clearfix">
|
<div class="row radio clearfix">
|
||||||
<input type="radio" name="preferred_quality" id="preferred_quality0" value="0" ${config['pref_qual_0']}><label>Highest Quality excluding Lossless</label>
|
<input type="radio" name="preferred_quality" id="preferred_quality0" value="0" ${config['pref_qual_0']}><label>Highest Quality excluding Lossless</label>
|
||||||
<input type="radio" name="preferred_quality" id="preferred_quality1" value="1" ${config['pref_qual_1']}><label>Highest Quality including Lossless</label>
|
<input type="radio" name="preferred_quality" id="preferred_quality1" value="1" ${config['pref_qual_1']}><label>Highest Quality including Lossless</label>
|
||||||
<input type="radio" name="preferred_quality" id="preferred_quality3" value="3" ${config['pref_qual_3']}><label>Lossless Only</label>
|
<input type="radio" name="preferred_quality" id="lossless_only" value="3" ${config['pref_qual_3']}><label>Lossless Only</label>
|
||||||
|
<div id="lossless_only_options">
|
||||||
|
<span style="padding-left: 20px">
|
||||||
|
Reject if target size is not in bitrate range: \
|
||||||
|
<input type="text" class="override-float" name="lossless_bitrate_from" value="${config['lossless_bitrate_from']}" size="4" title="e.g. if album length = 40 mins, from = 350 kbps, then min target size = 102.5 mb, anything less will be rejected">to\
|
||||||
|
<input type="text" class="override-float" name="lossless_bitrate_to" value="${config['lossless_bitrate_to']}" size="5" title="e.g. if album length = 40 mins, to = 2000 kbps, then max target size = 586 mb, anything greater will be rejected">kbps
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<input type="radio" id="preferred_bitrate" name="preferred_quality" value="2" ${config['pref_qual_2']}>Preferred Bitrate: <input type="text" class="override-float" name="preferred_bitrate" value="${config['pref_bitrate']}" size="3">kbps<br>
|
<input type="radio" id="preferred_bitrate" name="preferred_quality" value="2" ${config['pref_qual_2']}>Preferred Bitrate: <input type="text" class="override-float" name="preferred_bitrate" value="${config['pref_bitrate']}" size="3">kbps<br>
|
||||||
<div id="preferred_bitrate_options">
|
<div id="preferred_bitrate_options">
|
||||||
Reject if <strong>less than</strong> <input type="text" class="override-float" name="preferred_bitrate_low_buffer" value="${config['pref_bitrate_low']}" size="3">% or <strong>more than</strong> <input type="text" class="override-float" name="preferred_bitrate_high_buffer" value="${config['pref_bitrate_high']}" size="3">% of the target size (leave blank for no limit)<br><br>
|
Reject if <strong>less than</strong> <input type="text" class="override-float" name="preferred_bitrate_low_buffer" value="${config['pref_bitrate_low']}" size="3">% or <strong>more than</strong> <input type="text" class="override-float" name="preferred_bitrate_high_buffer" value="${config['pref_bitrate_high']}" size="3">% of the target size (leave blank for no limit)<br><br>
|
||||||
@@ -496,7 +503,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<label>Preferred Words</label>
|
<label>Preferred Words</label>
|
||||||
<input type="text" name="preferred_words" value="${config['preferred_words']}" size="50">
|
<input type="text" name="preferred_words" value="${config['preferred_words']}" size="50">
|
||||||
<small>Results with these words in the title will be preferred over results without them</small>
|
<small>Results with these words in the title will be preferred over results without them (search provider names can also be entered)</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>Required Words</label>
|
<label>Required Words</label>
|
||||||
@@ -1587,6 +1594,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ($("#lossless_only").is(":checked"))
|
||||||
|
{
|
||||||
|
$("#lossless_only_options").show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$("#lossless_only_options").hide();
|
||||||
|
}
|
||||||
|
|
||||||
if ($("#preferred_bitrate").is(":checked"))
|
if ($("#preferred_bitrate").is(":checked"))
|
||||||
{
|
{
|
||||||
$("#preferred_bitrate_options").show();
|
$("#preferred_bitrate_options").show();
|
||||||
@@ -1632,18 +1648,22 @@
|
|||||||
if ($("#preferred_bitrate").is(":checked"))
|
if ($("#preferred_bitrate").is(":checked"))
|
||||||
{
|
{
|
||||||
$("#preferred_bitrate_options").slideDown("fast");
|
$("#preferred_bitrate_options").slideDown("fast");
|
||||||
|
$("#lossless_only_options").slideUp("fast");
|
||||||
}
|
}
|
||||||
if ($("#preferred_quality0").is(":checked"))
|
if ($("#preferred_quality0").is(":checked"))
|
||||||
{
|
{
|
||||||
$("#preferred_bitrate_options").slideUp("fast");
|
$("#preferred_bitrate_options").slideUp("fast");
|
||||||
|
$("#lossless_only_options").slideUp("fast");
|
||||||
}
|
}
|
||||||
if ($("#preferred_quality1").is(":checked"))
|
if ($("#preferred_quality1").is(":checked"))
|
||||||
{
|
{
|
||||||
$("#preferred_bitrate_options").slideUp("fast");
|
$("#preferred_bitrate_options").slideUp("fast");
|
||||||
|
$("#lossless_only_options").slideUp("fast");
|
||||||
}
|
}
|
||||||
if ($("#preferred_quality3").is(":checked"))
|
if ($("#lossless_only").is(":checked"))
|
||||||
{
|
{
|
||||||
$("#preferred_bitrate_options").slideUp("fast");
|
$("#lossless_only_options").slideDown("fast");
|
||||||
|
$("#preferred_bitrate_options").slideUp("fast");
|
||||||
}
|
}
|
||||||
if ($("#nzb_downloader_sabnzbd").is(":checked"))
|
if ($("#nzb_downloader_sabnzbd").is(":checked"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -545,7 +545,7 @@ footer {
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
#ajaxMsg {
|
.ajaxMsg {
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important;
|
background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important;
|
||||||
background-image: linear-gradient(#ffffff, #eeeeee) !important;
|
background-image: linear-gradient(#ffffff, #eeeeee) !important;
|
||||||
@@ -576,16 +576,16 @@ footer {
|
|||||||
-o-opacity: 0.8 !important;
|
-o-opacity: 0.8 !important;
|
||||||
opacity: 0.8 !important;
|
opacity: 0.8 !important;
|
||||||
}
|
}
|
||||||
#ajaxMsg .msg {
|
.ajaxMsg .msg {
|
||||||
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
#ajaxMsg .loader {
|
.ajaxMsg .loader {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
#ajaxMsg.success {
|
.ajaxMsg .success {
|
||||||
background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important;
|
background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||||
background-image: linear-gradient(#d3ffd7, #c2edc6) !important;
|
background-image: linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||||
background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important;
|
background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||||
@@ -595,7 +595,7 @@ footer {
|
|||||||
padding: 15px 10px;
|
padding: 15px 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#ajaxMsg.error {
|
.ajaxMsg .error {
|
||||||
background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important;
|
background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||||
background-image: linear-gradient(#ffd3d3, #edc4c4) !important;
|
background-image: linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||||
background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important;
|
background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||||
@@ -605,7 +605,7 @@ footer {
|
|||||||
padding: 15px 10px;
|
padding: 15px 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#ajaxMsg .ui-icon {
|
.ajaxMsg .ui-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
|
|||||||
@@ -228,6 +228,21 @@ function showMsg(msg,loader,timeout,ms) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showArtistMsg(msg) {
|
||||||
|
var feedback = $("#ajaxMsg2");
|
||||||
|
update = $("#updatebar");
|
||||||
|
if ( update.is(":visible") ) {
|
||||||
|
var height = update.height() + 35;
|
||||||
|
feedback.css("bottom",height + "px");
|
||||||
|
} else {
|
||||||
|
feedback.removeAttr("style");
|
||||||
|
}
|
||||||
|
feedback.fadeIn();
|
||||||
|
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
||||||
|
feedback.css("padding","14px 10px")
|
||||||
|
$(feedback).prepend(message);
|
||||||
|
}
|
||||||
|
|
||||||
function doAjaxCall(url,elem,reload,form) {
|
function doAjaxCall(url,elem,reload,form) {
|
||||||
// Set Message
|
// Set Message
|
||||||
feedback = $("#ajaxMsg");
|
feedback = $("#ajaxMsg");
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
|
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
|
||||||
%endif
|
%endif
|
||||||
<td id="artistname"><a href="${result['url']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
<td id="artistname"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||||
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
|
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
|
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
|
||||||
%else:
|
%else:
|
||||||
<td id="add"><a href="addArtist?artistid=${result['id']}"><i class="fa fa-plus"></i> Add this artist</a></td>
|
<td id="add"><a href="${result['url']}"></i> View on MusicBrainz</a></td>
|
||||||
%endif
|
%endif
|
||||||
</tr>
|
</tr>
|
||||||
%endfor
|
%endfor
|
||||||
|
|||||||
+21
-8
@@ -38,7 +38,8 @@ SIGNAL = None
|
|||||||
SYS_PLATFORM = None
|
SYS_PLATFORM = None
|
||||||
SYS_ENCODING = None
|
SYS_ENCODING = None
|
||||||
|
|
||||||
VERBOSE = 1
|
QUIET = False
|
||||||
|
VERBOSE = False
|
||||||
DAEMON = False
|
DAEMON = False
|
||||||
CREATEPID = False
|
CREATEPID = False
|
||||||
PIDFILE= None
|
PIDFILE= None
|
||||||
@@ -103,6 +104,8 @@ PREFERRED_BITRATE_HIGH_BUFFER = None
|
|||||||
PREFERRED_BITRATE_LOW_BUFFER = None
|
PREFERRED_BITRATE_LOW_BUFFER = None
|
||||||
PREFERRED_BITRATE_ALLOW_LOSSLESS = False
|
PREFERRED_BITRATE_ALLOW_LOSSLESS = False
|
||||||
DETECT_BITRATE = False
|
DETECT_BITRATE = False
|
||||||
|
LOSSLESS_BITRATE_FROM = None
|
||||||
|
LOSSLESS_BITRATE_TO = None
|
||||||
ADD_ARTISTS = False
|
ADD_ARTISTS = False
|
||||||
CORRECT_METADATA = False
|
CORRECT_METADATA = False
|
||||||
MOVE_FILES = False
|
MOVE_FILES = False
|
||||||
@@ -248,7 +251,7 @@ PLEX_UPDATE = False
|
|||||||
PLEX_NOTIFY = False
|
PLEX_NOTIFY = False
|
||||||
NMA_ENABLED = False
|
NMA_ENABLED = False
|
||||||
NMA_APIKEY = None
|
NMA_APIKEY = None
|
||||||
NMA_PRIORITY = None
|
NMA_PRIORITY = 0
|
||||||
NMA_ONSNATCH = None
|
NMA_ONSNATCH = None
|
||||||
PUSHALOT_ENABLED = False
|
PUSHALOT_ENABLED = False
|
||||||
PUSHALOT_APIKEY = None
|
PUSHALOT_APIKEY = None
|
||||||
@@ -292,6 +295,8 @@ JOURNAL_MODE = None
|
|||||||
|
|
||||||
UMASK = None
|
UMASK = None
|
||||||
|
|
||||||
|
VERIFY_SSL_CERT = True
|
||||||
|
|
||||||
def CheckSection(sec):
|
def CheckSection(sec):
|
||||||
""" Check if INI section exists, if not create it """
|
""" Check if INI section exists, if not create it """
|
||||||
try:
|
try:
|
||||||
@@ -338,7 +343,7 @@ def initialize():
|
|||||||
|
|
||||||
with INIT_LOCK:
|
with INIT_LOCK:
|
||||||
|
|
||||||
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
|
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, QUIET, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
|
||||||
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
|
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
|
||||||
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
|
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
|
||||||
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \
|
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \
|
||||||
@@ -358,9 +363,9 @@ def initialize():
|
|||||||
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
|
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
|
||||||
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
||||||
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
||||||
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
|
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
|
||||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED
|
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT
|
||||||
|
|
||||||
|
|
||||||
if __INITIALIZED__:
|
if __INITIALIZED__:
|
||||||
@@ -438,6 +443,8 @@ def initialize():
|
|||||||
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
|
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
|
||||||
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
|
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
|
||||||
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
|
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
|
||||||
|
LOSSLESS_BITRATE_FROM = check_setting_int(CFG, 'General', 'lossless_bitrate_from', '')
|
||||||
|
LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '')
|
||||||
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
|
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
|
||||||
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
|
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
|
||||||
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
|
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
|
||||||
@@ -643,6 +650,8 @@ def initialize():
|
|||||||
|
|
||||||
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
|
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
|
||||||
|
|
||||||
|
VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', 1))
|
||||||
|
|
||||||
# update folder formats in the config & bump up config version
|
# update folder formats in the config & bump up config version
|
||||||
if CONFIG_VERSION == '0':
|
if CONFIG_VERSION == '0':
|
||||||
from headphones.helpers import replace_all
|
from headphones.helpers import replace_all
|
||||||
@@ -713,8 +722,9 @@ def initialize():
|
|||||||
if VERBOSE:
|
if VERBOSE:
|
||||||
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
|
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
|
||||||
|
|
||||||
# Start the logger, silence console logging if we need to
|
# Start the logger, disable console if needed
|
||||||
logger.initLogger(verbose=VERBOSE)
|
logger.initLogger(console=not QUIET, verbose=VERBOSE)
|
||||||
|
logger.initLogger(console=not QUIET, verbose=False)
|
||||||
|
|
||||||
if not CACHE_DIR:
|
if not CACHE_DIR:
|
||||||
# Put the cache dir in the data dir for now
|
# Put the cache dir in the data dir for now
|
||||||
@@ -862,6 +872,8 @@ def config_write():
|
|||||||
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
|
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
|
||||||
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
|
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
|
||||||
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
|
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
|
||||||
|
new_config['General']['lossless_bitrate_from'] = LOSSLESS_BITRATE_FROM
|
||||||
|
new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO
|
||||||
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
|
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
|
||||||
new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
|
new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
|
||||||
new_config['General']['move_files'] = int(MOVE_FILES)
|
new_config['General']['move_files'] = int(MOVE_FILES)
|
||||||
@@ -1014,7 +1026,7 @@ def config_write():
|
|||||||
new_config['NMA'] = {}
|
new_config['NMA'] = {}
|
||||||
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
|
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
|
||||||
new_config['NMA']['nma_apikey'] = NMA_APIKEY
|
new_config['NMA']['nma_apikey'] = NMA_APIKEY
|
||||||
new_config['NMA']['nma_priority'] = NMA_PRIORITY
|
new_config['NMA']['nma_priority'] = int(NMA_PRIORITY)
|
||||||
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
|
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
|
||||||
|
|
||||||
new_config['Pushalot'] = {}
|
new_config['Pushalot'] = {}
|
||||||
@@ -1092,6 +1104,7 @@ def config_write():
|
|||||||
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
|
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
|
||||||
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
|
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
|
||||||
new_config['Advanced']['journal_mode'] = JOURNAL_MODE
|
new_config['Advanced']['journal_mode'] = JOURNAL_MODE
|
||||||
|
new_config['Advanced']['verify_ssl_cert'] = int(VERIFY_SSL_CERT)
|
||||||
|
|
||||||
new_config.write()
|
new_config.write()
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -231,22 +231,32 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
|
|
||||||
if not forcefull:
|
if not forcefull:
|
||||||
|
|
||||||
|
new_release_group = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
check_release_date = rg_exists['ReleaseDate']
|
check_release_date = rg_exists['ReleaseDate']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
check_release_date = None
|
check_release_date = None
|
||||||
|
new_release_group = True
|
||||||
|
|
||||||
if check_release_date:
|
|
||||||
if check_release_date[0] is None:
|
if new_release_group:
|
||||||
|
|
||||||
|
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
||||||
|
new_releases = mb.get_new_releases(rgid,includeExtras)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
if check_release_date is None or check_release_date == u"None":
|
||||||
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
||||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||||
else:
|
else:
|
||||||
if len(check_release_date[0]) == 10:
|
if len(check_release_date) == 10:
|
||||||
release_date = check_release_date[0]
|
release_date = check_release_date
|
||||||
elif len(check_release_date[0]) == 7:
|
elif len(check_release_date) == 7:
|
||||||
release_date = check_release_date[0]+"-31"
|
release_date = check_release_date+"-31"
|
||||||
elif len(check_release_date[0]) == 4:
|
elif len(check_release_date) == 4:
|
||||||
release_date = check_release_date[0]+"-12-31"
|
release_date = check_release_date+"-12-31"
|
||||||
else:
|
else:
|
||||||
release_date = today
|
release_date = today
|
||||||
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
|
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
|
||||||
@@ -256,9 +266,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta))
|
logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta))
|
||||||
skip_log = 1
|
skip_log = 1
|
||||||
new_releases = 0
|
new_releases = 0
|
||||||
else:
|
|
||||||
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
|
||||||
new_releases = mb.get_new_releases(rgid,includeExtras)
|
|
||||||
|
|
||||||
if force_repackage == 1:
|
if force_repackage == 1:
|
||||||
new_releases = -1
|
new_releases = -1
|
||||||
@@ -496,7 +503,7 @@ def finalize_update(artistid, artistname, errors=False):
|
|||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
|
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
|
||||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid]))
|
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [artistid]))
|
||||||
#havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']]))
|
#havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']]))
|
||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artistname]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artistname]))
|
||||||
|
|
||||||
@@ -582,6 +589,7 @@ def addReleaseById(rid):
|
|||||||
controlValueDict = {"AlbumID": rgid}
|
controlValueDict = {"AlbumID": rgid}
|
||||||
|
|
||||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||||
|
"ReleaseID": rgid,
|
||||||
"ArtistName": release_dict['artist_name'],
|
"ArtistName": release_dict['artist_name'],
|
||||||
"AlbumTitle": release_dict['rg_title'],
|
"AlbumTitle": release_dict['rg_title'],
|
||||||
"AlbumASIN": release_dict['asin'],
|
"AlbumASIN": release_dict['asin'],
|
||||||
|
|||||||
+17
-4
@@ -43,19 +43,32 @@ class LogListHandler(logging.Handler):
|
|||||||
|
|
||||||
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
||||||
|
|
||||||
def initLogger(verbose=1):
|
def initLogger(console=False, verbose=False):
|
||||||
"""
|
"""
|
||||||
Setup logging for Headphones. It uses the logger instance with the name
|
Setup logging for Headphones. It uses the logger instance with the name
|
||||||
'headphones'. Three log handlers are added:
|
'headphones'. Three log handlers are added:
|
||||||
|
|
||||||
* RotatingFileHandler: for the file headphones.log
|
* RotatingFileHandler: for the file headphones.log
|
||||||
* LogListHandler: for Web UI
|
* LogListHandler: for Web UI
|
||||||
* StreamHandler: for console (if verbose > 0)
|
* StreamHandler: for console (if console)
|
||||||
|
|
||||||
|
Console logging is only enabled if console is set to True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Close and remove old handlers. This is required to reinit the loggers
|
||||||
|
# at runtime
|
||||||
|
for handler in logger.handlers[:]:
|
||||||
|
# Just make sure it is cleaned up.
|
||||||
|
if isinstance(handler, handlers.RotatingFileHandler):
|
||||||
|
handler.close()
|
||||||
|
elif isinstance(handler, logging.StreamHandler):
|
||||||
|
handler.flush()
|
||||||
|
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
|
||||||
# Configure the logger to accept all messages
|
# Configure the logger to accept all messages
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
logger.setLevel(logging.DEBUG if verbose == 2 else logging.INFO)
|
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
|
|
||||||
# Setup file logger
|
# Setup file logger
|
||||||
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
||||||
@@ -74,7 +87,7 @@ def initLogger(verbose=1):
|
|||||||
logger.addHandler(loglist_handler)
|
logger.addHandler(loglist_handler)
|
||||||
|
|
||||||
# Setup console logger
|
# Setup console logger
|
||||||
if verbose:
|
if console:
|
||||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = logging.StreamHandler()
|
||||||
console_handler.setFormatter(console_formatter)
|
console_handler.setFormatter(console_formatter)
|
||||||
|
|||||||
+28
-22
@@ -27,7 +27,7 @@ import time
|
|||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
from httplib import HTTPSConnection
|
from httplib import HTTPSConnection
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
from lib.pynma import pynma
|
||||||
import lib.oauth2 as oauth
|
import lib.oauth2 as oauth
|
||||||
import lib.pythontwitter as twitter
|
import lib.pythontwitter as twitter
|
||||||
|
|
||||||
@@ -380,34 +380,40 @@ class Plex:
|
|||||||
logger.warn('Error sending notification request to Plex Media Server')
|
logger.warn('Error sending notification request to Plex Media Server')
|
||||||
|
|
||||||
class NMA:
|
class NMA:
|
||||||
|
def notify(self, artist=None, album=None, snatched=None):
|
||||||
|
title = 'Headphones'
|
||||||
|
api = headphones.NMA_APIKEY
|
||||||
|
nma_priority = headphones.NMA_PRIORITY
|
||||||
|
|
||||||
def __init__(self):
|
logger.debug(u"NMA title: " + title)
|
||||||
|
logger.debug(u"NMA API: " + api)
|
||||||
|
logger.debug(u"NMA Priority: " + str(nma_priority))
|
||||||
|
|
||||||
self.apikey = headphones.NMA_APIKEY
|
if snatched:
|
||||||
self.priority = headphones.NMA_PRIORITY
|
event = snatched + " snatched!"
|
||||||
|
message = "Headphones has snatched: " + snatched
|
||||||
def _send(self, data):
|
|
||||||
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
|
|
||||||
|
|
||||||
def notify(self, artist=None, album=None, snatched_nzb=None):
|
|
||||||
|
|
||||||
apikey = self.apikey
|
|
||||||
priority = self.priority
|
|
||||||
|
|
||||||
if snatched_nzb:
|
|
||||||
event = snatched_nzb + " snatched!"
|
|
||||||
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
|
|
||||||
else:
|
else:
|
||||||
event = artist + ' - ' + album + ' complete!'
|
event = artist + ' - ' + album + ' complete!'
|
||||||
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||||
|
|
||||||
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
|
logger.debug(u"NMA event: " + event)
|
||||||
|
logger.debug(u"NMA message: " + message)
|
||||||
|
|
||||||
logger.info('Sending notification request to NotifyMyAndroid')
|
batch = False
|
||||||
request = self._send(data)
|
|
||||||
|
|
||||||
if not request:
|
p = pynma.PyNMA()
|
||||||
logger.warn('Error sending notification request to NotifyMyAndroid')
|
keys = api.split(',')
|
||||||
|
p.addkey(keys)
|
||||||
|
|
||||||
|
if len(keys) > 1: batch = True
|
||||||
|
|
||||||
|
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
|
||||||
|
|
||||||
|
if not response[api][u'code'] == u'200':
|
||||||
|
logger.error(u'Could not send notification to NotifyMyAndroid')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
class PUSHBULLET:
|
class PUSHBULLET:
|
||||||
|
|
||||||
|
|||||||
+15
-2
@@ -5,6 +5,7 @@ from bs4 import BeautifulSoup
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import feedparser
|
import feedparser
|
||||||
|
import headphones
|
||||||
|
|
||||||
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs):
|
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -17,9 +18,13 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
|||||||
if whitelist_status_code and type(whitelist_status_code) != list:
|
if whitelist_status_code and type(whitelist_status_code) != list:
|
||||||
whitelist_status_code = [whitelist_status_code]
|
whitelist_status_code = [whitelist_status_code]
|
||||||
|
|
||||||
|
# Disable verification of SSL certificates if requested. Note: this could
|
||||||
|
# pose a security issue!
|
||||||
|
kwargs["verify"] = headphones.VERIFY_SSL_CERT
|
||||||
|
|
||||||
# Map method to the request.XXX method. This is a simple hack, but it allows
|
# Map method to the request.XXX method. This is a simple hack, but it allows
|
||||||
# requests to apply more magic per method. See lib/requests/api.py.
|
# requests to apply more magic per method. See lib/requests/api.py.
|
||||||
request_method = getattr(requests, method)
|
request_method = getattr(requests, method.lower())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Request the URL
|
# Request the URL
|
||||||
@@ -45,7 +50,15 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
|||||||
logger.error("Request timed out.")
|
logger.error("Request timed out.")
|
||||||
except requests.HTTPError, e:
|
except requests.HTTPError, e:
|
||||||
if e.response is not None:
|
if e.response is not None:
|
||||||
logger.error("Request raise HTTP error with status code: %d", e.response.status_code)
|
if e.response.status_code >= 500:
|
||||||
|
cause = "remote server error"
|
||||||
|
elif e.response.status_code >= 400:
|
||||||
|
cause = "local request error"
|
||||||
|
else:
|
||||||
|
# I don't think we will end up here, but for completeness
|
||||||
|
cause = "unknown"
|
||||||
|
|
||||||
|
logger.error("Request raise HTTP error with status code %d (%s).", e.response.status_code, cause)
|
||||||
else:
|
else:
|
||||||
logger.error("Request raised HTTP error.")
|
logger.error("Request raised HTTP error.")
|
||||||
except requests.RequestException, e:
|
except requests.RequestException, e:
|
||||||
|
|||||||
+70
-13
@@ -21,6 +21,8 @@ from lib.pygazelle import encoding as gazelleencoding
|
|||||||
from lib.pygazelle import format as gazelleformat
|
from lib.pygazelle import format as gazelleformat
|
||||||
from lib.pygazelle import media as gazellemedia
|
from lib.pygazelle import media as gazellemedia
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
|
from base64 import b16encode, b32decode
|
||||||
|
from hashlib import sha1
|
||||||
|
|
||||||
import os, re, time
|
import os, re, time
|
||||||
import string
|
import string
|
||||||
@@ -28,12 +30,13 @@ import shutil
|
|||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
from headphones.common import USER_AGENT
|
from headphones.common import USER_AGENT
|
||||||
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
||||||
from headphones import utorrent, transmission, notifiers
|
from headphones import utorrent, transmission, notifiers
|
||||||
|
|
||||||
import lib.bencode as bencode
|
from lib.bencode import bencode as bencode, bdecode
|
||||||
|
|
||||||
import headphones.searcher_rutracker as rutrackersearch
|
import headphones.searcher_rutracker as rutrackersearch
|
||||||
rutracker = rutrackersearch.Rutracker()
|
rutracker = rutrackersearch.Rutracker()
|
||||||
@@ -148,11 +151,20 @@ def sort_search_results(resultlist, album, new):
|
|||||||
|
|
||||||
# Add a priority if it has any of the preferred words
|
# Add a priority if it has any of the preferred words
|
||||||
temp_list = []
|
temp_list = []
|
||||||
|
preferred_words = None
|
||||||
|
if headphones.PREFERRED_WORDS:
|
||||||
|
preferred_words = helpers.split_string(headphones.PREFERRED_WORDS)
|
||||||
for result in resultlist:
|
for result in resultlist:
|
||||||
if headphones.PREFERRED_WORDS and any(word.lower() in result[0].lower() for word in helpers.split_string(headphones.PREFERRED_WORDS)):
|
priority = 0
|
||||||
temp_list.append((result[0],result[1],result[2],result[3],result[4],1))
|
if preferred_words:
|
||||||
else:
|
if any(word.lower() in result[0].lower() for word in preferred_words):
|
||||||
temp_list.append((result[0],result[1],result[2],result[3],result[4],0))
|
priority = 1
|
||||||
|
# add a search provider priority (weighted based on position)
|
||||||
|
i = next((i for i, word in enumerate(preferred_words) if word in result[3].lower()), None)
|
||||||
|
if i != None:
|
||||||
|
priority += round((len(preferred_words) - i) / float(len(preferred_words)),2)
|
||||||
|
|
||||||
|
temp_list.append((result[0],result[1],result[2],result[3],result[4],priority))
|
||||||
|
|
||||||
resultlist = temp_list
|
resultlist = temp_list
|
||||||
|
|
||||||
@@ -215,6 +227,34 @@ def sort_search_results(resultlist, album, new):
|
|||||||
|
|
||||||
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
||||||
|
|
||||||
|
# lossless - ignore results if target size outside bitrate range
|
||||||
|
elif headphones.PREFERRED_QUALITY == 3 and (headphones.LOSSLESS_BITRATE_FROM or headphones.LOSSLESS_BITRATE_TO):
|
||||||
|
|
||||||
|
finallist = []
|
||||||
|
tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [album['AlbumID']])
|
||||||
|
|
||||||
|
if len(tracks):
|
||||||
|
|
||||||
|
albumlength = sum([pair[0] for pair in tracks])
|
||||||
|
mintargetsize = 0
|
||||||
|
maxtargetsize = 0
|
||||||
|
if headphones.LOSSLESS_BITRATE_FROM:
|
||||||
|
mintargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_FROM) * 128
|
||||||
|
if headphones.LOSSLESS_BITRATE_TO:
|
||||||
|
maxtargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_TO) * 128
|
||||||
|
|
||||||
|
if mintargetsize > 0 or maxtargetsize > 0:
|
||||||
|
for i, result in reversed(list(enumerate(resultlist))):
|
||||||
|
if int(result[1]) < mintargetsize and mintargetsize > 0 or int(result[1]) > maxtargetsize and maxtargetsize > 0:
|
||||||
|
if int(result[1]) < mintargetsize:
|
||||||
|
logger.info("%s is too small for this album - not considering it. (Size: %s, Minsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(mintargetsize))
|
||||||
|
else:
|
||||||
|
logger.info("%s is too large for this album - not considering it. (Size: %s, Maxsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(maxtargetsize))
|
||||||
|
del resultlist[i]
|
||||||
|
|
||||||
|
if len (resultlist):
|
||||||
|
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
||||||
@@ -534,8 +574,7 @@ def searchNZB(album, new=False, losslessOnly=False):
|
|||||||
|
|
||||||
data = request.request_json(
|
data = request.request_json(
|
||||||
url='http://api.omgwtfnzbs.org/json/',
|
url='http://api.omgwtfnzbs.org/json/',
|
||||||
params=params, headers=headers,
|
params=params, headers=headers
|
||||||
validator=lambda x: type(x) == dict
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
@@ -659,7 +698,7 @@ def send_to_downloader(data, bestqual, album):
|
|||||||
#Open the fresh torrent file again so we can extract the proper torrent name
|
#Open the fresh torrent file again so we can extract the proper torrent name
|
||||||
#Used later in post-processing.
|
#Used later in post-processing.
|
||||||
with open(download_path, 'rb') as fp:
|
with open(download_path, 'rb') as fp:
|
||||||
torrent_info = bencode.bdecode(fp.read())
|
torrent_info = bdecode(fp.read())
|
||||||
|
|
||||||
folder_name = torrent_info['info'].get('name', '')
|
folder_name = torrent_info['info'].get('name', '')
|
||||||
logger.info('Torrent folder name: %s' % folder_name)
|
logger.info('Torrent folder name: %s' % folder_name)
|
||||||
@@ -696,7 +735,7 @@ def send_to_downloader(data, bestqual, album):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.exception("Unhandled exception")
|
logger.exception("Unhandled exception")
|
||||||
|
|
||||||
else:
|
else:# if headphones.TORRENT_DOWNLOADER == 2:
|
||||||
logger.info("Sending torrent to uTorrent")
|
logger.info("Sending torrent to uTorrent")
|
||||||
|
|
||||||
# rutracker needs cookies to be set, pass the .torrent file instead of url
|
# rutracker needs cookies to be set, pass the .torrent file instead of url
|
||||||
@@ -705,7 +744,9 @@ def send_to_downloader(data, bestqual, album):
|
|||||||
else:
|
else:
|
||||||
file_or_url = bestqual[2]
|
file_or_url = bestqual[2]
|
||||||
|
|
||||||
folder_name = utorrent.addTorrent(file_or_url)
|
_hash = CalculateTorrentHash(file_or_url, data)
|
||||||
|
|
||||||
|
folder_name = utorrent.addTorrent(file_or_url, _hash)
|
||||||
|
|
||||||
if folder_name:
|
if folder_name:
|
||||||
logger.info('Torrent folder name: %s' % folder_name)
|
logger.info('Torrent folder name: %s' % folder_name)
|
||||||
@@ -757,7 +798,7 @@ def send_to_downloader(data, bestqual, album):
|
|||||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
||||||
logger.info(u"Sending NMA notification")
|
logger.info(u"Sending NMA notification")
|
||||||
nma = notifiers.NMA()
|
nma = notifiers.NMA()
|
||||||
nma.notify(snatched_nzb=name)
|
nma.notify(snatched=name)
|
||||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
||||||
logger.info(u"Sending Pushalot notification")
|
logger.info(u"Sending Pushalot notification")
|
||||||
pushalot = notifiers.PUSHALOT()
|
pushalot = notifiers.PUSHALOT()
|
||||||
@@ -1400,8 +1441,8 @@ def preprocess(resultlist):
|
|||||||
for result in resultlist:
|
for result in resultlist:
|
||||||
|
|
||||||
if result[4] == 'torrent':
|
if result[4] == 'torrent':
|
||||||
#Get out of here if we're using Transmission or uTorrent
|
#Get out of here if we're using Transmission
|
||||||
if headphones.TORRENT_DOWNLOADER != 0:
|
if headphones.TORRENT_DOWNLOADER == 1: ## if not a magnet link still need the .torrent to generate hash... uTorrent support labeling
|
||||||
return True, result
|
return True, result
|
||||||
# get outta here if rutracker
|
# get outta here if rutracker
|
||||||
if result[3] == 'rutracker.org':
|
if result[3] == 'rutracker.org':
|
||||||
@@ -1451,3 +1492,19 @@ def preprocess(resultlist):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def CalculateTorrentHash(link, data):
|
||||||
|
|
||||||
|
if link.startswith('magnet'):
|
||||||
|
tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0]
|
||||||
|
if len(tor_hash) == 32:
|
||||||
|
tor_hash = b16encode(b32decode(tor_hash)).lower()
|
||||||
|
else:
|
||||||
|
info = bdecode(data)["info"]
|
||||||
|
tor_hash = sha1(bencode(info)).hexdigest()
|
||||||
|
|
||||||
|
logger.debug('Torrent Hash: ' + str(tor_hash))
|
||||||
|
|
||||||
|
return tor_hash
|
||||||
@@ -18,6 +18,7 @@ from tempfile import mkdtemp
|
|||||||
class Rutracker():
|
class Rutracker():
|
||||||
|
|
||||||
logged_in = False
|
logged_in = False
|
||||||
|
|
||||||
# Stores a number of login attempts to prevent recursion.
|
# Stores a number of login attempts to prevent recursion.
|
||||||
#login_counter = 0
|
#login_counter = 0
|
||||||
|
|
||||||
@@ -51,7 +52,6 @@ class Rutracker():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Check if we're logged in
|
# Check if we're logged in
|
||||||
|
|
||||||
for cookie in self.cookiejar:
|
for cookie in self.cookiejar:
|
||||||
if cookie.name == 'bb_data':
|
if cookie.name == 'bb_data':
|
||||||
self.logged_in = True
|
self.logged_in = True
|
||||||
@@ -64,7 +64,6 @@ class Rutracker():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Build search url
|
# Build search url
|
||||||
|
|
||||||
searchterm = ''
|
searchterm = ''
|
||||||
if artist != 'Various Artists':
|
if artist != 'Various Artists':
|
||||||
searchterm = artist
|
searchterm = artist
|
||||||
@@ -83,7 +82,6 @@ class Rutracker():
|
|||||||
format = '+mp3||aac'
|
format = '+mp3||aac'
|
||||||
|
|
||||||
# sort by size, descending.
|
# sort by size, descending.
|
||||||
|
|
||||||
sort = '&o=7&s=2'
|
sort = '&o=7&s=2'
|
||||||
|
|
||||||
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
||||||
@@ -111,25 +109,21 @@ class Rutracker():
|
|||||||
#logger.debug (soup.prettify())
|
#logger.debug (soup.prettify())
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
|
|
||||||
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
||||||
title = link.get_text()
|
title = link.get_text()
|
||||||
titles.append(title)
|
titles.append(title)
|
||||||
|
|
||||||
# Download URL
|
# Download URL
|
||||||
|
|
||||||
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
||||||
url = link.get('href')
|
url = link.get('href')
|
||||||
urls.append(url)
|
urls.append(url)
|
||||||
|
|
||||||
# Seeders
|
# Seeders
|
||||||
|
|
||||||
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
||||||
seeder = link.get_text()
|
seeder = link.get_text()
|
||||||
seeders.append(seeder)
|
seeders.append(seeder)
|
||||||
|
|
||||||
# Size
|
# Size
|
||||||
|
|
||||||
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
||||||
size = link.u.string
|
size = link.u.string
|
||||||
sizes.append(size)
|
sizes.append(size)
|
||||||
@@ -138,30 +132,33 @@ class Rutracker():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Combine lists
|
# Combine lists
|
||||||
|
|
||||||
torrentlist = zip(titles, urls, seeders, sizes)
|
torrentlist = zip(titles, urls, seeders, sizes)
|
||||||
|
|
||||||
# return if nothing found
|
# return if nothing found
|
||||||
|
|
||||||
if not torrentlist:
|
if not torrentlist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# get headphones track count for album, return if not found
|
# don't bother checking track counts anymore, let searcher filter instead
|
||||||
|
# leave code in just in case
|
||||||
|
check_track_count = False
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
if check_track_count:
|
||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
|
||||||
hptrackcount = len(tracks)
|
|
||||||
|
|
||||||
if not hptrackcount:
|
# get headphones track count for album, return if not found
|
||||||
logger.info('headphones track info not found, cannot compare to torrent')
|
myDB = db.DBConnection()
|
||||||
return False
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||||
|
hptrackcount = len(tracks)
|
||||||
|
|
||||||
# Return all valid entries, ignored, required words now checked in searcher.py
|
if not hptrackcount:
|
||||||
|
logger.info('headphones track info not found, cannot compare to torrent')
|
||||||
|
return False
|
||||||
|
|
||||||
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
# Return all valid entries, ignored, required words now checked in searcher.py
|
||||||
|
|
||||||
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
||||||
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
|
||||||
|
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
||||||
|
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
||||||
|
|
||||||
for torrent in torrentlist:
|
for torrent in torrentlist:
|
||||||
|
|
||||||
@@ -170,104 +167,101 @@ class Rutracker():
|
|||||||
seeders = torrent[2]
|
seeders = torrent[2]
|
||||||
size = torrent[3]
|
size = torrent[3]
|
||||||
|
|
||||||
title = returntitle.lower()
|
|
||||||
|
|
||||||
if int(size) <= maxsize and int(seeders) >= minseeders:
|
if int(size) <= maxsize and int(seeders) >= minseeders:
|
||||||
|
|
||||||
# Check torrent info
|
|
||||||
|
|
||||||
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
|
||||||
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
|
||||||
|
|
||||||
# Debug
|
|
||||||
#for cookie in self.cookiejar:
|
|
||||||
# logger.debug ('Cookie: %s' % cookie)
|
|
||||||
|
|
||||||
try:
|
|
||||||
page = self.opener.open(url)
|
|
||||||
torrent = page.read()
|
|
||||||
if torrent:
|
|
||||||
decoded = bencode.bdecode(torrent)
|
|
||||||
metainfo = decoded['info']
|
|
||||||
page.close ()
|
|
||||||
except Exception, e:
|
|
||||||
logger.error('Error getting torrent: %s' % e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# get torrent track count and check for cue
|
|
||||||
|
|
||||||
trackcount = 0
|
|
||||||
cuecount = 0
|
|
||||||
|
|
||||||
if 'files' in metainfo: # multi
|
|
||||||
for pathfile in metainfo['files']:
|
|
||||||
path = pathfile['path']
|
|
||||||
for file in path:
|
|
||||||
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
|
||||||
trackcount += 1
|
|
||||||
if '.cue' in file:
|
|
||||||
cuecount += 1
|
|
||||||
|
|
||||||
#Torrent topic page
|
#Torrent topic page
|
||||||
|
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
||||||
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
||||||
logger.debug ('torrent title: %s' % title)
|
|
||||||
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
|
||||||
logger.debug ('rutracker trackcount: %s' % trackcount)
|
|
||||||
|
|
||||||
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
# add to list
|
||||||
# This is for the case where we have a single .flac/.wav which can be split by cue
|
if not check_track_count:
|
||||||
# Not great, but shouldn't be doing this too often
|
|
||||||
|
|
||||||
totallogcount = 0
|
|
||||||
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
|
||||||
page = self.opener.open(topicurl, timeout=60)
|
|
||||||
soup = BeautifulSoup(page.read())
|
|
||||||
findtoc = soup.find_all(text='TOC of the extracted CD')
|
|
||||||
if not findtoc:
|
|
||||||
findtoc = soup.find_all(text='TOC извлечённого CD')
|
|
||||||
for toc in findtoc:
|
|
||||||
logcount = 0
|
|
||||||
for toccontent in toc.find_all_next(text=True):
|
|
||||||
cut_string = toccontent.split('|')
|
|
||||||
new_string = cut_string[0].lstrip().rstrip()
|
|
||||||
if new_string == '1' or new_string == '01':
|
|
||||||
logcount = 1
|
|
||||||
elif logcount > 0:
|
|
||||||
if new_string.isdigit():
|
|
||||||
logcount += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
totallogcount = totallogcount + logcount
|
|
||||||
|
|
||||||
if totallogcount > 0:
|
|
||||||
trackcount = totallogcount
|
|
||||||
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
|
||||||
|
|
||||||
# If torrent track count = hp track count then return torrent,
|
|
||||||
# if greater, check for deluxe/special/foreign editions
|
|
||||||
# if less, then allow if it's a single track with a cue
|
|
||||||
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if trackcount == hptrackcount:
|
|
||||||
valid = True
|
valid = True
|
||||||
elif trackcount > hptrackcount:
|
else:
|
||||||
if any(deluxe in title for deluxe in deluxelist):
|
|
||||||
|
# Check torrent info
|
||||||
|
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
#for cookie in self.cookiejar:
|
||||||
|
# logger.debug ('Cookie: %s' % cookie)
|
||||||
|
|
||||||
|
try:
|
||||||
|
page = self.opener.open(url)
|
||||||
|
torrent = page.read()
|
||||||
|
if torrent:
|
||||||
|
decoded = bencode.bdecode(torrent)
|
||||||
|
metainfo = decoded['info']
|
||||||
|
page.close ()
|
||||||
|
except Exception, e:
|
||||||
|
logger.error('Error getting torrent: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# get torrent track count and check for cue
|
||||||
|
trackcount = 0
|
||||||
|
cuecount = 0
|
||||||
|
|
||||||
|
if 'files' in metainfo: # multi
|
||||||
|
for pathfile in metainfo['files']:
|
||||||
|
path = pathfile['path']
|
||||||
|
for file in path:
|
||||||
|
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
||||||
|
trackcount += 1
|
||||||
|
if '.cue' in file:
|
||||||
|
cuecount += 1
|
||||||
|
|
||||||
|
title = returntitle.lower()
|
||||||
|
logger.debug ('torrent title: %s' % title)
|
||||||
|
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
||||||
|
logger.debug ('rutracker trackcount: %s' % trackcount)
|
||||||
|
|
||||||
|
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
||||||
|
# This is for the case where we have a single .flac/.wav which can be split by cue
|
||||||
|
# Not great, but shouldn't be doing this too often
|
||||||
|
totallogcount = 0
|
||||||
|
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
||||||
|
page = self.opener.open(topicurl, timeout=60)
|
||||||
|
soup = BeautifulSoup(page.read())
|
||||||
|
findtoc = soup.find_all(text='TOC of the extracted CD')
|
||||||
|
if not findtoc:
|
||||||
|
findtoc = soup.find_all(text='TOC извлечённого CD')
|
||||||
|
for toc in findtoc:
|
||||||
|
logcount = 0
|
||||||
|
for toccontent in toc.find_all_next(text=True):
|
||||||
|
cut_string = toccontent.split('|')
|
||||||
|
new_string = cut_string[0].lstrip().rstrip()
|
||||||
|
if new_string == '1' or new_string == '01':
|
||||||
|
logcount = 1
|
||||||
|
elif logcount > 0:
|
||||||
|
if new_string.isdigit():
|
||||||
|
logcount += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
totallogcount = totallogcount + logcount
|
||||||
|
|
||||||
|
if totallogcount > 0:
|
||||||
|
trackcount = totallogcount
|
||||||
|
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
||||||
|
|
||||||
|
# If torrent track count = hp track count then return torrent,
|
||||||
|
# if greater, check for deluxe/special/foreign editions
|
||||||
|
# if less, then allow if it's a single track with a cue
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
if trackcount == hptrackcount:
|
||||||
valid = True
|
valid = True
|
||||||
|
elif trackcount > hptrackcount:
|
||||||
|
if any(deluxe in title for deluxe in deluxelist):
|
||||||
|
valid = True
|
||||||
|
|
||||||
# Add to list
|
# Add to list
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
rulist.append((returntitle, size, topicurl))
|
rulist.append((returntitle, size, topicurl))
|
||||||
else:
|
else:
|
||||||
if topicurl:
|
if topicurl:
|
||||||
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
||||||
|
|
||||||
|
|
||||||
return rulist
|
return rulist
|
||||||
|
|
||||||
def get_torrent(self, url, savelocation=None):
|
def get_torrent(self, url, savelocation=None):
|
||||||
|
|||||||
@@ -30,7 +30,14 @@ from headphones import logger, notifiers, request
|
|||||||
|
|
||||||
def addTorrent(link):
|
def addTorrent(link):
|
||||||
method = 'torrent-add'
|
method = 'torrent-add'
|
||||||
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
|
||||||
|
if link.endswith('.torrent'):
|
||||||
|
f = open(link,'rb')
|
||||||
|
metainfo = str(base64.b64encode(f.read()))
|
||||||
|
f.close()
|
||||||
|
arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR}
|
||||||
|
else:
|
||||||
|
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
||||||
|
|
||||||
response = torrentAction(method,arguments)
|
response = torrentAction(method,arguments)
|
||||||
|
|
||||||
|
|||||||
+140
-72
@@ -13,86 +13,154 @@
|
|||||||
# 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 re
|
import urllib, urllib2, urlparse, cookielib
|
||||||
import os
|
import json, re, os, time
|
||||||
import time
|
|
||||||
import base64
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
import simplejson as json
|
from headphones import logger
|
||||||
|
|
||||||
from headphones import logger, notifiers, request
|
class utorrentclient(object):
|
||||||
|
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
|
||||||
|
|
||||||
# This is just a simple script to send torrents to transmission. The
|
def __init__(self, base_url = None, username = None, password = None,):
|
||||||
# intention is to turn this into a class where we can check the state
|
|
||||||
# of the download, set the download dir, etc.
|
host = headphones.UTORRENT_HOST
|
||||||
# TODO: Store the session id so we don't need to make 2 calls
|
if not host.startswith('http'):
|
||||||
# Store torrent id so we can check up on it
|
host = 'http://' + host
|
||||||
|
|
||||||
|
if host.endswith('/'):
|
||||||
|
host = host[:-1]
|
||||||
|
|
||||||
|
if host.endswith('/gui'):
|
||||||
|
host = host[:-4]
|
||||||
|
|
||||||
|
self.base_url = host
|
||||||
|
self.username = headphones.UTORRENT_USERNAME
|
||||||
|
self.password = headphones.UTORRENT_PASSWORD
|
||||||
|
self.opener = self._make_opener('uTorrent', self.base_url, self.username, self.password)
|
||||||
|
self.token = self._get_token()
|
||||||
|
#TODO refresh token, when necessary
|
||||||
|
|
||||||
|
def _make_opener(self, realm, base_url, username, password):
|
||||||
|
"""uTorrent API need HTTP Basic Auth and cookie support for token verify."""
|
||||||
|
auth = urllib2.HTTPBasicAuthHandler()
|
||||||
|
auth.add_password(realm=realm,uri=base_url,user=username,passwd=password)
|
||||||
|
opener = urllib2.build_opener(auth)
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
|
||||||
|
cookie_jar = cookielib.CookieJar()
|
||||||
|
cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar)
|
||||||
|
|
||||||
|
handlers = [auth, cookie_handler]
|
||||||
|
opener = urllib2.build_opener(*handlers)
|
||||||
|
return opener
|
||||||
|
|
||||||
|
def _get_token(self):
|
||||||
|
url = urlparse.urljoin(self.base_url, 'gui/token.html')
|
||||||
|
try:
|
||||||
|
response = self.opener.open(url)
|
||||||
|
except urllib2.HTTPError as err:
|
||||||
|
logger.debug('URL: ' + str(url))
|
||||||
|
logger.debug('Error getting Token. uTorrent responded with error: ' + str(err))
|
||||||
|
match = re.search(utorrentclient.TOKEN_REGEX, response.read())
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def list(self, **kwargs):
|
||||||
|
params = [('list', '1')]
|
||||||
|
params += kwargs.items()
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def add_url(self, url):
|
||||||
|
#can receive magnet or normal .torrent link
|
||||||
|
params = [('action', 'add-url'), ('s', url)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def start(self, *hashes):
|
||||||
|
params = [('action', 'start'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def stop(self, *hashes):
|
||||||
|
params = [('action', 'stop'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def pause(self, *hashes):
|
||||||
|
params = [('action', 'pause'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def forcestart(self, *hashes):
|
||||||
|
params = [('action', 'forcestart'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def getfiles(self, hash):
|
||||||
|
params = [('action', 'getfiles'), ('hash', hash)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def getprops(self, hash):
|
||||||
|
params = [('action', 'getprops'), ('hash', hash)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def setprops(self, hash, s, v):
|
||||||
|
params = [('action', 'setprops'), ('hash', hash), ("s", s), ("v", v)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def setprio(self, hash, priority, *files):
|
||||||
|
params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))]
|
||||||
|
for file_index in files:
|
||||||
|
params.append(('f', str(file_index)))
|
||||||
|
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def _action(self, params, body=None, content_type=None):
|
||||||
|
url = self.base_url + '/gui/' + '?token=' + self.token + '&' + urllib.urlencode(params)
|
||||||
|
request = urllib2.Request(url)
|
||||||
|
|
||||||
|
if body:
|
||||||
|
request.add_data(body)
|
||||||
|
request.add_header('Content-length', len(body))
|
||||||
|
if content_type:
|
||||||
|
request.add_header('Content-type', content_type)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.opener.open(request)
|
||||||
|
return response.code, json.loads(response.read())
|
||||||
|
except urllib2.HTTPError as err:
|
||||||
|
logger.debug('URL: ' + str(url))
|
||||||
|
logger.debug('uTorrent webUI raised the following error: ' + str(err))
|
||||||
|
|
||||||
|
|
||||||
def addTorrent(link):
|
def labelTorrent(hash):
|
||||||
|
|
||||||
host = headphones.UTORRENT_HOST
|
|
||||||
username = headphones.UTORRENT_USERNAME
|
|
||||||
password = headphones.UTORRENT_PASSWORD
|
|
||||||
label = headphones.UTORRENT_LABEL
|
label = headphones.UTORRENT_LABEL
|
||||||
token = ''
|
uTorrentClient = utorrentclient()
|
||||||
|
settinglabel = True
|
||||||
|
while settinglabel:
|
||||||
|
torrentList = uTorrentClient.list()
|
||||||
|
for torrent in torrentList[1].get('torrents'):
|
||||||
|
if (torrent[0].lower() == hash):
|
||||||
|
uTorrentClient.setprops(hash,'label',label)
|
||||||
|
settinglabel = False
|
||||||
|
return True
|
||||||
|
|
||||||
if not host.startswith('http'):
|
|
||||||
host = 'http://' + host
|
|
||||||
|
|
||||||
if host.endswith('/'):
|
def dirTorrent(hash):
|
||||||
host = host[:-1]
|
uTorrentClient = utorrentclient()
|
||||||
|
torrentList = uTorrentClient.list()
|
||||||
|
for torrent in torrentList[1].get('torrents'):
|
||||||
|
if (torrent[0].lower() == hash):
|
||||||
|
return torrent[26]
|
||||||
|
return False
|
||||||
|
|
||||||
if host.endswith('/gui'):
|
|
||||||
host = host + '/'
|
|
||||||
else:
|
|
||||||
host = host + '/gui/'
|
|
||||||
|
|
||||||
# Retrieve session id
|
def addTorrent(link, hash):
|
||||||
auth = (username, password) if username and password else None
|
uTorrentClient = utorrentclient()
|
||||||
token_request = request.request_response(host + 'token.html', auth=auth)
|
uTorrentClient.add_url(link)
|
||||||
|
labelTorrent(hash)
|
||||||
token = re.findall('<div.*?>(.*?)</', token_request.content)[0]
|
return dirTorrent(hash)
|
||||||
guid = token_request.cookies['GUID']
|
|
||||||
|
|
||||||
cookies = dict(GUID = guid)
|
|
||||||
|
|
||||||
if link.startswith("magnet") or link.startswith("http") or link.endswith(".torrent"):
|
|
||||||
params = {'action':'add-url', 's':link, 'token':token}
|
|
||||||
response = request.request_json(host, params=params, auth=auth, cookies=cookies)
|
|
||||||
else:
|
|
||||||
params = {'action':'add-file', 'token':token}
|
|
||||||
files = {'torrent_file':{'music.torrent' : link}}
|
|
||||||
response = request.request_json(host, method="post", params=params, files=files, auth=auth, cookies=cookies)
|
|
||||||
if not response:
|
|
||||||
logger.error("Error sending torrent to uTorrent")
|
|
||||||
return
|
|
||||||
|
|
||||||
if link.startswith('magnet'):
|
|
||||||
tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0]
|
|
||||||
if len(tor_hash) == 32:
|
|
||||||
tor_hash = b16encode(b32decode(tor_hash)).lower()
|
|
||||||
else:
|
|
||||||
info = bdecode(link.content)["info"]
|
|
||||||
tor_hash = sha1(bencode(info)).hexdigest()
|
|
||||||
|
|
||||||
params = {'action':'setprops', 'hash':tor_hash,'s':'label', 'v':label, 'token':token}
|
|
||||||
response = request.request_json(host, params=params, auth=auth, cookies=cookies)
|
|
||||||
if not response:
|
|
||||||
logger.error("Error setting torrent label in uTorrent")
|
|
||||||
return
|
|
||||||
|
|
||||||
# folder info can probably be cleaned up with getprops
|
|
||||||
folder = None
|
|
||||||
|
|
||||||
params = {'list':'1', 'token':token}
|
|
||||||
response = request.request_json(host, params=params, auth=auth, cookies=cookies)
|
|
||||||
if not response:
|
|
||||||
logger.error("Error getting torrent information from uTorrent")
|
|
||||||
return
|
|
||||||
|
|
||||||
for torrent in response['torrents']:
|
|
||||||
folder = os.path.basename(torrent[26])
|
|
||||||
|
|
||||||
return folder
|
|
||||||
|
|||||||
+14
-2
@@ -736,6 +736,14 @@ class WebInterface(object):
|
|||||||
raise cherrypy.HTTPRedirect("logs")
|
raise cherrypy.HTTPRedirect("logs")
|
||||||
clearLogs.exposed = True
|
clearLogs.exposed = True
|
||||||
|
|
||||||
|
def toggleVerbose(self):
|
||||||
|
headphones.VERBOSE = not headphones.VERBOSE
|
||||||
|
logger.initLogger(not headphones.QUIET, headphones.VERBOSE)
|
||||||
|
logger.info("Verbose toggled, set to %s", headphones.VERBOSE)
|
||||||
|
logger.debug("If you read this message, debug logging is available")
|
||||||
|
raise cherrypy.HTTPRedirect("logs")
|
||||||
|
toggleVerbose.exposed = True
|
||||||
|
|
||||||
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
||||||
|
|
||||||
iDisplayStart = int(iDisplayStart)
|
iDisplayStart = int(iDisplayStart)
|
||||||
@@ -1000,6 +1008,8 @@ class WebInterface(object):
|
|||||||
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
|
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
|
||||||
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
||||||
"detect_bitrate" : checked(headphones.DETECT_BITRATE),
|
"detect_bitrate" : checked(headphones.DETECT_BITRATE),
|
||||||
|
"lossless_bitrate_from" : headphones.LOSSLESS_BITRATE_FROM,
|
||||||
|
"lossless_bitrate_to" : headphones.LOSSLESS_BITRATE_TO,
|
||||||
"move_files" : checked(headphones.MOVE_FILES),
|
"move_files" : checked(headphones.MOVE_FILES),
|
||||||
"rename_files" : checked(headphones.RENAME_FILES),
|
"rename_files" : checked(headphones.RENAME_FILES),
|
||||||
"correct_metadata" : checked(headphones.CORRECT_METADATA),
|
"correct_metadata" : checked(headphones.CORRECT_METADATA),
|
||||||
@@ -1136,8 +1146,8 @@ class WebInterface(object):
|
|||||||
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
|
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
|
||||||
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
|
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
|
||||||
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
|
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
|
||||||
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None,
|
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, lossless_bitrate_from=None, lossless_bitrate_to=None, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None,
|
||||||
plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
|
file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
|
||||||
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ):
|
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ):
|
||||||
|
|
||||||
headphones.HTTP_HOST = http_host
|
headphones.HTTP_HOST = http_host
|
||||||
@@ -1216,6 +1226,8 @@ class WebInterface(object):
|
|||||||
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
|
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
|
||||||
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
|
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
|
||||||
headphones.DETECT_BITRATE = detect_bitrate
|
headphones.DETECT_BITRATE = detect_bitrate
|
||||||
|
headphones.LOSSLESS_BITRATE_FROM = lossless_bitrate_from
|
||||||
|
headphones.LOSSLESS_BITRATE_TO = lossless_bitrate_to
|
||||||
headphones.MOVE_FILES = move_files
|
headphones.MOVE_FILES = move_files
|
||||||
headphones.CORRECT_METADATA = correct_metadata
|
headphones.CORRECT_METADATA = correct_metadata
|
||||||
headphones.RENAME_FILES = rename_files
|
headphones.RENAME_FILES = rename_files
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from pynma import PyNMA
|
||||||
|
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
from httplib import HTTPSConnection
|
||||||
|
from urllib import urlencode
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
API_SERVER = 'nma.usk.bz'
|
||||||
|
ADD_PATH = '/publicapi/notify'
|
||||||
|
|
||||||
|
USER_AGENT="PyNMA/v%s"%__version__
|
||||||
|
|
||||||
|
def uniq_preserve(seq): # Dave Kirby
|
||||||
|
# Order preserving
|
||||||
|
seen = set()
|
||||||
|
return [x for x in seq if x not in seen and not seen.add(x)]
|
||||||
|
|
||||||
|
def uniq(seq):
|
||||||
|
# Not order preserving
|
||||||
|
return {}.fromkeys(seq).keys()
|
||||||
|
|
||||||
|
class PyNMA(object):
|
||||||
|
"""PyNMA(apikey=[], developerkey=None)
|
||||||
|
takes 2 optional arguments:
|
||||||
|
- (opt) apykey: might me a string containing 1 key or an array of keys
|
||||||
|
- (opt) developerkey: where you can store your developer key
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, apikey=[], developerkey=None):
|
||||||
|
self._developerkey = None
|
||||||
|
self.developerkey(developerkey)
|
||||||
|
if apikey:
|
||||||
|
if type(apikey) == str:
|
||||||
|
apikey = [apikey]
|
||||||
|
self._apikey = uniq(apikey)
|
||||||
|
|
||||||
|
def addkey(self, key):
|
||||||
|
"Add a key (register ?)"
|
||||||
|
if type(key) == str:
|
||||||
|
if not key in self._apikey:
|
||||||
|
self._apikey.append(key)
|
||||||
|
elif type(key) == list:
|
||||||
|
for k in key:
|
||||||
|
if not k in self._apikey:
|
||||||
|
self._apikey.append(k)
|
||||||
|
|
||||||
|
def delkey(self, key):
|
||||||
|
"Removes a key (unregister ?)"
|
||||||
|
if type(key) == str:
|
||||||
|
if key in self._apikey:
|
||||||
|
self._apikey.remove(key)
|
||||||
|
elif type(key) == list:
|
||||||
|
for k in key:
|
||||||
|
if key in self._apikey:
|
||||||
|
self._apikey.remove(k)
|
||||||
|
|
||||||
|
def developerkey(self, developerkey):
|
||||||
|
"Sets the developer key (and check it has the good length)"
|
||||||
|
if type(developerkey) == str and len(developerkey) == 48:
|
||||||
|
self._developerkey = developerkey
|
||||||
|
|
||||||
|
def push(self, application="", event="", description="", url="", priority=0, batch_mode=False):
|
||||||
|
"""Pushes a message on the registered API keys.
|
||||||
|
takes 5 arguments:
|
||||||
|
- (req) application: application name [256]
|
||||||
|
- (req) event: event name [1000]
|
||||||
|
- (req) description: description [10000]
|
||||||
|
- (opt) url: url [512]
|
||||||
|
- (opt) priority: from -2 (lowest) to 2 (highest) (def:0)
|
||||||
|
- (opt) batch_mode: call API 5 by 5 (def:False)
|
||||||
|
|
||||||
|
Warning: using batch_mode will return error only if all API keys are bad
|
||||||
|
cf: http://nma.usk.bz/api.php
|
||||||
|
"""
|
||||||
|
datas = {
|
||||||
|
'application': application[:256].encode('utf8'),
|
||||||
|
'event': event[:1024].encode('utf8'),
|
||||||
|
'description': description[:10000].encode('utf8'),
|
||||||
|
'priority': priority
|
||||||
|
}
|
||||||
|
|
||||||
|
if url:
|
||||||
|
datas['url'] = url[:512]
|
||||||
|
|
||||||
|
if self._developerkey:
|
||||||
|
datas['developerkey'] = self._developerkey
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
if not batch_mode:
|
||||||
|
for key in self._apikey:
|
||||||
|
datas['apikey'] = key
|
||||||
|
res = self.callapi('POST', ADD_PATH, datas)
|
||||||
|
results[key] = res
|
||||||
|
else:
|
||||||
|
for i in range(0, len(self._apikey), 5):
|
||||||
|
datas['apikey'] = ",".join(self._apikey[i:i+5])
|
||||||
|
res = self.callapi('POST', ADD_PATH, datas)
|
||||||
|
results[datas['apikey']] = res
|
||||||
|
return results
|
||||||
|
|
||||||
|
def callapi(self, method, path, args):
|
||||||
|
headers = { 'User-Agent': USER_AGENT }
|
||||||
|
if method == "POST":
|
||||||
|
headers['Content-type'] = "application/x-www-form-urlencoded"
|
||||||
|
http_handler = HTTPSConnection(API_SERVER)
|
||||||
|
http_handler.request(method, path, urlencode(args), headers)
|
||||||
|
resp = http_handler.getresponse()
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self._parse_reponse(resp.read())
|
||||||
|
except Exception, e:
|
||||||
|
res = {'type': "pynmaerror",
|
||||||
|
'code': 600,
|
||||||
|
'message': str(e)
|
||||||
|
}
|
||||||
|
pass
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _parse_reponse(self, response):
|
||||||
|
root = parseString(response).firstChild
|
||||||
|
for elem in root.childNodes:
|
||||||
|
if elem.nodeType == elem.TEXT_NODE: continue
|
||||||
|
if elem.tagName == 'success':
|
||||||
|
res = dict(elem.attributes.items())
|
||||||
|
res['message'] = ""
|
||||||
|
res['type'] = elem.tagName
|
||||||
|
return res
|
||||||
|
if elem.tagName == 'error':
|
||||||
|
res = dict(elem.attributes.items())
|
||||||
|
res['message'] = elem.firstChild.nodeValue
|
||||||
|
res['type'] = elem.tagName
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user