diff --git a/lib/openanything.py b/lib/openanything.py new file mode 100644 index 00000000..413f576d --- /dev/null +++ b/lib/openanything.py @@ -0,0 +1,107 @@ +'''OpenAnything: a kind and thoughtful library for HTTP web services + +This program is part of 'Dive Into Python', a free Python book for +experienced programmers. Visit http://diveintopython.org/ for the +latest version. +''' + +__author__ = 'Mark Pilgrim (mark@diveintopython.org)' +__version__ = '$Revision: 1.6 $'[11:-2] +__date__ = '$Date: 2004/04/16 21:16:24 $' +__copyright__ = 'Copyright (c) 2004 Mark Pilgrim' +__license__ = 'Python' + +import urllib2, urlparse, gzip +from StringIO import StringIO + +USER_AGENT = 'OpenAnything/%s +http://diveintopython.org/http_web_services/' % __version__ + +class SmartRedirectHandler(urllib2.HTTPRedirectHandler): + def http_error_301(self, req, fp, code, msg, headers): + result = urllib2.HTTPRedirectHandler.http_error_301( + self, req, fp, code, msg, headers) + result.status = code + return result + + def http_error_302(self, req, fp, code, msg, headers): + result = urllib2.HTTPRedirectHandler.http_error_302( + self, req, fp, code, msg, headers) + result.status = code + return result + +class DefaultErrorHandler(urllib2.HTTPDefaultErrorHandler): + def http_error_default(self, req, fp, code, msg, headers): + result = urllib2.HTTPError( + req.get_full_url(), code, msg, headers, fp) + result.status = code + return result + +def openAnything(source, etag=None, lastmodified=None, agent=USER_AGENT): + """URL, filename, or string --> stream + + This function lets you define parsers that take any input source + (URL, pathname to local or network file, or actual data as a string) + and deal with it in a uniform manner. Returned object is guaranteed + to have all the basic stdio read methods (read, readline, readlines). + Just .close() the object when you're done with it. + + If the etag argument is supplied, it will be used as the value of an + If-None-Match request header. + + If the lastmodified argument is supplied, it must be a formatted + date/time string in GMT (as returned in the Last-Modified header of + a previous request). The formatted date/time will be used + as the value of an If-Modified-Since request header. + + If the agent argument is supplied, it will be used as the value of a + User-Agent request header. + """ + + if hasattr(source, 'read'): + return source + + if source == '-': + return sys.stdin + + if urlparse.urlparse(source)[0] == 'http': + # open URL with urllib2 + request = urllib2.Request(source) + request.add_header('User-Agent', agent) + if lastmodified: + request.add_header('If-Modified-Since', lastmodified) + if etag: + request.add_header('If-None-Match', etag) + request.add_header('Accept-encoding', 'gzip') + opener = urllib2.build_opener(SmartRedirectHandler(), DefaultErrorHandler()) + return opener.open(request) + + # try to open with native open function (if source is a filename) + try: + return open(source) + except (IOError, OSError): + pass + + # treat source as string + return StringIO(str(source)) + +def fetch(source, etag=None, lastmodified=None, agent=USER_AGENT): + '''Fetch data and metadata from a URL, file, stream, or string''' + result = {} + f = openAnything(source, etag, lastmodified, agent) + result['data'] = f.read() + if hasattr(f, 'headers'): + # save ETag, if the server sent one + result['etag'] = f.headers.get('ETag') + # save Last-Modified header, if the server sent one + result['lastmodified'] = f.headers.get('Last-Modified') + if f.headers.get('content-encoding') == 'gzip': + # data came back gzip-compressed, decompress it + result['data'] = gzip.GzipFile(fileobj=StringIO(result['data'])).read() + if hasattr(f, 'url'): + result['url'] = f.url + result['status'] = 200 + if hasattr(f, 'status'): + result['status'] = f.status + f.close() + return result + diff --git a/plexpy/config.py b/plexpy/config.py index d76d264a..53e6eed3 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -88,6 +88,7 @@ _CONFIG_DEFINITIONS = { 'FACEBOOK_APP_SECRET': (str, 'Facebook', ''), 'FACEBOOK_TOKEN': (str, 'Facebook', ''), 'FACEBOOK_GROUP': (str, 'Facebook', ''), + 'FACEBOOK_INCL_POSTER': (int, 'Facebook', 1), 'FACEBOOK_INCL_SUBJECT': (int, 'Facebook', 1), 'FACEBOOK_ON_PLAY': (int, 'Facebook', 0), 'FACEBOOK_ON_STOP': (int, 'Facebook', 0), diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 047e5ca5..7f71cbfd 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -20,6 +20,7 @@ import cherrypy from email.mime.text import MIMEText import email.utils from httplib import HTTPSConnection, HTTPConnection +import openanything import os import shlex import smtplib @@ -2069,9 +2070,11 @@ class FacebookNotifier(object): def __init__(self): self.redirect_uri = plexpy.CONFIG.FACEBOOK_REDIRECT_URI + self.access_token = plexpy.CONFIG.FACEBOOK_TOKEN self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET self.group_id = plexpy.CONFIG.FACEBOOK_GROUP + self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER self.incl_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT def notify(self, subject, message, **kwargs): @@ -2118,39 +2121,24 @@ class FacebookNotifier(object): return True def _post_facebook(self, message=None, **kwargs): - access_token = plexpy.CONFIG.FACEBOOK_TOKEN - group_id = plexpy.CONFIG.FACEBOOK_GROUP - - if group_id: - api = facebook.GraphAPI(access_token=access_token, version='2.5') + if self.group_id: + api = facebook.GraphAPI(access_token=self.access_token, version='2.5') attachment = {} - if 'metadata' in kwargs: + if self.incl_poster and 'metadata' in kwargs: + poster = '' + caption = 'View in Plex Web.' metadata = kwargs['metadata'] - if metadata['media_type'] == 'movie' and metadata['imdb_id']: - uri = 'i=' + metadata['imdb_id'] + if metadata['media_type'] == 'movie' and metadata.get('imdb_id', ''): title = metadata['title'] subtitle = metadata['year'] - elif metadata['media_type'] == 'show': - uri = 't=' + metadata['title'] + '&y=' + metadata['year'] - title = metadata['title'] - subtitle = metadata['year'] - elif metadata['media_type'] == 'episode': - uri = 't=' + metadata['grandparent_title'] - title = metadata['grandparent_title'] + ' - ' + metadata['title'] - subtitle = 'S' + metadata['parent_media_index'] + ' ' + '\xc2\xb7'.decode('utf8') + ' E' + metadata['media_index'] - else: - uri = '' - title = '' - subtitle = '' + uri = '/?i=' + metadata['imdb_id'] - # Get poster using OMDb API - poster = '' - if uri: + # Get poster using OMDb API http_handler = HTTPConnection("www.omdbapi.com") - http_handler.request('GET', '/?' + uri) + http_handler.request('GET', uri) response = http_handler.getresponse() request_status = response.status @@ -2161,16 +2149,47 @@ class FacebookNotifier(object): logger.warn(u"PlexPy Notifiers :: Unable to retrieve IMDB poster: %s" % response.reason) else: logger.warn(u"PlexPy Notifiers :: Unable to retrieve IMDB poster.") - + + elif (metadata['media_type'] == 'show' or metadata['media_type'] == 'episode') \ + and (metadata.get('imdb_id', '') or metadata.get('thetvdb_id', '')): + if metadata['media_type'] == 'show': + title = metadata['title'] + subtitle = metadata['year'] + elif metadata['media_type'] == 'episode': + title = metadata['grandparent_title'] + ' - ' + metadata['title'] + subtitle = 'S' + metadata['parent_media_index'] + ' ' + '\xc2\xb7'.decode('utf8') + \ + ' E' + metadata['media_index'] + + if metadata.get('imdb_id', ''): + uri = '/lookup/shows?imdb=' + metadata['imdb_id'] + elif metadata.get('thetvdb_id', ''): + uri = '/lookup/shows?thetvdb=' + metadata['thetvdb_id'] + + # Get poster using TVmaze API + request = urllib2.Request('http://api.tvmaze.com' + uri) + opener = urllib2.build_opener(openanything.SmartRedirectHandler()) + response = opener.open(request) + request_status = response.status + + if request_status == 301: + data = json.loads(response.read()) + image = data.get('image', '') + poster = image.get('original', image.get('medium','')) + elif request_status >= 400 and request_status < 500: + logger.warn(u"PlexPy Notifiers :: Unable to retrieve TVmaze poster: %s" % response.reason) + else: + logger.warn(u"PlexPy Notifiers :: Unable to retrieve TVmaze poster.") + if poster and poster != 'N/A': attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \ '/details/%2Flibrary%2Fmetadata%2F' + metadata['rating_key'] attachment['picture'] = poster attachment['name'] = title attachment['description'] = subtitle + attachment['caption'] = caption try: - api.put_wall_post(profile_id=group_id, message=message, attachment=attachment) + api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment) logger.info(u"PlexPy Notifiers :: Facebook notification sent.") except Exception as e: logger.warn(u"PlexPy Notifiers :: Error sending Facebook post: %s" % e) @@ -2227,6 +2246,13 @@ class FacebookNotifier(object): 'description': 'Your Facebook Group ID.', 'input_type': 'text' }, + {'label': 'Include Poster Image', + 'value': self.incl_poster, + 'name': 'facebook_incl_poster', + 'description': 'Include a poster in the notifications. \ + (PMS agent must be Freebase or TheTVDB. TheMovieDb is currently not supported.)', + 'input_type': 'checkbox' + }, {'label': 'Include Subject Line', 'value': self.incl_subject, 'name': 'facebook_incl_subject',