Merge branch 'develop'
|
Before Width: | Height: | Size: 612 B After Width: | Height: | Size: 576 B |
|
Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 601 B |
|
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 817 B |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 206 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 125 B |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 253 B After Width: | Height: | Size: 114 B |
|
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 120 B |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -1372,6 +1372,23 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<div class="row checkbox left">
|
||||
<input type="checkbox" class="bigcheck" name="join_enabled" id="join" value="1" ${config['join_enabled']} /><label for="join"><span class="option">Join</span></label>
|
||||
</div>
|
||||
<div id="joinoptions">
|
||||
<div class="row">
|
||||
<label>Join API Key</label><input type="text" name="join_apikey" value="${config['join_apikey']}" size="50">
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Device ID(s)</label><input type="text" name="join_deviceid" value="${config['join_deviceid']}" size="50"><small>Comma separated list. Leave blank to send to all devices</small>
|
||||
</div>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="join_onsnatch" value="1" ${config['join_onsnatch']} /><label>Notify on snatch?</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -2168,6 +2185,27 @@
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#join").is(":checked"))
|
||||
{
|
||||
$("#joinoptions").show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#joinoptions").hide();
|
||||
}
|
||||
|
||||
|
||||
$("#join").click(function(){
|
||||
if ($("#join").is(":checked"))
|
||||
{
|
||||
$("#joinoptions").slideDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#joinoptions").slideUp();
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#twitter").is(":checked"))
|
||||
{
|
||||
$("#twitteroptions").show();
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 860 B |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 89 B |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 93 B |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 205 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 860 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 251 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 335 B |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 228 B |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 688 B |
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 688 B |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 87 B |
|
Before Width: | Height: | Size: 135 B After Width: | Height: | Size: 101 B |
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 83 B |
|
Before Width: | Height: | Size: 838 B After Width: | Height: | Size: 56 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 116 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1003 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1010 B |
|
Before Width: | Height: | Size: 107 B After Width: | Height: | Size: 106 B |
|
Before Width: | Height: | Size: 106 B After Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 111 B After Width: | Height: | Size: 107 B |
|
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 103 B After Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 158 B |
|
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
@@ -621,8 +621,8 @@ def dbcheck():
|
||||
c.execute('UPDATE snatched SET TorrentHash = FolderName WHERE Status LIKE "Seed_%"')
|
||||
|
||||
# One off script to set CleanName to lower case
|
||||
clean_name_mixed = c.execute('SELECT CleanName FROM have ORDER BY Date Desc').fetchone()[0]
|
||||
if clean_name_mixed != clean_name_mixed.lower():
|
||||
clean_name_mixed = c.execute('SELECT CleanName FROM have ORDER BY Date Desc').fetchone()
|
||||
if clean_name_mixed and clean_name_mixed[0] != clean_name_mixed[0].lower():
|
||||
logger.info("Updating track clean name, this could take some time...")
|
||||
c.execute('UPDATE tracks SET CleanName = LOWER(CleanName) WHERE LOWER(CleanName) != CleanName')
|
||||
c.execute('UPDATE alltracks SET CleanName = LOWER(CleanName) WHERE LOWER(CleanName) != CleanName')
|
||||
|
||||
@@ -145,6 +145,10 @@ _CONFIG_DEFINITIONS = {
|
||||
'IGNORED_FILES': (list, 'Advanced', []), # path
|
||||
'INCLUDE_EXTRAS': (int, 'General', 0),
|
||||
'INTERFACE': (str, 'General', 'default'),
|
||||
'JOIN_APIKEY': (str, 'Join', ''),
|
||||
'JOIN_DEVICEID': (str, 'Join', ''),
|
||||
'JOIN_ENABLED': (int, 'Join', 0),
|
||||
'JOIN_ONSNATCH': (int, 'Join', 0),
|
||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
||||
'KAT': (int, 'Kat', 0),
|
||||
'KAT_PROXY_URL': (str, 'Kat', ''),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from urllib import urlencode
|
||||
from urllib import urlencode, quote_plus
|
||||
import urllib
|
||||
import subprocess
|
||||
import json
|
||||
@@ -148,7 +148,9 @@ class PROWL(object):
|
||||
|
||||
http_handler.request("POST",
|
||||
"/publicapi/add",
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
headers={
|
||||
'Content-type':
|
||||
"application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
@@ -203,28 +205,34 @@ class XBMC(object):
|
||||
url = host + '/xbmcCmds/xbmcHttp/?' + url_command
|
||||
|
||||
if self.password:
|
||||
return request.request_content(url, auth=(self.username, self.password))
|
||||
return request.request_content(url,
|
||||
auth=(self.username, self.password))
|
||||
else:
|
||||
return request.request_content(url)
|
||||
|
||||
def _sendjson(self, host, method, params={}):
|
||||
data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
|
||||
data = [
|
||||
{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
url = host + '/jsonrpc'
|
||||
|
||||
if self.password:
|
||||
response = request.request_json(url, method="post", data=json.dumps(data),
|
||||
headers=headers, auth=(self.username, self.password))
|
||||
response = request.request_json(
|
||||
url, method="post",
|
||||
data=json.dumps(data),
|
||||
headers=headers, auth=(
|
||||
self.username, self.password))
|
||||
else:
|
||||
response = request.request_json(url, method="post", data=json.dumps(data),
|
||||
response = request.request_json(url, method="post",
|
||||
data=json.dumps(data),
|
||||
headers=headers)
|
||||
|
||||
if response:
|
||||
return response[0]['result']
|
||||
|
||||
def update(self):
|
||||
# From what I read you can't update the music library on a per directory or per path basis
|
||||
# so need to update the whole thing
|
||||
# From what I read you can't update the music library on a per
|
||||
# directory or per path basis so need to update the whole thing
|
||||
|
||||
hosts = [x.strip() for x in self.hosts.split(',')]
|
||||
|
||||
@@ -247,18 +255,23 @@ class XBMC(object):
|
||||
logger.info('Sending notification command to XMBC @ ' + host)
|
||||
try:
|
||||
version = self._sendjson(host, 'Application.GetProperties',
|
||||
{'properties': ['version']})['version']['major']
|
||||
{'properties': ['version']})[
|
||||
'version']['major']
|
||||
|
||||
if version < 12: # Eden
|
||||
notification = header + "," + message + "," + time + "," + albumartpath
|
||||
notification = header + "," + message + "," + time + \
|
||||
"," + albumartpath
|
||||
notifycommand = {'command': 'ExecBuiltIn',
|
||||
'parameter': 'Notification(' + notification + ')'}
|
||||
'parameter': 'Notification(' +
|
||||
notification + ')'}
|
||||
request = self._sendhttp(host, notifycommand)
|
||||
|
||||
else: # Frodo
|
||||
params = {'title': header, 'message': message, 'displaytime': int(time),
|
||||
params = {'title': header, 'message': message,
|
||||
'displaytime': int(time),
|
||||
'image': albumartpath}
|
||||
request = self._sendjson(host, 'GUI.ShowNotification', params)
|
||||
request = self._sendjson(host, 'GUI.ShowNotification',
|
||||
params)
|
||||
|
||||
if not request:
|
||||
raise Exception
|
||||
@@ -323,22 +336,28 @@ class Plex(object):
|
||||
url = host + '/xbmcCmds/xbmcHttp/?' + command
|
||||
|
||||
if self.password:
|
||||
response = request.request_response(url, auth=(self.username, self.password))
|
||||
response = request.request_response(url, auth=(
|
||||
self.username, self.password))
|
||||
else:
|
||||
response = request.request_response(url)
|
||||
|
||||
return response
|
||||
|
||||
def _sendjson(self, host, method, params={}):
|
||||
data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
|
||||
data = [
|
||||
{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
url = host + '/jsonrpc'
|
||||
|
||||
if self.password:
|
||||
response = request.request_json(url, method="post", data=json.dumps(data),
|
||||
headers=headers, auth=(self.username, self.password))
|
||||
response = request.request_json(
|
||||
url, method="post",
|
||||
data=json.dumps(data),
|
||||
headers=headers, auth=(
|
||||
self.username, self.password))
|
||||
else:
|
||||
response = request.request_json(url, method="post", data=json.dumps(data),
|
||||
response = request.request_json(url, method="post",
|
||||
data=json.dumps(data),
|
||||
headers=headers)
|
||||
|
||||
if response:
|
||||
@@ -346,13 +365,14 @@ class Plex(object):
|
||||
|
||||
def update(self):
|
||||
|
||||
# From what I read you can't update the music library on a per directory or per path basis
|
||||
# so need to update the whole thing
|
||||
# From what I read you can't update the music library on a per
|
||||
# directory or per path basis so need to update the whole thing
|
||||
|
||||
hosts = [x.strip() for x in self.server_hosts.split(',')]
|
||||
|
||||
for host in hosts:
|
||||
logger.info('Sending library update command to Plex Media Server@ ' + host)
|
||||
logger.info(
|
||||
'Sending library update command to Plex Media Server@ ' + host)
|
||||
url = "%s/library/sections" % host
|
||||
if self.token:
|
||||
params = {'X-Plex-Token': self.token}
|
||||
@@ -369,7 +389,8 @@ class Plex(object):
|
||||
|
||||
for s in sections:
|
||||
if s.getAttribute('type') == "artist":
|
||||
url = "%s/library/sections/%s/refresh" % (host, s.getAttribute('key'))
|
||||
url = "%s/library/sections/%s/refresh" % (
|
||||
host, s.getAttribute('key'))
|
||||
request.request_response(url, params=params)
|
||||
|
||||
def notify(self, artist, album, albumartpath):
|
||||
@@ -381,27 +402,35 @@ class Plex(object):
|
||||
time = "3000" # in ms
|
||||
|
||||
for host in hosts:
|
||||
logger.info('Sending notification command to Plex client @ ' + host)
|
||||
logger.info(
|
||||
'Sending notification command to Plex client @ ' + host)
|
||||
try:
|
||||
version = self._sendjson(host, 'Application.GetProperties',
|
||||
{'properties': ['version']})['version']['major']
|
||||
{'properties': ['version']})[
|
||||
'version']['major']
|
||||
|
||||
if version < 12: # Eden
|
||||
notification = header + "," + message + "," + time + "," + albumartpath
|
||||
notification = header + "," + message + "," + time + \
|
||||
"," + albumartpath
|
||||
notifycommand = {'command': 'ExecBuiltIn',
|
||||
'parameter': 'Notification(' + notification + ')'}
|
||||
'parameter': 'Notification(' +
|
||||
notification + ')'}
|
||||
request = self._sendhttp(host, notifycommand)
|
||||
|
||||
else: # Frodo
|
||||
params = {'title': header, 'message': message, 'displaytime': int(time),
|
||||
params = {'title': header, 'message': message,
|
||||
'displaytime': int(time),
|
||||
'image': albumartpath}
|
||||
request = self._sendjson(host, 'GUI.ShowNotification', params)
|
||||
request = self._sendjson(host, 'GUI.ShowNotification',
|
||||
params)
|
||||
|
||||
if not request:
|
||||
raise Exception
|
||||
|
||||
except Exception:
|
||||
logger.error('Error sending notification request to Plex client @ ' + host)
|
||||
logger.error(
|
||||
'Error sending notification request to Plex client @ ' +
|
||||
host)
|
||||
|
||||
|
||||
class NMA(object):
|
||||
@@ -419,7 +448,8 @@ class NMA(object):
|
||||
message = "Headphones has snatched: " + snatched
|
||||
else:
|
||||
event = artist + ' - ' + album + ' complete!'
|
||||
message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||
message = "Headphones has downloaded and postprocessed: " + \
|
||||
artist + ' [' + album + ']'
|
||||
|
||||
logger.debug(u"NMA event: " + event)
|
||||
logger.debug(u"NMA message: " + message)
|
||||
@@ -433,7 +463,8 @@ class NMA(object):
|
||||
if len(keys) > 1:
|
||||
batch = True
|
||||
|
||||
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
|
||||
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')
|
||||
@@ -461,9 +492,11 @@ class PUSHBULLET(object):
|
||||
data['device_iden'] = self.deviceid
|
||||
|
||||
headers = {'Content-type': "application/json",
|
||||
'Authorization': 'Bearer ' + headphones.CONFIG.PUSHBULLET_APIKEY}
|
||||
'Authorization': 'Bearer ' +
|
||||
headphones.CONFIG.PUSHBULLET_APIKEY}
|
||||
|
||||
response = request.request_json(url, method="post", headers=headers, data=json.dumps(data))
|
||||
response = request.request_json(url, method="post", headers=headers,
|
||||
data=json.dumps(data))
|
||||
|
||||
if response:
|
||||
logger.info(u"PushBullet notifications sent.")
|
||||
@@ -492,7 +525,9 @@ class PUSHALOT(object):
|
||||
|
||||
http_handler.request("POST",
|
||||
"/api/sendmessage",
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
headers={
|
||||
'Content-type':
|
||||
"application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
@@ -512,6 +547,49 @@ class PUSHALOT(object):
|
||||
return False
|
||||
|
||||
|
||||
class JOIN(object):
|
||||
def __init__(self):
|
||||
|
||||
self.enabled = headphones.CONFIG.JOIN_ENABLED
|
||||
self.apikey = headphones.CONFIG.JOIN_APIKEY
|
||||
self.deviceid = headphones.CONFIG.JOIN_DEVICEID
|
||||
self.url = 'https://joinjoaomgcd.appspot.com/_ah/' \
|
||||
'api/messaging/v1/sendPush?apikey={apikey}' \
|
||||
'&title={title}&text={text}' \
|
||||
'&icon={icon}'
|
||||
|
||||
def notify(self, message, event):
|
||||
if not headphones.CONFIG.JOIN_ENABLED or \
|
||||
not headphones.CONFIG.JOIN_APIKEY:
|
||||
return
|
||||
|
||||
icon = "https://cdn.rawgit.com/Headphones/" \
|
||||
"headphones/develop/data/images/headphoneslogo.png"
|
||||
|
||||
if not self.deviceid:
|
||||
self.deviceid = "group.all"
|
||||
l = [x.strip() for x in self.deviceid.split(',')]
|
||||
if len(l) > 1:
|
||||
self.url += '&deviceIds={deviceid}'
|
||||
else:
|
||||
self.url += '&deviceId={deviceid}'
|
||||
|
||||
response = urllib2.urlopen(self.url.format(apikey=self.apikey,
|
||||
title=quote_plus(event),
|
||||
text=quote_plus(
|
||||
message.encode(
|
||||
"utf-8")),
|
||||
icon=icon,
|
||||
deviceid=self.deviceid))
|
||||
|
||||
if response:
|
||||
logger.info(u"Join notifications sent.")
|
||||
return True
|
||||
else:
|
||||
logger.error(u"Join notification failed.")
|
||||
return False
|
||||
|
||||
|
||||
class Synoindex(object):
|
||||
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
||||
self.util_loc = util_loc
|
||||
@@ -524,7 +602,8 @@ class Synoindex(object):
|
||||
|
||||
if not self.util_exists():
|
||||
logger.warn(
|
||||
"Error sending notification: synoindex utility not found at %s" % self.util_loc)
|
||||
"Error sending notification: synoindex utility "
|
||||
"not found at %s" % self.util_loc)
|
||||
return
|
||||
|
||||
if os.path.isfile(path):
|
||||
@@ -533,16 +612,19 @@ class Synoindex(object):
|
||||
cmd_arg = '-A'
|
||||
else:
|
||||
logger.warn(
|
||||
"Error sending notification: Path passed to synoindex was not a file or folder.")
|
||||
"Error sending notification: Path passed to synoindex "
|
||||
"was not a file or folder.")
|
||||
return
|
||||
|
||||
cmd = [self.util_loc, cmd_arg, path]
|
||||
logger.info("Calling synoindex command: %s" % str(cmd))
|
||||
try:
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=headphones.PROG_DIR)
|
||||
out, error = p.communicate()
|
||||
# synoindex never returns any codes other than '0', highly irritating
|
||||
# synoindex never returns any codes other than '0',
|
||||
# highly irritating
|
||||
except OSError, e:
|
||||
logger.warn("Error sending notification: %s" % str(e))
|
||||
|
||||
@@ -580,7 +662,8 @@ class PUSHOVER(object):
|
||||
|
||||
headers = {'Content-type': "application/x-www-form-urlencoded"}
|
||||
|
||||
response = request.request_response(url, method="POST", headers=headers, data=data)
|
||||
response = request.request_response(url, method="POST",
|
||||
headers=headers, data=data)
|
||||
|
||||
if response:
|
||||
logger.info(u"Pushover notifications sent.")
|
||||
@@ -614,20 +697,25 @@ class TwitterNotifier(object):
|
||||
def notify_snatch(self, title):
|
||||
if headphones.CONFIG.TWITTER_ONSNATCH:
|
||||
self._notifyTwitter(
|
||||
common.notifyStrings[common.NOTIFY_SNATCH] + ': ' + title + ' at ' + helpers.now())
|
||||
common.notifyStrings[
|
||||
common.NOTIFY_SNATCH] + ': ' + title + ' at ' +
|
||||
helpers.now())
|
||||
|
||||
def notify_download(self, title):
|
||||
if headphones.CONFIG.TWITTER_ENABLED:
|
||||
self._notifyTwitter(common.notifyStrings[
|
||||
common.NOTIFY_DOWNLOAD] + ': ' + title + ' at ' + helpers.now())
|
||||
common.NOTIFY_DOWNLOAD] + ': ' +
|
||||
title + ' at ' + helpers.now())
|
||||
|
||||
def test_notify(self):
|
||||
return self._notifyTwitter(
|
||||
"This is a test notification from Headphones at " + helpers.now(), force=True)
|
||||
"This is a test notification from Headphones at " + helpers.now(),
|
||||
force=True)
|
||||
|
||||
def _get_authorization(self):
|
||||
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key,
|
||||
secret=self.consumer_secret)
|
||||
oauth_client = oauth.Client(oauth_consumer)
|
||||
|
||||
logger.info('Requesting temp token from Twitter')
|
||||
@@ -635,32 +723,42 @@ class TwitterNotifier(object):
|
||||
resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET')
|
||||
|
||||
if resp['status'] != '200':
|
||||
logger.info('Invalid respond from Twitter requesting temp token: %s' % resp['status'])
|
||||
logger.info(
|
||||
'Invalid respond from Twitter requesting temp token: %s' %
|
||||
resp['status'])
|
||||
else:
|
||||
request_token = dict(parse_qsl(content))
|
||||
|
||||
headphones.CONFIG.TWITTER_USERNAME = request_token['oauth_token']
|
||||
headphones.CONFIG.TWITTER_PASSWORD = request_token['oauth_token_secret']
|
||||
headphones.CONFIG.TWITTER_PASSWORD = request_token[
|
||||
'oauth_token_secret']
|
||||
|
||||
return self.AUTHORIZATION_URL + "?oauth_token=" + request_token['oauth_token']
|
||||
return self.AUTHORIZATION_URL + "?oauth_token=" + request_token[
|
||||
'oauth_token']
|
||||
|
||||
def _get_credentials(self, key):
|
||||
request_token = {}
|
||||
|
||||
request_token['oauth_token'] = headphones.CONFIG.TWITTER_USERNAME
|
||||
request_token['oauth_token_secret'] = headphones.CONFIG.TWITTER_PASSWORD
|
||||
request_token[
|
||||
'oauth_token_secret'] = headphones.CONFIG.TWITTER_PASSWORD
|
||||
request_token['oauth_callback_confirmed'] = 'true'
|
||||
|
||||
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
|
||||
token = oauth.Token(request_token['oauth_token'],
|
||||
request_token['oauth_token_secret'])
|
||||
token.set_verifier(key)
|
||||
|
||||
logger.info('Generating and signing request for an access token using key ' + key)
|
||||
logger.info(
|
||||
'Generating and signing request for an access token using key ' +
|
||||
key)
|
||||
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key,
|
||||
secret=self.consumer_secret)
|
||||
logger.info('oauth_consumer: ' + str(oauth_consumer))
|
||||
oauth_client = oauth.Client(oauth_consumer, token)
|
||||
logger.info('oauth_client: ' + str(oauth_client))
|
||||
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST',
|
||||
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL,
|
||||
method='POST',
|
||||
body='oauth_verifier=%s' % key)
|
||||
logger.info('resp, content: ' + str(resp) + ',' + str(content))
|
||||
|
||||
@@ -669,14 +767,18 @@ class TwitterNotifier(object):
|
||||
|
||||
logger.info('resp[status] = ' + str(resp['status']))
|
||||
if resp['status'] != '200':
|
||||
logger.info('The request for a token with did not succeed: ' + str(resp['status']),
|
||||
logger.info('The request for a token with did not succeed: ' + str(
|
||||
resp['status']),
|
||||
logger.ERROR)
|
||||
return False
|
||||
else:
|
||||
logger.info('Your Twitter Access Token key: %s' % access_token['oauth_token'])
|
||||
logger.info('Access Token secret: %s' % access_token['oauth_token_secret'])
|
||||
logger.info('Your Twitter Access Token key: %s' % access_token[
|
||||
'oauth_token'])
|
||||
logger.info(
|
||||
'Access Token secret: %s' % access_token['oauth_token_secret'])
|
||||
headphones.CONFIG.TWITTER_USERNAME = access_token['oauth_token']
|
||||
headphones.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
||||
headphones.CONFIG.TWITTER_PASSWORD = access_token[
|
||||
'oauth_token_secret']
|
||||
return True
|
||||
|
||||
def _send_tweet(self, message=None):
|
||||
@@ -688,7 +790,8 @@ class TwitterNotifier(object):
|
||||
|
||||
logger.info(u"Sending tweet: " + message)
|
||||
|
||||
api = twitter.Api(username, password, access_token_key, access_token_secret)
|
||||
api = twitter.Api(username, password, access_token_key,
|
||||
access_token_secret)
|
||||
|
||||
try:
|
||||
api.PostUpdate(message)
|
||||
@@ -741,7 +844,8 @@ class OSX_NOTIFY(object):
|
||||
)
|
||||
|
||||
NSUserNotification = self.objc.lookUpClass('NSUserNotification')
|
||||
NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter')
|
||||
NSUserNotificationCenter = self.objc.lookUpClass(
|
||||
'NSUserNotificationCenter')
|
||||
NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool')
|
||||
|
||||
if not NSUserNotification or not NSUserNotificationCenter:
|
||||
@@ -756,14 +860,17 @@ class OSX_NOTIFY(object):
|
||||
if text:
|
||||
notification.setInformativeText_(text)
|
||||
if sound:
|
||||
notification.setSoundName_("NSUserNotificationDefaultSoundName")
|
||||
notification.setSoundName_(
|
||||
"NSUserNotificationDefaultSoundName")
|
||||
if image:
|
||||
source_img = self.AppKit.NSImage.alloc().initByReferencingFile_(image)
|
||||
source_img = self.AppKit.NSImage.alloc().\
|
||||
initByReferencingFile_(image)
|
||||
notification.setContentImage_(source_img)
|
||||
# notification.set_identityImage_(source_img)
|
||||
notification.setHasActionButton_(False)
|
||||
|
||||
notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
|
||||
notification_center = NSUserNotificationCenter.\
|
||||
defaultUserNotificationCenter()
|
||||
notification_center.deliverNotification_(notification)
|
||||
|
||||
del pool
|
||||
@@ -784,7 +891,8 @@ class BOXCAR(object):
|
||||
def notify(self, title, message, rgid=None):
|
||||
try:
|
||||
if rgid:
|
||||
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
||||
message += '<br></br><a href="http://musicbrainz.org/' \
|
||||
'release-group/%s">MusicBrainz</a>' % rgid
|
||||
|
||||
data = urllib.urlencode({
|
||||
'user_credentials': headphones.CONFIG.BOXCAR_TOKEN,
|
||||
@@ -820,8 +928,9 @@ class SubSonicNotifier(object):
|
||||
self.host = self.host + "/"
|
||||
|
||||
# Invoke request
|
||||
request.request_response(self.host + "musicFolderSettings.view?scanNow",
|
||||
auth=(self.username, self.password))
|
||||
request.request_response(
|
||||
self.host + "musicFolderSettings.view?scanNow",
|
||||
auth=(self.username, self.password))
|
||||
|
||||
|
||||
class Email(object):
|
||||
@@ -829,13 +938,15 @@ class Email(object):
|
||||
|
||||
message = MIMEText(message, 'plain', "utf-8")
|
||||
message['Subject'] = subject
|
||||
message['From'] = email.utils.formataddr(('Headphones', headphones.CONFIG.EMAIL_FROM))
|
||||
message['From'] = email.utils.formataddr(
|
||||
('Headphones', headphones.CONFIG.EMAIL_FROM))
|
||||
message['To'] = headphones.CONFIG.EMAIL_TO
|
||||
|
||||
try:
|
||||
if headphones.CONFIG.EMAIL_SSL:
|
||||
mailserver = smtplib.SMTP_SSL(headphones.CONFIG.EMAIL_SMTP_SERVER,
|
||||
headphones.CONFIG.EMAIL_SMTP_PORT)
|
||||
mailserver = smtplib.SMTP_SSL(
|
||||
headphones.CONFIG.EMAIL_SMTP_SERVER,
|
||||
headphones.CONFIG.EMAIL_SMTP_PORT)
|
||||
else:
|
||||
mailserver = smtplib.SMTP(headphones.CONFIG.EMAIL_SMTP_SERVER,
|
||||
headphones.CONFIG.EMAIL_SMTP_PORT)
|
||||
@@ -849,7 +960,8 @@ class Email(object):
|
||||
mailserver.login(headphones.CONFIG.EMAIL_SMTP_USER,
|
||||
headphones.CONFIG.EMAIL_SMTP_PASSWORD)
|
||||
|
||||
mailserver.sendmail(headphones.CONFIG.EMAIL_FROM, headphones.CONFIG.EMAIL_TO,
|
||||
mailserver.sendmail(headphones.CONFIG.EMAIL_FROM,
|
||||
headphones.CONFIG.EMAIL_TO,
|
||||
message.as_string())
|
||||
mailserver.quit()
|
||||
return True
|
||||
@@ -860,7 +972,6 @@ class Email(object):
|
||||
|
||||
|
||||
class TELEGRAM(object):
|
||||
|
||||
def notify(self, message, status):
|
||||
if not headphones.CONFIG.TELEGRAM_ENABLED:
|
||||
return
|
||||
@@ -878,14 +989,18 @@ class TELEGRAM(object):
|
||||
|
||||
# Send message to user using Telegram's Bot API
|
||||
try:
|
||||
response = requests.post(TELEGRAM_API % (token, "sendMessage"), data=payload)
|
||||
response = requests.post(TELEGRAM_API % (token, "sendMessage"),
|
||||
data=payload)
|
||||
except Exception, e:
|
||||
logger.info(u'Telegram notify failed: ' + str(e))
|
||||
|
||||
# Error logging
|
||||
sent_successfuly = True
|
||||
if not response.status_code == 200:
|
||||
logger.info(u'Could not send notification to TelegramBot (token=%s). Response: [%s]', (token, response.text))
|
||||
logger.info(
|
||||
u'Could not send notification to TelegramBot '
|
||||
u'(token=%s). Response: [%s]',
|
||||
(token, response.text))
|
||||
sent_successfuly = False
|
||||
|
||||
logger.info(u"Telegram notifications sent.")
|
||||
@@ -893,7 +1008,6 @@ class TELEGRAM(object):
|
||||
|
||||
|
||||
class SLACK(object):
|
||||
|
||||
def notify(self, message, status):
|
||||
if not headphones.CONFIG.SLACK_ENABLED:
|
||||
return
|
||||
@@ -904,7 +1018,8 @@ class SLACK(object):
|
||||
channel = headphones.CONFIG.SLACK_CHANNEL
|
||||
emoji = headphones.CONFIG.SLACK_EMOJI
|
||||
|
||||
payload = {'channel': channel, 'text': status + ': ' + message, 'icon_emoji': emoji}
|
||||
payload = {'channel': channel, 'text': status + ': ' + message,
|
||||
'icon_emoji': emoji}
|
||||
|
||||
try:
|
||||
response = requests.post(SLACK_URL, json=payload)
|
||||
@@ -913,7 +1028,9 @@ class SLACK(object):
|
||||
|
||||
sent_successfuly = True
|
||||
if not response.status_code == 200:
|
||||
logger.info(u'Could not send notification to Slack. Response: [%s]', (response.text))
|
||||
logger.info(
|
||||
u'Could not send notification to Slack. Response: [%s]',
|
||||
(response.text))
|
||||
sent_successfuly = False
|
||||
|
||||
logger.info(u"Slack notifications sent.")
|
||||
|
||||
@@ -574,6 +574,11 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
pushbullet = notifiers.PUSHBULLET()
|
||||
pushbullet.notify(pushmessage, statusmessage)
|
||||
|
||||
if headphones.CONFIG.JOIN_ENABLED:
|
||||
logger.info(u"Join request")
|
||||
join = notifiers.JOIN()
|
||||
join.notify(pushmessage, statusmessage)
|
||||
|
||||
if headphones.CONFIG.TELEGRAM_ENABLED:
|
||||
logger.info(u"Telegram request")
|
||||
telegram = notifiers.TELEGRAM()
|
||||
|
||||
@@ -32,6 +32,7 @@ import re
|
||||
from pygazelle import api as gazelleapi
|
||||
from pygazelle import encoding as gazelleencoding
|
||||
from pygazelle import format as gazelleformat
|
||||
from pygazelle import release_type as gazellerelease_type
|
||||
import headphones
|
||||
from headphones.common import USER_AGENT
|
||||
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
||||
@@ -1076,6 +1077,10 @@ def send_to_downloader(data, bestqual, album):
|
||||
logger.info(u"Sending PushBullet notification")
|
||||
pushbullet = notifiers.PUSHBULLET()
|
||||
pushbullet.notify(name, "Download started")
|
||||
if headphones.CONFIG.JOIN_ENABLED and headphones.CONFIG.JOIN_ONSNATCH:
|
||||
logger.info(u"Sending Join notification")
|
||||
join = notifiers.JOIN()
|
||||
join.notify(name, "Download started")
|
||||
if headphones.CONFIG.SLACK_ENABLED and headphones.CONFIG.SLACK_ONSNATCH:
|
||||
logger.info(u"Sending Slack notification")
|
||||
slack = notifiers.SLACK()
|
||||
@@ -1564,16 +1569,45 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None,
|
||||
if apolloobj and apolloobj.logged_in():
|
||||
logger.info(u"Searching %s..." % provider)
|
||||
all_torrents = []
|
||||
|
||||
# Specify release types to filter by
|
||||
if album['Type'] == 'Album':
|
||||
album_type = [gazellerelease_type.ALBUM]
|
||||
if album['Type'] == 'Soundtrack':
|
||||
album_type = [gazellerelease_type.SOUNDTRACK]
|
||||
if album['Type'] == 'EP':
|
||||
album_type = [gazellerelease_type.EP]
|
||||
# No musicbrainz match for this type
|
||||
#if album['Type'] == 'Anthology':
|
||||
# album_type = [gazellerelease_type.ANTHOLOGY]
|
||||
if album['Type'] == 'Compilation':
|
||||
album_type = [gazellerelease_type.COMPILATION]
|
||||
if album['Type'] == 'DJ-mix':
|
||||
album_type = [gazellerelease_type.DJ_MIX]
|
||||
if album['Type'] == 'Single':
|
||||
album_type = [gazellerelease_type.SINGLE]
|
||||
if album['Type'] == 'Live':
|
||||
album_type = [gazellerelease_type.LIVE_ALBUM]
|
||||
if album['Type'] == 'Remix':
|
||||
album_type = [gazellerelease_type.REMIX]
|
||||
if album['Type'] == 'Bootleg':
|
||||
album_type = [gazellerelease_type.BOOTLEG]
|
||||
if album['Type'] == 'Interview':
|
||||
album_type = [gazellerelease_type.INTERVIEW]
|
||||
if album['Type'] == 'Mixtape/Street':
|
||||
album_type = [gazellerelease_type.MIXTAPE]
|
||||
|
||||
for search_format in search_formats:
|
||||
if usersearchterm:
|
||||
all_torrents.extend(
|
||||
apolloobj.search_torrents(searchstr=usersearchterm, format=search_format,
|
||||
encoding=bitrate_string)['results'])
|
||||
encoding=bitrate_string, releasetype=album_type)['results'])
|
||||
else:
|
||||
all_torrents.extend(apolloobj.search_torrents(artistname=semi_clean_artist_term,
|
||||
groupname=semi_clean_album_term,
|
||||
format=search_format,
|
||||
encoding=bitrate_string)['results'])
|
||||
encoding=bitrate_string,
|
||||
releasetype=album_type)['results'])
|
||||
|
||||
# filter on format, size, and num seeders
|
||||
logger.info(u"Filtering torrents by format, maximum size, and minimum seeders...")
|
||||
|
||||
@@ -537,7 +537,7 @@ class WebInterface(object):
|
||||
myDB = db.DBConnection()
|
||||
upcoming = myDB.select(
|
||||
"SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate ASC")
|
||||
wanted = myDB.select("SELECT * from albums WHERE Status='Wanted'")
|
||||
wanted = myDB.select("SELECT * from albums WHERE Status='Wanted' order by ReleaseDate ASC")
|
||||
return serve_template(templatename="upcoming.html", title="Upcoming", upcoming=upcoming,
|
||||
wanted=wanted)
|
||||
|
||||
@@ -1412,7 +1412,11 @@ class WebInterface(object):
|
||||
"slack_url": headphones.CONFIG.SLACK_URL,
|
||||
"slack_channel": headphones.CONFIG.SLACK_CHANNEL,
|
||||
"slack_emoji": headphones.CONFIG.SLACK_EMOJI,
|
||||
"slack_onsnatch": checked(headphones.CONFIG.SLACK_ONSNATCH)
|
||||
"slack_onsnatch": checked(headphones.CONFIG.SLACK_ONSNATCH),
|
||||
"join_enabled": checked(headphones.CONFIG.JOIN_ENABLED),
|
||||
"join_onsnatch": checked(headphones.CONFIG.JOIN_ONSNATCH),
|
||||
"join_apikey": headphones.CONFIG.JOIN_APIKEY,
|
||||
"join_deviceid": headphones.CONFIG.JOIN_DEVICEID
|
||||
}
|
||||
|
||||
for k, v in config.iteritems():
|
||||
@@ -1480,7 +1484,8 @@ class WebInterface(object):
|
||||
"osx_notify_enabled", "osx_notify_onsnatch", "boxcar_enabled", "boxcar_onsnatch",
|
||||
"songkick_enabled", "songkick_filter_enabled",
|
||||
"mpc_enabled", "email_enabled", "email_ssl", "email_tls", "email_onsnatch",
|
||||
"customauth", "idtag", "deluge_paused"
|
||||
"customauth", "idtag", "deluge_paused",
|
||||
"join_enabled", "join_onsnatch"
|
||||
]
|
||||
for checked_config in checked_configs:
|
||||
if checked_config not in kwargs:
|
||||
@@ -1749,6 +1754,12 @@ class WebInterface(object):
|
||||
telegram = notifiers.TELEGRAM()
|
||||
telegram.notify("it works!", "lazers pew pew")
|
||||
|
||||
@cherrypy.expose
|
||||
def testJoin(self):
|
||||
logger.info("Testing Join notifications")
|
||||
join = notifiers.JOIN()
|
||||
join.notify("it works!", "Test message")
|
||||
|
||||
|
||||
class Artwork(object):
|
||||
@cherrypy.expose
|
||||
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.2 KiB |