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