From 3664879d923953e78b0440380769167264aeb896 Mon Sep 17 00:00:00 2001 From: pictuga Date: Sat, 9 Nov 2013 18:48:06 +0100 Subject: [PATCH] Add full FB API (Graph API) support --- feedify.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- morss.py | 56 +++++++++++++++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 6 deletions(-) diff --git a/feedify.py b/feedify.py index 340e7f2..be43823 100644 --- a/feedify.py +++ b/feedify.py @@ -11,6 +11,8 @@ import lxml.html import json import urlparse +import time + def toclass(query): pattern = r'\[class=([^\]]+)\]' repl = r'[@class and contains(concat(" ", normalize-space(@class), " "), " \1 ")]' @@ -59,7 +61,6 @@ def formatString(string, getter, error=False): elif re.search(r'^([^{}<>" ]+)(?:<"([^>]+)">)?(.*)$', string): match = re.search(r'^([^{}<>" ]+)(?:<"([^>]+)">)?(.*)$', string).groups() rawValue = getter(match[0]) - print repr(rawValue) if not isinstance(rawValue, basestring): if match[1] is not None: out = match[1].join(rawValue) @@ -76,9 +77,69 @@ def formatString(string, getter, error=False): else: return out +def PreWorker(url, cache): + if urlparse.urlparse(url).netloc == 'graph.facebook.com': + facebook = cache.new('facebook', True) + token = urlparse.parse_qs(urlparse.urlparse(url).query)['access_token'][0] + + if 't'+token not in facebook: + # this token ain't known, look for info about it + eurl = "https://graph.facebook.com/debug_token?input_token={token}&access_token={app_token}".format(token=token, app_token=morss.FBAPPTOKEN) + data = json.loads(urllib2.urlopen(eurl).read())['data'] + + app_id = str(data['app_id']) + user_id = str(data['user_id']) + expires = data['expires_at'] + short = 'issued_at' not in data + + facebook.set('t'+token, user_id) + facebook.set('e'+token, expires) + + good = True + + # do some woodoo to know if we already have sth better + + if 'u'+user_id not in facebook: + # grab a new one anyway, new user + facebook.set('o'+user_id, token) + good = True + else: + # maybe it's a better one + last = facebook.get('u'+user_id) + last_expires = facebook.get('e'+last, int) + + if expires > last_expires: + # new is better + good = True + + if good and short and app_id == morss.FBAPPID: + eurl = "https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={app_id}&client_secret={app_secret}&fb_exchange_token={short_lived_token}".format(app_id=morss.FBAPPID, app_secret=morss.FBSECRET, short_lived_token=token) + values = urlparse.parse_qs(urllib2.urlopen(eurl).read().strip()) + + token = values['access_token'][0] + expires = int(time.time() + int(values['expires'][0])) + + facebook.set('t'+token, user_id) + facebook.set('e'+token, expires) + + if good: + facebook.set('u'+user_id, token) + + # hey look for a newer token and use it + token = urlparse.parse_qs(urlparse.urlparse(url).query)['access_token'][0] + user_id = facebook.get('t'+token) + last = facebook.get('u'+user_id) + original = facebook.get('o'+user_id) + + nurl = url.replace(token, last) + ncache = url.replace(token, original) + cache.set('redirect', nurl) + cache.set('cache', ncache) + class Builder(object): - def __init__(self, link, data=None): + def __init__(self, link, data=None, cache=False): self.link = link + self.cache = cache if data is None: data = urllib2.urlopen(link).read() @@ -167,3 +228,18 @@ class Builder(object): feedItem['updated'] = self.string(item, 'item_time') self.feed.items.append(feedItem) + + + if urlparse.urlparse(self.link).netloc == 'graph.facebook.com': + if self.cache: + facebook = self.cache.new('facebook', True) + token = urlparse.parse_qs(urlparse.urlparse(self.link).query)['access_token'][0] + expires = facebook.get('e'+token, int) + lifespan = expires - time.time() + + if lifespan < 5*24*3600: + new = self.feed.items.append() + new.title = "APP AUTHORISATION RENEWAL NEEDED" + new.link = "https://www.facebook.com/dialog/oauth?client_id={app_id}&redirect_uri=http://test.morss.it/:facebook/".format(app_id=morss.FBAPPID) + new.desc = "Please renew your Facebook app token for this app to keep working for this feed.
Go!".format(new.link) + new.time = cache.get(expires, int) diff --git a/morss.py b/morss.py index 87c5113..1ff70f8 100644 --- a/morss.py +++ b/morss.py @@ -42,6 +42,10 @@ UA_HTML = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.11) G MIMETYPE = { 'xml': ['text/xml', 'application/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml'], 'html': ['text/html', 'application/xhtml+xml']} +FBAPPID = "" +FBSECRET = "" +FBAPPTOKEN = FBAPPID + '|' + FBSECRET + PROTOCOL = ['http', 'https', 'ftp'] if 'REQUEST_URI' in os.environ: @@ -360,9 +364,14 @@ def Fill(item, cache, feedurl='/', fast=False): else: link = None - # facebook, do nothing for now FIXME + # facebook if urlparse.urlparse(feedurl).netloc == 'graph.facebook.com': - link = None + match = lxml.html.fromstring(item.content).xpath('//a/@href') + if len(match) and urlparse.urlparse(match[0]).netloc != 'www.facebook.com': + link = match[0] + log(link) + else: + link = None if link is None: log('no used link') @@ -423,6 +432,9 @@ def Gather(url, cachePath, options): log(cache._hash) + # do some useful facebook work + feedify.PreWorker(url, cache) + if 'redirect' in cache: url = cache.get('redirect') log('url redirect') @@ -466,7 +478,7 @@ def Gather(url, cachePath, options): if style == 'normal': rss = feeds.parse(xml) elif style == 'feedify': - feed = feedify.Builder(url, xml) + feed = feedify.Builder(url, xml, cache) feed.build() rss = feed.feed elif style == 'html': @@ -526,7 +538,7 @@ if __name__ == '__main__': HOLD = True if 'HTTP_IF_NONE_MATCH' in os.environ: - if not options.force and time.time() - int(os.environ['HTTP_IF_NONE_MATCH'][1:-1]) < DELAY: + if not options.force and not options.facebook and time.time() - int(os.environ['HTTP_IF_NONE_MATCH'][1:-1]) < DELAY: print 'Status: 304' print log(url) @@ -538,6 +550,42 @@ if __name__ == '__main__': else: cachePath = os.path.expanduser('~') + '/.cache/morss' + if options.facebook: + facebook = Cache(cachePath, 'facebook', True) + + # get real token from code + code = urlparse.parse_qs(urlparse.urlparse(url).query)['code'][0] + eurl = "https://graph.facebook.com/oauth/access_token?client_id={app_id}&redirect_uri={redirect_uri}&client_secret={app_secret}&code={code_parameter}".format(app_id=FBAPPID, app_secret=FBSECRET, code_parameter=code, redirect_uri="http://test.morss.it/:facebook/") + token = urlparse.parse_qs(urllib2.urlopen(eurl).read().strip())['access_token'][0] + + # get long-lived access token + eurl = "https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={app_id}&client_secret={app_secret}&fb_exchange_token={short_lived_token}".format(app_id=FBAPPID, app_secret=FBSECRET, short_lived_token=token) + values = urlparse.parse_qs(urllib2.urlopen(eurl).read().strip()) + + ltoken = values['access_token'][0] + expires = int(time.time() + int(values['expires'][0])) + + # get user id + iurl = "https://graph.facebook.com/me?fields=id&access_token={token}".format(ltoken) + user_id = json.loads(urllib2.urlopen(iurl).read())['id'] + + # do sth out of it + facebook.set('t'+ltoken, user_id) + facebook.set('e'+ltoken, expires) + facebook.set('u'+user_id, ltoken) + + if 'o'+user_id not in token: + facebook.set('o'+user_id, ltoken) + + if 'REQUEST_URI' in os.environ: + print 'Status: 200' + print 'Content-Type: text/plain' + print '' + + print "token updated" + + sys.exit(0) + if 'REQUEST_URI' in os.environ: print 'Status: 200' print 'ETag: "%s"' % int(time.time())