diff --git a/utils/scripts/kagari.py b/utils/scripts/kagari.py index 0bb217b77e6099ee0524f9a3c6a174892248d120..171918def60456cb8db8906464a79d8bb2201aee 100755 --- a/utils/scripts/kagari.py +++ b/utils/scripts/kagari.py @@ -25,8 +25,9 @@ def create_parser(): 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""" + -search: searches the database with filters for info on the files + -dl: downloads the target file with an ID, or multiple files with filters + To download the entire database, use neither ID nor filters""" parser = argparse.ArgumentParser( usage=usage, @@ -52,7 +53,7 @@ def create_parser(): "-i", "--id", help='In "dl" mode, if specified, downloads target video file.' - + "\nIf absent in this mode, downloads the tar of the whole base." + + "\nIf absent in this mode, filter arguments are used instead." + '\nIn "search" mode, returns info of the target file.\n ', ) @@ -70,32 +71,32 @@ def create_parser(): "--check", action="store_true", help='In "dl" mode, first displays info on the targeted' - + "\nfile, and asks for confirmation before downloading.\n ", + + "\nfile(s), and asks for confirmation before downloading.\n ", ) parser.add_argument( "-a", "--author", - help='In "search" mode, filters by author. (case insensitive)' + help='In either mode, filters by author. (case insensitive)', ) parser.add_argument( "-c", "--category", dest="cat", - help='In "search" mode, filters by category.\n ', + help='In either mode, filters by category.\n ', ) parser.add_argument( - "-t", "--type", help='In "search" mode, filters by type.\n ' + "-t", "--type", help='In either 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 " + help='In either mode, filters on the names of sources and songs' + + "\nmatching the given string. (case insensitive)\n ", ) parser.add_argument( @@ -110,7 +111,7 @@ def create_parser(): "-j", "--json", action="store_true", - help='In "search" mode, output the result as a json.' + help='In "search" mode, output the result as a json.', ) return parser @@ -132,6 +133,44 @@ def make_request(url): return response +def search_query(args): + "Searches the database with ID or filters, and returns the response json" + 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) + + # Handling an error json + if "error" in kara_json: + default_emsg = "An unknown error happened when searching the database." + emsg = kara_json.get("message", default_emsg) + print(emsg) + sys.exit(1) + + return kara_json + + def build_kara_name(data): "Builds the name of the file from the response dictionary" src_name = data["source_name"] @@ -148,9 +187,13 @@ def build_kara_name(data): return kara_name, kara_additional -def kara_prompt(kara_string): +def kara_prompt(kara_string="", kara_nbr=0): "Simple yes/no prompt to confirm the download or not" - prompt_str = "You are about to download " + kara_string + prompt_str = "You are about to download " + if kara_string: + prompt_str += kara_string + else: + prompt_str += f"{kara_nbr} files" prompt_str += "\nDo you want to proceed ? ([y]/n) " answers = {"y": True, "n": False, "ye": True, "yes": True, "no": False} @@ -162,49 +205,38 @@ def kara_prompt(kara_string): ans = "y" except KeyboardInterrupt: ans = "n" - print("\n") + print("") 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" +def dl_mode(args, kara_json): + "Downloads the targeted files" - print(f'Downloading and writing archive as "{filename}"') - response = make_request(url) + kara_id = None + kara_string = "" + kara_nbr = 0 - # 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 + if len(kara_json) == 1: + if type(kara_json) is list: + kara_json = kara_json[0] + kara_name, kara_additional = build_kara_name(kara_json) + kara_string = '"' + kara_name + '"' + kara_additional + kara_id = kara_json["id"] 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}" + kara_nbr = len(kara_json) + + # If asked for check, display the prompt + if args.check: + prompt = kara_prompt(kara_string, kara_nbr) + if not prompt: + print("Download cancelled.") + return + + if kara_id: + url = f"https://kurisu.iiens.net/api/download/{kara_id}" path = args.path if args.path else os.getcwd() filename = path + "/" + kara_name + ".mkv" @@ -212,71 +244,64 @@ def dl_mode(args): response = make_request(url) # Writing the video file - with open(filename, 'wb') as out_file: - try: + with open(filename, "wb") as out_file: + shutil.copyfileobj(response, out_file) + else: + print(f"Download in progress: 0/{kara_nbr}", end="") + sys.stdout.flush() + for curr_nbr, kara in enumerate(kara_json): + kara_id = kara["id"] + kara_name, _ = build_kara_name(kara) + url = f"https://kurisu.iiens.net/api/download/{kara_id}" + path = args.path if args.path else os.getcwd() + filename = path + "/" + kara_name + ".mkv" + + response = make_request(url) + + # Writing the video file + with open(filename, "wb") as out_file: shutil.copyfileobj(response, out_file) - except KeyboardInterrupt: - print("\nDownload interrupted by user input.") - else: - print("All done !") + print(f"\rDownload in progress: {curr_nbr + 1}/{kara_nbr}", end="") + sys.stdout.flush() + print("") + print("All done !") -def search_mode(args): +def search_mode(args, kara_json): "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) - if args.json: pretty_output = json.dumps(kara_json, indent=4, ensure_ascii=False) + pretty_output += "\n" else: pretty_output = "" for res in kara_json: - new_kara = (" NEW!" if res['is_new'] == "1" else "") - output_string = build_kara_name(res) - output_string = f"[{res['id']}] {output_string[0]}\n" + \ - f"{output_string[1]} in {res['author_year']}{new_kara}\n" + \ - f" Size: {res['size']} Kio\n" + new_kara = " NEW!" if res["is_new"] == "1" else "" + output_info = build_kara_name(res) + output_string = ( + f"[{res['id']}] {output_info[0]}\n" + + f"{output_info[1]} in {res['author_year']}{new_kara}\n" + + f" Size: {res['size']} Kio\n" + ) pretty_output += output_string - if res['popularity']: - pretty_output += " Popularity: " + res['popularity'] + "\n" - if res['upload_comment']: - for comment_line in wrap(res['upload_comment'], fix_sentence_endings=True): + if res["popularity"]: + pretty_output += " Popularity: " + res["popularity"] + "\n" + if res["upload_comment"]: + for comment_line in wrap( + res["upload_comment"], fix_sentence_endings=True + ): pretty_output += " " + comment_line + "\n" pretty_output += "\n" # Displaying the received JSON, or writing it to a file if not args.path: - print(pretty_output, end='') + print(pretty_output, end="") 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 + emsg += str(err) print(emsg) sys.exit(1) @@ -286,10 +311,15 @@ def run(): parser = create_parser() args = parser.parse_args() + kara_json = search_query(args) + if args.mode == "dl": - dl_mode(args) + try: + dl_mode(args, kara_json) + except KeyboardInterrupt: + print("\nDownload interrupted by user input.") elif args.mode == "search": - search_mode(args) + search_mode(args, kara_json) else: print(f'Unknown mode "{args.mode}". Use either "dl" or "search".') sys.exit(1)