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