Skip to content

Commit

Permalink
Improve configuration
Browse files Browse the repository at this point in the history
Describe how to use the configuration file in the readme and make it
possible to specify cookie files in the configuration file.
  • Loading branch information
jo1gi committed Jan 27, 2024
1 parent db6f6a1 commit 608f843
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 27 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,28 @@ Not all fields are available for all audiobooks.

The file extension can be changed with the `--output-format` argument.

## Configuration
audiobook-dl can be configured using a configuration file, which should be placed at:
- Windows: `C:\\Users\\$user\\AppData\\Local\\jo1gi\\audiobook-dl\\audiobook-dl.toml`
- Mac: `/Users/$user/Library/Application Support/audiobook-dl/audiobook-dl.toml`
- Linux `$XDG_CONFIG_DIR/audiobook-dl/audiobook-dl.toml`

### Authentications
Source credentials can be provided in the configuration file:
```toml
[sources.yourcloudlibrary]
username = "yourusername"
password = "supersecretpassword"
library = "hometown"
```

Cookie files can be specified in a similar way:
```toml
[sources.everand]
cookie_file = "./everand_cookies.txt"
```
Paths are relative to the configuration directory.

## Contributions
Issues, bug-reports, pull requests or ideas for features and improvements are
**very welcome**.
Expand Down
23 changes: 13 additions & 10 deletions audiobookdl/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .utils.audiobook import Audiobook, Series
from .output.download import download
from .sources import find_compatible_source
from .config import load_config, Config
from .config import load_config, Config, SourceConfig

import os
import sys
Expand Down Expand Up @@ -65,41 +65,43 @@ def process_url(url: str, options, config: Config):
process_audiobook(audiobook, options)


def get_cookie_path(options) -> Optional[str]:
def get_cookie_path(options, config: Optional[SourceConfig]) -> Optional[str]:
"""
Find path to cookie file. The cookie files a looked for in cli arguments
and in the current directory.
:param options: Cli options
:param config: Configuration for source
:returns: Path to cookie file
"""
if options.cookie_file is not None and os.path.exists(options.cookie_file):
return options.cookie_file
if config is not None and config.cookie_file is not None and os.path.exists(config.cookie_file):
return config.cookie_file
if os.path.exists("./cookies.txt"):
return "./cookies.txt"
return None


def get_or_ask(attr: str, hidden: bool, source_name: str, options, config: Config) -> str:
def get_or_ask(attr: str, hidden: bool, options, config: Optional[SourceConfig]) -> str:
"""
Check for `attr` in cli options and config options.
Ask the user for the value if it is not found.
:param attr: Attribute to search for
:param hidden: Should the user input be hidden (Used for passwords)
:param source_name: Name of source
:param options: Cli options
:param config: Config file options
:param config: Configuration for source
:returns: `attr` value from either cli options, config options, or user input
"""
config_value = getattr(config.sources.get(source_name), attr, None)
config_value = getattr(config, attr, None)
value: Optional[str] = getattr(options, attr, None) or config_value
if value is None:
return Prompt.ask(attr.capitalize(), password=hidden)
return value


def login(url: str, source: Source, options, config: Config):
def login(url: str, source: Source, options, config: Optional[SourceConfig]):
"""
Login to source
Expand All @@ -111,7 +113,7 @@ def login(url: str, source: Source, options, config: Config):
login_data = {}
for name in source.login_data:
hidden = name == "password"
login_data[name] = get_or_ask(name, hidden, source.name, options, config)
login_data[name] = get_or_ask(name, hidden, options, config)
source.login(url, **login_data)


Expand All @@ -125,13 +127,14 @@ def authenticate(url: str, source: Source, options, config: Config):
:param config: Config file options
"""
logging.log(f"Authenticating with [magenta]{source.name}[/]")
source_config = config.sources.get(source.name)
# Load cookie file
cookie_path = get_cookie_path(options)
cookie_path = get_cookie_path(options, source_config)
if cookie_path is not None:
source.load_cookie_file(cookie_path)
# Authenticating with username and password
if source.supports_login and not source.authenticated:
login(url, source, options, config)
login(url, source, options, source_config)


def audiobook_from_series(source: Source, book) -> Audiobook:
Expand Down
65 changes: 48 additions & 17 deletions audiobookdl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SourceConfig:
username: Optional[str]
password: Optional[str]
library: Optional[str]
cookie_file: Optional[str]


@define
Expand All @@ -23,21 +24,50 @@ class Config:
output_template: Optional[str]


def read_config(location: Optional[str]) -> dict:
def load_config(overwrite: Optional[str]) -> Config:
"""
Load config file from disk
:param overwrite: Optional alternative location of config file
:returns: Content of config file
:raises: ConfigNotFound if location does not exists
"""
config_location = get_config_location(overwrite)
config_dict = read_config(config_location)
return structure_config(config_location, config_dict)


def config_dir() -> str:
"""
Get path of configuration directory
:returns: Path of configuration directory
"""
return appdirs.user_config_dir("audiobook-dl", "jo1gi")


def get_config_location(overwrite: Optional[str]) -> str:
"""
Get path of configuration file
:param overwrite: Overwrite of default configuration file location
:returns: Path of configuration file
"""
if overwrite:
if not os.path.exists(overwrite):
raise ConfigNotFound
return overwrite
return os.path.join(config_dir(), "audiobook-dl.toml")


def read_config(config_file: str) -> dict:
"""
Read config from disk as dictionary
:param location: Optional alternative location of config file
:param location: Location of configuration file
:returns: Content of config file as dictionary
:raises: ConfigNotFound if location does not exists
"""
if location:
if not os.path.exists(location):
raise ConfigNotFound
config_file = location
else:
config_dir = appdirs.user_config_dir("audiobook-dl", "jo1gi")
config_file = os.path.join(config_dir, "audiobook-dl.toml")
if os.path.exists(config_file):
with open(config_file, "rb") as f:
config_dict = tomli.load(f)
Expand All @@ -46,24 +76,25 @@ def read_config(location: Optional[str]) -> dict:
return config_dict



def load_config(location: Optional[str]) -> Config:
def structure_config(config_location: str, config_dict: dict) -> Config:
"""
Load config file from disk
Structure configuration file content as `Config`
:param location: Optional alternative location of config file
:returns: Content of config file
:raises: ConfigNotFound if location does not exists
:param config_dict: Configuration file as a dictionary
:returns: Configuration file `Config`
"""
config_dict = read_config(location)
# Add sources
sources: Dict[str, SourceConfig] = {}
if "sources" in config_dict:
for source_name, values in config_dict["sources"].items():
cookie_file = values.get("cookie_file")
if cookie_file:
cookie_file = os.path.relpath(values.get("cookie_file"), start=config_location)
sources[source_name] = SourceConfig(
username = values.get("username"),
password = values.get("password"),
library = values.get("library")
library = values.get("library"),
cookie_file = cookie_file
)
# Create config object
return Config(
Expand Down

0 comments on commit 608f843

Please sign in to comment.