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
adedffd98396
parent
9fabfd8ae1e2
branch
dclinton

Fixes typo checking wrong parameter (count instead of max_id)

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
42a0465d458f
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
42a0465d458f
    now:
126
42a0465d458f
      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
056bb4e69e89
  delta = long(now) - long(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
42a0465d458f
                         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
42a0465d458f
      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
08d7b67771ca
  def GetUserTimeline(self,
411
08d7b67771ca
                      id=None,
412
08d7b67771ca
                      user_id=None,
413
08d7b67771ca
                      screen_name=None,
414
08d7b67771ca
                      since_id=None,
415
08d7b67771ca
                      max_id=None,
416
08d7b67771ca
                      count=None,
417
08d7b67771ca
                      page=None):
418
c05ccbbb1d5c
    '''Fetch the sequence of public Status messages for a single user.
419
4805aee792f0
420
ee84714ea665
    The twitter.Api instance must be authenticated if the user is private.
421
ee84714ea665
422
4805aee792f0
    Args:
423
08d7b67771ca
      id:
424
08d7b67771ca
        Specifies the ID or screen name of the user for whom to return
425
08d7b67771ca
        the user_timeline. [optional]
426
08d7b67771ca
      user_id:
427
08d7b67771ca
        Specfies the ID of the user for whom to return the
428
08d7b67771ca
        user_timeline. Helpful for disambiguating when a valid user ID
429
08d7b67771ca
        is also a valid screen name. [optional]
430
08d7b67771ca
      screen_name:
431
08d7b67771ca
        Specfies the screen name of the user for whom to return the
432
08d7b67771ca
        user_timeline. Helpful for disambiguating when a valid screen
433
08d7b67771ca
        name is also a user ID. [optional]
434
806829c7dc10
      since_id:
435
806829c7dc10
        Returns only public statuses with an ID greater than (that is,
436
08d7b67771ca
        more recent than) the specified ID. [optional]
437
08d7b67771ca
      max_id:
438
08d7b67771ca
        Returns only statuses with an ID less than (that is, older
439
08d7b67771ca
        than) or equal to the specified ID. [optional]
440
08d7b67771ca
      count:
441
08d7b67771ca
        Specifies the number of statuses to retrieve. May not be
442
08d7b67771ca
        greater than 200.  [optional]
443
08d7b67771ca
      page:
444
08d7b67771ca
         Specifies the page of results to retrieve. Note: there are
445
08d7b67771ca
         pagination limits. [optional]
446
4805aee792f0
447
4805aee792f0
    Returns:
448
c05ccbbb1d5c
      A sequence of Status instances, one for each message up to count
449
4805aee792f0
    '''
450
ee84714ea665
    parameters = {}
451
08d7b67771ca
452
08d7b67771ca
    if id:
453
08d7b67771ca
      url = 'http://twitter.com/statuses/user_timeline/%s.json' % id
454
08d7b67771ca
    elif user_id:
455
9fabfd8ae1e2
      url = 'http://twitter.com/statuses/user_timeline.json?user_id=%d' % user_id
456
08d7b67771ca
    elif screen_name:
457
9fabfd8ae1e2
      url = ('http://twitter.com/statuses/user_timeline.json?screen_name=%s' %
458
08d7b67771ca
             screen_name)
459
08d7b67771ca
    elif not self._username:
460
ee84714ea665
      raise TwitterError("User must be specified if API is not authenticated.")
461
4805aee792f0
    else:
462
ee84714ea665
      url = 'http://twitter.com/statuses/user_timeline.json'
463
08d7b67771ca
464
08d7b67771ca
    if since_id:
465
08d7b67771ca
      try:
466
08d7b67771ca
        parameters['since_id'] = int(since_id)
467
08d7b67771ca
      except:
468
08d7b67771ca
        raise TwitterError("since_id must be an integer")
469
08d7b67771ca
470
08d7b67771ca
    if max_id:
471
08d7b67771ca
      try:
472
adedffd98396
        parameters['max_id'] = int(max_id)
473
08d7b67771ca
      except:
474
08d7b67771ca
        raise TwitterError("max_id must be an integer")
475
08d7b67771ca
476
08d7b67771ca
    if count:
477
08d7b67771ca
      try:
478
08d7b67771ca
        parameters['count'] = int(count)
479
08d7b67771ca
      except:
480
08d7b67771ca
        raise TwitterError("count must be an integer")
481
08d7b67771ca
482
08d7b67771ca
    if page:
483
08d7b67771ca
      try:
484
08d7b67771ca
        parameters['page'] = int(page)
485
08d7b67771ca
      except:
486
08d7b67771ca
        raise TwitterError("page must be an integer")
487
08d7b67771ca
488
4805aee792f0
    json = self._FetchUrl(url, parameters=parameters)
489
4805aee792f0
    data = simplejson.loads(json)
490
265fcf2a1e01
    self._CheckForTwitterError(data)
491
c05ccbbb1d5c
    return [NewStatusFromJsonDict(x) for x in data]
492
32a0266e88a8
493
ee84714ea665
  def GetStatus(self, id):
494
ee84714ea665
    '''Returns a single status message.
495
4805aee792f0
496
08d7b67771ca
    The twitter.Api instance must be authenticated if the status
497
08d7b67771ca
    message is private.
498
4805aee792f0
499
4805aee792f0
    Args:
500
32a0266e88a8
      id: The numerical ID of the status you're trying to retrieve.
501
4805aee792f0
502
4805aee792f0
    Returns:
503
c05ccbbb1d5c
      A Status instance representing that status message
504
4805aee792f0
    '''
505
ee84714ea665
    try:
506
ee84714ea665
      if id:
507
056bb4e69e89
        long(id)
508
ee84714ea665
    except:
509
056bb4e69e89
      raise TwitterError("id must be a long integer")
510
ee84714ea665
    url = 'http://twitter.com/statuses/show/%s.json' % id
511
ee84714ea665
    json = self._FetchUrl(url)
512
4805aee792f0
    data = simplejson.loads(json)
513
265fcf2a1e01
    self._CheckForTwitterError(data)
514
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
515
4805aee792f0
516
4da5ec4d173a
  def DestroyStatus(self, id):
517
4da5ec4d173a
    '''Destroys the status specified by the required ID parameter.
518
4da5ec4d173a
519
4da5ec4d173a
    The twitter.Api instance must be authenticated and thee
520
4da5ec4d173a
    authenticating user must be the author of the specified status.
521
4da5ec4d173a
522
4da5ec4d173a
    Args:
523
4da5ec4d173a
      id: The numerical ID of the status you're trying to destroy.
524
4da5ec4d173a
525
4da5ec4d173a
    Returns:
526
c05ccbbb1d5c
      A Status instance representing the destroyed status message
527
4da5ec4d173a
    '''
528
4da5ec4d173a
    try:
529
4da5ec4d173a
      if id:
530
056bb4e69e89
        long(id)
531
4da5ec4d173a
    except:
532
056bb4e69e89
      raise TwitterError("id must be a long integer")
533
4da5ec4d173a
    url = 'http://twitter.com/statuses/destroy/%s.json' % id
534
4da5ec4d173a
    json = self._FetchUrl(url, post_data={})
535
4da5ec4d173a
    data = simplejson.loads(json)
536
265fcf2a1e01
    self._CheckForTwitterError(data)
537
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
538
4da5ec4d173a
539
81cef10d2991
  def PostUpdate(self, status, in_reply_to_status_id=None):
540
c42880b017af
    '''Post a twitter status message from the authenticated user.
541
ee84714ea665
542
ee84714ea665
    The twitter.Api instance must be authenticated.
543
4805aee792f0
544
4805aee792f0
    Args:
545
81cef10d2991
      status:
546
81cef10d2991
        The message text to be posted.  Must be less than or equal to
547
81cef10d2991
        140 characters.
548
71b5f9cae083
      in_reply_to_status_id:
549
71b5f9cae083
        The ID of an existing status that the status to be posted is
550
71b5f9cae083
        in reply to.  This implicitly sets the in_reply_to_user_id
551
71b5f9cae083
        attribute of the resulting status to the user ID of the
552
71b5f9cae083
        message being replied to.  Invalid/missing status IDs will be
553
71b5f9cae083
        ignored. [Optional]
554
4805aee792f0
    Returns:
555
c05ccbbb1d5c
      A Status instance representing the message posted.
556
4805aee792f0
    '''
557
ee84714ea665
    if not self._username:
558
ee84714ea665
      raise TwitterError("The twitter.Api instance must be authenticated.")
559
81cef10d2991
560
c42880b017af
    url = 'http://twitter.com/statuses/update.json'
561
81cef10d2991
562
c867b935f947
    status_length = len(status)
563
c867b935f947
    if status_length > CHARACTER_LIMIT:
564
81cef10d2991
      raise TwitterError("Text must be less than or equal to %d characters. "
565
c867b935f947
                         "Was %d. Consider using PostUpdates." % 
566
c867b935f947
                         (CHARACTER_LIMIT, status_length))
567
81cef10d2991
568
81cef10d2991
    data = {'status': status}
569
71b5f9cae083
    if in_reply_to_status_id:
570
71b5f9cae083
      data['in_reply_to_status_id'] = in_reply_to_status_id
571
79429eb1b54d
    json = self._FetchUrl(url, post_data=data)
572
4805aee792f0
    data = simplejson.loads(json)
573
265fcf2a1e01
    self._CheckForTwitterError(data)
574
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
575
4805aee792f0
576
8bdda4c8187b
  def PostUpdates(self, status, continuation=None, **kwargs):
577
81cef10d2991
    '''Post one or more twitter status messages from the authenticated user.
578
81cef10d2991
579
81cef10d2991
    Unlike api.PostUpdate, this method will post multiple status updates
580
81cef10d2991
    if the message is longer than 140 characters.
581
81cef10d2991
582
81cef10d2991
    The twitter.Api instance must be authenticated.
583
81cef10d2991
584
81cef10d2991
    Args:
585
81cef10d2991
      status:
586
81cef10d2991
        The message text to be posted.  May be longer than 140 characters.
587
8bdda4c8187b
      continuation:
588
8bdda4c8187b
        The character string, if any, to be appended to all but the
589
8bdda4c8187b
        last message.  Note that Twitter strips trailing '...' strings
590
8bdda4c8187b
        from messages.  Consider using the unicode \u2026 character
591
8bdda4c8187b
        (horizontal ellipsis) instead. [Defaults to None]
592
81cef10d2991
      **kwargs:
593
81cef10d2991
        See api.PostUpdate for a list of accepted parameters.
594
81cef10d2991
    Returns:
595
c05ccbbb1d5c
      A list of Status instances representing the messages posted.
596
81cef10d2991
    '''
597
81cef10d2991
    results = list()
598
8bdda4c8187b
    if continuation is None:
599
8bdda4c8187b
      continuation = ''
600
8bdda4c8187b
    line_length = CHARACTER_LIMIT - len(continuation)
601
8bdda4c8187b
    lines = textwrap.wrap(status, line_length)
602
8bdda4c8187b
    for line in lines[0:-1]:
603
8bdda4c8187b
      results.append(self.PostUpdate(line + continuation, **kwargs))
604
8bdda4c8187b
    results.append(self.PostUpdate(lines[-1], **kwargs))
605
81cef10d2991
    return results
606
81cef10d2991
607
42a0465d458f
  def GetReplies(self, since=None, since_id=None, page=None):
608
ee84714ea665
    '''Get a sequence of status messages representing the 20 most recent
609
ee84714ea665
    replies (status updates prefixed with @username) to the authenticating
610
ee84714ea665
    user.
611
71b5f9cae083
612
a10f9a46dc3d
    Args:
613
42a0465d458f
      page:
614
a10f9a46dc3d
      since:
615
a10f9a46dc3d
        Narrows the returned results to just those statuses created
616
a10f9a46dc3d
        after the specified HTTP-formatted date. [optional]
617
a10f9a46dc3d
      since_id:
618
a10f9a46dc3d
        Returns only public statuses with an ID greater than (that is,
619
a10f9a46dc3d
        more recent than) the specified ID. [Optional]
620
ee84714ea665
621
ee84714ea665
    Returns:
622
c05ccbbb1d5c
      A sequence of Status instances, one for each reply to the user.
623
32a0266e88a8
    '''
624
ee84714ea665
    url = 'http://twitter.com/statuses/replies.json'
625
ee84714ea665
    if not self._username:
626
ee84714ea665
      raise TwitterError("The twitter.Api instance must be authenticated.")
627
a10f9a46dc3d
    parameters = {}
628
a10f9a46dc3d
    if since:
629
a10f9a46dc3d
      parameters['since'] = since
630
a10f9a46dc3d
    if since_id:
631
a10f9a46dc3d
      parameters['since_id'] = since_id
632
935cf080b638
    if page:
633
935cf080b638
      parameters['page'] = page
634
a10f9a46dc3d
    json = self._FetchUrl(url, parameters=parameters)
635
ee84714ea665
    data = simplejson.loads(json)
636
265fcf2a1e01
    self._CheckForTwitterError(data)
637
c05ccbbb1d5c
    return [NewStatusFromJsonDict(x) for x in data]
638
ee84714ea665
639
935cf080b638
  def GetFriends(self, user=None, page=None):
640
ee84714ea665
    '''Fetch the sequence of twitter.User instances, one for each friend.
641
ee84714ea665
642
ee84714ea665
    Args:
643
ee84714ea665
      user: the username or id of the user whose friends you are fetching.  If
644
ee84714ea665
      not specified, defaults to the authenticated user. [optional]
645
ee84714ea665
646
ee84714ea665
    The twitter.Api instance must be authenticated.
647
ee84714ea665
648
ee84714ea665
    Returns:
649
ee84714ea665
      A sequence of twitter.User instances, one for each friend
650
ee84714ea665
    '''
651
ee84714ea665
    if not self._username:
652
ee84714ea665
      raise TwitterError("twitter.Api instance must be authenticated")
653
ee84714ea665
    if user:
654
42a0465d458f
      url = 'http://twitter.com/statuses/friends/%s.json' % user
655
32a0266e88a8
    else:
656
ee84714ea665
      url = 'http://twitter.com/statuses/friends.json'
657
935cf080b638
    parameters = {}
658
935cf080b638
    if page:
659
935cf080b638
      parameters['page'] = page
660
935cf080b638
    json = self._FetchUrl(url, parameters=parameters)
661
ee84714ea665
    data = simplejson.loads(json)
662
265fcf2a1e01
    self._CheckForTwitterError(data)
663
c05ccbbb1d5c
    return [NewUserFromJsonDict(x) for x in data]
664
ee84714ea665
665
935cf080b638
  def GetFollowers(self, page=None):
666
ee84714ea665
    '''Fetch the sequence of twitter.User instances, one for each follower
667
ee84714ea665
668
ee84714ea665
    The twitter.Api instance must be authenticated.
669
ee84714ea665
670
ee84714ea665
    Returns:
671
ee84714ea665
      A sequence of twitter.User instances, one for each follower
672
ee84714ea665
    '''
673
ee84714ea665
    if not self._username:
674
ee84714ea665
      raise TwitterError("twitter.Api instance must be authenticated")
675
ee84714ea665
    url = 'http://twitter.com/statuses/followers.json'
676
935cf080b638
    parameters = {}
677
935cf080b638
    if page:
678
935cf080b638
      parameters['page'] = page
679
935cf080b638
    json = self._FetchUrl(url, parameters=parameters)
680
ee84714ea665
    data = simplejson.loads(json)
681
265fcf2a1e01
    self._CheckForTwitterError(data)
682
c05ccbbb1d5c
    return [NewUserFromJsonDict(x) for x in data]
683
ee84714ea665
684
e3638dafb13c
  def GetFeatured(self):
685
e3638dafb13c
    '''Fetch the sequence of twitter.User instances featured on twitter.com
686
e3638dafb13c
687
e3638dafb13c
    The twitter.Api instance must be authenticated.
688
e3638dafb13c
689
e3638dafb13c
    Returns:
690
e3638dafb13c
      A sequence of twitter.User instances
691
e3638dafb13c
    '''
692
e3638dafb13c
    url = 'http://twitter.com/statuses/featured.json'
693
e3638dafb13c
    json = self._FetchUrl(url)
694
e3638dafb13c
    data = simplejson.loads(json)
695
265fcf2a1e01
    self._CheckForTwitterError(data)
696
c05ccbbb1d5c
    return [NewUserFromJsonDict(x) for x in data]
697
e3638dafb13c
698
ee84714ea665
  def GetUser(self, user):
699
ee84714ea665
    '''Returns a single user.
700
ee84714ea665
701
ee84714ea665
    The twitter.Api instance must be authenticated.
702
ee84714ea665
703
ee84714ea665
    Args:
704
ee84714ea665
      user: The username or id of the user to retrieve.
705
ee84714ea665
706
ee84714ea665
    Returns:
707
ee84714ea665
      A twitter.User instance representing that user
708
ee84714ea665
    '''
709
ee84714ea665
    url = 'http://twitter.com/users/show/%s.json' % user
710
ee84714ea665
    json = self._FetchUrl(url)
711
ee84714ea665
    data = simplejson.loads(json)
712
265fcf2a1e01
    self._CheckForTwitterError(data)
713
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
714
ee84714ea665
715
935cf080b638
  def GetDirectMessages(self, since=None, since_id=None, page=None):
716
90377483d9fa
    '''Returns a list of the direct messages sent to the authenticating user.
717
90377483d9fa
718
90377483d9fa
    The twitter.Api instance must be authenticated.
719
90377483d9fa
720
32a0266e88a8
    Args:
721
90377483d9fa
      since:
722
90377483d9fa
        Narrows the returned results to just those statuses created
723
90377483d9fa
        after the specified HTTP-formatted date. [optional]
724
472ef0f2ea07
      since_id:
725
472ef0f2ea07
        Returns only public statuses with an ID greater than (that is,
726
472ef0f2ea07
        more recent than) the specified ID. [Optional]
727
90377483d9fa
728
90377483d9fa
    Returns:
729
32a0266e88a8
      A sequence of twitter.DirectMessage instances
730
90377483d9fa
    '''
731
90377483d9fa
    url = 'http://twitter.com/direct_messages.json'
732
90377483d9fa
    if not self._username:
733
90377483d9fa
      raise TwitterError("The twitter.Api instance must be authenticated.")
734
90377483d9fa
    parameters = {}
735
90377483d9fa
    if since:
736
90377483d9fa
      parameters['since'] = since
737
472ef0f2ea07
    if since_id:
738
472ef0f2ea07
      parameters['since_id'] = since_id
739
935cf080b638
    if page:
740
42a0465d458f
      parameters['page'] = page
741
90377483d9fa
    json = self._FetchUrl(url, parameters=parameters)
742
90377483d9fa
    data = simplejson.loads(json)
743
265fcf2a1e01
    self._CheckForTwitterError(data)
744
c05ccbbb1d5c
    return [NewDirectMessageFromJsonDict(x) for x in data]
745
32a0266e88a8
746
c42880b017af
  def PostDirectMessage(self, user, text):
747
c42880b017af
    '''Post a twitter direct message from the authenticated user
748
c42880b017af
749
c42880b017af
    The twitter.Api instance must be authenticated.
750
c42880b017af
751
c42880b017af
    Args:
752
c42880b017af
      user: The ID or screen name of the recipient user.
753
c42880b017af
      text: The message text to be posted.  Must be less than 140 characters.
754
c42880b017af
755
c42880b017af
    Returns:
756
32a0266e88a8
      A twitter.DirectMessage instance representing the message posted
757
c42880b017af
    '''
758
c42880b017af
    if not self._username:
759
c42880b017af
      raise TwitterError("The twitter.Api instance must be authenticated.")
760
c42880b017af
    url = 'http://twitter.com/direct_messages/new.json'
761
c42880b017af
    data = {'text': text, 'user': user}
762
79429eb1b54d
    json = self._FetchUrl(url, post_data=data)
763
32a0266e88a8
    data = simplejson.loads(json)
764
265fcf2a1e01
    self._CheckForTwitterError(data)
765
c05ccbbb1d5c
    return NewDirectMessageFromJsonDict(data)
766
32a0266e88a8
767
32a0266e88a8
  def DestroyDirectMessage(self, id):
768
32a0266e88a8
    '''Destroys the direct message specified in the required ID parameter.
769
32a0266e88a8
770
32a0266e88a8
    The twitter.Api instance must be authenticated, and the
771
32a0266e88a8
    authenticating user must be the recipient of the specified direct
772
32a0266e88a8
    message.
773
32a0266e88a8
774
32a0266e88a8
    Args:
775
32a0266e88a8
      id: The id of the direct message to be destroyed
776
32a0266e88a8
777
32a0266e88a8
    Returns:
778
32a0266e88a8
      A twitter.DirectMessage instance representing the message destroyed
779
32a0266e88a8
    '''
780
32a0266e88a8
    url = 'http://twitter.com/direct_messages/destroy/%s.json' % id
781
79429eb1b54d
    json = self._FetchUrl(url, post_data={})
782
c42880b017af
    data = simplejson.loads(json)
783
265fcf2a1e01
    self._CheckForTwitterError(data)
784
c05ccbbb1d5c
    return NewDirectMessageFromJsonDict(data)
785
90377483d9fa
786
79429eb1b54d
  def CreateFriendship(self, user):
787
79429eb1b54d
    '''Befriends the user specified in the user parameter as the authenticating user.
788
79429eb1b54d
789
79429eb1b54d
    The twitter.Api instance must be authenticated.
790
79429eb1b54d
791
79429eb1b54d
    Args:
792
79429eb1b54d
      The ID or screen name of the user to befriend.
793
79429eb1b54d
    Returns:
794
79429eb1b54d
      A twitter.User instance representing the befriended user.
795
79429eb1b54d
    '''
796
79429eb1b54d
    url = 'http://twitter.com/friendships/create/%s.json' % user
797
79429eb1b54d
    json = self._FetchUrl(url, post_data={})
798
79429eb1b54d
    data = simplejson.loads(json)
799
265fcf2a1e01
    self._CheckForTwitterError(data)
800
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
801
79429eb1b54d
802
79429eb1b54d
  def DestroyFriendship(self, user):
803
79429eb1b54d
    '''Discontinues friendship with the user specified in the user parameter.
804
79429eb1b54d
805
79429eb1b54d
    The twitter.Api instance must be authenticated.
806
79429eb1b54d
807
79429eb1b54d
    Args:
808
79429eb1b54d
      The ID or screen name of the user  with whom to discontinue friendship.
809
79429eb1b54d
    Returns:
810
79429eb1b54d
      A twitter.User instance representing the discontinued friend.
811
79429eb1b54d
    '''
812
79429eb1b54d
    url = 'http://twitter.com/friendships/destroy/%s.json' % user
813
79429eb1b54d
    json = self._FetchUrl(url, post_data={})
814
79429eb1b54d
    data = simplejson.loads(json)
815
265fcf2a1e01
    self._CheckForTwitterError(data)
816
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
817
79429eb1b54d
818
a1e65b8c9435
  def CreateFavorite(self, status):
819
a1e65b8c9435
    '''Favorites the status specified in the status parameter as the authenticating user.
820
a1e65b8c9435
    Returns the favorite status when successful.
821
a1e65b8c9435
822
a1e65b8c9435
    The twitter.Api instance must be authenticated.
823
a1e65b8c9435
824
a1e65b8c9435
    Args:
825
c05ccbbb1d5c
      The Status instance to mark as a favorite.
826
a1e65b8c9435
    Returns:
827
c05ccbbb1d5c
      A Status instance representing the newly-marked favorite.
828
a1e65b8c9435
    '''
829
a1e65b8c9435
    url = 'http://twitter.com/favorites/create/%s.json' % status.id
830
a1e65b8c9435
    json = self._FetchUrl(url, post_data={})
831
a1e65b8c9435
    data = simplejson.loads(json)
832
265fcf2a1e01
    self._CheckForTwitterError(data)
833
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
834
a1e65b8c9435
835
a1e65b8c9435
  def DestroyFavorite(self, status):
836
a1e65b8c9435
    '''Un-favorites the status specified in the ID parameter as the authenticating user.
837
a1e65b8c9435
    Returns the un-favorited status in the requested format when successful.
838
a1e65b8c9435
839
a1e65b8c9435
    The twitter.Api instance must be authenticated.
840
a1e65b8c9435
841
a1e65b8c9435
    Args:
842
c05ccbbb1d5c
      The Status to unmark as a favorite.
843
a1e65b8c9435
    Returns:
844
c05ccbbb1d5c
      A Status instance representing the newly-unmarked favorite.
845
a1e65b8c9435
    '''
846
a1e65b8c9435
    url = 'http://twitter.com/favorites/destroy/%s.json' % status.id
847
a1e65b8c9435
    json = self._FetchUrl(url, post_data={})
848
a1e65b8c9435
    data = simplejson.loads(json)
849
265fcf2a1e01
    self._CheckForTwitterError(data)
850
c05ccbbb1d5c
    return NewStatusFromJsonDict(data)
851
a1e65b8c9435
852
e2f8ed587060
  def GetUserByEmail(self, email):
853
e2f8ed587060
    '''Returns a single user by email address.
854
e2f8ed587060
855
e2f8ed587060
    Args:
856
e2f8ed587060
      email: The email of the user to retrieve.
857
e2f8ed587060
    Returns:
858
e2f8ed587060
      A twitter.User instance representing that user
859
e2f8ed587060
    '''
860
e2f8ed587060
    url = 'http://twitter.com/users/show.json?email=%s' % email
861
e2f8ed587060
    json = self._FetchUrl(url)
862
e2f8ed587060
    data = simplejson.loads(json)
863
265fcf2a1e01
    self._CheckForTwitterError(data)
864
c05ccbbb1d5c
    return NewUserFromJsonDict(data)
865
e2f8ed587060
866
42a0465d458f
  def Search(self,
867
42a0465d458f
             query,
868
42a0465d458f
             lang=None,
869
42a0465d458f
             rpp=None,
870
42a0465d458f
             page=None,
871
42a0465d458f
             since_id=None,
872
42a0465d458f
             geocode=None,
873
19260f87c32a
             show_user=None):
874
19260f87c32a
    '''Returns tweets that match a specified query.
875
19260f87c32a
876
19260f87c32a
    Args:
877
19260f87c32a
      query: The search query string, must be less than 140 characters
878
42a0465d458f
      lang:
879
42a0465d458f
        Restricts tweets to the given language, given by an ISO 639-1
880
19260f87c32a
        code. [Optional]
881
19260f87c32a
      rpp:
882
19260f87c32a
        The number of tweets to return per page, up to a max of 100. [Optional]
883
42a0465d458f
      page:
884
19260f87c32a
        The page number (starting at 1) to return, up to a max of
885
19260f87c32a
        roughly 1500 results (based on rpp * page. Note: there are
886
19260f87c32a
        pagination limits. [Optional]
887
42a0465d458f
      since_id:
888
19260f87c32a
        Returns tweets with status ids greater than the given id. [Optional]
889
42a0465d458f
      geocode:
890
19260f87c32a
        Returns tweets by users located within a given radius of the
891
19260f87c32a
        given latitude/longitude, where the user's location is taken
892
19260f87c32a
        from their Twitter profile. The parameter value is specified
893
19260f87c32a
        by "latitide,longitude,radius", where radius units must be
894
19260f87c32a
        specified as either "mi" (miles) or "km" (kilometers). Note
895
19260f87c32a
        that you cannot use the near operator via the API to geocode
896
19260f87c32a
        arbitrary locations; however you can use this geocode
897
19260f87c32a
        parameter to search near geocodes directly. [Optional]
898
19260f87c32a
      show_user:
899
19260f87c32a
        When true, prepends "<user>:" to the beginning of the
900
19260f87c32a
        tweet. This is useful for readers that do not display Atom's
901
19260f87c32a
        author field. The default is false. [Optional]
902
19260f87c32a
    Returns:
903
19260f87c32a
      A Results instance representing the search results
904
19260f87c32a
    '''
905
19260f87c32a
    url = 'http://search.twitter.com/search.json'
906
42a0465d458f
907
19260f87c32a
    parameters = {'q': query}
908
19260f87c32a
    if len(query) > 140:
909
19260f87c32a
      raise TwitterError('query must be <= 140 characters')
910
19260f87c32a
    if lang:
911
19260f87c32a
      parameters['lang'] = lang
912
19260f87c32a
    if rpp is not None:
913
19260f87c32a
      try:
914
19260f87c32a
        if int(rpp) > 100:
915
19260f87c32a
          raise TwitterError("'rpp' may not be greater than 100")
916
19260f87c32a
      except ValueError:
917
19260f87c32a
        raise TwitterError("'rpp' must be an integer")
918
19260f87c32a
      parameters['rpp'] = rpp
919
19260f87c32a
    if page:
920
19260f87c32a
      parameters['page'] = page
921
19260f87c32a
    if since_id:
922
19260f87c32a
      parameters['since_id'] = since_id
923
19260f87c32a
    if geocode:
924
19260f87c32a
      parameters['geocode'] = geocode
925
19260f87c32a
    if show_user:
926
19260f87c32a
      parameters['show_user'] = show_user
927
19260f87c32a
    json = self._FetchUrl(url, parameters=parameters)
928
19260f87c32a
    data = simplejson.loads(json)
929
19260f87c32a
    self._CheckForTwitterError(data)
930
19260f87c32a
    return NewResultsFromJsonDict(data)
931
19260f87c32a
932
ee84714ea665
  def SetCredentials(self, username, password):
933
ee84714ea665
    '''Set the username and password for this instance
934
ee84714ea665
935
ee84714ea665
    Args:
936
ee84714ea665
      username: The twitter username.
937
ee84714ea665
      password: The twitter password.
938
ee84714ea665
    '''
939
ee84714ea665
    self._username = username
940
ee84714ea665
    self._password = password
941
ee84714ea665
942
ee84714ea665
  def ClearCredentials(self):
943
ee84714ea665
    '''Clear the username and password for this instance
944
ee84714ea665
    '''
945
ee84714ea665
    self._username = None
946
ee84714ea665
    self._password = None
947
4805aee792f0
948
4805aee792f0
  def SetCache(self, cache):
949
4805aee792f0
    '''Override the default cache.  Set to None to prevent caching.
950
4805aee792f0
951
4805aee792f0
    Args:
952
4805aee792f0
      cache: an instance that supports the same API as the  twitter._FileCache
953
4805aee792f0
    '''
954
4805aee792f0
    self._cache = cache
955
4805aee792f0
956
4805aee792f0
  def SetUrllib(self, urllib):
957
4805aee792f0
    '''Override the default urllib implementation.
958
4805aee792f0
959
4805aee792f0
    Args:
960
4805aee792f0
      urllib: an instance that supports the same API as the urllib2 module
961
4805aee792f0
    '''
962
4805aee792f0
    self._urllib = urllib
963
4805aee792f0
964
4805aee792f0
  def SetCacheTimeout(self, cache_timeout):
965
4805aee792f0
    '''Override the default cache timeout.
966
4805aee792f0
967
4805aee792f0
    Args:
968
4805aee792f0
      cache_timeout: time, in seconds, that responses should be reused.
969
4805aee792f0
    '''
970
4805aee792f0
    self._cache_timeout = cache_timeout
971
4805aee792f0
972
4805aee792f0
  def SetUserAgent(self, user_agent):
973
4805aee792f0
    '''Override the default user agent
974
4805aee792f0
975
4805aee792f0
    Args:
976
4805aee792f0
      user_agent: a string that should be send to the server as the User-agent
977
4805aee792f0
    '''
978
90cd841771cf
    self._request_headers['User-Agent'] = user_agent
979
90cd841771cf
980
90cd841771cf
  def SetXTwitterHeaders(self, client, url, version):
981
90cd841771cf
    '''Set the X-Twitter HTTP headers that will be sent to the server.
982
90cd841771cf
983
90cd841771cf
    Args:
984
90cd841771cf
      client:
985
90cd841771cf
         The client name as a string.  Will be sent to the server as
986
90cd841771cf
         the 'X-Twitter-Client' header.
987
90cd841771cf
      url:
988
90cd841771cf
         The URL of the meta.xml as a string.  Will be sent to the server
989
90cd841771cf
         as the 'X-Twitter-Client-URL' header.
990
90cd841771cf
      version:
991
90cd841771cf
         The client version as a string.  Will be sent to the server
992
90cd841771cf
         as the 'X-Twitter-Client-Version' header.
993
90cd841771cf
    '''
994
90cd841771cf
    self._request_headers['X-Twitter-Client'] = client
995
90cd841771cf
    self._request_headers['X-Twitter-Client-URL'] = url
996
90cd841771cf
    self._request_headers['X-Twitter-Client-Version'] = version
997
4805aee792f0
998
cb10f131e83f
  def SetSource(self, source):
999
cb10f131e83f
    '''Suggest the "from source" value to be displayed on the Twitter web site.
1000
cb10f131e83f
1001
cb10f131e83f
    The value of the 'source' parameter must be first recognized by
1002
cb10f131e83f
    the Twitter server.  New source values are authorized on a case by
1003
cb10f131e83f
    case basis by the Twitter development team.
1004
cb10f131e83f
1005
cb10f131e83f
    Args:
1006
cb10f131e83f
      source:
1007
cb10f131e83f
        The source name as a string.  Will be sent to the server as
1008
cb10f131e83f
        the 'source' parameter.
1009
cb10f131e83f
    '''
1010
cb10f131e83f
    self._default_params['source'] = source
1011
cb10f131e83f
1012
4805aee792f0
  def _BuildUrl(self, url, path_elements=None, extra_params=None):
1013
4805aee792f0
    # Break url into consituent parts
1014
4805aee792f0
    (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
1015
17f566981b41
1016
4805aee792f0
    # Add any additional path elements to the path
1017
4805aee792f0
    if path_elements:
1018
4805aee792f0
      # Filter out the path elements that have a value of None
1019
4805aee792f0
      p = [i for i in path_elements if i]
1020
4805aee792f0
      if not path.endswith('/'):
1021
4805aee792f0
        path += '/'
1022
4805aee792f0
      path += '/'.join(p)
1023
17f566981b41
1024
4805aee792f0
    # Add any additional query parameters to the query string
1025
4805aee792f0
    if extra_params and len(extra_params) > 0:
1026
32a0266e88a8
      extra_query = self._EncodeParameters(extra_params)
1027
b0bbd9fa96ce
      # Add it to the existing query
1028
b0bbd9fa96ce
      if query:
1029
b0bbd9fa96ce
        query += '&' + extra_query
1030
b0bbd9fa96ce
      else:
1031
b0bbd9fa96ce
        query = extra_query
1032
17f566981b41
1033
4805aee792f0
    # Return the rebuilt URL
1034
4805aee792f0
    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
1035
4805aee792f0
1036
90cd841771cf
  def _InitializeRequestHeaders(self, request_headers):
1037
90cd841771cf
    if request_headers:
1038
90cd841771cf
      self._request_headers = request_headers
1039
90cd841771cf
    else:
1040
90cd841771cf
      self._request_headers = {}
1041
90cd841771cf
1042
90cd841771cf
  def _InitializeUserAgent(self):
1043
90cd841771cf
    user_agent = 'Python-urllib/%s (python-twitter/%s)' % \
1044
0ef37898cd2b
                 (self._urllib.__version__, __version__)
1045
90cd841771cf
    self.SetUserAgent(user_agent)
1046
90cd841771cf
1047
cb10f131e83f
  def _InitializeDefaultParameters(self):
1048
cb10f131e83f
    self._default_params = {}
1049
cb10f131e83f
1050
f73c99cf9b51
  def _AddAuthorizationHeader(self, username, password):
1051
f73c99cf9b51
    if username and password:
1052
f73c99cf9b51
      basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1]
1053
f73c99cf9b51
      self._request_headers['Authorization'] = 'Basic %s' % basic_auth
1054
f73c99cf9b51
1055
f73c99cf9b51
  def _RemoveAuthorizationHeader(self):
1056
f73c99cf9b51
    if self._request_headers and 'Authorization' in self._request_headers:
1057
f73c99cf9b51
      del self._request_headers['Authorization']
1058
f73c99cf9b51
1059
4805aee792f0
  def _GetOpener(self, url, username=None, password=None):
1060
4805aee792f0
    if username and password:
1061
f73c99cf9b51
      self._AddAuthorizationHeader(username, password)
1062
4805aee792f0
      handler = self._urllib.HTTPBasicAuthHandler()
1063
4805aee792f0
      (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
1064
4805aee792f0
      handler.add_password(Api._API_REALM, netloc, username, password)
1065
4805aee792f0
      opener = self._urllib.build_opener(handler)
1066
4805aee792f0
    else:
1067
4805aee792f0
      opener = self._urllib.build_opener()
1068
90cd841771cf
    opener.addheaders = self._request_headers.items()
1069
4805aee792f0
    return opener
1070
4805aee792f0
1071
4dfd36cb761d
  def _Encode(self, s):
1072
4dfd36cb761d
    if self._input_encoding:
1073
4dfd36cb761d
      return unicode(s, self._input_encoding).encode('utf-8')
1074
4dfd36cb761d
    else:
1075
4dfd36cb761d
      return unicode(s).encode('utf-8')
1076
4dfd36cb761d
1077
32a0266e88a8
  def _EncodeParameters(self, parameters):
1078
32a0266e88a8
    '''Return a string in key=value&key=value form
1079
32a0266e88a8
1080
32a0266e88a8
    Values of None are not included in the output string.
1081
32a0266e88a8
1082
32a0266e88a8
    Args:
1083
32a0266e88a8
      parameters:
1084
32a0266e88a8
        A dict of (key, value) tuples, where value is encoded as
1085
32a0266e88a8
        specified by self._encoding
1086
32a0266e88a8
    Returns:
1087
32a0266e88a8
      A URL-encoded string in "key=value&key=value" form
1088
32a0266e88a8
    '''
1089
32a0266e88a8
    if parameters is None:
1090
32a0266e88a8
      return None
1091
32a0266e88a8
    else:
1092
4dfd36cb761d
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
1093
32a0266e88a8
1094
32a0266e88a8
  def _EncodePostData(self, post_data):
1095
32a0266e88a8
    '''Return a string in key=value&key=value form
1096
32a0266e88a8
1097
32a0266e88a8
    Values are assumed to be encoded in the format specified by self._encoding,
1098
32a0266e88a8
    and are subsequently URL encoded.
1099
32a0266e88a8
1100
32a0266e88a8
    Args:
1101
32a0266e88a8
      post_data:
1102
32a0266e88a8
        A dict of (key, value) tuples, where value is encoded as
1103
32a0266e88a8
        specified by self._encoding
1104
32a0266e88a8
    Returns:
1105
32a0266e88a8
      A URL-encoded string in "key=value&key=value" form
1106
32a0266e88a8
    '''
1107
32a0266e88a8
    if post_data is None:
1108
32a0266e88a8
      return None
1109
32a0266e88a8
    else:
1110
4dfd36cb761d
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
1111
32a0266e88a8
1112
265fcf2a1e01
  def _CheckForTwitterError(self, data):
1113
265fcf2a1e01
    """Raises a TwitterError if twitter returns an error message.
1114
265fcf2a1e01
1115
265fcf2a1e01
    Args:
1116
265fcf2a1e01
      data: A python dict created from the Twitter json response
1117
265fcf2a1e01
    Raises:
1118
265fcf2a1e01
      TwitterError wrapping the twitter error message if one exists.
1119
265fcf2a1e01
    """
1120
265fcf2a1e01
    # Twitter errors are relatively unlikely, so it is faster
1121
265fcf2a1e01
    # to check first, rather than try and catch the exception
1122
265fcf2a1e01
    if 'error' in data:
1123
265fcf2a1e01
      raise TwitterError(data['error'])
1124
265fcf2a1e01
1125
4805aee792f0
  def _FetchUrl(self,
1126
4805aee792f0
                url,
1127
32a0266e88a8
                post_data=None,
1128
4805aee792f0
                parameters=None,
1129
4805aee792f0
                no_cache=None):
1130
ed04d2ff26fc
    '''Fetch a URL, optionally caching for a specified time.
1131
4805aee792f0
1132
4805aee792f0
    Args:
1133
4805aee792f0
      url: The URL to retrieve
1134
42a0465d458f
      post_data:
1135
b02aa8558d4f
        A dict of (str, unicode) key/value pairs.  If set, POST will be used.
1136
b02aa8558d4f
      parameters:
1137
42a0465d458f
        A dict whose key/value pairs should encoded and added
1138
b02aa8558d4f
        to the query string. [OPTIONAL]
1139
4805aee792f0
      no_cache: If true, overrides the cache on the current request
1140
4805aee792f0
1141
4805aee792f0
    Returns:
1142
4805aee792f0
      A string containing the body of the response.
1143
ed04d2ff26fc
    '''
1144
cb10f131e83f
    # Build the extra parameters dict
1145
cb10f131e83f
    extra_params = {}
1146
cb10f131e83f
    if self._default_params:
1147
cb10f131e83f
      extra_params.update(self._default_params)
1148
cb10f131e83f
    if parameters:
1149
cb10f131e83f
      extra_params.update(parameters)
1150
cb10f131e83f
1151
4805aee792f0
    # Add key/value parameters to the query string of the url
1152
cb10f131e83f
    url = self._BuildUrl(url, extra_params=extra_params)
1153
4805aee792f0
1154
4805aee792f0
    # Get a url opener that can handle basic auth
1155
ee84714ea665
    opener = self._GetOpener(url, username=self._username, password=self._password)
1156
4805aee792f0
1157
32a0266e88a8
    encoded_post_data = self._EncodePostData(post_data)
1158
c42880b017af
1159
4805aee792f0
    # Open and return the URL immediately if we're not going to cache
1160
32a0266e88a8
    if encoded_post_data or no_cache or not self._cache or not self._cache_timeout:
1161
32a0266e88a8
      url_data = opener.open(url, encoded_post_data).read()
1162
c1d12896a51f
      opener.close()
1163
4805aee792f0
    else:
1164
4805aee792f0
      # Unique keys are a combination of the url and the username
1165
ee84714ea665
      if self._username:
1166
ee84714ea665
        key = self._username + ':' + url
1167
4805aee792f0
      else:
1168
4805aee792f0
        key = url
1169
4805aee792f0
1170
4805aee792f0
      # See if it has been cached before
1171
4805aee792f0
      last_cached = self._cache.GetCachedTime(key)
1172
4805aee792f0
1173
4805aee792f0
      # If the cached version is outdated then fetch another and store it
1174
4805aee792f0
      if not last_cached or time.time() >= last_cached + self._cache_timeout:
1175
32a0266e88a8
        url_data = opener.open(url, encoded_post_data).read()
1176
c1d12896a51f
        opener.close()
1177
4805aee792f0
        self._cache.Set(key, url_data)
1178
4805aee792f0
      else:
1179
4805aee792f0
        url_data = self._cache.Get(key)
1180
17f566981b41
1181
4805aee792f0
    # Always return the latest version
1182
4805aee792f0
    return url_data
1183
4805aee792f0
1184
81cef10d2991
1185
4805aee792f0
class _FileCacheError(Exception):
1186
4805aee792f0
  '''Base exception class for FileCache related errors'''
1187
4805aee792f0
1188
4805aee792f0
class _FileCache(object):
1189
17f566981b41
1190
4805aee792f0
  DEPTH = 3
1191
17f566981b41
1192
4805aee792f0
  def __init__(self,root_directory=None):
1193
4805aee792f0
    self._InitializeRootDirectory(root_directory)
1194
17f566981b41
1195
4805aee792f0
  def Get(self,key):
1196
4805aee792f0
    path = self._GetPath(key)
1197
4805aee792f0
    if os.path.exists(path):
1198
4805aee792f0
      return open(path).read()
1199
4805aee792f0
    else:
1200
4805aee792f0
      return None
1201
17f566981b41
1202
4805aee792f0
  def Set(self,key,data):
1203
4805aee792f0
    path = self._GetPath(key)
1204
4805aee792f0
    directory = os.path.dirname(path)
1205
4805aee792f0
    if not os.path.exists(directory):
1206
4805aee792f0
      os.makedirs(directory)
1207
4805aee792f0
    if not os.path.isdir(directory):
1208
4805aee792f0
      raise _FileCacheError('%s exists but is not a directory' % directory)
1209
4805aee792f0
    temp_fd, temp_path = tempfile.mkstemp()
1210
4805aee792f0
    temp_fp = os.fdopen(temp_fd, 'w')
1211
4805aee792f0
    temp_fp.write(data)
1212
4805aee792f0
    temp_fp.close()
1213
4805aee792f0
    if not path.startswith(self._root_directory):
1214
4805aee792f0
      raise _FileCacheError('%s does not appear to live under %s' %
1215
4805aee792f0
                            (path, self._root_directory))
1216
744e374963dc
    if os.path.exists(path):
1217
744e374963dc
      os.remove(path)
1218
4805aee792f0
    os.rename(temp_path, path)
1219
17f566981b41
1220
4805aee792f0
  def Remove(self,key):
1221
4805aee792f0
    path = self._GetPath(key)
1222
4805aee792f0
    if not path.startswith(self._root_directory):
1223
4805aee792f0
      raise _FileCacheError('%s does not appear to live under %s' %
1224
4805aee792f0
                            (path, self._root_directory ))
1225
4805aee792f0
    if os.path.exists(path):
1226
17f566981b41
      os.remove(path)
1227
17f566981b41
1228
4805aee792f0
  def GetCachedTime(self,key):
1229
4805aee792f0
    path = self._GetPath(key)
1230
4805aee792f0
    if os.path.exists(path):
1231
4805aee792f0
      return os.path.getmtime(path)
1232
4805aee792f0
    else:
1233
4805aee792f0
      return None
1234
17f566981b41
1235
795f1c9d49c9
  def _GetUsername(self):
1236
3bef9042269d
    '''Attempt to find the username in a cross-platform fashion.'''
1237
d981d739f261
    try:
1238
d981d739f261
      return os.getenv('USER') or \
1239
d981d739f261
             os.getenv('LOGNAME') or \
1240
d981d739f261
             os.getenv('USERNAME') or \
1241
d981d739f261
             os.getlogin() or \
1242
d981d739f261
             'nobody'
1243
fc7763723e15
    except (IOError, OSError), e:
1244
d981d739f261
      return 'nobody'
1245
795f1c9d49c9
1246
795f1c9d49c9
  def _GetTmpCachePath(self):
1247
795f1c9d49c9
    username = self._GetUsername()
1248
795f1c9d49c9
    cache_directory = 'python.cache_' + username
1249
795f1c9d49c9
    return os.path.join(tempfile.gettempdir(), cache_directory)
1250
795f1c9d49c9
1251
4805aee792f0
  def _InitializeRootDirectory(self, root_directory):
1252
4805aee792f0
    if not root_directory:
1253
795f1c9d49c9
      root_directory = self._GetTmpCachePath()
1254
4805aee792f0
    root_directory = os.path.abspath(root_directory)
1255
4805aee792f0
    if not os.path.exists(root_directory):
1256
4805aee792f0
      os.mkdir(root_directory)
1257
4805aee792f0
    if not os.path.isdir(root_directory):
1258
4805aee792f0
      raise _FileCacheError('%s exists but is not a directory' %
1259
4805aee792f0
                            root_directory)
1260
4805aee792f0
    self._root_directory = root_directory
1261
17f566981b41
1262
4805aee792f0
  def _GetPath(self,key):
1263
a58f4a7ad991
    try:
1264
a58f4a7ad991
        hashed_key = md5(key).hexdigest()
1265
a58f4a7ad991
    except TypeError:
1266
a58f4a7ad991
        hashed_key = md5.new(key).hexdigest()
1267
42a0465d458f
1268
4805aee792f0
    return os.path.join(self._root_directory,
1269
4805aee792f0
                        self._GetPrefix(hashed_key),
1270
4805aee792f0
                        hashed_key)
1271
17f566981b41
1272
4805aee792f0
  def _GetPrefix(self,hashed_key):
1273
4805aee792f0
    return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])