From d9d6dc3fb062bab6550582c31b8da84cf8c6ff65 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20=C4=8Ciha=C5=99?= Date: Thu, 27 Aug 2009 08:25:31 +0000 Subject: [PATCH] Allow to post news to identi.ca / twitter. --- helper/twitter.py | 2133 +++++++++++++++++++++++++++++++++++++++++++++++++++++ render.py | 45 +- 2 files changed, 2177 insertions(+), 1 deletion(-) create mode 100755 helper/twitter.py diff --git a/helper/twitter.py b/helper/twitter.py new file mode 100755 index 0000000..9f854b6 --- /dev/null +++ b/helper/twitter.py @@ -0,0 +1,2133 @@ +#!/usr/bin/python +# +# 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''' + +__author__ = 'dewitt@google.com' +__version__ = '0.6-devel' + + +import base64 +import calendar +import os +import rfc822 +import simplejson +import sys +import tempfile +import textwrap +import time +import urllib +import urllib2 +import urlparse + +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + + +CHARACTER_LIMIT = 140 + + +class TwitterError(Exception): + '''Base class for Twitter errors''' + + @property + def message(self): + '''Returns the first argument used to construct this error.''' + return self.args[0] + + +class Status(object): + '''A class representing the Status structure used by the twitter API. + + The Status structure exposes the following properties: + + status.created_at + status.created_at_in_seconds # read only + status.favorited + status.in_reply_to_screen_name + status.in_reply_to_user_id + status.in_reply_to_status_id + status.truncated + status.source + status.id + status.text + status.relative_created_at # read only + status.user + ''' + def __init__(self, + created_at=None, + favorited=None, + id=None, + text=None, + user=None, + in_reply_to_screen_name=None, + in_reply_to_user_id=None, + in_reply_to_status_id=None, + truncated=None, + source=None, + now=None): + '''An object to hold a Twitter status message. + + This class is normally instantiated by the twitter.Api class and + returned in a sequence. + + Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007" + + Args: + created_at: The time this status message was posted + favorited: Whether this is a favorite of the authenticated user + id: The unique id of this status message + text: The text of this status message + relative_created_at: + A human readable string representing the posting time + user: + A twitter.User instance representing the person posting the message + now: + The current time, if the client choses to set it. Defaults to the + wall clock time. + ''' + self.created_at = created_at + self.favorited = favorited + self.id = id + self.text = text + self.user = user + self.now = now + self.in_reply_to_screen_name = in_reply_to_screen_name + self.in_reply_to_user_id = in_reply_to_user_id + self.in_reply_to_status_id = in_reply_to_status_id + self.truncated = truncated + self.source = source + + def GetCreatedAt(self): + '''Get the time this status message was posted. + + Returns: + The time this status message was posted + ''' + return self._created_at + + def SetCreatedAt(self, created_at): + '''Set the time this status message was posted. + + Args: + created_at: The time this status message was created + ''' + self._created_at = created_at + + created_at = property(GetCreatedAt, SetCreatedAt, + doc='The time this status message was posted.') + + def GetCreatedAtInSeconds(self): + '''Get the time this status message was posted, in seconds since the epoch. + + Returns: + The time this status message was posted, in seconds since the epoch. + ''' + return calendar.timegm(rfc822.parsedate(self.created_at)) + + created_at_in_seconds = property(GetCreatedAtInSeconds, + doc="The time this status message was " + "posted, in seconds since the epoch") + + def GetFavorited(self): + '''Get the favorited setting of this status message. + + Returns: + True if this status message is favorited; False otherwise + ''' + return self._favorited + + def SetFavorited(self, favorited): + '''Set the favorited state of this status message. + + Args: + favorited: boolean True/False favorited state of this status message + ''' + self._favorited = favorited + + favorited = property(GetFavorited, SetFavorited, + doc='The favorited state of this status message.') + + def GetId(self): + '''Get the unique id of this status message. + + Returns: + The unique id of this status message + ''' + return self._id + + def SetId(self, id): + '''Set the unique id of this status message. + + Args: + id: The unique id of this status message + ''' + self._id = id + + id = property(GetId, SetId, + doc='The unique id of this status message.') + + def GetInReplyToScreenName(self): + return self._in_reply_to_screen_name + + def SetInReplyToScreenName(self, in_reply_to_screen_name): + self._in_reply_to_screen_name = in_reply_to_screen_name + + in_reply_to_screen_name = property(GetInReplyToScreenName, SetInReplyToScreenName, + doc='') + + def GetInReplyToUserId(self): + return self._in_reply_to_user_id + + def SetInReplyToUserId(self, in_reply_to_user_id): + self._in_reply_to_user_id = in_reply_to_user_id + + in_reply_to_user_id = property(GetInReplyToUserId, SetInReplyToUserId, + doc='') + + def GetInReplyToStatusId(self): + return self._in_reply_to_status_id + + def SetInReplyToStatusId(self, in_reply_to_status_id): + self._in_reply_to_status_id = in_reply_to_status_id + + in_reply_to_status_id = property(GetInReplyToStatusId, SetInReplyToStatusId, + doc='') + + def GetTruncated(self): + return self._truncated + + def SetTruncated(self, truncated): + self._truncated = truncated + + truncated = property(GetTruncated, SetTruncated, + doc='') + + def GetSource(self): + return self._source + + def SetSource(self, source): + self._source = source + + source = property(GetSource, SetSource, + doc='') + + def GetText(self): + '''Get the text of this status message. + + Returns: + The text of this status message. + ''' + return self._text + + def SetText(self, text): + '''Set the text of this status message. + + Args: + text: The text of this status message + ''' + self._text = text + + text = property(GetText, SetText, + doc='The text of this status message') + + def GetRelativeCreatedAt(self): + '''Get a human redable string representing the posting time + + Returns: + A human readable string representing the posting time + ''' + fudge = 1.25 + delta = long(self.now) - long(self.created_at_in_seconds) + + if delta < (1 * fudge): + return 'about a second ago' + elif delta < (60 * (1/fudge)): + return 'about %d seconds ago' % (delta) + elif delta < (60 * fudge): + return 'about a minute ago' + elif delta < (60 * 60 * (1/fudge)): + return 'about %d minutes ago' % (delta / 60) + elif delta < (60 * 60 * fudge): + return 'about an hour ago' + elif delta < (60 * 60 * 24 * (1/fudge)): + return 'about %d hours ago' % (delta / (60 * 60)) + elif delta < (60 * 60 * 24 * fudge): + return 'about a day ago' + else: + return 'about %d days ago' % (delta / (60 * 60 * 24)) + + relative_created_at = property(GetRelativeCreatedAt, + doc='Get a human readable string representing' + 'the posting time') + + def GetUser(self): + '''Get a twitter.User reprenting the entity posting this status message. + + Returns: + A twitter.User reprenting the entity posting this status message + ''' + return self._user + + def SetUser(self, user): + '''Set a twitter.User reprenting the entity posting this status message. + + Args: + user: A twitter.User reprenting the entity posting this status message + ''' + self._user = user + + user = property(GetUser, SetUser, + doc='A twitter.User reprenting the entity posting this ' + 'status message') + + def GetNow(self): + '''Get the wallclock time for this status message. + + Used to calculate relative_created_at. Defaults to the time + the object was instantiated. + + Returns: + Whatever the status instance believes the current time to be, + in seconds since the epoch. + ''' + if self._now is None: + self._now = time.time() + return self._now + + def SetNow(self, now): + '''Set the wallclock time for this status message. + + Used to calculate relative_created_at. Defaults to the time + the object was instantiated. + + Args: + now: The wallclock time for this instance. + ''' + self._now = now + + now = property(GetNow, SetNow, + doc='The wallclock time for this status instance.') + + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): + try: + return other and \ + self.created_at == other.created_at and \ + self.id == other.id and \ + self.text == other.text and \ + self.user == other.user and \ + self.in_reply_to_screen_name == other.in_reply_to_screen_name and \ + self.in_reply_to_user_id == other.in_reply_to_user_id and \ + self.in_reply_to_status_id == other.in_reply_to_status_id and \ + self.truncated == other.truncated and \ + self.favorited == other.favorited and \ + self.source == other.source + except AttributeError: + return False + + def __str__(self): + '''A string representation of this twitter.Status instance. + + The return value is the same as the JSON string representation. + + Returns: + A string representation of this twitter.Status instance. + ''' + return self.AsJsonString() + + def AsJsonString(self): + '''A JSON string representation of this twitter.Status instance. + + Returns: + A JSON string representation of this twitter.Status instance + ''' + return simplejson.dumps(self.AsDict(), sort_keys=True) + + def AsDict(self): + '''A dict representation of this twitter.Status instance. + + The return value uses the same key names as the JSON representation. + + Return: + A dict representing this twitter.Status instance + ''' + data = {} + if self.created_at: + data['created_at'] = self.created_at + if self.favorited: + data['favorited'] = self.favorited + if self.id: + data['id'] = self.id + if self.text: + data['text'] = self.text + if self.user: + data['user'] = self.user.AsDict() + if self.in_reply_to_screen_name: + data['in_reply_to_screen_name'] = self.in_reply_to_screen_name + if self.in_reply_to_user_id: + data['in_reply_to_user_id'] = self.in_reply_to_user_id + if self.in_reply_to_status_id: + data['in_reply_to_status_id'] = self.in_reply_to_status_id + if self.truncated is not None: + data['truncated'] = self.truncated + if self.favorited is not None: + data['favorited'] = self.favorited + if self.source: + data['source'] = self.source + 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.Status instance + ''' + if 'user' in data: + user = User.NewFromJsonDict(data['user']) + else: + user = None + return Status(created_at=data.get('created_at', None), + favorited=data.get('favorited', None), + id=data.get('id', None), + text=data.get('text', None), + in_reply_to_screen_name=data.get('in_reply_to_screen_name', None), + in_reply_to_user_id=data.get('in_reply_to_user_id', None), + in_reply_to_status_id=data.get('in_reply_to_status_id', None), + truncated=data.get('truncated', None), + source=data.get('source', None), + user=user) + + +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 = 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) + +class DirectMessage(object): + '''A class representing the DirectMessage structure used by the twitter API. + + The DirectMessage structure exposes the following properties: + + direct_message.id + direct_message.created_at + direct_message.created_at_in_seconds # read only + direct_message.sender_id + direct_message.sender_screen_name + direct_message.recipient_id + direct_message.recipient_screen_name + direct_message.text + ''' + + def __init__(self, + id=None, + created_at=None, + sender_id=None, + sender_screen_name=None, + recipient_id=None, + recipient_screen_name=None, + text=None): + '''An object to hold a Twitter direct message. + + This class is normally instantiated by the twitter.Api class and + returned in a sequence. + + Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007" + + Args: + id: The unique id of this direct message + created_at: The time this direct message was posted + sender_id: The id of the twitter user that sent this message + sender_screen_name: The name of the twitter user that sent this message + recipient_id: The id of the twitter that received this message + recipient_screen_name: The name of the twitter that received this message + text: The text of this direct message + ''' + self.id = id + self.created_at = created_at + self.sender_id = sender_id + self.sender_screen_name = sender_screen_name + self.recipient_id = recipient_id + self.recipient_screen_name = recipient_screen_name + self.text = text + + def GetId(self): + '''Get the unique id of this direct message. + + Returns: + The unique id of this direct message + ''' + return self._id + + def SetId(self, id): + '''Set the unique id of this direct message. + + Args: + id: The unique id of this direct message + ''' + self._id = id + + id = property(GetId, SetId, + doc='The unique id of this direct message.') + + def GetCreatedAt(self): + '''Get the time this direct message was posted. + + Returns: + The time this direct message was posted + ''' + return self._created_at + + def SetCreatedAt(self, created_at): + '''Set the time this direct message was posted. + + Args: + created_at: The time this direct message was created + ''' + self._created_at = created_at + + created_at = property(GetCreatedAt, SetCreatedAt, + doc='The time this direct message was posted.') + + def GetCreatedAtInSeconds(self): + '''Get the time this direct message was posted, in seconds since the epoch. + + Returns: + The time this direct message was posted, in seconds since the epoch. + ''' + return calendar.timegm(rfc822.parsedate(self.created_at)) + + created_at_in_seconds = property(GetCreatedAtInSeconds, + doc="The time this direct message was " + "posted, in seconds since the epoch") + + def GetSenderId(self): + '''Get the unique sender id of this direct message. + + Returns: + The unique sender id of this direct message + ''' + return self._sender_id + + def SetSenderId(self, sender_id): + '''Set the unique sender id of this direct message. + + Args: + sender id: The unique sender id of this direct message + ''' + self._sender_id = sender_id + + sender_id = property(GetSenderId, SetSenderId, + doc='The unique sender id of this direct message.') + + def GetSenderScreenName(self): + '''Get the unique sender screen name of this direct message. + + Returns: + The unique sender screen name of this direct message + ''' + return self._sender_screen_name + + def SetSenderScreenName(self, sender_screen_name): + '''Set the unique sender screen name of this direct message. + + Args: + sender_screen_name: The unique sender screen name of this direct message + ''' + self._sender_screen_name = sender_screen_name + + sender_screen_name = property(GetSenderScreenName, SetSenderScreenName, + doc='The unique sender screen name of this direct message.') + + def GetRecipientId(self): + '''Get the unique recipient id of this direct message. + + Returns: + The unique recipient id of this direct message + ''' + return self._recipient_id + + def SetRecipientId(self, recipient_id): + '''Set the unique recipient id of this direct message. + + Args: + recipient id: The unique recipient id of this direct message + ''' + self._recipient_id = recipient_id + + recipient_id = property(GetRecipientId, SetRecipientId, + doc='The unique recipient id of this direct message.') + + def GetRecipientScreenName(self): + '''Get the unique recipient screen name of this direct message. + + Returns: + The unique recipient screen name of this direct message + ''' + return self._recipient_screen_name + + def SetRecipientScreenName(self, recipient_screen_name): + '''Set the unique recipient screen name of this direct message. + + Args: + recipient_screen_name: The unique recipient screen name of this direct message + ''' + self._recipient_screen_name = recipient_screen_name + + recipient_screen_name = property(GetRecipientScreenName, SetRecipientScreenName, + doc='The unique recipient screen name of this direct message.') + + def GetText(self): + '''Get the text of this direct message. + + Returns: + The text of this direct message. + ''' + return self._text + + def SetText(self, text): + '''Set the text of this direct message. + + Args: + text: The text of this direct message + ''' + self._text = text + + text = property(GetText, SetText, + doc='The text of this direct message') + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): + try: + return other and \ + self.id == other.id and \ + self.created_at == other.created_at and \ + self.sender_id == other.sender_id and \ + self.sender_screen_name == other.sender_screen_name and \ + self.recipient_id == other.recipient_id and \ + self.recipient_screen_name == other.recipient_screen_name and \ + self.text == other.text + except AttributeError: + return False + + def __str__(self): + '''A string representation of this twitter.DirectMessage instance. + + The return value is the same as the JSON string representation. + + Returns: + A string representation of this twitter.DirectMessage instance. + ''' + return self.AsJsonString() + + def AsJsonString(self): + '''A JSON string representation of this twitter.DirectMessage instance. + + Returns: + A JSON string representation of this twitter.DirectMessage instance + ''' + return simplejson.dumps(self.AsDict(), sort_keys=True) + + def AsDict(self): + '''A dict representation of this twitter.DirectMessage instance. + + The return value uses the same key names as the JSON representation. + + Return: + A dict representing this twitter.DirectMessage instance + ''' + data = {} + if self.id: + data['id'] = self.id + if self.created_at: + data['created_at'] = self.created_at + if self.sender_id: + data['sender_id'] = self.sender_id + if self.sender_screen_name: + data['sender_screen_name'] = self.sender_screen_name + if self.recipient_id: + data['recipient_id'] = self.recipient_id + if self.recipient_screen_name: + data['recipient_screen_name'] = self.recipient_screen_name + if self.text: + data['text'] = self.text + 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.DirectMessage instance + ''' + return DirectMessage(created_at=data.get('created_at', None), + recipient_id=data.get('recipient_id', None), + sender_id=data.get('sender_id', None), + text=data.get('text', None), + sender_screen_name=data.get('sender_screen_name', None), + id=data.get('id', None), + recipient_screen_name=data.get('recipient_screen_name', None)) + +class Api(object): + '''A python interface into the Twitter API + + By default, the Api caches results for 1 minute. + + Example usage: + + To create an instance of the twitter.Api class, with no authentication: + + >>> import twitter + >>> api = twitter.Api() + + To fetch the most recently posted public twitter status messages: + + >>> statuses = api.GetPublicTimeline() + >>> print [s.user.name for s in statuses] + [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #... + + To fetch a single user's public status messages, where "user" is either + a Twitter "short name" or their user id. + + >>> statuses = api.GetUserTimeline(user) + >>> print [s.text for s in statuses] + + To use authentication, instantiate the twitter.Api class with a + username and password: + + >>> api = twitter.Api(username='twitter user', password='twitter pass') + + To fetch your friends (after being authenticated): + + >>> users = api.GetFriends() + >>> print [u.name for u in users] + + To post a twitter status message (after being authenticated): + + >>> status = api.PostUpdate('I love python-twitter!') + >>> print status.text + I love python-twitter! + + There are many other methods, including: + + >>> api.PostUpdates(status) + >>> api.PostDirectMessage(user, text) + >>> api.GetUser(user) + >>> api.GetReplies() + >>> api.GetUserTimeline(user) + >>> api.GetStatus(id) + >>> api.DestroyStatus(id) + >>> api.GetFriendsTimeline(user) + >>> api.GetFriends(user) + >>> api.GetFollowers() + >>> api.GetFeatured() + >>> api.GetDirectMessages() + >>> api.PostDirectMessage(user, text) + >>> api.DestroyDirectMessage(id) + >>> api.DestroyFriendship(user) + >>> api.CreateFriendship(user) + >>> api.GetUserByEmail(email) + ''' + + DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute + + _API_REALM = 'Twitter API' + + def __init__(self, + username=None, + password=None, + input_encoding=None, + request_headers=None, + twitterserver='twitter.com'): + '''Instantiate a new twitter.Api object. + + Args: + username: The username of the twitter account. [optional] + password: The password for the twitter account. [optional] + input_encoding: The encoding used to encode input strings. [optional] + request_header: A dictionary of additional HTTP request headers. [optional] + twitterserver: you can use twitter.com or identi.ca [optional] + ''' + self._twitterserver = twitterserver + self._cache = _FileCache() + self._urllib = urllib2 + self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT + self._InitializeRequestHeaders(request_headers) + self._InitializeUserAgent() + self._InitializeDefaultParameters() + self._input_encoding = input_encoding + self.SetCredentials(username, password) + + def GetPublicTimeline(self, since_id=None): + '''Fetch the sequnce of public twitter.Status message for all users. + + Args: + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] + + Returns: + An sequence of twitter.Status instances, one for each message + ''' + parameters = {} + if since_id: + parameters['since_id'] = since_id + url = 'http://%s/statuses/public_timeline.json' % self._twitterserver + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [Status.NewFromJsonDict(x) for x in data] + + def GetFriendsTimeline(self, + user=None, + count=None, + since=None, + since_id=None): + '''Fetch the sequence of twitter.Status messages for a user's friends + + The twitter.Api instance must be authenticated if the user is private. + + Args: + user: + Specifies the ID or screen name of the user for whom to return + the friends_timeline. If unspecified, the username and password + must be set in the twitter.Api instance. [Optional] + count: + Specifies the number of statuses to retrieve. May not be + greater than 200. [Optional] + since: + Narrows the returned results to just those statuses created + after the specified HTTP-formatted date. [Optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] + + Returns: + A sequence of twitter.Status instances, one for each message + ''' + if user: + url = 'http://%s/statuses/friends_timeline/%s.json' % (self._twitterserver,user) + elif not user and not self._username: + raise TwitterError("User must be specified if API is not authenticated.") + else: + url = 'http://%s/statuses/friends_timeline.json' % self._twitterserver + parameters = {} + if count is not None: + try: + if int(count) > 200: + raise TwitterError("'count' may not be greater than 200") + except ValueError: + raise TwitterError("'count' must be an integer") + parameters['count'] = count + if since: + parameters['since'] = since + if since_id: + parameters['since_id'] = since_id + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [Status.NewFromJsonDict(x) for x in data] + + def GetUserTimeline(self, user=None, count=None, since=None, since_id=None): + '''Fetch the sequence of public twitter.Status messages for a single user. + + The twitter.Api instance must be authenticated if the user is private. + + Args: + user: + either the username (short_name) or id of the user to retrieve. If + not specified, then the current authenticated user is used. [optional] + count: the number of status messages to retrieve [optional] + since: + Narrows the returned results to just those statuses created + after the specified HTTP-formatted date. [optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] + + Returns: + A sequence of twitter.Status instances, one for each message up to count + ''' + try: + if count: + int(count) + except: + raise TwitterError("Count must be an integer") + parameters = {} + if count: + parameters['count'] = count + if since: + parameters['since'] = since + if since_id: + parameters['since_id'] = since_id + if user: + url = 'http://%s/statuses/user_timeline/%s.json' % (self._twitterserver,user) + elif not user and not self._username: + raise TwitterError("User must be specified if API is not authenticated.") + else: + url = 'http://%s/statuses/user_timeline.json' % self._twitterserver + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [Status.NewFromJsonDict(x) for x in data] + + def GetStatus(self, id): + '''Returns a single status message. + + The twitter.Api instance must be authenticated if the status message is private. + + Args: + id: The numerical ID of the status you're trying to retrieve. + + Returns: + A twitter.Status instance representing that status message + ''' + try: + if id: + long(id) + except: + raise TwitterError("id must be an long integer") + url = 'http://%s/statuses/show/%s.json' % (self._twitterserver,id) + json = self._FetchUrl(url) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def DestroyStatus(self, id): + '''Destroys the status specified by the required ID parameter. + + The twitter.Api instance must be authenticated and thee + authenticating user must be the author of the specified status. + + Args: + id: The numerical ID of the status you're trying to destroy. + + Returns: + A twitter.Status instance representing the destroyed status message + ''' + try: + if id: + long(id) + except: + raise TwitterError("id must be an integer") + url = 'http://%s/statuses/destroy/%s.json' % (self._twitterserver,id) + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def PostUpdate(self, status, in_reply_to_status_id=None): + '''Post a twitter status message from the authenticated user. + + The twitter.Api instance must be authenticated. + + Args: + status: + The message text to be posted. Must be less than or equal to + 140 characters. + in_reply_to_status_id: + The ID of an existing status that the status to be posted is + in reply to. This implicitly sets the in_reply_to_user_id + attribute of the resulting status to the user ID of the + message being replied to. Invalid/missing status IDs will be + ignored. [Optional] + Returns: + A twitter.Status instance representing the message posted. + ''' + if not self._username: + raise TwitterError("The twitter.Api instance must be authenticated.") + + url = 'http://%s/statuses/update.json' % self._twitterserver + + if len(status) > CHARACTER_LIMIT: + raise TwitterError("Text must be less than or equal to %d characters. " + "Consider using PostUpdates." % CHARACTER_LIMIT) + + data = {'status': status} + if in_reply_to_status_id: + data['in_reply_to_status_id'] = in_reply_to_status_id + json = self._FetchUrl(url, post_data=data) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def PostUpdates(self, status, continuation=None, **kwargs): + '''Post one or more twitter status messages from the authenticated user. + + Unlike api.PostUpdate, this method will post multiple status updates + if the message is longer than 140 characters. + + The twitter.Api instance must be authenticated. + + Args: + status: + The message text to be posted. May be longer than 140 characters. + continuation: + The character string, if any, to be appended to all but the + last message. Note that Twitter strips trailing '...' strings + from messages. Consider using the unicode \u2026 character + (horizontal ellipsis) instead. [Defaults to None] + **kwargs: + See api.PostUpdate for a list of accepted parameters. + Returns: + A of list twitter.Status instance representing the messages posted. + ''' + results = list() + if continuation is None: + continuation = '' + line_length = CHARACTER_LIMIT - len(continuation) + lines = textwrap.wrap(status, line_length) + for line in lines[0:-1]: + results.append(self.PostUpdate(line + continuation, **kwargs)) + results.append(self.PostUpdate(lines[-1], **kwargs)) + return results + + def GetReplies(self, since=None, since_id=None, page=None): + '''Get a sequence of status messages representing the 20 most recent + replies (status updates prefixed with @username) to the authenticating + user. + + Args: + page: + since: + Narrows the returned results to just those statuses created + after the specified HTTP-formatted date. [optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] + + Returns: + A sequence of twitter.Status instances, one for each reply to the user. + ''' + url = 'http://%s/statuses/replies.json' % self._twitterserver + if not self._username: + raise TwitterError("The twitter.Api instance must be authenticated.") + parameters = {} + if since: + parameters['since'] = since + if since_id: + parameters['since_id'] = since_id + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [Status.NewFromJsonDict(x) for x in data] + + def GetFriends(self, user=None, page=None): + '''Fetch the sequence of twitter.User instances, one for each friend. + + Args: + user: the username or id of the user whose friends you are fetching. If + not specified, defaults to the authenticated user. [optional] + + The twitter.Api instance must be authenticated. + + Returns: + A sequence of twitter.User instances, one for each friend + ''' + if not self._username: + raise TwitterError("twitter.Api instance must be authenticated") + if user: + url = 'http://%s/statuses/friends/%s.json' % (self._twitterserver,user) + else: + url = 'http://%s/statuses/friends.json' % self._twitterserver + parameters = {} + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [User.NewFromJsonDict(x) for x in data] + + def GetFollowers(self, page=None): + '''Fetch the sequence of twitter.User instances, one for each follower + + The twitter.Api instance must be authenticated. + + Returns: + A sequence of twitter.User instances, one for each follower + ''' + if not self._username: + raise TwitterError("twitter.Api instance must be authenticated") + url = 'http://%s/statuses/followers.json' % self._twitterserver + parameters = {} + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [User.NewFromJsonDict(x) for x in data] + + def GetFeatured(self): + '''Fetch the sequence of twitter.User instances featured on twitter.com + + The twitter.Api instance must be authenticated. + + Returns: + A sequence of twitter.User instances + ''' + url = 'http://%s/statuses/featured.json' % self._twitterserver + json = self._FetchUrl(url) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [User.NewFromJsonDict(x) for x in data] + + def GetUser(self, user): + '''Returns a single user. + + The twitter.Api instance must be authenticated. + + Args: + user: The username or id of the user to retrieve. + + Returns: + A twitter.User instance representing that user + ''' + url = 'http://%s/users/show/%s.json' % (self._twitterserver,user) + json = self._FetchUrl(url) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return User.NewFromJsonDict(data) + + def GetDirectMessages(self, since=None, since_id=None, page=None): + '''Returns a list of the direct messages sent to the authenticating user. + + The twitter.Api instance must be authenticated. + + Args: + since: + Narrows the returned results to just those statuses created + after the specified HTTP-formatted date. [optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] + + Returns: + A sequence of twitter.DirectMessage instances + ''' + url = 'http://%s/direct_messages.json' % self._twitterserver + if not self._username: + raise TwitterError("The twitter.Api instance must be authenticated.") + parameters = {} + if since: + parameters['since'] = since + if since_id: + parameters['since_id'] = since_id + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return [DirectMessage.NewFromJsonDict(x) for x in data] + + def PostDirectMessage(self, user, text): + '''Post a twitter direct message from the authenticated user + + The twitter.Api instance must be authenticated. + + Args: + user: The ID or screen name of the recipient user. + text: The message text to be posted. Must be less than 140 characters. + + Returns: + A twitter.DirectMessage instance representing the message posted + ''' + if not self._username: + raise TwitterError("The twitter.Api instance must be authenticated.") + url = 'http://%s/direct_messages/new.json' % self._twitterserver + data = {'text': text, 'user': user} + json = self._FetchUrl(url, post_data=data) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return DirectMessage.NewFromJsonDict(data) + + def DestroyDirectMessage(self, id): + '''Destroys the direct message specified in the required ID parameter. + + The twitter.Api instance must be authenticated, and the + authenticating user must be the recipient of the specified direct + message. + + Args: + id: The id of the direct message to be destroyed + + Returns: + A twitter.DirectMessage instance representing the message destroyed + ''' + url = 'http://%s/direct_messages/destroy/%s.json' % (self._twitterserver,id) + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return DirectMessage.NewFromJsonDict(data) + + def CreateFriendship(self, user): + '''Befriends the user specified in the user parameter as the authenticating user. + + The twitter.Api instance must be authenticated. + + Args: + The ID or screen name of the user to befriend. + Returns: + A twitter.User instance representing the befriended user. + ''' + url = 'http://%s/friendships/create/%s.json' % (self._twitterserver,user) + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return User.NewFromJsonDict(data) + + def DestroyFriendship(self, user): + '''Discontinues friendship with the user specified in the user parameter. + + The twitter.Api instance must be authenticated. + + Args: + The ID or screen name of the user with whom to discontinue friendship. + Returns: + A twitter.User instance representing the discontinued friend. + ''' + url = 'http://%s/friendships/destroy/%s.json' % (self._twitterserver,user) + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return User.NewFromJsonDict(data) + + def CreateFavorite(self, status): + '''Favorites the status specified in the status parameter as the authenticating user. + Returns the favorite status when successful. + + The twitter.Api instance must be authenticated. + + Args: + The twitter.Status instance to mark as a favorite. + Returns: + A twitter.Status instance representing the newly-marked favorite. + ''' + url = 'http://%s/favorites/create/%s.json' % (self._twitterserver, status.id) + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def DestroyFavorite(self, status): + '''Un-favorites the status specified in the ID parameter as the authenticating user. + Returns the un-favorited status in the requested format when successful. + + The twitter.Api instance must be authenticated. + + Args: + The twitter.Status to unmark as a favorite. + Returns: + A twitter.Status instance representing the newly-unmarked favorite. + ''' + url = 'http://%s/favorites/destroy/%s.json' % (self._twitterserver, status.id) + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def GetUserByEmail(self, email): + '''Returns a single user by email address. + + Args: + email: The email of the user to retrieve. + Returns: + A twitter.User instance representing that user + ''' + url = 'http://%s/users/show.json?email=%s' % (self._twitterserver, email) + json = self._FetchUrl(url) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return User.NewFromJsonDict(data) + + def SetCredentials(self, username, password): + '''Set the username and password for this instance + + Args: + username: The twitter username. + password: The twitter password. + ''' + self._username = username + self._password = password + + def ClearCredentials(self): + '''Clear the username and password for this instance + ''' + self._username = None + self._password = None + + def SetCache(self, cache): + '''Override the default cache. Set to None to prevent caching. + + Args: + cache: an instance that supports the same API as the twitter._FileCache + ''' + self._cache = cache + + def SetUrllib(self, urllib): + '''Override the default urllib implementation. + + Args: + urllib: an instance that supports the same API as the urllib2 module + ''' + self._urllib = urllib + + def SetCacheTimeout(self, cache_timeout): + '''Override the default cache timeout. + + Args: + cache_timeout: time, in seconds, that responses should be reused. + ''' + self._cache_timeout = cache_timeout + + def SetUserAgent(self, user_agent): + '''Override the default user agent + + Args: + user_agent: a string that should be send to the server as the User-agent + ''' + self._request_headers['User-Agent'] = user_agent + + def SetXTwitterHeaders(self, client, url, version): + '''Set the X-Twitter HTTP headers that will be sent to the server. + + Args: + client: + The client name as a string. Will be sent to the server as + the 'X-Twitter-Client' header. + url: + The URL of the meta.xml as a string. Will be sent to the server + as the 'X-Twitter-Client-URL' header. + version: + The client version as a string. Will be sent to the server + as the 'X-Twitter-Client-Version' header. + ''' + self._request_headers['X-Twitter-Client'] = client + self._request_headers['X-Twitter-Client-URL'] = url + self._request_headers['X-Twitter-Client-Version'] = version + + def SetSource(self, source): + '''Suggest the "from source" value to be displayed on the Twitter web site. + + The value of the 'source' parameter must be first recognized by + the Twitter server. New source values are authorized on a case by + case basis by the Twitter development team. + + Args: + source: + The source name as a string. Will be sent to the server as + the 'source' parameter. + ''' + self._default_params['source'] = source + + 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 _InitializeRequestHeaders(self, request_headers): + if request_headers: + self._request_headers = request_headers + else: + self._request_headers = {} + + def _InitializeUserAgent(self): + user_agent = 'Python-urllib/%s (python-twitter/%s)' % \ + (self._urllib.__version__, __version__) + self.SetUserAgent(user_agent) + + def _InitializeDefaultParameters(self): + self._default_params = {} + + def _AddAuthorizationHeader(self, username, password): + if username and password: + basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1] + self._request_headers['Authorization'] = 'Basic %s' % basic_auth + + def _RemoveAuthorizationHeader(self): + if self._request_headers and 'Authorization' in self._request_headers: + del self._request_headers['Authorization'] + + def _GetOpener(self, url, username=None, password=None): + if username and password: + self._AddAuthorizationHeader(username, password) + handler = self._urllib.HTTPBasicAuthHandler() + (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) + handler.add_password(Api._API_REALM, netloc, username, password) + opener = self._urllib.build_opener(handler) + else: + opener = self._urllib.build_opener() + opener.addheaders = self._request_headers.items() + return opener + + def _Encode(self, s): + if self._input_encoding: + return unicode(s, self._input_encoding).encode('utf-8') + else: + return unicode(s).encode('utf-8') + + 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, self._Encode(v)) for k, v in parameters.items() if v is not None])) + + def _EncodePostData(self, post_data): + '''Return a string in key=value&key=value form + + Values are assumed to be encoded in the format specified by self._encoding, + and are subsequently URL encoded. + + Args: + post_data: + 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 post_data is None: + return None + else: + return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()])) + + def _CheckForTwitterError(self, data): + """Raises a TwitterError if twitter returns an error message. + + Args: + data: A python dict created from the Twitter json response + Raises: + TwitterError wrapping the twitter error message if one exists. + """ + # Twitter errors are relatively unlikely, so it is faster + # to check first, rather than try and catch the exception + if 'error' in data: + raise TwitterError(data['error']) + + def _FetchUrl(self, + url, + post_data=None, + parameters=None, + no_cache=None): + '''Fetch a URL, optionally caching for a specified time. + + Args: + url: The URL to retrieve + post_data: + A dict of (str, unicode) key/value pairs. If set, POST will be used. + parameters: + A dict whose key/value pairs should encoded and added + to the query string. [OPTIONAL] + no_cache: If true, overrides the cache on the current request + + Returns: + A string containing the body of the response. + ''' + # Build the extra parameters dict + extra_params = {} + if self._default_params: + extra_params.update(self._default_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 + opener = self._GetOpener(url, username=self._username, password=self._password) + + encoded_post_data = self._EncodePostData(post_data) + + # Open and return the URL immediately if we're not going to cache + if encoded_post_data or no_cache or not self._cache or not self._cache_timeout: + url_data = opener.open(url, encoded_post_data).read() + opener.close() + else: + # Unique keys are a combination of the url and the username + if self._username: + key = self._username + ':' + url + else: + key = url + + # See if it has been cached before + last_cached = self._cache.GetCachedTime(key) + + # If the cached version is outdated then fetch another and store it + if not last_cached or time.time() >= last_cached + self._cache_timeout: + url_data = opener.open(url, encoded_post_data).read() + opener.close() + self._cache.Set(key, url_data) + else: + url_data = self._cache.Get(key) + + # Always return the latest version + return url_data + + +class _FileCacheError(Exception): + '''Base exception class for FileCache related errors''' + +class _FileCache(object): + + DEPTH = 3 + + def __init__(self,root_directory=None): + self._InitializeRootDirectory(root_directory) + + def Get(self,key): + path = self._GetPath(key) + if os.path.exists(path): + return open(path).read() + else: + return None + + def Set(self,key,data): + path = self._GetPath(key) + directory = os.path.dirname(path) + if not os.path.exists(directory): + os.makedirs(directory) + if not os.path.isdir(directory): + raise _FileCacheError('%s exists but is not a directory' % directory) + temp_fd, temp_path = tempfile.mkstemp() + temp_fp = os.fdopen(temp_fd, 'w') + temp_fp.write(data) + temp_fp.close() + if not path.startswith(self._root_directory): + raise _FileCacheError('%s does not appear to live under %s' % + (path, self._root_directory)) + if os.path.exists(path): + os.remove(path) + os.rename(temp_path, path) + + def Remove(self,key): + path = self._GetPath(key) + if not path.startswith(self._root_directory): + raise _FileCacheError('%s does not appear to live under %s' % + (path, self._root_directory )) + if os.path.exists(path): + os.remove(path) + + def GetCachedTime(self,key): + path = self._GetPath(key) + if os.path.exists(path): + return os.path.getmtime(path) + else: + return None + + def _GetUsername(self): + '''Attempt to find the username in a cross-platform fashion.''' + try: + return os.getenv('USER') or \ + os.getenv('LOGNAME') or \ + os.getenv('USERNAME') or \ + os.getlogin() or \ + 'nobody' + except (IOError, OSError), e: + return 'nobody' + + def _GetTmpCachePath(self): + username = self._GetUsername() + cache_directory = 'python.cache_' + username + return os.path.join(tempfile.gettempdir(), cache_directory) + + def _InitializeRootDirectory(self, root_directory): + if not root_directory: + root_directory = self._GetTmpCachePath() + root_directory = os.path.abspath(root_directory) + if not os.path.exists(root_directory): + os.mkdir(root_directory) + if not os.path.isdir(root_directory): + raise _FileCacheError('%s exists but is not a directory' % + root_directory) + self._root_directory = root_directory + + def _GetPath(self,key): + try: + hashed_key = md5(key).hexdigest() + except TypeError: + hashed_key = md5.new(key).hexdigest() + + return os.path.join(self._root_directory, + self._GetPrefix(hashed_key), + hashed_key) + + def _GetPrefix(self,hashed_key): + return os.path.sep.join(hashed_key[0:_FileCache.DEPTH]) diff --git a/render.py b/render.py index c7ce2fe..71b86fd 100755 --- a/render.py +++ b/render.py @@ -36,6 +36,7 @@ import helper.cache import helper.log import helper.date import helper.stringfmt +import helper.twitter import data.awards import data.themes @@ -107,6 +108,10 @@ SUMMARY_LISTS = re.compile('Mailing lists \(public\): ([0-9]*)') SUMMARY_FORUMS = re.compile('Discussion forums \(public\): ([0-9]*), containing ([0-9]*) messages') SUMMARY_TRACKER = re.compile('Tracker: (.*) \(([0-9]*) open/([0-9]*) total\)') +# Indenti.ca integration +IDENTICA_USER = 'phpmyadmin' +IDENTICA_PASSWORD = None + def copytree(src, dst): ''' Trimmed down version of shutil.copytree. Recursively copies a directory @@ -521,6 +526,30 @@ class SFGenerator: self.data['short_news'] = self.data['news'][:5] + def tweet(self): + ''' + Finds out whether we should send update to identi.ca and twitter and do so. + ''' + news = self.data['news'][0] + if IDENTICA_USER is None or IDENTICA_PASSWORD is None: + return + storage = helper.cache.Cache() + try: + last = storage.get('last-tweet') + except helper.cache.NoCache: + last = None + if last == news['link']: + helper.log.dbg('No need to tweet, the last news is still the same...') + return + tweet = '%s | http://www.phpmyadmin.net/ | #phpmyadmin' % news['title'] + helper.log.dbg('Tweeting to identi.ca: %s' % tweet) + api = helper.twitter.Api(username = IDENTICA_USER, + password = IDENTICA_PASSWORD, + twitterserver='identi.ca/api') + api.SetSource('phpMyAdmin website') + api.PostUpdate(tweet) + last = storage.set('last-tweet', news['link']) + def process_planet(self, feed): ''' Fills in planet based on planet feed. @@ -919,6 +948,8 @@ class SFGenerator: self.generate_sitemap() + self.tweet() + def render_pages(self): ''' Renders all content pages. @@ -1006,6 +1037,14 @@ if __name__ == '__main__': action='store', type='string', dest='log', help='Log filename, default is none.') + parser.add_option('-p', '--identica-password', + action='store', type='string', + dest='identica_password', + help='Pasword to identi.ca, default is not to post there.') + parser.add_option('-u', '--identica-user', + action='store', type='string', + dest='identica_user', + help='Username to identi.ca, defaull is %s.' % IDENTICA_USER) parser.set_defaults( verbose = helper.log.VERBOSE, @@ -1014,7 +1053,9 @@ if __name__ == '__main__': base_url = BASE_URL, clean = CLEAN_OUTPUT, log = None, - extension = EXTENSION + extension = EXTENSION, + identica_user = IDENTICA_USER, + identica_password = IDENTICA_PASSWORD ) (options, args) = parser.parse_args() @@ -1025,6 +1066,8 @@ if __name__ == '__main__': BASE_URL = options.base_url EXTENSION = options.extension CLEAN_OUTPUT = options.clean + IDENTICA_USER = options.identica_user + IDENTICA_PASSWORD = options.identica_password if options.log is not None: helper.log.LOG = open(options.log, 'w') -- 2.11.4.GIT