redd 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +42 -8
- data/lib/redd/api_client.rb +1 -1
- data/lib/redd/error.rb +6 -0
- data/lib/redd/middleware.rb +123 -0
- data/lib/redd/models/access.rb +19 -2
- data/lib/redd/utilities/error_handler.rb +1 -0
- data/lib/redd/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9143e5925475c5e0143ac3aacb6c10666986c18f
|
4
|
+
data.tar.gz: 6c7be0b33836c21aa36765937795cabe6c7aa8e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 541cffb6f3ad7b15b2d1560b30ae400d7d9db898290c7a252ee9ad2c7bae3d1c8aa9591d202e9f32190f28e0fe37539b33443a0d8f5adccc0c3af8aa84f5def4
|
7
|
+
data.tar.gz: 707d66c874d7c74fbbe1710d52247f5dbc4e5c5e88fc4ab66b7c739075a30b4cfeb28d59965d762779b61d3bd9ca323432b6e543316f24237f916b694612d12c
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
<div align="center">
|
2
2
|
<p>
|
3
3
|
<!-- Redd -->
|
4
|
-
<img src="logo.png" width="500"><br>
|
5
|
-
|
4
|
+
<img src="https://raw.githubusercontent.com/avinashbot/redd/master/logo.png" width="500"><br>
|
6
5
|
<!-- Badges -->
|
7
6
|
<a href="https://rubygems.org/gems/redd">
|
8
7
|
<img src="http://img.shields.io/gem/v/redd.svg?style=flat-square" alt="Gem Version">
|
@@ -14,7 +13,6 @@
|
|
14
13
|
<img src="http://img.shields.io/gem/dt/redd.svg?style=flat-square" alt="Gem Downloads">
|
15
14
|
</a>
|
16
15
|
</p>
|
17
|
-
|
18
16
|
<!-- Intro Text -->
|
19
17
|
<p>
|
20
18
|
<strong>Redd</strong> is a <strong>batteries-included</strong>
|
@@ -46,31 +44,67 @@ session = Redd.it(
|
|
46
44
|
|
47
45
|
session.subreddit('all').comment_stream do |comment|
|
48
46
|
if comment.body.include?('roll a dice')
|
49
|
-
comment.reply("
|
47
|
+
comment.reply("It's a #{rand(1..6)}!")
|
50
48
|
elsif comment.body.include?('flip a coin')
|
51
|
-
comment.reply("
|
49
|
+
comment.reply("It's a #{%w(heads tails).sample}!")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'sinatra'
|
56
|
+
require 'redd/middleware'
|
57
|
+
|
58
|
+
use Rack::Session::Cookie
|
59
|
+
use Redd::Middleware,
|
60
|
+
user_agent: 'Redd:Username App:v1.0.0 (by /u/Mustermind)',
|
61
|
+
client_id: 'PQgS0UaX9l70oQ',
|
62
|
+
secret: 'PsF_kVZrW8nSVCG5kNsIgl-AaXE',
|
63
|
+
redirect_uri: 'http://localhost:4567/auth/reddit/callback',
|
64
|
+
scope: %w(identity),
|
65
|
+
via: '/auth/reddit'
|
66
|
+
|
67
|
+
get '/' do
|
68
|
+
reddit = request.env['redd.session']
|
69
|
+
|
70
|
+
if reddit
|
71
|
+
"Hello /u/#{reddit.me.name}! <a href='/logout'>Logout</a>"
|
72
|
+
else
|
73
|
+
"<a href='/auth/reddit'>Sign in with reddit</a>"
|
52
74
|
end
|
53
75
|
end
|
76
|
+
|
77
|
+
get '/auth/reddit/callback' do
|
78
|
+
redirect to('/') unless request.env['redd.error']
|
79
|
+
"Error: #{request.env['redd.error'].message} (<a href='/'>Back</a>)"
|
80
|
+
end
|
81
|
+
|
82
|
+
get '/logout' do
|
83
|
+
request.env['redd.session'] = nil
|
84
|
+
redirect to('/')
|
85
|
+
end
|
54
86
|
```
|
55
87
|
|
56
88
|
### FAQ
|
57
89
|
|
58
|
-
####
|
90
|
+
#### Are those examples fully functional?
|
59
91
|
**Yes**, that's all there is to it! You don't need to handle rate-limiting, refresh access tokens or protect against issues on reddit's end (like 5xx errors).
|
60
92
|
|
61
93
|
#### Where can I find the documentation?
|
62
94
|
|
63
95
|
[**Gem**](http://www.rubydoc.info/gems/redd/Redd/Models/Session) / [**GitHub**](http://www.rubydoc.info/github/avinashbot/redd/master/Redd/Models/Session)
|
64
96
|
|
97
|
+
#### Where can I ask for help if I'm having issues?
|
98
|
+
Check out the [**official subreddit**](https://www.reddit.com/r/Redd) or raise a [**GitHub issue**](https://github.com/avinashbot/redd/issues/new).
|
99
|
+
|
65
100
|
#### How do I request a feature / contribute?
|
66
101
|
|
67
|
-
- The quickest way to get a feature into Redd is to raise a GitHub issue.
|
102
|
+
- The quickest way to get a feature into Redd is to raise a [**GitHub issue**](https://github.com/avinashbot/redd/issues/new).
|
68
103
|
- Pull requests are also appreciated!
|
69
104
|
- Don't hesitate! There are no stupid questions!
|
70
105
|
|
71
106
|
#### How can I contact you?
|
72
107
|
[Reddit](https://www.reddit.com/message/compose/?to=Mustermind) /
|
73
|
-
[GitHub](https://github.com/avinashbot/redd/issues/new) /
|
74
108
|
[Email](mailto:avinash@dwarapu.me)
|
75
109
|
|
76
110
|
---
|
data/lib/redd/api_client.rb
CHANGED
@@ -87,7 +87,7 @@ module Redd
|
|
87
87
|
# If access is nil, panic
|
88
88
|
raise 'client access is nil, try calling #authenticate' if @access.nil?
|
89
89
|
# Refresh access if auto_refresh is enabled
|
90
|
-
refresh if @access.
|
90
|
+
refresh if @auto_refresh && @access.permanent? && @access.expired?
|
91
91
|
end
|
92
92
|
|
93
93
|
def handle_retryable_errors
|
data/lib/redd/error.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Redd
|
4
|
+
# An error raised by {Redd::Middleware} when there was an error returned by reddit.
|
5
|
+
class TokenRetrievalError < StandardError; end
|
6
|
+
|
4
7
|
# An error with the API.
|
5
8
|
class APIError < StandardError
|
6
9
|
attr_reader :response, :name
|
@@ -42,6 +45,9 @@ module Redd
|
|
42
45
|
# Returned when reddit raises a 404 error.
|
43
46
|
class NotFound < ResponseError; end
|
44
47
|
|
48
|
+
# Too many requests and not enough rate limiting.
|
49
|
+
class TooManyRequests < ResponseError; end
|
50
|
+
|
45
51
|
# An unknown error on reddit's end. Usually fixed with a retry.
|
46
52
|
class ServerError < ResponseError; end
|
47
53
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
require_relative '../redd'
|
7
|
+
|
8
|
+
module Redd
|
9
|
+
# Rack middleware.
|
10
|
+
class Middleware
|
11
|
+
# @param opts [Hash] the options to create the object with
|
12
|
+
# @option opts [String] :user_agent your app's *unique* and *descriptive* user agent
|
13
|
+
# @option opts [String] :client_id the client id of your app
|
14
|
+
# @option opts [String] :redirect_uri the provided redirect URI
|
15
|
+
# @option opts [String] :secret ('') the app secret (for the web type)
|
16
|
+
# @option opts [Array<String>] :scope (['identity']) a list of scopes to request
|
17
|
+
# @option opts ['temporary', 'permanent'] :duration ('permanent') the duration to request the
|
18
|
+
# code for.
|
19
|
+
# @option opts [Boolean] :auto_refresh (true) allow refreshing a permanent access automatically
|
20
|
+
# (only if duration is 'permanent')
|
21
|
+
# @option opts [String] :via ('/auth/reddit') the relative path in the application that
|
22
|
+
# redirects a user to reddit
|
23
|
+
def initialize(app, opts = {})
|
24
|
+
@app = app
|
25
|
+
strategy_opts = opts.select { |k| %i(user_agent client_id secret redirect_uri).include?(k) }
|
26
|
+
@strategy = Redd::AuthStrategies::Web.new(strategy_opts)
|
27
|
+
|
28
|
+
@user_agent = opts.fetch(:user_agent, "Redd:Web Application:v#{Redd::VERSION} (by unknown)")
|
29
|
+
@client_id = opts.fetch(:client_id)
|
30
|
+
@redirect_uri = opts.fetch(:redirect_uri)
|
31
|
+
@scope = opts.fetch(:scope, ['identity'])
|
32
|
+
@duration = opts.fetch(:duration, 'permanent')
|
33
|
+
@auto_refresh = opts.fetch(:auto_refresh, true) && @duration == 'permanent'
|
34
|
+
@via = opts.fetch(:via, '/auth/reddit')
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
# This is done for thread safety so that each thread has its own copy
|
39
|
+
# of the middleware logic.
|
40
|
+
dup._call(env)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def _call(env)
|
46
|
+
@request = Rack::Request.new(env)
|
47
|
+
return redirect_to_reddit! if @request.path == @via
|
48
|
+
|
49
|
+
before_call
|
50
|
+
response = @app.call(env)
|
51
|
+
after_call
|
52
|
+
response
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Creates a unique state and redirects the user to reddit for authentication.
|
58
|
+
def redirect_to_reddit!
|
59
|
+
state = SecureRandom.urlsafe_base64
|
60
|
+
url = Redd.url(
|
61
|
+
client_id: @client_id,
|
62
|
+
redirect_uri: @redirect_uri,
|
63
|
+
scope: @scope,
|
64
|
+
duration: @duration,
|
65
|
+
state: state
|
66
|
+
)
|
67
|
+
@request.session[:redd_state] = state
|
68
|
+
[302, { 'Location' => url }, []]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Do any setup before calling the rest of the application.
|
72
|
+
def before_call
|
73
|
+
# Convert the code to an access token if returning from authentication.
|
74
|
+
create_session! if @request.base_url + @request.path == @redirect_uri
|
75
|
+
# Clear the state for any other request.
|
76
|
+
@request.session.delete(:redd_state)
|
77
|
+
# Load a Session model from the access token in the user's cookies.
|
78
|
+
@request.env['redd.session'] = (@request.session[:redd_session] ? parse_session : nil)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Do any cleanup or changes after calling the application.
|
82
|
+
def after_call
|
83
|
+
env_session = @request.env['redd.session']
|
84
|
+
if env_session && env_session.client.access
|
85
|
+
# Make sure to flush any changes made to the Session client to the browser.
|
86
|
+
@request.session[:redd_session] = env_session.client.access.to_h
|
87
|
+
else
|
88
|
+
# Clear the session if the app explicitly set 'redd.session' to nil.
|
89
|
+
@request.session.delete(:redd_session)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Assigns a single string representing a reddit authentication errors.
|
94
|
+
def handle_token_error
|
95
|
+
message = nil
|
96
|
+
message = 'invalid_state' if @request.GET['state'] != @request.session[:redd_state]
|
97
|
+
message = @request.GET['error'] if @request.GET['error']
|
98
|
+
raise Redd::TokenRetrievalError, message if message
|
99
|
+
end
|
100
|
+
|
101
|
+
# Store the access token and other details in the user's browser, assigning any errors to
|
102
|
+
# the 'redd.error' env variable.
|
103
|
+
def create_session!
|
104
|
+
# Skip authorizing if there was an error from the authorization.
|
105
|
+
handle_token_error
|
106
|
+
# Try to get a code (the rescue block will also prevent crazy crashes)
|
107
|
+
access = @strategy.authenticate(@request.GET['code'])
|
108
|
+
@request.session[:redd_session] = access.to_h
|
109
|
+
rescue Redd::TokenRetrievalError, Redd::ResponseError => error
|
110
|
+
@request.env['redd.error'] = error
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return a {Redd::Models::Session} based on the hash saved into the browser's session.
|
114
|
+
def parse_session
|
115
|
+
client = Redd::APIClient.new(
|
116
|
+
@strategy,
|
117
|
+
user_agent: @user_agent, limit_time: 0, auto_refresh: @auto_refresh
|
118
|
+
)
|
119
|
+
client.access = Redd::Models::Access.new(@strategy, @request.session[:redd_session])
|
120
|
+
Redd::Models::Session.new(client)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/redd/models/access.rb
CHANGED
@@ -5,10 +5,27 @@ require_relative 'basic_model'
|
|
5
5
|
module Redd
|
6
6
|
module Models
|
7
7
|
# Models access_token and related keys.
|
8
|
+
# @note This model also supports an additional key, called `:created_at` which is a UNIX time
|
9
|
+
# representing the time the access was created. The default value is the time the object was
|
10
|
+
# initialized.
|
8
11
|
class Access < BasicModel
|
12
|
+
# Create a non-lazily initialized Access.
|
13
|
+
# @param client [Object] (deprecated) the client to create the object with
|
14
|
+
# @param attributes [Hash] the Access's attributes
|
15
|
+
# @example
|
16
|
+
# access = Redd::Models::Access.new(access_token: ...)
|
17
|
+
def initialize(client = nil, attributes = {})
|
18
|
+
if client.is_a?(Hash)
|
19
|
+
super(nil, client)
|
20
|
+
else
|
21
|
+
super(client, attributes)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
9
25
|
def expired?(grace_period = 60)
|
26
|
+
# We're not sure, so we just assume it hasn't expired.
|
10
27
|
return false unless @attributes[:expires_in]
|
11
|
-
Time.now > @created_at + (@attributes[:expires_in] - grace_period)
|
28
|
+
Time.now.to_i > @attributes[:created_at] + (@attributes[:expires_in] - grace_period)
|
12
29
|
end
|
13
30
|
|
14
31
|
def permanent?
|
@@ -18,7 +35,7 @@ module Redd
|
|
18
35
|
private
|
19
36
|
|
20
37
|
def after_initialize
|
21
|
-
@created_at
|
38
|
+
@attributes[:created_at] ||= Time.now.to_i
|
22
39
|
end
|
23
40
|
end
|
24
41
|
end
|
data/lib/redd/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Avinash Dwarapu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- lib/redd/auth_strategies/web.rb
|
163
163
|
- lib/redd/client.rb
|
164
164
|
- lib/redd/error.rb
|
165
|
+
- lib/redd/middleware.rb
|
165
166
|
- lib/redd/models/access.rb
|
166
167
|
- lib/redd/models/basic_model.rb
|
167
168
|
- lib/redd/models/comment.rb
|