drink-socially 0.0.4
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.
- data/LICENSE +674 -0
- data/README.md +54 -0
- data/config/endpoints.yml +231 -0
- data/lib/drink-socially.rb +43 -0
- data/lib/drink-socially/api.rb +124 -0
- data/lib/drink-socially/api/credential.rb +69 -0
- data/lib/drink-socially/api/notification.rb +13 -0
- data/lib/drink-socially/api/object.rb +83 -0
- data/lib/drink-socially/api/pagination.rb +73 -0
- data/lib/drink-socially/api/rate_limit.rb +17 -0
- data/lib/drink-socially/api/url_tokenizer.rb +47 -0
- data/lib/drink-socially/config.rb +48 -0
- data/lib/drink-socially/extensions/hash.rb +17 -0
- data/lib/drink-socially/http_service.rb +61 -0
- data/lib/drink-socially/http_service/response.rb +27 -0
- data/lib/drink-socially/version.rb +8 -0
- data/spec/cases/drink-socially/api/credential_spec.rb +107 -0
- data/spec/cases/drink-socially/api/object_spec.rb +112 -0
- data/spec/cases/drink-socially/api/pagination_spec.rb +43 -0
- data/spec/cases/drink-socially/api/rate_limit_spec.rb +15 -0
- data/spec/cases/drink-socially/api/url_tokenizer_spec.rb +41 -0
- data/spec/cases/drink-socially/api_spec.rb +79 -0
- data/spec/cases/drink-socially/config_spec.rb +95 -0
- data/spec/cases/drink-socially/extensions/hash_spec.rb +17 -0
- data/spec/cases/drink-socially/http_service/response_spec.rb +38 -0
- data/spec/cases/drink-socially/http_service_spec.rb +70 -0
- data/spec/cases/drink-socially/version_spec.rb +14 -0
- data/spec/cases/drink-socially_spec.rb +41 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/config.yml +3 -0
- metadata +202 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module NRB
|
4
|
+
module Untappd
|
5
|
+
class API
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
class Credential
|
10
|
+
|
11
|
+
class IncompleteCredentialError < ArgumentError; end
|
12
|
+
|
13
|
+
def self.valid_attrs
|
14
|
+
[ :access_token, :client_id, :client_secret ]
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def initialize(opts={})
|
19
|
+
opts.slice!(*self.class.valid_attrs)
|
20
|
+
@creds = {}
|
21
|
+
!! opts[:access_token] && @creds[:access_token] = CGI::escape(opts[:access_token])
|
22
|
+
!! opts[:client_id] && @creds[:client_id] = CGI::escape(opts[:client_id])
|
23
|
+
!! opts[:client_secret] && @creds[:client_secret] = CGI::escape(opts[:client_secret])
|
24
|
+
raise IncompleteCredentialError.new('Provide either API access token or API client id & secret') unless valid?
|
25
|
+
end
|
26
|
+
|
27
|
+
valid_attrs.each do |m|
|
28
|
+
define_method m do
|
29
|
+
@creds[m]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def is_client?
|
35
|
+
!! @creds[:client_id] && !! @creds[:client_secret]
|
36
|
+
end
|
37
|
+
alias_method :is_app?, :is_client?
|
38
|
+
|
39
|
+
|
40
|
+
def is_user?
|
41
|
+
!! @creds[:access_token]
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def merge(hash)
|
46
|
+
@creds.merge(hash)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def to_h
|
51
|
+
@creds
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def to_param
|
56
|
+
@creds.map { |k,v| "#{k}=#{v}" }.join("&")
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def valid?
|
62
|
+
(is_client? || is_user?) && ! (is_client? && is_user?)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module NRB
|
2
|
+
module Untappd
|
3
|
+
class API
|
4
|
+
class Object < HTTPService::Response
|
5
|
+
|
6
|
+
attr_reader :attributes, :error_message, :pagination, :results
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
super
|
10
|
+
parse_error_response
|
11
|
+
setup_pagination(args)
|
12
|
+
extract_results args[:results_path]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def define_attributes_from(hash)
|
18
|
+
return unless hash.respond_to?(:keys)
|
19
|
+
@attributes ||= []
|
20
|
+
perform_unless_respond_to(hash.keys) do |attr|
|
21
|
+
@attributes.push(attr.to_sym)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def define_methods_from(hash)
|
27
|
+
return unless hash.respond_to?(:keys)
|
28
|
+
perform_unless_respond_to(hash.keys) do |meth|
|
29
|
+
define_singleton_method meth, lambda { hash[meth] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def extract_from_body(path)
|
35
|
+
return unless !! body
|
36
|
+
path.inject(body) do |node,method_name|
|
37
|
+
break unless node.respond_to? method_name
|
38
|
+
node.send method_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def extract_results(path)
|
44
|
+
@results = extract_from_body path if !! path
|
45
|
+
define_attributes_from @results
|
46
|
+
define_methods_from @results
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def parse_error_response
|
51
|
+
return unless errored?
|
52
|
+
@error_message = if body[:meta]
|
53
|
+
"[#{body[:meta][:error_type]}] #{body[:meta][:error_detail]}"
|
54
|
+
|
55
|
+
elsif body[:error]
|
56
|
+
body[:error]
|
57
|
+
|
58
|
+
else
|
59
|
+
"Could not parse error message from response body"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def perform_unless_respond_to(arr)
|
65
|
+
return unless arr.respond_to?(:each)
|
66
|
+
arr.each do |key|
|
67
|
+
unless respond_to?(key)
|
68
|
+
yield key
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def setup_pagination(args)
|
75
|
+
@paginator = args[:paginator_class] || NRB::Untappd::API::Pagination
|
76
|
+
@pagination = @paginator.from_response self
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'uri'
|
2
|
+
module NRB
|
3
|
+
module Untappd
|
4
|
+
class API
|
5
|
+
class Pagination
|
6
|
+
|
7
|
+
def self.from_response(response)
|
8
|
+
# It's duck types (almost) all the way down
|
9
|
+
return unless response.respond_to?(:body) &&
|
10
|
+
response.body.respond_to?(:response) &&
|
11
|
+
response.body.response.respond_to?(:pagination) &&
|
12
|
+
!! response.body.response.pagination
|
13
|
+
new response.body.response.pagination
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def initialize(pagination)
|
18
|
+
@api_max_id = pagination[:max_id].to_i
|
19
|
+
@uris = { api_next: URI.parse(pagination[:next_url]),
|
20
|
+
api_prev: URI.parse(pagination[:since_url]),
|
21
|
+
api_since: URI.parse(pagination[:since_url])
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def next_id
|
27
|
+
@api_max_id + 1
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Warning: The API will return the most recent results if next_id is
|
32
|
+
# greater than the most recent result
|
33
|
+
def next_uri
|
34
|
+
uri_for query: "since=#{next_id}", uri: :next
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def next_url
|
39
|
+
next_uri.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def prev_id
|
44
|
+
@api_max_id - 1
|
45
|
+
end
|
46
|
+
alias_method :previous_id, :prev_id
|
47
|
+
|
48
|
+
|
49
|
+
def prev_uri
|
50
|
+
uri_for query: "max_id=#{prev_id}", uri: :prev
|
51
|
+
end
|
52
|
+
alias_method :previous_uri, :prev_uri
|
53
|
+
|
54
|
+
|
55
|
+
def prev_url
|
56
|
+
prev_uri.to_s
|
57
|
+
end
|
58
|
+
alias_method :previous_url, :prev_url
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def uri_for(opts)
|
63
|
+
return nil unless !! opts[:uri]
|
64
|
+
return @uris[otps[:uri]] if @uris[opts[:uri]]
|
65
|
+
@uris[otps[:uri]] = @uris["api_#{opts[:uri]}".to_sym]
|
66
|
+
@uris[otps[:uri]].query = opts[:query] if opts[:query]
|
67
|
+
@uris[otps[:uri]]
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module NRB
|
2
|
+
module Untappd
|
3
|
+
class API
|
4
|
+
class RateLimit
|
5
|
+
|
6
|
+
attr_reader :limit, :remaining
|
7
|
+
|
8
|
+
def initialize(headers)
|
9
|
+
return unless headers.respond_to?(:[])
|
10
|
+
@limit = headers["x-ratelimit-limit"]
|
11
|
+
@remaining = headers["x-ratelimit-remaining"]
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
module NRB
|
3
|
+
module Untappd
|
4
|
+
class API
|
5
|
+
class URLTokenizer
|
6
|
+
attr_reader :map, :string
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
@map = args[:map]
|
10
|
+
@string = args[:string]
|
11
|
+
raise ArgumentError unless @map && @string
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def tr
|
16
|
+
return @string unless @string =~ /:[#{word_chars}]+:/
|
17
|
+
result = ""
|
18
|
+
s = StringScanner.new(@string)
|
19
|
+
until s.eos? do
|
20
|
+
word = s.scan(/:[#{word_chars}]+:|[#{url_chars}]+/)
|
21
|
+
raise RuntimeError.new("String Scanner failed. File a bug.") if word.nil?
|
22
|
+
result += tr_word(word)
|
23
|
+
end
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def tr_word(word)
|
30
|
+
return word unless word =~ /:([#{word_chars}]+):/
|
31
|
+
return map[$1.to_sym].to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def url_chars
|
36
|
+
word_chars + Regexp.quote('/')
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def word_chars
|
41
|
+
"0-9a-zA-Z" + Regexp.quote("$-_.+!*'(),")
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module NRB
|
4
|
+
module Untappd
|
5
|
+
class Config
|
6
|
+
|
7
|
+
class NoConfig < ArgumentError; end
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
attr_reader :data
|
12
|
+
|
13
|
+
def_delegators :@data, :[], :each, :keys
|
14
|
+
|
15
|
+
def initialize(args)
|
16
|
+
load_data(args)
|
17
|
+
define_accessors
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def define_accessors
|
23
|
+
return unless @data
|
24
|
+
@data.keys.each do |name|
|
25
|
+
unless respond_to?(name)
|
26
|
+
define_singleton_method name, lambda { data[name] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def find_stream(args)
|
33
|
+
if args[:filename]
|
34
|
+
File.open args[:filename]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def load_data(args)
|
40
|
+
args[:stream] ||= find_stream(args)
|
41
|
+
raise NoConfig.new("Please supply :filename or :stream to Config.new") unless !! args[:stream]
|
42
|
+
loader = args[:stream_loader] || YAML
|
43
|
+
@data = loader.load_stream(args[:stream])[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware/parse_oj'
|
3
|
+
|
4
|
+
module NRB
|
5
|
+
class HTTPService
|
6
|
+
|
7
|
+
autoload :Response, 'drink-socially/http_service/response'
|
8
|
+
|
9
|
+
DEFAULT_MIDDLEWARE = Proc.new do |faraday|
|
10
|
+
faraday.adapter Faraday.default_adapter
|
11
|
+
faraday.request :url_encoded
|
12
|
+
faraday.response :oj
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :faraday_middleware
|
17
|
+
|
18
|
+
def default_middleware; DEFAULT_MIDDLEWARE; end
|
19
|
+
def default_http_class; Faraday; end
|
20
|
+
def default_response_class; Response; end
|
21
|
+
|
22
|
+
|
23
|
+
def make_request(args={}, connection_opts={})
|
24
|
+
new(args,connection_opts).make_request
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
self.faraday_middleware = self.default_middleware
|
30
|
+
|
31
|
+
|
32
|
+
def initialize(args={}, connection_opts={})
|
33
|
+
@connection_opts = connection_opts
|
34
|
+
@response_class = args.delete(:response_class) || self.class.default_response_class
|
35
|
+
@verb = args.delete(:verb)
|
36
|
+
@url = args.delete(:url)
|
37
|
+
@params = process_args(args)
|
38
|
+
@args = args
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def make_request
|
43
|
+
connection = self.class.default_http_class.new @url, @connection_opts, &self.class.faraday_middleware
|
44
|
+
response = connection.send @verb, @url, @params
|
45
|
+
args = @args.merge( { body: response.body, headers: response.headers, status: response.status.to_i } )
|
46
|
+
@response_class.new args
|
47
|
+
rescue Faraday::Error::ParsingError => e
|
48
|
+
self.class.default_response_class.new body: {error: e.message}, status: 500
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :params, :verb, :url
|
54
|
+
|
55
|
+
def process_args(args)
|
56
|
+
return args unless @verb == :post
|
57
|
+
args.inject("") { |str,pair| str += "#{pair.first}=#{pair.last}&" }.chop
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
module NRB
|
4
|
+
class HTTPService
|
5
|
+
class Response
|
6
|
+
|
7
|
+
attr_reader :status, :body, :headers
|
8
|
+
|
9
|
+
def errored?
|
10
|
+
! success?
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def initialize(args)
|
15
|
+
@status = args[:status]
|
16
|
+
@body = Hashie::Mash.new args[:body]
|
17
|
+
@headers = Hashie::Mash.new args[:headers]
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def success?
|
22
|
+
@status >= 200 && @status < 300
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|