social_plus-web_api 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 478a90847f6f39922aa12fc9f1712da864e5af9d
4
+ data.tar.gz: ccfa4efaede205efb439c1886773b1f956f167a1
5
+ SHA512:
6
+ metadata.gz: f0775913c356fc0cb0df82688633624e516d4efbf0f1cd2494ce06b56e02f84b5a986d0cec74116af1c24a762f7e2eccc38b2f0aed7a80fa48150cf15469b0b3
7
+ data.tar.gz: 0ce553e92923ed27fe2bec14264b7afa2297675419fee39ee95f09398d570e11d0b91b60122a124941c1b9acfe2835ad9df0c0889a26c911a3be4c26cbd470a4
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /vendor/bundle/
16
+ /bin/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
@@ -0,0 +1,75 @@
1
+ AllCops:
2
+ Exclude:
3
+ - bin/*
4
+ - db/schema.rb
5
+ - vendor/**/*
6
+
7
+ #Metrics/AbcSize:
8
+ # Max: 52
9
+
10
+ Metrics/LineLength:
11
+ Max: 192
12
+ AllowURI: true
13
+ URISchemes:
14
+ - http
15
+ - https
16
+
17
+ Style/MultilineOperationIndentation:
18
+ EnforcedStyle: indented
19
+
20
+ Style/NumericLiterals:
21
+ MinDigits: 6
22
+
23
+ Style/SpaceAroundEqualsInParameterDefault:
24
+ EnforcedStyle: no_space
25
+
26
+ Style/MethodCalledOnDoEndBlock:
27
+ Enabled: true
28
+
29
+ Style/SymbolArray:
30
+ Enabled: true
31
+
32
+ Style/ExtraSpacing:
33
+ Enabled: true
34
+
35
+ Style/BlockDelimiters:
36
+ EnforcedStyle: semantic
37
+
38
+ Style/FormatString:
39
+ Enabled: false
40
+
41
+ Style/PercentLiteralDelimiters:
42
+ Enabled: false
43
+
44
+ Style/RescueModifier:
45
+ Enabled: false
46
+
47
+ Style/SignalException:
48
+ Enabled: false
49
+
50
+ Style/SpaceInsideBrackets:
51
+ Enabled: false
52
+
53
+ Style/UnneededPercentQ:
54
+ Enabled: false
55
+
56
+ Style/CollectionMethods:
57
+ Enabled: false
58
+
59
+ Style/Encoding:
60
+ Enabled: false
61
+
62
+ Style/InlineComment:
63
+ Enabled: false
64
+
65
+ Style/BlockDelimiters:
66
+ Enabled: false
67
+
68
+ Style/StringLiterals:
69
+ Enabled: false
70
+
71
+ Style/ExtraSpacing:
72
+ Enabled: false
73
+
74
+ Style/GlobalVars:
75
+ Enabled: false
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.0
5
+ - 2.3.0
6
+ - 2.4.0
7
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in social_plus-web_api.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 feedforce Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ # SocialPlus::WebApi
2
+
3
+ [![Travis Status](https://img.shields.io/travis/feedforce/social_plus-web_api.svg?style=flat-square)][travisci]
4
+ [![License](https://img.shields.io/github/license/feedforce/social_plus-web_api.svg?style=flat-square)][license]
5
+ [![Gem](https://img.shields.io/gem/v/social_plus-web_api.svg?style=flat-square)][gem-link]
6
+
7
+ [travisci]: https://travis-ci.org/feedforce/social_plus-web_api
8
+ [license]: https://github.com/feedforce/social_plus-web_api/blob/master/LICENSE.txt
9
+ [gem-link]: http://badge.fury.io/rb/social_plus-web_api
10
+
11
+ This gem provides fundamental access to Social Plus's Web API.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'social_plus-web_api'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install social_plus-web_api
28
+
29
+ ## Usage
30
+
31
+ Instantiate an instance of {SocialPlus::WebApi::Client} with a valid API key.
32
+
33
+ ```ruby
34
+ client = SocialPlus::WebApi::Client.new(API_KEY)
35
+ ```
36
+
37
+ ### API access via GET method
38
+
39
+ ```ruby
40
+ begin
41
+ result = client.execute(:api_name, arguments_as_hash)
42
+ rescue SocialPlus::WebApi::ApiError => e
43
+ # handle exceptions
44
+ end
45
+ ```
46
+
47
+ ### API access via POST method
48
+
49
+ ```ruby
50
+ begin
51
+ result = client.execute(:api_name, arguments_as_hash, via: :post)
52
+ rescue SocialPlus::WebApi::ApiError => e
53
+ # handle exceptions
54
+ end
55
+ ```
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+ Dir[File.join(__dir__, 'lib/tasks/*.rake')].each(&method(:load))
@@ -0,0 +1,12 @@
1
+ # :nodoc:
2
+ module SocialPlus
3
+ # :nodoc:
4
+ module WebApi
5
+ end
6
+ end
7
+
8
+ require 'social_plus/web_api/version'
9
+ require 'social_plus/web_api/api_error'
10
+ require 'social_plus/web_api/client'
11
+ require 'social_plus/web_api/user'
12
+ require 'social_plus/web_api/profile'
@@ -0,0 +1,50 @@
1
+ require 'net/http'
2
+
3
+ # :nodoc:
4
+ module SocialPlus
5
+ # :nodoc:
6
+ module WebApi
7
+ # An Exception class which wraps errors from SocialPlus Web API
8
+ class ApiError < StandardError
9
+ # @overload initialize(error)
10
+ # @param error [Hash] a Hash which represents an API error
11
+ # @option error [String] message the error message
12
+ # @option error [Integer] code the error code
13
+ def initialize(error)
14
+ super(error['message'])
15
+ @code = error['code']
16
+ end
17
+
18
+ # @return [Integer] the error code (API error code or HTTP status)
19
+ attr_reader :code
20
+ end
21
+
22
+ require 'social_plus/web_api/invalid_token'
23
+ require 'social_plus/web_api/http_response_error'
24
+
25
+ class ApiError < StandardError
26
+ EXCEPTION_CLASSES = Hash.new(ApiError).tap do |exception_classes|
27
+ exception_classes[4] = InvalidToken
28
+ end
29
+
30
+ def self.exception_from_api_result(response, result)
31
+ if social_plus_error?(response, result)
32
+ error = result['error']
33
+ raise EXCEPTION_CLASSES[error['code']], error
34
+ else
35
+ raise HttpResponseError, response
36
+ end
37
+ end
38
+
39
+ private
40
+ def self.social_plus_error?(response, result)
41
+ case response
42
+ when Net::HTTPServerError, Net::HTTPClientError
43
+ result.key?('error') && %w(message code).all? { |key| result['error'].key?(key) }
44
+ else
45
+ false
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'net/https'
4
+
5
+ require 'active_support/core_ext/object/to_query'
6
+ require 'active_support/core_ext/hash/except'
7
+
8
+ require 'social_plus/web_api/api_error'
9
+ require 'social_plus/web_api/invalid_token'
10
+ require 'social_plus/web_api/version'
11
+
12
+ # :nodoc:
13
+ module SocialPlus
14
+ # :nodoc:
15
+ module WebApi
16
+ # A class which wraps calls to Social Plus Web API
17
+ class Client
18
+ API_KEY_RE = /\A[0-9a-f]{40}\z/
19
+ private_constant :API_KEY_RE
20
+
21
+ # @param [String] api_key A Social Plus API key
22
+ # @raise [ArgumentError] when `api_key` is invalid
23
+ def initialize(api_key)
24
+ raise ArgumentError, 'invalid API key' unless API_KEY_RE =~ api_key
25
+ @api_key = api_key.freeze
26
+ freeze
27
+ end
28
+
29
+ # Executes a Social Plus Web API
30
+ #
31
+ # @param [String, Symbol] method An API method name
32
+ # @param [Hash] parameters Parameters to API except `key`
33
+ # @option parameters [Symbol] :via HTTP method(default `:get`)
34
+ # @return [Hash] a Hash generated by parsing the JSON returned from the API call, except `status`,
35
+ # just `{}` on parsing failure
36
+ # @raise [ApiError] when the API returns a status other than 200 OK
37
+ def execute(method, parameters={})
38
+ parameters = parameters.with_indifferent_access
39
+ http_method = parameters.delete(:via) || :get
40
+ response = request(http_method, method, parameters.merge(key: @api_key))
41
+ result = parse_as_json(response.body)
42
+
43
+ ApiError.exception_from_api_result(response, result) unless response.is_a?(Net::HTTPOK)
44
+
45
+ result.except('status')
46
+ end
47
+
48
+ SOCIAL_PLUS_FQDN = URI('https://api.socialplus.jp/')
49
+ private_constant :SOCIAL_PLUS_FQDN
50
+
51
+ USER_AGENT = 'SocialPlus Web API gem/%s' % VERSION
52
+ private_constant :USER_AGENT
53
+
54
+ private
55
+ def request(http_method, api_method, parameters)
56
+ uri = request_uri(api_method)
57
+ request = send("create_#{http_method.downcase}_request", uri.path, parameters)
58
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
59
+ http.request(request)
60
+ end
61
+ end
62
+
63
+ def create_get_request(path, parameters)
64
+ Net::HTTP::Get.new(path + '?' + parameters.to_query, 'User-Agent' => USER_AGENT)
65
+ end
66
+
67
+ def create_post_request(path, parameters)
68
+ Net::HTTP::Post.new(path, 'User-Agent' => USER_AGENT).tap { |request| request.body = parameters.to_query }
69
+ end
70
+
71
+ def request_uri(method)
72
+ SOCIAL_PLUS_FQDN.dup.tap do |uri|
73
+ uri.path = '/api/%s' % method
74
+ end
75
+ end
76
+
77
+ def parse_as_json(json_text)
78
+ json_text ||= '{}'
79
+ JSON.parse(json_text)
80
+ rescue JSON::ParserError
81
+ {}
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,18 @@
1
+ # :nodoc:
2
+ module SocialPlus
3
+ # :nodoc:
4
+ module WebApi
5
+ # An Exception class raised when SocialPlus Web API reports http response error
6
+ #
7
+ class HttpResponseError < ApiError
8
+ # @overload initialize(response)
9
+ # @param response [Net::HTTPResponse] HTTP Response (except 200 OK)
10
+ def initialize(response)
11
+ super(
12
+ 'message' => response.message,
13
+ 'code' => response.code.to_i
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ # :nodoc:
2
+ module SocialPlus
3
+ # :nodoc:
4
+ module WebApi
5
+ # An Exception class raised when SocialPlus Web API reports invalid token
6
+ class InvalidToken < ApiError; end
7
+ end
8
+ end
@@ -0,0 +1,55 @@
1
+ # :nodoc:
2
+ module SocialPlus
3
+ # :nodoc:
4
+ module WebApi
5
+ PREFECTURES = {
6
+ 1 => '北海道',
7
+ 2 => '青森県',
8
+ 3 => '岩手県',
9
+ 4 => '宮城県',
10
+ 5 => '秋田県',
11
+ 6 => '山形県',
12
+ 7 => '福島県',
13
+ 8 => '茨城県',
14
+ 9 => '栃木県',
15
+ 10 => '群馬県',
16
+ 11 => '埼玉県',
17
+ 12 => '千葉県',
18
+ 13 => '東京都',
19
+ 14 => '神奈川県',
20
+ 15 => '新潟県',
21
+ 16 => '富山県',
22
+ 17 => '石川県',
23
+ 18 => '福井県',
24
+ 19 => '山梨県',
25
+ 20 => '長野県',
26
+ 21 => '岐阜県',
27
+ 22 => '静岡県',
28
+ 23 => '愛知県',
29
+ 24 => '三重県',
30
+ 25 => '滋賀県',
31
+ 26 => '京都府',
32
+ 27 => '大阪府',
33
+ 28 => '兵庫県',
34
+ 29 => '奈良県',
35
+ 30 => '和歌山県',
36
+ 31 => '鳥取県',
37
+ 32 => '島根県',
38
+ 33 => '岡山県',
39
+ 34 => '広島県',
40
+ 35 => '山口県',
41
+ 36 => '徳島県',
42
+ 37 => '香川県',
43
+ 38 => '愛媛県',
44
+ 39 => '高知県',
45
+ 40 => '福岡県',
46
+ 41 => '佐賀県',
47
+ 42 => '長崎県',
48
+ 43 => '熊本県',
49
+ 44 => '大分県',
50
+ 45 => '宮崎県',
51
+ 46 => '鹿児島県',
52
+ 47 => '沖縄県'
53
+ }
54
+ end
55
+ end
@@ -0,0 +1,196 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/object/inclusion'
4
+ require 'active_support/core_ext/object/try'
5
+ require 'active_support/core_ext/string/conversions'
6
+
7
+ require 'social_plus/web_api/prefectures'
8
+
9
+ # :nodoc:
10
+ module SocialPlus
11
+ # :nodoc:
12
+ module WebApi
13
+ # rubocop: disable Metrics/ClassLength, Metrics/AbcSize
14
+
15
+ # A class which represents authenticated user's information(profile and email) obtained from Social Plus
16
+ class Profile
17
+ # rubocop: disable Metrics/MethodLength
18
+
19
+ # @param [Hash<String,Object>] params a user's information obtained from Social Plus.
20
+ # @option params [Hash] "profile" profile of a user
21
+ # @option params [Array] "email" email addresses of a user
22
+
23
+ def initialize(params)
24
+ params = params.with_indifferent_access
25
+
26
+ profile = params[:profile]
27
+ @full_name, @given_name, @family_name, @full_name_kana, @given_name_kana, @family_name_kana = extract_names(profile).freeze
28
+ @zip_code = profile[:postal_code]
29
+ @prefecture, @prefecture_name, @city, @location = extract_location(profile).freeze
30
+ @gender = profile[:gender].in?([1, 2]) ? profile[:gender] : nil
31
+ @urls = profile[:uri].respond_to?(:map) ? filter_http_urls(profile[:uri]) : []
32
+ @birthday = /\A\d{4}-\d{2}-\d{2}\z/ =~ profile[:birthday] ? profile[:birthday].try(:to_date).freeze : nil
33
+
34
+ emails = params[:email]
35
+ @emails = emails && emails.respond_to?(:map) ? emails.map { |email| email[:email].freeze }.compact.freeze : []
36
+
37
+ freeze
38
+ end
39
+ # rubocop: enable Metrics/MethodLength
40
+
41
+ # @return [String] Returns the user's full name
42
+ attr_reader :full_name
43
+
44
+ # @return [String] Returns the user's family name
45
+ attr_reader :family_name
46
+
47
+ # @return [String] Returns the user's given name
48
+ attr_reader :given_name
49
+
50
+ # @return [String] Returns the user's full name in kana
51
+ attr_reader :full_name_kana
52
+
53
+ # @return [String] Returns the user's family name in kana
54
+ attr_reader :family_name_kana
55
+
56
+ # @return [String] Returns the user's given name in kana
57
+ attr_reader :given_name_kana
58
+
59
+ # @return [String] Returns the zip code of user's address
60
+ attr_reader :zip_code
61
+
62
+ # @return [Integer] Returns the prefecture code of user's address
63
+ attr_reader :prefecture
64
+
65
+ # @return [String] Returns the name of prefecture of user's address
66
+ attr_reader :prefecture_name
67
+
68
+ # @return [Integer] Returns the city code of user's address
69
+ attr_reader :city
70
+
71
+ # @return [String] Returns the rest of user's address
72
+ attr_reader :location
73
+
74
+ # @return [Date] Returns the user's birthday
75
+ attr_reader :birthday
76
+
77
+ # @return [Array<URI>] Returns the user's URLs
78
+ attr_reader :urls
79
+
80
+ # @return [Integer] Returns the user's gender
81
+ attr_reader :gender
82
+
83
+ # @return [Array<String>] Returns the user's E-Mail addresses
84
+ attr_reader :emails
85
+
86
+ private
87
+
88
+ def filter_http_urls(uris)
89
+ uris.map { |uri_string| URI(uri_string).freeze rescue nil }.select { |url| url.try(:scheme).in?(%w(http https)) }.freeze
90
+ end
91
+
92
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
93
+
94
+ # @return [Array] Returns Array of full name, given name, family name, full name in kana, given name in kana and family name in kana
95
+ def extract_names(profile)
96
+ full_name_kanji = profile[:full_name_kanji] || ''
97
+ last_name_kanji = profile[:last_name_kanji] || ''
98
+ first_name_kanji = profile[:first_name_kanji] || ''
99
+
100
+ full_name_kana = profile[:full_name_kana] || ''
101
+ last_name_kana = profile[:last_name_kana] || ''
102
+ first_name_kana = profile[:first_name_kana] || ''
103
+
104
+ full_name = profile[:full_name] || ''
105
+ last_name = profile[:last_name] || ''
106
+ first_name = profile[:first_name] || ''
107
+ user_name = profile[:user_name] || ''
108
+
109
+ # NOTE: "".present? #=> false
110
+
111
+ # *: set
112
+ # -: not set
113
+ # : no care
114
+ # full(K) | last(K) | first(K) | full | last | first | user |
115
+ # #1 ******* | ******* | ******** | | | | |
116
+ # #1 ******* | ******* | -------- | | | | |
117
+ # #1 ******* | ------- | ******** | | | | |
118
+ # #1' | | | **** | **** | ***** | |
119
+ # #1' | | | **** | **** | ----- | |
120
+ # #1' | | | **** | ---- | ***** | |
121
+ # #2 ------- | ******* | ******** | | | | |
122
+ # #2 ------- | ******* | -------- | | | | |
123
+ # #2 ------- | ------- | ******** | | | | |
124
+ # #2' | | | ---- | **** | ***** | |
125
+ # #2' | | | ---- | **** | ----- | |
126
+ # #2' | | | ---- | ---- | ***** | |
127
+ # #3 ******* | ------- | -------- | | | | |
128
+ # #3' | | | **** | ---- | ----- | |
129
+ # #4 ------- | ------- | -------- | ---- | ---- | ----- | **** |
130
+ # #5 ------- | ------- | -------- | ---- | ---- | ----- | ---- |
131
+
132
+ # #1
133
+ if full_name_kanji.present? && (last_name_kanji.present? || first_name_kanji.present?)
134
+ names = [ full_name_kanji, first_name_kanji, last_name_kanji ]
135
+ # #1'
136
+ elsif full_name.present? && (last_name.present? || first_name.present?)
137
+ names = [ full_name, first_name, last_name ]
138
+ # #2
139
+ elsif last_name_kanji.present? || first_name_kanji.present?
140
+ names = [ [last_name_kanji, first_name_kanji].join(' ').strip, first_name_kanji, last_name_kanji ]
141
+ # #2'
142
+ elsif last_name.present? || first_name.present?
143
+ names = [ [last_name, first_name].join(' ').strip, first_name, last_name ]
144
+ # #3
145
+ elsif full_name_kanji.present?
146
+ names = [ full_name_kanji, '', full_name_kanji ]
147
+ # #3'
148
+ elsif full_name.present?
149
+ names = [ full_name, '', full_name ]
150
+ # #4
151
+ elsif user_name.present?
152
+ names = [ user_name, '', '' ]
153
+ # #5
154
+ else
155
+ names = [ '', '', '' ]
156
+ end
157
+
158
+ if full_name_kana.present? && (last_name_kana.present? || first_name_kana.present?)
159
+ kana = [ full_name_kana, first_name_kana, last_name_kana ]
160
+ elsif last_name_kana.present? || first_name_kana.present?
161
+ kana = [ [last_name_kana, first_name_kana].join(' ').strip, first_name_kana, last_name_kana ]
162
+ elsif full_name_kana.present?
163
+ kana = [ full_name_kana, '', full_name_kana ]
164
+ else
165
+ kana = [ '', '', '' ]
166
+ end
167
+
168
+ names + kana
169
+ end
170
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
171
+
172
+ # @return [Array] Returns prefecture code, prefecture_name, city code and location(rest of the address)
173
+ def extract_location(profile)
174
+ location = profile[:location].presence || ''
175
+
176
+ city_code = city_code(profile)
177
+ prefecture_code = city_code ? city_code_to_prefecture_code(city_code) : nil
178
+ return [ nil, '', nil, location ] unless city_code && prefecture_code
179
+
180
+ prefecture_name = PREFECTURES[prefecture_code]
181
+ location_without_prefecture = location.sub(/\A#{prefecture_name}/, '')
182
+ [ prefecture_code, prefecture_name, city_code, location_without_prefecture ]
183
+ end
184
+
185
+ def city_code(profile)
186
+ profile[:location_jis_id].to_i.presence
187
+ end
188
+
189
+ def city_code_to_prefecture_code(city_code)
190
+ code = city_code / 1000
191
+ PREFECTURES.key?(code) ? code : nil
192
+ end
193
+ end
194
+ # rubocop: enable Metrics/ClassLength
195
+ end
196
+ end