companion_api 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: faaf43ab4e675ad4722c24ffa580e0733d85096cb2a2b4af6521d24103112752
4
+ data.tar.gz: c15027cddb74d2517e52aa5f428333d604b23729aa90bc06e97f13f4aa2397a0
5
+ SHA512:
6
+ metadata.gz: 634fa00ee966d63382e3a951c960f027ccf6c8e9557f0e419d04850d161d05052e71f390fb2bba57ac7d67b596fcfeff03a8aa955464ecc0886d5ab22c742740
7
+ data.tar.gz: 3757590fbd5d285c6523f9c681873a8c66637375cba0aa7df4082725bdf5b420a0a00f1d1ff2b265910d85573ea5be2ba901177e34ef06056bf0fafa32ee521c
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ .bundle
14
+ Gemfile.lock
15
+
16
+ .DS_Store
17
+
18
+ .rvmrc
19
+ .idea
20
+
21
+ .env
22
+ .test.credentials
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,190 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ # Enable all the rules by default
5
+ EnabledByDefault: true
6
+ # Command line rubocop options
7
+ ExtraDetails: true
8
+ DisplayStyleGuide: true
9
+ DisplayCopNames: true
10
+ # Target versions
11
+ # We do not specify Ruby version, because CodeClimate should by default
12
+ # read .ruby-version, and send it down to Rubocop
13
+ # (see https://github.com/codeclimate/codeclimate-rubocop/pull/68)
14
+ # TargetRubyVersion: 2.4
15
+ # We will lock Rails version to the newest we have in the company, so we're
16
+ # always looking to the future.
17
+ TargetRailsVersion: 5.1
18
+ # Exclude auto-generated and vendored files
19
+ Exclude:
20
+ - 'db/schema.rb'
21
+
22
+ #-------------------------------------------------------------------------------
23
+ # Rules configuration
24
+ #-------------------------------------------------------------------------------
25
+
26
+ Bundler/OrderedGems:
27
+ TreatCommentsAsGroupSeparators: true
28
+
29
+ Layout/AlignHash:
30
+ EnforcedColonStyle: table
31
+ EnforcedHashRocketStyle: table
32
+ EnforcedLastArgumentHashStyle: always_inspect
33
+
34
+ Layout/DotPosition:
35
+ EnforcedStyle: trailing
36
+
37
+ Layout/MultilineAssignmentLayout:
38
+ EnforcedStyle: same_line
39
+
40
+ Layout/MultilineMethodCallIndentation:
41
+ EnforcedStyle: indented_relative_to_receiver
42
+
43
+ # Requere spaces inside of brackets, e.g. [ 1, 2, 3 ]
44
+ Layout/SpaceInsideArrayLiteralBrackets:
45
+ Enabled: false
46
+ EnforcedStyle: compact
47
+
48
+ Metrics/AbcSize:
49
+ Max: 25
50
+
51
+ Metrics/ClassLength:
52
+ Max: 250
53
+ CountComments: false
54
+
55
+ Metrics/LineLength:
56
+ Max: 120
57
+ Exclude:
58
+ - 'db/migrate/*'
59
+ - 'spec/**/*'
60
+ IgnoredPatterns:
61
+ - '^#.*'
62
+
63
+ Metrics/MethodLength:
64
+ Max: 30
65
+ Exclude:
66
+ - 'db/migrate/*'
67
+
68
+ Metrics/ModuleLength:
69
+ Max: 250
70
+ CountComments: false
71
+
72
+ Rails/SafeNavigation:
73
+ ConvertTry: true
74
+
75
+ Style/Alias:
76
+ EnforcedStyle: prefer_alias_method
77
+
78
+ Style/ClassCheck:
79
+ EnforcedStyle: kind_of?
80
+
81
+ Style/Lambda:
82
+ EnforcedStyle: literal
83
+
84
+ Style/TrailingCommaInArguments:
85
+ EnforcedStyleForMultiline: consistent_comma
86
+
87
+ Style/TrailingCommaInArrayLiteral:
88
+ EnforcedStyleForMultiline: consistent_comma
89
+
90
+ Style/TrailingCommaInHashLiteral:
91
+ EnforcedStyleForMultiline: consistent_comma
92
+
93
+ #-------------------------------------------------------------------------------
94
+ # RSpec rules
95
+ #-------------------------------------------------------------------------------
96
+
97
+ RSpec/MultipleExpectations:
98
+ Max: 7
99
+
100
+ # Name your test subject if you need to reference it explicitly. (http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject)
101
+ RSpec/NamedSubject:
102
+ Enabled: false
103
+
104
+ # Spec path should end with _spec (http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FilePath)
105
+ # example: meta_tags/view_helper*displayingdescription*_spec.rb.
106
+ RSpec/FilePath:
107
+ Enabled: false
108
+
109
+ # The second argument to describe should be the method being tested. '#instance' or '.class'. (http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeMethod)
110
+ # example: describe MetaTags::ViewHelper, 'displaying description' do
111
+ RSpec/DescribeMethod:
112
+ Enabled: false
113
+
114
+ # Example has too many lines. (http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleLength)
115
+ RSpec/ExampleLength:
116
+ Enabled: false
117
+
118
+ RSpec/VerifiedDoubles:
119
+ Enabled: false
120
+
121
+ #-------------------------------------------------------------------------------
122
+ # Disabled rules
123
+ #-------------------------------------------------------------------------------
124
+
125
+ # Yes, we have meta-tags.rb for simplicity
126
+ Naming/FileName:
127
+ Enabled: false
128
+
129
+ # Allow not null columns without a default value
130
+ Rails/NotNullColumn:
131
+ Enabled: false
132
+
133
+ # This check does not distinguish between "private :x" and "private def x"
134
+ Style/AccessModifierDeclarations:
135
+ Enabled: false
136
+
137
+ Style/ClassAndModuleChildren:
138
+ Enabled: false
139
+
140
+ # Do not require copyright information
141
+ Style/Copyright:
142
+ Enabled: false
143
+
144
+ # Do not require class and module documentation
145
+ Style/Documentation:
146
+ Enabled: false
147
+
148
+ # Do not require method documentation
149
+ Style/DocumentationMethod:
150
+ Enabled: false
151
+
152
+ # Allow double negation (!!@var)
153
+ Style/DoubleNegation:
154
+ Enabled: false
155
+
156
+ # Do not enforce if modifier, sometimes it is better to split for long lines
157
+ Style/IfUnlessModifier:
158
+ Enabled: false
159
+
160
+ # Allow trailing comments
161
+ Style/InlineComment:
162
+ Enabled: false
163
+
164
+ # Do not require parentheses around method arguments
165
+ Style/MethodCallWithArgsParentheses:
166
+ Enabled: false
167
+
168
+ # Do not enforce `else` on conditions
169
+ Style/MissingElse:
170
+ Enabled: false
171
+
172
+ # Allow both `.positive?` and `> 0` variants
173
+ Style/NumericPredicate:
174
+ Enabled: false
175
+
176
+ # Do not enforce using symbols for hash keys
177
+ Style/StringHashKeys:
178
+ Enabled: false
179
+
180
+ # Do not enforce usage of single quotes on strings
181
+ Style/StringLiterals:
182
+ Enabled: false
183
+
184
+ # Do not require to use %i[] around symbol arrays
185
+ Style/SymbolArray:
186
+ Enabled: false
187
+
188
+ # Do not require to use %w[] around word arrays
189
+ Style/WordArray:
190
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.1
7
+ before_install: gem install bundler -v 1.16.3
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gem 'pbkdf2', github: 'emerose/pbkdf2-ruby', ref: 'f9a337e3559de7e61792f2c761a91ea57d53e095'
6
+
7
+ group :test do
8
+ gem 'rubocop', '0.60.0'
9
+ gem 'rubocop-rspec', require: false
10
+ end
11
+
12
+ # Specify your gem's dependencies in companion_api.gemspec
13
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Nils Berenbold
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.
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # CompanionApi
2
+
3
+ This gem provides a wrapper to the SE Companion App Api, right now it only supports the marketplace, you are free to add the other endpoints.
4
+
5
+ This gem is basically a rewrite of the php Version from Vekien (https://github.com/xivapi/companion-php)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'companion_api'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install companion_api
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ CompanionApi.configure do |config|
27
+ config.profile_directory = "PATH" # Here are the profiles stored for later use
28
+ config.logger = Logger.new("PATH")
29
+ config.debug = false
30
+ end
31
+ ```
32
+
33
+ For rails put initialization into config/initializers/global.rb
34
+
35
+ ```ruby
36
+ CompanionApi.configure do |config|
37
+ config.profile_directory = Rails.root.join("tmp", "profiles").to_s # Here are the profiles stored for later use
38
+ config.logger = Logger.new(Rails.root.join("log", "companion.log"))
39
+ config.debug = false
40
+ end
41
+ ```
42
+
43
+ ### Login
44
+
45
+ ```ruby
46
+ api = CompanionApi::Base.new('test_profile')
47
+ api.login!("USERNAME", "PASSWORD")
48
+ ```
49
+
50
+ afterwards you are able to select a character and use it for the marketplace Api
51
+
52
+ The token you'll receive is valid for 24 hours, if the token expires a custom ```CompanionApi::TokenExpiredError``` is raised
53
+
54
+ ```ruby
55
+ characters = api.login.characters
56
+ api.login.select_character(characters.first['cid'])
57
+ ```
58
+
59
+ you can check if you are logged in to a character by using ```api.loggedin?```
60
+
61
+ ### Marketplace
62
+
63
+ ```ruby
64
+ # load a earth shards
65
+ result = api.market.market_listings_by_category(58)
66
+ >> result["items"]
67
+
68
+ result = api.market.item_market_listings(5)
69
+ >> result["entries"]
70
+
71
+ # load only hq results
72
+ result = api.market.item_market_listings(23_769, hq: true)
73
+ >> result["entriesHq"]
74
+
75
+ result = api.market.transaction_history(5)
76
+ >> result["history"]
77
+ ```
78
+
79
+ ## Development
80
+
81
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
82
+
83
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
84
+
85
+ ## Contributing
86
+
87
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nberenbold/companion-api-ruby.
88
+
89
+ ## License
90
+
91
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'companion_api'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'companion_api/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'companion_api'
7
+ spec.version = CompanionApi::VERSION
8
+ spec.authors = ['Nils Berenbold']
9
+ spec.email = ['nils.berenbold@gmail.com']
10
+
11
+ spec.summary = 'A Wrapper for the Companion App Api'
12
+ spec.description = 'A Wrapper for the Companion App Api'
13
+ spec.homepage = 'https://xivapi.com'
14
+ spec.license = 'MIT'
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.16'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+
29
+ spec.add_dependency 'activesupport', '~> 5'
30
+ spec.add_dependency 'faraday', '~> 0.9'
31
+ spec.add_dependency 'faraday_middleware'
32
+ spec.add_dependency 'httpclient'
33
+ spec.add_dependency 'nokogiri'
34
+
35
+ spec.required_ruby_version = '>= 2.2.0'
36
+ end
@@ -0,0 +1 @@
1
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtDLA2/jQxScusojWLHyhCYNW8S9yM+DuaLgiXZC/tk79H1u4jiip1kRx2zrQ9nvnMqYMNYarqKDlJmIL10B5UWY+vDXdXXiXx5qQtj+NgPslhiZMDxpA0EfDvBdQ7Hmk/m1qocP0i0uYVzvyZBKWN1Xj1QDuZzKfXP6rpfPVB1rSwxAMpEcyMHhtsTsEf+w9Sr+Wy/oJQau+k+rLhYtln+HsF9LCn5d1O4AuBFOHDh5gAitRjl2cp0hHYRYHQ0WAwYqeTqi+HW2XJAU/cCWXF9LFjKH7Q6LBrcQzgbFsn1jwE4gKGl/DxZWFFLMYoUrHU5z/cGfVH8k6Lheu02b9hQIDAQAB
@@ -0,0 +1,35 @@
1
+ module CompanionApi
2
+ class Base
3
+ attr_accessor :profile
4
+ attr_accessor :account
5
+ attr_accessor :login
6
+ attr_accessor :market
7
+
8
+ def initialize(profile_name)
9
+ @profile = Profile.new(profile_name)
10
+ @account = CompanionApi::Resources::Account.new(@profile)
11
+ @login = CompanionApi::Resources::Login.new(@profile)
12
+ @market = CompanionApi::Resources::Market.new(@profile)
13
+ end
14
+
15
+ def login!(username, password)
16
+ raise CompanionApi::Error, 'no username or password specified' if username.blank? || password.blank?
17
+
18
+ @account.auto_login!(username, password)
19
+ end
20
+
21
+ def token_auth!
22
+ @account.token_login!
23
+
24
+ url = @account.login_url
25
+ CompanionApi.config.logger.info("please visit #{url} and hit enter afterwards")
26
+ gets
27
+ res = @login.post_auth
28
+ raise CompanionApi::Error, 'invalid response received' if res['status'] != 200
29
+ end
30
+
31
+ def loggedin?
32
+ @login.character.present?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ module CompanionApi
2
+ class Configuration
3
+ attr_accessor :profile_directory
4
+
5
+ attr_accessor :directory
6
+
7
+ attr_accessor :logger
8
+
9
+ attr_accessor :debug
10
+
11
+ def initialize
12
+ @directory = File.join(File.dirname(__FILE__), '..', '..', 'config')
13
+ @debug = false
14
+ @logger = Logger.new(STDOUT)
15
+ end
16
+ end
17
+
18
+ class << self
19
+ def configure
20
+ @config ||= Configuration.new
21
+ yield @config
22
+ end
23
+
24
+ def config
25
+ @config ||= Configuration.new
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module CompanionApi
2
+ class Error < StandardError; end
3
+ class LoginError < StandardError; end
4
+ class TokenExpiredError < StandardError; end
5
+ end
@@ -0,0 +1,37 @@
1
+ module CompanionApi
2
+ class Profile
3
+ attr_accessor :profile_name
4
+ attr_accessor :file
5
+ attr_accessor :settings
6
+
7
+ def initialize(profile_name)
8
+ @file = File.join(CompanionApi.config.profile_directory, "#{profile_name}.json")
9
+ @settings = {}
10
+
11
+ load
12
+ end
13
+
14
+ def set(key, value)
15
+ @settings[key.to_sym] = value
16
+ save!
17
+ end
18
+
19
+ def get(key)
20
+ @settings[key.to_sym]
21
+ end
22
+
23
+ protected
24
+
25
+ def load
26
+ return save unless File.exist?(@file)
27
+
28
+ @settings = JSON.parse(open(@file).read, symbolize_names: true)
29
+ end
30
+
31
+ def save!
32
+ File.open(@file, 'w') do |f|
33
+ f.write(@settings.to_json)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,96 @@
1
+ module CompanionApi
2
+ class Request
3
+ SQEX_AUTH_URI = 'https://secure.square-enix.com/oauth/oa/oauthauth'.freeze
4
+ SQEX_LOGIN_URI = 'https://secure.square-enix.com/oauth/oa/oauthlogin'.freeze
5
+ OAUTH_CALLBACK = 'https://companion.finalfantasyxiv.com/api/0/auth/callback'.freeze
6
+
7
+ URI = 'https://companion.finalfantasyxiv.com'.freeze
8
+ URI_SE = 'https://secure.square-enix.com'.freeze
9
+ VERSION = '/sight-v060/sight'.freeze
10
+
11
+ attr_accessor :args
12
+
13
+ attr_accessor :headers
14
+
15
+ def initialize(args = {})
16
+ defaults = {
17
+ version: VERSION,
18
+ endpoint: nil,
19
+ json: nil,
20
+ form: nil,
21
+ query: nil,
22
+ cookies: nil,
23
+ redirect: true,
24
+ return202: false,
25
+ }
26
+
27
+ @args = args.reverse_merge(defaults)
28
+
29
+ raise ComapnionApi::Error, 'maximum redirects reached' if args[:redirects].to_i > 3
30
+
31
+ @args[:version] = '' if @args[:uri].include?(URI_SE)
32
+
33
+ @headers = {
34
+ 'Accept' => '*/*',
35
+ 'Accept-Language' => 'en-gb',
36
+ 'Accept-Encoding' => 'br, gzip, deflate',
37
+ 'User-Agent' => 'ffxivcomapp-e/1.0.3.0 CFNetwork/974.2.1 Darwin/18.0.0',
38
+ 'request-id' => @args[:requestId] || CompanionApi.uuid,
39
+ 'token' => @args[:token],
40
+ }
41
+
42
+ @headers = @headers.reverse_merge(args[:headers]) if args[:headers].present?
43
+ end
44
+
45
+ def post!
46
+ execute!(:post)
47
+ end
48
+
49
+ def get!
50
+ execute!(:get)
51
+ end
52
+
53
+ protected
54
+
55
+ def execute!(method)
56
+ CompanionApi.debug("executing %{method} to endpoint %{endpoint} with url %{uri}", method: method, endpoint: endpoint, uri: @args[:uri])
57
+
58
+ conn = Faraday.new(url: @args[:uri]) do |builder|
59
+ # builder.response :logger
60
+ builder.request :url_encoded
61
+ builder.use FaradayMiddleware::FollowRedirects
62
+ builder.adapter :httpclient
63
+
64
+ # builder.adapter :httpclient do |http|
65
+ # http.set_cookie_store("/tmp/test.dat")
66
+ # end
67
+ end
68
+
69
+ 30.times do
70
+ body = (@args[:json] || @args[:form])
71
+
72
+ res = conn.send(method) do |req|
73
+ req.url(endpoint, @args[:query])
74
+ req.headers = @headers
75
+ req.body = body
76
+ end
77
+
78
+ CompanionApi.debug("received status %{status}", status: res.status)
79
+
80
+ raise CompanionApi::TokenExpiredError, 'token has expired' if res.status == 403
81
+
82
+ return res if @args[:return202] || res.status != 202
83
+
84
+ sleep(0.5)
85
+ end
86
+
87
+ raise CompanionApi::Error, 'could not get a valid response after 30 tries'
88
+ end
89
+
90
+ def endpoint
91
+ return '' if @args[:absolute]
92
+
93
+ @args[:version] + @args[:endpoint]
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,121 @@
1
+ module CompanionApi
2
+ module Resources
3
+ class Account
4
+ PLATFORM_IOS = 1
5
+ PLATFORM_ANDROID = 2
6
+
7
+ attr_accessor :profile
8
+
9
+ def initialize(profile)
10
+ @profile = profile
11
+ end
12
+
13
+ def auto_login!(username, password)
14
+ req = CompanionApi::Request.new(
15
+ uri: login_url,
16
+ requestId: CompanionApi.uuid,
17
+ absolute: true,
18
+ )
19
+
20
+ res = req.get!
21
+ body = res.body
22
+
23
+ if !body.include?('cis_sessid') && body.include?('loginForm')
24
+ html = Nokogiri::HTML(body)
25
+ form = html.at_css("form[id='loginForm']")
26
+ stored = form.at_css("input[name='_STORED_']")[:value]
27
+
28
+ action = CGI.unescape(form[:action])
29
+
30
+ data = {
31
+ "_STORED_": stored,
32
+ "sqexid": username,
33
+ "password": password,
34
+ }
35
+
36
+ req = CompanionApi::Request.new(
37
+ uri: CompanionApi::Request::URI_SE,
38
+ endpoint: "/oauth/oa/#{action}",
39
+ requestId: CompanionApi.uuid,
40
+ # token: profile.get("token"),
41
+ form: data,
42
+ )
43
+
44
+ res = req.post!
45
+ body = res.body
46
+ end
47
+
48
+ html = Nokogiri::HTML(body)
49
+ form = html.at_css("form[name='mainForm']")
50
+ raise CompanionApi::LoginError, 'unexpected response received' if form.nil?
51
+
52
+ cis_sessid = form.at_css("input[name='cis_sessid']")[:value]
53
+
54
+ data = {
55
+ "cis_sessid": cis_sessid,
56
+ "provision": '',
57
+ "_c": 1,
58
+ }
59
+
60
+ req = CompanionApi::Request.new(
61
+ uri: form[:action],
62
+ absolute: true,
63
+ # requestId: CompanionApi.uuid,
64
+ query: {
65
+ token: @profile.get('token'),
66
+ uid: @profile.get('uid'),
67
+ },
68
+ return202: true,
69
+ form: data,
70
+ )
71
+
72
+ res = req.post!
73
+
74
+ raise CompanionApi::LoginError, 'Login status could not be validated.' if res.status != 202
75
+
76
+ CompanionApi.refresh_uuid
77
+ true
78
+ end
79
+
80
+ def login_url
81
+ @profile.set('userId', CompanionApi.uuid)
82
+
83
+ json = get_token
84
+ @profile.set('token', json['token'])
85
+ @profile.set('salt', json['salt'])
86
+
87
+ CompanionApi::Request::SQEX_AUTH_URI + '?' + {
88
+ 'client_id' => 'ffxiv_comapp',
89
+ 'lang' => 'en-us',
90
+ 'response_type' => 'code',
91
+ 'redirect_uri' => redirect_uri,
92
+ }.to_query
93
+ end
94
+
95
+ def redirect_uri
96
+ uid = CompanionApi.pbkdf2(@profile.get('userId'), @profile.get('salt'))
97
+ @profile.set('uid', uid)
98
+
99
+ CompanionApi::Request::OAUTH_CALLBACK + '?' + {
100
+ 'token' => @profile.get('token'),
101
+ 'uid' => uid,
102
+ 'request_id' => CompanionApi.uuid,
103
+ }.to_query
104
+ end
105
+
106
+ def get_token
107
+ crypted_uuid = CompanionApi.rsa.public_encrypt(@profile.get('userId'))
108
+ uid = Base64.encode64(crypted_uuid)
109
+
110
+ req = CompanionApi::Request.new(
111
+ uri: CompanionApi::Request::URI,
112
+ endpoint: '/login/token',
113
+ json: { 'platform' => PLATFORM_ANDROID, 'uid' => uid },
114
+ )
115
+
116
+ res = req.post!
117
+ JSON.parse(res.body)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,83 @@
1
+ module CompanionApi
2
+ module Resources
3
+ class Login
4
+ attr_accessor :profile
5
+ attr_accessor :account
6
+ attr_accessor :characters
7
+ attr_accessor :character
8
+
9
+ def initialize(profile)
10
+ @profile = profile
11
+ end
12
+
13
+ def post_auth
14
+ req = CompanionApi::Request.new(
15
+ uri: CompanionApi::Request::URI,
16
+ endpoint: '/login/auth',
17
+ requestId: CompanionApi.uuid,
18
+ token: @profile.get('token'),
19
+ query: {
20
+ token: @profile.get('token'),
21
+ uid: @profile.get('uid'),
22
+ request_id: CompanionApi.uuid,
23
+ },
24
+ )
25
+
26
+ res = req.post!
27
+ JSON.parse(res.body)
28
+ end
29
+
30
+ def characters
31
+ return @characters if @characters.present?
32
+
33
+ req = CompanionApi::Request.new(
34
+ uri: CompanionApi::Request::URI,
35
+ endpoint: '/login/characters',
36
+ token: @profile.get("token"),
37
+ )
38
+
39
+ res = req.get!
40
+ json = JSON.parse(res.body)
41
+
42
+ raise CompanionApi::LoginError, 'no valid accounts found' if json["accounts"].blank?
43
+
44
+ @account = json["accounts"][0]
45
+ @characters = @account["characters"]
46
+
47
+ @characters
48
+ end
49
+
50
+ def select_character(cid)
51
+ req = CompanionApi::Request.new(
52
+ uri: CompanionApi::Request::URI,
53
+ endpoint: "/login/characters/#{cid}",
54
+ token: @profile.get("token"),
55
+ json: {
56
+ 'appLocaleType' => 'EU',
57
+ },
58
+ )
59
+
60
+ res = req.post!
61
+ json = JSON.parse(res.body)
62
+
63
+ region = json["region"].chomp("/")
64
+ @profile.set("region", region)
65
+
66
+ load_character
67
+ end
68
+
69
+ def load_character
70
+ req = CompanionApi::Request.new(
71
+ uri: @profile.get('region'),
72
+ endpoint: '/login/character',
73
+ token: @profile.get("token"),
74
+ )
75
+
76
+ res = req.get!
77
+ json = JSON.parse(res.body)
78
+
79
+ @character = json
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,39 @@
1
+ module CompanionApi
2
+ module Resources
3
+ class Market
4
+ attr_accessor :profile
5
+
6
+ def initialize(profile)
7
+ @profile = profile
8
+ end
9
+
10
+ def item_market_listings(item_id, hq: false)
11
+ endpoint = "/market/items/catalog/#{item_id}"
12
+ endpoint += "/hq" if hq
13
+
14
+ request_result(endpoint)
15
+ end
16
+
17
+ def market_listings_by_category(category_id)
18
+ request_result("/market/items/category/#{category_id}")
19
+ end
20
+
21
+ def transaction_history(catalog_id)
22
+ request_result("/market/items/history/catalog/#{catalog_id}")
23
+ end
24
+
25
+ protected
26
+
27
+ def request_result(endpoint)
28
+ req = CompanionApi::Request.new(
29
+ uri: @profile.get('region'),
30
+ endpoint: endpoint,
31
+ token: @profile.get("token"),
32
+ )
33
+
34
+ res = req.get!
35
+ JSON.parse(res.body)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module CompanionApi
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,51 @@
1
+ require 'faraday'
2
+ require 'httpclient'
3
+ require 'faraday_middleware'
4
+ require 'nokogiri'
5
+
6
+ require 'json'
7
+ require 'base64'
8
+ require 'openssl'
9
+ require 'securerandom'
10
+ require 'pbkdf2'
11
+ require 'logger'
12
+
13
+ require 'active_support/core_ext/hash'
14
+
15
+ require 'companion_api/version'
16
+ require 'companion_api/base'
17
+ require 'companion_api/configuration'
18
+ require 'companion_api/profile'
19
+ require 'companion_api/request'
20
+ require 'companion_api/error'
21
+
22
+ require 'companion_api/resources/account'
23
+ require 'companion_api/resources/login'
24
+ require 'companion_api/resources/market'
25
+
26
+ module CompanionApi
27
+ class << self
28
+ def uuid
29
+ @uuid ||= SecureRandom.uuid.upcase
30
+ end
31
+
32
+ def refresh_uuid
33
+ @uuid = SecureRandom.uuid.upcase
34
+ end
35
+
36
+ def pbkdf2(value, salt)
37
+ PBKDF2.new(password: value, salt: salt, iterations: 1000, hash_function: OpenSSL::Digest::SHA1, key_length: 128).hex_string
38
+ end
39
+
40
+ def rsa
41
+ pem = File.read(File.join(CompanionApi.config.directory, 'public-key.pem')).strip
42
+ OpenSSL::PKey::RSA.new(Base64.decode64(pem))
43
+ end
44
+
45
+ def debug(message, args = {})
46
+ return if config.debug == false || config.logger.nil?
47
+
48
+ config.logger.debug(format(message, args))
49
+ end
50
+ end
51
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: companion_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nils Berenbold
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faraday_middleware
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: httpclient
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: nokogiri
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: A Wrapper for the Companion App Api
126
+ email:
127
+ - nils.berenbold@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".rubocop.yml"
135
+ - ".travis.yml"
136
+ - Gemfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - bin/console
141
+ - bin/setup
142
+ - companion_api.gemspec
143
+ - config/public-key.pem
144
+ - lib/companion_api.rb
145
+ - lib/companion_api/base.rb
146
+ - lib/companion_api/configuration.rb
147
+ - lib/companion_api/error.rb
148
+ - lib/companion_api/profile.rb
149
+ - lib/companion_api/request.rb
150
+ - lib/companion_api/resources/account.rb
151
+ - lib/companion_api/resources/login.rb
152
+ - lib/companion_api/resources/market.rb
153
+ - lib/companion_api/version.rb
154
+ homepage: https://xivapi.com
155
+ licenses:
156
+ - MIT
157
+ metadata: {}
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: 2.2.0
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 2.7.7
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: A Wrapper for the Companion App Api
178
+ test_files: []