draft
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Hazel 2024-05-24 14:46:38 +02:00
parent c0fbd16929
commit cef87460a7
6 changed files with 119 additions and 35 deletions

6
.vscode/launch.json vendored
View File

@ -17,6 +17,12 @@
"request": "launch", "request": "launch",
"program": "development/actual_donwload.py", "program": "development/actual_donwload.py",
"console": "integratedTerminal" "console": "integratedTerminal"
},
{
"name": "Python Debugger: Music Kraken",
"type": "debugpy",
"request": "launch", // run the module
"module": "music_kraken",
} }
] ]
} }

View File

@ -4,10 +4,9 @@ from pathlib import Path
from typing import Dict, List, Set, Type from typing import Dict, List, Set, Type
from .. import console from .. import console
from ..download import Downloader from ..download import Downloader, Page
from ..download.results import GoToResults, Option, PageResults, Results from ..download.results import GoToResults, Option, PageResults, Results
from ..objects import Album, Artist, DatabaseObject, Song from ..objects import Album, Artist, DatabaseObject, Song
from ..pages import Page
from ..utils import BColors, output from ..utils import BColors, output
from ..utils.config import main_settings, write_config from ..utils.config import main_settings, write_config
from ..utils.enums.colors import BColors from ..utils.enums.colors import BColors

View File

@ -8,8 +8,8 @@ from copy import copy
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from string import Formatter from string import Formatter
from typing import (TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, List,
TypedDict, Union) Optional, Set, Tuple, Type, TypedDict, Union)
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -23,7 +23,7 @@ from ..utils import BColors, output, trace
from ..utils.config import main_settings, youtube_settings from ..utils.config import main_settings, youtube_settings
from ..utils.enums import ALL_SOURCE_TYPES, SourceType from ..utils.enums import ALL_SOURCE_TYPES, SourceType
from ..utils.enums.album import AlbumType from ..utils.enums.album import AlbumType
from ..utils.exception import MKMissingNameException from ..utils.exception import MKComposeException, MKMissingNameException
from ..utils.exception.download import UrlNotFoundException from ..utils.exception.download import UrlNotFoundException
from ..utils.path_manager import LOCATIONS from ..utils.path_manager import LOCATIONS
from ..utils.shared import DEBUG_PAGES from ..utils.shared import DEBUG_PAGES
@ -75,7 +75,7 @@ class Downloader:
self.scan_for_pages(**kwargs) self.scan_for_pages(**kwargs)
def register_page(self, page_type: Type[Page], **kwargs): def register_page(self, page_type: Type[Page], **kwargs):
if page_type in _registered_pages: if page_type in self._registered_pages:
return return
self._registered_pages[page_type].add(page_type( self._registered_pages[page_type].add(page_type(
@ -343,12 +343,14 @@ class Page:
return super().__new__(cls) return super().__new__(cls)
def __init__(self, download_options: DownloadOptions = None, fetch_options: FetchOptions = None, **kwargs): def __init__(self, download_options: DownloadOptions = None, fetch_options: FetchOptions = None, **kwargs):
if self.SOURCE_TYPE is not None:
self.SOURCE_TYPE.register_page(self) self.SOURCE_TYPE.register_page(self)
self.download_options: DownloadOptions = download_options or DownloadOptions() self.download_options: DownloadOptions = download_options or DownloadOptions()
self.fetch_options: FetchOptions = fetch_options or FetchOptions() self.fetch_options: FetchOptions = fetch_options or FetchOptions()
def __del__(self): def __del__(self):
if self.SOURCE_TYPE is not None:
self.SOURCE_TYPE.deregister_page() self.SOURCE_TYPE.deregister_page()
def _search_regex(self, pattern, string, default=None, fatal=True, flags=0, group=None): def _search_regex(self, pattern, string, default=None, fatal=True, flags=0, group=None):
@ -451,23 +453,73 @@ class Option:
This could represent a data object, a string or a page. This could represent a data object, a string or a page.
""" """
def __init__(self, value: Any, text: Optional[str] = None, keys: Set[str] = None, hidden: bool = False): def __init__(
self,
value: Any,
text: Optional[str] = None,
keys: List[Any] = None,
hidden: bool = False,
parse_key: Callable[[Any], Any] = lambda x: x,
):
self._parse_key: Callable[[Any], Any] = parse_key
self.value = value self.value = value
self.text = text or str(value) self.text = text or str(value)
self.hidden = hidden self.hidden = hidden
self.keys = keys or set() self._raw_keys = set(keys or [])
self.keys.add(self.text) self._raw_keys.add(self.text)
self.keys = set(self.parse_key(key) for key in self._raw_keys)
def register_key(self, key: Any):
self._raw_keys.add(key)
self.keys.add(self._parse_key(key))
@property
def parse_key(self) -> Callable[[Any], Any]:
return self._parse_key
@parse_key.setter
def parse_key(self, value: Callable[[Any], Any]):
self._parse_key = value
self.keys = set(self._parse_key(key) for key in self._raw_keys)
def __str__(self):
return self.text
class SelectOption: class Select:
def __init__(self, options: List[Option] = None): def __init__(
self,
options: List[Option] = None,
option_factory: Callable[[Any], Option] = None,
raw_options: List[Any] = None,
parse_option_key: Callable[[Any], Any] = lambda x: x,
ask_for_creating_option: Callable[[Option], bool] = lambda x: True,
**kwargs
):
self._parse_option_key: Callable[[Any], Any] = parse_option_key
self._ask_for_creating_option: Callable[[Option], bool] = ask_for_creating_option
self._key_to_option: Dict[Any, Option] = dict() self._key_to_option: Dict[Any, Option] = dict()
self._options: List[Option] = options self._options: List[Option] = []
self.extend(options or []) options = options or []
self.option_factory: Optional[Callable[[Any], Option]] = option_factory
if self.can_create_options:
for raw_option in raw_options or []:
self.append(self.option_factory(raw_option))
elif raw_options is not None:
raise MKComposeException("Cannot create options without a factory.")
self.extend(options)
@property
def can_create_options(self) -> bool:
return self.option_factory is not None
def append(self, option: Option): def append(self, option: Option):
option.parse_key = self._parse_option_key
self._options.append(option) self._options.append(option)
for key in option.keys: for key in option.keys:
self._key_to_option[key] = option self._key_to_option[key] = option
@ -475,3 +527,32 @@ class SelectOption:
def extend(self, options: List[Option]): def extend(self, options: List[Option]):
for option in options: for option in options:
self.append(option) self.append(option)
def __iter__(self) -> Generator[Option, None, None]:
for option in self._options:
if option.hidden:
continue
yield option
def __contains__(self, key: Any) -> bool:
return key in self._key_to_option
def __getitem__(self, key: Any) -> Option:
return self._key_to_option[key]
def create_option(self, key: Any, **kwargs) -> Option:
if not self.can_create_options:
raise MKComposeException("Cannot create options without a factory.")
option = self.option_factory(key, **kwargs)
self.append(option)
return option
def choose(self, key: Any) -> Optional[Option]:
if key not in self:
if self.can_create_options and self._ask_for_creating_option(key):
return self.create_option(key)
return None
return self[key]

View File

@ -1,8 +1,12 @@
from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Generator, List, Tuple, Type, Union from typing import TYPE_CHECKING, Dict, Generator, List, Tuple, Type, Union
from ..objects import DatabaseObject from ..objects import DatabaseObject
from . import Page
if TYPE_CHECKING:
from . import Page
@dataclass @dataclass

View File

@ -1,26 +1,17 @@
from typing import List, Optional, Type, Tuple
from urllib.parse import urlparse, urlunparse, parse_qs
from enum import Enum from enum import Enum
import requests from typing import List, Optional, Tuple, Type
from urllib.parse import parse_qs, urlparse, urlunparse
import python_sponsorblock import python_sponsorblock
import requests
from ...objects import Source, DatabaseObject, Song, Target
from .._abstract import Page
from ...objects import (
Artist,
Source,
Song,
Album,
Label,
Target,
FormattedText,
ID3Timestamp
)
from ...connection import Connection from ...connection import Connection
from ...download import Page
from ...objects import (Album, Artist, DatabaseObject, FormattedText,
ID3Timestamp, Label, Song, Source, Target)
from ...utils.config import logging_settings, main_settings, youtube_settings
from ...utils.enums import ALL_SOURCE_TYPES, SourceType
from ...utils.support_classes.download_result import DownloadResult from ...utils.support_classes.download_result import DownloadResult
from ...utils.config import youtube_settings, logging_settings, main_settings
from ...utils.enums import SourceType, ALL_SOURCE_TYPES
def get_invidious_url(path: str = "", params: str = "", query: str = "", fragment: str = "") -> str: def get_invidious_url(path: str = "", params: str = "", query: str = "", fragment: str = "") -> str:

View File

@ -3,6 +3,9 @@ class MKBaseException(Exception):
self.message = message self.message = message
super().__init__(message, **kwargs) super().__init__(message, **kwargs)
# Compose exceptions. Those usually mean a bug on my side.
class MKComposeException(MKBaseException):
pass
# Downloading # Downloading
class MKDownloadException(MKBaseException): class MKDownloadException(MKBaseException):