diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py
index 2a2a78d0..df2a9d27 100644
--- a/plexpy/notifiers.py
+++ b/plexpy/notifiers.py
@@ -46,49 +46,73 @@ import logger
import request
from plexpy.helpers import checked
-AGENT_IDS = {"growl": 0,
- "prowl": 1,
- "xbmc": 2,
- "plex": 3,
- "nma": 4,
- "pushalot": 5,
- "pushbullet": 6,
- "pushover": 7,
- "osx": 8,
- "boxcar": 9,
- "email": 10,
- "twitter": 11,
- "ifttt": 12,
- "telegram": 13,
- "slack": 14,
- "scripts": 15,
- "facebook": 16,
- "browser": 17,
- "join": 18,
- "hipchat": 19
+AGENT_IDS = {'growl': 0,
+ 'prowl': 1,
+ 'xbmc': 2,
+ 'plex': 3,
+ 'nma': 4,
+ 'pushalot': 5,
+ 'pushbullet': 6,
+ 'pushover': 7,
+ 'osx': 8,
+ 'boxcar': 9,
+ 'email': 10,
+ 'twitter': 11,
+ 'ifttt': 12,
+ 'telegram': 13,
+ 'slack': 14,
+ 'scripts': 15,
+ 'facebook': 16,
+ 'browser': 17,
+ 'join': 18,
+ 'hipchat': 19
}
def available_notification_agents():
- agents = [{'label': 'Growl',
+ agents = [{'label': 'Boxcar',
+ 'name': 'boxcar',
+ 'id': AGENT_IDS['boxcar']
+ },
+ {'label': 'Browser',
+ 'name': 'browser',
+ 'id': AGENT_IDS['browser']
+ },
+ {'label': 'Email',
+ 'name': 'email',
+ 'id': AGENT_IDS['email']
+ },
+ {'label': 'Facebook',
+ 'name': 'facebook',
+ 'id': AGENT_IDS['facebook']
+ },
+ {'label': 'Growl',
'name': 'growl',
'id': AGENT_IDS['growl']
},
- {'label': 'Prowl',
- 'name': 'prowl',
- 'id': AGENT_IDS['prowl']
+ {'label': 'Hipchat',
+ 'name': 'hipchat',
+ 'id': AGENT_IDS['hipchat']
},
- {'label': 'XBMC',
- 'name': 'xbmc',
- 'id': AGENT_IDS['xbmc']
+ {'label': 'IFTTT',
+ 'name': 'ifttt',
+ 'id': AGENT_IDS['ifttt']
+ },
+ {'label': 'Join',
+ 'name': 'join',
+ 'id': AGENT_IDS['join']
+ },
+ {'label': 'Notify My Android',
+ 'name': 'nma',
+ 'id': AGENT_IDS['nma']
},
{'label': 'Plex Home Theater',
'name': 'plex',
'id': AGENT_IDS['plex']
},
- {'label': 'Notify My Android',
- 'name': 'nma',
- 'id': AGENT_IDS['nma']
+ {'label': 'Prowl',
+ 'name': 'prowl',
+ 'id': AGENT_IDS['prowl']
},
{'label': 'Pushalot',
'name': 'pushalot',
@@ -102,49 +126,25 @@ def available_notification_agents():
'name': 'pushover',
'id': AGENT_IDS['pushover']
},
- {'label': 'Boxcar',
- 'name': 'boxcar',
- 'id': AGENT_IDS['boxcar']
- },
- {'label': 'Email',
- 'name': 'email',
- 'id': AGENT_IDS['email']
- },
- {'label': 'Twitter',
- 'name': 'twitter',
- 'id': AGENT_IDS['twitter']
- },
- {'label': 'IFTTT',
- 'name': 'ifttt',
- 'id': AGENT_IDS['ifttt']
- },
- {'label': 'Telegram',
- 'name': 'telegram',
- 'id': AGENT_IDS['telegram']
+ {'label': 'Scripts',
+ 'name': 'scripts',
+ 'id': AGENT_IDS['scripts']
},
{'label': 'Slack',
'name': 'slack',
'id': AGENT_IDS['slack']
},
- {'label': 'Scripts',
- 'name': 'scripts',
- 'id': AGENT_IDS['scripts']
- },
- {'label': 'Facebook',
- 'name': 'facebook',
- 'id': AGENT_IDS['facebook']
- },
- {'label': 'Browser',
- 'name': 'browser',
- 'id': AGENT_IDS['browser']
+ {'label': 'Telegram',
+ 'name': 'telegram',
+ 'id': AGENT_IDS['telegram']
},
- {'label': 'Join',
- 'name': 'join',
- 'id': AGENT_IDS['join']
+ {'label': 'Twitter',
+ 'name': 'twitter',
+ 'id': AGENT_IDS['twitter']
},
- {'label': 'Hipchat',
- 'name': 'hipchat',
- 'id': AGENT_IDS['hipchat']
+ {'label': 'XBMC',
+ 'name': 'xbmc',
+ 'id': AGENT_IDS['xbmc']
}
]
@@ -595,6 +595,444 @@ class Notifier(object):
return config_options
+class BOXCAR(Notifier):
+ """
+ Boxcar notifications
+ """
+ _DEFAULT_CONFIG = {'token': '',
+ 'sound': ''
+ }
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ try:
+ data = urllib.urlencode({
+ 'user_credentials': self.config['token'],
+ 'notification[title]': subject.encode('utf-8'),
+ 'notification[long_message]': body.encode('utf-8'),
+ 'notification[sound]': self.config['sound']
+ })
+
+ req = urllib2.Request('https://new.boxcar.io/api/notifications')
+ 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.config['token'],
+ 'name': 'boxcar_token',
+ 'description': 'Your Boxcar access token.',
+ 'input_type': 'text'
+ },
+ {'label': 'Sound',
+ 'value': self.config['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 BROWSER(Notifier):
+ """
+ Browser notifications
+ """
+ _DEFAULT_CONFIG = {'enabled': 0,
+ 'auto_hide_delay': 5
+ }
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ logger.info(u"PlexPy Notifiers :: Browser notification sent.")
+ return True
+
+ def get_notifications(self):
+ if not self.config['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.config['auto_hide_delay']}
+ notifications.append(notification)
+
+ return {'notifications': notifications}
+
+ def return_config_options(self):
+ config_option = [{'label': 'Enable Browser Notifications',
+ 'value': self.config['enabled'],
+ 'name': 'browser_enabled',
+ 'description': 'Enable to display desktop notifications from your browser.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Allow Notifications',
+ 'value': 'Allow Notifications',
+ 'name': 'browser_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.config['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 EMAIL(Notifier):
+ """
+ Email notifications
+ """
+ _DEFAULT_CONFIG = {'from_name': '',
+ 'from': '',
+ 'to': '',
+ 'cc': '',
+ 'bcc': '',
+ 'smtp_server': '',
+ 'smtp_port': 25,
+ 'smtp_user': '',
+ 'smtp_password': '',
+ 'tls': 0,
+ 'html_support': 1
+ }
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ if self.config['html_support']:
+ body = body.replace('\n', '
')
+ msg = MIMEMultipart('alternative')
+ msg.attach(MIMEText(bleach.clean(body, strip=True), 'plain', 'utf-8'))
+ msg.attach(MIMEText(body, 'html', 'utf-8'))
+ else:
+ msg = MIMEText(body, 'plain', 'utf-8')
+
+ msg['Subject'] = subject
+ msg['From'] = email.utils.formataddr((self.config['from_name'], self.config['from']))
+ msg['To'] = self.config['to']
+ msg['CC'] = self.config['cc']
+
+ recipients = [x.strip() for x in self.config['to'].split(';')] \
+ + [x.strip() for x in self.config['cc'].split(';')] \
+ + [x.strip() for x in self.config['bcc'].split(';')]
+ recipients = filter(None, recipients)
+
+ try:
+ mailserver = smtplib.SMTP(self.config['smtp_server'], self.config['smtp_port'])
+
+ if self.config['tls']:
+ mailserver.starttls()
+
+ mailserver.ehlo()
+
+ if self.config['smtp_user']:
+ mailserver.login(self.config['smtp_user'], self.config['smtp_password'])
+
+ mailserver.sendmail(self.config['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.config['from_name'],
+ 'name': 'email_from_name',
+ 'description': 'The name of the sender.',
+ 'input_type': 'text'
+ },
+ {'label': 'From',
+ 'value': self.config['from'],
+ 'name': 'email_from',
+ 'description': 'The email address of the sender.',
+ 'input_type': 'text'
+ },
+ {'label': 'To',
+ 'value': self.config['to'],
+ 'name': 'email_to',
+ 'description': 'The email address(es) of the recipients, separated by semicolons (;).',
+ 'input_type': 'text'
+ },
+ {'label': 'CC',
+ 'value': self.config['cc'],
+ 'name': 'email_cc',
+ 'description': 'The email address(es) to CC, separated by semicolons (;).',
+ 'input_type': 'text'
+ },
+ {'label': 'BCC',
+ 'value': self.config['bcc'],
+ 'name': 'email_bcc',
+ 'description': 'The email address(es) to BCC, separated by semicolons (;).',
+ 'input_type': 'text'
+ },
+ {'label': 'SMTP Server',
+ 'value': self.config['smtp_server'],
+ 'name': 'email_smtp_server',
+ 'description': 'Host for the SMTP server.',
+ 'input_type': 'text'
+ },
+ {'label': 'SMTP Port',
+ 'value': self.config['smtp_port'],
+ 'name': 'email_smtp_port',
+ 'description': 'Port for the SMTP server.',
+ 'input_type': 'number'
+ },
+ {'label': 'SMTP User',
+ 'value': self.config['smtp_user'],
+ 'name': 'email_smtp_user',
+ 'description': 'User for the SMTP server.',
+ 'input_type': 'text'
+ },
+ {'label': 'SMTP Password',
+ 'value': self.config['smtp_password'],
+ 'name': 'email_smtp_password',
+ 'description': 'Password for the SMTP server.',
+ 'input_type': 'password'
+ },
+ {'label': 'TLS',
+ 'value': self.config['tls'],
+ 'name': 'email_tls',
+ 'description': 'Does the server use encryption.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Enable HTML Support',
+ 'value': self.config['html_support'],
+ 'name': 'email_html_support',
+ 'description': 'Style your messages using HTML tags.',
+ 'input_type': 'checkbox'
+ }
+ ]
+
+ return config_option
+
+
+class FACEBOOK(Notifier):
+ """
+ Facebook notifications
+ """
+ _DEFAULT_CONFIG = {'redirect_uri': '',
+ 'access_token': '',
+ 'app_id': '',
+ 'app_secret': '',
+ 'group_id': '',
+ 'incl_pmslink': 0,
+ 'incl_poster': 0,
+ 'incl_subject': 1
+ }
+
+ def _get_authorization(self):
+ return facebook.auth_url(app_id=self.config['app_id'],
+ canvas_url=self.config['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.config['redirect_uri'] + '/facebookStep2',
+ app_id=self.config['app_id'],
+ app_secret=self.config['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.config['app_id'],
+ app_secret=self.config['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.config['group_id']:
+ api = facebook.GraphAPI(access_token=self.config['access_token'], version='2.5')
+
+ try:
+ api.put_wall_post(profile_id=self.config['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 notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ attachment = {}
+
+ if self.config['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.config['incl_pmslink']:
+ attachment['link'] = plex_url
+ attachment['caption'] = 'View on Plex Web'
+ 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.config['incl_subject']:
+ return self._post_facebook(subject + '\r\n' + body, attachment=attachment)
+ else:
+ return self._post_facebook(body, attachment=attachment)
+
+ 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.config['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.config['app_id'],
+ 'name': 'facebook_app_id',
+ 'description': 'Your Facebook app ID.',
+ 'input_type': 'text'
+ },
+ {'label': 'Facebook App Secret',
+ 'value': self.config['app_secret'],
+ 'name': 'facebook_app_secret',
+ 'description': 'Your Facebook app secret.',
+ 'input_type': 'text'
+ },
+ {'label': 'Request Authorization',
+ 'value': 'Request Authorization',
+ 'name': 'facebook_facebookStep1',
+ 'description': 'Request Facebook authorization. (Ensure you allow the browser pop-up).',
+ 'input_type': 'button'
+ },
+ {'label': 'Facebook Group ID',
+ 'value': self.config['group_id'],
+ 'name': 'facebook_group_id',
+ 'description': 'Your Facebook Group ID.',
+ 'input_type': 'text'
+ },
+ {'label': 'Include Poster Image',
+ 'value': self.config['incl_poster'],
+ 'name': 'facebook_incl_poster',
+ 'description': 'Include a poster with the notifications.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Include Link to Plex Web',
+ 'value': self.config['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.config['incl_subject'],
+ 'name': 'facebook_incl_subject',
+ 'description': 'Include the subject line with the notifications.',
+ 'input_type': 'checkbox'
+ }
+ ]
+
+ return config_option
+
+
class GROWL(Notifier):
"""
Growl notifications, for OS X.
@@ -682,11 +1120,316 @@ class GROWL(Notifier):
return config_option
-class PROWL(Notifier):
+class HIPCHAT(Notifier):
"""
- Prowl notifications.
+ Hipchat notifications
"""
- _DEFAULT_CONFIG = {'keys': '',
+ _DEFAULT_CONFIG = {'api_url': '',
+ 'color': '',
+ 'emoticon': '',
+ 'incl_pmslink': 0,
+ 'incl_poster': 0,
+ 'incl_subject': 1
+ }
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subjecy or not body:
+ return
+
+ data = {'notify': 'false'}
+
+ text = body.encode('utf-8')
+
+ if self.config['incl_subject']:
+ data['from'] = subject.encode('utf-8')
+
+ if self.config['color']:
+ data['color'] = self.config['color']
+
+ if self.config['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()
+ plex_url = pretty_metadata.get_plex_url()
+
+ card = {'title': title,
+ 'format': 'medium',
+ 'style': 'application',
+ 'id': uuid.uuid4().hex,
+ 'activity': {'html': text,
+ 'icon': {'url': poster_url}},
+ 'description': {'format': 'text',
+ 'value': subtitle},
+ 'thumbnail': {'url': poster_url}
+ }
+
+ attributes = []
+ if poster_link:
+ card['url'] = poster_link
+ attributes.append({'value': {'label': caption,
+ 'url': poster_link}})
+ if self.config['incl_pmslink']:
+ attributes.append({'value': {'label': 'View on Plex Web',
+ 'url': plex_url}})
+ if attributes:
+ card['attributes'] = attributes
+
+ data['message'] = text
+ data['card'] = card
+
+ else:
+ if self.config['emoticon']:
+ text = self.config['emoticon'] + ' ' + text
+ data['message'] = text
+ data['message_format'] = 'text'
+
+ hiphost = urlparse(self.config['api_url']).hostname
+ hipfullq = urlparse(self.config['api_url']).path + '?' + urlparse(self.config['api_url']).query
+
+ http_handler = HTTPSConnection(hiphost)
+ http_handler.request("POST",
+ hipfullq,
+ headers={'Content-type': "application/json"},
+ body=json.dumps(data))
+ response = http_handler.getresponse()
+ request_status = response.status
+
+ if request_status == 200 or request_status == 204:
+ logger.info(u"PlexPy Notifiers :: Hipchat notification sent.")
+ return True
+ elif request_status >= 400 and request_status < 500:
+ logger.warn(u"PlexPy Notifiers :: Hipchat notification failed: [%s] %s" % (request_status, response.reason))
+ return False
+ else:
+ logger.warn(u"PlexPy Notifiers :: Hipchat notification failed.")
+ return False
+
+ def return_config_options(self):
+ config_option = [{'label': 'Hipchat Custom Integrations Full URL',
+ 'value': self.config['api_url'],
+ 'name': 'hipchat_api_url',
+ 'description': 'Your Hipchat BYO integration URL. You can get a key from'
+ ' here.',
+ 'input_type': 'text'
+ },
+ {'label': 'Hipchat Color',
+ 'value': self.config['color'],
+ 'name': 'hipchat_color',
+ 'description': 'Background color for the message.',
+ 'input_type': 'select',
+ 'select_options': {'': '',
+ 'gray': 'gray',
+ 'green': 'green',
+ 'purple': 'purple',
+ 'random': 'random',
+ 'red': 'red',
+ 'yellow': 'yellow'
+ }
+ },
+ {'label': 'Hipchat Emoticon',
+ 'value': self.config['emoticon'],
+ 'name': 'hipchat_emoticon',
+ 'description': 'Include an emoticon tag at the beginning of text notifications (e.g. (taco)). Leave blank for none.'
+ ' Use a stock emoticon or create a custom emoticon'
+ ' here.',
+ 'input_type': 'text'
+ },
+ {'label': 'Include Poster',
+ 'value': self.config['incl_poster'],
+ 'name': 'hipchat_incl_poster',
+ 'description': 'Include a poster with the notifications.
Note: This will change the notification type to HTML and emoticons will no longer work.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Include Link to Plex Web',
+ 'value': self.config['incl_pmslink'],
+ 'name': 'hipchat_incl_pmslink',
+ 'description': 'Include a link to the media in Plex Web with the notifications.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Include Subject Line',
+ 'value': self.config['incl_subject'],
+ 'name': 'hipchat_incl_subject',
+ 'description': 'Includes the subject with the notifications.',
+ 'input_type': 'checkbox'
+ }
+ ]
+
+ return config_option
+
+
+class IFTTT(Notifier):
+ """
+ IFTTT notifications
+ """
+ _DEFAULT_CONFIG = {'key': '',
+ 'event': 'plexpy'
+ }
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ event = unicode(self.config['event']).format(action=action)
+
+ data = {'value1': subject.encode("utf-8"),
+ 'value2': body.encode("utf-8")}
+
+ http_handler = HTTPSConnection("maker.ifttt.com")
+ http_handler.request("POST",
+ "/trigger/%s/with/key/%s" % (event, self.config['key']),
+ headers={'Content-type': "application/json"},
+ body=json.dumps(data))
+ response = http_handler.getresponse()
+ request_status = response.status
+
+ 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 return_config_options(self):
+ config_option = [{'label': 'Ifttt Maker Channel Key',
+ 'value': self.config['key'],
+ 'name': 'ifttt_key',
+ 'description': 'Your Ifttt key. You can get a key from'
+ ' here.',
+ 'input_type': 'text'
+ },
+ {'label': 'Ifttt Event',
+ 'value': self.config['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 JOIN(Notifier):
+ """
+ Join notifications
+ """
+ _DEFAULT_CONFIG = {'apikey': '',
+ 'device_id': '',
+ 'incl_subject': 1
+ }
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ deviceid_key = 'deviceId%s' % ('s' if len(self.config['device_id'].split(',')) > 1 else '')
+
+ data = {'apikey': self.config['apikey'],
+ deviceid_key: self.config['device_id'],
+ 'text': body.encode("utf-8")}
+
+ if self.config['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 get_devices(self):
+ if self.config['apikey']:
+ http_handler = HTTPSConnection("joinjoaomgcd.appspot.com")
+ http_handler.request("GET",
+ "/_ah/api/registration/v1/listDevices?%s" % urlencode({'apikey': self.config['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.config['apikey'],
+ 'name': 'join_apikey',
+ 'description': 'Your Join API key. Required for group notifications.',
+ 'input_type': 'text',
+ 'refresh': True
+ },
+ {'label': 'Device ID(s) or Group ID',
+ 'value': self.config['device_id'],
+ 'name': 'join_device_id',
+ '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.config['incl_subject'],
+ 'name': 'join_incl_subject',
+ 'description': 'Include the subject line with the notifications.',
+ 'input_type': 'checkbox'
+ }
+ ]
+
+ return config_option
+
+
+class NMA(Notifier):
+ """
+ Notify My Android notifications
+ """
+ _DEFAULT_CONFIG = {'apikey': '',
'priority': 0
}
@@ -694,40 +1437,35 @@ class PROWL(Notifier):
if not subject or not body:
return
- data = {'apikey': self.config['keys'],
- 'application': 'PlexPy',
- 'event': subject.encode("utf-8"),
- 'description': body.encode("utf-8"),
- 'priority': self.config['priority']}
+ title = 'PlexPy'
+ batch = False
- http_handler = HTTPSConnection("api.prowlapp.com")
- http_handler.request("POST",
- "/publicapi/add",
- headers={'Content-type': "application/x-www-form-urlencoded"},
- body=urlencode(data))
- response = http_handler.getresponse()
- request_status = response.status
+ p = pynma.PyNMA()
+ keys = self.config['apikey'].split(',')
+ p.addkey(keys)
- if request_status == 200:
- logger.info(u"PlexPy Notifiers :: Prowl notification sent.")
- return True
- elif request_status == 401:
- logger.warn(u"PlexPy Notifiers :: Prowl notification failed: [%s] %s" % (request_status, response.reason))
+ if len(keys) > 1:
+ batch = True
+
+ response = p.push(title, subject, body, priority=self.config['priority'], batch_mode=batch)
+
+ if not response[self.config['apikey']][u'code'] == u'200':
+ logger.warn(u"PlexPy Notifiers :: NotifyMyAndroid notification failed.")
return False
else:
- logger.warn(u"PlexPy Notifiers :: Prowl notification failed.")
- return False
+ logger.info(u"PlexPy Notifiers :: NotifyMyAndroid notification sent.")
+ return True
def return_config_options(self):
- config_option = [{'label': 'Prowl API Key',
- 'value': self.config['keys'],
- 'name': 'prowl_keys',
- 'description': 'Your Prowl API key.',
+ config_option = [{'label': 'NotifyMyAndroid API Key',
+ 'value': self.config['apikey'],
+ 'name': 'nma_apikey',
+ 'description': 'Your NotifyMyAndroid API key. Separate multiple api keys with commas.',
'input_type': 'text'
},
{'label': 'Priority',
'value': self.config['priority'],
- 'name': 'prowl_priority',
+ 'name': 'nma_priority',
'description': 'Set the priority.',
'input_type': 'select',
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
@@ -737,89 +1475,95 @@ class PROWL(Notifier):
return config_option
-class XBMC(Notifier):
+class OSX(Notifier):
"""
- XBMC notifications
+ OSX notifications
"""
- _DEFAULT_CONFIG = {'hosts': '',
- 'username': '',
- 'password': ''
+ _DEFAULT_CONFIG = {'notify_app': '/Applications/PlexPy'
}
- def _sendhttp(self, host, command):
- url_command = urllib.urlencode(command)
- url = host + '/xbmcCmds/xbmcHttp/?' + url_command
+ def __init__(self, config=None):
+ self.set_config(config)
- if self.config['password']:
- return request.request_content(url, auth=(self.config['username'], self.config['password']))
- else:
- return request.request_content(url)
+ try:
+ self.objc = __import__("objc")
+ self.AppKit = __import__("AppKit")
+ except:
+ # logger.error(u"PlexPy Notifiers :: Cannot load OSX Notifications agent.")
+ pass
- def _sendjson(self, host, method, params={}):
- data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
- headers = {'Content-Type': 'application/json'}
- url = host + '/jsonrpc'
+ def validate(self):
+ try:
+ self.objc = __import__("objc")
+ self.AppKit = __import__("AppKit")
+ return True
+ except:
+ return False
- if self.config['password']:
- response = request.request_json(url, method="post", data=json.dumps(data), headers=headers,
- auth=(self.config['username'], self.config['password']))
- else:
- response = request.request_json(url, method="post", data=json.dumps(data), headers=headers)
+ def _swizzle(self, cls, SEL, func):
+ old_IMP = cls.instanceMethodForSelector_(SEL)
- if response:
- return response[0]['result']
+ 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 _swizzled_bundleIdentifier(self, original, swizzled):
+ return 'ade.plexpy.osxnotify'
def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
- hosts = [x.strip() for x in self.config['hosts'].split(',')]
+ subtitle = kwargs.get('subtitle', '')
+ sound = kwargs.get('sound', '')
+ image = kwargs.get('image', '')
- display_time = "3000" # in ms
+ try:
+ self._swizzle(self.objc.lookUpClass('NSBundle'),
+ b'bundleIdentifier',
+ self._swizzled_bundleIdentifier)
- for host in hosts:
- logger.info(u"PlexPy Notifiers :: Sending notification command to XMBC @ " + host)
- try:
- version = self._sendjson(host, 'Application.GetProperties', {'properties': ['version']})['version']['major']
+ NSUserNotification = self.objc.lookUpClass('NSUserNotification')
+ NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter')
+ NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool')
- if version < 12: # Eden
- notification = subject + "," + body + "," + display_time
- notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + notification + ')'}
- request = self._sendhttp(host, notifycommand)
-
- else: # Frodo
- params = {'title': subject, 'message': body, 'displaytime': int(display_time)}
- request = self._sendjson(host, 'GUI.ShowNotification', params)
-
- if not request:
- raise Exception
- else:
- logger.info(u"PlexPy Notifiers :: XBMC notification sent.")
-
- except Exception:
- logger.warn(u"PlexPy Notifiers :: XBMC notification failed.")
+ if not NSUserNotification or not NSUserNotificationCenter:
return False
- return True
-
+ pool = NSAutoreleasePool.alloc().init()
+
+ notification = NSUserNotification.alloc().init()
+ notification.setTitle_(subject)
+ if subtitle:
+ notification.setSubtitle_(subtitle)
+ if body:
+ notification.setInformativeText_(body)
+ 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 return_config_options(self):
- config_option = [{'label': 'XBMC Host:Port',
- 'value': self.config['hosts'],
- 'name': 'xbmc_hosts',
- 'description': 'Host running XBMC (e.g. http://localhost:8080). Separate multiple hosts with commas (,).',
+ config_option = [{'label': 'Register Notify App',
+ 'value': self.config['notify_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'
- },
- {'label': 'XBMC Username',
- 'value': self.config['username'],
- 'name': 'xbmc_username',
- 'description': 'Username of your XBMC client API (blank for none).',
- 'input_type': 'text'
- },
- {'label': 'XBMC Password',
- 'value': self.config['password'],
- 'name': 'xbmc_password',
- 'description': 'Password of your XBMC client API (blank for none).',
- 'input_type': 'password'
}
]
@@ -915,11 +1659,11 @@ class PLEX(Notifier):
return config_option
-class NMA(Notifier):
+class PROWL(Notifier):
"""
- Notify My Android notifications
+ Prowl notifications.
"""
- _DEFAULT_CONFIG = {'apikey': '',
+ _DEFAULT_CONFIG = {'keys': '',
'priority': 0
}
@@ -927,35 +1671,40 @@ class NMA(Notifier):
if not subject or not body:
return
- title = 'PlexPy'
- batch = False
+ data = {'apikey': self.config['keys'],
+ 'application': 'PlexPy',
+ 'event': subject.encode("utf-8"),
+ 'description': body.encode("utf-8"),
+ 'priority': self.config['priority']}
- p = pynma.PyNMA()
- keys = self.config['apikey'].split(',')
- p.addkey(keys)
+ http_handler = HTTPSConnection("api.prowlapp.com")
+ http_handler.request("POST",
+ "/publicapi/add",
+ headers={'Content-type': "application/x-www-form-urlencoded"},
+ body=urlencode(data))
+ response = http_handler.getresponse()
+ request_status = response.status
- if len(keys) > 1:
- batch = True
-
- response = p.push(title, subject, body, priority=self.config['priority'], batch_mode=batch)
-
- if not response[self.config['apikey']][u'code'] == u'200':
- logger.warn(u"PlexPy Notifiers :: NotifyMyAndroid notification failed.")
+ if request_status == 200:
+ logger.info(u"PlexPy Notifiers :: Prowl notification sent.")
+ return True
+ elif request_status == 401:
+ logger.warn(u"PlexPy Notifiers :: Prowl notification failed: [%s] %s" % (request_status, response.reason))
return False
else:
- logger.info(u"PlexPy Notifiers :: NotifyMyAndroid notification sent.")
- return True
+ logger.warn(u"PlexPy Notifiers :: Prowl notification failed.")
+ return False
def return_config_options(self):
- config_option = [{'label': 'NotifyMyAndroid API Key',
- 'value': self.config['apikey'],
- 'name': 'nma_apikey',
- 'description': 'Your NotifyMyAndroid API key. Separate multiple api keys with commas.',
+ config_option = [{'label': 'Prowl API Key',
+ 'value': self.config['keys'],
+ 'name': 'prowl_keys',
+ 'description': 'Your Prowl API key.',
'input_type': 'text'
},
{'label': 'Priority',
'value': self.config['priority'],
- 'name': 'nma_priority',
+ 'name': 'prowl_priority',
'description': 'Set the priority.',
'input_type': 'select',
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
@@ -1235,630 +1984,6 @@ class PUSHOVER(Notifier):
return config_option
-class BOXCAR(Notifier):
- """
- Boxcar notifications
- """
- _DEFAULT_CONFIG = {'token': '',
- 'sound': ''
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- try:
- data = urllib.urlencode({
- 'user_credentials': self.config['token'],
- 'notification[title]': subject.encode('utf-8'),
- 'notification[long_message]': body.encode('utf-8'),
- 'notification[sound]': self.config['sound']
- })
-
- req = urllib2.Request('https://new.boxcar.io/api/notifications')
- 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.config['token'],
- 'name': 'boxcar_token',
- 'description': 'Your Boxcar access token.',
- 'input_type': 'text'
- },
- {'label': 'Sound',
- 'value': self.config['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(Notifier):
- """
- Email notifications
- """
- _DEFAULT_CONFIG = {'from_name': '',
- 'from': '',
- 'to': '',
- 'cc': '',
- 'bcc': '',
- 'smtp_server': '',
- 'smtp_port': 25,
- 'smtp_user': '',
- 'smtp_password': '',
- 'tls': 0,
- 'html_support': 1
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- if self.config['html_support']:
- body = body.replace('\n', '
')
- msg = MIMEMultipart('alternative')
- msg.attach(MIMEText(bleach.clean(body, strip=True), 'plain', 'utf-8'))
- msg.attach(MIMEText(body, 'html', 'utf-8'))
- else:
- msg = MIMEText(body, 'plain', 'utf-8')
-
- msg['Subject'] = subject
- msg['From'] = email.utils.formataddr((self.config['from_name'], self.config['from']))
- msg['To'] = self.config['to']
- msg['CC'] = self.config['cc']
-
- recipients = [x.strip() for x in self.config['to'].split(';')] \
- + [x.strip() for x in self.config['cc'].split(';')] \
- + [x.strip() for x in self.config['bcc'].split(';')]
- recipients = filter(None, recipients)
-
- try:
- mailserver = smtplib.SMTP(self.config['smtp_server'], self.config['smtp_port'])
-
- if self.config['tls']:
- mailserver.starttls()
-
- mailserver.ehlo()
-
- if self.config['smtp_user']:
- mailserver.login(self.config['smtp_user'], self.config['smtp_password'])
-
- mailserver.sendmail(self.config['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.config['from_name'],
- 'name': 'email_from_name',
- 'description': 'The name of the sender.',
- 'input_type': 'text'
- },
- {'label': 'From',
- 'value': self.config['from'],
- 'name': 'email_from',
- 'description': 'The email address of the sender.',
- 'input_type': 'text'
- },
- {'label': 'To',
- 'value': self.config['to'],
- 'name': 'email_to',
- 'description': 'The email address(es) of the recipients, separated by semicolons (;).',
- 'input_type': 'text'
- },
- {'label': 'CC',
- 'value': self.config['cc'],
- 'name': 'email_cc',
- 'description': 'The email address(es) to CC, separated by semicolons (;).',
- 'input_type': 'text'
- },
- {'label': 'BCC',
- 'value': self.config['bcc'],
- 'name': 'email_bcc',
- 'description': 'The email address(es) to BCC, separated by semicolons (;).',
- 'input_type': 'text'
- },
- {'label': 'SMTP Server',
- 'value': self.config['smtp_server'],
- 'name': 'email_smtp_server',
- 'description': 'Host for the SMTP server.',
- 'input_type': 'text'
- },
- {'label': 'SMTP Port',
- 'value': self.config['smtp_port'],
- 'name': 'email_smtp_port',
- 'description': 'Port for the SMTP server.',
- 'input_type': 'number'
- },
- {'label': 'SMTP User',
- 'value': self.config['smtp_user'],
- 'name': 'email_smtp_user',
- 'description': 'User for the SMTP server.',
- 'input_type': 'text'
- },
- {'label': 'SMTP Password',
- 'value': self.config['smtp_password'],
- 'name': 'email_smtp_password',
- 'description': 'Password for the SMTP server.',
- 'input_type': 'password'
- },
- {'label': 'TLS',
- 'value': self.config['tls'],
- 'name': 'email_tls',
- 'description': 'Does the server use encryption.',
- 'input_type': 'checkbox'
- },
- {'label': 'Enable HTML Support',
- 'value': self.config['html_support'],
- 'name': 'email_html_support',
- 'description': 'Style your messages using HTML tags.',
- 'input_type': 'checkbox'
- }
- ]
-
- return config_option
-
-
-class TWITTER(Notifier):
- """
- Twitter notifications
- """
- REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
- ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
- AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
- SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
- _DEFAULT_CONFIG = {'access_token': '',
- 'access_token_secret': '',
- 'consumer_key': '',
- 'consumer_secret': '',
- 'incl_poster': 0,
- 'incl_subject': 1
- }
-
- def _send_tweet(self, message=None, attachment=None):
- consumer_key = self.config['consumer_key']
- consumer_secret = self.config['consumer_secret']
- access_token = self.config['access_token']
- access_token_secret = self.config['access_token_secret']
-
- # logger.info(u"PlexPy Notifiers :: Sending tweet: " + message)
-
- api = twitter.Api(consumer_key, consumer_secret, access_token, access_token_secret)
-
- try:
- api.PostUpdate(message, media=attachment)
- logger.info(u"PlexPy Notifiers :: Twitter notification sent.")
- return True
- except Exception as e:
- logger.warn(u"PlexPy Notifiers :: Twitter notification failed: %s" % e)
- return False
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- poster_url = ''
- if self.config['incl_poster'] and 'metadata' in kwargs:
- metadata = kwargs['metadata']
- poster_url = metadata.get('poster_url','')
-
- if self.config['incl_subject']:
- return self._send_tweet(subject + '\r\n' + body, attachment=poster_url)
- else:
- return self._send_tweet(body, attachment=poster_url)
-
- def return_config_options(self):
- config_option = [{'label': 'Instructions',
- 'description': 'Step 1: Visit \
- Twitter Apps to Create New App. A vaild "Website" is not required.
\
- 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.config['consumer_key'],
- 'name': 'twitter_consumer_key',
- 'description': 'Your Twitter consumer key.',
- 'input_type': 'text'
- },
- {'label': 'Twitter Consumer Secret',
- 'value': self.config['consumer_secret'],
- 'name': 'twitter_consumer_secret',
- 'description': 'Your Twitter consumer secret.',
- 'input_type': 'text'
- },
- {'label': 'Twitter Access Token',
- 'value': self.config['access_token'],
- 'name': 'twitter_access_token',
- 'description': 'Your Twitter access token.',
- 'input_type': 'text'
- },
- {'label': 'Twitter Access Token Secret',
- 'value': self.config['access_token_secret'],
- 'name': 'twitter_access_token_secret',
- 'description': 'Your Twitter access token secret.',
- 'input_type': 'text'
- },
- {'label': 'Include Poster Image',
- 'value': self.config['incl_poster'],
- 'name': 'twitter_incl_poster',
- 'description': 'Include a poster with the notifications.',
- 'input_type': 'checkbox'
- },
- {'label': 'Include Subject Line',
- 'value': self.config['incl_subject'],
- 'name': 'twitter_incl_subject',
- 'description': 'Include the subject line with the notifications.',
- 'input_type': 'checkbox'
- }
- ]
-
- return config_option
-
-
-class IFTTT(Notifier):
- """
- IFTTT notifications
- """
- _DEFAULT_CONFIG = {'key': '',
- 'event': 'plexpy'
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- event = unicode(self.config['event']).format(action=action)
-
- data = {'value1': subject.encode("utf-8"),
- 'value2': body.encode("utf-8")}
-
- http_handler = HTTPSConnection("maker.ifttt.com")
- http_handler.request("POST",
- "/trigger/%s/with/key/%s" % (event, self.config['key']),
- headers={'Content-type': "application/json"},
- body=json.dumps(data))
- response = http_handler.getresponse()
- request_status = response.status
-
- 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 return_config_options(self):
- config_option = [{'label': 'Ifttt Maker Channel Key',
- 'value': self.config['key'],
- 'name': 'ifttt_key',
- 'description': 'Your Ifttt key. You can get a key from'
- ' here.',
- 'input_type': 'text'
- },
- {'label': 'Ifttt Event',
- 'value': self.config['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(Notifier):
- """
- Telegram notifications
- """
- _DEFAULT_CONFIG = {'bot_token': '',
- 'chat_id': '',
- 'disable_web_preview': 0,
- 'html_support': 1,
- 'incl_poster': 0,
- 'incl_subject': 1
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not body or not subject:
- return
-
- data = {'chat_id': self.config['chat_id']}
-
- if self.config['incl_subject']:
- text = subject.encode('utf-8') + '\r\n' + body.encode('utf-8')
- else:
- text = body.encode('utf-8')
-
- if self.config['incl_poster'] and 'metadata' in kwargs:
- poster_data = {'chat_id': self.config['chat_id'],
- 'disable_notification': True}
-
- 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.config['bot_token'], 'sendPhoto'),
- data=poster_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.config['html_support']:
- data['parse_mode'] = 'HTML'
-
- if self.config['disable_web_preview']:
- data['disable_web_page_preview'] = True
-
- http_handler = HTTPSConnection("api.telegram.org")
- http_handler.request('POST',
- '/bot%s/%s' % (self.config['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 return_config_options(self):
- config_option = [{'label': 'Telegram Bot Token',
- 'value': self.config['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.config['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.config['incl_poster'],
- 'name': 'telegram_incl_poster',
- 'description': 'Include a poster with the notifications.',
- 'input_type': 'checkbox'
- },
- {'label': 'Include Subject Line',
- 'value': self.config['incl_subject'],
- 'name': 'telegram_incl_subject',
- 'description': 'Include the subject line with the notifications.',
- 'input_type': 'checkbox'
- },
- {'label': 'Enable HTML Support',
- 'value': self.config['html_support'],
- 'name': 'telegram_html_support',
- 'description': 'Style your messages using these HTML tags: b, i, a[href], code, pre.',
- 'input_type': 'checkbox'
- },
- {'label': 'Disable Web Page Previews',
- 'value': self.config['disable_web_preview'],
- 'name': 'telegram_disable_web_preview',
- 'description': 'Disables automatic link previews for links in the message',
- 'input_type': 'checkbox'
- }
- ]
-
- return config_option
-
-
-class SLACK(Notifier):
- """
- Slack Notifications
- """
- _DEFAULT_CONFIG = {'hook': '',
- 'channel': '',
- 'username': '',
- 'icon_emoji': '',
- 'incl_pmslink': 0,
- 'incl_poster': 0,
- 'incl_subject': 1
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- if self.config['incl_subject']:
- text = subject.encode('utf-8') + '\r\n' + body.encode("utf-8")
- else:
- text = body.encode("utf-8")
-
- data = {'text': text}
- if self.config['channel'] != '':
- data['channel'] = self.config['channel']
- if self.config['username'] != '':
- data['username'] = self.config['username']
- if self.config['icon_emoji'] != '':
- if urlparse(self.config['icon_emoji']).scheme == '':
- data['icon_emoji'] = self.config['icon_emoji']
- else:
- data['icon_url'] = self.config['icon_emoji']
-
- if self.config['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()
- subtitle = pretty_metadata.get_subtitle()
-
- # Build Slack post attachment
- attachment = {'fallback': 'Image for %s' % title,
- 'title': title,
- 'text': subtitle,
- 'image_url': poster_url,
- 'thumb_url': poster_url
- }
-
- fields = []
- if poster_link:
- attachment['title_link'] = poster_link
- fields.append({'value': '<%s|%s>' % (poster_link, caption),
- 'short': True})
- if self.config['incl_pmslink']:
- fields.append({'value': '<%s|%s>' % (plex_url, 'View on Plex Web'),
- 'short': True})
- if fields:
- attachment['fields'] = fields
-
- data['attachments'] = [attachment]
-
- slackhost = urlparse(self.config['hook']).hostname
- slackpath = urlparse(self.config['hook']).path
-
- http_handler = HTTPSConnection(slackhost)
- http_handler.request("POST",
- slackpath,
- headers={'Content-type': "application/json"},
- 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 return_config_options(self):
- config_option = [{'label': 'Slack Webhook URL',
- 'value': self.config['hook'],
- 'name': 'slack_hook',
- 'description': 'Your Slack incoming webhook URL.',
- 'input_type': 'text'
- },
- {'label': 'Slack Channel',
- 'value': self.config['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.config['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.config['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.config['incl_poster'],
- 'name': 'slack_incl_poster',
- 'description': 'Include a poster with the notifications.',
- 'input_type': 'checkbox'
- },
- {'label': 'Include Link to Plex Web',
- 'value': self.config['incl_pmslink'],
- 'name': 'slack_incl_pmslink',
- 'description': 'Include a second link to the media in Plex Web with the notifications.',
- 'input_type': 'checkbox'
- },
- {'label': 'Include Subject Line',
- 'value': self.config['incl_subject'],
- 'name': 'slack_incl_subject',
- 'description': 'Include the subject line with the notifications.',
- 'input_type': 'checkbox'
- }
- ]
-
- return config_option
-
-
class SCRIPTS(Notifier):
"""
Script notifications
@@ -2039,72 +2164,38 @@ class SCRIPTS(Notifier):
return config_option
-class FACEBOOK(Notifier):
+class SLACK(Notifier):
"""
- Facebook notifications
+ Slack Notifications
"""
- _DEFAULT_CONFIG = {'redirect_uri': '',
- 'access_token': '',
- 'app_id': '',
- 'app_secret': '',
- 'group_id': '',
+ _DEFAULT_CONFIG = {'hook': '',
+ 'channel': '',
+ 'username': '',
+ 'icon_emoji': '',
'incl_pmslink': 0,
'incl_poster': 0,
'incl_subject': 1
}
- def _get_authorization(self):
- return facebook.auth_url(app_id=self.config['app_id'],
- canvas_url=self.config['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.config['redirect_uri'] + '/facebookStep2',
- app_id=self.config['app_id'],
- app_secret=self.config['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.config['app_id'],
- app_secret=self.config['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.config['group_id']:
- api = facebook.GraphAPI(access_token=self.config['access_token'], version='2.5')
-
- try:
- api.put_wall_post(profile_id=self.config['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 notify(self, subject='', body='', action='', **kwargs):
if not subject or not body:
return
- attachment = {}
+ if self.config['incl_subject']:
+ text = subject.encode('utf-8') + '\r\n' + body.encode("utf-8")
+ else:
+ text = body.encode("utf-8")
+
+ data = {'text': text}
+ if self.config['channel'] != '':
+ data['channel'] = self.config['channel']
+ if self.config['username'] != '':
+ data['username'] = self.config['username']
+ if self.config['icon_emoji'] != '':
+ if urlparse(self.config['icon_emoji']).scheme == '':
+ data['icon_emoji'] = self.config['icon_emoji']
+ else:
+ data['icon_url'] = self.config['icon_emoji']
if self.config['incl_poster'] and 'metadata' in kwargs:
# Grab formatted metadata
@@ -2113,89 +2204,92 @@ class FACEBOOK(Notifier):
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'))
+ title = pretty_metadata.get_title()
subtitle = pretty_metadata.get_subtitle()
- # Build Facebook post attachment
+ # Build Slack post attachment
+ attachment = {'fallback': 'Image for %s' % title,
+ 'title': title,
+ 'text': subtitle,
+ 'image_url': poster_url,
+ 'thumb_url': poster_url
+ }
+
+ fields = []
+ if poster_link:
+ attachment['title_link'] = poster_link
+ fields.append({'value': '<%s|%s>' % (poster_link, caption),
+ 'short': True})
if self.config['incl_pmslink']:
- attachment['link'] = plex_url
- attachment['caption'] = 'View on Plex Web'
- elif poster_link:
- attachment['link'] = poster_link
- attachment['caption'] = caption
- else:
- attachment['link'] = poster_url
+ fields.append({'value': '<%s|%s>' % (plex_url, 'View on Plex Web'),
+ 'short': True})
+ if fields:
+ attachment['fields'] = fields
- attachment['picture'] = poster_url
- attachment['name'] = title
- attachment['description'] = subtitle
+ data['attachments'] = [attachment]
- if self.config['incl_subject']:
- return self._post_facebook(subject + '\r\n' + body, attachment=attachment)
+ slackhost = urlparse(self.config['hook']).hostname
+ slackpath = urlparse(self.config['hook']).path
+
+ http_handler = HTTPSConnection(slackhost)
+ http_handler.request("POST",
+ slackpath,
+ headers={'Content-type': "application/json"},
+ 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:
- return self._post_facebook(body, attachment=attachment)
+ logger.warn(u"PlexPy Notifiers :: Slack notification failed.")
+ 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.config['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)',
+ config_option = [{'label': 'Slack Webhook URL',
+ 'value': self.config['hook'],
+ 'name': 'slack_hook',
+ 'description': 'Your Slack incoming webhook URL.',
'input_type': 'text'
},
- {'label': 'Facebook App ID',
- 'value': self.config['app_id'],
- 'name': 'facebook_app_id',
- 'description': 'Your Facebook app ID.',
+ {'label': 'Slack Channel',
+ 'value': self.config['channel'],
+ 'name': 'slack_channel',
+ 'description': 'Your Slack channel name (begin with \'#\'). Leave blank for webhook integration default.',
'input_type': 'text'
},
- {'label': 'Facebook App Secret',
- 'value': self.config['app_secret'],
- 'name': 'facebook_app_secret',
- 'description': 'Your Facebook app secret.',
- 'input_type': 'text'
+ {'label': 'Slack Username',
+ 'value': self.config['username'],
+ 'name': 'slack_username',
+ 'description': 'The Slack username which will be shown. Leave blank for webhook integration default.',
+ 'input_type': 'text'
},
- {'label': 'Request Authorization',
- 'value': 'Request Authorization',
- 'name': 'facebook_facebookStep1',
- 'description': 'Request Facebook authorization. (Ensure you allow the browser pop-up).',
- 'input_type': 'button'
- },
- {'label': 'Facebook Group ID',
- 'value': self.config['group_id'],
- 'name': 'facebook_group_id',
- 'description': 'Your Facebook Group ID.',
- 'input_type': 'text'
+ {'label': 'Slack Icon',
+ 'value': self.config['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.config['incl_poster'],
- 'name': 'facebook_incl_poster',
+ 'name': 'slack_incl_poster',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Link to Plex Web',
'value': self.config['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.',
+ 'name': 'slack_incl_pmslink',
+ 'description': 'Include a second link to the media in Plex Web with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.config['incl_subject'],
- 'name': 'facebook_incl_subject',
+ 'name': 'slack_incl_subject',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
@@ -2204,304 +2298,117 @@ class FACEBOOK(Notifier):
return config_option
-class BROWSER(Notifier):
+class TELEGRAM(Notifier):
"""
- Browser notifications
+ Telegram notifications
"""
- _DEFAULT_CONFIG = {'enabled': 0,
- 'auto_hide_delay': 5
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- logger.info(u"PlexPy Notifiers :: Browser notification sent.")
- return True
-
- def get_notifications(self):
- if not self.config['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.config['auto_hide_delay']}
- notifications.append(notification)
-
- return {'notifications': notifications}
-
- def return_config_options(self):
- config_option = [{'label': 'Enable Browser Notifications',
- 'value': self.config['enabled'],
- 'name': 'browser_enabled',
- 'description': 'Enable to display desktop notifications from your browser.',
- 'input_type': 'checkbox'
- },
- {'label': 'Allow Notifications',
- 'value': 'Allow Notifications',
- 'name': 'browser_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.config['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(Notifier):
- """
- Join notifications
- """
- _DEFAULT_CONFIG = {'apikey': '',
- 'device_id': '',
- 'incl_subject': 1
- }
-
- def notify(self, subject='', body='', action='', **kwargs):
- if not subject or not body:
- return
-
- deviceid_key = 'deviceId%s' % ('s' if len(self.config['device_id'].split(',')) > 1 else '')
-
- data = {'apikey': self.config['apikey'],
- deviceid_key: self.config['device_id'],
- 'text': body.encode("utf-8")}
-
- if self.config['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 get_devices(self):
- if self.config['apikey']:
- http_handler = HTTPSConnection("joinjoaomgcd.appspot.com")
- http_handler.request("GET",
- "/_ah/api/registration/v1/listDevices?%s" % urlencode({'apikey': self.config['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.config['apikey'],
- 'name': 'join_apikey',
- 'description': 'Your Join API key. Required for group notifications.',
- 'input_type': 'text',
- 'refresh': True
- },
- {'label': 'Device ID(s) or Group ID',
- 'value': self.config['device_id'],
- 'name': 'join_device_id',
- '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.config['incl_subject'],
- 'name': 'join_incl_subject',
- 'description': 'Include the subject line with the notifications.',
- 'input_type': 'checkbox'
- }
- ]
-
- return config_option
-
-
-class HIPCHAT(Notifier):
- """
- Hipchat notifications
- """
- _DEFAULT_CONFIG = {'api_url': '',
- 'color': '',
- 'emoticon': '',
- 'incl_pmslink': 0,
+ _DEFAULT_CONFIG = {'bot_token': '',
+ 'chat_id': '',
+ 'disable_web_preview': 0,
+ 'html_support': 1,
'incl_poster': 0,
'incl_subject': 1
}
def notify(self, subject='', body='', action='', **kwargs):
- if not subjecy or not body:
+ if not body or not subject:
return
- data = {'notify': 'false'}
-
- text = body.encode('utf-8')
+ data = {'chat_id': self.config['chat_id']}
if self.config['incl_subject']:
- data['from'] = subject.encode('utf-8')
-
- if self.config['color']:
- data['color'] = self.config['color']
+ text = subject.encode('utf-8') + '\r\n' + body.encode('utf-8')
+ else:
+ text = body.encode('utf-8')
if self.config['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()
- plex_url = pretty_metadata.get_plex_url()
+ poster_data = {'chat_id': self.config['chat_id'],
+ 'disable_notification': True}
- card = {'title': title,
- 'format': 'medium',
- 'style': 'application',
- 'id': uuid.uuid4().hex,
- 'activity': {'html': text,
- 'icon': {'url': poster_url}},
- 'description': {'format': 'text',
- 'value': subtitle},
- 'thumbnail': {'url': poster_url}
- }
+ metadata = kwargs['metadata']
+ poster_url = metadata.get('poster_url','')
- attributes = []
- if poster_link:
- card['url'] = poster_link
- attributes.append({'value': {'label': caption,
- 'url': poster_link}})
- if self.config['incl_pmslink']:
- attributes.append({'value': {'label': 'View on Plex Web',
- 'url': plex_url}})
- if attributes:
- card['attributes'] = attributes
+ if poster_url:
+ files = {'photo': (poster_url, urllib.urlopen(poster_url).read())}
+ response = requests.post('https://api.telegram.org/bot%s/%s' % (self.config['bot_token'], 'sendPhoto'),
+ data=poster_data,
+ files=files)
+ request_status = response.status_code
+ request_content = json.loads(response.text)
- data['message'] = text
- data['card'] = card
+ 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.")
- else:
- if self.config['emoticon']:
- text = self.config['emoticon'] + ' ' + text
- data['message'] = text
- data['message_format'] = 'text'
+ data['text'] = text
- hiphost = urlparse(self.config['api_url']).hostname
- hipfullq = urlparse(self.config['api_url']).path + '?' + urlparse(self.config['api_url']).query
+ if self.config['html_support']:
+ data['parse_mode'] = 'HTML'
+
+ if self.config['disable_web_preview']:
+ data['disable_web_page_preview'] = True
+
+ http_handler = HTTPSConnection("api.telegram.org")
+ http_handler.request('POST',
+ '/bot%s/%s' % (self.config['bot_token'], 'sendMessage'),
+ headers={'Content-type': 'application/x-www-form-urlencoded'},
+ body=urlencode(data))
- http_handler = HTTPSConnection(hiphost)
- http_handler.request("POST",
- hipfullq,
- headers={'Content-type': "application/json"},
- body=json.dumps(data))
response = http_handler.getresponse()
request_status = response.status
- if request_status == 200 or request_status == 204:
- logger.info(u"PlexPy Notifiers :: Hipchat notification sent.")
+ 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 :: Hipchat notification failed: [%s] %s" % (request_status, response.reason))
+ logger.warn(u"PlexPy Notifiers :: Telegram notification failed: [%s] %s" % (request_status, response.reason))
return False
else:
- logger.warn(u"PlexPy Notifiers :: Hipchat notification failed.")
+ logger.warn(u"PlexPy Notifiers :: Telegram notification failed.")
return False
def return_config_options(self):
- config_option = [{'label': 'Hipchat Custom Integrations Full URL',
- 'value': self.config['api_url'],
- 'name': 'hipchat_api_url',
- 'description': 'Your Hipchat BYO integration URL. You can get a key from'
- ' here.',
+ config_option = [{'label': 'Telegram Bot Token',
+ 'value': self.config['bot_token'],
+ 'name': 'telegram_bot_token',
+ 'description': 'Your Telegram bot token. '
+ 'Contact @BotFather'
+ ' on Telegram to get one.',
'input_type': 'text'
},
- {'label': 'Hipchat Color',
- 'value': self.config['color'],
- 'name': 'hipchat_color',
- 'description': 'Background color for the message.',
- 'input_type': 'select',
- 'select_options': {'': '',
- 'gray': 'gray',
- 'green': 'green',
- 'purple': 'purple',
- 'random': 'random',
- 'red': 'red',
- 'yellow': 'yellow'
- }
- },
- {'label': 'Hipchat Emoticon',
- 'value': self.config['emoticon'],
- 'name': 'hipchat_emoticon',
- 'description': 'Include an emoticon tag at the beginning of text notifications (e.g. (taco)). Leave blank for none.'
- ' Use a stock emoticon or create a custom emoticon'
- ' here.',
+ {'label': 'Telegram Chat ID, Group ID, or Channel Username',
+ 'value': self.config['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',
+ {'label': 'Include Poster Image',
'value': self.config['incl_poster'],
- 'name': 'hipchat_incl_poster',
- 'description': 'Include a poster with the notifications.
Note: This will change the notification type to HTML and emoticons will no longer work.',
- 'input_type': 'checkbox'
- },
- {'label': 'Include Link to Plex Web',
- 'value': self.config['incl_pmslink'],
- 'name': 'hipchat_incl_pmslink',
- 'description': 'Include a link to the media in Plex Web with the notifications.',
+ 'name': 'telegram_incl_poster',
+ 'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.config['incl_subject'],
- 'name': 'hipchat_incl_subject',
- 'description': 'Includes the subject with the notifications.',
+ 'name': 'telegram_incl_subject',
+ 'description': 'Include the subject line with the notifications.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Enable HTML Support',
+ 'value': self.config['html_support'],
+ 'name': 'telegram_html_support',
+ 'description': 'Style your messages using these HTML tags: b, i, a[href], code, pre.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Disable Web Page Previews',
+ 'value': self.config['disable_web_preview'],
+ 'name': 'telegram_disable_web_preview',
+ 'description': 'Disables automatic link previews for links in the message',
'input_type': 'checkbox'
}
]
@@ -2509,100 +2416,193 @@ class HIPCHAT(Notifier):
return config_option
-class OSX(Notifier):
+class TWITTER(Notifier):
"""
- OSX notifications
+ Twitter notifications
"""
- _DEFAULT_CONFIG = {'notify_app': '/Applications/PlexPy'
+ REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
+ ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
+ AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
+ SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
+ _DEFAULT_CONFIG = {'access_token': '',
+ 'access_token_secret': '',
+ 'consumer_key': '',
+ 'consumer_secret': '',
+ 'incl_poster': 0,
+ 'incl_subject': 1
}
- def __init__(self, config=None):
- self.set_config(config)
+ def _send_tweet(self, message=None, attachment=None):
+ consumer_key = self.config['consumer_key']
+ consumer_secret = self.config['consumer_secret']
+ access_token = self.config['access_token']
+ access_token_secret = self.config['access_token_secret']
+
+ # logger.info(u"PlexPy Notifiers :: Sending tweet: " + message)
+
+ api = twitter.Api(consumer_key, consumer_secret, access_token, access_token_secret)
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")
+ api.PostUpdate(message, media=attachment)
+ logger.info(u"PlexPy Notifiers :: Twitter notification sent.")
return True
- except:
+ except Exception as e:
+ logger.warn(u"PlexPy Notifiers :: Twitter notification failed: %s" % e)
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 _swizzled_bundleIdentifier(self, original, swizzled):
- return 'ade.plexpy.osxnotify'
-
def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
- subtitle = kwargs.get('subtitle', '')
- sound = kwargs.get('sound', '')
- image = kwargs.get('image', '')
+ poster_url = ''
+ if self.config['incl_poster'] and 'metadata' in kwargs:
+ metadata = kwargs['metadata']
+ poster_url = metadata.get('poster_url','')
- 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_(subject)
- if subtitle:
- notification.setSubtitle_(subtitle)
- if body:
- notification.setInformativeText_(body)
- 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
+ if self.config['incl_subject']:
+ return self._send_tweet(subject + '\r\n' + body, attachment=poster_url)
+ else:
+ return self._send_tweet(body, attachment=poster_url)
def return_config_options(self):
- config_option = [{'label': 'Register Notify App',
- 'value': self.config['notify_app'],
- 'name': 'osx_notify_app',
- 'description': 'Enter the path/application name to be registered with the '
- 'Notification Center, default is /Applications/PlexPy.',
+ config_option = [{'label': 'Instructions',
+ 'description': 'Step 1: Visit \
+ Twitter Apps to Create New App. A vaild "Website" is not required.
\
+ 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.config['consumer_key'],
+ 'name': 'twitter_consumer_key',
+ 'description': 'Your Twitter consumer key.',
'input_type': 'text'
+ },
+ {'label': 'Twitter Consumer Secret',
+ 'value': self.config['consumer_secret'],
+ 'name': 'twitter_consumer_secret',
+ 'description': 'Your Twitter consumer secret.',
+ 'input_type': 'text'
+ },
+ {'label': 'Twitter Access Token',
+ 'value': self.config['access_token'],
+ 'name': 'twitter_access_token',
+ 'description': 'Your Twitter access token.',
+ 'input_type': 'text'
+ },
+ {'label': 'Twitter Access Token Secret',
+ 'value': self.config['access_token_secret'],
+ 'name': 'twitter_access_token_secret',
+ 'description': 'Your Twitter access token secret.',
+ 'input_type': 'text'
+ },
+ {'label': 'Include Poster Image',
+ 'value': self.config['incl_poster'],
+ 'name': 'twitter_incl_poster',
+ 'description': 'Include a poster with the notifications.',
+ 'input_type': 'checkbox'
+ },
+ {'label': 'Include Subject Line',
+ 'value': self.config['incl_subject'],
+ 'name': 'twitter_incl_subject',
+ 'description': 'Include the subject line with the notifications.',
+ 'input_type': 'checkbox'
}
]
return config_option
-
+
+
+class XBMC(Notifier):
+ """
+ XBMC notifications
+ """
+ _DEFAULT_CONFIG = {'hosts': '',
+ 'username': '',
+ 'password': ''
+ }
+
+ def _sendhttp(self, host, command):
+ url_command = urllib.urlencode(command)
+ url = host + '/xbmcCmds/xbmcHttp/?' + url_command
+
+ if self.config['password']:
+ return request.request_content(url, auth=(self.config['username'], self.config['password']))
+ else:
+ return request.request_content(url)
+
+ def _sendjson(self, host, method, params={}):
+ data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
+ headers = {'Content-Type': 'application/json'}
+ url = host + '/jsonrpc'
+
+ if self.config['password']:
+ response = request.request_json(url, method="post", data=json.dumps(data), headers=headers,
+ auth=(self.config['username'], self.config['password']))
+ else:
+ response = request.request_json(url, method="post", data=json.dumps(data), headers=headers)
+
+ if response:
+ return response[0]['result']
+
+ def notify(self, subject='', body='', action='', **kwargs):
+ if not subject or not body:
+ return
+
+ hosts = [x.strip() for x in self.config['hosts'].split(',')]
+
+ display_time = "3000" # in ms
+
+ for host in hosts:
+ logger.info(u"PlexPy Notifiers :: Sending notification command to XMBC @ " + host)
+ try:
+ version = self._sendjson(host, 'Application.GetProperties', {'properties': ['version']})['version']['major']
+
+ if version < 12: # Eden
+ notification = subject + "," + body + "," + display_time
+ notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + notification + ')'}
+ request = self._sendhttp(host, notifycommand)
+
+ else: # Frodo
+ params = {'title': subject, 'message': body, 'displaytime': int(display_time)}
+ request = self._sendjson(host, 'GUI.ShowNotification', params)
+
+ if not request:
+ raise Exception
+ else:
+ logger.info(u"PlexPy Notifiers :: XBMC notification sent.")
+
+ except Exception:
+ logger.warn(u"PlexPy Notifiers :: XBMC notification failed.")
+ return False
+
+ return True
+
+ def return_config_options(self):
+ config_option = [{'label': 'XBMC Host:Port',
+ 'value': self.config['hosts'],
+ 'name': 'xbmc_hosts',
+ 'description': 'Host running XBMC (e.g. http://localhost:8080). Separate multiple hosts with commas (,).',
+ 'input_type': 'text'
+ },
+ {'label': 'XBMC Username',
+ 'value': self.config['username'],
+ 'name': 'xbmc_username',
+ 'description': 'Username of your XBMC client API (blank for none).',
+ 'input_type': 'text'
+ },
+ {'label': 'XBMC Password',
+ 'value': self.config['password'],
+ 'name': 'xbmc_password',
+ 'description': 'Password of your XBMC client API (blank for none).',
+ 'input_type': 'password'
+ }
+ ]
+
+ return config_option
+
def upgrade_config_to_db():
logger.info(u"PlexPy Notifiers :: Upgrading to new notification system...")