{{ showCookiesData || '无Cookie数据' }}
diff --git a/app/src/store/coreapi.ts b/app/src/store/coreapi.ts
index 2a26e67..a07d3da 100644
--- a/app/src/store/coreapi.ts
+++ b/app/src/store/coreapi.ts
@@ -200,6 +200,27 @@ export const useApiStore = defineStore('coreapi', () => {
});
}
+ // 获取抖音扫码登录二维码
+ async function QrCodeGet() {
+ return http.request
>('/api/config/qrcode', 'get').then(r => {
+ return r.data;
+ });
+ }
+
+ // 轮询扫码状态
+ async function QrCodeStatus(token: string) {
+ return http.request>('/api/config/qrstatus?token=' + token, 'get').then(r => {
+ return r.data;
+ });
+ }
+
+ // 扫码成功后绑定到账号
+ async function QrCodeBind(token: string, id: string) {
+ return http.request>(`/api/config/qrbind?token=${token}&id=${id}`, 'post_json').then(r => {
+ return r.data;
+ });
+ }
+
return {
deleteCookie,
UpdateConfig,
@@ -216,6 +237,9 @@ export const useApiStore = defineStore('coreapi', () => {
VideoCategories,
VideoCleanup,
CookiePageList,
- DeleteAuthorVideos
+ DeleteAuthorVideos,
+ QrCodeGet,
+ QrCodeStatus,
+ QrCodeBind
};
});
diff --git a/service/DyQrCodeService.cs b/service/DyQrCodeService.cs
new file mode 100644
index 0000000..030fabf
--- /dev/null
+++ b/service/DyQrCodeService.cs
@@ -0,0 +1,154 @@
+using System.Collections.Concurrent;
+using System.Net;
+using Newtonsoft.Json.Linq;
+
+namespace dy.net.service
+{
+ public class DyQrCodeService
+ {
+ private static readonly ConcurrentDictionary _sessions = new();
+
+ private class QrSession
+ {
+ public CookieContainer CookieJar { get; set; } = new();
+ public DateTime CreatedAt { get; set; } = DateTime.Now;
+ public string? FinalCookies { get; set; }
+ public string? SecUserId { get; set; }
+ }
+
+ private const string Fp = "kj5j6uhv_tvKYUFA0_qzgZ_4l9c_9Amt_DCywEfwbVFCJ";
+ private const string NextUrl = "https://creator.douyin.com/content/manage";
+ private const string Service = "https://creator.douyin.com";
+ private const string Aid = "2906";
+ private const string Ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36";
+ private const string Referer = "https://creator.douyin.com/";
+
+ public async Task<(string token, string qrBase64)> GetQrCodeAsync()
+ {
+ var jar = new CookieContainer();
+ using var client = BuildClient(jar, autoRedirect: true);
+
+ var url = $"https://sso.douyin.com/get_qrcode/?next={Uri.EscapeDataString(NextUrl)}&aid={Aid}&service={Uri.EscapeDataString(Service)}&is_vcd=1&fp={Fp}";
+ var response = await client.GetStringAsync(url);
+ var json = JObject.Parse(response);
+
+ var token = json["data"]?["token"]?.ToString()
+ ?? throw new Exception("获取二维码失败:响应中无 token");
+ var qrBase64 = json["data"]?["qrcode"]?.ToString()
+ ?? throw new Exception("获取二维码失败:响应中无图片");
+
+ _sessions[token] = new QrSession { CookieJar = jar };
+ CleanExpiredSessions();
+
+ return (token, qrBase64);
+ }
+
+ public async Task<(int status, string? cookies, string? secUserId)> CheckStatusAsync(string token)
+ {
+ if (!_sessions.TryGetValue(token, out var session))
+ return (-1, null, null);
+
+ if (DateTime.Now - session.CreatedAt > TimeSpan.FromMinutes(10))
+ {
+ _sessions.TryRemove(token, out _);
+ return (-1, null, null);
+ }
+
+ if (session.FinalCookies != null)
+ return (3, session.FinalCookies, session.SecUserId);
+
+ using var client = BuildClient(session.CookieJar, autoRedirect: false);
+
+ var serviceParam = Uri.EscapeDataString($"{Service}/?logintype=user&loginapp=douyin&jump={NextUrl}");
+ var url = $"https://sso.douyin.com/check_qrconnect/?next={Uri.EscapeDataString(NextUrl)}&token={token}&service={serviceParam}&loginapp=douyin&jump={Uri.EscapeDataString(NextUrl)}&correct_service={serviceParam}&aid={Aid}&is_vcd=1&fp={Fp}";
+ var response = await client.GetStringAsync(url);
+ var json = JObject.Parse(response);
+
+ var statusStr = json["data"]?["status"]?.ToString() ?? "1";
+ int.TryParse(statusStr, out int statusCode);
+
+ if (statusCode == 3)
+ {
+ var redirectUrl = json["data"]?["redirect_url"]?.ToString();
+ if (!string.IsNullOrEmpty(redirectUrl))
+ {
+ try
+ {
+ using var followClient = BuildClient(session.CookieJar, autoRedirect: true);
+ await followClient.GetAsync(redirectUrl);
+ session.FinalCookies = ExtractCookieString(session.CookieJar);
+ session.SecUserId = await GetSecUserIdAsync(session.CookieJar);
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error($"跟进登录重定向失败: {ex.Message}");
+ }
+ }
+ }
+
+ return (statusCode, session.FinalCookies, session.SecUserId);
+ }
+
+ public (string cookies, string secUserId) GetSessionResult(string token)
+ {
+ if (_sessions.TryGetValue(token, out var session))
+ return (session.FinalCookies ?? "", session.SecUserId ?? "");
+ return ("", "");
+ }
+
+ public void RemoveSession(string token) => _sessions.TryRemove(token, out _);
+
+ private static HttpClient BuildClient(CookieContainer jar, bool autoRedirect)
+ {
+ var handler = new HttpClientHandler
+ {
+ CookieContainer = jar,
+ AllowAutoRedirect = autoRedirect,
+ UseCookies = true
+ };
+ var client = new HttpClient(handler);
+ client.DefaultRequestHeaders.Add("User-Agent", Ua);
+ client.DefaultRequestHeaders.Add("Referer", Referer);
+ return client;
+ }
+
+ private static string ExtractCookieString(CookieContainer jar)
+ {
+ var domains = new[] { "https://www.douyin.com", "https://creator.douyin.com", "https://sso.douyin.com" };
+ var all = new Dictionary();
+ foreach (var domain in domains)
+ {
+ foreach (Cookie c in jar.GetCookies(new Uri(domain)))
+ all[c.Name] = c.Value;
+ }
+ return string.Join("; ", all.Select(kv => $"{kv.Key}={kv.Value}"));
+ }
+
+ private static async Task GetSecUserIdAsync(CookieContainer jar)
+ {
+ try
+ {
+ using var client = BuildClient(jar, autoRedirect: true);
+ var resp = await client.GetStringAsync("https://creator.douyin.com/web/api/userinfo/");
+ var json = JObject.Parse(resp);
+ return json["data"]?["sec_uid"]?.ToString()
+ ?? json["data"]?["sec_user_id"]?.ToString()
+ ?? json["sec_uid"]?.ToString();
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error($"获取 sec_user_id 失败: {ex.Message}");
+ return null;
+ }
+ }
+
+ private static void CleanExpiredSessions()
+ {
+ var expired = _sessions
+ .Where(kv => DateTime.Now - kv.Value.CreatedAt > TimeSpan.FromMinutes(10))
+ .Select(kv => kv.Key).ToList();
+ foreach (var key in expired)
+ _sessions.TryRemove(key, out _);
+ }
+ }
+}