Allow to post news to identi.ca / twitter.
[phpmyadmin-website.git] / helper / twitter.py
blob9f854b6da114a746e94f9d0497deee1f50b58226
1 #!/usr/bin/python
3 # Copyright 2007 Google Inc. All Rights Reserved.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 '''A library that provides a python interface to the Twitter API'''
19 __author__ = 'dewitt@google.com'
20 __version__ = '0.6-devel'
23 import base64
24 import calendar
25 import os
26 import rfc822
27 import simplejson
28 import sys
29 import tempfile
30 import textwrap
31 import time
32 import urllib
33 import urllib2
34 import urlparse
36 try:
37 from hashlib import md5
38 except ImportError:
39 from md5 import md5
42 CHARACTER_LIMIT = 140
45 class TwitterError(Exception):
46 '''Base class for Twitter errors'''
48 @property
49 def message(self):
50 '''Returns the first argument used to construct this error.'''
51 return self.args[0]
54 class Status(object):
55 '''A class representing the Status structure used by the twitter API.
57 The Status structure exposes the following properties:
59 status.created_at
60 status.created_at_in_seconds # read only
61 status.favorited
62 status.in_reply_to_screen_name
63 status.in_reply_to_user_id
64 status.in_reply_to_status_id
65 status.truncated
66 status.source
67 status.id
68 status.text
69 status.relative_created_at # read only
70 status.user
71 '''
72 def __init__(self,
73 created_at=None,
74 favorited=None,
75 id=None,
76 text=None,
77 user=None,
78 in_reply_to_screen_name=None,
79 in_reply_to_user_id=None,
80 in_reply_to_status_id=None,
81 truncated=None,
82 source=None,
83 now=None):
84 '''An object to hold a Twitter status message.
86 This class is normally instantiated by the twitter.Api class and
87 returned in a sequence.
89 Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
91 Args:
92 created_at: The time this status message was posted
93 favorited: Whether this is a favorite of the authenticated user
94 id: The unique id of this status message
95 text: The text of this status message
96 relative_created_at:
97 A human readable string representing the posting time
98 user:
99 A twitter.User instance representing the person posting the message
100 now:
101 The current time, if the client choses to set it. Defaults to the
102 wall clock time.
104 self.created_at = created_at
105 self.favorited = favorited
106 self.id = id
107 self.text = text
108 self.user = user
109 self.now = now
110 self.in_reply_to_screen_name = in_reply_to_screen_name
111 self.in_reply_to_user_id = in_reply_to_user_id
112 self.in_reply_to_status_id = in_reply_to_status_id
113 self.truncated = truncated
114 self.source = source
116 def GetCreatedAt(self):
117 '''Get the time this status message was posted.
119 Returns:
120 The time this status message was posted
122 return self._created_at
124 def SetCreatedAt(self, created_at):
125 '''Set the time this status message was posted.
127 Args:
128 created_at: The time this status message was created
130 self._created_at = created_at
132 created_at = property(GetCreatedAt, SetCreatedAt,
133 doc='The time this status message was posted.')
135 def GetCreatedAtInSeconds(self):
136 '''Get the time this status message was posted, in seconds since the epoch.
138 Returns:
139 The time this status message was posted, in seconds since the epoch.
141 return calendar.timegm(rfc822.parsedate(self.created_at))
143 created_at_in_seconds = property(GetCreatedAtInSeconds,
144 doc="The time this status message was "
145 "posted, in seconds since the epoch")
147 def GetFavorited(self):
148 '''Get the favorited setting of this status message.
150 Returns:
151 True if this status message is favorited; False otherwise
153 return self._favorited
155 def SetFavorited(self, favorited):
156 '''Set the favorited state of this status message.
158 Args:
159 favorited: boolean True/False favorited state of this status message
161 self._favorited = favorited
163 favorited = property(GetFavorited, SetFavorited,
164 doc='The favorited state of this status message.')
166 def GetId(self):
167 '''Get the unique id of this status message.
169 Returns:
170 The unique id of this status message
172 return self._id
174 def SetId(self, id):
175 '''Set the unique id of this status message.
177 Args:
178 id: The unique id of this status message
180 self._id = id
182 id = property(GetId, SetId,
183 doc='The unique id of this status message.')
185 def GetInReplyToScreenName(self):
186 return self._in_reply_to_screen_name
188 def SetInReplyToScreenName(self, in_reply_to_screen_name):
189 self._in_reply_to_screen_name = in_reply_to_screen_name
191 in_reply_to_screen_name = property(GetInReplyToScreenName, SetInReplyToScreenName,
192 doc='')
194 def GetInReplyToUserId(self):
195 return self._in_reply_to_user_id
197 def SetInReplyToUserId(self, in_reply_to_user_id):
198 self._in_reply_to_user_id = in_reply_to_user_id
200 in_reply_to_user_id = property(GetInReplyToUserId, SetInReplyToUserId,
201 doc='')
203 def GetInReplyToStatusId(self):
204 return self._in_reply_to_status_id
206 def SetInReplyToStatusId(self, in_reply_to_status_id):
207 self._in_reply_to_status_id = in_reply_to_status_id
209 in_reply_to_status_id = property(GetInReplyToStatusId, SetInReplyToStatusId,
210 doc='')
212 def GetTruncated(self):
213 return self._truncated
215 def SetTruncated(self, truncated):
216 self._truncated = truncated
218 truncated = property(GetTruncated, SetTruncated,
219 doc='')
221 def GetSource(self):
222 return self._source
224 def SetSource(self, source):
225 self._source = source
227 source = property(GetSource, SetSource,
228 doc='')
230 def GetText(self):
231 '''Get the text of this status message.
233 Returns:
234 The text of this status message.
236 return self._text
238 def SetText(self, text):
239 '''Set the text of this status message.
241 Args:
242 text: The text of this status message
244 self._text = text
246 text = property(GetText, SetText,
247 doc='The text of this status message')
249 def GetRelativeCreatedAt(self):
250 '''Get a human redable string representing the posting time
252 Returns:
253 A human readable string representing the posting time
255 fudge = 1.25
256 delta = long(self.now) - long(self.created_at_in_seconds)
258 if delta < (1 * fudge):
259 return 'about a second ago'
260 elif delta < (60 * (1/fudge)):
261 return 'about %d seconds ago' % (delta)
262 elif delta < (60 * fudge):
263 return 'about a minute ago'
264 elif delta < (60 * 60 * (1/fudge)):
265 return 'about %d minutes ago' % (delta / 60)
266 elif delta < (60 * 60 * fudge):
267 return 'about an hour ago'
268 elif delta < (60 * 60 * 24 * (1/fudge)):
269 return 'about %d hours ago' % (delta / (60 * 60))
270 elif delta < (60 * 60 * 24 * fudge):
271 return 'about a day ago'
272 else:
273 return 'about %d days ago' % (delta / (60 * 60 * 24))
275 relative_created_at = property(GetRelativeCreatedAt,
276 doc='Get a human readable string representing'
277 'the posting time')
279 def GetUser(self):
280 '''Get a twitter.User reprenting the entity posting this status message.
282 Returns:
283 A twitter.User reprenting the entity posting this status message
285 return self._user
287 def SetUser(self, user):
288 '''Set a twitter.User reprenting the entity posting this status message.
290 Args:
291 user: A twitter.User reprenting the entity posting this status message
293 self._user = user
295 user = property(GetUser, SetUser,
296 doc='A twitter.User reprenting the entity posting this '
297 'status message')
299 def GetNow(self):
300 '''Get the wallclock time for this status message.
302 Used to calculate relative_created_at. Defaults to the time
303 the object was instantiated.
305 Returns:
306 Whatever the status instance believes the current time to be,
307 in seconds since the epoch.
309 if self._now is None:
310 self._now = time.time()
311 return self._now
313 def SetNow(self, now):
314 '''Set the wallclock time for this status message.
316 Used to calculate relative_created_at. Defaults to the time
317 the object was instantiated.
319 Args:
320 now: The wallclock time for this instance.
322 self._now = now
324 now = property(GetNow, SetNow,
325 doc='The wallclock time for this status instance.')
328 def __ne__(self, other):
329 return not self.__eq__(other)
331 def __eq__(self, other):
332 try:
333 return other and \
334 self.created_at == other.created_at and \
335 self.id == other.id and \
336 self.text == other.text and \
337 self.user == other.user and \
338 self.in_reply_to_screen_name == other.in_reply_to_screen_name and \
339 self.in_reply_to_user_id == other.in_reply_to_user_id and \
340 self.in_reply_to_status_id == other.in_reply_to_status_id and \
341 self.truncated == other.truncated and \
342 self.favorited == other.favorited and \
343 self.source == other.source
344 except AttributeError:
345 return False
347 def __str__(self):
348 '''A string representation of this twitter.Status instance.
350 The return value is the same as the JSON string representation.
352 Returns:
353 A string representation of this twitter.Status instance.
355 return self.AsJsonString()
357 def AsJsonString(self):
358 '''A JSON string representation of this twitter.Status instance.
360 Returns:
361 A JSON string representation of this twitter.Status instance
363 return simplejson.dumps(self.AsDict(), sort_keys=True)
365 def AsDict(self):
366 '''A dict representation of this twitter.Status instance.
368 The return value uses the same key names as the JSON representation.
370 Return:
371 A dict representing this twitter.Status instance
373 data = {}
374 if self.created_at:
375 data['created_at'] = self.created_at
376 if self.favorited:
377 data['favorited'] = self.favorited
378 if self.id:
379 data['id'] = self.id
380 if self.text:
381 data['text'] = self.text
382 if self.user:
383 data['user'] = self.user.AsDict()
384 if self.in_reply_to_screen_name:
385 data['in_reply_to_screen_name'] = self.in_reply_to_screen_name
386 if self.in_reply_to_user_id:
387 data['in_reply_to_user_id'] = self.in_reply_to_user_id
388 if self.in_reply_to_status_id:
389 data['in_reply_to_status_id'] = self.in_reply_to_status_id
390 if self.truncated is not None:
391 data['truncated'] = self.truncated
392 if self.favorited is not None:
393 data['favorited'] = self.favorited
394 if self.source:
395 data['source'] = self.source
396 return data
398 @staticmethod
399 def NewFromJsonDict(data):
400 '''Create a new instance based on a JSON dict.
402 Args:
403 data: A JSON dict, as converted from the JSON in the twitter API
404 Returns:
405 A twitter.Status instance
407 if 'user' in data:
408 user = User.NewFromJsonDict(data['user'])
409 else:
410 user = None
411 return Status(created_at=data.get('created_at', None),
412 favorited=data.get('favorited', None),
413 id=data.get('id', None),
414 text=data.get('text', None),
415 in_reply_to_screen_name=data.get('in_reply_to_screen_name', None),
416 in_reply_to_user_id=data.get('in_reply_to_user_id', None),
417 in_reply_to_status_id=data.get('in_reply_to_status_id', None),
418 truncated=data.get('truncated', None),
419 source=data.get('source', None),
420 user=user)
423 class User(object):
424 '''A class representing the User structure used by the twitter API.
426 The User structure exposes the following properties:
428 user.id
429 user.name
430 user.screen_name
431 user.location
432 user.description
433 user.profile_image_url
434 user.profile_background_tile
435 user.profile_background_image_url
436 user.profile_sidebar_fill_color
437 user.profile_background_color
438 user.profile_link_color
439 user.profile_text_color
440 user.protected
441 user.utc_offset
442 user.time_zone
443 user.url
444 user.status
445 user.statuses_count
446 user.followers_count
447 user.friends_count
448 user.favourites_count
450 def __init__(self,
451 id=None,
452 name=None,
453 screen_name=None,
454 location=None,
455 description=None,
456 profile_image_url=None,
457 profile_background_tile=None,
458 profile_background_image_url=None,
459 profile_sidebar_fill_color=None,
460 profile_background_color=None,
461 profile_link_color=None,
462 profile_text_color=None,
463 protected=None,
464 utc_offset=None,
465 time_zone=None,
466 followers_count=None,
467 friends_count=None,
468 statuses_count=None,
469 favourites_count=None,
470 url=None,
471 status=None):
472 self.id = id
473 self.name = name
474 self.screen_name = screen_name
475 self.location = location
476 self.description = description
477 self.profile_image_url = profile_image_url
478 self.profile_background_tile = profile_background_tile
479 self.profile_background_image_url = profile_background_image_url
480 self.profile_sidebar_fill_color = profile_sidebar_fill_color
481 self.profile_background_color = profile_background_color
482 self.profile_link_color = profile_link_color
483 self.profile_text_color = profile_text_color
484 self.protected = protected
485 self.utc_offset = utc_offset
486 self.time_zone = time_zone
487 self.followers_count = followers_count
488 self.friends_count = friends_count
489 self.statuses_count = statuses_count
490 self.favourites_count = favourites_count
491 self.url = url
492 self.status = status
495 def GetId(self):
496 '''Get the unique id of this user.
498 Returns:
499 The unique id of this user
501 return self._id
503 def SetId(self, id):
504 '''Set the unique id of this user.
506 Args:
507 id: The unique id of this user.
509 self._id = id
511 id = property(GetId, SetId,
512 doc='The unique id of this user.')
514 def GetName(self):
515 '''Get the real name of this user.
517 Returns:
518 The real name of this user
520 return self._name
522 def SetName(self, name):
523 '''Set the real name of this user.
525 Args:
526 name: The real name of this user
528 self._name = name
530 name = property(GetName, SetName,
531 doc='The real name of this user.')
533 def GetScreenName(self):
534 '''Get the short username of this user.
536 Returns:
537 The short username of this user
539 return self._screen_name
541 def SetScreenName(self, screen_name):
542 '''Set the short username of this user.
544 Args:
545 screen_name: the short username of this user
547 self._screen_name = screen_name
549 screen_name = property(GetScreenName, SetScreenName,
550 doc='The short username of this user.')
552 def GetLocation(self):
553 '''Get the geographic location of this user.
555 Returns:
556 The geographic location of this user
558 return self._location
560 def SetLocation(self, location):
561 '''Set the geographic location of this user.
563 Args:
564 location: The geographic location of this user
566 self._location = location
568 location = property(GetLocation, SetLocation,
569 doc='The geographic location of this user.')
571 def GetDescription(self):
572 '''Get the short text description of this user.
574 Returns:
575 The short text description of this user
577 return self._description
579 def SetDescription(self, description):
580 '''Set the short text description of this user.
582 Args:
583 description: The short text description of this user
585 self._description = description
587 description = property(GetDescription, SetDescription,
588 doc='The short text description of this user.')
590 def GetUrl(self):
591 '''Get the homepage url of this user.
593 Returns:
594 The homepage url of this user
596 return self._url
598 def SetUrl(self, url):
599 '''Set the homepage url of this user.
601 Args:
602 url: The homepage url of this user
604 self._url = url
606 url = property(GetUrl, SetUrl,
607 doc='The homepage url of this user.')
609 def GetProfileImageUrl(self):
610 '''Get the url of the thumbnail of this user.
612 Returns:
613 The url of the thumbnail of this user
615 return self._profile_image_url
617 def SetProfileImageUrl(self, profile_image_url):
618 '''Set the url of the thumbnail of this user.
620 Args:
621 profile_image_url: The url of the thumbnail of this user
623 self._profile_image_url = profile_image_url
625 profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl,
626 doc='The url of the thumbnail of this user.')
628 def GetProfileBackgroundTile(self):
629 '''Boolean for whether to tile the profile background image.
631 Returns:
632 True if the background is to be tiled, False if not, None if unset.
634 return self._profile_background_tile
636 def SetProfileBackgroundTile(self, profile_background_tile):
637 '''Set the boolean flag for whether to tile the profile background image.
639 Args:
640 profile_background_tile: Boolean flag for whether to tile or not.
642 self._profile_background_tile = profile_background_tile
644 profile_background_tile = property(GetProfileBackgroundTile, SetProfileBackgroundTile,
645 doc='Boolean for whether to tile the background image.')
647 def GetProfileBackgroundImageUrl(self):
648 return self._profile_background_image_url
650 def SetProfileBackgroundImageUrl(self, profile_background_image_url):
651 self._profile_background_image_url = profile_background_image_url
653 profile_background_image_url = property(GetProfileBackgroundImageUrl, SetProfileBackgroundImageUrl,
654 doc='The url of the profile background of this user.')
656 def GetProfileSidebarFillColor(self):
657 return self._profile_sidebar_fill_color
659 def SetProfileSidebarFillColor(self, profile_sidebar_fill_color):
660 self._profile_sidebar_fill_color = profile_sidebar_fill_color
662 profile_sidebar_fill_color = property(GetProfileSidebarFillColor, SetProfileSidebarFillColor)
664 def GetProfileBackgroundColor(self):
665 return self._profile_background_color
667 def SetProfileBackgroundColor(self, profile_background_color):
668 self._profile_background_color = profile_background_color
670 profile_background_color = property(GetProfileBackgroundColor, SetProfileBackgroundColor)
672 def GetProfileLinkColor(self):
673 return self._profile_link_color
675 def SetProfileLinkColor(self, profile_link_color):
676 self._profile_link_color = profile_link_color
678 profile_link_color = property(GetProfileLinkColor, SetProfileLinkColor)
680 def GetProfileTextColor(self):
681 return self._profile_text_color
683 def SetProfileTextColor(self, profile_text_color):
684 self._profile_text_color = profile_text_color
686 profile_text_color = property(GetProfileTextColor, SetProfileTextColor)
688 def GetProtected(self):
689 return self._protected
691 def SetProtected(self, protected):
692 self._protected = protected
694 protected = property(GetProtected, SetProtected)
696 def GetUtcOffset(self):
697 return self._utc_offset
699 def SetUtcOffset(self, utc_offset):
700 self._utc_offset = utc_offset
702 utc_offset = property(GetUtcOffset, SetUtcOffset)
704 def GetTimeZone(self):
705 '''Returns the current time zone string for the user.
707 Returns:
708 The descriptive time zone string for the user.
710 return self._time_zone
712 def SetTimeZone(self, time_zone):
713 '''Sets the user's time zone string.
715 Args:
716 time_zone: The descriptive time zone to assign for the user.
718 self._time_zone = time_zone
720 time_zone = property(GetTimeZone, SetTimeZone)
722 def GetStatus(self):
723 '''Get the latest twitter.Status of this user.
725 Returns:
726 The latest twitter.Status of this user
728 return self._status
730 def SetStatus(self, status):
731 '''Set the latest twitter.Status of this user.
733 Args:
734 status: The latest twitter.Status of this user
736 self._status = status
738 status = property(GetStatus, SetStatus,
739 doc='The latest twitter.Status of this user.')
741 def GetFriendsCount(self):
742 '''Get the friend count for this user.
744 Returns:
745 The number of users this user has befriended.
747 return self._friends_count
749 def SetFriendsCount(self, count):
750 '''Set the friend count for this user.
752 Args:
753 count: The number of users this user has befriended.
755 self._friends_count = count
757 friends_count = property(GetFriendsCount, SetFriendsCount,
758 doc='The number of friends for this user.')
760 def GetFollowersCount(self):
761 '''Get the follower count for this user.
763 Returns:
764 The number of users following this user.
766 return self._followers_count
768 def SetFollowersCount(self, count):
769 '''Set the follower count for this user.
771 Args:
772 count: The number of users following this user.
774 self._followers_count = count
776 followers_count = property(GetFollowersCount, SetFollowersCount,
777 doc='The number of users following this user.')
779 def GetStatusesCount(self):
780 '''Get the number of status updates for this user.
782 Returns:
783 The number of status updates for this user.
785 return self._statuses_count
787 def SetStatusesCount(self, count):
788 '''Set the status update count for this user.
790 Args:
791 count: The number of updates for this user.
793 self._statuses_count = count
795 statuses_count = property(GetStatusesCount, SetStatusesCount,
796 doc='The number of updates for this user.')
798 def GetFavouritesCount(self):
799 '''Get the number of favourites for this user.
801 Returns:
802 The number of favourites for this user.
804 return self._favourites_count
806 def SetFavouritesCount(self, count):
807 '''Set the favourite count for this user.
809 Args:
810 count: The number of favourites for this user.
812 self._favourites_count = count
814 favourites_count = property(GetFavouritesCount, SetFavouritesCount,
815 doc='The number of favourites for this user.')
817 def __ne__(self, other):
818 return not self.__eq__(other)
820 def __eq__(self, other):
821 try:
822 return other and \
823 self.id == other.id and \
824 self.name == other.name and \
825 self.screen_name == other.screen_name and \
826 self.location == other.location and \
827 self.description == other.description and \
828 self.profile_image_url == other.profile_image_url and \
829 self.profile_background_tile == other.profile_background_tile and \
830 self.profile_background_image_url == other.profile_background_image_url and \
831 self.profile_sidebar_fill_color == other.profile_sidebar_fill_color and \
832 self.profile_background_color == other.profile_background_color and \
833 self.profile_link_color == other.profile_link_color and \
834 self.profile_text_color == other.profile_text_color and \
835 self.protected == other.protected and \
836 self.utc_offset == other.utc_offset and \
837 self.time_zone == other.time_zone and \
838 self.url == other.url and \
839 self.statuses_count == other.statuses_count and \
840 self.followers_count == other.followers_count and \
841 self.favourites_count == other.favourites_count and \
842 self.friends_count == other.friends_count and \
843 self.status == other.status
844 except AttributeError:
845 return False
847 def __str__(self):
848 '''A string representation of this twitter.User instance.
850 The return value is the same as the JSON string representation.
852 Returns:
853 A string representation of this twitter.User instance.
855 return self.AsJsonString()
857 def AsJsonString(self):
858 '''A JSON string representation of this twitter.User instance.
860 Returns:
861 A JSON string representation of this twitter.User instance
863 return simplejson.dumps(self.AsDict(), sort_keys=True)
865 def AsDict(self):
866 '''A dict representation of this twitter.User instance.
868 The return value uses the same key names as the JSON representation.
870 Return:
871 A dict representing this twitter.User instance
873 data = {}
874 if self.id:
875 data['id'] = self.id
876 if self.name:
877 data['name'] = self.name
878 if self.screen_name:
879 data['screen_name'] = self.screen_name
880 if self.location:
881 data['location'] = self.location
882 if self.description:
883 data['description'] = self.description
884 if self.profile_image_url:
885 data['profile_image_url'] = self.profile_image_url
886 if self.profile_background_tile is not None:
887 data['profile_background_tile'] = self.profile_background_tile
888 if self.profile_background_image_url:
889 data['profile_sidebar_fill_color'] = self.profile_background_image_url
890 if self.profile_background_color:
891 data['profile_background_color'] = self.profile_background_color
892 if self.profile_link_color:
893 data['profile_link_color'] = self.profile_link_color
894 if self.profile_text_color:
895 data['profile_text_color'] = self.profile_text_color
896 if self.protected is not None:
897 data['protected'] = self.protected
898 if self.utc_offset:
899 data['utc_offset'] = self.utc_offset
900 if self.time_zone:
901 data['time_zone'] = self.time_zone
902 if self.url:
903 data['url'] = self.url
904 if self.status:
905 data['status'] = self.status.AsDict()
906 if self.friends_count:
907 data['friends_count'] = self.friends_count
908 if self.followers_count:
909 data['followers_count'] = self.followers_count
910 if self.statuses_count:
911 data['statuses_count'] = self.statuses_count
912 if self.favourites_count:
913 data['favourites_count'] = self.favourites_count
914 return data
916 @staticmethod
917 def NewFromJsonDict(data):
918 '''Create a new instance based on a JSON dict.
920 Args:
921 data: A JSON dict, as converted from the JSON in the twitter API
922 Returns:
923 A twitter.User instance
925 if 'status' in data:
926 status = Status.NewFromJsonDict(data['status'])
927 else:
928 status = None
929 return User(id=data.get('id', None),
930 name=data.get('name', None),
931 screen_name=data.get('screen_name', None),
932 location=data.get('location', None),
933 description=data.get('description', None),
934 statuses_count=data.get('statuses_count', None),
935 followers_count=data.get('followers_count', None),
936 favourites_count=data.get('favourites_count', None),
937 friends_count=data.get('friends_count', None),
938 profile_image_url=data.get('profile_image_url', None),
939 profile_background_tile = data.get('profile_background_tile', None),
940 profile_background_image_url = data.get('profile_background_image_url', None),
941 profile_sidebar_fill_color = data.get('profile_sidebar_fill_color', None),
942 profile_background_color = data.get('profile_background_color', None),
943 profile_link_color = data.get('profile_link_color', None),
944 profile_text_color = data.get('profile_text_color', None),
945 protected = data.get('protected', None),
946 utc_offset = data.get('utc_offset', None),
947 time_zone = data.get('time_zone', None),
948 url=data.get('url', None),
949 status=status)
951 class DirectMessage(object):
952 '''A class representing the DirectMessage structure used by the twitter API.
954 The DirectMessage structure exposes the following properties:
956 direct_message.id
957 direct_message.created_at
958 direct_message.created_at_in_seconds # read only
959 direct_message.sender_id
960 direct_message.sender_screen_name
961 direct_message.recipient_id
962 direct_message.recipient_screen_name
963 direct_message.text
966 def __init__(self,
967 id=None,
968 created_at=None,
969 sender_id=None,
970 sender_screen_name=None,
971 recipient_id=None,
972 recipient_screen_name=None,
973 text=None):
974 '''An object to hold a Twitter direct message.
976 This class is normally instantiated by the twitter.Api class and
977 returned in a sequence.
979 Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
981 Args:
982 id: The unique id of this direct message
983 created_at: The time this direct message was posted
984 sender_id: The id of the twitter user that sent this message
985 sender_screen_name: The name of the twitter user that sent this message
986 recipient_id: The id of the twitter that received this message
987 recipient_screen_name: The name of the twitter that received this message
988 text: The text of this direct message
990 self.id = id
991 self.created_at = created_at
992 self.sender_id = sender_id
993 self.sender_screen_name = sender_screen_name
994 self.recipient_id = recipient_id
995 self.recipient_screen_name = recipient_screen_name
996 self.text = text
998 def GetId(self):
999 '''Get the unique id of this direct message.
1001 Returns:
1002 The unique id of this direct message
1004 return self._id
1006 def SetId(self, id):
1007 '''Set the unique id of this direct message.
1009 Args:
1010 id: The unique id of this direct message
1012 self._id = id
1014 id = property(GetId, SetId,
1015 doc='The unique id of this direct message.')
1017 def GetCreatedAt(self):
1018 '''Get the time this direct message was posted.
1020 Returns:
1021 The time this direct message was posted
1023 return self._created_at
1025 def SetCreatedAt(self, created_at):
1026 '''Set the time this direct message was posted.
1028 Args:
1029 created_at: The time this direct message was created
1031 self._created_at = created_at
1033 created_at = property(GetCreatedAt, SetCreatedAt,
1034 doc='The time this direct message was posted.')
1036 def GetCreatedAtInSeconds(self):
1037 '''Get the time this direct message was posted, in seconds since the epoch.
1039 Returns:
1040 The time this direct message was posted, in seconds since the epoch.
1042 return calendar.timegm(rfc822.parsedate(self.created_at))
1044 created_at_in_seconds = property(GetCreatedAtInSeconds,
1045 doc="The time this direct message was "
1046 "posted, in seconds since the epoch")
1048 def GetSenderId(self):
1049 '''Get the unique sender id of this direct message.
1051 Returns:
1052 The unique sender id of this direct message
1054 return self._sender_id
1056 def SetSenderId(self, sender_id):
1057 '''Set the unique sender id of this direct message.
1059 Args:
1060 sender id: The unique sender id of this direct message
1062 self._sender_id = sender_id
1064 sender_id = property(GetSenderId, SetSenderId,
1065 doc='The unique sender id of this direct message.')
1067 def GetSenderScreenName(self):
1068 '''Get the unique sender screen name of this direct message.
1070 Returns:
1071 The unique sender screen name of this direct message
1073 return self._sender_screen_name
1075 def SetSenderScreenName(self, sender_screen_name):
1076 '''Set the unique sender screen name of this direct message.
1078 Args:
1079 sender_screen_name: The unique sender screen name of this direct message
1081 self._sender_screen_name = sender_screen_name
1083 sender_screen_name = property(GetSenderScreenName, SetSenderScreenName,
1084 doc='The unique sender screen name of this direct message.')
1086 def GetRecipientId(self):
1087 '''Get the unique recipient id of this direct message.
1089 Returns:
1090 The unique recipient id of this direct message
1092 return self._recipient_id
1094 def SetRecipientId(self, recipient_id):
1095 '''Set the unique recipient id of this direct message.
1097 Args:
1098 recipient id: The unique recipient id of this direct message
1100 self._recipient_id = recipient_id
1102 recipient_id = property(GetRecipientId, SetRecipientId,
1103 doc='The unique recipient id of this direct message.')
1105 def GetRecipientScreenName(self):
1106 '''Get the unique recipient screen name of this direct message.
1108 Returns:
1109 The unique recipient screen name of this direct message
1111 return self._recipient_screen_name
1113 def SetRecipientScreenName(self, recipient_screen_name):
1114 '''Set the unique recipient screen name of this direct message.
1116 Args:
1117 recipient_screen_name: The unique recipient screen name of this direct message
1119 self._recipient_screen_name = recipient_screen_name
1121 recipient_screen_name = property(GetRecipientScreenName, SetRecipientScreenName,
1122 doc='The unique recipient screen name of this direct message.')
1124 def GetText(self):
1125 '''Get the text of this direct message.
1127 Returns:
1128 The text of this direct message.
1130 return self._text
1132 def SetText(self, text):
1133 '''Set the text of this direct message.
1135 Args:
1136 text: The text of this direct message
1138 self._text = text
1140 text = property(GetText, SetText,
1141 doc='The text of this direct message')
1143 def __ne__(self, other):
1144 return not self.__eq__(other)
1146 def __eq__(self, other):
1147 try:
1148 return other and \
1149 self.id == other.id and \
1150 self.created_at == other.created_at and \
1151 self.sender_id == other.sender_id and \
1152 self.sender_screen_name == other.sender_screen_name and \
1153 self.recipient_id == other.recipient_id and \
1154 self.recipient_screen_name == other.recipient_screen_name and \
1155 self.text == other.text
1156 except AttributeError:
1157 return False
1159 def __str__(self):
1160 '''A string representation of this twitter.DirectMessage instance.
1162 The return value is the same as the JSON string representation.
1164 Returns:
1165 A string representation of this twitter.DirectMessage instance.
1167 return self.AsJsonString()
1169 def AsJsonString(self):
1170 '''A JSON string representation of this twitter.DirectMessage instance.
1172 Returns:
1173 A JSON string representation of this twitter.DirectMessage instance
1175 return simplejson.dumps(self.AsDict(), sort_keys=True)
1177 def AsDict(self):
1178 '''A dict representation of this twitter.DirectMessage instance.
1180 The return value uses the same key names as the JSON representation.
1182 Return:
1183 A dict representing this twitter.DirectMessage instance
1185 data = {}
1186 if self.id:
1187 data['id'] = self.id
1188 if self.created_at:
1189 data['created_at'] = self.created_at
1190 if self.sender_id:
1191 data['sender_id'] = self.sender_id
1192 if self.sender_screen_name:
1193 data['sender_screen_name'] = self.sender_screen_name
1194 if self.recipient_id:
1195 data['recipient_id'] = self.recipient_id
1196 if self.recipient_screen_name:
1197 data['recipient_screen_name'] = self.recipient_screen_name
1198 if self.text:
1199 data['text'] = self.text
1200 return data
1202 @staticmethod
1203 def NewFromJsonDict(data):
1204 '''Create a new instance based on a JSON dict.
1206 Args:
1207 data: A JSON dict, as converted from the JSON in the twitter API
1208 Returns:
1209 A twitter.DirectMessage instance
1211 return DirectMessage(created_at=data.get('created_at', None),
1212 recipient_id=data.get('recipient_id', None),
1213 sender_id=data.get('sender_id', None),
1214 text=data.get('text', None),
1215 sender_screen_name=data.get('sender_screen_name', None),
1216 id=data.get('id', None),
1217 recipient_screen_name=data.get('recipient_screen_name', None))
1219 class Api(object):
1220 '''A python interface into the Twitter API
1222 By default, the Api caches results for 1 minute.
1224 Example usage:
1226 To create an instance of the twitter.Api class, with no authentication:
1228 >>> import twitter
1229 >>> api = twitter.Api()
1231 To fetch the most recently posted public twitter status messages:
1233 >>> statuses = api.GetPublicTimeline()
1234 >>> print [s.user.name for s in statuses]
1235 [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #...
1237 To fetch a single user's public status messages, where "user" is either
1238 a Twitter "short name" or their user id.
1240 >>> statuses = api.GetUserTimeline(user)
1241 >>> print [s.text for s in statuses]
1243 To use authentication, instantiate the twitter.Api class with a
1244 username and password:
1246 >>> api = twitter.Api(username='twitter user', password='twitter pass')
1248 To fetch your friends (after being authenticated):
1250 >>> users = api.GetFriends()
1251 >>> print [u.name for u in users]
1253 To post a twitter status message (after being authenticated):
1255 >>> status = api.PostUpdate('I love python-twitter!')
1256 >>> print status.text
1257 I love python-twitter!
1259 There are many other methods, including:
1261 >>> api.PostUpdates(status)
1262 >>> api.PostDirectMessage(user, text)
1263 >>> api.GetUser(user)
1264 >>> api.GetReplies()
1265 >>> api.GetUserTimeline(user)
1266 >>> api.GetStatus(id)
1267 >>> api.DestroyStatus(id)
1268 >>> api.GetFriendsTimeline(user)
1269 >>> api.GetFriends(user)
1270 >>> api.GetFollowers()
1271 >>> api.GetFeatured()
1272 >>> api.GetDirectMessages()
1273 >>> api.PostDirectMessage(user, text)
1274 >>> api.DestroyDirectMessage(id)
1275 >>> api.DestroyFriendship(user)
1276 >>> api.CreateFriendship(user)
1277 >>> api.GetUserByEmail(email)
1280 DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute
1282 _API_REALM = 'Twitter API'
1284 def __init__(self,
1285 username=None,
1286 password=None,
1287 input_encoding=None,
1288 request_headers=None,
1289 twitterserver='twitter.com'):
1290 '''Instantiate a new twitter.Api object.
1292 Args:
1293 username: The username of the twitter account. [optional]
1294 password: The password for the twitter account. [optional]
1295 input_encoding: The encoding used to encode input strings. [optional]
1296 request_header: A dictionary of additional HTTP request headers. [optional]
1297 twitterserver: you can use twitter.com or identi.ca [optional]
1299 self._twitterserver = twitterserver
1300 self._cache = _FileCache()
1301 self._urllib = urllib2
1302 self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
1303 self._InitializeRequestHeaders(request_headers)
1304 self._InitializeUserAgent()
1305 self._InitializeDefaultParameters()
1306 self._input_encoding = input_encoding
1307 self.SetCredentials(username, password)
1309 def GetPublicTimeline(self, since_id=None):
1310 '''Fetch the sequnce of public twitter.Status message for all users.
1312 Args:
1313 since_id:
1314 Returns only public statuses with an ID greater than (that is,
1315 more recent than) the specified ID. [Optional]
1317 Returns:
1318 An sequence of twitter.Status instances, one for each message
1320 parameters = {}
1321 if since_id:
1322 parameters['since_id'] = since_id
1323 url = 'http://%s/statuses/public_timeline.json' % self._twitterserver
1324 json = self._FetchUrl(url, parameters=parameters)
1325 data = simplejson.loads(json)
1326 self._CheckForTwitterError(data)
1327 return [Status.NewFromJsonDict(x) for x in data]
1329 def GetFriendsTimeline(self,
1330 user=None,
1331 count=None,
1332 since=None,
1333 since_id=None):
1334 '''Fetch the sequence of twitter.Status messages for a user's friends
1336 The twitter.Api instance must be authenticated if the user is private.
1338 Args:
1339 user:
1340 Specifies the ID or screen name of the user for whom to return
1341 the friends_timeline. If unspecified, the username and password
1342 must be set in the twitter.Api instance. [Optional]
1343 count:
1344 Specifies the number of statuses to retrieve. May not be
1345 greater than 200. [Optional]
1346 since:
1347 Narrows the returned results to just those statuses created
1348 after the specified HTTP-formatted date. [Optional]
1349 since_id:
1350 Returns only public statuses with an ID greater than (that is,
1351 more recent than) the specified ID. [Optional]
1353 Returns:
1354 A sequence of twitter.Status instances, one for each message
1356 if user:
1357 url = 'http://%s/statuses/friends_timeline/%s.json' % (self._twitterserver,user)
1358 elif not user and not self._username:
1359 raise TwitterError("User must be specified if API is not authenticated.")
1360 else:
1361 url = 'http://%s/statuses/friends_timeline.json' % self._twitterserver
1362 parameters = {}
1363 if count is not None:
1364 try:
1365 if int(count) > 200:
1366 raise TwitterError("'count' may not be greater than 200")
1367 except ValueError:
1368 raise TwitterError("'count' must be an integer")
1369 parameters['count'] = count
1370 if since:
1371 parameters['since'] = since
1372 if since_id:
1373 parameters['since_id'] = since_id
1374 json = self._FetchUrl(url, parameters=parameters)
1375 data = simplejson.loads(json)
1376 self._CheckForTwitterError(data)
1377 return [Status.NewFromJsonDict(x) for x in data]
1379 def GetUserTimeline(self, user=None, count=None, since=None, since_id=None):
1380 '''Fetch the sequence of public twitter.Status messages for a single user.
1382 The twitter.Api instance must be authenticated if the user is private.
1384 Args:
1385 user:
1386 either the username (short_name) or id of the user to retrieve. If
1387 not specified, then the current authenticated user is used. [optional]
1388 count: the number of status messages to retrieve [optional]
1389 since:
1390 Narrows the returned results to just those statuses created
1391 after the specified HTTP-formatted date. [optional]
1392 since_id:
1393 Returns only public statuses with an ID greater than (that is,
1394 more recent than) the specified ID. [Optional]
1396 Returns:
1397 A sequence of twitter.Status instances, one for each message up to count
1399 try:
1400 if count:
1401 int(count)
1402 except:
1403 raise TwitterError("Count must be an integer")
1404 parameters = {}
1405 if count:
1406 parameters['count'] = count
1407 if since:
1408 parameters['since'] = since
1409 if since_id:
1410 parameters['since_id'] = since_id
1411 if user:
1412 url = 'http://%s/statuses/user_timeline/%s.json' % (self._twitterserver,user)
1413 elif not user and not self._username:
1414 raise TwitterError("User must be specified if API is not authenticated.")
1415 else:
1416 url = 'http://%s/statuses/user_timeline.json' % self._twitterserver
1417 json = self._FetchUrl(url, parameters=parameters)
1418 data = simplejson.loads(json)
1419 self._CheckForTwitterError(data)
1420 return [Status.NewFromJsonDict(x) for x in data]
1422 def GetStatus(self, id):
1423 '''Returns a single status message.
1425 The twitter.Api instance must be authenticated if the status message is private.
1427 Args:
1428 id: The numerical ID of the status you're trying to retrieve.
1430 Returns:
1431 A twitter.Status instance representing that status message
1433 try:
1434 if id:
1435 long(id)
1436 except:
1437 raise TwitterError("id must be an long integer")
1438 url = 'http://%s/statuses/show/%s.json' % (self._twitterserver,id)
1439 json = self._FetchUrl(url)
1440 data = simplejson.loads(json)
1441 self._CheckForTwitterError(data)
1442 return Status.NewFromJsonDict(data)
1444 def DestroyStatus(self, id):
1445 '''Destroys the status specified by the required ID parameter.
1447 The twitter.Api instance must be authenticated and thee
1448 authenticating user must be the author of the specified status.
1450 Args:
1451 id: The numerical ID of the status you're trying to destroy.
1453 Returns:
1454 A twitter.Status instance representing the destroyed status message
1456 try:
1457 if id:
1458 long(id)
1459 except:
1460 raise TwitterError("id must be an integer")
1461 url = 'http://%s/statuses/destroy/%s.json' % (self._twitterserver,id)
1462 json = self._FetchUrl(url, post_data={})
1463 data = simplejson.loads(json)
1464 self._CheckForTwitterError(data)
1465 return Status.NewFromJsonDict(data)
1467 def PostUpdate(self, status, in_reply_to_status_id=None):
1468 '''Post a twitter status message from the authenticated user.
1470 The twitter.Api instance must be authenticated.
1472 Args:
1473 status:
1474 The message text to be posted. Must be less than or equal to
1475 140 characters.
1476 in_reply_to_status_id:
1477 The ID of an existing status that the status to be posted is
1478 in reply to. This implicitly sets the in_reply_to_user_id
1479 attribute of the resulting status to the user ID of the
1480 message being replied to. Invalid/missing status IDs will be
1481 ignored. [Optional]
1482 Returns:
1483 A twitter.Status instance representing the message posted.
1485 if not self._username:
1486 raise TwitterError("The twitter.Api instance must be authenticated.")
1488 url = 'http://%s/statuses/update.json' % self._twitterserver
1490 if len(status) > CHARACTER_LIMIT:
1491 raise TwitterError("Text must be less than or equal to %d characters. "
1492 "Consider using PostUpdates." % CHARACTER_LIMIT)
1494 data = {'status': status}
1495 if in_reply_to_status_id:
1496 data['in_reply_to_status_id'] = in_reply_to_status_id
1497 json = self._FetchUrl(url, post_data=data)
1498 data = simplejson.loads(json)
1499 self._CheckForTwitterError(data)
1500 return Status.NewFromJsonDict(data)
1502 def PostUpdates(self, status, continuation=None, **kwargs):
1503 '''Post one or more twitter status messages from the authenticated user.
1505 Unlike api.PostUpdate, this method will post multiple status updates
1506 if the message is longer than 140 characters.
1508 The twitter.Api instance must be authenticated.
1510 Args:
1511 status:
1512 The message text to be posted. May be longer than 140 characters.
1513 continuation:
1514 The character string, if any, to be appended to all but the
1515 last message. Note that Twitter strips trailing '...' strings
1516 from messages. Consider using the unicode \u2026 character
1517 (horizontal ellipsis) instead. [Defaults to None]
1518 **kwargs:
1519 See api.PostUpdate for a list of accepted parameters.
1520 Returns:
1521 A of list twitter.Status instance representing the messages posted.
1523 results = list()
1524 if continuation is None:
1525 continuation = ''
1526 line_length = CHARACTER_LIMIT - len(continuation)
1527 lines = textwrap.wrap(status, line_length)
1528 for line in lines[0:-1]:
1529 results.append(self.PostUpdate(line + continuation, **kwargs))
1530 results.append(self.PostUpdate(lines[-1], **kwargs))
1531 return results
1533 def GetReplies(self, since=None, since_id=None, page=None):
1534 '''Get a sequence of status messages representing the 20 most recent
1535 replies (status updates prefixed with @username) to the authenticating
1536 user.
1538 Args:
1539 page:
1540 since:
1541 Narrows the returned results to just those statuses created
1542 after the specified HTTP-formatted date. [optional]
1543 since_id:
1544 Returns only public statuses with an ID greater than (that is,
1545 more recent than) the specified ID. [Optional]
1547 Returns:
1548 A sequence of twitter.Status instances, one for each reply to the user.
1550 url = 'http://%s/statuses/replies.json' % self._twitterserver
1551 if not self._username:
1552 raise TwitterError("The twitter.Api instance must be authenticated.")
1553 parameters = {}
1554 if since:
1555 parameters['since'] = since
1556 if since_id:
1557 parameters['since_id'] = since_id
1558 if page:
1559 parameters['page'] = page
1560 json = self._FetchUrl(url, parameters=parameters)
1561 data = simplejson.loads(json)
1562 self._CheckForTwitterError(data)
1563 return [Status.NewFromJsonDict(x) for x in data]
1565 def GetFriends(self, user=None, page=None):
1566 '''Fetch the sequence of twitter.User instances, one for each friend.
1568 Args:
1569 user: the username or id of the user whose friends you are fetching. If
1570 not specified, defaults to the authenticated user. [optional]
1572 The twitter.Api instance must be authenticated.
1574 Returns:
1575 A sequence of twitter.User instances, one for each friend
1577 if not self._username:
1578 raise TwitterError("twitter.Api instance must be authenticated")
1579 if user:
1580 url = 'http://%s/statuses/friends/%s.json' % (self._twitterserver,user)
1581 else:
1582 url = 'http://%s/statuses/friends.json' % self._twitterserver
1583 parameters = {}
1584 if page:
1585 parameters['page'] = page
1586 json = self._FetchUrl(url, parameters=parameters)
1587 data = simplejson.loads(json)
1588 self._CheckForTwitterError(data)
1589 return [User.NewFromJsonDict(x) for x in data]
1591 def GetFollowers(self, page=None):
1592 '''Fetch the sequence of twitter.User instances, one for each follower
1594 The twitter.Api instance must be authenticated.
1596 Returns:
1597 A sequence of twitter.User instances, one for each follower
1599 if not self._username:
1600 raise TwitterError("twitter.Api instance must be authenticated")
1601 url = 'http://%s/statuses/followers.json' % self._twitterserver
1602 parameters = {}
1603 if page:
1604 parameters['page'] = page
1605 json = self._FetchUrl(url, parameters=parameters)
1606 data = simplejson.loads(json)
1607 self._CheckForTwitterError(data)
1608 return [User.NewFromJsonDict(x) for x in data]
1610 def GetFeatured(self):
1611 '''Fetch the sequence of twitter.User instances featured on twitter.com
1613 The twitter.Api instance must be authenticated.
1615 Returns:
1616 A sequence of twitter.User instances
1618 url = 'http://%s/statuses/featured.json' % self._twitterserver
1619 json = self._FetchUrl(url)
1620 data = simplejson.loads(json)
1621 self._CheckForTwitterError(data)
1622 return [User.NewFromJsonDict(x) for x in data]
1624 def GetUser(self, user):
1625 '''Returns a single user.
1627 The twitter.Api instance must be authenticated.
1629 Args:
1630 user: The username or id of the user to retrieve.
1632 Returns:
1633 A twitter.User instance representing that user
1635 url = 'http://%s/users/show/%s.json' % (self._twitterserver,user)
1636 json = self._FetchUrl(url)
1637 data = simplejson.loads(json)
1638 self._CheckForTwitterError(data)
1639 return User.NewFromJsonDict(data)
1641 def GetDirectMessages(self, since=None, since_id=None, page=None):
1642 '''Returns a list of the direct messages sent to the authenticating user.
1644 The twitter.Api instance must be authenticated.
1646 Args:
1647 since:
1648 Narrows the returned results to just those statuses created
1649 after the specified HTTP-formatted date. [optional]
1650 since_id:
1651 Returns only public statuses with an ID greater than (that is,
1652 more recent than) the specified ID. [Optional]
1654 Returns:
1655 A sequence of twitter.DirectMessage instances
1657 url = 'http://%s/direct_messages.json' % self._twitterserver
1658 if not self._username:
1659 raise TwitterError("The twitter.Api instance must be authenticated.")
1660 parameters = {}
1661 if since:
1662 parameters['since'] = since
1663 if since_id:
1664 parameters['since_id'] = since_id
1665 if page:
1666 parameters['page'] = page
1667 json = self._FetchUrl(url, parameters=parameters)
1668 data = simplejson.loads(json)
1669 self._CheckForTwitterError(data)
1670 return [DirectMessage.NewFromJsonDict(x) for x in data]
1672 def PostDirectMessage(self, user, text):
1673 '''Post a twitter direct message from the authenticated user
1675 The twitter.Api instance must be authenticated.
1677 Args:
1678 user: The ID or screen name of the recipient user.
1679 text: The message text to be posted. Must be less than 140 characters.
1681 Returns:
1682 A twitter.DirectMessage instance representing the message posted
1684 if not self._username:
1685 raise TwitterError("The twitter.Api instance must be authenticated.")
1686 url = 'http://%s/direct_messages/new.json' % self._twitterserver
1687 data = {'text': text, 'user': user}
1688 json = self._FetchUrl(url, post_data=data)
1689 data = simplejson.loads(json)
1690 self._CheckForTwitterError(data)
1691 return DirectMessage.NewFromJsonDict(data)
1693 def DestroyDirectMessage(self, id):
1694 '''Destroys the direct message specified in the required ID parameter.
1696 The twitter.Api instance must be authenticated, and the
1697 authenticating user must be the recipient of the specified direct
1698 message.
1700 Args:
1701 id: The id of the direct message to be destroyed
1703 Returns:
1704 A twitter.DirectMessage instance representing the message destroyed
1706 url = 'http://%s/direct_messages/destroy/%s.json' % (self._twitterserver,id)
1707 json = self._FetchUrl(url, post_data={})
1708 data = simplejson.loads(json)
1709 self._CheckForTwitterError(data)
1710 return DirectMessage.NewFromJsonDict(data)
1712 def CreateFriendship(self, user):
1713 '''Befriends the user specified in the user parameter as the authenticating user.
1715 The twitter.Api instance must be authenticated.
1717 Args:
1718 The ID or screen name of the user to befriend.
1719 Returns:
1720 A twitter.User instance representing the befriended user.
1722 url = 'http://%s/friendships/create/%s.json' % (self._twitterserver,user)
1723 json = self._FetchUrl(url, post_data={})
1724 data = simplejson.loads(json)
1725 self._CheckForTwitterError(data)
1726 return User.NewFromJsonDict(data)
1728 def DestroyFriendship(self, user):
1729 '''Discontinues friendship with the user specified in the user parameter.
1731 The twitter.Api instance must be authenticated.
1733 Args:
1734 The ID or screen name of the user with whom to discontinue friendship.
1735 Returns:
1736 A twitter.User instance representing the discontinued friend.
1738 url = 'http://%s/friendships/destroy/%s.json' % (self._twitterserver,user)
1739 json = self._FetchUrl(url, post_data={})
1740 data = simplejson.loads(json)
1741 self._CheckForTwitterError(data)
1742 return User.NewFromJsonDict(data)
1744 def CreateFavorite(self, status):
1745 '''Favorites the status specified in the status parameter as the authenticating user.
1746 Returns the favorite status when successful.
1748 The twitter.Api instance must be authenticated.
1750 Args:
1751 The twitter.Status instance to mark as a favorite.
1752 Returns:
1753 A twitter.Status instance representing the newly-marked favorite.
1755 url = 'http://%s/favorites/create/%s.json' % (self._twitterserver, status.id)
1756 json = self._FetchUrl(url, post_data={})
1757 data = simplejson.loads(json)
1758 self._CheckForTwitterError(data)
1759 return Status.NewFromJsonDict(data)
1761 def DestroyFavorite(self, status):
1762 '''Un-favorites the status specified in the ID parameter as the authenticating user.
1763 Returns the un-favorited status in the requested format when successful.
1765 The twitter.Api instance must be authenticated.
1767 Args:
1768 The twitter.Status to unmark as a favorite.
1769 Returns:
1770 A twitter.Status instance representing the newly-unmarked favorite.
1772 url = 'http://%s/favorites/destroy/%s.json' % (self._twitterserver, status.id)
1773 json = self._FetchUrl(url, post_data={})
1774 data = simplejson.loads(json)
1775 self._CheckForTwitterError(data)
1776 return Status.NewFromJsonDict(data)
1778 def GetUserByEmail(self, email):
1779 '''Returns a single user by email address.
1781 Args:
1782 email: The email of the user to retrieve.
1783 Returns:
1784 A twitter.User instance representing that user
1786 url = 'http://%s/users/show.json?email=%s' % (self._twitterserver, email)
1787 json = self._FetchUrl(url)
1788 data = simplejson.loads(json)
1789 self._CheckForTwitterError(data)
1790 return User.NewFromJsonDict(data)
1792 def SetCredentials(self, username, password):
1793 '''Set the username and password for this instance
1795 Args:
1796 username: The twitter username.
1797 password: The twitter password.
1799 self._username = username
1800 self._password = password
1802 def ClearCredentials(self):
1803 '''Clear the username and password for this instance
1805 self._username = None
1806 self._password = None
1808 def SetCache(self, cache):
1809 '''Override the default cache. Set to None to prevent caching.
1811 Args:
1812 cache: an instance that supports the same API as the twitter._FileCache
1814 self._cache = cache
1816 def SetUrllib(self, urllib):
1817 '''Override the default urllib implementation.
1819 Args:
1820 urllib: an instance that supports the same API as the urllib2 module
1822 self._urllib = urllib
1824 def SetCacheTimeout(self, cache_timeout):
1825 '''Override the default cache timeout.
1827 Args:
1828 cache_timeout: time, in seconds, that responses should be reused.
1830 self._cache_timeout = cache_timeout
1832 def SetUserAgent(self, user_agent):
1833 '''Override the default user agent
1835 Args:
1836 user_agent: a string that should be send to the server as the User-agent
1838 self._request_headers['User-Agent'] = user_agent
1840 def SetXTwitterHeaders(self, client, url, version):
1841 '''Set the X-Twitter HTTP headers that will be sent to the server.
1843 Args:
1844 client:
1845 The client name as a string. Will be sent to the server as
1846 the 'X-Twitter-Client' header.
1847 url:
1848 The URL of the meta.xml as a string. Will be sent to the server
1849 as the 'X-Twitter-Client-URL' header.
1850 version:
1851 The client version as a string. Will be sent to the server
1852 as the 'X-Twitter-Client-Version' header.
1854 self._request_headers['X-Twitter-Client'] = client
1855 self._request_headers['X-Twitter-Client-URL'] = url
1856 self._request_headers['X-Twitter-Client-Version'] = version
1858 def SetSource(self, source):
1859 '''Suggest the "from source" value to be displayed on the Twitter web site.
1861 The value of the 'source' parameter must be first recognized by
1862 the Twitter server. New source values are authorized on a case by
1863 case basis by the Twitter development team.
1865 Args:
1866 source:
1867 The source name as a string. Will be sent to the server as
1868 the 'source' parameter.
1870 self._default_params['source'] = source
1872 def _BuildUrl(self, url, path_elements=None, extra_params=None):
1873 # Break url into consituent parts
1874 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
1876 # Add any additional path elements to the path
1877 if path_elements:
1878 # Filter out the path elements that have a value of None
1879 p = [i for i in path_elements if i]
1880 if not path.endswith('/'):
1881 path += '/'
1882 path += '/'.join(p)
1884 # Add any additional query parameters to the query string
1885 if extra_params and len(extra_params) > 0:
1886 extra_query = self._EncodeParameters(extra_params)
1887 # Add it to the existing query
1888 if query:
1889 query += '&' + extra_query
1890 else:
1891 query = extra_query
1893 # Return the rebuilt URL
1894 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
1896 def _InitializeRequestHeaders(self, request_headers):
1897 if request_headers:
1898 self._request_headers = request_headers
1899 else:
1900 self._request_headers = {}
1902 def _InitializeUserAgent(self):
1903 user_agent = 'Python-urllib/%s (python-twitter/%s)' % \
1904 (self._urllib.__version__, __version__)
1905 self.SetUserAgent(user_agent)
1907 def _InitializeDefaultParameters(self):
1908 self._default_params = {}
1910 def _AddAuthorizationHeader(self, username, password):
1911 if username and password:
1912 basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1]
1913 self._request_headers['Authorization'] = 'Basic %s' % basic_auth
1915 def _RemoveAuthorizationHeader(self):
1916 if self._request_headers and 'Authorization' in self._request_headers:
1917 del self._request_headers['Authorization']
1919 def _GetOpener(self, url, username=None, password=None):
1920 if username and password:
1921 self._AddAuthorizationHeader(username, password)
1922 handler = self._urllib.HTTPBasicAuthHandler()
1923 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
1924 handler.add_password(Api._API_REALM, netloc, username, password)
1925 opener = self._urllib.build_opener(handler)
1926 else:
1927 opener = self._urllib.build_opener()
1928 opener.addheaders = self._request_headers.items()
1929 return opener
1931 def _Encode(self, s):
1932 if self._input_encoding:
1933 return unicode(s, self._input_encoding).encode('utf-8')
1934 else:
1935 return unicode(s).encode('utf-8')
1937 def _EncodeParameters(self, parameters):
1938 '''Return a string in key=value&key=value form
1940 Values of None are not included in the output string.
1942 Args:
1943 parameters:
1944 A dict of (key, value) tuples, where value is encoded as
1945 specified by self._encoding
1946 Returns:
1947 A URL-encoded string in "key=value&key=value" form
1949 if parameters is None:
1950 return None
1951 else:
1952 return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
1954 def _EncodePostData(self, post_data):
1955 '''Return a string in key=value&key=value form
1957 Values are assumed to be encoded in the format specified by self._encoding,
1958 and are subsequently URL encoded.
1960 Args:
1961 post_data:
1962 A dict of (key, value) tuples, where value is encoded as
1963 specified by self._encoding
1964 Returns:
1965 A URL-encoded string in "key=value&key=value" form
1967 if post_data is None:
1968 return None
1969 else:
1970 return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
1972 def _CheckForTwitterError(self, data):
1973 """Raises a TwitterError if twitter returns an error message.
1975 Args:
1976 data: A python dict created from the Twitter json response
1977 Raises:
1978 TwitterError wrapping the twitter error message if one exists.
1980 # Twitter errors are relatively unlikely, so it is faster
1981 # to check first, rather than try and catch the exception
1982 if 'error' in data:
1983 raise TwitterError(data['error'])
1985 def _FetchUrl(self,
1986 url,
1987 post_data=None,
1988 parameters=None,
1989 no_cache=None):
1990 '''Fetch a URL, optionally caching for a specified time.
1992 Args:
1993 url: The URL to retrieve
1994 post_data:
1995 A dict of (str, unicode) key/value pairs. If set, POST will be used.
1996 parameters:
1997 A dict whose key/value pairs should encoded and added
1998 to the query string. [OPTIONAL]
1999 no_cache: If true, overrides the cache on the current request
2001 Returns:
2002 A string containing the body of the response.
2004 # Build the extra parameters dict
2005 extra_params = {}
2006 if self._default_params:
2007 extra_params.update(self._default_params)
2008 if parameters:
2009 extra_params.update(parameters)
2011 # Add key/value parameters to the query string of the url
2012 url = self._BuildUrl(url, extra_params=extra_params)
2014 # Get a url opener that can handle basic auth
2015 opener = self._GetOpener(url, username=self._username, password=self._password)
2017 encoded_post_data = self._EncodePostData(post_data)
2019 # Open and return the URL immediately if we're not going to cache
2020 if encoded_post_data or no_cache or not self._cache or not self._cache_timeout:
2021 url_data = opener.open(url, encoded_post_data).read()
2022 opener.close()
2023 else:
2024 # Unique keys are a combination of the url and the username
2025 if self._username:
2026 key = self._username + ':' + url
2027 else:
2028 key = url
2030 # See if it has been cached before
2031 last_cached = self._cache.GetCachedTime(key)
2033 # If the cached version is outdated then fetch another and store it
2034 if not last_cached or time.time() >= last_cached + self._cache_timeout:
2035 url_data = opener.open(url, encoded_post_data).read()
2036 opener.close()
2037 self._cache.Set(key, url_data)
2038 else:
2039 url_data = self._cache.Get(key)
2041 # Always return the latest version
2042 return url_data
2045 class _FileCacheError(Exception):
2046 '''Base exception class for FileCache related errors'''
2048 class _FileCache(object):
2050 DEPTH = 3
2052 def __init__(self,root_directory=None):
2053 self._InitializeRootDirectory(root_directory)
2055 def Get(self,key):
2056 path = self._GetPath(key)
2057 if os.path.exists(path):
2058 return open(path).read()
2059 else:
2060 return None
2062 def Set(self,key,data):
2063 path = self._GetPath(key)
2064 directory = os.path.dirname(path)
2065 if not os.path.exists(directory):
2066 os.makedirs(directory)
2067 if not os.path.isdir(directory):
2068 raise _FileCacheError('%s exists but is not a directory' % directory)
2069 temp_fd, temp_path = tempfile.mkstemp()
2070 temp_fp = os.fdopen(temp_fd, 'w')
2071 temp_fp.write(data)
2072 temp_fp.close()
2073 if not path.startswith(self._root_directory):
2074 raise _FileCacheError('%s does not appear to live under %s' %
2075 (path, self._root_directory))
2076 if os.path.exists(path):
2077 os.remove(path)
2078 os.rename(temp_path, path)
2080 def Remove(self,key):
2081 path = self._GetPath(key)
2082 if not path.startswith(self._root_directory):
2083 raise _FileCacheError('%s does not appear to live under %s' %
2084 (path, self._root_directory ))
2085 if os.path.exists(path):
2086 os.remove(path)
2088 def GetCachedTime(self,key):
2089 path = self._GetPath(key)
2090 if os.path.exists(path):
2091 return os.path.getmtime(path)
2092 else:
2093 return None
2095 def _GetUsername(self):
2096 '''Attempt to find the username in a cross-platform fashion.'''
2097 try:
2098 return os.getenv('USER') or \
2099 os.getenv('LOGNAME') or \
2100 os.getenv('USERNAME') or \
2101 os.getlogin() or \
2102 'nobody'
2103 except (IOError, OSError), e:
2104 return 'nobody'
2106 def _GetTmpCachePath(self):
2107 username = self._GetUsername()
2108 cache_directory = 'python.cache_' + username
2109 return os.path.join(tempfile.gettempdir(), cache_directory)
2111 def _InitializeRootDirectory(self, root_directory):
2112 if not root_directory:
2113 root_directory = self._GetTmpCachePath()
2114 root_directory = os.path.abspath(root_directory)
2115 if not os.path.exists(root_directory):
2116 os.mkdir(root_directory)
2117 if not os.path.isdir(root_directory):
2118 raise _FileCacheError('%s exists but is not a directory' %
2119 root_directory)
2120 self._root_directory = root_directory
2122 def _GetPath(self,key):
2123 try:
2124 hashed_key = md5(key).hexdigest()
2125 except TypeError:
2126 hashed_key = md5.new(key).hexdigest()
2128 return os.path.join(self._root_directory,
2129 self._GetPrefix(hashed_key),
2130 hashed_key)
2132 def _GetPrefix(self,hashed_key):
2133 return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])