This commit is contained in:
lijianyou
2025-09-30 15:00:32 +08:00
parent 1b262f06b8
commit b4e759cf69
202 changed files with 22035 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
using Newtonsoft.Json;
using System.Text;
using System.Web;
namespace dy.net.utils
{
public class BogusManager
{
/// <summary>
/// 字符串方法生成X-Bogus参数
/// </summary>
/// <param name="endpoint">请求端点</param>
/// <param name="userAgent">用户代理</param>
/// <returns>带X-Bogus的完整端点</returns>
public static string XbStr2Endpoint(string endpoint, string userAgent)
{
try
{
var (finalEndpoint, _) = XBogusManager.GetXBogus(endpoint, userAgent);
return finalEndpoint;
}
catch (Exception ex)
{
throw new InvalidOperationException($"生成X-Bogus失败: {ex.Message}");
}
}
/// <summary>
/// 字典方法生成X-Bogus参数
/// </summary>
/// <param name="baseEndpoint">基础端点</param>
/// <param name="parameters">请求参数</param>
/// <param name="userAgent">用户代理</param>
/// <returns>带X-Bogus的完整端点</returns>
public static string XbModel2Endpoint(string baseEndpoint, Dictionary<string, string> parameters, string userAgent)
{
if (parameters == null)
throw new ArgumentException("参数必须是字典类型");
// 构建参数字符串
var paramStr = string.Join("&", parameters.Select(kv => $"{kv.Key}={HttpUtility.UrlEncode(kv.Value)}"));
try
{
var (_, xbValue) = XBogusManager.GetXBogus(paramStr, userAgent);
// 检查基础端点是否已有查询参数
var separator = baseEndpoint.Contains("?") ? "&" : "?";
return $"{baseEndpoint}{separator}{paramStr}&X-Bogus={xbValue}";
}
catch (Exception ex)
{
throw new InvalidOperationException($"生成X-Bogus失败: {ex.Message}");
}
}
}
public static class XBogusManager
{
/// <summary>
/// 生成X-Bogus签名
/// </summary>
/// <param name="input">输入字符串(参数或完整URL</param>
/// <param name="userAgent">用户代理</param>
/// <returns>(带签名的URL,签名值)</returns>
public static (string, string) GetXBogus(string input, string userAgent)
{
// X-Bogus核心算法实现(参考公开逆向分析结果)
// 注意:该算法可能随版本更新而变化,需根据实际情况调整
// 1. 提取关键信息
var isUrl = input.Contains("http") || input.Contains("?");
string queryString = isUrl ? HttpUtility.ParseQueryString(new Uri(input).Query).ToString() : input;
// 2. 生成基础哈希
var hash = GenerateBaseHash(queryString, userAgent);
// 3. 应用签名算法
var xbValue = ComputeXBogus(hash, userAgent);
// 4. 构建结果
if (isUrl)
{
var uri = new Uri(input);
var newQuery = string.IsNullOrEmpty(uri.Query)
? $"X-Bogus={xbValue}"
: $"{uri.Query.Substring(1)}&X-Bogus={xbValue}";
return ($"{uri.Scheme}://{uri.Host}{uri.AbsolutePath}?{newQuery}", xbValue);
}
else
{
return ($"X-Bogus={xbValue}", xbValue);
}
}
private static string GenerateBaseHash(string input, string userAgent)
{
// 基础哈希计算逻辑
using (var sha1 = System.Security.Cryptography.SHA1.Create())
{
var bytes = Encoding.UTF8.GetBytes($"{input}_{userAgent}_3.0.0");
var hashBytes = sha1.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
private static string ComputeXBogus(string baseHash, string userAgent)
{
// 核心签名计算(模拟算法,实际需根据最新逆向结果调整)
var time = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
var rand = new Random();
var randStr = new string(Enumerable.Repeat("abcdefghijklmnopqrstuvwxyz0123456789", 6)
.Select(s => s[rand.Next(s.Length)]).ToArray());
// 组合签名
return $"DFSz{time:x8}{randStr}{baseHash.Substring(0, 16)}";
}
}
// 其他辅助类示例
public class TokenManager
{
/// <summary>
/// 生成msToken
/// </summary>
public static async Task<string> GenRealMsTokenAsync()
{
try
{
using HttpClient _httpClient = new HttpClient();
var payload = new
{
magic = 538969122,
version = 1,
dataType = 8,
strData = @"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",
tspFromClient = DateTimeUtil.GetUnixTimestampMilliseconds()
};
var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47");
var response = await _httpClient.PostAsync("https://mssdk.bytedance.com/web/report", content);
response.EnsureSuccessStatusCode();
var sss = await response.Content.ReadAsStringAsync();
//var cookies = response.Headers.GetValues("Set-Cookie");
var msToken = response.Headers.GetValues("X-Ms-Token").FirstOrDefault();
//var msToken = cookies.FirstOrDefault(c => c.StartsWith("msToken="))?.Split(';')[0].Split('=')[1];
if (msToken != null && (msToken.Length != 120 || msToken.Length != 128))
return msToken;
throw new Exception("msToken格式不正确");
}
catch (Exception ex)
{
// 生成虚假token
return GenFalseMsToken();
}
}
public static string GenFalseMsToken()
{
var rand = new Random();
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(chars, 126)
.Select(s => s[rand.Next(s.Length)]).ToArray()) + "==";
}
}
}
+20
View File
@@ -0,0 +1,20 @@
namespace dy.net.utils
{
public class ByteToGbConverter
{
/// <summary>
/// 将字节数转换为GB(采用二进制换算:1GB = 1024^3 Byte
/// </summary>
/// <param name="bytes">字节数(需使用long类型避免大数值溢出)</param>
/// <param name="decimalPlaces">保留的小数位数</param>
/// <returns>转换后的GB值</returns>
public static double ConvertBytesToGb(long bytes, int decimalPlaces = 2)
{
// 1GB = 1024 * 1024 * 1024 = 1073741824 Byte
double gb = (double)bytes / 1073741824;
// 四舍五入保留指定小数位数
return Math.Round(gb, decimalPlaces);
}
}
}
+69
View File
@@ -0,0 +1,69 @@
namespace dy.net.utils
{
public static class CookieValidator
{
/// <summary>
/// 粗略验证Cookie字符串的格式是否合法
/// 核心校验规则:
/// 1. 不为空或纯空白字符串
/// 2. 由分号分隔的键值对组成(键值对格式为 key=value,key不能为空)
/// 3. 键名(key)不包含非法字符(分号、逗号、空格、等号)
/// 注:此方法为粗略验证,不严格遵循RFC 6265标准(如未校验控制字符、长度限制等)
/// </summary>
/// <param name="cookieStr">待验证的Cookie字符串</param>
/// <returns>格式是否合法(true=合法,false=非法)</returns>
public static bool IsRoughlyValidCookieFormat(string cookieStr)
{
// 规则1:Cookie字符串不能为空或纯空白
if (string.IsNullOrWhiteSpace(cookieStr))
{
Serilog.Log.Error("验证失败:Cookie字符串为空或纯空白");
return false;
}
// 按分号分隔Cookie项(处理项前后的空格,过滤空项)
var cookieItems = cookieStr.Split(';')
.Select(item => item.Trim()) // 去除项前后空格(如 "a=b; c=d" → ["a=b", "c=d"]
.Where(item => !string.IsNullOrWhiteSpace(item)) // 过滤空项(如末尾分号导致的空项)
.ToList();
// 若分割后无有效项(如全是分号或空格)
if (!cookieItems.Any())
{
Serilog.Log.Error("验证失败:Cookie字符串仅包含分隔符或空格");
return false;
}
// 定义键名(key)的非法字符(粗略验证,选取最常见的非法字符)
char[] invalidKeyChars = { ';', ',', ' ', '=' };
// 遍历每个Cookie项,验证键值对格式
foreach (var item in cookieItems)
{
// 规则2:每个项必须包含等号(=),且等号不能是第一个字符(保证key非空)
int equalsIndex = item.IndexOf('=');
if (equalsIndex <= 0) // equalsIndex=0 → 以等号开头(key为空);equalsIndex=-1 → 无等号
{
Serilog.Log.Error($"验证失败:Cookie项 [{item}] 缺少等号或键名为空");
return false;
}
// 提取键名(key)并验证
string key = item.Substring(0, equalsIndex).Trim(); // 再次Trim以防key前后有空格(如 " key =value"
// 规则3:键名不能包含非法字符
if (key.Any(c => invalidKeyChars.Contains(c)))
{
Serilog.Log.Error($"验证失败:Cookie项 [{item}] 的键名 [{key}] 包含非法字符(; , 空格 =");
return false;
}
// (可选)粗略验证value:此处不做严格限制,允许value为空(如 "key=")或包含特殊字符
}
// 所有项均通过验证
//Serilog.Log.Error("Cookie格式粗略验证通过");
return true;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
namespace dy.net.utils
{
public class DateTimeUtil
{
public static DateTime Convert10BitTimestamp(long timestamp)
{
// 1. 定义Unix时间戳的起始时间(1970-01-01 UTC
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// 2. 计算目标时间(UTC时区):起始时间 + 时间戳(秒数)
DateTime utcTime = epoch.AddSeconds(timestamp);
// 3. 转换为本地时间(可选,根据需求选择UTC或本地时间)
DateTime localTime = utcTime.ToLocalTime();
return localTime;
// 4. 格式化为yyyy-MM-dd HH:mm:ss
//return localTime.ToString("yyyy-MM-dd HH:mm:ss");
}
/// <summary>
/// 获取当前Unix时间戳(毫秒级)
/// </summary>
/// <returns>毫秒级时间戳(long类型)</returns>
public static long GetUnixTimestampMilliseconds()
{
DateTimeOffset epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
// 计算当前UTC时间与纪元时间的差值(毫秒)
long timestamp = (long)(DateTimeOffset.UtcNow - epoch).TotalMilliseconds;
return timestamp;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using ClockSnowFlake;
using System.Security.Cryptography;
using System.Text;
namespace dy.net.utils
{
public static class Md5Util
{
public static string JWT_TOKEN_KEY = "dy.net-key-" + IdGener.GetGuid();
public static string Md5(this string inputString)
{
// 将输入字符串转换为字节数组
byte[] inputBytes = Encoding.UTF8.GetBytes(inputString);
// 创建一个 MD5 对象
using MD5 md5 = MD5.Create();
// 计算哈希值
byte[] hashBytes = md5.ComputeHash(inputBytes);
// 将哈希值转换为十六进制字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("x2"));
}
string hashString = sb.ToString();
return hashString;
}
public static string UPLOAD_PATH_PRO = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "upload");
public static string UPLOAD_PATH_DEV = Path.Combine(Directory.GetCurrentDirectory(), "upload");
}
}
+51
View File
@@ -0,0 +1,51 @@
using dy.net.dto;
using System.Xml.Linq;
namespace dy.net.utils
{
/// <summary>
/// NFO文件生成器
/// </summary>
public class NfoFileGenerator
{
/// <summary>
/// 根据视频信息生成NFO文件
/// </summary>
/// <param name="videoInfo">视频信息对象</param>
/// <param name="filePath">保存NFO文件的路径</param>
public static void GenerateNfoFile(VideoNfo videoInfo, string filePath)
{
if (videoInfo == null)
throw new ArgumentNullException(nameof(videoInfo));
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
// 创建根元素
XElement root = new XElement("movie"); // 对于电影使用"movie",电视剧可以使用"tvshow"
// 添加视频信息
if (!string.IsNullOrEmpty(videoInfo.Title))
root.Add(new XElement("title", videoInfo.Title));
if (!string.IsNullOrEmpty(videoInfo.Author))
root.Add(new XElement("author", videoInfo.Author));
if (!string.IsNullOrEmpty(videoInfo.Thumbnail))
root.Add(new XElement("thumb", new XAttribute("aspect", "poster"), videoInfo.Thumbnail));
if (!string.IsNullOrEmpty(videoInfo.Poster))
root.Add(new XElement("fanart",
new XElement("thumb", videoInfo.Poster)
));
// 创建XDocument并保存
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
root
);
doc.Save(filePath);
}
}
}
+128
View File
@@ -0,0 +1,128 @@
using dy.net.dto;
using System.Xml.Linq;
namespace dy.net.utils
{
/// <summary>
/// NFO文件生成器,负责将VideoInfo对象转换为XML格式的NFO文件
/// </summary>
public class NfoGenerator
{
/// <summary>
/// 生成视频NFO文件
/// </summary>
/// <param name="videoInfo">视频信息对象,包含所有需要写入NFO的元数据</param>
/// <param name="outputPath">输出文件的完整路径,包括文件名</param>
public void GenerateNfoFile(VideoNFOInfo videoInfo, string outputPath)
{
try
{
// 创建根元素,电影用"movie",电视剧集用"episodedetails",电视节目用"tvshow"
XElement root = new XElement("movie");
// 添加基本信息
if (!string.IsNullOrEmpty(videoInfo.Title))
root.Add(new XElement("title", videoInfo.Title));
if (!string.IsNullOrEmpty(videoInfo.OriginalTitle))
root.Add(new XElement("originaltitle", videoInfo.OriginalTitle));
if (!string.IsNullOrEmpty(videoInfo.SortTitle))
root.Add(new XElement("sorttitle", videoInfo.SortTitle));
if (videoInfo.Year > 0)
root.Add(new XElement("year", videoInfo.Year));
if (!string.IsNullOrEmpty(videoInfo.Plot))
root.Add(new XElement("plot", videoInfo.Plot));
if (!string.IsNullOrEmpty(videoInfo.Outline))
root.Add(new XElement("outline", videoInfo.Outline));
if (!string.IsNullOrEmpty(videoInfo.Tagline))
root.Add(new XElement("tagline", videoInfo.Tagline));
// 添加人员信息
if (!string.IsNullOrEmpty(videoInfo.Director))
root.Add(new XElement("director", videoInfo.Director));
// 添加演员
foreach (var actor in videoInfo.Actors)
{
root.Add(new XElement("actor",
new XElement("name", actor)
));
}
// 添加编剧
foreach (var writer in videoInfo.Writers)
{
root.Add(new XElement("writer", writer));
}
// 添加媒体信息
if (!string.IsNullOrEmpty(videoInfo.Genre))
root.Add(new XElement("genre", videoInfo.Genre));
if (videoInfo.Rating > 0)
root.Add(new XElement("rating", videoInfo.Rating));
if (videoInfo.Votes > 0)
root.Add(new XElement("votes", videoInfo.Votes));
if (!string.IsNullOrEmpty(videoInfo.Studio))
root.Add(new XElement("studio", videoInfo.Studio));
if (videoInfo.Premiered.HasValue)
root.Add(new XElement("premiered", videoInfo.Premiered.Value.ToString("yyyy-MM-dd")));
if (!string.IsNullOrEmpty(videoInfo.Runtime))
root.Add(new XElement("runtime", videoInfo.Runtime));
// 添加文件信息
if (!string.IsNullOrEmpty(videoInfo.FileName))
root.Add(new XElement("filenameandpath", videoInfo.FileName));
if (videoInfo.FileSize > 0)
root.Add(new XElement("filesize", videoInfo.FileSize));
// 添加新增字段
if (!string.IsNullOrEmpty(videoInfo.Country))
root.Add(new XElement("country", videoInfo.Country));
if (!string.IsNullOrEmpty(videoInfo.Language))
root.Add(new XElement("language", videoInfo.Language));
if (!string.IsNullOrEmpty(videoInfo.VideoCodec))
root.Add(new XElement("codec", videoInfo.VideoCodec));
if (!string.IsNullOrEmpty(videoInfo.AudioCodec))
root.Add(new XElement("audiocodec", videoInfo.AudioCodec));
if (!string.IsNullOrEmpty(videoInfo.Resolution))
root.Add(new XElement("resolution", videoInfo.Resolution));
// 创建文档并保存
XDocument doc = new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"),
root
);
// 确保目录存在
var directory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
doc.Save(outputPath);
Console.WriteLine($"NFO文件已生成: {outputPath}");
}
catch (Exception ex)
{
Console.WriteLine($"生成NFO文件时出错: {ex.Message}");
}
}
}
}