提交功能

This commit is contained in:
Bob.Song
2026-02-08 16:58:32 +08:00
commit 9dd1e6c278
67 changed files with 4588 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
using Newtonsoft.Json;
namespace ACBuildService;
/// <summary>
/// 抖音 分享文本中的视频数据
/// </summary>
public class DouYinShareRouterData
{
[JsonProperty("loaderData")] public LoaderData LoaderData { get; set; }
}
public class LoaderData
{
[JsonProperty("video_(id)/page")] public VideoIdPage VideoIdPage { get; set; }
}
public class VideoIdPage
{
[JsonProperty("isAutoOpenApp")] public bool IsAutoOpenApp { get; set; }
[JsonProperty("appName")] public string AppName { get; set; }
[JsonProperty("query")] public Query Query { get; set; }
[JsonProperty("isSpider")] public bool IsSpider { get; set; }
[JsonProperty("ua")] public string Ua { get; set; }
[JsonProperty("isVideoOptimize")] public bool IsVideoOptimize { get; set; }
[JsonProperty("commonContext")] public CommonContext CommonContext { get; set; }
[JsonProperty("itemId")] public string ItemId { get; set; }
[JsonProperty("webId")] public string WebId { get; set; }
[JsonProperty("renderInSSR")] public long RenderInSsr { get; set; }
[JsonProperty("host")] public string Host { get; set; }
[JsonProperty("videoInfoRes")] public VideoInfoRes VideoInfoRes { get; set; }
[JsonProperty("darkModeAdaptation")] public bool DarkModeAdaptation { get; set; }
[JsonProperty("lastPath")] public string LastPath { get; set; }
[JsonProperty("isNotSupportWebp")] public bool IsNotSupportWebp { get; set; }
[JsonProperty("serverToken")] public string ServerToken { get; set; }
}
public class CommonContext
{
[JsonProperty("webId")] public string WebId { get; set; }
[JsonProperty("renderInSSR")] public long RenderInSsr { get; set; }
[JsonProperty("appName")] public string AppName { get; set; }
[JsonProperty("query")] public Query Query { get; set; }
[JsonProperty("host")] public string Host { get; set; }
[JsonProperty("isSpider")] public bool IsSpider { get; set; }
[JsonProperty("lastPath")] public string LastPath { get; set; }
[JsonProperty("isNotSupportWebp")] public bool IsNotSupportWebp { get; set; }
[JsonProperty("ua")] public string Ua { get; set; }
}
public class Query
{
[JsonProperty("titleType")] public string TitleType { get; set; }
[JsonProperty("iid")] public string Iid { get; set; }
[JsonProperty("u_code")] public string UCode { get; set; }
[JsonProperty("from_ssr")] public long FromSsr { get; set; }
[JsonProperty("mid")] public string Mid { get; set; }
[JsonProperty("share_sign")] public string ShareSign { get; set; }
[JsonProperty("with_sec_did")] public long WithSecDid { get; set; }
[JsonProperty("from_aid")] public long FromAid { get; set; }
[JsonProperty("from")] public string From { get; set; }
[JsonProperty("region")] public string Region { get; set; }
[JsonProperty("did")] public string Did { get; set; }
[JsonProperty("share_version")] public long ShareVersion { get; set; }
[JsonProperty("ts")] public long Ts { get; set; }
}
public class VideoInfoRes
{
[JsonProperty("status_code")] public long StatusCode { get; set; }
[JsonProperty("filter_list")] public List<object> FilterList { get; set; }
[JsonProperty("item_list")] public List<ItemList> ItemList { get; set; }
[JsonProperty("extra")] public Extra Extra { get; set; }
}
public class Extra
{
[JsonProperty("now")] public long Now { get; set; }
}
public class ItemList
{
[JsonProperty("video")] public Video Video { get; set; }
[JsonProperty("music")] public Music Music { get; set; }
[JsonProperty("create_time")] public long CreateTime { get; set; }
[JsonProperty("author")] public Author Author { get; set; }
[JsonProperty("risk_infos")] public RiskInfos RiskInfos { get; set; }
[JsonProperty("mix_info")] public MixInfo MixInfo { get; set; }
[JsonProperty("aweme_id")] public string AwemeId { get; set; }
[JsonProperty("group_id_str")] public string GroupIdStr { get; set; }
[JsonProperty("text_extra")] public List<TextExtra> TextExtra { get; set; }
[JsonProperty("aweme_type")] public long AwemeType { get; set; }
[JsonProperty("desc")] public string Desc { get; set; }
[JsonProperty("statistics")] public Statistics Statistics { get; set; }
}
public class Author
{
[JsonProperty("avatar_medium")] public AvatarMedium AvatarMedium { get; set; }
[JsonProperty("unique_id")] public string UniqueId { get; set; }
[JsonProperty("sec_uid")] public string SecUid { get; set; }
[JsonProperty("signature")] public string Signature { get; set; }
[JsonProperty("mplatform_followers_count")]
public long MplatformFollowersCount { get; set; }
[JsonProperty("avatar_thumb")] public AvatarMedium AvatarThumb { get; set; }
[JsonProperty("follow_status")] public long FollowStatus { get; set; }
[JsonProperty("short_id")] public string ShortId { get; set; }
[JsonProperty("following_count")] public long FollowingCount { get; set; }
[JsonProperty("nickname")] public string Nickname { get; set; }
[JsonProperty("favoriting_count")] public long FavoritingCount { get; set; }
}
public class AvatarMedium
{
[JsonProperty("url_list")] public List<Uri> UrlList { get; set; }
[JsonProperty("uri")] public string Uri { get; set; }
}
public class MixInfo
{
[JsonProperty("mix_id")] public string MixId { get; set; }
[JsonProperty("cover_url")] public AvatarMedium CoverUrl { get; set; }
[JsonProperty("next_info")] public NextInfo NextInfo { get; set; }
[JsonProperty("statis")] public Statis Statis { get; set; }
[JsonProperty("create_time")] public long CreateTime { get; set; }
[JsonProperty("extra")] public string Extra { get; set; }
[JsonProperty("mix_name")] public string MixName { get; set; }
[JsonProperty("status")] public Status Status { get; set; }
[JsonProperty("desc")] public string Desc { get; set; }
}
public class NextInfo
{
[JsonProperty("cover_url")] public AvatarMedium CoverUrl { get; set; }
[JsonProperty("mix_name")] public string MixName { get; set; }
[JsonProperty("desc")] public string Desc { get; set; }
}
public class Statis
{
[JsonProperty("collect_vv")] public long CollectVv { get; set; }
[JsonProperty("current_episode")] public long CurrentEpisode { get; set; }
[JsonProperty("play_vv")] public long PlayVv { get; set; }
[JsonProperty("updated_to_episode")] public long UpdatedToEpisode { get; set; }
}
public class Status
{
[JsonProperty("is_collected")] public long IsCollected { get; set; }
[JsonProperty("status")] public long StatusStatus { get; set; }
}
public class Music
{
[JsonProperty("duration")] public long Duration { get; set; }
[JsonProperty("author")] public string Author { get; set; }
[JsonProperty("cover_medium")] public AvatarMedium CoverMedium { get; set; }
[JsonProperty("mid")] public string Mid { get; set; }
[JsonProperty("title")] public string Title { get; set; }
[JsonProperty("cover_hd")] public AvatarMedium CoverHd { get; set; }
[JsonProperty("cover_large")] public AvatarMedium CoverLarge { get; set; }
[JsonProperty("cover_thumb")] public AvatarMedium CoverThumb { get; set; }
[JsonProperty("status")] public long Status { get; set; }
}
public class RiskInfos
{
[JsonProperty("warn")] public bool Warn { get; set; }
[JsonProperty("type")] public long Type { get; set; }
[JsonProperty("content")] public string Content { get; set; }
[JsonProperty("reflow_unplayable")] public long ReflowUnplayable { get; set; }
}
public class Statistics
{
[JsonProperty("comment_count")] public long CommentCount { get; set; }
[JsonProperty("share_count")] public long ShareCount { get; set; }
[JsonProperty("aweme_id")] public string AwemeId { get; set; }
[JsonProperty("digg_count")] public long DiggCount { get; set; }
[JsonProperty("play_count")] public long PlayCount { get; set; }
[JsonProperty("collect_count")] public long CollectCount { get; set; }
}
public class TextExtra
{
[JsonProperty("hashtag_id")] public long HashtagId { get; set; }
[JsonProperty("start")] public long Start { get; set; }
[JsonProperty("end")] public long End { get; set; }
[JsonProperty("type")] public long Type { get; set; }
[JsonProperty("hashtag_name")] public string HashtagName { get; set; }
}
public class Video
{
[JsonProperty("cover")] public AvatarMedium Cover { get; set; }
[JsonProperty("play_addr")] public AvatarMedium PlayAddr { get; set; }
[JsonProperty("width")] public long Width { get; set; }
[JsonProperty("height")] public long Height { get; set; }
}

View File

@@ -0,0 +1,177 @@
using System.ComponentModel;
using System.Text.RegularExpressions;
using Downloader;
using Newtonsoft.Json;
using RestSharp;
namespace ACBuildService;
public class DouYin : DownloadPlatforms
{
private static readonly Dictionary<string, string> _defaultHeaders = new()
{
{
"User-Agent",
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
},
{ "sec-fetch-site", "same-origin" },
{ "sec-fetch-mode", "cors" },
{ "sec-fetch-dest", "empty" },
{ "sec-ch-ua-platform", "Windows" },
{ "sec-ch-ua-mobile", "?0" },
{ "sec-ch-ua", "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"" },
{ "referer", "https://www.douyin.com/?recommend=1" },
{ "priority", "u=1, i" },
{ "pragma", "no-cache" },
{ "cache-control", "no-cache" },
{ "accept-language", "zh-CN,zh;q=0.9,en;q=0.8" },
{
"accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
},
{ "dnt", "1" }
};
public async override Task<string> ExtractUrlAsync(string text)
{
Log.Info($"开始解析抖音链接 {text}");
return Regex.Match(text, @"https?://[^\s]+").Value;
}
public override async Task<VideoModel> ParseShare(string DownloadUrlText)
{
if (!DownloadUrlText.Contains("https://"))
{
Log.Error("请输入正确的分享链接");
return null;
}
var downloadUrl = await ExtractUrlAsync(DownloadUrlText);
VideoModel Data = await ExtractVideoDataAsync(downloadUrl);
Data.ShareId = downloadUrl.Replace("https://v.douyin.com/", "").Replace("/", "");
return Data;
}
/// <summary>
/// 根据链接 https://v.douyin.com/ircqoExo/ 解析出页面中的数据
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public override Task<VideoModel> ExtractVideoDataAsync(string url)
{
try
{
Log.Info($"开始解析链接 {url}");
// 创建RestClient
RestClient client = new(url);
// 创建请求对象
RestRequest request = new();
// 设置 User-Agent 模拟手机浏览器
request.AddHeaders(_defaultHeaders);
// 发送请求并获取响应
var response = client.Execute(request);
if (!response.IsSuccessful) throw new HttpRequestException("request is fail");
var content = response.Content;
Log.Info($"开始解析响应内容 {content}");
if (content is null) throw new InvalidDataException("content is null");
const string routerDataPattern = @"_ROUTER_DATA\s*=\s*(\{.*?\})<";
var matchJson = Regex.Match(content, routerDataPattern);
Log.Info($"开始解析匹配到的json {matchJson}");
if (matchJson.Groups.Count < 2) throw new InvalidDataException("未匹配到合法的数据matchJson.Groups.Count < 2");
var videoJson = matchJson.Groups[1].Value;
Log.Info($"开始解析匹配到的json {videoJson}");
// 反序列化JSON字符串为C#对象
var videoData = JsonConvert.DeserializeObject<DouYinShareRouterData>(videoJson);
if (videoData is null) throw new InvalidDataException("JSON解析数据为空请检查分享链接是否正确如有更多问题请查看日志");
var videoInfoData = videoData.LoaderData.VideoIdPage.VideoInfoRes.ItemList.First();
var video = new VideoModel
{
Platform = ShortVideoPlatformEnum.DouYin,
VideoId = videoInfoData.AwemeId,
AuthorName = videoInfoData.Author.Nickname,
UniqueId = videoInfoData.Author.UniqueId == ""
? videoInfoData.Author.ShortId
: videoInfoData.Author.UniqueId,
AuthorAvatar = videoInfoData.Author.AvatarThumb.UrlList.First().ToString(),
Title = videoInfoData.Author.Signature,
Cover = videoInfoData.Video.Cover.UrlList.Last().ToString(),
Mp3Url = "",
CreatedTime =
DateTimeOffset.FromUnixTimeSeconds(videoInfoData.CreateTime)
.ToString("yyyy-MM-dd HH:mm:ss"),
Desc = videoInfoData.Desc,
Duration = "",
DiggCount = videoInfoData.Statistics.DiggCount,
CollectCount = videoInfoData.Statistics.CollectCount,
CommentCount = videoInfoData.Statistics.CommentCount,
ShareCount = videoInfoData.Statistics.ShareCount
};
switch (videoInfoData.AwemeType)
{
case 2:
video.VideoUrl = videoInfoData.Video.Cover.UrlList.First().ToString();
break;
default:
video.VideoUrl = videoInfoData.Video.PlayAddr.UrlList.First().ToString().Replace("playwm", "play");
break;
}
if (video.VideoUrl.Contains("ratio=720p"))
{
video.VideoUrl = video.VideoUrl.Replace("ratio=720p", "ratio=1080p");
}
return Task.FromResult(video);
}
catch (Exception e)
{
Log.Error(e.Message);
throw;
}
}
public override async Task<bool> DownloadAsync(string url, string savePath, string fileName,
EventHandler<DownloadProgressChangedEventArgs> onProgressChanged,
EventHandler<AsyncCompletedEventArgs> onProgressCompleted)
{
DownloadConfiguration downloadConfiguration = new()
{
ChunkCount = 8, // Download in 8 chunks (increase for larger files)
MaxTryAgainOnFailure = 5,
Timeout = 10000, // 10 seconds timeout for each request
RequestConfiguration = new RequestConfiguration
{
UserAgent = _defaultHeaders.GetValueOrDefault("User-Agent")
}
};
DownloadService downloader = new(downloadConfiguration);
downloader.DownloadProgressChanged += onProgressChanged;
downloader.DownloadFileCompleted += onProgressCompleted;
try
{
await downloader.DownloadFileTaskAsync(url, Path.Combine(savePath, fileName));
return true;
}
catch (Exception ex)
{
Log.Info($"Download failed: {ex}");
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
using System.ComponentModel;
using Downloader;
namespace ACBuildService;
public abstract class DownloadPlatforms
{
public abstract Task<string> ExtractUrlAsync(string text);
public abstract Task<VideoModel> ParseShare(string DownloadUrlText);
public abstract Task<VideoModel> ExtractVideoDataAsync(string url);
public abstract Task<bool> DownloadAsync(string url, string savePath, string fileName,
EventHandler<DownloadProgressChangedEventArgs> onProgressChanged,
EventHandler<AsyncCompletedEventArgs> onProgressCompleted);
}

View File

@@ -0,0 +1,53 @@
using System.ComponentModel;
using Downloader;
namespace ACBuildService;
public class KuaiShou : DownloadPlatforms
{
private static readonly Dictionary<string, string> _defaultHeaders = new()
{
{
"User-Agent",
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
},
{ "sec-fetch-site", "same-origin" },
{ "sec-fetch-mode", "cors" },
{ "sec-fetch-dest", "empty" },
{ "sec-ch-ua-platform", "Windows" },
{ "sec-ch-ua-mobile", "?0" },
{ "sec-ch-ua", "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"" },
{ "referer", "https://www.douyin.com/?recommend=1" },
{ "priority", "u=1, i" },
{ "pragma", "no-cache" },
{ "cache-control", "no-cache" },
{ "accept-language", "zh-CN,zh;q=0.9,en;q=0.8" },
{
"accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
},
{ "dnt", "1" }
};
public override Task<string> ExtractUrlAsync(string text)
{
throw new NotImplementedException();
}
public override Task<VideoModel> ParseShare(string DownloadUrlText)
{
throw new NotImplementedException();
}
public override Task<VideoModel> ExtractVideoDataAsync(string url)
{
throw new NotImplementedException();
}
public override Task<bool> DownloadAsync(string url, string savePath, string fileName,
EventHandler<DownloadProgressChangedEventArgs> onProgressChanged,
EventHandler<AsyncCompletedEventArgs> onProgressCompleted)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,151 @@
using System.Threading.Channels;
namespace ACBuildService;
public static class VideoDownload
{
private static readonly Channel<string> _queue = Channel.CreateUnbounded<string>();
private static readonly CancellationTokenSource _cts = new CancellationTokenSource();
private static readonly TimeSpan _interval = TimeSpan.FromSeconds(20);
static VideoDownload()
{
Task.Run(ProcessQueueAsync);
}
public static void Add(string shareText)
{
_queue.Writer.TryWrite(shareText);
}
private static async Task ProcessQueueAsync()
{
var timer = new PeriodicTimer(_interval);
try
{
while (await _queue.Reader.WaitToReadAsync(_cts.Token) &&
await timer.WaitForNextTickAsync(_cts.Token))
{
if (_queue.Reader.TryRead(out var shareText))
{
// 这里开始处理任务,这个处理过程本身的时间不计入间隔
// PeriodicTimer 会保证两次 WaitForNextTickAsync 之间间隔10秒
await ProcessVideoDownloadAsync(shareText);
}
}
}
catch (OperationCanceledException)
{
// 正常取消
}
}
private static async Task ProcessVideoDownloadAsync(string shareText)
{
try
{
var service = GetService(shareText);
if (service != null)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 开始处理: {shareText}");
await DownloadVideoAsync(service, shareText);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 处理完成: {shareText}");
}
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 处理失败: {shareText}, 错误: {ex.Message}");
}
}
// 模拟下载视频的方法
private static async Task DownloadVideoAsync(DownloadPlatforms service, string shareText)
{
//开始解析数据
var videoModel = await service.ParseShare(shareText);
if (videoModel == null) return;
var rootPath = $"{HttpService.BaseRootPath}videos/";
var t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var fileName = $"{t}.mp4";
var filePath = Path.Combine(rootPath, fileName);
if (File.Exists(filePath))
{
Log.Error("文件已存在");
return;
}
var ret = await service.DownloadAsync(videoModel.VideoUrl, rootPath, fileName, null, null);
if (ret)
{
var dbItem = new VideoTable
{
SourcePath = shareText,
FilePath = fileName,
AuthorName = videoModel.AuthorName,
Title = videoModel.Title,
Desc = videoModel.Desc,
Duration = videoModel.Duration,
Like = (int)videoModel.DiggCount,
Collect = (int)videoModel.CollectCount,
Message = (int)videoModel.CommentCount,
Share = (int)videoModel.ShareCount,
CreateTime = DateTime.Now
};
DB.Main.Insertable(dbItem).ExecuteCommand();
}
//解析数据后等待一会
await Task.Delay(3000);
}
#region Service
private static Dictionary<ShortVideoPlatformEnum, DownloadPlatforms> _services =
new Dictionary<ShortVideoPlatformEnum, DownloadPlatforms>();
private static DownloadPlatforms GetService(string text)
{
var platform = GetPlatform(text);
if (_services.TryGetValue(platform, out var value))
{
return value;
}
if (platform == ShortVideoPlatformEnum.DouYin)
{
value = new DouYin();
}
else if (platform == ShortVideoPlatformEnum.KuaiShou)
{
value = new KuaiShou();
}
if (value != null)
{
_services[platform] = value;
}
return value;
}
private static ShortVideoPlatformEnum GetPlatform(string text)
{
if (text.Contains("douyin"))
{
return ShortVideoPlatformEnum.DouYin;
}
if (text.Contains("kuaishou"))
{
return ShortVideoPlatformEnum.KuaiShou;
}
return ShortVideoPlatformEnum.None;
}
#endregion
}

View File

@@ -0,0 +1,200 @@
// using System.ComponentModel;
// using System.Text.RegularExpressions;
// using Downloader;
// using Newtonsoft.Json;
// using RestSharp;
//
// namespace ACBuildService;
//
// public class VideoDownloadService
// {
// private static readonly Dictionary<string, string> _defaultHeaders = new()
// {
// {
// "User-Agent",
// "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
// },
// { "sec-fetch-site", "same-origin" },
// { "sec-fetch-mode", "cors" },
// { "sec-fetch-dest", "empty" },
// { "sec-ch-ua-platform", "Windows" },
// { "sec-ch-ua-mobile", "?0" },
// { "sec-ch-ua", "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"" },
// { "referer", "https://www.douyin.com/?recommend=1" },
// { "priority", "u=1, i" },
// { "pragma", "no-cache" },
// { "cache-control", "no-cache" },
// { "accept-language", "zh-CN,zh;q=0.9,en;q=0.8" },
// {
// "accept",
// "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
// },
// { "dnt", "1" }
// };
//
// public async Task<VideoModel> ParseShare(string DownloadUrlText)
// {
// if (!DownloadUrlText.Contains("https://"))
// {
// // throw new ValidationException("请输入正确的分享链接");
// Log.Error("请输入正确的分享链接");
// return null;
// }
//
// VideoModel Data = null;
//
// if (DownloadUrlText.Contains(ShortVideoPlatformEnum.DouYin.ToString().ToLower()))
// {
// var downloadUrl = await ExtractUrlAsync(DownloadUrlText);
// Data = await ExtractVideoDataAsync(downloadUrl);
// Data.ShareId = downloadUrl.Replace("https://v.douyin.com/", "").Replace("/", "");
// }
//
// // else if (DownloadUrlText.Contains(ShortVideoPlatformEnum.KuaiShou.ToString().ToLower()))
// // {
// // var downloadUrl = await _kuaiShortVideoService.ExtractUrlAsync(DownloadUrlText);
// // Data = await _kuaiShortVideoService.ExtractVideoDataAsync(downloadUrl);
// // }
// // else
// // {
// // throw new ValidationException("暂不支持该平台");
// // }
// // Log.Info($"解析结果 data={JSON.Serialize(Data)}");
// return Data;
// }
//
// /// <summary>
// /// 解析dy分享中的文本 6.17 05/18 U@L.wf fbn:/ 悬疑推理:亡者和自己的手机上午一同下葬,下午却给警察发来短信 本期的故事,来自于高分推理神剧《天堂岛疑云》中的谜案《亡灵的短信》。# 悬疑推理 # 每日推荐电影
// /// # 一剪到底 https://v.douyin.com/ircqoExo/ 复制此链接打开Dou音搜索直接观看视频
// /// https://v.douyin.com/ircqoExo/
// /// </summary>
// /// <param name="text"></param>
// /// <returns></returns>
// public async Task<string> ExtractUrlAsync(string text)
// {
// Log.Info($"开始解析抖音链接 {text}");
// return Regex.Match(text, @"https?://[^\s]+").Value;
// }
//
// /// <summary>
// /// 根据链接 https://v.douyin.com/ircqoExo/ 解析出页面中的数据
// /// </summary>
// /// <param name="url"></param>
// /// <returns></returns>
// public Task<VideoModel> ExtractVideoDataAsync(string url)
// {
// try
// {
// Log.Info($"开始解析链接 {url}");
// // 创建RestClient
// RestClient client = new(url);
//
// // 创建请求对象
// RestRequest request = new();
//
// // 设置 User-Agent 模拟手机浏览器
// request.AddHeaders(_defaultHeaders);
//
// // 发送请求并获取响应
// var response = client.Execute(request);
// if (!response.IsSuccessful) throw new HttpRequestException("request is fail");
//
// var content = response.Content;
// Log.Info($"开始解析响应内容 {content}");
// if (content is null) throw new InvalidDataException("content is null");
//
// const string routerDataPattern = @"_ROUTER_DATA\s*=\s*(\{.*?\})<";
//
// var matchJson = Regex.Match(content, routerDataPattern);
//
// Log.Info($"开始解析匹配到的json {matchJson}");
// if (matchJson.Groups.Count < 2) throw new InvalidDataException("未匹配到合法的数据matchJson.Groups.Count < 2");
//
// var videoJson = matchJson.Groups[1].Value;
// Log.Info($"开始解析匹配到的json {videoJson}");
// // 反序列化JSON字符串为C#对象
// var videoData = JsonConvert.DeserializeObject<DouYinShareRouterData>(videoJson);
//
// if (videoData is null) throw new InvalidDataException("JSON解析数据为空请检查分享链接是否正确如有更多问题请查看日志");
//
// var videoInfoData = videoData.LoaderData.VideoIdPage.VideoInfoRes.ItemList.First();
//
// var video = new VideoModel
// {
// Platform = ShortVideoPlatformEnum.DouYin,
// VideoId = videoInfoData.AwemeId,
// AuthorName = videoInfoData.Author.Nickname,
// UniqueId = videoInfoData.Author.UniqueId == ""
// ? videoInfoData.Author.ShortId
// : videoInfoData.Author.UniqueId,
// AuthorAvatar = videoInfoData.Author.AvatarThumb.UrlList.First().ToString(),
// Title = videoInfoData.Author.Signature,
// Cover = videoInfoData.Video.Cover.UrlList.Last().ToString(),
// Mp3Url = "",
// CreatedTime =
// DateTimeOffset.FromUnixTimeSeconds(videoInfoData.CreateTime)
// .ToString("yyyy-MM-dd HH:mm:ss"),
// Desc = videoInfoData.Desc,
// Duration = "",
// DiggCount = videoInfoData.Statistics.DiggCount,
// CollectCount = videoInfoData.Statistics.CollectCount,
// CommentCount = videoInfoData.Statistics.CommentCount,
// ShareCount = videoInfoData.Statistics.ShareCount
// };
// switch (videoInfoData.AwemeType)
// {
// case 2:
// video.VideoUrl = videoInfoData.Video.Cover.UrlList.First().ToString();
// break;
// default:
// video.VideoUrl = videoInfoData.Video.PlayAddr.UrlList.First().ToString().Replace("playwm", "play");
// break;
// }
//
// if (video.VideoUrl.Contains("ratio=720p"))
// {
// video.VideoUrl = video.VideoUrl.Replace("ratio=720p", "ratio=1080p");
// }
//
// return Task.FromResult(video);
// }
// catch (Exception e)
// {
// Log.Error(e.Message);
// throw;
// }
// }
//
//
// public async Task DownloadAsync(string url, string savePath, string fileName,
// EventHandler<DownloadProgressChangedEventArgs> onProgressChanged,
// EventHandler<AsyncCompletedEventArgs> onProgressCompleted
// )
// {
// DownloadConfiguration downloadConfiguration = new()
// {
// ChunkCount = 8, // Download in 8 chunks (increase for larger files)
// MaxTryAgainOnFailure = 5,
// Timeout = 10000, // 10 seconds timeout for each request
// RequestConfiguration = new RequestConfiguration
// {
// UserAgent = _defaultHeaders.GetValueOrDefault("User-Agent")
// }
// };
//
// DownloadService downloader = new(downloadConfiguration);
//
// downloader.DownloadProgressChanged += onProgressChanged;
// downloader.DownloadFileCompleted += onProgressCompleted;
//
// try
// {
// await downloader.DownloadFileTaskAsync(url, savePath + fileName);
// }
// catch (Exception ex)
// {
// Log.Info($"Download failed: {ex}");
// throw;
// }
// }
// }

View File

@@ -0,0 +1,70 @@
namespace ACBuildService;
public enum ShortVideoPlatformEnum
{
None,
// 抖音
DouYin,
// 快手
KuaiShou,
/// <summary>
/// B站
/// </summary>
Bilibili,
}
[Serializable]
public class VideoModel
{
// 短视频所属的平台
public ShortVideoPlatformEnum Platform { get; set; }
// 视频ID
public string VideoId { get; set; }
// 作者
public string AuthorName { get; set; }
// Unique Id
public string UniqueId { get; set; }
// 头像
public string AuthorAvatar { get; set; }
// 视频标题
public string Title { get; set; }
// 封面
public string Cover { get; set; }
// 收藏数
public long CollectCount { get; set; }
// 点赞数
public long DiggCount { get; set; }
// 分享数
public long ShareCount { get; set; }
// 评论数
public long CommentCount { get; set; }
// 视频地址
public string VideoUrl { get; set; }
// 音频地址
public string Mp3Url { get; set; }
// 创建时间
public string CreatedTime { get; set; }
// 视频描述
public string Desc { get; set; }
// 视频时长
public string Duration { get; set; }
public string ShareId { get; set; }
}