def pprint_row(serie_name, lang, mode, index=False, remove=False): if index and remove: return paint(f"[!red][#] {serie_name} [{mode}]") if index and not remove: ret_str = paint(f"[#green][>] {serie_name}") else: ret_str = f"[ ] {serie_name}" return ret_str + paint(f" [#cyan]({lang}) [#blue][{mode}]")
def pprint_settings(): config = get_config() labels = ("Current path", "Backup", "Telegram") path_str = paint(config.get("path"), c_settings) backup = get_last_backup() backup_str = paint(backup, c_settings) telegram_str = ((paint(config.get("telegram-bot-token"), c_settings) + paint(":", style=Style.BOLD) + paint(config.get("telegram-chat-id"), c_settings)) if config.get("telegram-bot-token") else "") values = (path_str, backup_str, telegram_str) return "\n".join(f"{paint(lab,style=Style.BOLD)}: {val}" for lab, val in zip(labels, values))
def get_spotify_songs(playlist_link): config = get_config() try: client = Spotify(client_credentials_manager=SpotifyClientCredentials( config.get("spotify-id"), config.get("spotify-secret-id"))) playlist = client.playlist(playlist_link) except SpotifyOauthError: print("\n" + paint("Error! ", Color.RED) + "Please run the configuration once.") print("Run " + paint("saberio --config", Color.BLUE)) exit(-1) return [( sanitize_song_name(song.get("track").get("name")), song.get("track").get("artists")[0].get("name"), ) for song in playlist.get("tracks").get("items")]
def download(action): serie_list = [list(serie.values()) for serie in CONFIG.get("serie")] for name, url, folder, lang, mode in serie_list: SPINNER.start(paint(f"Scanning [#blue]{name}")) eurostreaming_url = path.join(get_eurostreaming_site(), url) page = LinkFinder(eurostreaming_url, sub=lang == "eng") eps_to_download = episodes_to_download(folder, page, mode) for se, ep in eps_to_download: spinner(SPINNER.start, "Finding link for", name, se, ep) try: link = page.get_direct_links(se, ep) except ValueError: spinner(SPINNER.fail, "Fail to get the link for", name, se, ep) if action != "test": send_telegram_log(name, se, ep, success=False) continue if action == "run": basepath = path.join(CONFIG.get("path"), folder, f"Stagione {se}") if not path.exists(basepath): makedirs(basepath) filename = path.join( basepath, f"{sanitize_name(name)}_s{int(se):02d}e{ep:02d}.mp4") spinner(SPINNER.start, "Downloading", name, se, ep) try: download_video(link, name, filename) except Exception: spinner(SPINNER.fail, "Fail to download", name, se, ep) send_telegram_log(name, se, ep, success=False) continue spinner(SPINNER.succeed, "Downloaded", name, se, ep) send_telegram_log(name, se, ep) elif action == "test": spinner(SPINNER.info, "Found", name, se, ep)
def pprint_actions(mode=None): if mode == "confirm": actions = {"y": "confirm", "n": "back"} elif mode == "add": actions = {"ws": "move", "c": "confirm", "b": "back"} elif mode == "back": actions = {"b": "back"} elif mode == "settings": actions = { "u": "backup", "r": "restore", "p": "path", "t": "telegram", "e": "eurostreaming", "b": "back", } elif mode == "path": actions = {"e": "edit", "b": "back"} else: actions = { "ws": "move", "a": "add", "r": "remove", "e": "settings", "q": "quit", } return ("-" * sum(len(action) + 5 for action in actions.values()) + "\n" + " ".join( paint(f"[[@bold]{key}[/@]]:[#magenta]{action}") for key, action in actions.items()))
def download_song(song_name, link, filename): colored_song_name = paint(song_name, Color.BLUE) if not link: SPINNER.fail(f"No link working for {colored_song_name}") SPINNER.start(f"Downloading {colored_song_name}") zip_song = get(link, headers=HEADERS) open(filename, "wb").write(zip_song.content) SPINNER.succeed(f"Downloaded {colored_song_name}")
def pprint_settings(): config = get_config() labels = ("Eurostreaming", "Current path", "Backup", "Telegram") eurostreaming_url = f"[#blue]{config.get('eurostreaming')}" path_str = f"[#blue]{config.get('path')}" backup_str = f"[#blue]{get_last_backup()}" telegram_str = (f"[#blue]{config.get('telegram-bot-token')}[/# @bold]:" f"[/@ #blue]{config.get('telegram-chat-id')}") telegram_str = config.get("telegram-bot-token") and telegram_str or "" values = (eurostreaming_url, path_str, backup_str, telegram_str) return "\n".join( paint(f"[@bold]{lab}[/]: {val}") for lab, val in zip(labels, values))
def pprint_row(anime_name, season, mode, index=False, remove=False): if index and remove: return paint(f"[#] {anime_name} [{mode}]", background=Background.RED) if index and not remove: ret_str = paint("[>] ", c_anime_menu) + paint(anime_name, c_anime_menu) else: ret_str = "[ ] " + paint(anime_name) return (ret_str + paint(f" (s{season}) ", c_season_menu) + paint(f"[{mode}]", c_mode_menu))
def retrieve_params(args): spotify_playlist_link = None if not args.file and not args.song: spotify_playlist_link = input( paint("> ", Color.WHITE) + paint("Spotify", Color.GREEN) + paint(" playlist link: ", Color.WHITE)) while not search("https://open.spotify.com/playlist/", spotify_playlist_link): spotify_playlist_link = input( paint("Bad link!", Color.RED) + paint(" Retry: ", Color.WHITE)) if not args.p: playlist_name = input( paint("> Choose a name for the playlist: ", Color.WHITE)) else: playlist_name = args.p if not args.auto and not args.list and not args.test: while (automatic := input( paint("> Choose mode: ", Color.WHITE) + paint("[auto|list|test] ", Color.WHITE, style=Style.BOLD)). lower()) not in ("auto", "list", "test"): pass auto = automatic != "list" test = automatic == "test"
def songs_table(songs): headers = [ paint(h, style=Style.BOLD) for h in ["", "Code", "Song", "Mapper", "Up", "Down", "Difficulty", "Date"] ] entries = [( i, paint(code, Color.MAGENTA), lines_splitting(name), paint(mapper, Color.BLUE), paint(up, Color.GREEN), paint(down, Color.RED), paint(difficulties, Color.YELLOW), paint(f"{date:%d.%m.%Y}", Color.GRAY), ) for i, (code, name, _, difficulties, up, down, mapper, date) in enumerate(songs, 1)] return tabulate(entries, headers=headers, tablefmt="fancy_grid", disable_numparse=True)
def spinner(func, action, anime, season, episode): func( paint(f"{action} ", c_action_download) + paint(f"{anime} ", c_anime_download) + paint(f"{season}x{episode}", c_episode_download))
def spinner(func, action, serie, season, episode): func( paint(f"[#white]{action} [#blue]{serie} [#magenta]{season}x{episode}"))
def main(): args = argparsing() # spotify config if args.config: print("To get a key, go to " + paint("https://developer.spotify.com/dashboard/applications", Color.MAGENTA) + " and create a new application.") client_id = input("Client ID: ") secret_id = input("Secret ID: ") save_config(client_id, secret_id) try: client = Spotify( client_credentials_manager=SpotifyClientCredentials( client_id, secret_id)) client.search("test") print(paint("Configuration successful!", Color.GREEN)) except SpotifyOauthError: print(paint("Configuration failed.", Color.RED)) exit(-1) # script if args.dir and not path.exists(path.join(args.dir)): print( paint("Path ", Color.RED) + paint(args.dir, Color.RED, style=Style.UNDERLINE) + paint(" doesn't exist!", Color.RED)) exit(-1) path_to_folder = args.dir or "." spotify_playlist_link, playlist_name, automatic, test = retrieve_params( args=args) if args.file: try: songs_to_search = [(*special_song_name(lines), None) for lines in open(args.file).read().split("\n") if lines] except FileNotFoundError: print( paint("File ", Color.RED) + paint(args.file, Color.RED, style=Style.UNDERLINE) + paint(" not found!", Color.RED)) exit(-1) print( paint("> Songs list provided via file ", Color.WHITE) + paint(args.file, Color.BLUE)) elif args.song: songs_to_search = [(*special_song_name(args.song), None)] print( paint("> Single song search ", Color.WHITE) + paint(args.song, Color.BLUE)) else: songs_to_search = [ (None, f"{title} {artist}", title) for title, artist in get_spotify_songs(spotify_playlist_link) ] print( paint("> Songs list provided via ", Color.WHITE) + paint("Spotify playlist", Color.BLUE)) if not test and not path.exists(path.join(path_to_folder, playlist_name)): mkdir(path.join(path_to_folder, playlist_name)) print() # downloading for code_song, song_more, song_less in songs_to_search: song_to_download = None if not code_song: bsaber_songs = search_songs(song_more) if song_less: bsaber_songs = combine_search(bsaber_songs, search_songs(song_less)) SPINNER.succeed(f"Searched for {paint(song_more,Color.BLUE)}") if bsaber_songs: bsaber_songs = sorted(bsaber_songs, key=lambda x: (-x[4] + 1) / (x[5] + 1)) if not automatic: print(songs_table(bsaber_songs)) n = input(paint("> Choose a song: [0:skip] ", Color.WHITE)) while not match(r"\d+", n) or int(n) not in range( len(bsaber_songs) + 1): n = input( paint("Wrong! ", Color.RED) + paint("> Retry: [0:skip] ", Color.WHITE)) else: n = 1 if int(n): song_to_download = bsaber_songs[int(n) - 1] else: SPINNER.fail(f"Skipped {paint(song_more,Color.BLUE)}") else: SPINNER.fail( f"No song found for {paint(song_more,Color.BLUE)}") else: SPINNER.succeed(f"Searched for {paint(song_more,Color.BLUE)} " f"[{paint(code_song,Color.MAGENTA)}]") song_to_download = get_info_by_code(code_song) # song downloading if song_to_download: code_song, song_name, song_link = song_to_download[:3] path_to_file = path.join( path_to_folder, playlist_name, multisub({ "/": "_", " – ": "_", " ": "_" }, song_name), ) filename = f"{path_to_file}.zip" if playlist_name else song_name if test: SPINNER.succeed(f"Matched with {paint(song_name,Color.BLUE)}") elif not path.exists(filename): download_song(song_name, song_link, filename) else: SPINNER.succeed( f"Already downloaded {paint(song_name,Color.BLUE)}") open(path.join(path_to_folder, f"{playlist_name}.log"), "a+").write(f"{song_name} #{code_song}\n") print("\n")
def lines_splitting(name): MAX_CHAR = 45 if len(name) <= MAX_CHAR: return paint(name, Color.WHITE) return paint(name[:MAX_CHAR], Color.WHITE) + "\n" + lines_splitting( name[MAX_CHAR:])
def manage(): index = 0 k = "start" while k != "q": anime_list = [ list(anime.values()) for anime in get_config().get("anime") ] print(pprint_anime(anime_list, index)) print(pprint_actions()) k = direct_input(choices=("w", "s", "e", "a", "r", "q")) erase(len(anime_list or [0]) + 2) if k in ("w", "s"): if k == "w" and index: index -= 1 if k == "s" and index < len(anime_list) - 1: index += 1 if k == "e": e_k = "start" while e_k != "b": print(pprint_settings()) print(pprint_actions(mode="settings")) e_k = direct_input(choices=("u", "r", "p", "t", "c", "b")) erase(5) if e_k == "p": base = paint("Path", style=Style.BOLD) + ": " new_path = strict_input( base, wrong_text=paint("Wrong path! ", Color.RED) + base, check=path.exists, flush=True, ) add_new_path(new_path) e_k = "" elif e_k == "r": backup_filename = get_last_backup() if backup_filename: backup_dict = load(open(backup_filename)) save_config(backup_dict) elif e_k == "u": now = datetime.now() config = get_config() dump( config, open(f"{now:%Y-%m-%d}_saturno-backup.json", "w"), indent=4, ) elif e_k == "t": base = paint("Telegram bot token", style=Style.BOLD) + ": " telegram_bot_token = strict_input( base, wrong_text=paint("Invalid token! ", Color.RED) + base, check=is_bot_valid, flush=True, ) base = paint("Telegram chat ID", style=Style.BOLD) + ": " telegram_chat_id = strict_input( base, wrong_text=paint("Invalid chat ID! ", Color.RED) + base, regex=r"\-?\d+$", flush=True, ) add_telegram_config(telegram_bot_token, telegram_chat_id) elif e_k == "c": print("Color can be:", end="") sample("color") erase(2) color_labels = [ "anime name [menu]", "season [menu]", "mode [menu]", "action [download]", "anime name [download]", "episode [download]", "setting", "button legend", ] colors = list() for label in color_labels: base = "Color for " + paint(f"{label}", style=Style.BOLD) + ": " color_choose = strict_input( base, wrong_text=paint("Invalid color! ", Color.RED) + base, check=is_color_valid, flush=True, ) colors.append(color_choose) erase() add_colors(colors) if anime_list and k == "r": print(pprint_anime(anime_list, index, remove=True)) print(pprint_actions(mode="confirm")) r_k = direct_input(choices=("y", "b")) if r_k == "y": remove_anime(index) index = 0 erase(len(anime_list) + 2) if k == "a": q_index = 0 q_k = "start" # anime search query = input(paint("Anime name", style=Style.BOLD) + ": ") erase() query_list = search_anime(query) if not query_list: print(f"No anime found with {paint(query,Color.BLUE)}!") print(pprint_actions(mode="back")) q_k = direct_input(choices=("b", )) erase(3) while q_k not in ("c", "b"): print(pprint_query(query_list, q_index)) print(pprint_actions(mode="add")) q_k = direct_input() erase(len(query_list) + 2) if q_k in ("w", "s"): if q_k == "w" and q_index: q_index -= 1 if q_k == "s" and q_index < len(query_list) - 1: q_index += 1 # new anime if q_k == "c": base = paint("Season", style=Style.BOLD) + ": " season = strict_input( base, f"{paint('This is not a season!',Color.RED)} {base}", regex=r"\d{1,2}$", flush=True, ) base = paint("Folder name", style=Style.BOLD) + ": " name = strict_input( base, f"{paint('Folder name must be unique!', Color.RED)} {base}", check=is_folder_unique, flush=True, ) base = paint("Mode [full|new]", style=Style.BOLD) + ": " mode = strict_input( base, f"{paint('Mode must be full or new!', Color.RED)} {base}", choices=("full", "new"), flush=True, ) print(recap_new_anime(*query_list[q_index], season, name, mode)) print(pprint_actions(mode="confirm")) c_k = direct_input(choices=("y", "n")) if c_k == "y": add_anime(*query_list[q_index], season, name, mode) erase(7) index = 0
def pprint_query(query_list, selected): return "\n".join( paint(f"[#green][>] {name}") if selected == i else f"[ ] {name}" for i, (name, _) in enumerate(query_list))
def manage(eurostreaming_url): index = 0 k = "start" while k != "q": serie_list = [ list(serie.values()) for serie in get_config().get("serie") ] print(pprint_serie(serie_list, index)) print(pprint_actions()) k = direct_input(choices=("w", "s", "e", "a", "r", "q")) erase(len(serie_list or [0]) + 2) if k in ("w", "s"): if k == "w" and index: index -= 1 if k == "s" and index < len(serie_list) - 1: index += 1 if k == "e": e_k = "start" while e_k != "b": print(pprint_settings()) print(pprint_actions(mode="settings")) e_k = direct_input(choices=("u", "r", "p", "t", "y", "e", "b")) erase(6) if e_k == "p": base = paint("[@bold]Path[/]: ") new_path = strict_input( base, wrong_text=paint(f"[#red]Wrong path![/] {base}"), check=lambda x: path.exists(x.strip()), flush=True, ) add_new_path(new_path) e_k = "" elif e_k == "r": backup_filename = get_last_backup() if backup_filename: backup_dict = load(open(backup_filename)) save_config(backup_dict) elif e_k == "u": now = datetime.now() config = get_config() dump( config, open(f"{now:%Y-%m-%d}_europlexo-backup.json", "w"), indent=4, ) elif e_k == "t": base = paint("[@bold]Telegram bot token[/]: ") telegram_bot_token = strict_input( base, wrong_text=paint(f"[#red]Invalid token! {base}"), check=is_bot_valid, flush=True, ) base = paint("[@bold]Telegram chat ID[/]: ") telegram_chat_id = strict_input( base, wrong_text=paint(f"[#red]Invalid chat ID![/] {base}"), regex=r"\-?\d+$", flush=True, ) add_telegram_config(telegram_bot_token, telegram_chat_id) elif e_k == "e": base = paint("[@bold]Eurostreaming site[/]: ") eurostreaming_url = strict_input( base, wrong_text=paint( f"[#red]Wrong site, unreacheable![/] {base}"), check=is_eurostreaming_valid, flush=True, ) add_eurostreaming_site(eurostreaming_url) if serie_list and k == "r": print(pprint_serie(serie_list, index, remove=True)) print(pprint_actions(mode="confirm")) r_k = direct_input(choices=("y", "n")) if r_k == "y": remove_serie(index) index = 0 erase(len(serie_list) + 2) if k == "a": q_index = 0 q_k = "start" # serie search query = input(paint("[@bold]Serie name[/]: ")) erase() SPINNER.start(paint(f"Searching for [#blue]{query}")) query_list = get_suggestion_list(eurostreaming_url, query) SPINNER.stop() if not query_list: ppaint(f"No serie found with [#blue]{query}") print(pprint_actions(mode="back")) q_k = direct_input(choices=("b", )) erase(3) while q_k not in ("c", "b"): print(pprint_query(query_list, q_index)) print(pprint_actions(mode="add")) q_k = direct_input() erase(len(query_list) + 2) if q_k in ("w", "s"): if q_k == "w" and q_index: q_index -= 1 if q_k == "s" and q_index < len(query_list) - 1: q_index += 1 # new serie if q_k == "c": base = paint("[@bold]Folder name[/]: ") folder = strict_input( base, paint(f"[#red]Folder name must be unique![/] {base}"), check=is_folder_unique, flush=True, ) base = paint("[@bold]Language [eng|ita][/]: ") lang = strict_input( base, paint(f"[#red]Language must be 'eng' or 'ita'![/] {base}"), choices=("eng", "ita"), flush=True, ) base = paint("[@bold]Mode [full|new|last][/]: ") mode = strict_input( base, paint( f"[#red]Mode must be 'full', 'new' or 'last'![/] {base}" ), choices=("full", "new", "last"), flush=True, ) print(recap_new_serie(*query_list[q_index], folder, lang, mode)) print(pprint_actions(mode="confirm")) c_k = direct_input(choices=("y", "n")) if c_k == "y": name, link = query_list[q_index] link = multisub({eurostreaming_url: "", "/": ""}, link) add_serie(name, link, folder, lang, mode) erase(7) index = 0
def recap_new_serie(name, url, folder, lang, mode): return paint( f"Name: [#blue]{name}[/]\nLink: [#blue]{url}[/]\nFolder: [#blue]{folder}[/]\n" f"Language: [#blue]{lang}[/]\nMode: [#blue]{mode}", )