Class: NexusMods

Inherits:
Object
  • Object
show all
Defined in:
lib/nexus_mods.rb,
lib/nexus_mods/api/mod.rb,
lib/nexus_mods/version.rb,
lib/nexus_mods/api/game.rb,
lib/nexus_mods/api/user.rb,
lib/nexus_mods/api_client.rb,
lib/nexus_mods/api/category.rb,
lib/nexus_mods/api/mod_file.rb,
lib/nexus_mods/api/resource.rb,
lib/nexus_mods/cacheable_api.rb,
lib/nexus_mods/api/api_limits.rb,
lib/nexus_mods/api/mod_updates.rb,
lib/nexus_mods/cacheable_with_expiry.rb,
lib/nexus_mods/core_extensions/cacheable/method_generator.rb

Overview

Ruby API to access NexusMods REST API

Defined Under Namespace

Modules: Api, CacheableApi, CacheableWithExpiry, CoreExtensions Classes: ApiClient, ApiError, InvalidApiKeyError, LimitsExceededError

Constant Summary collapse

VERSION =
'2.5.0'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: nil, game_domain_name: 'skyrimspecialedition', mod_id: 1, file_id: 1, api_cache_expiry: {}, api_cache_file: "#{Dir.tmpdir}/nexus_mods_api_cache.json", logger: Logger.new($stdout), log_level: :info) ⇒ NexusMods

Constructor

Parameters
  • api_key (String or nil): The API key to be used, or nil for another authentication [default: nil]

  • game_domain_name (String): Game domain name to query by default [default: ‘skyrimspecialedition’]

  • mod_id (Integer): Mod to query by default [default: 1]

  • file_id (Integer): File to query by default [default: 1]

  • api_cache_expiry (Hash<Symbol,Integer>): Expiry times in seconds, per expiry key. Possible keys are:

    • games: Expiry associated to queries on games [default: 1 day]

    • mod: Expiry associated to queries on mod [default: 1 day]

    • mod_files: Expiry associated to queries on mod files [default: 1 day]

  • api_cache_file (String): File used to store the NexusMods API cache, or nil for no cache [default: “#Dir.tmpdir/nexus_mods_api_cache.json”]

  • logger (Logger): The logger to be used for log messages [default: Logger.new(STDOUT)]

  • log_level (Symbol): The logger level to be set [default: :info]



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/nexus_mods.rb', line 50

def initialize(
  api_key: nil,
  game_domain_name: 'skyrimspecialedition',
  mod_id: 1,
  file_id: 1,
  api_cache_expiry: {},
  api_cache_file: "#{Dir.tmpdir}/nexus_mods_api_cache.json",
  logger: Logger.new($stdout),
  log_level: :info
)
  @game_domain_name = game_domain_name
  @mod_id = mod_id
  @file_id = file_id
  @logger = logger
  @logger.level = log_level
  @premium = false
  @api_client = ApiClient.new(
    api_key:,
    api_cache_expiry:,
    api_cache_file:,
    logger:
  )

  # Check that the key is correct and know if the user is premium
  begin
    @premium = @api_client.api('users/validate')['is_premium?']
  rescue LimitsExceededError
    raise
  rescue ApiError
    raise InvalidApiKeyError, 'Invalid API key'
  end
end

Instance Attribute Details

#game_domain_nameObject

The default game domain name to be queried

String


30
31
32
# File 'lib/nexus_mods.rb', line 30

def game_domain_name
  @game_domain_name
end

#mod_idObject

The default mod id to be queried

Integer


34
35
36
# File 'lib/nexus_mods.rb', line 34

def mod_id
  @mod_id
end

Instance Method Details

#api_limitsObject

Get limits of API calls. This call does not count in the limits.

Result
  • ApiLimits: API calls limits



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/nexus_mods.rb', line 88

def api_limits
  api_limits_headers = @api_client.http('users/validate').headers
  Api::ApiLimits.new(
    nexus_mods: self,
    daily_limit: Integer(api_limits_headers['x-rl-daily-limit']),
    daily_remaining: Integer(api_limits_headers['x-rl-daily-remaining']),
    daily_reset: Time.parse(api_limits_headers['x-rl-daily-reset']).utc,
    hourly_limit: Integer(api_limits_headers['x-rl-hourly-limit']),
    hourly_remaining: Integer(api_limits_headers['x-rl-hourly-remaining']),
    hourly_reset: Time.parse(api_limits_headers['x-rl-hourly-reset']).utc
  )
end

#games(clear_cache: false) ⇒ Object

Get the list of games

Parameters
  • clear_cache (Boolean): Should we clear the API cache for this resource? [default: false]

Result
  • Array<Game>: List of games



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/nexus_mods.rb', line 107

def games(clear_cache: false)
  @api_client.api('games', clear_cache:).map do |game_json|
    # First create categories tree
    # Hash<Integer, [Category, Integer]>: Category and its parent category id, per category id
    categories = game_json['categories'].to_h do |category_json|
      category_id = category_json['category_id']
      [
        category_id,
        [
          Api::Category.new(
            nexus_mods: self,
            id: category_id,
            name: category_json['name']
          ),
          category_json['parent_category']
        ]
      ]
    end
    categories.each_value do |(category, parent_category_id)|
      # Ignore missing parent categories: this situation happens.
      category.parent_category = categories[parent_category_id]&.first if parent_category_id
    end
    Api::Game.new(
      nexus_mods: self,
      id: game_json['id'],
      name: game_json['name'],
      forum_url: game_json['forum_url'],
      nexusmods_url: game_json['nexusmods_url'],
      genre: game_json['genre'],
      domain_name: game_json['domain_name'],
      approved_date: Time.at(game_json['approved_date']).utc,
      files_count: game_json['file_count'],
      files_views: game_json['file_views'],
      files_endorsements: game_json['file_endorsements'],
      downloads_count: game_json['downloads'],
      authors_count: game_json['authors'],
      mods_count: game_json['mods'],
      categories: categories.values.map { |(category, _parent_category_id)| category }
    )
  end
end

#games_cache_timestampObject

Get the cached timestamp of the list of games

Result
  • Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache



153
154
155
# File 'lib/nexus_mods.rb', line 153

def games_cache_timestamp
  @api_client.api_cache_timestamp('games')
end

#mod(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false, check_updates: false) ⇒ Object

Get information about a mod

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

  • clear_cache (Boolean): Should we clear the API cache for this resource? [default: false]

  • check_updates (Boolean): Should we check updates? If yes then an extra call to updated_mods may be done to check for updates before retrieving the mod information. 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.

Result
  • Mod: Mod information



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/nexus_mods.rb', line 177

def mod(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false, check_updates: false)
  mod_cache_up_to_date?(game_domain_name:, mod_id:) if check_updates
  mod_json = @api_client.api("games/#{game_domain_name}/mods/#{mod_id}", clear_cache:)
  Api::Mod.new(
    nexus_mods: self,
    uid: mod_json['uid'],
    mod_id: mod_json['mod_id'],
    game_id: mod_json['game_id'],
    allow_rating: mod_json['allow_rating'],
    domain_name: mod_json['domain_name'],
    category_id: mod_json['category_id'],
    version: mod_json['version'],
    created_time: Time.parse(mod_json['created_time']),
    updated_time: Time.parse(mod_json['updated_time']),
    author: mod_json['author'],
    contains_adult_content: mod_json['contains_adult_content'],
    status: mod_json['status'],
    available: mod_json['available'],
    uploader: Api::User.new(
      nexus_mods: self,
      member_id: mod_json['user']['member_id'],
      member_group_id: mod_json['user']['member_group_id'],
      name: mod_json['user']['name'],
      profile_url: mod_json['uploaded_users_profile_url']
    ),
    name: mod_json['name'],
    summary: mod_json['summary'],
    description: mod_json['description'],
    picture_url: mod_json['picture_url'],
    downloads_count: mod_json['mod_downloads'],
    unique_downloads_count: mod_json['mod_unique_downloads'],
    endorsements_count: mod_json['endorsement_count']
  )
end

#mod_cache_timestamp(game_domain_name: @game_domain_name, mod_id: @mod_id) ⇒ Object

Get the cached timestamp of a mod information

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

Result
  • Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache



219
220
221
# File 'lib/nexus_mods.rb', line 219

def mod_cache_timestamp(game_domain_name: @game_domain_name, mod_id: @mod_id)
  @api_client.api_cache_timestamp("games/#{game_domain_name}/mods/#{mod_id}")
end

#mod_cache_up_to_date?(game_domain_name: @game_domain_name, mod_id: @mod_id) ⇒ Boolean

Does a given mod id have fresh information in our cache? This may fire queries to the updated mods API to get info from NexusMods about the latest updated mods. 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.

Here is the algorithm: If it is not in the cache, then it is not up-to-date. Otherwise, the API allows us to know if it has been updated up to 1 month in the past. Therefore if the current cache timestamp is older than 1 month, assume that it has to be updated. Otherwise query the API to know the latest updated mods since 1 month:

  • If the mod ID is not there, then it is up-to-date.

  • If the mod ID is there, then check if our cache timestamp is older than the last update timestamp from NexusMods.

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

Result
  • Boolean: Is the mod cache up-to-date?

Returns:

  • (Boolean)


365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/nexus_mods.rb', line 365

def mod_cache_up_to_date?(game_domain_name: @game_domain_name, mod_id: @mod_id)
  existing_cache_timestamp = mod_cache_timestamp(game_domain_name:, mod_id:)
  mod_up_to_date =
    if existing_cache_timestamp.nil? || existing_cache_timestamp < Time.now - (30 * 24 * 60 * 60)
      # It's not in the cache
      # or it's older than 1 month
      false
    else
      found_mod_updates = updated_mods(game_domain_name:, since: :one_month).find { |mod_updates| mod_updates.mod_id == mod_id }
      # true if it has not been updated on NexusMods since 1 month
      # or our cache timestamp is more recent
      found_mod_updates.nil? || found_mod_updates.latest_mod_activity < existing_cache_timestamp
    end
  if mod_up_to_date
    update_time = updated_mods_cache_timestamp(game_domain_name:, since: :one_month)
    set_mod_cache_timestamp(cache_timestamp: update_time, game_domain_name:, mod_id:) if update_time > existing_cache_timestamp
  end
  mod_up_to_date
end

#mod_files(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false, check_updates: false) ⇒ Object

Get files belonging to a mod

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

  • clear_cache (Boolean): Should we clear the API cache for this resource? [default: false]

  • check_updates (Boolean): Should we check updates? If yes then an extra call to updated_mods may be done to check for updates before retrieving the mod information. 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.

Result
  • Array<ModFile>: List of mod’s files



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/nexus_mods.rb', line 245

def mod_files(game_domain_name: @game_domain_name, mod_id: @mod_id, clear_cache: false, check_updates: false)
  mod_files_cache_up_to_date?(game_domain_name:, mod_id:) if check_updates
  @api_client.api("games/#{game_domain_name}/mods/#{mod_id}/files", clear_cache:)['files'].map do |file_json|
    Api::ModFile.new(
      nexus_mods: self,
      game_domain_name:,
      mod_id:,
      ids: file_json['id'],
      uid: file_json['uid'],
      id: file_json['file_id'],
      name: file_json['name'],
      version: file_json['version'],
      category_id: file_json['category_id'],
      category_name: file_json['category_name'],
      is_primary: file_json['is_primary'],
      size: file_json['size_in_bytes'],
      file_name: file_json['file_name'],
      uploaded_time: Time.parse(file_json['uploaded_time']),
      mod_version: file_json['mod_version'],
      external_virus_scan_url: file_json['external_virus_scan_url'],
      description: file_json['description'],
      changelog_html: file_json['changelog_html'],
      content_preview_url: file_json['content_preview_link']
    )
  end
end

#mod_files_cache_timestamp(game_domain_name: @game_domain_name, mod_id: @mod_id) ⇒ Object

Get the cached timestamp of a mod files information

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

Result
  • Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache



279
280
281
# File 'lib/nexus_mods.rb', line 279

def mod_files_cache_timestamp(game_domain_name: @game_domain_name, mod_id: @mod_id)
  @api_client.api_cache_timestamp("games/#{game_domain_name}/mods/#{mod_id}/files")
end

#mod_files_cache_up_to_date?(game_domain_name: @game_domain_name, mod_id: @mod_id) ⇒ Boolean

Does a given mod id have fresh files information in our cache? This may fire queries to the updated mods API to get info from NexusMods about the latest updated mods. 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.

Here is the algorithm: If it is not in the cache, then it is not up-to-date. Otherwise, the API allows us to know if it has been updated up to 1 month in the past. Therefore if the current cache timestamp is older than 1 month, assume that it has to be updated. Otherwise query the API to know the latest updated mods since 1 month:

  • If the mod ID is not there, then it is up-to-date.

  • If the mod ID is there, then check if our cache timestamp is older than the last update timestamp from NexusMods.

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

Result
  • Boolean: Is the mod cache up-to-date?

Returns:

  • (Boolean)


402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/nexus_mods.rb', line 402

def mod_files_cache_up_to_date?(game_domain_name: @game_domain_name, mod_id: @mod_id)
  existing_cache_timestamp = mod_files_cache_timestamp(game_domain_name:, mod_id:)
  mod_up_to_date =
    if existing_cache_timestamp.nil? || existing_cache_timestamp < Time.now - (30 * 24 * 60 * 60)
      # It's not in the cache
      # or it's older than 1 month
      false
    else
      found_mod_updates = updated_mods(game_domain_name:, since: :one_month).find { |mod_updates| mod_updates.mod_id == mod_id }
      # true if it has not been updated on NexusMods since 1 month
      # or our cache timestamp is more recent
      found_mod_updates.nil? || found_mod_updates.latest_file_update < existing_cache_timestamp
    end
  if mod_up_to_date
    update_time = updated_mods_cache_timestamp(game_domain_name:, since: :one_month)
    set_mod_files_cache_timestamp(cache_timestamp: update_time, game_domain_name:, mod_id:) if update_time > existing_cache_timestamp
  end
  mod_up_to_date
end

#set_games_cache_timestamp(cache_timestamp:) ⇒ Object

Set the cached timestamp of the list of games. This should be used only to update the cache timestamp of a resource we know is still up-to-date without fetching the resource for real again.

Parameters
  • cache_timestamp (Time): The cache timestamp to set for this resource



162
163
164
# File 'lib/nexus_mods.rb', line 162

def set_games_cache_timestamp(cache_timestamp:)
  @api_client.set_api_cache_timestamp('games', cache_timestamp:)
end

#set_mod_cache_timestamp(cache_timestamp:, game_domain_name: @game_domain_name, mod_id: @mod_id) ⇒ Object

Set the cached timestamp of a mod information. This should be used only to update the cache timestamp of a resource we know is still up-to-date without fetching the resource for real again.

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

  • cache_timestamp (Time): The cache timestamp to set for this resource



230
231
232
# File 'lib/nexus_mods.rb', line 230

def set_mod_cache_timestamp(cache_timestamp:, game_domain_name: @game_domain_name, mod_id: @mod_id)
  @api_client.set_api_cache_timestamp("games/#{game_domain_name}/mods/#{mod_id}", cache_timestamp:)
end

#set_mod_files_cache_timestamp(cache_timestamp:, game_domain_name: @game_domain_name, mod_id: @mod_id) ⇒ Object

Set the cached timestamp of a mod files information. This should be used only to update the cache timestamp of a resource we know is still up-to-date without fetching the resource for real again.

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • mod_id (Integer): The mod ID [default: @mod_id]

  • cache_timestamp (Time): The cache timestamp to set for this resource



290
291
292
# File 'lib/nexus_mods.rb', line 290

def set_mod_files_cache_timestamp(cache_timestamp:, game_domain_name: @game_domain_name, mod_id: @mod_id)
  @api_client.set_api_cache_timestamp("games/#{game_domain_name}/mods/#{mod_id}/files", cache_timestamp:)
end

#set_updated_mods_cache_timestamp(cache_timestamp:, game_domain_name: @game_domain_name, since: :one_day) ⇒ Object

Set the cached timestamp of updated mod ids. This should be used only to update the cache timestamp of a resource we know is still up-to-date without fetching the resource for real again.

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • since (Symbol): The time from which we look for updated mods [default: :one_day] Possible values are:

    • one_day: Since 1 day

    • one_week: Since 1 week

    • one_month: Since 1 month

  • cache_timestamp (Time): The cache timestamp to set for this resource



344
345
346
# File 'lib/nexus_mods.rb', line 344

def set_updated_mods_cache_timestamp(cache_timestamp:, game_domain_name: @game_domain_name, since: :one_day)
  @api_client.set_api_cache_timestamp("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since), cache_timestamp:)
end

#updated_mods(game_domain_name: @game_domain_name, since: :one_day, clear_cache: false) ⇒ Object

Get a list of updated mod ids since a given time

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • since (Symbol): The time from which we look for updated mods [default: :one_day] Possible values are:

    • one_day: Since 1 day

    • one_week: Since 1 week

    • one_month: Since 1 month

  • clear_cache (Boolean): Should we clear the API cache for this resource? [default: false]

Result
  • Array<ModUpdates>: Mod’s updates information



306
307
308
309
310
311
312
313
314
315
316
# File 'lib/nexus_mods.rb', line 306

def updated_mods(game_domain_name: @game_domain_name, since: :one_day, clear_cache: false)
  @api_client.api("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since), clear_cache:).map do |updated_mod_json|
    Api::ModUpdates.new(
      nexus_mods: self,
      game_domain_name:,
      mod_id: updated_mod_json['mod_id'],
      latest_file_update: Time.at(updated_mod_json['latest_file_update']).utc,
      latest_mod_activity: Time.at(updated_mod_json['latest_mod_activity']).utc
    )
  end
end

#updated_mods_cache_timestamp(game_domain_name: @game_domain_name, since: :one_day) ⇒ Object

Get the cached timestamp of updated mod ids

Parameters
  • game_domain_name (String): Game domain name to query by default [default: @game_domain_name]

  • since (Symbol): The time from which we look for updated mods [default: :one_day] Possible values are:

    • one_day: Since 1 day

    • one_week: Since 1 week

    • one_month: Since 1 month

Result
  • Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache



329
330
331
# File 'lib/nexus_mods.rb', line 329

def updated_mods_cache_timestamp(game_domain_name: @game_domain_name, since: :one_day)
  @api_client.api_cache_timestamp("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since))
end