GTG

Merge lp:~gtg-user/gtg/identica-backend into lp:~gtg/gtg/old-trunk

Proposed by Luca Invernizzi
Status: Merged
Merged at revision: 880
Proposed branch: lp:~gtg-user/gtg/identica-backend
Merge into: lp:~gtg/gtg/old-trunk
Diff against target: 2839 lines (+2827/-0)
2 files modified
GTG/backends/backend_identica.py (+280/-0)
GTG/backends/twitter.py (+2547/-0)
To merge this branch: bzr merge lp:~gtg-user/gtg/identica-backend
Reviewer Review Type Date Requested Status
Gtg developers Pending
Review via email: mp+33479@code.launchpad.net

Description of the change

identi.ca backend, which follows the lines of the twitter backend, but does not authenticate through oauth (and uses a different library).

Note: i've added a branch with all my merge requests merged. https://code.edge.launchpad.net/~gtg-user/gtg/all_the_backends_merge_requests/

To post a comment you must log in.
lp:~gtg-user/gtg/identica-backend updated
876. By Luca Invernizzi

removed print statements

877. By Luca Invernizzi

merge w/ trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'GTG/backends/backend_identica.py'
--- GTG/backends/backend_identica.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_identica.py 2010-08-25 16:29:48 +0000
@@ -0,0 +1,280 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20'''
21Identi.ca backend: imports direct messages, replies and/or the user timeline.
22'''
23
24import os
25import re
26import sys
27import uuid
28import urllib2
29
30from GTG import _
31from GTG.backends.genericbackend import GenericBackend
32from GTG.core import CoreConfig
33from GTG.backends.backendsignals import BackendSignals
34from GTG.backends.periodicimportbackend import PeriodicImportBackend
35from GTG.backends.syncengine import SyncEngine
36from GTG.tools.logger import Log
37
38#The Ubuntu version of python twitter is not updated:
39# it does not have identi.ca support. Meanwhile, we ship the right version
40# with our code.
41import GTG.backends.twitter as twitter
42
43
44
45class Backend(PeriodicImportBackend):
46 '''
47 Identi.ca backend: imports direct messages, replies and/or the user
48 timeline.
49 '''
50
51
52 _general_description = { \
53 GenericBackend.BACKEND_NAME: "backend_identica", \
54 GenericBackend.BACKEND_HUMAN_NAME: _("Identi.ca"), \
55 GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
56 GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_IMPORT, \
57 GenericBackend.BACKEND_DESCRIPTION: \
58 _("Imports your identi.ca messages into your GTG " + \
59 "tasks. You can choose to either import all your " + \
60 "messages or just those with a set of hash tags. \n" + \
61 "The message will be interpreted following this" + \
62 " format: \n" + \
63 "<b>my task title, task description #tag @anothertag</b>\n" + \
64 " Tags can be anywhere in the message"),\
65 }
66
67 base_url = "http://identi.ca/api/"
68
69 _static_parameters = { \
70 "username": { \
71 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING, \
72 GenericBackend.PARAM_DEFAULT_VALUE: "", },
73 "password": { \
74 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_PASSWORD, \
75 GenericBackend.PARAM_DEFAULT_VALUE: "", },
76 "period": { \
77 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_INT, \
78 GenericBackend.PARAM_DEFAULT_VALUE: 2, },
79 "import-tags": { \
80 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
81 GenericBackend.PARAM_DEFAULT_VALUE: ["#todo"], },
82 "import-from-replies": { \
83 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_BOOL, \
84 GenericBackend.PARAM_DEFAULT_VALUE: False, },
85 "import-from-my-tweets": { \
86 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_BOOL, \
87 GenericBackend.PARAM_DEFAULT_VALUE: False, },
88 "import-from-direct-messages": { \
89 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_BOOL, \
90 GenericBackend.PARAM_DEFAULT_VALUE: True, },
91 }
92
93 def __init__(self, parameters):
94 '''
95 See GenericBackend for an explanation of this function.
96 Re-loads the saved state of the synchronization
97 '''
98 super(Backend, self).__init__(parameters)
99 #loading the list of already imported tasks
100 self.data_path = os.path.join('backends/identica/', "tasks_dict-%s" %\
101 self.get_id())
102 self.sync_engine = self._load_pickled_file(self.data_path, \
103 SyncEngine())
104
105 def save_state(self):
106 '''
107 See GenericBackend for an explanation of this function.
108 Saves the state of the synchronization.
109 '''
110 self._store_pickled_file(self.data_path, self.sync_engine)
111
112 def do_periodic_import(self):
113 '''
114 See GenericBackend for an explanation of this function.
115 '''
116 #we need to authenticate only to see the direct messages or the replies
117 # (why the replies? Don't know. But python-twitter requires that)
118 # (invernizzi)
119 with self.controlled_execution(self._parameters['username'],\
120 self._parameters['password'], \
121 self.base_url, \
122 self) as api:
123 #select what to import
124 tweets_to_import = []
125 if self._parameters["import-from-direct-messages"]:
126 tweets_to_import += api.GetDirectMessages()
127 if self._parameters["import-from-my-tweets"]:
128 tweets_to_import += \
129 api.GetUserTimeline(self._parameters["username"])
130 if self._parameters["import-from-replies"]:
131 tweets_to_import += \
132 api.GetReplies(self._parameters["username"])
133 #do the import
134 for tweet in tweets_to_import:
135 self._process_tweet(tweet)
136
137 def _process_tweet(self, tweet):
138 '''
139 Given a tweet, checks if a task representing it must be
140 created in GTG and, if so, it creates it.
141
142 @param tweet: a tweet.
143 '''
144 self.cancellation_point()
145 tweet_id = str(tweet.GetId())
146 is_syncable = self._is_tweet_syncable(tweet)
147 #the "lambda" is because we don't consider tweets deletion (to be
148 # faster)
149 action, tid = self.sync_engine.analyze_remote_id(\
150 tweet_id, \
151 self.datastore.has_task, \
152 lambda tweet_id: True, \
153 is_syncable)
154 Log.debug("processing tweet (%s, %s)" % (action, is_syncable))
155
156 self.cancellation_point()
157 if action == None or action == SyncEngine.UPDATE:
158 return
159
160 elif action == SyncEngine.ADD:
161 tid = str(uuid.uuid4())
162 task = self.datastore.task_factory(tid)
163 self._populate_task(task, tweet)
164 #we care only to add tweets and if the list of tags which must be
165 #imported changes (lost-syncability can happen). Thus, we don't
166 # care about SyncMeme(s)
167 self.sync_engine.record_relationship(local_id = tid,\
168 remote_id = tweet_id, \
169 meme = None)
170 self.datastore.push_task(task)
171
172 elif action == SyncEngine.LOST_SYNCABILITY:
173 self.sync_engine.break_relationship(remote_id = tweet_id)
174 self.datastore.request_task_deletion(tid)
175
176 self.save_state()
177
178 def _populate_task(self, task, message):
179 '''
180 Given a twitter message and a GTG task, fills the task with the content
181 of the message
182 '''
183 try:
184 #this works only for some messages
185 task.add_tag("@" + message.GetSenderScreenName())
186 except:
187 pass
188 text = message.GetText()
189
190 #convert #hastags to @tags
191 matches = re.finditer("(?<![^|\s])(#\w+)", text)
192 for g in matches:
193 text = text[:g.start()] + '@' + text[g.start() + 1:]
194 #add tags objects (it's not enough to have @tag in the text to add a
195 # tag
196 for tag in self._extract_tags_from_text(text):
197 task.add_tag(tag)
198
199 split_text = text.split(",", 1)
200 task.set_title(split_text[0])
201 if len(split_text) > 1:
202 task.set_text(split_text[1])
203
204 task.add_remote_id(self.get_id(), str(message.GetId()))
205
206 def _is_tweet_syncable(self, tweet):
207 '''
208 Returns True if the given tweet matches the user-specified tags to be
209 synced
210
211 @param tweet: a tweet
212 '''
213 if CoreConfig.ALLTASKS_TAG in self._parameters["import-tags"]:
214 return True
215 else:
216 tags = set(Backend._extract_tags_from_text(tweet.GetText()))
217 return tags.intersection(set(self._parameters["import-tags"])) \
218 != set()
219
220 @staticmethod
221 def _extract_tags_from_text(text):
222 '''
223 Given a string, returns a list of @tags and #hashtags
224 '''
225 return list(re.findall(r'(?:^|[\s])((?:#|@)\w+)', text))
226
227###############################################################################
228### AUTHENTICATION ############################################################
229###############################################################################
230
231 class controlled_execution(object):
232 '''
233 This class performs the login to identica and execute the appropriate
234 response if something goes wrong during authentication or at network
235 level
236 '''
237
238 def __init__(self, username, password, base_url, backend):
239 '''
240 Sets the login parameters
241 '''
242 self.username = username
243 self.password = password
244 self.backend = backend
245 self.base_url = base_url
246
247 def __enter__(self):
248 '''
249 Logins to identica and returns the Api object
250 '''
251 return twitter.Api(self.username, self.password, \
252 base_url = self.base_url)
253
254 def __exit__(self, type, value, traceback):
255 '''
256 Analyzes the eventual exception risen during the connection to
257 identica
258 '''
259 if isinstance(value, urllib2.HTTPError):
260 if value.getcode() == 401:
261 self.signal_authentication_wrong()
262 if value.getcode() in [502, 404]:
263 self.signal_network_down()
264 elif isinstance(value, twitter.TwitterError):
265 self.signal_authentication_wrong()
266 elif isinstance(value, urllib2.URLError):
267 self.signal_network_down()
268 else:
269 return False
270 return True
271
272 def signal_authentication_wrong(self):
273 self.backend.quit(disable = True)
274 BackendSignals().backend_failed(self.backend.get_id(), \
275 BackendSignals.ERRNO_AUTHENTICATION)
276
277 def signal_network_down(self):
278 BackendSignals().backend_failed(self.backend.get_id(), \
279 BackendSignals.ERRNO_NETWORK)
280
0281
=== added file 'GTG/backends/twitter.py'
--- GTG/backends/twitter.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/twitter.py 2010-08-25 16:29:48 +0000
@@ -0,0 +1,2547 @@
1#!/usr/bin/python2.4
2#
3# Copyright 2007 Google Inc. All Rights Reserved.
4#
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
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
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.
16
17'''A library that provides a python interface to the Twitter API'''
18
19__author__ = 'dewitt@google.com'
20__version__ = '0.7-devel'
21
22
23import base64
24import calendar
25import httplib
26import os
27import rfc822
28import simplejson
29import sys
30import tempfile
31import textwrap
32import time
33import urllib
34import urllib2
35import urlparse
36import gzip
37import StringIO
38
39try:
40 from hashlib import md5
41except ImportError:
42 from md5 import md5
43
44
45CHARACTER_LIMIT = 140
46
47# A singleton representing a lazily instantiated FileCache.
48DEFAULT_CACHE = object()
49
50
51class TwitterError(Exception):
52 '''Base class for Twitter errors'''
53
54 @property
55 def message(self):
56 '''Returns the first argument used to construct this error.'''
57 return self.args[0]
58
59
60class Status(object):
61 '''A class representing the Status structure used by the twitter API.
62
63 The Status structure exposes the following properties:
64
65 status.created_at
66 status.created_at_in_seconds # read only
67 status.favorited
68 status.in_reply_to_screen_name
69 status.in_reply_to_user_id
70 status.in_reply_to_status_id
71 status.truncated
72 status.source
73 status.id
74 status.text
75 status.location
76 status.relative_created_at # read only
77 status.user
78 '''
79 def __init__(self,
80 created_at=None,
81 favorited=None,
82 id=None,
83 text=None,
84 location=None,
85 user=None,
86 in_reply_to_screen_name=None,
87 in_reply_to_user_id=None,
88 in_reply_to_status_id=None,
89 truncated=None,
90 source=None,
91 now=None):
92 '''An object to hold a Twitter status message.
93
94 This class is normally instantiated by the twitter.Api class and
95 returned in a sequence.
96
97 Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
98
99 Args:
100 created_at: The time this status message was posted
101 favorited: Whether this is a favorite of the authenticated user
102 id: The unique id of this status message
103 text: The text of this status message
104 location: the geolocation string associated with this message
105 relative_created_at:
106 A human readable string representing the posting time
107 user:
108 A twitter.User instance representing the person posting the message
109 now:
110 The current time, if the client choses to set it. Defaults to the
111 wall clock time.
112 '''
113 self.created_at = created_at
114 self.favorited = favorited
115 self.id = id
116 self.text = text
117 self.location = location
118 self.user = user
119 self.now = now
120 self.in_reply_to_screen_name = in_reply_to_screen_name
121 self.in_reply_to_user_id = in_reply_to_user_id
122 self.in_reply_to_status_id = in_reply_to_status_id
123 self.truncated = truncated
124 self.source = source
125
126 def GetCreatedAt(self):
127 '''Get the time this status message was posted.
128
129 Returns:
130 The time this status message was posted
131 '''
132 return self._created_at
133
134 def SetCreatedAt(self, created_at):
135 '''Set the time this status message was posted.
136
137 Args:
138 created_at: The time this status message was created
139 '''
140 self._created_at = created_at
141
142 created_at = property(GetCreatedAt, SetCreatedAt,
143 doc='The time this status message was posted.')
144
145 def GetCreatedAtInSeconds(self):
146 '''Get the time this status message was posted, in seconds since the epoch.
147
148 Returns:
149 The time this status message was posted, in seconds since the epoch.
150 '''
151 return calendar.timegm(rfc822.parsedate(self.created_at))
152
153 created_at_in_seconds = property(GetCreatedAtInSeconds,
154 doc="The time this status message was "
155 "posted, in seconds since the epoch")
156
157 def GetFavorited(self):
158 '''Get the favorited setting of this status message.
159
160 Returns:
161 True if this status message is favorited; False otherwise
162 '''
163 return self._favorited
164
165 def SetFavorited(self, favorited):
166 '''Set the favorited state of this status message.
167
168 Args:
169 favorited: boolean True/False favorited state of this status message
170 '''
171 self._favorited = favorited
172
173 favorited = property(GetFavorited, SetFavorited,
174 doc='The favorited state of this status message.')
175
176 def GetId(self):
177 '''Get the unique id of this status message.
178
179 Returns:
180 The unique id of this status message
181 '''
182 return self._id
183
184 def SetId(self, id):
185 '''Set the unique id of this status message.
186
187 Args:
188 id: The unique id of this status message
189 '''
190 self._id = id
191
192 id = property(GetId, SetId,
193 doc='The unique id of this status message.')
194
195 def GetInReplyToScreenName(self):
196 return self._in_reply_to_screen_name
197
198 def SetInReplyToScreenName(self, in_reply_to_screen_name):
199 self._in_reply_to_screen_name = in_reply_to_screen_name
200
201 in_reply_to_screen_name = property(GetInReplyToScreenName, SetInReplyToScreenName,
202 doc='')
203
204 def GetInReplyToUserId(self):
205 return self._in_reply_to_user_id
206
207 def SetInReplyToUserId(self, in_reply_to_user_id):
208 self._in_reply_to_user_id = in_reply_to_user_id
209
210 in_reply_to_user_id = property(GetInReplyToUserId, SetInReplyToUserId,
211 doc='')
212
213 def GetInReplyToStatusId(self):
214 return self._in_reply_to_status_id
215
216 def SetInReplyToStatusId(self, in_reply_to_status_id):
217 self._in_reply_to_status_id = in_reply_to_status_id
218
219 in_reply_to_status_id = property(GetInReplyToStatusId, SetInReplyToStatusId,
220 doc='')
221
222 def GetTruncated(self):
223 return self._truncated
224
225 def SetTruncated(self, truncated):
226 self._truncated = truncated
227
228 truncated = property(GetTruncated, SetTruncated,
229 doc='')
230
231 def GetSource(self):
232 return self._source
233
234 def SetSource(self, source):
235 self._source = source
236
237 source = property(GetSource, SetSource,
238 doc='')
239
240 def GetText(self):
241 '''Get the text of this status message.
242
243 Returns:
244 The text of this status message.
245 '''
246 return self._text
247
248 def SetText(self, text):
249 '''Set the text of this status message.
250
251 Args:
252 text: The text of this status message
253 '''
254 self._text = text
255
256 text = property(GetText, SetText,
257 doc='The text of this status message')
258
259 def GetLocation(self):
260 '''Get the geolocation associated with this status message
261
262 Returns:
263 The geolocation string of this status message.
264 '''
265 return self._location
266
267 def SetLocation(self, location):
268 '''Set the geolocation associated with this status message
269
270 Args:
271 location: The geolocation string of this status message
272 '''
273 self._location = location
274
275 location = property(GetLocation, SetLocation,
276 doc='The geolocation string of this status message')
277
278 def GetRelativeCreatedAt(self):
279 '''Get a human redable string representing the posting time
280
281 Returns:
282 A human readable string representing the posting time
283 '''
284 fudge = 1.25
285 delta = long(self.now) - long(self.created_at_in_seconds)
286
287 if delta < (1 * fudge):
288 return 'about a second ago'
289 elif delta < (60 * (1/fudge)):
290 return 'about %d seconds ago' % (delta)
291 elif delta < (60 * fudge):
292 return 'about a minute ago'
293 elif delta < (60 * 60 * (1/fudge)):
294 return 'about %d minutes ago' % (delta / 60)
295 elif delta < (60 * 60 * fudge) or delta / (60 * 60) == 1:
296 return 'about an hour ago'
297 elif delta < (60 * 60 * 24 * (1/fudge)):
298 return 'about %d hours ago' % (delta / (60 * 60))
299 elif delta < (60 * 60 * 24 * fudge) or delta / (60 * 60 * 24) == 1:
300 return 'about a day ago'
301 else:
302 return 'about %d days ago' % (delta / (60 * 60 * 24))
303
304 relative_created_at = property(GetRelativeCreatedAt,
305 doc='Get a human readable string representing'
306 'the posting time')
307
308 def GetUser(self):
309 '''Get a twitter.User reprenting the entity posting this status message.
310
311 Returns:
312 A twitter.User reprenting the entity posting this status message
313 '''
314 return self._user
315
316 def SetUser(self, user):
317 '''Set a twitter.User reprenting the entity posting this status message.
318
319 Args:
320 user: A twitter.User reprenting the entity posting this status message
321 '''
322 self._user = user
323
324 user = property(GetUser, SetUser,
325 doc='A twitter.User reprenting the entity posting this '
326 'status message')
327
328 def GetNow(self):
329 '''Get the wallclock time for this status message.
330
331 Used to calculate relative_created_at. Defaults to the time
332 the object was instantiated.
333
334 Returns:
335 Whatever the status instance believes the current time to be,
336 in seconds since the epoch.
337 '''
338 if self._now is None:
339 self._now = time.time()
340 return self._now
341
342 def SetNow(self, now):
343 '''Set the wallclock time for this status message.
344
345 Used to calculate relative_created_at. Defaults to the time
346 the object was instantiated.
347
348 Args:
349 now: The wallclock time for this instance.
350 '''
351 self._now = now
352
353 now = property(GetNow, SetNow,
354 doc='The wallclock time for this status instance.')
355
356
357 def __ne__(self, other):
358 return not self.__eq__(other)
359
360 def __eq__(self, other):
361 try:
362 return other and \
363 self.created_at == other.created_at and \
364 self.id == other.id and \
365 self.text == other.text and \
366 self.location == other.location and \
367 self.user == other.user and \
368 self.in_reply_to_screen_name == other.in_reply_to_screen_name and \
369 self.in_reply_to_user_id == other.in_reply_to_user_id and \
370 self.in_reply_to_status_id == other.in_reply_to_status_id and \
371 self.truncated == other.truncated and \
372 self.favorited == other.favorited and \
373 self.source == other.source
374 except AttributeError:
375 return False
376
377 def __str__(self):
378 '''A string representation of this twitter.Status instance.
379
380 The return value is the same as the JSON string representation.
381
382 Returns:
383 A string representation of this twitter.Status instance.
384 '''
385 return self.AsJsonString()
386
387 def AsJsonString(self):
388 '''A JSON string representation of this twitter.Status instance.
389
390 Returns:
391 A JSON string representation of this twitter.Status instance
392 '''
393 return simplejson.dumps(self.AsDict(), sort_keys=True)
394
395 def AsDict(self):
396 '''A dict representation of this twitter.Status instance.
397
398 The return value uses the same key names as the JSON representation.
399
400 Return:
401 A dict representing this twitter.Status instance
402 '''
403 data = {}
404 if self.created_at:
405 data['created_at'] = self.created_at
406 if self.favorited:
407 data['favorited'] = self.favorited
408 if self.id:
409 data['id'] = self.id
410 if self.text:
411 data['text'] = self.text
412 if self.location:
413 data['location'] = self.location
414 if self.user:
415 data['user'] = self.user.AsDict()
416 if self.in_reply_to_screen_name:
417 data['in_reply_to_screen_name'] = self.in_reply_to_screen_name
418 if self.in_reply_to_user_id:
419 data['in_reply_to_user_id'] = self.in_reply_to_user_id
420 if self.in_reply_to_status_id:
421 data['in_reply_to_status_id'] = self.in_reply_to_status_id
422 if self.truncated is not None:
423 data['truncated'] = self.truncated
424 if self.favorited is not None:
425 data['favorited'] = self.favorited
426 if self.source:
427 data['source'] = self.source
428 return data
429
430 @staticmethod
431 def NewFromJsonDict(data):
432 '''Create a new instance based on a JSON dict.
433
434 Args:
435 data: A JSON dict, as converted from the JSON in the twitter API
436 Returns:
437 A twitter.Status instance
438 '''
439 if 'user' in data:
440 user = User.NewFromJsonDict(data['user'])
441 else:
442 user = None
443 return Status(created_at=data.get('created_at', None),
444 favorited=data.get('favorited', None),
445 id=data.get('id', None),
446 text=data.get('text', None),
447 location=data.get('location', None),
448 in_reply_to_screen_name=data.get('in_reply_to_screen_name', None),
449 in_reply_to_user_id=data.get('in_reply_to_user_id', None),
450 in_reply_to_status_id=data.get('in_reply_to_status_id', None),
451 truncated=data.get('truncated', None),
452 source=data.get('source', None),
453 user=user)
454
455
456class User(object):
457 '''A class representing the User structure used by the twitter API.
458
459 The User structure exposes the following properties:
460
461 user.id
462 user.name
463 user.screen_name
464 user.location
465 user.description
466 user.profile_image_url
467 user.profile_background_tile
468 user.profile_background_image_url
469 user.profile_sidebar_fill_color
470 user.profile_background_color
471 user.profile_link_color
472 user.profile_text_color
473 user.protected
474 user.utc_offset
475 user.time_zone
476 user.url
477 user.status
478 user.statuses_count
479 user.followers_count
480 user.friends_count
481 user.favourites_count
482 '''
483 def __init__(self,
484 id=None,
485 name=None,
486 screen_name=None,
487 location=None,
488 description=None,
489 profile_image_url=None,
490 profile_background_tile=None,
491 profile_background_image_url=None,
492 profile_sidebar_fill_color=None,
493 profile_background_color=None,
494 profile_link_color=None,
495 profile_text_color=None,
496 protected=None,
497 utc_offset=None,
498 time_zone=None,
499 followers_count=None,
500 friends_count=None,
501 statuses_count=None,
502 favourites_count=None,
503 url=None,
504 status=None):
505 self.id = id
506 self.name = name
507 self.screen_name = screen_name
508 self.location = location
509 self.description = description
510 self.profile_image_url = profile_image_url
511 self.profile_background_tile = profile_background_tile
512 self.profile_background_image_url = profile_background_image_url
513 self.profile_sidebar_fill_color = profile_sidebar_fill_color
514 self.profile_background_color = profile_background_color
515 self.profile_link_color = profile_link_color
516 self.profile_text_color = profile_text_color
517 self.protected = protected
518 self.utc_offset = utc_offset
519 self.time_zone = time_zone
520 self.followers_count = followers_count
521 self.friends_count = friends_count
522 self.statuses_count = statuses_count
523 self.favourites_count = favourites_count
524 self.url = url
525 self.status = status
526
527
528 def GetId(self):
529 '''Get the unique id of this user.
530
531 Returns:
532 The unique id of this user
533 '''
534 return self._id
535
536 def SetId(self, id):
537 '''Set the unique id of this user.
538
539 Args:
540 id: The unique id of this user.
541 '''
542 self._id = id
543
544 id = property(GetId, SetId,
545 doc='The unique id of this user.')
546
547 def GetName(self):
548 '''Get the real name of this user.
549
550 Returns:
551 The real name of this user
552 '''
553 return self._name
554
555 def SetName(self, name):
556 '''Set the real name of this user.
557
558 Args:
559 name: The real name of this user
560 '''
561 self._name = name
562
563 name = property(GetName, SetName,
564 doc='The real name of this user.')
565
566 def GetScreenName(self):
567 '''Get the short username of this user.
568
569 Returns:
570 The short username of this user
571 '''
572 return self._screen_name
573
574 def SetScreenName(self, screen_name):
575 '''Set the short username of this user.
576
577 Args:
578 screen_name: the short username of this user
579 '''
580 self._screen_name = screen_name
581
582 screen_name = property(GetScreenName, SetScreenName,
583 doc='The short username of this user.')
584
585 def GetLocation(self):
586 '''Get the geographic location of this user.
587
588 Returns:
589 The geographic location of this user
590 '''
591 return self._location
592
593 def SetLocation(self, location):
594 '''Set the geographic location of this user.
595
596 Args:
597 location: The geographic location of this user
598 '''
599 self._location = location
600
601 location = property(GetLocation, SetLocation,
602 doc='The geographic location of this user.')
603
604 def GetDescription(self):
605 '''Get the short text description of this user.
606
607 Returns:
608 The short text description of this user
609 '''
610 return self._description
611
612 def SetDescription(self, description):
613 '''Set the short text description of this user.
614
615 Args:
616 description: The short text description of this user
617 '''
618 self._description = description
619
620 description = property(GetDescription, SetDescription,
621 doc='The short text description of this user.')
622
623 def GetUrl(self):
624 '''Get the homepage url of this user.
625
626 Returns:
627 The homepage url of this user
628 '''
629 return self._url
630
631 def SetUrl(self, url):
632 '''Set the homepage url of this user.
633
634 Args:
635 url: The homepage url of this user
636 '''
637 self._url = url
638
639 url = property(GetUrl, SetUrl,
640 doc='The homepage url of this user.')
641
642 def GetProfileImageUrl(self):
643 '''Get the url of the thumbnail of this user.
644
645 Returns:
646 The url of the thumbnail of this user
647 '''
648 return self._profile_image_url
649
650 def SetProfileImageUrl(self, profile_image_url):
651 '''Set the url of the thumbnail of this user.
652
653 Args:
654 profile_image_url: The url of the thumbnail of this user
655 '''
656 self._profile_image_url = profile_image_url
657
658 profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl,
659 doc='The url of the thumbnail of this user.')
660
661 def GetProfileBackgroundTile(self):
662 '''Boolean for whether to tile the profile background image.
663
664 Returns:
665 True if the background is to be tiled, False if not, None if unset.
666 '''
667 return self._profile_background_tile
668
669 def SetProfileBackgroundTile(self, profile_background_tile):
670 '''Set the boolean flag for whether to tile the profile background image.
671
672 Args:
673 profile_background_tile: Boolean flag for whether to tile or not.
674 '''
675 self._profile_background_tile = profile_background_tile
676
677 profile_background_tile = property(GetProfileBackgroundTile, SetProfileBackgroundTile,
678 doc='Boolean for whether to tile the background image.')
679
680 def GetProfileBackgroundImageUrl(self):
681 return self._profile_background_image_url
682
683 def SetProfileBackgroundImageUrl(self, profile_background_image_url):
684 self._profile_background_image_url = profile_background_image_url
685
686 profile_background_image_url = property(GetProfileBackgroundImageUrl, SetProfileBackgroundImageUrl,
687 doc='The url of the profile background of this user.')
688
689 def GetProfileSidebarFillColor(self):
690 return self._profile_sidebar_fill_color
691
692 def SetProfileSidebarFillColor(self, profile_sidebar_fill_color):
693 self._profile_sidebar_fill_color = profile_sidebar_fill_color
694
695 profile_sidebar_fill_color = property(GetProfileSidebarFillColor, SetProfileSidebarFillColor)
696
697 def GetProfileBackgroundColor(self):
698 return self._profile_background_color
699
700 def SetProfileBackgroundColor(self, profile_background_color):
701 self._profile_background_color = profile_background_color
702
703 profile_background_color = property(GetProfileBackgroundColor, SetProfileBackgroundColor)
704
705 def GetProfileLinkColor(self):
706 return self._profile_link_color
707
708 def SetProfileLinkColor(self, profile_link_color):
709 self._profile_link_color = profile_link_color
710
711 profile_link_color = property(GetProfileLinkColor, SetProfileLinkColor)
712
713 def GetProfileTextColor(self):
714 return self._profile_text_color
715
716 def SetProfileTextColor(self, profile_text_color):
717 self._profile_text_color = profile_text_color
718
719 profile_text_color = property(GetProfileTextColor, SetProfileTextColor)
720
721 def GetProtected(self):
722 return self._protected
723
724 def SetProtected(self, protected):
725 self._protected = protected
726
727 protected = property(GetProtected, SetProtected)
728
729 def GetUtcOffset(self):
730 return self._utc_offset
731
732 def SetUtcOffset(self, utc_offset):
733 self._utc_offset = utc_offset
734
735 utc_offset = property(GetUtcOffset, SetUtcOffset)
736
737 def GetTimeZone(self):
738 '''Returns the current time zone string for the user.
739
740 Returns:
741 The descriptive time zone string for the user.
742 '''
743 return self._time_zone
744
745 def SetTimeZone(self, time_zone):
746 '''Sets the user's time zone string.
747
748 Args:
749 time_zone: The descriptive time zone to assign for the user.
750 '''
751 self._time_zone = time_zone
752
753 time_zone = property(GetTimeZone, SetTimeZone)
754
755 def GetStatus(self):
756 '''Get the latest twitter.Status of this user.
757
758 Returns:
759 The latest twitter.Status of this user
760 '''
761 return self._status
762
763 def SetStatus(self, status):
764 '''Set the latest twitter.Status of this user.
765
766 Args:
767 status: The latest twitter.Status of this user
768 '''
769 self._status = status
770
771 status = property(GetStatus, SetStatus,
772 doc='The latest twitter.Status of this user.')
773
774 def GetFriendsCount(self):
775 '''Get the friend count for this user.
776
777 Returns:
778 The number of users this user has befriended.
779 '''
780 return self._friends_count
781
782 def SetFriendsCount(self, count):
783 '''Set the friend count for this user.
784
785 Args:
786 count: The number of users this user has befriended.
787 '''
788 self._friends_count = count
789
790 friends_count = property(GetFriendsCount, SetFriendsCount,
791 doc='The number of friends for this user.')
792
793 def GetFollowersCount(self):
794 '''Get the follower count for this user.
795
796 Returns:
797 The number of users following this user.
798 '''
799 return self._followers_count
800
801 def SetFollowersCount(self, count):
802 '''Set the follower count for this user.
803
804 Args:
805 count: The number of users following this user.
806 '''
807 self._followers_count = count
808
809 followers_count = property(GetFollowersCount, SetFollowersCount,
810 doc='The number of users following this user.')
811
812 def GetStatusesCount(self):
813 '''Get the number of status updates for this user.
814
815 Returns:
816 The number of status updates for this user.
817 '''
818 return self._statuses_count
819
820 def SetStatusesCount(self, count):
821 '''Set the status update count for this user.
822
823 Args:
824 count: The number of updates for this user.
825 '''
826 self._statuses_count = count
827
828 statuses_count = property(GetStatusesCount, SetStatusesCount,
829 doc='The number of updates for this user.')
830
831 def GetFavouritesCount(self):
832 '''Get the number of favourites for this user.
833
834 Returns:
835 The number of favourites for this user.
836 '''
837 return self._favourites_count
838
839 def SetFavouritesCount(self, count):
840 '''Set the favourite count for this user.
841
842 Args:
843 count: The number of favourites for this user.
844 '''
845 self._favourites_count = count
846
847 favourites_count = property(GetFavouritesCount, SetFavouritesCount,
848 doc='The number of favourites for this user.')
849
850 def __ne__(self, other):
851 return not self.__eq__(other)
852
853 def __eq__(self, other):
854 try:
855 return other and \
856 self.id == other.id and \
857 self.name == other.name and \
858 self.screen_name == other.screen_name and \
859 self.location == other.location and \
860 self.description == other.description and \
861 self.profile_image_url == other.profile_image_url and \
862 self.profile_background_tile == other.profile_background_tile and \
863 self.profile_background_image_url == other.profile_background_image_url and \
864 self.profile_sidebar_fill_color == other.profile_sidebar_fill_color and \
865 self.profile_background_color == other.profile_background_color and \
866 self.profile_link_color == other.profile_link_color and \
867 self.profile_text_color == other.profile_text_color and \
868 self.protected == other.protected and \
869 self.utc_offset == other.utc_offset and \
870 self.time_zone == other.time_zone and \
871 self.url == other.url and \
872 self.statuses_count == other.statuses_count and \
873 self.followers_count == other.followers_count and \
874 self.favourites_count == other.favourites_count and \
875 self.friends_count == other.friends_count and \
876 self.status == other.status
877 except AttributeError:
878 return False
879
880 def __str__(self):
881 '''A string representation of this twitter.User instance.
882
883 The return value is the same as the JSON string representation.
884
885 Returns:
886 A string representation of this twitter.User instance.
887 '''
888 return self.AsJsonString()
889
890 def AsJsonString(self):
891 '''A JSON string representation of this twitter.User instance.
892
893 Returns:
894 A JSON string representation of this twitter.User instance
895 '''
896 return simplejson.dumps(self.AsDict(), sort_keys=True)
897
898 def AsDict(self):
899 '''A dict representation of this twitter.User instance.
900
901 The return value uses the same key names as the JSON representation.
902
903 Return:
904 A dict representing this twitter.User instance
905 '''
906 data = {}
907 if self.id:
908 data['id'] = self.id
909 if self.name:
910 data['name'] = self.name
911 if self.screen_name:
912 data['screen_name'] = self.screen_name
913 if self.location:
914 data['location'] = self.location
915 if self.description:
916 data['description'] = self.description
917 if self.profile_image_url:
918 data['profile_image_url'] = self.profile_image_url
919 if self.profile_background_tile is not None:
920 data['profile_background_tile'] = self.profile_background_tile
921 if self.profile_background_image_url:
922 data['profile_sidebar_fill_color'] = self.profile_background_image_url
923 if self.profile_background_color:
924 data['profile_background_color'] = self.profile_background_color
925 if self.profile_link_color:
926 data['profile_link_color'] = self.profile_link_color
927 if self.profile_text_color:
928 data['profile_text_color'] = self.profile_text_color
929 if self.protected is not None:
930 data['protected'] = self.protected
931 if self.utc_offset:
932 data['utc_offset'] = self.utc_offset
933 if self.time_zone:
934 data['time_zone'] = self.time_zone
935 if self.url:
936 data['url'] = self.url
937 if self.status:
938 data['status'] = self.status.AsDict()
939 if self.friends_count:
940 data['friends_count'] = self.friends_count
941 if self.followers_count:
942 data['followers_count'] = self.followers_count
943 if self.statuses_count:
944 data['statuses_count'] = self.statuses_count
945 if self.favourites_count:
946 data['favourites_count'] = self.favourites_count
947 return data
948
949 @staticmethod
950 def NewFromJsonDict(data):
951 '''Create a new instance based on a JSON dict.
952
953 Args:
954 data: A JSON dict, as converted from the JSON in the twitter API
955 Returns:
956 A twitter.User instance
957 '''
958 if 'status' in data:
959 status = Status.NewFromJsonDict(data['status'])
960 else:
961 status = None
962 return User(id=data.get('id', None),
963 name=data.get('name', None),
964 screen_name=data.get('screen_name', None),
965 location=data.get('location', None),
966 description=data.get('description', None),
967 statuses_count=data.get('statuses_count', None),
968 followers_count=data.get('followers_count', None),
969 favourites_count=data.get('favourites_count', None),
970 friends_count=data.get('friends_count', None),
971 profile_image_url=data.get('profile_image_url', None),
972 profile_background_tile = data.get('profile_background_tile', None),
973 profile_background_image_url = data.get('profile_background_image_url', None),
974 profile_sidebar_fill_color = data.get('profile_sidebar_fill_color', None),
975 profile_background_color = data.get('profile_background_color', None),
976 profile_link_color = data.get('profile_link_color', None),
977 profile_text_color = data.get('profile_text_color', None),
978 protected = data.get('protected', None),
979 utc_offset = data.get('utc_offset', None),
980 time_zone = data.get('time_zone', None),
981 url=data.get('url', None),
982 status=status)
983
984class DirectMessage(object):
985 '''A class representing the DirectMessage structure used by the twitter API.
986
987 The DirectMessage structure exposes the following properties:
988
989 direct_message.id
990 direct_message.created_at
991 direct_message.created_at_in_seconds # read only
992 direct_message.sender_id
993 direct_message.sender_screen_name
994 direct_message.recipient_id
995 direct_message.recipient_screen_name
996 direct_message.text
997 '''
998
999 def __init__(self,
1000 id=None,
1001 created_at=None,
1002 sender_id=None,
1003 sender_screen_name=None,
1004 recipient_id=None,
1005 recipient_screen_name=None,
1006 text=None):
1007 '''An object to hold a Twitter direct message.
1008
1009 This class is normally instantiated by the twitter.Api class and
1010 returned in a sequence.
1011
1012 Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
1013
1014 Args:
1015 id: The unique id of this direct message
1016 created_at: The time this direct message was posted
1017 sender_id: The id of the twitter user that sent this message
1018 sender_screen_name: The name of the twitter user that sent this message
1019 recipient_id: The id of the twitter that received this message
1020 recipient_screen_name: The name of the twitter that received this message
1021 text: The text of this direct message
1022 '''
1023 self.id = id
1024 self.created_at = created_at
1025 self.sender_id = sender_id
1026 self.sender_screen_name = sender_screen_name
1027 self.recipient_id = recipient_id
1028 self.recipient_screen_name = recipient_screen_name
1029 self.text = text
1030
1031 def GetId(self):
1032 '''Get the unique id of this direct message.
1033
1034 Returns:
1035 The unique id of this direct message
1036 '''
1037 return self._id
1038
1039 def SetId(self, id):
1040 '''Set the unique id of this direct message.
1041
1042 Args:
1043 id: The unique id of this direct message
1044 '''
1045 self._id = id
1046
1047 id = property(GetId, SetId,
1048 doc='The unique id of this direct message.')
1049
1050 def GetCreatedAt(self):
1051 '''Get the time this direct message was posted.
1052
1053 Returns:
1054 The time this direct message was posted
1055 '''
1056 return self._created_at
1057
1058 def SetCreatedAt(self, created_at):
1059 '''Set the time this direct message was posted.
1060
1061 Args:
1062 created_at: The time this direct message was created
1063 '''
1064 self._created_at = created_at
1065
1066 created_at = property(GetCreatedAt, SetCreatedAt,
1067 doc='The time this direct message was posted.')
1068
1069 def GetCreatedAtInSeconds(self):
1070 '''Get the time this direct message was posted, in seconds since the epoch.
1071
1072 Returns:
1073 The time this direct message was posted, in seconds since the epoch.
1074 '''
1075 return calendar.timegm(rfc822.parsedate(self.created_at))
1076
1077 created_at_in_seconds = property(GetCreatedAtInSeconds,
1078 doc="The time this direct message was "
1079 "posted, in seconds since the epoch")
1080
1081 def GetSenderId(self):
1082 '''Get the unique sender id of this direct message.
1083
1084 Returns:
1085 The unique sender id of this direct message
1086 '''
1087 return self._sender_id
1088
1089 def SetSenderId(self, sender_id):
1090 '''Set the unique sender id of this direct message.
1091
1092 Args:
1093 sender id: The unique sender id of this direct message
1094 '''
1095 self._sender_id = sender_id
1096
1097 sender_id = property(GetSenderId, SetSenderId,
1098 doc='The unique sender id of this direct message.')
1099
1100 def GetSenderScreenName(self):
1101 '''Get the unique sender screen name of this direct message.
1102
1103 Returns:
1104 The unique sender screen name of this direct message
1105 '''
1106 return self._sender_screen_name
1107
1108 def SetSenderScreenName(self, sender_screen_name):
1109 '''Set the unique sender screen name of this direct message.
1110
1111 Args:
1112 sender_screen_name: The unique sender screen name of this direct message
1113 '''
1114 self._sender_screen_name = sender_screen_name
1115
1116 sender_screen_name = property(GetSenderScreenName, SetSenderScreenName,
1117 doc='The unique sender screen name of this direct message.')
1118
1119 def GetRecipientId(self):
1120 '''Get the unique recipient id of this direct message.
1121
1122 Returns:
1123 The unique recipient id of this direct message
1124 '''
1125 return self._recipient_id
1126
1127 def SetRecipientId(self, recipient_id):
1128 '''Set the unique recipient id of this direct message.
1129
1130 Args:
1131 recipient id: The unique recipient id of this direct message
1132 '''
1133 self._recipient_id = recipient_id
1134
1135 recipient_id = property(GetRecipientId, SetRecipientId,
1136 doc='The unique recipient id of this direct message.')
1137
1138 def GetRecipientScreenName(self):
1139 '''Get the unique recipient screen name of this direct message.
1140
1141 Returns:
1142 The unique recipient screen name of this direct message
1143 '''
1144 return self._recipient_screen_name
1145
1146 def SetRecipientScreenName(self, recipient_screen_name):
1147 '''Set the unique recipient screen name of this direct message.
1148
1149 Args:
1150 recipient_screen_name: The unique recipient screen name of this direct message
1151 '''
1152 self._recipient_screen_name = recipient_screen_name
1153
1154 recipient_screen_name = property(GetRecipientScreenName, SetRecipientScreenName,
1155 doc='The unique recipient screen name of this direct message.')
1156
1157 def GetText(self):
1158 '''Get the text of this direct message.
1159
1160 Returns:
1161 The text of this direct message.
1162 '''
1163 return self._text
1164
1165 def SetText(self, text):
1166 '''Set the text of this direct message.
1167
1168 Args:
1169 text: The text of this direct message
1170 '''
1171 self._text = text
1172
1173 text = property(GetText, SetText,
1174 doc='The text of this direct message')
1175
1176 def __ne__(self, other):
1177 return not self.__eq__(other)
1178
1179 def __eq__(self, other):
1180 try:
1181 return other and \
1182 self.id == other.id and \
1183 self.created_at == other.created_at and \
1184 self.sender_id == other.sender_id and \
1185 self.sender_screen_name == other.sender_screen_name and \
1186 self.recipient_id == other.recipient_id and \
1187 self.recipient_screen_name == other.recipient_screen_name and \
1188 self.text == other.text
1189 except AttributeError:
1190 return False
1191
1192 def __str__(self):
1193 '''A string representation of this twitter.DirectMessage instance.
1194
1195 The return value is the same as the JSON string representation.
1196
1197 Returns:
1198 A string representation of this twitter.DirectMessage instance.
1199 '''
1200 return self.AsJsonString()
1201
1202 def AsJsonString(self):
1203 '''A JSON string representation of this twitter.DirectMessage instance.
1204
1205 Returns:
1206 A JSON string representation of this twitter.DirectMessage instance
1207 '''
1208 return simplejson.dumps(self.AsDict(), sort_keys=True)
1209
1210 def AsDict(self):
1211 '''A dict representation of this twitter.DirectMessage instance.
1212
1213 The return value uses the same key names as the JSON representation.
1214
1215 Return:
1216 A dict representing this twitter.DirectMessage instance
1217 '''
1218 data = {}
1219 if self.id:
1220 data['id'] = self.id
1221 if self.created_at:
1222 data['created_at'] = self.created_at
1223 if self.sender_id:
1224 data['sender_id'] = self.sender_id
1225 if self.sender_screen_name:
1226 data['sender_screen_name'] = self.sender_screen_name
1227 if self.recipient_id:
1228 data['recipient_id'] = self.recipient_id
1229 if self.recipient_screen_name:
1230 data['recipient_screen_name'] = self.recipient_screen_name
1231 if self.text:
1232 data['text'] = self.text
1233 return data
1234
1235 @staticmethod
1236 def NewFromJsonDict(data):
1237 '''Create a new instance based on a JSON dict.
1238
1239 Args:
1240 data: A JSON dict, as converted from the JSON in the twitter API
1241 Returns:
1242 A twitter.DirectMessage instance
1243 '''
1244 return DirectMessage(created_at=data.get('created_at', None),
1245 recipient_id=data.get('recipient_id', None),
1246 sender_id=data.get('sender_id', None),
1247 text=data.get('text', None),
1248 sender_screen_name=data.get('sender_screen_name', None),
1249 id=data.get('id', None),
1250 recipient_screen_name=data.get('recipient_screen_name', None))
1251
1252class Api(object):
1253 '''A python interface into the Twitter API
1254
1255 By default, the Api caches results for 1 minute.
1256
1257 Example usage:
1258
1259 To create an instance of the twitter.Api class, with no authentication:
1260
1261 >>> import twitter
1262 >>> api = twitter.Api()
1263
1264 To fetch the most recently posted public twitter status messages:
1265
1266 >>> statuses = api.GetPublicTimeline()
1267 >>> print [s.user.name for s in statuses]
1268 [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #...
1269
1270 To fetch a single user's public status messages, where "user" is either
1271 a Twitter "short name" or their user id.
1272
1273 >>> statuses = api.GetUserTimeline(user)
1274 >>> print [s.text for s in statuses]
1275
1276 To use authentication, instantiate the twitter.Api class with a
1277 username and password:
1278
1279 >>> api = twitter.Api(username='twitter user', password='twitter pass')
1280
1281 To fetch your friends (after being authenticated):
1282
1283 >>> users = api.GetFriends()
1284 >>> print [u.name for u in users]
1285
1286 To post a twitter status message (after being authenticated):
1287
1288 >>> status = api.PostUpdate('I love python-twitter!')
1289 >>> print status.text
1290 I love python-twitter!
1291
1292 There are many other methods, including:
1293
1294 >>> api.PostUpdates(status)
1295 >>> api.PostDirectMessage(user, text)
1296 >>> api.GetUser(user)
1297 >>> api.GetReplies()
1298 >>> api.GetUserTimeline(user)
1299 >>> api.GetStatus(id)
1300 >>> api.DestroyStatus(id)
1301 >>> api.GetFriendsTimeline(user)
1302 >>> api.GetFriends(user)
1303 >>> api.GetFollowers()
1304 >>> api.GetFeatured()
1305 >>> api.GetDirectMessages()
1306 >>> api.PostDirectMessage(user, text)
1307 >>> api.DestroyDirectMessage(id)
1308 >>> api.DestroyFriendship(user)
1309 >>> api.CreateFriendship(user)
1310 >>> api.GetUserByEmail(email)
1311 >>> api.VerifyCredentials()
1312 '''
1313
1314 DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute
1315
1316 _API_REALM = 'Twitter API'
1317
1318 def __init__(self,
1319 username=None,
1320 password=None,
1321 input_encoding=None,
1322 request_headers=None,
1323 cache=DEFAULT_CACHE,
1324 shortner=None,
1325 base_url=None,
1326 use_gzip_compression=False):
1327 '''Instantiate a new twitter.Api object.
1328
1329 Args:
1330 username:
1331 The username of the twitter account. [optional]
1332 password:
1333 The password for the twitter account. [optional]
1334 input_encoding:
1335 The encoding used to encode input strings. [optional]
1336 request_header:
1337 A dictionary of additional HTTP request headers. [optional]
1338 cache:
1339 The cache instance to use. Defaults to DEFAULT_CACHE.
1340 Use None to disable caching. [optional]
1341 shortner:
1342 The shortner instance to use. Defaults to None.
1343 See shorten_url.py for an example shortner. [optional]
1344 base_url:
1345 The base URL to use to contact the Twitter API.
1346 Defaults to https://twitter.com. [optional]
1347 use_gzip_compression:
1348 Set to True to tell enable gzip compression for any call
1349 made to Twitter. Defaults to False. [optional]
1350 '''
1351 self.SetCache(cache)
1352 self._urllib = urllib2
1353 self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
1354 self._InitializeRequestHeaders(request_headers)
1355 self._InitializeUserAgent()
1356 self._InitializeDefaultParameters()
1357 self._input_encoding = input_encoding
1358 self._use_gzip = use_gzip_compression
1359 self.SetCredentials(username, password)
1360 if base_url is None:
1361 self.base_url = 'https://twitter.com'
1362 else:
1363 self.base_url = base_url
1364
1365 def GetPublicTimeline(self,
1366 since_id=None):
1367 '''Fetch the sequnce of public twitter.Status message for all users.
1368
1369 Args:
1370 since_id:
1371 Returns only public statuses with an ID greater than
1372 (that is, more recent than) the specified ID. [optional]
1373
1374 Returns:
1375 An sequence of twitter.Status instances, one for each message
1376 '''
1377 parameters = {}
1378
1379 if since_id:
1380 parameters['since_id'] = since_id
1381
1382 url = '%s/statuses/public_timeline.json' % self.base_url
1383 json = self._FetchUrl(url, parameters=parameters)
1384 data = simplejson.loads(json)
1385
1386 self._CheckForTwitterError(data)
1387
1388 return [Status.NewFromJsonDict(x) for x in data]
1389
1390 def FilterPublicTimeline(self,
1391 term,
1392 since_id=None):
1393 '''Filter the public twitter timeline by a given search term on
1394 the local machine.
1395
1396 Args:
1397 term:
1398 term to search by.
1399 since_id:
1400 Returns only public statuses with an ID greater than
1401 (that is, more recent than) the specified ID. [optional]
1402
1403 Returns:
1404 A sequence of twitter.Status instances, one for each message
1405 containing the term
1406 '''
1407 statuses = self.GetPublicTimeline(since_id)
1408 results = []
1409
1410 for s in statuses:
1411 if s.text.lower().find(term.lower()) != -1:
1412 results.append(s)
1413
1414 return results
1415
1416 def GetSearch(self,
1417 term,
1418 geocode=None,
1419 since_id=None,
1420 per_page=15,
1421 page=1,
1422 lang="en",
1423 show_user="true",
1424 query_users=False):
1425 '''Return twitter search results for a given term.
1426
1427 Args:
1428 term:
1429 term to search by.
1430 since_id:
1431 Returns only public statuses with an ID greater than
1432 (that is, more recent than) the specified ID. [optional]
1433 geocode:
1434 geolocation information in the form (latitude, longitude, radius)
1435 [optional]
1436 per_page:
1437 number of results to return. Default is 15 [optional]
1438 page:
1439 which page of search results to return
1440 lang:
1441 language for results. Default is English [optional]
1442 show_user:
1443 prefixes screen name in status
1444 query_users:
1445 If set to False, then all users only have screen_name and
1446 profile_image_url available.
1447 If set to True, all information of users are available,
1448 but it uses lots of request quota, one per status.
1449 Returns:
1450 A sequence of twitter.Status instances, one for each message containing
1451 the term
1452 '''
1453 # Build request parameters
1454 parameters = {}
1455
1456 if since_id:
1457 parameters['since_id'] = since_id
1458
1459 if not term:
1460 return []
1461
1462 parameters['q'] = urllib.quote_plus(term)
1463 parameters['show_user'] = show_user
1464 parameters['lang'] = lang
1465 parameters['rpp'] = per_page
1466 parameters['page'] = page
1467
1468 if geocode is not None:
1469 parameters['geocode'] = ','.join(map(str, geocode))
1470
1471 # Make and send requests
1472 url = 'http://search.twitter.com/search.json'
1473 json = self._FetchUrl(url, parameters=parameters)
1474 data = simplejson.loads(json)
1475
1476 self._CheckForTwitterError(data)
1477
1478 results = []
1479
1480 for x in data['results']:
1481 temp = Status.NewFromJsonDict(x)
1482
1483 if query_users:
1484 # Build user object with new request
1485 temp.user = self.GetUser(urllib.quote(x['from_user']))
1486 else:
1487 temp.user = User(screen_name=x['from_user'], profile_image_url=x['profile_image_url'])
1488
1489 results.append(temp)
1490
1491 # Return built list of statuses
1492 return results # [Status.NewFromJsonDict(x) for x in data['results']]
1493
1494 def GetFriendsTimeline(self,
1495 user=None,
1496 count=None,
1497 since=None,
1498 since_id=None):
1499 '''Fetch the sequence of twitter.Status messages for a user's friends
1500
1501 The twitter.Api instance must be authenticated if the user is private.
1502
1503 Args:
1504 user:
1505 Specifies the ID or screen name of the user for whom to return
1506 the friends_timeline. If unspecified, the username and password
1507 must be set in the twitter.Api instance. [Optional]
1508 count:
1509 Specifies the number of statuses to retrieve. May not be
1510 greater than 200. [Optional]
1511 since:
1512 Narrows the returned results to just those statuses created
1513 after the specified HTTP-formatted date. [Optional]
1514 since_id:
1515 Returns only public statuses with an ID greater than (that is,
1516 more recent than) the specified ID. [Optional]
1517
1518 Returns:
1519 A sequence of twitter.Status instances, one for each message
1520 '''
1521 if not user and not self._username:
1522 raise TwitterError("User must be specified if API is not authenticated.")
1523 if user:
1524 url = '%s/statuses/friends_timeline/%s.json' % (self.base_url, user)
1525 else:
1526 url = '%s/statuses/friends_timeline.json' % self.base_url
1527 parameters = {}
1528 if count is not None:
1529 try:
1530 if int(count) > 200:
1531 raise TwitterError("'count' may not be greater than 200")
1532 except ValueError:
1533 raise TwitterError("'count' must be an integer")
1534 parameters['count'] = count
1535 if since:
1536 parameters['since'] = since
1537 if since_id:
1538 parameters['since_id'] = since_id
1539 json = self._FetchUrl(url, parameters=parameters)
1540 data = simplejson.loads(json)
1541 self._CheckForTwitterError(data)
1542 return [Status.NewFromJsonDict(x) for x in data]
1543
1544 def GetUserTimeline(self,
1545 id=None,
1546 user_id=None,
1547 screen_name=None,
1548 since_id=None,
1549 max_id=None,
1550 count=None,
1551 page=None):
1552 '''Fetch the sequence of public Status messages for a single user.
1553
1554 The twitter.Api instance must be authenticated if the user is private.
1555
1556 Args:
1557 id:
1558 Specifies the ID or screen name of the user for whom to return
1559 the user_timeline. [optional]
1560 user_id:
1561 Specfies the ID of the user for whom to return the
1562 user_timeline. Helpful for disambiguating when a valid user ID
1563 is also a valid screen name. [optional]
1564 screen_name:
1565 Specfies the screen name of the user for whom to return the
1566 user_timeline. Helpful for disambiguating when a valid screen
1567 name is also a user ID. [optional]
1568 since_id:
1569 Returns only public statuses with an ID greater than (that is,
1570 more recent than) the specified ID. [optional]
1571 max_id:
1572 Returns only statuses with an ID less than (that is, older
1573 than) or equal to the specified ID. [optional]
1574 count:
1575 Specifies the number of statuses to retrieve. May not be
1576 greater than 200. [optional]
1577 page:
1578 Specifies the page of results to retrieve. Note: there are
1579 pagination limits. [optional]
1580
1581 Returns:
1582 A sequence of Status instances, one for each message up to count
1583 '''
1584 parameters = {}
1585
1586 if id:
1587 url = '%s/statuses/user_timeline/%s.json' % (self.base_url, id)
1588 elif user_id:
1589 url = '%s/statuses/user_timeline.json?user_id=%d' % (self.base_url, user_id)
1590 elif screen_name:
1591 url = ('%s/statuses/user_timeline.json?screen_name=%s' % (self.base_url,
1592 screen_name))
1593 elif not self._username:
1594 raise TwitterError("User must be specified if API is not authenticated.")
1595 else:
1596 url = '%s/statuses/user_timeline.json' % self.base_url
1597
1598 if since_id:
1599 try:
1600 parameters['since_id'] = long(since_id)
1601 except:
1602 raise TwitterError("since_id must be an integer")
1603
1604 if max_id:
1605 try:
1606 parameters['max_id'] = long(max_id)
1607 except:
1608 raise TwitterError("max_id must be an integer")
1609
1610 if count:
1611 try:
1612 parameters['count'] = int(count)
1613 except:
1614 raise TwitterError("count must be an integer")
1615
1616 if page:
1617 try:
1618 parameters['page'] = int(page)
1619 except:
1620 raise TwitterError("page must be an integer")
1621
1622 json = self._FetchUrl(url, parameters=parameters)
1623 data = simplejson.loads(json)
1624 self._CheckForTwitterError(data)
1625 return [Status.NewFromJsonDict(x) for x in data]
1626
1627 def GetStatus(self, id):
1628 '''Returns a single status message.
1629
1630 The twitter.Api instance must be authenticated if the status message is private.
1631
1632 Args:
1633 id: The numerical ID of the status you're trying to retrieve.
1634
1635 Returns:
1636 A twitter.Status instance representing that status message
1637 '''
1638 try:
1639 if id:
1640 long(id)
1641 except:
1642 raise TwitterError("id must be an long integer")
1643 url = '%s/statuses/show/%s.json' % (self.base_url, id)
1644 json = self._FetchUrl(url)
1645 data = simplejson.loads(json)
1646 self._CheckForTwitterError(data)
1647 return Status.NewFromJsonDict(data)
1648
1649 def DestroyStatus(self, id):
1650 '''Destroys the status specified by the required ID parameter.
1651
1652 The twitter.Api instance must be authenticated and thee
1653 authenticating user must be the author of the specified status.
1654
1655 Args:
1656 id: The numerical ID of the status you're trying to destroy.
1657
1658 Returns:
1659 A twitter.Status instance representing the destroyed status message
1660 '''
1661 try:
1662 if id:
1663 long(id)
1664 except:
1665 raise TwitterError("id must be an integer")
1666 url = '%s/statuses/destroy/%s.json' % (self.base_url, id)
1667 json = self._FetchUrl(url, post_data={})
1668 data = simplejson.loads(json)
1669 self._CheckForTwitterError(data)
1670 return Status.NewFromJsonDict(data)
1671
1672 def PostUpdate(self, status, in_reply_to_status_id=None):
1673 '''Post a twitter status message from the authenticated user.
1674
1675 The twitter.Api instance must be authenticated.
1676
1677 Args:
1678 status:
1679 The message text to be posted. Must be less than or equal to
1680 140 characters.
1681 in_reply_to_status_id:
1682 The ID of an existing status that the status to be posted is
1683 in reply to. This implicitly sets the in_reply_to_user_id
1684 attribute of the resulting status to the user ID of the
1685 message being replied to. Invalid/missing status IDs will be
1686 ignored. [Optional]
1687 Returns:
1688 A twitter.Status instance representing the message posted.
1689 '''
1690 if not self._username:
1691 raise TwitterError("The twitter.Api instance must be authenticated.")
1692
1693 url = '%s/statuses/update.json' % self.base_url
1694
1695 if len(status) > CHARACTER_LIMIT:
1696 raise TwitterError("Text must be less than or equal to %d characters. "
1697 "Consider using PostUpdates." % CHARACTER_LIMIT)
1698
1699 data = {'status': status}
1700 if in_reply_to_status_id:
1701 data['in_reply_to_status_id'] = in_reply_to_status_id
1702 json = self._FetchUrl(url, post_data=data)
1703 data = simplejson.loads(json)
1704 self._CheckForTwitterError(data)
1705 return Status.NewFromJsonDict(data)
1706
1707 def PostUpdates(self, status, continuation=None, **kwargs):
1708 '''Post one or more twitter status messages from the authenticated user.
1709
1710 Unlike api.PostUpdate, this method will post multiple status updates
1711 if the message is longer than 140 characters.
1712
1713 The twitter.Api instance must be authenticated.
1714
1715 Args:
1716 status:
1717 The message text to be posted. May be longer than 140 characters.
1718 continuation:
1719 The character string, if any, to be appended to all but the
1720 last message. Note that Twitter strips trailing '...' strings
1721 from messages. Consider using the unicode \u2026 character
1722 (horizontal ellipsis) instead. [Defaults to None]
1723 **kwargs:
1724 See api.PostUpdate for a list of accepted parameters.
1725 Returns:
1726 A of list twitter.Status instance representing the messages posted.
1727 '''
1728 results = list()
1729 if continuation is None:
1730 continuation = ''
1731 line_length = CHARACTER_LIMIT - len(continuation)
1732 lines = textwrap.wrap(status, line_length)
1733 for line in lines[0:-1]:
1734 results.append(self.PostUpdate(line + continuation, **kwargs))
1735 results.append(self.PostUpdate(lines[-1], **kwargs))
1736 return results
1737
1738 def GetReplies(self, since=None, since_id=None, page=None):
1739 '''Get a sequence of status messages representing the 20 most recent
1740 replies (status updates prefixed with @username) to the authenticating
1741 user.
1742
1743 Args:
1744 page:
1745 since:
1746 Narrows the returned results to just those statuses created
1747 after the specified HTTP-formatted date. [optional]
1748 since_id:
1749 Returns only public statuses with an ID greater than (that is,
1750 more recent than) the specified ID. [Optional]
1751
1752 Returns:
1753 A sequence of twitter.Status instances, one for each reply to the user.
1754 '''
1755 url = '%s/statuses/replies.json' % self.base_url
1756 if not self._username:
1757 raise TwitterError("The twitter.Api instance must be authenticated.")
1758 parameters = {}
1759 if since:
1760 parameters['since'] = since
1761 if since_id:
1762 parameters['since_id'] = since_id
1763 if page:
1764 parameters['page'] = page
1765 json = self._FetchUrl(url, parameters=parameters)
1766 data = simplejson.loads(json)
1767 self._CheckForTwitterError(data)
1768 return [Status.NewFromJsonDict(x) for x in data]
1769
1770 def GetFriends(self, user=None, page=None):
1771 '''Fetch the sequence of twitter.User instances, one for each friend.
1772
1773 Args:
1774 user: the username or id of the user whose friends you are fetching. If
1775 not specified, defaults to the authenticated user. [optional]
1776
1777 The twitter.Api instance must be authenticated.
1778
1779 Returns:
1780 A sequence of twitter.User instances, one for each friend
1781 '''
1782 if not user and not self._username:
1783 raise TwitterError("twitter.Api instance must be authenticated")
1784 if user:
1785 url = '%s/statuses/friends/%s.json' % (self.base_url, user)
1786 else:
1787 url = '%s/statuses/friends.json' % self.base_url
1788 parameters = {}
1789 if page:
1790 parameters['page'] = page
1791 json = self._FetchUrl(url, parameters=parameters)
1792 data = simplejson.loads(json)
1793 self._CheckForTwitterError(data)
1794 return [User.NewFromJsonDict(x) for x in data]
1795
1796 def GetFriendIDs(self, user=None, page=None):
1797 '''Returns a list of twitter user id's for every person
1798 the specified user is following.
1799
1800 Args:
1801 user:
1802 The id or screen_name of the user to retrieve the id list for
1803 [optional]
1804 page:
1805 Specifies the page number of the results beginning at 1.
1806 A single page contains 5000 ids. This is recommended for users
1807 with large id lists. If not provided all id's are returned.
1808 (Please note that the result set isn't guaranteed to be 5000
1809 every time as suspended users will be filtered.)
1810 [optional]
1811
1812 Returns:
1813 A list of integers, one for each user id.
1814 '''
1815 if not user and not self._username:
1816 raise TwitterError("twitter.Api instance must be authenticated")
1817 if user:
1818 url = '%s/friends/ids/%s.json' % (self.base_url, user)
1819 else:
1820 url = '%s/friends/ids.json' % self.base_url
1821 parameters = {}
1822 if page:
1823 parameters['page'] = page
1824 json = self._FetchUrl(url, parameters=parameters)
1825 data = simplejson.loads(json)
1826 self._CheckForTwitterError(data)
1827 return data
1828
1829 def GetFollowers(self, page=None):
1830 '''Fetch the sequence of twitter.User instances, one for each follower
1831
1832 The twitter.Api instance must be authenticated.
1833
1834 Returns:
1835 A sequence of twitter.User instances, one for each follower
1836 '''
1837 if not self._username:
1838 raise TwitterError("twitter.Api instance must be authenticated")
1839 url = '%s/statuses/followers.json' % self.base_url
1840 parameters = {}
1841 if page:
1842 parameters['page'] = page
1843 json = self._FetchUrl(url, parameters=parameters)
1844 data = simplejson.loads(json)
1845 self._CheckForTwitterError(data)
1846 return [User.NewFromJsonDict(x) for x in data]
1847
1848 def GetFeatured(self):
1849 '''Fetch the sequence of twitter.User instances featured on twitter.com
1850
1851 The twitter.Api instance must be authenticated.
1852
1853 Returns:
1854 A sequence of twitter.User instances
1855 '''
1856 url = '%s/statuses/featured.json' % self.base_url
1857 json = self._FetchUrl(url)
1858 data = simplejson.loads(json)
1859 self._CheckForTwitterError(data)
1860 return [User.NewFromJsonDict(x) for x in data]
1861
1862 def GetUser(self, user):
1863 '''Returns a single user.
1864
1865 The twitter.Api instance must be authenticated.
1866
1867 Args:
1868 user: The username or id of the user to retrieve.
1869
1870 Returns:
1871 A twitter.User instance representing that user
1872 '''
1873 url = '%s/users/show/%s.json' % (self.base_url, user)
1874 json = self._FetchUrl(url)
1875 data = simplejson.loads(json)
1876 self._CheckForTwitterError(data)
1877 return User.NewFromJsonDict(data)
1878
1879 def GetDirectMessages(self, since=None, since_id=None, page=None):
1880 '''Returns a list of the direct messages sent to the authenticating user.
1881
1882 The twitter.Api instance must be authenticated.
1883
1884 Args:
1885 since:
1886 Narrows the returned results to just those statuses created
1887 after the specified HTTP-formatted date. [optional]
1888 since_id:
1889 Returns only public statuses with an ID greater than (that is,
1890 more recent than) the specified ID. [Optional]
1891
1892 Returns:
1893 A sequence of twitter.DirectMessage instances
1894 '''
1895 url = '%s/direct_messages.json' % self.base_url
1896 if not self._username:
1897 raise TwitterError("The twitter.Api instance must be authenticated.")
1898 parameters = {}
1899 if since:
1900 parameters['since'] = since
1901 if since_id:
1902 parameters['since_id'] = since_id
1903 if page:
1904 parameters['page'] = page
1905 json = self._FetchUrl(url, parameters=parameters)
1906 data = simplejson.loads(json)
1907 self._CheckForTwitterError(data)
1908 return [DirectMessage.NewFromJsonDict(x) for x in data]
1909
1910 def PostDirectMessage(self, user, text):
1911 '''Post a twitter direct message from the authenticated user
1912
1913 The twitter.Api instance must be authenticated.
1914
1915 Args:
1916 user: The ID or screen name of the recipient user.
1917 text: The message text to be posted. Must be less than 140 characters.
1918
1919 Returns:
1920 A twitter.DirectMessage instance representing the message posted
1921 '''
1922 if not self._username:
1923 raise TwitterError("The twitter.Api instance must be authenticated.")
1924 url = '%s/direct_messages/new.json' % self.base_url
1925 data = {'text': text, 'user': user}
1926 json = self._FetchUrl(url, post_data=data)
1927 data = simplejson.loads(json)
1928 self._CheckForTwitterError(data)
1929 return DirectMessage.NewFromJsonDict(data)
1930
1931 def DestroyDirectMessage(self, id):
1932 '''Destroys the direct message specified in the required ID parameter.
1933
1934 The twitter.Api instance must be authenticated, and the
1935 authenticating user must be the recipient of the specified direct
1936 message.
1937
1938 Args:
1939 id: The id of the direct message to be destroyed
1940
1941 Returns:
1942 A twitter.DirectMessage instance representing the message destroyed
1943 '''
1944 url = '%s/direct_messages/destroy/%s.json' % (self.base_url, id)
1945 json = self._FetchUrl(url, post_data={})
1946 data = simplejson.loads(json)
1947 self._CheckForTwitterError(data)
1948 return DirectMessage.NewFromJsonDict(data)
1949
1950 def CreateFriendship(self, user):
1951 '''Befriends the user specified in the user parameter as the authenticating user.
1952
1953 The twitter.Api instance must be authenticated.
1954
1955 Args:
1956 The ID or screen name of the user to befriend.
1957 Returns:
1958 A twitter.User instance representing the befriended user.
1959 '''
1960 url = '%s/friendships/create/%s.json' % (self.base_url, user)
1961 json = self._FetchUrl(url, post_data={})
1962 data = simplejson.loads(json)
1963 self._CheckForTwitterError(data)
1964 return User.NewFromJsonDict(data)
1965
1966 def DestroyFriendship(self, user):
1967 '''Discontinues friendship with the user specified in the user parameter.
1968
1969 The twitter.Api instance must be authenticated.
1970
1971 Args:
1972 The ID or screen name of the user with whom to discontinue friendship.
1973 Returns:
1974 A twitter.User instance representing the discontinued friend.
1975 '''
1976 url = '%s/friendships/destroy/%s.json' % (self.base_url, user)
1977 json = self._FetchUrl(url, post_data={})
1978 data = simplejson.loads(json)
1979 self._CheckForTwitterError(data)
1980 return User.NewFromJsonDict(data)
1981
1982 def CreateFavorite(self, status):
1983 '''Favorites the status specified in the status parameter as the authenticating user.
1984 Returns the favorite status when successful.
1985
1986 The twitter.Api instance must be authenticated.
1987
1988 Args:
1989 The twitter.Status instance to mark as a favorite.
1990 Returns:
1991 A twitter.Status instance representing the newly-marked favorite.
1992 '''
1993 url = '%s/favorites/create/%s.json' % (self.base_url, status.id)
1994 json = self._FetchUrl(url, post_data={})
1995 data = simplejson.loads(json)
1996 self._CheckForTwitterError(data)
1997 return Status.NewFromJsonDict(data)
1998
1999 def DestroyFavorite(self, status):
2000 '''Un-favorites the status specified in the ID parameter as the authenticating user.
2001 Returns the un-favorited status in the requested format when successful.
2002
2003 The twitter.Api instance must be authenticated.
2004
2005 Args:
2006 The twitter.Status to unmark as a favorite.
2007 Returns:
2008 A twitter.Status instance representing the newly-unmarked favorite.
2009 '''
2010 url = '%s/favorites/destroy/%s.json' % (self.base_url, status.id)
2011 json = self._FetchUrl(url, post_data={})
2012 data = simplejson.loads(json)
2013 self._CheckForTwitterError(data)
2014 return Status.NewFromJsonDict(data)
2015
2016 def GetFavorites(self,
2017 user=None,
2018 page=None):
2019 '''Return a list of Status objects representing favorited tweets.
2020 By default, returns the (up to) 20 most recent tweets for the
2021 authenticated user.
2022
2023 Args:
2024 user:
2025 The username or id of the user whose favorites you are fetching.
2026 If not specified, defaults to the authenticated user. [optional]
2027
2028 page:
2029 Retrieves the 20 next most recent favorite statuses. [optional]
2030 '''
2031 parameters = {}
2032
2033 if page:
2034 parameters['page'] = page
2035
2036 if user:
2037 url = '%s/favorites/%s.json' % (self.base_url, user)
2038 elif not user and not self._username:
2039 raise TwitterError("User must be specified if API is not authenticated.")
2040 else:
2041 url = '%s/favorites.json' % self.base_url
2042
2043 json = self._FetchUrl(url, parameters=parameters)
2044 data = simplejson.loads(json)
2045
2046 self._CheckForTwitterError(data)
2047
2048 return [Status.NewFromJsonDict(x) for x in data]
2049
2050 def GetMentions(self,
2051 since_id=None,
2052 max_id=None,
2053 page=None):
2054 '''Returns the 20 most recent mentions (status containing @username)
2055 for the authenticating user.
2056
2057 Args:
2058 since_id:
2059 Returns only public statuses with an ID greater than
2060 (that is, more recent than) the specified ID. [optional]
2061
2062 max_id:
2063 Returns only statuses with an ID less than
2064 (that is, older than) the specified ID. [optional]
2065
2066 page:
2067 Retrieves the 20 next most recent replies. [optional]
2068
2069 Returns:
2070 A sequence of twitter.Status instances, one for each mention of the user.
2071 see: http://apiwiki.twitter.com/REST-API-Documentation#statuses/mentions
2072 '''
2073
2074 url = '%s/statuses/mentions.json' % self.base_url
2075
2076 if not self._username:
2077 raise TwitterError("The twitter.Api instance must be authenticated.")
2078
2079 parameters = {}
2080
2081 if since_id:
2082 parameters['since_id'] = since_id
2083 if max_id:
2084 parameters['max_id'] = max_id
2085 if page:
2086 parameters['page'] = page
2087
2088 json = self._FetchUrl(url, parameters=parameters)
2089 data = simplejson.loads(json)
2090
2091 self._CheckForTwitterError(data)
2092
2093 return [Status.NewFromJsonDict(x) for x in data]
2094
2095 def GetUserByEmail(self, email):
2096 '''Returns a single user by email address.
2097
2098 Args:
2099 email: The email of the user to retrieve.
2100 Returns:
2101 A twitter.User instance representing that user
2102 '''
2103 url = '%s/users/show.json?email=%s' % (self.base_url, email)
2104 json = self._FetchUrl(url)
2105 data = simplejson.loads(json)
2106 self._CheckForTwitterError(data)
2107 return User.NewFromJsonDict(data)
2108
2109 def VerifyCredentials(self):
2110 '''Returns a twitter.User instance if the authenticating user is valid.
2111
2112 Returns:
2113 A twitter.User instance representing that user if the
2114 credentials are valid, None otherwise.
2115 '''
2116 if not self._username:
2117 raise TwitterError("Api instance must first be given user credentials.")
2118 url = '%s/account/verify_credentials.json' % self.base_url
2119 try:
2120 json = self._FetchUrl(url, no_cache=True)
2121 except urllib2.HTTPError, http_error:
2122 if http_error.code == httplib.UNAUTHORIZED:
2123 return None
2124 else:
2125 raise http_error
2126 data = simplejson.loads(json)
2127 self._CheckForTwitterError(data)
2128 return User.NewFromJsonDict(data)
2129
2130 def SetCredentials(self, username, password):
2131 '''Set the username and password for this instance
2132
2133 Args:
2134 username: The twitter username.
2135 password: The twitter password.
2136 '''
2137 self._username = username
2138 self._password = password
2139
2140 def ClearCredentials(self):
2141 '''Clear the username and password for this instance
2142 '''
2143 self._username = None
2144 self._password = None
2145
2146 def SetCache(self, cache):
2147 '''Override the default cache. Set to None to prevent caching.
2148
2149 Args:
2150 cache: an instance that supports the same API as the twitter._FileCache
2151 '''
2152 if cache == DEFAULT_CACHE:
2153 self._cache = _FileCache()
2154 else:
2155 self._cache = cache
2156
2157 def SetUrllib(self, urllib):
2158 '''Override the default urllib implementation.
2159
2160 Args:
2161 urllib: an instance that supports the same API as the urllib2 module
2162 '''
2163 self._urllib = urllib
2164
2165 def SetCacheTimeout(self, cache_timeout):
2166 '''Override the default cache timeout.
2167
2168 Args:
2169 cache_timeout: time, in seconds, that responses should be reused.
2170 '''
2171 self._cache_timeout = cache_timeout
2172
2173 def SetUserAgent(self, user_agent):
2174 '''Override the default user agent
2175
2176 Args:
2177 user_agent: a string that should be send to the server as the User-agent
2178 '''
2179 self._request_headers['User-Agent'] = user_agent
2180
2181 def SetXTwitterHeaders(self, client, url, version):
2182 '''Set the X-Twitter HTTP headers that will be sent to the server.
2183
2184 Args:
2185 client:
2186 The client name as a string. Will be sent to the server as
2187 the 'X-Twitter-Client' header.
2188 url:
2189 The URL of the meta.xml as a string. Will be sent to the server
2190 as the 'X-Twitter-Client-URL' header.
2191 version:
2192 The client version as a string. Will be sent to the server
2193 as the 'X-Twitter-Client-Version' header.
2194 '''
2195 self._request_headers['X-Twitter-Client'] = client
2196 self._request_headers['X-Twitter-Client-URL'] = url
2197 self._request_headers['X-Twitter-Client-Version'] = version
2198
2199 def SetSource(self, source):
2200 '''Suggest the "from source" value to be displayed on the Twitter web site.
2201
2202 The value of the 'source' parameter must be first recognized by
2203 the Twitter server. New source values are authorized on a case by
2204 case basis by the Twitter development team.
2205
2206 Args:
2207 source:
2208 The source name as a string. Will be sent to the server as
2209 the 'source' parameter.
2210 '''
2211 self._default_params['source'] = source
2212
2213 def GetRateLimitStatus(self):
2214 '''Fetch the rate limit status for the currently authorized user.
2215
2216 Returns:
2217 A dictionary containing the time the limit will reset (reset_time),
2218 the number of remaining hits allowed before the reset (remaining_hits),
2219 the number of hits allowed in a 60-minute period (hourly_limit), and the
2220 time of the reset in seconds since The Epoch (reset_time_in_seconds).
2221 '''
2222 url = '%s/account/rate_limit_status.json' % self.base_url
2223 json = self._FetchUrl(url, no_cache=True)
2224 data = simplejson.loads(json)
2225
2226 self._CheckForTwitterError(data)
2227
2228 return data
2229
2230 def MaximumHitFrequency(self):
2231 '''Determines the minimum number of seconds that a program must wait before
2232 hitting the server again without exceeding the rate_limit imposed for the
2233 currently authenticated user.
2234
2235 Returns:
2236 The minimum second interval that a program must use so as to not exceed
2237 the rate_limit imposed for the user.
2238 '''
2239 rate_status = self.GetRateLimitStatus()
2240 reset_time = rate_status.get('reset_time', None)
2241 limit = rate_status.get('remaining_hits', None)
2242
2243 if reset_time and limit:
2244 # put the reset time into a datetime object
2245 reset = datetime.datetime(*rfc822.parsedate(reset_time)[:7])
2246
2247 # find the difference in time between now and the reset time + 1 hour
2248 delta = reset + datetime.timedelta(hours=1) - datetime.datetime.utcnow()
2249
2250 # determine the minimum number of seconds allowed as a regular interval
2251 max_frequency = int(delta.seconds / limit)
2252
2253 # return the number of seconds
2254 return max_frequency
2255
2256 return 0
2257
2258 def _BuildUrl(self, url, path_elements=None, extra_params=None):
2259 # Break url into consituent parts
2260 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
2261
2262 # Add any additional path elements to the path
2263 if path_elements:
2264 # Filter out the path elements that have a value of None
2265 p = [i for i in path_elements if i]
2266 if not path.endswith('/'):
2267 path += '/'
2268 path += '/'.join(p)
2269
2270 # Add any additional query parameters to the query string
2271 if extra_params and len(extra_params) > 0:
2272 extra_query = self._EncodeParameters(extra_params)
2273 # Add it to the existing query
2274 if query:
2275 query += '&' + extra_query
2276 else:
2277 query = extra_query
2278
2279 # Return the rebuilt URL
2280 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
2281
2282 def _InitializeRequestHeaders(self, request_headers):
2283 if request_headers:
2284 self._request_headers = request_headers
2285 else:
2286 self._request_headers = {}
2287
2288 def _InitializeUserAgent(self):
2289 user_agent = 'Python-urllib/%s (python-twitter/%s)' % \
2290 (self._urllib.__version__, __version__)
2291 self.SetUserAgent(user_agent)
2292
2293 def _InitializeDefaultParameters(self):
2294 self._default_params = {}
2295
2296 def _AddAuthorizationHeader(self, username, password):
2297 if username and password:
2298 basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1]
2299 self._request_headers['Authorization'] = 'Basic %s' % basic_auth
2300
2301 def _RemoveAuthorizationHeader(self):
2302 if self._request_headers and 'Authorization' in self._request_headers:
2303 del self._request_headers['Authorization']
2304
2305 def _DecompressGzippedResponse(self, response):
2306 raw_data = response.read()
2307 if response.headers.get('content-encoding', None) == 'gzip':
2308 url_data = gzip.GzipFile(fileobj=StringIO.StringIO(raw_data)).read()
2309 else:
2310 url_data = raw_data
2311 return url_data
2312
2313 def _GetOpener(self, url, username=None, password=None):
2314 if username and password:
2315 self._AddAuthorizationHeader(username, password)
2316 handler = self._urllib.HTTPBasicAuthHandler()
2317 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
2318 handler.add_password(Api._API_REALM, netloc, username, password)
2319 opener = self._urllib.build_opener(handler)
2320 else:
2321 opener = self._urllib.build_opener()
2322 opener.addheaders = self._request_headers.items()
2323 return opener
2324
2325 def _Encode(self, s):
2326 if self._input_encoding:
2327 return unicode(s, self._input_encoding).encode('utf-8')
2328 else:
2329 return unicode(s).encode('utf-8')
2330
2331 def _EncodeParameters(self, parameters):
2332 '''Return a string in key=value&key=value form
2333
2334 Values of None are not included in the output string.
2335
2336 Args:
2337 parameters:
2338 A dict of (key, value) tuples, where value is encoded as
2339 specified by self._encoding
2340 Returns:
2341 A URL-encoded string in "key=value&key=value" form
2342 '''
2343 if parameters is None:
2344 return None
2345 else:
2346 return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
2347
2348 def _EncodePostData(self, post_data):
2349 '''Return a string in key=value&key=value form
2350
2351 Values are assumed to be encoded in the format specified by self._encoding,
2352 and are subsequently URL encoded.
2353
2354 Args:
2355 post_data:
2356 A dict of (key, value) tuples, where value is encoded as
2357 specified by self._encoding
2358 Returns:
2359 A URL-encoded string in "key=value&key=value" form
2360 '''
2361 if post_data is None:
2362 return None
2363 else:
2364 return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
2365
2366 def _CheckForTwitterError(self, data):
2367 """Raises a TwitterError if twitter returns an error message.
2368
2369 Args:
2370 data: A python dict created from the Twitter json response
2371 Raises:
2372 TwitterError wrapping the twitter error message if one exists.
2373 """
2374 # Twitter errors are relatively unlikely, so it is faster
2375 # to check first, rather than try and catch the exception
2376 if 'error' in data:
2377 raise TwitterError(data['error'])
2378
2379 def _FetchUrl(self,
2380 url,
2381 post_data=None,
2382 parameters=None,
2383 no_cache=None,
2384 use_gzip_compression=None):
2385 '''Fetch a URL, optionally caching for a specified time.
2386
2387 Args:
2388 url:
2389 The URL to retrieve
2390 post_data:
2391 A dict of (str, unicode) key/value pairs.
2392 If set, POST will be used.
2393 parameters:
2394 A dict whose key/value pairs should encoded and added
2395 to the query string. [optional]
2396 no_cache:
2397 If true, overrides the cache on the current request
2398 use_gzip_compression:
2399 If True, tells the server to gzip-compress the response.
2400 It does not apply to POST requests.
2401 Defaults to None, which will get the value to use from
2402 the instance variable self._use_gzip [optional]
2403
2404 Returns:
2405 A string containing the body of the response.
2406 '''
2407 # Build the extra parameters dict
2408 extra_params = {}
2409 if self._default_params:
2410 extra_params.update(self._default_params)
2411 if parameters:
2412 extra_params.update(parameters)
2413
2414 # Add key/value parameters to the query string of the url
2415 url = self._BuildUrl(url, extra_params=extra_params)
2416
2417 # Get a url opener that can handle basic auth
2418 opener = self._GetOpener(url, username=self._username, password=self._password)
2419
2420 if use_gzip_compression is None:
2421 use_gzip = self._use_gzip
2422 else:
2423 use_gzip = use_gzip_compression
2424
2425 # Set up compression
2426 if use_gzip and not post_data:
2427 opener.addheaders.append(('Accept-Encoding', 'gzip'))
2428
2429 encoded_post_data = self._EncodePostData(post_data)
2430
2431 # Open and return the URL immediately if we're not going to cache
2432 if encoded_post_data or no_cache or not self._cache or not self._cache_timeout:
2433 response = opener.open(url, encoded_post_data)
2434 url_data = self._DecompressGzippedResponse(response)
2435 opener.close()
2436 else:
2437 # Unique keys are a combination of the url and the username
2438 if self._username:
2439 key = self._username + ':' + url
2440 else:
2441 key = url
2442
2443 # See if it has been cached before
2444 last_cached = self._cache.GetCachedTime(key)
2445
2446 # If the cached version is outdated then fetch another and store it
2447 if not last_cached or time.time() >= last_cached + self._cache_timeout:
2448 response = opener.open(url, encoded_post_data)
2449 url_data = self._DecompressGzippedResponse(response)
2450 opener.close()
2451 self._cache.Set(key, url_data)
2452 else:
2453 url_data = self._cache.Get(key)
2454
2455 # Always return the latest version
2456 return url_data
2457
2458
2459class _FileCacheError(Exception):
2460 '''Base exception class for FileCache related errors'''
2461
2462class _FileCache(object):
2463
2464 DEPTH = 3
2465
2466 def __init__(self,root_directory=None):
2467 self._InitializeRootDirectory(root_directory)
2468
2469 def Get(self,key):
2470 path = self._GetPath(key)
2471 if os.path.exists(path):
2472 return open(path).read()
2473 else:
2474 return None
2475
2476 def Set(self,key,data):
2477 path = self._GetPath(key)
2478 directory = os.path.dirname(path)
2479 if not os.path.exists(directory):
2480 os.makedirs(directory)
2481 if not os.path.isdir(directory):
2482 raise _FileCacheError('%s exists but is not a directory' % directory)
2483 temp_fd, temp_path = tempfile.mkstemp()
2484 temp_fp = os.fdopen(temp_fd, 'w')
2485 temp_fp.write(data)
2486 temp_fp.close()
2487 if not path.startswith(self._root_directory):
2488 raise _FileCacheError('%s does not appear to live under %s' %
2489 (path, self._root_directory))
2490 if os.path.exists(path):
2491 os.remove(path)
2492 os.rename(temp_path, path)
2493
2494 def Remove(self,key):
2495 path = self._GetPath(key)
2496 if not path.startswith(self._root_directory):
2497 raise _FileCacheError('%s does not appear to live under %s' %
2498 (path, self._root_directory ))
2499 if os.path.exists(path):
2500 os.remove(path)
2501
2502 def GetCachedTime(self,key):
2503 path = self._GetPath(key)
2504 if os.path.exists(path):
2505 return os.path.getmtime(path)
2506 else:
2507 return None
2508
2509 def _GetUsername(self):
2510 '''Attempt to find the username in a cross-platform fashion.'''
2511 try:
2512 return os.getenv('USER') or \
2513 os.getenv('LOGNAME') or \
2514 os.getenv('USERNAME') or \
2515 os.getlogin() or \
2516 'nobody'
2517 except (IOError, OSError), e:
2518 return 'nobody'
2519
2520 def _GetTmpCachePath(self):
2521 username = self._GetUsername()
2522 cache_directory = 'python.cache_' + username
2523 return os.path.join(tempfile.gettempdir(), cache_directory)
2524
2525 def _InitializeRootDirectory(self, root_directory):
2526 if not root_directory:
2527 root_directory = self._GetTmpCachePath()
2528 root_directory = os.path.abspath(root_directory)
2529 if not os.path.exists(root_directory):
2530 os.mkdir(root_directory)
2531 if not os.path.isdir(root_directory):
2532 raise _FileCacheError('%s exists but is not a directory' %
2533 root_directory)
2534 self._root_directory = root_directory
2535
2536 def _GetPath(self,key):
2537 try:
2538 hashed_key = md5(key).hexdigest()
2539 except TypeError:
2540 hashed_key = md5.new(key).hexdigest()
2541
2542 return os.path.join(self._root_directory,
2543 self._GetPrefix(hashed_key),
2544 hashed_key)
2545
2546 def _GetPrefix(self,hashed_key):
2547 return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])
02548
=== added file 'data/icons/hicolor/scalable/apps/backend_identica.png'
1Binary files data/icons/hicolor/scalable/apps/backend_identica.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_identica.png 2010-08-25 16:29:48 +0000 differ2549Binary files data/icons/hicolor/scalable/apps/backend_identica.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_identica.png 2010-08-25 16:29:48 +0000 differ

Subscribers

People subscribed via source and target branches

to status/vote changes: