feat: build
This commit is contained in:
5
music_kraken/cli/__init__.py
Normal file
5
music_kraken/cli/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .informations import print_paths
|
||||
from .main_downloader import download
|
||||
from .options.settings import settings
|
||||
from .options.frontend import set_frontend
|
||||
|
1
music_kraken/cli/informations/__init__.py
Normal file
1
music_kraken/cli/informations/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .paths import print_paths
|
22
music_kraken/cli/informations/paths.py
Normal file
22
music_kraken/cli/informations/paths.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from ..utils import cli_function
|
||||
|
||||
from ...utils.path_manager import LOCATIONS
|
||||
from ...utils.config import main_settings
|
||||
|
||||
|
||||
def all_paths():
|
||||
return {
|
||||
"Temp dir": main_settings["temp_directory"],
|
||||
"Music dir": main_settings["music_directory"],
|
||||
"Conf dir": LOCATIONS.CONFIG_DIRECTORY,
|
||||
"Conf file": LOCATIONS.CONFIG_FILE,
|
||||
"logging file": main_settings["log_file"],
|
||||
"FFMPEG bin": main_settings["ffmpeg_binary"],
|
||||
"Cache Dir": main_settings["cache_directory"],
|
||||
}
|
||||
|
||||
|
||||
@cli_function
|
||||
def print_paths():
|
||||
for name, path in all_paths().items():
|
||||
print(f"{name}:\t{path}")
|
420
music_kraken/cli/main_downloader.py
Normal file
420
music_kraken/cli/main_downloader.py
Normal file
@@ -0,0 +1,420 @@
|
||||
import random
|
||||
from typing import Set, Type, Dict, List
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from .utils import cli_function
|
||||
from .options.first_config import initial_config
|
||||
|
||||
from ..utils.config import write_config, main_settings
|
||||
from ..utils.regex import URL_PATTERN
|
||||
from ..utils.string_processing import fit_to_file_system
|
||||
from ..utils.support_classes.query import Query
|
||||
from ..utils.support_classes.download_result import DownloadResult
|
||||
from ..utils.exception.download import UrlNotFoundException
|
||||
from ..utils.enums.colors import BColors
|
||||
from ..download.results import Results, Option, PageResults
|
||||
from ..download.page_attributes import Pages
|
||||
from ..pages import Page
|
||||
from ..objects import Song, Album, Artist, DatabaseObject
|
||||
|
||||
"""
|
||||
This is the implementation of the Shell
|
||||
|
||||
# Behaviour
|
||||
|
||||
## Searching
|
||||
|
||||
```mkshell
|
||||
> s: {querry or url}
|
||||
|
||||
# examples
|
||||
> s: https://musify.club/release/some-random-release-183028492
|
||||
> s: r: #a an Artist #r some random Release
|
||||
```
|
||||
|
||||
Searches for an url, or an query
|
||||
|
||||
### Query Syntax
|
||||
|
||||
```
|
||||
#a {artist} #r {release} #t {track}
|
||||
```
|
||||
|
||||
You can escape stuff like `#` doing this: `\#`
|
||||
|
||||
## Downloading
|
||||
|
||||
To download something, you either need a direct link, or you need to have already searched for options
|
||||
|
||||
```mkshell
|
||||
> d: {option ids or direct url}
|
||||
|
||||
# examples
|
||||
> d: 0, 3, 4
|
||||
> d: 1
|
||||
> d: https://musify.club/release/some-random-release-183028492
|
||||
```
|
||||
|
||||
## Misc
|
||||
|
||||
### Exit
|
||||
|
||||
```mkshell
|
||||
> q
|
||||
> quit
|
||||
> exit
|
||||
> abort
|
||||
```
|
||||
|
||||
### Current Options
|
||||
|
||||
```mkshell
|
||||
> .
|
||||
```
|
||||
|
||||
### Previous Options
|
||||
|
||||
```
|
||||
> ..
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
EXIT_COMMANDS = {"q", "quit", "exit", "abort"}
|
||||
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
|
||||
PAGE_NAME_FILL = "-"
|
||||
MAX_PAGE_LEN = 21
|
||||
|
||||
|
||||
def get_existing_genre() -> List[str]:
|
||||
"""
|
||||
gets the name of all subdirectories of shared.MUSIC_DIR,
|
||||
but filters out all directories, where the name matches with any patern
|
||||
from shared.NOT_A_GENRE_REGEX.
|
||||
"""
|
||||
existing_genres: List[str] = []
|
||||
|
||||
# get all subdirectories of MUSIC_DIR, not the files in the dir.
|
||||
existing_subdirectories: List[Path] = [f for f in main_settings["music_directory"].iterdir() if f.is_dir()]
|
||||
|
||||
for subdirectory in existing_subdirectories:
|
||||
name: str = subdirectory.name
|
||||
|
||||
if not any(re.match(regex_pattern, name) for regex_pattern in main_settings["not_a_genre_regex"]):
|
||||
existing_genres.append(name)
|
||||
|
||||
existing_genres.sort()
|
||||
|
||||
return existing_genres
|
||||
|
||||
|
||||
def get_genre():
|
||||
existing_genres = get_existing_genre()
|
||||
for i, genre_option in enumerate(existing_genres):
|
||||
print(f"{i + 1:0>2}: {genre_option}")
|
||||
|
||||
while True:
|
||||
genre = input("Id or new genre: ")
|
||||
|
||||
if genre.isdigit():
|
||||
genre_id = int(genre) - 1
|
||||
if genre_id >= len(existing_genres):
|
||||
print(f"No genre under the id {genre_id + 1}.")
|
||||
continue
|
||||
|
||||
return existing_genres[genre_id]
|
||||
|
||||
new_genre = fit_to_file_system(genre)
|
||||
|
||||
agree_inputs = {"y", "yes", "ok"}
|
||||
verification = input(f"create new genre \"{new_genre}\"? (Y/N): ").lower()
|
||||
if verification in agree_inputs:
|
||||
return new_genre
|
||||
|
||||
|
||||
def help_message():
|
||||
print()
|
||||
print(random.choice(main_settings["happy_messages"]))
|
||||
print()
|
||||
|
||||
|
||||
class Downloader:
|
||||
def __init__(
|
||||
self,
|
||||
exclude_pages: Set[Type[Page]] = None,
|
||||
exclude_shady: bool = False,
|
||||
max_displayed_options: int = 10,
|
||||
option_digits: int = 3,
|
||||
genre: str = None,
|
||||
process_metadata_anyway: bool = False,
|
||||
) -> None:
|
||||
self.pages: Pages = Pages(exclude_pages=exclude_pages, exclude_shady=exclude_shady)
|
||||
|
||||
self.page_dict: Dict[str, Type[Page]] = dict()
|
||||
|
||||
self.max_displayed_options = max_displayed_options
|
||||
self.option_digits: int = option_digits
|
||||
|
||||
self.current_results: Results = None
|
||||
self._result_history: List[Results] = []
|
||||
|
||||
self.genre = genre or get_genre()
|
||||
self.process_metadata_anyway = process_metadata_anyway
|
||||
|
||||
print()
|
||||
print(f"Downloading to: \"{self.genre}\"")
|
||||
print()
|
||||
|
||||
def print_current_options(self):
|
||||
self.page_dict = dict()
|
||||
|
||||
print()
|
||||
|
||||
page_count = 0
|
||||
for option in self.current_results.formated_generator(max_items_per_page=self.max_displayed_options):
|
||||
if isinstance(option, Option):
|
||||
color = BColors.BOLD.value if self.pages.is_downloadable(option.music_object) else BColors.GREY.value
|
||||
print(f"{color}{option.index:0{self.option_digits}} {option.music_object.option_string}{BColors.ENDC.value}")
|
||||
else:
|
||||
prefix = ALPHABET[page_count % len(ALPHABET)]
|
||||
print(
|
||||
f"{BColors.HEADER.value}({prefix}) ------------------------{option.__name__:{PAGE_NAME_FILL}<{MAX_PAGE_LEN}}------------{BColors.ENDC.value}")
|
||||
|
||||
self.page_dict[prefix] = option
|
||||
self.page_dict[option.__name__] = option
|
||||
|
||||
page_count += 1
|
||||
|
||||
print()
|
||||
|
||||
def set_current_options(self, current_options: Results):
|
||||
if main_settings["result_history"]:
|
||||
self._result_history.append(current_options)
|
||||
|
||||
if main_settings["history_length"] != -1:
|
||||
if len(self._result_history) > main_settings["history_length"]:
|
||||
self._result_history.pop(0)
|
||||
|
||||
self.current_results = current_options
|
||||
|
||||
def previous_option(self) -> bool:
|
||||
if not main_settings["result_history"]:
|
||||
print("History is turned of.\nGo to main_settings, and change the value at 'result_history' to 'true'.")
|
||||
return False
|
||||
|
||||
if len(self._result_history) <= 1:
|
||||
print(f"No results in history.")
|
||||
return False
|
||||
self._result_history.pop()
|
||||
self.current_results = self._result_history[-1]
|
||||
return True
|
||||
|
||||
def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query:
|
||||
song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True)
|
||||
album = None if not "r" in key_text else Album(title=key_text["r"], dynamic=True)
|
||||
artist = None if not "a" in key_text else Artist(name=key_text["a"], dynamic=True)
|
||||
|
||||
if song is not None:
|
||||
if album is not None:
|
||||
song.album_collection.append(album)
|
||||
if artist is not None:
|
||||
song.main_artist_collection.append(artist)
|
||||
return Query(raw_query=query, music_object=song)
|
||||
|
||||
if album is not None:
|
||||
if artist is not None:
|
||||
album.artist_collection.append(artist)
|
||||
return Query(raw_query=query, music_object=album)
|
||||
|
||||
if artist is not None:
|
||||
return Query(raw_query=query, music_object=artist)
|
||||
|
||||
return Query(raw_query=query)
|
||||
|
||||
def search(self, query: str):
|
||||
if re.match(URL_PATTERN, query) is not None:
|
||||
try:
|
||||
page, data_object = self.pages.fetch_url(query)
|
||||
except UrlNotFoundException as e:
|
||||
print(f"{e.url} could not be attributed/parsed to any yet implemented site.\n"
|
||||
f"PR appreciated if the site isn't implemented.\n"
|
||||
f"Recommendations and suggestions on sites to implement appreciated.\n"
|
||||
f"But don't be a bitch if I don't end up implementing it.")
|
||||
return
|
||||
self.set_current_options(PageResults(page, data_object.options))
|
||||
self.print_current_options()
|
||||
return
|
||||
|
||||
special_characters = "#\\"
|
||||
query = query + " "
|
||||
|
||||
key_text = {}
|
||||
|
||||
skip_next = False
|
||||
escape_next = False
|
||||
new_text = ""
|
||||
latest_key: str = None
|
||||
for i in range(len(query) - 1):
|
||||
current_char = query[i]
|
||||
next_char = query[i + 1]
|
||||
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
continue
|
||||
|
||||
if escape_next:
|
||||
new_text += current_char
|
||||
escape_next = False
|
||||
|
||||
# escaping
|
||||
if current_char == "\\":
|
||||
if next_char in special_characters:
|
||||
escape_next = True
|
||||
continue
|
||||
|
||||
if current_char == "#":
|
||||
if latest_key is not None:
|
||||
key_text[latest_key] = new_text
|
||||
new_text = ""
|
||||
|
||||
latest_key = next_char
|
||||
skip_next = True
|
||||
continue
|
||||
|
||||
new_text += current_char
|
||||
|
||||
if latest_key is not None:
|
||||
key_text[latest_key] = new_text
|
||||
|
||||
parsed_query: Query = self._process_parsed(key_text, query)
|
||||
|
||||
self.set_current_options(self.pages.search(parsed_query))
|
||||
self.print_current_options()
|
||||
|
||||
def goto(self, index: int):
|
||||
page: Type[Page]
|
||||
music_object: DatabaseObject
|
||||
|
||||
try:
|
||||
page, music_object = self.current_results.get_music_object_by_index(index)
|
||||
except KeyError:
|
||||
print()
|
||||
print(f"The option {index} doesn't exist.")
|
||||
print()
|
||||
return
|
||||
|
||||
self.pages.fetch_details(music_object)
|
||||
|
||||
print(music_object)
|
||||
print(music_object.options)
|
||||
self.set_current_options(PageResults(page, music_object.options))
|
||||
|
||||
self.print_current_options()
|
||||
|
||||
def download(self, download_str: str, download_all: bool = False) -> bool:
|
||||
to_download: List[DatabaseObject] = []
|
||||
|
||||
if re.match(URL_PATTERN, download_str) is not None:
|
||||
_, music_objects = self.pages.fetch_url(download_str)
|
||||
to_download.append(music_objects)
|
||||
|
||||
else:
|
||||
index: str
|
||||
for index in download_str.split(", "):
|
||||
if not index.strip().isdigit():
|
||||
print()
|
||||
print(f"Every download thingie has to be an index, not {index}.")
|
||||
print()
|
||||
return False
|
||||
|
||||
for index in download_str.split(", "):
|
||||
to_download.append(self.current_results.get_music_object_by_index(int(index))[1])
|
||||
|
||||
print()
|
||||
print("Downloading:")
|
||||
for download_object in to_download:
|
||||
print(download_object.option_string)
|
||||
print()
|
||||
|
||||
_result_map: Dict[DatabaseObject, DownloadResult] = dict()
|
||||
|
||||
for database_object in to_download:
|
||||
r = self.pages.download(music_object=database_object, genre=self.genre, download_all=download_all,
|
||||
process_metadata_anyway=self.process_metadata_anyway)
|
||||
_result_map[database_object] = r
|
||||
|
||||
for music_object, result in _result_map.items():
|
||||
print()
|
||||
print(music_object.option_string)
|
||||
print(result)
|
||||
|
||||
return True
|
||||
|
||||
def process_input(self, input_str: str) -> bool:
|
||||
input_str = input_str.strip()
|
||||
processed_input: str = input_str.lower()
|
||||
|
||||
if processed_input in EXIT_COMMANDS:
|
||||
return True
|
||||
|
||||
if processed_input == ".":
|
||||
self.print_current_options()
|
||||
return False
|
||||
|
||||
if processed_input == "..":
|
||||
if self.previous_option():
|
||||
self.print_current_options()
|
||||
return False
|
||||
|
||||
if processed_input.startswith("s: "):
|
||||
self.search(input_str[3:])
|
||||
return False
|
||||
|
||||
if processed_input.startswith("d: "):
|
||||
return self.download(input_str[3:])
|
||||
|
||||
if processed_input.isdigit():
|
||||
self.goto(int(processed_input))
|
||||
return False
|
||||
|
||||
if processed_input != "help":
|
||||
print(f"{BColors.WARNING.value}Invalid input.{BColors.ENDC.value}")
|
||||
help_message()
|
||||
return False
|
||||
|
||||
def mainloop(self):
|
||||
while True:
|
||||
if self.process_input(input("> ")):
|
||||
return
|
||||
|
||||
|
||||
@cli_function
|
||||
def download(
|
||||
genre: str = None,
|
||||
download_all: bool = False,
|
||||
direct_download_url: str = None,
|
||||
command_list: List[str] = None,
|
||||
process_metadata_anyway: bool = False,
|
||||
):
|
||||
if main_settings["hasnt_yet_started"]:
|
||||
code = initial_config()
|
||||
if code == 0:
|
||||
main_settings["hasnt_yet_started"] = False
|
||||
write_config()
|
||||
print(f"{BColors.OKGREEN.value}Restart the programm to use it.{BColors.ENDC.value}")
|
||||
else:
|
||||
print(f"{BColors.FAIL.value}Something went wrong configuring.{BColors.ENDC.value}")
|
||||
|
||||
shell = Downloader(genre=genre, process_metadata_anyway=process_metadata_anyway)
|
||||
|
||||
if command_list is not None:
|
||||
for command in command_list:
|
||||
shell.process_input(command)
|
||||
return
|
||||
|
||||
if direct_download_url is not None:
|
||||
if shell.download(direct_download_url, download_all=download_all):
|
||||
return
|
||||
|
||||
shell.mainloop()
|
0
music_kraken/cli/options/__init__.py
Normal file
0
music_kraken/cli/options/__init__.py
Normal file
26
music_kraken/cli/options/cache.py
Normal file
26
music_kraken/cli/options/cache.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from logging import getLogger
|
||||
|
||||
from ..utils import cli_function
|
||||
from ...connection.cache import Cache
|
||||
|
||||
|
||||
@cli_function
|
||||
def clear_cache():
|
||||
"""
|
||||
Deletes the cache.
|
||||
:return:
|
||||
"""
|
||||
|
||||
Cache("main", getLogger("cache")).clear()
|
||||
print("Cleared cache")
|
||||
|
||||
|
||||
@cli_function
|
||||
def clean_cache():
|
||||
"""
|
||||
Deletes the outdated cache. (all expired cached files, and not indexed files)
|
||||
:return:
|
||||
"""
|
||||
|
||||
Cache("main", getLogger("cache")).clean()
|
||||
print("Cleaned cache")
|
6
music_kraken/cli/options/first_config.py
Normal file
6
music_kraken/cli/options/first_config.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .frontend import set_frontend
|
||||
|
||||
|
||||
def initial_config():
|
||||
code = set_frontend(no_cli=True)
|
||||
return code
|
196
music_kraken/cli/options/frontend.py
Normal file
196
music_kraken/cli/options/frontend.py
Normal file
@@ -0,0 +1,196 @@
|
||||
from typing import Dict, List
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ..utils import cli_function
|
||||
|
||||
from ...objects import Country
|
||||
from ...utils import config, write_config
|
||||
from ...utils.config import youtube_settings
|
||||
from ...connection import Connection
|
||||
|
||||
|
||||
@dataclass
|
||||
class Instance:
|
||||
"""
|
||||
Attributes which influence the quality of an instance:
|
||||
|
||||
- users
|
||||
"""
|
||||
name: str
|
||||
uri: str
|
||||
regions: List[Country]
|
||||
users: int = 0
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} with {self.users} users."
|
||||
|
||||
|
||||
class FrontendInstance:
|
||||
SETTING_NAME = "placeholder"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.region_instances: Dict[Country, List[Instance]] = defaultdict(list)
|
||||
self.all_instances: List[Instance] = []
|
||||
|
||||
def add_instance(self, instance: Instance):
|
||||
self.all_instances.append(instance)
|
||||
|
||||
youtube_lists = youtube_settings["youtube_url"]
|
||||
existing_netlocs = set(tuple(url.netloc for url in youtube_lists))
|
||||
|
||||
parsed_instance = urlparse(instance.uri)
|
||||
instance_netloc = parsed_instance.netloc
|
||||
|
||||
if instance_netloc not in existing_netlocs:
|
||||
youtube_lists.append(parsed_instance)
|
||||
youtube_settings.__setitem__("youtube_url", youtube_lists, is_parsed=True)
|
||||
|
||||
for region in instance.regions:
|
||||
self.region_instances[region].append(instance)
|
||||
|
||||
def fetch(self, silent: bool = False):
|
||||
if not silent:
|
||||
print(f"Downloading {type(self).__name__} instances...")
|
||||
|
||||
def set_instance(self, instance: Instance):
|
||||
youtube_settings.__setitem__(self.SETTING_NAME, instance.uri)
|
||||
|
||||
def _choose_country(self) -> List[Instance]:
|
||||
print("Input the country code, an example would be \"US\"")
|
||||
print('\n'.join(f'{region.name} ({region.alpha_2})' for region in self.region_instances))
|
||||
print()
|
||||
|
||||
|
||||
available_instances = set(i.alpha_2 for i in self.region_instances)
|
||||
|
||||
chosen_region = ""
|
||||
|
||||
while chosen_region not in available_instances:
|
||||
chosen_region = input("nearest country: ").strip().upper()
|
||||
|
||||
return self.region_instances[Country.by_alpha_2(chosen_region)]
|
||||
|
||||
def choose(self, silent: bool = False):
|
||||
instances = self.all_instances if silent else self._choose_country()
|
||||
instances.sort(key=lambda x: x.users, reverse=True)
|
||||
|
||||
if silent:
|
||||
self.set_instance(instances[0])
|
||||
return
|
||||
|
||||
# output the options
|
||||
print("Choose your instance (input needs to be a digit):")
|
||||
for i, instance in enumerate(instances):
|
||||
print(f"{i}) {instance}")
|
||||
|
||||
print()
|
||||
|
||||
# ask for index
|
||||
index = ""
|
||||
while not index.isdigit() or int(index) >= len(instances):
|
||||
index = input("> ").strip()
|
||||
|
||||
instance = instances[int(index)]
|
||||
print()
|
||||
print(f"Setting the instance to {instance}")
|
||||
|
||||
self.set_instance(instance)
|
||||
|
||||
|
||||
class Invidious(FrontendInstance):
|
||||
SETTING_NAME = "invidious_instance"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.connection = Connection(host="https://api.invidious.io/")
|
||||
self.endpoint = "https://api.invidious.io/instances.json"
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
||||
def _process_instance(self, all_instance_data: dict):
|
||||
instance_data = all_instance_data[1]
|
||||
stats = instance_data["stats"]
|
||||
|
||||
if not instance_data["api"]:
|
||||
return
|
||||
if instance_data["type"] != "https":
|
||||
return
|
||||
|
||||
region = instance_data["region"]
|
||||
|
||||
instance = Instance(
|
||||
name=all_instance_data[0],
|
||||
uri=instance_data["uri"],
|
||||
regions=[Country.by_alpha_2(region)],
|
||||
users=stats["usage"]["users"]["total"]
|
||||
)
|
||||
|
||||
self.add_instance(instance)
|
||||
|
||||
def fetch(self, silent: bool):
|
||||
r = self.connection.get(self.endpoint)
|
||||
if r is None:
|
||||
return
|
||||
|
||||
for instance in r.json():
|
||||
self._process_instance(all_instance_data=instance)
|
||||
|
||||
|
||||
class Piped(FrontendInstance):
|
||||
SETTING_NAME = "piped_instance"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.connection = Connection(host="https://raw.githubusercontent.com")
|
||||
|
||||
super().__init__()
|
||||
|
||||
def process_instance(self, instance_data: str):
|
||||
cells = instance_data.split(" | ")
|
||||
|
||||
instance = Instance(
|
||||
name=cells[0].strip(),
|
||||
uri=cells[1].strip(),
|
||||
regions=[Country.by_emoji(flag) for flag in cells[2].split(", ")]
|
||||
)
|
||||
|
||||
self.add_instance(instance)
|
||||
|
||||
def fetch(self, silent: bool = False):
|
||||
r = self.connection.get("https://raw.githubusercontent.com/wiki/TeamPiped/Piped-Frontend/Instances.md")
|
||||
if r is None:
|
||||
return
|
||||
|
||||
process = False
|
||||
|
||||
for line in r.content.decode("utf-8").split("\n"):
|
||||
line = line.strip()
|
||||
|
||||
if line != "" and process:
|
||||
self.process_instance(line)
|
||||
|
||||
if line.startswith("---"):
|
||||
process = True
|
||||
|
||||
|
||||
class FrontendSelection:
|
||||
def __init__(self):
|
||||
self.invidious = Invidious()
|
||||
self.piped = Piped()
|
||||
|
||||
def choose(self, silent: bool = False):
|
||||
self.invidious.fetch(silent)
|
||||
self.invidious.choose(silent)
|
||||
|
||||
self.piped.fetch(silent)
|
||||
self.piped.choose(silent)
|
||||
|
||||
|
||||
@cli_function
|
||||
def set_frontend(silent: bool = False):
|
||||
shell = FrontendSelection()
|
||||
shell.choose(silent=silent)
|
||||
|
||||
return 0
|
||||
|
71
music_kraken/cli/options/settings.py
Normal file
71
music_kraken/cli/options/settings.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from ..utils import cli_function
|
||||
|
||||
from ...utils.config import config, write_config
|
||||
from ...utils import exception
|
||||
|
||||
|
||||
def modify_setting(_name: str, _value: str, invalid_ok: bool = True) -> bool:
|
||||
try:
|
||||
config.set_name_to_value(_name, _value)
|
||||
except exception.config.SettingException as e:
|
||||
if invalid_ok:
|
||||
print(e)
|
||||
return False
|
||||
else:
|
||||
raise e
|
||||
|
||||
write_config()
|
||||
return True
|
||||
|
||||
|
||||
def print_settings():
|
||||
for i, attribute in enumerate(config):
|
||||
print(f"{i:0>2}: {attribute.name}={attribute.value}")
|
||||
|
||||
|
||||
def modify_setting_by_index(index: int) -> bool:
|
||||
attribute = list(config)[index]
|
||||
|
||||
print()
|
||||
print(attribute)
|
||||
|
||||
input__ = input(f"{attribute.name}=")
|
||||
if not modify_setting(attribute.name, input__.strip()):
|
||||
return modify_setting_by_index(index)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def modify_setting_by_index(index: int) -> bool:
|
||||
attribute = list(config)[index]
|
||||
|
||||
print()
|
||||
print(attribute)
|
||||
|
||||
input__ = input(f"{attribute.name}=")
|
||||
if not modify_setting(attribute.name, input__.strip()):
|
||||
return modify_setting_by_index(index)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli_function
|
||||
def settings(
|
||||
name: str = None,
|
||||
value: str = None,
|
||||
):
|
||||
if name is not None and value is not None:
|
||||
modify_setting(name, value, invalid_ok=True)
|
||||
return
|
||||
|
||||
while True:
|
||||
print_settings()
|
||||
|
||||
input_ = input("Id of setting to modify: ")
|
||||
print()
|
||||
if input_.isdigit() and int(input_) < len(config):
|
||||
if modify_setting_by_index(int(input_)):
|
||||
return
|
||||
else:
|
||||
print("Please input a valid ID.")
|
||||
print()
|
42
music_kraken/cli/utils.py
Normal file
42
music_kraken/cli/utils.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from ..utils.shared import get_random_message
|
||||
|
||||
|
||||
def cli_function(function):
|
||||
def wrapper(*args, **kwargs):
|
||||
silent = kwargs.get("no_cli", False)
|
||||
if "no_cli" in kwargs:
|
||||
del kwargs["no_cli"]
|
||||
|
||||
if silent:
|
||||
return function(*args, **kwargs)
|
||||
return
|
||||
|
||||
code = 0
|
||||
|
||||
print_cute_message()
|
||||
print()
|
||||
try:
|
||||
code = function(*args, **kwargs)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nRaise an issue if I fucked up:\nhttps://github.com/HeIIow2/music-downloader/issues")
|
||||
|
||||
finally:
|
||||
print()
|
||||
print_cute_message()
|
||||
print("See you soon! :3")
|
||||
|
||||
exit()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def print_cute_message():
|
||||
message = get_random_message()
|
||||
try:
|
||||
print(message)
|
||||
except UnicodeEncodeError:
|
||||
message = str(c for c in message if 0 < ord(c) < 127)
|
||||
print(message)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user