Re-implement the GetFriends() of python-twitter with paging logic so that all friends...
authorAndrew Flegg <andrew@bleb.org>
Sun, 25 Jul 2010 14:17:38 +0000 (15:17 +0100)
committerAndrew Flegg <andrew@bleb.org>
Sun, 25 Jul 2010 17:01:55 +0000 (18:01 +0100)
package/debian/control
package/src/org/maemo/hermes/engine/twitter/api.py [new file with mode: 0644]
package/src/org/maemo/hermes/engine/twitter/provider.py
package/src/org/maemo/hermes/engine/twitter/service.py
package/src/org/maemo/hermes/engine/twitter/user.py [new file with mode: 0644]

index 7ab30f2..84a2dd0 100644 (file)
@@ -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 (file)
index 0000000..44aca61
--- /dev/null
@@ -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 <andrew@bleb.org> 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]))
+
index 87508ed..66c01aa 100644 (file)
@@ -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)
index e50132b..1343b7a 100644 (file)
@@ -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 (file)
index 0000000..e5c4167
--- /dev/null
@@ -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)
+