redd 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.rspec +2 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +10 -0
- data/Rakefile +10 -0
- data/github/redd.png +0 -0
- data/lib/redd.rb +14 -0
- data/lib/redd/RedditKit.LICENSE.md +13 -0
- data/lib/redd/base.rb +35 -0
- data/lib/redd/client/authenticated.rb +88 -0
- data/lib/redd/client/authenticated/account.rb +9 -0
- data/lib/redd/client/authenticated/apps.rb +12 -0
- data/lib/redd/client/authenticated/flair.rb +9 -0
- data/lib/redd/client/authenticated/gold.rb +12 -0
- data/lib/redd/client/authenticated/links_comments.rb +9 -0
- data/lib/redd/client/authenticated/live.rb +12 -0
- data/lib/redd/client/authenticated/moderation.rb +9 -0
- data/lib/redd/client/authenticated/multis.rb +9 -0
- data/lib/redd/client/authenticated/private_messages.rb +40 -0
- data/lib/redd/client/authenticated/subreddits.rb +9 -0
- data/lib/redd/client/authenticated/users.rb +9 -0
- data/lib/redd/client/authenticated/wiki.rb +9 -0
- data/lib/redd/client/oauth2.rb +9 -0
- data/lib/redd/client/unauthenticated.rb +104 -0
- data/lib/redd/client/unauthenticated/account.rb +21 -0
- data/lib/redd/client/unauthenticated/links_comments.rb +15 -0
- data/lib/redd/client/unauthenticated/listing.rb +44 -0
- data/lib/redd/client/unauthenticated/subreddits.rb +13 -0
- data/lib/redd/client/unauthenticated/utilities.rb +61 -0
- data/lib/redd/client/unauthenticated/wiki.rb +9 -0
- data/lib/redd/error.rb +93 -0
- data/lib/redd/object/comment.rb +41 -0
- data/lib/redd/object/listing.rb +24 -0
- data/lib/redd/object/submission.rb +94 -0
- data/lib/redd/object/subreddit.rb +10 -0
- data/lib/redd/rate_limit.rb +48 -0
- data/lib/redd/response/parse_json.rb +31 -0
- data/lib/redd/response/raise_error.rb +20 -0
- data/lib/redd/thing.rb +27 -0
- data/lib/redd/version.rb +5 -0
- data/redd.gemspec +32 -0
- data/spec/redd_spec.rb +7 -0
- data/spec/spec_helper.rb +33 -0
- metadata +234 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "redd/version"
|
3
|
+
require "redd/rate_limit"
|
4
|
+
require "redd/response/parse_json"
|
5
|
+
require "redd/response/raise_error"
|
6
|
+
require "redd/client/unauthenticated/account"
|
7
|
+
require "redd/client/unauthenticated/links_comments"
|
8
|
+
require "redd/client/unauthenticated/listing"
|
9
|
+
require "redd/client/unauthenticated/subreddits"
|
10
|
+
require "redd/client/unauthenticated/utilities"
|
11
|
+
require "redd/client/unauthenticated/wiki"
|
12
|
+
|
13
|
+
module Redd
|
14
|
+
module Client
|
15
|
+
# The Client used to connect without needing login credentials.
|
16
|
+
class Unauthenticated
|
17
|
+
include Redd::Client::Unauthenticated::Account
|
18
|
+
include Redd::Client::Unauthenticated::LinksComments
|
19
|
+
include Redd::Client::Unauthenticated::Listing
|
20
|
+
include Redd::Client::Unauthenticated::Subreddits
|
21
|
+
include Redd::Client::Unauthenticated::Utilities
|
22
|
+
include Redd::Client::Unauthenticated::Wiki
|
23
|
+
|
24
|
+
# @!attribute [r] api_endpoint
|
25
|
+
# @return [String] The site to connect to.
|
26
|
+
attr_accessor :api_endpoint
|
27
|
+
|
28
|
+
# @!attribute [r] user_agent
|
29
|
+
# @return [String] The user-agent used to communicate with reddit.
|
30
|
+
attr_accessor :user_agent
|
31
|
+
|
32
|
+
# @!attribute [r] rate_limit
|
33
|
+
# @return [#after_limit] The handler that takes care of rate limiting.
|
34
|
+
attr_accessor :rate_limit
|
35
|
+
|
36
|
+
# Set up an unauthenticated connection to reddit.
|
37
|
+
#
|
38
|
+
# @param [Hash] options A hash of options to connect using.
|
39
|
+
# @option options [#after_limit] :rate_limit The handler that takes care
|
40
|
+
# of rate limiting.
|
41
|
+
# @option options [String] :user_agent The User-Agent string to use in the
|
42
|
+
# header of every request.
|
43
|
+
# @option options [String] :api_endpoint The main domain to connect
|
44
|
+
# to, in this case, the URL for reddit.
|
45
|
+
def initialize(options = {})
|
46
|
+
@rate_limit = options[:rate_limit] || Redd::RateLimit.new
|
47
|
+
@user_agent = options[:user_agent] || "Redd/Ruby, v#{Redd::VERSION}"
|
48
|
+
@api_endpoint = options[:api_endpoint] || "http://www.reddit.com/"
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Gets the Faraday connection or creates one if it doesn't exist yet.
|
54
|
+
#
|
55
|
+
# @return [Faraday] A new Faraday connection.
|
56
|
+
def connection
|
57
|
+
@connection ||= Faraday.new(url: api_endpoint) do |faraday|
|
58
|
+
faraday.use Faraday::Request::UrlEncoded
|
59
|
+
faraday.use Redd::Response::ParseJson
|
60
|
+
faraday.use Redd::Response::RaiseError
|
61
|
+
faraday.adapter Faraday.default_adapter
|
62
|
+
|
63
|
+
faraday.headers["User-Agent"] = user_agent
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Send a request to the given path.
|
68
|
+
#
|
69
|
+
# @param [#to_sym] method The HTTP verb to use.
|
70
|
+
# @param [String] path The path under the api endpoint to request from.
|
71
|
+
# @param [Hash] params The additional parameters to send (defualt: {}).
|
72
|
+
# @return [Faraday::Response] A Faraday Response.
|
73
|
+
def request(method, path, params = {})
|
74
|
+
rate_limit.after_limit do
|
75
|
+
connection.send(method.to_sym, path, params)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Performs a GET request via {#request}.
|
80
|
+
# @see #request
|
81
|
+
def get(*args)
|
82
|
+
request(:get, *args)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Performs a POST request via {#request}.
|
86
|
+
# @see #request
|
87
|
+
def post(*args)
|
88
|
+
request(:post, *args)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Performs a PUT request via {#request}.
|
92
|
+
# @see #request
|
93
|
+
def put(*args)
|
94
|
+
request(:put, *args)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Performs a DELETE request via {#request}.
|
98
|
+
# @see #request
|
99
|
+
def delete(*args)
|
100
|
+
request(:delete, *args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Redd
|
2
|
+
module Client
|
3
|
+
class Unauthenticated
|
4
|
+
module Account
|
5
|
+
def login(username, password, remember = false)
|
6
|
+
meth = :post
|
7
|
+
path = "/api/login"
|
8
|
+
params = {
|
9
|
+
api_type: "json", user: username,
|
10
|
+
passwd: password, rem: remember
|
11
|
+
}
|
12
|
+
response = send(meth, path, params)
|
13
|
+
data = response.body[:json][:data]
|
14
|
+
|
15
|
+
require "redd/client/authenticated"
|
16
|
+
Redd::Client::Authenticated.new(data[:cookie], data[:modhash])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Redd
|
2
|
+
module Client
|
3
|
+
class Unauthenticated
|
4
|
+
module LinksComments
|
5
|
+
# @note Reddit does accept a subreddit, but with fullnames and urls, I
|
6
|
+
# assumed that was unnecessary.
|
7
|
+
def get_info(params = {})
|
8
|
+
meth = :get
|
9
|
+
path = "/api/info.json"
|
10
|
+
object_from_response(meth, path, params)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Redd
|
2
|
+
module Client
|
3
|
+
class Unauthenticated
|
4
|
+
module Listing
|
5
|
+
def by_id(*fullnames)
|
6
|
+
names = fullnames.join(",")
|
7
|
+
|
8
|
+
meth = :get
|
9
|
+
path = "/by_id/#{names}.json"
|
10
|
+
object_from_response(meth, path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_hot(*args)
|
14
|
+
get_listing(:hot, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_new(*args)
|
18
|
+
get_listing(:new, *args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_random(*args)
|
22
|
+
get_listing(:random, *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_top(*args)
|
26
|
+
get_listing(:top, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_controversial(*args)
|
30
|
+
get_listing(:controversial, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_listing(type, subreddit = nil, params = {})
|
36
|
+
meth = :get
|
37
|
+
path = "/#{type}.json"
|
38
|
+
path = path.prepend("/r/#{subreddit}") if subreddit
|
39
|
+
object_from_response(meth, path, params)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "redd/thing"
|
2
|
+
require "redd/object/listing"
|
3
|
+
require "redd/object/comment"
|
4
|
+
require "redd/object/submission"
|
5
|
+
require "redd/object/subreddit"
|
6
|
+
|
7
|
+
module Redd
|
8
|
+
module Client
|
9
|
+
class Unauthenticated
|
10
|
+
module Utilities
|
11
|
+
private
|
12
|
+
|
13
|
+
def extract_fullname(object)
|
14
|
+
object.is_a?(String) ? object : extract_attribute(object, :fullname)
|
15
|
+
end
|
16
|
+
|
17
|
+
def extract_attribute(object, attribute)
|
18
|
+
object.send(attribute) if object.respond_to?(attribute)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @todo "more"
|
22
|
+
def object_from_kind(kind) # rubocop:disable Style/MethodLength
|
23
|
+
case kind
|
24
|
+
when "Listing"
|
25
|
+
Redd::Object::Listing
|
26
|
+
when "t1"
|
27
|
+
Redd::Object::Comment
|
28
|
+
when "t3"
|
29
|
+
Redd::Object::Submission
|
30
|
+
when "t5"
|
31
|
+
Redd::Object::Subreddit
|
32
|
+
else
|
33
|
+
Redd::Thing
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def objects_from_listing(thing)
|
38
|
+
thing[:data][:children].map do |child|
|
39
|
+
get_object_from_body(child)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_object_from_body(body)
|
44
|
+
object = object_from_kind(body[:kind])
|
45
|
+
|
46
|
+
if object == Redd::Object::Listing
|
47
|
+
things = objects_from_listing(body)
|
48
|
+
object.new(things)
|
49
|
+
else
|
50
|
+
object.new(self, body)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def object_from_response(*args)
|
55
|
+
body = request(*args).body
|
56
|
+
get_object_from_body(body)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/redd/error.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Redd
|
2
|
+
class Error < StandardError
|
3
|
+
class << self
|
4
|
+
# @note I ripped off RedditKit.rb :|
|
5
|
+
def from_response(response) # rubocop:disable Style/CyclomaticComplexity Style/MethodLength
|
6
|
+
status = response[:status]
|
7
|
+
body = parse_error(response[:body])
|
8
|
+
case status
|
9
|
+
when 200
|
10
|
+
case body
|
11
|
+
when /WRONG_PASSWORD/i
|
12
|
+
InvalidCredentials
|
13
|
+
when /BAD_CAPTCHA/i
|
14
|
+
InvalidCaptcha
|
15
|
+
when /RATELIMIT/i
|
16
|
+
RateLimited
|
17
|
+
when /BAD_CSS_NAME/i
|
18
|
+
InvalidClassName
|
19
|
+
when /TOO_OLD/i
|
20
|
+
Archived
|
21
|
+
when /TOO_MUCH_FLAIR_CSS/i
|
22
|
+
TooManyClassNames
|
23
|
+
when /USER_REQUIRED/i
|
24
|
+
AuthenticationRequired
|
25
|
+
end
|
26
|
+
when 400
|
27
|
+
BadRequest
|
28
|
+
when 403
|
29
|
+
case body
|
30
|
+
when /USER_REQUIRED/i
|
31
|
+
AuthenticationRequired
|
32
|
+
else
|
33
|
+
PermissionDenied
|
34
|
+
end
|
35
|
+
when 404
|
36
|
+
NotFound
|
37
|
+
when 409
|
38
|
+
Conflict
|
39
|
+
when 500
|
40
|
+
InternalServerError
|
41
|
+
when 502
|
42
|
+
BadGateway
|
43
|
+
when 503
|
44
|
+
ServiceUnavailable
|
45
|
+
when 504
|
46
|
+
TimedOut
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_error(body)
|
51
|
+
return nil unless body.is_a?(Hash)
|
52
|
+
|
53
|
+
if body.key?(:json) && body[:json].key?(:errors)
|
54
|
+
body[:json][:errors].first
|
55
|
+
elsif body.key?(:jquery)
|
56
|
+
body[:jquery].to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class AuthenticationRequired < Error; end
|
62
|
+
|
63
|
+
class InvalidCaptcha < Error; end
|
64
|
+
|
65
|
+
class BadGateway < Error; end
|
66
|
+
|
67
|
+
class InvalidMultiredditName < Error; end
|
68
|
+
|
69
|
+
class Conflict < Error; end
|
70
|
+
|
71
|
+
class InternalServerError < Error; end
|
72
|
+
|
73
|
+
class InvalidClassName < Error; end
|
74
|
+
|
75
|
+
class InvalidCredentials < Error; end
|
76
|
+
|
77
|
+
class NotFound < Error; end
|
78
|
+
|
79
|
+
class PermissionDenied < Error; end
|
80
|
+
|
81
|
+
class RateLimited < Error; end
|
82
|
+
|
83
|
+
class RequestError < Error; end
|
84
|
+
|
85
|
+
class ServiceUnavailable < Error; end
|
86
|
+
|
87
|
+
class TooManyClassNames < Error; end
|
88
|
+
|
89
|
+
class Archived < Error; end
|
90
|
+
|
91
|
+
class TimedOut < Error; end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "redd/thing"
|
2
|
+
|
3
|
+
module Redd
|
4
|
+
module Object
|
5
|
+
# A comment made on links.
|
6
|
+
class Comment < Redd::Thing
|
7
|
+
# @!attribute [r] subreddit
|
8
|
+
# @return [String] The name of the subreddit this comment belongs to.
|
9
|
+
# @todo Convert to a Subreddit object?
|
10
|
+
attr_reader :subreddit
|
11
|
+
|
12
|
+
# @!attribute [r] parent_id
|
13
|
+
# @return [String] The id of the parent comment.
|
14
|
+
# @todo parent - get the parent comment directly.
|
15
|
+
attr_reader :parent_id
|
16
|
+
|
17
|
+
# @!attribute [r] body
|
18
|
+
# @return [String] The text of the comment in markdown.
|
19
|
+
attr_reader :body
|
20
|
+
|
21
|
+
# @!attribute [r] body_html
|
22
|
+
# @return [String] The text of the comment in html.
|
23
|
+
# @note Be warned: this isn't actual html, but escaped html. So all the
|
24
|
+
# <'s and >'s are converted to <'s and >'s.
|
25
|
+
attr_reader :body_html
|
26
|
+
|
27
|
+
# @!attribute [r] author_flair_text
|
28
|
+
# @return [String] The user's flair.
|
29
|
+
attr_reader :author_flair_text
|
30
|
+
|
31
|
+
# @!attribute [r] author_flair_css_class
|
32
|
+
# @return [String] The CSS class of the user's flair.
|
33
|
+
attr_reader :author_flair_css_class
|
34
|
+
|
35
|
+
# @return [Boolean] Whether the comment is a root rather than a reply.
|
36
|
+
def root?
|
37
|
+
!parent_id || parent_id == fullname
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Redd
|
4
|
+
module Object
|
5
|
+
# A listing of various reddit things.
|
6
|
+
# @see http://www.reddit.com/dev/api#listings
|
7
|
+
# @see http://stackoverflow.com/a/2080118
|
8
|
+
class Listing
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@things, :[], :length, :size, :each, :map, :empty?
|
12
|
+
|
13
|
+
# @!attribute [r] things
|
14
|
+
# @return [Array] A list of things in the listing.
|
15
|
+
attr_reader :things
|
16
|
+
|
17
|
+
# Create a new listing with the given things.
|
18
|
+
# @param [Array] things A list of things to generate a Listing out of.
|
19
|
+
def initialize(things)
|
20
|
+
@things = things
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|