2023-07-18 14:25:14 +02:00
|
|
|
from tidal_scraper.helper import log_error
|
|
|
|
|
2023-06-29 16:28:31 +02:00
|
|
|
import json
|
|
|
|
from datetime import datetime
|
2023-07-18 14:25:14 +02:00
|
|
|
from tidalapi import session, user, playlist, media, album, artist, Quality
|
2023-06-29 16:28:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
class State:
|
2023-07-12 17:45:04 +02:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
conf: dict | None = None,
|
2023-07-18 14:25:14 +02:00
|
|
|
state_dir: str | None = None,
|
|
|
|
user_id: int | None = None,
|
|
|
|
quality: str | None = None,
|
2023-07-12 17:45:04 +02:00
|
|
|
errorfile: str | None = None,
|
|
|
|
):
|
|
|
|
if conf is None:
|
2023-07-18 14:25:14 +02:00
|
|
|
assert user_id is not None
|
|
|
|
assert quality is not None
|
|
|
|
assert state_dir is not None
|
2023-07-12 17:45:04 +02:00
|
|
|
assert errorfile is not None
|
|
|
|
else:
|
2023-07-18 14:25:14 +02:00
|
|
|
user_id = user_id or conf["user_id"]
|
|
|
|
quality = quality or conf["quality"]
|
|
|
|
state_dir = state_dir or conf["state_dir"]
|
2023-07-12 17:45:04 +02:00
|
|
|
errorfile = errorfile or conf["error_log"]
|
|
|
|
|
2023-06-29 16:28:31 +02:00
|
|
|
match quality:
|
|
|
|
case "master":
|
2023-07-18 14:25:14 +02:00
|
|
|
q = Quality.master
|
2023-06-29 16:28:31 +02:00
|
|
|
case "lossless":
|
2023-07-18 14:25:14 +02:00
|
|
|
q = Quality.lossless
|
2023-06-29 16:28:31 +02:00
|
|
|
case "high":
|
2023-07-18 14:25:14 +02:00
|
|
|
q = Quality.high
|
2023-06-29 16:28:31 +02:00
|
|
|
case "low":
|
2023-07-18 14:25:14 +02:00
|
|
|
q = Quality.low
|
2023-06-29 16:28:31 +02:00
|
|
|
case _:
|
2023-07-05 11:50:05 +02:00
|
|
|
raise Exception("Bad Quality String")
|
2023-07-18 14:25:14 +02:00
|
|
|
api_config = session.Config(quality=q)
|
|
|
|
self.conf = conf
|
2023-06-29 16:28:31 +02:00
|
|
|
self.user_id = user_id
|
2023-07-18 14:25:14 +02:00
|
|
|
self.session = session.Session(api_config)
|
2023-07-05 11:50:05 +02:00
|
|
|
self.favorites = user.Favorites(self.session, user_id)
|
2023-07-18 14:25:14 +02:00
|
|
|
self.errorfile = errorfile
|
|
|
|
self._state = {
|
|
|
|
"albums": {},
|
|
|
|
"artists": {},
|
|
|
|
"playlists": {},
|
|
|
|
"tracks": {},
|
|
|
|
}
|
2023-06-29 16:28:31 +02:00
|
|
|
|
2023-07-05 11:50:05 +02:00
|
|
|
def login(self, auth_file: str | None = None) -> None:
|
2023-06-29 16:28:31 +02:00
|
|
|
s = self.session
|
2023-07-18 14:25:14 +02:00
|
|
|
if auth_file is None:
|
|
|
|
assert self.conf is not None
|
|
|
|
auth_file = self.conf["state_dir"] + "auth.json"
|
2023-06-29 16:28:31 +02:00
|
|
|
try:
|
|
|
|
assert auth_file
|
|
|
|
with open(auth_file, "r") as f:
|
|
|
|
a = json.load(f)
|
|
|
|
s.load_oauth_session(
|
|
|
|
a["token_type"],
|
|
|
|
a["access_token"],
|
|
|
|
a["refresh_token"],
|
|
|
|
datetime.fromtimestamp(a["expiry_time"]),
|
|
|
|
)
|
2023-06-29 23:57:07 +02:00
|
|
|
except (FileNotFoundError, IndexError, AssertionError):
|
2023-06-29 16:28:31 +02:00
|
|
|
s.login_oauth_simple()
|
|
|
|
if (
|
|
|
|
s.token_type
|
|
|
|
and s.access_token
|
|
|
|
and s.refresh_token
|
|
|
|
and s.expiry_time
|
|
|
|
and auth_file
|
|
|
|
):
|
|
|
|
data = {
|
|
|
|
"token_type": s.token_type,
|
|
|
|
"access_token": s.access_token,
|
|
|
|
"refresh_token": s.refresh_token,
|
|
|
|
"expiry_time": s.expiry_time.timestamp(),
|
|
|
|
}
|
|
|
|
with open(auth_file, "w") as f:
|
2023-07-18 14:25:14 +02:00
|
|
|
json.dump(data, fp=f, indent=4)
|
2023-06-29 16:28:31 +02:00
|
|
|
|
|
|
|
assert self.session.check_login()
|
|
|
|
|
|
|
|
def set_dl_state(
|
|
|
|
self,
|
|
|
|
obj: playlist.Playlist | media.Track | album.Album | artist.Artist,
|
|
|
|
downloaded: bool,
|
|
|
|
) -> None:
|
|
|
|
match type(obj):
|
|
|
|
case album.Album:
|
|
|
|
t = "albums"
|
|
|
|
case artist.Artist:
|
|
|
|
t = "artists"
|
|
|
|
case playlist.Playlist:
|
|
|
|
t = "playlists"
|
|
|
|
case media.Track:
|
|
|
|
t = "tracks"
|
|
|
|
case _:
|
2023-07-18 14:25:14 +02:00
|
|
|
raise Exception("Object of incorrect type received")
|
2023-06-29 16:28:31 +02:00
|
|
|
|
|
|
|
self._state[t][obj.id] = downloaded
|
|
|
|
|
2023-07-18 14:25:14 +02:00
|
|
|
def state_downloaded(
|
|
|
|
self, obj: playlist.Playlist | media.Track | album.Album | artist.Artist
|
|
|
|
) -> bool:
|
|
|
|
match type(obj):
|
|
|
|
case album.Album:
|
|
|
|
t = "albums"
|
|
|
|
case artist.Artist:
|
|
|
|
t = "artists"
|
|
|
|
case playlist.Playlist:
|
|
|
|
t = "playlists"
|
|
|
|
case media.Track:
|
|
|
|
t = "tracks"
|
|
|
|
case _:
|
|
|
|
raise Exception("Object of incorrect type received")
|
|
|
|
return self._state[t].get(str(obj.id), False)
|
|
|
|
|
2023-07-05 11:50:05 +02:00
|
|
|
def write_dl_state(self, statefile: str) -> None:
|
|
|
|
with open(statefile, "w") as f:
|
2023-07-18 14:25:14 +02:00
|
|
|
json.dump(self._state, fp=f, indent=4)
|
2023-06-29 16:28:31 +02:00
|
|
|
|
2023-07-18 14:25:14 +02:00
|
|
|
def load_dl_state(self, state_file: str) -> None:
|
|
|
|
try:
|
|
|
|
with open(state_file, "r") as f:
|
|
|
|
self._state = json.load(f)
|
2023-06-29 23:57:07 +02:00
|
|
|
|
2023-07-18 14:25:14 +02:00
|
|
|
for t in self._state.values():
|
|
|
|
for k, v in t.items():
|
|
|
|
assert isinstance(k, (str, type(None)))
|
|
|
|
assert isinstance(v, (bool, type(None)))
|
|
|
|
except (FileNotFoundError, IndexError, KeyError):
|
|
|
|
log_error(
|
|
|
|
self.errorfile or "error.log",
|
|
|
|
f"Could not find state file at {state_file}",
|
|
|
|
)
|
|
|
|
except (json.decoder.JSONDecodeError, AssertionError):
|
|
|
|
log_error(
|
|
|
|
self.errorfile or "error.log",
|
|
|
|
f"Statefile at {state_file} is malformed",
|
|
|
|
)
|