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