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
42a0465d458f
parent
19260f87c32a
branch
dclinton

Cleanup whitespace

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
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
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
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
42a0465d458f
  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
42a0465d458f
      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
42a0465d458f
      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
42a0465d458f
      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
42a0465d458f
  def Search(self,
824
42a0465d458f
             query,
825
42a0465d458f
             lang=None,
826
42a0465d458f
             rpp=None,
827
42a0465d458f
             page=None,
828
42a0465d458f
             since_id=None,
829
42a0465d458f
             geocode=None,
830
19260f87c32a
             show_user=None):
831
19260f87c32a
    '''Returns tweets that match a specified query.
832
19260f87c32a
833
19260f87c32a
    Args:
834
19260f87c32a
      query: The search query string, must be less than 140 characters
835
42a0465d458f
      lang:
836
42a0465d458f
        Restricts tweets to the given language, given by an ISO 639-1
837
19260f87c32a
        code. [Optional]
838
19260f87c32a
      rpp:
839
19260f87c32a
        The number of tweets to return per page, up to a max of 100. [Optional]
840
42a0465d458f
      page:
841
19260f87c32a
        The page number (starting at 1) to return, up to a max of
842
19260f87c32a
        roughly 1500 results (based on rpp * page. Note: there are
843
19260f87c32a
        pagination limits. [Optional]
844
42a0465d458f
      since_id:
845
19260f87c32a
        Returns tweets with status ids greater than the given id. [Optional]
846
42a0465d458f
      geocode:
847
19260f87c32a
        Returns tweets by users located within a given radius of the
848
19260f87c32a
        given latitude/longitude, where the user's location is taken
849
19260f87c32a
        from their Twitter profile. The parameter value is specified
850
19260f87c32a
        by "latitide,longitude,radius", where radius units must be
851
19260f87c32a
        specified as either "mi" (miles) or "km" (kilometers). Note
852
19260f87c32a
        that you cannot use the near operator via the API to geocode
853
19260f87c32a
        arbitrary locations; however you can use this geocode
854
19260f87c32a
        parameter to search near geocodes directly. [Optional]
855
19260f87c32a
      show_user:
856
19260f87c32a
        When true, prepends "<user>:" to the beginning of the
857
19260f87c32a
        tweet. This is useful for readers that do not display Atom's
858
19260f87c32a
        author field. The default is false. [Optional]
859
19260f87c32a
    Returns:
860
19260f87c32a
      A Results instance representing the search results
861
19260f87c32a
    '''
862
19260f87c32a
    url = 'http://search.twitter.com/search.json'
863
42a0465d458f
864
19260f87c32a
    parameters = {'q': query}
865
19260f87c32a
    if len(query) > 140:
866
19260f87c32a
      raise TwitterError('query must be <= 140 characters')
867
19260f87c32a
    if lang:
868
19260f87c32a
      parameters['lang'] = lang
869
19260f87c32a
    if rpp is not None:
870
19260f87c32a
      try:
871
19260f87c32a
        if int(rpp) > 100:
872
19260f87c32a
          raise TwitterError("'rpp' may not be greater than 100")
873
19260f87c32a
      except ValueError:
874
19260f87c32a
        raise TwitterError("'rpp' must be an integer")
875
19260f87c32a
      parameters['rpp'] = rpp
876
19260f87c32a
    if page:
877
19260f87c32a
      parameters['page'] = page
878
19260f87c32a
    if since_id:
879
19260f87c32a
      parameters['since_id'] = since_id
880
19260f87c32a
    if geocode:
881
19260f87c32a
      parameters['geocode'] = geocode
882
19260f87c32a
    if show_user:
883
19260f87c32a
      parameters['show_user'] = show_user
884
19260f87c32a
    json = self._FetchUrl(url, parameters=parameters)
885
19260f87c32a
    data = simplejson.loads(json)
886
19260f87c32a
    self._CheckForTwitterError(data)
887
19260f87c32a
    return NewResultsFromJsonDict(data)
888
19260f87c32a
889
ee84714ea665
  def SetCredentials(self, username, password):
890
ee84714ea665
    '''Set the username and password for this instance
891
ee84714ea665
892
ee84714ea665
    Args:
893
ee84714ea665
      username: The twitter username.
894
ee84714ea665
      password: The twitter password.
895
ee84714ea665
    '''
896
ee84714ea665
    self._username = username
897
ee84714ea665
    self._password = password
898
ee84714ea665
899
ee84714ea665
  def ClearCredentials(self):
900
ee84714ea665
    '''Clear the username and password for this instance
901
ee84714ea665
    '''
902
ee84714ea665
    self._username = None
903
ee84714ea665
    self._password = None
904
4805aee792f0
905
4805aee792f0
  def SetCache(self, cache):
906
4805aee792f0
    '''Override the default cache.  Set to None to prevent caching.
907
4805aee792f0
908
4805aee792f0
    Args:
909
4805aee792f0
      cache: an instance that supports the same API as the  twitter._FileCache
910
4805aee792f0
    '''
911
4805aee792f0
    self._cache = cache
912
4805aee792f0
913
4805aee792f0
  def SetUrllib(self, urllib):
914
4805aee792f0
    '''Override the default urllib implementation.
915
4805aee792f0
916
4805aee792f0
    Args:
917
4805aee792f0
      urllib: an instance that supports the same API as the urllib2 module
918
4805aee792f0
    '''
919
4805aee792f0
    self._urllib = urllib
920
4805aee792f0
921
4805aee792f0
  def SetCacheTimeout(self, cache_timeout):
922
4805aee792f0
    '''Override the default cache timeout.
923
4805aee792f0
924
4805aee792f0
    Args:
925
4805aee792f0
      cache_timeout: time, in seconds, that responses should be reused.
926
4805aee792f0
    '''
927
4805aee792f0
    self._cache_timeout = cache_timeout
928
4805aee792f0
929
4805aee792f0
  def SetUserAgent(self, user_agent):
930
4805aee792f0
    '''Override the default user agent
931
4805aee792f0
932
4805aee792f0
    Args:
933
4805aee792f0
      user_agent: a string that should be send to the server as the User-agent
934
4805aee792f0
    '''
935
90cd841771cf
    self._request_headers['User-Agent'] = user_agent
936
90cd841771cf
937
90cd841771cf
  def SetXTwitterHeaders(self, client, url, version):
938
90cd841771cf
    '''Set the X-Twitter HTTP headers that will be sent to the server.
939
90cd841771cf
940
90cd841771cf
    Args:
941
90cd841771cf
      client:
942
90cd841771cf
         The client name as a string.  Will be sent to the server as
943
90cd841771cf
         the 'X-Twitter-Client' header.
944
90cd841771cf
      url:
945
90cd841771cf
         The URL of the meta.xml as a string.  Will be sent to the server
946
90cd841771cf
         as the 'X-Twitter-Client-URL' header.
947
90cd841771cf
      version:
948
90cd841771cf
         The client version as a string.  Will be sent to the server
949
90cd841771cf
         as the 'X-Twitter-Client-Version' header.
950
90cd841771cf
    '''
951
90cd841771cf
    self._request_headers['X-Twitter-Client'] = client
952
90cd841771cf
    self._request_headers['X-Twitter-Client-URL'] = url
953
90cd841771cf
    self._request_headers['X-Twitter-Client-Version'] = version
954
4805aee792f0
955
cb10f131e83f
  def SetSource(self, source):
956
cb10f131e83f
    '''Suggest the "from source" value to be displayed on the Twitter web site.
957
cb10f131e83f
958
cb10f131e83f
    The value of the 'source' parameter must be first recognized by
959
cb10f131e83f
    the Twitter server.  New source values are authorized on a case by
960
cb10f131e83f
    case basis by the Twitter development team.
961
cb10f131e83f
962
cb10f131e83f
    Args:
963
cb10f131e83f
      source:
964
cb10f131e83f
        The source name as a string.  Will be sent to the server as
965
cb10f131e83f
        the 'source' parameter.
966
cb10f131e83f
    '''
967
cb10f131e83f
    self._default_params['source'] = source
968
cb10f131e83f
969
4805aee792f0
  def _BuildUrl(self, url, path_elements=None, extra_params=None):
970
4805aee792f0
    # Break url into consituent parts
971
4805aee792f0
    (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
972
17f566981b41
973
4805aee792f0
    # Add any additional path elements to the path
974
4805aee792f0
    if path_elements:
975
4805aee792f0
      # Filter out the path elements that have a value of None
976
4805aee792f0
      p = [i for i in path_elements if i]
977
4805aee792f0
      if not path.endswith('/'):
978
4805aee792f0
        path += '/'
979
4805aee792f0
      path += '/'.join(p)
980
17f566981b41
981
4805aee792f0
    # Add any additional query parameters to the query string
982
4805aee792f0
    if extra_params and len(extra_params) > 0:
983
32a0266e88a8
      extra_query = self._EncodeParameters(extra_params)
984
b0bbd9fa96ce
      # Add it to the existing query
985
b0bbd9fa96ce
      if query:
986
b0bbd9fa96ce
        query += '&' + extra_query
987
b0bbd9fa96ce
      else:
988
b0bbd9fa96ce
        query = extra_query
989
17f566981b41
990
4805aee792f0
    # Return the rebuilt URL
991
4805aee792f0
    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
992
4805aee792f0
993
90cd841771cf
  def _InitializeRequestHeaders(self, request_headers):
994
90cd841771cf
    if request_headers:
995
90cd841771cf
      self._request_headers = request_headers
996
90cd841771cf
    else:
997
90cd841771cf
      self._request_headers = {}
998
90cd841771cf
999
90cd841771cf
  def _InitializeUserAgent(self):
1000
90cd841771cf
    user_agent = 'Python-urllib/%s (python-twitter/%s)' % \
1001
0ef37898cd2b
                 (self._urllib.__version__, __version__)
1002
90cd841771cf
    self.SetUserAgent(user_agent)
1003
90cd841771cf
1004
cb10f131e83f
  def _InitializeDefaultParameters(self):
1005
cb10f131e83f
    self._default_params = {}
1006
cb10f131e83f
1007
f73c99cf9b51
  def _AddAuthorizationHeader(self, username, password):
1008
f73c99cf9b51
    if username and password:
1009
f73c99cf9b51
      basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1]
1010
f73c99cf9b51
      self._request_headers['Authorization'] = 'Basic %s' % basic_auth
1011
f73c99cf9b51
1012
f73c99cf9b51
  def _RemoveAuthorizationHeader(self):
1013
f73c99cf9b51
    if self._request_headers and 'Authorization' in self._request_headers:
1014
f73c99cf9b51
      del self._request_headers['Authorization']
1015
f73c99cf9b51
1016
4805aee792f0
  def _GetOpener(self, url, username=None, password=None):
1017
4805aee792f0
    if username and password:
1018
f73c99cf9b51
      self._AddAuthorizationHeader(username, password)
1019
4805aee792f0
      handler = self._urllib.HTTPBasicAuthHandler()
1020
4805aee792f0
      (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
1021
4805aee792f0
      handler.add_password(Api._API_REALM, netloc, username, password)
1022
4805aee792f0
      opener = self._urllib.build_opener(handler)
1023
4805aee792f0
    else:
1024
4805aee792f0
      opener = self._urllib.build_opener()
1025
90cd841771cf
    opener.addheaders = self._request_headers.items()
1026
4805aee792f0
    return opener
1027
4805aee792f0
1028
4dfd36cb761d
  def _Encode(self, s):
1029
4dfd36cb761d
    if self._input_encoding:
1030
4dfd36cb761d
      return unicode(s, self._input_encoding).encode('utf-8')
1031
4dfd36cb761d
    else:
1032
4dfd36cb761d
      return unicode(s).encode('utf-8')
1033
4dfd36cb761d
1034
32a0266e88a8
  def _EncodeParameters(self, parameters):
1035
32a0266e88a8
    '''Return a string in key=value&key=value form
1036
32a0266e88a8
1037
32a0266e88a8
    Values of None are not included in the output string.
1038
32a0266e88a8
1039
32a0266e88a8
    Args:
1040
32a0266e88a8
      parameters:
1041
32a0266e88a8
        A dict of (key, value) tuples, where value is encoded as
1042
32a0266e88a8
        specified by self._encoding
1043
32a0266e88a8
    Returns:
1044
32a0266e88a8
      A URL-encoded string in "key=value&key=value" form
1045
32a0266e88a8
    '''
1046
32a0266e88a8
    if parameters is None:
1047
32a0266e88a8
      return None
1048
32a0266e88a8
    else:
1049
4dfd36cb761d
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
1050
32a0266e88a8
1051
32a0266e88a8
  def _EncodePostData(self, post_data):
1052
32a0266e88a8
    '''Return a string in key=value&key=value form
1053
32a0266e88a8
1054
32a0266e88a8
    Values are assumed to be encoded in the format specified by self._encoding,
1055
32a0266e88a8
    and are subsequently URL encoded.
1056
32a0266e88a8
1057
32a0266e88a8
    Args:
1058
32a0266e88a8
      post_data:
1059
32a0266e88a8
        A dict of (key, value) tuples, where value is encoded as
1060
32a0266e88a8
        specified by self._encoding
1061
32a0266e88a8
    Returns:
1062
32a0266e88a8
      A URL-encoded string in "key=value&key=value" form
1063
32a0266e88a8
    '''
1064
32a0266e88a8
    if post_data is None:
1065
32a0266e88a8
      return None
1066
32a0266e88a8
    else:
1067
4dfd36cb761d
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
1068
32a0266e88a8
1069
265fcf2a1e01
  def _CheckForTwitterError(self, data):
1070
265fcf2a1e01
    """Raises a TwitterError if twitter returns an error message.
1071
265fcf2a1e01
1072
265fcf2a1e01
    Args:
1073
265fcf2a1e01
      data: A python dict created from the Twitter json response
1074
265fcf2a1e01
    Raises:
1075
265fcf2a1e01
      TwitterError wrapping the twitter error message if one exists.
1076
265fcf2a1e01
    """
1077
265fcf2a1e01
    # Twitter errors are relatively unlikely, so it is faster
1078
265fcf2a1e01
    # to check first, rather than try and catch the exception
1079
265fcf2a1e01
    if 'error' in data:
1080
265fcf2a1e01
      raise TwitterError(data['error'])
1081
265fcf2a1e01
1082
4805aee792f0
  def _FetchUrl(self,
1083
4805aee792f0
                url,
1084
32a0266e88a8
                post_data=None,
1085
4805aee792f0
                parameters=None,
1086
4805aee792f0
                no_cache=None):
1087
ed04d2ff26fc
    '''Fetch a URL, optionally caching for a specified time.
1088
4805aee792f0
1089
4805aee792f0
    Args:
1090
4805aee792f0
      url: The URL to retrieve
1091
42a0465d458f
      post_data:
1092
b02aa8558d4f
        A dict of (str, unicode) key/value pairs.  If set, POST will be used.
1093
b02aa8558d4f
      parameters:
1094
42a0465d458f
        A dict whose key/value pairs should encoded and added
1095
b02aa8558d4f
        to the query string. [OPTIONAL]
1096
4805aee792f0
      no_cache: If true, overrides the cache on the current request
1097
4805aee792f0
1098
4805aee792f0
    Returns:
1099
4805aee792f0
      A string containing the body of the response.
1100
ed04d2ff26fc
    '''
1101
cb10f131e83f
    # Build the extra parameters dict
1102
cb10f131e83f
    extra_params = {}
1103
cb10f131e83f
    if self._default_params:
1104
cb10f131e83f
      extra_params.update(self._default_params)
1105
cb10f131e83f
    if parameters:
1106
cb10f131e83f
      extra_params.update(parameters)
1107
cb10f131e83f
1108
4805aee792f0
    # Add key/value parameters to the query string of the url
1109
cb10f131e83f
    url = self._BuildUrl(url, extra_params=extra_params)
1110
4805aee792f0
1111
4805aee792f0
    # Get a url opener that can handle basic auth
1112
ee84714ea665
    opener = self._GetOpener(url, username=self._username, password=self._password)
1113
4805aee792f0
1114
32a0266e88a8
    encoded_post_data = self._EncodePostData(post_data)
1115
c42880b017af
1116
4805aee792f0
    # Open and return the URL immediately if we're not going to cache
1117
32a0266e88a8
    if encoded_post_data or no_cache or not self._cache or not self._cache_timeout:
1118
32a0266e88a8
      url_data = opener.open(url, encoded_post_data).read()
1119
c1d12896a51f
      opener.close()
1120
4805aee792f0
    else:
1121
4805aee792f0
      # Unique keys are a combination of the url and the username
1122
ee84714ea665
      if self._username:
1123
ee84714ea665
        key = self._username + ':' + url
1124
4805aee792f0
      else:
1125
4805aee792f0
        key = url
1126
4805aee792f0
1127
4805aee792f0
      # See if it has been cached before
1128
4805aee792f0
      last_cached = self._cache.GetCachedTime(key)
1129
4805aee792f0
1130
4805aee792f0
      # If the cached version is outdated then fetch another and store it
1131
4805aee792f0
      if not last_cached or time.time() >= last_cached + self._cache_timeout:
1132
32a0266e88a8
        url_data = opener.open(url, encoded_post_data).read()
1133
c1d12896a51f
        opener.close()
1134
4805aee792f0
        self._cache.Set(key, url_data)
1135
4805aee792f0
      else:
1136
4805aee792f0
        url_data = self._cache.Get(key)
1137
17f566981b41
1138
4805aee792f0
    # Always return the latest version
1139
4805aee792f0
    return url_data
1140
4805aee792f0
1141
81cef10d2991
1142
4805aee792f0
class _FileCacheError(Exception):
1143
4805aee792f0
  '''Base exception class for FileCache related errors'''
1144
4805aee792f0
1145
4805aee792f0
class _FileCache(object):
1146
17f566981b41
1147
4805aee792f0
  DEPTH = 3
1148
17f566981b41
1149
4805aee792f0
  def __init__(self,root_directory=None):
1150
4805aee792f0
    self._InitializeRootDirectory(root_directory)
1151
17f566981b41
1152
4805aee792f0
  def Get(self,key):
1153
4805aee792f0
    path = self._GetPath(key)
1154
4805aee792f0
    if os.path.exists(path):
1155
4805aee792f0
      return open(path).read()
1156
4805aee792f0
    else:
1157
4805aee792f0
      return None
1158
17f566981b41
1159
4805aee792f0
  def Set(self,key,data):
1160
4805aee792f0
    path = self._GetPath(key)
1161
4805aee792f0
    directory = os.path.dirname(path)
1162
4805aee792f0
    if not os.path.exists(directory):
1163
4805aee792f0
      os.makedirs(directory)
1164
4805aee792f0
    if not os.path.isdir(directory):
1165
4805aee792f0
      raise _FileCacheError('%s exists but is not a directory' % directory)
1166
4805aee792f0
    temp_fd, temp_path = tempfile.mkstemp()
1167
4805aee792f0
    temp_fp = os.fdopen(temp_fd, 'w')
1168
4805aee792f0
    temp_fp.write(data)
1169
4805aee792f0
    temp_fp.close()
1170
4805aee792f0
    if not path.startswith(self._root_directory):
1171
4805aee792f0
      raise _FileCacheError('%s does not appear to live under %s' %
1172
4805aee792f0
                            (path, self._root_directory))
1173
744e374963dc
    if os.path.exists(path):
1174
744e374963dc
      os.remove(path)
1175
4805aee792f0
    os.rename(temp_path, path)
1176
17f566981b41
1177
4805aee792f0
  def Remove(self,key):
1178
4805aee792f0
    path = self._GetPath(key)
1179
4805aee792f0
    if not path.startswith(self._root_directory):
1180
4805aee792f0
      raise _FileCacheError('%s does not appear to live under %s' %
1181
4805aee792f0
                            (path, self._root_directory ))
1182
4805aee792f0
    if os.path.exists(path):
1183
17f566981b41
      os.remove(path)
1184
17f566981b41
1185
4805aee792f0
  def GetCachedTime(self,key):
1186
4805aee792f0
    path = self._GetPath(key)
1187
4805aee792f0
    if os.path.exists(path):
1188
4805aee792f0
      return os.path.getmtime(path)
1189
4805aee792f0
    else:
1190
4805aee792f0
      return None
1191
17f566981b41
1192
795f1c9d49c9
  def _GetUsername(self):
1193
3bef9042269d
    '''Attempt to find the username in a cross-platform fashion.'''
1194
d981d739f261
    try:
1195
d981d739f261
      return os.getenv('USER') or \
1196
d981d739f261
             os.getenv('LOGNAME') or \
1197
d981d739f261
             os.getenv('USERNAME') or \
1198
d981d739f261
             os.getlogin() or \
1199
d981d739f261
             'nobody'
1200
fc7763723e15
    except (IOError, OSError), e:
1201
d981d739f261
      return 'nobody'
1202
795f1c9d49c9
1203
795f1c9d49c9
  def _GetTmpCachePath(self):
1204
795f1c9d49c9
    username = self._GetUsername()
1205
795f1c9d49c9
    cache_directory = 'python.cache_' + username
1206
795f1c9d49c9
    return os.path.join(tempfile.gettempdir(), cache_directory)
1207
795f1c9d49c9
1208
4805aee792f0
  def _InitializeRootDirectory(self, root_directory):
1209
4805aee792f0
    if not root_directory:
1210
795f1c9d49c9
      root_directory = self._GetTmpCachePath()
1211
4805aee792f0
    root_directory = os.path.abspath(root_directory)
1212
4805aee792f0
    if not os.path.exists(root_directory):
1213
4805aee792f0
      os.mkdir(root_directory)
1214
4805aee792f0
    if not os.path.isdir(root_directory):
1215
4805aee792f0
      raise _FileCacheError('%s exists but is not a directory' %
1216
4805aee792f0
                            root_directory)
1217
4805aee792f0
    self._root_directory = root_directory
1218
17f566981b41
1219
4805aee792f0
  def _GetPath(self,key):
1220
a58f4a7ad991
    try:
1221
a58f4a7ad991
        hashed_key = md5(key).hexdigest()
1222
a58f4a7ad991
    except TypeError:
1223
a58f4a7ad991
        hashed_key = md5.new(key).hexdigest()
1224
42a0465d458f
1225
4805aee792f0
    return os.path.join(self._root_directory,
1226
4805aee792f0
                        self._GetPrefix(hashed_key),
1227
4805aee792f0
                        hashed_key)
1228
17f566981b41
1229
4805aee792f0
  def _GetPrefix(self,hashed_key):
1230
4805aee792f0
    return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])