diff --git a/contentapi.py b/contentapi.py index 6f60dc9..ed87a65 100644 --- a/contentapi.py +++ b/contentapi.py @@ -1,19 +1,17 @@ import requests +import logging -# Exception for 401 errors, meaning your token was bad (expired maybe?) class AuthenticationError(Exception): - pass + """ Exception for 401 errors, meaning your token was bad (expired maybe?) """ -# Exception for 400 errors, meaning you gave something funky to the API -# (maybe your search was malformed?) class BadRequestError(Exception): - pass + """Exception for 400 errors, meaning you gave something funky to the API + (maybe your search was malformed?) """ -# Exception for 404 errors, meaning whatever you were looking for wasn't found -# (This is rare from the API) class NotFoundError(Exception): - pass + """Exception for 404 errors, meaning whatever you were looking for wasn't found + (This is rare from the API) """ # Your gateway to the static endpoints for contentapi. It's a context because it needs # to track stuff like "which api am I contacting" and "which user am I authenticating as (if any)" @@ -22,16 +20,18 @@ class ApiContext: # You MUST define the endpoint when creating the API context! You can optionally set # the token on startup, or you can set it at any time. Set to a "falsey" value to # to browse as an anonymous user - def __init__(self, endpoint, logger, token = False): + def __init__(self, endpoint: str, logger: logging.Logger, token = False): self.endpoint = endpoint self.logger = logger self.token = token + # Generate the standard headers we use for most requests. You usually don't need to # change anything here, just make sure your token is set if you want to be logged in def gen_header(self, content_type = "application/json"): headers = { - "Content-Type" : content_type + "Content-Type" : content_type, + "Accept" : content_type } if self.token: headers["Authorization"] = "Bearer" + self.token @@ -43,18 +43,26 @@ class ApiContext: if response.status_code == 200: return response.json() elif response.status_code == 400: - raise BadRequestError(format("Bad request: {}", response.content)) + raise BadRequestError("Bad request: %s" % response.content) elif response.status_code == 401: raise AuthenticationError("Your token is bad!") elif response.status_code == 404: raise NotFoundError("Could not find content!") else: - raise Exception(format("Unknown error ({}) - {}", response.status_code, response.content)) + raise Exception("Unknown error (%s) - %s" % (response.status_code, response.content)) # Perform a standard get request and return the pre-parsed object (all contentapi endpoints # return objects). Throws exception on error def get(self, endpoint): - response = requests.get(self.endpoint + "/" + endpoint, headers = self.gen_header()) + url = self.endpoint + "/" + endpoint + # self.logger.debug("GET: " + url) # Not necessary, DEBUG in requests does this + response = requests.get(url, headers = self.gen_header()) + return self.parse_response(response) + + def post(self, endpoint, data): + url = self.endpoint + "/" + endpoint + # self.logger.debug("POST: " + url) + response = requests.post(url, headers = self.gen_header(), json = data) return self.parse_response(response) # Connect to the API to determine if your token is still valid. @@ -62,6 +70,20 @@ class ApiContext: try: return self.token and self.get("user/me") except Exception as ex: - self.logger.debug(format("Error from endpoint: {}", ex)) + self.logger.debug("Error from endpoint: %s" % ex) return False + + # Basic login endpoint, should return your token on success + def login(self, username, password, expire_seconds = False): + data = { + "username" : username, + "password" : password + } + if expire_seconds: + data["expireSeconds"] = expire_seconds + return self.post("user/login", data) + + # Get information about the API. Very useful to test your connection to the API + def api_status(self): + return self.get("status") \ No newline at end of file diff --git a/main.py b/main.py index 553a7c5..3687545 100644 --- a/main.py +++ b/main.py @@ -2,8 +2,10 @@ import os import json import logging -import contentapi import toml +import getpass + +import contentapi import myutils CONFIGFILE="config.toml" @@ -21,6 +23,9 @@ def main(): load_or_create_global_config() logging.info("Config: " + json.dumps(config, indent = 2)) context = contentapi.ApiContext(config["api"], logging) + logging.info("Testing connection to API at " + config["api"]) + logging.debug(json.dumps(context.api_status(), indent = 2)) + authenticate(config, context) print("Program end") # Loads the config from file into the global config var. If the file @@ -29,7 +34,7 @@ def main(): def load_or_create_global_config(): global config # Check if the config file exists - if os.path.exists(CONFIGFILE): + if os.path.isfile(CONFIGFILE): # Read and deserialize the config file with open(CONFIGFILE, 'r', encoding='utf-8') as f: temp_config = toml.load(f) @@ -43,6 +48,35 @@ def load_or_create_global_config(): myutils.set_logging_level(config["default_loglevel"]) +# Either pull the token from a file, or get the login from the command +# line if that doesn't work. WILL test your token against the real API +# even if it's pulled from file! +def authenticate(config, context: contentapi.ApiContext): + message = "No token file found" + if os.path.isfile(config["tokenfile"]): + with open(config["tokenfile"], 'r') as f: + token = f.read() + if context.is_token_valid(): + context.token = token + logging.info("Logged in using token file " + config["tokenfile"]) + return + else: + message = "Token file expired" + + while True: + print(message + ", Please enter login for " + config["api"]) + username = input("Username: ") + password = getpass.getpass("Password: ") + try: + token = context.login(username, password, config["expire_seconds"]) + with open(config["tokenfile"], 'w') as f: + f.write(token) + logging.info("Token accepted, written to " + config["tokenfile"]) + context.token = token + except Exception as ex: + print("ERROR: Could not login: %s" % ex) + + # Because python reasons if __name__ == "__main__": main() \ No newline at end of file