飞牛fpk打包,新增一个初始化页面。用于飞牛fpk安装完成后初始配置
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
using dy.net.dto;
|
using dy.net.dto;
|
||||||
using dy.net.model;
|
using dy.net.model;
|
||||||
using dy.net.service;
|
using dy.net.service;
|
||||||
|
using dy.net.utils;
|
||||||
using dy.sync.lib;
|
using dy.sync.lib;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@@ -126,6 +127,18 @@ namespace dy.net.Controllers
|
|||||||
var follows = await douyinFollowService.GetGroupByCookieAsync();
|
var follows = await douyinFollowService.GetGroupByCookieAsync();
|
||||||
return ApiResult.Success(follows);
|
return ApiResult.Success(follows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已经初始化了
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("isInit")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> IsInit()
|
||||||
|
{
|
||||||
|
var init= await dyCookieService.IsInit();
|
||||||
|
return ApiResult.Success(false);
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 新增用户Cookie
|
/// 新增用户Cookie
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -142,6 +155,78 @@ namespace dy.net.Controllers
|
|||||||
return ApiResult.Fail("添加失败");
|
return ApiResult.Fail("添加失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 非docker初始化
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("deskinit")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> DeskInitAsync([FromBody] DouyinCookie dyUserCookies)
|
||||||
|
{
|
||||||
|
dyUserCookies.Id = IdGener.GetLong().ToString();
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(dyUserCookies.SavePath))
|
||||||
|
{
|
||||||
|
return ApiResult.Fail("收藏存储路径不能为空");
|
||||||
|
}
|
||||||
|
if (!DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.SavePath))
|
||||||
|
{
|
||||||
|
return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.SavePath}添加读写权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(dyUserCookies.FavSavePath) && !DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.FavSavePath))
|
||||||
|
{
|
||||||
|
return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.FavSavePath}添加读写权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(dyUserCookies.UpSavePath) && !DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.UpSavePath))
|
||||||
|
{
|
||||||
|
return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.UpSavePath}添加读写权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(dyUserCookies.ImgSavePath) && !DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.ImgSavePath))
|
||||||
|
{
|
||||||
|
return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.ImgSavePath}添加读写权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await dyCookieService.Add(dyUserCookies);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
|
||||||
|
return ApiResult.Success();
|
||||||
|
}
|
||||||
|
return ApiResult.Fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("appport")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> GetAppPort()
|
||||||
|
{
|
||||||
|
//return ApiResult.Success(10105);
|
||||||
|
return ApiResult.Success(string.IsNullOrWhiteSpace(Appsettings.Get("appPort"))?10101: Convert.ToInt32(Appsettings.Get("appPort")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 快速开启或停止
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("switch")]
|
||||||
|
public async Task<IActionResult> SwitchAsync([FromBody] DouyinCookieStopDto dto)
|
||||||
|
{
|
||||||
|
var result = await dyCookieService.Switch(dto);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
ReStartJob();
|
||||||
|
return ApiResult.Success();
|
||||||
|
}
|
||||||
|
return ApiResult.Fail("添加失败");
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新用户Cookie
|
/// 更新用户Cookie
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -194,12 +279,20 @@ namespace dy.net.Controllers
|
|||||||
return ApiResult.Success(data);
|
return ApiResult.Success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("UpdateConfig")]
|
[HttpPost("UpdateConfig")]
|
||||||
public async Task<IActionResult> UpdateConfig(AppConfig config)
|
public async Task<IActionResult> UpdateConfig(AppConfig config)
|
||||||
{
|
{
|
||||||
var data = await commonService.UpdateConfig(config);
|
var data = await commonService.UpdateConfig(config);
|
||||||
if (data)
|
if (data)
|
||||||
{
|
{
|
||||||
|
if (config.OnlySyncNew)
|
||||||
|
{
|
||||||
|
var d = await douyinCookieService.SetOnlySyncNew();
|
||||||
|
if (d)
|
||||||
|
Serilog.Log.Debug("仅同步新视频配置已生效,后续所有类型的视频同步将只会读取最近一页约20条数据");
|
||||||
|
}
|
||||||
|
|
||||||
ReStartJob();
|
ReStartJob();
|
||||||
}
|
}
|
||||||
return ApiResult.Success(data);
|
return ApiResult.Success(data);
|
||||||
@@ -239,12 +332,39 @@ namespace dy.net.Controllers
|
|||||||
|
|
||||||
[HttpGet("checktag")]
|
[HttpGet("checktag")]
|
||||||
public async Task<IActionResult> CheckTag()
|
public async Task<IActionResult> CheckTag()
|
||||||
|
{
|
||||||
|
|
||||||
|
var deploy = Appsettings.Get("deploy");
|
||||||
|
if(string.IsNullOrWhiteSpace(deploy))
|
||||||
|
{
|
||||||
|
return await GetDockerTagVersions();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(deploy== "fn")//飞牛
|
||||||
|
{
|
||||||
|
return ApiResult.Success(new List<string> { "beta_"+Appsettings.Get("fnVersion") });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return await GetDockerTagVersions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IActionResult> GetDockerTagVersions()
|
||||||
{
|
{
|
||||||
var data = await DouyinHttpHelper.GetTenImage(Appsettings.Get("tagName"));
|
var data = await DouyinHttpHelper.GetTenImage(Appsettings.Get("tagName"));
|
||||||
if (data.IsSuccessStatusCode)
|
if (data.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var content = await data.Content.ReadAsStringAsync();
|
var content = await data.Content.ReadAsStringAsync();
|
||||||
return Ok(content);
|
|
||||||
|
var tagData = JsonConvert.DeserializeObject<DouyinApiResponse<List<string>>>(content);
|
||||||
|
if (tagData != null && tagData.Data != null && tagData.Data.Count > 0)
|
||||||
|
return ApiResult.Success(tagData.Data);
|
||||||
|
return ApiResult.Fail();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
+102
-39
@@ -3,6 +3,8 @@ using dy.net.service;
|
|||||||
using dy.net.utils;
|
using dy.net.utils;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace dy.net
|
namespace dy.net
|
||||||
@@ -10,24 +12,22 @@ namespace dy.net
|
|||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
// 常量定义
|
// 常量定义
|
||||||
private static string DefaultListenUrl = "http://*:10101";
|
private static string DefaultListenUrl = "10101";
|
||||||
private const string SpaRootPath = "app/dist";
|
private const string SpaRootPath = "app/dist";
|
||||||
private const string SpaSourcePath = "app/";
|
private const string SpaSourcePath = "app/";
|
||||||
private const string SwaggerDocTitle = "dy.net WebApi Docs";
|
private const string SwaggerDocTitle = "dy.net WebApi Docs";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打包时注意,如果是false,前端不允许修改开启下载图片和视频的选项
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// 初始化编码提供器
|
// 初始化编码提供器
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
// 构建Web应用
|
// 构建Web应用
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
//from docker yaml file 环境变量 或者 dockerfile 或appsettings.json
|
|
||||||
DefaultListenUrl = builder.Configuration.GetValue<string>(SystemStaticUtil.ASPNETCORE_URLS) ?? DefaultListenUrl;
|
|
||||||
|
|
||||||
var isDevelopment = builder.Environment.IsDevelopment();
|
var isDevelopment = builder.Environment.IsDevelopment();
|
||||||
|
|
||||||
@@ -39,16 +39,13 @@ namespace dy.net
|
|||||||
|
|
||||||
// 构建应用
|
// 构建应用
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
Log.Debug("ffmpeg is on");
|
|
||||||
|
|
||||||
// 配置中间件
|
// 配置中间件
|
||||||
ConfigureMiddleware(app, builder.Environment);
|
ConfigureMiddleware(app, builder.Environment);
|
||||||
|
|
||||||
// 初始化应用服务
|
// 初始化应用服务
|
||||||
InitApplicationServices(app, isDevelopment);
|
InitApplicationServices(app, isDevelopment);
|
||||||
|
|
||||||
Serilog.Log.Debug("dy.sync service is starting...");
|
Log.Debug($"dy.sync app is started successfully on {DefaultListenUrl}");
|
||||||
Log.Debug("dy.sync service is started successfully");
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
@@ -58,17 +55,84 @@ namespace dy.net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ConfigureHost(WebApplicationBuilder builder, bool isDevelopment)
|
private static void ConfigureHost(WebApplicationBuilder builder, bool isDevelopment)
|
||||||
{
|
{
|
||||||
// 设置监听地址
|
|
||||||
builder.WebHost.UseUrls(DefaultListenUrl);
|
|
||||||
|
|
||||||
|
|
||||||
|
//// 配置配置文件
|
||||||
|
//builder.Host.ConfigureAppConfiguration((context, config) =>
|
||||||
|
//{
|
||||||
|
// //config.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
// // .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
|
// // .AddEnvironmentVariables();
|
||||||
|
|
||||||
|
// // 关键:获取程序集所在的物理目录(而非当前工作目录)
|
||||||
|
// var assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||||
|
// // 兜底:如果获取失败,回退到当前目录(可选)
|
||||||
|
// var basePath = string.IsNullOrEmpty(assemblyPath) ? Directory.GetCurrentDirectory() : assemblyPath;
|
||||||
|
|
||||||
|
// config.SetBasePath(basePath) // 使用程序集目录作为基础路径
|
||||||
|
// .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
|
// .AddEnvironmentVariables();
|
||||||
|
//});
|
||||||
// 配置配置文件
|
// 配置配置文件
|
||||||
builder.Host.ConfigureAppConfiguration((context, config) =>
|
builder.Host.ConfigureAppConfiguration((context, config) =>
|
||||||
{
|
{
|
||||||
config.SetBasePath(Directory.GetCurrentDirectory())
|
// 定义配置文件名(统一小写,适配Linux大小写敏感特性)
|
||||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
const string configFileName = "appsettings.json";
|
||||||
|
|
||||||
|
// 步骤1:获取多维度的候选基础路径(覆盖不同部署场景)
|
||||||
|
var candidateBasePaths = new List<string>
|
||||||
|
{
|
||||||
|
// 候选1:程序集所在目录(优先,解决工作目录不一致问题)
|
||||||
|
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
|
||||||
|
// 候选2:当前工作目录(兜底)
|
||||||
|
Directory.GetCurrentDirectory(),
|
||||||
|
// 候选3:应用根目录(针对单文件发布/容器部署场景)
|
||||||
|
AppContext.BaseDirectory,
|
||||||
|
// 候选4:自定义环境变量指定的配置目录(灵活性扩展)
|
||||||
|
Environment.GetEnvironmentVariable("APP_CONFIG_DIR")
|
||||||
|
}
|
||||||
|
// 过滤空值和无效路径
|
||||||
|
.Where(path => !string.IsNullOrEmpty(path) && Directory.Exists(path))
|
||||||
|
.Distinct() // 去重
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 步骤2:遍历候选路径,查找存在的配置文件
|
||||||
|
string configFilePath = null;
|
||||||
|
foreach (var basePath in candidateBasePaths)
|
||||||
|
{
|
||||||
|
var tempPath = Path.Combine(basePath, configFileName);
|
||||||
|
if (File.Exists(tempPath))
|
||||||
|
{
|
||||||
|
configFilePath = tempPath;
|
||||||
|
break; // 找到第一个存在的配置文件即可
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤3:配置加载(增加容错和日志提示)
|
||||||
|
if (!string.IsNullOrEmpty(configFilePath))
|
||||||
|
{
|
||||||
|
// 找到配置文件,正常加载
|
||||||
|
var basePath = Path.GetDirectoryName(configFilePath);
|
||||||
|
config.SetBasePath(basePath)
|
||||||
|
.AddJsonFile(configFileName, optional: false, reloadOnChange: true)
|
||||||
.AddEnvironmentVariables();
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
|
// 可选:输出日志,确认配置文件加载路径(方便排查)
|
||||||
|
//Console.WriteLine($"成功加载配置文件:{configFilePath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 未找到配置文件,抛出明确异常(或根据需求调整为兜底逻辑)
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
$"未找到配置文件 {configFileName},已检查以下路径:{string.Join("; ", candidateBasePaths)}",
|
||||||
|
configFileName);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//from docker yaml file 环境变量 或者 dockerfile 或appsettings.json
|
||||||
|
DefaultListenUrl = $"http://*:{builder.Configuration.GetValue<string>(SystemStaticUtil.APP_PORT) ?? DefaultListenUrl}";
|
||||||
|
// 设置监听地址
|
||||||
|
builder.WebHost.UseUrls(DefaultListenUrl);
|
||||||
// 配置日志
|
// 配置日志
|
||||||
builder.Host.ConfigureLogging(logging => logging.ClearProviders())
|
builder.Host.ConfigureLogging(logging => logging.ClearProviders())
|
||||||
.UseSerilog();
|
.UseSerilog();
|
||||||
@@ -81,7 +145,10 @@ namespace dy.net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ConfigureServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment environment)
|
private static void ConfigureServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment environment)
|
||||||
{
|
{
|
||||||
PrintApp();
|
|
||||||
|
//打印logo
|
||||||
|
//PrintApp();
|
||||||
|
|
||||||
services.AddSingleton(new Appsettings (config));
|
services.AddSingleton(new Appsettings (config));
|
||||||
// 雪花ID生成器
|
// 雪花ID生成器
|
||||||
services.AddSnowFlakeId(options => options.WorkId = new Random().Next(1, 100));
|
services.AddSnowFlakeId(options => options.WorkId = new Random().Next(1, 100));
|
||||||
@@ -105,16 +172,16 @@ namespace dy.net
|
|||||||
services.AddServicesFromNamespace("dy.net.repository")
|
services.AddServicesFromNamespace("dy.net.repository")
|
||||||
.AddServicesFromNamespace("dy.net.service");
|
.AddServicesFromNamespace("dy.net.service");
|
||||||
|
|
||||||
services.AddSingleton<FFmpegHelper>();
|
services.AddScoped<FFmpegHelper>();
|
||||||
|
|
||||||
// SPA静态文件支持
|
// SPA静态文件支持
|
||||||
services.AddSpaStaticFiles(options => options.RootPath = SpaRootPath);
|
services.AddSpaStaticFiles(options => options.RootPath = SpaRootPath);
|
||||||
|
|
||||||
// 开发环境启用Swagger
|
// 开发环境启用Swagger
|
||||||
if (environment.IsDevelopment())
|
//if (environment.IsDevelopment())
|
||||||
{
|
//{
|
||||||
services.AddSwagger();
|
// services.AddSwagger();
|
||||||
}
|
//}
|
||||||
|
|
||||||
// 响应压缩
|
// 响应压缩
|
||||||
services.AddResponseCompression();
|
services.AddResponseCompression();
|
||||||
@@ -134,10 +201,10 @@ namespace dy.net
|
|||||||
app.UseResponseCompression();
|
app.UseResponseCompression();
|
||||||
|
|
||||||
// 开发环境启用SwaggerUI
|
// 开发环境启用SwaggerUI
|
||||||
if (environment.IsDevelopment())
|
//if (environment.IsDevelopment())
|
||||||
{
|
//{
|
||||||
app.UseCustomSwaggerUI(options => options.Title = SwaggerDocTitle);
|
// app.UseCustomSwaggerUI(options => options.Title = SwaggerDocTitle);
|
||||||
}
|
//}
|
||||||
|
|
||||||
// 路由
|
// 路由
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
@@ -175,25 +242,31 @@ namespace dy.net
|
|||||||
var userService = services.GetRequiredService<AdminUserService>();
|
var userService = services.GetRequiredService<AdminUserService>();
|
||||||
userService.InitUser();
|
userService.InitUser();
|
||||||
|
|
||||||
// 初始化Cookie
|
|
||||||
var cookieService = services.GetRequiredService<DouyinCookieService>();
|
|
||||||
cookieService.InitCookie();
|
|
||||||
|
|
||||||
// 初始化配置
|
|
||||||
var commonService = services.GetRequiredService<DouyinCommonService>();
|
var commonService = services.GetRequiredService<DouyinCommonService>();
|
||||||
var config = commonService.InitConfig();
|
|
||||||
|
|
||||||
// 更新视频类型--兼容老版本
|
// 更新视频类型--兼容老版本
|
||||||
commonService.UpdateCollectViedoType();
|
commonService.UpdateCollectViedoType();
|
||||||
// 重置博主作品同步状态为未同步
|
// 重置博主作品同步状态为未同步
|
||||||
commonService.UpdateAllCookieSyncedToZero();
|
commonService.UpdateAllCookieSyncedToZero();
|
||||||
|
|
||||||
|
// 初始化配置
|
||||||
|
var config = commonService.InitConfig();
|
||||||
if (!isDevelopment)
|
if (!isDevelopment)
|
||||||
{
|
{
|
||||||
// 启动定时任务
|
// 启动定时任务
|
||||||
var quartzJobService = services.GetRequiredService<DouyinQuartzJobService>();
|
var quartzJobService = services.GetRequiredService<DouyinQuartzJobService>();
|
||||||
quartzJobService.InitOrReStartAllJobs(config?.Cron <= 0 ? "30" : config.Cron.ToString());
|
quartzJobService.InitOrReStartAllJobs(config?.Cron <= 0 ? "30" : config.Cron.ToString());
|
||||||
}
|
}
|
||||||
|
// 初始化Cookie
|
||||||
|
var deploy= Appsettings.Get("deploy");
|
||||||
|
if (deploy != null&&deploy=="docker")//docker环境直接初始化一个默认的配置
|
||||||
|
{
|
||||||
|
var cookieService = services.GetRequiredService<DouyinCookieService>();
|
||||||
|
cookieService.InitCookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -206,7 +279,6 @@ namespace dy.net
|
|||||||
private static void PrintApp()
|
private static void PrintApp()
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.DarkCyan;
|
Console.ForegroundColor = ConsoleColor.DarkCyan;
|
||||||
Console.WriteLine();
|
|
||||||
|
|
||||||
// 步骤1:定义原始ASCII艺术字行(无缩进)
|
// 步骤1:定义原始ASCII艺术字行(无缩进)
|
||||||
List<string> originalArtLines = new List<string>
|
List<string> originalArtLines = new List<string>
|
||||||
@@ -262,15 +334,6 @@ namespace dy.net
|
|||||||
|
|
||||||
// 打印完成后,光标移到艺术字下方
|
// 打印完成后,光标移到艺术字下方
|
||||||
Console.SetCursorPosition(0, indentedArtLines.Count);
|
Console.SetCursorPosition(0, indentedArtLines.Count);
|
||||||
|
|
||||||
//string asciiArt = @"=================================================================================================================";
|
|
||||||
//string asciiArt = $@"——————————————————————{DateTime.Now:yyyy-MM-dd HH:mm:ss}——————————————————————";
|
|
||||||
|
|
||||||
//foreach (char ch in asciiArt)
|
|
||||||
//{
|
|
||||||
// Console.Write(ch);
|
|
||||||
// Thread.Sleep(2);
|
|
||||||
//}
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
|
||||||
-->
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<DeleteExistingFiles>true</DeleteExistingFiles>
|
|
||||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
|
||||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
|
||||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
|
||||||
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
|
||||||
<PublishProvider>FileSystem</PublishProvider>
|
|
||||||
<PublishUrl>bin\Release\net6.0\publish\</PublishUrl>
|
|
||||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
|
||||||
<_TargetId>Folder</_TargetId>
|
|
||||||
<SiteUrlToLaunchAfterPublish />
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<ProjectGuid>680660ef-acae-43a9-ab6c-b75532e758ad</ProjectGuid>
|
|
||||||
<SelfContained>false</SelfContained>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
|
||||||
-->
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
|
||||||
<History>True|2025-12-21T05:23:11.0272294Z||;True|2025-12-21T13:22:56.8339811+08:00||;False|2025-12-21T13:22:50.2395530+08:00||;True|2025-12-21T13:11:36.6129339+08:00||;True|2025-12-21T12:15:18.4086793+08:00||;True|2025-12-21T12:15:11.4448190+08:00||;True|2025-12-20T14:05:36.6224332+08:00||;False|2025-12-20T14:05:28.6359828+08:00||;True|2025-12-20T13:58:30.1641630+08:00||;True|2025-12-20T13:55:16.1759645+08:00||;True|2025-12-20T12:16:22.5224516+08:00||;True|2025-12-19T09:50:16.9517676+08:00||;True|2025-12-19T09:49:47.0871734+08:00||;False|2025-12-19T09:49:36.7638757+08:00||;True|2025-12-19T09:35:13.2489248+08:00||;True|2025-12-14T19:09:59.9031104+08:00||;True|2025-12-13T21:21:10.0022707+08:00||;True|2025-12-11T22:07:16.6229994+08:00||;True|2025-12-11T22:05:56.7363206+08:00||;True|2025-12-11T22:01:07.5862406+08:00||;True|2025-12-11T22:00:43.5796594+08:00||;True|2025-12-08T21:20:37.8369271+08:00||;False|2025-12-08T21:20:21.1646252+08:00||;True|2025-12-08T20:51:50.6845619+08:00||;True|2025-12-08T16:43:45.2425324+08:00||;False|2025-12-08T16:42:23.0308163+08:00||;True|2025-12-08T16:41:52.4103868+08:00||;True|2025-12-08T16:41:49.8972006+08:00||;False|2025-12-08T16:41:39.5343442+08:00||;True|2025-12-08T12:40:19.2430740+08:00||;True|2025-12-08T12:02:30.6005542+08:00||;True|2025-12-08T11:34:29.6673185+08:00||;False|2025-12-08T11:30:56.2857042+08:00||;True|2025-12-08T10:52:43.7427594+08:00||;True|2025-12-08T07:05:03.2845531+08:00||;True|2025-12-07T23:02:10.0037815+08:00||;True|2025-12-07T22:48:25.7859648+08:00||;True|2025-12-07T20:49:13.6683281+08:00||;True|2025-12-07T08:47:30.1236366+08:00||;True|2025-12-06T13:14:32.4410202+08:00||;True|2025-12-06T13:07:29.7182102+08:00||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;True|2025-12-06T13:03:28.9521030+08:00||;True|2025-12-05T23:11:51.5026151+08:00||;True|2025-12-05T12:46:26.1415079+08:00||;False|2025-12-05T12:46:19.1274332+08:00||;True|2025-12-05T09:04:36.7111717+08:00||;True|2025-12-05T08:22:51.7343317+08:00||;True|2025-12-05T08:20:32.0383739+08:00||;True|2025-12-04T21:58:29.4112766+08:00||;True|2025-12-04T21:58:10.0382218+08:00||;True|2025-12-04T21:57:55.3894059+08:00||;True|2025-12-04T21:10:38.8943797+08:00||;True|2025-12-04T21:09:31.5024569+08:00||;True|2025-12-04T08:22:47.9617427+08:00||;False|2025-12-04T08:22:41.3759733+08:00||;True|2025-12-04T08:14:02.2478893+08:00||;True|2025-12-04T07:57:00.2208139+08:00||;True|2025-12-03T23:58:13.3693303+08:00||;True|2025-12-03T23:57:40.4257893+08:00||;True|2025-12-03T23:56:38.8717769+08:00||;True|2025-12-03T23:56:24.0663568+08:00||;False|2025-12-03T23:55:36.1902974+08:00||;True|2025-12-03T23:51:30.6688504+08:00||;True|2025-12-03T23:34:17.1648971+08:00||;False|2025-12-03T23:34:07.7649161+08:00||;True|2025-12-03T22:32:56.2435003+08:00||;True|2025-12-03T22:27:45.6059666+08:00||;True|2025-12-03T15:55:28.0186597+08:00||;False|2025-12-03T15:54:17.5032724+08:00||;True|2025-12-03T15:53:10.2273509+08:00||;True|2025-12-02T22:11:37.4645042+08:00||;False|2025-12-02T22:10:47.1320259+08:00||;True|2025-12-02T14:39:58.8932130+08:00||;True|2025-12-02T14:36:37.1529072+08:00||;True|2025-12-02T12:58:22.0951548+08:00||;True|2025-12-02T09:30:33.7066474+08:00||;True|2025-12-02T09:30:14.5481844+08:00||;True|2025-12-02T09:30:00.3123364+08:00||;False|2025-12-02T09:29:54.4615520+08:00||;True|2025-12-02T09:13:01.7995414+08:00||;False|2025-12-02T09:12:55.8360281+08:00||;True|2025-12-02T09:12:31.0156791+08:00||;True|2025-12-02T08:55:11.3773395+08:00||;True|2025-12-02T08:53:21.3927713+08:00||;False|2025-12-02T08:48:55.8638037+08:00||;True|2025-12-02T07:29:25.2192447+08:00||;False|2025-12-02T07:29:10.2504665+08:00||;True|2025-12-02T07:28:29.0769286+08:00||;True|2025-12-01T21:53:07.6474834+08:00||;True|2025-12-01T21:44:28.1674315+08:00||;True|2025-12-01T21:18:47.2119609+08:00||;True|2025-12-01T20:51:19.9948301+08:00||;True|2025-12-01T20:50:36.9904697+08:00||;True|2025-12-01T20:44:10.1695142+08:00||;True|2025-12-01T19:27:58.0479456+08:00||;True|2025-12-01T19:16:02.2288214+08:00||;False|2025-12-01T19:15:56.0372115+08:00||;</History>
|
|
||||||
<LastFailureDetails />
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<File Include="dto/AdminUserDto.cs">
|
|
||||||
<publishTime>11/12/2025 21:56:08</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/DouyinUpSecUserIdDto.cs">
|
|
||||||
<publishTime>11/22/2025 17:11:30</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/DouyinVideoInfo.cs">
|
|
||||||
<publishTime>11/22/2025 17:11:30</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/DouyinVideoNfo.cs">
|
|
||||||
<publishTime>11/22/2025 17:00:49</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/DouyinVideoPageRequestDto.cs">
|
|
||||||
<publishTime>11/22/2025 17:11:19</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/DouyinVideoRequestDto.cs">
|
|
||||||
<publishTime>11/22/2025 17:11:30</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/InitSystemInfo.cs">
|
|
||||||
<publishTime>11/12/2025 21:56:08</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/VideoStaticsDto.cs">
|
|
||||||
<publishTime>11/23/2025 22:21:42</publishTime>
|
|
||||||
</File>
|
|
||||||
<File Include="dto/VideoTypeEnum.cs">
|
|
||||||
<publishTime>11/23/2025 19:09:25</publishTime>
|
|
||||||
</File>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -96,8 +96,8 @@ Cookie 及 `sec_user_id` 是同步功能的核心,需严格按步骤获取,
|
|||||||
|
|
||||||
| 镜像标签 | 架构 |
|
| 镜像标签 | 架构 |
|
||||||
| ----------------- | -------------- |
|
| ----------------- | -------------- |
|
||||||
| `beta_1.9.0` | x86_64 (amd64) |
|
| `beta_1.9.2` | x86_64 (amd64) |
|
||||||
| `arm_1.9.0` | ARM64 |
|
| `arm_1.9.2` | ARM64 |
|
||||||
|
|
||||||
### 最新镜像查看
|
### 最新镜像查看
|
||||||
[镜像列表](http://nas.synology2023.online:10108/api/docker/dysync/1)
|
[镜像列表](http://nas.synology2023.online:10108/api/docker/dysync/1)
|
||||||
@@ -115,7 +115,7 @@ docker run -d --restart=always \
|
|||||||
-v /opt/dysync/uper:/app/uper \
|
-v /opt/dysync/uper:/app/uper \
|
||||||
-p 10103:10101 \
|
-p 10103:10101 \
|
||||||
--name dysync2025 \
|
--name dysync2025 \
|
||||||
ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.9.0
|
ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.9.2
|
||||||
# 注意:-p 后面的容器端口,可以用环境变量类似:ASPNETCORE_URLS = http://+:10108 指定
|
# 注意:-p 后面的容器端口,可以用环境变量类似:ASPNETCORE_URLS = http://+:10108 指定
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
dysync:
|
dysync:
|
||||||
image: ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.9.0
|
image: ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.9.2
|
||||||
container_name: dysync2025 # 容器名称
|
container_name: dysync2025 # 容器名称
|
||||||
restart: unless-stopped # 始终重启容器,除非容器被手动停止或Docker服务停止
|
restart: unless-stopped # 始终重启容器,除非容器被手动停止或Docker服务停止
|
||||||
ports:
|
ports:
|
||||||
@@ -215,6 +215,10 @@ services:
|
|||||||
|
|
||||||
14. ✅ 增加配置项及关注列表(手动添加部分)导出导入功能
|
14. ✅ 增加配置项及关注列表(手动添加部分)导出导入功能
|
||||||
|
|
||||||
15. ✅ 增加移动端(主要统计数据和日志)
|
15. ✅ 增加移动端(主要统计数据和日志以及最近十条同步记录-可手机端播放)
|
||||||
|
|
||||||
|
16. ✅ 增加开关配置是否仅同步最近视频,默认开启(针对之前收藏或者点赞了很多乱七八糟的视频,太多,又不想一个个去抖音取消的情况)
|
||||||
|
|
||||||
|
17. ✅ 完成飞牛fpk打包:[github](https://github.com/jianzhichu/FnDepot)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ const showVersionNotification = () => {
|
|||||||
useApiStore()
|
useApiStore()
|
||||||
.CheckTag()
|
.CheckTag()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 1) {
|
if (res.code === 0) {
|
||||||
dyVersions.value = res.data;
|
dyVersions.value = res.data;
|
||||||
const versionLen = dyVersions.value.length;
|
const versionLen = dyVersions.value.length;
|
||||||
|
|
||||||
|
|||||||
+150
-151
@@ -1,10 +1,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getBase64 } from '@/utils/file';
|
import { getBase64 } from '@/utils/file';
|
||||||
import { FormInstance } from 'ant-design-vue';
|
import { FormInstance } from 'ant-design-vue';
|
||||||
import { reactive, ref, onMounted, UnwrapRef } from 'vue';
|
import { reactive, ref, onMounted, UnwrapRef, watch } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Dayjs } from 'dayjs';
|
import { Dayjs } from 'dayjs';
|
||||||
import { EditFilled } from '@ant-design/icons-vue';
|
import {
|
||||||
|
EditFilled,
|
||||||
|
DeleteFilled,
|
||||||
|
SearchOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
StopOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
import { useApiStore } from '@/store';
|
import { useApiStore } from '@/store';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
@@ -16,17 +25,14 @@ columns.value = [
|
|||||||
dataIndex: 'userName',
|
dataIndex: 'userName',
|
||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
{ title: '状态', dataIndex: 'status', width: 180 },
|
|
||||||
{ title: '收藏路径', dataIndex: 'savePath' },
|
{ title: '收藏路径', dataIndex: 'savePath' },
|
||||||
{ title: '喜欢路径', dataIndex: 'favSavePath' },
|
{ title: '喜欢路径', dataIndex: 'favSavePath' },
|
||||||
{ title: '博主路径', dataIndex: 'upSavePath' },
|
{ title: '博主路径', dataIndex: 'upSavePath' },
|
||||||
{ title: '图文视频', dataIndex: 'imgSavePath' },
|
{ title: '图文视频', dataIndex: 'imgSavePath' },
|
||||||
{ title: 'Cookie', dataIndex: 'cookies' },
|
{ title: 'Cookie', dataIndex: 'cookies' },
|
||||||
{ title: '有效状态', dataIndex: 'statusMsg' },
|
{ title: '有效状态', dataIndex: 'statusMsg' },
|
||||||
// { title: '博主信息', dataIndex: 'upSecUserIds' },
|
{ title: '状态', dataIndex: 'status', width: 180 },
|
||||||
// { title: '自己', dataIndex: 'secUserId' },
|
{ title: '操作', dataIndex: 'edit', width: 350 }, // 加宽操作列宽度
|
||||||
{ title: '操作', dataIndex: 'edit', width: 200 },
|
|
||||||
// { title: 'id', dataIndex: 'id', width: 200, hiden: false },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 定义数组中单个对象的类型:包含uper和uid字段(可选字符串类型,根据实际需求调整是否可选)
|
// 定义数组中单个对象的类型:包含uper和uid字段(可选字符串类型,根据实际需求调整是否可选)
|
||||||
@@ -49,18 +55,9 @@ type DataItem = {
|
|||||||
upSecUserIds?: string;
|
upSecUserIds?: string;
|
||||||
upSavePath?: string;
|
upSavePath?: string;
|
||||||
imgSavePath?: string;
|
imgSavePath?: string;
|
||||||
|
useSinglePath?: boolean; // 新增:是否全部用一个地址
|
||||||
};
|
};
|
||||||
|
|
||||||
// const dataSource = reactive<DataItem[]>([
|
|
||||||
// {
|
|
||||||
// userName: 'Li Zhi',
|
|
||||||
// cookies: '131231',
|
|
||||||
// savePath: 'x',
|
|
||||||
// status: 1,
|
|
||||||
// id: '1',
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const datas: UnwrapRef<DataItem[]> = reactive([]);
|
const datas: UnwrapRef<DataItem[]> = reactive([]);
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
@@ -96,7 +93,6 @@ const GetRecords = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// onMounted(() => {});
|
|
||||||
|
|
||||||
function addNew() {
|
function addNew() {
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
@@ -114,11 +110,12 @@ const newCookie = (cookie?: DataItem) => {
|
|||||||
cookie.savePath = undefined;
|
cookie.savePath = undefined;
|
||||||
cookie.favSavePath = undefined;
|
cookie.favSavePath = undefined;
|
||||||
cookie.secUserId = undefined;
|
cookie.secUserId = undefined;
|
||||||
cookie.status = 0;
|
cookie.status = 0; // 0=关闭,1=开启
|
||||||
cookie.id = '0';
|
cookie.id = '0';
|
||||||
cookie.upSecUserIdsJson = undefined;
|
cookie.upSecUserIdsJson = undefined;
|
||||||
cookie.upSavePath = undefined;
|
cookie.upSavePath = undefined;
|
||||||
cookie.imgSavePath = undefined;
|
cookie.imgSavePath = undefined;
|
||||||
|
cookie.useSinglePath = false; // 新增:默认不使用单一路径
|
||||||
return cookie;
|
return cookie;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +128,19 @@ const copyObject = (target: any, source?: any) => {
|
|||||||
|
|
||||||
const form = reactive<DataItem>(newCookie());
|
const form = reactive<DataItem>(newCookie());
|
||||||
|
|
||||||
|
// 新增:监听收藏路径变化,当启用单一路径时同步到其他路径
|
||||||
|
watch(
|
||||||
|
[() => form.savePath, () => form.useSinglePath],
|
||||||
|
([newSavePath, useSinglePath]) => {
|
||||||
|
if (useSinglePath && newSavePath) {
|
||||||
|
form.favSavePath = newSavePath;
|
||||||
|
form.upSavePath = newSavePath;
|
||||||
|
form.imgSavePath = newSavePath;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
return newCookie(form);
|
return newCookie(form);
|
||||||
}
|
}
|
||||||
@@ -146,7 +156,6 @@ const formLoading = ref(false);
|
|||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
formLoading.value = true;
|
formLoading.value = true;
|
||||||
let self = this;
|
|
||||||
|
|
||||||
formModel.value
|
formModel.value
|
||||||
?.validateFields()
|
?.validateFields()
|
||||||
@@ -156,9 +165,6 @@ function submit() {
|
|||||||
} else {
|
} else {
|
||||||
copyObject(editRecord.value, resData);
|
copyObject(editRecord.value, resData);
|
||||||
}
|
}
|
||||||
// alert(JSON.stringify(form.upSecUserIdsJson));
|
|
||||||
// console.log('1', authors);
|
|
||||||
// console.log('2', res);
|
|
||||||
useApiStore()
|
useApiStore()
|
||||||
.UpdateConfig(resData)
|
.UpdateConfig(resData)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -181,17 +187,15 @@ function submit() {
|
|||||||
|
|
||||||
const editRecord = ref<DataItem>();
|
const editRecord = ref<DataItem>();
|
||||||
|
|
||||||
import { Modal } from 'ant-design-vue'; // 假设使用Ant Design Vue的Modal组件
|
import { Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
const deleted = (id: string) => {
|
const deleted = (id: string) => {
|
||||||
// 显示确认对话框
|
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: '确定要删除这条记录吗?此操作不可撤销。',
|
content: '确定要删除这条记录吗?此操作不可撤销。',
|
||||||
okText: '确认',
|
okText: '确认',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
// 用户确认后执行删除操作
|
|
||||||
useApiStore()
|
useApiStore()
|
||||||
.deleteCookie(id)
|
.deleteCookie(id)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -204,21 +208,61 @@ const deleted = (id: string) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
// 用户取消删除,不执行任何操作
|
|
||||||
console.log('已取消删除');
|
console.log('已取消删除');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* 编辑
|
|
||||||
* @param record
|
|
||||||
*/
|
|
||||||
function edit(record: DataItem) {
|
function edit(record: DataItem) {
|
||||||
editRecord.value = record;
|
editRecord.value = record;
|
||||||
copyObject(form, record);
|
copyObject(form, record);
|
||||||
|
// 确保useSinglePath有默认值
|
||||||
|
if (form.useSinglePath === undefined) {
|
||||||
|
form.useSinglePath = false;
|
||||||
|
}
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:切换同步状态方法
|
||||||
|
const switchSyncStatus = (record: DataItem) => {
|
||||||
|
const targetStatus = record.status === 1 ? 0 : 1;
|
||||||
|
const statusText = targetStatus === 1 ? '开启' : '停止';
|
||||||
|
const title = `确认${statusText}同步`;
|
||||||
|
const content = `确定要${statusText}【${record.userName || '该'}】Cookie的同步任务吗?`;
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
okText: '确认',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: () => {
|
||||||
|
loading.value = true;
|
||||||
|
useApiStore()
|
||||||
|
.SwitchCookieStatus({
|
||||||
|
id: record.id,
|
||||||
|
status: targetStatus,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
if (res.code === 0) {
|
||||||
|
message.success(`${statusText}同步成功`);
|
||||||
|
GetRecords(); // 刷新列表
|
||||||
|
} else {
|
||||||
|
message.error(`${statusText}同步失败:${res.message || '未知错误'}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
loading.value = false;
|
||||||
|
console.error('切换同步状态失败:', err);
|
||||||
|
message.error('切换同步状态失败,请稍后重试');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
console.log(`已取消${statusText}同步`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
type Status = 0 | 1;
|
type Status = 0 | 1;
|
||||||
|
|
||||||
const StatusDict = {
|
const StatusDict = {
|
||||||
@@ -242,12 +286,17 @@ const showUpers = (recode: DataItem) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addRow = () => {
|
const addRow = () => {
|
||||||
|
if (!form.upSecUserIdsJson) {
|
||||||
|
form.upSecUserIdsJson = [];
|
||||||
|
}
|
||||||
form.upSecUserIdsJson.push({ uper: '', uid: '', syncAll: false });
|
form.upSecUserIdsJson.push({ uper: '', uid: '', syncAll: false });
|
||||||
};
|
};
|
||||||
const removeRow = (index) => {
|
const removeRow = (index: number) => {
|
||||||
|
if (form.upSecUserIdsJson) {
|
||||||
form.upSecUserIdsJson.splice(index, 1);
|
form.upSecUserIdsJson.splice(index, 1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const rowCount = 4;
|
const rowCount = 8;
|
||||||
|
|
||||||
// 组件挂载时获取配置
|
// 组件挂载时获取配置
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -255,6 +304,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
const downImgVideo = ref(true);
|
const downImgVideo = ref(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-modal :title="form._isNew ? '新增' : '编辑'" v-model:visible="showModal" @ok="submit" @cancel="cancel" width="1000px">
|
<a-modal :title="form._isNew ? '新增' : '编辑'" v-model:visible="showModal" @ok="submit" @cancel="cancel" width="1000px">
|
||||||
<a-form ref="formModel" :model="form" :labelCol="{ span: 3 }" :wrapperCol="{ span: 20 }">
|
<a-form ref="formModel" :model="form" :labelCol="{ span: 3 }" :wrapperCol="{ span: 20 }">
|
||||||
@@ -264,21 +314,10 @@ const downImgVideo = ref(true);
|
|||||||
<a-form-item label="id" required name="id" v-show="false">
|
<a-form-item label="id" required name="id" v-show="false">
|
||||||
<a-input v-model:value="form.id" />
|
<a-input v-model:value="form.id" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="收藏的存储路径" name="savePath">
|
<a-form-item label="Cookie值" name="cookies">
|
||||||
<a-input v-model:value="form.savePath" />
|
<a-textarea v-model:value="form.cookies" :rows="rowCount" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="Cookie值" name="cookies">
|
|
||||||
<a-textarea v-model:value="form.cookies" :rows='rowCount' />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="喜欢的存储路径" name="favSavePath">
|
|
||||||
<div style="display: flex; align-items: center; gap: 6px;">
|
|
||||||
<a-input v-model:value="form.favSavePath" style="flex: 1;" />
|
|
||||||
<a-tooltip title="同步“我喜欢的”视频时,必填!!!">
|
|
||||||
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="我的secUserId" name="secUserId">
|
<a-form-item label="我的secUserId" name="secUserId">
|
||||||
<div style="display: flex; align-items: center; gap: 6px;">
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
<a-input v-model:value="form.secUserId" style="flex: 1;" />
|
<a-input v-model:value="form.secUserId" style="flex: 1;" />
|
||||||
@@ -287,58 +326,52 @@ const downImgVideo = ref(true);
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="收藏的存储路径" name="savePath">
|
||||||
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
|
<a-input v-model:value="form.savePath" />
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 新增:是否全部用一个地址开关 -->
|
||||||
|
<a-form-item label="统一存储路径" name="useSinglePath">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<a-switch v-model:checked="form.useSinglePath" :checked-value="true" :un-checked-value="false" size="default" />
|
||||||
|
<span>{{ form.useSinglePath ? '开启(共用收藏视频路径,如果是容器部署-docker-compose只需要映射一个路径就行了)' : '关闭(各路径独立配置)' }}</span>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="喜欢的存储路径" name="favSavePath">
|
||||||
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
|
<a-input v-model:value="form.favSavePath" :disabled="form.useSinglePath" placeholder="开启统一存储路径模式后将自动同步收藏路径的值" />
|
||||||
|
<a-tooltip title="同步“我喜欢的”视频时,必填!!!">
|
||||||
|
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="关注的存储路径" name="upSavePath">
|
<a-form-item label="关注的存储路径" name="upSavePath">
|
||||||
<div style="display: flex; align-items: center; gap: 6px;">
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
<a-input v-model:value="form.upSavePath" style="flex: 1;" />
|
<a-input v-model:value="form.upSavePath" :disabled="form.useSinglePath" placeholder="开启统一存储路径模式后将自动同步收藏路径的值" style="flex: 1;" />
|
||||||
<a-tooltip title="同步指定博主视频时必填!!!">
|
<a-tooltip title="同步指定博主视频时必填!!!">
|
||||||
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- <a-form-item label="博主配置" name="upSecUserIdsJson">
|
|
||||||
<a-form-item-rest>
|
|
||||||
<a-button type="primary" @click="addRow" style="margin-bottom: 12px">
|
|
||||||
添加
|
|
||||||
<template #icon>
|
|
||||||
<PlusOutlined />
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
|
|
||||||
<div v-for="(row, index) in form.upSecUserIdsJson" :key="index" style="display: flex; gap: 12px; margin-bottom: 8px; align-items: center">
|
|
||||||
<a-input v-model:value="row.uper" placeholder="博主别名,可自定义" style="flex: 1" />
|
|
||||||
|
|
||||||
<a-input v-model:value="row.uid" placeholder="博主secUserId" style="flex: 3" />
|
|
||||||
|
|
||||||
<div style="flex: 1; display: flex; align-items: center;">
|
|
||||||
<a-tooltip title="默认关闭,仅同步 UP 主最新一页数据;开启将同步全部作品(量大不建议开启)">
|
|
||||||
<span style="margin-right: 8px; cursor: default;color:#faad14">同步全部作品</span>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-switch v-model:checked="row.syncAll" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a-button type="text" danger @click="removeRow(index)">
|
|
||||||
<template #icon>
|
|
||||||
<DeleteOutlined />
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</a-form-item-rest>
|
|
||||||
</a-form-item> -->
|
|
||||||
<a-form-item label="图文的存储路径" name="imgSavePath">
|
<a-form-item label="图文的存储路径" name="imgSavePath">
|
||||||
<div style="display: flex; align-items: center; gap: 6px;">
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
<a-input v-model:value="form.imgSavePath" style="flex: 1;" />
|
<a-input v-model:value="form.imgSavePath" :disabled="form.useSinglePath" placeholder="开启统一存储路径模式后将自动同步收藏路径的值" style="flex: 1;" />
|
||||||
<a-tooltip title="同步图文视频必填!!!">
|
<a-tooltip title="同步图文视频必填!!!">
|
||||||
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="是否启用" name="status">
|
<!-- 同步状态开关 -->
|
||||||
<a-select style="width: 110px;" v-model:value="form.status" :options="[
|
<a-form-item label="同步状态" name="status">
|
||||||
{ label: '停止同步', value: 0 },
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
{ label: '开启同步', value: 1 },
|
<a-switch v-model:checked="form.status" :checked-value="1" :un-checked-value="0" size="default" />
|
||||||
]" />
|
<span>{{ form.status === 1 ? '开启' : '停止' }}</span>
|
||||||
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -346,7 +379,6 @@ const downImgVideo = ref(true);
|
|||||||
<!-- 成员表格 -->
|
<!-- 成员表格 -->
|
||||||
<a-table v-bind="$attrs" :columns="columns" :dataSource="dataSource" :pagination="false">
|
<a-table v-bind="$attrs" :columns="columns" :dataSource="dataSource" :pagination="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
<!-- 关键修改:将 justify-between 改为 justify-end(使子元素靠右侧对齐) -->
|
|
||||||
<div class="flex justify-end pr-4">
|
<div class="flex justify-end pr-4">
|
||||||
<a-button type="primary" @click="GetRecords()" :loading="formLoading" class="mr-2">
|
<a-button type="primary" @click="GetRecords()" :loading="formLoading" class="mr-2">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@@ -364,33 +396,43 @@ const downImgVideo = ref(true);
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #bodyCell="{ column, text, record }">
|
<template #bodyCell="{ column, text, record }">
|
||||||
|
|
||||||
<template v-if="column.dataIndex === 'status'">
|
<template v-if="column.dataIndex === 'status'">
|
||||||
<a-badge class="text-subtext" :color="'green'">
|
<a-badge class="text-subtext" :color="text === 1 ? 'green' : 'red'">
|
||||||
<template #text>
|
<template #text>
|
||||||
<span class="text-subtext">{{ StatusDict[text as Status] }}</span>
|
<span class="text-subtext">{{ StatusDict[text as Status] }}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-badge>
|
</a-badge>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'cookies'">
|
<template v-else-if="column.dataIndex === 'cookies'">
|
||||||
<!-- 触发按钮 -->
|
|
||||||
<a-button @click="showCookies(record)">查看</a-button>
|
<a-button @click="showCookies(record)">查看</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'upSecUserIds'">
|
<template v-else-if="column.dataIndex === 'upSecUserIds'">
|
||||||
<!-- 触发按钮 -->
|
|
||||||
<a-button @click="showUpers(record)">查看</a-button>
|
<a-button @click="showUpers(record)">查看</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'edit'">
|
<template v-else-if="column.dataIndex === 'edit'">
|
||||||
<a-button :disabled="showModal" type="link" @click="edit(record)">
|
<!-- 同步状态切换按钮 -->
|
||||||
|
<a-button :disabled="loading" type="link" @click="switchSyncStatus(record)" :style="{ color: record.status === 1 ? '#ff4d4f' : 'green' }">
|
||||||
|
<template #icon>
|
||||||
|
<span v-if="record.status === 1" style="margin-right:5px;">
|
||||||
|
<StopOutlined />
|
||||||
|
</span>
|
||||||
|
<span v-else style="margin-right:5px;">
|
||||||
|
<ClockCircleOutlined />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
{{ record.status === 1 ? '停止同步' : '开启同步' }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button :disabled="showModal || loading" type="link" @click="edit(record)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<EditFilled />
|
<EditFilled />
|
||||||
</template>
|
</template>
|
||||||
编辑
|
编辑
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
<a-button type="link" @click="deleted(record.id)" danger>
|
<a-button :disabled="loading" type="link" @click="deleted(record.id)" danger>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DeleteFilled />
|
<DeleteOutlined />
|
||||||
</template>
|
</template>
|
||||||
删除
|
删除
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -401,22 +443,21 @@ const downImgVideo = ref(true);
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
|
||||||
<!-- Modal弹窗 -->
|
<!-- Cookie详情弹窗 -->
|
||||||
<a-modal title="Cookie 详情" :visible="showCookiesModal" style="width:1200px;" @cancel="showCookiesModal = false" @ok="showCookiesModal = false">
|
<a-modal title="Cookie 详情" :visible="showCookiesModal" style="width:1200px;" @cancel="showCookiesModal = false" @ok="showCookiesModal = false">
|
||||||
<!-- 弹窗内容 -->
|
|
||||||
<div class="cookie-content">
|
<div class="cookie-content">
|
||||||
{{ showCookiesData || '无Cookie数据' }}
|
{{ showCookiesData || '无Cookie数据' }}
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- Modal弹窗 -->
|
<!-- 博主信息弹窗 -->
|
||||||
<a-modal title="要同步的博主信息" :visible="showUpersModal" style="width:1200px;" @cancel="showUpersModal = false" @ok="showUpersModal = false">
|
<a-modal title="要同步的博主信息" :visible="showUpersModal" style="width:1200px;" @cancel="showUpersModal = false" @ok="showUpersModal = false">
|
||||||
<!-- 弹窗内容 -->
|
|
||||||
<div class="cookie-content">
|
<div class="cookie-content">
|
||||||
{{ showUpersData || '未设置' }}
|
{{ showUpersData || '未设置' }}
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.cookie-content {
|
.cookie-content {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
@@ -426,101 +467,59 @@ const downImgVideo = ref(true);
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
/* 透明滚动条样式 - WebKit内核浏览器 */
|
/* 透明滚动条样式 - WebKit内核浏览器 */
|
||||||
.cookie-content::-webkit-scrollbar {
|
.cookie-content::-webkit-scrollbar {
|
||||||
width: 6px; /* 更细的滚动条 */
|
width: 6px; /* 更细的滚动条 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 轨道完全透明 */
|
|
||||||
.cookie-content::-webkit-scrollbar-track {
|
.cookie-content::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滑块半透明(默认几乎看不见) */
|
|
||||||
.cookie-content::-webkit-scrollbar-thumb {
|
.cookie-content::-webkit-scrollbar-thumb {
|
||||||
background: rgba(150, 150, 150, 0.2); /* 浅灰透明 */
|
background: rgba(150, 150, 150, 0.2); /* 浅灰透明 */
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hover时稍微显示一点 */
|
|
||||||
.cookie-content::-webkit-scrollbar-thumb:hover {
|
.cookie-content::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(150, 150, 150, 0.4); /* 略深一点的透明 */
|
background: rgba(150, 150, 150, 0.4); /* 略深一点的透明 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 角落也透明 */
|
|
||||||
.cookie-content::-webkit-scrollbar-corner {
|
.cookie-content::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Firefox 透明滚动条适配 */
|
/* Firefox 透明滚动条适配 */
|
||||||
.cookie-content {
|
.cookie-content {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: rgba(150, 150, 150, 0.2) transparent;
|
scrollbar-color: rgba(150, 150, 150, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cookie-content {
|
/* a-textarea 透明滚动条 */
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.cookie-content::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
.cookie-content::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.cookie-content::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(150, 150, 150, 0.2);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.cookie-content::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(150, 150, 150, 0.4);
|
|
||||||
}
|
|
||||||
.cookie-content::-webkit-scrollbar-corner {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.cookie-content {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: rgba(150, 150, 150, 0.2) transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------- 新增:a-textarea 透明滚动条 ---------------------- */
|
|
||||||
/* 1. 穿透 scoped,定位 a-textarea 内部的原生 textarea 元素 */
|
|
||||||
:deep(.ant-input-textarea-input) {
|
:deep(.ant-input-textarea-input) {
|
||||||
/* 确保内容超出时显示滚动条(a-textarea 默认已配置,可省略) */
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
/* Firefox 透明滚动条:thin 细滚动条 + 滑块颜色/轨道颜色 */
|
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: rgba(150, 150, 150, 0.2) transparent;
|
scrollbar-color: rgba(150, 150, 150, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. WebKit 浏览器(Chrome/Safari/Edge)透明滚动条 */
|
|
||||||
/* 滚动条宽度 */
|
|
||||||
:deep(.ant-input-textarea-input)::-webkit-scrollbar {
|
:deep(.ant-input-textarea-input)::-webkit-scrollbar {
|
||||||
width: 6px; /* 与 cookie-content 保持一致的细滚动条 */
|
width: 6px;
|
||||||
height: 6px; /* 横向滚动条(如需) */
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条轨道(完全透明) */
|
|
||||||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-track {
|
:deep(.ant-input-textarea-input)::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条滑块(半透明,hover 时加深) */
|
|
||||||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-thumb {
|
:deep(.ant-input-textarea-input)::-webkit-scrollbar-thumb {
|
||||||
background: rgba(150, 150, 150, 0.2); /* 浅灰透明,默认几乎看不见 */
|
background: rgba(150, 150, 150, 0.2);
|
||||||
border-radius: 3px; /* 圆角优化 */
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-thumb:hover {
|
:deep(.ant-input-textarea-input)::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(150, 150, 150, 0.4); /* hover 时略深,提升交互感知 */
|
background: rgba(150, 150, 150, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条角落(完全透明,避免留白) */
|
|
||||||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-corner {
|
:deep(.ant-input-textarea-input)::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 禁用状态的输入框样式优化 */
|
||||||
|
:deep(.ant-input-disabled) {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
color: #666 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -8,9 +8,20 @@
|
|||||||
import LoginBox from './LoginBox.vue';
|
import LoginBox from './LoginBox.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { useApiStore } from '@/store';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
onMounted(() => {
|
||||||
|
useApiStore()
|
||||||
|
.AppisInit()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code == 0 && res.data) {
|
||||||
|
} else {
|
||||||
|
router.push('/init');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
function onLoginSuccess() {
|
function onLoginSuccess() {
|
||||||
if (isMobileBrowser()) router.push('/mobile');
|
if (isMobileBrowser()) router.push('/mobile');
|
||||||
else router.push('/dashboard');
|
else router.push('/dashboard');
|
||||||
|
|||||||
@@ -338,10 +338,10 @@ const loadLogData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载最新同步的视频5个
|
// 加载最新同步的视频10个
|
||||||
const TopVideo = () => {
|
const TopVideo = () => {
|
||||||
useApiStore()
|
useApiStore()
|
||||||
.TopVideo(5)
|
.TopVideo(10)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
topVideos.value = res.data;
|
topVideos.value = res.data;
|
||||||
|
|||||||
@@ -21,6 +21,14 @@
|
|||||||
<span>每次最大下载数量:10-30</span>
|
<span>每次最大下载数量:10-30</span>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item has-feedback label="仅同步最新视频" name="OnlySyncNew" :wrapper-col="{ span: 20 }">
|
||||||
|
<a-switch v-model:checked="formState.OnlySyncNew" />
|
||||||
|
<div class="flex items-start mt-1 text-sm text-gray-500">
|
||||||
|
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
|
||||||
|
<span>开启后,仅同步最近收藏的20条,以及未来新收藏的,不会去同步之前的视频,默认开启, 避免突然大量下载,导致风控</span>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 文件保存配置 -->
|
<!-- 文件保存配置 -->
|
||||||
@@ -143,23 +151,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- <a-form-item has-feedback label="日志保留(天数)" name="LogKeepDay" :wrapper-col="{ span: 6 }" style="margin-left:30px">
|
|
||||||
<a-input-number v-model:value="formState.LogKeepDay" placeholder="请输入保留天数" :min="1" :max="90" />
|
|
||||||
<div class="flex items-start mt-1 text-sm text-gray-500">
|
|
||||||
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
|
|
||||||
<span>系统运行日志的保留天数,范围1-90天,过期自动清理</span>
|
|
||||||
</div>
|
|
||||||
</a-form-item> -->
|
|
||||||
<!-- <a-form-item has-feedback label="是否自动去重" name="AutoDistinct" :wrapper-col="{ span: 10 }">
|
|
||||||
<a-switch v-model:checked="formState.AutoDistinct" disabled />
|
|
||||||
<div class="flex items-start mt-1 text-sm text-gray-500">
|
|
||||||
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
|
|
||||||
<span>
|
|
||||||
启用后,同一个视频,只会下载一次。(但是暂时不能决定保留哪个文件夹的)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</a-form-item> -->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -180,18 +171,27 @@
|
|||||||
<!-- 配置导入导出悬浮按钮(优化布局+动画) -->
|
<!-- 配置导入导出悬浮按钮(优化布局+动画) -->
|
||||||
<div class="config-float-btn-container">
|
<div class="config-float-btn-container">
|
||||||
<!-- 主按钮 -->
|
<!-- 主按钮 -->
|
||||||
|
<a-tooltip title="配置导出导入" placement="left">
|
||||||
|
|
||||||
<a-button class="main-float-btn" type="primary" shape="circle">
|
<a-button class="main-float-btn" type="primary" shape="circle">
|
||||||
<tool-outlined />
|
<tool-outlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
<!-- 子按钮容器(新增:绝对定位向上展开) -->
|
<!-- 子按钮容器(新增:绝对定位向上展开) -->
|
||||||
<div class="float-sub-btn-wrapper">
|
<div class="float-sub-btn-wrapper">
|
||||||
<a-button class="sub-float-btn export-btn" type="default" shape="circle" @click="exportConfig" tooltip="导出配置">
|
<!-- 关键修改:添加 Tooltip 组件包裹导出按钮 -->
|
||||||
|
<a-tooltip title="导出配置" placement="left">
|
||||||
|
<a-button class="sub-float-btn export-btn" type="default" shape="circle" @click="exportConfig">
|
||||||
<cloud-download-outlined />
|
<cloud-download-outlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="sub-float-btn import-btn" type="default" shape="circle" @click="triggerImportFile" tooltip="导入配置">
|
</a-tooltip>
|
||||||
|
<!-- 关键修改:添加 Tooltip 组件包裹导入按钮 -->
|
||||||
|
<a-tooltip title="导入配置" placement="left">
|
||||||
|
<a-button class="sub-float-btn import-btn" type="default" shape="circle" @click="triggerImportFile">
|
||||||
<cloud-upload-outlined />
|
<cloud-upload-outlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input ref="importFileInput" type="file" accept=".json" class="import-file-input" @change="handleImportFile">
|
<input ref="importFileInput" type="file" accept=".json" class="import-file-input" @change="handleImportFile">
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, toRaw, ref, watch, onMounted, computed, nextTick } from 'vue';
|
import { reactive, toRaw, ref, watch, onMounted, computed, nextTick } from 'vue';
|
||||||
import type { UnwrapRef } from 'vue';
|
import type { UnwrapRef } from 'vue';
|
||||||
import { Form } from 'ant-design-vue';
|
import { Form, Tooltip } from 'ant-design-vue'; // 关键修改:导入 Tooltip 组件
|
||||||
import type { Rule } from 'ant-design-vue/es/form';
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
import type { FormInstance } from 'ant-design-vue';
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
import { useApiStore } from '@/store';
|
import { useApiStore } from '@/store';
|
||||||
@@ -212,11 +212,9 @@ import {
|
|||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
CheckOutlined,
|
CheckOutlined,
|
||||||
// 新增图标
|
ToolOutlined, // 修正:原代码中用了 tool-outlined 但未导入
|
||||||
SettingOutlined,
|
CloudDownloadOutlined,
|
||||||
UpOutlined,
|
CloudUploadOutlined,
|
||||||
DownloadOutlined,
|
|
||||||
UploadOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
// 表单引用
|
// 表单引用
|
||||||
@@ -258,6 +256,7 @@ interface FormState {
|
|||||||
AutoDistinct: boolean;
|
AutoDistinct: boolean;
|
||||||
PriorityLevel: string;
|
PriorityLevel: string;
|
||||||
DownDynamicVideo: boolean;
|
DownDynamicVideo: boolean;
|
||||||
|
OnlySyncNew: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单初始数据
|
// 表单初始数据
|
||||||
@@ -278,6 +277,7 @@ const formState: UnwrapRef<FormState> = reactive({
|
|||||||
AutoDistinct: false,
|
AutoDistinct: false,
|
||||||
PriorityLevel: '',
|
PriorityLevel: '',
|
||||||
DownDynamicVideo: false,
|
DownDynamicVideo: false,
|
||||||
|
OnlySyncNew: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 实时计算完整模板(可选:让用户实时预览,提交时无需重复计算)
|
// 实时计算完整模板(可选:让用户实时预览,提交时无需重复计算)
|
||||||
@@ -354,6 +354,7 @@ const getConfig = () => {
|
|||||||
AutoDistinct: res.data.autoDistinct,
|
AutoDistinct: res.data.autoDistinct,
|
||||||
PriorityLevel: res.data.priorityLevel,
|
PriorityLevel: res.data.priorityLevel,
|
||||||
DownDynamicVideo: res.data.downDynamicVideo,
|
DownDynamicVideo: res.data.downDynamicVideo,
|
||||||
|
OnlySyncNew: res.data.onlySyncNew,
|
||||||
});
|
});
|
||||||
|
|
||||||
tagData.value = JSON.parse(res.data.priorityLevel);
|
tagData.value = JSON.parse(res.data.priorityLevel);
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ interface NaviGuard {
|
|||||||
const loginGuard: NavigationGuard = function (to, from) {
|
const loginGuard: NavigationGuard = function (to, from) {
|
||||||
// console.log('Authorization', http.checkAuthorization())
|
// console.log('Authorization', http.checkAuthorization())
|
||||||
const account = useAccountStore();
|
const account = useAccountStore();
|
||||||
if (!http.checkAuthorization() && !/^\/(login|home|init|mobile)?$/.test(to.fullPath)) {
|
if (!http.checkAuthorization() && !/^\/(init|login|home|mobile)?$/.test(to.fullPath)) {
|
||||||
|
console.log(123)
|
||||||
|
console.log(to.fullPath)
|
||||||
account.setLogged(false)
|
account.setLogged(false)
|
||||||
return '/login';
|
return '/login';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,13 +19,26 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'mobile',
|
name: 'mobile',
|
||||||
redirect: '/mobile',
|
redirect: '/mobile',
|
||||||
meta: {
|
meta: {
|
||||||
title: '登录',
|
title: '移动端首页',
|
||||||
renderMenu: false,
|
renderMenu: false,
|
||||||
icon: 'CreditCardOutlined',
|
icon: 'CreditCardOutlined',
|
||||||
},
|
},
|
||||||
children: null,
|
children: null,
|
||||||
component: () => import('@/pages/mobile/MobileDashboard.vue'),
|
component: () => import('@/pages/mobile/MobileDashboard.vue'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'init',
|
||||||
|
redirect: '/init',
|
||||||
|
meta: {
|
||||||
|
title: '初始化',
|
||||||
|
renderMenu: false,
|
||||||
|
icon: 'CreditCardOutlined',
|
||||||
|
},
|
||||||
|
children: null,
|
||||||
|
component: () => import('@/pages/desk/index.vue'),
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/',
|
// path: '/',
|
||||||
// name: 'init',
|
// name: 'init',
|
||||||
@@ -70,6 +83,18 @@ const routes: RouteRecordRaw[] = [
|
|||||||
children: null,
|
children: null,
|
||||||
component: () => import('@/pages/mobile/MobileDashboard.vue'),
|
component: () => import('@/pages/mobile/MobileDashboard.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/init',
|
||||||
|
name: 'init',
|
||||||
|
meta: {
|
||||||
|
icon: 'LoginOutlined',
|
||||||
|
view: 'blank',
|
||||||
|
target: '_blank',
|
||||||
|
cacheable: false,
|
||||||
|
},
|
||||||
|
children: null,
|
||||||
|
component: () => import('@/pages/desk/index.vue'),
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/init',
|
// path: '/init',
|
||||||
// name: '初始化',
|
// name: '初始化',
|
||||||
|
|||||||
@@ -128,6 +128,31 @@ export const useApiStore = defineStore('coreapi', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async function DeskInitAsync(param: object) {
|
||||||
|
return http.request<any, Response<any>>('/api/config/deskinit', 'post_json', param).then(r => {
|
||||||
|
return r;
|
||||||
|
}).finally(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function AppisInit() {
|
||||||
|
return http.request<any, Response<any>>('/api/config/isInit', 'get').then(r => {
|
||||||
|
return r;
|
||||||
|
}).finally(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function GetAppPort() {
|
||||||
|
return http.request<any, Response<any>>('/api/config/appport', 'get').then(r => {
|
||||||
|
return r;
|
||||||
|
}).finally(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function deleteCookie(id: string) {
|
async function deleteCookie(id: string) {
|
||||||
return http.request<any, Response<any>>('/api/config/delete?id=' + id, 'get').then(r => {
|
return http.request<any, Response<any>>('/api/config/delete?id=' + id, 'get').then(r => {
|
||||||
@@ -202,6 +227,14 @@ export const useApiStore = defineStore('coreapi', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//快速停止或启动cookie配置
|
||||||
|
async function SwitchCookieStatus(param: object) {
|
||||||
|
return http.request<any, Response<any>>('/api/config/switch', 'post_json', param).then(r => {
|
||||||
|
return r;
|
||||||
|
}).finally(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//添加非关注的博主
|
//添加非关注的博主
|
||||||
async function AddFollow(param: object) {
|
async function AddFollow(param: object) {
|
||||||
@@ -264,6 +297,10 @@ export const useApiStore = defineStore('coreapi', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
GetAppPort,
|
||||||
|
AppisInit,
|
||||||
|
DeskInitAsync,
|
||||||
|
SwitchCookieStatus,
|
||||||
TopVideo,
|
TopVideo,
|
||||||
LogDetail,
|
LogDetail,
|
||||||
MobileLogs,
|
MobileLogs,
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"deploy": "docker", //fn,docker
|
||||||
"dbconn": "",
|
"dbconn": "",
|
||||||
//"tagName": "arm_1.9.0",
|
"tagName": "beta_1.9.2", //arm_1.9.2
|
||||||
//"tagName": "dev_1.9.0",
|
"dbtype": "Sqlite",
|
||||||
"tagName": "beta_1.9.0",
|
"fnVersion": "1.0.0"
|
||||||
"dbtype": "Sqlite"
|
|
||||||
}
|
}
|
||||||
+17
-12
@@ -40,41 +40,42 @@
|
|||||||
<None Remove="logs\**" />
|
<None Remove="logs\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Remove="wwwroot\index.html" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ClockSnowFlake" Version="1.1.0" />
|
<PackageReference Include="ClockSnowFlake" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.16" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.16" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
|
||||||
<PackageReference Include="Quartz" Version="3.8.0" />
|
<PackageReference Include="Quartz" Version="3.8.0" />
|
||||||
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
|
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
|
||||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.8.0" />
|
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.8.0" />
|
||||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.8.0" />
|
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.8.0" />
|
||||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.128" />
|
<PackageReference Include="SqlSugarCore" Version="5.1.4.128" />
|
||||||
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.124" />
|
<!--<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.124" />-->
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" />
|
<!--<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.3.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.3.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.6.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.6.3" />-->
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
<!--<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
|
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
|
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />-->
|
||||||
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
|
<!--<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />-->
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="wwwroot\" />
|
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="db\dy1.sqlite" />
|
<None Include="db\dy1.sqlite" />
|
||||||
<None Include="db\dy.sqlite" />
|
<None Include="db\dy2.sqlite" />
|
||||||
<None Include="dy.net.sln" />
|
<None Include="dy.net.sln" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -98,6 +99,10 @@
|
|||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\" />
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
|
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||||
<!--<Exec WorkingDirectory="$(SpaRoot)" Command="yarn install" />
|
<!--<Exec WorkingDirectory="$(SpaRoot)" Command="yarn install" />
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ActiveDebugProfile>tiny.ddns</ActiveDebugProfile>
|
<ActiveDebugProfile>tiny.ddns</ActiveDebugProfile>
|
||||||
<NameOfLastUsedPublishProfile>E:\code\dysync\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
|
<NameOfLastUsedPublishProfile>E:\code\dysync\Properties\PublishProfiles\fn.pubxml</NameOfLastUsedPublishProfile>
|
||||||
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID>
|
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID>
|
||||||
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
|
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
+158
-158
@@ -7,15 +7,15 @@ using Microsoft.AspNetCore.Mvc.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.ResponseCompression;
|
using Microsoft.AspNetCore.ResponseCompression;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi.Models;
|
//using Microsoft.OpenApi.Models;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Serilog.Filters;
|
using Serilog.Filters;
|
||||||
using Serilog.Formatting.Compact;
|
//using Serilog.Formatting.Compact;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
//using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using Swashbuckle.AspNetCore.SwaggerUI;
|
//using Swashbuckle.AspNetCore.SwaggerUI;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
@@ -356,117 +356,117 @@ namespace dy.net.extension
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// SwaggerUi
|
///// SwaggerUi
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <param name="app"></param>
|
///// <param name="app"></param>
|
||||||
/// <param name="options"></param>
|
///// <param name="options"></param>
|
||||||
public static void UseCustomSwaggerUI(this IApplicationBuilder app, Action<SwaggerOptions> options)
|
//public static void UseCustomSwaggerUI(this IApplicationBuilder app, Action<SwaggerOptions> options)
|
||||||
{
|
//{
|
||||||
SwaggerOptions option = new SwaggerOptions();
|
// SwaggerOptions option = new SwaggerOptions();
|
||||||
options?.Invoke(option);
|
// options?.Invoke(option);
|
||||||
//启用中间件服务生成Swagger作为JSON终结点
|
// //启用中间件服务生成Swagger作为JSON终结点
|
||||||
app.UseSwagger(c =>
|
// app.UseSwagger(c =>
|
||||||
{
|
// {
|
||||||
//c.SerializeAsV2 = true;
|
// //c.SerializeAsV2 = true;
|
||||||
//c.RouteTemplate = "api-docs/{documentName}/swagger.json";
|
// //c.RouteTemplate = "api-docs/{documentName}/swagger.json";
|
||||||
c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
|
// c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
|
||||||
{
|
// {
|
||||||
swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}" } };
|
// swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}" } };
|
||||||
OpenApiPaths paths = new OpenApiPaths();
|
// OpenApiPaths paths = new OpenApiPaths();
|
||||||
foreach (var path in swaggerDoc.Paths)
|
// foreach (var path in swaggerDoc.Paths)
|
||||||
{
|
// {
|
||||||
//if ( path.Key.StartsWith("/v1/api") )//做版本控制
|
// //if ( path.Key.StartsWith("/v1/api") )//做版本控制
|
||||||
paths.Add(path.Key, path.Value);
|
// paths.Add(path.Key, path.Value);
|
||||||
}
|
// }
|
||||||
swaggerDoc.Paths = paths;
|
// swaggerDoc.Paths = paths;
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
//启用中间件服务对swagger-ui,指定Swagger JSON终结点
|
// //启用中间件服务对swagger-ui,指定Swagger JSON终结点
|
||||||
app.UseSwaggerUI(c =>
|
// app.UseSwaggerUI(c =>
|
||||||
{
|
// {
|
||||||
//c.MaxDisplayedTags(5);
|
// //c.MaxDisplayedTags(5);
|
||||||
//c.DisplayOperationId();//唯一标识操作
|
// //c.DisplayOperationId();//唯一标识操作
|
||||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", option.Title);
|
// c.SwaggerEndpoint("/swagger/v1/swagger.json", option.Title);
|
||||||
//c.SwaggerEndpoint("/swagger/v2/swagger.json", "V2 Docs");
|
// //c.SwaggerEndpoint("/swagger/v2/swagger.json", "V2 Docs");
|
||||||
c.RoutePrefix = "swagger";//根路由
|
// c.RoutePrefix = "swagger";//根路由
|
||||||
c.EnableDeepLinking();//启用深度链接--不知道干嘛的
|
// c.EnableDeepLinking();//启用深度链接--不知道干嘛的
|
||||||
c.DisplayRequestDuration();//调试,显示接口响应时间
|
// c.DisplayRequestDuration();//调试,显示接口响应时间
|
||||||
c.EnableValidator();//验证
|
// c.EnableValidator();//验证
|
||||||
c.DocExpansion(DocExpansion.List);//默认展开
|
// c.DocExpansion(DocExpansion.List);//默认展开
|
||||||
c.DefaultModelsExpandDepth(-1);//隐藏model
|
// c.DefaultModelsExpandDepth(-1);//隐藏model
|
||||||
c.DefaultModelExpandDepth(3);//model展开层级
|
// c.DefaultModelExpandDepth(3);//model展开层级
|
||||||
c.EnableFilter();//筛选--如果接口过多可以开启
|
// c.EnableFilter();//筛选--如果接口过多可以开启
|
||||||
c.DefaultModelRendering(ModelRendering.Model);//设置显示参数的实体或Example
|
// c.DefaultModelRendering(ModelRendering.Model);//设置显示参数的实体或Example
|
||||||
//c.SupportedSubmitMethods(SubmitMethod.Get , SubmitMethod.Head , SubmitMethod.Post);//
|
// //c.SupportedSubmitMethods(SubmitMethod.Get , SubmitMethod.Head , SubmitMethod.Post);//
|
||||||
|
|
||||||
//c.OAuthClientId("test-id");
|
// //c.OAuthClientId("test-id");
|
||||||
//c.OAuthClientSecret("test-secret");
|
// //c.OAuthClientSecret("test-secret");
|
||||||
//c.OAuthRealm("test-realm");
|
// //c.OAuthRealm("test-realm");
|
||||||
//c.OAuthAppName("test-app");
|
// //c.OAuthAppName("test-app");
|
||||||
//c.OAuthScopeSeparator(" ");
|
// //c.OAuthScopeSeparator(" ");
|
||||||
//c.OAuthAdditionalQueryStringParams(new Dictionary<string, string> { { "foo", "bar" } });
|
// //c.OAuthAdditionalQueryStringParams(new Dictionary<string, string> { { "foo", "bar" } });
|
||||||
//c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
|
// //c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
|
||||||
});
|
// });
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Swagger
|
///// Swagger
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <param name="services"></param>
|
///// <param name="services"></param>
|
||||||
public static IServiceCollection AddSwagger(this IServiceCollection services, Action<SwaggerGenOptions> options = null)
|
//public static IServiceCollection AddSwagger(this IServiceCollection services, Action<SwaggerGenOptions> options = null)
|
||||||
{
|
//{
|
||||||
if (options != null)
|
// if (options != null)
|
||||||
services.AddSwaggerGen(options);
|
// services.AddSwaggerGen(options);
|
||||||
else
|
// else
|
||||||
services.AddSwaggerGen(DefaultSwaggerGenOptions());
|
// services.AddSwaggerGen(DefaultSwaggerGenOptions());
|
||||||
return services;
|
// return services;
|
||||||
}
|
//}
|
||||||
|
|
||||||
private static Action<SwaggerGenOptions> DefaultSwaggerGenOptions()
|
//private static Action<SwaggerGenOptions> DefaultSwaggerGenOptions()
|
||||||
{
|
//{
|
||||||
Action<SwaggerGenOptions> options = o =>
|
// Action<SwaggerGenOptions> options = o =>
|
||||||
{
|
// {
|
||||||
o.OperationFilter<SwaggerAuthorizationFilter>();
|
// o.OperationFilter<SwaggerAuthorizationFilter>();
|
||||||
|
|
||||||
o.SwaggerDoc("v1", new OpenApiInfo
|
// o.SwaggerDoc("v1", new OpenApiInfo
|
||||||
{
|
// {
|
||||||
Version = "v1",
|
// Version = "v1",
|
||||||
Title = "dy.net API Swagger Document",
|
// Title = "dy.net API Swagger Document",
|
||||||
|
|
||||||
});
|
// });
|
||||||
o.OrderActionsBy((apiDesc) => $"{apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.HttpMethod}");
|
// o.OrderActionsBy((apiDesc) => $"{apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.HttpMethod}");
|
||||||
o.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
// o.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
||||||
{
|
// {
|
||||||
Description = "请在下方输入:Bearer {Token}",
|
// Description = "请在下方输入:Bearer {Token}",
|
||||||
Name = "Authorization",
|
// Name = "Authorization",
|
||||||
In = ParameterLocation.Header,
|
// In = ParameterLocation.Header,
|
||||||
Type = SecuritySchemeType.ApiKey,
|
// Type = SecuritySchemeType.ApiKey,
|
||||||
BearerFormat = "JWT",
|
// BearerFormat = "JWT",
|
||||||
Scheme = "Bearer",
|
// Scheme = "Bearer",
|
||||||
});
|
// });
|
||||||
o.AddSecurityRequirement(new OpenApiSecurityRequirement
|
// o.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
{
|
// {
|
||||||
{
|
// {
|
||||||
new OpenApiSecurityScheme
|
// new OpenApiSecurityScheme
|
||||||
{
|
// {
|
||||||
Reference = new OpenApiReference {
|
// Reference = new OpenApiReference {
|
||||||
Type = ReferenceType.SecurityScheme,
|
// Type = ReferenceType.SecurityScheme,
|
||||||
Id = "Bearer",
|
// Id = "Bearer",
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
new[] { "readAccess", "writeAccess" }
|
// new[] { "readAccess", "writeAccess" }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
o.DocumentFilter<SwaggerHiddenApiFilter>();
|
// o.DocumentFilter<SwaggerHiddenApiFilter>();
|
||||||
var XmlPath = $"{AppContext.BaseDirectory}{AppDomain.CurrentDomain.FriendlyName}.xml";
|
// var XmlPath = $"{AppContext.BaseDirectory}{AppDomain.CurrentDomain.FriendlyName}.xml";
|
||||||
o.IncludeXmlComments(XmlPath);
|
// o.IncludeXmlComments(XmlPath);
|
||||||
o.EnableAnnotations();
|
// o.EnableAnnotations();
|
||||||
};
|
// };
|
||||||
return options;
|
// return options;
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -483,7 +483,7 @@ namespace dy.net.extension
|
|||||||
.Filter.ByExcluding(e => e.Level == LogEventLevel.Information) // 排除Info级别的日志
|
.Filter.ByExcluding(e => e.Level == LogEventLevel.Information) // 排除Info级别的日志
|
||||||
.Filter.ByExcluding(Matching.FromSource("Microsoft"))
|
.Filter.ByExcluding(Matching.FromSource("Microsoft"))
|
||||||
.Filter.ByExcluding(Matching.FromSource("Quartz"))
|
.Filter.ByExcluding(Matching.FromSource("Quartz"))
|
||||||
.WriteTo.Console(new RenderedCompactJsonFormatter(), LogEventLevel.Debug)
|
//.WriteTo.Console(new RenderedCompactJsonFormatter(), LogEventLevel.Debug)
|
||||||
//.WriteTo.MySQL(connectionString: builder.Configuration.GetConnectionString("DbConnectionString"), tableName: "Logs") // 输出到数据库
|
//.WriteTo.MySQL(connectionString: builder.Configuration.GetConnectionString("DbConnectionString"), tableName: "Logs") // 输出到数据库
|
||||||
.WriteTo.Logger(configure => configure
|
.WriteTo.Logger(configure => configure
|
||||||
.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Debug)
|
.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Debug)
|
||||||
@@ -552,34 +552,34 @@ namespace dy.net.extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class SwaggerAuthorizationFilter : IOperationFilter
|
//public class SwaggerAuthorizationFilter : IOperationFilter
|
||||||
{
|
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
|
||||||
{
|
|
||||||
operation.Parameters ??= new List<OpenApiParameter>();
|
|
||||||
_ = context.ApiDescription.ActionDescriptor.AttributeRouteInfo;
|
|
||||||
|
|
||||||
//先判断是否是匿名访问,
|
|
||||||
if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor)
|
|
||||||
{
|
|
||||||
var Authorizes = descriptor.MethodInfo.GetCustomAttributes(typeof(AuthorizeFilter), true);
|
|
||||||
//非匿名的方法,链接中添加accesstoken值
|
|
||||||
if (Authorizes.Any())
|
|
||||||
{
|
|
||||||
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
|
|
||||||
//operation.Parameters.Add(new OpenApiParameter()
|
|
||||||
//{
|
//{
|
||||||
// Required = true,
|
// public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
// Name = "Bearer",
|
// {
|
||||||
// In = ParameterLocation.Header,
|
// operation.Parameters ??= new List<OpenApiParameter>();
|
||||||
// Description = "You Must Request With token",
|
// _ = context.ApiDescription.ActionDescriptor.AttributeRouteInfo;
|
||||||
// Style = ParameterStyle.DeepObject,
|
|
||||||
|
|
||||||
//});
|
// //先判断是否是匿名访问,
|
||||||
}
|
// if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor)
|
||||||
}
|
// {
|
||||||
}
|
// var Authorizes = descriptor.MethodInfo.GetCustomAttributes(typeof(AuthorizeFilter), true);
|
||||||
}
|
// //非匿名的方法,链接中添加accesstoken值
|
||||||
|
// if (Authorizes.Any())
|
||||||
|
// {
|
||||||
|
// operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||||
|
// //operation.Parameters.Add(new OpenApiParameter()
|
||||||
|
// //{
|
||||||
|
// // Required = true,
|
||||||
|
// // Name = "Bearer",
|
||||||
|
// // In = ParameterLocation.Header,
|
||||||
|
// // Description = "You Must Request With token",
|
||||||
|
// // Style = ParameterStyle.DeepObject,
|
||||||
|
|
||||||
|
// //});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -591,27 +591,27 @@ namespace dy.net.extension
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SwaggerHiddenApiFilter : IDocumentFilter
|
//public class SwaggerHiddenApiFilter : IDocumentFilter
|
||||||
{
|
//{
|
||||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
// public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||||
{
|
// {
|
||||||
foreach (ApiDescription apiDescription in context.ApiDescriptions)
|
// foreach (ApiDescription apiDescription in context.ApiDescriptions)
|
||||||
{
|
// {
|
||||||
if (apiDescription.TryGetMethodInfo(out MethodInfo method))
|
// if (apiDescription.TryGetMethodInfo(out MethodInfo method))
|
||||||
{
|
// {
|
||||||
if (method.ReflectedType.CustomAttributes.Any(t => t.AttributeType == typeof(HiddenApiAttribute))
|
// if (method.ReflectedType.CustomAttributes.Any(t => t.AttributeType == typeof(HiddenApiAttribute))
|
||||||
|| method.CustomAttributes.Any(t => t.AttributeType == typeof(HiddenApiAttribute)))
|
// || method.CustomAttributes.Any(t => t.AttributeType == typeof(HiddenApiAttribute)))
|
||||||
{
|
// {
|
||||||
string key = "/" + apiDescription.RelativePath;
|
// string key = "/" + apiDescription.RelativePath;
|
||||||
if (key.Contains("?"))
|
// if (key.Contains("?"))
|
||||||
{
|
// {
|
||||||
int idx = key.IndexOf("?", StringComparison.Ordinal);
|
// int idx = key.IndexOf("?", StringComparison.Ordinal);
|
||||||
key = key.Substring(0, idx);
|
// key = key.Substring(0, idx);
|
||||||
}
|
// }
|
||||||
swaggerDoc.Paths.Remove(key);
|
// swaggerDoc.Paths.Remove(key);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ namespace dy.net.job
|
|||||||
var cookies = await GetValidCookies();
|
var cookies = await GetValidCookies();
|
||||||
if (cookies == null || !cookies.Any())
|
if (cookies == null || !cookies.Any())
|
||||||
{
|
{
|
||||||
Log.Debug($"{VideoType}-无有效的Cookie,任务终止!!!");
|
Log.Debug($"{VideoType}-未配置cookie或为开启同步,任务终止!!!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.Debug($"{VideoType}-共发现{cookies.Count}个Cookie,同步任务即将开始...");
|
Log.Debug($"{VideoType}-共发现{cookies.Count}个Cookie,同步任务即将开始...");
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace dy.net.job
|
|||||||
{
|
{
|
||||||
hasmore = false;
|
hasmore = false;
|
||||||
}
|
}
|
||||||
ck.StatusMsg = err.StatusCode == 8 ? "已过期" : "正 常";
|
ck.StatusMsg = err.StatusCode == 8 ? "无效" : "正 常";
|
||||||
ck.StatusCode = err.StatusCode;
|
ck.StatusCode = err.StatusCode;
|
||||||
await _dyCookieService.UpdateAsync(ck);
|
await _dyCookieService.UpdateAsync(ck);
|
||||||
});
|
});
|
||||||
@@ -103,7 +103,12 @@ namespace dy.net.job
|
|||||||
|
|
||||||
if (follows.Count > 0)
|
if (follows.Count > 0)
|
||||||
{
|
{
|
||||||
await _followService.Sync(follows, ck );
|
var (add, update, succ) = await _followService.Sync(follows, ck);
|
||||||
|
if (!succ) hasmore = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasmore = add > 100|| update > 100;//一次最多100条数据
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ namespace dy.net.model
|
|||||||
/// 是否下载动态图视频
|
/// 是否下载动态图视频
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DownDynamicVideo { get; set; }
|
public bool DownDynamicVideo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅同步新视频(github 有人提议增加这个配置项,因为之前收藏了很多烂七八糟的视频。不想同步,又不想一个一个清除)
|
||||||
|
/// </summary>
|
||||||
|
public bool OnlySyncNew { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,16 @@ namespace dy.net.model
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(Length = 100, IsNullable = true)]
|
[SugarColumn(Length = 100, IsNullable = true)]
|
||||||
public string StatusMsg { get; set; }
|
public string StatusMsg { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 是否统一一个路径-用 SavePath
|
||||||
|
/// </summary>
|
||||||
|
public bool useSinglePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 服务端口--不存数据库
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsIgnore =true)]
|
||||||
|
public int AppPort { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using dy.net.model;
|
using dy.net.dto;
|
||||||
|
using dy.net.model;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
@@ -42,5 +43,12 @@ namespace dy.net.repository
|
|||||||
.First();
|
.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<bool> SwitchAsync(DouyinCookieStopDto dto)
|
||||||
|
{
|
||||||
|
var res =await Db.Updateable<DouyinCookie>().SetColumns(x => new DouyinCookie { Status = dto.Status }).Where(x => x.Id == dto.Id).ExecuteCommandAsync();
|
||||||
|
|
||||||
|
return res > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace dy.net.repository
|
|||||||
{
|
{
|
||||||
var data = this.Db.Queryable<DouyinFollowed>()
|
var data = this.Db.Queryable<DouyinFollowed>()
|
||||||
.LeftJoin<DouyinCookie>((f, u) => f.mySelfId == u.MyUserId)
|
.LeftJoin<DouyinCookie>((f, u) => f.mySelfId == u.MyUserId)
|
||||||
.Where((f, u) => u.Status == 1) // 注意:LeftJoin+u.Status==1 等价于 InnerJoin(u必须存在)
|
//.Where((f, u) => u.Status == 1) // 注意:LeftJoin+u.Status==1 等价于 InnerJoin(u必须存在)
|
||||||
.GroupBy((f, u) => f.mySelfId) // 按 mySelfId 分组
|
.GroupBy((f, u) => f.mySelfId) // 按 mySelfId 分组
|
||||||
.Select((f, u) => new DouyinFollowGroupDto
|
.Select((f, u) => new DouyinFollowGroupDto
|
||||||
{
|
{
|
||||||
@@ -39,10 +39,10 @@ namespace dy.net.repository
|
|||||||
public async Task<(List<DouyinFollowed> list, int totalCount)> GetPagedAsync(FollowRequestDto dto)
|
public async Task<(List<DouyinFollowed> list, int totalCount)> GetPagedAsync(FollowRequestDto dto)
|
||||||
{
|
{
|
||||||
var where = this.Db.Queryable<DouyinFollowed>()
|
var where = this.Db.Queryable<DouyinFollowed>()
|
||||||
.Where(x=>x.mySelfId==dto.MySelfId)
|
.Where(x => x.mySelfId == dto.MySelfId)
|
||||||
.WhereIF(!string.IsNullOrWhiteSpace(dto.FollowUserName), x => x.UperName.Contains(dto.FollowUserName));
|
.WhereIF(!string.IsNullOrWhiteSpace(dto.FollowUserName), x => x.UperName.Contains(dto.FollowUserName));
|
||||||
var totalCount = await where.CountAsync();
|
var totalCount = await where.CountAsync();
|
||||||
var list = await where.OrderByDescending(x=>x.OpenSync).OrderByDescending(x => x.LastSyncTime).Skip((dto.PageIndex - 1) * dto.PageSize).Take(dto.PageSize).ToListAsync();
|
var list = await where.OrderByDescending(x => x.OpenSync).OrderByDescending(x => x.LastSyncTime).Skip((dto.PageIndex - 1) * dto.PageSize).Take(dto.PageSize).ToListAsync();
|
||||||
return (list, totalCount);
|
return (list, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ namespace dy.net.repository
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<DouyinFollowed> GetBySecUId(string uperId,string myId)
|
public async Task<DouyinFollowed> GetBySecUId(string uperId, string myId)
|
||||||
{
|
{
|
||||||
return await this.GetFirstAsync(x => x.UperId == uperId && x.mySelfId == myId);
|
return await this.GetFirstAsync(x => x.UperId == uperId && x.mySelfId == myId);
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ namespace dy.net.repository
|
|||||||
public async Task<List<DouyinFollowed>> GetSyncFollows(string userId)
|
public async Task<List<DouyinFollowed>> GetSyncFollows(string userId)
|
||||||
{
|
{
|
||||||
return await this.Db.Queryable<DouyinFollowed>()
|
return await this.Db.Queryable<DouyinFollowed>()
|
||||||
.Where(x => x.OpenSync == true).Where(x=>x.mySelfId== userId)
|
.Where(x => x.OpenSync == true).Where(x => x.mySelfId == userId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,14 +94,14 @@ namespace dy.net.repository
|
|||||||
/// <param name="followInfos"></param>
|
/// <param name="followInfos"></param>
|
||||||
/// <param name="ck"></param>
|
/// <param name="ck"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<bool> Sync(List<FollowingsItem> followInfos, DouyinCookie ck)
|
public async Task<(int add, int update, bool succ)> Sync(List<FollowingsItem> followInfos, DouyinCookie ck)
|
||||||
{
|
{
|
||||||
// 基础参数校验
|
// 基础参数校验
|
||||||
if (followInfos == null) followInfos = new List<FollowingsItem>();
|
if (followInfos == null) followInfos = new List<FollowingsItem>();
|
||||||
if (ck == null || string.IsNullOrWhiteSpace(ck.MyUserId))
|
if (ck == null || string.IsNullOrWhiteSpace(ck.MyUserId))
|
||||||
{
|
{
|
||||||
Serilog.Log.Error("同步关注列表失败:当前用户ID为空");
|
Serilog.Log.Error("同步关注列表失败:当前用户ID为空");
|
||||||
return false;
|
return (0, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -111,7 +111,7 @@ namespace dy.net.repository
|
|||||||
if (!currentSecUids.Any())
|
if (!currentSecUids.Any())
|
||||||
{
|
{
|
||||||
Serilog.Log.Debug($"同步关注列表:当前批次无有效数据({ck.UserName}),直接返回成功");
|
Serilog.Log.Debug($"同步关注列表:当前批次无有效数据({ck.UserName}),直接返回成功");
|
||||||
return true;
|
return (0, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 查询当前批次对应的现有记录(仅查需要对比的,减少数据量)
|
// 2. 查询当前批次对应的现有记录(仅查需要对比的,减少数据量)
|
||||||
@@ -177,7 +177,7 @@ namespace dy.net.repository
|
|||||||
if (!batchAddSuccess)
|
if (!batchAddSuccess)
|
||||||
{
|
{
|
||||||
Serilog.Log.Error("同步关注列表失败:新增关注分批插入异常");
|
Serilog.Log.Error("同步关注列表失败:新增关注分批插入异常");
|
||||||
return false;
|
return (toAddFollows.Count, toUpdateFollows.Count, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,18 +198,19 @@ namespace dy.net.repository
|
|||||||
if (!batchUpdateSuccess)
|
if (!batchUpdateSuccess)
|
||||||
{
|
{
|
||||||
Serilog.Log.Error("同步关注列表失败:关注信息更新异常");
|
Serilog.Log.Error("同步关注列表失败:关注信息更新异常");
|
||||||
return false;
|
return (toAddFollows.Count, toUpdateFollows.Count, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 【重要】删除逻辑已移除:增量场景下不能通过批次对比删除,需单独设计取消关注逻辑
|
// 【重要】删除逻辑已移除:增量场景下不能通过批次对比删除,需单独设计取消关注逻辑
|
||||||
Serilog.Log.Debug($"dy_followed_users({ck.UserName})关注列表同步完成:新增{toAddFollows.Count}条,更新{toUpdateFollows.Count}条");
|
Serilog.Log.Debug($"dy_followed_users({ck.UserName})关注列表同步完成:新增{toAddFollows.Count}条,更新{toUpdateFollows.Count}条");
|
||||||
return true;
|
return (toAddFollows.Count, toUpdateFollows.Count, true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Error(ex, $"同步关注列表失败({ck.UserName}):{ex.Message}");
|
Serilog.Log.Error(ex, $"同步关注列表失败({ck.UserName}):{ex.Message}");
|
||||||
return false;
|
return (0, 0, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using dy.net.utils;
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
//using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||||
|
|
||||||
namespace dy.net.service
|
namespace dy.net.service
|
||||||
{
|
{
|
||||||
@@ -59,7 +59,8 @@ namespace dy.net.service
|
|||||||
FollowedTitleSeparator = "",
|
FollowedTitleSeparator = "",
|
||||||
AutoDistinct = true,//默认开启
|
AutoDistinct = true,//默认开启
|
||||||
PriorityLevel = "[{\"id\":1,\"name\":\"喜欢的视频\",\"sort\":1},{\"id\":2,\"name\":\"收藏的视频\",\"sort\":2},{\"id\":3,\"name\":\"关注的视频\",\"sort\":3}]",
|
PriorityLevel = "[{\"id\":1,\"name\":\"喜欢的视频\",\"sort\":1},{\"id\":2,\"name\":\"收藏的视频\",\"sort\":2},{\"id\":3,\"name\":\"关注的视频\",\"sort\":3}]",
|
||||||
IsFirstRunning = true
|
IsFirstRunning = true,
|
||||||
|
OnlySyncNew = true
|
||||||
};
|
};
|
||||||
sqlSugarClient.Insertable(config).ExecuteCommand();
|
sqlSugarClient.Insertable(config).ExecuteCommand();
|
||||||
return config;
|
return config;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using ClockSnowFlake;
|
using ClockSnowFlake;
|
||||||
|
using dy.net.dto;
|
||||||
using dy.net.model;
|
using dy.net.model;
|
||||||
using dy.net.repository;
|
using dy.net.repository;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
@@ -26,12 +27,22 @@ namespace dy.net.service
|
|||||||
{
|
{
|
||||||
return _cookieRepository.GetAllAsync();
|
return _cookieRepository.GetAllAsync();
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> IsInit()
|
||||||
|
{
|
||||||
|
return await _cookieRepository.ExistsAsync(x => !string.IsNullOrEmpty(x.Id));
|
||||||
|
}
|
||||||
public async Task<bool> Add(DouyinCookie dyUserCookies)
|
public async Task<bool> Add(DouyinCookie dyUserCookies)
|
||||||
{
|
{
|
||||||
return await _cookieRepository.InsertAsync(dyUserCookies);
|
return await _cookieRepository.InsertAsync(dyUserCookies);
|
||||||
}
|
}
|
||||||
|
public async Task<bool> Switch(DouyinCookieStopDto dto)
|
||||||
|
{
|
||||||
|
return await _cookieRepository.SwitchAsync(dto);
|
||||||
|
}
|
||||||
public bool InitCookie()
|
public bool InitCookie()
|
||||||
{
|
{
|
||||||
var exist = _cookieRepository.GetDefault();
|
var exist = _cookieRepository.GetDefault();
|
||||||
@@ -41,7 +52,7 @@ namespace dy.net.service
|
|||||||
}
|
}
|
||||||
var cookie = new DouyinCookie
|
var cookie = new DouyinCookie
|
||||||
{
|
{
|
||||||
UserName = "douyin",
|
UserName = "douyin2025",
|
||||||
Cookies = "-",
|
Cookies = "-",
|
||||||
SecUserId = "-",
|
SecUserId = "-",
|
||||||
Id = IdGener.GetLong().ToString(),
|
Id = IdGener.GetLong().ToString(),
|
||||||
@@ -96,5 +107,23 @@ namespace dy.net.service
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将所有同步类型的同步状态改为已同步,这样,之后就不会再扫描所有接口数据,只会读取最新一页的数据了
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> SetOnlySyncNew()
|
||||||
|
{
|
||||||
|
var cks= await _cookieRepository.GetAllAsync();
|
||||||
|
|
||||||
|
foreach (var item in cks)
|
||||||
|
{
|
||||||
|
item.CollHasSyncd = 1;
|
||||||
|
item.FavHasSyncd = 1;
|
||||||
|
item.UperSyncd = 1;
|
||||||
|
}
|
||||||
|
var d= await _cookieRepository.UpdateRangeAsync(cks);
|
||||||
|
return d>0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ namespace dy.net.service
|
|||||||
/// <param name="followInfos"></param>
|
/// <param name="followInfos"></param>
|
||||||
/// <param name="ck"></param>
|
/// <param name="ck"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<bool> Sync(List<FollowingsItem> followInfos, DouyinCookie ck)
|
public async Task<(int add, int update, bool succ)> Sync(List<FollowingsItem> followInfos, DouyinCookie ck)
|
||||||
{
|
{
|
||||||
return await _followRepository.Sync(followInfos, ck);
|
return await _followRepository.Sync(followInfos, ck);
|
||||||
}
|
}
|
||||||
|
|||||||
-324
@@ -1,324 +0,0 @@
|
|||||||
|
|
||||||
using Org.BouncyCastle.Crypto.Digests;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Web;
|
|
||||||
|
|
||||||
namespace dy.net.utils
|
|
||||||
{
|
|
||||||
public class ABogus
|
|
||||||
{
|
|
||||||
private static readonly int[] _arguments = { 0, 1, 14 };
|
|
||||||
private static readonly string _uaKey = "\u0000\u0001\u000e";
|
|
||||||
private static readonly string _endString = "cus";
|
|
||||||
private static readonly int[] _version = { 1, 0, 1, 5 };
|
|
||||||
private static readonly string _browser = "1536|742|1536|864|0|0|0|0|1536|864|1536|864|1536|742|24|24|MacIntel";
|
|
||||||
private static readonly uint[] _reg = {
|
|
||||||
1937774191,
|
|
||||||
1226093241,
|
|
||||||
388252375,
|
|
||||||
3666478592,
|
|
||||||
2842636476,
|
|
||||||
372324522,
|
|
||||||
3817729613,
|
|
||||||
2969243214
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> _str = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "s0", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" },
|
|
||||||
{ "s1", "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=" },
|
|
||||||
{ "s2", "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=" },
|
|
||||||
{ "s3", "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe" },
|
|
||||||
{ "s4", "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private List<byte> _chunk = new List<byte>();
|
|
||||||
private int _size = 0;
|
|
||||||
private uint[] _registers;
|
|
||||||
private int[] _uaCode;
|
|
||||||
private string _browserInfo;
|
|
||||||
private int _browserLength;
|
|
||||||
private int[] _browserCode;
|
|
||||||
|
|
||||||
public ABogus(string platform = null)
|
|
||||||
{
|
|
||||||
_registers = (uint[])_reg.Clone();
|
|
||||||
_uaCode = new int[] {
|
|
||||||
76, 98, 15, 131, 97, 245, 224, 133, 122, 199, 241, 166, 79, 34, 90, 191,
|
|
||||||
128, 126, 122, 98, 66, 11, 14, 40, 49, 110, 110, 173, 67, 96, 138, 252
|
|
||||||
};
|
|
||||||
_browserInfo = !string.IsNullOrEmpty(platform) ? GenerateBrowserInfo(platform) : _browser;
|
|
||||||
_browserLength = _browserInfo.Length;
|
|
||||||
_browserCode = CharCodeAt(_browserInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetValue(Dictionary<string, string> urlParams, string method = "GET",
|
|
||||||
long startTime = 0, long endTime = 0,
|
|
||||||
double? randomNum1 = null, double? randomNum2 = null, double? randomNum3 = null)
|
|
||||||
{
|
|
||||||
string string1 = GenerateString1(randomNum1, randomNum2, randomNum3);
|
|
||||||
string string2 = GenerateString2(urlParams, method, startTime, endTime);
|
|
||||||
string combined = string1 + string2;
|
|
||||||
return GenerateResult(combined, "s4");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateString1(double? randomNum1 = null, double? randomNum2 = null, double? randomNum3 = null)
|
|
||||||
{
|
|
||||||
return FromCharCode(List1(randomNum1)) + FromCharCode(List2(randomNum2)) + FromCharCode(List3(randomNum3));
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateString2(Dictionary<string, string> urlParams, string method, long startTime, long endTime)
|
|
||||||
{
|
|
||||||
var paramsStr = string.Join("&", urlParams.Select(kv => $"{kv.Key}={kv.Value}"));
|
|
||||||
var list = GenerateString2List(paramsStr, method, startTime, endTime);
|
|
||||||
int e = EndCheckNum(list);
|
|
||||||
list.AddRange(_browserCode);
|
|
||||||
list.Add(e);
|
|
||||||
return RC4Encrypt(FromCharCode(list.ToArray()), "y");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] List1(double? randomNum = null, int a = 170, int b = 85, int c = 45)
|
|
||||||
{
|
|
||||||
return RandomList(randomNum, a, b, 1, 2, 5, c & a);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] List2(double? randomNum = null, int a = 170, int b = 85)
|
|
||||||
{
|
|
||||||
return RandomList(randomNum, a, b, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] List3(double? randomNum = null, int a = 170, int b = 85)
|
|
||||||
{
|
|
||||||
return RandomList(randomNum, a, b, 1, 0, 5, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] RandomList(double? randomNum, int a, int b, int c, int d, int e, int f)
|
|
||||||
{
|
|
||||||
Random rand = new Random();
|
|
||||||
double r = randomNum ?? rand.NextDouble() * 10000;
|
|
||||||
int[] v = {
|
|
||||||
(int)r,
|
|
||||||
(int)r & 255,
|
|
||||||
(int)r >> 8
|
|
||||||
};
|
|
||||||
int[] result = {
|
|
||||||
v[1] & a | c,
|
|
||||||
v[1] & b | d,
|
|
||||||
v[2] & a | e,
|
|
||||||
v[2] & b | f
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FromCharCode(params int[] codes)
|
|
||||||
{
|
|
||||||
return string.Join("", codes.Select(c => (char)c));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] CharCodeAt(string s)
|
|
||||||
{
|
|
||||||
return s.Select(c => (int)c).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string RC4Encrypt(string plaintext, string key)
|
|
||||||
{
|
|
||||||
int[] s = Enumerable.Range(0, 256).ToArray();
|
|
||||||
int j = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
j = (j + s[i] + key[i % key.Length]) % 256;
|
|
||||||
(s[i], s[j]) = (s[j], s[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int x = 0;
|
|
||||||
j = 0;
|
|
||||||
var cipher = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < plaintext.Length; i++)
|
|
||||||
{
|
|
||||||
x = (x + 1) % 256;
|
|
||||||
j = (j + s[x]) % 256;
|
|
||||||
(s[x], s[j]) = (s[j], s[x]);
|
|
||||||
int t = (s[x] + s[j]) % 256;
|
|
||||||
cipher.Append((char)(s[t] ^ plaintext[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return cipher.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<int> GenerateString2List(string urlParams, string method, long startTime, long endTime)
|
|
||||||
{
|
|
||||||
startTime = startTime != 0 ? startTime : DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
||||||
endTime = endTime != 0 ? endTime : startTime + new Random().Next(4, 8);
|
|
||||||
|
|
||||||
var paramsArray = GenerateParamsCode(urlParams);
|
|
||||||
var methodArray = GenerateMethodCode(method);
|
|
||||||
|
|
||||||
return new List<int> {
|
|
||||||
44,
|
|
||||||
(int)(endTime >> 24) & 255,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
24,
|
|
||||||
paramsArray[21],
|
|
||||||
methodArray[21],
|
|
||||||
0,
|
|
||||||
_uaCode[23],
|
|
||||||
(int)(endTime >> 16) & 255,
|
|
||||||
0, 0, 0, 1,
|
|
||||||
0,
|
|
||||||
239,
|
|
||||||
paramsArray[22],
|
|
||||||
methodArray[22],
|
|
||||||
_uaCode[24],
|
|
||||||
(int)(endTime >> 8) & 255,
|
|
||||||
(int)(endTime >> 0) & 255,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
(int)(startTime >> 24) & 255,
|
|
||||||
0, 0, 14,
|
|
||||||
(int)(startTime >> 16) & 255,
|
|
||||||
(int)(startTime >> 8) & 255,
|
|
||||||
0,
|
|
||||||
(int)(startTime >> 0) & 255,
|
|
||||||
3,
|
|
||||||
(int)(endTime / 256 / 256 / 256 / 256) >> 0,
|
|
||||||
1,
|
|
||||||
(int)(startTime / 256 / 256 / 256 / 256) >> 0,
|
|
||||||
1,
|
|
||||||
_browserLength,
|
|
||||||
0, 0, 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] GenerateParamsCode(string paramsStr)
|
|
||||||
{
|
|
||||||
return Sm3ToArray(Sm3ToArray(paramsStr + _endString));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] GenerateMethodCode(string method)
|
|
||||||
{
|
|
||||||
return Sm3ToArray(Sm3ToArray(method + _endString));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public int[] Sm3ToArray(string data)
|
|
||||||
{
|
|
||||||
byte[] input = Encoding.UTF8.GetBytes(data);
|
|
||||||
byte[] hash = SM3Hash(input);
|
|
||||||
return hash.Select(b => (int)b).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] Sm3ToArray(int[] data)
|
|
||||||
{
|
|
||||||
byte[] input = data.SelectMany(BitConverter.GetBytes).ToArray();
|
|
||||||
//byte[] input = Encoding.UTF8.GetBytes(data);
|
|
||||||
byte[] hash = SM3Hash(input);
|
|
||||||
return hash.Select(b => (int)b).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] SM3Hash(byte[] input)
|
|
||||||
{
|
|
||||||
//// 使用gmssl实现
|
|
||||||
//Sm3Digest sm3 = new Sm3Digest();
|
|
||||||
//byte[] output = new byte[sm3.GetDigestSize()];
|
|
||||||
//sm3.BlockUpdate(input, 0, input.Length);
|
|
||||||
//sm3.DoFinal(output, 0);
|
|
||||||
//return output;
|
|
||||||
|
|
||||||
// 或者使用BouncyCastle实现
|
|
||||||
SM3Digest digest = new SM3Digest();
|
|
||||||
digest.BlockUpdate(input, 0, input.Length);
|
|
||||||
byte[] output = new byte[digest.GetDigestSize()];
|
|
||||||
digest.DoFinal(output, 0);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
//private int[] SM3ToArray(string data)
|
|
||||||
//{
|
|
||||||
// // 这里需要实现SM3哈希算法
|
|
||||||
// // 由于SM3实现较复杂,可以使用第三方库或参考标准实现
|
|
||||||
// // 这里简化为使用SHA256替代
|
|
||||||
// using var sha256 = SHA256.Create();
|
|
||||||
// byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
|
|
||||||
// return hash.Select(b => (int)b).ToArray();
|
|
||||||
//}
|
|
||||||
|
|
||||||
private int EndCheckNum(List<int> list)
|
|
||||||
{
|
|
||||||
int r = 0;
|
|
||||||
foreach (var i in list)
|
|
||||||
{
|
|
||||||
r ^= i;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateBrowserInfo(string platform)
|
|
||||||
{
|
|
||||||
Random rand = new Random();
|
|
||||||
int innerWidth = rand.Next(1280, 1920);
|
|
||||||
int innerHeight = rand.Next(720, 1080);
|
|
||||||
int outerWidth = rand.Next(innerWidth, 1920);
|
|
||||||
int outerHeight = rand.Next(innerHeight, 1080);
|
|
||||||
int screenX = 0;
|
|
||||||
int screenY = rand.Next(0, 1) == 0 ? 0 : 30;
|
|
||||||
|
|
||||||
return string.Join("|", new object[] {
|
|
||||||
innerWidth, innerHeight,
|
|
||||||
outerWidth, outerHeight,
|
|
||||||
screenX, screenY,
|
|
||||||
0, 0,
|
|
||||||
outerWidth, outerHeight,
|
|
||||||
outerWidth, outerHeight,
|
|
||||||
innerWidth, innerHeight,
|
|
||||||
24, 24,
|
|
||||||
platform
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateResult(string s, string e)
|
|
||||||
{
|
|
||||||
var result = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < s.Length; i += 3)
|
|
||||||
{
|
|
||||||
int n;
|
|
||||||
if (i + 2 < s.Length)
|
|
||||||
{
|
|
||||||
n = (s[i] << 16) | (s[i + 1] << 8) | s[i + 2];
|
|
||||||
}
|
|
||||||
else if (i + 1 < s.Length)
|
|
||||||
{
|
|
||||||
n = (s[i] << 16) | (s[i + 1] << 8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
n = s[i] << 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 18; j >= 0; j -= 6)
|
|
||||||
{
|
|
||||||
int mask = j == 18 ? 0xFC0000 :
|
|
||||||
j == 12 ? 0x03F000 :
|
|
||||||
j == 6 ? 0x0FC0 : 0x3F;
|
|
||||||
|
|
||||||
if (j == 6 && i + 1 >= s.Length) break;
|
|
||||||
if (j == 0 && i + 2 >= s.Length) break;
|
|
||||||
|
|
||||||
result.Append(_str[e][(n & mask) >> j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int padding = (4 - result.Length % 4) % 4;
|
|
||||||
result.Append('=', padding);
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,535 +0,0 @@
|
|||||||
using Org.BouncyCastle.Crypto.Digests;
|
|
||||||
using Org.BouncyCastle.Crypto.Engines;
|
|
||||||
using Org.BouncyCastle.Crypto.Parameters;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace dy.net.utils
|
|
||||||
{
|
|
||||||
public static class StringProcessor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 将字符串转换为字符数组 (ASCII)
|
|
||||||
/// </summary>
|
|
||||||
public static int[] ToOrdArray(string s)
|
|
||||||
{
|
|
||||||
return s.Select(c => (int)c).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将整数数组转回字符串
|
|
||||||
/// </summary>
|
|
||||||
public static string ToCharStr(int[] arr)
|
|
||||||
{
|
|
||||||
return new string(arr.Select(i => (char)i).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// JavaScript 无符号右移操作 (>>>)
|
|
||||||
/// </summary>
|
|
||||||
public static int JsShiftRight(int value, int n)
|
|
||||||
{
|
|
||||||
uint uValue = (uint)value;
|
|
||||||
return (int)(uValue >> n);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成伪随机混淆字节字符串 (长度为 length * 4)
|
|
||||||
/// </summary>
|
|
||||||
private static Random _random = new Random();
|
|
||||||
public static string GenerateRandomBytes(int length = 3)
|
|
||||||
{
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
int rd = _random.Next(10000);
|
|
||||||
|
|
||||||
result.Append((char)(((rd & 255) & 170) | 1));
|
|
||||||
result.Append((char)(((rd & 255) & 85) | 2));
|
|
||||||
result.Append((char)((JsShiftRight(rd, 8) & 170) | 5));
|
|
||||||
result.Append((char)((JsShiftRight(rd, 8) & 85) | 40));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CryptoUtility
|
|
||||||
{
|
|
||||||
public string Salt { get; set; }
|
|
||||||
public List<string> Base64Alphabet { get; set; }
|
|
||||||
|
|
||||||
private readonly int[] _bigArray = {
|
|
||||||
121, 243, 55, 234, 103, 36, 47, 228, 30, 231, 106, 6, 115, 95, 78, 101,
|
|
||||||
250, 207, 198, 50, 139, 227, 220, 105, 97, 143, 34, 28, 194, 215, 18, 100,
|
|
||||||
159, 160, 43, 8, 169, 217, 180, 120, 247, 45, 90, 11, 27, 197, 46, 3,
|
|
||||||
84, 72, 5, 68, 62, 56, 221, 75, 144, 79, 73, 161, 178, 81, 64, 187,
|
|
||||||
134, 117, 186, 118, 16, 241, 130, 71, 89, 147, 122, 129, 65, 40, 88, 150,
|
|
||||||
110, 219, 199, 255, 181, 254, 48, 4, 195, 248, 208, 32, 116, 167, 69, 201,
|
|
||||||
17, 124, 125, 104, 96, 83, 80, 127, 236, 108, 154, 126, 204, 15, 20, 135,
|
|
||||||
112, 158, 13, 1, 188, 164, 210, 237, 222, 98, 212, 77, 253, 42, 170, 202,
|
|
||||||
26, 22, 29, 182, 251, 10, 173, 152, 58, 138, 54, 141, 185, 33, 157, 31,
|
|
||||||
252, 132, 233, 235, 102, 196, 191, 223, 240, 148, 39, 123, 92, 82, 128, 109,
|
|
||||||
57, 24, 38, 113, 209, 245, 2, 119, 153, 229, 189, 214, 230, 174, 232, 63,
|
|
||||||
52, 205, 86, 140, 66, 175, 111, 171, 246, 133, 238, 193, 99, 60, 74, 91,
|
|
||||||
225, 51, 76, 37, 145, 211, 166, 151, 213, 206, 0, 200, 244, 176, 218, 44,
|
|
||||||
184, 172, 49, 216, 93, 168, 53, 21, 183, 41, 67, 85, 224, 155, 226, 242,
|
|
||||||
87, 177, 146, 70, 190, 12, 162, 19, 137, 114, 25, 165, 163, 192, 23, 59,
|
|
||||||
9, 94, 179, 107, 35, 7, 142, 131, 239, 203, 149, 136, 61, 249, 14, 156
|
|
||||||
};
|
|
||||||
|
|
||||||
public CryptoUtility(string salt, List<string> base64Alphabet)
|
|
||||||
{
|
|
||||||
Salt = salt;
|
|
||||||
Base64Alphabet = base64Alphabet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 计算 SM3 哈希并返回 byte 数组(即整数列表)
|
|
||||||
/// </summary>
|
|
||||||
public static byte[] Sm3Hash(byte[] input)
|
|
||||||
{
|
|
||||||
var digest = new SM3Digest();
|
|
||||||
digest.BlockUpdate(input, 0, input.Length);
|
|
||||||
byte[] output = new byte[digest.GetDigestSize()];
|
|
||||||
digest.DoFinal(output, 0);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对输入数据计算 SM3 哈希,并返回整数数组
|
|
||||||
/// </summary>
|
|
||||||
public int[] Sm3ToArray(object input)
|
|
||||||
{
|
|
||||||
byte[] bytes;
|
|
||||||
|
|
||||||
if (input is string str)
|
|
||||||
{
|
|
||||||
bytes = Encoding.UTF8.GetBytes(str);
|
|
||||||
}
|
|
||||||
else if (input is int[] arr)
|
|
||||||
{
|
|
||||||
bytes = arr.Select(b => (byte)b).ToArray();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Input must be string or int[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] hash = Sm3Hash(bytes);
|
|
||||||
return hash.Select(b => (int)b).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加盐值
|
|
||||||
/// </summary>
|
|
||||||
public string AddSalt(string param)
|
|
||||||
{
|
|
||||||
return param + Salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理参数(可选加盐)
|
|
||||||
/// </summary>
|
|
||||||
public object ProcessParam(object param, bool addSalt)
|
|
||||||
{
|
|
||||||
if (param is string s && addSalt)
|
|
||||||
{
|
|
||||||
return AddSalt(s);
|
|
||||||
}
|
|
||||||
return param;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取参数哈希数组(双重哈希)
|
|
||||||
/// </summary>
|
|
||||||
public int[] ParamsToArray(object param, bool addSalt = true)
|
|
||||||
{
|
|
||||||
var processed = ProcessParam(param, addSalt);
|
|
||||||
var firstHash = Sm3ToArray(processed);
|
|
||||||
return Sm3ToArray(firstHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RC4 加密
|
|
||||||
/// </summary>
|
|
||||||
public static byte[] Rc4Encrypt(byte[] key, string plaintext)
|
|
||||||
{
|
|
||||||
byte[] data = Encoding.UTF8.GetBytes(plaintext);
|
|
||||||
var rc4 = new RC4Engine();
|
|
||||||
rc4.Init(true, new KeyParameter(key));
|
|
||||||
|
|
||||||
byte[] output = new byte[data.Length];
|
|
||||||
rc4.ProcessBytes(data, 0, data.Length, output, 0);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 自定义 Base64 编码
|
|
||||||
/// </summary>
|
|
||||||
public string Base64Encode(string input, int selectedAlphabet = 0)
|
|
||||||
{
|
|
||||||
string alphabet = Base64Alphabet[selectedAlphabet];
|
|
||||||
var binary = new StringBuilder();
|
|
||||||
|
|
||||||
foreach (char c in input)
|
|
||||||
{
|
|
||||||
binary.Append(Convert.ToString(c, 2).PadLeft(8, '0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (binary.Length % 6 != 0)
|
|
||||||
{
|
|
||||||
binary.Append('0');
|
|
||||||
}
|
|
||||||
|
|
||||||
var chunks = new List<int>();
|
|
||||||
for (int i = 0; i < binary.Length; i += 6)
|
|
||||||
{
|
|
||||||
string chunk = binary.ToString(i, Math.Min(6, binary.Length - i));
|
|
||||||
chunks.Add(Convert.ToInt32(chunk, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = new StringBuilder();
|
|
||||||
foreach (int index in chunks)
|
|
||||||
{
|
|
||||||
output.Append(alphabet[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Padding
|
|
||||||
int padding = (6 - (binary.Length % 6)) % 6;
|
|
||||||
output.Append('=', padding / 2);
|
|
||||||
|
|
||||||
return output.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ABogus 自定义编码逻辑(类似Base64但不同分组)
|
|
||||||
/// </summary>
|
|
||||||
public string AbogusEncode(string input, int selectedAlphabet)
|
|
||||||
{
|
|
||||||
var abogus = new List<char>();
|
|
||||||
string alphabet = Base64Alphabet[selectedAlphabet];
|
|
||||||
|
|
||||||
for (int i = 0; i < input.Length; i += 3)
|
|
||||||
{
|
|
||||||
int n = 0;
|
|
||||||
if (i + 2 < input.Length)
|
|
||||||
{
|
|
||||||
n = (input[i] << 16) | (input[i + 1] << 8) | input[i + 2];
|
|
||||||
}
|
|
||||||
else if (i + 1 < input.Length)
|
|
||||||
{
|
|
||||||
n = (input[i] << 16) | (input[i + 1] << 8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
n = input[i] << 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] masks = { 0xFC0000, 0x03F000, 0x0FC0, 0x3F };
|
|
||||||
int[] shifts = { 18, 12, 6, 0 };
|
|
||||||
|
|
||||||
for (int j = 0; j < 4; j++)
|
|
||||||
{
|
|
||||||
if ((j == 2 && i + 1 >= input.Length) || (j == 3 && i + 2 >= input.Length))
|
|
||||||
break;
|
|
||||||
int val = (n & masks[j]) >> shifts[j];
|
|
||||||
abogus.Add(alphabet[val]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (abogus.Count % 4 != 0)
|
|
||||||
{
|
|
||||||
abogus.Add('=');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(abogus.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 字节数组变换加密(RC4-like 流密码)
|
|
||||||
/// </summary>
|
|
||||||
public string TransformBytes(int[] bytesList)
|
|
||||||
{
|
|
||||||
string bytesStr = StringProcessor.ToCharStr(bytesList);
|
|
||||||
var result = new List<char>();
|
|
||||||
|
|
||||||
int indexB = _bigArray[1];
|
|
||||||
int initialValue = 0;
|
|
||||||
int valueE = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < bytesStr.Length; i++)
|
|
||||||
{
|
|
||||||
char ch = bytesStr[i];
|
|
||||||
int charValue = ch;
|
|
||||||
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
initialValue = _bigArray[indexB];
|
|
||||||
int sumInitial = indexB + initialValue;
|
|
||||||
|
|
||||||
_bigArray[1] = initialValue;
|
|
||||||
_bigArray[indexB] = indexB;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int sumInitial = initialValue + valueE;
|
|
||||||
sumInitial %= _bigArray.Length;
|
|
||||||
valueE = _bigArray[(i + 2) % _bigArray.Length];
|
|
||||||
sumInitial = (indexB + valueE) % _bigArray.Length;
|
|
||||||
initialValue = _bigArray[sumInitial];
|
|
||||||
}
|
|
||||||
|
|
||||||
int sumInitialFinal = (indexB + (i == 0 ? initialValue : valueE)) % _bigArray.Length;
|
|
||||||
int valueF = _bigArray[sumInitialFinal];
|
|
||||||
int encryptedChar = charValue ^ valueF;
|
|
||||||
result.Add((char)encryptedChar);
|
|
||||||
|
|
||||||
// 更新状态
|
|
||||||
valueE = _bigArray[(i + 2) % _bigArray.Length];
|
|
||||||
sumInitialFinal = (indexB + valueE) % _bigArray.Length;
|
|
||||||
int temp = _bigArray[sumInitialFinal];
|
|
||||||
_bigArray[sumInitialFinal] = _bigArray[(i + 2) % _bigArray.Length];
|
|
||||||
_bigArray[(i + 2) % _bigArray.Length] = temp;
|
|
||||||
indexB = sumInitialFinal;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(result.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BrowserFingerprintGenerator
|
|
||||||
{
|
|
||||||
private static Random _random = new Random();
|
|
||||||
|
|
||||||
public static string GenerateFingerprint(string browserType = "Edge")
|
|
||||||
{
|
|
||||||
return browserType switch
|
|
||||||
{
|
|
||||||
"Chrome" => _GenerateFingerprint("Win32"),
|
|
||||||
"Firefox" => _GenerateFingerprint("Win32"),
|
|
||||||
"Safari" => _GenerateFingerprint("MacIntel"),
|
|
||||||
"Edge" => _GenerateFingerprint("Win32"),
|
|
||||||
_ => _GenerateFingerprint("Win32")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string _GenerateFingerprint(string platform)
|
|
||||||
{
|
|
||||||
int innerWidth = _random.Next(1024, 1921);
|
|
||||||
int innerHeight = _random.Next(768, 1081);
|
|
||||||
int outerWidth = innerWidth + _random.Next(24, 33);
|
|
||||||
int outerHeight = innerHeight + _random.Next(75, 91);
|
|
||||||
int screenX = 0;
|
|
||||||
int screenY = _random.Next(2) == 0 ? 0 : 30;
|
|
||||||
int sizeWidth = _random.Next(1024, 1921);
|
|
||||||
int sizeHeight = _random.Next(768, 1081);
|
|
||||||
int availWidth = _random.Next(1280, 1921);
|
|
||||||
int availHeight = _random.Next(800, 1081);
|
|
||||||
|
|
||||||
return $"{innerWidth}|{innerHeight}|{outerWidth}|{outerHeight}|" +
|
|
||||||
$"{screenX}|{screenY}|0|0|{sizeWidth}|{sizeHeight}|" +
|
|
||||||
$"{availWidth}|{availHeight}|{innerWidth}|{innerHeight}|24|24|{platform}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ABogus2
|
|
||||||
{
|
|
||||||
private int aid = 6383;
|
|
||||||
private int pageId = 0;
|
|
||||||
private string salt = "cus";
|
|
||||||
private bool boe = false;
|
|
||||||
private double ddrt = 8.5;
|
|
||||||
private double ic = 8.5;
|
|
||||||
private List<string> paths = new() {
|
|
||||||
"^/webcast/", "^/aweme/v1/", "^/aweme/v2/", "/v1/message/send", "^/live/", "^/captcha/", "^/ecom/"
|
|
||||||
};
|
|
||||||
|
|
||||||
private byte[] uaKey = { 0x00, 0x01, 0x0E };
|
|
||||||
|
|
||||||
private string character = "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe";
|
|
||||||
private string character2 = "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe";
|
|
||||||
|
|
||||||
private List<string> characterList;
|
|
||||||
private CryptoUtility cryptoUtility;
|
|
||||||
|
|
||||||
private string userAgent;
|
|
||||||
private string browserFp;
|
|
||||||
|
|
||||||
private int[] sortIndex = {
|
|
||||||
18, 20, 52, 26, 30, 34, 58, 38, 40, 53, 42, 21, 27, 54, 55, 31, 35, 57, 39, 41, 43, 22, 28,
|
|
||||||
32, 60, 36, 23, 29, 33, 37, 44, 45, 59, 46, 47, 48, 49, 50, 24, 25, 65, 66, 70, 71
|
|
||||||
};
|
|
||||||
|
|
||||||
private int[] sortIndex2 = {
|
|
||||||
18, 20, 26, 30, 34, 38, 40, 42, 21, 27, 31, 35, 39, 41, 43, 22, 28, 32, 36, 23, 29, 33, 37,
|
|
||||||
44, 45, 46, 47, 48, 49, 50, 24, 25, 52, 53, 54, 55, 57, 58, 59, 60, 65, 66, 70, 71
|
|
||||||
};
|
|
||||||
|
|
||||||
public List<int> Options { get; set; } = new() { 0, 1, 14 }; // POST 默认
|
|
||||||
|
|
||||||
public ABogus2(string fp = "", string userAgent = "", List<int> options = null)
|
|
||||||
{
|
|
||||||
if (options != null) Options = options;
|
|
||||||
|
|
||||||
this.userAgent = !string.IsNullOrEmpty(userAgent)
|
|
||||||
? userAgent
|
|
||||||
: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0";
|
|
||||||
|
|
||||||
this.browserFp = !string.IsNullOrEmpty(fp)
|
|
||||||
? fp
|
|
||||||
: BrowserFingerprintGenerator.GenerateFingerprint("Edge");
|
|
||||||
|
|
||||||
characterList = new List<string> { character, character2 };
|
|
||||||
cryptoUtility = new CryptoUtility(salt, characterList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string EncodeData(string data, int alphabetIndex = 0)
|
|
||||||
{
|
|
||||||
return cryptoUtility.AbogusEncode(data, alphabetIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public (string paramsWithAbogus, string abogus, string userAgent, string body) GenerateAbogus(string paramsStr, string body = "")
|
|
||||||
{
|
|
||||||
var abDir = new Dictionary<int, object>
|
|
||||||
{
|
|
||||||
{ 8, 3 },
|
|
||||||
{ 15, new {
|
|
||||||
aid = this.aid,
|
|
||||||
pageId = this.pageId,
|
|
||||||
boe = this.boe,
|
|
||||||
ddrt = this.ddrt,
|
|
||||||
paths = this.paths,
|
|
||||||
track = new { mode = 0, delay = 300, paths = new List<object>() },
|
|
||||||
dump = true,
|
|
||||||
rpU = ""
|
|
||||||
}},
|
|
||||||
{ 18, 44 },
|
|
||||||
{ 19, new[] { 1, 0, 1, 0, 1 } },
|
|
||||||
{ 66, 0 },
|
|
||||||
{ 69, 0 },
|
|
||||||
{ 70, 0 },
|
|
||||||
{ 71, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
long startEncryption = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
|
||||||
int[] array1 = cryptoUtility.ParamsToArray(paramsStr); // 双重哈希
|
|
||||||
int[] array2 = body != "" ? cryptoUtility.ParamsToArray(body) : new int[0];
|
|
||||||
string encodedUa = cryptoUtility.Base64Encode(
|
|
||||||
StringProcessor.ToCharStr(
|
|
||||||
Array.ConvertAll(CryptoUtility.Rc4Encrypt(uaKey, userAgent), b => (int)b)
|
|
||||||
),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
int[] array3 = cryptoUtility.Sm3ToArray(encodedUa); // 不加盐
|
|
||||||
|
|
||||||
long endEncryption = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
|
||||||
|
|
||||||
// 插入时间戳高位
|
|
||||||
abDir[20] = (byte)((startEncryption >> 24) & 0xFF);
|
|
||||||
abDir[21] = (byte)((startEncryption >> 16) & 0xFF);
|
|
||||||
abDir[22] = (byte)((startEncryption >> 8) & 0xFF);
|
|
||||||
abDir[23] = (byte)(startEncryption & 0xFF);
|
|
||||||
abDir[24] = (int)((startEncryption >> 32) & 0xFF);
|
|
||||||
abDir[25] = (int)((startEncryption >> 40) & 0xFF);
|
|
||||||
|
|
||||||
// 请求选项
|
|
||||||
abDir[26] = (byte)((Options[0] >> 24) & 0xFF);
|
|
||||||
abDir[27] = (byte)((Options[0] >> 16) & 0xFF);
|
|
||||||
abDir[28] = (byte)((Options[0] >> 8) & 0xFF);
|
|
||||||
abDir[29] = (byte)(Options[0] & 0xFF);
|
|
||||||
|
|
||||||
abDir[30] = (byte)((Options[1] >> 8) & 0xFF);
|
|
||||||
abDir[31] = (byte)(Options[1] & 0xFF);
|
|
||||||
abDir[32] = (byte)((Options[1] >> 24) & 0xFF);
|
|
||||||
abDir[33] = (byte)((Options[1] >> 16) & 0xFF);
|
|
||||||
|
|
||||||
abDir[34] = (byte)((Options[2] >> 24) & 0xFF);
|
|
||||||
abDir[35] = (byte)((Options[2] >> 16) & 0xFF);
|
|
||||||
abDir[36] = (byte)((Options[2] >> 8) & 0xFF);
|
|
||||||
abDir[37] = (byte)(Options[2] & 0xFF);
|
|
||||||
|
|
||||||
abDir[38] = array1[21];
|
|
||||||
abDir[39] = array1[22];
|
|
||||||
abDir[40] = array2.Length > 21 ? array2[21] : 0;
|
|
||||||
abDir[41] = array2.Length > 22 ? array2[22] : 0;
|
|
||||||
abDir[42] = array3.Length > 23 ? array3[23] : 0;
|
|
||||||
abDir[43] = array3.Length > 24 ? array3[24] : 0;
|
|
||||||
|
|
||||||
abDir[44] = (byte)((endEncryption >> 24) & 0xFF);
|
|
||||||
abDir[45] = (byte)((endEncryption >> 16) & 0xFF);
|
|
||||||
abDir[46] = (byte)((endEncryption >> 8) & 0xFF);
|
|
||||||
abDir[47] = (byte)(endEncryption & 0xFF);
|
|
||||||
abDir[48] = abDir[8];
|
|
||||||
abDir[49] = (int)((endEncryption >> 32) & 0xFF);
|
|
||||||
abDir[50] = (int)((endEncryption >> 40) & 0xFF);
|
|
||||||
|
|
||||||
abDir[51] = (byte)((pageId >> 24) & 0xFF);
|
|
||||||
abDir[52] = (byte)((pageId >> 16) & 0xFF);
|
|
||||||
abDir[53] = (byte)((pageId >> 8) & 0xFF);
|
|
||||||
abDir[54] = (byte)(pageId & 0xFF);
|
|
||||||
abDir[55] = pageId;
|
|
||||||
abDir[56] = aid;
|
|
||||||
abDir[57] = (byte)(aid & 0xFF);
|
|
||||||
abDir[58] = (byte)((aid >> 8) & 0xFF);
|
|
||||||
abDir[59] = (byte)((aid >> 16) & 0xFF);
|
|
||||||
abDir[60] = (byte)((aid >> 24) & 0xFF);
|
|
||||||
|
|
||||||
abDir[64] = browserFp.Length;
|
|
||||||
abDir[65] = browserFp.Length;
|
|
||||||
|
|
||||||
// 排序取值
|
|
||||||
var sortedValues = sortIndex
|
|
||||||
.Select(k => Convert.ToInt32(abDir.GetValueOrDefault(k, 0)))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var fpArray = StringProcessor.ToOrdArray(browserFp).ToList();
|
|
||||||
|
|
||||||
int abXor = 0;
|
|
||||||
abXor = sortIndex2
|
|
||||||
.Select(k => Convert.ToInt32(abDir.GetValueOrDefault(k, 0)))
|
|
||||||
.Aggregate(0, (x, y) => x ^ y);
|
|
||||||
|
|
||||||
|
|
||||||
sortedValues.AddRange(fpArray);
|
|
||||||
sortedValues.Add(abXor);
|
|
||||||
|
|
||||||
string randomBytes = StringProcessor.GenerateRandomBytes();
|
|
||||||
string transformed = cryptoUtility.TransformBytes(sortedValues.ToArray());
|
|
||||||
|
|
||||||
string abogusBytesStr = randomBytes + transformed;
|
|
||||||
string abogus = cryptoUtility.AbogusEncode(abogusBytesStr, 0);
|
|
||||||
|
|
||||||
string finalParams = $"{paramsStr}&a_bogus={abogus}";
|
|
||||||
|
|
||||||
return (finalParams, abogus, userAgent, body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试代码
|
|
||||||
//public class ABogusTest
|
|
||||||
//{
|
|
||||||
// //public static void Main()
|
|
||||||
// //{
|
|
||||||
// // string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0";
|
|
||||||
// // string edgeFp = BrowserFingerprintGenerator.GenerateFingerprint("Edge");
|
|
||||||
// // var abogus = new ABogus2(fp: edgeFp, userAgent: userAgent);
|
|
||||||
|
|
||||||
// // // GET请求测试
|
|
||||||
// // string getParams = "device_platform=webapp&aid=6383&channel=channel_pc_web&sec_user_id=MS4wLjABAAAArDVBosPJF3eIWVEFp0szuJ-e1V_-rK0ieJeWwpE77E8&max_cursor=0&locate_query=false&show_live_replay_strategy=1&need_time_list=1&time_list_query=0&whale_cut_token=&cut_version=1&count=18&publish_video_strategy_type=2&from_user_page=1&update_version_code=170400&pc_client_type=1&pc_libra_divert=Windows&support_h265=1&support_dash=0&version_code=290100&version_name=29.1.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=131.0.0.0&browser_online=true&engine_name=Blink&engine_version=131.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50";
|
|
||||||
// // var getResult = abogus.GenerateABogus(getParams);
|
|
||||||
// // Console.WriteLine($"GET 完整URL: https://www.douyin.com/aweme/v1/web/aweme/detail/?{getResult.Params}");
|
|
||||||
// // Console.WriteLine($"GET ABogus: {getResult.ABogus}");
|
|
||||||
|
|
||||||
// // // POST请求测试
|
|
||||||
// // string postParams = "device_platform=webapp&aid=6383&channel=channel_pc_web&pc_client_type=1&pc_libra_divert=Windows&update_version_code=170400&support_h265=1&support_dash=0&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=131.0.0.0&browser_online=true&engine_name=Blink&engine_version=131.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50";
|
|
||||||
// // string postBody = "aweme_type=0&item_id=7467485482314763572&play_delta=1&source=0";
|
|
||||||
// // var postResult = abogus.GenerateABogus(postParams, postBody);
|
|
||||||
// // Console.WriteLine($"POST 完整URL: https://www.douyin.com/aweme/v2/web/aweme/stats/?{postResult.Params}");
|
|
||||||
// // Console.WriteLine($"POST ABogus: {postResult.ABogus}");
|
|
||||||
// // Console.WriteLine($"POST Body: {postResult.Body}");
|
|
||||||
// //}
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -74,5 +74,88 @@
|
|||||||
}
|
}
|
||||||
return totalSize;
|
return totalSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查指定文件夹是否有读取权限
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryPath">文件夹路径</param>
|
||||||
|
/// <returns>有读权限返回true,否则返回false</returns>
|
||||||
|
public static bool HasDirectoryReadPermission(string directoryPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 首先检查文件夹是否存在
|
||||||
|
if (!Directory.Exists(directoryPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 尝试枚举文件夹内容(核心验证读权限)
|
||||||
|
// EnumerateFileSystemEntries 会触发实际的读权限检查
|
||||||
|
var entries = Directory.EnumerateFileSystemEntries(directoryPath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// 明确捕获无权限异常
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 其他异常(如路径无效、文件夹被占用等),视为无有效读权限
|
||||||
|
Console.WriteLine($"检查读权限时发生非权限异常: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查指定文件夹是否有写入权限
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryPath">文件夹路径</param>
|
||||||
|
/// <returns>有写权限返回true,否则返回false</returns>
|
||||||
|
public static bool HasDirectoryWritePermission(string directoryPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 检查文件夹是否存在
|
||||||
|
if (!Directory.Exists(directoryPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 核心验证:在文件夹内创建临时文件(最直接的写权限验证)
|
||||||
|
string tempFileName = $"temp_perm_check_{Guid.NewGuid()}.tmp";
|
||||||
|
string tempFilePath = Path.Combine(directoryPath, tempFileName);
|
||||||
|
|
||||||
|
// 尝试创建临时文件
|
||||||
|
using (FileStream fs = File.Create(tempFilePath, 1, FileOptions.DeleteOnClose))
|
||||||
|
{
|
||||||
|
// FileOptions.DeleteOnClose 确保即使程序异常退出,临时文件也会被删除
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"检查写权限时发生非权限异常: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查指定文件夹是否同时有读写权限
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryPath">文件夹路径</param>
|
||||||
|
/// <returns>同时有读写权限返回true,否则返回false</returns>
|
||||||
|
public static bool HasDirectoryReadWritePermission(string directoryPath)
|
||||||
|
{
|
||||||
|
return HasDirectoryReadPermission(directoryPath) && HasDirectoryWritePermission(directoryPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace dy.net.utils
|
|
||||||
{
|
|
||||||
public static class ObjectExtensions
|
|
||||||
{
|
|
||||||
public static void PrintAsTable(this object obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
Console.WriteLine("对象为null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PropertyInfo[] properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
|
|
||||||
if (properties.Length == 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine("没有找到公共属性");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算最大宽度
|
|
||||||
int maxNameLength = "属性名".Length;
|
|
||||||
int maxValueLength = "值".Length;
|
|
||||||
|
|
||||||
foreach (var prop in properties)
|
|
||||||
{
|
|
||||||
maxNameLength = Math.Max(maxNameLength, prop.Name.Length);
|
|
||||||
|
|
||||||
object value = prop.GetValue(obj);
|
|
||||||
string valueStr = value?.ToString() ?? "null";
|
|
||||||
maxValueLength = Math.Max(maxValueLength, valueStr.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建表格
|
|
||||||
string separator = "+" + new string('-', maxNameLength + 2) + "+" + new string('-', maxValueLength + 2) + "+";
|
|
||||||
|
|
||||||
Serilog.Log.Debug(separator);
|
|
||||||
Serilog.Log.Debug($"| {"属性名".PadRight(maxNameLength)} | {"值".PadRight(maxValueLength)} |");
|
|
||||||
Serilog.Log.Debug(separator);
|
|
||||||
|
|
||||||
foreach (var prop in properties)
|
|
||||||
{
|
|
||||||
string name = prop.Name;
|
|
||||||
object value = prop.GetValue(obj);
|
|
||||||
string valueStr = value?.ToString() ?? "null";
|
|
||||||
|
|
||||||
// 处理过长的字符串
|
|
||||||
if (valueStr.Length > maxValueLength)
|
|
||||||
{
|
|
||||||
valueStr = valueStr.Substring(0, maxValueLength - 3) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
Serilog.Log.Debug($"| {name.PadRight(maxNameLength)} | {valueStr.PadRight(maxValueLength)} |");
|
|
||||||
}
|
|
||||||
|
|
||||||
Serilog.Log.Debug(separator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
//public static string DOWN_IMAGE_VIDEO_ENABLE="DOWN_IMGVIDEO";
|
//public static string DOWN_IMAGE_VIDEO_ENABLE="DOWN_IMGVIDEO";
|
||||||
|
|
||||||
public static string ASPNETCORE_URLS = "ASPNETCORE_URLS";
|
public static string APP_PORT = "appPort";
|
||||||
|
|
||||||
|
|
||||||
public static string DY_FOLLOWEDS = "dy_followeds";
|
public static string DY_FOLLOWEDS = "dy_followeds";
|
||||||
|
|||||||
-290
@@ -1,290 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace dy.net.utils
|
|
||||||
{
|
|
||||||
public class XBogus
|
|
||||||
{
|
|
||||||
private readonly int?[] _array;
|
|
||||||
private readonly string _character;
|
|
||||||
private readonly byte[] _uaKey = { 0x00, 0x01, 0x0c };
|
|
||||||
private readonly string _userAgent;
|
|
||||||
|
|
||||||
public string Params { get; private set; }
|
|
||||||
public string Xb { get; private set; }
|
|
||||||
|
|
||||||
public XBogus(string userAgent = "")
|
|
||||||
{
|
|
||||||
// 初始化 Array 数组(对应 Python 的 self.Array)
|
|
||||||
_array = new int?[128];
|
|
||||||
// 数字 0-9 对应 ASCII 48-57
|
|
||||||
for (int i = 48; i <= 57; i++)
|
|
||||||
_array[i] = i - 48;
|
|
||||||
// 字母 A-F 对应 ASCII 65-70,映射为 10-15
|
|
||||||
for (int i = 65; i <= 70; i++)
|
|
||||||
_array[i] = i - 55;
|
|
||||||
// 字母 a-f 对应 ASCII 97-102,映射为 10-15
|
|
||||||
for (int i = 97; i <= 102; i++)
|
|
||||||
_array[i] = i - 87;
|
|
||||||
|
|
||||||
// 字符映射表
|
|
||||||
_character = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=";
|
|
||||||
|
|
||||||
// 用户代理,默认值与 Python 一致
|
|
||||||
_userAgent = string.IsNullOrEmpty(userAgent)
|
|
||||||
? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
|
|
||||||
: userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将字符串通过 MD5 哈希转换为整数数组
|
|
||||||
/// </summary>
|
|
||||||
private int[] Md5StrToArray(string md5Str)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(md5Str) && md5Str.Length > 32)
|
|
||||||
return md5Str.Select(c => (int)c).ToArray();
|
|
||||||
|
|
||||||
var result = new List<int>();
|
|
||||||
for (int i = 0; i < md5Str.Length; i += 2)
|
|
||||||
{
|
|
||||||
if (i + 1 >= md5Str.Length)
|
|
||||||
break;
|
|
||||||
|
|
||||||
int? high = _array[md5Str[i]];
|
|
||||||
int? low = _array[md5Str[i + 1]];
|
|
||||||
if (high == null || low == null)
|
|
||||||
result.Add(0);
|
|
||||||
else
|
|
||||||
result.Add(((int)high << 4) | (int)low);
|
|
||||||
}
|
|
||||||
return result.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 多轮 MD5 哈希加密 URL 参数
|
|
||||||
/// </summary>
|
|
||||||
private int[] Md5Encrypt(string urlParams)
|
|
||||||
{
|
|
||||||
string firstMd5 = Md5(urlParams);
|
|
||||||
int[] firstArray = Md5StrToArray(firstMd5);
|
|
||||||
string secondMd5 = Md5(firstArray);
|
|
||||||
return Md5StrToArray(secondMd5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 计算 MD5 哈希值
|
|
||||||
/// </summary>
|
|
||||||
private string Md5(object input)
|
|
||||||
{
|
|
||||||
int[] dataArray;
|
|
||||||
switch (input)
|
|
||||||
{
|
|
||||||
case string str:
|
|
||||||
dataArray = Md5StrToArray(str);
|
|
||||||
break;
|
|
||||||
case int[] arr:
|
|
||||||
dataArray = arr;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentException("Invalid input type. Expected string or int array.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var md5 = MD5.Create())
|
|
||||||
{
|
|
||||||
byte[] bytes = dataArray.Select(i => (byte)(i & 0xFF)).ToArray();
|
|
||||||
byte[] hashBytes = md5.ComputeHash(bytes);
|
|
||||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 第一次编码转换
|
|
||||||
/// </summary>
|
|
||||||
private string EncodingConversion(
|
|
||||||
int a, int b, int c, int e, int d, int t, int f, int r, int n, int o,
|
|
||||||
int i, int _, int x, int u, int s, int l, int v, int h, int p)
|
|
||||||
{
|
|
||||||
var bytes = new byte[]
|
|
||||||
{
|
|
||||||
(byte)a, (byte)i, (byte)b, (byte)_ , (byte)c, (byte)x,
|
|
||||||
(byte)e, (byte)u, (byte)d, (byte)s, (byte)t, (byte)l,
|
|
||||||
(byte)f, (byte)v, (byte)r, (byte)h, (byte)n, (byte)p, (byte)o
|
|
||||||
};
|
|
||||||
return Encoding.GetEncoding("ISO-8859-1").GetString(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 第二次编码转换
|
|
||||||
/// </summary>
|
|
||||||
private string EncodingConversion2(int a, int b, string c)
|
|
||||||
{
|
|
||||||
return ((char)a).ToString() + ((char)b).ToString() + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RC4 加密算法
|
|
||||||
/// </summary>
|
|
||||||
private byte[] Rc4Encrypt(byte[] key, byte[] data)
|
|
||||||
{
|
|
||||||
int[] S = Enumerable.Range(0, 256).ToArray();
|
|
||||||
int j = 0;
|
|
||||||
|
|
||||||
// 初始化 S 盒
|
|
||||||
for (int i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
j = (j + S[i] + key[i % key.Length]) % 256;
|
|
||||||
(S[i], S[j]) = (S[j], S[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成密文
|
|
||||||
var encrypted = new byte[data.Length];
|
|
||||||
int i2 = 0, j2 = 0;
|
|
||||||
for (int k = 0; k < data.Length; k++)
|
|
||||||
{
|
|
||||||
i2 = (i2 + 1) % 256;
|
|
||||||
j2 = (j2 + S[i2]) % 256;
|
|
||||||
(S[i2], S[j2]) = (S[j2], S[i2]);
|
|
||||||
int t = (S[i2] + S[j2]) % 256;
|
|
||||||
encrypted[k] = (byte)(data[k] ^ S[t]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 位运算计算
|
|
||||||
/// </summary>
|
|
||||||
private string Calculation(int a1, int a2, int a3)
|
|
||||||
{
|
|
||||||
int x1 = (a1 & 0xFF) << 16;
|
|
||||||
int x2 = (a2 & 0xFF) << 8;
|
|
||||||
int x3 = x1 | x2 | (a3 & 0xFF);
|
|
||||||
|
|
||||||
char c1 = _character[(x3 & 0x0FC0000) >> 18]; // 16515072 = 0x0FC0000
|
|
||||||
char c2 = _character[(x3 & 0x003F000) >> 12]; // 258048 = 0x003F000
|
|
||||||
char c3 = _character[(x3 & 0x0000FC0) >> 6]; // 4032 = 0x0000FC0
|
|
||||||
char c4 = _character[x3 & 0x3F];
|
|
||||||
|
|
||||||
return $"{c1}{c2}{c3}{c4}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取 X-Bogus 值
|
|
||||||
/// </summary>
|
|
||||||
public (string Params, string Xb, string UserAgent) GetXBogus(string urlParams)
|
|
||||||
{
|
|
||||||
// 计算 array1
|
|
||||||
byte[] uaBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(_userAgent);
|
|
||||||
byte[] rc4Ua = Rc4Encrypt(_uaKey, uaBytes);
|
|
||||||
string base64Ua = Convert.ToBase64String(rc4Ua);
|
|
||||||
string md5Ua = Md5(base64Ua);
|
|
||||||
int[] array1 = Md5StrToArray(md5Ua);
|
|
||||||
|
|
||||||
// 计算 array2(固定 MD5:d41d8cd98f00b204e9800998ecf8427e 是空字符串的 MD5)
|
|
||||||
int[] array2 = Md5StrToArray(Md5(Md5StrToArray("d41d8cd98f00b204e9800998ecf8427e")));
|
|
||||||
|
|
||||||
// 计算 URL 参数的 MD5 数组
|
|
||||||
int[] urlParamsArray = Md5Encrypt(urlParams);
|
|
||||||
|
|
||||||
// 时间戳和固定值
|
|
||||||
long timer = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
||||||
int ct = 536919696;
|
|
||||||
|
|
||||||
// 构建 new_array
|
|
||||||
var newArray = new List<double>
|
|
||||||
{
|
|
||||||
64, 0.00390625, 1, 12,
|
|
||||||
urlParamsArray.Length > 14 ? urlParamsArray[14] : 0,
|
|
||||||
urlParamsArray.Length > 15 ? urlParamsArray[15] : 0,
|
|
||||||
array2.Length > 14 ? array2[14] : 0,
|
|
||||||
array2.Length > 15 ? array2[15] : 0,
|
|
||||||
array1.Length > 14 ? array1[14] : 0,
|
|
||||||
array1.Length > 15 ? array1[15] : 0,
|
|
||||||
(timer >> 24) & 0xFF,
|
|
||||||
(timer >> 16) & 0xFF,
|
|
||||||
(timer >> 8) & 0xFF,
|
|
||||||
timer & 0xFF,
|
|
||||||
(ct >> 24) & 0xFF,
|
|
||||||
(ct >> 16) & 0xFF,
|
|
||||||
(ct >> 8) & 0xFF,
|
|
||||||
ct & 0xFF
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算异或结果
|
|
||||||
int xorResult = (int)newArray[0];
|
|
||||||
for (int i = 1; i < newArray.Count; i++)
|
|
||||||
{
|
|
||||||
int b = (int)newArray[i];
|
|
||||||
xorResult ^= b;
|
|
||||||
}
|
|
||||||
newArray.Add(xorResult);
|
|
||||||
|
|
||||||
// 拆分 array3 和 array4
|
|
||||||
var array3 = new List<int>();
|
|
||||||
var array4 = new List<int>();
|
|
||||||
for (int i = 0; i < newArray.Count; i++)
|
|
||||||
{
|
|
||||||
array3.Add((int)newArray[i]);
|
|
||||||
if (i + 1 < newArray.Count)
|
|
||||||
array4.Add((int)newArray[i + 1]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并数组
|
|
||||||
int[] mergeArray = array3.Concat(array4).ToArray();
|
|
||||||
|
|
||||||
// 生成乱码
|
|
||||||
string encoding1 = EncodingConversion(
|
|
||||||
mergeArray[0], mergeArray[1], mergeArray[2], mergeArray[3], mergeArray[4],
|
|
||||||
mergeArray[5], mergeArray[6], mergeArray[7], mergeArray[8], mergeArray[9],
|
|
||||||
mergeArray[10], mergeArray[11], mergeArray[12], mergeArray[13], mergeArray[14],
|
|
||||||
mergeArray[15], mergeArray[16], mergeArray[17], mergeArray[18]
|
|
||||||
);
|
|
||||||
|
|
||||||
byte[] encoding1Bytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(encoding1);
|
|
||||||
byte[] rc4Key = Encoding.GetEncoding("ISO-8859-1").GetBytes("ÿ");
|
|
||||||
byte[] rc4Encrypted = Rc4Encrypt(rc4Key, encoding1Bytes);
|
|
||||||
string rc4Str = Encoding.GetEncoding("ISO-8859-1").GetString(rc4Encrypted);
|
|
||||||
|
|
||||||
string garbledCode = EncodingConversion2(2, 255, rc4Str);
|
|
||||||
|
|
||||||
// 计算 X-Bogus
|
|
||||||
StringBuilder xbBuilder = new StringBuilder();
|
|
||||||
for (int i = 0; i < garbledCode.Length; i += 3)
|
|
||||||
{
|
|
||||||
if (i + 2 >= garbledCode.Length)
|
|
||||||
break;
|
|
||||||
|
|
||||||
int a = garbledCode[i];
|
|
||||||
int b = garbledCode[i + 1];
|
|
||||||
int c = garbledCode[i + 2];
|
|
||||||
xbBuilder.Append(Calculation(a, b, c));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 结果赋值
|
|
||||||
Xb = xbBuilder.ToString();
|
|
||||||
Params = $"{urlParams}&X-Bogus={Xb}";
|
|
||||||
|
|
||||||
return (Params, Xb, _userAgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试代码
|
|
||||||
//public class XBogusTest
|
|
||||||
//{
|
|
||||||
// public static void Main()
|
|
||||||
// {
|
|
||||||
// string ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
|
|
||||||
// var xb = new XBogus(ua);
|
|
||||||
|
|
||||||
// string dyUrlParams = "device_platform=webapp&aid=6383&channel=channel_pc_web&sec_user_id=MS4wLjABAAAAW9FWcqS7RdQAWPd2AA5fL_ilmqsIFUCQ_Iym6Yh9_cUa6ZRqVLjVQSUjlHrfXY1Y&max_cursor=0&locate_query=false&show_live_replay_strategy=1&need_time_list=1&time_list_query=0&whale_cut_token=&cut_version=1&count=18&publish_video_strategy_type=2&pc_client_type=1&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=122.0.0.0&browser_online=true&engine_name=Blink&engine_version=122.0.0.0&os_name=Windows&os_version=10&cpu_core_num=12&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7335414539335222835&msToken=p9Y7fUBuq9DKvAuN27Peml6JbaMqG2ZcXfFiyDv1jcHrCN00uidYqUgSuLsKl1onC-E_n82m-aKKYE0QGEmxIWZx9iueQ6WLbvzPfqnMk4GBAlQIHcDzxb38FLXXQxAm";
|
|
||||||
// string tkUrlParams = "WebIdLastTime=1713796127&abTestVersion=%5Bobject%20Object%5D&aid=1988&appType=t&app_language=zh-Hans&app_name=tiktok_web&browser_name=Mozilla&browser_online=true&browser_platform=Win32&browser_version=5.0%20%28Windows%20NT%2010.0%3B%20Win64%3B%20x64%29%20AppleWebKit%2F537.36%20%28KHTML%2C%20like%20Gecko%29%20Chrome%2F123.0.0.0%20Safari%2F537.36&channel=tiktok_web&device_id=7360698239018452498&odinId=7360698115047851026®ion=TW&tz_name=Asia%2FHong_Kong&uniqueId=rei_toy625";
|
|
||||||
|
|
||||||
// var dyResult = xb.GetXBogus(dyUrlParams);
|
|
||||||
// Console.WriteLine($"Douyin - URL: {dyResult.Params}, X-Bogus: {dyResult.Xb}, UA: {dyResult.UserAgent}");
|
|
||||||
|
|
||||||
// var tkResult = xb.GetXBogus(tkUrlParams);
|
|
||||||
// Console.WriteLine($"TikTok - URL: {tkResult.Params}, X-Bogus: {tkResult.Xb}, UA: {tkResult.UserAgent}");
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user