norman-disqus-api 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,126 @@
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
+ @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
+ opts = user_api_key ? {:api_key => user_api_key} : {}
33
+ list = Forum.list(opts)
34
+ if list
35
+ list.select{|f| f.id == forum_id}.first
36
+ end
37
+ end
38
+
39
+ # Returns the forum API Key for this forum.
40
+ def key(user_api_key = nil)
41
+ @key ||= load_key(user_api_key)
42
+ end
43
+
44
+ # Returns an array of threads belonging to this forum.
45
+ def threads(force_update = false)
46
+ if (@threads.nil? or @threads.empty? or force_update)
47
+ @threads = Disqus::Thread.list(self)
48
+ end
49
+ @threads
50
+ end
51
+
52
+ # Returns a thread associated with the given URL.
53
+ #
54
+ # A thread will only have an associated URL if it was automatically
55
+ # created by Disqus javascript embedded on that page.
56
+ def get_thread_by_url(url)
57
+ response = Disqus::Api::get_thread_by_url(:url => url, :forum_api_key => key)
58
+ if response["succeeded"]
59
+ t = response["message"]
60
+ Thread.new(t["id"], self, t["slug"], t["title"], t["created_at"], t["allow_comments"], t["url"], t["identifier"])
61
+ else
62
+ raise_api_error(response)
63
+ end
64
+ end
65
+
66
+ # Create or retrieve a thread by an arbitrary identifying string of your
67
+ # choice. For example, you could use your local database's ID for the
68
+ # thread. This method allows you to decouple thread identifiers from the
69
+ # URL's on which they might be appear. (Disqus would normally use a
70
+ # thread's URL to identify it, which is problematic when URL's do not
71
+ # uniquely identify a resource.) If no thread exists for the given
72
+ # identifier (paired with the forum) yet, one will be created.
73
+ #
74
+ # Returns a Thread object representing the thread that was created or
75
+ # retrieved.
76
+ def thread_by_identifier(identifier, title)
77
+ # TODO - should we separate thread retrieval from thread creation? The API to me seems confusing here.
78
+ response = Disqus::Api::thread_by_identifier(:identifier => identifier, :title => title, :forum_api_key => key)
79
+ if response["succeeded"]
80
+ t = response["message"]["thread"]
81
+ Thread.new(t["id"], self, t["slug"], t["title"], t["created_at"], t["allow_comments"], t["url"], t["identifier"])
82
+ else
83
+ raise_api_error(response)
84
+ end
85
+ end
86
+
87
+ # Sets the provided values on the thread object.
88
+ #
89
+ # Returns an empty success message.
90
+ #
91
+ # Options:
92
+ #
93
+ # * <tt>:title</tt> - the title of the thread
94
+ # * <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.
95
+ # * <tt>:url</tt> - the URL this thread is on, if known.
96
+ # * <tt>:allow_comment</tt> - whether this thread is open to new comments
97
+ def update_thread(thread_id, opts = {})
98
+ result = Disqus::Api::update_thread(
99
+ :forum_api_key => key,
100
+ :thread_id => thread_id,
101
+ :title => opts[:title],
102
+ :slug => opts[:slug],
103
+ :url => opts[:url],
104
+ :allow_comments => opts[:allow_comments]
105
+ )
106
+ return result["succeeded"]
107
+ end
108
+
109
+ private
110
+
111
+ def raise_api_error(response)
112
+ raise "Error: #{response['code']}: #{response['message']}"
113
+ end
114
+
115
+ def load_key(user_api_key = nil)
116
+ opts = user_api_key ? {:api_key => user_api_key} : {}
117
+ response = Disqus::Api::get_forum_api_key(opts.merge(:forum_id => self.id))
118
+ if response["succeeded"]
119
+ return @key = response["message"]
120
+ else
121
+ raise_api_error(response)
122
+ end
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,51 @@
1
+ module Disqus
2
+
3
+ class Post
4
+ attr_reader :id, :forum, :thread, :created_at, :message, :parent_post, :shown, :is_anonymous, :author
5
+
6
+ def initialize(id, forum, thread, created_at, message, parent_post, shown, is_anonymous, author)
7
+ @id, @forum, @thread, @created_at, @message, @parent_post, @shown, @is_anonymous, @author = id.to_i, forum, thread, Time.parse(created_at.to_s), message, parent_post, shown, is_anonymous, author
8
+ end
9
+
10
+ # Returns an array of Post objects representing all posts belonging to the
11
+ # given thread. The array is sorted by the "created_at" date descending.
12
+ def self.list(thread)
13
+ response = Disqus::Api::get_thread_posts(:thread_id =>thread.id, :forum_api_key => thread.forum.key)
14
+ if response["succeeded"]
15
+ posts = response["message"].map do |post|
16
+ author = nil
17
+ if post["is_anonymous"]
18
+ author = AnonymousAuthor.new(
19
+ post["anonymous_author"]["name"],
20
+ post["anonymous_author"]["url"],
21
+ post["anonymous_author"]["email_hash"]
22
+ )
23
+ else
24
+ author = Author.new(
25
+ post["author"]["id"].to_i,
26
+ post["author"]["username"],
27
+ post["author"]["display_name"],
28
+ post["author"]["url"],
29
+ post["author"]["email_hash"],
30
+ post["author"]["has_avatar"]
31
+ )
32
+ end
33
+ Post.new(
34
+ post["id"],
35
+ thread.forum,
36
+ thread,
37
+ post["created_at"],
38
+ post["message"],
39
+ post["parent_post"],
40
+ post["shown"],
41
+ post["is_anonymous"],
42
+ author
43
+ )
44
+ end
45
+ posts.sort!{|a,b| a.created_at <=> b.created_at}
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+
@@ -0,0 +1,73 @@
1
+ module Disqus
2
+
3
+ class Thread
4
+ attr_reader :id, :forum, :slug, :title, :created_at, :allow_comments, :url, :identifier, :forum
5
+
6
+ def initialize(id, forum, slug, title, created_at, allow_comments, url, identifier)
7
+ @id, @forum, @slug, @title, @created_at, @allow_comments, @url, @identifier = id.to_i, forum, slug, title, Time.parse(created_at.to_s), allow_comments, url, identifier
8
+ @posts = []
9
+ end
10
+
11
+ # Threads are equal if all their attributes are equal.
12
+ def ==(other_thread)
13
+ id == other_thread.id &&
14
+ forum == other_thread.forum &&
15
+ slug == other_thread.slug &&
16
+ title == other_thread.title &&
17
+ created_at == other_thread.created_at &&
18
+ allow_comments == other_thread.allow_comments &&
19
+ url == other_thread.url &&
20
+ identifier == other_thread.identifier
21
+ end
22
+
23
+ # Returns an array of Thread objects representing the threads belonging to the given Forum.
24
+ def self.list(forum, opts = {})
25
+ response = Disqus::Api::get_thread_list(opts.merge(:forum_id =>forum.id, :forum_api_key => forum.key))
26
+ if response["succeeded"]
27
+ list = response["message"].map do |thread|
28
+ Thread.new(
29
+ thread["id"],
30
+ forum,
31
+ thread["slug"],
32
+ thread["title"],
33
+ thread["created_at"],
34
+ thread["allow_comments"],
35
+ thread["url"],
36
+ thread["identifier"]
37
+ )
38
+ end
39
+ end
40
+ end
41
+
42
+ # Returns an array of posts belonging to this thread.
43
+ def posts(force_update = false)
44
+ if (@posts.nil? or @posts.empty? or force_update)
45
+ @posts = Disqus::Post.list(self)
46
+ end
47
+ @posts
48
+ end
49
+
50
+ # Sets the provided values on the thread object.
51
+ #
52
+ # Options:
53
+ #
54
+ # * :thread_id
55
+ # * :title
56
+ # * :slug
57
+ # * :url
58
+ # * :allow_comments
59
+ def update(opts = {})
60
+ result = Disqus::Api::update_thread(opts.merge(
61
+ :forum_api_key => forum.key,
62
+ :thread_id => id,
63
+ :title => title,
64
+ :slug => slug,
65
+ :url => url,
66
+ :allow_comments => allow_comments)
67
+ )
68
+ return result["succeeded"]
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,45 @@
1
+ module Disqus
2
+
3
+ # Shortcuts to access the widgets as simple functions as opposed to using
4
+ # their full qualified names. These helpers are loaded automatically in
5
+ # Rails and Merb apps.
6
+ #
7
+ # For Sinatra, Camping, Nitro or other frameworks, you can include the
8
+ # helper if you wish, or use the fully-qualified names. Really this is just
9
+ # here for aesthetic purposes and to make it less likely to step on anyone's
10
+ # namespace.
11
+ module ViewHelpers
12
+
13
+ # See Disqus::Widget.thread
14
+ def disqus_thread(options = {})
15
+ Disqus::Widget::thread(options)
16
+ end
17
+
18
+ # See Disqus::Widget.comment_counts
19
+ def disqus_comment_counts(options = {})
20
+ Disqus::Widget::comment_counts(options)
21
+ end
22
+
23
+ # See Disqus::Widget.top_commenters
24
+ def disqus_top_commenters(options = {})
25
+ Disqus::Widget::top_commenters(options)
26
+ end
27
+
28
+ # See Disqus::Widget.popular_threads
29
+ def disqus_popular_threads(options = {})
30
+ Disqus::Widget::popular_threads(options)
31
+ end
32
+
33
+ # See Disqus::Widget.recent_comments
34
+ def disqus_recent_comments(options = {})
35
+ Disqus::Widget::recent_comments(options)
36
+ end
37
+
38
+ # See Disqus::Widget.combo
39
+ def disqus_combo(options = {})
40
+ Disqus::Widget::combo(options)
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,181 @@
1
+ module Disqus
2
+
3
+ # Disqus Widget generator.
4
+ #
5
+ # All of the methods accept various options, and "account" is required for
6
+ # all of them. You can avoid having to pass in the account each time by
7
+ # setting it in the defaults like this:
8
+ #
9
+ # Disqus::defaults[:account] = "my_account"
10
+ class Widget
11
+
12
+ VALID_COLORS = ['blue', 'grey', 'green', 'red', 'orange']
13
+ VALID_NUM_ITEMS = 5..20
14
+ VALID_DEFAULT_TABS = ['people', 'recent', 'popular']
15
+ VALID_AVATAR_SIZES = [24, 32, 48, 92, 128]
16
+ VALID_ORIENTATIONS = ['horizontal', 'vertical']
17
+
18
+ ROOT_PATH = 'http://disqus.com/forums/%s/'
19
+ THREAD = ROOT_PATH + 'embed.js'
20
+ COMBO = ROOT_PATH + 'combination_widget.js?num_items=%d&color=%s&default_tab=%s'
21
+ RECENT = ROOT_PATH + 'recent_comments_widget.js?num_items=%d&avatar_size=%d'
22
+ POPULAR = ROOT_PATH + 'popular_threads_widget.js?num_items=%d'
23
+ TOP = ROOT_PATH + 'top_commenters_widget.js?num_items=%d&avatar_size=%d&orientation=%s'
24
+ class << self
25
+
26
+ # Show the main Disqus thread widget.
27
+ # Options:
28
+ # * <tt>account:</tt> Your Discus account (required).
29
+ def thread(opts = {})
30
+ opts = Disqus::defaults.merge(opts)
31
+ opts[:view_thread_text] ||= "View the discussion thread"
32
+ validate_opts!(opts)
33
+ s = '<div id="disqus_thread"></div>'
34
+ s << '<script type="text/javascript" src="' + THREAD + '"></script>'
35
+ s << '<noscript><a href="http://%s.disqus.com/?url=ref">'
36
+ s << opts[:view_thread_text]
37
+ s << '</a></noscript>'
38
+ if opts[:show_powered_by]
39
+ s << '<a href="http://disqus.com" class="dsq-brlink">blog comments '
40
+ s << 'powered by <span class="logo-disqus">Disqus</span></a>'
41
+ end
42
+ s % [opts[:account], opts[:account]]
43
+ end
44
+
45
+ # Loads Javascript to show the number of comments for the page.
46
+ #
47
+ # The Javascript sets the inner html to the comment count for any links
48
+ # on the page that have the anchor "disqus_thread". For example, "View
49
+ # Comments" below would be replaced by "1 comment" or "23 comments" etc.
50
+ #
51
+ # <a href="http://my.website/article-permalink#disqus_thread">View Comments</a>
52
+ # <a href="http://my.website/different-permalink#disqus_thread">View Comments</a>
53
+ # Options:
54
+ # * <tt>account:</tt> Your Discus account (required).
55
+ def comment_counts(opts = {})
56
+ opts = Disqus::defaults.merge(opts)
57
+ validate_opts!(opts)
58
+ s = <<-WHIMPER
59
+ <script type="text/javascript">
60
+ //<[CDATA[
61
+ (function() {
62
+ var links = document.getElementsByTagName('a');
63
+ var query = '?';
64
+ for(var i = 0; i < links.length; i++) {
65
+ if(links[i].href.indexOf('#disqus_thread') >= 0) {
66
+ query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
67
+ }
68
+ }
69
+ document.write('<script type="text/javascript" src="#{ROOT_PATH}get_num_replies.js' + query + '"></' + 'script>');
70
+ })();
71
+ //]]>
72
+ </script>
73
+ WHIMPER
74
+ s % opts[:account]
75
+ end
76
+
77
+ # Show the top commenters Disqus thread widget.
78
+ # Options:
79
+ # * <tt>account:</tt> Your Discus account (required).
80
+ # * <tt>header:</tt> HTML snipper with header (default h2) tag and text.
81
+ # * <tt>show_powered_by:</tt> Show or hide the powered by Disqus text.
82
+ # * <tt>num_items:</tt>: How many items to show.
83
+ # * <tt>hide_mods:</tt> Don't show moderators.
84
+ # * <tt>hide_avatars:</tt> Don't show avatars.
85
+ # * <tt>avatar_size:</tt> Avatar size.
86
+ def top_commenters(opts = {})
87
+ opts = Disqus::defaults.merge(opts)
88
+ opts[:header] ||= '<h2 class="dsq-widget-title">Top Commenters</h2>'
89
+ validate_opts!(opts)
90
+ s = '<div id="dsq-topcommenters" class="dsq-widget">'
91
+ s << opts[:header]
92
+ s << '<script type="text/javascript" src="'
93
+ s << TOP
94
+ s << '&hide_avatars=1' if opts[:hide_avatars]
95
+ s << '&hide_mods=1' if opts[:hide_mods]
96
+ s << '"></script>'
97
+ s << '</div>'
98
+ if opts[:show_powered_by]
99
+ s << '<a href="http://disqus.com">Powered by Disqus</a>'
100
+ end
101
+ s % [opts[:account], opts[:num_items], opts[:avatar_size], opts[:orientation]]
102
+ end
103
+
104
+ # Show the popular threads Disqus widget.
105
+ # Options:
106
+ # * <tt>account:</tt> Your Discus account (required).
107
+ # * <tt>header:</tt> HTML snipper with header (default h2) tag and text.
108
+ # * <tt>num_items:</tt>: How many items to show.
109
+ # * <tt>hide_mods:</tt> Don't show moderators.
110
+ def popular_threads(opts = {})
111
+ opts = Disqus::defaults.merge(opts)
112
+ opts[:header] ||= '<h2 class="dsq-widget-title">Popular Threads</h2>'
113
+ validate_opts!(opts)
114
+ s = '<div id="dsq-popthreads" class="dsq-widget">'
115
+ s << opts[:header]
116
+ s << '<script type="text/javascript" src="'
117
+ s << POPULAR
118
+ s << '&hide_mods=1' if opts[:hide_mods]
119
+ s << '"></script>'
120
+ s << '</div>'
121
+ s << '<a href="http://disqus.com">Powered by Disqus</a>' if opts[:show_powered_by]
122
+ s % [opts[:account], opts[:num_items]]
123
+ end
124
+
125
+ # Show the recent comments Disqus widget.
126
+ # Options:
127
+ # * <tt>account:</tt> Your Discus account (required).
128
+ # * <tt>header:</tt> HTML snipper with header (default h2) tag and text.
129
+ # * <tt>num_items:</tt>: How many items to show.
130
+ # * <tt>hide_avatars:</tt> Don't show avatars.
131
+ # * <tt>avatar_size:</tt> Avatar size.
132
+ def recent_comments(opts = {})
133
+ opts = Disqus::defaults.merge(opts)
134
+ opts[:header] ||= '<h2 class="dsq-widget-title">Recent Comments</h2>'
135
+ validate_opts!(opts)
136
+ s = '<div id="dsq-recentcomments" class="dsq-widget">'
137
+ s << opts[:header]
138
+ s << '<script type="text/javascript" src="'
139
+ s << RECENT
140
+ s << '&hide_avatars=1' if opts[:hide_avatars]
141
+ s << '"></script>'
142
+ s << '</div>'
143
+ if opts[:show_powered_by]
144
+ s << '<a href="http://disqus.com">Powered by Disqus</a>'
145
+ end
146
+ s % [opts[:account], opts[:num_items], opts[:avatar_size]]
147
+ end
148
+
149
+ # Show the Disqus combo widget. This is a three-tabbed box with links
150
+ # popular threads, top posters, and recent threads.
151
+ # Options:
152
+ # * <tt>:account:</tt> Your Discus account (required).
153
+ # * <tt>:num_items:</tt> How many items to show.
154
+ # * <tt>:hide_mods:</tt> Don't show moderators.
155
+ # * <tt>:default_tab:</tt> Should be 'people', 'recent', or 'popular'.
156
+ def combo(opts = {})
157
+ opts = Disqus::defaults.merge(opts)
158
+ validate_opts!(opts)
159
+ s = '<script type="text/javascript" src="'
160
+ s << COMBO
161
+ s << '&hide_mods=1' if opts[:hide_mods]
162
+ s << '"></script>'
163
+ s % [opts[:account], opts[:num_items], opts[:color], opts[:default_tab]]
164
+ end
165
+
166
+ private
167
+
168
+ def validate_opts!(opts)
169
+ raise ArgumentError.new("You must specify an :account") if !opts[:account]
170
+ raise ArgumentError.new("Invalid color") if opts[:color] && !VALID_COLORS.include?(opts[:color])
171
+ raise ArgumentError.new("Invalid num_items") if opts[:num_items] && !VALID_NUM_ITEMS.include?(opts[:num_items])
172
+ raise ArgumentError.new("Invalid default_tab") if opts[:default_tab] && !VALID_DEFAULT_TABS.include?(opts[:default_tab])
173
+ raise ArgumentError.new("Invalid avatar size") if opts[:avatar_size] && !VALID_AVATAR_SIZES.include?(opts[:avatar_size])
174
+ raise ArgumentError.new("Invalid orientation") if opts[:orientation] && !VALID_ORIENTATIONS.include?(opts[:orientation])
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+
181
+ end