177 lines
6.7 KiB
C#
177 lines
6.7 KiB
C#
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;
|
||
}
|
||
} |