IanLewis / disqus-python-client (http://code.google.com/p/disqus-python-client/)
A Disqus (http://www.disqus.com/) API client written in python.
Clone this repository (size: 34.9 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/IanLewis/disqus-python-client/
| commit 4: | 428fb0e5b45f |
| parent 3: | b82a802e5fee |
| branch: | default |
Added tlslite dateFuncs
- View IanLewis's profile
-
IanLewis's public repos »
- django-lifestream
- 232dtt
- django-hgwebproxy2
- django-piston
- jogging
- django-app-engine
- kay
- HttpMailet
- disqus-python-client
- kay-twitterauth
- kay-choiceauth-demo
- stardict
- langtest
- pyaws
- django-storages
- kay-choiceauth
- feedparser
- django-autocomplete
- django-project-template
- twitter_timeline
- django-hgwebproxy
- django-wwwsqldesigner
- Chunked Response
- my
- misopotato
- jsonref
- jsonschema
- homepage
- growltestrunner
- cmsplugin-news
- oauth-python-twitter
- Snipplr
- Send message
9 months ago
Changed (Δ15.9 KB):
raw changeset »
disqus/__init__.py
src/disqus/__init__.py (399 lines added, 0 lines removed)
src/tlslite/utils/dateFuncs.py (75 lines added, 0 lines removed)
Up to file-list src/disqus/__init__.py:
1 |
#:coding=utf8: |
|
2 |
||
3 |
""" |
|
4 |
disqus-api-client |
|
5 |
||
6 |
A disqus api client for python. |
|
7 |
""" |
|
8 |
||
9 |
import simplejson |
|
10 |
import httplib |
|
11 |
import urllib |
|
12 |
||
13 |
_debug = False |
|
14 |
||
15 |
HOST = "disqus.com" |
|
16 |
BASE_URL = "/api/%s/" |
|
17 |
||
18 |
REQUEST_METHODS = { |
|
19 |
"create_post": "POST", |
|
20 |
"get_forum_list": "GET", |
|
21 |
"get_forum_api_key": "GET", |
|
22 |
"get_thread_list": "GET", |
|
23 |
"get_num_posts": "GET", |
|
24 |
"get_thread_by_url": "GET", |
|
25 |
"get_thread_posts": "GET", |
|
26 |
"thread_by_identiier": "POST", |
|
27 |
"update_thread": "POST", |
|
28 |
} |
|
29 |
||
30 |
class DisqusService(object): |
|
31 |
forum_key_cache = {} |
|
32 |
||
33 |
def login(self, api_key): |
|
34 |
self.user_api_key = api_key |
|
35 |
||
36 |
def create_post(self, **kwargs): |
|
37 |
""" |
|
38 |
Key: Forum Key |
|
39 |
Method: POST |
|
40 |
Arguments: |
|
41 |
Required: |
|
42 |
"thread_id": the thread to post to |
|
43 |
"message": the content of the post |
|
44 |
"author_name": the post creator's name |
|
45 |
"author_email": their email address. |
|
46 |
Optional: |
|
47 |
"parent_post": the id of the parent post |
|
48 |
"created_at": the UTC date this post was created in the format |
|
49 |
%Y-%m-%dT%H:%M (the current time will be used by |
|
50 |
default) |
|
51 |
"author_url": the author's homepage; and "ip_address", their |
|
52 |
IP address. |
|
53 |
||
54 |
Action: Creates a new post on the thread. Does not check against spam |
|
55 |
filters or ban list. This is intended to allow automated |
|
56 |
importing of comments. |
|
57 |
||
58 |
Result: The post object just created. See "Object Formats" header below |
|
59 |
for details on post objects. |
|
60 |
""" |
|
61 |
pass |
|
62 |
||
63 |
def get_forum_list(self): |
|
64 |
""" |
|
65 |
Key: User Key |
|
66 |
Arguments: None. |
|
67 |
||
68 |
Result: A list of objects representing all forums the user owns. The |
|
69 |
user is determined by the API key. See "Object Formats" header |
|
70 |
below for details on forum objects. |
|
71 |
""" |
|
72 |
resp = self._http_request("get_forum_list") |
|
73 |
return [self._decode_forum(f) for f in resp] |
|
74 |
||
75 |
def get_user_api_key(self): |
|
76 |
if not getattr(self, "user_api_key"): |
|
77 |
raise Exception("Please login") |
|
78 |
return self.user_api_key |
|
79 |
||
80 |
def get_forum_api_key(self, forum_id): |
|
81 |
""" |
|
82 |
Key: User Key |
|
83 |
Arguments: "forum_id", the unique id of the forum. |
|
84 |
||
85 |
Result: A string which is the Forum Key for the given forum. |
|
86 |
""" |
|
87 |
if not self.forum_key_cache.get(str(forum_id)): |
|
88 |
self.forum_key_cache[str(forum_id)] = self._http_request("get_forum_api_key", { "forum_id":forum_id }) |
|
89 |
return self.forum_key_cache[str(forum_id)] |
|
90 |
||
91 |
def get_thread_list(self, forum_id): |
|
92 |
""" |
|
93 |
Key: Forum Key |
|
94 |
Arguments: None. |
|
95 |
||
96 |
Result: A list of objects representing all threads belonging to the |
|
97 |
given forum. See "Object Formats" for details on thread |
|
98 |
objects. |
|
99 |
""" |
|
100 |
resp = self._http_request("get_thread_list", { |
|
101 |
"forum_api_key": self.get_forum_api_key(forum_id), |
|
102 |
}) |
|
103 |
return [self._decode_thread(t) for t in resp] |
|
104 |
||
105 |
def get_num_posts(self, forum_id, thread_ids): |
|
106 |
""" |
|
107 |
Key: Forum Key |
|
108 |
Arguments: "thread_ids": a comma-separated list of thread IDs belonging |
|
109 |
to the given forum. |
|
110 |
||
111 |
Result: An object mapping each thread_id to a list of two numbers. The |
|
112 |
first number is the number of visible comments on on the |
|
113 |
thread; this would be useful for showing users of the site |
|
114 |
(e.g., "5 Comments"). The second number is the total number of |
|
115 |
comments on the thread. These numbers are different because |
|
116 |
some forums require moderator approval, some messages are |
|
117 |
flagged as spam, etc. |
|
118 |
""" |
|
119 |
pass |
|
120 |
||
121 |
def get_thread_by_url(self, forum_id, url): |
|
122 |
""" |
|
123 |
Key: Forum Key |
|
124 |
Arguments: "url", the URL to check for an associated thread. |
|
125 |
||
126 |
Result: A thread object if one was found, otherwise null. Only finds |
|
127 |
threads associated with the given forum. Note that there is |
|
128 |
no one-to-one mapping between threads and URLs: a thread will |
|
129 |
only have an associated URL if it was automatically created by |
|
130 |
Disqus javascript embedded on that page. Therefore, we |
|
131 |
recommend using thread_by_identifier whenever possible, and |
|
132 |
this method is provided mainly for handling comments from |
|
133 |
before your forum was using the API. |
|
134 |
""" |
|
135 |
pass |
|
136 |
||
137 |
def get_thread_posts(self, forum_id, thread_id): |
|
138 |
""" |
|
139 |
Key: Forum Key |
|
140 |
Arguments: "thread_id": the ID of a thread belonging to the given |
|
141 |
forum. |
|
142 |
||
143 |
Result: A list of objects representing all posts belonging to the |
|
144 |
given forum. See "Object Formats" for details on post objects. |
|
145 |
""" |
|
146 |
pass |
|
147 |
||
148 |
def thread_by_identifier(self, forum_id, title, identifier): |
|
149 |
""" |
|
150 |
Key: Forum Key |
|
151 |
Method: POST |
|
152 |
Arguments: "title": the title of the thread to possibly be created |
|
153 |
"identifier": a string of your choosing (see Action). |
|
154 |
||
155 |
Action: Create or retrieve a thread by an arbitrary identifying string |
|
156 |
of your choice. For example, you could use your local |
|
157 |
database's ID for the thread. This method allows you to |
|
158 |
decouple thread identifiers from the URLs on which they might |
|
159 |
be appear. (Disqus would normally use a thread's URL to |
|
160 |
identify it, which is problematic when URLs do not uniquely |
|
161 |
identify a resource.) If no thread yet exists for the given |
|
162 |
identifier (paired with the forum), one will be created. |
|
163 |
||
164 |
Result: An object with two keys: |
|
165 |
"thread": which is the thread object corresponding to the |
|
166 |
identifier |
|
167 |
"created": which indicates whether the thread was created |
|
168 |
as a result of this method call. If created, it |
|
169 |
will have the specified title. |
|
170 |
""" |
|
171 |
pass |
|
172 |
||
173 |
def update_thread(self, forum_id, **kwargs): |
|
174 |
""" |
|
175 |
Key: Forum Key |
|
176 |
Method: POST |
|
177 |
Arguments: |
|
178 |
Required: |
|
179 |
"thread_id": the ID of a thread belonging to the given forum. |
|
180 |
Optional: |
|
181 |
any of "title", "slug", "url", and "allow_comments". |
|
182 |
||
183 |
Action: Sets the provided values on the thread object. See Object |
|
184 |
Formats for field meanings. |
|
185 |
||
186 |
Result: An empty success message. |
|
187 |
""" |
|
188 |
pass |
|
189 |
||
190 |
def _http_request(self, method_name, data={}, user_key_required=True): |
|
191 |
if user_key_required: |
|
192 |
data["user_api_key"] = self.get_user_api_key() |
|
193 |
||
194 |
method = REQUEST_METHODS[method_name] |
|
195 |
||
196 |
url = (BASE_URL +"?%s") % (method_name, urllib.urlencode(data)) |
|
197 |
||
198 |
con = httplib.HTTPConnection(HOST) |
|
199 |
con.request(method, url) |
|
200 |
||
201 |
return self._decode_response(simplejson.load(con.getresponse())) |
|
202 |
||
203 |
||
204 |
def _decode_response(self, dct): |
|
205 |
if dct.get("code") == "ok" and dct.get("succeeded"): |
|
206 |
return dct.get("message") |
|
207 |
else: |
|
208 |
#TODO: raise Proper exception |
|
209 |
raise Exception("%s: %s" % (dct.get("code"), dct.get("message"))) |
|
210 |
||
211 |
def _decode_forum(self, dct): |
|
212 |
if _debug: |
|
213 |
print "decode_forum: %r" % dct |
|
214 |
return Forum( |
|
215 |
service=self, |
|
216 |
id=dct.get("id"), |
|
217 |
shortname=dct.get("shortname"), |
|
218 |
name=dct.get("name"), |
|
219 |
) |
|
220 |
||
221 |
def _decode_thread(self, dct): |
|
222 |
if _debug: |
|
223 |
print "decode_thread: %r" % dct |
|
224 |
return Thread( |
|
225 |
id=dct.get("id"), |
|
226 |
forum=dct.get("forum"), |
|
227 |
slug=dct.get("slug"), |
|
228 |
title=dct.get("title"), |
|
229 |
created_at=dct.get("created_at"), |
|
230 |
allow_comments=dct.get("allow_comments"), |
|
231 |
url=dct.get("url"), |
|
232 |
identifier=dct.get("identifier"), |
|
233 |
) |
|
234 |
||
235 |
def _decode_post(self, dct): |
|
236 |
if self._debug: |
|
237 |
print "decode_post: %r" % dct |
|
238 |
return Post( |
|
239 |
id=dct.get("id"), |
|
240 |
forum=dtt.get("forum"), |
|
241 |
thread=dct.get("thread"), |
|
242 |
created_at=dct.get("created_at"), |
|
243 |
message=dct.get("message"), |
|
244 |
parent_post=dct.get("parent_post"), |
|
245 |
shown=dct.get("shown"), |
|
246 |
is_anonymous=dct.get("is_anonymous"), |
|
247 |
anonymous_author=dct.get("anonymous_author"), |
|
248 |
author=dct.get("author"), |
|
249 |
) |
|
250 |
||
251 |
def decode_author(self, dct): |
|
252 |
if self._debug: |
|
253 |
print "decode_author: %r" % dct |
|
254 |
return Author( |
|
255 |
id=dct.get("id"), |
|
256 |
username=dct.get("username"), |
|
257 |
display_name=dct.get("display_name"), |
|
258 |
url=dct.get("url"), |
|
259 |
email_hash=dct.get("email_hash"), |
|
260 |
has_avatar=dct.get("has_avatar"), |
|
261 |
) |
|
262 |
||
263 |
def decode_anonymous_author(self, dct): |
|
264 |
if self._debug: |
|
265 |
print "decode_anonymous_author: %r" % dct |
|
266 |
return AnonymousAuthor( |
|
267 |
name=dct["name"], |
|
268 |
url=dct["url"], |
|
269 |
email_hash=dct["email_hash"], |
|
270 |
) |
|
271 |
||
272 |
class Forum(object): |
|
273 |
""" |
|
274 |
id field: a unique alphanumeric string identifying this Forum object. |
|
275 |
shortname: the unique string used in disqus.com URLs relating to this |
|
276 |
forum. For example, if the shortname is "bmb", the forum's |
|
277 |
community page is at http://bmb.disqus.com/. |
|
278 |
name: a string for displaying the forum's full title, |
|
279 |
like "The Eyeball Kid's Blog". |
|
280 |
""" |
|
281 |
def __init__(self, service, id, shortname, name): |
|
282 |
self.service = service |
|
283 |
self.id = id |
|
284 |
self.shortname = shortname |
|
285 |
self.name = name |
|
286 |
||
287 |
def get_thread_list(self): |
|
288 |
return self.service.get_thread_list(self.id) |
|
289 |
||
290 |
class Thread(object): |
|
291 |
""" |
|
292 |
id: a unique alphanumeric string identifying this Thread object. |
|
293 |
forum: the id for the forum this thread belongs to. |
|
294 |
slug: the per-forum-unique string used for identifying this thread in |
|
295 |
disqus.com URLs relating to this thread. Composed of |
|
296 |
underscore-separated alphanumeric strings. |
|
297 |
title: the title of the thread. |
|
298 |
created_at: the UTC date this thread was created, in the format |
|
299 |
%Y-%m-%dT%H:%M. |
|
300 |
allow_comments: whether this thread is open to new comments. |
|
301 |
url: the URL this thread is on, if known. |
|
302 |
identifier: the user-provided identifier for this thread, as in |
|
303 |
thread_by_identifier above (if available) |
|
304 |
""" |
|
305 |
def __init__(self, |
|
306 |
id, |
|
307 |
forum, |
|
308 |
slug, |
|
309 |
title, |
|
310 |
created_at, |
|
311 |
allow_comments, |
|
312 |
url, |
|
313 |
identifier): |
|
314 |
self.id = id |
|
315 |
self.forum = forum |
|
316 |
self.slug = slug |
|
317 |
self.title = title |
|
318 |
self.created_at = created_at |
|
319 |
self.allow_comments = allow_comments |
|
320 |
self.url = url |
|
321 |
self.identifier = identifier |
|
322 |
||
323 |
class Post(object): |
|
324 |
def __init__(self, |
|
325 |
id, |
|
326 |
forum, |
|
327 |
thread, |
|
328 |
created_at, |
|
329 |
message, |
|
330 |
parent_post, |
|
331 |
shown, |
|
332 |
is_anonymous=False, |
|
333 |
anonymous_author=None, |
|
334 |
author=None): |
|
335 |
""" |
|
336 |
id: a unique alphanumeric string identifying this Post object. |
|
337 |
forum: the id for the forum this post belongs to. |
|
338 |
thread: the id for the thread this post belongs to. |
|
339 |
created_at: the UTC date this post was created, in the format %Y-%m-%dT%H:%M. |
|
340 |
message: the contents of the post, such as "First post". |
|
341 |
parent_post: the id of the parent post, if any |
|
342 |
shown: whether the post is currently visible or not. |
|
343 |
is_anonymous: whether the comment was left anonymously, as opposed to a |
|
344 |
registered Disqus account. |
|
345 |
anonymous_author: An AnoymousAuthor object. Present only when is_anonymous is true. |
|
346 |
author: Author object. Present only when is_anonymous is false. An object containing these fields: |
|
347 |
||
348 |
""" |
|
349 |
self.id = id |
|
350 |
self.forum = forum |
|
351 |
self.thread = thread |
|
352 |
self.created_at = created_at |
|
353 |
self.message = message |
|
354 |
self.parent_post = parent_post |
|
355 |
self.shown = shown |
|
356 |
self.is_anonymous = is_anonymous |
|
357 |
if self.is_anoymous: |
|
358 |
self.anonymous_author = anonymous_author |
|
359 |
self.author = None |
|
360 |
else: |
|
361 |
self.author = author |
|
362 |
self.anonymous_author = None |
|
363 |
||
364 |
class Author(object): |
|
365 |
def __init__(self, |
|
366 |
id, |
|
367 |
username, |
|
368 |
display_name, |
|
369 |
url, |
|
370 |
email_hash, |
|
371 |
has_avatar): |
|
372 |
""" |
|
373 |
id: the unique id of the commenter's Disqus account |
|
374 |
username: the author's username |
|
375 |
display_name: the author's full name, if provided |
|
376 |
url: their optionally provided homepage |
|
377 |
email_hash: md5 of the author's email address |
|
378 |
has_avatar: whether the user has an avatar on disqus.com |
|
379 |
""" |
|
380 |
self.id = id |
|
381 |
self.username = username |
|
382 |
self.display_name = display_name |
|
383 |
self.url = url |
|
384 |
self.email_hash = email_hash |
|
385 |
self.has_avatar = has_avatar |
|
386 |
||
387 |
class AnonymousAuthor(object): |
|
388 |
def __init__(self, |
|
389 |
name, |
|
390 |
url, |
|
391 |
email_hash): |
|
392 |
""" |
|
393 |
name: the display name of the commenter |
|
394 |
url: their optionally provided homepage |
|
395 |
email_hash: md5 of the author's email address |
|
396 |
""" |
|
397 |
self.name = name |
|
398 |
self.url = url |
|
399 |
self.email_hash = email_hash |
Up to file-list src/tlslite/utils/dateFuncs.py:
1 |
||
2 |
import os |
|
3 |
||
4 |
#Functions for manipulating datetime objects |
|
5 |
#CCYY-MM-DDThh:mm:ssZ |
|
6 |
def parseDateClass(s): |
|
7 |
year, month, day = s.split("-") |
|
8 |
day, tail = day[:2], day[2:] |
|
9 |
hour, minute, second = tail[1:].split(":") |
|
10 |
second = second[:2] |
|
11 |
year, month, day = int(year), int(month), int(day) |
|
12 |
hour, minute, second = int(hour), int(minute), int(second) |
|
13 |
return createDateClass(year, month, day, hour, minute, second) |
|
14 |
||
15 |
||
16 |
if os.name != "java": |
|
17 |
from datetime import datetime, timedelta |
|
18 |
||
19 |
#Helper functions for working with a date/time class |
|
20 |
def createDateClass(year, month, day, hour, minute, second): |
|
21 |
return datetime(year, month, day, hour, minute, second) |
|
22 |
||
23 |
def printDateClass(d): |
|
24 |
#Split off fractional seconds, append 'Z' |
|
25 |
return d.isoformat().split(".")[0]+"Z" |
|
26 |
||
27 |
def getNow(): |
|
28 |
return datetime.utcnow() |
|
29 |
||
30 |
def getHoursFromNow(hours): |
|
31 |
return datetime.utcnow() + timedelta(hours=hours) |
|
32 |
||
33 |
def getMinutesFromNow(minutes): |
|
34 |
return datetime.utcnow() + timedelta(minutes=minutes) |
|
35 |
||
36 |
def isDateClassExpired(d): |
|
37 |
return d < datetime.utcnow() |
|
38 |
||
39 |
def isDateClassBefore(d1, d2): |
|
40 |
return d1 < d2 |
|
41 |
||
42 |
else: |
|
43 |
#Jython 2.1 is missing lots of python 2.3 stuff, |
|
44 |
#which we have to emulate here: |
|
45 |
import java |
|
46 |
import jarray |
|
47 |
||
48 |
def createDateClass(year, month, day, hour, minute, second): |
|
49 |
c = java.util.Calendar.getInstance() |
|
50 |
c.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) |
|
51 |
c.set(year, month-1, day, hour, minute, second) |
|
52 |
return c |
|
53 |
||
54 |
def printDateClass(d): |
|
55 |
return "%04d-%02d-%02dT%02d:%02d:%02dZ" % \ |
|
56 |
(d.get(d.YEAR), d.get(d.MONTH)+1, d.get(d.DATE), \ |
|
57 |
d.get(d.HOUR_OF_DAY), d.get(d.MINUTE), d.get(d.SECOND)) |
|
58 |
||
59 |
def getNow(): |
|
60 |
c = java.util.Calendar.getInstance() |
|
61 |
c.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) |
|
62 |
c.get(c.HOUR) #force refresh? |
|
63 |
return c |
|
64 |
||
65 |
def getHoursFromNow(hours): |
|
66 |
d = getNow() |
|
67 |
d.add(d.HOUR, hours) |
|
68 |
return d |
|
69 |
||
70 |
def isDateClassExpired(d): |
|
71 |
n = getNow() |
|
72 |
return d.before(n) |
|
73 |
||
74 |
def isDateClassBefore(d1, d2): |
|
75 |
return d1.before(d2) |
