Source code for pynxm

# Copyright 2019 Daniel Nunes
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A python wrapper for the Nexus API."""

__version__ = "0.1.0"

import json
import platform
import uuid
import webbrowser

import requests
from websocket import create_connection

USER_AGENT = "pynxm/{} ({}; {}) {}/{}".format(
    __version__,
    platform.platform(),
    platform.architecture()[0],
    platform.python_implementation(),
    platform.python_version(),
)
BASE_URL = "https://api.nexusmods.com/v1/"


[docs]class LimitReachedError(Exception): """ Exception raised when the request rate limit has been reached. """ pass
[docs]class RequestError(Exception): """ Exception raised when a request returns an error code. """ pass
[docs]class Nexus(object): """ The class used for connecting to the Nexus API. Requires an API key from your Nexus account. """ def __init__(self, api_key): self.session = requests.Session() self.session.headers.update( { "user-agent": USER_AGENT, "apikey": api_key, "content-type": "application/json", } )
[docs] @classmethod def sso(cls, app_slug, sso_token, sso_id=None): """ Application login via Single Sign-On (SSO). :param app_slug: A string with the application slug. :param sso_token: A string with the connection token. :param sso_id: An optional string with an id used in previous connections. :return: A 'Nexus' instance, ready to be used. """ ws = create_connection("wss://sso.nexusmods.com") if sso_id is None: sso_id = str(uuid.uuid4()) ws.send(json.dumps({"id": sso_id, "token": sso_token})) webbrowser.open( "https://www.nexusmods.com/" "sso?id={}&application={}".format(sso_id, app_slug) ) api_key = ws.recv() return cls(api_key)
def _make_request(self, operation, endpoint, payload=None, data=None, headers=None): if payload is None: payload = {} if data is None: data = {} if headers is None: headers = {} response = self.session.request( operation.upper(), BASE_URL + endpoint, params=payload, data=data, headers=headers, timeout=30, ) status_code = response.status_code if status_code not in (200, 201): if status_code == 429: raise LimitReachedError( "You have reached your request limit. " "Please wait one hour before trying again." ) else: try: msg = response.json()["message"] except KeyError: msg = response.json()["error"] raise RequestError("Status Code {} - {}".format(status_code, msg)) return response.json()
[docs] def colour_schemes_list(self): """ Returns a list of all colour schemes, including the primary, secondary and 'darker' colours. """ return self._make_request("get", "colourschemes.json")
[docs] def user_details(self): """ Returns the user's details. """ return self._make_request("get", "users/validate.json")
[docs] def user_tracked_list(self): """ Returns a list of all the mods being tracked by the current user. """ return self._make_request("get", "user/tracked_mods.json")
[docs] def user_tracked_add(self, game, mod_id): """ Tracks this mod with the current user. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. """ self._make_request( "post", "user/tracked_mods.json", payload={"domain_name": game}, data={"mod_id": mod_id}, headers={"content-type": "application/x-www-form-urlencoded"}, )
[docs] def user_tracked_delete(self, game, mod_id): """ Stop tracking this mod with the current user. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. """ self._make_request( "delete", "user/tracked_mods.json", payload={"domain_name": game}, data={"mod_id": mod_id}, headers={"content-type": "application/x-www-form-urlencoded"}, )
[docs] def user_endorsements_list(self): """ Returns a list of all endorsements for the current user. """ return self._make_request("get", "user/endorsements.json")
[docs] def game_details(self, game): """ Returns specified game, along with download count, file count and categories. :param game: A string with Nexus' game domain. """ return self._make_request("get", "games/{}.json".format(game))
[docs] def game_list(self, include_unapproved=False): """ Returns a list of all games. :param include_unapproved: A boolean on whether to include unapproved games. """ return self._make_request( "get", "games.json", payload={"include_unapproved": include_unapproved} )
[docs] def game_updated_list(self, game, period): """ Returns a list of mods that have been updated in a given period, with timestamps of their last update. :param game: A string with Nexus' game domain. :param period: Acceptable values: '1d' (1 day), '1w' (1 week) or '1m' (1 month). """ if period not in ("1d", "1w", "1m"): raise ValueError("Allowed values for 'period' argument: '1d', '1w', '1m'.") return self._make_request( "get", "games/{}/mods/updated.json".format(game), payload={"period": period} )
[docs] def game_latest_added_list(self, game): """ Retrieve 10 latest added mods for a specified game. :param game: A string with Nexus' game domain. """ return self._make_request("get", "games/{}/mods/latest_added.json".format(game))
[docs] def game_latest_updated_list(self, game): """ Retrieve 10 latest updated mods for a specified game. :param game: A string with Nexus' game domain. """ return self._make_request( "get", "games/{}/mods/latest_updated.json".format(game) )
[docs] def mod_details(self, game, mod_id): """ Retrieve specified mod details, from a specified game. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. """ return self._make_request("get", "games/{}/mods/{}.json".format(game, mod_id))
[docs] def mod_endorse(self, game, mod_id): """ Endorse a mod. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. """ return self._make_request( "post", "games/{}/mods/{}/endorse.json".format(game, mod_id) )
[docs] def mod_abstain(self, game, mod_id): """ Abstain from endorsing a mod. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. """ return self._make_request( "post", "games/{}/mods/{}/abstain.json".format(game, mod_id) )
[docs] def mod_file_list(self, game, mod_id, categories=None): """ Lists all files for a specific mod. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. :param categories: Filter file category. Accepts either a list of categories or a comma-separated string of categories. Possible categories: 'main', 'update', 'optional', 'old_version' and 'miscellaneous'. """ if isinstance(categories, (tuple, list)): payload = {"category": ",".join(categories)} elif isinstance(categories, str): payload = {"category": categories} else: payload = None return self._make_request( "get", "games/{}/mods/{}/files.json".format(game, mod_id), payload=payload )
[docs] def mod_file_details(self, game, mod_id, file_id): """ View a specified mod file, using a specified game and mod. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. :param file_id: A string with the file id. """ return self._make_request( "get", "games/{}/mods/{}/files/{}.json".format(game, mod_id, file_id) )
[docs] def mod_changelog_list(self, game, mod_id): """ Returns list of changelogs for mod. :param game: A string with Nexus' game domain. :param mod_id: A string the mod id. """ return self._make_request( "get", "games/{}/mods/{}/changelogs.json".format(game, mod_id) )