diff --git a/src/music_kraken/__main__.py b/src/music_kraken/__main__.py index 6ba0cff..4aa30ef 100644 --- a/src/music_kraken/__main__.py +++ b/src/music_kraken/__main__.py @@ -65,6 +65,13 @@ if __name__ == "__main__": help="Resets the config file to the default one.", action="store_true" ) + + parser.add_argument( + "--invidious", + "-i", + help="Set a good and fast invidious instance from your homecountry, to reduce the latency.", + action="store_true" + ) arguments = parser.parse_args() @@ -94,6 +101,11 @@ if __name__ == "__main__": if os.path.exists(music_kraken.shared.CONFIG_FILE): os.remove(music_kraken.shared.CONFIG_FILE) music_kraken.read() + + if arguments.invidious: + from .cli.options import invidious + invidious() + exit() # getting the genre genre: str = arguments.genre diff --git a/src/music_kraken/cli/__init__.py b/src/music_kraken/cli/__init__.py index c21af81..b4e57ef 100644 --- a/src/music_kraken/cli/__init__.py +++ b/src/music_kraken/cli/__init__.py @@ -1 +1,2 @@ -from .download.shell import Shell \ No newline at end of file +from .download.shell import Shell +from .options import invidious \ No newline at end of file diff --git a/src/music_kraken/cli/options/__init__.py b/src/music_kraken/cli/options/__init__.py index e69de29..0ab4d7f 100644 --- a/src/music_kraken/cli/options/__init__.py +++ b/src/music_kraken/cli/options/__init__.py @@ -0,0 +1,4 @@ +from .invidious.shell import InvidiousShell + +def invidious(): + shell = InvidiousShell() \ No newline at end of file diff --git a/src/music_kraken/cli/options/invidious/__init__.py b/src/music_kraken/cli/options/invidious/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/music_kraken/cli/options/invidious/shell.py b/src/music_kraken/cli/options/invidious/shell.py new file mode 100644 index 0000000..1d83d9c --- /dev/null +++ b/src/music_kraken/cli/options/invidious/shell.py @@ -0,0 +1,102 @@ +from typing import Dict, List +import requests +from dataclasses import dataclass +from collections import defaultdict + +from ....utils import config, write +from ....utils import exception + + +INSTANCES_ENDPOINT = "https://api.invidious.io/instances.json" + +@dataclass +class Instance: + """ + Attributes which influence the quality of an instance: + + - users + """ + name: str + uri: str + region: str + users: int + + def __str__(self) -> str: + return f"{self.name} with {self.users} users." + + +class InvidiousShell: + def __init__(self): + self.region_flags = dict() + self.region_instances: Dict[str, List[Instance]] = defaultdict(list) + + self.download_instances() + self.region = self.get_country() + print() + self.choose_instance() + + + 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"] + flag = instance_data["flag"] + + self.region_flags[region] = flag + + instance = Instance( + name=all_instance_data[0], + uri=instance_data["uri"], + region=region, + users=stats["usage"]["users"]["total"] + ) + + self.region_instances[region].append(instance) + + def download_instances(self): + print("Download idonvidious instances...") + r = requests.get(INSTANCES_ENDPOINT) + + for instance in r.json(): + self.process_instance(all_instance_data=instance) + + def get_country(self): + print("Input the country code, an example would be \"US\"") + print(f"({' | '.join(f'{region}-{flag}' for region, flag in self.region_flags.items())})") + + chosen_region = "" + + while chosen_region.upper() not in self.region_instances: + chosen_region = input("nearest country: ").strip().upper() + + return chosen_region + + def choose_instance(self): + instance_list = self.region_instances[self.region] + instance_list.sort(key=lambda x: x.users, reverse=True) + + # output the options + print("Choose your instance (input needs to be a digit):") + for i, instance in enumerate(instance_list): + print(f"{i}) {instance}") + + print() + + # ask for index + index = "" + while not index.isdigit() or int(index) >= len(instance_list): + index = input("> ").strip() + + instance = instance_list[int(index)] + print() + print(f"Setting the instance to {instance}") + + config.set_name_to_value("invidious_instance", instance.uri) + write() + \ No newline at end of file diff --git a/src/music_kraken/utils/config/connection.py b/src/music_kraken/utils/config/connection.py index 1dfb4bf..4f35f6a 100644 --- a/src/music_kraken/utils/config/connection.py +++ b/src/music_kraken/utils/config/connection.py @@ -19,7 +19,7 @@ class UrlStringAttribute(StringAttribute): def validate(self, value: str): v = value.strip() url = re.match(URL_PATTERN, v) - if v != url: + if url is None: raise SettingValueError( setting_name=self.name, setting_value=v, @@ -64,18 +64,14 @@ class ConnectionSection(Section): ) # INVIDIOUS INSTANCES LIST - self.INVIDIOUS_INSTANCE = UrlListAttribute( - name="invidious_instances", + self.INVIDIOUS_INSTANCE = UrlStringAttribute( + name="invidious_instance", description="This is a List, where you can define the invidious instances,\n" "the youtube downloader should use.\n" "Here is a list of active ones: https://docs.invidious.io/instances/\n" "Instances that use cloudflare or have source code changes could cause issues.\n" "Hidden instances (.onion) will only work, when setting 'tor=true'.", - value=[ - "https://yt.artemislena.eu/", - "https://watch.thekitty.zone/", - "https://y.com.sb/" - ] + value="https://yt.artemislena.eu/" ) # INVIDIOUS PROXY self.INVIDIOUS_PROXY_VIDEOS = BoolAttribute( @@ -88,7 +84,9 @@ class ConnectionSection(Section): self.USE_TOR, self.TOR_PORT, self.CHUNK_SIZE, - self.SHOW_DOWNLOAD_ERRORS_THRESHOLD + self.SHOW_DOWNLOAD_ERRORS_THRESHOLD, + self.INVIDIOUS_INSTANCE, + self.INVIDIOUS_PROXY_VIDEOS ] super().__init__()