using MegghysAPI.Modules; using Microsoft.AspNetCore.Mvc; using System.Text.Json; using System.Text.Json.Nodes; namespace MegghysAPI.Controllers { [Route("api/bili")] [ApiController] public class BiliController : MControllerBase { [HttpGet("test-cap")] public async Task TestCap([FromQuery] string gt, [FromQuery] string challenge) { var solution = await CapResolver.SolveGeetestAsync("https://www.bilibili.com", gt, challenge); return Ok(ResponseOK(solution)); } [HttpGet("resolve-grisk-id")] public async Task ResolveGriskId([FromQuery] string v_voucher, [FromQuery] string? csrf) { if (string.IsNullOrEmpty(v_voucher)) { return BadRequest(ResponseBadRequest("v_voucher 不能为空")); } try { // 1. 调用B站注册接口获取 gt, challenge, token var (gt, challenge, token) = await RegisterFromVVoucher(v_voucher, csrf); // 2. 使用 CapResolver 解决验证码, 获取 validate var solution = await CapResolver.SolveGeetestAsync("https://live.bilibili.com", gt, challenge); if (solution == null) { throw new Exception("从 CapSolver 获取 validate 失败"); } string validate = solution.Validate; // 3. 调用B站验证接口获取 grisk_id var griskId = await ValidateToGriskId(challenge, token, validate, csrf); return Ok(ResponseOK(new { grisk_id = griskId })); } catch (Exception ex) { Logs.Error($"ResolveGriskId 失败: {ex.Message}"); return StatusCode(500, ResponseInternalError(ex.Message)); } } public const string FirstHost = "https://i.ukamnads.icu/api-bilibili/"; public const string SecondHost = "https://api.bilibili.com/"; private static readonly string[] PreferredHosts = { FirstHost, SecondHost }; private async Task<(string gt, string challenge, string token)> RegisterFromVVoucher(string vVoucher, string? csrf) { var path = "x/gaia-vgate/v1/register"; var payload = new Dictionary { { "v_voucher", vVoucher } }; if (!string.IsNullOrEmpty(csrf)) { payload["csrf"] = csrf; } var root = await PostFormWithFailoverAsync(path, payload, "注册接口"); var data = root["data"] ?? throw new Exception("注册接口返回缺少 data 字段"); var geetest = data["geetest"] ?? throw new Exception("注册返回缺少 geetest 字段"); var gt = (string?)geetest["gt"] ?? throw new Exception("注册返回缺少 gt 字段"); var challenge = (string?)geetest["challenge"] ?? throw new Exception("注册返回缺少 challenge 字段"); var apiToken = (string?)data["token"] ?? throw new Exception("注册返回缺少 token 字段"); return (gt, challenge, apiToken); } private async Task ValidateToGriskId(string challenge, string token, string validate, string? csrf) { var path = "x/gaia-vgate/v1/validate"; var seccode = $"{validate}|jordan"; var payload = new Dictionary { { "challenge", challenge }, { "token", token }, { "validate", validate }, { "seccode", seccode }, }; if (!string.IsNullOrEmpty(csrf)) { payload["csrf"] = csrf; } var root = await PostFormWithFailoverAsync(path, payload, "验证接口"); var data = root["data"] ?? throw new Exception("验证接口返回缺少 data 字段"); var isValid = (int?)data["is_valid"]; var griskId = (string?)data["grisk_id"]; if (isValid != 1 || string.IsNullOrEmpty(griskId)) { throw new Exception($"验证失败或缺少 grisk_id: is_valid={isValid} grisk_id={griskId}"); } return griskId; } private async Task PostFormWithFailoverAsync(string path, Dictionary payload, string operation) { Exception? lastException = null; foreach (var host in PreferredHosts) { try { using var content = new FormUrlEncodedContent(payload); var response = await Utils._client.PostAsync($"{host}{path}", content); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonNode.Parse(json) ?? throw new Exception($"{operation} 返回内容解析失败"); } catch (Exception ex) { lastException = ex; if (host == FirstHost) { Logs.Warn($"{operation} 使用 FirstHost 调用失败: {ex.Message}"); } else { Logs.Error($"{operation} 使用 SecondHost 调用失败: {ex.Message}"); } } } throw new Exception($"{operation} 调用失败: {lastException?.Message}", lastException); } } }