nexus_mods 2.3.0 → 2.5.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/lib/nexus_mods/api/api_limits.rb +6 -1
- data/lib/nexus_mods/api/category.rb +6 -1
- data/lib/nexus_mods/api/game.rb +6 -1
- data/lib/nexus_mods/api/mod.rb +14 -1
- data/lib/nexus_mods/api/mod_file.rb +24 -1
- data/lib/nexus_mods/api/mod_updates.rb +27 -1
- data/lib/nexus_mods/api/resource.rb +20 -0
- data/lib/nexus_mods/api/user.rb +6 -1
- data/lib/nexus_mods/version.rb +1 -1
- data/lib/nexus_mods.rb +94 -2
- data/spec/nexus_mods_test/scenarios/nexus_mods/api/mod_file_spec.rb +232 -45
- data/spec/nexus_mods_test/scenarios/nexus_mods/api/mod_spec.rb +245 -57
- data/spec/nexus_mods_test/scenarios/nexus_mods/api/mod_updates_spec.rb +28 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f6c2d6e72c966955e6153a3381458fba4f56c36deac811d125edc962046b5a0
|
|
4
|
+
data.tar.gz: d0a2ec92da860dbb53b608e1de22762b3562bac1d4fe1333066f868612983747
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d159a8824c0c6b723ec7f3361f1df52162cebf3d4f91833e072ceeb94089fb84d102d46d597d0ee24e92c6983e56ddcd87a95a6a4ffbb282331a7f3aae94cc84
|
|
7
|
+
data.tar.gz: 9dda81c7f0f593190547ed55aeff36a3040610d13ee59f7c5ff6ffed7df1d95d806685d9cf77e090e3a2410a24a516a32617b9fc047d59cb30f149607022aaba
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [v2.5.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v2.4.0...v2.5.0) (2023-04-12 19:15:44)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* [[Feature] Add links between resources](https://github.com/Muriel-Salvan/nexus_mods/commit/84c474b75f3ee1e4ac0c8bbda0d2ad25022f1f07)
|
|
6
|
+
|
|
7
|
+
# [v2.4.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v2.3.0...v2.4.0) (2023-04-12 17:23:29)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* [[Feature] Add options and methods for mod and mod_files to query for updates to check data freshness](https://github.com/Muriel-Salvan/nexus_mods/commit/72a933a6ac22faae601fbdc547cc9e0ffa8908fc)
|
|
12
|
+
|
|
1
13
|
# [v2.3.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v2.2.0...v2.3.0) (2023-04-12 15:08:22)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
4
6
|
|
|
5
7
|
# Object giving the NexusMods API limits
|
|
6
|
-
class ApiLimits
|
|
8
|
+
class ApiLimits < Resource
|
|
7
9
|
|
|
8
10
|
attr_reader(
|
|
9
11
|
*%i[
|
|
@@ -19,6 +21,7 @@ class NexusMods
|
|
|
19
21
|
# Constructor
|
|
20
22
|
#
|
|
21
23
|
# Parameters::
|
|
24
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
22
25
|
# * *daily_limit* (Integer): The daily limit
|
|
23
26
|
# * *daily_remaining* (Integer): The daily remaining
|
|
24
27
|
# * *daily_reset* (Integer): The daily reset time
|
|
@@ -26,6 +29,7 @@ class NexusMods
|
|
|
26
29
|
# * *hourly_remaining* (Integer): The hourly remaining
|
|
27
30
|
# * *hourly_reset* (Integer): The hourly reset time
|
|
28
31
|
def initialize(
|
|
32
|
+
nexus_mods:,
|
|
29
33
|
daily_limit:,
|
|
30
34
|
daily_remaining:,
|
|
31
35
|
daily_reset:,
|
|
@@ -33,6 +37,7 @@ class NexusMods
|
|
|
33
37
|
hourly_remaining:,
|
|
34
38
|
hourly_reset:
|
|
35
39
|
)
|
|
40
|
+
super(nexus_mods:)
|
|
36
41
|
@daily_limit = daily_limit
|
|
37
42
|
@daily_remaining = daily_remaining
|
|
38
43
|
@daily_reset = daily_reset
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
4
6
|
|
|
5
7
|
# Categories defined for a game in NexusMods
|
|
6
|
-
class Category
|
|
8
|
+
class Category < Resource
|
|
7
9
|
|
|
8
10
|
attr_reader(
|
|
9
11
|
*%i[
|
|
@@ -21,14 +23,17 @@ class NexusMods
|
|
|
21
23
|
# Constructor
|
|
22
24
|
#
|
|
23
25
|
# Parameters::
|
|
26
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
24
27
|
# *id* (Integer): The category id
|
|
25
28
|
# *name* (String): The category id
|
|
26
29
|
# *parent_category* (Category or nil): The parent category, or nil if none [default: nil]
|
|
27
30
|
def initialize(
|
|
31
|
+
nexus_mods:,
|
|
28
32
|
id:,
|
|
29
33
|
name:,
|
|
30
34
|
parent_category: nil
|
|
31
35
|
)
|
|
36
|
+
super(nexus_mods:)
|
|
32
37
|
@id = id
|
|
33
38
|
@name = name
|
|
34
39
|
@parent_category = parent_category
|
data/lib/nexus_mods/api/game.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
@@ -5,7 +7,7 @@ class NexusMods
|
|
|
5
7
|
# A NexusMods game.
|
|
6
8
|
# Attributes info can be taken from there:
|
|
7
9
|
# * https://github.com/Nexus-Mods/node-nexus-api/blob/master/docs/interfaces/_types_.igameinfo.md
|
|
8
|
-
class Game
|
|
10
|
+
class Game < Resource
|
|
9
11
|
|
|
10
12
|
attr_reader(
|
|
11
13
|
*%i[
|
|
@@ -29,6 +31,7 @@ class NexusMods
|
|
|
29
31
|
# Constructor
|
|
30
32
|
#
|
|
31
33
|
# Parameters::
|
|
34
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
32
35
|
# * *id* (Integer): The game's id
|
|
33
36
|
# * *name* (String): The game's name
|
|
34
37
|
# * *forum_url* (String): The game's forum's URL
|
|
@@ -44,6 +47,7 @@ class NexusMods
|
|
|
44
47
|
# * *mods_count* (Integer): The game's mods' count [default: 0]
|
|
45
48
|
# * *categories* (Array<Category>): The list of game's categories [default: []]
|
|
46
49
|
def initialize(
|
|
50
|
+
nexus_mods:,
|
|
47
51
|
id:,
|
|
48
52
|
name:,
|
|
49
53
|
forum_url:,
|
|
@@ -59,6 +63,7 @@ class NexusMods
|
|
|
59
63
|
mods_count: 0,
|
|
60
64
|
categories: []
|
|
61
65
|
)
|
|
66
|
+
super(nexus_mods:)
|
|
62
67
|
@id = id
|
|
63
68
|
@name = name
|
|
64
69
|
@forum_url = forum_url
|
data/lib/nexus_mods/api/mod.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
@@ -5,7 +7,7 @@ class NexusMods
|
|
|
5
7
|
# A NexusMods mod.
|
|
6
8
|
# Attributes info can be taken from there:
|
|
7
9
|
# * https://github.com/Nexus-Mods/node-nexus-api/blob/master/docs/interfaces/_types_.imodinfo.md
|
|
8
|
-
class Mod
|
|
10
|
+
class Mod < Resource
|
|
9
11
|
|
|
10
12
|
attr_reader(
|
|
11
13
|
*%i[
|
|
@@ -36,6 +38,7 @@ class NexusMods
|
|
|
36
38
|
# Constructor
|
|
37
39
|
#
|
|
38
40
|
# Parameters::
|
|
41
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
39
42
|
# * *uid* (Integer): The mod's uid
|
|
40
43
|
# * *mod_id* (Integer): The mod's id
|
|
41
44
|
# * *game_id* (Integer): The mod's game id
|
|
@@ -58,6 +61,7 @@ class NexusMods
|
|
|
58
61
|
# * *unique_downloads_count* (Integer): The mod's unique downloads' count [default: 0]
|
|
59
62
|
# * *endorsements_count* (Integer): The mod's endorsements' count [default: 0]
|
|
60
63
|
def initialize(
|
|
64
|
+
nexus_mods:,
|
|
61
65
|
uid:,
|
|
62
66
|
mod_id:,
|
|
63
67
|
game_id:,
|
|
@@ -80,6 +84,7 @@ class NexusMods
|
|
|
80
84
|
unique_downloads_count: 0,
|
|
81
85
|
endorsements_count: 0
|
|
82
86
|
)
|
|
87
|
+
super(nexus_mods:)
|
|
83
88
|
@uid = uid
|
|
84
89
|
@mod_id = mod_id
|
|
85
90
|
@game_id = game_id
|
|
@@ -134,6 +139,14 @@ class NexusMods
|
|
|
134
139
|
@endorsements_count == other.endorsements_count
|
|
135
140
|
end
|
|
136
141
|
|
|
142
|
+
# Get associated files information
|
|
143
|
+
#
|
|
144
|
+
# Result::
|
|
145
|
+
# * Array<ModFile>: The list of mod files information
|
|
146
|
+
def files
|
|
147
|
+
@nexus_mods.mod_files(game_domain_name: domain_name, mod_id:)
|
|
148
|
+
end
|
|
149
|
+
|
|
137
150
|
end
|
|
138
151
|
|
|
139
152
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
@@ -5,10 +7,12 @@ class NexusMods
|
|
|
5
7
|
# A NexusMods file.
|
|
6
8
|
# Attributes info can be taken from there:
|
|
7
9
|
# * https://github.com/Nexus-Mods/node-nexus-api/blob/master/docs/interfaces/_types_.ifileinfo.md
|
|
8
|
-
class ModFile
|
|
10
|
+
class ModFile < Resource
|
|
9
11
|
|
|
10
12
|
attr_reader(
|
|
11
13
|
*%i[
|
|
14
|
+
game_domain_name
|
|
15
|
+
mod_id
|
|
12
16
|
ids
|
|
13
17
|
uid
|
|
14
18
|
id
|
|
@@ -43,6 +47,9 @@ class NexusMods
|
|
|
43
47
|
# Constructor
|
|
44
48
|
#
|
|
45
49
|
# Parameters::
|
|
50
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
51
|
+
# * *game_domain_name* (String): The game this file belongs to
|
|
52
|
+
# * *mod_id* (Integer): The mod id this file belongs to
|
|
46
53
|
# * *ids* (Array<Integer>): The file's list of IDs
|
|
47
54
|
# * *uid* (Integer): The file's UID
|
|
48
55
|
# * *id* (Integer): The file's main ID
|
|
@@ -60,6 +67,9 @@ class NexusMods
|
|
|
60
67
|
# * *changelog_html* (String): The file's change log in HTML
|
|
61
68
|
# * *content_preview_url* (String): URL to a JSON that gives info on the file's content
|
|
62
69
|
def initialize(
|
|
70
|
+
nexus_mods:,
|
|
71
|
+
game_domain_name:,
|
|
72
|
+
mod_id:,
|
|
63
73
|
ids:,
|
|
64
74
|
uid:,
|
|
65
75
|
id:,
|
|
@@ -77,6 +87,9 @@ class NexusMods
|
|
|
77
87
|
changelog_html:,
|
|
78
88
|
content_preview_url:
|
|
79
89
|
)
|
|
90
|
+
super(nexus_mods:)
|
|
91
|
+
@game_domain_name = game_domain_name
|
|
92
|
+
@mod_id = mod_id
|
|
80
93
|
@ids = ids
|
|
81
94
|
@uid = uid
|
|
82
95
|
@id = id
|
|
@@ -105,6 +118,8 @@ class NexusMods
|
|
|
105
118
|
# * Boolean: Are objects equal?
|
|
106
119
|
def ==(other)
|
|
107
120
|
other.is_a?(ModFile) &&
|
|
121
|
+
@game_domain_name == other.game_domain_name &&
|
|
122
|
+
@mod_id == other.mod_id &&
|
|
108
123
|
@ids == other.ids &&
|
|
109
124
|
@uid == other.uid &&
|
|
110
125
|
@id == other.id &&
|
|
@@ -123,6 +138,14 @@ class NexusMods
|
|
|
123
138
|
@content_preview_url == other.content_preview_url
|
|
124
139
|
end
|
|
125
140
|
|
|
141
|
+
# Get associated mod information
|
|
142
|
+
#
|
|
143
|
+
# Result::
|
|
144
|
+
# * Mod: The corresponding mod
|
|
145
|
+
def mod
|
|
146
|
+
@nexus_mods.mod(game_domain_name:, mod_id:)
|
|
147
|
+
end
|
|
148
|
+
|
|
126
149
|
end
|
|
127
150
|
|
|
128
151
|
end
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
4
6
|
|
|
5
7
|
# A NexusMods mod updates.
|
|
6
|
-
class ModUpdates
|
|
8
|
+
class ModUpdates < Resource
|
|
7
9
|
|
|
8
10
|
attr_reader(
|
|
9
11
|
*%i[
|
|
12
|
+
game_domain_name
|
|
10
13
|
mod_id
|
|
11
14
|
latest_file_update
|
|
12
15
|
latest_mod_activity
|
|
@@ -16,14 +19,20 @@ class NexusMods
|
|
|
16
19
|
# Constructor
|
|
17
20
|
#
|
|
18
21
|
# Parameters::
|
|
22
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
23
|
+
# * *game_domain_name* (String): The game this file belongs to
|
|
19
24
|
# * *mod_id* (Integer): The mod's id
|
|
20
25
|
# * *latest_file_update* (Time): The mod's latest file update
|
|
21
26
|
# * *latest_mod_activity* (Time): The mod's latest activity
|
|
22
27
|
def initialize(
|
|
28
|
+
nexus_mods:,
|
|
29
|
+
game_domain_name:,
|
|
23
30
|
mod_id:,
|
|
24
31
|
latest_file_update:,
|
|
25
32
|
latest_mod_activity:
|
|
26
33
|
)
|
|
34
|
+
super(nexus_mods:)
|
|
35
|
+
@game_domain_name = game_domain_name
|
|
27
36
|
@mod_id = mod_id
|
|
28
37
|
@latest_file_update = latest_file_update
|
|
29
38
|
@latest_mod_activity = latest_mod_activity
|
|
@@ -37,11 +46,28 @@ class NexusMods
|
|
|
37
46
|
# * Boolean: Are objects equal?
|
|
38
47
|
def ==(other)
|
|
39
48
|
other.is_a?(ModUpdates) &&
|
|
49
|
+
@game_domain_name == game_domain_name &&
|
|
40
50
|
@mod_id == other.mod_id &&
|
|
41
51
|
@latest_file_update == other.latest_file_update &&
|
|
42
52
|
@latest_mod_activity == other.latest_mod_activity
|
|
43
53
|
end
|
|
44
54
|
|
|
55
|
+
# Get associated mod information
|
|
56
|
+
#
|
|
57
|
+
# Result::
|
|
58
|
+
# * Mod: The corresponding mod
|
|
59
|
+
def mod
|
|
60
|
+
@nexus_mods.mod(game_domain_name:, mod_id:)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get associated mod files information
|
|
64
|
+
#
|
|
65
|
+
# Result::
|
|
66
|
+
# * Array<ModFile>: The corresponding mod files
|
|
67
|
+
def mod_files
|
|
68
|
+
@nexus_mods.mod_files(game_domain_name:, mod_id:)
|
|
69
|
+
end
|
|
70
|
+
|
|
45
71
|
end
|
|
46
72
|
|
|
47
73
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class NexusMods
|
|
2
|
+
|
|
3
|
+
module Api
|
|
4
|
+
|
|
5
|
+
# Base class for any API resource
|
|
6
|
+
class Resource
|
|
7
|
+
|
|
8
|
+
# Constructor
|
|
9
|
+
#
|
|
10
|
+
# Parameters::
|
|
11
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
12
|
+
def initialize(nexus_mods:)
|
|
13
|
+
@nexus_mods = nexus_mods
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
data/lib/nexus_mods/api/user.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
require 'nexus_mods/api/resource'
|
|
2
|
+
|
|
1
3
|
class NexusMods
|
|
2
4
|
|
|
3
5
|
module Api
|
|
4
6
|
|
|
5
7
|
# A user on NExusMods.
|
|
6
8
|
# Mainly used for uploaders information.
|
|
7
|
-
class User
|
|
9
|
+
class User < Resource
|
|
8
10
|
|
|
9
11
|
attr_reader(
|
|
10
12
|
*%i[
|
|
@@ -18,16 +20,19 @@ class NexusMods
|
|
|
18
20
|
# Constructor
|
|
19
21
|
#
|
|
20
22
|
# Parameters::
|
|
23
|
+
# * *nexus_mods* (NexusMods): The NexusMods API instance that the resource can use to query for other resources
|
|
21
24
|
# * *member_id* (Integer): The user's member id
|
|
22
25
|
# * *member_group_id* (Integer): The user's member group id
|
|
23
26
|
# * *name* (String): The user's name
|
|
24
27
|
# * *profile_url* (String): The user's profile URL
|
|
25
28
|
def initialize(
|
|
29
|
+
nexus_mods:,
|
|
26
30
|
member_id:,
|
|
27
31
|
member_group_id:,
|
|
28
32
|
name:,
|
|
29
33
|
profile_url:
|
|
30
34
|
)
|
|
35
|
+
super(nexus_mods:)
|
|
31
36
|
@member_id = member_id
|
|
32
37
|
@member_group_id = member_group_id
|
|
33
38
|
@name = name
|
data/lib/nexus_mods/version.rb
CHANGED
data/lib/nexus_mods.rb
CHANGED
|
@@ -88,6 +88,7 @@ class NexusMods
|
|
|
88
88
|
def api_limits
|
|
89
89
|
api_limits_headers = @api_client.http('users/validate').headers
|
|
90
90
|
Api::ApiLimits.new(
|
|
91
|
+
nexus_mods: self,
|
|
91
92
|
daily_limit: Integer(api_limits_headers['x-rl-daily-limit']),
|
|
92
93
|
daily_remaining: Integer(api_limits_headers['x-rl-daily-remaining']),
|
|
93
94
|
daily_reset: Time.parse(api_limits_headers['x-rl-daily-reset']).utc,
|
|
@@ -113,6 +114,7 @@ class NexusMods
|
|
|
113
114
|
category_id,
|
|
114
115
|
[
|
|
115
116
|
Api::Category.new(
|
|
117
|
+
nexus_mods: self,
|
|
116
118
|
id: category_id,
|
|
117
119
|
name: category_json['name']
|
|
118
120
|
),
|
|
@@ -125,6 +127,7 @@ class NexusMods
|
|
|
125
127
|
category.parent_category = categories[parent_category_id]&.first if parent_category_id
|
|
126
128
|
end
|
|
127
129
|
Api::Game.new(
|
|
130
|
+
nexus_mods: self,
|
|
128
131
|
id: game_json['id'],
|
|
129
132
|
name: game_json['name'],
|
|
130
133
|
forum_url: game_json['forum_url'],
|
|
@@ -166,11 +169,16 @@ class NexusMods
|
|
|
166
169
|
# * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
|
|
167
170
|
# * *mod_id* (Integer): The mod ID [default: @mod_id]
|
|
168
171
|
# * *clear_cache* (Boolean): Should we clear the API cache for this resource? [default: false]
|
|
172
|
+
# * *check_updates* (Boolean): Should we check updates?
|
|
173
|
+
# If yes then an extra call to updated_mods may be done to check for updates before retrieving the mod information.
|
|
174
|
+
# In case the mod was previously retrieved and may be in an old cache, then using this will optimize the calls to NexusMods API to the minimum.
|
|
169
175
|
# Result::
|
|
170
176
|
# * Mod: Mod information
|
|
171
|
-
def mod(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false)
|
|
177
|
+
def mod(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false, check_updates: false)
|
|
178
|
+
mod_cache_up_to_date?(game_domain_name:, mod_id:) if check_updates
|
|
172
179
|
mod_json = @api_client.api("games/#{game_domain_name}/mods/#{mod_id}", clear_cache:)
|
|
173
180
|
Api::Mod.new(
|
|
181
|
+
nexus_mods: self,
|
|
174
182
|
uid: mod_json['uid'],
|
|
175
183
|
mod_id: mod_json['mod_id'],
|
|
176
184
|
game_id: mod_json['game_id'],
|
|
@@ -185,6 +193,7 @@ class NexusMods
|
|
|
185
193
|
status: mod_json['status'],
|
|
186
194
|
available: mod_json['available'],
|
|
187
195
|
uploader: Api::User.new(
|
|
196
|
+
nexus_mods: self,
|
|
188
197
|
member_id: mod_json['user']['member_id'],
|
|
189
198
|
member_group_id: mod_json['user']['member_group_id'],
|
|
190
199
|
name: mod_json['user']['name'],
|
|
@@ -228,11 +237,18 @@ class NexusMods
|
|
|
228
237
|
# * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
|
|
229
238
|
# * *mod_id* (Integer): The mod ID [default: @mod_id]
|
|
230
239
|
# * *clear_cache* (Boolean): Should we clear the API cache for this resource? [default: false]
|
|
240
|
+
# * *check_updates* (Boolean): Should we check updates?
|
|
241
|
+
# If yes then an extra call to updated_mods may be done to check for updates before retrieving the mod information.
|
|
242
|
+
# In case the mod files were previously retrieved and may be in an old cache, then using this will optimize the calls to NexusMods API to the minimum.
|
|
231
243
|
# Result::
|
|
232
244
|
# * Array<ModFile>: List of mod's files
|
|
233
|
-
def mod_files(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false)
|
|
245
|
+
def mod_files(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false, check_updates: false)
|
|
246
|
+
mod_files_cache_up_to_date?(game_domain_name:, mod_id:) if check_updates
|
|
234
247
|
@api_client.api("games/#{game_domain_name}/mods/#{mod_id}/files", clear_cache:)['files'].map do |file_json|
|
|
235
248
|
Api::ModFile.new(
|
|
249
|
+
nexus_mods: self,
|
|
250
|
+
game_domain_name:,
|
|
251
|
+
mod_id:,
|
|
236
252
|
ids: file_json['id'],
|
|
237
253
|
uid: file_json['uid'],
|
|
238
254
|
id: file_json['file_id'],
|
|
@@ -290,6 +306,8 @@ class NexusMods
|
|
|
290
306
|
def updated_mods(game_domain_name: @game_domain_name, since: :one_day, clear_cache: false)
|
|
291
307
|
@api_client.api("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since), clear_cache:).map do |updated_mod_json|
|
|
292
308
|
Api::ModUpdates.new(
|
|
309
|
+
nexus_mods: self,
|
|
310
|
+
game_domain_name:,
|
|
293
311
|
mod_id: updated_mod_json['mod_id'],
|
|
294
312
|
latest_file_update: Time.at(updated_mod_json['latest_file_update']).utc,
|
|
295
313
|
latest_mod_activity: Time.at(updated_mod_json['latest_mod_activity']).utc
|
|
@@ -327,6 +345,80 @@ class NexusMods
|
|
|
327
345
|
@api_client.set_api_cache_timestamp("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since), cache_timestamp:)
|
|
328
346
|
end
|
|
329
347
|
|
|
348
|
+
# Does a given mod id have fresh information in our cache?
|
|
349
|
+
# This may fire queries to the updated mods API to get info from NexusMods about the latest updated mods.
|
|
350
|
+
# If we know the mod is up-to-date, then its mod information cache timestamp will be set to the time when we checked for updates if it was greater than the cache date.
|
|
351
|
+
#
|
|
352
|
+
# Here is the algorithm:
|
|
353
|
+
# If it is not in the cache, then it is not up-to-date.
|
|
354
|
+
# Otherwise, the API allows us to know if it has been updated up to 1 month in the past.
|
|
355
|
+
# Therefore if the current cache timestamp is older than 1 month, assume that it has to be updated.
|
|
356
|
+
# Otherwise query the API to know the latest updated mods since 1 month:
|
|
357
|
+
# * If the mod ID is not there, then it is up-to-date.
|
|
358
|
+
# * If the mod ID is there, then check if our cache timestamp is older than the last update timestamp from NexusMods.
|
|
359
|
+
#
|
|
360
|
+
# Parameters::
|
|
361
|
+
# * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
|
|
362
|
+
# * *mod_id* (Integer): The mod ID [default: @mod_id]
|
|
363
|
+
# Result::
|
|
364
|
+
# * Boolean: Is the mod cache up-to-date?
|
|
365
|
+
def mod_cache_up_to_date?(game_domain_name: @game_domain_name, mod_id: @mod_id)
|
|
366
|
+
existing_cache_timestamp = mod_cache_timestamp(game_domain_name:, mod_id:)
|
|
367
|
+
mod_up_to_date =
|
|
368
|
+
if existing_cache_timestamp.nil? || existing_cache_timestamp < Time.now - (30 * 24 * 60 * 60)
|
|
369
|
+
# It's not in the cache
|
|
370
|
+
# or it's older than 1 month
|
|
371
|
+
false
|
|
372
|
+
else
|
|
373
|
+
found_mod_updates = updated_mods(game_domain_name:, since: :one_month).find { |mod_updates| mod_updates.mod_id == mod_id }
|
|
374
|
+
# true if it has not been updated on NexusMods since 1 month
|
|
375
|
+
# or our cache timestamp is more recent
|
|
376
|
+
found_mod_updates.nil? || found_mod_updates.latest_mod_activity < existing_cache_timestamp
|
|
377
|
+
end
|
|
378
|
+
if mod_up_to_date
|
|
379
|
+
update_time = updated_mods_cache_timestamp(game_domain_name:, since: :one_month)
|
|
380
|
+
set_mod_cache_timestamp(cache_timestamp: update_time, game_domain_name:, mod_id:) if update_time > existing_cache_timestamp
|
|
381
|
+
end
|
|
382
|
+
mod_up_to_date
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Does a given mod id have fresh files information in our cache?
|
|
386
|
+
# This may fire queries to the updated mods API to get info from NexusMods about the latest updated mods.
|
|
387
|
+
# If we know the mod is up-to-date, then its mod information cache timestamp will be set to the time when we checked for updates if it was greater than the cache date.
|
|
388
|
+
#
|
|
389
|
+
# Here is the algorithm:
|
|
390
|
+
# If it is not in the cache, then it is not up-to-date.
|
|
391
|
+
# Otherwise, the API allows us to know if it has been updated up to 1 month in the past.
|
|
392
|
+
# Therefore if the current cache timestamp is older than 1 month, assume that it has to be updated.
|
|
393
|
+
# Otherwise query the API to know the latest updated mods since 1 month:
|
|
394
|
+
# * If the mod ID is not there, then it is up-to-date.
|
|
395
|
+
# * If the mod ID is there, then check if our cache timestamp is older than the last update timestamp from NexusMods.
|
|
396
|
+
#
|
|
397
|
+
# Parameters::
|
|
398
|
+
# * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
|
|
399
|
+
# * *mod_id* (Integer): The mod ID [default: @mod_id]
|
|
400
|
+
# Result::
|
|
401
|
+
# * Boolean: Is the mod cache up-to-date?
|
|
402
|
+
def mod_files_cache_up_to_date?(game_domain_name: @game_domain_name, mod_id: @mod_id)
|
|
403
|
+
existing_cache_timestamp = mod_files_cache_timestamp(game_domain_name:, mod_id:)
|
|
404
|
+
mod_up_to_date =
|
|
405
|
+
if existing_cache_timestamp.nil? || existing_cache_timestamp < Time.now - (30 * 24 * 60 * 60)
|
|
406
|
+
# It's not in the cache
|
|
407
|
+
# or it's older than 1 month
|
|
408
|
+
false
|
|
409
|
+
else
|
|
410
|
+
found_mod_updates = updated_mods(game_domain_name:, since: :one_month).find { |mod_updates| mod_updates.mod_id == mod_id }
|
|
411
|
+
# true if it has not been updated on NexusMods since 1 month
|
|
412
|
+
# or our cache timestamp is more recent
|
|
413
|
+
found_mod_updates.nil? || found_mod_updates.latest_file_update < existing_cache_timestamp
|
|
414
|
+
end
|
|
415
|
+
if mod_up_to_date
|
|
416
|
+
update_time = updated_mods_cache_timestamp(game_domain_name:, since: :one_month)
|
|
417
|
+
set_mod_files_cache_timestamp(cache_timestamp: update_time, game_domain_name:, mod_id:) if update_time > existing_cache_timestamp
|
|
418
|
+
end
|
|
419
|
+
mod_up_to_date
|
|
420
|
+
end
|
|
421
|
+
|
|
330
422
|
private
|
|
331
423
|
|
|
332
424
|
# Get the URL parameters from the required period
|
|
@@ -25,60 +25,55 @@ describe NexusMods::Api::ModFile do
|
|
|
25
25
|
expect_mod_file_to_be2487(sorted_mod_files[1])
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
expect_http_call_to(
|
|
30
|
-
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
31
|
-
json: { files: json_example_mod_files }
|
|
32
|
-
)
|
|
33
|
-
expect_mod_files_to_be_example(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014))
|
|
34
|
-
end
|
|
28
|
+
context 'when testing a mod with 2 files' do
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
30
|
+
before do
|
|
31
|
+
expect_http_call_to(
|
|
32
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
33
|
+
json: { 'files' => json_example_mod_files }
|
|
34
|
+
)
|
|
35
|
+
end
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
json: { files: json_example_mod_files }
|
|
48
|
-
)
|
|
49
|
-
expect_mod_files_to_be_example(nexus_mods(game_domain_name: 'skyrimspecialedition').mod_files(mod_id: 2014))
|
|
50
|
-
end
|
|
37
|
+
it 'returns a mod files list' do
|
|
38
|
+
expect_mod_files_to_be_example(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014))
|
|
39
|
+
end
|
|
51
40
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
json: { files: json_example_mod_files }
|
|
56
|
-
)
|
|
57
|
-
nexus_mods.game_domain_name = 'skyrimspecialedition'
|
|
58
|
-
expect_mod_files_to_be_example(nexus_mods.mod_files(mod_id: 2014))
|
|
59
|
-
end
|
|
41
|
+
it 'returns the default mod files list' do
|
|
42
|
+
expect_mod_files_to_be_example(nexus_mods(mod_id: 2014).mod_files(game_domain_name: 'skyrimspecialedition'))
|
|
43
|
+
end
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
it 'returns mod files list for the default game' do
|
|
46
|
+
expect_mod_files_to_be_example(nexus_mods(game_domain_name: 'skyrimspecialedition').mod_files(mod_id: 2014))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'returns mod files list for the default game set using accessor' do
|
|
50
|
+
nexus_mods.game_domain_name = 'skyrimspecialedition'
|
|
51
|
+
expect_mod_files_to_be_example(nexus_mods.mod_files(mod_id: 2014))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'returns mod files list for the default game and mod' do
|
|
55
|
+
expect_mod_files_to_be_example(nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014).mod_files)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'returns mod files list for the default game and mod using accessor' do
|
|
59
|
+
nexus_mods.mod_id = 2014
|
|
60
|
+
expect_mod_files_to_be_example(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition'))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'returns the mod associated to the mod file' do
|
|
64
|
+
expect_http_call_to(
|
|
65
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
66
|
+
json: json_complete_mod
|
|
67
|
+
)
|
|
68
|
+
expect_mod_to_be_complete(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014).first.mod)
|
|
69
|
+
end
|
|
68
70
|
|
|
69
|
-
it 'returns mod files list for the default game and mod using accessor' do
|
|
70
|
-
expect_http_call_to(
|
|
71
|
-
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
72
|
-
json: { files: json_example_mod_files }
|
|
73
|
-
)
|
|
74
|
-
nexus_mods.mod_id = 2014
|
|
75
|
-
expect_mod_files_to_be_example(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition'))
|
|
76
71
|
end
|
|
77
72
|
|
|
78
73
|
it 'compares objects for equality' do
|
|
79
74
|
expect_http_call_to(
|
|
80
75
|
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
81
|
-
json: { files
|
|
76
|
+
json: { 'files' => [json_mod_file2472] }
|
|
82
77
|
)
|
|
83
78
|
mod_file1 = nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014).mod_files.first
|
|
84
79
|
mod_file2 = nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014).mod_files.first
|
|
@@ -96,11 +91,12 @@ describe NexusMods::Api::ModFile do
|
|
|
96
91
|
archived: 7,
|
|
97
92
|
unknown: 100
|
|
98
93
|
}.each do |category, category_id|
|
|
94
|
+
|
|
99
95
|
it "accepts mod files having category #{category}" do
|
|
100
96
|
expect_http_call_to(
|
|
101
97
|
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
102
98
|
json: {
|
|
103
|
-
files
|
|
99
|
+
'files' => [
|
|
104
100
|
{
|
|
105
101
|
'id' => [
|
|
106
102
|
2472,
|
|
@@ -132,6 +128,197 @@ describe NexusMods::Api::ModFile do
|
|
|
132
128
|
expect(mod_file.category).to eq category
|
|
133
129
|
expect(mod_file.category_id).to eq category_id
|
|
134
130
|
end
|
|
131
|
+
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'when checking cache data freshness' do
|
|
135
|
+
|
|
136
|
+
it 'returns that mod files never retrieved are not up-to-date' do
|
|
137
|
+
expect(nexus_mods.mod_files_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context 'when retrieving mod files previously' do
|
|
141
|
+
|
|
142
|
+
before do
|
|
143
|
+
expect_http_call_to(
|
|
144
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
145
|
+
json: { 'files' => [json_mod_file2472] }
|
|
146
|
+
)
|
|
147
|
+
nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
context 'when retrieved 40 days ago' do
|
|
151
|
+
|
|
152
|
+
let(:forty_days_ago) { Time.now - (40 * 24 * 60 * 60) }
|
|
153
|
+
|
|
154
|
+
before do
|
|
155
|
+
nexus_mods.set_mod_files_cache_timestamp(cache_timestamp: forty_days_ago, game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'returns that mod files are not up-to-date' do
|
|
159
|
+
expect(nexus_mods.mod_files_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be false
|
|
160
|
+
expect(nexus_mods.mod_files_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq forty_days_ago
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context 'when retrieved 2 days ago' do
|
|
166
|
+
|
|
167
|
+
let(:two_days_ago) { Time.now - (2 * 24 * 60 * 60) }
|
|
168
|
+
|
|
169
|
+
before do
|
|
170
|
+
nexus_mods.set_mod_files_cache_timestamp(cache_timestamp: two_days_ago, game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'returns that mod files are up-to-date after checking updated mods and not finding it, and updates its cache timestamp to the update time' do
|
|
174
|
+
expect_http_call_to(
|
|
175
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
176
|
+
json: []
|
|
177
|
+
)
|
|
178
|
+
expect(nexus_mods.mod_files_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be true
|
|
179
|
+
expect(nexus_mods.mod_files_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(
|
|
180
|
+
nexus_mods.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'returns that mod files are up-to-date after checking updated mods and finding that cache is more recent, and updates its cache timestamp to the update time' do
|
|
185
|
+
expect_http_call_to(
|
|
186
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
187
|
+
json: [
|
|
188
|
+
{
|
|
189
|
+
'mod_id' => 2014,
|
|
190
|
+
# Mock that mod was updated 3 days ago
|
|
191
|
+
'latest_file_update' => Integer((Time.now - (3 * 24 * 60 * 60)).strftime('%s')),
|
|
192
|
+
'latest_mod_activity' => 1
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
)
|
|
196
|
+
expect(nexus_mods.mod_files_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be true
|
|
197
|
+
expect(nexus_mods.mod_files_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(
|
|
198
|
+
nexus_mods.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'returns that mod files are not up-to-date after checking updated mods and finding that cache is less recent' do
|
|
203
|
+
expect_http_call_to(
|
|
204
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
205
|
+
json: [
|
|
206
|
+
{
|
|
207
|
+
'mod_id' => 2014,
|
|
208
|
+
# Mock that mod was updated yesterday
|
|
209
|
+
'latest_file_update' => Integer((Time.now - (24 * 60 * 60)).strftime('%s')),
|
|
210
|
+
'latest_mod_activity' => 1
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
)
|
|
214
|
+
expect(nexus_mods.mod_files_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be false
|
|
215
|
+
expect(nexus_mods.mod_files_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq two_days_ago
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context 'when retrieved 3 minutes ago with an updated mods query 4 minutes ago in the cache' do
|
|
221
|
+
|
|
222
|
+
let(:three_minutes_ago) { Time.now - (3 * 60) }
|
|
223
|
+
let(:four_minutes_ago) { Time.now - (4 * 60) }
|
|
224
|
+
|
|
225
|
+
before do
|
|
226
|
+
expect_http_call_to(
|
|
227
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
228
|
+
json: [
|
|
229
|
+
{
|
|
230
|
+
'mod_id' => 2014,
|
|
231
|
+
# Mock that mod was updated 3 days ago
|
|
232
|
+
'latest_file_update' => Integer((Time.now - (3 * 24 * 60 * 60)).strftime('%s')),
|
|
233
|
+
'latest_mod_activity' => 1
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
)
|
|
237
|
+
nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
238
|
+
nexus_mods.set_mod_files_cache_timestamp(cache_timestamp: three_minutes_ago, game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
239
|
+
nexus_mods.set_updated_mods_cache_timestamp(cache_timestamp: four_minutes_ago, game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'returns that mod files are up-to-date but doesn\'t change its mod cache timestamp' do
|
|
243
|
+
expect(nexus_mods.mod_files_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be true
|
|
244
|
+
expect(nexus_mods.mod_files_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq three_minutes_ago
|
|
245
|
+
expect(nexus_mods.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: :one_month)).to eq four_minutes_ago
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
context 'when checking for updates' do
|
|
255
|
+
|
|
256
|
+
before do
|
|
257
|
+
nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'does not check for updates if mod files have not been retrieved before' do
|
|
261
|
+
expect_http_call_to(
|
|
262
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
263
|
+
json: { 'files' => [json_mod_file2472] }
|
|
264
|
+
)
|
|
265
|
+
expect_mod_file_to_be2472(nexus_mods.mod_files(check_updates: true).first)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'does not check for updates if mod files have been retrieved more than 1 month ago, and re-query the mod' do
|
|
269
|
+
expect_http_call_to(
|
|
270
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
271
|
+
json: { 'files' => [json_mod_file2472] },
|
|
272
|
+
times: 2
|
|
273
|
+
)
|
|
274
|
+
nexus_mods.mod_files
|
|
275
|
+
nexus_mods.set_mod_files_cache_timestamp(cache_timestamp: Time.now - (40 * 24 * 60 * 60), game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
276
|
+
expect_mod_file_to_be2472(nexus_mods.mod_files(check_updates: true).first)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'checks for updates when mod has been retrieved less than 1 month ago and does nothing if its date is less recent than the cache' do
|
|
280
|
+
expect_http_call_to(
|
|
281
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
282
|
+
json: { 'files' => [json_mod_file2472] }
|
|
283
|
+
)
|
|
284
|
+
expect_http_call_to(
|
|
285
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
286
|
+
json: [
|
|
287
|
+
{
|
|
288
|
+
'mod_id' => 2014,
|
|
289
|
+
# Mock that mod was updated 25 days ago
|
|
290
|
+
'latest_file_update' => Integer((Time.now - (25 * 24 * 60 * 60)).strftime('%s')),
|
|
291
|
+
'latest_mod_activity' => 1
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
)
|
|
295
|
+
nexus_mods.mod_files
|
|
296
|
+
nexus_mods.set_mod_files_cache_timestamp(cache_timestamp: Time.now - (20 * 24 * 60 * 60), game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
297
|
+
expect_mod_file_to_be2472(nexus_mods.mod_files(check_updates: true).first)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it 'checks for updates when mod has been retrieved less than 1 month ago and re-query the mod if its date is more recent than the cache' do
|
|
301
|
+
expect_http_call_to(
|
|
302
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
303
|
+
json: { 'files' => [json_mod_file2472] },
|
|
304
|
+
times: 2
|
|
305
|
+
)
|
|
306
|
+
expect_http_call_to(
|
|
307
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
308
|
+
json: [
|
|
309
|
+
{
|
|
310
|
+
'mod_id' => 2014,
|
|
311
|
+
# Mock that mod was updated 15 days ago
|
|
312
|
+
'latest_file_update' => Integer((Time.now - (15 * 24 * 60 * 60)).strftime('%s')),
|
|
313
|
+
'latest_mod_activity' => 1
|
|
314
|
+
}
|
|
315
|
+
]
|
|
316
|
+
)
|
|
317
|
+
nexus_mods.mod_files
|
|
318
|
+
nexus_mods.set_mod_files_cache_timestamp(cache_timestamp: Time.now - (20 * 24 * 60 * 60), game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
319
|
+
expect_mod_file_to_be2472(nexus_mods.mod_files(check_updates: true).first)
|
|
320
|
+
end
|
|
321
|
+
|
|
135
322
|
end
|
|
136
323
|
|
|
137
324
|
end
|
|
@@ -6,73 +6,261 @@ describe NexusMods::Api::Mod do
|
|
|
6
6
|
expect_validate_user
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
expect_http_call_to(
|
|
11
|
-
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
12
|
-
json: json_complete_mod
|
|
13
|
-
)
|
|
14
|
-
expect_mod_to_be_complete(nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014))
|
|
15
|
-
end
|
|
9
|
+
context 'when accessing a partial mod' do
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
11
|
+
before do
|
|
12
|
+
expect_http_call_to(
|
|
13
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
14
|
+
json: json_partial_mod
|
|
15
|
+
)
|
|
16
|
+
end
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
json: json_complete_mod
|
|
29
|
-
)
|
|
30
|
-
expect_mod_to_be_complete(nexus_mods(mod_id: 2014).mod(game_domain_name: 'skyrimspecialedition'))
|
|
31
|
-
end
|
|
18
|
+
it 'returns the mod information' do
|
|
19
|
+
expect_mod_to_be_partial(nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014))
|
|
20
|
+
end
|
|
32
21
|
|
|
33
|
-
it 'returns mod information for the default game' do
|
|
34
|
-
expect_http_call_to(
|
|
35
|
-
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
36
|
-
json: json_complete_mod
|
|
37
|
-
)
|
|
38
|
-
expect_mod_to_be_complete(nexus_mods(game_domain_name: 'skyrimspecialedition').mod(mod_id: 2014))
|
|
39
22
|
end
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
24
|
+
context 'when accessing a complete mod' do
|
|
25
|
+
|
|
26
|
+
before do
|
|
27
|
+
expect_http_call_to(
|
|
28
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
29
|
+
json: json_complete_mod
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'returns the mod information' do
|
|
34
|
+
expect_mod_to_be_complete(nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'returns the default mod information' do
|
|
38
|
+
expect_mod_to_be_complete(nexus_mods(mod_id: 2014).mod(game_domain_name: 'skyrimspecialedition'))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'returns mod information for the default game' do
|
|
42
|
+
expect_mod_to_be_complete(nexus_mods(game_domain_name: 'skyrimspecialedition').mod(mod_id: 2014))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns mod information for the default game set using accessor' do
|
|
46
|
+
nexus_mods.game_domain_name = 'skyrimspecialedition'
|
|
47
|
+
expect_mod_to_be_complete(nexus_mods.mod(mod_id: 2014))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'returns mod information for the default game and mod' do
|
|
51
|
+
expect_mod_to_be_complete(nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014).mod)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'returns mod information for the default game and mod set using accessor' do
|
|
55
|
+
nexus_mods.mod_id = 2014
|
|
56
|
+
expect_mod_to_be_complete(nexus_mods.mod(game_domain_name: 'skyrimspecialedition'))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'compares objects for equality' do
|
|
60
|
+
mod1 = nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
61
|
+
mod2 = nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
62
|
+
expect(mod1.object_id).not_to eq mod2.object_id
|
|
63
|
+
expect(mod1).to eq mod2
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'returns the mod files associated to the mod' do
|
|
67
|
+
expect_http_call_to(
|
|
68
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
69
|
+
json: { 'files' => [json_mod_file2472] }
|
|
70
|
+
)
|
|
71
|
+
expect_mod_file_to_be2472(nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014).files.first)
|
|
72
|
+
end
|
|
49
73
|
|
|
50
|
-
it 'returns mod information for the default game and mod' do
|
|
51
|
-
expect_http_call_to(
|
|
52
|
-
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
53
|
-
json: json_complete_mod
|
|
54
|
-
)
|
|
55
|
-
expect_mod_to_be_complete(nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014).mod)
|
|
56
74
|
end
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
context 'when checking cache data freshness' do
|
|
77
|
+
|
|
78
|
+
it 'returns that a mod never retrieved is not up-to-date' do
|
|
79
|
+
expect(nexus_mods.mod_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context 'when retrieving a mod previously' do
|
|
83
|
+
|
|
84
|
+
before do
|
|
85
|
+
expect_http_call_to(
|
|
86
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
87
|
+
json: json_complete_mod
|
|
88
|
+
)
|
|
89
|
+
nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context 'when retrieved 40 days ago' do
|
|
93
|
+
|
|
94
|
+
let(:forty_days_ago) { Time.now - (40 * 24 * 60 * 60) }
|
|
95
|
+
|
|
96
|
+
before do
|
|
97
|
+
nexus_mods.set_mod_cache_timestamp(cache_timestamp: forty_days_ago, game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns that the mod is not up-to-date' do
|
|
101
|
+
expect(nexus_mods.mod_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be false
|
|
102
|
+
expect(nexus_mods.mod_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq forty_days_ago
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context 'when retrieved 2 days ago' do
|
|
108
|
+
|
|
109
|
+
let(:two_days_ago) { Time.now - (2 * 24 * 60 * 60) }
|
|
110
|
+
|
|
111
|
+
before do
|
|
112
|
+
nexus_mods.set_mod_cache_timestamp(cache_timestamp: two_days_ago, game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'returns that the mod is up-to-date after checking updated mods and not finding it, and updates its cache timestamp to the update time' do
|
|
116
|
+
expect_http_call_to(
|
|
117
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
118
|
+
json: []
|
|
119
|
+
)
|
|
120
|
+
expect(nexus_mods.mod_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be true
|
|
121
|
+
expect(nexus_mods.mod_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(
|
|
122
|
+
nexus_mods.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'returns that the mod is up-to-date after checking updated mods and finding that cache is more recent, and updates its cache timestamp to the update time' do
|
|
127
|
+
expect_http_call_to(
|
|
128
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
129
|
+
json: [
|
|
130
|
+
{
|
|
131
|
+
'mod_id' => 2014,
|
|
132
|
+
# Mock that mod was updated 3 days ago
|
|
133
|
+
'latest_file_update' => 1,
|
|
134
|
+
'latest_mod_activity' => Integer((Time.now - (3 * 24 * 60 * 60)).strftime('%s'))
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
)
|
|
138
|
+
expect(nexus_mods.mod_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be true
|
|
139
|
+
expect(nexus_mods.mod_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(
|
|
140
|
+
nexus_mods.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'returns that the mod is not up-to-date after checking updated mods and finding that cache is less recent' do
|
|
145
|
+
expect_http_call_to(
|
|
146
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
147
|
+
json: [
|
|
148
|
+
{
|
|
149
|
+
'mod_id' => 2014,
|
|
150
|
+
# Mock that mod was updated yesterday
|
|
151
|
+
'latest_file_update' => 1,
|
|
152
|
+
'latest_mod_activity' => Integer((Time.now - (24 * 60 * 60)).strftime('%s'))
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
expect(nexus_mods.mod_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be false
|
|
157
|
+
expect(nexus_mods.mod_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq two_days_ago
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'when retrieved 3 minutes ago with an updated mods query 4 minutes ago in the cache' do
|
|
163
|
+
|
|
164
|
+
let(:three_minutes_ago) { Time.now - (3 * 60) }
|
|
165
|
+
let(:four_minutes_ago) { Time.now - (4 * 60) }
|
|
166
|
+
|
|
167
|
+
before do
|
|
168
|
+
expect_http_call_to(
|
|
169
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
170
|
+
json: [
|
|
171
|
+
{
|
|
172
|
+
'mod_id' => 2014,
|
|
173
|
+
# Mock that mod was updated 3 days ago
|
|
174
|
+
'latest_file_update' => 1,
|
|
175
|
+
'latest_mod_activity' => Integer((Time.now - (3 * 24 * 60 * 60)).strftime('%s'))
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
180
|
+
nexus_mods.set_mod_cache_timestamp(cache_timestamp: three_minutes_ago, game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
181
|
+
nexus_mods.set_updated_mods_cache_timestamp(cache_timestamp: four_minutes_ago, game_domain_name: 'skyrimspecialedition', since: :one_month)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'returns that the mod is up-to-date but doesn\'t change its mod cache timestamp' do
|
|
185
|
+
expect(nexus_mods.mod_cache_up_to_date?(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to be true
|
|
186
|
+
expect(nexus_mods.mod_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq three_minutes_ago
|
|
187
|
+
expect(nexus_mods.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: :one_month)).to eq four_minutes_ago
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
end
|
|
193
|
+
|
|
65
194
|
end
|
|
66
195
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
196
|
+
context 'when checking for updates' do
|
|
197
|
+
|
|
198
|
+
before do
|
|
199
|
+
nexus_mods(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'does not check for updates if mod has not been retrieved before' do
|
|
203
|
+
expect_http_call_to(
|
|
204
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
205
|
+
json: json_complete_mod
|
|
206
|
+
)
|
|
207
|
+
expect_mod_to_be_complete(nexus_mods.mod(check_updates: true))
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'does not check for updates if mod has been retrieved more than 1 month ago, and re-query the mod' do
|
|
211
|
+
expect_http_call_to(
|
|
212
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
213
|
+
json: json_complete_mod,
|
|
214
|
+
times: 2
|
|
215
|
+
)
|
|
216
|
+
nexus_mods.mod
|
|
217
|
+
nexus_mods.set_mod_cache_timestamp(cache_timestamp: Time.now - (40 * 24 * 60 * 60), game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
218
|
+
expect_mod_to_be_complete(nexus_mods.mod(check_updates: true))
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it 'checks for updates when mod has been retrieved less than 1 month ago and does nothing if its date is less recent than the cache' do
|
|
222
|
+
expect_http_call_to(
|
|
223
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
224
|
+
json: json_complete_mod
|
|
225
|
+
)
|
|
226
|
+
expect_http_call_to(
|
|
227
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
228
|
+
json: [
|
|
229
|
+
{
|
|
230
|
+
'mod_id' => 2014,
|
|
231
|
+
# Mock that mod was updated 25 days ago
|
|
232
|
+
'latest_file_update' => 1,
|
|
233
|
+
'latest_mod_activity' => Integer((Time.now - (25 * 24 * 60 * 60)).strftime('%s'))
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
)
|
|
237
|
+
nexus_mods.mod
|
|
238
|
+
nexus_mods.set_mod_cache_timestamp(cache_timestamp: Time.now - (20 * 24 * 60 * 60), game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
239
|
+
expect_mod_to_be_complete(nexus_mods.mod(check_updates: true))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'checks for updates when mod has been retrieved less than 1 month ago and re-query the mod if its date is more recent than the cache' do
|
|
243
|
+
expect_http_call_to(
|
|
244
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
245
|
+
json: json_complete_mod,
|
|
246
|
+
times: 2
|
|
247
|
+
)
|
|
248
|
+
expect_http_call_to(
|
|
249
|
+
path: '/v1/games/skyrimspecialedition/mods/updated.json?period=1m',
|
|
250
|
+
json: [
|
|
251
|
+
{
|
|
252
|
+
'mod_id' => 2014,
|
|
253
|
+
# Mock that mod was updated 15 days ago
|
|
254
|
+
'latest_file_update' => 1,
|
|
255
|
+
'latest_mod_activity' => Integer((Time.now - (15 * 24 * 60 * 60)).strftime('%s'))
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
)
|
|
259
|
+
nexus_mods.mod
|
|
260
|
+
nexus_mods.set_mod_cache_timestamp(cache_timestamp: Time.now - (20 * 24 * 60 * 60), game_domain_name: 'skyrimspecialedition', mod_id: 2014)
|
|
261
|
+
expect_mod_to_be_complete(nexus_mods.mod(check_updates: true))
|
|
262
|
+
end
|
|
263
|
+
|
|
76
264
|
end
|
|
77
265
|
|
|
78
266
|
end
|
|
@@ -78,6 +78,34 @@ describe NexusMods::Api::ModUpdates do
|
|
|
78
78
|
expect(mod_updates1).to eq mod_updates2
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
it 'returns the mod associated to the mod updates' do
|
|
82
|
+
expect_http_call_to(
|
|
83
|
+
path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
|
|
84
|
+
json: [
|
|
85
|
+
json_mod_updates2014
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
expect_http_call_to(
|
|
89
|
+
path: '/v1/games/skyrimspecialedition/mods/2014.json',
|
|
90
|
+
json: json_complete_mod
|
|
91
|
+
)
|
|
92
|
+
expect_mod_to_be_complete(nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since]).first.mod)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns the mod files associated to the mod updates' do
|
|
96
|
+
expect_http_call_to(
|
|
97
|
+
path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
|
|
98
|
+
json: [
|
|
99
|
+
json_mod_updates2014
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
expect_http_call_to(
|
|
103
|
+
path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
|
|
104
|
+
json: { 'files' => [json_mod_file2472] }
|
|
105
|
+
)
|
|
106
|
+
expect_mod_file_to_be2472(nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since]).first.mod_files.first)
|
|
107
|
+
end
|
|
108
|
+
|
|
81
109
|
end
|
|
82
110
|
|
|
83
111
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nexus_mods
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muriel Salvan
|
|
@@ -138,6 +138,7 @@ files:
|
|
|
138
138
|
- lib/nexus_mods/api/mod.rb
|
|
139
139
|
- lib/nexus_mods/api/mod_file.rb
|
|
140
140
|
- lib/nexus_mods/api/mod_updates.rb
|
|
141
|
+
- lib/nexus_mods/api/resource.rb
|
|
141
142
|
- lib/nexus_mods/api/user.rb
|
|
142
143
|
- lib/nexus_mods/api_client.rb
|
|
143
144
|
- lib/nexus_mods/cacheable_api.rb
|