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...")