xitiomet is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

xitiomet / python-twitter (fork of saiyr / python-twitter)

Mostly the same, but i added support for Mentions http://apiwiki.twitter.com/Twitter-REST-API-Met...

Clone this repository (size: 393.3 KB): HTTPS / SSH
hg clone https://bitbucket.org/xitiomet/python-twitter
hg clone ssh://hg@bitbucket.org/xitiomet/python-twitter

python-twitter / twitter.py

commit
737a5a105dcf
parent
23adb15c0a18
branch
dclinton

Added the builder for the search Results object.

1
fc159c49c080
#!/usr/bin/python2.4
2
4805aee792f0
#
3
4805aee792f0
# Copyright 2007 Google Inc. All Rights Reserved.
4
fceb5d2374d2
#
5
fceb5d2374d2
# Licensed under the Apache License, Version 2.0 (the "License");
6
fceb5d2374d2
# you may not use this file except in compliance with the License.
7
fceb5d2374d2
# You may obtain a copy of the License at
8
fceb5d2374d2
#
9
fceb5d2374d2
#     http://www.apache.org/licenses/LICENSE-2.0
10
fceb5d2374d2
#
11
fceb5d2374d2
# Unless required by applicable law or agreed to in writing, software
12
fceb5d2374d2
# distributed under the License is distributed on an "AS IS" BASIS,
13
fceb5d2374d2
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
fceb5d2374d2
# See the License for the specific language governing permissions and
15
fceb5d2374d2
# limitations under the License.
16
4805aee792f0
17
4805aee792f0
'''A library that provides a python interface to the Twitter API'''
18
4805aee792f0
19
4805aee792f0
__author__ = 'dewitt@google.com'
20
8aaa14d8c799
__version__ = '0.6-devel'
21
4805aee792f0
22
4805aee792f0
23
f73c99cf9b51
import base64
24
3a4752d10bb7
import calendar
25
4805aee792f0
import os
26
3a4752d10bb7
import rfc822
27
4805aee792f0
import simplejson
28
4dfd36cb761d
import sys
29
4805aee792f0
import tempfile
30
81cef10d2991
import textwrap
31
4805aee792f0
import time
32
4805aee792f0
import urllib
33
4805aee792f0
import urllib2
34
4805aee792f0
import urlparse
35
0ef37898cd2b
36
c05ccbbb1d5c
import twitter_pb2
37
c05ccbbb1d5c
38
a58f4a7ad991
try:
39
c05ccbbb1d5c
  from hashlib import md5
40
a58f4a7ad991
except ImportError:
41
c05ccbbb1d5c
  from md5 import md5
42
a58f4a7ad991
43
4805aee792f0
44
81cef10d2991
CHARACTER_LIMIT = 140
45
81cef10d2991
46
81cef10d2991
47
4805aee792f0
class TwitterError(Exception):
48
4805aee792f0
  '''Base class for Twitter errors'''
49
2367f0c7822c
  
50
2367f0c7822c
  @property
51
2367f0c7822c
  def message(self):
52
2367f0c7822c
    '''Returns the first argument used to construct this error.'''
53
2367f0c7822c
    return self.args[0]
54
4805aee792f0
55
4805aee792f0
56
c05ccbbb1d5c
def _CopyProperty(source, destination, name, destination_name=None):
57
c05ccbbb1d5c
  '''Optionally copies a property from source to destination.
58
4805aee792f0
59
c05ccbbb1d5c
  Properties will be copied if and only if they appear in the dict
60
c05ccbbb1d5c
  and are not None.
61
4805aee792f0
62
c05ccbbb1d5c
  Args:
63
c05ccbbb1d5c
    source: A python dict
64
c05ccbbb1d5c
    destination: A python object with a property accessor
65
c05ccbbb1d5c
    name: The name of the source property
66
c05ccbbb1d5c
    destination_name: The name of the destination property, if different from name
67
4805aee792f0
  '''
68
c05ccbbb1d5c
  if destination_name is None:
69
c05ccbbb1d5c
    destination_name = name
70
c05ccbbb1d5c
  try:
71
c05ccbbb1d5c
    value = source[name]
72
c05ccbbb1d5c
    if value is not None:
73
c05ccbbb1d5c
      # If the destination_name has one or more '.' in it
74
c05ccbbb1d5c
      # traverse the destination object downward to find
75
c05ccbbb1d5c
      # the actual destination object and property name
76
c05ccbbb1d5c
      parts = destination_name.split('.')
77
c05ccbbb1d5c
      for part in parts[0:-1]:
78
c05ccbbb1d5c
        destination = getattr(destination, part)
79
c05ccbbb1d5c
        destination_name = parts[-1]
80
c05ccbbb1d5c
      setattr(destination, destination_name, value)
81
c05ccbbb1d5c
  except KeyError:
82
c05ccbbb1d5c
    # source property not found
83
c05ccbbb1d5c
    pass
84
4805aee792f0
85
4805aee792f0
86
c05ccbbb1d5c
def NewStatusFromJsonDict(data):
87
c05ccbbb1d5c
  '''Create a new Status instance based on a JSON dict.
88
17f566981b41
89
c05ccbbb1d5c
  Args:
90
c05ccbbb1d5c
    data: A JSON dict, as parsed from a twitter API response
91
c05ccbbb1d5c
  Returns:
92
c05ccbbb1d5c
    A Status instance
93
c05ccbbb1d5c
  '''
94
c05ccbbb1d5c
  status = twitter_pb2.Status()
95
c05ccbbb1d5c
  _CopyProperty(data, status, 'created_at')
96
c05ccbbb1d5c
  _CopyProperty(data, status, 'favorited')
97
c05ccbbb1d5c
  _CopyProperty(data, status, 'id')
98
c05ccbbb1d5c
  _CopyProperty(data, status, 'text')
99
c05ccbbb1d5c
  _CopyProperty(data, status, 'in_reply_to_screen_name')
100
c05ccbbb1d5c
  _CopyProperty(data, status, 'in_reply_to_user_id')
101
c05ccbbb1d5c
  _CopyProperty(data, status, 'in_reply_to_status_id')
102
c05ccbbb1d5c
  _CopyProperty(data, status, 'truncated')
103
c05ccbbb1d5c
  _CopyProperty(data, status, 'source')
104
c05ccbbb1d5c
  if 'user' in data:
105
c05ccbbb1d5c
    status.user.CopyFrom(NewUserFromJsonDict(data['user']))
106
c05ccbbb1d5c
  return status
107
c05ccbbb1d5c
108
c05ccbbb1d5c
109
c05ccbbb1d5c
def ComputeCreatedAtInSeconds(status):
110
c05ccbbb1d5c
  '''Returns the number of seconds past the epoch in the current timezone.
111
c05ccbbb1d5c
112
c05ccbbb1d5c
  Args:
113
c05ccbbb1d5c
    status: A status instance
114
c05ccbbb1d5c
  Returns:
115
c05ccbbb1d5c
    The number of seconds since the status was created.
116
c05ccbbb1d5c
  '''
117
c05ccbbb1d5c
  return calendar.timegm(rfc822.parsedate(status.created_at))
118
c05ccbbb1d5c
119
c05ccbbb1d5c
120
c05ccbbb1d5c
def ComputeRelativeCreatedAt(status, now=None):
121
c05ccbbb1d5c
  '''Get a human redable string representing the posting time
122
c05ccbbb1d5c
123
c05ccbbb1d5c
  Args:
124
c05ccbbb1d5c
    status: A status instance
125
c05ccbbb1d5c
    now: 
126
c05ccbbb1d5c
      The current time, if the client choses to set it.  Defaults 
127
c05ccbbb1d5c
      to the wall clock time.
128
c05ccbbb1d5c
  Returns:
129
c05ccbbb1d5c
    A human readable string representing the posting time
130
4805aee792f0
    '''
131
c05ccbbb1d5c
  if now is None:
132
c05ccbbb1d5c
    now = time.time()
133
c05ccbbb1d5c
  fudge = 1.25
134
c05ccbbb1d5c
  created_at_in_seconds = ComputeCreatedAtInSeconds(status)
135
c05ccbbb1d5c
  delta = int(now) - int(created_at_in_seconds)
136
c05ccbbb1d5c
  if delta < (1 * fudge):
137
c05ccbbb1d5c
    return 'about a second ago'
138
c05ccbbb1d5c
  elif delta < (60 * (1/fudge)):
139
c05ccbbb1d5c
    return 'about %d seconds ago' % (delta)
140
c05ccbbb1d5c
  elif delta < (60 * fudge):
141
c05ccbbb1d5c
    return 'about a minute ago'
142
c05ccbbb1d5c
  elif delta < (60 * 60 * (1/fudge)):
143
c05ccbbb1d5c
    return 'about %d minutes ago' % (delta / 60)
144
c05ccbbb1d5c
  elif delta < (60 * 60 * fudge):
145
c05ccbbb1d5c
    return 'about an hour ago'
146
c05ccbbb1d5c
  elif delta < (60 * 60 * 24 * (1/fudge)):
147
c05ccbbb1d5c
    return 'about %d hours ago' % (delta / (60 * 60))
148
c05ccbbb1d5c
  elif delta < (60 * 60 * 24 * fudge):
149
c05ccbbb1d5c
    return 'about a day ago'
150
c05ccbbb1d5c
  else:
151
c05ccbbb1d5c
    return 'about %d days ago' % (delta / (60 * 60 * 24))
152
4805aee792f0
153
4805aee792f0
154
c05ccbbb1d5c
def NewUserFromJsonDict(data):
155
c05ccbbb1d5c
  '''Create a new User instance based on a JSON dict.
156
4805aee792f0
157
c05ccbbb1d5c
  Args:
158
c05ccbbb1d5c
    data: A JSON dict, as parsed from a twitter API response
159
c05ccbbb1d5c
  Returns:
160
c05ccbbb1d5c
    A User instance
161
c05ccbbb1d5c
  '''
162
c05ccbbb1d5c
  user = twitter_pb2.User()
163
c05ccbbb1d5c
  _CopyProperty(data, user, 'id')
164
c05ccbbb1d5c
  _CopyProperty(data, user, 'name')
165
c05ccbbb1d5c
  _CopyProperty(data, user, 'screen_name')
166
c05ccbbb1d5c
  _CopyProperty(data, user, 'location')
167
c05ccbbb1d5c
  _CopyProperty(data, user, 'description')
168
c05ccbbb1d5c
  _CopyProperty(data, user, 'statuses_count')
169
c05ccbbb1d5c
  _CopyProperty(data, user, 'followers_count')
170
c05ccbbb1d5c
  _CopyProperty(data, user, 'favourites_count', 'favorites_count')
171
c05ccbbb1d5c
  _CopyProperty(data, user, 'friends_count')
172
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_image_url', 'profile.image_url')
173
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_background_tile', 'profile.background_tile')
174
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_background_image_url', 'profile.background_image_url')
175
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_sidebar_fill_color', 'profile.sidebar_fill_color')
176
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_background_color', 'profile.background_color')
177
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_link_color', 'profile.link_color')
178
c05ccbbb1d5c
  _CopyProperty(data, user, 'profile_text_color', 'profile.text_color')
179
c05ccbbb1d5c
  _CopyProperty(data, user, 'protected')
180
c05ccbbb1d5c
  _CopyProperty(data, user, 'utc_offset')
181
c05ccbbb1d5c
  _CopyProperty(data, user, 'time_zone')
182
c05ccbbb1d5c
  _CopyProperty(data, user, 'url')
183
c05ccbbb1d5c
  if 'status' in data:
184
c05ccbbb1d5c
    user.status.CopyFrom(NewStatusFromJsonDict(data['status']))
185
c05ccbbb1d5c
  return user
186
4805aee792f0
187
4805aee792f0
188
c05ccbbb1d5c
def NewDirectMessageFromJsonDict(data):
189
c05ccbbb1d5c
  '''Create a new DirectMessage instance based on a JSON dict.
190
4805aee792f0
191
c05ccbbb1d5c
  Args:
192
c05ccbbb1d5c
    data: A JSON dict, as parsed from a twitter API response
193
c05ccbbb1d5c
  Returns:
194
c05ccbbb1d5c
    A DirectMessage instance
195
c05ccbbb1d5c
  '''
196
c05ccbbb1d5c
  direct_message = twitter_pb2.DirectMessage()
197
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'created_at')
198
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'recipient_id')
199
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'sender_id')
200
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'text')
201
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'sender_screen_name')
202
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'id')
203
c05ccbbb1d5c
  _CopyProperty(data, direct_message, 'recipient_screen_name')
204
c05ccbbb1d5c
  return direct_message
205
17f566981b41
206
4805aee792f0
207
737a5a105dcf
def NewResultsFromJsonDict(data):
208
737a5a105dcf
  '''Create a new Results instance based on a JSON dict.
209
737a5a105dcf
210
737a5a105dcf
  Args:
211
737a5a105dcf
    data: A JSON dict, as parsed from a twitter API response
212
737a5a105dcf
  Returns:
213
737a5a105dcf
    A Results instance
214
737a5a105dcf
  '''
215
737a5a105dcf
  results = twitter_pb2.Results()
216
737a5a105dcf
  _CopyProperty(data, results, 'completed_in')
217
737a5a105dcf
  _CopyProperty(data, results, 'max_id')
218
737a5a105dcf
  _CopyProperty(data, results, 'next_page')
219
737a5a105dcf
  _CopyProperty(data, results, 'page')
220
737a5a105dcf
  _CopyProperty(data, results, 'query')
221
737a5a105dcf
  _CopyProperty(data, results, 'refresh_url')
222
737a5a105dcf
  _CopyProperty(data, results, 'since_id')
223
737a5a105dcf
  _CopyProperty(data, results, 'results_per_page')
224
737a5a105dcf
  if 'results' in data:
225
737a5a105dcf
    for result_data in data['results']:
226
737a5a105dcf
      result = results.results.add()
227
737a5a105dcf
      result.CopyFrom(NewResultFromJsonDict(result_data))
228
737a5a105dcf
  return results
229
737a5a105dcf
230
737a5a105dcf
231
737a5a105dcf
def NewResultFromJsonDict(data):
232
737a5a105dcf
  '''Create a new Result instance based on a JSON dict.
233
737a5a105dcf
234
737a5a105dcf
  Args:
235
737a5a105dcf
    data: A JSON dict, as parsed from a twitter API response
236
737a5a105dcf
  Returns:
237
737a5a105dcf
    A Result instance
238
737a5a105dcf
  '''
239
737a5a105dcf
  result = twitter_pb2.Results.Result()
240
737a5a105dcf
  _CopyProperty(data, result, 'created_at')
241
737a5a105dcf
  _CopyProperty(data, result, 'from_user')
242
737a5a105dcf
  _CopyProperty(data, result, 'from_user_id')
243
737a5a105dcf
  _CopyProperty(data, result, 'id')
244
737a5a105dcf
  _CopyProperty(data, result, 'iso_language_code')
245
737a5a105dcf
  _CopyProperty(data, result, 'profile_image_url')
246
737a5a105dcf
  _CopyProperty(data, result, 'source')
247
737a5a105dcf
  _CopyProperty(data, result, 'text')
248
737a5a105dcf
  _CopyProperty(data, result, 'to_user')
249
737a5a105dcf
  _CopyProperty(data, result, 'to_user_id')
250
737a5a105dcf
  return result
251
737a5a105dcf
252
737a5a105dcf
253
4805aee792f0
class Api(object):
254
4805aee792f0
  '''A python interface into the Twitter API
255
4805aee792f0
256
4805aee792f0
  By default, the Api caches results for 1 minute.
257
17f566981b41
258
4805aee792f0
  Example usage:
259
4805aee792f0
260
ee84714ea665
    To create an instance of the twitter.Api class, with no authentication:
261
4805aee792f0
262
e67a0a1b407c
      >>> import twitter
263
e67a0a1b407c
      >>> api = twitter.Api()
264
4805aee792f0
265
e67a0a1b407c
    To fetch the most recently posted public twitter status messages:
266
4805aee792f0
267
e67a0a1b407c
      >>> statuses = api.GetPublicTimeline()
268
e67a0a1b407c
      >>> print [s.user.name for s in statuses]
269
e67a0a1b407c
      [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #...
270
4805aee792f0
271
3e87a5de4fc0
    To fetch a single user's public status messages, where "user" is either
272
3e87a5de4fc0
    a Twitter "short name" or their user id.
273
4805aee792f0
274
3e87a5de4fc0
      >>> statuses = api.GetUserTimeline(user)
275
e67a0a1b407c
      >>> print [s.text for s in statuses]
276
4805aee792f0
277
ee84714ea665
    To use authentication, instantiate the twitter.Api class with a
278
ee84714ea665
    username and password:
279
e67a0a1b407c
280
ee84714ea665
      >>> api = twitter.Api(username='twitter user', password='twitter pass')
281
32a0266e88a8
282
ee84714ea665
    To fetch your friends (after being authenticated):
283
ee84714ea665
284
ee84714ea665
      >>> users = api.GetFriends()
285
e67a0a1b407c
      >>> print [u.name for u in users]
286
e67a0a1b407c
287
ee84714ea665
    To post a twitter status message (after being authenticated):
288
17f566981b41
289
ee84714ea665
      >>> status = api.PostUpdate('I love python-twitter!')
290
e67a0a1b407c
      >>> print status.text
291
e67a0a1b407c
      I love python-twitter!
292
b9effe73b630
293
b9effe73b630
    There are many other methods, including:
294
b9effe73b630
295
81cef10d2991
      >>> api.PostUpdates(status)
296
b9effe73b630
      >>> api.PostDirectMessage(user, text)
297
b9effe73b630
      >>> api.GetUser(user)
298
b9effe73b630
      >>> api.GetReplies()
299
b9effe73b630
      >>> api.GetUserTimeline(user)
300
b9effe73b630
      >>> api.GetStatus(id)
301
6c1724c3654d
      >>> api.DestroyStatus(id)
302
b9effe73b630
      >>> api.GetFriendsTimeline(user)
303
b9effe73b630
      >>> api.GetFriends(user)
304
b9effe73b630
      >>> api.GetFollowers()
305
b9effe73b630
      >>> api.GetFeatured()
306
b9effe73b630
      >>> api.GetDirectMessages()
307
b9effe73b630
      >>> api.PostDirectMessage(user, text)
308
b9effe73b630
      >>> api.DestroyDirectMessage(id)
309
b9effe73b630
      >>> api.DestroyFriendship(user)
310
b9effe73b630
      >>> api.CreateFriendship(user)
311
e2f8ed587060
      >>> api.GetUserByEmail(email)
312
4805aee792f0
  '''
313
4805aee792f0
314
17f566981b41
  DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute
315
4805aee792f0
316
4805aee792f0
  _API_REALM = 'Twitter API'
317
17f566981b41
318
90cd841771cf
  def __init__(self,
319
90cd841771cf
               username=None,
320
90cd841771cf
               password=None,
321
90cd841771cf
               input_encoding=None,
322
90cd841771cf
               request_headers=None):
323
ee84714ea665
    '''Instantiate a new twitter.Api object.
324
ee84714ea665
325
ee84714ea665
    Args:
326
ee84714ea665
      username: The username of the twitter account.  [optional]
327
ee84714ea665
      password: The password for the twitter account. [optional]
328
90cd841771cf
      input_encoding: The encoding used to encode input strings. [optional]
329
90cd841771cf
      request_header: A dictionary of additional HTTP request headers. [optional]
330
ee84714ea665
    '''
331
4805aee792f0
    self._cache = _FileCache()
332
4805aee792f0
    self._urllib = urllib2
333
4805aee792f0
    self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
334
90cd841771cf
    self._InitializeRequestHeaders(request_headers)
335
90cd841771cf
    self._InitializeUserAgent()
336
cb10f131e83f
    self._InitializeDefaultParameters()
337
4dfd36cb761d
    self._input_encoding = input_encoding
338
ee84714ea665
    self.SetCredentials(username, password)
339
4805aee792f0
340
ee84714ea665
  def GetPublicTimeline(self, since_id=None):
341
c05ccbbb1d5c
    '''Fetch the sequnce of public Status message for all users.
342
4805aee792f0
343
ee84714ea665
    Args:
344
32a0266e88a8
      since_id:
345
ee84714ea665
        Returns only public statuses with an ID greater than (that is,
346
ee84714ea665
        more recent than) the specified ID. [Optional]
347
32a0266e88a8
348
4805aee792f0
    Returns:
349
c05ccbbb1d5c
      An sequence of Status instances, one for each message
350
4805aee792f0
    '''
351
ee84714ea665
    parameters = {}
352
ee84714ea665
    if since_id:
353
ee84714ea665
      parameters['since_id'] = since_id
354
4805aee792f0
    url = 'http://twitter.com/statuses/public_timeline.json'
355
ee84714ea665
    json = self._FetchUrl(url,  parameters=parameters)
356
4805aee792f0
    data = simplejson.loads(json)
357
265fcf2a1e01
    self._CheckForTwitterError(data)
358
c05ccbbb1d5c
    return [NewStatusFromJsonDict(x) for x in data]
359
4805aee792f0
360
11710035d0b9
  def GetFriendsTimeline(self,
361
11710035d0b9
                         user=None,
362
11710035d0b9
                         count=None,
363
11710035d0b9
                         since=None, 
364
11710035d0b9
                         since_id=None):
365
c05ccbbb1d5c
    '''Fetch the sequence of Status messages for a user's friends
366
ee84714ea665
367
ee84714ea665
    The twitter.Api instance must be authenticated if the user is private.
368
ee84714ea665
369
ee84714ea665
    Args:
370
32a0266e88a8
      user:
371
ee84714ea665
        Specifies the ID or screen name of the user for whom to return
372
32a0266e88a8
        the friends_timeline.  If unspecified, the username and password
373
11710035d0b9
        must be set in the twitter.Api instance.  [Optional]
374
11710035d0b9
      count: 
375
11710035d0b9
        Specifies the number of statuses to retrieve. May not be
376
11710035d0b9
        greater than 200. [Optional]
377
ee84714ea665
      since:
378
ee84714ea665
        Narrows the returned results to just those statuses created
379
11710035d0b9
        after the specified HTTP-formatted date. [Optional]
380
806829c7dc10
      since_id:
381
806829c7dc10
        Returns only public statuses with an ID greater than (that is,
382
806829c7dc10
        more recent than) the specified ID. [Optional]
383
ee84714ea665
384
ee84714ea665
    Returns:
385
c05ccbbb1d5c
      A sequence of Status instances, one for each message
386
ee84714ea665
    '''
387
ee84714ea665
    if user:
388
ee84714ea665
      url = 'http://twitter.com/statuses/friends_timeline/%s.json' % user
389
ee84714ea665
    elif not user and not self._username:
390
ee84714ea665
      raise TwitterError("User must be specified if API is not authenticated.")
391
32a0266e88a8
    else:
392
ee84714ea665
      url = 'http://twitter.com/statuses/friends_timeline.json'
393
ee84714ea665
    parameters = {}
394
11710035d0b9
    if count is not None:
395
11710035d0b9
      try:
396
11710035d0b9
        if int(count) > 200:
397
11710035d0b9
          raise TwitterError("'count' may not be greater than 200")
398
11710035d0b9
      except ValueError:
399
11710035d0b9
        raise TwitterError("'count' must be an integer")
400
11710035d0b9
      parameters['count'] = count
401
ee84714ea665
    if since:
402
ee84714ea665
      parameters['since'] = since
403
806829c7dc10
    if since_id:
404
806829c7dc10
      parameters['since_id'] = since_id
405
ee84714ea665
    json = self._FetchUrl(url, parameters=parameters)
406
ee84714ea665
    data = simplejson.loads(json)
407
265fcf2a1e01
    self._CheckForTwitterError(data)
408
c05ccbbb1d5c
    return [NewStatusFromJsonDict(x) for x in data]
409
ee84714ea665
410
806829c7dc10
  def GetUserTimeline(self, user=None, count=None, since=None, since_id=None):
411
c05ccbbb1d5c
    '''Fetch the sequence of public Status messages for a single user.
412
4805aee792f0
413
ee84714ea665
    The twitter.Api instance must be authenticated if the user is private.
414
ee84714ea665
415
4805aee792f0
    Args:
416
e565d3a31699
      user:
417
32a0266e88a8
        either the username (short_name) or id of the user to retrieve.  If
418
ee84714ea665
        not specified, then the current authenticated user is used. [optional]
419
ee84714ea665
      count: the number of status messages to retrieve [optional]
420
ee84714ea665
      since:
421
ee84714ea665
        Narrows the returned results to just those statuses created
422
ee84714ea665
        after the specified HTTP-formatted date. [optional]
423
806829c7dc10
      since_id:
424
806829c7dc10
        Returns only public statuses with an ID greater than (that is,
425
806829c7dc10
        more recent than) the specified ID. [Optional]
426
4805aee792f0
427
4805aee792f0
    Returns:
428
c05ccbbb1d5c
      A sequence of Status instances, one for each message up to count
429
4805aee792f0
    '''
430
4805aee792f0
    try:
431
4805aee792f0
      if count:
432
4805aee792f0
        int(count)
433
4805aee792f0
    except:
434
4805aee792f0
      raise TwitterError("Count must be an integer")
435
ee84714ea665
    parameters = {}
436
4805aee792f0
    if count:
437
ee84714ea665
      parameters['count'] = count
438
ee84714ea665
    if since:
439
ee84714ea665
      parameters['since'] = since
440
806829c7dc10
    if since_id:
441
806829c7dc10
      parameters['since_id'] = since_id
442
ee84714ea665
    if user:
443
ee84714ea665
      url = 'http://twitter.com/statuses/user_timeline/%s.json' % user
444
ee84714ea665
    elif not user and not self._username:
445
ee84714ea665
      raise TwitterError("User must be specified if API is not authenticated.")
446
4805aee792f0
    else:
447
ee84714ea665
      url = 'http://twitter.com/statuses/user_timeline.json'
448
4805aee792f0
    json = self._FetchUrl(url, parameters=parameters)
449
4805aee792f0
    data = simplejson.loads(json)
450
265fcf2a1e01
    self._CheckForTwitterError(data)
451
c05ccbbb1d5c
    return [NewStatusFromJsonDict(x) for x in data]
452
32a0266e88a8
453
ee84714ea665
  def GetStatus(self, id):
454
ee84714ea665
    '''Returns a single status message.
455
4805aee792f0
456
ee84714ea665
    The twitter.Api instance must be authenticated if the status message is private.
457
4805aee792f0
458
4805aee792f0
    Args:
459
32a0266e88a8
      id: The numerical ID of the status you're trying to retrieve.
460
4805aee792f0
461
4805aee792f0
    Returns:
462
c05ccbbb1d5c
      A Status instance representing that status message
463
4805aee792f0
    '''
464
ee84714ea665
    try:
465
ee84714ea665
      if id:
466
ee84714ea665
        int(id)
467
ee84714ea665
    except:
468
ee84714ea665
      raise TwitterError("id must be an integer")
469
ee84714ea665
    url = 'http://twitter.com/statuses/show/%s.json' % id
470
ee84714ea665
    json = self._FetchUrl(url)
471
4805aee792f0
    data = simplejson.loads(json)
472
265fcf2a1e01
    self._CheckForTwitterError(data)
473
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
474
4805aee792f0
475
4da5ec4d173a
  def DestroyStatus(self, id):
476
4da5ec4d173a
    '''Destroys the status specified by the required ID parameter.
477
4da5ec4d173a
478
4da5ec4d173a
    The twitter.Api instance must be authenticated and thee
479
4da5ec4d173a
    authenticating user must be the author of the specified status.
480
4da5ec4d173a
481
4da5ec4d173a
    Args:
482
4da5ec4d173a
      id: The numerical ID of the status you're trying to destroy.
483
4da5ec4d173a
484
4da5ec4d173a
    Returns:
485
c05ccbbb1d5c
      A Status instance representing the destroyed status message
486
4da5ec4d173a
    '''
487
4da5ec4d173a
    try:
488
4da5ec4d173a
      if id:
489
4da5ec4d173a
        int(id)
490
4da5ec4d173a
    except:
491
4da5ec4d173a
      raise TwitterError("id must be an integer")
492
4da5ec4d173a
    url = 'http://twitter.com/statuses/destroy/%s.json' % id
493
4da5ec4d173a
    json = self._FetchUrl(url, post_data={})
494
4da5ec4d173a
    data = simplejson.loads(json)
495
265fcf2a1e01
    self._CheckForTwitterError(data)
496
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
497
4da5ec4d173a
498
81cef10d2991
  def PostUpdate(self, status, in_reply_to_status_id=None):
499
c42880b017af
    '''Post a twitter status message from the authenticated user.
500
ee84714ea665
501
ee84714ea665
    The twitter.Api instance must be authenticated.
502
4805aee792f0
503
4805aee792f0
    Args:
504
81cef10d2991
      status:
505
81cef10d2991
        The message text to be posted.  Must be less than or equal to
506
81cef10d2991
        140 characters.
507
71b5f9cae083
      in_reply_to_status_id:
508
71b5f9cae083
        The ID of an existing status that the status to be posted is
509
71b5f9cae083
        in reply to.  This implicitly sets the in_reply_to_user_id
510
71b5f9cae083
        attribute of the resulting status to the user ID of the
511
71b5f9cae083
        message being replied to.  Invalid/missing status IDs will be
512
71b5f9cae083
        ignored. [Optional]
513
4805aee792f0
    Returns:
514
c05ccbbb1d5c
      A Status instance representing the message posted.
515
4805aee792f0
    '''
516
ee84714ea665
    if not self._username:
517
ee84714ea665
      raise TwitterError("The twitter.Api instance must be authenticated.")
518
81cef10d2991
519
c42880b017af
    url = 'http://twitter.com/statuses/update.json'
520
81cef10d2991
521
81cef10d2991
    if len(status) > CHARACTER_LIMIT:
522
81cef10d2991
      raise TwitterError("Text must be less than or equal to %d characters. "
523
81cef10d2991
                         "Consider using PostUpdates." % CHARACTER_LIMIT)
524
81cef10d2991
525
81cef10d2991
    data = {'status': status}
526
71b5f9cae083
    if in_reply_to_status_id:
527
71b5f9cae083
      data['in_reply_to_status_id'] = in_reply_to_status_id
528
79429eb1b54d
    json = self._FetchUrl(url, post_data=data)
529
4805aee792f0
    data = simplejson.loads(json)
530
265fcf2a1e01
    self._CheckForTwitterError(data)
531
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
532
4805aee792f0
533
8bdda4c8187b
  def PostUpdates(self, status, continuation=None, **kwargs):
534
81cef10d2991
    '''Post one or more twitter status messages from the authenticated user.
535
81cef10d2991
536
81cef10d2991
    Unlike api.PostUpdate, this method will post multiple status updates
537
81cef10d2991
    if the message is longer than 140 characters.
538
81cef10d2991
539
81cef10d2991
    The twitter.Api instance must be authenticated.
540
81cef10d2991
541
81cef10d2991
    Args:
542
81cef10d2991
      status:
543
81cef10d2991
        The message text to be posted.  May be longer than 140 characters.
544
8bdda4c8187b
      continuation:
545
8bdda4c8187b
        The character string, if any, to be appended to all but the
546
8bdda4c8187b
        last message.  Note that Twitter strips trailing '...' strings
547
8bdda4c8187b
        from messages.  Consider using the unicode \u2026 character
548
8bdda4c8187b
        (horizontal ellipsis) instead. [Defaults to None]
549
81cef10d2991
      **kwargs:
550
81cef10d2991
        See api.PostUpdate for a list of accepted parameters.
551
81cef10d2991
    Returns:
552
c05ccbbb1d5c
      A list of Status instances representing the messages posted.
553
81cef10d2991
    '''
554
81cef10d2991
    results = list()
555
8bdda4c8187b
    if continuation is None:
556
8bdda4c8187b
      continuation = ''
557
8bdda4c8187b
    line_length = CHARACTER_LIMIT - len(continuation)
558
8bdda4c8187b
    lines = textwrap.wrap(status, line_length)
559
8bdda4c8187b
    for line in lines[0:-1]:
560
8bdda4c8187b
      results.append(self.PostUpdate(line + continuation, **kwargs))
561
8bdda4c8187b
    results.append(self.PostUpdate(lines[-1], **kwargs))
562
81cef10d2991
    return results
563
81cef10d2991
564
935cf080b638
  def GetReplies(self, since=None, since_id=None, page=None): 
565
ee84714ea665
    '''Get a sequence of status messages representing the 20 most recent
566
ee84714ea665
    replies (status updates prefixed with @username) to the authenticating
567
ee84714ea665
    user.
568
71b5f9cae083
569
a10f9a46dc3d
    Args:
570
935cf080b638
      page: 
571
a10f9a46dc3d
      since:
572
a10f9a46dc3d
        Narrows the returned results to just those statuses created
573
a10f9a46dc3d
        after the specified HTTP-formatted date. [optional]
574
a10f9a46dc3d
      since_id:
575
a10f9a46dc3d
        Returns only public statuses with an ID greater than (that is,
576
a10f9a46dc3d
        more recent than) the specified ID. [Optional]
577
ee84714ea665
578
ee84714ea665
    Returns:
579
c05ccbbb1d5c
      A sequence of Status instances, one for each reply to the user.
580
32a0266e88a8
    '''
581
ee84714ea665
    url = 'http://twitter.com/statuses/replies.json'
582
ee84714ea665
    if not self._username:
583
ee84714ea665
      raise TwitterError("The twitter.Api instance must be authenticated.")
584
a10f9a46dc3d
    parameters = {}
585
a10f9a46dc3d
    if since:
586
a10f9a46dc3d
      parameters['since'] = since
587
a10f9a46dc3d
    if since_id:
588
a10f9a46dc3d
      parameters['since_id'] = since_id
589
935cf080b638
    if page:
590
935cf080b638
      parameters['page'] = page
591
a10f9a46dc3d
    json = self._FetchUrl(url, parameters=parameters)
592
ee84714ea665
    data = simplejson.loads(json)
593
265fcf2a1e01
    self._CheckForTwitterError(data)
594
c05ccbbb1d5c
    return [NewStatusFromJsonDict(x) for x in data]
595
ee84714ea665
596
935cf080b638
  def GetFriends(self, user=None, page=None):
597
ee84714ea665
    '''Fetch the sequence of twitter.User instances, one for each friend.
598
ee84714ea665
599
ee84714ea665
    Args:
600
ee84714ea665
      user: the username or id of the user whose friends you are fetching.  If
601
ee84714ea665
      not specified, defaults to the authenticated user. [optional]
602
ee84714ea665
603
ee84714ea665
    The twitter.Api instance must be authenticated.
604
ee84714ea665
605
ee84714ea665
    Returns:
606
ee84714ea665
      A sequence of twitter.User instances, one for each friend
607
ee84714ea665
    '''
608
ee84714ea665
    if not self._username:
609
ee84714ea665
      raise TwitterError("twitter.Api instance must be authenticated")
610
ee84714ea665
    if user:
611
935cf080b638
      url = 'http://twitter.com/statuses/friends/%s.json' % user 
612
32a0266e88a8
    else:
613
ee84714ea665
      url = 'http://twitter.com/statuses/friends.json'
614
935cf080b638
    parameters = {}
615
935cf080b638
    if page:
616
935cf080b638
      parameters['page'] = page
617
935cf080b638
    json = self._FetchUrl(url, parameters=parameters)
618
ee84714ea665
    data = simplejson.loads(json)
619
265fcf2a1e01
    self._CheckForTwitterError(data)
620
c05ccbbb1d5c
    return [NewUserFromJsonDict(x) for x in data]
621
ee84714ea665
622
935cf080b638
  def GetFollowers(self, page=None):
623
ee84714ea665
    '''Fetch the sequence of twitter.User instances, one for each follower
624
ee84714ea665
625
ee84714ea665
    The twitter.Api instance must be authenticated.
626
ee84714ea665
627
ee84714ea665
    Returns:
628
ee84714ea665
      A sequence of twitter.User instances, one for each follower
629
ee84714ea665
    '''
630
ee84714ea665
    if not self._username:
631
ee84714ea665
      raise TwitterError("twitter.Api instance must be authenticated")
632
ee84714ea665
    url = 'http://twitter.com/statuses/followers.json'
633
935cf080b638
    parameters = {}
634
935cf080b638
    if page:
635
935cf080b638
      parameters['page'] = page
636
935cf080b638
    json = self._FetchUrl(url, parameters=parameters)
637
ee84714ea665
    data = simplejson.loads(json)
638
265fcf2a1e01
    self._CheckForTwitterError(data)
639
c05ccbbb1d5c
    return [NewUserFromJsonDict(x) for x in data]
640
ee84714ea665
641
e3638dafb13c
  def GetFeatured(self):
642
e3638dafb13c
    '''Fetch the sequence of twitter.User instances featured on twitter.com
643
e3638dafb13c
644
e3638dafb13c
    The twitter.Api instance must be authenticated.
645
e3638dafb13c
646
e3638dafb13c
    Returns:
647
e3638dafb13c
      A sequence of twitter.User instances
648
e3638dafb13c
    '''
649
e3638dafb13c
    url = 'http://twitter.com/statuses/featured.json'
650
e3638dafb13c
    json = self._FetchUrl(url)
651
e3638dafb13c
    data = simplejson.loads(json)
652
265fcf2a1e01
    self._CheckForTwitterError(data)
653
c05ccbbb1d5c
    return [NewUserFromJsonDict(x) for x in data]
654
e3638dafb13c
655
ee84714ea665
  def GetUser(self, user):
656
ee84714ea665
    '''Returns a single user.
657
ee84714ea665
658
ee84714ea665
    The twitter.Api instance must be authenticated.
659
ee84714ea665
660
ee84714ea665
    Args:
661
ee84714ea665
      user: The username or id of the user to retrieve.
662
ee84714ea665
663
ee84714ea665
    Returns:
664
ee84714ea665
      A twitter.User instance representing that user
665
ee84714ea665
    '''
666
ee84714ea665
    url = 'http://twitter.com/users/show/%s.json' % user
667
ee84714ea665
    json = self._FetchUrl(url)
668
ee84714ea665
    data = simplejson.loads(json)
669
265fcf2a1e01
    self._CheckForTwitterError(data)
670
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
671
ee84714ea665
672
935cf080b638
  def GetDirectMessages(self, since=None, since_id=None, page=None):
673
90377483d9fa
    '''Returns a list of the direct messages sent to the authenticating user.
674
90377483d9fa
675
90377483d9fa
    The twitter.Api instance must be authenticated.
676
90377483d9fa
677
32a0266e88a8
    Args:
678
90377483d9fa
      since:
679
90377483d9fa
        Narrows the returned results to just those statuses created
680
90377483d9fa
        after the specified HTTP-formatted date. [optional]
681
472ef0f2ea07
      since_id:
682
472ef0f2ea07
        Returns only public statuses with an ID greater than (that is,
683
472ef0f2ea07
        more recent than) the specified ID. [Optional]
684
90377483d9fa
685
90377483d9fa
    Returns:
686
32a0266e88a8
      A sequence of twitter.DirectMessage instances
687
90377483d9fa
    '''
688
90377483d9fa
    url = 'http://twitter.com/direct_messages.json'
689
90377483d9fa
    if not self._username:
690
90377483d9fa
      raise TwitterError("The twitter.Api instance must be authenticated.")
691
90377483d9fa
    parameters = {}
692
90377483d9fa
    if since:
693
90377483d9fa
      parameters['since'] = since
694
472ef0f2ea07
    if since_id:
695
472ef0f2ea07
      parameters['since_id'] = since_id
696
935cf080b638
    if page:
697
935cf080b638
      parameters['page'] = page 
698
90377483d9fa
    json = self._FetchUrl(url, parameters=parameters)
699
90377483d9fa
    data = simplejson.loads(json)
700
265fcf2a1e01
    self._CheckForTwitterError(data)
701
c05ccbbb1d5c
    return [NewDirectMessageFromJsonDict(x) for x in data]
702
32a0266e88a8
703
c42880b017af
  def PostDirectMessage(self, user, text):
704
c42880b017af
    '''Post a twitter direct message from the authenticated user
705
c42880b017af
706
c42880b017af
    The twitter.Api instance must be authenticated.
707
c42880b017af
708
c42880b017af
    Args:
709
c42880b017af
      user: The ID or screen name of the recipient user.
710
c42880b017af
      text: The message text to be posted.  Must be less than 140 characters.
711
c42880b017af
712
c42880b017af
    Returns:
713
32a0266e88a8
      A twitter.DirectMessage instance representing the message posted
714
c42880b017af
    '''
715
c42880b017af
    if not self._username:
716
c42880b017af
      raise TwitterError("The twitter.Api instance must be authenticated.")
717
c42880b017af
    url = 'http://twitter.com/direct_messages/new.json'
718
c42880b017af
    data = {'text': text, 'user': user}
719
79429eb1b54d
    json = self._FetchUrl(url, post_data=data)
720
32a0266e88a8
    data = simplejson.loads(json)
721
265fcf2a1e01
    self._CheckForTwitterError(data)
722
c05ccbbb1d5c
    return NewDirectMessageFromJsonDict(data)
723
32a0266e88a8
724
32a0266e88a8
  def DestroyDirectMessage(self, id):
725
32a0266e88a8
    '''Destroys the direct message specified in the required ID parameter.
726
32a0266e88a8
727
32a0266e88a8
    The twitter.Api instance must be authenticated, and the
728
32a0266e88a8
    authenticating user must be the recipient of the specified direct
729
32a0266e88a8
    message.
730
32a0266e88a8
731
32a0266e88a8
    Args:
732
32a0266e88a8
      id: The id of the direct message to be destroyed
733
32a0266e88a8
734
32a0266e88a8
    Returns:
735
32a0266e88a8
      A twitter.DirectMessage instance representing the message destroyed
736
32a0266e88a8
    '''
737
32a0266e88a8
    url = 'http://twitter.com/direct_messages/destroy/%s.json' % id
738
79429eb1b54d
    json = self._FetchUrl(url, post_data={})
739
c42880b017af
    data = simplejson.loads(json)
740
265fcf2a1e01
    self._CheckForTwitterError(data)
741
c05ccbbb1d5c
    return NewDirectMessageFromJsonDict(data)
742
90377483d9fa
743
79429eb1b54d
  def CreateFriendship(self, user):
744
79429eb1b54d
    '''Befriends the user specified in the user parameter as the authenticating user.
745
79429eb1b54d
746
79429eb1b54d
    The twitter.Api instance must be authenticated.
747
79429eb1b54d
748
79429eb1b54d
    Args:
749
79429eb1b54d
      The ID or screen name of the user to befriend.
750
79429eb1b54d
    Returns:
751
79429eb1b54d
      A twitter.User instance representing the befriended user.
752
79429eb1b54d
    '''
753
79429eb1b54d
    url = 'http://twitter.com/friendships/create/%s.json' % user
754
79429eb1b54d
    json = self._FetchUrl(url, post_data={})
755
79429eb1b54d
    data = simplejson.loads(json)
756
265fcf2a1e01
    self._CheckForTwitterError(data)
757
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
758
79429eb1b54d
759
79429eb1b54d
  def DestroyFriendship(self, user):
760
79429eb1b54d
    '''Discontinues friendship with the user specified in the user parameter.
761
79429eb1b54d
762
79429eb1b54d
    The twitter.Api instance must be authenticated.
763
79429eb1b54d
764
79429eb1b54d
    Args:
765
79429eb1b54d
      The ID or screen name of the user  with whom to discontinue friendship.
766
79429eb1b54d
    Returns:
767
79429eb1b54d
      A twitter.User instance representing the discontinued friend.
768
79429eb1b54d
    '''
769
79429eb1b54d
    url = 'http://twitter.com/friendships/destroy/%s.json' % user
770
79429eb1b54d
    json = self._FetchUrl(url, post_data={})
771
79429eb1b54d
    data = simplejson.loads(json)
772
265fcf2a1e01
    self._CheckForTwitterError(data)
773
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
774
79429eb1b54d
775
a1e65b8c9435
  def CreateFavorite(self, status):
776
a1e65b8c9435
    '''Favorites the status specified in the status parameter as the authenticating user.
777
a1e65b8c9435
    Returns the favorite status when successful.
778
a1e65b8c9435
779
a1e65b8c9435
    The twitter.Api instance must be authenticated.
780
a1e65b8c9435
781
a1e65b8c9435
    Args:
782
c05ccbbb1d5c
      The Status instance to mark as a favorite.
783
a1e65b8c9435
    Returns:
784
c05ccbbb1d5c
      A Status instance representing the newly-marked favorite.
785
a1e65b8c9435
    '''
786
a1e65b8c9435
    url = 'http://twitter.com/favorites/create/%s.json' % status.id
787
a1e65b8c9435
    json = self._FetchUrl(url, post_data={})
788
a1e65b8c9435
    data = simplejson.loads(json)
789
265fcf2a1e01
    self._CheckForTwitterError(data)
790
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
791
a1e65b8c9435
792
a1e65b8c9435
  def DestroyFavorite(self, status):
793
a1e65b8c9435
    '''Un-favorites the status specified in the ID parameter as the authenticating user.
794
a1e65b8c9435
    Returns the un-favorited status in the requested format when successful.
795
a1e65b8c9435
796
a1e65b8c9435
    The twitter.Api instance must be authenticated.
797
a1e65b8c9435
798
a1e65b8c9435
    Args:
799
c05ccbbb1d5c
      The Status to unmark as a favorite.
800
a1e65b8c9435
    Returns:
801
c05ccbbb1d5c
      A Status instance representing the newly-unmarked favorite.
802
a1e65b8c9435
    '''
803
a1e65b8c9435
    url = 'http://twitter.com/favorites/destroy/%s.json' % status.id
804
a1e65b8c9435
    json = self._FetchUrl(url, post_data={})
805
a1e65b8c9435
    data = simplejson.loads(json)
806
265fcf2a1e01
    self._CheckForTwitterError(data)
807
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
808
a1e65b8c9435
809
e2f8ed587060
  def GetUserByEmail(self, email):
810
e2f8ed587060
    '''Returns a single user by email address.
811
e2f8ed587060
812
e2f8ed587060
    Args:
813
e2f8ed587060
      email: The email of the user to retrieve.
814
e2f8ed587060
    Returns:
815
e2f8ed587060
      A twitter.User instance representing that user
816
e2f8ed587060
    '''
817
e2f8ed587060
    url = 'http://twitter.com/users/show.json?email=%s' % email
818
e2f8ed587060
    json = self._FetchUrl(url)
819
e2f8ed587060
    data = simplejson.loads(json)
820
265fcf2a1e01
    self._CheckForTwitterError(data)
821
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
822
e2f8ed587060
823
ee84714ea665
  def SetCredentials(self, username, password):
824
ee84714ea665
    '''Set the username and password for this instance
825
ee84714ea665
826
ee84714ea665
    Args:
827
ee84714ea665
      username: The twitter username.
828
ee84714ea665
      password: The twitter password.
829
ee84714ea665
    '''
830
ee84714ea665
    self._username = username
831
ee84714ea665
    self._password = password
832
ee84714ea665
833
ee84714ea665
  def ClearCredentials(self):
834
ee84714ea665
    '''Clear the username and password for this instance
835
ee84714ea665
    '''
836
ee84714ea665
    self._username = None
837
ee84714ea665
    self._password = None
838
4805aee792f0
839
4805aee792f0
  def SetCache(self, cache):
840
4805aee792f0
    '''Override the default cache.  Set to None to prevent caching.
841
4805aee792f0
842
4805aee792f0
    Args:
843
4805aee792f0
      cache: an instance that supports the same API as the  twitter._FileCache
844
4805aee792f0
    '''
845
4805aee792f0
    self._cache = cache
846
4805aee792f0
847
4805aee792f0
  def SetUrllib(self, urllib):
848
4805aee792f0
    '''Override the default urllib implementation.
849
4805aee792f0
850
4805aee792f0
    Args:
851
4805aee792f0
      urllib: an instance that supports the same API as the urllib2 module
852
4805aee792f0
    '''
853
4805aee792f0
    self._urllib = urllib
854
4805aee792f0
855
4805aee792f0
  def SetCacheTimeout(self, cache_timeout):
856
4805aee792f0
    '''Override the default cache timeout.
857
4805aee792f0
858
4805aee792f0
    Args:
859
4805aee792f0
      cache_timeout: time, in seconds, that responses should be reused.
860
4805aee792f0
    '''
861
4805aee792f0
    self._cache_timeout = cache_timeout
862
4805aee792f0
863
4805aee792f0
  def SetUserAgent(self, user_agent):
864
4805aee792f0
    '''Override the default user agent
865
4805aee792f0
866
4805aee792f0
    Args:
867
4805aee792f0
      user_agent: a string that should be send to the server as the User-agent
868
4805aee792f0
    '''
869
90cd841771cf
    self._request_headers['User-Agent'] = user_agent
870
90cd841771cf
871
90cd841771cf
  def SetXTwitterHeaders(self, client, url, version):
872
90cd841771cf
    '''Set the X-Twitter HTTP headers that will be sent to the server.
873
90cd841771cf
874
90cd841771cf
    Args:
875
90cd841771cf
      client:
876
90cd841771cf
         The client name as a string.  Will be sent to the server as
877
90cd841771cf
         the 'X-Twitter-Client' header.
878
90cd841771cf
      url:
879
90cd841771cf
         The URL of the meta.xml as a string.  Will be sent to the server
880
90cd841771cf
         as the 'X-Twitter-Client-URL' header.
881
90cd841771cf
      version:
882
90cd841771cf
         The client version as a string.  Will be sent to the server
883
90cd841771cf
         as the 'X-Twitter-Client-Version' header.
884
90cd841771cf
    '''
885
90cd841771cf
    self._request_headers['X-Twitter-Client'] = client
886
90cd841771cf
    self._request_headers['X-Twitter-Client-URL'] = url
887
90cd841771cf
    self._request_headers['X-Twitter-Client-Version'] = version
888
4805aee792f0
889
cb10f131e83f
  def SetSource(self, source):
890
cb10f131e83f
    '''Suggest the "from source" value to be displayed on the Twitter web site.
891
cb10f131e83f
892
cb10f131e83f
    The value of the 'source' parameter must be first recognized by
893
cb10f131e83f
    the Twitter server.  New source values are authorized on a case by
894
cb10f131e83f
    case basis by the Twitter development team.
895
cb10f131e83f
896
cb10f131e83f
    Args:
897
cb10f131e83f
      source:
898
cb10f131e83f
        The source name as a string.  Will be sent to the server as
899
cb10f131e83f
        the 'source' parameter.
900
cb10f131e83f
    '''
901
cb10f131e83f
    self._default_params['source'] = source
902
cb10f131e83f
903
4805aee792f0
  def _BuildUrl(self, url, path_elements=None, extra_params=None):
904
4805aee792f0
    # Break url into consituent parts
905
4805aee792f0
    (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
906
17f566981b41
907
4805aee792f0
    # Add any additional path elements to the path
908
4805aee792f0
    if path_elements:
909
4805aee792f0
      # Filter out the path elements that have a value of None
910
4805aee792f0
      p = [i for i in path_elements if i]
911
4805aee792f0
      if not path.endswith('/'):
912
4805aee792f0
        path += '/'
913
4805aee792f0
      path += '/'.join(p)
914
17f566981b41
915
4805aee792f0
    # Add any additional query parameters to the query string
916
4805aee792f0
    if extra_params and len(extra_params) > 0:
917
32a0266e88a8
      extra_query = self._EncodeParameters(extra_params)
918
b0bbd9fa96ce
      # Add it to the existing query
919
b0bbd9fa96ce
      if query:
920
b0bbd9fa96ce
        query += '&' + extra_query
921
b0bbd9fa96ce
      else:
922
b0bbd9fa96ce
        query = extra_query
923
17f566981b41
924
4805aee792f0
    # Return the rebuilt URL
925
4805aee792f0
    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
926
4805aee792f0
927
90cd841771cf
  def _InitializeRequestHeaders(self, request_headers):
928
90cd841771cf
    if request_headers:
929
90cd841771cf
      self._request_headers = request_headers
930
90cd841771cf
    else:
931
90cd841771cf
      self._request_headers = {}
932
90cd841771cf
933
90cd841771cf
  def _InitializeUserAgent(self):
934
90cd841771cf
    user_agent = 'Python-urllib/%s (python-twitter/%s)' % \
935
0ef37898cd2b
                 (self._urllib.__version__, __version__)
936
90cd841771cf
    self.SetUserAgent(user_agent)
937
90cd841771cf
938
cb10f131e83f
  def _InitializeDefaultParameters(self):
939
cb10f131e83f
    self._default_params = {}
940
cb10f131e83f
941
f73c99cf9b51
  def _AddAuthorizationHeader(self, username, password):
942
f73c99cf9b51
    if username and password:
943
f73c99cf9b51
      basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1]
944
f73c99cf9b51
      self._request_headers['Authorization'] = 'Basic %s' % basic_auth
945
f73c99cf9b51
946
f73c99cf9b51
  def _RemoveAuthorizationHeader(self):
947
f73c99cf9b51
    if self._request_headers and 'Authorization' in self._request_headers:
948
f73c99cf9b51
      del self._request_headers['Authorization']
949
f73c99cf9b51
950
4805aee792f0
  def _GetOpener(self, url, username=None, password=None):
951
4805aee792f0
    if username and password:
952
f73c99cf9b51
      self._AddAuthorizationHeader(username, password)
953
4805aee792f0
      handler = self._urllib.HTTPBasicAuthHandler()
954
4805aee792f0
      (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
955
4805aee792f0
      handler.add_password(Api._API_REALM, netloc, username, password)
956
4805aee792f0
      opener = self._urllib.build_opener(handler)
957
4805aee792f0
    else:
958
4805aee792f0
      opener = self._urllib.build_opener()
959
90cd841771cf
    opener.addheaders = self._request_headers.items()
960
4805aee792f0
    return opener
961
4805aee792f0
962
4dfd36cb761d
  def _Encode(self, s):
963
4dfd36cb761d
    if self._input_encoding:
964
4dfd36cb761d
      return unicode(s, self._input_encoding).encode('utf-8')
965
4dfd36cb761d
    else:
966
4dfd36cb761d
      return unicode(s).encode('utf-8')
967
4dfd36cb761d
968
32a0266e88a8
  def _EncodeParameters(self, parameters):
969
32a0266e88a8
    '''Return a string in key=value&key=value form
970
32a0266e88a8
971
32a0266e88a8
    Values of None are not included in the output string.
972
32a0266e88a8
973
32a0266e88a8
    Args:
974
32a0266e88a8
      parameters:
975
32a0266e88a8
        A dict of (key, value) tuples, where value is encoded as
976
32a0266e88a8
        specified by self._encoding
977
32a0266e88a8
    Returns:
978
32a0266e88a8
      A URL-encoded string in "key=value&key=value" form
979
32a0266e88a8
    '''
980
32a0266e88a8
    if parameters is None:
981
32a0266e88a8
      return None
982
32a0266e88a8
    else:
983
4dfd36cb761d
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
984
32a0266e88a8
985
32a0266e88a8
  def _EncodePostData(self, post_data):
986
32a0266e88a8
    '''Return a string in key=value&key=value form
987
32a0266e88a8
988
32a0266e88a8
    Values are assumed to be encoded in the format specified by self._encoding,
989
32a0266e88a8
    and are subsequently URL encoded.
990
32a0266e88a8
991
32a0266e88a8
    Args:
992
32a0266e88a8
      post_data:
993
32a0266e88a8
        A dict of (key, value) tuples, where value is encoded as
994
32a0266e88a8
        specified by self._encoding
995
32a0266e88a8
    Returns:
996
32a0266e88a8
      A URL-encoded string in "key=value&key=value" form
997
32a0266e88a8
    '''
998
32a0266e88a8
    if post_data is None:
999
32a0266e88a8
      return None
1000
32a0266e88a8
    else:
1001
4dfd36cb761d
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
1002
32a0266e88a8
1003
265fcf2a1e01
  def _CheckForTwitterError(self, data):
1004
265fcf2a1e01
    """Raises a TwitterError if twitter returns an error message.
1005
265fcf2a1e01
1006
265fcf2a1e01
    Args:
1007
265fcf2a1e01
      data: A python dict created from the Twitter json response
1008
265fcf2a1e01
    Raises:
1009
265fcf2a1e01
      TwitterError wrapping the twitter error message if one exists.
1010
265fcf2a1e01
    """
1011
265fcf2a1e01
    # Twitter errors are relatively unlikely, so it is faster
1012
265fcf2a1e01
    # to check first, rather than try and catch the exception
1013
265fcf2a1e01
    if 'error' in data:
1014
265fcf2a1e01
      raise TwitterError(data['error'])
1015
265fcf2a1e01
1016
4805aee792f0
  def _FetchUrl(self,
1017
4805aee792f0
                url,
1018
32a0266e88a8
                post_data=None,
1019
4805aee792f0
                parameters=None,
1020
4805aee792f0
                no_cache=None):
1021
ed04d2ff26fc
    '''Fetch a URL, optionally caching for a specified time.
1022
4805aee792f0
1023
4805aee792f0
    Args:
1024
4805aee792f0
      url: The URL to retrieve
1025
b02aa8558d4f
      post_data: 
1026
b02aa8558d4f
        A dict of (str, unicode) key/value pairs.  If set, POST will be used.
1027
b02aa8558d4f
      parameters:
1028
b02aa8558d4f
        A dict whose key/value pairs should encoded and added 
1029
b02aa8558d4f
        to the query string. [OPTIONAL]
1030
4805aee792f0
      no_cache: If true, overrides the cache on the current request
1031
4805aee792f0
1032
4805aee792f0
    Returns:
1033
4805aee792f0
      A string containing the body of the response.
1034
ed04d2ff26fc
    '''
1035
cb10f131e83f
    # Build the extra parameters dict
1036
cb10f131e83f
    extra_params = {}
1037
cb10f131e83f
    if self._default_params:
1038
cb10f131e83f
      extra_params.update(self._default_params)
1039
cb10f131e83f
    if parameters:
1040
cb10f131e83f
      extra_params.update(parameters)
1041
cb10f131e83f
1042
4805aee792f0
    # Add key/value parameters to the query string of the url
1043
cb10f131e83f
    url = self._BuildUrl(url, extra_params=extra_params)
1044
4805aee792f0
1045
4805aee792f0
    # Get a url opener that can handle basic auth
1046
ee84714ea665
    opener = self._GetOpener(url, username=self._username, password=self._password)
1047
4805aee792f0
1048
32a0266e88a8
    encoded_post_data = self._EncodePostData(post_data)
1049
c42880b017af
1050
4805aee792f0
    # Open and return the URL immediately if we're not going to cache
1051
32a0266e88a8
    if encoded_post_data or no_cache or not self._cache or not self._cache_timeout:
1052
32a0266e88a8
      url_data = opener.open(url, encoded_post_data).read()
1053
c1d12896a51f
      opener.close()
1054
4805aee792f0
    else:
1055
4805aee792f0
      # Unique keys are a combination of the url and the username
1056
ee84714ea665
      if self._username:
1057
ee84714ea665
        key = self._username + ':' + url
1058
4805aee792f0
      else:
1059
4805aee792f0
        key = url
1060
4805aee792f0
1061
4805aee792f0
      # See if it has been cached before
1062
4805aee792f0
      last_cached = self._cache.GetCachedTime(key)
1063
4805aee792f0
1064
4805aee792f0
      # If the cached version is outdated then fetch another and store it
1065
4805aee792f0
      if not last_cached or time.time() >= last_cached + self._cache_timeout:
1066
32a0266e88a8
        url_data = opener.open(url, encoded_post_data).read()
1067
c1d12896a51f
        opener.close()
1068
4805aee792f0
        self._cache.Set(key, url_data)
1069
4805aee792f0
      else:
1070
4805aee792f0
        url_data = self._cache.Get(key)
1071
17f566981b41
1072
4805aee792f0
    # Always return the latest version
1073
4805aee792f0
    return url_data
1074
4805aee792f0
1075
81cef10d2991
1076
4805aee792f0
class _FileCacheError(Exception):
1077
4805aee792f0
  '''Base exception class for FileCache related errors'''
1078
4805aee792f0
1079
4805aee792f0
class _FileCache(object):
1080
17f566981b41
1081
4805aee792f0
  DEPTH = 3
1082
17f566981b41
1083
4805aee792f0
  def __init__(self,root_directory=None):
1084
4805aee792f0
    self._InitializeRootDirectory(root_directory)
1085
17f566981b41
1086
4805aee792f0
  def Get(self,key):
1087
4805aee792f0
    path = self._GetPath(key)
1088
4805aee792f0
    if os.path.exists(path):
1089
4805aee792f0
      return open(path).read()
1090
4805aee792f0
    else:
1091
4805aee792f0
      return None
1092
17f566981b41
1093
4805aee792f0
  def Set(self,key,data):
1094
4805aee792f0
    path = self._GetPath(key)
1095
4805aee792f0
    directory = os.path.dirname(path)
1096
4805aee792f0
    if not os.path.exists(directory):
1097
4805aee792f0
      os.makedirs(directory)
1098
4805aee792f0
    if not os.path.isdir(directory):
1099
4805aee792f0
      raise _FileCacheError('%s exists but is not a directory' % directory)
1100
4805aee792f0
    temp_fd, temp_path = tempfile.mkstemp()
1101
4805aee792f0
    temp_fp = os.fdopen(temp_fd, 'w')
1102
4805aee792f0
    temp_fp.write(data)
1103
4805aee792f0
    temp_fp.close()
1104
4805aee792f0
    if not path.startswith(self._root_directory):
1105
4805aee792f0
      raise _FileCacheError('%s does not appear to live under %s' %
1106
4805aee792f0
                            (path, self._root_directory))
1107
744e374963dc
    if os.path.exists(path):
1108
744e374963dc
      os.remove(path)
1109
4805aee792f0
    os.rename(temp_path, path)
1110
17f566981b41
1111
4805aee792f0
  def Remove(self,key):
1112
4805aee792f0
    path = self._GetPath(key)
1113
4805aee792f0
    if not path.startswith(self._root_directory):
1114
4805aee792f0
      raise _FileCacheError('%s does not appear to live under %s' %
1115
4805aee792f0
                            (path, self._root_directory ))
1116
4805aee792f0
    if os.path.exists(path):
1117
17f566981b41
      os.remove(path)
1118
17f566981b41
1119
4805aee792f0
  def GetCachedTime(self,key):
1120
4805aee792f0
    path = self._GetPath(key)
1121
4805aee792f0
    if os.path.exists(path):
1122
4805aee792f0
      return os.path.getmtime(path)
1123
4805aee792f0
    else:
1124
4805aee792f0
      return None
1125
17f566981b41
1126
795f1c9d49c9
  def _GetUsername(self):
1127
3bef9042269d
    '''Attempt to find the username in a cross-platform fashion.'''
1128
d981d739f261
    try:
1129
d981d739f261
      return os.getenv('USER') or \
1130
d981d739f261
             os.getenv('LOGNAME') or \
1131
d981d739f261
             os.getenv('USERNAME') or \
1132
d981d739f261
             os.getlogin() or \
1133
d981d739f261
             'nobody'
1134
fc7763723e15
    except (IOError, OSError), e:
1135
d981d739f261
      return 'nobody'
1136
795f1c9d49c9
1137
795f1c9d49c9
  def _GetTmpCachePath(self):
1138
795f1c9d49c9
    username = self._GetUsername()
1139
795f1c9d49c9
    cache_directory = 'python.cache_' + username
1140
795f1c9d49c9
    return os.path.join(tempfile.gettempdir(), cache_directory)
1141
795f1c9d49c9
1142
4805aee792f0
  def _InitializeRootDirectory(self, root_directory):
1143
4805aee792f0
    if not root_directory:
1144
795f1c9d49c9
      root_directory = self._GetTmpCachePath()
1145
4805aee792f0
    root_directory = os.path.abspath(root_directory)
1146
4805aee792f0
    if not os.path.exists(root_directory):
1147
4805aee792f0
      os.mkdir(root_directory)
1148
4805aee792f0
    if not os.path.isdir(root_directory):
1149
4805aee792f0
      raise _FileCacheError('%s exists but is not a directory' %
1150
4805aee792f0
                            root_directory)
1151
4805aee792f0
    self._root_directory = root_directory
1152
17f566981b41
1153
4805aee792f0
  def _GetPath(self,key):
1154
a58f4a7ad991
    try:
1155
a58f4a7ad991
        hashed_key = md5(key).hexdigest()
1156
a58f4a7ad991
    except TypeError:
1157
a58f4a7ad991
        hashed_key = md5.new(key).hexdigest()
1158
a58f4a7ad991
        
1159
4805aee792f0
    return os.path.join(self._root_directory,
1160
4805aee792f0
                        self._GetPrefix(hashed_key),
1161
4805aee792f0
                        hashed_key)
1162
17f566981b41
1163
4805aee792f0
  def _GetPrefix(self,hashed_key):
1164
4805aee792f0
    return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])