{"repo": "andretripol/pico-pu-api-control", "files": {"PicoPuApiControl.Core/BalanceClient.cs": {"0a8cc0f9bae46101722234bb2afa4c8b8d752425": {"t": 1786068388.7539876, "n": 2194, "h": "745c156d12c29c58", "c": "using System.Net.Http.Headers;\nusing System.Net.Http.Json;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace PicoPuApiControl.Core;\n\npublic sealed class BalanceInfo\n{\n [JsonPropertyName(\"currency\")]\n public string? Currency { get; set; }\n\n [JsonPropertyName(\"total_balance\")]\n public string? TotalBalance { get; set; }\n\n [JsonPropertyName(\"granted_balance\")]\n public string? GrantedBalance { get; set; }\n\n [JsonPropertyName(\"topped_up_balance\")]\n public string? ToppedUpBalance { get; set; }\n}\n\npublic sealed class BalanceResponse\n{\n [JsonPropertyName(\"is_available\")]\n public bool IsAvailable { get; set; }\n\n [JsonPropertyName(\"balance_infos\")]\n public List? BalanceInfos { get; set; }\n}\n\npublic sealed class BalanceApiException : Exception\n{\n public int StatusCode { get; }\n\n public BalanceApiException(int statusCode, string body, string providerName = \"DeepSeek\")\n : base($\"{providerName} API returned {statusCode}: {body}\")\n {\n StatusCode = statusCode;\n }\n}\n\npublic sealed class BalanceClient\n{\n private readonly HttpClient _http;\n private readonly string _baseUrl;\n\n public BalanceClient(string baseUrl = \"https://api.deepseek.com\", HttpClient? http = null)\n {\n _baseUrl = baseUrl.TrimEnd('/');\n _http = http ?? new HttpClient();\n }\n\n public async Task GetBalanceAsync(string apiKey, CancellationToken ct = default)\n {\n using var request = new HttpRequestMessage(HttpMethod.Get, $\"{_baseUrl}/user/balance\");\n request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", apiKey);\n request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(\"application/json\"));\n\n using var response = await _http.SendAsync(request, ct).ConfigureAwait(false);\n var body = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false);\n\n if (!response.IsSuccessStatusCode)\n throw new BalanceApiException((int)response.StatusCode, body);\n\n if (string.IsNullOrWhiteSpace(body))\n return null;\n\n return JsonSerializer.Deserialize(body);\n }\n}\n"}}, "PicoPuApiControl.Core/ProviderCatalog.cs": {"0a8cc0f9bae46101722234bb2afa4c8b8d752425": {"t": 1786068389.3814323, "n": 3991, "h": "7ffa1fcdb56e4b5d", "c": "namespace PicoPuApiControl.Core;\n\npublic static class ProviderIds\n{\n public const string DeepSeek = \"deepseek\";\n public const string OpenAi = \"openai\";\n public const string Claude = \"claude\";\n public const string Qwen = \"qwen\";\n public const string MiMo = \"mimo\";\n public const string Grok = \"grok\";\n public const string Gemini = \"gemini\";\n public const string Glm = \"glm\";\n public const string Kimi = \"kimi\";\n public const string OpenRouter = \"openrouter\";\n}\n\npublic enum BalanceMode\n{\n Automatic,\n Manual,\n}\n\npublic sealed record ProviderDefinition(\n string Id,\n string DisplayName,\n string ShortName,\n string BaseUrl,\n string BalancePath,\n string PortalUrl,\n BalanceMode BalanceMode)\n{\n public bool HasBalanceEndpoint => this.BalanceMode == PicoPuApiControl.Core.BalanceMode.Automatic;\n public bool IsManual => this.BalanceMode == PicoPuApiControl.Core.BalanceMode.Manual;\n}\n\n/// \n/// Providers with an official balance endpoint are queried automatically.\n/// Providers whose ordinary API key does not expose a public balance are\n/// available in explicit manual mode; their amounts never pretend to be\n/// provider-reported data.\n/// \npublic static class ProviderCatalog\n{\n private static readonly IReadOnlyList Providers =\n [\n new(\n ProviderIds.DeepSeek,\n \"DeepSeek\",\n \"DS\",\n \"https://api.deepseek.com\",\n \"/user/balance\",\n \"https://platform.deepseek.com\",\n BalanceMode.Automatic),\n new(\n ProviderIds.OpenAi,\n \"OpenAI\",\n \"AI\",\n \"\",\n \"\",\n \"https://platform.openai.com/usage\",\n BalanceMode.Manual),\n new(\n ProviderIds.Claude,\n \"Claude\",\n \"CL\",\n \"\",\n \"\",\n \"https://console.anthropic.com/settings/billing\",\n BalanceMode.Manual),\n new(\n ProviderIds.Qwen,\n \"Qwen\",\n \"QW\",\n \"\",\n \"\",\n \"https://bailian.console.aliyun.com/\",\n BalanceMode.Manual),\n new(\n ProviderIds.MiMo,\n \"MiMo\",\n \"MI\",\n \"\",\n \"\",\n \"https://platform.xiaomimimo.com/\",\n BalanceMode.Manual),\n new(\n ProviderIds.Grok,\n \"Grok\",\n \"GK\",\n \"\",\n \"\",\n \"https://console.x.ai/\",\n BalanceMode.Manual),\n new(\n ProviderIds.Gemini,\n \"Gemini\",\n \"GE\",\n \"\",\n \"\",\n \"https://aistudio.google.com/\",\n BalanceMode.Manual),\n new(\n ProviderIds.Glm,\n \"GLM\",\n \"GL\",\n \"\",\n \"\",\n \"https://open.bigmodel.cn/\",\n BalanceMode.Manual),\n new(\n ProviderIds.Kimi,\n \"Kimi\",\n \"KM\",\n \"\",\n \"\",\n \"https://platform.moonshot.cn/\",\n BalanceMode.Manual),\n new(\n ProviderIds.OpenRouter,\n \"OpenRouter\",\n \"OR\",\n \"https://openrouter.ai\",\n \"/api/v1/key\",\n \"https://openrouter.ai/credits\",\n BalanceMode.Automatic),\n ];\n\n public static IReadOnlyList All => Providers;\n\n public static ProviderDefinition Get(string providerId)\n => Providers.FirstOrDefault(p => string.Equals(p.Id, providerId, StringComparison.OrdinalIgnoreCase))\n ?? throw new ArgumentException($\"Unknown provider: {providerId}\", nameof(providerId));\n\n public static bool TryGet(string? providerId, out ProviderDefinition definition)\n {\n definition = Providers.FirstOrDefault(p =>\n string.Equals(p.Id, providerId, StringComparison.OrdinalIgnoreCase))!;\n return definition is not null;\n }\n}\n"}}}}