forked from DRMTalks/devine
Rework Profile/Authentication System
- Removed `devine auth` command and sub-commands due to lack of support, risk of data, and general quirks of it. - Removed `profiles` config data, you must now specify which profile you wish to use each time with -p/--profile. If you use a specific profile a lot more than others, you should make it the default. See below. - Added a `default` key to each service mapping in `credentials` that will be used if -p/--profile is not specified. - Each service mapping in `credentials` is no longer forced to use profiles. You can now simply specify `Service: username:password` if you only use one credential. - Auth-less Services now simply have to specify no credential and have no cookie file. - There is no longer an error for not having a cookie and/or credential for the chosen profile, as a profile no longer has to be chosen. - Cookies are now checked for in 3 different locations in the following order: 1. `/Cookies/{Service Name}.txt` 2. `/Cookies/Service Name/{profile}.txt` 3. `/Cookies/Service Name/default.txt` This means you now have more options on organization and layout of Cookie files, similarly to the new Credentials config. Note: `/Cookies/Service Name/.txt` also works as an alternative to `default.txt`. The benefit of this is `.txt` will always be at the top of your folder.
This commit is contained in:
parent
1c6e91b6f9
commit
837061cf91
51
CONFIG.md
51
CONFIG.md
|
@ -67,25 +67,30 @@ DSNP:
|
|||
default: chromecdm_903_l3
|
||||
```
|
||||
|
||||
## credentials (dict)
|
||||
## credentials (dict[str, str|list|dict])
|
||||
|
||||
Specify login credentials to use for each Service by Profile as Key (case-sensitive).
|
||||
|
||||
The value should be `email:password` or `username:password` (with some exceptions).
|
||||
The first section does not have to be an email or username. It may also be a Phone number.
|
||||
Specify login credentials to use for each Service, and optionally per-profile.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
AMZN:
|
||||
ALL4: jane@gmail.com:LoremIpsum100 # directly
|
||||
AMZN: # or per-profile, optionally with a default
|
||||
default: jane@example.tld:LoremIpsum99 # <-- used by default if -p/--profile is not used
|
||||
james: james@gmail.com:TheFriend97
|
||||
jane: jane@example.tld:LoremIpsum99
|
||||
john: john@example.tld:LoremIpsum98
|
||||
NF:
|
||||
NF: # the `default` key is not necessary, but no credential will be used by default
|
||||
john: john@gmail.com:TheGuyWhoPaysForTheNetflix69420
|
||||
```
|
||||
|
||||
Credentials must be specified per-profile. You cannot specify a fallback or default credential.
|
||||
The value should be in string form, i.e. `john@gmail.com:password123` or `john:password123`.
|
||||
Any arbitrary values can be used on the left (username/password/phone) and right (password/secret).
|
||||
You can also specify these in list form, i.e., `["john@gmail.com", ":PasswordWithAColon"]`.
|
||||
|
||||
If you specify multiple credentials with keys like the `AMZN` and `NF` example above, then you should
|
||||
use a `default` key or no credential will be loaded automatically unless you use `-p/--profile`. You
|
||||
do not have to use a `default` key at all.
|
||||
|
||||
Please be aware that this information is sensitive and to keep it safe. Do not share your config.
|
||||
|
||||
## curl_impersonate (dict)
|
||||
|
@ -260,34 +265,6 @@ together.
|
|||
- `set_title`
|
||||
Set the container title to `Show SXXEXX Episode Name` or `Movie (Year)`. Default: `true`
|
||||
|
||||
## profiles (dict)
|
||||
|
||||
Pre-define Profiles to use Per-Service.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
AMZN: jane
|
||||
DSNP: john
|
||||
```
|
||||
|
||||
You can also specify a fallback value to pre-define if a match was not made.
|
||||
This can be done using `default` key. This can help reduce redundancy in your specifications.
|
||||
|
||||
```yaml
|
||||
AMZN: jane
|
||||
DSNP: john
|
||||
default: james
|
||||
```
|
||||
|
||||
If a Service doesn't require a profile (as it does not require Credentials or Authorization of any kind), you can
|
||||
disable the profile checks by specifying `false` as the profile for the Service.
|
||||
|
||||
```yaml
|
||||
ALL4: false
|
||||
CTV: false
|
||||
```
|
||||
|
||||
## proxy_providers (dict)
|
||||
|
||||
Enable external proxy provider services.
|
||||
|
|
35
README.md
35
README.md
|
@ -252,22 +252,33 @@ sure that the version of devine you have locally is supported by the Service cod
|
|||
> automatically download. Python importing the files triggers the download to begin. However, it may cause a delay on
|
||||
> startup.
|
||||
|
||||
## Profiles (Cookies & Credentials)
|
||||
## Cookies & Credentials
|
||||
|
||||
Just like a streaming service, devine associates both a cookie and/or credential as a Profile. You can associate up to
|
||||
one cookie and one credential per-profile, depending on which (or both) are needed by the Service. This system allows
|
||||
you to configure multiple accounts per-service and choose which to use at any time.
|
||||
Devine can authenticate with Services using Cookies and/or Credentials. Credentials are stored in the config, and
|
||||
Cookies are stored in the data directory which can be found by running `devine env info`.
|
||||
|
||||
Credentials are stored in the config, and Cookies are stored in the data directory. You can find the location of these
|
||||
by running `devine env info`. However, you can manage profiles with `devine auth --help`. E.g. to add a new John
|
||||
profile to Netflix with a Cookie and Credential, take a look at the following CLI call,
|
||||
`devine auth add John NF --cookie "C:\Users\John\Downloads\netflix.com.txt --credential "john@gmail.com:pass123"`
|
||||
To add a Credential to a Service, take a look at the [Credentials Config](CONFIG.md#credentials-dictstr-strlistdict)
|
||||
for information on setting up one or more credentials per-service. You can add one or more Credential per-service and
|
||||
use `-p/--profile` to choose which Credential to use.
|
||||
|
||||
You can also delete a credential with `devine auth delete`. E.g., to delete the cookie for John that we just added, run
|
||||
`devine auth delete John --cookie`. Take a look at `devine auth delete --help` for more information.
|
||||
To add a Cookie to a Service, use a Cookie file extension to make a `cookies.txt` file and move it into the Cookies
|
||||
directory. You must rename the `cookies.txt` file to that of the Service tag (case-sensitive), e.g., `NF.txt`. You can
|
||||
also place it in a Service Cookie folder, e.g., `/Cookies/NF/default.txt` or `/Cookies/NF/.txt`.
|
||||
|
||||
> __Note__ Profile names are case-sensitive and unique per-service. They also have no arbitrary character or length
|
||||
> limit, but for convenience I don't recommend using any special characters as your terminal may get confused.
|
||||
You can add multiple Cookies to the `/Cookies/NF/` folder with their own unique name and then use `-p/--profile` to
|
||||
choose which one to use. E.g., `/Cookies/NF/sam.txt` and then use it with `--profile sam`. If you make a Service Cookie
|
||||
folder without a `.txt` or `default.txt`, but with another file, then no Cookies will be loaded unless you use
|
||||
`-p/--profile` like shown. This allows you to opt in to authentication at whim.
|
||||
|
||||
> [!TIP]
|
||||
> - If your Service does not require Authentication, then do not define any Credential or Cookie for that Service.
|
||||
> - You can use both Cookies and Credentials at the same time, so long as your Service takes and uses both.
|
||||
> - If you are using profiles, then make sure you use the same name on the Credential name and Cookie file name when
|
||||
> using `-p/--profile`.
|
||||
|
||||
> [!WARNING]
|
||||
> Profile names are case-sensitive and unique per-service. They have no arbitrary character or length limit, but for
|
||||
> convenience sake I don't recommend using any special characters as your terminal may get confused.
|
||||
|
||||
### Cookie file format and Extensions
|
||||
|
||||
|
|
|
@ -1,266 +0,0 @@
|
|||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
import tkinter.filedialog
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from devine.core.config import Config, config
|
||||
from devine.core.constants import context_settings
|
||||
from devine.core.credential import Credential
|
||||
|
||||
|
||||
@click.group(
|
||||
short_help="Manage cookies and credentials for profiles of services.",
|
||||
context_settings=context_settings)
|
||||
@click.pass_context
|
||||
def auth(ctx: click.Context) -> None:
|
||||
"""Manage cookies and credentials for profiles of services."""
|
||||
ctx.obj = logging.getLogger("auth")
|
||||
|
||||
|
||||
@auth.command(
|
||||
name="list",
|
||||
short_help="List profiles and their state for a service or all services.",
|
||||
context_settings=context_settings)
|
||||
@click.argument("service", type=str, required=False)
|
||||
@click.pass_context
|
||||
def list_(ctx: click.Context, service: Optional[str] = None) -> None:
|
||||
"""
|
||||
List profiles and their state for a service or all services.
|
||||
|
||||
\b
|
||||
Profile and Service names are case-insensitive.
|
||||
"""
|
||||
log = ctx.obj
|
||||
service_f = service
|
||||
|
||||
auth_data: dict[str, dict[str, list]] = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
if config.directories.cookies.exists():
|
||||
for cookie_dir in config.directories.cookies.iterdir():
|
||||
service = cookie_dir.name
|
||||
for cookie in cookie_dir.glob("*.txt"):
|
||||
if cookie.stem not in auth_data[service]:
|
||||
auth_data[service][cookie.stem].append("Cookie")
|
||||
|
||||
for service, credentials in config.credentials.items():
|
||||
for profile in credentials:
|
||||
auth_data[service][profile].append("Credential")
|
||||
|
||||
for service, profiles in dict(sorted(auth_data.items())).items(): # type:ignore
|
||||
if service_f and service != service_f.upper():
|
||||
continue
|
||||
log.info(service)
|
||||
for profile, authorizations in dict(sorted(profiles.items())).items():
|
||||
log.info(f' "{profile}": {", ".join(authorizations)}')
|
||||
|
||||
|
||||
@auth.command(
|
||||
short_help="View profile cookies and credentials for a service.",
|
||||
context_settings=context_settings)
|
||||
@click.argument("profile", type=str)
|
||||
@click.argument("service", type=str)
|
||||
@click.pass_context
|
||||
def view(ctx: click.Context, profile: str, service: str) -> None:
|
||||
"""
|
||||
View profile cookies and credentials for a service.
|
||||
|
||||
\b
|
||||
Profile and Service names are case-sensitive.
|
||||
"""
|
||||
log = ctx.obj
|
||||
service_f = service
|
||||
profile_f = profile
|
||||
found = False
|
||||
|
||||
for cookie_dir in config.directories.cookies.iterdir():
|
||||
if cookie_dir.name == service_f:
|
||||
for cookie in cookie_dir.glob("*.txt"):
|
||||
if cookie.stem == profile_f:
|
||||
log.info(f"Cookie: {cookie}")
|
||||
log.debug(cookie.read_text(encoding="utf8").strip())
|
||||
found = True
|
||||
break
|
||||
|
||||
for service, credentials in config.credentials.items():
|
||||
if service == service_f:
|
||||
for profile, credential in credentials.items():
|
||||
if profile == profile_f:
|
||||
log.info(f"Credential: {':'.join(list(credential))}")
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise click.ClickException(
|
||||
f"Could not find Profile '{profile_f}' for Service '{service_f}'."
|
||||
f"\nThe profile and service values are case-sensitive."
|
||||
)
|
||||
|
||||
|
||||
@auth.command(
|
||||
short_help="Check what profile is used by services.",
|
||||
context_settings=context_settings)
|
||||
@click.argument("service", type=str, required=False)
|
||||
@click.pass_context
|
||||
def status(ctx: click.Context, service: Optional[str] = None) -> None:
|
||||
"""
|
||||
Check what profile is used by services.
|
||||
|
||||
\b
|
||||
Service names are case-sensitive.
|
||||
"""
|
||||
log = ctx.obj
|
||||
found_profile = False
|
||||
for service_, profile in config.profiles.items():
|
||||
if not service or service_.upper() == service.upper():
|
||||
log.info(f"{service_}: {profile or '--'}")
|
||||
found_profile = True
|
||||
|
||||
if not found_profile:
|
||||
log.info(f"No profile has been explicitly set for {service}")
|
||||
|
||||
default = config.profiles.get("default", "not set")
|
||||
log.info(f"The default profile is {default}")
|
||||
|
||||
|
||||
@auth.command(
|
||||
short_help="Delete a profile and all of its authorization from a service.",
|
||||
context_settings=context_settings)
|
||||
@click.argument("profile", type=str)
|
||||
@click.argument("service", type=str)
|
||||
@click.option("--cookie", is_flag=True, default=False, help="Only delete the cookie.")
|
||||
@click.option("--credential", is_flag=True, default=False, help="Only delete the credential.")
|
||||
@click.pass_context
|
||||
def delete(ctx: click.Context, profile: str, service: str, cookie: bool, credential: bool):
|
||||
"""
|
||||
Delete a profile and all of its authorization from a service.
|
||||
|
||||
\b
|
||||
By default this does remove both Cookies and Credentials.
|
||||
You may remove only one of them with --cookie or --credential.
|
||||
|
||||
\b
|
||||
Profile and Service names are case-sensitive.
|
||||
Comments may be removed from config!
|
||||
"""
|
||||
log = ctx.obj
|
||||
service_f = service
|
||||
profile_f = profile
|
||||
found = False
|
||||
|
||||
if not credential:
|
||||
for cookie_dir in config.directories.cookies.iterdir():
|
||||
if cookie_dir.name == service_f:
|
||||
for cookie_ in cookie_dir.glob("*.txt"):
|
||||
if cookie_.stem == profile_f:
|
||||
cookie_.unlink()
|
||||
log.info(f"Deleted Cookie: {cookie_}")
|
||||
found = True
|
||||
break
|
||||
|
||||
if not cookie:
|
||||
for key, credentials in config.credentials.items():
|
||||
if key == service_f:
|
||||
for profile, credential_ in credentials.items():
|
||||
if profile == profile_f:
|
||||
config_path = Config._Directories.user_configs / Config._Filenames.root_config
|
||||
yaml, data = YAML(), None
|
||||
yaml.default_flow_style = False
|
||||
data = yaml.load(config_path)
|
||||
del data["credentials"][key][profile_f]
|
||||
yaml.dump(data, config_path)
|
||||
log.info(f"Deleted Credential: {credential_}")
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise click.ClickException(
|
||||
f"Could not find Profile '{profile_f}' for Service '{service_f}'."
|
||||
f"\nThe profile and service values are case-sensitive."
|
||||
)
|
||||
|
||||
|
||||
@auth.command(
|
||||
short_help="Add a Credential and/or Cookies to an existing or new profile for a service.",
|
||||
context_settings=context_settings)
|
||||
@click.argument("profile", type=str)
|
||||
@click.argument("service", type=str)
|
||||
@click.option("--cookie", type=str, default=None, help="Direct path to Cookies to add.")
|
||||
@click.option("--credential", type=str, default=None, help="Direct Credential string to add.")
|
||||
@click.pass_context
|
||||
def add(ctx: click.Context, profile: str, service: str, cookie: Optional[str] = None, credential: Optional[str] = None):
|
||||
"""
|
||||
Add a Credential and/or Cookies to an existing or new profile for a service.
|
||||
|
||||
\b
|
||||
Cancel the Open File dialogue when presented if you do not wish to provide
|
||||
cookies. The Credential should be in `Username:Password` form. The username
|
||||
may be an email. If you do not wish to add a Credential, just hit enter.
|
||||
|
||||
\b
|
||||
Profile and Service names are case-sensitive!
|
||||
Comments may be removed from config!
|
||||
"""
|
||||
log = ctx.obj
|
||||
service = service.upper()
|
||||
profile = profile.lower()
|
||||
|
||||
if cookie:
|
||||
cookie = Path(cookie)
|
||||
if not cookie.is_file():
|
||||
log.error(f"No such file or directory: {cookie}.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Opening File Dialogue, select a Cookie file to import.")
|
||||
cookie = tkinter.filedialog.askopenfilename(
|
||||
title="Select a Cookie file (Cancel to skip)",
|
||||
filetypes=[("Cookies", "*.txt"), ("All files", "*.*")]
|
||||
)
|
||||
if cookie:
|
||||
cookie = Path(cookie)
|
||||
else:
|
||||
log.info("Skipped adding a Cookie...")
|
||||
|
||||
if credential:
|
||||
try:
|
||||
credential = Credential.loads(credential)
|
||||
except ValueError as e:
|
||||
raise click.ClickException(str(e))
|
||||
else:
|
||||
credential = input("Credential: ")
|
||||
if credential:
|
||||
try:
|
||||
credential = Credential.loads(credential)
|
||||
except ValueError as e:
|
||||
raise click.ClickException(str(e))
|
||||
else:
|
||||
log.info("Skipped adding a Credential...")
|
||||
|
||||
if cookie:
|
||||
final_path = (config.directories.cookies / service / profile).with_suffix(".txt")
|
||||
final_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if final_path.exists():
|
||||
log.error(f"A Cookie file for the Profile {profile} on {service} already exists.")
|
||||
sys.exit(1)
|
||||
shutil.move(cookie, final_path)
|
||||
log.info(f"Moved Cookie file to: {final_path}")
|
||||
|
||||
if credential:
|
||||
config_path = Config._Directories.user_configs / Config._Filenames.root_config
|
||||
yaml, data = YAML(), None
|
||||
yaml.default_flow_style = False
|
||||
data = yaml.load(config_path)
|
||||
if not data:
|
||||
data = {}
|
||||
if "credentials" not in data:
|
||||
data["credentials"] = {}
|
||||
if service not in data["credentials"]:
|
||||
data["credentials"][service] = {}
|
||||
data["credentials"][service][profile] = credential.dumps()
|
||||
yaml.dump(data, config_path)
|
||||
log.info(f"Added Credential: {credential}")
|
|
@ -28,6 +28,7 @@ from pymediainfo import MediaInfo
|
|||
from pywidevine.cdm import Cdm as WidevineCdm
|
||||
from pywidevine.device import Device
|
||||
from pywidevine.remotecdm import RemoteCdm
|
||||
from requests.cookies import RequestsCookieJar
|
||||
from rich.console import Group
|
||||
from rich.live import Live
|
||||
from rich.padding import Padding
|
||||
|
@ -68,7 +69,7 @@ class dl:
|
|||
token_normalize_func=Services.get_tag
|
||||
))
|
||||
@click.option("-p", "--profile", type=str, default=None,
|
||||
help="Profile to use for Credentials and Cookies (if available). Overrides profile set by config.")
|
||||
help="Profile to use for Credentials and Cookies (if available).")
|
||||
@click.option("-q", "--quality", type=QUALITY_LIST, default=[],
|
||||
help="Download Resolution(s), defaults to the best available resolution.")
|
||||
@click.option("-v", "--vcodec", type=click.Choice(Video.Codec, case_sensitive=False),
|
||||
|
@ -155,17 +156,14 @@ class dl:
|
|||
self.log = logging.getLogger("download")
|
||||
|
||||
self.service = Services.get_tag(ctx.invoked_subcommand)
|
||||
self.profile = profile
|
||||
|
||||
with console.status("Preparing Service and Profile Authentication...", spinner="dots"):
|
||||
if profile:
|
||||
self.profile = profile
|
||||
self.log.info(f"Profile: '{self.profile}' from the --profile argument")
|
||||
else:
|
||||
self.profile = self.get_profile(self.service)
|
||||
self.log.info(f"Profile: '{self.profile}' from the config")
|
||||
if self.profile:
|
||||
self.log.info(f"Using profile: '{self.profile}'")
|
||||
|
||||
with console.status("Loading Service Config...", spinner="dots"):
|
||||
service_config_path = Services.get_path(self.service) / config.filenames.config
|
||||
if service_config_path.is_file():
|
||||
if service_config_path.exists():
|
||||
self.service_config = yaml.safe_load(service_config_path.read_text(encoding="utf8"))
|
||||
self.log.info("Service Config loaded")
|
||||
else:
|
||||
|
@ -289,14 +287,11 @@ class dl:
|
|||
else:
|
||||
vaults_only = not cdm_only
|
||||
|
||||
if self.profile:
|
||||
with console.status("Authenticating with Service...", spinner="dots"):
|
||||
cookies = self.get_cookie_jar(self.service, self.profile)
|
||||
credential = self.get_credentials(self.service, self.profile)
|
||||
if not cookies and not credential:
|
||||
self.log.error(f"The Profile '{self.profile}' has no Cookies or Credentials, Check for typos")
|
||||
sys.exit(1)
|
||||
service.authenticate(cookies, credential)
|
||||
with console.status("Authenticating with Service...", spinner="dots"):
|
||||
cookies = self.get_cookie_jar(self.service, self.profile)
|
||||
credential = self.get_credentials(self.service, self.profile)
|
||||
service.authenticate(cookies, credential)
|
||||
if cookies or credential:
|
||||
self.log.info("Authenticated with Service")
|
||||
|
||||
with console.status("Fetching Title Metadata...", spinner="dots"):
|
||||
|
@ -663,13 +658,9 @@ class dl:
|
|||
))
|
||||
|
||||
# update cookies
|
||||
cookie_file = config.directories.cookies / service.__class__.__name__ / f"{self.profile}.txt"
|
||||
cookie_file = self.get_cookie_path(self.service, self.profile)
|
||||
if cookie_file.exists():
|
||||
cookie_jar = MozillaCookieJar(cookie_file)
|
||||
cookie_jar.load()
|
||||
for cookie in service.session.cookies:
|
||||
cookie_jar.set_cookie(cookie)
|
||||
cookie_jar.save(ignore_discard=True)
|
||||
self.save_cookies(cookie_file, service.session.cookies)
|
||||
|
||||
dl_time = time_elapsed_since(start_time)
|
||||
|
||||
|
@ -954,10 +945,24 @@ class dl:
|
|||
return profile
|
||||
|
||||
@staticmethod
|
||||
def get_cookie_jar(service: str, profile: str) -> Optional[MozillaCookieJar]:
|
||||
"""Get Profile's Cookies as Mozilla Cookie Jar if available."""
|
||||
cookie_file = config.directories.cookies / service / f"{profile}.txt"
|
||||
if cookie_file.is_file():
|
||||
def get_cookie_path(service: str, profile: Optional[str]) -> Optional[Path]:
|
||||
"""Get Service Cookie File Path for Profile."""
|
||||
direct_cookie_file = config.directories.cookies / f"{service}.txt"
|
||||
profile_cookie_file = config.directories.cookies / service / f"{profile}.txt"
|
||||
default_cookie_file = config.directories.cookies / service / "default.txt"
|
||||
|
||||
if direct_cookie_file.exists():
|
||||
return direct_cookie_file
|
||||
elif profile_cookie_file.exists():
|
||||
return profile_cookie_file
|
||||
elif default_cookie_file.exists():
|
||||
return default_cookie_file
|
||||
|
||||
@staticmethod
|
||||
def get_cookie_jar(service: str, profile: Optional[str]) -> Optional[MozillaCookieJar]:
|
||||
"""Get Service Cookies for Profile."""
|
||||
cookie_file = dl.get_cookie_path(service, profile)
|
||||
if cookie_file:
|
||||
cookie_jar = MozillaCookieJar(cookie_file)
|
||||
cookie_data = html.unescape(cookie_file.read_text("utf8")).splitlines(keepends=False)
|
||||
for i, line in enumerate(cookie_data):
|
||||
|
@ -972,17 +977,29 @@ class dl:
|
|||
cookie_file.write_text(cookie_data, "utf8")
|
||||
cookie_jar.load(ignore_discard=True, ignore_expires=True)
|
||||
return cookie_jar
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_credentials(service: str, profile: str) -> Optional[Credential]:
|
||||
"""Get Profile's Credential if available."""
|
||||
cred = config.credentials.get(service, {}).get(profile)
|
||||
if cred:
|
||||
if isinstance(cred, list):
|
||||
return Credential(*cred)
|
||||
return Credential.loads(cred)
|
||||
return None
|
||||
def save_cookies(path: Path, cookies: RequestsCookieJar):
|
||||
cookie_jar = MozillaCookieJar(path)
|
||||
cookie_jar.load()
|
||||
for cookie in cookies:
|
||||
cookie_jar.set_cookie(cookie)
|
||||
cookie_jar.save(ignore_discard=True)
|
||||
|
||||
@staticmethod
|
||||
def get_credentials(service: str, profile: Optional[str]) -> Optional[Credential]:
|
||||
"""Get Service Credentials for Profile."""
|
||||
credentials = config.credentials.get(service)
|
||||
if credentials:
|
||||
if isinstance(credentials, dict):
|
||||
if profile:
|
||||
credentials = credentials.get(profile) or credentials.get("default")
|
||||
else:
|
||||
credentials = credentials.get("default")
|
||||
if credentials:
|
||||
if isinstance(credentials, list):
|
||||
return Credential(*credentials)
|
||||
return Credential.loads(credentials) # type: ignore
|
||||
|
||||
@staticmethod
|
||||
def get_cdm(service: str, profile: Optional[str] = None) -> WidevineCdm:
|
||||
|
|
|
@ -60,7 +60,6 @@ class Config:
|
|||
self.key_vaults: list[dict[str, Any]] = kwargs.get("key_vaults", [])
|
||||
self.muxing: dict = kwargs.get("muxing") or {}
|
||||
self.nordvpn: dict = kwargs.get("nordvpn") or {}
|
||||
self.profiles: dict = kwargs.get("profiles") or {}
|
||||
self.proxy_providers: dict = kwargs.get("proxy_providers") or {}
|
||||
self.serve: dict = kwargs.get("serve") or {}
|
||||
self.services: dict = kwargs.get("services") or {}
|
||||
|
|
Loading…
Reference in New Issue