Skip to content
This repository
Browse code

First commit: import project. Largely inspired by the Twitter gem fro…

…m John Nunemaker, Wynn Netherland, Erik Michaels-Ober and Steve Richert.
  • Loading branch information...
commit 7cbc6083ad59333ef47b3df7b5086e2d30fd16f5 0 parents
Jérémy Van de Wyngaert authored May 06, 2011

Showing 50 changed files with 3,213 additions and 0 deletions. Show diff stats Hide diff stats

  1. 3  Gemfile
  2. 82  Gemfile.lock
  3. 2  HISTORY.mkd
  4. 20  LICENSE.mkd
  5. 147  README.mkd
  6. 23  Rakefile
  7. 38  disqussion.gemspec
  8. 25  lib/disqussion.rb
  9. 21  lib/disqussion/api.rb
  10. 46  lib/disqussion/client.rb
  11. BIN  lib/disqussion/client/.DS_Store
  12. 19  lib/disqussion/client/applications.rb
  13. 4  lib/disqussion/client/blacklists.rb
  14. 4  lib/disqussion/client/categories.rb
  15. 4  lib/disqussion/client/exports.rb
  16. 110  lib/disqussion/client/forums.rb
  17. 4  lib/disqussion/client/imports.rb
  18. 4  lib/disqussion/client/posts.rb
  19. 4  lib/disqussion/client/reactions.rb
  20. 4  lib/disqussion/client/reports.rb
  21. 4  lib/disqussion/client/threads.rb
  22. 5  lib/disqussion/client/trends.rb
  23. 103  lib/disqussion/client/users.rb
  24. 90  lib/disqussion/client/utils.rb
  25. 4  lib/disqussion/client/whitelists.rb
  26. 121  lib/disqussion/configuration.rb
  27. 36  lib/disqussion/connection.rb
  28. 51  lib/disqussion/error.rb
  29. 38  lib/disqussion/request.rb
  30. 3  lib/disqussion/version.rb
  31. 9  lib/disqussion/view_helpers.rb
  32. 183  lib/disqussion/widget.rb
  33. 41  lib/faraday/response/raise_http_4xx.rb
  34. 20  lib/faraday/response/raise_http_5xx.rb
  35. 63  spec/disqussion/api_spec.rb
  36. BIN  spec/disqussion/client/.DS_Store
  37. 24  spec/disqussion/client/applications_spec.rb
  38. 21  spec/disqussion/client/forums_spec.rb
  39. 33  spec/disqussion/client/users_spec.rb
  40. 15  spec/disqussion/client_spec.rb
  41. 101  spec/disqussion_spec.rb
  42. 30  spec/faraday/response_spec.rb
  43. 129  spec/fixtures/applications/listUsage.json
  44. 12  spec/fixtures/forums/details.json
  45. 21  spec/fixtures/forums/listCategories.json
  46. 533  spec/fixtures/forums/listThreads.json
  47. 891  spec/fixtures/posts/list.json
  48. 19  spec/fixtures/users/details.json
  49. 4  spec/fixtures/users/follow.json
  50. 45  spec/helper.rb
3  Gemfile
... ...
@@ -0,0 +1,3 @@
  1
+source :gemcutter
  2
+
  3
+gemspec
82  Gemfile.lock
... ...
@@ -0,0 +1,82 @@
  1
+PATH
  2
+  remote: .
  3
+  specs:
  4
+    disqussion (0.0.1)
  5
+      faraday (~> 0.6.1)
  6
+      faraday_middleware (~> 0.6.3)
  7
+      hashie (~> 1.0.0)
  8
+      multi_json (~> 1.0.0)
  9
+      rash (~> 0.3.0)
  10
+      simple_oauth (~> 0.1.4)
  11
+
  12
+GEM
  13
+  remote: http://rubygems.org/
  14
+  specs:
  15
+    ZenTest (4.5.0)
  16
+    addressable (2.2.5)
  17
+    archive-tar-minitar (0.5.2)
  18
+    columnize (0.3.2)
  19
+    crack (0.1.8)
  20
+    diff-lcs (1.1.2)
  21
+    faraday (0.6.1)
  22
+      addressable (~> 2.2.4)
  23
+      multipart-post (~> 1.1.0)
  24
+      rack (< 2, >= 1.1.0)
  25
+    faraday_middleware (0.6.3)
  26
+      faraday (~> 0.6.0)
  27
+    hashie (1.0.0)
  28
+    json (1.5.1)
  29
+    linecache19 (0.5.12)
  30
+      ruby_core_source (>= 0.1.4)
  31
+    maruku (0.6.0)
  32
+      syntax (>= 1.0.0)
  33
+    multi_json (1.0.1)
  34
+    multipart-post (1.1.0)
  35
+    nokogiri (1.4.4)
  36
+    rack (1.2.2)
  37
+    rake (0.8.7)
  38
+    rash (0.3.0)
  39
+      hashie (~> 1.0.0)
  40
+    rspec (2.5.0)
  41
+      rspec-core (~> 2.5.0)
  42
+      rspec-expectations (~> 2.5.0)
  43
+      rspec-mocks (~> 2.5.0)
  44
+    rspec-core (2.5.2)
  45
+    rspec-expectations (2.5.0)
  46
+      diff-lcs (~> 1.1.2)
  47
+    rspec-mocks (2.5.0)
  48
+    ruby-debug-base19 (0.11.25)
  49
+      columnize (>= 0.3.1)
  50
+      linecache19 (>= 0.5.11)
  51
+      ruby_core_source (>= 0.1.4)
  52
+    ruby-debug19 (0.11.6)
  53
+      columnize (>= 0.3.1)
  54
+      linecache19 (>= 0.5.11)
  55
+      ruby-debug-base19 (>= 0.11.19)
  56
+    ruby_core_source (0.1.5)
  57
+      archive-tar-minitar (>= 0.5.2)
  58
+    simple_oauth (0.1.4)
  59
+    simplecov (0.4.2)
  60
+      simplecov-html (~> 0.4.4)
  61
+    simplecov-html (0.4.4)
  62
+    syntax (1.0.0)
  63
+    webmock (1.6.2)
  64
+      addressable (>= 2.2.2)
  65
+      crack (>= 0.1.7)
  66
+    yard (0.6.8)
  67
+
  68
+PLATFORMS
  69
+  ruby
  70
+
  71
+DEPENDENCIES
  72
+  ZenTest (~> 4.5)
  73
+  disqussion!
  74
+  json (~> 1.5)
  75
+  maruku (~> 0.6)
  76
+  nokogiri (~> 1.4)
  77
+  rake (~> 0.8)
  78
+  rspec (~> 2.5)
  79
+  ruby-debug19
  80
+  simplecov (~> 0.4)
  81
+  webmock (~> 1.6)
  82
+  yard (~> 0.6)
2  HISTORY.mkd
Source Rendered
... ...
@@ -0,0 +1,2 @@
  1
+HISTORY
  2
+=======
20  LICENSE.mkd
Source Rendered
... ...
@@ -0,0 +1,20 @@
  1
+Copyright (c) 2011 Jérémy Van de Wyngaert
  2
+
  3
+Permission is hereby granted, free of charge, to any person obtaining
  4
+a copy of this software and associated documentation files (the
  5
+"Software"), to deal in the Software without restriction, including
  6
+without limitation the rights to use, copy, modify, merge, publish,
  7
+distribute, sublicense, and/or sell copies of the Software, and to
  8
+permit persons to whom the Software is furnished to do so, subject to
  9
+the following conditions:
  10
+
  11
+The above copyright notice and this permission notice shall be
  12
+included in all copies or substantial portions of the Software.
  13
+
  14
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
147  README.mkd
Source Rendered
... ...
@@ -0,0 +1,147 @@
  1
+The Disqussion Ruby Gem
  2
+====================
  3
+A Ruby wrapper for the Disqus API
  4
+
  5
+Installation
  6
+------------
  7
+``` sh
  8
+gem install disqussion
  9
+```
  10
+
  11
+Documentation
  12
+-------------
  13
+<http://rdoc.info/gems/twitter>
  14
+
  15
+Does your project or organization use this gem?
  16
+-----------------------------------------------
  17
+Add it to the [apps](http://github.com/jeremyvdw/disqussion/wiki/apps) wiki!
  18
+
  19
+Continuous Integration
  20
+----------------------
  21
+[![Build Status](http://travis-ci.org/jnunemaker/twitter.png)](http://travis-ci.org/jnunemaker/twitter)
  22
+
  23
+What's in 0.1?
  24
+------------------
  25
+
  26
+
  27
+```
  28
+The error classes have gone through a transformation to make them consistent with [Disqus's documented response codes](http://disqus.com/api/docs/errors/). These changes should make it easier to rescue from specific errors and take action accordingly. We've also added support for two new classes of error.
  29
+
  30
+<table>
  31
+  <thead>
  32
+    <tr>
  33
+      <th>Response Code</th>
  34
+      <th>Error</th>
  35
+    </tr>
  36
+  </thead>
  37
+  <tbody>
  38
+    <tr>
  39
+      <td><tt>400</tt></td>
  40
+      <td><tt>Disqus::</tt></td>
  41
+    </tr>
  42
+    <tr>
  43
+      <td><tt>401</tt></td>
  44
+      <td><tt>Disqus::</tt></td>
  45
+    </tr>
  46
+    <tr>
  47
+      <td><tt>403</tt></td>
  48
+      <td><tt>Disqus::</tt></td>
  49
+    </tr>
  50
+    <tr>
  51
+      <td><tt>404</tt></td>
  52
+      <td><tt>Disqus::NotFound</tt></td>
  53
+    </tr>
  54
+    <tr>
  55
+      <td><tt>500</tt></td>
  56
+      <td><tt>Disqus::InternalServerError</tt></td>
  57
+    </tr>
  58
+  </tbody>
  59
+</table>
  60
+
  61
+Here are a few reasons use (and improve) this gem:
  62
+
  63
+* Full Ruby 1.9 compatibility: All code and specs now work in the latest version of Ruby
  64
+* Support for HTTP proxies: Access Disqus from China, Iran, or inside your office firewall
  65
+* Support for multiple HTTP adapters: NetHttp (default), em-net-http, Typhoeus, Patron, or ActionDispatch
  66
+* SSL: On by default for increased [speed](http://gist.github.com/652330) and security
  67
+* Improved error handling: More easily rescue from rate-limit errors or fail whales
  68
+
  69
+Help! I'm getting: "Did not recognize your engine specification. Please specify either a symbol or a class. (RuntimeError)"
  70
+---------------------------------------------------------------------------------------------------------------------------
  71
+
  72
+If you're using the JSON request format (i.e., the default), you'll need to
  73
+explicitly require a JSON library. We recommend [yajl-ruby](http://github.com/brianmario/yajl-ruby).
  74
+
  75
+Usage Examples
  76
+--------------
  77
+``` ruby
  78
+require "rubygems"
  79
+require "disqussion"
  80
+
  81
+# Get a user's details
  82
+puts Disqussion.details("the88").url
  83
+
  84
+# Certain methods require authentication. To get your Disqus credentials,
  85
+# register an app at http://disqus.com/api/applications/
  86
+Disqussion.configure do |config|
  87
+  config.api_key = YOUR_API_KEY
  88
+  config.api_secret = YOUR_API_SECRET
  89
+end
  90
+
  91
+# Read the most recent tweet in your home timeline
  92
+puts Disqussion.home_timeline.first.text
  93
+
  94
+# Who's your most popular friend?
  95
+puts Disqussion.friends.users.sort{|a, b| a.followers_count <=> b.followers_count}.reverse.first.name
  96
+
  97
+# Who's your most popular follower?
  98
+puts Disqussion.followers.users.sort{|a, b| a.followers_count <=> b.followers_count}.reverse.first.name
  99
+
  100
+# Get your rate limit status
  101
+puts Disqussion.rate_limit_status.remaining_hits.to_s + " Disqussion API request(s) remaining this hour"
  102
+```
  103
+
  104
+Contributing
  105
+------------
  106
+In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project.
  107
+
  108
+Here are some ways *you* can contribute:
  109
+
  110
+* by using alpha, beta, and prerelease versions
  111
+* by reporting bugs
  112
+* by suggesting new features
  113
+* by writing or editing documentation
  114
+* by writing specifications
  115
+* by writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace)
  116
+* by refactoring code
  117
+* by closing [issues](http://github.com/jeremyvdw/disqussion/issues)
  118
+* by reviewing patches
  119
+
  120
+All contributors will be added to the [HISTORY](https://github.com/jeremyvdw/disqussion/blob/master/HISTORY.mkd)
  121
+file and will receive the respect and gratitude of the community.
  122
+
  123
+Submitting an Issue
  124
+-------------------
  125
+We use the [GitHub issue tracker](http://github.com/jeremyvdw/disqussion/issues) to track bugs and
  126
+features. Before submitting a bug report or feature request, check to make sure it hasn't already
  127
+been submitted. You can indicate support for an existing issuse by voting it up. When submitting a
  128
+bug report, please include a [Gist](http://gist.github.com/) that includes a stack trace and any
  129
+details that may be necessary to reproduce the bug, including your gem version, Ruby version, and
  130
+operating system. Ideally, a bug report should include a pull request with failing specs.
  131
+
  132
+Submitting a Pull Request
  133
+-------------------------
  134
+1. Fork the project.
  135
+2. Create a topic branch.
  136
+3. Implement your feature or bug fix.
  137
+4. Add documentation for your feature or bug fix.
  138
+5. Run <tt>bundle exec rake doc:yard</tt>. If your changes are not 100% documented, go back to step 4.
  139
+6. Add specs for your feature or bug fix.
  140
+7. Run <tt>bundle exec rake spec</tt>. If your changes are not 100% covered, go back to step 6.
  141
+8. Commit and push your changes.
  142
+9. Submit a pull request. Please do not include changes to the gemspec, version, or history file. (If you want to create your own version for some reason, please do so in a separate commit.)
  143
+
  144
+Copyright
  145
+---------
  146
+Copyright (c) 2011 Jérémy Van de Wyngaert.
  147
+See [LICENSE](https://github.com/jeremyvdw/disqussion/blob/master/LICENSE.mkd) for details.
23  Rakefile
... ...
@@ -0,0 +1,23 @@
  1
+require 'bundler'
  2
+Bundler::GemHelper.install_tasks
  3
+
  4
+require 'rspec/core/rake_task'
  5
+RSpec::Core::RakeTask.new(:spec)
  6
+
  7
+task :test => :spec
  8
+task :default => :spec
  9
+
  10
+namespace :doc do
  11
+  require 'yard'
  12
+  YARD::Rake::YardocTask.new do |task|
  13
+    task.files   = ['HISTORY.mkd', 'LICENSE.mkd', 'lib/**/*.rb']
  14
+    task.options = [
  15
+      '--protected',
  16
+      '--output-dir', 'doc/yard',
  17
+      '--tag', 'format:Supported formats',
  18
+      '--tag', 'authenticated:Requires Authentication',
  19
+      '--tag', 'rate_limited:Rate Limited',
  20
+      '--markup', 'markdown',
  21
+    ]
  22
+  end
  23
+end
38  disqussion.gemspec
... ...
@@ -0,0 +1,38 @@
  1
+# -*- encoding: utf-8 -*-
  2
+require File.expand_path('../lib/disqussion/version', __FILE__)
  3
+
  4
+Gem::Specification.new do |s|
  5
+  s.name        = "disqussion"
  6
+  s.version     = Disqussion::VERSION.dup
  7
+  s.platform    = Gem::Platform::RUBY
  8
+  s.authors     = ["Jérémy Van de Wyngaert"]
  9
+  s.email       = ['jeremyvdw@gmail.com']
  10
+  s.homepage    = 'https://github.com/jeremyvdw/disqussion'
  11
+  s.summary     = %q{Disqus API v3 wrapper}
  12
+  s.description = %q{Disqus API v3 wrapper}
  13
+
  14
+  s.rubyforge_project = "disqussion"
  15
+
  16
+  s.files         = `git ls-files`.split("\n")
  17
+  s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
  18
+  s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
  19
+  s.require_paths = ["lib"]
  20
+
  21
+  s.add_development_dependency('json', '~> 1.5')
  22
+  s.add_development_dependency('nokogiri', '~> 1.4')
  23
+  s.add_development_dependency('maruku', '~> 0.6')
  24
+  s.add_development_dependency('rake', '~> 0.8')
  25
+  s.add_development_dependency('rspec', '~> 2.5')
  26
+  s.add_development_dependency('simplecov', '~> 0.4')
  27
+  s.add_development_dependency('webmock', '~> 1.6')
  28
+  s.add_development_dependency('yard', '~> 0.6')
  29
+  s.add_development_dependency('ZenTest', '~> 4.5')
  30
+  s.add_development_dependency('ruby-debug19')
  31
+  
  32
+  s.add_runtime_dependency('hashie', '~> 1.0.0')
  33
+  s.add_runtime_dependency('faraday', '~> 0.6.1')
  34
+  s.add_runtime_dependency('faraday_middleware', '~> 0.6.3')
  35
+  s.add_runtime_dependency('multi_json', '~> 1.0.0')
  36
+  s.add_runtime_dependency('rash', '~> 0.3.0')
  37
+  s.add_runtime_dependency('simple_oauth', '~> 0.1.4')
  38
+end
25  lib/disqussion.rb
... ...
@@ -0,0 +1,25 @@
  1
+require 'disqussion/error'
  2
+require 'disqussion/configuration'
  3
+require 'disqussion/api'
  4
+require 'disqussion/client'
  5
+
  6
+module Disqussion
  7
+  extend Configuration
  8
+
  9
+  # Alias for Disqussion::Client.new
  10
+  #
  11
+  # @return [Disqussion::Client]
  12
+  def self.client(options={})
  13
+    Disqussion::Client.new(options)
  14
+  end
  15
+
  16
+  # Delegate to Disqussion::Client
  17
+  def self.method_missing(method, *args, &block)
  18
+    return super unless client.respond_to?(method)
  19
+    client.send(method, *args, &block)
  20
+  end
  21
+
  22
+  def self.respond_to?(method, include_private = false)
  23
+    client.respond_to?(method, include_private) || super(method, include_private)
  24
+  end
  25
+end
21  lib/disqussion/api.rb
... ...
@@ -0,0 +1,21 @@
  1
+require 'disqussion/connection'
  2
+require 'disqussion/request'
  3
+
  4
+module Disqussion
  5
+  # @private
  6
+  class API
  7
+    # @private
  8
+    attr_accessor *Configuration::VALID_OPTIONS_KEYS
  9
+
  10
+    # Creates a new API
  11
+    def initialize(options={})
  12
+      options = Disqussion.options.merge(options)
  13
+      Configuration::VALID_OPTIONS_KEYS.each do |key|
  14
+        send("#{key}=", options[key])
  15
+      end
  16
+    end
  17
+
  18
+    include Connection
  19
+    include Request
  20
+  end
  21
+end
46  lib/disqussion/client.rb
... ...
@@ -0,0 +1,46 @@
  1
+module Disqussion
  2
+  # Wrapper for the Disqussion REST API
  3
+  #
  4
+  # @note All methods have been separated into client classes and follow the same grouping used in {http://disqus.com/api/docs/ the Disqus API Documentation}.
  5
+  # @see http://docs.disqus.com/developers/api/
  6
+  class Client < API
  7
+    require 'disqussion/client/applications.rb'
  8
+    require 'disqussion/client/blacklists.rb'
  9
+    require 'disqussion/client/categories.rb'
  10
+    require 'disqussion/client/exports.rb'
  11
+    require 'disqussion/client/forums.rb'
  12
+    require 'disqussion/client/imports.rb'
  13
+    require 'disqussion/client/posts.rb'
  14
+    require 'disqussion/client/reactions.rb'
  15
+    require 'disqussion/client/reports.rb'
  16
+    require 'disqussion/client/threads.rb'
  17
+    require 'disqussion/client/trends.rb'
  18
+    require 'disqussion/client/users.rb'
  19
+    require 'disqussion/client/utils.rb'
  20
+    require 'disqussion/client/whitelists.rb'
  21
+    
  22
+    alias :api_endpoint :endpoint
  23
+    
  24
+    include Disqussion::Client::Utils
  25
+    
  26
+    ['applications',
  27
+    'blacklists',
  28
+    'categories',
  29
+    'exports',
  30
+    'forums',
  31
+    'imports',
  32
+    'posts',
  33
+    'reactions',
  34
+    'reports',
  35
+    'threads',
  36
+    'trends',
  37
+    'users',
  38
+    'whitelists'].each do |classname|
  39
+      class_eval <<-END
  40
+        def self.#{classname}
  41
+          Disqussion::#{classname.capitalize}.new
  42
+        end
  43
+      END
  44
+    end
  45
+  end
  46
+end
BIN  lib/disqussion/client/.DS_Store
Binary file not shown
19  lib/disqussion/client/applications.rb
... ...
@@ -0,0 +1,19 @@
  1
+module Disqussion
  2
+  class Applications < Client
  3
+    # Returns the API usage per day for this application.
  4
+    # 
  5
+    # @accessibility: public key, secret key
  6
+    # @methods: GET
  7
+    # @format: json, jsonp
  8
+    # @authenticated: true
  9
+    # @limited: false
  10
+    # @return [Hashie::Rash] API usage per day for this application.
  11
+    # @param application [Integer] Defaults to null
  12
+    # @param days [Integer] Defaults to 30, Maximum length of 30
  13
+    # @see http://disqus.com/api/3.0/applications/listUsage.json
  14
+    def listUsage(*args)
  15
+      options = args.last.is_a?(Hash) ? args.pop : {}
  16
+      get('applications/listUsage', options)
  17
+    end
  18
+  end
  19
+end
4  lib/disqussion/client/blacklists.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Blacklists < Client
  3
+  end
  4
+end
4  lib/disqussion/client/categories.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Categories < Client
  3
+  end
  4
+end
4  lib/disqussion/client/exports.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Exports < Client
  3
+  end
  4
+end
110  lib/disqussion/client/forums.rb
... ...
@@ -0,0 +1,110 @@
  1
+module Disqussion
  2
+  class Forums < Client
  3
+    # NOTE: to be implemented when debugged
  4
+    # Creates a new forum.
  5
+    # @accessibility: public key, secret key
  6
+    # @methods: GET
  7
+    # @format: json, jsonp
  8
+    # @authenticated: true
  9
+    # @limited: false
  10
+    # @param website [String] Disqus website name.
  11
+    # @param name [String] Forum name.
  12
+    # @param short_name [String] Forum short name (aka forum id).
  13
+    # @return {CURRENTLY BUGGED}
  14
+    # @example Creates a new forum 'myforum'
  15
+    #   Disqus.create("myforum")
  16
+    # @see: http://disqus.com/api/3.0/forums/create.json
  17
+    def create(website, name, short_name)
  18
+      response = get('forums/create', website, name, short_name)
  19
+    end
  20
+    
  21
+    # Returns forum details.
  22
+    # @accessibility: public key, secret key
  23
+    # @methods: GET
  24
+    # @format: json, jsonp
  25
+    # @authenticated: false
  26
+    # @limited: false
  27
+    # @param forum [String] Forum ID (aka short name).
  28
+    # @return [Hashie::Rash] Details on the requested forum.
  29
+    # @example Return extended information for forum 'myforum'
  30
+    #   Disqus.details("myforum")
  31
+    # @see: http://disqus.com/api/3.0/forums/details.json
  32
+    def details(forum)
  33
+      response = get('forums/details', forum)
  34
+    end
  35
+    
  36
+    # Returns a list of categories within a forum.
  37
+    # @accessibility: public key, secret key
  38
+    # @methods: GET
  39
+    # @format: json, jsonp
  40
+    # @authenticated: false
  41
+    # @limited: false
  42
+    # @param forum [String] Forum ID (aka short name).
  43
+    # @return [Hashie::Rash] Details on the requested list of categories.
  44
+    # @param options [Hash] A customizable set of options.
  45
+    # @option options [Datetime, Timestamp] :since_id. Unix timestamp (or ISO datetime standard). Defaults to null
  46
+    # @option options [Integer] :cursor. Defaults to null
  47
+    # @option options [Integer] :limit. Defaults to 25. Maximum length of 100
  48
+    # @option options [String] :order. Defaults to "asc". Choices: asc, desc
  49
+    # @example Return extended information for forum 'myforum'
  50
+    #   Disqus.listCategories("myforum", {:cursor => 10, :limit => 10, :order => 'asc'})
  51
+    # @see: http://disqus.com/api/3.0/forums/details.json
  52
+    def listCategories(forum, options = {})
  53
+      response = get('forums/listCategories', forum, options)
  54
+    end
  55
+    
  56
+    # NOTE: to be implemented
  57
+    # Returns a list of users active within a forum ordered by most likes received.
  58
+    # @see: http://disqus.com/api/3.0/forums/listMostLikedUsers.json
  59
+    def listMostLikedUsers(forum, options = {})
  60
+    end
  61
+    
  62
+    # Returns a list of posts within a forum.
  63
+    # @accessibility: public key, secret key
  64
+    # @methods: GET
  65
+    # @format: json, jsonp, rss
  66
+    # @authenticated: false
  67
+    # @limited: false
  68
+    # @param forum [String] Forum ID (aka short name).
  69
+    # @return [Hashie::Rash] Details on the list of posts.
  70
+    # @param options [Hash] A customizable set of options.
  71
+    # @option options [Datetime, Timestamp] :since_id. Unix timestamp (or ISO datetime standard). Defaults to null
  72
+    # @option options [Integer, String] :related. Allows multiple. Defaults to []. You may specify relations to include with your response. Choices: thread.
  73
+    # @option options [Integer] :cursor. Defaults to null
  74
+    # @option options [Integer] :limit. Defaults to 25. Maximum length of 100
  75
+    # @option options [Integer] :query. Defaults to null. 
  76
+    # @option options [String, Array] :include allows multiple. Defaults to ["approved"]. Choices: unapproved, approved, spam, deleted, flagged
  77
+    # @option options [String] :order. Defaults to "asc". Choices: asc, desc
  78
+    # @example Return list of (all) posts for forum 'myforum', including related threads
  79
+    #   Disqus.listPosts("myforum", {:related => ["thread"], :include => ["spam", "deleted", "flagged"]})
  80
+    # @see: http://disqus.com/api/3.0/forums/listPosts.json
  81
+    def listPosts(forum, options = {})
  82
+      response = get('forums/listPosts', forum, options)
  83
+    end
  84
+    
  85
+    # Returns a list of threads within a forum.
  86
+    # @accessibility: public key, secret key
  87
+    # @methods: GET
  88
+    # @format: json, jsonp, rss
  89
+    # @authenticated: false
  90
+    # @limited: false
  91
+    # @param forum [String] Forum ID (aka short name).
  92
+    # @return [Hashie::Rash] Details on the list of posts.
  93
+    # @param options [Hash] A customizable set of options.
  94
+    # @option options [Integer, String, Array] :thread. Allows multiple. Defaults to null. Looks up a thread by ID. You may pass use the 'ident' or 'link' query types instead of an ID by including 'forum'
  95
+    # @option options [Datetime, Timestamp] :since_id. Unix timestamp (or ISO datetime standard). Defaults to null
  96
+    # @option options [Integer, String] :related. Allows multiple. Defaults to []. You may specify relations to include with your response. Choices: forum, author.
  97
+    # @option options [Integer] :cursor. Defaults to null
  98
+    # @option options [Integer] :limit. Defaults to 25. Maximum length of 100
  99
+    # @option options [String, Array] :include allows multiple. Defaults to ["open", "close"]. Choices: open, closed, killed.
  100
+    # @option options [String] :order. Defaults to "desc". Choices: asc, desc
  101
+    # @example Return list of (all) threads for forum 'myforum', including closed ones and related forums.
  102
+    #   Disqus.listThreads("myforum", {:related => ["forum"], :include => ["close"]})
  103
+    # @see: http://disqus.com/api/3.0/forums/listThreads.json
  104
+    def listThreads(forum, options = {})
  105
+      # NOTE: extract :thread to tie it up
  106
+      response = get('forums/listThreads', forum, options)
  107
+    end
  108
+    
  109
+  end
  110
+end
4  lib/disqussion/client/imports.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Imports < Client
  3
+  end
  4
+end
4  lib/disqussion/client/posts.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Posts < Client
  3
+  end
  4
+end
4  lib/disqussion/client/reactions.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Reactions < Client
  3
+  end
  4
+end
4  lib/disqussion/client/reports.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Reports < Client
  3
+  end
  4
+end
4  lib/disqussion/client/threads.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+    class Threads < Client
  3
+  end
  4
+end
5  lib/disqussion/client/trends.rb
... ...
@@ -0,0 +1,5 @@
103  lib/disqussion/client/users.rb
... ...
@@ -0,0 +1,103 @@
  1
+module Disqussion
  2
+  class Users < Client
  3
+    # Returns details of a user
  4
+    # @accessibility: public key, secret key
  5
+    # @methods: GET
  6
+    # @format: json, jsonp
  7
+    # @authenticated: false
  8
+    # @limited: false
  9
+    # @param user [Integer, String] A Disqus user ID or screen name.
  10
+    # @return [Hashie::Rash] Details on the requested user.
  11
+    # @example Return extended information for 'the88'
  12
+    #   Disqus.user("the88")
  13
+    #   Disqus.user(6138058)  # Same as above
  14
+    # @see: http://disqus.com/api/3.0/users/details.json
  15
+    def details(*args)
  16
+      options = args.last.is_a?(Hash) ? args.pop : {}
  17
+      user = args.first
  18
+      merge_user_into_options!(user, options)
  19
+      response = get('users/details', options)
  20
+    end
  21
+    
  22
+    # Follow a user
  23
+    # @accessibility: public key, secret key
  24
+    # @methods: GET
  25
+    # @format: json, jsonp
  26
+    # @authenticated: true
  27
+    # @limited: false
  28
+    # @param target [Integer, String] A Disqus user ID or screen name.
  29
+    # @return [Hashie::Rash] Details on the requested user.
  30
+    # @example Return extended information for 'the88'
  31
+    #   Disqus.follow("the88")
  32
+    #   Disqus.follow(6138058)  # Same as above
  33
+    # @see: http://disqus.com/api/3.0/users/details.json
  34
+    def follow(*args)
  35
+      options = args.last.is_a?(Hash) ? args.pop : {}
  36
+      target = args.first
  37
+      merge_target_into_options!(target, options)
  38
+      response = post('users/follow', options)
  39
+    end
  40
+    
  41
+    # Returns a list of forums a user has been active on.
  42
+    def listActiveForums
  43
+      
  44
+    end
  45
+    
  46
+    # BETA
  47
+    # Returns a list of threads a user has participated in sorted by last activity.
  48
+    def listActiveThreads
  49
+      
  50
+    end
  51
+    
  52
+    # BETA
  53
+    # Returns a list of various activity types made by the user.
  54
+    def listActivity
  55
+      
  56
+    end
  57
+    
  58
+    # BETA
  59
+    # Returns a list of users a user is being followed by.
  60
+    def listFollowers
  61
+      
  62
+    end
  63
+    
  64
+    # BETA
  65
+    # Returns a list of users a user is following.
  66
+    def listFollowing
  67
+      
  68
+    end
  69
+
  70
+    # Returns a list of forums a user owns.
  71
+    def listForums
  72
+      
  73
+    end
  74
+    
  75
+    # BETA
  76
+    # Returns a list of forums a user has been active on recenty, sorted by the user's activity.
  77
+    def listMostActiveForums
  78
+      
  79
+    end
  80
+    
  81
+    # Returns a list of posts made by the user.
  82
+    def listPosts
  83
+      
  84
+    end
  85
+    
  86
+    # Unfollow a user
  87
+    # @accessibility: public key, secret key
  88
+    # @methods: GET
  89
+    # @format: json, jsonp
  90
+    # @authenticated: true
  91
+    # @limited: false
  92
+    # @param user [Integer, String] A Disqus user ID or screen name.
  93
+    # @return [Hashie::Rash] Details on the requested user.
  94
+    # @example Return extended information for 'the88'
  95
+    #   Disqus.unfollow("the88")
  96
+    #   Disqus.unfollow(6138058)  # Same as above
  97
+    # @see: http://disqus.com/api/3.0/users/details.json
  98
+    def unfollow
  99
+      merge_user_into_options!(user, options)
  100
+      response = post('users/unfollow', options)
  101
+    end
  102
+  end
  103
+end
90  lib/disqussion/client/utils.rb
... ...
@@ -0,0 +1,90 @@
  1
+# -*- encoding: utf-8 -*-
  2
+module Disqussion
  3
+  class Client
  4
+    # @private
  5
+    module Utils
  6
+      private
  7
+
  8
+      # Take a single user ID or screen name and merge it into an options hash with the correct key
  9
+      #
  10
+      # @param user_id_or_username [Integer, String] A Disqus user ID or username.
  11
+      # @param options [Hash] A customizable set of options.
  12
+      # @return [Hash]
  13
+      def merge_user_into_options!(user_id_or_username, options={})
  14
+        case user_id_or_username
  15
+        when Fixnum
  16
+          options[:user] = user_id_or_username
  17
+        when String
  18
+          options[:"user:username"] = user_id_or_username
  19
+        end
  20
+        options
  21
+      end
  22
+
  23
+      # Take a single user ID or screen name and merge it into an options hash with the correct key
  24
+      #
  25
+      # @param user_id_or_username [Integer, String] A Disqus user ID or username.
  26
+      # @param options [Hash] A customizable set of options.
  27
+      # @return [Hash]
  28
+      def merge_target_into_options!(user_id_or_username, options={})
  29
+        case user_id_or_username
  30
+        when Fixnum
  31
+          options[:target] = user_id_or_username
  32
+        when String
  33
+          options[:"target:username"] = user_id_or_username
  34
+        end
  35
+        options
  36
+      end
  37
+
  38
+      # Take a multiple user IDs and screen names and merge them into an options hash with the correct keys
  39
+      #
  40
+      # @param users_id_or_usernames [Array] An array of Disqus user IDs or usernames.
  41
+      # @param options [Hash] A customizable set of options.
  42
+      # @return [Hash]
  43
+      def merge_users_into_options!(user_ids_or_usernames, options={})
  44
+        user_ids, usernames = [], []
  45
+        user_ids_or_usernames.flatten.each do |user_id_or_username|
  46
+          case user_id_or_username
  47
+          when Fixnum
  48
+            user_ids << user_id_or_username
  49
+          when String
  50
+            usernames << user_id_or_username
  51
+          end
  52
+        end
  53
+        options[:user_id] = user_ids.join(',') unless user_ids.empty?
  54
+        options[:username] = usernames.join(',') unless usernames.empty?
  55
+        options
  56
+      end
  57
+
  58
+      # Take a single owner ID or owner screen name and merge it into an options hash with the correct key
  59
+      # (for Disqus API endpoints that want :owner_id and :owner_username)
  60
+      #
  61
+      # @param owner_id_or_owner_username [Integer, String] A Disqus user ID or username.
  62
+      # @param options [Hash] A customizable set of options.
  63
+      # @return [Hash]
  64
+      def merge_owner_into_options!(owner_id_or_owner_username, options={})
  65
+        case owner_id_or_owner_username
  66
+        when Fixnum
  67
+          options[:owner_id] = owner_id_or_owner_username
  68
+        when String
  69
+          options[:owner_username] = owner_id_or_owner_username
  70
+        end
  71
+        options
  72
+      end
  73
+
  74
+      # Take a single list ID or slug and merge it into an options hash with the correct key
  75
+      #
  76
+      # @param list_id_or_slug [Integer, String] A Disqus list ID or slug.
  77
+      # @param options [Hash] A customizable set of options.
  78
+      # @return [Hash]
  79
+      def merge_list_into_options!(list_id_or_username, options={})
  80
+        case list_id_or_username
  81
+        when Fixnum
  82
+          options[:list_id] = list_id_or_username
  83
+        when String
  84
+          options[:slug] = list_id_or_username
  85
+        end
  86
+        options
  87
+      end
  88
+    end
  89
+  end
  90
+end
4  lib/disqussion/client/whitelists.rb
... ...
@@ -0,0 +1,4 @@
  1
+module Disqussion
  2
+  class Whitelists < Client
  3
+  end
  4
+end
121  lib/disqussion/configuration.rb
... ...
@@ -0,0 +1,121 @@
  1
+require 'faraday'
  2
+require 'disqussion/version'
  3
+
  4
+module Disqussion
  5
+  # Defines constants and methods related to configuration
  6
+  module Configuration
  7
+    # An array of valid keys in the options hash when configuring a {Disqussion::API}
  8
+    VALID_OPTIONS_KEYS = [
  9
+      :adapter,
  10
+      :endpoint,
  11
+      :api_key,
  12
+      :api_secret,
  13
+      :developer,
  14
+      :container_id,
  15
+      :avatar_size,
  16
+      :color,
  17
+      :default_tab,
  18
+      :hide_avatars,
  19
+      :hide_mods,
  20
+      :num_items,
  21
+      :show_powered_by,
  22
+      :orientation,
  23
+      :format,
  24
+      :proxy,
  25
+      :user_agent].freeze
  26
+
  27
+    # An array of valid request/response formats
  28
+    #
  29
+    # @note only json for now
  30
+    VALID_FORMATS = [:json].freeze
  31
+
  32
+    # The adapter that will be used to connect if none is set
  33
+    #
  34
+    # @note The default faraday adapter is Net::HTTP.
  35
+    DEFAULT_ADAPTER = if defined?(EventMachine) && EM.reactor_running?
  36
+      EMSynchrony
  37
+    else
  38
+      Faraday.default_adapter
  39
+    end
  40
+
  41
+    # By default, don't set an application key
  42
+    DEFAULT_API_KEY = nil
  43
+
  44
+    # By default, don't set an application secret
  45
+    DEFAULT_API_SECRET = nil
  46
+
  47
+    # The endpoint that will be used to connect if none is set
  48
+    #
  49
+    # @note This is configurable in case you want to use HTTP instead of HTTPS, specify a different API version, or use a Disqussion-compatible endpoint.
  50
+    # @see http://status.net/wiki/Disqussion-compatible_API
  51
+    # @see http://en.blog.wordpress.com/2009/12/12/disqussion-api/
  52
+    # @see http://staff.tumblr.com/post/287703110/api
  53
+    # @see http://developer.typepad.com/typepad-disqussion-api/disqussion-api.html
  54
+    DEFAULT_API_VERSION = '3.0'.freeze
  55
+    DEFAULT_ENDPOINT = "https://disqus.com/api/#{DEFAULT_API_VERSION}/".freeze
  56
+
  57
+    # The response format appended to the path and sent in the 'Accept' header if none is set
  58
+    #
  59
+    # @note JSON is preferred over XML because it is more concise and faster to parse.
  60
+    DEFAULT_FORMAT = :json
  61
+
  62
+    # By default, don't use a proxy server
  63
+    DEFAULT_PROXY = nil
  64
+
  65
+    # The user agent that will be sent to the API endpoint if none is set
  66
+    DEFAULT_USER_AGENT = "Disqussion Ruby Gem #{Disqussion::VERSION}".freeze
  67
+
  68
+    DEFAULT_DEVELOPER       = false
  69
+    DEFAULT_CONTAINER_ID    = 'disqus_thread'
  70
+    DEFAULT_AVATAR_SIZE     = 48
  71
+    DEFAULT_COLOR           = "grey"
  72
+    DEFAULT_DEFAULT_TAB     = "popular"
  73
+    DEFAULT_HIDE_AVATARS    = false
  74
+    DEFAULT_HIDE_MODS       = true
  75
+    DEFAULT_NUM_ITEMS       = 15
  76
+    DEFAULT_SHOW_POWERED_BY = true
  77
+    DEFAULT_ORIENTATION     = "horizontal"
  78
+
  79
+    # @private
  80
+    attr_accessor *VALID_OPTIONS_KEYS
  81
+
  82
+    # When this module is extended, set all configuration options to their default values
  83
+    def self.extended(base)
  84
+      base.reset
  85
+    end
  86
+
  87
+    # Convenience method to allow configuration options to be set in a block
  88
+    def configure
  89
+      yield self
  90
+    end
  91
+
  92
+    # Create a hash of options and their values
  93
+    def options
  94
+      options = {}
  95
+      VALID_OPTIONS_KEYS.each{|k| options[k] = send(k) }
  96
+      options
  97
+    end
  98
+
  99
+    # Reset all configuration options to defaults
  100
+    def reset
  101
+      self.adapter            = DEFAULT_ADAPTER
  102
+      self.endpoint           = DEFAULT_ENDPOINT
  103
+      self.api_key            = DEFAULT_API_KEY
  104
+      self.api_secret         = DEFAULT_API_SECRET
  105
+      self.developer          = DEFAULT_DEVELOPER
  106
+      self.container_id       = DEFAULT_CONTAINER_ID
  107
+      self.avatar_size        = DEFAULT_AVATAR_SIZE
  108
+      self.color              = DEFAULT_COLOR
  109
+      self.default_tab        = DEFAULT_DEFAULT_TAB
  110
+      self.hide_avatars       = DEFAULT_HIDE_AVATARS
  111
+      self.hide_mods          = DEFAULT_HIDE_MODS
  112
+      self.num_items          = DEFAULT_NUM_ITEMS
  113
+      self.show_powered_by    = DEFAULT_SHOW_POWERED_BY
  114
+      self.orientation        = DEFAULT_ORIENTATION
  115
+      self.format             = DEFAULT_FORMAT
  116
+      self.proxy              = DEFAULT_PROXY
  117
+      self.user_agent         = DEFAULT_USER_AGENT
  118
+      self
  119
+    end
  120
+  end
  121
+end
36  lib/disqussion/connection.rb
... ...
@@ -0,0 +1,36 @@
  1
+require 'faraday_middleware'
  2
+require 'faraday/response/raise_http_4xx'
  3
+require 'faraday/response/raise_http_5xx'
  4
+
  5
+module Disqussion
  6
+  # @private
  7
+  module Connection
  8
+    private
  9
+
  10
+    def connection(raw=false)
  11
+      options = {
  12
+        :headers => {'Accept' => "application/#{format}", 'User-Agent' => user_agent},
  13
+        :proxy => proxy,
  14
+        :ssl => {:verify => false},
  15
+        :url => api_endpoint,
  16
+      }
  17
+
  18
+      Faraday.new(options) do |builder|
  19
+        builder.use Faraday::Request::Multipart
  20
+        builder.use Faraday::Request::UrlEncoded
  21
+        builder.use Faraday::Response::RaiseHttp4xx
  22
+        builder.use Faraday::Response::Rashify unless raw
  23
+        unless raw
  24
+          case format.to_s.downcase
  25
+          when 'json'
  26
+            builder.use Faraday::Response::ParseJson
  27
+          when 'xml'
  28
+            builder.use Faraday::Response::ParseXml
  29
+          end
  30
+        end
  31
+        builder.use Faraday::Response::RaiseHttp5xx
  32
+        builder.adapter(adapter)
  33
+      end
  34
+    end
  35
+  end
  36
+end
51  lib/disqussion/error.rb
... ...
@@ -0,0 +1,51 @@
  1
+module Disqussion
  2
+  # Custom error class for rescuing from all Disqus errors
  3
+  class Error < StandardError
  4
+    attr_reader :http_headers
  5
+
  6
+    def initialize(message, http_headers)
  7
+      @http_headers = Hash[http_headers]
  8
+      super message
  9
+    end
  10
+
  11
+    def ratelimit_reset
  12
+      Time.at(@http_headers.values_at('x-ratelimit-reset', 'X-RateLimit-Reset').detect{|value| value}.to_i)
  13
+    end
  14
+
  15
+    def ratelimit_limit
  16
+      @http_headers.values_at('x-ratelimit-limit', 'X-RateLimit-Limit').detect{|value| value}.to_i
  17
+    end
  18
+
  19
+    def ratelimit_remaining
  20
+      @http_headers.values_at('x-ratelimit-remaining', 'X-RateLimit-Remaining').detect{|value| value}.to_i
  21
+    end
  22
+
  23
+    def retry_after
  24
+      [(ratelimit_reset - Time.now).ceil, 0].max
  25
+    end
  26
+  end
  27
+
  28
+  # Raised when Disqus returns the HTTP status code 400
  29
+  class BadRequest < Error; end
  30
+
  31
+  # Raised when Disqus returns the HTTP status code 401
  32
+  class Unauthorized < Error; end
  33
+
  34
+  # Raised when Disqus returns the HTTP status code 403
  35
+  class Forbidden < Error; end
  36
+
  37
+  # Raised when Disqus returns the HTTP status code 404
  38
+  class NotFound < Error; end
  39
+
  40
+  # Raised when Disqus returns the HTTP status code 406
  41
+  class NotAcceptable < Error; end
  42
+
  43
+  # Raised when Disqus returns the HTTP status code 500
  44
+  class InternalServerError < Error; end
  45
+
  46
+  # Raised when Disqus returns the HTTP status code 502
  47
+  class BadGateway < Error; end
  48
+
  49
+  # Raised when Disqus returns the HTTP status code 503
  50
+  class ServiceUnavailable < Error; end
  51
+end
38  lib/disqussion/request.rb
... ...
@@ -0,0 +1,38 @@
  1
+module Disqussion
  2
+  # Defines HTTP request methods
  3
+  module Request
  4
+    # Perform an HTTP GET request
  5
+    def get(path, options={}, raw=false)
  6
+      request(:get, path, options, raw)
  7
+    end
  8
+
  9
+    # Perform an HTTP POST request
  10
+    def post(path, options={}, raw=false)
  11
+      request(:post, path, options, raw)
  12
+    end
  13
+
  14
+    private
  15
+
  16
+    # Perform an HTTP request
  17
+    def request(method, path, options, raw=false)
  18
+      # identify our request
  19
+      # @see: http://docs.disqus.com/help/52/
  20
+      options ||= {}
  21
+      options.merge!(:api_secret => self.api_secret) unless options.has_key?(:api_secret)
  22
+      response = connection(raw).send(method) do |request|
  23
+        case method
  24
+        when :get
  25
+          request.url(formatted_path(path), options)
  26
+        when :post
  27
+          request.path = formatted_path(path)
  28
+          request.body = options unless options.empty?
  29
+        end
  30
+      end
  31
+      raw ? response : response.body
  32
+    end
  33
+
  34
+    def formatted_path(path)
  35
+      [path, format].compact.join('.')
  36
+    end
  37
+  end
  38
+end
3  lib/disqussion/version.rb
... ...
@@ -0,0 +1,3 @@
  1
+module Disqussion
  2
+  VERSION = '0.0.1'.freeze unless defined?(::Disqussion::VERSION)
  3
+end
9  lib/disqussion/view_helpers.rb
... ...
@@ -0,0 +1,9 @@
  1
+# Shortcuts to access the widgets as simple functions as opposed to using
  2
+# their full qualified names.
  3
+%w[combo comment_counts popular_threads recent_comments thread top_commenters].each do |method|
  4
+  eval(<<-EOM)
  5
+    def disqus_#{method}(options = {})
  6
+      Disqus::Widget.#{method}(options)
  7
+    end
  8
+  EOM
  9
+end
183  lib/disqussion/widget.rb
... ...
@@ -0,0 +1,183 @@
  1
+module EventMachine
  2
+  module Disqus
  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 = ''
  34
+          if opts[:developer]
  35
+            s << '<script type="text/javascript">var disqus_developer = 1;</script>'
  36
+          end
  37
+          s << '<div id="disqus_thread"></div>'
  38
+          s << '<script type="text/javascript" src="' + THREAD + '"></script>'
  39
+          s << '<noscript><a href="http://%s.disqus.com/?url=ref">'
  40
+          s << opts[:view_thread_text]
  41
+          s << '</a></noscript>'
  42
+          if opts[:show_powered_by]
  43
+            s << '<a href="http://disqus.com" class="dsq-brlink">blog comments '
  44
+            s << 'powered by <span class="logo-disqus">Disqus</span></a>'
  45
+          end
  46
+          s % [opts[:account], opts[:account]]
  47
+        end
  48
+      
  49
+        # Loads Javascript to show the number of comments for the page.
  50
+        #
  51
+        # The Javascript sets the inner html to the comment count for any links
  52
+        # on the page that have the anchor "disqus_thread". For example, "View
  53
+        # Comments" below would be replaced by "1 comment" or "23 comments" etc.
  54
+        #
  55
+        #   <a href="http://my.website/article-permalink#disqus_thread">View Comments</a>
  56
+        #   <a href="http://my.website/different-permalink#disqus_thread">View Comments</a>
  57
+        # Options:
  58
+        # * <tt>account:</tt> Your Discus account (required).
  59
+        def comment_counts(opts = {})
  60
+          opts = Disqus::defaults.merge(opts)        
  61
+          validate_opts!(opts)
  62
+          s = <<-WHIMPER
  63
+          <script type="text/javascript">