diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e62b828 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml new file mode 100644 index 0000000..767f5b9 --- /dev/null +++ b/.github/workflows/ruby.yml @@ -0,0 +1,53 @@ +name: Rake and Deploy + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: ['3.3', '3.4', '4.0'] + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + - name: Run tests + run: bundle exec rake + + deploy: + if: github.ref == 'refs/heads/master' + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + pages: write + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + bundler-cache: true + - name: Generate YARD docs + run: bundle exec yard doc + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./doc + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 9106b2a..b04a8c8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ /pkg/ /spec/reports/ /tmp/ + +# rspec failure tracking +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..d8c0c4a --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,10 @@ +AllCops: + SuggestExtensions: false +plugins: + - rubocop-rake +require: + - standard +Layout/EndOfLine: + EnforcedStyle: lf +inherit_gem: + standard: config/base.yml \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..f4f6c7e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "shopify.ruby-extensions-pack", + "misogi.ruby-rubocop", + "castwide.solargraph" + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4904b7b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,10 @@ +# Code of Conduct + +"xivapi" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.): + +* Participants will be tolerant of opposing views. +* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. +* When interpreting the words and actions of others, participants should always assume good intentions. +* Behaviour which can be reasonably considered harassment will not be tolerated. + +If you have any concerns about behaviour within this project, please contact us on our [Discord server](https://discord.gg/MFFVHWC). diff --git a/Gemfile b/Gemfile index 073f456..9618562 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,15 @@ +# frozen_string_literal: true + source "https://rubygems.org" -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +gem "bundler", ">= 4.0", group: :development +gem "rake", "~> 13.0", group: :development +gem "rspec", "~> 3.0", group: :development +gem "rubocop-rspec", "~> 3.9", group: :development +gem "rubocop-rake", "~> 0.7.1", group: :development +gem "solargraph", ">= 0.58", group: :development +gem "standard", ">= 1.35.1", group: :development +gem "yard", group: :development # Specify your gem's dependencies in xivapi.gemspec gemspec diff --git a/Gemfile.lock b/Gemfile.lock index ef01861..e2d2440 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,40 +1,203 @@ PATH remote: . specs: - xivapi (0.3.3) - rest-client (>= 2.0) + xivapi (0.4.0) + net-http (~> 0.9.1) GEM remote: https://rubygems.org/ specs: - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - http-accept (1.7.0) - http-cookie (1.0.3) - domain_name (~> 0.5) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) - netrc (0.11.0) - rake (10.4.2) - rest-client (2.1.0) - http-accept (>= 1.7.0, < 2.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - yard (0.9.19) + ast (2.4.3) + backport (1.2.0) + benchmark (0.5.0) + diff-lcs (1.6.2) + jaro_winkler (1.7.0) + json (2.19.4) + kramdown (2.5.2) + rexml (>= 3.4.4) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + net-http (0.9.1) + uri (>= 0.11.1) + nokogiri (1.19.3-x64-mingw-ucrt) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-gnu) + racc (~> 1.4) + observer (0.1.2) + open3 (0.2.1) + ostruct (0.6.3) + parallel (1.28.0) + parser (3.3.11.1) + ast (~> 2.4.1) + racc + prism (1.9.0) + racc (1.8.1) + rainbow (3.1.1) + rake (13.4.2) + rbs (3.10.4) + logger + tsort + regexp_parser (2.12.0) + reverse_markdown (3.0.2) + nokogiri + rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.7) + rubocop (1.84.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rake (0.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + rubocop-rspec (3.9.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + ruby-progressbar (1.13.0) + solargraph (0.58.3) + ast (~> 2.4.3) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (>= 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + open3 (~> 0.2.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (>= 3.6.1, <= 4.0.0.dev.4) + reverse_markdown (~> 3.0) + rubocop (~> 1.76) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-activesupport-concern (~> 0.0) + yard-solargraph (~> 0.1) + standard (1.54.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.84.0) + standard-custom (~> 1.0.0) + standard-performance (~> 1.8) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.9.0) + lint_roller (~> 1.1) + rubocop-performance (~> 1.26.0) + thor (1.5.0) + tilt (2.7.0) + tsort (0.2.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + yard (0.9.43) + yard-activesupport-concern (0.0.1) + yard (>= 0.8) + yard-solargraph (0.1.0) + yard (~> 0.9) PLATFORMS - ruby + x64-mingw-ucrt + x86_64-linux DEPENDENCIES - bundler (~> 1.16) - rake (~> 10.0) + bundler (>= 4.0) + rake (~> 13.0) + rspec (~> 3.0) + rubocop-rake (~> 0.7.1) + rubocop-rspec (~> 3.9) + solargraph (>= 0.58) + standard (>= 1.35.1) xivapi! - yard (~> 0.9.19) + yard + +CHECKSUMS + ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 + backport (1.2.0) sha256=912c7dfdd9ee4625d013ddfccb6205c3f92da69a8990f65c440e40f5b2fc7f75 + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 + jaro_winkler (1.7.0) sha256=8daa05a5cd05bf7d27c1c65caa8ab0d47c05419d80210feaa17126db24044ada + json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac + kramdown (2.5.2) sha256=1ba542204c66b6f9111ff00dcc26075b95b220b07f2905d8261740c82f7f02fa + kramdown-parser-gfm (1.1.0) sha256=fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729 + language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc + lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996 + nokogiri (1.19.3-x64-mingw-ucrt) sha256=8bb7132cad356c879a1286eaabcb5e68326cb2490317984280fbc62f456d506a + nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976 + observer (0.1.2) sha256=d8a3107131ba661138d748e7be3dbafc0d82e732fffba9fccb3d7829880950ac + open3 (0.2.1) sha256=8e2d7d2113526351201438c1aa35c8139f0141c9e8913baa007c898973bf3952 + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970 + parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54 + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 + racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f + rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a + rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + rbs (3.10.4) sha256=b17d7c4be4bb31a11a3b529830f0aa206a807ca42f2e7921a3027dfc6b7e5ce8 + regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb + reverse_markdown (3.0.2) sha256=818ebb92ce39dbb1a291690dd1ec9a6d62530d4725296b17e9c8f668f9a5b8af + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 + rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d + rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 + rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c + rubocop (1.84.2) sha256=5692cea54168f3dc8cb79a6fe95c5424b7ea893c707ad7a4307b0585e88dbf5f + rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 + rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 + rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d + rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2 + ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 + solargraph (0.58.3) sha256=debefdc927d1e72383b2c4add89e71373d902b9904617efdb687749509fe2e69 + standard (1.54.0) sha256=7a4b08f83d9893083c8f03bc486f0feeb6a84d48233b40829c03ef4767ea0100 + standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b + standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2 + thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 + tilt (2.7.0) sha256=0d5b9ba69f6a36490c64b0eee9f6e9aad517e20dcc848800a06eb116f08c6ab3 + tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f + unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 + xivapi (0.4.0) + yard (0.9.43) sha256=cf8733a8f0485df2a162927e9b5f182215a61f6d22de096b8f402c726a1c5821 + yard-activesupport-concern (0.0.1) sha256=be790cb0efc23e2e87677063598ac8b743586154657bbd9655a7f03ce78390ef + yard-solargraph (0.1.0) sha256=a19a4619c942181a618fb9458970a9d2534cf7fda69fc43949629a7948a5930e BUNDLED WITH - 1.17.1 + 4.0.6 diff --git a/README.md b/README.md index e0e7b05..51321b3 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,53 @@ -# XIVAPI +# xivapi-ruby -A Ruby library for [XIVAPI](https://www.xivapi.com/). +An asynchronous Ruby client gem for working with [XIVAPI](https://v2.xivapi.com/), providing access to Final Fantasy XIV game data. It lets you fetch, search, and work with FFXIV data using a clean, modern Ruby gem interface. + +If you need help or run into any issues, please [open an issue](https://github.com/xivapi/xivapi-ruby/issues) on GitHub or join the [XIVAPI Discord server](https://discord.gg/MFFVHWC) for support. ## Installation Add this line to your application's Gemfile: ```ruby -gem 'xivapi', git: 'https://github.com/xivapi/xivapi-ruby.git', tag: 'v0.3.3' +gem 'xivapi', git: 'https://github.com/xivapi/xivapi-ruby.git', tag: 'v0.4.0' ``` -And then run: +Then run the following in your terminal: +```bash +bundle install ``` -$ bundle install -``` - -## Documentation - -Full documentation for this library can be found [here](https://xivapi.github.io/xivapi-ruby/). For basic usage information, please see below. ## Usage -Start by initializing a client. You can obtain an API key by signing in to XIVAPI [here](https://www.xivapi.com/app). The key is available under the Dev Account page after signing in. - -```rb -require 'xivapi' - -# Basic configuration -client = XIVAPI::Client.new(api_key: 'abc123') - -# Advanced configuration -client = XIVAPI::Client.new(api_key: 'abc123', language: 'fr') -``` - -Now that you have a client, you can use it to contact the API. Examples have been provided below for the various endpoints. For the full list of endpoints and their parameters, please reference the [request documentation](https://xivapi.github.io/xivapi-ruby/XIVAPI/Request.html). - -### Response data -The data returned from the API is automatically converted into [OpenStruct](https://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html) objects with snake_cased keys. If the request returns multiple results (e.g. character search), it will be provided to you in the form of an `XIVAPI::Paginator` object. The paginator is [Enumerable](https://ruby-doc.org/core-2.4.1/Enumerable.html), allowing you to access the data with methods like `first`, `each`, `map` and `to_a`. - -See the examples below to get a better idea of how to access the data. - -### Examples -#### Search -```rb ->> achievements = client.search(indexes: 'achievement', string: 'tankless') -=> ... ->> achievements.map(&:name) -=> ["A Tankless Job II (Dark Knight)", "A Tankless Job I (Paladin)", "A Tankless Job I (Warrior)", "A Tankless Job II (Warrior)", "A Tankless Job I (Dark Knight)", "A Tankless Job II (Paladin)"] ->> achievements.first.points -=> 10 -``` - -#### Content -```rb ->> client.content -=> ["Achievement", "AchievementCategory", "AchievementKind", ...] - ->> achievement = client.content(name: 'Achievement', limit: 1).first -=> ... ->> achievement.name -=> "To Crush Your Enemies I" - ->> achievements = client.content(name: 'Achievement', ids: 4..5) -=> ... ->> achievements.map(&:name) -=> ["To Crush Your Enemies IV", "To Crush Your Enemies V"] -``` - -#### Servers -```rb ->> client.servers -=> ["Adamantoise", "Aegis", "Alexander", ...] -``` - -#### Character -```rb ->> characters = client.character_search(name: 'raelys skyborn', server: 'behemoth') -=> ... ->> id = characters.first.id -=> 7660136 ->> character = client.character(id: id, all_data: true) -=> ... ->> character.character.name -=> "Raelys Skyborn" ->> character.achievements.list.last.id -=> 692 -``` - -#### Free Company -```rb ->> fcs = client.free_company_search(name: 'lodestone', server: 'behemoth') -=> ... ->> id = fcs.first.id -=> "9234349560946590421" ->> fc = client.free_company(id: id, members: true) -=> ... ->> fc.free_company.name -=> "Lodestone" ->> fc.free_company_members.first.name -=> "Raelys Skyborn" -``` +```ruby +# Client inialization +client = XIVAPI::Client.new +client = XIVAPI::Client.new(language: "jp", version: "7.0", verbose: true) -#### Linkshell -```rb ->> linkshells = client.linkshell_search(name: 'thunderbirds', server: 'behemoth') -=> ... ->> id = linkshells.first.id -=> "21955048183495181" ->> linkshell = client.linkshell(id: id).linkshell -=> ... ->> linkshell.name -=> "Thunderbirds" -``` +# Raw requests +client.request("/sheet/Item/1") +client.request("/sheet/Item/1", language: "jp", version: "7.0") # optional -#### PVP Team -```rb ->> teams = client.pvp_team_search(name: 'kill', server: 'chaos') -=> ... ->> team = client.pvp_team(id: teams.first.id).pvp_team -=> ... ->> team.name -=> "!Kill_For_A_Friend!" -``` - -#### Patch List -```rb ->> patch = client.patch_list.last -=> ... ->> patch.name -=> "Patch 4.4: Prelude In Violet" ->> Time.at(patch.release_date.to_i) -=> 2018-09-18 10:00:00 +0000 +# Or using typed endpoints +sheets = XIVAPI::Sheets.new(client) # ~ must pass the client +sheets.get("Item", 1) ``` ## Development -After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +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. -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). +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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/xivapi/xivapi-ruby. +Bug reports and pull requests are welcome on GitHub at https://github.com/xivapi/xivapi-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/xivapi/xivapi-ruby/blob/main/CODE_OF_CONDUCT.md). ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). + +## Code of Conduct + +Everyone interacting in the Xivapi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/xivapi/xivapi-ruby/blob/main/CODE_OF_CONDUCT.md). diff --git a/Rakefile b/Rakefile index 43022f7..25c166f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,14 @@ +# frozen_string_literal: true + require "bundler/gem_tasks" -task :default => :spec +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +require "rubocop/rake_task" + +RuboCop::RakeTask.new(:standard) do |task| + task.options = ["--require", "standard", "--format", "progress"] +end + +task default: %i[spec standard] diff --git a/bin/console b/bin/console index fc7127f..d00fc3d 100755 --- a/bin/console +++ b/bin/console @@ -1,8 +1,8 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require "bundler/setup" require "xivapi" require "irb" -@client = XIVAPI::Client.new IRB.start(__FILE__) diff --git a/lib/xivapi.rb b/lib/xivapi.rb index 7953fe2..7ab190a 100644 --- a/lib/xivapi.rb +++ b/lib/xivapi.rb @@ -1,59 +1,79 @@ -require 'xivapi/version' +# frozen_string_literal: true -require 'xivapi/errors' -require 'xivapi/http' -require 'xivapi/page' -require 'xivapi/paginator' -require 'xivapi/request' +require_relative "xivapi/version" # The version of the gem -require 'rest-client' -require 'json' -require 'ostruct' +require_relative "xivapi/assets" +require_relative "xivapi/sheets" +require_relative "xivapi/versions" -# Base module for the XIVAPI library -module XIVAPI - include XIVAPI::Errors +require "uri" +require "net/http" +require "json" - # The allowed language options - LANGUAGE_OPTIONS = %w(en ja de fr cn kr).freeze +module XIVAPI + # Known languages supported by the gata data format. + LANGUAGE_CODES = %w[ja en de fr chs cht kr].freeze - # Client for making requests to XIVAPI + # An asynchronous Ruby wrapper for XIVAPI. class Client - include XIVAPI::Request - - # @return [String] The API key - attr_accessor :api_key - - # @return [true, false] Whether or not to query the staging API instead of production - attr_accessor :staging - - # Initializes a new client for querying XIVAPI - # @param api_key [String] API key provided by XIVAPI - # @param language [String] Requested response langauge - # @param staging [true, false] Whether or not to query the staging API instead of production - def initialize(api_key: nil, language: :en, staging: false) - @api_key = api_key + attr_accessor :logger, :verbose, :version + # Initialize a new client for making API requests. + # @param language [String] The supported by the gata data format + # @param version [String] The supported version of the game to use for the API. + # @param verbose [true, false] Whether to enable verbose logging. + def initialize(language = "en", version = "latest", verbose = false) self.language = language - self.staging = staging + @version = version + @verbose = verbose end - # @return [String] The language - def language - @language - end + # @return [String] The supported by the gata data format + attr_reader :language # @param language [String, Symbol] The language to set for the client - # @return The language + # @return The supported by the gata data format def language=(language) lang = language.to_s.downcase - raise ArgumentError, 'Unsupported language' unless LANGUAGE_OPTIONS.include?(lang) + raise ArgumentError, "Unsupported language" unless LANGUAGE_CODES.include?(lang) + @language = lang end - # @return [Hash] The default parameters for the client - def default_params - { private_key: @api_key, language: @language } + # Fetch information about rows and their related data that match the provided search query. + # @param params [Hash] Query paramters accepted by the search endpoint. + # @return [Hash] Response structure for the search endpoint. + def search(params = {}) + request("search", params) + end + + # Make a raw request to the API at the specified path with the specified parameters. + # @param path [String] The path to make the request to, relative to the base API URL. + # @param params [Hash] The query parameters to include in the request. + # @return [Hash, String] The parsed JSON response or a bytestring of the response body if the content type is not JSON. + def request(path, params = {}) + merged = {language: @language, version: @version}.merge(params) + if merged.any? { |k, v| v.nil? } + raise ArgumentError, "Nil values are not allowed in query parameters" + elsif merged[:verbose] == true + raise ArgumentError, "Verbose mode is not supported as a query parameter. Set verbose mode on the client instance instead." + end + + uri = URI.join("https://v2.xivapi.com/api/", path) + uri.query = URI.encode_www_form(merged.compact) unless merged.empty? + puts "=> #{uri}" if @verbose + response = Net::HTTP.get_response(uri) + raise response.error! unless response.is_a?(Net::HTTPSuccess) + + if response.content_type&.include?("application/json") + json = JSON.parse(response.body) + + raise json["error"].to_s if json.is_a?(Hash) && json["error"] + + json + else + response.body + end end end end diff --git a/lib/xivapi/assets.rb b/lib/xivapi/assets.rb new file mode 100644 index 0000000..f133016 --- /dev/null +++ b/lib/xivapi/assets.rb @@ -0,0 +1,27 @@ +module XIVAPI + # Endpoints for accessing game data on a file-by-file basis. Commonly useful for fetching icons or other textures to display on the web. + class Assets + attr_accessor :client + + # Initialize a new Assets client for making API requests. + # @param client [XIVAPI::Client] The client to use for making API + # @return [XIVAPI::Assets] A new Assets client instance. + def initialize(client) + @client = client + end + + # Read an asset from the game at the specified path, converting it into a usable format. If no valid conversion between the game file type and specified format exists, an error will be returned. + # @param params [Hash] Query parameters accepted by the asset endpoint. + # @return [Buffer, String] An image of the specified asset in the specified format. + def get(params = {}) + client.request("asset", params) + end + + # Retrieve the specified map, composing it from split source files if necessary. + # @param params [Hash] Query parameters accepted by the map endpoint. + # @return [Buffer, String] An image of the specified map in the specified format. + def map(territory, index, params = {}) + client.request("asset/map/#{territory}/#{index}", params) + end + end +end diff --git a/lib/xivapi/errors.rb b/lib/xivapi/errors.rb deleted file mode 100644 index 7696e6b..0000000 --- a/lib/xivapi/errors.rb +++ /dev/null @@ -1,24 +0,0 @@ -module XIVAPI - # Custom errors - module Errors - # Standard request error - class RequestError < StandardError - def initialize(response) - if response.headers[:content_type] =~ /json/ - message = JSON.parse(response)['Message'] - else - message = 'Error contacting the API.' - end - - super(message) - end - end - - # Rate limited - class RateLimitError < StandardError - def initialize - super('Too many requests.') - end - end - end -end diff --git a/lib/xivapi/http.rb b/lib/xivapi/http.rb deleted file mode 100644 index d1dd3f3..0000000 --- a/lib/xivapi/http.rb +++ /dev/null @@ -1,75 +0,0 @@ -module XIVAPI - # Makes HTTP request to XIVAPI - module HTTP - # Base URL for XIVAPI - API_BASE = 'https://xivapi.com'.freeze - - # Base URL for the staging environment of XIVAPI - STAGING_API_BASE = 'https://staging.xivapi.com'.freeze - - # Makes a request to XIVAPI - # @param client [XIVAPI::Client] The client making the request - # @param endpoint [String, Symbol] The endpoint to request - # @param params [Hash] Request parameters - # @param payload [Hash] Request body - # @return the results of the request - def request(client, endpoint, params = {}, payload = nil) - url = request_url(client, endpoint) - query_params = params.merge(client.default_params) - .reject { |_, v| v.nil? || v.size == 0 } - - begin - if payload - response = RestClient::Request.execute(method: :get, url: url, headers: { params: query_params }, - payload: payload.to_json) - else - response = RestClient.get(url, params: query_params) - end - - body = JSON.parse(response.body) - objectify(body) - rescue RestClient::ExceptionWithResponse => e - if e.http_code == 429 - raise XIVAPI::RateLimitError.new - else - raise XIVAPI::RequestError.new(e.response) - end - rescue RestClient::Exception => e - raise e - end - end - - private - def request_url(client, endpoint) - "#{client.staging ? STAGING_API_BASE : API_BASE}/#{endpoint}" - end - - def objectify(response) - case response - when Hash - result = {} - - response.each do |key, value| - case value - when Hash, Array - new_value = objectify(value) - else - new_value = value - end - - result[underscore(key)] = new_value - end - - OpenStruct.new(result) - when Array - response.map { |data| objectify(data) } - else - response - end - end - - def underscore(key) - key.gsub('PvPTeam', 'PvpTeam').gsub(/([a-z\d])([A-Z])/,'\1_\2').gsub('.', '_').downcase - end - end -end diff --git a/lib/xivapi/page.rb b/lib/xivapi/page.rb deleted file mode 100644 index 6109cbd..0000000 --- a/lib/xivapi/page.rb +++ /dev/null @@ -1,18 +0,0 @@ -module XIVAPI - # A single page of results from XIVAPI - class Page - # @return the results - attr_reader :results - - # @return [Integer] the page number of the next results - attr_reader :next_page - - # Creates an object for storing a Page of results from XIVAPI - # @param response The response of an HTTP request - def initialize(response) - @results = response.results - pagination = response.pagination - @next_page = pagination.page_next unless pagination.page == pagination.page_total - end - end -end diff --git a/lib/xivapi/paginator.rb b/lib/xivapi/paginator.rb deleted file mode 100644 index bf0106e..0000000 --- a/lib/xivapi/paginator.rb +++ /dev/null @@ -1,40 +0,0 @@ -module XIVAPI - # Paginates XIVAPI results - class Paginator - include Enumerable - include XIVAPI::HTTP - - # @param client [XIVAPI::Client] Client that is sending the request - # @param params [Hash] Query parameters - # @param endpoint [String] API endpoint - # @param limit [Integer] Total number of results to limit to - # @param body [Hash] Request body (for advanced search) - # @param per_page [Integer] Number of results per page, defaults to limit - def initialize(client, params, endpoint, limit, body = nil, per_page = limit) - @client = client - @params = params.merge(limit: per_page) - @endpoint = endpoint - @limit = limit - @body = body - end - - # An enumerator for XIVAPI results - def each - total = 0 - next_page = 1 - - while next_page && total < @limit - page = self.next(next_page) - page.results.take(@limit - total).each { |result| yield result } - next_page = page.next_page - total += page.results.size - end - end - - # The next page in the enumeration of results - def next(page) - response = request(@client, @endpoint, @params.merge(page: page), @body) - Page.new(response) - end - end -end diff --git a/lib/xivapi/request.rb b/lib/xivapi/request.rb deleted file mode 100644 index 8051296..0000000 --- a/lib/xivapi/request.rb +++ /dev/null @@ -1,153 +0,0 @@ -# Collection of requests that can be made to XIVAPI -module XIVAPI::Request - include XIVAPI::HTTP - - # The results limit applied to Lodestone requests - LODESTONE_LIMIT = 50.freeze - - # Options used to retrieve all data when querying a character - ALL_CHARACTER_DATA = 'AC,MIMO,CJ,FR,FC,FCM,PVP'.freeze - - # @param indexes [String, Array ] One or more indexes to search on - # @param string [String] Value to search for in the string column - # @param string_column [String] Column to perform the string search on - # @param string_algo [String] Algorithm to use for the search - # @param sort_field [String] Column to sort results by - # @param sort_order [String] Order to sort results by - # @param limit [Integer] Total number of results to limit to - # @param body [Hash] Request body for advanced ElasticSearch queries - # @param filters [String, Array ] One or more filters to search on - # @param columns [String, Array ] One or more columns to limit results to - # @return [XIVAPI::Paginator] Enumerable search results - def search(indexes: [], string: '', string_column: nil, string_algo: nil, - sort_field: nil, sort_order: nil, limit: 100, body: nil, filters: [], columns: []) - params = { indexes: [*indexes].join(','), string: string, string_column: string_column, string_algo: string_algo, - sort_field: sort_field, sort_order: sort_order, filters: [*filters].join(','), columns: [*columns].join(',') } - XIVAPI::Paginator.new(self, params, 'search', limit, body) - end - - # @param string [String] String of lore to search for - # @param limit [Integer] Total number of results to limit to - # @return [XIVAPI::Paginator] Enumerable lore results - def lore(string: '', limit: 100) - XIVAPI::Paginator.new(self, { string: string }, 'lore', limit) - end - - # @param name [String] Name of the content (e.g. Achievement, Action, Item) - # @param ids [Integer, Array] One or more IDs to retrieve - # @param minify [true, false] Minify resulting data where depth > 1 - # @param limit [Integer] Total number of results to limit to - # @param columns [String, Array ] One or more columns to limit results to - # @return [Array, OpenStruct, XIVAPI::Paginator] - # Calling with no parameters will return the list of content names - # Calling with a name and a single ID will return that specific content - # Calling with a name and not a singe ID will return enumerable results - def content(name: nil, ids: [], minify: false, limit: 100, columns: []) - if name.nil? - request(self, 'content') - elsif [*ids].size == 1 - params = { minify: minify ? 1 : 0, columns: [*columns].join(',') } - request(self, "#{name}/#{[*ids].first}", params) - else - params = { ids: [*ids].join(','), columns: [*columns].join(',') } - XIVAPI::Paginator.new(self, params, name, limit) - end - end - - # @param group [true, false] Group the servers by data center - # @return [Array] List of servers - def servers(group: false) - endpoint = group ? 'servers/dc' : 'servers' - request(self, endpoint) - end - - # @param id [Integer] Character ID - # @param all_data [true, false] Return the full set of character data - # @param extended [true, false] Return additional data for various fields (e.g. name, icon) - # @param data [String, Array ] Additional data to request, see: https://xivapi.com/docs/Character#character - # @param columns [String, Array ] One or more columns to limit results to - # @return [OpenStruct] The requested character - def character(id: nil, all_data: false, extended: false, data: [], columns: []) - params = { data: character_data(all_data, data), columns: [*columns].join(',') } - params[:extended] = 1 if extended - request(self, "character/#{id}", params) - end - - # @param name [String] Character name - # @param server [String] Character server - # @param columns [String, Array ] One or more columns to limit results to - # @return [XIVAPI::Paginator] Enumerable search results - def character_search(name: nil, server: nil, columns: []) - params = { name: name, server: server, columns: [*columns].join(',') } - XIVAPI::Paginator.new(self, params, 'character/search', LODESTONE_LIMIT) - end - - # @param id [Integer] Character ID - # @param token [String] Verification token to check for - # @return [true, false] Whether or not the character is verified - def character_verified?(id: nil, token: nil) - character(id: id, columns: 'Character.Bio').character.bio.match?(token) - end - - # @param id [Integer] Free company ID - # @param members [true, false] Return member data - # @param columns [String, Array ] One or more columns to limit results to - # @return [OpenStruct] The requested free company - def free_company(id: nil, members: false, columns: []) - params = { data: members ? 'FCM' : nil, columns: [*columns].join(',') } - request(self, "freecompany/#{id}", params) - end - - # @param name [String] Free company name - # @param server [String] Free company server - # @param columns [String, Array ] One or more columns to limit results to - # @return [XIVAPI::Paginator] Enumerable search results - def free_company_search(name: nil, server: nil, columns: []) - params = { name: name, server: server, columns: [*columns].join(',') } - XIVAPI::Paginator.new(self, params, 'freecompany/search', LODESTONE_LIMIT) - end - - # @param id [Integer] Linkshell ID - # @param columns [String, Array ] One or more columns to limit results to - # @return [OpenStruct] The requested linkshell - def linkshell(id: nil, columns: []) - params = { columns: [*columns].join(',') } - request(self, "linkshell/#{id}", params) - end - - # @param name [String] Linkshell name - # @param server [String] Linkshell server - # @param columns [String, Array ] One or more columns to limit results to - # @return [XIVAPI::Paginator] Enumerable search results - def linkshell_search(name: nil, server: nil, columns: []) - params = { name: name, server: server, columns: [*columns].join(',') } - XIVAPI::Paginator.new(self, params, 'linkshell/search', LODESTONE_LIMIT) - end - - # @param id [Integer] PVP team ID - # @param columns [String, Array ] One or more columns to limit results to - # @return [OpenStruct] The requested PVP team - def pvp_team(id: nil, columns: []) - params = { columns: [*columns].join(',') } - request(self, "pvpteam/#{id}", params) - end - - # @param name [String] PVP team name - # @param server [String] PVP team server - # @param columns [String, Array ] One or more columns to limit results to - # @return [XIVAPI::Paginator] Enumerable search results - def pvp_team_search(name: nil, server: nil, columns: []) - params = { name: name, server: server, columns: [*columns].join(',') } - XIVAPI::Paginator.new(self, params, 'pvpteam/search', LODESTONE_LIMIT) - end - - # @return [Array] List of game patches - def patch_list - request(self, 'patchlist') - end - - private - def character_data(all_data, data) - all_data ? ALL_CHARACTER_DATA : [*data].join(',') - end -end diff --git a/lib/xivapi/sheets.rb b/lib/xivapi/sheets.rb new file mode 100644 index 0000000..cdd21ad --- /dev/null +++ b/lib/xivapi/sheets.rb @@ -0,0 +1,35 @@ +module XIVAPI + class Sheets + attr_accessor :client + + # Initialize a new Sheets client for making API requests. + # @param client [XIVAPI::Client] The client to use for making API + # @return [XIVAPI::Sheets] A new Sheets client instance. + def initialize(client) + @client = client + end + + # List known excel sheets that can be read by the API. + # @return [Array] The list of known sheets. + def all + client.request("sheet") + end + + # Read information about one or more rows and their related data. + # @param sheet [String] The sheet to fetch the rows from. + # @param params [Hash] The parameters to fetch the rows with. + # @return [Array] A list of rows with typed fields. + def list(sheet, params = {}) + client.request("sheet/#{sheet}", params) + end + + # Read detailed, filterable information from a single sheet row and its related data. + # @param sheet [String] Name of the sheet to read. + # @param row_id [Integer] The ID of the row to read. + # @param params [Hash] The parameters to fetch the row with. + # @return [Hash] The row with typed fields. + def get(sheet, row_id, params = {}) + client.request("sheet/#{sheet}/#{row_id}", params) + end + end +end diff --git a/lib/xivapi/version.rb b/lib/xivapi/version.rb index 32d6666..b56ff87 100644 --- a/lib/xivapi/version.rb +++ b/lib/xivapi/version.rb @@ -1,4 +1,5 @@ +# frozen_string_literal: true + module XIVAPI - # Gem version - VERSION = "0.3.3" + VERSION = "0.4.0" end diff --git a/lib/xivapi/versions.rb b/lib/xivapi/versions.rb new file mode 100644 index 0000000..11fa921 --- /dev/null +++ b/lib/xivapi/versions.rb @@ -0,0 +1,20 @@ +module XIVAPI + # Endpoints for querying metadata about the versions recorded by the boilmaster system. + class Versions + attr_accessor :client + + # Initialize a new Versions client for making API requests. + # @param client [XIVAPI::Client] The client to use for making API + # @return [XIVAPI::Versions] A new Versions client instance. + def initialize(client) + @client = client + end + + # List versions understood by the API. + # @return [Array] The list of known versions. + def all + result = client.request("version") + result["versions"].flat_map { |v| v["names"] } + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..a2a74af --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "xivapi" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/spec/xivapi/assets_spec.rb b/spec/xivapi/assets_spec.rb new file mode 100644 index 0000000..13f73be --- /dev/null +++ b/spec/xivapi/assets_spec.rb @@ -0,0 +1,24 @@ +RSpec.describe XIVAPI::Assets do + let(:client) { XIVAPI::Client.new } + let(:assets) { described_class.new(client) } + + it "fetches an asset" do + result = assets.get( + path: "ui/icon/051000/051474_hr1.tex", + format: "png" + ) + expect(result).to be_a(String) + expect(result.bytesize).to be > 0 + end + + it "fetches a map" do + result = assets.map("s1d1", "00", version: "latest") + expect(result.bytesize).to be > 0 + end + + it "raises on invalid asset" do + expect { + assets.get(path: "invalid/path.tex", format: "png") + }.to raise_error(Net::HTTPClientException) + end +end diff --git a/spec/xivapi/client_spec.rb b/spec/xivapi/client_spec.rb new file mode 100644 index 0000000..973c8a9 --- /dev/null +++ b/spec/xivapi/client_spec.rb @@ -0,0 +1,56 @@ +RSpec.describe XIVAPI::Client do + let(:client) { described_class.new } + + describe "#search" do + it "performs a search request and returns results" do + result = client.search( + query: 'Name="Iron War Axe"', + sheets: "Item", + limit: 3 + ) + + expect(result).to be_a(Hash) + expect(result["results"]).to be_an(Array) + expect(result["results"].length).to be <= 3 + end + + it "supports partial text search" do + result = client.search( + query: 'Name~"sword"', + sheets: "Item", + limit: 3 + ) + + expect(result["results"]).to be_an(Array) + + result["results"].each do |item| + next unless item["fields"]["Name"] + expect(item["fields"]["Name"].downcase).to include("sword") + end + end + + it "raises an error for invalid query syntax" do + expect { + client.search(query: "invalid syntax", sheets: "Item") + }.to raise_error(Net::HTTPClientException) + end + end + + describe "verbose logging" do + it "prints debug output when verbose is enabled" do + client.verbose = true + + expect { + client.search(query: 'Name="Gil"', sheets: "Item", limit: 1) + }.to output(/https:\/\/v2.xivapi.com\/api\/search/i).to_stdout + end + + it "does not print debug output when verbose is disabled" do + client.verbose = false + + expect { + client.search(query: 'Name="Gil"', sheets: "Item", limit: 1) + }.not_to output.to_stdout + end + end +end diff --git a/spec/xivapi/sheets_spec.rb b/spec/xivapi/sheets_spec.rb new file mode 100644 index 0000000..41252c8 --- /dev/null +++ b/spec/xivapi/sheets_spec.rb @@ -0,0 +1,27 @@ +RSpec.describe XIVAPI::Sheets do + let(:client) { XIVAPI::Client.new } + let(:sheets) { described_class.new(client) } + + it "lists all sheets" do + result = sheets.all + expect(result["sheets"]).to be_an(Array) + expect(result["sheets"]).not_to be_empty + end + + it "lists rows" do + result = sheets.list("Item", limit: 5) + expect(result["rows"]).to be_an(Array) + expect(result["rows"]).not_to be_empty + end + + it "gets a row" do + result = sheets.get("Item", 1, fields: "Name") + expect(result["fields"]["Name"]).to eq("Gil") + end + + it "raises for invalid sheet" do + expect { + sheets.list("NotASheet") + }.to raise_error(Net::HTTPClientException) + end +end diff --git a/spec/xivapi/versions_spec.rb b/spec/xivapi/versions_spec.rb new file mode 100644 index 0000000..d8ed94b --- /dev/null +++ b/spec/xivapi/versions_spec.rb @@ -0,0 +1,11 @@ +RSpec.describe XIVAPI::Versions do + let(:client) { XIVAPI::Client.new } + let(:versions) { described_class.new(client) } + + it "fetches versions" do + result = versions.all + expect(result).to be_an(Array) + expect(result).not_to be_empty + expect(result.all? { |v| v.is_a?(String) }).to be true + end +end diff --git a/xivapi.gemspec b/xivapi.gemspec index 2449d76..88ba67c 100644 --- a/xivapi.gemspec +++ b/xivapi.gemspec @@ -1,25 +1,24 @@ +# frozen_string_literal: true -lib = File.expand_path("../lib", __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "xivapi/version" +require_relative "lib/xivapi/version" Gem::Specification.new do |spec| - spec.name = "xivapi" - spec.version = XIVAPI::VERSION - spec.authors = ["Matt Antonelli"] - spec.email = ["contact@raelys.com"] + spec.name = "xivapi" + spec.version = XIVAPI::VERSION + spec.authors = ["Matt Antonelli"] + spec.email = ["matt@antonelli.dev"] - spec.summary = %q{A Ruby library for XIVAPI} - spec.description = %q{A Ruby library for XIVAPI (http://www.xivapi.com)} - spec.homepage = "https://github.com/xivapi/xivapi-ruby" - spec.license = "MIT" + spec.summary = "An asynchronous Ruby client gem for working with XIVAPI" + spec.description = "An asynchronous Ruby client gem for working with XIVAPI" + spec.homepage = "https://github.com/xivapi/xivapi-ruby" + spec.license = "MIT" + spec.required_ruby_version = ">= 3.3.0" - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + spec.require_paths = ["lib"] + spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end - spec.add_development_dependency "bundler", "~> 1.16" - spec.add_development_dependency "rake", "~> 10.0" - spec.add_development_dependency "yard", "~> 0.9.19" - spec.add_dependency "rest-client", ">= 2.0" + spec.add_dependency "net-http", "~> 0.9.1" + spec.metadata["rubygems_mfa_required"] = "true" end