From ef99b02cfabf1d81b245b8e414190e41cbd6b648 Mon Sep 17 00:00:00 2001 From: gouedard2016 <felix.gouedard@ensiie.fr> Date: Sat, 6 Feb 2021 11:51:21 +0100 Subject: [PATCH] Add first version of Kurisu client Kagari --- utils/scripts/kagari | 274 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100755 utils/scripts/kagari diff --git a/utils/scripts/kagari b/utils/scripts/kagari new file mode 100755 index 00000000..2fe012e9 --- /dev/null +++ b/utils/scripts/kagari @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import json +import shutil +import argparse + +from urllib.error import URLError +from urllib.parse import urlencode +from urllib.request import urlopen + + +class CustomFormatter( + argparse.RawTextHelpFormatter, + argparse.RawDescriptionHelpFormatter, +): + pass + + +def create_parser(): + "Creates and returns a parser for the CLI arguments" + usage = "kagari [-h] (dl [dl options] | search [search options])" + + desc = """Client to interact with the Kurisu API. Two main modes: + -dl: downloads the specified file, or a tar file of the entire database + -search: searches the database with filters for info on the files""" + + parser = argparse.ArgumentParser( + usage=usage, + description=desc, + formatter_class=CustomFormatter, + add_help=False, + ) + + parser.add_argument( + "-h", + "--help", + action="help", + default=argparse.SUPPRESS, + help="Show this help message and exit.\n ", + ) + + parser.add_argument( + "mode", + help='The mode used. Can be either "dl" or "search".', + ) + + parser.add_argument( + "-i", + "--id", + help='In "dl" mode, if specified, downloads target video file.' + + "\nIf absent in this mode, downloads the tar of the whole base." + + '\nIn "search" mode, returns info of the target file.\n ', + ) + + parser.add_argument( + "-p", + "--path", + help='In "dl" mode, indicates the folder in which to store the file.' + + "\nIf absent in this mode, defaults to current working directory." + + '\nIn "search" mode, if specified, saves the output as a file' + + " at the given path.\n ", + ) + + parser.add_argument( + "-C", + "--check", + action="store_true", + help='In "dl" mode, first displays info on the targeted' + + "\nfile, and asks for confirmation before downloading.\n ", + ) + + parser.add_argument( + "-a", + "--author", + help='In "search" mode, filters by author. (case insensitive)' + ) + + parser.add_argument( + "-c", + "--category", + dest="cat", + help='In "search" mode, filters by category.\n ', + ) + + parser.add_argument( + "-t", "--type", help='In "search" mode, filters by type.\n ' + ) + + parser.add_argument( + "-s", + "--string", + dest="search", + help='In "search" mode, filters on the names of sources and songs' + + "\nmatching the given string. (case insensitive)\n " + ) + + parser.add_argument( + "-r", + "--random", + action="store_true", + help='In "search" mode, randomizes the order of the files ' + + "in the response.\n ", + ) + + return parser + + +def make_request(url): + "Executes an HTTP request with basic error handling" + try: + response = urlopen(url) + except URLError as err: + if hasattr(err, "reason"): + emsg = f"Failed to reach target URL. Reason: {err.reason}" + print(emsg) + sys.exit(1) + elif hasattr(err, "code"): + emsg = f"The server could not fulfill the request. Code {err.code}" + print(emsg) + sys.exit(1) + return response + + +def build_kara_name(data): + "Builds the name of the file from the response dictionary" + src_name = data["source_name"] + song_name = data["song_name"] + song_type = data["song_type"] + song_nbr = data["song_number"] + category = data["category"] + language = data["language"] + author = data["author_name"] + + kara_name = f"{src_name} - {song_type}{song_nbr} - {song_name}" + kara_additional = f" ({category}/{language}) by {author}" + + return kara_name, kara_additional + + +def kara_prompt(kara_string): + "Simple yes/no prompt to confirm the download or not" + prompt_str = "You are about to download " + kara_string + prompt_str += "\nDo you want to proceed ? ([y]/n) " + + answers = {"y": True, "n": False, "ye": True, "yes": True, "no": False} + ans = None + while ans not in answers.keys(): + try: + ans = input(prompt_str).lower() + if not ans: + ans = "y" + except KeyboardInterrupt: + ans = "n" + print("\n") + except EOFError: + ans = "y" + + return answers[ans] + + +def dl_mode(args): + "Downloads either the whole database as a tar, or a single video file" + + # No ID, download the tar of the database + if not args.id: + url = "https://kurisu.iiens.net/kara.tar" + path = args.path if args.path else os.getcwd() + filename = path + "/kara.tar" + + print(f'Downloading and writing archive as "{filename}"') + response = make_request(url) + + # Writing the tar file + with open(filename, 'wb') as out_file: + try: + shutil.copyfileobj(response, out_file) + except KeyboardInterrupt: + print("\nDownload interrupted by user input.") + else: + print("All done !") + # An ID, download target file + else: + search_url = f"https://kurisu.iiens.net/api?id={args.id}" + search = make_request(search_url) + kara_data = json.loads(search.read().decode("utf-8")) + kara_name, kara_additional = build_kara_name(kara_data) + + # If asked for check, display the prompt + if args.check: + kara_string = '"' + kara_name + '"' + kara_additional + prompt = kara_prompt(kara_string) + if not prompt: + print("Download cancelled.") + return + + url = f"https://kurisu.iiens.net/api/download/{args.id}" + path = args.path if args.path else os.getcwd() + filename = path + "/" + kara_name + ".mkv" + + print(f'Downloading and writing video as "{filename}"') + response = make_request(url) + + # Writing the video file + with open(filename, 'wb') as out_file: + try: + shutil.copyfileobj(response, out_file) + except KeyboardInterrupt: + print("\nDownload interrupted by user input.") + else: + print("All done !") + + +def search_mode(args): + "Performs a search request on the database and returns the answer" + url = "https://kurisu.iiens.net/api" + url_values = dict() + + # If ID, it's the only parameter needed + if args.id: + url_values["id"] = args.id + # No ID, building the query parameters + else: + for arg_name in ["author", "cat", "type", "search", "random"]: + arg = getattr(args, arg_name) + if arg: + url_values[arg_name] = arg + + url_args = urlencode(url_values) + if url_args: + url += "?" + url_args + + response = make_request(url) + + try: + kara_json = json.loads(response.read().decode("utf-8")) + except ValueError: + emsg = "The response JSON could not be loaded." + print(emsg) + sys.exit(1) + + # Displaying the received JSON, or writing it to a file + pretty_output = json.dumps(kara_json, indent=4, ensure_ascii=False) + if not args.path: + print(pretty_output) + else: + try: + with open(args.path, "w") as output_file: + output_file.write(pretty_output) + except OSError as err: + emsg = "Cannot create/open the search output file: " + emsg += err + print(emsg) + sys.exit(1) + + +def run(): + "Runs the command" + parser = create_parser() + args = parser.parse_args() + + if args.mode == "dl": + dl_mode(args) + elif args.mode == "search": + search_mode(args) + else: + print(f'Unknown mode "{args.mode}". Use either "dl" or "search".') + sys.exit(1) + + +if __name__ == "__main__": + run() -- GitLab