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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c41f2214b01198dd4f0a44f243bc73364af33b6c
4
- data.tar.gz: a33587b7a36a41c11b3a4faa51814af24d86565f
3
+ metadata.gz: 9143e5925475c5e0143ac3aacb6c10666986c18f
4
+ data.tar.gz: 6c7be0b33836c21aa36765937795cabe6c7aa8e6
5
5
  SHA512:
6
- metadata.gz: ba2ecdabcf4c991710428b08e57437a00e0b5c85a0164d7786fe4363f65f51617e544519393bc48cae3672debaa0b8c2ea4743aae67ae1e23dedfee32e24dfe9
7
- data.tar.gz: ef71ea37b3375b7328f9f6f6298d8cb867dda0aa38eec6b0480fcc12063b935a6fcfea99216d0bac5563577fd8a86c98697ef1a697ebfab09365a3e60390e3a5
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("I just rolled a dice! It's a #{rand(1..6)}!")
47
+ comment.reply("It's a #{rand(1..6)}!")
50
48
  elsif comment.body.include?('flip a coin')
51
- comment.reply("I just flipped a coin! It's a #{%w(heads tails).sample}!")
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
- #### Is that bot fully functional?
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
  ---
@@ -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.expired? && @auto_refresh
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
@@ -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 = Time.now
38
+ @attributes[:created_at] ||= Time.now.to_i
22
39
  end
23
40
  end
24
41
  end
@@ -14,6 +14,7 @@ module Redd
14
14
  400 => Redd::BadRequest,
15
15
  403 => Redd::Forbidden,
16
16
  404 => Redd::NotFound,
17
+ 429 => Redd::TooManyRequests,
17
18
  500 => Redd::ServerError,
18
19
  502 => Redd::ServerError,
19
20
  503 => Redd::ServerError,
data/lib/redd/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Redd
4
- VERSION = '0.8.5'
4
+ VERSION = '0.8.6'
5
5
  end
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.5
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-14 00:00:00.000000000 Z
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