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)