nexus_mods 1.1.0 → 2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +12 -1
- data/examples/api_caching.rb +41 -0
- data/examples/api_limits.rb +12 -0
- data/examples/games.rb +8 -0
- data/examples/log_debug.rb +10 -0
- data/examples/mods.rb +24 -0
- data/lib/nexus_mods/api_client.rb +1 -14
- data/lib/nexus_mods/version.rb +1 -1
- data/lib/nexus_mods.rb +0 -3
- data/spec/nexus_mods_test/helpers.rb +1 -9
- metadata +12 -17
- data/lib/nexus_mods/file_cache.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2325f79c99fb7304d16a0e4b12682229d8187ae30f6d0321796a4d3d5e587fb
|
4
|
+
data.tar.gz: 1cce0b8b4a82f8c903be9d6b7441d739d3b5dffa375c699444e8f2e87af4f2be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d868fa12c0e79385a7377e08ff5c4d4b0ccab2b3c78bedfc99cc9761c7a85c98073260c1409534b2ba8ccebdf74e10f03ddb7dd619901a05c809514798818d9
|
7
|
+
data.tar.gz: 45b7c418a3c0aeeac13b3b6e5d7a2d095513cc96e78e30f9900bee7852ff17389a512afe347fe345b33cd97ab099831cd020ee09c1aab70241f576c980447f5b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# [v2.0.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v1.1.1...v2.0.0) (2023-04-11 10:15:58)
|
2
|
+
|
3
|
+
### Breaking changes
|
4
|
+
|
5
|
+
* [[Breaking] Remove http cache file and useless support for etags as the API does not cache at http level](https://github.com/Muriel-Salvan/nexus_mods/commit/e157524e17ef0ed1a7013f14b15eaa35bb309592)
|
6
|
+
|
7
|
+
# [v1.1.1](https://github.com/Muriel-Salvan/nexus_mods/compare/v1.1.0...v1.1.1) (2023-04-10 19:39:28)
|
8
|
+
|
9
|
+
### Patches
|
10
|
+
|
11
|
+
* [Improved documentation and added examples](https://github.com/Muriel-Salvan/nexus_mods/commit/0eb3fd00e0d1ad34db04a30ab2043a49371dc64f)
|
12
|
+
|
1
13
|
# [v1.1.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v1.0.0...v1.1.0) (2023-04-10 19:19:16)
|
2
14
|
|
3
15
|
### Features
|
data/README.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
Simple Ruby API letting you handle [NexusMods](https://www.nexusmods.com/) REST API.
|
4
4
|
|
5
|
+
## Main features
|
6
|
+
|
7
|
+
* Get the API **limits**.
|
8
|
+
* Get the **games** information.
|
9
|
+
* Get individual **mods** and **mod files** information.
|
10
|
+
* Configurable **caching** with expiry times to save API calls to nexusmods.com.
|
11
|
+
* All served in an object-oriented **API in full Ruby**.
|
12
|
+
|
13
|
+
See the [examples](examples) for more details on how to use it.
|
14
|
+
Those examples expect that you set a valid NexusMods API key in the `NEXUS_MODS_API_KEY` environment variable.
|
15
|
+
|
5
16
|
## Install
|
6
17
|
|
7
18
|
Via gem
|
@@ -54,7 +65,7 @@ Any contribution is welcome:
|
|
54
65
|
|
55
66
|
## Credits
|
56
67
|
|
57
|
-
- [Muriel Salvan]
|
68
|
+
- [Muriel Salvan](https://x-aeon.com/muriel)
|
58
69
|
|
59
70
|
## License
|
60
71
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'nexus_mods'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# Make sure a previous file for caching was not here before
|
5
|
+
test_api_cache_file = 'nexus_mods_test_api_cache.json'
|
6
|
+
FileUtils.rm_f test_api_cache_file
|
7
|
+
|
8
|
+
nexus_mods = NexusMods.new(
|
9
|
+
api_key: ENV.fetch('NEXUS_MODS_API_KEY'),
|
10
|
+
api_cache_file: test_api_cache_file
|
11
|
+
)
|
12
|
+
|
13
|
+
initial_remaining = nexus_mods.api_limits.daily_remaining
|
14
|
+
puts "Before fetching anything, daily API remaining is #{initial_remaining}"
|
15
|
+
|
16
|
+
puts 'Fetch the list of games (without using cache)...'
|
17
|
+
puts "Fetched #{nexus_mods.games.size} games."
|
18
|
+
|
19
|
+
puts "After fetching those games, daily API remaining is #{nexus_mods.api_limits.daily_remaining}"
|
20
|
+
|
21
|
+
puts 'Now we fetch again the list of games (this should use the cache)...'
|
22
|
+
puts "Fetched #{nexus_mods.games.size} games."
|
23
|
+
|
24
|
+
puts "After fetching those games a second time, daily API remaining is #{nexus_mods.api_limits.daily_remaining}"
|
25
|
+
|
26
|
+
puts 'Now we close the current NexusMods instance ad re-instantiate a new one from scratch using the same API cache file'
|
27
|
+
|
28
|
+
new_nexus_mods = NexusMods.new(
|
29
|
+
api_key: ENV.fetch('NEXUS_MODS_API_KEY'),
|
30
|
+
api_cache_file: test_api_cache_file
|
31
|
+
)
|
32
|
+
|
33
|
+
puts "Before fetching anything from the new instance, daily API remaining is #{new_nexus_mods.api_limits.daily_remaining}"
|
34
|
+
|
35
|
+
puts 'Now we fetch the list of games from the new instance (this should use the cache that was stored in the file)...'
|
36
|
+
puts "Fetched #{new_nexus_mods.games.size} games."
|
37
|
+
|
38
|
+
puts "After fetching those games from the new instance, daily API remaining is #{new_nexus_mods.api_limits.daily_remaining}"
|
39
|
+
|
40
|
+
puts
|
41
|
+
puts "As a conclusion, we used 2 instances of NexusMods that have fetched games 3 times, and it consumed #{initial_remaining - new_nexus_mods.api_limits.daily_remaining} real API call."
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'nexus_mods'
|
2
|
+
|
3
|
+
api_limits = NexusMods.new(api_key: ENV.fetch('NEXUS_MODS_API_KEY')).api_limits
|
4
|
+
puts <<~EO_OUTPUT
|
5
|
+
API limits:
|
6
|
+
daily_limit: #{api_limits.daily_limit}
|
7
|
+
daily_remaining: #{api_limits.daily_remaining}
|
8
|
+
daily_reset: #{api_limits.daily_reset}
|
9
|
+
hourly_limit: #{api_limits.hourly_limit}
|
10
|
+
hourly_remaining: #{api_limits.hourly_remaining}
|
11
|
+
hourly_reset: #{api_limits.hourly_reset}
|
12
|
+
EO_OUTPUT
|
data/examples/games.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'nexus_mods'
|
2
|
+
|
3
|
+
games = NexusMods.new(api_key: ENV.fetch('NEXUS_MODS_API_KEY')).games
|
4
|
+
puts "Found a total of #{games.size} games."
|
5
|
+
puts 'Here is the top 10 by number of downloads:'
|
6
|
+
games.sort_by { |game| -game.downloads_count }[0..9].each do |game|
|
7
|
+
puts "* #{game.name} (#{game.mods_count} mods, #{game.downloads_count} downloads)"
|
8
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'nexus_mods'
|
2
|
+
|
3
|
+
puts 'Example of fetching a mod with log debug activated, and without using the cache (so that we always see the query):'
|
4
|
+
puts
|
5
|
+
NexusMods.new(
|
6
|
+
api_key: ENV.fetch('NEXUS_MODS_API_KEY'),
|
7
|
+
log_level: :debug,
|
8
|
+
game_domain_name: 'skyrimspecialedition',
|
9
|
+
mod_id: 42_521
|
10
|
+
).mod(clear_cache: true)
|
data/examples/mods.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'nexus_mods'
|
2
|
+
|
3
|
+
nexus_mods = NexusMods.new(
|
4
|
+
api_key: ENV.fetch('NEXUS_MODS_API_KEY'),
|
5
|
+
game_domain_name: 'skyrimspecialedition'
|
6
|
+
)
|
7
|
+
some_mod_ids = [
|
8
|
+
266,
|
9
|
+
2_347,
|
10
|
+
17_230,
|
11
|
+
42_521
|
12
|
+
]
|
13
|
+
puts 'Here are some details about a few mods for Skyrim Special Edition:'
|
14
|
+
puts
|
15
|
+
some_mod_ids.each do |mod_id|
|
16
|
+
mod = nexus_mods.mod(mod_id:)
|
17
|
+
mod_files = nexus_mods.mod_files(mod_id:)
|
18
|
+
puts <<~EO_OUTPUT
|
19
|
+
===== #{mod.name} (v#{mod.version}) by #{mod.uploader.name} (#{mod.downloads_count} downloads)
|
20
|
+
#{mod.summary}
|
21
|
+
* Last 5 files: #{mod_files.reverse[0..4].map(&:file_name).join(', ')}
|
22
|
+
|
23
|
+
EO_OUTPUT
|
24
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'faraday'
|
3
|
-
require 'faraday-http-cache'
|
4
|
-
require 'nexus_mods/file_cache'
|
5
3
|
require 'nexus_mods/cacheable_api'
|
6
4
|
|
7
5
|
class NexusMods
|
@@ -23,7 +21,6 @@ class NexusMods
|
|
23
21
|
#
|
24
22
|
# Parameters::
|
25
23
|
# * *api_key* (String or nil): The API key to be used, or nil for another authentication [default: nil]
|
26
|
-
# * *http_cache_file* (String): File used to store the HTTP cache, or nil for no cache [default: "#{Dir.tmpdir}/nexus_mods_http_cache.json"]
|
27
24
|
# * *api_cache_expiry* (Hash<Symbol,Integer>): Expiry times in seconds, per expiry key. Possible keys are:
|
28
25
|
# * *games*: Expiry associated to queries on games [default: 1 day]
|
29
26
|
# * *mod*: Expiry associated to queries on mod [default: 1 day]
|
@@ -32,7 +29,6 @@ class NexusMods
|
|
32
29
|
# * *logger* (Logger): The logger to be used for log messages [default: Logger.new(STDOUT)]
|
33
30
|
def initialize(
|
34
31
|
api_key: nil,
|
35
|
-
http_cache_file: "#{Dir.tmpdir}/nexus_mods_http_cache.json",
|
36
32
|
api_cache_expiry: DEFAULT_API_CACHE_EXPIRY,
|
37
33
|
api_cache_file: "#{Dir.tmpdir}/nexus_mods_api_cache.json",
|
38
34
|
logger: Logger.new($stdout)
|
@@ -43,16 +39,7 @@ class NexusMods
|
|
43
39
|
ApiClient.api_client = self
|
44
40
|
@logger = logger
|
45
41
|
# Initialize our HTTP client
|
46
|
-
@
|
47
|
-
@http_client = Faraday.new do |builder|
|
48
|
-
# Indicate that the cache is not shared, meaning that private resources (depending on the session) can be cached as we consider only 1 user is using it for a given file cache.
|
49
|
-
# Use Marshal serializer as some URLs can't get decoded correctly due to UTF-8 issues
|
50
|
-
builder.use :http_cache,
|
51
|
-
store: @http_cache,
|
52
|
-
shared_cache: false,
|
53
|
-
serializer: Marshal
|
54
|
-
builder.adapter Faraday.default_adapter
|
55
|
-
end
|
42
|
+
@http_client = Faraday.new
|
56
43
|
Cacheable.cache_adapter = :persistent_json
|
57
44
|
load_api_cache
|
58
45
|
end
|
data/lib/nexus_mods/version.rb
CHANGED
data/lib/nexus_mods.rb
CHANGED
@@ -39,7 +39,6 @@ class NexusMods
|
|
39
39
|
# * *game_domain_name* (String): Game domain name to query by default [default: 'skyrimspecialedition']
|
40
40
|
# * *mod_id* (Integer): Mod to query by default [default: 1]
|
41
41
|
# * *file_id* (Integer): File to query by default [default: 1]
|
42
|
-
# * *http_cache_file* (String): File used to store the HTTP cache, or nil for no cache [default: "#{Dir.tmpdir}/nexus_mods_http_cache.json"]
|
43
42
|
# * *api_cache_expiry* (Hash<Symbol,Integer>): Expiry times in seconds, per expiry key. Possible keys are:
|
44
43
|
# * *games*: Expiry associated to queries on games [default: 1 day]
|
45
44
|
# * *mod*: Expiry associated to queries on mod [default: 1 day]
|
@@ -52,7 +51,6 @@ class NexusMods
|
|
52
51
|
game_domain_name: 'skyrimspecialedition',
|
53
52
|
mod_id: 1,
|
54
53
|
file_id: 1,
|
55
|
-
http_cache_file: "#{Dir.tmpdir}/nexus_mods_http_cache.json",
|
56
54
|
api_cache_expiry: {},
|
57
55
|
api_cache_file: "#{Dir.tmpdir}/nexus_mods_api_cache.json",
|
58
56
|
logger: Logger.new($stdout),
|
@@ -66,7 +64,6 @@ class NexusMods
|
|
66
64
|
@premium = false
|
67
65
|
@api_client = ApiClient.new(
|
68
66
|
api_key:,
|
69
|
-
http_cache_file:,
|
70
67
|
api_cache_expiry:,
|
71
68
|
api_cache_file:,
|
72
69
|
logger:
|
@@ -56,7 +56,6 @@ module NexusModsTest
|
|
56
56
|
if @nexus_mods.nil?
|
57
57
|
args[:api_key] = MOCKED_API_KEY unless args.key?(:api_key)
|
58
58
|
# By default running tests should not persistent cache files
|
59
|
-
args[:http_cache_file] = nil unless args.key?(:http_cache_file)
|
60
59
|
args[:api_cache_file] = nil unless args.key?(:api_cache_file)
|
61
60
|
# Redirect any log into a string so that they don't pollute the tests output and they could be asserted.
|
62
61
|
@nexus_mods_logger = StringIO.new
|
@@ -115,11 +114,6 @@ module NexusModsTest
|
|
115
114
|
'User-Agent' => "nexus_mods (#{RUBY_PLATFORM}) Ruby/#{RUBY_VERSION}",
|
116
115
|
'apikey' => api_key
|
117
116
|
}
|
118
|
-
if @expected_returned_etags.include? mocked_etag
|
119
|
-
expected_request_headers['If-None-Match'] = mocked_etag
|
120
|
-
else
|
121
|
-
@expected_returned_etags << mocked_etag
|
122
|
-
end
|
123
117
|
@expected_stubs << [
|
124
118
|
stub_request(http_method, "https://#{host}#{path}").with(headers: expected_request_headers).to_return(
|
125
119
|
status: [code, message],
|
@@ -190,9 +184,6 @@ RSpec.configure do |config|
|
|
190
184
|
@nexus_mods = nil
|
191
185
|
# Reload the ApiClient as it stores caches at class level
|
192
186
|
NexusMods::ApiClient.clear_cacheable_expiry_caches
|
193
|
-
# Keep a list of the etags we should have returned, so that we know when queries should contain them
|
194
|
-
# Array<String>
|
195
|
-
@expected_returned_etags = []
|
196
187
|
# List of expected stubs and the number of times they were supposed to mock
|
197
188
|
# Array< [ WebMock::RequestStub, Integer ] >
|
198
189
|
@expected_stubs = []
|
@@ -205,6 +196,7 @@ RSpec.configure do |config|
|
|
205
196
|
config.around do |example|
|
206
197
|
example.call
|
207
198
|
ensure
|
199
|
+
# This would dump the logs in case of debug mode
|
208
200
|
reset_nexus_mods
|
209
201
|
end
|
210
202
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexus_mods
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Muriel Salvan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.7'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: faraday-http-cache
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '2.4'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '2.4'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: cacheable
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,10 +117,20 @@ extra_rdoc_files:
|
|
131
117
|
- CHANGELOG.md
|
132
118
|
- LICENSE.md
|
133
119
|
- README.md
|
120
|
+
- examples/api_caching.rb
|
121
|
+
- examples/api_limits.rb
|
122
|
+
- examples/games.rb
|
123
|
+
- examples/log_debug.rb
|
124
|
+
- examples/mods.rb
|
134
125
|
files:
|
135
126
|
- CHANGELOG.md
|
136
127
|
- LICENSE.md
|
137
128
|
- README.md
|
129
|
+
- examples/api_caching.rb
|
130
|
+
- examples/api_limits.rb
|
131
|
+
- examples/games.rb
|
132
|
+
- examples/log_debug.rb
|
133
|
+
- examples/mods.rb
|
138
134
|
- lib/nexus_mods.rb
|
139
135
|
- lib/nexus_mods/api/api_limits.rb
|
140
136
|
- lib/nexus_mods/api/category.rb
|
@@ -147,7 +143,6 @@ files:
|
|
147
143
|
- lib/nexus_mods/cacheable_with_expiry.rb
|
148
144
|
- lib/nexus_mods/core_extensions/cacheable/cache_adapters/persistent_json_adapter.rb
|
149
145
|
- lib/nexus_mods/core_extensions/cacheable/method_generator.rb
|
150
|
-
- lib/nexus_mods/file_cache.rb
|
151
146
|
- lib/nexus_mods/version.rb
|
152
147
|
- spec/nexus_mods_test/factories/games.rb
|
153
148
|
- spec/nexus_mods_test/factories/mod_files.rb
|
@@ -1,71 +0,0 @@
|
|
1
|
-
class NexusMods
|
2
|
-
|
3
|
-
# Simple key/value file cache
|
4
|
-
class FileCache
|
5
|
-
|
6
|
-
# Constructor
|
7
|
-
#
|
8
|
-
# Parameters::
|
9
|
-
# * *file* (String): File to use as a cache
|
10
|
-
def initialize(file)
|
11
|
-
@file = file
|
12
|
-
@cache_content = File.exist?(file) ? JSON.parse(File.read(file)) : {}
|
13
|
-
end
|
14
|
-
|
15
|
-
# Dump the cache in file
|
16
|
-
def dump
|
17
|
-
File.write(@file, @cache_content.to_json)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Get the cache content as a Hash
|
21
|
-
#
|
22
|
-
# Result::
|
23
|
-
# * Hash<String, Object>: Cache content
|
24
|
-
def to_h
|
25
|
-
@cache_content
|
26
|
-
end
|
27
|
-
|
28
|
-
# Is a given key present in the cache?
|
29
|
-
#
|
30
|
-
# Parameters::
|
31
|
-
# * *key* (String): The key
|
32
|
-
# Result::
|
33
|
-
# * Boolean: Is a given key present in the cache?
|
34
|
-
def key?(key)
|
35
|
-
@cache_content.key?(key)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Read a key from the cache
|
39
|
-
#
|
40
|
-
# Parameters:
|
41
|
-
# * *key* (String): The cache key
|
42
|
-
# Result::
|
43
|
-
# * Object or nil: JSON-serializable object storing the value, or nil in case of cache-miss
|
44
|
-
def read(key)
|
45
|
-
@cache_content.key?(key) ? @cache_content[key] : nil
|
46
|
-
end
|
47
|
-
|
48
|
-
alias [] read
|
49
|
-
|
50
|
-
# Write a key/value in the cache
|
51
|
-
#
|
52
|
-
# Parameters:
|
53
|
-
# * *key* (String): The key
|
54
|
-
# * *value* (Object): JSON-serializable object storing the value
|
55
|
-
def write(key, value)
|
56
|
-
@cache_content[key] = value
|
57
|
-
end
|
58
|
-
|
59
|
-
alias []= write
|
60
|
-
|
61
|
-
# Delete a key in the cache
|
62
|
-
#
|
63
|
-
# Parameters:
|
64
|
-
# * *key* (String): The key
|
65
|
-
def delete(key)
|
66
|
-
@cache_content.delete(key)
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|