Browse files

Initial release.

  • Loading branch information...
0 parents commit c6873bc9e35929c9197f4b27e01fd893968a4e1f Sam Symons committed Nov 10, 2013
Showing with 15,106 additions and 0 deletions.
  1. +19 −0 .gitignore
  2. +5 −0 .travis.yml
  3. +1 −0 .yardopts
  4. +18 −0 Gemfile
  5. +11 −0 Guardfile
  6. +22 −0 LICENSE.md
  7. +147 −0 README.md
  8. +1 −0 Rakefile
  9. +26 −0 lib/redditkit.rb
  10. +60 −0 lib/redditkit/base.rb
  11. +148 −0 lib/redditkit/client.rb
  12. +73 −0 lib/redditkit/client/account.rb
  13. +63 −0 lib/redditkit/client/apps.rb
  14. +36 −0 lib/redditkit/client/captcha.rb
  15. +54 −0 lib/redditkit/client/comments.rb
  16. +148 −0 lib/redditkit/client/flair.rb
  17. +134 −0 lib/redditkit/client/links.rb
  18. +50 −0 lib/redditkit/client/miscellaneous.rb
  19. +184 −0 lib/redditkit/client/moderation.rb
  20. +207 −0 lib/redditkit/client/multireddits.rb
  21. +74 −0 lib/redditkit/client/private_messages.rb
  22. +25 −0 lib/redditkit/client/search.rb
  23. +120 −0 lib/redditkit/client/subreddits.rb
  24. +109 −0 lib/redditkit/client/users.rb
  25. +142 −0 lib/redditkit/client/utilities.rb
  26. +41 −0 lib/redditkit/client/voting.rb
  27. +75 −0 lib/redditkit/client/wiki.rb
  28. +54 −0 lib/redditkit/comment.rb
  29. +17 −0 lib/redditkit/creatable.rb
  30. +111 −0 lib/redditkit/error.rb
  31. +140 −0 lib/redditkit/link.rb
  32. +19 −0 lib/redditkit/moderator_action.rb
  33. +32 −0 lib/redditkit/multireddit.rb
  34. +14 −0 lib/redditkit/multireddit_description.rb
  35. +22 −0 lib/redditkit/paginated_response.rb
  36. +27 −0 lib/redditkit/private_message.rb
  37. +29 −0 lib/redditkit/response/parse_json.rb
  38. +21 −0 lib/redditkit/response/raise_error.rb
  39. +86 −0 lib/redditkit/subreddit.rb
  40. +20 −0 lib/redditkit/thing.rb
  41. +30 −0 lib/redditkit/user.rb
  42. +18 −0 lib/redditkit/version.rb
  43. +37 −0 lib/redditkit/votable.rb
  44. +25 −0 redditkit.gemspec
  45. +39 −0 spec/cassettes/RedditKit_Client/should_raise_an_error_with_invalid_credentials.yml
  46. +132 −0 spec/cassettes/RedditKit_Client_Account/_sign_in/signs_the_user_in.yml
  47. +133 −0 spec/cassettes/RedditKit_Client_Account/_update_session/updates_the_current_session.yml
  48. +38 −0 spec/cassettes/RedditKit_Client_Captcha/_captcha_url/returns_a_CAPTCHA_url_from_an_identifier.yml
  49. +44 −0 ...settes/RedditKit_Client_Captcha/_needs_captcha_/checks_if_the_current_account_needs_a_CAPTCHA.yml
  50. +38 −0 spec/cassettes/RedditKit_Client_Captcha/_new_captcha_identifier/returns_a_new_CAPTCHA_identifier.yml
  51. +47 −0 spec/cassettes/RedditKit_Client_Comments/_comment/requests_the_correct_resource.yml
  52. +140 −0 ...assettes/RedditKit_Client_Comments/_comments/with_a_RedditKit_Link/returns_comments_on_a_link.yml
  53. +89 −0 ...ssettes/RedditKit_Client_Comments/_comments/with_a_link_identifier/returns_comments_on_a_link.yml
  54. +313 −0 spec/cassettes/RedditKit_Client_Comments/_submit_comment/requests_the_correct_resource.yml
  55. +52 −0 spec/cassettes/RedditKit_Client_Flair/_apply_flair_template/clears_flair_templates.yml
  56. +49 −0 spec/cassettes/RedditKit_Client_Flair/_clear_flair_templates/clears_flair_templates.yml
  57. +46 −0 spec/cassettes/RedditKit_Client_Flair/_create_flair_template/creates_a_flair_template.yml
  58. +46 −0 spec/cassettes/RedditKit_Client_Flair/_create_flair_template/raises_InvalidClassName.yml
  59. +47 −0 spec/cassettes/RedditKit_Client_Flair/_create_flair_template/raises_TooManyClassNames.yml
  60. +60 −0 spec/cassettes/RedditKit_Client_Flair/_delete_user_flair/requests_the_correct_resource.yml
  61. +44 −0 spec/cassettes/RedditKit_Client_Flair/_flair_list/returns_the_list_of_flair.yml
  62. +49 −0 spec/cassettes/RedditKit_Client_Flair/_set_flair/requests_the_correct_resource.yml
  63. +49 −0 spec/cassettes/RedditKit_Client_Flair/_set_flair_options/sets_flair_options.yml
  64. +51 −0 spec/cassettes/RedditKit_Client_Flair/_set_flair_with_csv/requests_the_correct_resource.yml
  65. +49 −0 spec/cassettes/RedditKit_Client_Flair/_toggle_flair/requests_the_correct_resource.yml
  66. +498 −0 spec/cassettes/RedditKit_Client_Links/_front_page/requests_the_correct_category.yml
  67. +603 −0 spec/cassettes/RedditKit_Client_Links/_front_page/requests_the_correct_resource.yml
  68. +46 −0 spec/cassettes/RedditKit_Client_Links/_hide/requests_the_correct_resource.yml
  69. +55 −0 spec/cassettes/RedditKit_Client_Links/_link/returns_a_link.yml
  70. +402 −0 spec/cassettes/RedditKit_Client_Links/_links/contains_pagination_information.yml
  71. +186 −0 spec/cassettes/RedditKit_Client_Links/_links/requests_a_certain_number_of_links.yml
  72. +603 −0 .../cassettes/RedditKit_Client_Links/_links/requests_front_page_links_if_no_subreddit_is_present.yml
  73. +375 −0 spec/cassettes/RedditKit_Client_Links/_links/requests_links_with_the_correct_time_frame.yml
  74. +402 −0 spec/cassettes/RedditKit_Client_Links/_links/requests_the_correct_subreddit_and_category.yml
  75. +99 −0 spec/cassettes/RedditKit_Client_Links/_links_with_domain/returns_links_with_a_specific_domain.yml
  76. +46 −0 spec/cassettes/RedditKit_Client_Links/_mark_nsfw/requests_the_correct_resource.yml
  77. +91 −0 spec/cassettes/RedditKit_Client_Links/_random_link/returns_a_random_link.yml
  78. +54 −0 ...es/RedditKit_Client_Links/_submit/raises_RedditKit_InvalidCaptcha_if_no_CAPTCHA_is_filled_out.yml
  79. +46 −0 spec/cassettes/RedditKit_Client_Links/_unhide/requests_the_correct_resource.yml
  80. +46 −0 spec/cassettes/RedditKit_Client_Links/_unmark_nsfw/requests_the_correct_resource.yml
  81. +46 −0 spec/cassettes/RedditKit_Client_Miscellaneous/_delete/requests_the_correct_resource.yml
  82. +52 −0 spec/cassettes/RedditKit_Client_Miscellaneous/_edit/requests_the_correct_resource.yml
  83. +46 −0 spec/cassettes/RedditKit_Client_Miscellaneous/_save/saves_an_object.yml
  84. +89 −0 spec/cassettes/RedditKit_Client_Miscellaneous/_unsave/unsaves_an_object.yml
  85. +53 −0 ...settes/RedditKit_Client_Moderation/_accept_moderator_invitation/requests_the_correct_resource.yml
  86. +103 −0 spec/cassettes/RedditKit_Client_Moderation/_ban/requests_the_correct_resource.yml
  87. +47 −0 ...assettes/RedditKit_Client_Moderation/_contributors_to_subreddit/requests_the_correct_resource.yml
  88. +46 −0 spec/cassettes/RedditKit_Client_Moderation/_ignore_reports/requests_the_correct_resource.yml
  89. +153 −0 ...ssettes/RedditKit_Client_Moderation/_moderation_log/returns_RedditKit_ModeratorAction_objects.yml
  90. +48 −0 .../cassettes/RedditKit_Client_Moderation/_moderators_of_subreddit/requests_the_correct_resource.yml
  91. +55 −0 spec/cassettes/RedditKit_Client_Moderation/_reset_subreddit_header/requests_the_correct_resource.yml
  92. +46 −0 spec/cassettes/RedditKit_Client_Moderation/_set_contest_mode/requests_the_correct_resource.yml
  93. +46 −0 spec/cassettes/RedditKit_Client_Moderation/_set_sticky_post/requests_the_correct_resource.yml
  94. +54 −0 spec/cassettes/RedditKit_Client_Moderation/_unban/requests_the_correct_resource.yml
  95. +46 −0 spec/cassettes/RedditKit_Client_Moderation/_unignore_reports/requests_the_correct_resource.yml
  96. +177 −0 ...RedditKit_Client_Multireddits/_add_subreddit_to_multireddit/adds_a_subreddit_to_a_multireddit.yml
  97. +137 −0 spec/cassettes/RedditKit_Client_Multireddits/_create_multireddit/creates_a_multireddit.yml
  98. +133 −0 ...Client_Multireddits/_create_multireddit/raises_RedditKit_Conflict_when_using_an_existing_name.yml
  99. +134 −0 spec/cassettes/RedditKit_Client_Multireddits/_delete_multireddit/deletes_a_multireddit.yml
  100. +39 −0 spec/cassettes/RedditKit_Client_Multireddits/_multireddit/with_a_path/returns_a_multireddit.yml
  101. +39 −0 spec/cassettes/RedditKit_Client_Multireddits/_multireddit/without_a_path/returns_a_multireddit.yml
  102. +81 −0 ...nt_Multireddits/_multireddit_description/with_a_multireddit/returns_a_multireddit_description.yml
  103. +45 −0 ...ultireddit_description/with_a_username_and_multireddit_name/returns_a_multireddit_description.yml
  104. +47 −0 spec/cassettes/RedditKit_Client_Multireddits/_my_multireddits/return_s_the_user_s_multireddits.yml
  105. +175 −0 ...Client_Multireddits/_remove_subreddit_from_multireddit/removes_a_subreddit_from_a_multireddit.yml
  106. +134 −0 spec/cassettes/RedditKit_Client_Multireddits/_rename_multireddit/renames_a_multireddit.yml
  107. +48 −0 .../RedditKit_Client_Multireddits/_set_multireddit_description/returns_a_multireddit_description.yml
  108. +184 −0 spec/cassettes/RedditKit_Client_Multireddits/_update_multireddit/updates_a_multireddit.yml
  109. +46 −0 ...ettes/RedditKit_Client_PrivateMessages/_block_author_of_message/requests_the_correct_resource.yml
  110. +51 −0 spec/cassettes/RedditKit_Client_PrivateMessages/_mark_as_read/requests_the_correct_resource.yml
  111. +51 −0 spec/cassettes/RedditKit_Client_PrivateMessages/_mark_as_unread/requests_the_correct_resource.yml
  112. +187 −0 spec/cassettes/RedditKit_Client_PrivateMessages/_messages/requests_the_correct_resource.yml
  113. +46 −0 spec/cassettes/RedditKit_Client_PrivateMessages/_unblock/requests_the_correct_resource.yml
  114. +878 −0 spec/cassettes/RedditKit_Client_Search/_search/restricts_searches_to_a_specific_subreddit.yml
  115. +130 −0 spec/cassettes/RedditKit_Client_Search/_search/returns_a_specific_number_of_results.yml
  116. +844 −0 spec/cassettes/RedditKit_Client_Search/_search/returns_search_results.yml
  117. +181 −0 spec/cassettes/RedditKit_Client_Subreddits/_random_subreddit/returns_a_random_subreddit.yml
  118. +37 −0 spec/cassettes/RedditKit_Client_Subreddits/_recommended_subreddits/returns_subreddit_names.yml
  119. +875 −0 spec/cassettes/RedditKit_Client_Subreddits/_search_subreddits_by_name/returns_subreddit_names.yml
  120. +100 −0 spec/cassettes/RedditKit_Client_Subreddits/_subreddit/returns_a_specified_subreddit.yml
  121. +505 −0 spec/cassettes/RedditKit_Client_Subreddits/_subreddits/returns_a_specified_number_of_subreddits.yml
  122. +510 −0 ...cassettes/RedditKit_Client_Subreddits/_subreddits/returns_subreddits_from_a_specific_category.yml
  123. +41 −0 spec/cassettes/RedditKit_Client_Subreddits/_subreddits_by_topic/returns_subreddit_names.yml
  124. +46 −0 spec/cassettes/RedditKit_Client_Subreddits/_subscribe/requests_the_correct_resource.yml
  125. +415 −0 ...s/RedditKit_Client_Subreddits/_subscribed_subreddits/returns_a_specified_number_of_subreddits.yml
  126. +58 −0 ...edditKit_Client_Subreddits/_subscribed_subreddits/returns_subreddits_from_a_specific_category.yml
Sorry, we could not display the entire diff because it was too big.
19 .gitignore
@@ -0,0 +1,19 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
+tags
+.env
5 .travis.yml
@@ -0,0 +1,5 @@
+language: ruby
+rvm:
+ - 1.9.2
+ - 1.9.3
+ - 2.0.0
1 .yardopts
@@ -0,0 +1 @@
+--no-private
18 Gemfile
@@ -0,0 +1,18 @@
+source 'https://rubygems.org'
+
+group :development do
+ gem 'awesome_print', :require => 'ap'
+ gem 'guard-rspec'
+ gem 'guard-yard'
+ gem 'pry'
+ gem 'yard'
+end
+
+group :test do
+ gem 'rspec'
+ gem 'simplecov', :require => false
+ gem 'vcr', '~> 2.6.0'
+ gem 'webmock', '~> 1.12.0'
+end
+
+gemspec
11 Guardfile
@@ -0,0 +1,11 @@
+guard :rspec do
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('spec/spec_helper.rb') { "spec" }
+end
+
+guard 'yard' do
+ watch(%r{lib/.+\.rb})
+ watch(%r{docs/.+\.md})
+ watch(%r{README\.md})
+end
22 LICENSE.md
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Sam Symons
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
147 README.md
@@ -0,0 +1,147 @@
+# RedditKit.rb
+
+RedditKit.rb is a [reddit API](http://www.reddit.com/dev/api) wrapper, written in Ruby.
+
+## Installation
+
+Add this to your Gemfile:
+
+ gem 'redditkit', '~> 1.0'
+
+Or install it directly:
+
+ gem install redditkit
+
+## Getting Started
+
+RedditKit.rb is structured closely to the wonderful [Octokit.rb](https://github.com/octokit/octokit.rb) and [Twitter](https://github.com/sferik/twitter) gems. If you're familiar with either of those, you'll feel right at home here. You can find the [project's documentation on the RedditKit website](http://redditkit.com/redditkit.rb/).
+
+RedditKit.rb is used through either the `RedditKit` module, or `RedditKit::Client` objects, like so:
+
+**Module usage:**
+```ruby
+RedditKit.sign_in 'username', 'password'
+subreddits = RedditKit.subscribed_subreddits
+```
+
+**Instance method usage:**
+```ruby
+client = RedditKit::Client.new 'username', 'password'
+subreddits = client.subscribed_subreddits
+```
+
+Using RedditKit.rb at the module level allows you to use a single account without having to keep track of RedditKit::Client instances. Working at the instance method level makes it possible to use multiple accounts at once, with one client object per account.
+
+> RedditKit.rb doesn't have any built-in rate limiting. reddit's API rules require that you make no more than 30 requests per minute and try to avoid requesting the same page more than once every 30 seconds. You can read up on the API rules [on their wiki page](https://github.com/reddit/reddit/wiki/API).
+
+### Authentication
+
+```ruby
+client = RedditKit::Client.new 'username', 'password'
+client.signed_in? # => true
+```
+
+## More Examples
+
+**Fetch a user and check their link karma:**
+
+```ruby
+user = RedditKit.user 'samsymons'
+puts "#{user.username} has #{user.link_karma} link karma."
+```
+
+**Subscribe to a subreddit:**
+
+```ruby
+authenticated_client = RedditKit::Client.new 'samsymons', 'password'
+authenticated_client.subscribe 'ruby'
+```
+
+**Upvote the top post in a subreddit:**
+
+```ruby
+posts = authenticated_client.posts 'programming', :category => :top, :time => :all
+authenticated_client.upvote posts.first
+```
+
+**Send private messages:**
+
+```ruby
+authenticated_client.send_message 'How are you?', 'amberlynns', :subject => 'Hi!'
+```
+
+## Pagination
+
+Some RedditKit.rb methods accept pagination options and return `RedditKit::PaginatedResponse` objects upon completion. These options allow you to, for example, limit the number of results returned, or fetch results before/after a specific object.
+
+`RedditKit::PaginatedResponse` forwards its enumeration methods to its `results` array, so you can iterate over it like you would with a standard array.
+
+``` ruby
+paginated_response = RedditKit.front_page
+
+paginated_response.each do |link|
+ # Do something with each link.
+end
+```
+
+## Multiple Accounts
+
+## Configuration
+
+You can configure various aspects of RedditKit.rb's operation, including its default API endpoint and user agent, by setting attributes on `RedditKit::Client`.
+
+**You should set your user agent to the name and version of your app, along with your reddit username. That way, if you ever have a buggy version of your app abusing the API, the reddit admins will know who to contact.**
+
+## Contributing
+
+### Debugging
+
+Because RedditKit.rb is built atop Faraday, you can modify its middleware stack to add new behaviour. This is particularly handy for debugging as you can turn on Faraday's response logger.
+
+```ruby
+RedditKit.middleware = Faraday::Builder.new do |builder|
+ builder.use Faraday::Request::UrlEncoded
+ builder.use RedditKit::Response::RaiseError
+ builder.use RedditKit::Response::ParseJSON
+ builder.use Faraday::Response::Logger
+ builder.adapter Faraday.default_adapter
+end
+```
+
+### Writing Tests
+
+Tests assume the presence of a `.env` file at the project's root. This file should contain the following three environment variables:
+
+* `REDDITKIT_TEST_USERNAME` The username of a reddit account dedicated solely to testing.
+* `REDDITKIT_TEST_PASSWORD` The password for the reddit account.
+* `REDDITKIT_TEST_SUBREDDIT` A subreddit for which the provided reddit account has admin privileges. This subreddit should be also be dedicated to testing as the test suite will run many different methods on it, creating & deleting various resources.
+
+## Requirements
+
+Ruby 1.9.3 and Ruby 2.0.0 are officially supported.
+
+## Need Help?
+
+Open an [issue](https://github.com/samsymons/RedditKit.rb/issues), or hit me up on [Twitter](http://twitter.com/sam_symons).
+
+## License
+
+Copyright (c) 2013 Sam Symons (http://samsymons.com/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
1 Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
26 lib/redditkit.rb
@@ -0,0 +1,26 @@
+require 'redditkit/client'
+
+# The main RedditKit module.
+module RedditKit
+ class << self
+
+ # A RedditKit::Client, used when calling methods on the RedditKit module itself.
+ #
+ # @return [RedditKit::Client]
+ def client
+ @client ||= RedditKit::Client.new
+ end
+
+ def respond_to?(method_name, include_private = false)
+ client.respond_to?(method_name, include_private) || super
+ end
+
+ private
+
+ def method_missing(method_name, *args, &block)
+ return super unless client.respond_to?(method_name)
+ client.send(method_name, *args, &block)
+ end
+
+ end
+end
60 lib/redditkit/base.rb
@@ -0,0 +1,60 @@
+module RedditKit
+
+ # A base class for RedditKit's model objects, automatically generating attribute and predicate methods.
+ class Base
+
+ attr_reader :attributes
+
+ class << self
+
+ def attr_reader(*attrs)
+ attrs.each do |attr|
+ define_attribute_method(attr)
+ define_predicate_method(attr)
+ end
+ end
+
+ private
+
+ def define_attribute_method(method)
+ define_method(method) do
+ memoize(method) do
+ @attributes[method]
+ end
+ end
+ end
+
+ def define_predicate_method(method)
+ define_method(:"#{method}?") do
+ !!@attributes[method]
+ end
+ end
+
+ end
+
+ def initialize(attributes = {})
+ kind = attributes[:kind]
+ data = attributes[:data]
+
+ @attributes = data || {}
+ @attributes[:kind] = kind
+ end
+
+ def [](method)
+ send(method.to_sym)
+ rescue NoMethodError
+ nil
+ end
+
+ private
+
+ def memoize(key, &block)
+ ivar = :"@#{key}"
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
+
+ result = block.call
+ instance_variable_set(ivar, result)
+ end
+
+ end
+end
148 lib/redditkit/client.rb
@@ -0,0 +1,148 @@
+require 'faraday'
+require 'redditkit/error'
+require 'redditkit/version'
+require 'redditkit/client/account'
+require 'redditkit/client/apps'
+require 'redditkit/client/captcha'
+require 'redditkit/client/comments'
+require 'redditkit/client/flair'
+require 'redditkit/client/links'
+require 'redditkit/client/miscellaneous'
+require 'redditkit/client/moderation'
+require 'redditkit/client/multireddits'
+require 'redditkit/client/private_messages'
+require 'redditkit/client/search'
+require 'redditkit/client/subreddits'
+require 'redditkit/client/users'
+require 'redditkit/client/utilities'
+require 'redditkit/client/voting'
+require 'redditkit/client/wiki'
+require 'redditkit/response/parse_json'
+require 'redditkit/response/raise_error'
+
+module RedditKit
+
+ # The client for the reddit API, handling all interactions with reddit's servers.
+ class Client
+ include RedditKit::Client::Account
+ include RedditKit::Client::Apps
+ include RedditKit::Client::Captcha
+ include RedditKit::Client::Comments
+ include RedditKit::Client::Flair
+ include RedditKit::Client::Links
+ include RedditKit::Client::Miscellaneous
+ include RedditKit::Client::Moderation
+ include RedditKit::Client::Multireddits
+ include RedditKit::Client::PrivateMessages
+ include RedditKit::Client::Search
+ include RedditKit::Client::Subreddits
+ include RedditKit::Client::Users
+ include RedditKit::Client::Utilities
+ include RedditKit::Client::Voting
+ include RedditKit::Client::Wiki
+
+ attr_reader :username
+ attr_reader :current_user
+ attr_reader :cookie
+ attr_reader :modhash
+
+ attr_accessor :api_endpoint
+ attr_accessor :authentication_endpoint
+ attr_accessor :user_agent
+ attr_accessor :middleware
+
+ def initialize(username = nil, password = nil)
+ @username = username
+ @password = password
+
+ @cookie = nil
+ @modhash = nil
+
+ unless @username.nil? or @password.nil?
+ sign_in @username, @password
+ end
+ end
+
+ def api_endpoint
+ @api_endpoint ||= 'http://www.reddit.com/'
+ end
+
+ def authentication_endpoint
+ @authentication_endpoint ||= 'https://ssl.reddit.com/'
+ end
+
+ def user_agent
+ @user_agent ||= "RedditKit.rb #{RedditKit::Version.to_s}"
+ end
+
+ def middleware
+ @middleware ||= Faraday::Builder.new do |builder|
+ builder.use Faraday::Request::UrlEncoded
+ builder.use RedditKit::Response::RaiseError
+ builder.use RedditKit::Response::ParseJSON
+ builder.adapter Faraday.default_adapter
+ end
+ end
+
+ private
+
+ def get(path, params = nil)
+ request(:get, path, params, connection)
+ end
+
+ def post(path, params = nil)
+ request(:post, path, params, connection)
+ end
+
+ def https_post(path, params = nil)
+ request(:post, path, params, https_connection)
+ end
+
+ def put(path, params = nil)
+ request(:put, path, params, connection)
+ end
+
+ def delete_path(path, params = nil)
+ request(:delete, path, params, connection)
+ end
+
+ def request(method, path, parameters, request_connection)
+ if signed_in?
+ request_parameters = parameters || {}
+
+ unless method == :get
+ raise RedditKit::NotAuthenticated unless signed_in?
+ end
+
+ request = authenticated_request_configuration(method, path, request_parameters)
+ request_connection.send(method.to_sym, path, parameters, &request).env
+ else
+ request_connection.send(method.to_sym, path, parameters).env
+ end
+ rescue Faraday::Error::ClientError
+ raise RedditKit::RequestError
+ end
+
+ def authenticated_request_configuration(method, path, parameters)
+ raise RedditKit::NotAuthenticated unless signed_in?
+
+ Proc.new do |request|
+ request.headers['Cookie'] = "reddit_session=#{@cookie}"
+ request.headers['X-Modhash'] = @modhash
+ end
+ end
+
+ def connection
+ @connection ||= connection_with_url(api_endpoint)
+ end
+
+ def https_connection
+ @https_connection ||= connection_with_url(authentication_endpoint)
+ end
+
+ def connection_with_url(url)
+ Faraday.new(url, { :builder => middleware })
+ end
+
+ end
+end
73 lib/redditkit/client/account.rb
@@ -0,0 +1,73 @@
+module RedditKit
+ class Client
+
+ # Methods which operate on a user's account.
+ module Account
+
+ # Sign in to a reddit account.
+ #
+ # @see http://www.reddit.com/dev/api#POST_api_login
+ # @param username [String] The reddit account's username.
+ # @param password [String] The reddit account's password.
+ def sign_in(username, password)
+ response = https_post 'api/login', { :user => username, :passwd => password, :api_type => :json }
+ body = response[:body]
+
+ data = body[:json][:data]
+
+ @modhash = data[:modhash]
+ @cookie = data[:cookie]
+ @username = username
+
+ @current_user = user @username
+ @username = @current_user.username
+ end
+
+ # Check is a user is currently signed in.
+ #
+ # @return [Boolean]
+ def signed_in?
+ !!@modhash and !!@cookie
+ end
+
+ # Sign the current user out.
+ def sign_out
+ @username = nil
+ @current_user = nil
+
+ @modhash = nil
+ @cookie = nil
+ end
+
+ # Updates the current user's email or password.
+ #
+ # @see http://www.reddit.com/dev/api#POST_api_update
+ # @option options [String] current_password The user's current password.
+ # @option options [String] new_password The user's new password.
+ # @option options [String] email The user's new email address.
+ def update_account(options)
+ password = options[:new_password]
+ parameters = { :curpass => options[:current_password], :email => options[:email], :newpass => password, :verpass => password, :api_type => :json }
+
+ post('api/update', parameters)
+ end
+
+ # Invalidates all session cookies and updates the current one.
+ #
+ # @see http://www.reddit.com/dev/api#POST_api_clear_sessions
+ # @param password [String] The authenticated user's current password.
+ # @return [String] The new cookie value.
+ def update_session(password)
+ response = post('api/clear_sessions', :dest => api_endpoint, :curpass => password, :api_type => 'json')
+
+ response_headers = response[:response_headers]
+ full_cookie_string = response_headers['set-cookie']
+ cookie = full_cookie_string[/reddit_session=(.*?);/m, 1]
+
+ @cookie = cookie
+ cookie
+ end
+
+ end
+ end
+end
63 lib/redditkit/client/apps.rb
@@ -0,0 +1,63 @@
+module RedditKit
+ class Client
+
+ # Methods for operating on apps in the current user's account.
+ module Apps
+
+ # Create or update an app.
+ #
+ # @param name [String] The app's name.
+ # @option options [String] description The app's description.
+ # @option options [String] about_url The app's URL.
+ # @option options [String] redirect_url The app's redirect URL.
+ # @option options [String] app_identifier The identifier of the app, if you are updating an existing one.
+ def create_app(name, options = {})
+ description = options[:description]
+ about_url = options[:about_url]
+ redirect_url = options[:redirect_url]
+ app_identifier = options[:app_identifier]
+ parameters = { :client_id => app_identifier, :name => name, :description => description, :about_url => about_url, :redirect_uri => redirect_url }
+
+ post('api/updateapp', parameters)
+ end
+ alias update_app create_app
+
+ # Delete an app.
+ #
+ # @param app_identifier [String] The identifier of the app.
+ def delete_app(app_identifier)
+ post('api/deleteapp', { :client_id => app_identifier })
+ end
+
+ # Revoke an app.
+ #
+ # @param app_identifier [String] The identifier of the app.
+ def revoke_app(app_identifier)
+ post('api/revokeapp', { :client_id => app_identifier })
+ end
+
+ # Add a user as a developer of an app.
+ #
+ # @param user [String, RedditKit::User] The username of the user to add, or a RedditKit::User.
+ # @param app_identifier [String] The identifier of the app.
+ def add_developer(user, app_identifier)
+ username = extract_string user, :username
+ parameters = { :name => username, :client_id => app_identifier }
+
+ post('api/adddeveloper', parameters)
+ end
+
+ # Remove an app's developer.
+ #
+ # @param user [String, RedditKit::User] The username of the user to add, or a RedditKit::User.
+ # @param app_identifier [String] The identifier of the app.
+ def remove_developer(user, app_identifier)
+ username = extract_string user, :username
+ parameters = { :name => username, :client_id => app_identifier }
+
+ post('api/removedeveloper', parameters)
+ end
+
+ end
+ end
+end
36 lib/redditkit/client/captcha.rb
@@ -0,0 +1,36 @@
+module RedditKit
+ class Client
+
+ # Methods for retrieving and submitting CAPTCHAs.
+ module Captcha
+
+ # Whether the current user will need to answer a CAPTCHA for methods which may require one.
+ #
+ # @return [Boolean]
+ def needs_captcha?
+ response = get('api/needs_captcha.json', nil)
+ needs_captcha = response[:body]
+
+ needs_captcha == 'true'
+ end
+
+ # Returns a new CATPCHA identifier.
+ #
+ # @return [String] The CAPTCHA identifier.
+ def new_captcha_identifier
+ response = post('api/new_captcha', { :api_type => :json })
+ data = response[:body][:json][:data]
+
+ data[:iden]
+ end
+
+ # Returns the URL for a CAPTCHA image with a given identifier.
+ #
+ # @return [String]
+ def captcha_url(captcha_identifier)
+ "http://reddit.com/captcha/#{captcha_identifier}.png"
+ end
+
+ end
+ end
+end
54 lib/redditkit/client/comments.rb
@@ -0,0 +1,54 @@
+require 'redditkit/comment'
+
+module RedditKit
+ class Client
+
+ # Methods for interacting with comment threads.
+ module Comments
+
+ # Get a comment object from its full name.
+ #
+ # @param comment_full_name [String] The full name of the comment.
+ # @return [RedditKit::Comment]
+ # @note This method does not include any replies to the comment.
+ def comment(comment_full_name)
+ comments = objects_from_response(:get, 'api/info.json', { :id => comment_full_name })
+ comments.first
+ end
+
+ # Get comments on a link.
+ #
+ # @param link [String, RedditKit::Link] The identifier of the link, or a RedditKit::Link.
+ # @option options [Integer] :limit The number of comments to return.
+ # @return [Array<RedditKit::Comment>]
+ def comments(link, options = {})
+ return nil unless link
+
+ link_id = extract_id link
+ path = "comments/#{link_id}.json"
+
+ comments_from_response(:get, path, options)
+ end
+
+ # Submit a comment on a link or comment.
+ #
+ # @param link_or_comment [String, RedditKit::Comment, RedditKit::Link] The object to comment on.
+ # @param text [String] The text of the comment, formatted as Markdown.
+ # @return [RedditKit::Comment] The new comment object.
+ def submit_comment(link_or_comment, text)
+ object_full_name = extract_full_name link_or_comment
+ parameters = { :text => text, :thing_id => object_full_name, :api_type => :json }
+
+ response = post('/api/comment', parameters)
+ response_data = response[:body][:json][:data]
+
+ full_comment_data = response_data[:things].first
+ comment_data = full_comment_data[:data]
+ comment_full_name = comment_data[:id]
+
+ comment comment_full_name
+ end
+
+ end
+ end
+end
148 lib/redditkit/client/flair.rb
@@ -0,0 +1,148 @@
+require 'ostruct'
+
+module RedditKit
+ class Client
+
+ # Methods for interacting with flair in subreddits.
+ module Flair
+
+ # Lists users and their flair in a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @option options [1..1000] :limit The number of items to return.
+ # @option options [String] :before Only return objects before this id.
+ # @option options [String] :after Only return objects after this id.
+ def flair_list(subreddit, options = {})
+ subreddit_name = extract_string(subreddit, :display_name)
+ list = get("/r/#{subreddit_name}/api/flairlist.json", options)
+ users = list[:body][:users]
+
+ users.collect { |user| OpenStruct.new(user) }
+ end
+
+ # Creates a flair template in a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @param type [user, link] The template's type. Defaults to user.
+ # @option options [String] text The text value for the template.
+ # @option options [String] css_class The CSS class for the template.
+ # @option options [Boolean] user_editable Whether the template should be editable by users.
+ def create_flair_template(subreddit, type, options = {})
+ subreddit_name = extract_string(subreddit, :display_name)
+ flair_type = (type.to_s == 'link') ? 'LINK_FLAIR' : 'USER_FLAIR'
+
+ parameters = { :r => subreddit_name, :flair_type => flair_type, :text => options[:text], :css_class => options[:css_class], :api_type => :json }
+ parameters[:text_editable] = 'on' if options[:user_editable]
+
+ post('api/flairtemplate', parameters)
+ end
+
+ # Deletes a flair template.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @param template_identifier [String] The template's identifier.
+ def delete_flair_template(subreddit, template_identifier)
+ subreddit_name = extract_string(subreddit, :display_name)
+ parameters = { :flair_template_id => template_identifier, :r => subreddit_name }
+
+ post('api/deleteflairtemplate', parameters)
+ end
+
+ # Toggles flair for a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @param flair_enabled [Boolean] Whether to enable flair for the subreddit.
+ def toggle_flair(subreddit, flair_enabled)
+ post('api/setflairenabled', { :r => subreddit, :flair_enabled => flair_enabled })
+ end
+
+ # Sets flair on a link or user.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's name, or a RedditKit::Subreddit.
+ # @option options [String] text The text value for the template.
+ # @option options [String] css_class The CSS class for the template.
+ # @option options [String, RedditKit::Link] link A link's full name, or a RedditKit::Link.
+ # @option options [String, RedditKit::User] user A user's username, or a RedditKit::User.
+ # @note Raises RedditKit::BadClassName if any CSS classes contain invalid characters, or RedditKit::TooManyClassNames if there are too many.
+ def set_flair(options)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ link_full_name = extract_full_name options[:link]
+ username = extract_string options[:user], :username
+
+ parameters = { :r => subreddit_name, :text => options[:text], :css_class => options[:css_class], :name => username, :link => link_full_name }
+
+ post('api/flair', parameters)
+ end
+
+ # Sets a subreddit's flair using a string formatted as CSV.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @param csv_string [String] A string in CSV format.
+ # @note Each line in the string should be in the format 'user,flair-text,css_class'.
+ def set_flair_with_csv(subreddit, csv_string)
+ subreddit_name = extract_string(subreddit, :display_name)
+ parameters = { :r => subreddit_name, :flair_csv => csv_string }
+
+ post('api/flaircsv', parameters)
+ end
+
+ # Clears a user's flair.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @param user [String, RedditKit::User] A subreddit's name, or a RedditKit::Subreddit.
+ def delete_user_flair(subreddit, user)
+ subreddit_name = extract_string(subreddit, :display_name)
+ username = extract_string(user, :username)
+ parameters = { :name => username, :r => subreddit_name }
+
+ post('api/deleteflair', parameters)
+ end
+
+ # Clears all flair templates of a certain type.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's name, or a RedditKit::Subreddit.
+ # @option options [user, link] type The template's type. Defaults to user.
+ def clear_flair_templates(options)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ flair_type = 'USER_FLAIR'
+ flair_type = 'LINK_FLAIR' if options[:type].to_s == 'link'
+
+ parameters = { :r => subreddit_name, :flair_type => flair_type, :text => options[:text], :css_class => options[:css_class] }
+
+ post('api/clearflairtemplates', parameters)
+ end
+
+ # Applys a flair template to a link or user.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's name, or a RedditKit::Subreddit.
+ # @option options [String] template_id The template's identifier.
+ # @option options [String, RedditKit::Link] link A link's full name, or a RedditKit::Link.
+ # @option options [String, RedditKit::User] user A user's username, or a RedditKit::User.
+ def apply_flair_template(options)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ link_full_name = extract_full_name options[:link]
+ username = extract_string options[:user], :username
+
+ parameters = { :flair_template_id => options[:template_id], :r => subreddit_name, :name => username, :link => link_full_name }
+
+ post('api/selectflair', parameters)
+ end
+
+ # Sets flair options for a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's name, or a RedditKit::Subreddit.
+ # @option options [Boolean] flair_enabled Whether to enable flair for the subreddit.
+ # @option options [left, right] flair_position The position of user flair.
+ # @option options [left, right] link_flair_position The position of link flair.
+ # @option options [Boolean] flair_self_assign_enabled Whether users may assign their own flair.
+ # @option options [Boolean] link_flair_self_assign_enabled Whether users may assign their own link flair.
+ def set_flair_options(subreddit, options = {})
+ subreddit_name = extract_string(subreddit, :display_name)
+ options.merge!({ :r => subreddit_name, :uh => @modhash })
+
+ post('api/flairconfig', options)
+ end
+
+ end
+ end
+end
134 lib/redditkit/client/links.rb
@@ -0,0 +1,134 @@
+require 'redditkit/link'
+
+module RedditKit
+ class Client
+
+ # Methods for retrieving, submitting and interacting with links.
+ module Links
+
+ # Gets the links currently on the front page.
+ #
+ # @option options [hot, new, rising, controversial, top] :category The category from which to retrieve links.
+ # @option options [hour, day, week, month, year, all] :time The time from which to retrieve links. Defaults to all time.
+ # @option options [1..100] :limit The number of links to return.
+ # @option options [String] :before Only return links before this identifier.
+ # @option options [String] :after Only return links after this identifier.
+ # @return [RedditKit::PaginatedResponse]
+ def front_page(options = {})
+ links nil, options
+ end
+
+ # Gets an array of links from a specific subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] The display name of the subreddit, or a RedditKit::Subreddit.
+ # @option options [hot, new, rising, controversial, top] :category The category from which to retrieve links.
+ # @option options [hour, day, week, month, year, all] :time The time from which to retrieve links. Defaults to all time.
+ # @option options [1..100] :limit The number of links to return.
+ # @option options [String] :before Only return links before this identifier.
+ # @option options [String] :after Only return links after this identifier.
+ # @return [RedditKit::PaginatedResponse]
+ def links(subreddit, options = {})
+ subreddit_name = extract_string(subreddit, :display_name) if subreddit
+ category = options[:category] || :hot
+
+ path = "%s/#{category.to_s}.json" % ('r/' + subreddit_name if subreddit_name)
+
+ options[:t] = options[:time] if options[:time]
+ options.delete :category
+ options.delete :time
+
+ objects_from_response(:get, path, options)
+ end
+
+ # Gets a link object from its full name.
+ #
+ # @param link_full_name [String] The full name of the link.
+ # @return [RedditKit::Link]
+ # @note This method will return nil if there is not a user currently signed in.
+ def link(link_full_name)
+ links = objects_from_response(:get, 'api/info.json', { :id => link_full_name })
+ links.first
+ end
+
+ # Gets links with a specific domain.
+ #
+ # @param domain [String] The domain for which to get links.
+ # @option options [hour, day, week, month, year] :time The time from which to retrieve links. Defaults to all time.
+ # @option options [1..100] :limit The number of links to return.
+ # @option options [String] :before Only return links before this identifier.
+ # @option options [String] :after Only return links after this identifier.
+ # @return [RedditKit::PaginatedResponse]
+ # @example links = RedditKit.links_with_domain "github.com"
+ def links_with_domain(domain, options = {})
+ parameters = { :url => domain, :t => options[:time] }
+ options.merge! parameters
+ options.delete :t
+
+ objects_from_response(:get, 'api/info.json', options)
+ end
+
+ # Submits a link or self post to reddit.
+ #
+ # @param title [String] The title of the post.
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's display name, or a RedditKit::Subreddit.
+ # @option options [String] :url The URL for the post. Note that if this value is present, :text will be ignored.
+ # @option options [String] :text The text value for the post, as Markdown.
+ # @option options [String] :captcha_identifier An identifier for a CAPTCHA, if the current user is required to fill one out.
+ # @option options [String] :captcha_value The value for the CAPTCHA with the given identifier, as filled out by the user.
+ def submit(title, subreddit, options = {})
+ subreddit_name = extract_string subreddit, :display_name
+ parameters = { :title => title, :sr => subreddit_name, :iden => options[:captcha_identifier], :captcha => options[:captcha_value] }
+
+ if options[:url]
+ parameters[:url] = options[:url]
+ else
+ parameters[:text] = options[:text]
+ end
+
+ post('api/submit', parameters)
+ end
+
+ # Marks a link as not safe for work.
+ #
+ # @param link [String, RedditKit::Link] A link's full name, or a RedditKit::Link.
+ def mark_nsfw(link)
+ post('api/marknsfw', { :id => extract_full_name(link) })
+ end
+
+ # Marks a link as safe for work.
+ #
+ # @param link [String, RedditKit::Subreddit] A link's full name, or a RedditKit::Link.
+ def mark_sfw(link)
+ post('api/unmarknsfw', { :id => extract_full_name(link) })
+ end
+ alias unmark_nsfw mark_sfw
+
+ # Hides a link.
+ #
+ # @param link [String, RedditKit::Link] A link's full name, or a RedditKit::Link.
+ def hide(link)
+ post('api/hide', { :id => extract_full_name(link) })
+ end
+
+ # Unhides a link.
+ #
+ # @param link [String, RedditKit::Link] A link's full name, or a RedditKit::Link.
+ def unhide(link)
+ post('api/unhide', { :id => extract_full_name(link) })
+ end
+
+ # Gets a random link.
+ #
+ # @return [RedditKit::Link]
+ def random_link
+ response = get('/random', nil)
+ headers = response[:response_headers]
+ location = headers[:location]
+
+ link_id = location[/\/tb\/(.*)/, 1]
+ link "t3_#{link_id}"
+ end
+
+ end
+ end
+end
50 lib/redditkit/client/miscellaneous.rb
@@ -0,0 +1,50 @@
+module RedditKit
+ class Client
+
+ # Methods which don't belong in any clear categroy, such as editing and deleting items on reddit.
+ module Miscellaneous
+
+ # Edit the text or a self post or comment.
+ #
+ # @param object [String, RedditKit::Comment, RedditKit::Link] A link or comment's full name, a RedditKit::Link, or a RedditKit::Subreddit.
+ # @option options [String] text The new text for the link or comment.
+ def edit(object, options)
+ parameters = { :text => options[:text], :thing_id => extract_full_name(object) }
+ post('/api/editusertext', parameters)
+ end
+
+ # Deletes a link or comment.
+ #
+ # @param object [String, RedditKit::Comment, RedditKit::Link] A link or comment's full name, a RedditKit::Link, or a RedditKit::Subreddit.
+ def delete(object)
+ full_name = extract_full_name object
+ post('api/del', { :id => full_name })
+ end
+
+ # Saves a link or comment.
+ #
+ # @param object [String, RedditKit::Link, RedditKit::Subreddit] A link or comment's full name, a RedditKit::Link, or a RedditKit::Subreddit.
+ def save(object)
+ full_name = extract_full_name object
+ post('api/save', { :id => full_name })
+ end
+
+ # Unsaves a link or comment.
+ #
+ # @param object [String, RedditKit::Link, RedditKit::Subreddit] A link or comment's full name, a RedditKit::Link, or a RedditKit::Subreddit.
+ def unsave(object)
+ full_name = extract_full_name object
+ post('api/unsave', { :id => full_name })
+ end
+
+ # Reports a link or comment. The reddit API will also hide the link or comment.
+ #
+ # @param object [String, RedditKit::Link, RedditKit::Comment] A link or comment's full name, a RedditKit::Link, or a RedditKit::Subreddit.
+ def report(object)
+ full_name = extract_full_name object
+ post('api/report', { :id => full_name })
+ end
+
+ end
+ end
+end
184 lib/redditkit/client/moderation.rb
@@ -0,0 +1,184 @@
+require 'redditkit/moderator_action'
+
+module RedditKit
+ class Client
+
+ # Methods for moderating subreddits.
+ module Moderation
+
+ # Ban a user. This requires moderator privileges on the specified subreddit.
+ #
+ # @option options [String, RedditKit::User] user The user's username, or a RedditKit::User.
+ # @option options [String, RedditKit::Subreddit] subreddit The subreddit's name, or a RedditKit::Subreddit.
+ # @note If a subreddit's name is passed as the :subreddit option, a second HTTP request will be made to get the RedditKit::Subreddit object.
+ def ban(options)
+ subreddit_object = options[:subreddit]
+ subreddit_object = subreddit(subreddit_object) if subreddit_object.is_a? String
+
+ friend_request :container => subreddit_object.full_name, :name => options[:user], :subreddit => subreddit_object.name, :type => :banned
+ end
+
+ # Lift the ban on a user. This requires moderator privileges on the specified subreddit.
+ #
+ # @option options [String, RedditKit::User] user The user's username, or a RedditKit::User.
+ # @option options [RedditKit::Subreddit] subreddit The subreddit in which to ban the user.
+ def unban(options)
+ subreddit_object = options[:subreddit]
+ subreddit_object = subreddit(subreddit_object) if subreddit_object.is_a? String
+
+ unfriend_request :container => subreddit_object.full_name, :name => options[:user], :subreddit => subreddit_object.name, :type => :banned
+ end
+
+ # Approves an unmoderated link.
+ #
+ # @param link [String, RedditKit::Link] A link's full name, or a RedditKit::Link.
+ def approve(link)
+ full_name = extract_full_name link
+ post('api/approve', { :id => full_name, :api_type => :json })
+ end
+
+ # Removes a link or comment.
+ #
+ # @param object [String, RedditKit::Comment, RedditKit::Link] The full name of a link/comment, a RedditKit::Comment, or a RedditKit::Link.
+ def remove(object)
+ full_name = extract_full_name object
+ post('api/remove', { :id => full_name, :api_type => :json })
+ end
+
+ # Ignores the reports on a link or comment.
+ #
+ # @param object [String, RedditKit::Comment, RedditKit::Link] The full name of a link/comment, a RedditKit::Comment, or a RedditKit::Link.
+ def ignore_reports(object)
+ full_name = extract_full_name object
+ post('api/ignore_reports', { :id => full_name, :api_type => :json })
+ end
+
+ # Unignores the reports on a link or comment.
+ #
+ # @param object [String, RedditKit::Comment, RedditKit::Link] The full name of a link/comment, a RedditKit::Comment, or a RedditKit::Link.
+ def unignore_reports(object)
+ full_name = extract_full_name object
+ post('api/unignore_reports', { :id => full_name, :api_type => :json })
+ end
+
+ # Distinguishes a comment as being posted by a moderator or admin.
+ #
+ # @option options [String, RedditKit::Comment] comment The full name of a comment, or a RedditKit::Comment.
+ # @option options [yes, no, admin, special] distinguish How to distinguish the comment. Defaults to yes.
+ # @note admin and special values may only be used if the current user has the right privileges.
+ def distinguish(options)
+ full_name = extract_full_name options[:comment]
+ how = options[:distinguish] || :yes
+ parameters = { :id => full_name, :api_type => :json }
+
+ post("api/distinguish/#{how}", parameters)
+ end
+
+ # Sets a post as have its contest mode enabled or disabled.
+ #
+ # @param link [String, RedditKit::Link] The full name of a link, or a RedditKit::Link.
+ # @option options [Boolean] enabled Whether to enable contest mode for the link's comments.
+ def set_contest_mode(link, options = {})
+ full_name = extract_full_name link
+
+ # reddit doesn't accept straight boolean values for this method, hence the need to use strings.
+ enabled = 'True'
+ if options.has_key? :enabled
+ enabled = options[:enabled] ? 'True' : 'False'
+ end
+
+ post('api/set_contest_mode', { :id => full_name, :state => enabled, :api_type => :json })
+ end
+
+ # Sets a post as sticky within its parent subreddit. This will replace the existing sticky post, if there is one.
+ #
+ # @param link [String, RedditKit::Link] The full name of a link, or a RedditKit::Link.
+ # @option options [Boolean] sticky Whether to mark the post as sticky or unsticky (true for sticky, false for unsticky). Defaults to true.
+ def set_sticky_post(link, options = {})
+ full_name = extract_full_name link
+
+ # reddit doesn't accept straight boolean values for this method, hence the need to use strings.
+ sticky = 'True'
+ if options.has_key? :sticky
+ sticky = options[:sticky] ? 'True' : 'False'
+ end
+
+ post('api/set_subreddit_sticky', { :id => full_name, :state => sticky, :api_type => :json })
+ end
+
+ # Get the moderators of a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] The display name of a subreddit, or a RedditKit::Subreddit.
+ # @return [Array<OpenStruct>]
+ def moderators_of_subreddit(subreddit)
+ subreddit_name = extract_string(subreddit, :display_name)
+ response = get("r/#{subreddit_name}/about/moderators.json", nil)
+
+ moderators = response[:body][:data][:children]
+ moderators.collect { |moderator| OpenStruct.new(moderator) }
+ end
+
+ # Get the contributors to a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] The display name of a subreddit, or a RedditKit::Subreddit.
+ # @return [Array<OpenStruct>]
+ def contributors_to_subreddit(subreddit)
+ subreddit_name = extract_string(subreddit, :display_name)
+ response = get("r/#{subreddit_name}/about/contributors.json", nil)
+
+ contributors = response[:body][:data][:children]
+ contributors.collect { |contributor| OpenStruct.new(contributor) }
+ end
+
+ # Accepts an invitation to become a moderator of a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] The display name of the subreddit, or a RedditKit::Subreddit.
+ def accept_moderator_invitation(subreddit)
+ subreddit_name = extract_string(subreddit, :display_name)
+ post('api/accept_moderator_invite', { :r => subreddit_name })
+ end
+
+ # Resign as a contributor to a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's full name, or a RedditKit::Subreddit.
+ def resign_as_contributor(subreddit)
+ full_name = extract_full_name subreddit
+ post('api/leavecontributor', { :id => full_name })
+ end
+
+ # Resign as a moderator of a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's full name, or a RedditKit::Subreddit.
+ def resign_as_moderator(subreddit)
+ full_name = extract_full_name subreddit
+ post('api/leavemoderator', { :id => full_name })
+ end
+
+ # Resets a subreddit's header image.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] The display name of the subreddit, or a RedditKit::Subreddit.
+ def reset_subreddit_header(subreddit)
+ subreddit_name = extract_string(subreddit, :display_name)
+ post('api/delete_sr_header', { :r => subreddit_name })
+ end
+
+ # Configures a subreddit's settings.
+ #
+ # @option parameters [String] title The subreddit's title (this is the value that will in a browser's title bar, not the subreddit's name).
+ # @option parameters [String] description The subreddit's description.
+ # @option parameters [Boolean] over_18 Whether the subreddit requires visitors to be over 18.
+ def set_subreddit_settings(parameters)
+ end
+
+ # Gets the moderation log for a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's display name, or a RedditKit::Subreddit.
+ # @return [RedditKit::PaginatedResponse]
+ def moderation_log(subreddit)
+ display_name = extract_string subreddit, :display_name
+ objects_from_response(:get, "r/#{display_name}/about/log.json", nil)
+ end
+
+ end
+ end
+end
207 lib/redditkit/client/multireddits.rb
@@ -0,0 +1,207 @@
+require 'json'
+require 'redditkit/multireddit'
+require 'redditkit/multireddit_description'
+
+module RedditKit
+ class Client
+
+ # Methods for interacting with multireddits.
+ module Multireddits
+
+ # Fetch the current user's multireddits.
+ #
+ # @return [Array<RedditKit::Multireddit>]
+ def my_multireddits
+ objects_from_response(:get, 'api/multi/mine.json', nil)
+ end
+
+ # Fetch a single multireddit.
+ #
+ # @overload multireddit(path)
+ # @param path [String] The multireddit's path.
+ # @overload multireddit(username, multireddit_name)
+ # @param username [String] The username of the user who owns the multireddit.
+ # @param multireddit_name [String] The multireddit's name.
+ # @return [RedditKit::Multireddit]
+ def multireddit(*args)
+ path = "api/multi"
+
+ if args.length == 1
+ path << args.first
+ else
+ path << path_for_multireddit(args.first, args.last << '.json')
+ end
+
+ object_from_response(:get, path, nil)
+ end
+
+ # Fetches the description for a multireddit.
+ #
+ # @overload multireddit_description(multireddit)
+ # @param multireddit [RedditKit::Multireddit] A RedditKit::Multireddit object.
+ # @overload multireddit_description(user, multireddit_name)
+ # @param user [String, RedditKit::User] The name of the user who owns the multireddit.
+ # @param multireddit_name [String] The name of the multireddit.
+ # @raise [RedditKit::NotAuthenticated] if there is not a user signed in.
+ def multireddit_description(*args)
+ raise RedditKit::NotAuthenticated unless signed_in?
+
+ multireddit = args.pop
+ user = args.first
+ multireddit_path = nil
+
+ if user.nil?
+ multireddit_path = multireddit.path
+ else
+ username = extract_string(user, :username)
+ multireddit_path = path_for_multireddit username, multireddit
+ end
+
+ object_from_response(:get, "api/multi#{multireddit_path}/description", nil)
+ end
+
+ # Sets the description for a multireddit.
+ #
+ # @param multireddit [String, RedditKit::Multireddit] The name of the multireddit, or a RedditKit::Multireddit.
+ # @param description [String] The new description for the subreddit.
+ # @return [RedditKit::MultiredditDescription] The updated multireddit description.
+ def set_multireddit_description(multireddit, description)
+ multireddit_name = extract_string(multireddit, :name)
+ multireddit_path = path_for_multireddit username, multireddit_name
+
+ model = { :body_md => description }
+ parameters = { :multipath => multireddit_path, :model => model.to_json }
+ path = "api/multi#{multireddit_path}/description"
+
+ response = put path, parameters
+
+ RedditKit::MultiredditDescription.new response[:body]
+ end
+
+ # Creates a new multireddit.
+ #
+ # @param multireddit [String, RedditKit::Multireddit] The name of the multireddit, or a RedditKit::Multireddit.
+ # @param subreddits [Array] An array of subreddit names or RedditKit::Subreddit objects.
+ # @param visibility [public, private] An array of subreddit names to be added to the multireddit.
+ def create_multireddit(multireddit, subreddits = [], visibility = 'private')
+ create_or_update_multireddit(:post, multireddit, subreddits, visibility)
+ end
+
+ # Updates an existing multireddit.
+ #
+ # @param multireddit [String, RedditKit::Multireddit] The name of the multireddit, or a RedditKit::Multireddit.
+ # @param subreddits [Array] An array of subreddit names or RedditKit::Subreddit objects.
+ # @param visibility [public, private] An array of subreddit names to be added to the multireddit.
+ def update_multireddit(multireddit, subreddits = [], visibility = 'private')
+ create_or_update_multireddit(:put, multireddit, subreddits, visibility)
+ end
+
+ # Copies a multireddit.
+ #
+ # @overload copy_multireddit(multireddit, copied_name)
+ # @param multireddit [String, RedditKit::Multireddit] The name of the multireddit, or a RedditKit::Multireddit.
+ # @param copied_name [String] The copied name for the multireddit.
+ # @overload copy_multireddit(user, multireddit_name, copied_name)
+ # @param user [String, RedditKit::User] The name of the user who owns the multireddit.
+ # @param multireddit_name [String] The name of the multireddit.
+ # @param copied_name [String] The copied name for the multireddit.
+ def copy_multireddit(*args)
+ copied_name = args.pop
+ multireddit_name = extract_string(args.pop, :name)
+ user = args.first
+
+ if user.nil?
+ user = @username
+ else
+ user = extract_string(user, :username)
+ end
+
+ target_multireddit_path = path_for_multireddit user, multireddit_name
+ destination_multireddit_path = path_for_multireddit user, copied_name
+ parameters = { :from => target_multireddit_path, :to => destination_multireddit_path }
+
+ post('api/multi/copy', parameters)
+ end
+
+ # Rename a multireddit owned by the current user.
+ #
+ # @param from [String] The multireddit's current name.
+ # @param to [String] The new name for the multireddit.
+ def rename_multireddit(from, to)
+ old_multireddit_path = path_for_multireddit @username, from
+ new_multireddit_path = path_for_multireddit @username, to
+
+ parameters = { :from => old_multireddit_path, :to => new_multireddit_path }
+ response = post('api/multi/rename', parameters)
+
+ RedditKit::Multireddit.new response[:body]
+ end
+
+ # Delete a multireddit.
+ #
+ # @param multireddit [String, RedditKit::Multireddit] A multireddit's name, or a RedditKit::Multireddit.
+ def delete_multireddit(multireddit)
+ multireddit_name = extract_string(multireddit, :name)
+
+ multireddit_path = path_for_multireddit @username, multireddit_name
+ path = "api/multi#{multireddit_path}"
+ parameters = { :multireddit => path_for_multireddit(@username, multireddit_name) }
+
+ delete_path(path, parameters)
+ end
+
+ # Add a subreddit to a multireddit owned by the current user.
+ #
+ # @param multireddit [String, RedditKit::Multireddit] The multireddit's name, or a RedditKit::Multireddit.
+ # @param subreddit [String, RedditKit::Subreddit] The subreddit's name, or a RedditKit::Subreddit.
+ def add_subreddit_to_multireddit(multireddit, subreddit)
+ multireddit_name = extract_string multireddit, :name
+ subreddit_name = extract_string subreddit, :name
+
+ multireddit_path = path_for_multireddit @username, multireddit_name
+ path = "api/multi#{multireddit_path}/r/#{subreddit_name}"
+ model = { :name => subreddit_name }
+
+ put(path, { :model => model.to_json })
+ end
+
+ # Removes a subreddit from a multireddit owned by the current user.
+ #
+ # @param multireddit [String, RedditKit::Multireddit] The multireddit's name, or a RedditKit::Multireddit.
+ # @param subreddit [String, RedditKit::Subreddit] The subreddit's name, or a RedditKit::Subreddit.
+ def remove_subreddit_from_multireddit(multireddit, subreddit)
+ multireddit_name = extract_string multireddit, :name
+ subreddit_name = extract_string subreddit, :name
+
+ multireddit_path = path_for_multireddit @username, multireddit_name
+ path = "api/multi#{multireddit_path}/r/#{subreddit_name}"
+
+ delete_path(path, nil)
+ end
+
+ private
+
+ # Creates or updates a multireddit.
+ #
+ # @param method [post, put] The HTTP method for the request. POST creates a multireddit, PUT updates one.
+ # @param multireddit [String, RedditKit::Multireddit] The name of the multireddit, or a RedditKit::Multireddit.
+ # @param subreddits [Array] An array of subreddit names or RedditKit::Subreddit objects.
+ # @param visibility [public, private] An array of subreddit names to be added to the multireddit.
+ def create_or_update_multireddit(method, multireddit, subreddits = [], visibility = 'private')
+ multireddit_name = extract_string(multireddit, :name)
+ multireddit_path = path_for_multireddit @username, multireddit_name
+ path = "api/multi#{multireddit_path}"
+
+ subreddit_hashes = subreddits.collect do |subreddit|
+ { :name => extract_string(subreddit, :name) }
+ end
+
+ model = { :visibility => visibility, :subreddits => subreddit_hashes }
+ parameters = { :multipath => multireddit_path, :model => model.to_json }
+
+ request(method, path, parameters, connection)
+ end
+
+ end
+ end
+end
74 lib/redditkit/client/private_messages.rb
@@ -0,0 +1,74 @@
+require 'redditkit/private_message'
+
+module RedditKit
+ class Client
+
+ # Methods for retrieving and sending private messages.
+ module PrivateMessages
+
+ # Gets the current user's private messages or comment replies.
+ #
+ # @option options [inbox, unread, sent, messages, mentions, moderator, comments, selfreply] :category The category from which to return messages.
+ # @option options [1..100] :limit The number of messages to return.
+ # @option options [String] :before Only return subreddits before this id.
+ # @option options [String] :after Only return subreddits after this id.
+ # @option options [Boolean] :mark Whether to mark requested messages as read.
+ # @return [RedditKit::PaginatedResponse]
+ def messages(options = {})
+ category = options[:category] || 'inbox'
+ path = "message/#{category}.json"
+ options.delete :category
+
+ objects_from_response(:get, path, options)
+ end
+
+ # Send a message to another reddit user.
+ #
+ # @param message [String] The text of the message.
+ # @param recipient [String, RedditKit::User] The recipient of the message.
+ # @option options [String] :subject The subject of the message.
+ # @option options [String] :captcha_identifier A CAPTCHA identifier to send with the message, if the current user is required to fill one out.
+ # @option options [String] :captcha_value The value of the CAPTCHA to send with the message, if the current user is required to fill one out.
+ def send_message(message, recipient, options = {})
+ username = extract_string(recipient, :username)
+ parameters = { :to => username, :text => message, :subject => options[:subject], :captcha => options[:captcha_value], :iden => options[:captcha_identifier] }
+
+ post('api/compose', parameters)
+ end
+
+ # Marks a message as read.
+ #
+ # @param message [String, RedditKit::PrivateMessage] A private message's full name, or a RedditKit::PrivateMessage.
+ def mark_as_read(message)
+ parameters = { :id => extract_full_name(message) }
+ post('api/read_message', parameters)
+ end
+
+ # Marks a message as unread.
+ #
+ # @param message [String, RedditKit::PrivateMessage] A private message's full name, or a RedditKit::PrivateMessage.
+ def mark_as_unread(message)
+ parameters = { :id => extract_full_name(message) }
+ post('api/unread_message', parameters)
+ end
+
+ # Blocks the author of a private message or comment.
+ # Users cannot be blocked based on username as reddit only allows you to block those who have harassed you (thus leaving a message in your inbox).
+ #
+ # @param message [String, RedditKit::PrivateMessage] A private message's full name, or a RedditKit::PrivateMessage.
+ def block_author_of_message(message)
+ parameters = { :id => extract_full_name(message) }
+ post('api/block', parameters)
+ end
+
+ # Unblocks a user.
+ #
+ # @param user [String, RedditKit::User] A user's username, or a RedditKit::User.
+ def unblock(user)
+ enemy_name = extract_string(user, :username)
+ unfriend_request :container => current_user.full_name, :name => enemy_name, :type => :enemy
+ end
+
+ end
+ end
+end
25 lib/redditkit/client/search.rb
@@ -0,0 +1,25 @@
+module RedditKit
+ class Client
+
+ # Methods for searching reddit's links.
+ module Search
+
+ # Search for links.
+ #
+ # @param query [String] The search query.
+ # @option options [true, false] :restrict_to_subreddit Whether to search only in a specified subreddit.
+ # @option options [String, RedditKit::Subreddit] :subreddit The optional subreddit to search.
+ # @option options [1..100] limit The number of links to return.
+ # @option options [String] before Only return links before this full name.
+ # @option options [String] after Only return links after this full name.
+ # @return [RedditKit::PaginatedResponse]
+ def search(query, options = {})
+ path = "%s/search.json" % ('r/' + options[:subreddit] if options[:subreddit])
+ parameters = { :q => query, :restrict_sr => options[:restrict_to_subreddit], :limit => options[:limit] }
+
+ objects_from_response(:get, path, parameters)
+ end
+
+ end
+ end
+end
120 lib/redditkit/client/subreddits.rb
@@ -0,0 +1,120 @@
+require 'redditkit/subreddit'
+
+module RedditKit
+ class Client
+
+ # Methods for interacting with subreddits.
+ module Subreddits
+
+ # Gets subreddits from a specified category.
+ #
+ # @option options [new, popular, banned] category The category of subreddits. Defaults to popular.
+ # @option options [1..100] limit The number of subreddits to return.
+ # @option options [String] before Only return subreddits before this id.
+ # @option options [String] after Only return subreddits after this id.
+ # @return [RedditKit::PaginatedResponse]
+ def subreddits(options = {})
+ category = options[:category] or 'popular'
+ path = "reddits/#{category}.json"
+ options.delete :category
+
+ objects_from_response(:get, path, options)
+ end
+
+ # Gets the current user's subscribed subreddits.
+ #
+ # @option options [subscriber, contributor, moderator] category The category from which to return subreddits. Defaults to subscriber.
+ # @option options [1..100] limit The number of subreddits to return.
+ # @option options [String] before Only return subreddits before this id.
+ # @option options [String] after Only return subreddits after this id.
+ # @return [RedditKit::PaginatedResponse]
+ def subscribed_subreddits(options = {})
+ category = options[:category] or 'subscriber'
+ path = "subreddits/mine/#{category}.json"
+ options.delete :category
+
+ objects_from_response(:get, path, options)
+ end
+
+ # Gets a subreddit object.
+ #
+ # @param subreddit_name [String] A subreddit's display name.
+ # @return [RedditKit::Subreddit]
+ # @example client.subreddit "programming"
+ def subreddit(subreddit_name)
+ object_from_response(:get, "r/#{subreddit_name}/about.json", nil)
+ end
+
+ # Subscribes to a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's full name, or a RedditKit::Subreddit.
+ def subscribe(subreddit)
+ full_name = extract_full_name subreddit
+ parameters = { :action => 'sub', :sr => full_name }
+
+ post("api/subscribe", parameters)
+ end
+
+ # Unsubscribes from a subreddit.
+ #
+ # @param subreddit [String, RedditKit::Subreddit] A subreddit's full name, or a RedditKit::Subreddit.
+ def unsubscribe(subreddit)
+ full_name = extract_full_name subreddit
+ parameters = { :action => 'unsub', :sr => full_name }
+
+ post("api/subscribe", parameters)
+ end
+
+ # Gets a random subreddit.
+ #
+ # @return [RedditKit::Subreddit]
+ def random_subreddit
+ response = get('r/random', nil)
+ headers = response[:response_headers]
+ location = headers[:location]
+
+ subreddit_name = location[/\/r\/(.*)\//, 1]
+ subreddit subreddit_name
+ end
+
+ # Searches for subreddits with a specific name.
+ #
+ # @param name [String] The name to search for.
+ # @return [RedditKit::PaginatedResponse]
+ def search_subreddits_by_name(name)
+ parameters = { :q => name }
+ objects_from_response :get, 'subreddits/search.json', parameters
+ end
+
+ # Gets an array of subreddit names by topic.
+ #
+ # @param topic [String] The desired topic.
+ # @return [Array<String>] An array of subreddit names.
+ # @example RedditKit.subreddits_by_topic 'programming'
+ def subreddits_by_topic(topic)
+ parameters = { :query => topic }
+
+ response = get('api/subreddits_by_topic.json', parameters)
+ body = response[:body]
+
+ body.collect { |subreddit| subreddit[:name] }
+ end
+
+ # Gets an array of recommended subreddits.
+ #
+ # @param subreddits [Array<String>] An array of subreddit names.
+ # @return [Array<String>] An array of recommended subreddit names.
+ # @example RedditKit.recommended_subreddits %w(ruby programming)
+ def recommended_subreddits(subreddits)
+ names = subreddits.join(',')
+ parameters = { :srnames => names }
+
+ response = get('api/subreddit_recommendations.json', parameters)
+ body = response[:body]
+
+ body.collect { |subreddit| subreddit[:sr_name] }
+ end
+
+ end
+ end
+end
109 lib/redditkit/client/users.rb
@@ -0,0 +1,109 @@
+require 'redditkit/user'
+
+module RedditKit
+ class Client
+
+ # Methods for interacting with reddit users.
+ module Users
+
+ # Gets a user object.
+ #
+ # @param username [String] A reddit account's username. Gets the current user if this is nil.
+ # @return [RedditKit::User]
+ # @example current_user = client.user
+ # @example user = client.user 'amberlynns'
+ def user(username = nil)
+ if username
+ object_from_response(:get, "user/#{username}/about.json", nil)
+ else
+ object_from_response(:get, "api/me.json", nil)
+ end
+ end
+
+ # Gets links and comments for the current user.
+ #
+ # @option options [overview, comments, submitted, liked, disliked] :category The category from which to return links and comments. Defaults to overview.
+ # @option options [1..100] :limit The number of links and comments to return.
+ # @option options [String] :before Only return links and comments before this id.
+ # @option options [String] :after Only return links and comments after this id.
+ # @return [RedditKit::PaginatedResponse]
+ def my_content(options = {})
+ category = options[:category] || :overview
+ path = "user/#{@username}/#{category}.json"
+ options.delete :category
+
+ objects_from_response(:get, path, options)
+ end
+
+ # Gets links and comments for a user.
+ #
+ # @option options [overview, comments, submitted, liked, disliked] :category The category from which to return links and comments. Defaults to overview.
+ # @option options [1..100] :limit The number of links and comments to return.
+ # @option options [String] :before Only return links and comments before this id.
+ # @option options [String] :after Only return links and comments after this id.
+ # @return [RedditKit::PaginatedResponse]
+ # @note Public access to the liked and disliked categories is disabled by default, so this will return an empty array for most users.
+ def user_content(user, options = {})
+ username = user
+
+ path = "user/#{username}/%s.json" % (options[:category] if options[:category])
+ options.delete :category
+
+ objects_from_response(:get, path, options)
+ end
+
+ # Gets the current user's friends.
+ #
+ # @return [Array<OpenStruct>]
+ def friends
+ response = request(:get, 'prefs/friends.json', nil, https_connection)
+ body = response[:body]
+ friends = body[0][:data][:children]
+
+ friends.collect { |friend| OpenStruct.new(friend) }
+ end
+
+ # Adds a user to the current user's friend list.
+ #
+ # @param user [String, RedditKit::User] A user's username, or a RedditKit::User.
+ def friend(user)
+ friend_name = extract_string(user, :username)
+ friend_request :container => current_user.full_name, :name => friend_name, :type => :friend
+ end
+
+ # Removes a user from the current user's friend list.
+ #
+ # @param user [String, RedditKit::User] A user's ID, or a RedditKit::User.
+ def unfriend(user)
+ friend_name = extract_string(user, :username)
+ unfriend_request :container => current_user.full_name, :name => friend_name, :type => :friend
+ end
+
+ # Checks whether a specific username is available.
+ #
+ # @param username [String] A username for which to check availability.
+ # @return [Boolean]
+ # @example puts "Username is available" if client.username_available? 'some_username'
+ def username_available?(username)
+ response = get('api/username_available.json', :user => username)
+ available = response[:body]
+
+ available == 'true'
+ end
+
+ # Registers a new reddit account.
+ #
+ # @option options [String] username The username to register.
+ # @option options [String] password The password for the account.
+ # @option options [String] email The optional email address for the account.
+ # @option options [String] captcha_identifier The identifier for the CAPTCHA challenge solved by the user.
+ # @option options [String] captcha The user's response to the CAPTCHA challenge.
+ # @option options [Boolean] remember Whether to keep the user's session cookie beyond the current session.
+ def register(username, password, options = {})
+ parameters = { :user => username, :passwd => password, :passwd2 => password, :captcha => options[:captcha], :iden => options[:captcha_identifier] }
+ post('api/register', parameters)
+ end
+
+ end
+ end
+end
142 lib/redditkit/client/utilities.rb
@@ -0,0 +1,142 @@
+require 'redditkit/paginated_response'
+
+module RedditKit
+ class Client
+
+ # Methods for streamlining requests to reddit's servers.
+ module Utilities
+
+ private
+
+ # Return an id from a string or a RedditKit::Thing object.
+ #
+ # @param object [String, RedditKit::Thing] A string or object.
+ # @return [String]
+ def extract_id(object)
+ extract_string object, :id
+ end
+
+ # Return a full name from a string or a RedditKit::Thing object.
+ #
+ # @param object [String, RedditKit::Thing] A string or object.
+ # @return [String]
+ def extract_full_name(object)
+ extract_string object, :full_name
+ end
+
+ def extract_string(object, attribute_name)
+ case object
+ when ::String
+ object
+ else
+ object.send attribute_name if object.respond_to? attribute_name
+ end
+ end
+
+ # Return the class of an object from a response.
+ #
+ # @param response [Faraday::Response] A response.
+ # @return [Class]
+ def object_class_from_response(response)
+ kind = object_kind_from_response(response)
+ object_class_from_kind(kind)
+ end
+
+ # Return the class of an object of a given kind.
+ #
+ # @param kind [String] The object's kind.
+ # @return [Class]
+ def object_class_from_kind(kind)
+ case kind
+ when "t1"
+ RedditKit::Comment
+ when "t2"
+ RedditKit::User
+ when "t3"
+ RedditKit::Link
+ when "t4"
+ RedditKit::PrivateMessage
+ when "t5"
+ RedditKit::Subreddit
+ when "LabeledMulti"
+ RedditKit::Multireddit
+ when "LabeledMultiDescription"
+ RedditKit::MultiredditDescription
+ when "modaction"
+ RedditKit::ModeratorAction
+ end
+ end
+
+ def object_kind_from_response(response)
+ response[:kind]
+ end
+
+ def object_from_response(request_type, path, parameters = {})
+ response = send(request_type.to_sym, path, parameters)
+ body = response[:body]
+
+ object_class = object_class_from_response(body)
+ object_class.new(body) if object_class
+ end
+
+ def objects_from_response(request_type, path, parameters = {})
+ response = send(request_type.to_sym, path, parameters)
+ body = response[:body]
+
+ if body.is_a?(Hash) and body[:kind] == 'Listing'
+ data = body[:data]
+ results = objects_from_listing(body)
+
+ RedditKit::PaginatedResponse.new(data[:before], data[:after], results)
+ elsif body.is_a?(Array)
+ objects_from_array body
+ elsif body.is_a?(Hash)
+ objects_from_array body[:data]
+ end
+ end
+
+ def objects_from_listing(listing)
+ children = listing[:data][:children]
+ objects_from_array children
+ end
+
+ def objects_from_array(array)
+ array.map do |thing|
+ object_class = object_class_from_response(thing)
+ object_class.new(thing) if object_class
+ end
+ end
+
+ def comments_from_response(request_type, path, parameters = {})
+ response = send(request_type.to_sym, path, parameters)
+ body = response[:body]
+ comments_listing = body.last
+
+ objects_from_listing(comments_listing)
+ end
+
+ def path_for_multireddit(username, multireddit_name)
+ "/user/#{username}/m/#{multireddit_name}"
+ end
+
+ def friend_request(options)
+ if options[:subreddit]
+ options[:r] = options[:subreddit]
+ options.delete :subreddit
+ end
+
+ post('api/friend', options)
+ end
+
+ def unfriend_request(options)
+ if options[:subreddit]
+ options[:r] = options[:subreddit]
+ options.delete :subreddit
+ end
+
+ post('api/unfriend', options)
+ end
+
+ end
+ end
+end
41 lib/redditkit/client/voting.rb
@@ -0,0 +1,41 @@
+module RedditKit
+ class Client
+
+ # Methods for voting on links and comments.
+ module Voting
+
+ # Upvotes a link or comment.
+ #
+ # @param link_or_comment [String, RedditKit::Comment, RedditKit::Link] The link or comment to upvote.
+ def upvote(link_or_comment)
+ vote link_or_comment, 1
+ end
+
+ # Downvotes a link or comment.
+ #
+ # @param link_or_comment [String, RedditKit::Comment, RedditKit::Link] The link or comment to downvote.
+ def downvote(link_or_comment)
+ vote link_or_comment, -1
+ end
+
+ # Withdraws a vote on a link or comment.
+ #
+ # @param link_or_comment [String, RedditKit::Comment, RedditKit::Link] The link or comment from which to withdraw the vote.
+ def withdraw_vote(link_or_comment)
+ vote link_or_comment, 0
+ end
+
+ # Votes on a link or comment.
+ #
+ # @param link_or_comment [String, RedditKit::Comment, RedditKit::Link] The link or comment from which to withdraw the vote.
+ # @param direction [-1, 0, 1] Downvote, no vote, and upvote respectively.
+ def vote(link_or_comment, direction)
+ full_name = extract_full_name(link_or_comment)
+ parameters = { :id => full_name, :dir => direction, :api_type => 'json' }
+
+ post('api/vote', parameters)
+ end
+
+ end
+ end
+end
75 lib/redditkit/client/wiki.rb
@@ -0,0 +1,75 @@
+module RedditKit
+ class Client
+
+ # Methods for interacting with a subreddit's wiki.
+ module Wiki
+
+ # Adds a user as an approved editor of a wiki page.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's display name, or a RedditKit::Subreddit.
+ # @option options [String, RedditKit::User] user A user's full name, or a RedditKit::User.
+ # @option options [String] page The name of an existing wiki page.
+ def add_wiki_editor(options)
+ username = extract_string(options[:user], :username)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ parameters = { :page => options[:page], :username => username }
+
+ post("r/#{subreddit_name}/api/wiki/alloweditor/add", parameters)
+ end
+
+ # Removes a user from being an approved editor of a wiki page.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's display name, or a RedditKit::Subreddit.
+ # @option options [String, RedditKit::User] user A user's full name, or a RedditKit::User.
+ # @option options [String] page The name of an existing wiki page.
+ def remove_wiki_editor(options)
+ username = extract_string(options[:user], :username)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ parameters = { :page => options[:page], :username => username }
+
+ post("r/#{subreddit_name}/api/wiki/alloweditor/del", parameters)
+ end
+
+ # Edits a wiki page.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's display name, or a RedditKit::Subreddit.
+ # @option options [String] page The name of an existing wiki page.
+ # @option options [String] content The contents of the edit.
+ # @option options [String] reason The reason for the edit.
+ # @option options [String] previous_revision The starting revision for this edit.
+ def edit_wiki_page(options)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ parameters = { :page => options[:page], :previous => options[:previous_revision], :content => options[:content], :reason => options[:reason] }
+
+ post("r/#{subreddit_name}/api/wiki/edit", parameters)
+ end
+
+ # Hides a wiki page revision. If the revision is already hidden, this will unhide it.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's display name, or a RedditKit::Subreddit.
+ # @option options [String] page The name of an existing wiki page.
+ # @option options [String] revision The revision to hide.
+ # @return [Boolean] True if the revision is now hidden, false if it is not hidden.
+ def hide_wiki_revision(options)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ options.delete :subreddit
+
+ response = post("r/#{subreddit_name}/api/wiki/hide", options)
+ response[:body][:status]
+ end
+
+ # Reverts to a specific wiki page revision.
+ #
+ # @option options [String, RedditKit::Subreddit] subreddit A subreddit's display name, or a RedditKit::Subreddit.
+ # @option options [String] page The name of an existing wiki page.
+ # @option options [String] revision The revision to revert to.
+ def revert_to_revision(options)
+ subreddit_name = extract_string(options[:subreddit], :display_name)
+ options.delete :subreddit
+
+ post("r/#{subreddit_name}/api/wiki/revert", options)
+ end
+
+ end
+ end
+end
54 lib/redditkit/comment.rb
@@ -0,0 +1,54 @@
+require 'redditkit/thing'
+require 'redditkit/creatable'
+require 'redditkit/votable'
+
+module RedditKit
+
+ # A class representing a comment.
+ class Comment < RedditKit::Thing
+ include RedditKit::Creatable
+ include RedditKit::Votable
+
+ attr_reader :approved_by
+ attr_reader :author
+ attr_reader :author_flair_css_class
+ attr_reader :author_flair_text
+ attr_reader :author_id
+ attr_reader :banned_by
+ attr_reader :body
+ attr_reader :body_html
+ attr_reader :distinguished
+ attr_reader :edited
+ attr_reader :gilded
+ attr_reader :link_id
+ attr_reader :num_reports
+ attr_reader :score_hidden
+ attr_reader :subreddit
+ attr_reader :subreddit_id
+
+ alias text body
+ alias posted_at created_at
+
+ # Whether a comment has been deleted by its submitter or a moderator.
+ def deleted?
+ author == '[deleted]' and body == '[deleted]'
+ end
+
+ # The replies to a comment.
+ def replies
+ replies_listing = @attributes[:replies]
+ return Array.new if replies_listing.empty?
+
+ replies_array = replies_listing[:data][:children]
+
+ @comment_objects ||= replies_array.map do |comment|
+ RedditKit::Comment.new(comment)
+ end
+ end
+
+ # Whether the comment has any replies.
+ def replies?
+ !replies.empty?
+ end
+ end
+end
17 lib/redditkit/creatable.rb
@@ -0,0 +1,17 @@
+require 'time'
+
+module RedditKit
+
+ # Methods which return the time of creation for objects.
+ module Creatable
+
+ # The time when the object was created on reddit.
+ #
+ # @return [Time]
+ def created_at
+ created = @attributes[:created_utc]
+ @created_at ||= Time.at(created) if created
+ end
+
+ end
+end
111 lib/redditkit/error.rb
@@ -0,0 +1,111 @@
+require 'json'
+
+module RedditKit
+ class Error < StandardError
+ class << self
+
+ def from_status_code_and_body(status_code, body)
+ error_value = extract_error_value body
+ return if status_code == 200 and error_value.nil?
+
+ case status_code
+ when 200