mwilden-disqus 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008-2010 Norman Clarke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require "rake"
2
+ require "rake/testtask"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "rake/clean"
6
+ require 'lib/disqus/version'
7
+
8
+ CLEAN << "pkg" << "doc" << "coverage"
9
+
10
+ Rake::GemPackageTask.new(eval(File.read("mwilden-disqus.gemspec"))) { |pkg| }
11
+ Rake::TestTask.new(:test) { |t| t.pattern = "test/*_test.rb" }
12
+
13
+ Rake::RDocTask.new do |r|
14
+ r.rdoc_dir = "doc"
15
+ r.rdoc_files.include "lib/**/*.rb"
16
+ end
17
+
18
+ begin
19
+ require "rcov/rcovtask"
20
+ Rcov::RcovTask.new do |r|
21
+ r.test_files = FileList["test/**/*_test.rb"]
22
+ r.verbose = true
23
+ r.rcov_opts << "--exclude gems/*"
24
+ end
25
+ rescue LoadError
26
+ end
data/lib/disqus/api.rb ADDED
@@ -0,0 +1,258 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module Disqus
7
+
8
+ # == Disqus API
9
+ #
10
+ # The Api class implements the Disqus API directly. It is not really
11
+ # intended to be used directly, but rather to use the domain objects of
12
+ # Forum, Thread, Post, Author and AnonymousAuthor. For full information on
13
+ # the Disqus API, please see the {Disqus developer info}[http://disqus.com/docs/api/].
14
+ #
15
+ # Each method in the Api class takes as a single argument a hash of options,
16
+ # and returns a Hash with 3 keys:
17
+ #
18
+ # * 'succeeded' - contains true or false indicating whether the API call succeeded
19
+ # * 'code' - if the API call did not succeed, this will contain an error code.
20
+ # * 'message' - contains the object being returned on success, or an error message on failure.
21
+ #
22
+ # === API Keys
23
+ #
24
+ # There are two different kinds of API keys:
25
+ #
26
+ # ==== User Keys
27
+ #
28
+ # Every Disqus account has a User Key; it is used to perform actions
29
+ # associated with that account. This can be passed in as an option, or
30
+ # configured as follows:
31
+ #
32
+ # Disqus::defaults[:api_key] = "the_user_api_key"
33
+ #
34
+ # ==== Forum Keys
35
+ #
36
+ # Every Disqus forum has a Forum Key. It can be shared among trusted
37
+ # moderators of a forum, and is used to perform actions associated with that
38
+ # forum. The creator of a forum can get the forum's key through the API.
39
+ class Api
40
+
41
+ ROOT = 'http://disqus.com/api'
42
+ API_VERSION = '1.1'
43
+
44
+ class << self
45
+
46
+ # Creates a new post on the thread. Does not check against spam filters or ban list.
47
+ # This is intended to allow automated importing of comments.
48
+ #
49
+ # Returns a Hash containing a representation of the post just created:
50
+ #
51
+ # Required options hash elements:
52
+ #
53
+ # * <tt>:forum_api_key</tt> - the API key for the forum
54
+ # * <tt>:thread_id</tt> - the thread to post to
55
+ # * <tt>:message</tt> - the content of the post
56
+ # * <tt>:author_name</tt> - the post creator's name
57
+ # * <tt>:author_email</tt> - the post creator's email address
58
+ #
59
+ # Optional:
60
+ #
61
+ # * <tt>:parent_post</tt> - the id of the parent post
62
+ # * <tt>:created_at</tt> - the UTC date this post was created, in the format <tt>%Y-%m-%dT%H:%M</tt> (the current time will be used by default)
63
+ # * <tt>:author_url</tt> - the author's homepage
64
+ # * <tt>:ip_address</tt> - the author's IP address
65
+ def create_post(opts = {})
66
+ opts[:api_key] ||= Disqus::defaults[:api_key]
67
+ post_data = {
68
+ :forum_api_key => opts[:forum_api_key],
69
+ :thread_id => opts[:thread_id],
70
+ :message => opts[:message],
71
+ :author_name => opts[:author_name],
72
+ :author_email => opts[:author_email]
73
+ }
74
+ [:parent_post, :created_at, :author_url, :ip_address].each do |key|
75
+ post_data[key] = opts[key] if opts[key]
76
+ end
77
+ JSON.parse(post('create_post/', post_data))
78
+ end
79
+
80
+ # Returns an array of hashes representing all forums the user owns. The
81
+ # user is determined by the API key.
82
+ #
83
+ # Options:
84
+ #
85
+ # * <tt>:api_key</tt> - The User's API key (defaults to
86
+ # Disqus::defaults[:api_key])
87
+ def get_forum_list(opts = {})
88
+ opts[:api_key] ||= Disqus::defaults[:api_key]
89
+ JSON.parse(get('get_forum_list', :user_api_key => opts[:api_key]))
90
+ end
91
+
92
+ # Returns A string which is the Forum Key for the given forum.
93
+ #
94
+ # Required options hash elements:
95
+ #
96
+ # * <tt>:forum_id</tt> - the unique id of the forum
97
+ #
98
+ # Optional:
99
+ #
100
+ # * <tt>:api_key</tt> - The User's API key (defaults to Disqus::defaults[:api_key])
101
+ def get_forum_api_key(opts = {})
102
+ opts[:api_key] ||= Disqus::defaults[:api_key]
103
+ JSON.parse(get('get_forum_api_key', :user_api_key => opts[:api_key], :forum_id => opts[:forum_id]))
104
+ end
105
+
106
+ # Returns: An array of hashes representing all threads belonging to the
107
+ # given forum.
108
+ #
109
+ # Required options hash elements:
110
+ #
111
+ # * <tt>:forum_api_key</tt> - the API key for the forum
112
+ # * <tt>:forum_id</tt> - the unique id of the forum
113
+ def get_thread_list(opts = {})
114
+ JSON.parse(get('get_thread_list', :forum_id => opts[:forum_id], :forum_api_key => opts[:forum_api_key]))
115
+ end
116
+
117
+ # Returns a hash having thread_ids as keys and 2-element arrays as
118
+ # values.
119
+ #
120
+ # The first array element is the number of visible comments on on the
121
+ # thread; this would be useful for showing users of the site (e.g., "5
122
+ # Comments").
123
+ #
124
+ # The second array element is the total number of comments on the
125
+ # thread.
126
+ #
127
+ # These numbers are different because some forums require moderator
128
+ # approval, some messages are flagged as spam, etc.
129
+ #
130
+ # Required options hash elements:
131
+ #
132
+ # * <tt>:forum_api_key</tt> - the API key for the forum
133
+ # * <tt>:thread_ids</tt> - an array of thread IDs belonging to the given forum.
134
+ def get_num_posts(opts = {})
135
+ opts[:api_key] ||= Disqus::defaults[:api_key]
136
+ JSON.parse(get('get_num_posts', :thread_ids => opts[:thread_ids].join(","), :forum_api_key => opts[:forum_api_key]))
137
+ end
138
+
139
+ # Returns a hash representing a thread if one was found, otherwise null.
140
+ #
141
+ # It only finds threads associated with the given forum.
142
+ #
143
+ # Note that there is no one-to-one mapping between threads and URL's; a
144
+ # thread will only have an associated URL if it was automatically
145
+ # created by Disqus javascript embedded on that page. Therefore, we
146
+ # recommend using thread_by_identifier whenever possible. This method is
147
+ # provided mainly for handling comments from before your forum was using
148
+ # the API.
149
+ #
150
+ # Required options hash elements:
151
+ #
152
+ # * <tt>:forum_api_key</tt> - the API key for the forum
153
+ # * <tt>:url</tt> - the URL to check for an associated thread
154
+ def get_thread_by_url(opts = {})
155
+ JSON.parse(get('get_thread_by_url', :url => opts[:url], :forum_api_key => opts[:forum_api_key]))
156
+ end
157
+
158
+ # Returns an array of hashes representing representing all posts
159
+ # belonging to the given forum.
160
+ #
161
+ # Required options hash elements:
162
+ #
163
+ # * <tt>:forum_api_key</tt> - the API key for the forum
164
+ # * <tt>:thread_id</tt> - the ID of a thread belonging to the given forum
165
+ def get_thread_posts(opts = {})
166
+ JSON.parse(get('get_thread_posts', :thread_id => opts[:thread_id], :forum_api_key => opts[:forum_api_key]))
167
+ end
168
+
169
+ # Create or retrieve a thread by an arbitrary identifying string of your
170
+ # choice. For example, you could use your local database's ID for the
171
+ # thread. This method allows you to decouple thread identifiers from the
172
+ # URL's on which they might be appear. (Disqus would normally use a
173
+ # thread's URL to identify it, which is problematic when URL's do not
174
+ # uniquely identify a resource.) If no thread exists for the given
175
+ # identifier yet (paired with the forum), one will be created.
176
+ #
177
+ # Returns a hash with two keys:
178
+ #
179
+ # * "thread", which is a hash representing the thread corresponding to the identifier; and
180
+ # * "created", which indicates whether the thread was created as a result of this method call. If created, it will have the specified title.
181
+ #
182
+ # Required options hash elements:
183
+ #
184
+ # * <tt>:forum_api_key</tt> - the API key for the forum
185
+ # * <tt>:title</tt> - the title of the thread to possibly be created
186
+ # * <tt>:identifier</tt> - a string of your choosing
187
+ def thread_by_identifier(opts = {})
188
+ JSON.parse(post('thread_by_identifier/', :forum_api_key => opts[:forum_api_key],
189
+ :identifier => opts[:identifier],
190
+ :title => opts[:title] ))
191
+ end
192
+
193
+ # Sets the provided values on the thread object.
194
+ #
195
+ # Returns an empty success message.
196
+ #
197
+ # Required options hash elements:
198
+ #
199
+ # * <tt>:forum_api_key</tt> - the API key for the forum
200
+ # * <tt>:thread_id</tt> - the ID of a thread belonging to the given forum
201
+ #
202
+ # Optional:
203
+ #
204
+ # * <tt>:title</tt> - the title of the thread
205
+ # * <tt>:slug</tt> - the per-forum-unique string used for identifying this thread in disqus.com URL's relating to this thread. Composed of underscore-separated alphanumeric strings.
206
+ # * <tt>:url</tt> - the URL this thread is on, if known.
207
+ # * <tt>:allow_comment</tt> - whether this thread is open to new comments
208
+ def update_thread(opts = {})
209
+ JSON.parse(post('update_thread/', opts))
210
+ end
211
+
212
+ # Widget to includes a comment form suitable for use with the Disqus
213
+ # API. This is different from the other widgets in that you can specify
214
+ # the thread identifier being commented on.
215
+ def comment_form(forum_shortname, thread_identifier)
216
+ url = 'http://disqus.com/api/reply.js?' +
217
+ "forum_shortname=#{escape(forum_shortname)}&" +
218
+ "thread_identifier=#{escape(thread_identifier)}"
219
+ s = '<div id="dsq-reply">'
220
+ s << '<script type="text/javascript" src="%s"></script>' % url
221
+ s << '</div>'
222
+ return s
223
+ end
224
+
225
+ private
226
+
227
+ def escape(string)
228
+ URI::encode(string, /[^a-z0-9]/i)
229
+ end
230
+
231
+ def get(*args)
232
+ args << { :api_version => API_VERSION }
233
+ open(make_url(*args)) {|u| u.read }
234
+ end
235
+
236
+ def post(*args)
237
+ args << { :api_version => API_VERSION }
238
+ url = ROOT + '/' + args.shift
239
+ post_params = {}
240
+ args.shift.each { |k, v| post_params[k.to_s]=v.to_s }
241
+ Net::HTTP.post_form(URI.parse(url),post_params).body
242
+ end
243
+
244
+ def make_url(*args)
245
+ url = ROOT + '/' + args.shift + '/?'
246
+ args.shift.each { |k, v| url += "#{k}=#{escape(v.to_s)}&" }
247
+ return url.chomp('&')
248
+ end
249
+
250
+ def validate_opts!(opts)
251
+ raise ArgumentError.new("You must specify an :api_key") if !opts[:api_key]
252
+ end
253
+
254
+ end
255
+
256
+ end
257
+
258
+ end
@@ -0,0 +1,27 @@
1
+ module Disqus
2
+
3
+ class BaseAuthor
4
+ attr_reader :url, :email_hash
5
+ end
6
+
7
+ class Author < BaseAuthor
8
+ attr_reader :id, :username, :display_name, :has_avatar
9
+ def initialize(id, username, display_name, url, email_hash, has_avatar)
10
+ @id, @username, @display_name, @url, @email_hash, @has_avatar = id, username, display_name, url, email_hash, has_avatar
11
+ end
12
+
13
+ # Returns the user's <tt>display_name</tt> or <tt>username</tt> if <tt>display_name</tt> is blank.
14
+ def name
15
+ @display_name.blank? ? @username : @display_name
16
+ end
17
+
18
+ end
19
+
20
+ class AnonymousAuthor < BaseAuthor
21
+ attr_reader :name
22
+ def initialize(name, url, email_hash)
23
+ @name, @url, @email_hash = name, url, email_hash
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,125 @@
1
+ module Disqus
2
+
3
+ class Forum
4
+ attr_reader :id, :shortname, :name, :created_at, :threads
5
+
6
+ def initialize(id, shortname, name, created_at, include_threads = false)
7
+ @id, @shortname, @name, @created_at = id.to_i, shortname, name, Time.parse(created_at.to_s)
8
+ @key = nil
9
+ @forum_threads = include_threads ? load_threads : []
10
+ end
11
+
12
+ def ==(other_forum)
13
+ id == other_forum.id &&
14
+ shortname == other_forum.shortname &&
15
+ name == other_forum.name &&
16
+ key == other_forum.key
17
+ end
18
+
19
+ # Returns an array of Forum objects belonging to the user indicated by the API key.
20
+ def self.list(user_api_key = nil)
21
+ opts = user_api_key ? {:api_key => user_api_key} : {}
22
+ response = Disqus::Api::get_forum_list(opts)
23
+ if response["succeeded"]
24
+ return response["message"].map{|forum| Forum.new(forum["id"], forum["shortname"], forum["name"], forum["created_at"])}
25
+ else
26
+ raise_api_error(response)
27
+ end
28
+ end
29
+
30
+ # Returns a Forum object corresponding to the given forum_id or nil if it was not found.
31
+ def self.find(forum_id, user_api_key = nil)
32
+ list = Forum.list(user_api_key)
33
+ if list
34
+ list.select{|f| f.id == forum_id}.first
35
+ end
36
+ end
37
+
38
+ # Returns the forum API Key for this forum.
39
+ def key(user_api_key = nil)
40
+ @key ||= load_key(user_api_key)
41
+ end
42
+
43
+ # Returns an array of threads belonging to this forum.
44
+ def forum_threads(force_update = false)
45
+ if (@forum_threads.nil? or @forum_threads.empty? or force_update)
46
+ @forum_threads = Disqus::Thread.list(self)
47
+ end
48
+ @forum_threads
49
+ end
50
+
51
+ # Returns a thread associated with the given URL.
52
+ #
53
+ # A thread will only have an associated URL if it was automatically
54
+ # created by Disqus javascript embedded on that page.
55
+ def get_thread_by_url(url)
56
+ response = Disqus::Api::get_thread_by_url(:url => url, :forum_api_key => key)
57
+ if response["succeeded"]
58
+ t = response["message"]
59
+ Thread.new(t["id"], self, t["slug"], t["title"], t["created_at"], t["allow_comments"], t["url"], t["identifier"])
60
+ else
61
+ raise_api_error(response)
62
+ end
63
+ end
64
+
65
+ # Create or retrieve a thread by an arbitrary identifying string of your
66
+ # choice. For example, you could use your local database's ID for the
67
+ # thread. This method allows you to decouple thread identifiers from the
68
+ # URL's on which they might be appear. (Disqus would normally use a
69
+ # thread's URL to identify it, which is problematic when URL's do not
70
+ # uniquely identify a resource.) If no thread exists for the given
71
+ # identifier (paired with the forum) yet, one will be created.
72
+ #
73
+ # Returns a Thread object representing the thread that was created or
74
+ # retrieved.
75
+ def thread_by_identifier(identifier, title)
76
+ # TODO - should we separate thread retrieval from thread creation? The API to me seems confusing here.
77
+ response = Disqus::Api::thread_by_identifier(:identifier => identifier, :title => title, :forum_api_key => key)
78
+ if response["succeeded"]
79
+ t = response["message"]["thread"]
80
+ Thread.new(t["id"], self, t["slug"], t["title"], t["created_at"], t["allow_comments"], t["url"], t["identifier"])
81
+ else
82
+ raise_api_error(response)
83
+ end
84
+ end
85
+
86
+ # Sets the provided values on the thread object.
87
+ #
88
+ # Returns an empty success message.
89
+ #
90
+ # Options:
91
+ #
92
+ # * <tt>:title</tt> - the title of the thread
93
+ # * <tt>:slug</tt> - the per-forum-unique string used for identifying this thread in disqus.com URL's relating to this thread. Composed of underscore-separated alphanumeric strings.
94
+ # * <tt>:url</tt> - the URL this thread is on, if known.
95
+ # * <tt>:allow_comment</tt> - whether this thread is open to new comments
96
+ def update_thread(thread_id, opts = {})
97
+ result = Disqus::Api::update_thread(
98
+ :forum_api_key => key,
99
+ :thread_id => thread_id,
100
+ :title => opts[:title],
101
+ :slug => opts[:slug],
102
+ :url => opts[:url],
103
+ :allow_comments => opts[:allow_comments]
104
+ )
105
+ return result["succeeded"]
106
+ end
107
+
108
+ private
109
+
110
+ def raise_api_error(response)
111
+ raise "Error: #{response['code']}: #{response['message']}"
112
+ end
113
+
114
+ def load_key(user_api_key = nil)
115
+ opts = user_api_key ? {:api_key => user_api_key} : {}
116
+ response = Disqus::Api::get_forum_api_key(opts.merge(:forum_id => self.id))
117
+ if response["succeeded"]
118
+ return @key = response["message"]
119
+ else
120
+ raise_api_error(response)
121
+ end
122
+ end
123
+
124
+ end
125
+ end