# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PlexPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see
\
Step 2: Go to Keys and Access Tokens and click \
Create my access token.
\
Step 3: Fill in the Consumer Key, Consumer Secret, \
Access Token, and Access Token Secret below.',
'input_type': 'help'
},
{'label': 'Twitter Consumer Key',
'value': self.consumer_key,
'name': 'twitter_consumer_key',
'description': 'Your Twitter consumer key.',
'input_type': 'text'
},
{'label': 'Twitter Consumer Secret',
'value': self.consumer_secret,
'name': 'twitter_consumer_secret',
'description': 'Your Twitter consumer secret.',
'input_type': 'text'
},
{'label': 'Twitter Access Token',
'value': self.access_token,
'name': 'twitter_access_token',
'description': 'Your Twitter access token.',
'input_type': 'text'
},
{'label': 'Twitter Access Token Secret',
'value': self.access_token_secret,
'name': 'twitter_access_token_secret',
'description': 'Your Twitter access token secret.',
'input_type': 'text'
},
{'label': 'Include Poster Image',
'value': self.incl_poster,
'name': 'twitter_incl_poster',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'twitter_incl_subject',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
return config_option
class OSX_NOTIFY(object):
def __init__(self):
self.app = plexpy.CONFIG.OSX_NOTIFY_APP
try:
self.objc = __import__("objc")
self.AppKit = __import__("AppKit")
except:
# logger.error(u"PlexPy Notifiers :: Cannot load OSX Notifications agent.")
pass
def validate(self):
try:
self.objc = __import__("objc")
self.AppKit = __import__("AppKit")
return True
except:
return False
def swizzle(self, cls, SEL, func):
old_IMP = cls.instanceMethodForSelector_(SEL)
def wrapper(self, *args, **kwargs):
return func(self, old_IMP, *args, **kwargs)
new_IMP = self.objc.selector(wrapper, selector=old_IMP.selector,
signature=old_IMP.signature)
self.objc.classAddMethod(cls, SEL, new_IMP)
def notify(self, title, subtitle=None, text=None, sound=True, image=None):
try:
self.swizzle(self.objc.lookUpClass('NSBundle'),
b'bundleIdentifier',
self.swizzled_bundleIdentifier)
NSUserNotification = self.objc.lookUpClass('NSUserNotification')
NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter')
NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool')
if not NSUserNotification or not NSUserNotificationCenter:
return False
pool = NSAutoreleasePool.alloc().init()
notification = NSUserNotification.alloc().init()
notification.setTitle_(title)
if subtitle:
notification.setSubtitle_(subtitle)
if text:
notification.setInformativeText_(text)
if sound:
notification.setSoundName_("NSUserNotificationDefaultSoundName")
if 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.deliverNotification_(notification)
logger.info(u"PlexPy Notifiers :: OSX Notify notification sent.")
del pool
return True
except Exception as e:
logger.warn(u"PlexPy Notifiers :: OSX notification failed: %s" % e)
return False
def swizzled_bundleIdentifier(self, original, swizzled):
return 'ade.plexpy.osxnotify'
def return_config_options(self):
config_option = [{'label': 'Register Notify App',
'value': self.app,
'name': 'osx_notify_app',
'description': 'Enter the path/application name to be registered with the '
'Notification Center, default is /Applications/PlexPy.',
'input_type': 'text'
}
]
return config_option
class BOXCAR(object):
def __init__(self):
self.url = 'https://new.boxcar.io/api/notifications'
self.token = plexpy.CONFIG.BOXCAR_TOKEN
self.sound = plexpy.CONFIG.BOXCAR_SOUND
def notify(self, title, message):
if not title or not message:
return
try:
data = urllib.urlencode({
'user_credentials': self.token,
'notification[title]': title.encode('utf-8'),
'notification[long_message]': message.encode('utf-8'),
'notification[sound]': self.sound
})
req = urllib2.Request(self.url)
handle = urllib2.urlopen(req, data)
handle.close()
logger.info(u"PlexPy Notifiers :: Boxcar2 notification sent.")
return True
except urllib2.URLError as e:
logger.warn(u"PlexPy Notifiers :: Boxcar2 notification failed: %s" % e)
return False
def get_sounds(self):
sounds = {'': '',
'beep-crisp': 'Beep (Crisp)',
'beep-soft': 'Beep (Soft)',
'bell-modern': 'Bell (Modern)',
'bell-one-tone': 'Bell (One Tone)',
'bell-simple': 'Bell (Simple)',
'bell-triple': 'Bell (Triple)',
'bird-1': 'Bird (1)',
'bird-2': 'Bird (2)',
'boing': 'Boing',
'cash': 'Cash',
'clanging': 'Clanging',
'detonator-charge': 'Detonator Charge',
'digital-alarm': 'Digital Alarm',
'done': 'Done',
'echo': 'Echo',
'flourish': 'Flourish',
'harp': 'Harp',
'light': 'Light',
'magic-chime':'Magic Chime',
'magic-coin': 'Magic Coin',
'no-sound': 'No Sound',
'notifier-1': 'Notifier (1)',
'notifier-2': 'Notifier (2)',
'notifier-3': 'Notifier (3)',
'orchestral-long': 'Orchestral (Long)',
'orchestral-short': 'Orchestral (Short)',
'score': 'Score',
'success': 'Success',
'up': 'Up'}
return sounds
def return_config_options(self):
config_option = [{'label': 'Boxcar Access Token',
'value': self.token,
'name': 'boxcar_token',
'description': 'Your Boxcar access token.',
'input_type': 'text'
},
{'label': 'Sound',
'value': self.sound,
'name': 'boxcar_sound',
'description': 'Set the notification sound. Leave blank for the default sound.',
'input_type': 'select',
'select_options': self.get_sounds()
}
]
return config_option
class Email(object):
def __init__(self):
self.from_name = plexpy.CONFIG.EMAIL_FROM_NAME
self.email_from = plexpy.CONFIG.EMAIL_FROM
self.email_to = plexpy.CONFIG.EMAIL_TO
self.email_cc = plexpy.CONFIG.EMAIL_CC
self.email_bcc = plexpy.CONFIG.EMAIL_BCC
self.smtp_server = plexpy.CONFIG.EMAIL_SMTP_SERVER
self.smtp_port = plexpy.CONFIG.EMAIL_SMTP_PORT
self.smtp_user = plexpy.CONFIG.EMAIL_SMTP_USER
self.smtp_password = plexpy.CONFIG.EMAIL_SMTP_PASSWORD
self.tls = plexpy.CONFIG.EMAIL_TLS
self.html_support = plexpy.CONFIG.EMAIL_HTML_SUPPORT
def notify(self, subject, message):
if not subject or not message:
return
if self.html_support:
msg = MIMEMultipart('alternative')
msg.attach(MIMEText(bleach.clean(message, strip=True), 'plain', 'utf-8'))
msg.attach(MIMEText(message, 'html', 'utf-8'))
else:
msg = MIMEText(message, 'plain', 'utf-8')
msg['Subject'] = subject
msg['From'] = email.utils.formataddr((self.from_name, self.email_from))
msg['To'] = self.email_to
msg['CC'] = self.email_cc
recipients = [x.strip() for x in self.email_to.split(';')] \
+ [x.strip() for x in self.email_cc.split(';')] \
+ [x.strip() for x in self.email_bcc.split(';')]
recipients = filter(None, recipients)
try:
mailserver = smtplib.SMTP(self.smtp_server, self.smtp_port)
if self.tls:
mailserver.starttls()
mailserver.ehlo()
if self.smtp_user:
mailserver.login(self.smtp_user, self.smtp_password)
mailserver.sendmail(self.email_from, recipients, msg.as_string())
mailserver.quit()
logger.info(u"PlexPy Notifiers :: Email notification sent.")
return True
except Exception as e:
logger.warn(u"PlexPy Notifiers :: Email notification failed: %s" % e)
return False
def return_config_options(self):
config_option = [{'label': 'From Name',
'value': self.from_name,
'name': 'email_from_name',
'description': 'The name of the sender.',
'input_type': 'text'
},
{'label': 'From',
'value': self.email_from,
'name': 'email_from',
'description': 'The email address of the sender.',
'input_type': 'text'
},
{'label': 'To',
'value': self.email_to,
'name': 'email_to',
'description': 'The email address(es) of the recipients, separated by semicolons (;).',
'input_type': 'text'
},
{'label': 'CC',
'value': self.email_cc,
'name': 'email_cc',
'description': 'The email address(es) to CC, separated by semicolons (;).',
'input_type': 'text'
},
{'label': 'BCC',
'value': self.email_bcc,
'name': 'email_bcc',
'description': 'The email address(es) to BCC, separated by semicolons (;).',
'input_type': 'text'
},
{'label': 'SMTP Server',
'value': self.smtp_server,
'name': 'email_smtp_server',
'description': 'Host for the SMTP server.',
'input_type': 'text'
},
{'label': 'SMTP Port',
'value': self.smtp_port,
'name': 'email_smtp_port',
'description': 'Port for the SMTP server.',
'input_type': 'number'
},
{'label': 'SMTP User',
'value': self.smtp_user,
'name': 'email_smtp_user',
'description': 'User for the SMTP server.',
'input_type': 'text'
},
{'label': 'SMTP Password',
'value': self.smtp_password,
'name': 'email_smtp_password',
'description': 'Password for the SMTP server.',
'input_type': 'password'
},
{'label': 'TLS',
'value': self.tls,
'name': 'email_tls',
'description': 'Does the server use encryption.',
'input_type': 'checkbox'
},
{'label': 'Enable HTML Support',
'value': self.html_support,
'name': 'email_html_support',
'description': 'Style your messages using HTML tags.',
'input_type': 'checkbox'
}
]
return config_option
class IFTTT(object):
def __init__(self):
self.apikey = plexpy.CONFIG.IFTTT_KEY
self.event = plexpy.CONFIG.IFTTT_EVENT
def notify(self, message, subject, action):
if not message or not subject:
return
event = unicode(self.event).format(action=action)
http_handler = HTTPSConnection("maker.ifttt.com")
data = {'value1': subject.encode("utf-8"),
'value2': message.encode("utf-8")}
# logger.debug(u"Ifttt SENDING: %s" % json.dumps(data))
http_handler.request("POST",
"/trigger/%s/with/key/%s" % (event, self.apikey),
headers={'Content-type': "application/json"},
body=json.dumps(data))
response = http_handler.getresponse()
request_status = response.status
# logger.debug(u"Ifttt response status: %r" % request_status)
# logger.debug(u"Ifttt response headers: %r" % response.getheaders())
# logger.debug(u"Ifttt response body: %r" % response.read())
if request_status == 200:
logger.info(u"PlexPy Notifiers :: Ifttt notification sent.")
return True
elif request_status >= 400 and request_status < 500:
logger.warn(u"PlexPy Notifiers :: Ifttt notification failed: [%s] %s" % (request_status, response.reason))
return False
else:
logger.warn(u"PlexPy Notifiers :: Ifttt notification failed.")
return False
def test(self):
return self.notify('PlexPy', 'Test Message')
def return_config_options(self):
config_option = [{'label': 'Ifttt Maker Channel Key',
'value': self.apikey,
'name': 'ifttt_key',
'description': 'Your Ifttt key. You can get a key from'
' here.',
'input_type': 'text'
},
{'label': 'Ifttt Event',
'value': self.event,
'name': 'ifttt_event',
'description': 'The Ifttt maker event to fire. You can include'
' the {action} to be substituted with the action name.'
' The notification subject and body will be sent'
' as value1 and value2 respectively.',
'input_type': 'text'
}
]
return config_option
class TELEGRAM(object):
def __init__(self):
self.enabled = plexpy.CONFIG.TELEGRAM_ENABLED
self.bot_token = plexpy.CONFIG.TELEGRAM_BOT_TOKEN
self.chat_id = plexpy.CONFIG.TELEGRAM_CHAT_ID
self.html_support = plexpy.CONFIG.TELEGRAM_HTML_SUPPORT
self.incl_poster = plexpy.CONFIG.TELEGRAM_INCL_POSTER
self.incl_subject = plexpy.CONFIG.TELEGRAM_INCL_SUBJECT
def conf(self, options):
return cherrypy.config['config'].get('Telegram', options)
def notify(self, message, event, **kwargs):
if not message or not event:
return
data = {'chat_id': self.chat_id}
if self.incl_subject:
text = event.encode('utf-8') + ': ' + message.encode('utf-8')
else:
text = message.encode('utf-8')
if self.incl_poster and 'metadata' in kwargs:
metadata = kwargs['metadata']
poster_url = metadata.get('poster_url','')
if poster_url:
files = {'photo': (poster_url, urllib.urlopen(poster_url).read())}
response = requests.post('https://api.telegram.org/bot%s/%s' % (self.bot_token, 'sendPhoto'),
data=data,
files=files)
request_status = response.status_code
request_content = json.loads(response.text)
if request_status == 200:
logger.info(u"PlexPy Notifiers :: Telegram poster sent.")
elif request_status >= 400 and request_status < 500:
logger.warn(u"PlexPy Notifiers :: Telegram poster failed: %s" % request_content.get('description'))
else:
logger.warn(u"PlexPy Notifiers :: Telegram poster failed.")
data['text'] = text
if self.html_support:
data['parse_mode'] = 'HTML'
http_handler = HTTPSConnection("api.telegram.org")
http_handler.request('POST',
'/bot%s/%s' % (self.bot_token, 'sendMessage'),
headers={'Content-type': 'application/x-www-form-urlencoded'},
body=urlencode(data))
response = http_handler.getresponse()
request_status = response.status
if request_status == 200:
logger.info(u"PlexPy Notifiers :: Telegram notification sent.")
return True
elif request_status >= 400 and request_status < 500:
logger.warn(u"PlexPy Notifiers :: Telegram notification failed: [%s] %s" % (request_status, response.reason))
return False
else:
logger.warn(u"PlexPy Notifiers :: Telegram notification failed.")
return False
def updateLibrary(self):
# For uniformity reasons not removed
return
def test(self, bot_token, chat_id):
self.enabled = True
self.bot_token = bot_token
self.chat_id = chat_id
self.notify('Main Screen Activate', 'Test Message')
def return_config_options(self):
config_option = [{'label': 'Telegram Bot Token',
'value': self.bot_token,
'name': 'telegram_bot_token',
'description': 'Your Telegram bot token. '
'Contact @BotFather'
' on Telegram to get one.',
'input_type': 'text'
},
{'label': 'Telegram Chat ID, Group ID, or Channel Username',
'value': self.chat_id,
'name': 'telegram_chat_id',
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. '
'Contact @myidbot'
' on Telegram to get an ID.',
'input_type': 'text'
},
{'label': 'Include Poster Image',
'value': self.incl_poster,
'name': 'telegram_incl_poster',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'telegram_incl_subject',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Enable HTML Support',
'value': self.html_support,
'name': 'telegram_html_support',
'description': 'Style your messages using these HTML tags: b, i, a[href], code, pre',
'input_type': 'checkbox'
}
]
return config_option
class SLACK(object):
"""
Slack Notifications
"""
def __init__(self):
self.enabled = plexpy.CONFIG.SLACK_ENABLED
self.slack_hook = plexpy.CONFIG.SLACK_HOOK
self.channel = plexpy.CONFIG.SLACK_CHANNEL
self.username = plexpy.CONFIG.SLACK_USERNAME
self.icon_emoji = plexpy.CONFIG.SLACK_ICON_EMOJI
self.incl_pmslink = plexpy.CONFIG.SLACK_INCL_PMSLINK
self.incl_poster = plexpy.CONFIG.SLACK_INCL_POSTER
self.incl_subject = plexpy.CONFIG.SLACK_INCL_SUBJECT
def conf(self, options):
return cherrypy.config['config'].get('Slack', options)
def notify(self, message, event, **kwargs):
if not message or not event:
return
if self.incl_subject:
text = event.encode('utf-8') + ': ' + message.encode("utf-8")
else:
text = message.encode("utf-8")
data = {'text': text}
if self.channel != '': data['channel'] = self.channel
if self.username != '': data['username'] = self.username
if self.icon_emoji != '':
if urlparse(self.icon_emoji).scheme == '':
data['icon_emoji'] = self.icon_emoji
else:
data['icon_url'] = self.icon_emoji
if self.incl_poster and 'metadata' in kwargs:
# Grab formatted metadata
pretty_metadata = PrettyMetadata(kwargs['metadata'])
poster_url = pretty_metadata.get_poster_url()
plex_url = pretty_metadata.get_plex_url()
poster_link = pretty_metadata.get_poster_link()
caption = pretty_metadata.get_caption()
title = pretty_metadata.get_title('-')
# Build Slack post attachment
attachment = {}
if self.incl_pmslink:
caption = 'View on Plex Web'
attachment['title_link'] = plex_url
attachment['text'] = caption
elif poster_link:
attachment['title_link'] = poster_link
attachment['text'] = caption
attachment['fallback'] = 'Image for %s' % title
attachment['title'] = title
attachment['image_url'] = poster_url
data['attachments'] = [attachment]
url = urlparse(self.slack_hook).path
http_handler = HTTPSConnection("hooks.slack.com")
http_handler.request("POST",
url,
headers={'Content-type': "application/x-www-form-urlencoded"},
body=json.dumps(data))
response = http_handler.getresponse()
request_status = response.status
if request_status == 200:
logger.info(u"PlexPy Notifiers :: Slack notification sent.")
return True
elif request_status >= 400 and request_status < 500:
logger.warn(u"PlexPy Notifiers :: Slack notification failed: [%s] %s" % (request_status, response.reason))
return False
else:
logger.warn(u"PlexPy Notifiers :: Slack notification failed.")
return False
def updateLibrary(self):
#For uniformity reasons not removed
return
def test(self):
self.enabled = True
return self.notify('Main Screen Activate', 'Test Message')
def return_config_options(self):
config_option = [{'label': 'Slack Webhook URL',
'value': self.slack_hook,
'name': 'slack_hook',
'description': 'Your Slack incoming webhook URL.',
'input_type': 'text'
},
{'label': 'Slack Channel',
'value': self.channel,
'name': 'slack_channel',
'description': 'Your Slack channel name (begin with \'#\'). Leave blank for webhook integration default.',
'input_type': 'text'
},
{'label': 'Slack Username',
'value': self.username,
'name': 'slack_username',
'description': 'The Slack username which will be shown. Leave blank for webhook integration default.',
'input_type': 'text'
},
{'label': 'Slack Icon',
'value': self.icon_emoji,
'description': 'The icon you wish to show, use Slack emoji or image url. Leave blank for webhook integration default.',
'name': 'slack_icon_emoji',
'input_type': 'text'
},
{'label': 'Include Poster Image',
'value': self.incl_poster,
'name': 'slack_incl_poster',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Link to Plex Web',
'value': self.incl_pmslink,
'name': 'slack_incl_pmslink',
'description': 'Include a link to the media in Plex Web with the notifications.
'
'If disabled, the link will go to IMDB, TVDB, TMDb, or Last.fm instead, if available.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'slack_incl_subject',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
return config_option
class Scripts(object):
def __init__(self, **kwargs):
self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.ps1', '.py', '.pyw', '.rb', '.sh')
self.script_folder = plexpy.CONFIG.SCRIPTS_FOLDER
def conf(self, options):
return cherrypy.config['config'].get('Scripts', options)
def updateLibrary(self):
# For uniformity reasons not removed
return
def test(self, subject, message, *args, **kwargs):
self.notify(subject, message, *args, **kwargs)
return
def list_scripts(self):
scriptdir = self.script_folder
scripts = {'': ''}
if scriptdir and not os.path.exists(scriptdir):
return scripts
for root, dirs, files in os.walk(scriptdir):
for f in files:
name, ext = os.path.splitext(f)
if ext in self.script_exts:
rfp = os.path.join(os.path.relpath(root, scriptdir), f)
fp = os.path.join(root, f)
scripts[fp] = rfp
return scripts
def notify(self, subject='', message='', notify_action='', script_args=None, *args, **kwargs):
"""
Args:
subject(string, optional): Head text,
message(string, optional): Body text,
notify_action(string): 'play'
script_args(list): ["python2", '-p', '-zomg']
"""
logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" %
(notify_action or None, script_args or None))
if script_args is None:
script_args = []
if not self.script_folder:
return
# Make sure we use the correct script..
if notify_action == 'play':
script = plexpy.CONFIG.SCRIPTS_ON_PLAY_SCRIPT
elif notify_action == 'stop':
script = plexpy.CONFIG.SCRIPTS_ON_STOP_SCRIPT
elif notify_action == 'pause':
script = plexpy.CONFIG.SCRIPTS_ON_PAUSE_SCRIPT
elif notify_action == 'resume':
script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT
elif notify_action == 'watched':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
elif notify_action == 'buffer':
script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT
elif notify_action == 'created':
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
elif notify_action == 'intdown':
script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT
elif notify_action == 'intup':
script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT
elif notify_action == 'extdown':
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'pmsupdate':
script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT
elif notify_action == 'concurrent':
script = plexpy.CONFIG.SCRIPTS_ON_CONCURRENT_SCRIPT
elif notify_action == 'newdevice':
script = plexpy.CONFIG.SCRIPTS_ON_NEWDEVICE_SCRIPT
else:
# For manual scripts
script = kwargs.get('script', '')
# Don't try to run the script if the action does not have one
if notify_action and not script:
logger.debug(u"PlexPy Notifiers :: No script selected for action %s, exiting..." % notify_action)
return
elif not script:
logger.debug(u"PlexPy Notifiers :: No script selected, exiting...")
return
name, ext = os.path.splitext(script)
if ext == '.php':
prefix = 'php'
elif ext == '.pl':
prefix = 'perl'
elif ext == '.ps1':
prefix = 'powershell -executionPolicy bypass -file'
elif ext == '.py':
prefix = 'python'
elif ext == '.pyw':
prefix = 'pythonw'
elif ext == '.rb':
prefix = 'ruby'
else:
prefix = ''
if os.name == 'nt':
script = script.encode(plexpy.SYS_ENCODING, 'ignore')
if prefix:
script = prefix.split() + [script]
else:
script = [script]
# For manual notifications
if script_args and isinstance(script_args, basestring):
# attemps for format it for the user
script_args = shlex.split(script_args)
# Windows handles unicode very badly.
# https://bugs.python.org/issue19264
if script_args and os.name == 'nt':
script_args = [s.encode(plexpy.SYS_ENCODING, 'ignore') for s in script_args]
# Allow overrides for shitty systems
if prefix and script_args:
if script_args[0] in ['python2', 'python', 'pythonw', 'php', 'ruby', 'perl']:
script[0] = script_args[0]
del script_args[0]
script.extend(script_args)
logger.debug(u"PlexPy Notifiers :: Full script is: %s" % script)
try:
p = subprocess.Popen(script, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=plexpy.CONFIG.SCRIPTS_FOLDER)
out, error = p.communicate()
status = p.returncode
if out and status:
out = out.strip()
logger.debug(u"PlexPy Notifiers :: Script returned: %s" % out)
if error:
error = error.strip()
logger.error(u"PlexPy Notifiers :: Script error: %s" % error)
return False
else:
logger.info(u"PlexPy Notifiers :: Script notification sent.")
return True
except OSError as e:
logger.error(u"PlexPy Notifiers :: Failed to run script: %s" % e)
return False
def return_config_options(self):
config_option = [{'label': 'Supported File Types',
'description': ', '.join(self.script_exts),
'input_type': 'help'
},
{'label': 'Script Folder',
'value': self.script_folder,
'name': 'scripts_folder',
'description': 'Add your script folder.',
'input_type': 'text',
},
{'label': 'Playback Start',
'value': plexpy.CONFIG.SCRIPTS_ON_PLAY_SCRIPT,
'name': 'scripts_on_play_script',
'description': 'Choose the script for on play.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Playback Stop',
'value': plexpy.CONFIG.SCRIPTS_ON_STOP_SCRIPT,
'name': 'scripts_on_stop_script',
'description': 'Choose the script for on stop.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Playback Pause',
'value': plexpy.CONFIG.SCRIPTS_ON_PAUSE_SCRIPT,
'name': 'scripts_on_pause_script',
'description': 'Choose the script for on pause.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Playback Resume',
'value': plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT,
'name': 'scripts_on_resume_script',
'description': 'Choose the script for on resume.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Watched',
'value': plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT,
'name': 'scripts_on_watched_script',
'description': 'Choose the script for on watched.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Buffer Warnings',
'value': plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT,
'name': 'scripts_on_buffer_script',
'description': 'Choose the script for buffer warnings.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Recently Added',
'value': plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT,
'name': 'scripts_on_created_script',
'description': 'Choose the script for recently added.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Down',
'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT,
'name': 'scripts_on_intdown_script',
'description': 'Choose the script for Plex server down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT,
'name': 'scripts_on_intup_script',
'description': 'Choose the script for Plex server back up.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT,
'name': 'scripts_on_extup_script',
'description': 'Choose the script for Plex remote access back up.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Update Available',
'value': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT,
'name': 'scripts_on_pmsupdate_script',
'description': 'Choose the script for Plex update available.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'User Concurrent Streams',
'value': plexpy.CONFIG.SCRIPTS_ON_CONCURRENT_SCRIPT,
'name': 'scripts_on_concurrent_script',
'description': 'Choose the script for user concurrent streams.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'User New Device',
'value': plexpy.CONFIG.SCRIPTS_ON_NEWDEVICE_SCRIPT,
'name': 'scripts_on_newdevice_script',
'description': 'Choose the script for user new device.',
'input_type': 'select',
'select_options': self.list_scripts()
}
]
return config_option
class FacebookNotifier(object):
def __init__(self):
self.redirect_uri = plexpy.CONFIG.FACEBOOK_REDIRECT_URI
self.access_token = plexpy.CONFIG.FACEBOOK_TOKEN
self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID
self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET
self.group_id = plexpy.CONFIG.FACEBOOK_GROUP
self.incl_pmslink = plexpy.CONFIG.FACEBOOK_INCL_PMSLINK
self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER
self.incl_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT
def notify(self, subject, message, **kwargs):
if not subject or not message:
return
attachment = {}
if self.incl_poster and 'metadata' in kwargs:
# Grab formatted metadata
pretty_metadata = PrettyMetadata(kwargs['metadata'])
poster_url = pretty_metadata.get_poster_url()
plex_url = pretty_metadata.get_plex_url()
poster_link = pretty_metadata.get_poster_link()
caption = pretty_metadata.get_caption()
title = pretty_metadata.get_title('\xc2\xb7'.decode('utf8'))
subtitle = pretty_metadata.get_subtitle()
# Build Facebook post attachment
if self.incl_pmslink:
caption = 'View on Plex Web'
attachment['link'] = plex_url
attachment['caption'] = caption
elif poster_link:
attachment['link'] = poster_link
attachment['caption'] = caption
else:
attachment['link'] = poster_url
attachment['picture'] = poster_url
attachment['name'] = title
attachment['description'] = subtitle
if self.incl_subject:
self._post_facebook(subject + ': ' + message, attachment=attachment)
else:
self._post_facebook(message, attachment=attachment)
def test_notify(self):
return self._post_facebook(u"PlexPy Notifiers :: This is a test notification from PlexPy at " + helpers.now())
def _get_authorization(self):
return facebook.auth_url(app_id=self.app_id,
canvas_url=self.redirect_uri + '/facebookStep2',
perms=['user_managed_groups','publish_actions'])
def _get_credentials(self, code):
logger.info(u"PlexPy Notifiers :: Requesting access token from Facebook")
try:
# Request user access token
api = facebook.GraphAPI(version='2.5')
response = api.get_access_token_from_code(code=code,
redirect_uri=self.redirect_uri + '/facebookStep2',
app_id=self.app_id,
app_secret=self.app_secret)
access_token = response['access_token']
# Request extended user access token
api = facebook.GraphAPI(access_token=access_token, version='2.5')
response = api.extend_access_token(app_id=self.app_id,
app_secret=self.app_secret)
access_token = response['access_token']
plexpy.CONFIG.FACEBOOK_TOKEN = access_token
plexpy.CONFIG.write()
except Exception as e:
logger.error(u"PlexPy Notifiers :: Error requesting Facebook access token: %s" % e)
return False
return True
def _post_facebook(self, message=None, attachment=None):
if self.group_id:
api = facebook.GraphAPI(access_token=self.access_token, version='2.5')
try:
api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment)
logger.info(u"PlexPy Notifiers :: Facebook notification sent.")
return True
except Exception as e:
logger.warn(u"PlexPy Notifiers :: Error sending Facebook post: %s" % e)
return False
else:
logger.warn(u"PlexPy Notifiers :: Error sending Facebook post: No Facebook Group ID provided.")
return False
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': 'Step 1: Visit \
Facebook Developers to add a new app using basic setup.
\
Step 2: Click Add Product on the left, then Get Started \
for Facebook Login.
\
Step 3: Fill in Valid OAuth redirect URIs with your PlexPy URL (e.g. http://localhost:8181).
\
Step 4: Click App Review on the left and toggle "make public" to Yes.
\
Step 5: Fill in the PlexPy URL below with the exact same URL from Step 3.
\
Step 6: Fill in the App ID and App Secret below.
\
Step 7: Click the Request Authorization button below.
\
Step 8: Fill in your Group ID below.',
'input_type': 'help'
},
{'label': 'PlexPy URL',
'value': self.redirect_uri,
'name': 'facebook_redirect_uri',
'description': 'Your PlexPy URL. This will tell Facebook where to redirect you after authorization.\
(e.g. http://localhost:8181)',
'input_type': 'text'
},
{'label': 'Facebook App ID',
'value': self.app_id,
'name': 'facebook_app_id',
'description': 'Your Facebook app ID.',
'input_type': 'text'
},
{'label': 'Facebook App Secret',
'value': self.app_secret,
'name': 'facebook_app_secret',
'description': 'Your Facebook app secret.',
'input_type': 'text'
},
{'label': 'Request Authorization',
'value': 'Request Authorization',
'name': 'facebookStep1',
'description': 'Request Facebook authorization. (Ensure you allow the browser pop-up).',
'input_type': 'button'
},
{'label': 'Facebook Group ID',
'value': self.group_id,
'name': 'facebook_group',
'description': 'Your Facebook Group ID.',
'input_type': 'text'
},
{'label': 'Include Poster Image',
'value': self.incl_poster,
'name': 'facebook_incl_poster',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Link to Plex Web',
'value': self.incl_pmslink,
'name': 'facebook_incl_pmslink',
'description': 'Include a link to the media in Plex Web with the notifications.
'
'If disabled, the link will go to IMDB, TVDB, TMDb, or Last.fm instead, if available.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'facebook_incl_subject',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
return config_option
class Browser(object):
def __init__(self):
self.enabled = plexpy.CONFIG.BROWSER_ENABLED
self.auto_hide_delay = plexpy.CONFIG.BROWSER_AUTO_HIDE_DELAY
def notify(self, subject, message):
if not subject or not message:
return
logger.info(u"PlexPy Notifiers :: Browser notification sent.")
return True
def get_notifications(self):
if not self.enabled:
return
monitor_db = database.MonitorDatabase()
result = monitor_db.select('SELECT subject_text, body_text FROM notify_log '
'WHERE agent_id = 17 AND timestamp >= ? ',
args=[time.time() - 3])
notifications = []
for item in result:
notification = {'subject_text': item['subject_text'],
'body_text': item['body_text'],
'delay': self.auto_hide_delay}
notifications.append(notification)
return {'notifications': notifications}
def test(self, bot_token, chat_id):
self.enabled = True
self.notify('PlexPy', 'Test Notification')
def return_config_options(self):
config_option = [{'label': 'Enable Browser Notifications',
'value': self.enabled,
'name': 'browser_enabled',
'description': 'Enable to display desktop notifications from your browser.',
'input_type': 'checkbox'
},
{'label': 'Allow Notifications',
'value': 'Allow Notifications',
'name': 'allow_browser',
'description': 'Click to allow browser notifications. You must click this button for each browser.',
'input_type': 'button'
},
{'label': 'Auto Hide Delay',
'value': self.auto_hide_delay,
'name': 'browser_auto_hide_delay',
'description': 'Set the number of seconds for the notification to remain visible. \
Set 0 to disable auto hiding. (Note: Some browsers have a maximum time limit.)',
'input_type': 'number'
}
]
return config_option
class JOIN(object):
def __init__(self):
self.apikey = plexpy.CONFIG.JOIN_APIKEY
self.deviceid = plexpy.CONFIG.JOIN_DEVICEID
self.incl_subject = plexpy.CONFIG.JOIN_INCL_SUBJECT
def conf(self, options):
return cherrypy.config['config'].get('PUSHBULLET', options)
def notify(self, message, subject):
if not message or not subject:
return
deviceid_key = 'deviceId%s' % ('s' if len(self.deviceid.split(',')) > 1 else '')
data = {'apikey': self.apikey,
deviceid_key: self.deviceid,
'text': message.encode("utf-8")}
if self.incl_subject:
data['title'] = subject.encode("utf-8")
response = requests.post('https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush',
params=data)
request_status = response.status_code
if request_status == 200:
data = json.loads(response.text)
if data.get('success'):
logger.info(u"PlexPy Notifiers :: Join notification sent.")
return True
else:
error_msg = data.get('errorMessage')
logger.info(u"PlexPy Notifiers :: Join notification failed: %s" % error_msg)
return False
elif request_status >= 400 and request_status < 500:
logger.warn(u"PlexPy Notifiers :: Join notification failed: [%s] %s" % (request_status, response.reason))
return False
else:
logger.warn(u"PlexPy Notifiers :: Join notification failed.")
return False
def test(self, apikey, deviceid):
self.enabled = True
self.apikey = apikey
self.deviceid = deviceid
self.notify('Main Screen Activate', 'Test Message')
def get_devices(self):
if self.apikey:
http_handler = HTTPSConnection("joinjoaomgcd.appspot.com")
http_handler.request("GET",
"/_ah/api/registration/v1/listDevices?%s" % urlencode({'apikey': self.apikey}))
response = http_handler.getresponse()
request_status = response.status
if request_status == 200:
data = json.loads(response.read())
if data.get('success'):
devices = data.get('records', [])
devices = {d['deviceId']: d['deviceName'] for d in devices}
devices.update({'': ''})
return devices
else:
error_msg = data.get('errorMessage')
logger.info(u"PlexPy Notifiers :: Unable to retrieve Join devices list: %s" % error_msg)
return {'': ''}
elif request_status >= 400 and request_status < 500:
logger.warn(u"PlexPy Notifiers :: Unable to retrieve Join devices list: %s" % response.reason)
return {'': ''}
else:
logger.warn(u"PlexPy Notifiers :: Unable to retrieve Join devices list.")
return {'': ''}
else:
return {'': ''}
def return_config_options(self):
devices = '
'.join(['%s: %s'
% (v, k) for k, v in self.get_devices().iteritems() if k])
if not devices:
devices = 'Enter your Join API key to load your device list.'
config_option = [{'label': 'Join API Key',
'value': self.apikey,
'name': 'join_apikey',
'description': 'Your Join API key. Required for group notifications.',
'input_type': 'text'
},
{'label': 'Device ID(s) or Group ID',
'value': self.deviceid,
'name': 'join_deviceid',
'description': 'Set your Join device ID or group ID. ' \
'Separate multiple devices with commas (,).',
'input_type': 'text',
},
{'label': 'Your Devices IDs',
'description': devices,
'input_type': 'help'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'join_incl_subject',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
return config_option
class HIPCHAT(object):
def __init__(self):
self.apiurl = plexpy.CONFIG.HIPCHAT_URL
self.color = plexpy.CONFIG.HIPCHAT_COLOR
self.emoticon = plexpy.CONFIG.HIPCHAT_EMOTICON
self.from_subject = plexpy.CONFIG.HIPCHAT_FROM_SUBJECT
self.incl_pmslink = plexpy.CONFIG.FACEBOOK_INCL_PMSLINK
self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER
self.incl_subject = plexpy.CONFIG.HIPCHAT_INCL_SUBJECT
def notify(self, message, subject, **kwargs):
if not message or not subject:
return
data = {'notify': 'false'}
text = message.encode('utf-8')
if self.incl_subject:
text = subject.encode('utf-8') + ': ' + text
if self.emoticon:
text = self.emoticon + ' ' + text
if self.color:
data['color'] = self.color
if self.from_subject:
data['from'] = subject.encode('utf-8')
if self.incl_poster and 'metadata' in kwargs:
pretty_metadata = PrettyMetadata(kwargs['metadata'])
poster_url = pretty_metadata.get_poster_url()
poster_link = pretty_metadata.get_poster_link()
caption = pretty_metadata.get_caption()
title = pretty_metadata.get_title('-')
subtitle = pretty_metadata.get_subtitle()
message = ( '{0}
'
'
{2} {3} |