From 62140ac855e1c635c25c21e5019937f2b019bb59 Mon Sep 17 00:00:00 2001 From: Andrew Williams Date: Thu, 28 Jul 2011 14:06:59 +0100 Subject: [PATCH] Abstract out the backend API call system to a generic class, Added Flair management --- app/reddit/api.py | 155 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 39 deletions(-) diff --git a/app/reddit/api.py b/app/reddit/api.py index ba3bd7d..020d050 100644 --- a/app/reddit/api.py +++ b/app/reddit/api.py @@ -17,6 +17,74 @@ class NotLoggedIn(Exception): class LoginError(Exception): pass +REDDIT = "http://www.reddit.com" + +class RedditAPI: + """ + Generic class for authenticated Reddit API access + """ + + REDDIT_API_LOGIN = "%s/api/login" % REDDIT + + def __init__(self, username=None, password=None): + if username and password: + self.login(username, password) + + @staticmethod + def _url(api, sr=None): + # Inspired by the offical reddit client + + if api[0] == '/': + api = api[1:] + + if sr: + url = '%s/r/%s/%s' % (REDDIT, sr, api) + else: + url = '%s/%s' % (REDDIT, api) + + return '%s.json' % url + + def login(self, username, password): + data = { 'user': username, + 'passwd': password, + 'api_type': 'json' } + url = "%s/%s" % (self.REDDIT_API_LOGIN, username) + + jsondoc = self._request(url, data, method='POST') + + if jsondoc and 'json' in jsondoc: + if 'data' in jsondoc['json']: + self.login_cookie = jsondoc['json']['data']['cookie'] + self.modhash = jsondoc['json']['data']['modhash'] + if self.login_cookie: + return True + elif 'errors' in jsondoc['json']: + raise LoginError(jsondoc['json']['errors']) + + return False + + def _request(self, url, data, method='GET'): + if not hasattr(self, '_opener'): + self._opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) + urllib2.install_opener(self._opener) + + data = urllib.urlencode(data) + if method == 'GET': + if '?' in url: + url = '%s&%s' % (url, data) + else: + url = '%s?%s' % (url, data) + data = None + + print url + resp = self._opener.open(urllib2.Request(url, data)) + resptxt = resp.read() + if resp.info()['Content-Type'] == 'text/plain': + logging.info('returning plaintext') + return resptxt + return json.loads(resptxt) + + class Comment(dict): """ Abstraction for comment data provided by JSON Comments can be identifed by Kind = 1 """ @@ -63,7 +131,8 @@ class Message(dict): def __str__(self): return self.__unicode__() -class Inbox(): + +class Inbox(RedditAPI): """ Reddit Inbox class, accesses a user's inbox and provides a iterable list of messages @@ -74,33 +143,8 @@ class Inbox(): """ - REDDIT = "http://www.reddit.com" - REDDIT_API_LOGIN = "%s/api/login" % REDDIT - REDDIT_API_INBOX = "%s/message/inbox/.json?mark=false" % REDDIT - REDDIT_API_COMPOSE = "%s/api/compose/" % REDDIT - - def __init__(self, username=None, password=None): - if username and password: - self.login(username, password) - - def login(self, username, password): - data = { 'user': username, - 'passwd': password, - 'api_type': 'json' } - url = "%s/%s" % (self.REDDIT_API_LOGIN, username) - - jsondoc = json.load(self._url_request(url, data)) - - if jsondoc and 'json' in jsondoc: - if 'data' in jsondoc['json']: - self.login_cookie = jsondoc['json']['data']['cookie'] - self.modhash = jsondoc['json']['data']['modhash'] - if self.login_cookie: - return True - elif 'errors' in jsondoc['json']: - raise LoginError(jsondoc['json']['errors']) - - return False + REDDIT_API_INBOX = '/message/inbox/' + REDDIT_API_COMPOSE = '/api/compose/' @property def _inbox_data(self): @@ -109,7 +153,7 @@ class Inbox(): raise NotLoggedIn if not hasattr(self, '__inbox_cache') or not len(self.__inbox_cache): - inbox = json.load(self._opener.open(self.REDDIT_API_INBOX)) + inbox = self._request(self._url(self.REDDIT_API_INBOX), {'mark': 'false'}, method='GET') if inbox and 'data' in inbox: self.__inbox_cache = [] @@ -120,14 +164,6 @@ class Inbox(): return self.__inbox_cache - def _url_request(self, url, data): - if not hasattr(self, '_opener'): - self._opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) - urllib2.install_opener(self._opener) - - req = urllib2.Request(url, urllib.urlencode(data)) - return self._opener.open(req) - def __len__(self): return len(self._inbox_data) @@ -146,6 +182,47 @@ class Inbox(): 'text': text.encode('utf-8'), 'uh': self.modhash, 'thing_id': '' } - url = "%s" % (self.REDDIT_API_COMPOSE) + url = self._url(self.REDDIT_API_COMPOSE) + jsondoc = self._request(url, data, method='POST') - jsondoc = json.load(self._url_request(url, data)) + +class Flair(RedditAPI): + """ + Manages a subreddit's flair list + """ + + REDDIT_API_FLAIR = "/api/flair" + REDDIT_API_FLAIRLIST = "/api/flairlist" + + def flairlist(self, subreddit, start=None): + + if not self.login_cookie: + raise NotLoggedIn + + data = { 'r': subreddit, 'limit': 1000, 'uh': self.modhash } + if start: data['after'] = start + url = self._url(self.REDDIT_API_FLAIRLIST, subreddit) + + jsondoc = self._request(url, data) + users = jsondoc['users'] + + if len(users) == 1000: + # Assume we have more to get + users.extend(self.flairlist(subreddit, jsondoc['next'])) + return users + + def clear_flair(self, subreddit, user): + """ + Clears a user's flair + """ + return self.set_flair(subreddit, user, '', '') + + def set_flair(self, subreddit, user, text, css_class): + + data = { 'r': subreddit, 'name': user, 'uh': self.modhash, 'text': text, 'css_class': css_class } + url = self._url(self.REDDIT_API_FLAIR) + jsondoc = self._request(url, data, method='POST') + + if jsondoc: + return True + return False