Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
92a6c86
feat(yandex_music): add QR authentication and token auto-refresh
trudenboy Apr 7, 2026
9eea38b
feat(yandex_music): sync provider from ma-provider-yandex-music v2.7.2
github-actions[bot] Apr 7, 2026
13d605d
feat(yandex_music): sync provider from ma-provider-yandex-music v2.7.2
github-actions[bot] Apr 7, 2026
8c03ad6
feat(yandex_music): sync provider from ma-provider-yandex-music v2.8.0
github-actions[bot] Apr 9, 2026
aeb818a
feat(yandex_music): sync provider from ma-provider-yandex-music v2.8.0
github-actions[bot] Apr 9, 2026
e66dbe7
feat(yandex_music): sync provider from ma-provider-yandex-music v2.8.0
github-actions[bot] Apr 9, 2026
bd16eba
feat(yandex_music): sync provider from ma-provider-yandex-music v2.8.0
github-actions[bot] Apr 9, 2026
6cb58db
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 9, 2026
097a956
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 9, 2026
5c89c3c
feat(yandex_music): sync provider from ma-provider-yandex-music v2.8.0
github-actions[bot] Apr 9, 2026
139a75f
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 10, 2026
cc48936
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 10, 2026
86cc63c
chore: regenerate requirements_all.txt
trudenboy Apr 10, 2026
d4160dd
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 11, 2026
7e33d1e
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 11, 2026
e84549b
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 13, 2026
4cbb976
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 13, 2026
cf5e8cc
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 15, 2026
2d72c82
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 15, 2026
8101c40
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 15, 2026
4343118
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 16, 2026
ae05b14
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 16, 2026
8d81717
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.0
github-actions[bot] Apr 16, 2026
ed76724
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 17, 2026
fb45738
feat(yandex_music): sync provider from ma-provider-yandex-music v2.9.1
github-actions[bot] Apr 17, 2026
431fe98
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.0
github-actions[bot] Apr 19, 2026
917ecc0
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.0
github-actions[bot] Apr 19, 2026
c98604d
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 19, 2026
496ee52
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 20, 2026
faebce6
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.0
github-actions[bot] Apr 20, 2026
f4c1642
feat(kion_music): sync provider from ma-provider-kion-music v2.6.7
github-actions[bot] Apr 20, 2026
7024760
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.0
github-actions[bot] Apr 20, 2026
d8deb2c
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 20, 2026
99f76c7
Remove kion_music provider from yandex_music PR branch
trudenboy Apr 20, 2026
66acb7b
Restore kion_music to upstream/dev state
trudenboy Apr 20, 2026
8ab1309
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.0
github-actions[bot] Apr 20, 2026
c28c7ad
Bump kion_music yandex-music requirement to 3.0.0
trudenboy Apr 20, 2026
3376fbf
Revert "Bump kion_music yandex-music requirement to 3.0.0"
trudenboy Apr 20, 2026
94015a7
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 20, 2026
0bb21a4
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.0
github-actions[bot] Apr 20, 2026
5577981
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 20, 2026
bd801d6
Update ya-passport-auth and yandex-music versions
trudenboy Apr 20, 2026
b566e91
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 20, 2026
ff3c031
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 20, 2026
d7b0644
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.1
github-actions[bot] Apr 20, 2026
e4187b9
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.1
github-actions[bot] Apr 20, 2026
d4df765
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 21, 2026
67f1d51
feat(yandex_music): sync provider from ma-provider-yandex-music v3.0.1
github-actions[bot] Apr 21, 2026
72e1d05
feat(yandex_music): sync provider from ma-provider-yandex-music v3.1.0
github-actions[bot] Apr 21, 2026
b798cd1
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 21, 2026
e64d861
feat(yandex_music): sync provider from ma-provider-yandex-music v3.1.1
github-actions[bot] Apr 21, 2026
ed104c1
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 21, 2026
3612ae7
feat(yandex_music): sync provider from ma-provider-yandex-music v3.1.2
github-actions[bot] Apr 21, 2026
826dc09
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 21, 2026
681e0ba
feat(yandex_music): sync provider from ma-provider-yandex-music v3.2.0
github-actions[bot] Apr 21, 2026
cd6cd2f
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 22, 2026
dc9b7cf
feat(yandex_music): sync provider from ma-provider-yandex-music v3.2.1
github-actions[bot] Apr 22, 2026
be93423
feat(yandex_music): sync provider from ma-provider-yandex-music v3.3.0
github-actions[bot] Apr 23, 2026
e5a70ce
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 23, 2026
c172f24
feat(yandex_music): sync provider from ma-provider-yandex-music v3.3.1
github-actions[bot] Apr 23, 2026
1fa8115
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 23, 2026
869ef78
feat(yandex_music): sync provider from ma-provider-yandex-music v3.4.0
github-actions[bot] Apr 23, 2026
db23b01
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 23, 2026
3b439fd
feat(yandex_music): sync provider from ma-provider-yandex-music v3.4.0
github-actions[bot] Apr 23, 2026
9614f95
Remove gql dependency from requirements
trudenboy Apr 23, 2026
be4d0f9
Update requirements_all.txt to remove warning
trudenboy Apr 23, 2026
be76923
Remove ya-passport-auth from requirements
trudenboy Apr 23, 2026
068417e
Remove yandex-music dependency from requirements
trudenboy Apr 23, 2026
43538d5
fix(requirements): restore requirements_all.txt via gen_requirements_…
trudenboy Apr 23, 2026
6048467
feat(yandex_music): sync provider from ma-provider-yandex-music v3.4.1
github-actions[bot] Apr 23, 2026
785fc5e
chore: remove tests/providers/kion_music/test_integration.py
trudenboy Apr 23, 2026
093128a
chore: regenerate requirements_all.txt (yandex-music 3.0.0)
trudenboy Apr 23, 2026
6d934be
Revert "chore: regenerate requirements_all.txt (yandex-music 3.0.0)"
trudenboy Apr 23, 2026
95edc90
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 28, 2026
d1f502a
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 28, 2026
e9ce07a
fix(scripts): sort provider dirs in gen_requirements_all for determin…
trudenboy Apr 28, 2026
0a2c6a2
Revert "fix(scripts): sort provider dirs in gen_requirements_all for …
trudenboy Apr 28, 2026
8200f5c
chore: pin yandex-music==3.0.0 in requirements_all.txt to match CI re…
trudenboy Apr 28, 2026
be10725
chore: flip yandex-music pin back to 2.2.0 (CI inode order shifted ag…
trudenboy Apr 28, 2026
f4ee6cc
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 28, 2026
7f9bdc8
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 28, 2026
cc5e670
Merge branch 'dev' into upstream/yandex_music
trudenboy Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 77 additions & 9 deletions music_assistant/providers/yandex_music/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@

from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption, ConfigValueType
from music_assistant_models.enums import ConfigEntryType, ProviderFeature
from music_assistant_models.errors import InvalidDataError

Comment thread
trudenboy marked this conversation as resolved.
from .constants import (
CONF_ACTION_AUTH_QR,
CONF_ACTION_CLEAR_AUTH,
CONF_BASE_URL,
CONF_LIKED_TRACKS_MAX_TRACKS,
CONF_MY_WAVE_MAX_TRACKS,
CONF_QUALITY,
CONF_REMEMBER_SESSION,
CONF_TOKEN,
CONF_X_TOKEN,
DEFAULT_BASE_URL,
QUALITY_BALANCED,
QUALITY_EFFICIENT,
QUALITY_HIGH,
QUALITY_SUPERB,
)
from .provider import YandexMusicProvider
from .yandex_auth import perform_qr_auth

if TYPE_CHECKING:
from music_assistant_models.config_entries import ProviderConfig
Expand Down Expand Up @@ -55,7 +60,7 @@ async def setup(


async def get_config_entries(
mass: MusicAssistant, # noqa: ARG001
mass: MusicAssistant,
instance_id: str | None = None, # noqa: ARG001
action: str | None = None,
values: dict[str, ConfigValueType] | None = None,
Expand All @@ -64,33 +69,96 @@ async def get_config_entries(
if values is None:
values = {}

# Handle QR auth action
if action == CONF_ACTION_AUTH_QR:
session_id = values.get("session_id")
if not session_id:
raise InvalidDataError("Missing session_id for QR authentication")
x_token, music_token = await perform_qr_auth(mass, str(session_id))
Comment thread
trudenboy marked this conversation as resolved.
Comment thread
trudenboy marked this conversation as resolved.
values[CONF_TOKEN] = music_token
if values.get(CONF_REMEMBER_SESSION, True):
values[CONF_X_TOKEN] = x_token
else:
values[CONF_X_TOKEN] = None

Comment thread
trudenboy marked this conversation as resolved.
# Handle clear auth action
if action == CONF_ACTION_CLEAR_AUTH:
values[CONF_TOKEN] = None
values[CONF_X_TOKEN] = None

# Check if user is authenticated
is_authenticated = bool(values.get(CONF_TOKEN))

# Dynamic label text
if not is_authenticated:
label_text = (
"Scan a QR code with the Yandex app on your phone to authenticate.\n\n"
"Alternatively, you can enter a music token manually in the advanced settings."
)
elif action == CONF_ACTION_AUTH_QR:
label_text = "Authenticated to Yandex Music. Don't forget to save to complete setup."
else:
label_text = "Authenticated to Yandex Music."

return (
# Authentication
# Status label
ConfigEntry(
key=CONF_TOKEN,
type=ConfigEntryType.SECURE_STRING,
label="Yandex Music Token",
description="Enter your Yandex Music OAuth token. "
"See the documentation for how to obtain it.",
required=True,
key="label_text",
type=ConfigEntryType.LABEL,
label=label_text,
),
# QR authentication (primary)
ConfigEntry(
key=CONF_ACTION_AUTH_QR,
type=ConfigEntryType.ACTION,
label="Login with QR code",
description="Opens a QR code page — scan it with the Yandex app on your phone.",
action=CONF_ACTION_AUTH_QR,
action_label="Login with QR code",
hidden=is_authenticated,
),
# Remember session toggle
ConfigEntry(
key=CONF_REMEMBER_SESSION,
type=ConfigEntryType.BOOLEAN,
label="Remember session (auto-refresh token)",
description="When enabled, stores a long-lived session token to automatically "
"refresh your music token when it expires. When disabled, you must "
"re-authenticate manually when the token expires.",
default_value=True,
hidden=is_authenticated,
value=cast("str", values.get(CONF_TOKEN)) if values else None,
),
# Clear auth
ConfigEntry(
key=CONF_ACTION_CLEAR_AUTH,
type=ConfigEntryType.ACTION,
label="Reset authentication",
description="Clear the current authentication details.",
action=CONF_ACTION_CLEAR_AUTH,
action_label="Reset authentication",
hidden=not is_authenticated,
),
# Manual token entry (advanced fallback)
ConfigEntry(
key=CONF_TOKEN,
type=ConfigEntryType.SECURE_STRING,
label="Yandex Music Token (manual)",
description="Advanced: manually enter a music token instead of using QR login. "
"See the documentation for how to obtain it.",
required=False,
Comment thread
trudenboy marked this conversation as resolved.
Outdated
hidden=is_authenticated,
advanced=True,
value=cast("str", values.get(CONF_TOKEN)) if values else None,
),
Comment thread
trudenboy marked this conversation as resolved.
Outdated
# x_token (internal storage, always hidden)
ConfigEntry(
key=CONF_X_TOKEN,
type=ConfigEntryType.SECURE_STRING,
label="Session token",
hidden=True,
required=False,
value=cast("str", values.get(CONF_X_TOKEN)) if values else None,
),
# Quality
ConfigEntry(
key=CONF_QUALITY,
Expand Down
4 changes: 2 additions & 2 deletions music_assistant/providers/yandex_music/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,10 +772,10 @@ async def _do_request(c: ClientAsync) -> dict[str, Any] | None:
)
except Exception as err:
LOGGER.warning(
"get-file-info lossless for track %s: Unexpected error: %s",
"get-file-info lossless for track %s: Unexpected %s: %s",
track_id,
type(err).__name__,
err,
exc_info=True,
)

Comment thread
trudenboy marked this conversation as resolved.
return None
Expand Down
5 changes: 5 additions & 0 deletions music_assistant/providers/yandex_music/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@

# Actions
CONF_ACTION_AUTH = "auth"
CONF_ACTION_AUTH_QR = "auth_qr"
CONF_ACTION_CLEAR_AUTH = "clear_auth"

# QR authentication config keys
CONF_X_TOKEN = "x_token"
CONF_REMEMBER_SESSION = "remember_session"

# Labels
LABEL_TOKEN = "token_label"
LABEL_AUTH_INSTRUCTIONS = "auth_instructions_label"
Expand Down
54 changes: 49 additions & 5 deletions music_assistant/providers/yandex_music/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
CONF_LIKED_TRACKS_MAX_TRACKS,
CONF_MY_WAVE_MAX_TRACKS,
CONF_TOKEN,
CONF_X_TOKEN,
DEFAULT_BASE_URL,
DISCOVERY_INITIAL_TRACKS,
FOR_YOU_FOLDER_ID,
Expand Down Expand Up @@ -84,6 +85,7 @@
parse_track,
)
from .streaming import YandexMusicStreamingManager
from .yandex_auth import refresh_music_token

if TYPE_CHECKING:
from music_assistant_models.streamdetails import StreamDetails
Expand Down Expand Up @@ -157,12 +159,54 @@ def _get_browse_names(self) -> dict[str, str]:
async def handle_async_init(self) -> None:
"""Handle async initialization of the provider."""
token = self.config.get_value(CONF_TOKEN)
if not token:
raise LoginFailed("No Yandex Music token provided")

x_token = self.config.get_value(CONF_X_TOKEN)
base_url = self.config.get_value(CONF_BASE_URL, DEFAULT_BASE_URL)
self._client = YandexMusicClient(str(token), base_url=str(base_url))
await self._client.connect()

if not token and not x_token:
raise LoginFailed("No Yandex Music token provided. Please authenticate.")

# Try existing music token first (fast path)
if token:
try:
self._client = YandexMusicClient(str(token), base_url=str(base_url))
await self._client.connect()
except LoginFailed:
self.logger.warning("Music token is invalid or expired")
# Clear the dead token so restarts go straight to refresh
self._update_config_value(CONF_TOKEN, None, encrypted=True)
if x_token:
self.logger.info("Attempting to refresh from session token")
token = None
self._client = None
else:
raise

# Refresh from x_token if music token absent or failed
if not token and x_token:
try:
new_music_token = await refresh_music_token(str(x_token))
self._update_config_value(CONF_TOKEN, new_music_token, encrypted=True)
self._client = YandexMusicClient(new_music_token, base_url=str(base_url))
await self._client.connect()
self.logger.info("Refreshed music token from session token")
except LoginFailed as err:
# Definitive auth failure — clear dead credentials
self.logger.warning("Session token is invalid or expired")
self._update_config_value(CONF_TOKEN, None, encrypted=True)
self._update_config_value(CONF_X_TOKEN, None, encrypted=True)
raise LoginFailed("Session token expired. Please re-authenticate.") from err
except asyncio.CancelledError:
raise
except Exception as err:
# Transient/network failure — keep credentials for retry
self.logger.warning(
"Session token refresh failed (network): %s",
type(err).__name__,
)
raise ProviderUnavailableError(
"Unable to refresh music token right now. Please try again later."
) from err
Comment thread
trudenboy marked this conversation as resolved.

# Suppress yandex_music library DEBUG dumps (full API request/response JSON)
logging.getLogger("yandex_music").setLevel(self.logger.level + 10)
self._streaming = YandexMusicStreamingManager(self)
Expand Down
12 changes: 11 additions & 1 deletion music_assistant/providers/yandex_music/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,9 @@ async def get_audio_stream(
) from err

bytes_before = bytes_yielded
# block_skip = bytes re-downloaded for AES-block alignment.
# Needed below to compute actual HTTP bytes received.
block_skip = bytes_before - block_start
async for chunk in self._decrypt_response_stream(
response, key_bytes, block_size, bytes_yielded
):
Expand All @@ -555,7 +558,14 @@ async def get_audio_stream(

# window complete — check if EOF
window_got = bytes_yielded - bytes_before
if response.status == 200 or window_got < _RANGE_WINDOW:
# received = actual HTTP bytes the server sent for this Range
# request. window_got alone understates the window when
# block_skip > 0 (reconnect at a non-AES-block boundary):
# the decryptor skips block_skip bytes, so window_got would be
# smaller than _RANGE_WINDOW even for a full server response,
# causing premature stream termination without this correction.
received = window_got + block_skip
if response.status == 200 or received < _RANGE_WINDOW:
return # full file received or last partial window
# Exact-boundary guard: if file size is an exact multiple of
# _RANGE_WINDOW the size check above won't catch EOF.
Expand Down
Loading
Loading