5 Commits

Author SHA1 Message Date
FrigaT
8abc6c5074 fix
All checks were successful
Release / pack-and-publish (release) Successful in 32s
2026-04-13 15:42:42 +03:00
FrigaT
b8f78a5856 fix execute context 2026-04-13 15:41:44 +03:00
FrigaT
21a0c5abe6 обновление json parse
All checks were successful
Release / pack-and-publish (release) Successful in 45s
2026-04-13 13:55:23 +03:00
FrigaT
a40b36ef96 Доработана конвертация json
All checks were successful
Release / pack-and-publish (release) Successful in 35s
2026-04-13 13:49:37 +03:00
FrigaT
266aa2e181 fix
All checks were successful
Release / pack-and-publish (release) Successful in 31s
2026-04-13 02:30:14 +03:00
14 changed files with 191 additions and 33 deletions

3
.gitignore vendored
View File

@@ -360,4 +360,5 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
/YaMusicCli/YaMusicCli.csproj

16
YaMusicCli/Program.cs Normal file
View File

@@ -0,0 +1,16 @@
using YandexMusic.API.Extensions.API;
internal class Program
{
private async static Task Main(string[] args)
{
var client = new YandexMusic.YandexMusicClient();
var type = await client.Authorize("y0__xDy2budARje-AYg7rmliBc11LbYoMeUiwiO6f6mSCAMDYVIKg");
var playlists = (await client.GetFavoritesAsync()).Where(t => t.Owner.Uid == client.Account.Uid).ToList();
var playlist = await client.GetPlaylistAsync("97ae0768-8a40-8485-9fa4-b6c856bc6b21");
var tracks = await client.GetTracksAsync(new[] { "78412759" });
await playlist.InsertTracksAsync(tracks.ToArray());
}
}

View File

@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using YandexMusic.API.Converters;
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Common.Providers;
@@ -10,13 +11,6 @@ public abstract class CommonRequestProvider : IRequestProvider
/// <summary>Хранилище данных авторизации.</summary>
protected readonly AuthStorage storage;
/// <summary>Настройки сериализации JSON (регистронезависимые, поддержка enum-строк).</summary>
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};
/// <summary>Инициализирует новый экземпляр провайдера.</summary>
/// <param name="authStorage">Хранилище авторизации.</param>
protected CommonRequestProvider(AuthStorage authStorage)
@@ -36,6 +30,16 @@ public abstract class CommonRequestProvider : IRequestProvider
{
var json = await response.Content.ReadAsStringAsync();
JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
Converters = {
new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower),
new IntToStringConverter(),
new YExecutionContextConverter(api, storage),
}
};
if (!response.IsSuccessStatusCode)
{
var error = JsonSerializer.Deserialize<YErrorResponse>(json, JsonOptions);

View File

@@ -0,0 +1,34 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace YandexMusic.API.Converters;
public class IntToStringConverter : JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number)
{
// Пытаемся извлечь число как int или long
if (reader.TryGetInt32(out int intValue))
return intValue.ToString();
if (reader.TryGetInt64(out long longValue))
return longValue.ToString();
}
else if (reader.TokenType == JsonTokenType.String)
{
return reader.GetString();
}
else if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
throw new JsonException($"Не удалось преобразовать {reader.TokenType} в строку.");
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using YandexMusic.API.Converters;
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Models.Account;
@@ -36,5 +37,6 @@ public class YAccount
public bool ServiceAvailable { get; set; }
[JsonConverter(typeof(IntToStringConverter))]
public string Uid { get; set; }
}

View File

@@ -20,7 +20,7 @@ public class YCoverConverter : JsonConverter<YCover>
"from-artist-photos" or "from-album-cover" => JsonSerializer.Deserialize<YCoverImage>(root.GetRawText(), options),
"pic" => JsonSerializer.Deserialize<YCoverPic>(root.GetRawText(), options),
"mosaic" => JsonSerializer.Deserialize<YCoverMosaic>(root.GetRawText(), options),
_ => JsonSerializer.Deserialize<YCover>(root.GetRawText(), options)
_ => new YCover() { Type = YCoverType.Error }
};
}

View File

@@ -1,4 +1,3 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace YandexMusic.API.Models.Common.Cover;
@@ -8,9 +7,7 @@ public enum YCoverType
{
Color,
Error,
[EnumMember(Value = "from-artist-photos")]
FromArtistPhotos,
[EnumMember(Value = "from-album-cover")]
FromAlbumCover,
Mosaic,
Pic,

View File

@@ -5,7 +5,7 @@ using YandexMusic.API.Common;
namespace YandexMusic.API.Models.Common;
/// <summary>Конвертер для внедрения контекста выполнения (API и хранилище) в модели.</summary>
public class YExecutionContextConverter : JsonConverter<YBaseModel>
public class YExecutionContextConverter : JsonConverter<object>
{
private readonly YandexMusicApi _api;
private readonly AuthStorage _storage;
@@ -16,26 +16,27 @@ public class YExecutionContextConverter : JsonConverter<YBaseModel>
_storage = storage;
}
public override YBaseModel? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override bool CanConvert(Type typeToConvert) =>
typeof(YBaseModel).IsAssignableFrom(typeToConvert);
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Десериализуем объект без контекста
var obj = (YBaseModel?)JsonSerializer.Deserialize(ref reader, typeToConvert, options);
if (obj != null)
// Убираем этот конвертер из опций, чтобы избежать рекурсии
var innerOptions = new JsonSerializerOptions(options);
innerOptions.Converters.Remove(this);
var obj = JsonSerializer.Deserialize(ref reader, typeToConvert, innerOptions);
if (obj is YBaseModel baseModel)
{
obj.Context = new YExecutionContext
{
API = _api,
Storage = _storage
};
baseModel.Context = new YExecutionContext { API = _api, Storage = _storage };
}
return obj;
}
public override void Write(Utf8JsonWriter writer, YBaseModel value, JsonSerializerOptions options)
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
// При сериализации игнорируем контекст
var cloneOptions = new JsonSerializerOptions(options);
cloneOptions.Converters.Remove(this);
JsonSerializer.Serialize(writer, value, cloneOptions);
var innerOptions = new JsonSerializerOptions(options);
innerOptions.Converters.Remove(this);
JsonSerializer.Serialize(writer, value, innerOptions);
}
}
}

View File

@@ -1,11 +1,12 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace YandexMusic.API.Models.Common;
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum YTrackSharingFlag
{
[EnumMember(Value = "VIDEO_ALLOWED")]
[JsonStringEnumMemberName("VIDEO_ALLOWED")]
VideoAllowed,
[EnumMember(Value = "COVER_ONLY")]
[JsonStringEnumMemberName("COVER_ONLY")]
CoverOnly
}

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using YandexMusic.API.Converters;
using YandexMusic.API.Models.Common;
using YandexMusic.API.Models.Common.Cover;
using YandexMusic.API.Models.Track;
@@ -44,6 +45,7 @@ public class YPlaylist : YBaseModel
public string Image { get; set; }
public bool IsBanner { get; set; }
public bool IsPremiere { get; set; }
[JsonConverter(typeof(IntToStringConverter))]
public string Kind { get; set; }
public List<YPlaylist> LastOwnerPlaylists { get; set; }
public int LikesCount { get; set; }

View File

@@ -31,7 +31,7 @@ public abstract class YRequestBuilder<TResponse, TParams>
?? throw new NotImplementedException($"Отсутствует атрибут {nameof(YRequestAttribute)}");
api = yandex;
storage = auth;
device = $"os=CSharp; os_version=; manufacturer=K1llM@n; model=Yandex Music API; clid=; device_id={storage.DeviceId}; uuid=random";
device = $"os=CSharp; os_version=; manufacturer=FrigaT; model=Yandex Music API; clid=; device_id={storage.DeviceId}; uuid=random";
_jsonOptions = new JsonSerializerOptions
{

View File

@@ -0,0 +1,100 @@
param(
[string]$Version
)
Write-Host "=== YandexMusic Manual Publisher ===" -ForegroundColor Cyan
# --- Ask for version if not provided ---
if (-not $Version) {
$Version = Read-Host "Введите версию пакета (например 1.2.3). Пусто = взять из git-тега"
if (-not $Version) {
Write-Host "Читаю версию из git..." -ForegroundColor Yellow
$tag = git describe --tags --abbrev=0 2>$null
if (-not $tag) {
Write-Host "ОШИБКА: версия не указана и git-тег не найден." -ForegroundColor Red
exit 1
}
$Version = $tag.TrimStart("v")
}
}
Write-Host "Используемая версия: $Version" -ForegroundColor Green
# --- Paths ---
$csprojPath = "YandexMusic/YandexMusic.csproj"
$backupPath = "$csprojPath.bak"
$artifacts = "artifacts"
# --- Backup original csproj ---
Write-Host "Создаю резервную копию $csprojPath ..." -ForegroundColor Cyan
Copy-Item $csprojPath $backupPath -Force
# --- Replace ProjectReference with PackageReference ---
Write-Host "Патчу csproj..." -ForegroundColor Cyan
(Get-Content $csprojPath) `
-replace '<ProjectReference Include="..\/YandexMusic.API\/YandexMusic.API.csproj" \/>',
"<PackageReference Include=`"YandexMusic.API`" Version=`"$Version`" />" |
Set-Content $csprojPath
Write-Host "ProjectReference → PackageReference заменён." -ForegroundColor Green
# --- Build & Pack ---
$projects = @(
"YandexMusic.API",
"YandexMusic"
)
if (Test-Path $artifacts) {
Remove-Item $artifacts -Recurse -Force
}
New-Item -ItemType Directory -Path $artifacts | Out-Null
try {
foreach ($proj in $projects) {
Write-Host "Restoring $proj ..." -ForegroundColor Cyan
dotnet restore $proj
Write-Host "Building $proj ..." -ForegroundColor Cyan
dotnet build $proj -c Release -p:Version=$Version
Write-Host "Packing $proj ..." -ForegroundColor Cyan
dotnet pack $proj -c Release --no-build -p:PackageVersion=$Version -o $artifacts
}
Write-Host "Сборка и упаковка завершены." -ForegroundColor Green
# --- Publish ---
$apiKey = $env:NUGET_API_KEY
if (-not $apiKey) {
Write-Host "ОШИБКА: переменная окружения NUGET_API_KEY не установлена." -ForegroundColor Red
exit 1
}
Write-Host "Публикую пакеты..." -ForegroundColor Cyan
dotnet nuget push "$artifacts\*.nupkg" `
--source "https://git.frigat.duckdns.org/api/packages/FrigaT/nuget/index.json" `
--api-key $apiKey `
--skip-duplicate
Write-Host "Публикация завершена." -ForegroundColor Green
}
finally {
# --- Restore original csproj ---
Write-Host "Восстанавливаю оригинальный csproj..." -ForegroundColor Cyan
Move-Item $backupPath $csprojPath -Force
# --- Cleanup artifacts ---
if (Test-Path $artifacts) {
Write-Host "Удаляю artifacts..." -ForegroundColor Cyan
Remove-Item $artifacts -Recurse -Force
}
Write-Host "Откат завершён." -ForegroundColor Green
}
Write-Host "Готово." -ForegroundColor Cyan

View File

@@ -1,4 +1,5 @@
<Solution>
<Project Path="YaMusicCli/YaMusicCli.csproj" Id="5cce354e-7517-4a94-9584-197daa3ad6a4" />
<Project Path="YandexMusic.API/YandexMusic.API.csproj" />
<Project Path="YandexMusic/YandexMusic.csproj" Id="044fcef4-86d2-4cc9-9f7e-a577c19ae5c3" />
</Solution>

View File

@@ -38,7 +38,6 @@ public class YandexMusicClient : IDisposable
{
_api = new YandexMusicApi();
_storage = new AuthStorage();
_player = _api.Ynison.GetPlayer(_storage);
}
#region Авторизация