From: Andrew Flegg Date: Sun, 25 Jul 2010 14:17:38 +0000 (+0100) Subject: Re-implement the GetFriends() of python-twitter with paging logic so that all friends... X-Git-Tag: 0.8.5~1 X-Git-Url: http://vcs.maemo.org/git/?p=hermes;a=commitdiff_plain;h=167cf601c347800d99a57f6c088e89c6e84762be Re-implement the GetFriends() of python-twitter with paging logic so that all friends are found. Fixes MB#7613. --- diff --git a/package/debian/control b/package/debian/control index 7ab30f2..84a2dd0 100644 --- a/package/debian/control +++ b/package/debian/control @@ -9,8 +9,9 @@ Package: hermes Architecture: all Depends: ${shlibs:Depends}, ${misc:Depends}, python-imaging | python2.5-imaging, python-osso | python2.5-osso, python-hildon | - python2.5-hildon, python-twitter, python-facebook, python-evolution, - gnome-python, python-gobject (>= 2.16), python-conic + python2.5-hildon, python-simplejson, python-facebook, + python-evolution, gnome-python, python-gobject (>= 2.16), + python-conic Description: Enrich contacts' information from social networks Hermes, the Greek god of communication, will fill in the gaps in your contacts' address book. Photos and birthdays for your friends on diff --git a/package/src/org/maemo/hermes/engine/twitter/api.py b/package/src/org/maemo/hermes/engine/twitter/api.py new file mode 100644 index 0000000..44aca61 --- /dev/null +++ b/package/src/org/maemo/hermes/engine/twitter/api.py @@ -0,0 +1,127 @@ +from org.maemo.hermes.engine.twitter.user import User +import urllib, urllib2 +import base64 +import simplejson +import urlparse + +class TwitterApi(): + """Twitter backend for Hermes. Inspired by + http://code.google.com/p/python-twitter/source/browse/twitter.py + + Copyright (c) Andrew Flegg 2010. + Released under the Artistic Licence.""" + + + # ----------------------------------------------------------------------- + def __init__(self, username, password): + self._username = username + self._password = password + + + # ----------------------------------------------------------------------- + def get_friends(self): + '''Return the full list of people being followed by 'username'.''' + + url = 'https://twitter.com/statuses/friends.json' + cursor = -1 + users = [] + while True: + json = self._FetchUrl(url, parameters = { 'cursor': cursor}) + data = simplejson.loads(json) + if 'error' in data: + raise Exception(data['error']) + + for x in data['users']: + users.append(User.NewFromJsonDict(x)) + + cursor = data['next_cursor'] + if cursor <= data['previous_cursor']: + break + + return users + + + # ----------------------------------------------------------------------- + def _FetchUrl(self, + url, + parameters=None): + '''Fetch a URL, optionally caching for a specified time. + + Args: + url: + The URL to retrieve + parameters: + A dict whose key/value pairs should encoded and added + to the query string. [optional] + + Returns: + A string containing the body of the response. + ''' + + # Build the extra parameters dict + extra_params = {} + if parameters: + extra_params.update(parameters) + + # Add key/value parameters to the query string of the url + url = self._BuildUrl(url, extra_params=extra_params) + + # Get a url opener that can handle basic auth + basic_auth = base64.encodestring('%s:%s' % (self._username, self._password))[:-1] + + handler = urllib2.HTTPBasicAuthHandler() + (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) + handler.add_password('Twitter API', netloc, self._username, self._password) + opener = urllib2.build_opener(handler) + opener.addheaders = {'Authorization': 'Basic %s' % basic_auth}.items() + + url_data = opener.open(url, None).read() + opener.close() + return url_data + + + # ----------------------------------------------------------------------- + def _BuildUrl(self, url, path_elements=None, extra_params=None): + # Break url into consituent parts + (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) + + # Add any additional path elements to the path + if path_elements: + # Filter out the path elements that have a value of None + p = [i for i in path_elements if i] + if not path.endswith('/'): + path += '/' + path += '/'.join(p) + + # Add any additional query parameters to the query string + if extra_params and len(extra_params) > 0: + extra_query = self._EncodeParameters(extra_params) + # Add it to the existing query + if query: + query += '&' + extra_query + else: + query = extra_query + + # Return the rebuilt URL + return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) + + + + # ----------------------------------------------------------------------- + def _EncodeParameters(self, parameters): + '''Return a string in key=value&key=value form + + Values of None are not included in the output string. + + Args: + parameters: + A dict of (key, value) tuples, where value is encoded as + specified by self._encoding + Returns: + A URL-encoded string in "key=value&key=value" form + ''' + if parameters is None: + return None + else: + return urllib.urlencode(dict([(k, unicode(v).encode('utf-8')) for k, v in parameters.items() if v is not None])) + diff --git a/package/src/org/maemo/hermes/engine/twitter/provider.py b/package/src/org/maemo/hermes/engine/twitter/provider.py index 87508ed..66c01aa 100644 --- a/package/src/org/maemo/hermes/engine/twitter/provider.py +++ b/package/src/org/maemo/hermes/engine/twitter/provider.py @@ -2,7 +2,7 @@ import gnome.gconf import gtk, hildon import org.maemo.hermes.engine.provider import org.maemo.hermes.engine.twitter.service -import twitter +from org.maemo.hermes.engine.twitter.api import TwitterApi class Provider(org.maemo.hermes.engine.provider.Provider): """Twitter provider for Hermes. @@ -105,6 +105,6 @@ class Provider(org.maemo.hermes.engine.provider.Provider): username = self._gconf.get_string("/apps/maemo/hermes/twitter_user") or '' password = self._gconf.get_string("/apps/maemo/hermes/twitter_pwd") or '' - api = twitter.Api(username=username, password=password) + api = TwitterApi(username, password) - return org.maemo.hermes.engine.twitter.service.Service(self.get_id(), api) \ No newline at end of file + return org.maemo.hermes.engine.twitter.service.Service(self.get_id(), api) diff --git a/package/src/org/maemo/hermes/engine/twitter/service.py b/package/src/org/maemo/hermes/engine/twitter/service.py index e50132b..1343b7a 100644 --- a/package/src/org/maemo/hermes/engine/twitter/service.py +++ b/package/src/org/maemo/hermes/engine/twitter/service.py @@ -80,7 +80,7 @@ class Service(org.maemo.hermes.engine.service.Service): # ----------------------------------------------------------------------- def _get_tweeters(self): try: - return self._twitter.GetFriends() + return self._twitter.get_friends() except urllib2.HTTPError, e: if e.code >= 500 and e.code <= 599: print "Twitter down (fail whale): " + e.message diff --git a/package/src/org/maemo/hermes/engine/twitter/user.py b/package/src/org/maemo/hermes/engine/twitter/user.py new file mode 100644 index 0000000..e5c4167 --- /dev/null +++ b/package/src/org/maemo/hermes/engine/twitter/user.py @@ -0,0 +1,553 @@ +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +'''A library that provides a python interface to the Twitter API. + + http://code.google.com/p/python-twitter/''' + +__author__ = 'dewitt@google.com' +__version__ = '0.7-devel' + + + +class User(object): + '''A class representing the User structure used by the twitter API. + + The User structure exposes the following properties: + + user.id + user.name + user.screen_name + user.location + user.description + user.profile_image_url + user.profile_background_tile + user.profile_background_image_url + user.profile_sidebar_fill_color + user.profile_background_color + user.profile_link_color + user.profile_text_color + user.protected + user.utc_offset + user.time_zone + user.url + user.status + user.statuses_count + user.followers_count + user.friends_count + user.favourites_count + ''' + def __init__(self, + id=None, + name=None, + screen_name=None, + location=None, + description=None, + profile_image_url=None, + profile_background_tile=None, + profile_background_image_url=None, + profile_sidebar_fill_color=None, + profile_background_color=None, + profile_link_color=None, + profile_text_color=None, + protected=None, + utc_offset=None, + time_zone=None, + followers_count=None, + friends_count=None, + statuses_count=None, + favourites_count=None, + url=None, + status=None): + self.id = id + self.name = name + self.screen_name = screen_name + self.location = location + self.description = description + self.profile_image_url = profile_image_url + self.profile_background_tile = profile_background_tile + self.profile_background_image_url = profile_background_image_url + self.profile_sidebar_fill_color = profile_sidebar_fill_color + self.profile_background_color = profile_background_color + self.profile_link_color = profile_link_color + self.profile_text_color = profile_text_color + self.protected = protected + self.utc_offset = utc_offset + self.time_zone = time_zone + self.followers_count = followers_count + self.friends_count = friends_count + self.statuses_count = statuses_count + self.favourites_count = favourites_count + self.url = url + self.status = status + + + def GetId(self): + '''Get the unique id of this user. + + Returns: + The unique id of this user + ''' + return self._id + + def SetId(self, id): + '''Set the unique id of this user. + + Args: + id: The unique id of this user. + ''' + self._id = id + + id = property(GetId, SetId, + doc='The unique id of this user.') + + def GetName(self): + '''Get the real name of this user. + + Returns: + The real name of this user + ''' + return self._name + + def SetName(self, name): + '''Set the real name of this user. + + Args: + name: The real name of this user + ''' + self._name = name + + name = property(GetName, SetName, + doc='The real name of this user.') + + def GetScreenName(self): + '''Get the short username of this user. + + Returns: + The short username of this user + ''' + return self._screen_name + + def SetScreenName(self, screen_name): + '''Set the short username of this user. + + Args: + screen_name: the short username of this user + ''' + self._screen_name = screen_name + + screen_name = property(GetScreenName, SetScreenName, + doc='The short username of this user.') + + def GetLocation(self): + '''Get the geographic location of this user. + + Returns: + The geographic location of this user + ''' + return self._location + + def SetLocation(self, location): + '''Set the geographic location of this user. + + Args: + location: The geographic location of this user + ''' + self._location = location + + location = property(GetLocation, SetLocation, + doc='The geographic location of this user.') + + def GetDescription(self): + '''Get the short text description of this user. + + Returns: + The short text description of this user + ''' + return self._description + + def SetDescription(self, description): + '''Set the short text description of this user. + + Args: + description: The short text description of this user + ''' + self._description = description + + description = property(GetDescription, SetDescription, + doc='The short text description of this user.') + + def GetUrl(self): + '''Get the homepage url of this user. + + Returns: + The homepage url of this user + ''' + return self._url + + + def SetUrl(self, url): + '''Set the homepage url of this user. + + Args: + url: The homepage url of this user + ''' + self._url = url + + url = property(GetUrl, SetUrl, + doc='The homepage url of this user.') + + def GetProfileImageUrl(self): + '''Get the url of the thumbnail of this user. + + Returns: + The url of the thumbnail of this user + ''' + return self._profile_image_url + + def SetProfileImageUrl(self, profile_image_url): + '''Set the url of the thumbnail of this user. + + Args: + profile_image_url: The url of the thumbnail of this user + ''' + self._profile_image_url = profile_image_url + + profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl, + doc='The url of the thumbnail of this user.') + + def GetProfileBackgroundTile(self): + '''Boolean for whether to tile the profile background image. + + Returns: + True if the background is to be tiled, False if not, None if unset. + ''' + return self._profile_background_tile + + def SetProfileBackgroundTile(self, profile_background_tile): + '''Set the boolean flag for whether to tile the profile background image. + + Args: + profile_background_tile: Boolean flag for whether to tile or not. + ''' + self._profile_background_tile = profile_background_tile + + profile_background_tile = property(GetProfileBackgroundTile, SetProfileBackgroundTile, + doc='Boolean for whether to tile the background image.') + + def GetProfileBackgroundImageUrl(self): + return self._profile_background_image_url + + def SetProfileBackgroundImageUrl(self, profile_background_image_url): + self._profile_background_image_url = profile_background_image_url + + profile_background_image_url = property(GetProfileBackgroundImageUrl, SetProfileBackgroundImageUrl, + doc='The url of the profile background of this user.') + + def GetProfileSidebarFillColor(self): + return self._profile_sidebar_fill_color + + def SetProfileSidebarFillColor(self, profile_sidebar_fill_color): + self._profile_sidebar_fill_color = profile_sidebar_fill_color + + profile_sidebar_fill_color = property(GetProfileSidebarFillColor, SetProfileSidebarFillColor) + + def GetProfileBackgroundColor(self): + return self._profile_background_color + + def SetProfileBackgroundColor(self, profile_background_color): + self._profile_background_color = profile_background_color + + profile_background_color = property(GetProfileBackgroundColor, SetProfileBackgroundColor) + + def GetProfileLinkColor(self): + return self._profile_link_color + + def SetProfileLinkColor(self, profile_link_color): + self._profile_link_color = profile_link_color + + profile_link_color = property(GetProfileLinkColor, SetProfileLinkColor) + + def GetProfileTextColor(self): + return self._profile_text_color + + def SetProfileTextColor(self, profile_text_color): + self._profile_text_color = profile_text_color + + profile_text_color = property(GetProfileTextColor, SetProfileTextColor) + + def GetProtected(self): + return self._protected + + def SetProtected(self, protected): + self._protected = protected + + protected = property(GetProtected, SetProtected) + + def GetUtcOffset(self): + return self._utc_offset + + def SetUtcOffset(self, utc_offset): + self._utc_offset = utc_offset + + utc_offset = property(GetUtcOffset, SetUtcOffset) + + def GetTimeZone(self): + '''Returns the current time zone string for the user. + + Returns: + The descriptive time zone string for the user. + ''' + return self._time_zone + + def SetTimeZone(self, time_zone): + '''Sets the user's time zone string. + + Args: + time_zone: The descriptive time zone to assign for the user. + ''' + self._time_zone = time_zone + + time_zone = property(GetTimeZone, SetTimeZone) + + def GetStatus(self): + '''Get the latest twitter.Status of this user. + + Returns: + The latest twitter.Status of this user + ''' + return self._status + + def SetStatus(self, status): + '''Set the latest twitter.Status of this user. + + Args: + status: The latest twitter.Status of this user + ''' + self._status = status + + status = property(GetStatus, SetStatus, + doc='The latest twitter.Status of this user.') + + def GetFriendsCount(self): + '''Get the friend count for this user. + + Returns: + The number of users this user has befriended. + ''' + return self._friends_count + + def SetFriendsCount(self, count): + '''Set the friend count for this user. + + Args: + count: The number of users this user has befriended. + ''' + self._friends_count = count + + friends_count = property(GetFriendsCount, SetFriendsCount, + doc='The number of friends for this user.') + + def GetFollowersCount(self): + '''Get the follower count for this user. + + Returns: + The number of users following this user. + ''' + return self._followers_count + + def SetFollowersCount(self, count): + '''Set the follower count for this user. + + Args: + count: The number of users following this user. + ''' + self._followers_count = count + + followers_count = property(GetFollowersCount, SetFollowersCount, + doc='The number of users following this user.') + + def GetStatusesCount(self): + '''Get the number of status updates for this user. + + Returns: + The number of status updates for this user. + ''' + return self._statuses_count + + def SetStatusesCount(self, count): + '''Set the status update count for this user. + + Args: + count: The number of updates for this user. + ''' + self._statuses_count = count + + statuses_count = property(GetStatusesCount, SetStatusesCount, + doc='The number of updates for this user.') + + + def GetFavouritesCount(self): + '''Get the number of favourites for this user. + + Returns: + The number of favourites for this user. + ''' + return self._favourites_count + + def SetFavouritesCount(self, count): + '''Set the favourite count for this user. + + Args: + count: The number of favourites for this user. + ''' + self._favourites_count = count + + favourites_count = property(GetFavouritesCount, SetFavouritesCount, + doc='The number of favourites for this user.') + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): + try: + return other and \ + self.id == other.id and \ + self.name == other.name and \ + self.screen_name == other.screen_name and \ + self.location == other.location and \ + self.description == other.description and \ + self.profile_image_url == other.profile_image_url and \ + self.profile_background_tile == other.profile_background_tile and \ + self.profile_background_image_url == other.profile_background_image_url and \ + self.profile_sidebar_fill_color == other.profile_sidebar_fill_color and \ + self.profile_background_color == other.profile_background_color and \ + self.profile_link_color == other.profile_link_color and \ + self.profile_text_color == other.profile_text_color and \ + self.protected == other.protected and \ + self.utc_offset == other.utc_offset and \ + self.time_zone == other.time_zone and \ + self.url == other.url and \ + self.statuses_count == other.statuses_count and \ + self.followers_count == other.followers_count and \ + self.favourites_count == other.favourites_count and \ + self.friends_count == other.friends_count and \ + self.status == other.status + except AttributeError: + return False + + def __str__(self): + '''A string representation of this twitter.User instance. + + The return value is the same as the JSON string representation. + + Returns: + A string representation of this twitter.User instance. + ''' + return self.AsJsonString() + + def AsJsonString(self): + '''A JSON string representation of this twitter.User instance. + + Returns: + A JSON string representation of this twitter.User instance + ''' + return simplejson.dumps(self.AsDict(), sort_keys=True) + + def AsDict(self): + '''A dict representation of this twitter.User instance. + + The return value uses the same key names as the JSON representation. + + Return: + A dict representing this twitter.User instance + ''' + data = {} + if self.id: + data['id'] = self.id + if self.name: + data['name'] = self.name + if self.screen_name: + data['screen_name'] = self.screen_name + if self.location: + data['location'] = self.location + if self.description: + data['description'] = self.description + if self.profile_image_url: + data['profile_image_url'] = self.profile_image_url + if self.profile_background_tile is not None: + data['profile_background_tile'] = self.profile_background_tile + if self.profile_background_image_url: + data['profile_sidebar_fill_color'] = self.profile_background_image_url + if self.profile_background_color: + data['profile_background_color'] = self.profile_background_color + if self.profile_link_color: + data['profile_link_color'] = self.profile_link_color + if self.profile_text_color: + data['profile_text_color'] = self.profile_text_color + if self.protected is not None: + data['protected'] = self.protected + if self.utc_offset: + data['utc_offset'] = self.utc_offset + if self.time_zone: + data['time_zone'] = self.time_zone + if self.url: + data['url'] = self.url + if self.status: + data['status'] = self.status.AsDict() + if self.friends_count: + data['friends_count'] = self.friends_count + if self.followers_count: + data['followers_count'] = self.followers_count + if self.statuses_count: + data['statuses_count'] = self.statuses_count + if self.favourites_count: + data['favourites_count'] = self.favourites_count + return data + + @staticmethod + def NewFromJsonDict(data): + '''Create a new instance based on a JSON dict. + + Args: + data: A JSON dict, as converted from the JSON in the twitter API + Returns: + A twitter.User instance + ''' + if 'status' in data: + status = None #Status.NewFromJsonDict(data['status']) + else: + status = None + return User(id=data.get('id', None), + name=data.get('name', None), + screen_name=data.get('screen_name', None), + location=data.get('location', None), + description=data.get('description', None), + statuses_count=data.get('statuses_count', None), + followers_count=data.get('followers_count', None), + favourites_count=data.get('favourites_count', None), + friends_count=data.get('friends_count', None), + profile_image_url=data.get('profile_image_url', None), + profile_background_tile = data.get('profile_background_tile', None), + profile_background_image_url = data.get('profile_background_image_url', None), + profile_sidebar_fill_color = data.get('profile_sidebar_fill_color', None), + profile_background_color = data.get('profile_background_color', None), + profile_link_color = data.get('profile_link_color', None), + profile_text_color = data.get('profile_text_color', None), + protected = data.get('protected', None), + utc_offset = data.get('utc_offset', None), + time_zone = data.get('time_zone', None), + url=data.get('url', None), + status=status) +