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 againpython-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]) |