diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4c7b22..cde6449 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# 更新日志
+* `2021/06/12` v1.4.0
+ 1. [修复] 视频无封面时崩溃的问题。
+ 2. [优化] 视频分区代码与图标。
+ 3. [优化] 屏蔽频道中的失效视频。
+ 4. [优化] 二维码登录逻辑。
+ 5. [新增] 弹幕下载(借鉴并重写于项目 [niconvert](https://github.com/muzuiget/niconvert))。
+ 6. [新增] 弹幕样式设置。
+ 7. [新增] 下载弹幕、下载封面的选项。
+
* `2021/04/27` v1.3.8
1. [修复] FLV视频只有1个分段时下载失败的问题。
2. [修复] UP主视频页面闪退的问题。
diff --git a/README.md b/README.md
index e412790..1938d19 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
- [x] 支持视频、番剧、剧集、电影、课程下载
- [x] **支持用户收藏夹、订阅、稍后再看、历史记录下载**
- [x] ~~**支持港澳台番剧下载,解除地区限制**~~
+- [x] 支持弹幕下载、样式设置
- [x] 支持封面下载
- [x] 支持断点续传
- [x] 支持Aria2c
@@ -60,11 +61,14 @@
[全部更新日志](https://github.com/FlySelfLog/downkyi/blob/main/CHANGELOG.md)
-* `2021/04/27` v1.3.8
- 1. [修复] FLV视频只有1个分段时下载失败的问题。
- 2. [修复] UP主视频页面闪退的问题。
- 3. [修复] 电视剧图标丢失的问题。
- 4. [新增] UP主视频页面下载全部视频的按钮。
+* `2021/06/12` v1.4.0
+ 1. [修复] 视频无封面时崩溃的问题。
+ 2. [优化] 视频分区代码与图标。
+ 3. [优化] 屏蔽频道中的失效视频。
+ 4. [优化] 二维码登录逻辑。
+ 5. [新增] 弹幕下载(借鉴并重写于项目 [niconvert](https://github.com/muzuiget/niconvert))。
+ 6. [新增] 弹幕样式设置。
+ 7. [新增] 下载弹幕、下载封面的选项。
## 下载
diff --git a/src/Core/Common.cs b/src/Core/Common.cs
new file mode 100644
index 0000000..61ac6ab
--- /dev/null
+++ b/src/Core/Common.cs
@@ -0,0 +1,440 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace Core
+{
+ public static class Common
+ {
+ // 配置文件所在路径
+ public static readonly string ConfigPath = "./Config/";
+
+ // 日志、历史等文件所在路径
+ public static readonly string RecordPath = "./Config/";
+
+ ///
+ /// 判断字符串是否以http或https开头,且域名为bilibili.com
+ ///
+ ///
+ ///
+ public static bool IsUrl(string input)
+ {
+ if (input.StartsWith("http://") || input.StartsWith("https://"))
+ {
+ if (input.Contains("www.bilibili.com")) { return true; }
+ else { return false; }
+ }
+ else { return false; }
+ }
+
+
+ ///
+ /// 判断是否为avid
+ ///
+ ///
+ ///
+ public static bool IsAvid(string input)
+ {
+ if (input.StartsWith("av"))
+ {
+ bool isInt = Regex.IsMatch(input.Remove(0, 2), @"^\d+$");
+ return isInt;
+ }
+ return false;
+ }
+
+ ///
+ /// 判断是否为bvid
+ ///
+ ///
+ ///
+ public static bool IsBvid(string input)
+ {
+ if (input.StartsWith("BV") && input.Length == 12)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 判断是否是用户空间的url
+ ///
+ ///
+ ///
+ public static bool IsUserSpaceUrl(string input)
+ {
+ if (input.StartsWith("http://") || input.StartsWith("https://"))
+ {
+ if (input.Contains("space.bilibili.com"))
+ {
+ return true;
+ }
+ else { return false; }
+ }
+ else { return false; }
+ }
+
+ ///
+ /// 判断是否是用户id
+ ///
+ ///
+ ///
+ public static bool IsUserId(string input)
+ {
+ string inputLower = input.ToLower();
+ if (inputLower.StartsWith("uid:"))
+ {
+ string uid = inputLower.TrimStart(new char[] { 'u', 'i', 'd', ':' });
+ return IsInt(uid);
+ }
+ else if (inputLower.StartsWith("uid"))
+ {
+ string uid = inputLower.TrimStart(new char[] { 'u', 'i', 'd' });
+ return IsInt(uid);
+ }
+ else { return false; }
+ }
+
+ ///
+ /// 获取用户id
+ ///
+ ///
+ ///
+ public static long GetUserId(string url)
+ {
+ string[] strList = url.Split('?');
+ string baseUrl = strList[0];
+
+ var match = Regex.Match(baseUrl, @"\d+");
+ if (match.Success)
+ {
+ return GetInt(match.Value);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ ///
+ /// 从url中解析id,包括bvid和番剧的id
+ ///
+ ///
+ ///
+ public static string GetVideoId(string url)
+ {
+ string[] strList = url.Split('?');
+ string baseUrl = strList[0];
+
+ string[] str2List = baseUrl.Split('/');
+ string id = str2List[str2List.Length - 1];
+
+ if (id == "")
+ {
+ // 字符串末尾
+ return str2List[str2List.Length - 2];
+ }
+
+ return id;
+ }
+
+ ///
+ /// 判断是视频还是番剧
+ ///
+ ///
+ ///
+ public static VideoType GetVideoType(string url)
+ {
+ if (url.ToLower().Contains("/video/bv"))
+ {
+ return VideoType.VIDEO;
+ }
+
+ if (url.ToLower().Contains("/video/av"))
+ {
+ return VideoType.VIDEO_AV;
+ }
+
+ if (url.ToLower().Contains("/bangumi/play/ss"))
+ {
+ return VideoType.BANGUMI_SEASON;
+ }
+
+ if (url.ToLower().Contains("/bangumi/play/ep"))
+ {
+ return VideoType.BANGUMI_EPISODE;
+ }
+
+ if (url.ToLower().Contains("/bangumi/media/md"))
+ {
+ return VideoType.BANGUMI_MEDIA;
+ }
+
+ if (url.ToLower().Contains("/cheese/play/ss"))
+ {
+ return VideoType.CHEESE_SEASON;
+ }
+
+ if (url.ToLower().Contains("/cheese/play/ep"))
+ {
+ return VideoType.CHEESE_EPISODE;
+ }
+
+ return VideoType.NONE;
+ }
+
+ ///
+ /// 格式化Duration时间
+ ///
+ ///
+ ///
+ public static string FormatDuration(long duration)
+ {
+ string formatDuration;
+ if (duration / 60 > 0)
+ {
+ long dur = duration / 60;
+ if (dur / 60 > 0)
+ {
+ formatDuration = $"{dur / 60}h{dur % 60}m{duration % 60}s";
+ }
+ else
+ {
+ formatDuration = $"{duration / 60}m{duration % 60}s";
+ }
+ }
+ else
+ {
+ formatDuration = $"{duration}s";
+ }
+ return formatDuration;
+ }
+
+ ///
+ /// 格式化Duration时间,格式为00:00:00
+ ///
+ ///
+ ///
+ public static string FormatDuration2(long duration)
+ {
+ string formatDuration;
+ if (duration / 60 > 0)
+ {
+ long dur = duration / 60;
+ if (dur / 60 > 0)
+ {
+ formatDuration = string.Format("{0:D2}", dur / 60) + ":" + string.Format("{0:D2}", dur % 60) + ":" + string.Format("{0:D2}", duration % 60);
+ }
+ else
+ {
+ formatDuration = "00:" + string.Format("{0:D2}", duration / 60) + ":" + string.Format("{0:D2}", duration % 60);
+ }
+ }
+ else
+ {
+ formatDuration = "00:00:" + string.Format("{0:D2}", duration);
+ }
+ return formatDuration;
+ }
+
+ ///
+ /// 格式化Duration时间,格式为00:00
+ ///
+ ///
+ ///
+ public static string FormatDuration3(long duration)
+ {
+ string formatDuration;
+ if (duration / 60 > 0)
+ {
+ long dur = duration / 60;
+ if (dur / 60 > 0)
+ {
+ formatDuration = string.Format("{0:D2}", dur / 60) + ":" + string.Format("{0:D2}", dur % 60) + ":" + string.Format("{0:D2}", duration % 60);
+ }
+ else
+ {
+ formatDuration = string.Format("{0:D2}", duration / 60) + ":" + string.Format("{0:D2}", duration % 60);
+ }
+ }
+ else
+ {
+ formatDuration = "00:" + string.Format("{0:D2}", duration);
+ }
+ return formatDuration;
+ }
+
+ ///
+ /// 格式化数字,超过10000的数字将单位改为万,超过100000000的数字将单位改为亿,并保留1位小数
+ ///
+ ///
+ ///
+ public static string FormatNumber(long number)
+ {
+ if (number > 99999999)
+ {
+ return (number / 100000000.0f).ToString("F1") + "亿";
+ }
+
+ if (number > 9999)
+ {
+ return (number / 10000.0f).ToString("F1") + "万";
+ }
+ else
+ {
+ return number.ToString();
+ }
+ }
+
+ ///
+ /// 去除非法字符
+ ///
+ ///
+ ///
+ public static string FormatFileName(string originName)
+ {
+ string destName = originName;
+ // Windows中不能作为文件名的字符
+ destName = destName.Replace("\\", " ");
+ destName = destName.Replace("/", " ");
+ destName = destName.Replace(":", " ");
+ destName = destName.Replace("*", " ");
+ destName = destName.Replace("?", " ");
+ destName = destName.Replace("\"", " ");
+ destName = destName.Replace("<", " ");
+ destName = destName.Replace(">", " ");
+ destName = destName.Replace("|", " ");
+
+ // 转义字符
+ destName = destName.Replace("\a", "");
+ destName = destName.Replace("\b", "");
+ destName = destName.Replace("\f", "");
+ destName = destName.Replace("\n", "");
+ destName = destName.Replace("\r", "");
+ destName = destName.Replace("\t", "");
+ destName = destName.Replace("\v", "");
+
+ // 控制字符
+ destName = Regex.Replace(destName, @"\p{C}+", string.Empty);
+
+ return destName.Trim();
+ }
+
+ ///
+ /// 格式化网速
+ ///
+ ///
+ ///
+ public static string FormatSpeed(float speed)
+ {
+ string formatSpeed;
+ if (speed <= 0)
+ {
+ formatSpeed = "0B/s";
+ }
+ else if (speed < 1024)
+ {
+ formatSpeed = speed.ToString("#.##") + "B/s";
+ }
+ else if (speed < 1024 * 1024)
+ {
+ formatSpeed = (speed / 1024).ToString("#.##") + "KB/s";
+ }
+ else
+ {
+ formatSpeed = (speed / 1024 / 1024).ToString("#.##") + "MB/s";
+ }
+ return formatSpeed;
+ }
+
+ ///
+ /// 格式化字节大小,可用于文件大小的显示
+ ///
+ ///
+ ///
+ public static string FormatFileSize(long fileSize)
+ {
+ string formatFileSize;
+ if (fileSize <= 0)
+ {
+ formatFileSize = "0B";
+ }
+ else if (fileSize < 1024)
+ {
+ formatFileSize = fileSize.ToString() + "B";
+ }
+ else if (fileSize < 1024 * 1024)
+ {
+ formatFileSize = (fileSize / 1024.0).ToString("#.##") + "KB";
+ }
+ else if (fileSize < 1024 * 1024 * 1024)
+ {
+ formatFileSize = (fileSize / 1024.0 / 1024.0).ToString("#.##") + "MB";
+ }
+ else
+ {
+ formatFileSize = (fileSize / 1024.0 / 1024.0 / 1024.0).ToString("#.##") + "GB";
+ }
+ return formatFileSize;
+ }
+
+ private static long GetInt(string value)
+ {
+ if (IsInt(value))
+ {
+ return long.Parse(value);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ private static bool IsInt(string value)
+ {
+ return Regex.IsMatch(value, @"^\d+$");
+ }
+
+ }
+
+
+ ///
+ /// 支持的视频类型
+ ///
+ public enum VideoType
+ {
+ NONE,
+ VIDEO, // 对应 /video/BV
+ VIDEO_AV, // 对应 /video/av
+ // BANGUMI, // BANGUMI细分为以下三个部分
+ BANGUMI_SEASON, // 对应 /bangumi/play/ss
+ BANGUMI_EPISODE, // 对应 /bangumi/play/ep
+ BANGUMI_MEDIA, // 对应 /bangumi/media/md
+ CHEESE_SEASON, // 对应 /cheese/play/ss
+ CHEESE_EPISODE // 对应 /cheese/play/ep
+ }
+
+ ///
+ /// 线程
+ ///
+ public enum ThreadStatus
+ {
+ MAIN_UI, // 主线程
+ START_PARSE, // 开始解析url
+ ADD_ALL_TO_DOWNLOAD,
+ START_DOWNLOAD
+ }
+
+ ///
+ /// 视频的编码格式,flv也视为编码放这里
+ ///
+ [Serializable]
+ public enum VideoCodec
+ {
+ NONE = 1,
+ AVC,
+ HEVC,
+ FLV
+ }
+
+}
diff --git a/src/Core/Downloader.cs b/src/Core/Downloader.cs
new file mode 100644
index 0000000..02e3459
--- /dev/null
+++ b/src/Core/Downloader.cs
@@ -0,0 +1,534 @@
+using Core.entity;
+using Core.settings;
+using Newtonsoft.Json;
+using System;
+using System.Net;
+using System.Text.RegularExpressions;
+
+namespace Core
+{
+ public static class Downloader
+ {
+ ///
+ /// 获得远程文件的大小
+ ///
+ ///
+ ///
+ ///
+ public static long GetRemoteFileSize(string url, string referer)
+ {
+ try
+ {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
+ request.Method = "GET";
+ request.Timeout = 30 * 1000;
+ request.UserAgent = Utils.GetUserAgent();
+ //request.ContentType = "text/html;charset=UTF-8";
+ request.Headers["accept-language"] = "zh-CN,zh;q=0.9,en;q=0.8";
+ request.Referer = referer;
+
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ return response.ContentLength;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetRemoteFileSize()发生异常: {0}", e);
+ return 0;
+ }
+ }
+
+ ///
+ /// 获得视频详情及播放列表
+ ///
+ ///
+ ///
+ public static VideoViewData GetVideoInfo(string bvid, long aid, string referer, bool isBackup = false)
+ {
+ string url;
+ if (bvid != null)
+ {
+ url = $"https://api.bilibili.com/x/web-interface/view?bvid={bvid}";
+ }
+ else if (aid >= 0)
+ {
+ url = $"https://api.bilibili.com/x/web-interface/view?aid={aid}";
+ }
+ else
+ { return null; }
+
+ // 采用备用的api,只能获取cid
+ if (isBackup)
+ {
+ string backupUrl = $"https://api.bilibili.com/x/player/pagelist?bvid={bvid}&jsonp=jsonp";
+ url = backupUrl;
+ }
+
+ string response = Utils.RequestWeb(url, referer);
+
+ try
+ {
+ VideoView videoView;
+ if (isBackup)
+ {
+ Pagelist pagelist = JsonConvert.DeserializeObject(response);
+
+ videoView = new VideoView
+ {
+ code = pagelist.code,
+ message = pagelist.message,
+ ttl = pagelist.ttl
+ };
+ videoView.data.pages = pagelist.data;
+ }
+ else
+ {
+ videoView = JsonConvert.DeserializeObject(response);
+ }
+
+ if (videoView != null)
+ {
+ if (videoView.data != null)
+ {
+ return videoView.data;
+ }
+ else
+ {
+ return null;
+
+ // 进入备选的url中
+ //return GetVideoInfo(bvid, referer, true);
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+ catch (JsonReaderException e)
+ {
+ Console.WriteLine("GetVideoInfo()发生JsonReader异常: {0}", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetVideoInfo()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ ///
+ /// 通过seasonId获得番剧的剧集详情
+ ///
+ ///
+ ///
+ public static BangumiSeasonResult GetBangumiSeason(long seasonId, string referer)
+ {
+ string url = $"https://api.bilibili.com/pgc/view/web/season?season_id={seasonId}";
+ string response = Utils.RequestWeb(url, referer);
+
+ try
+ {
+ BangumiSeason bangumiSeason = JsonConvert.DeserializeObject(response);
+ if (bangumiSeason != null) { return bangumiSeason.result; }
+ else { return null; }
+ }
+ catch (JsonReaderException e)
+ {
+ Console.WriteLine("GetBangumiSeason()发生JsonReader异常: {0}", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetBangumiSeason()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ public static long GetBangumiSeasonIdByMedia(long mediaId, string referer)
+ {
+ string url = $"https://api.bilibili.com/pgc/review/user?media_id={mediaId}";
+ string response = Utils.RequestWeb(url, referer);
+
+ try
+ {
+ BangumiMedia bangumiMedia = JsonConvert.DeserializeObject(response);
+ if (bangumiMedia.result.media != null) { return bangumiMedia.result.media.season_id; }
+ else { return 0; }
+ }
+ catch (JsonReaderException e)
+ {
+ Console.WriteLine("GetBangumiSeasonIdByMedia()发生JsonReader异常: {0}", e);
+ return 0;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetBangumiSeasonIdByMedia()发生异常: {0}", e);
+ return 0;
+ }
+ }
+
+ public static long GetBangumiSeasonIdByEpisode(long episode, string referer)
+ {
+ string url = $"https://www.bilibili.com/bangumi/play/ep{episode}";
+ string response = Utils.RequestWeb(url, referer);
+
+ // "ssId": 28324,
+ string pattern = "\"ssId\":\\s?\\d+,";
+ Regex regex = new Regex(pattern);
+ Match match = regex.Match(response);
+
+ // 删除多余的字符
+ string ssId = match.Value.Replace("ssId", "");
+ ssId = ssId.Replace("\"", "");
+ ssId = ssId.Replace(":", "");
+ ssId = ssId.Replace(" ", "");
+ ssId = ssId.Replace(",", "");
+
+ long seasonId;
+ try
+ {
+ seasonId = long.Parse(ssId);
+ }
+ catch (FormatException e)
+ {
+ Console.WriteLine("GetBangumiSeasonIdByEpisode()发生异常: {0}", e);
+ return 0;
+ }
+ return seasonId;
+ }
+
+ ///
+ /// 通过ep_id获得课程的信息
+ ///
+ ///
+ ///
+ ///
+ public static CheeseSeasonData GetCheeseSeason(long seasonId, long episode, string referer)
+ {
+ string url = $"https://api.bilibili.com/pugv/view/web/season?";
+ if (seasonId != 0)
+ {
+ url += $"season_id={seasonId}";
+ }
+ else if (episode != 0)
+ {
+ url += $"ep_id={episode}";
+ }
+
+ string response = Utils.RequestWeb(url, referer);
+
+ try
+ {
+ CheeseSeason cheeseSeason = JsonConvert.DeserializeObject(response);
+ if (cheeseSeason != null) { return cheeseSeason.data; }
+ else { return null; }
+ }
+ catch (JsonReaderException e)
+ {
+ Console.WriteLine("GetCheeseSeason()发生JsonReader异常: {0}", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetCheeseSeason()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ //public static long GetCheeseEpisodeIdBySeasonId(long seasonId, string referer)
+ //{
+ // string url = $"https://api.bilibili.com/pugv/view/web/ep/list?season_id={seasonId}&pn=1";
+ // string response = Utils.RequestWeb(url, referer);
+
+ // try
+ // {
+ // CheeseList cheeseList = JsonConvert.DeserializeObject(response);
+ // if (cheeseList.data.items != null && cheeseList.data.items.Count > 0)
+ // {
+ // return cheeseList.data.items[0].id;
+ // }
+ // else { return 0; }
+ // }
+ // catch (JsonReaderException e)
+ // {
+ // Console.WriteLine("发生异常: {0}", e);
+ // return 0;
+ // }
+ //}
+
+ ///
+ /// 获得音视频流链接
+ ///
+ /// 视频的bvid
+ /// 视频的cid
+ public static PlayUrlData GetStreamInfo(string bvid, long avid, long cid, long episodeId, int quality, string referer, bool isProxy = false, int proxy = 0)
+ {
+ string baseUrlVideo = "https://api.bilibili.com/x/player/playurl";
+ string baseUrlSeason = "https://api.bilibili.com/pgc/player/web/playurl";
+ string baseUrlCheese = "https://api.bilibili.com/pugv/player/web/playurl";
+ string baseUrl;
+ VideoType videoType = Common.GetVideoType(referer);
+ switch (videoType)
+ {
+ case VideoType.VIDEO:
+ baseUrl = baseUrlVideo;
+ break;
+ case VideoType.VIDEO_AV:
+ baseUrl = baseUrlVideo;
+ break;
+ case VideoType.BANGUMI_SEASON:
+ baseUrl = baseUrlSeason;
+ break;
+ case VideoType.BANGUMI_EPISODE:
+ baseUrl = baseUrlSeason;
+ break;
+ case VideoType.BANGUMI_MEDIA:
+ baseUrl = baseUrlSeason;
+ break;
+ case VideoType.CHEESE_SEASON:
+ baseUrl = baseUrlCheese;
+ break;
+ case VideoType.CHEESE_EPISODE:
+ baseUrl = baseUrlCheese;
+ break;
+ default:
+ baseUrl = baseUrlVideo;
+ break;
+ }
+ // TODO 没有的参数不加入url
+ //string url = $"{baseUrl}?cid={cid}&bvid={bvid}&avid={avid}&ep_id={episodeId}&qn={quality}&otype=json&fourk=1&fnver=0&fnval=16";
+ string url = $"{baseUrl}?cid={cid}&qn={quality}&otype=json&fourk=1&fnver=0&fnval=16";
+ if (bvid != null)
+ {
+ url += $"&bvid={bvid}";
+ }
+ if (avid != 0)
+ {
+ url += $"&avid={avid}";
+ }
+ if (episodeId != 0)
+ {
+ url += $"&ep_id={episodeId}";
+ }
+
+ // 代理网址
+ //https://www.biliplus.com/BPplayurl.php?cid=180873425&qn=116&type=&otype=json&fourk=1&bvid=BV1pV411o7yD&ep_id=317925&fnver=0&fnval=16&module=pgc
+ if (isProxy && proxy == 1)
+ {
+ string proxyUrl1 = "https://www.biliplus.com/BPplayurl.php";
+ url = $"{proxyUrl1}?cid={cid}&bvid={bvid}&qn={quality}&otype=json&fourk=1&fnver=0&fnval=16&module=pgc";
+ }
+ else if (isProxy && proxy == 2)
+ {
+ string proxyUrl2 = "https://biliplus.ipcjs.top/BPplayurl.php";
+ url = $"{proxyUrl2}?cid={cid}&bvid={bvid}&qn={quality}&otype=json&fourk=1&fnver=0&fnval=16&module=pgc";
+ }
+
+ string response = Utils.RequestWeb(url, referer);
+ //Console.WriteLine(response);
+
+ try
+ {
+ PlayUrl playUrl;
+ if (isProxy)
+ {
+ PlayUrlData playUrlData = JsonConvert.DeserializeObject(response);
+
+ playUrl = new PlayUrl
+ {
+ result = playUrlData
+ };
+ }
+ else
+ {
+ playUrl = JsonConvert.DeserializeObject(response);
+ }
+
+ if (playUrl != null)
+ {
+ if (playUrl.data != null) { return playUrl.data; }
+ if (playUrl.result != null) { return playUrl.result; }
+
+ // 无法从B站获取数据,进入代理网站
+ if (Settings.GetInstance().IsLiftingOfRegion() == ALLOW_STATUS.YES)
+ {
+ switch (proxy)
+ {
+ case 0:
+ return GetStreamInfo(bvid, avid, cid, episodeId, quality, referer, true, 1);
+ case 1:
+ return GetStreamInfo(bvid, avid, cid, episodeId, quality, referer, true, 2);
+ case 2:
+ return null;
+ }
+ }
+
+ return null;
+ }
+ else { return null; }
+ }
+ catch (JsonReaderException e)
+ {
+ Console.WriteLine("GetStreamInfo()发生JsonReader异常: {0}", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetStreamInfo()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ //public static List GetAllDanmaku(long cid, long publishTime, string referer)
+ //{
+ // List danmakus = new List();
+
+ // // 设置视频发布日期
+ // DateTime publishDate = new DateTime(1970, 1, 1);
+ // publishDate = publishDate.AddSeconds(publishTime);
+
+ // // 获得有弹幕的日期date
+ // List danmakuDateList = new List();
+ // while (true)
+ // {
+ // string month = publishDate.ToString("yyyy-MM");
+ // string url = $"https://api.bilibili.com/x/v2/dm/history/index?type=1&oid={cid}&month={month}";
+ // string response = Utils.RequestWeb(url, referer);
+
+ // DanmuDate danmakuDate;
+ // try
+ // {
+ // danmakuDate = JsonConvert.DeserializeObject(response);
+ // }
+ // catch (Exception e)
+ // {
+ // Console.WriteLine("GetAllDanmaku()发生异常: {0}", e);
+ // continue;
+ // }
+ // if (danmakuDate != null || danmakuDate.data != null) { danmakuDateList.AddRange(danmakuDate.data); }
+
+ // if (publishDate.CompareTo(DateTime.Now) > 0) { break; }
+ // publishDate = publishDate.AddMonths(1);
+ // Thread.Sleep(100);
+ // }
+
+ // // 获取弹幕
+ // foreach (var date in danmakuDateList)
+ // {
+ // Console.WriteLine(date);
+
+ // List danmakusOfOneDay = GetDanmaku(cid, date);
+
+ // foreach (Danmaku danmaku in danmakusOfOneDay)
+ // {
+ // if (danmakus.Find(it => it.DanmuId == danmaku.DanmuId) == null)
+ // {
+ // danmakus.Add(danmaku);
+ // }
+ // }
+ // }
+
+ // // 按弹幕发布时间排序
+ // danmakus = danmakus.OrderBy(it => it.Timestamp).ToList();
+
+ // return danmakus;
+ //}
+
+ //public static List GetDanmaku(long cid, string date, string referer)
+ //{
+ // string url = $"https://api.bilibili.com/x/v2/dm/history?type=1&oid={cid}&date={date}";
+ // string response = Utils.RequestWeb(url, referer);
+
+ // //
+ // // {"code":-101,"message":"账号未登录","ttl":1}
+ // if (response.Contains(""))
+ // {
+ // List danmakus = new List();
+
+ // XmlDocument doc = new XmlDocument();
+ // doc.LoadXml(response);
+ // // 取得节点名为d的XmlNode集合
+ // XmlNodeList danmuList = doc.GetElementsByTagName("d");
+ // foreach (XmlNode node in danmuList)
+ // {
+ // // 返回的是文字内容
+ // string nodeText = node.InnerText;
+ // // 节点p属性值
+ // string childValue = node.Attributes["p"].Value;
+ // // 拆分属性
+ // string[] attrs = childValue.Split(',');
+
+ // Danmaku danmaku = new Danmaku
+ // {
+ // Text = nodeText,
+ // Time = float.Parse(attrs[0]),
+ // Type = int.Parse(attrs[1]),
+ // Fontsize = int.Parse(attrs[2]),
+ // Color = long.Parse(attrs[3]),
+ // Timestamp = long.Parse(attrs[4]),
+ // Pool = int.Parse(attrs[5]),
+ // UserId = attrs[6],
+ // DanmuId = attrs[7]
+ // };
+ // danmakus.Add(danmaku);
+ // }
+
+ // return danmakus;
+ // }
+ // else
+ // {
+ // DanmuFromWeb danmu = JsonConvert.DeserializeObject(response);
+ // if (danmu != null) { Console.WriteLine(danmu.message); }
+ // return null;
+ // }
+ //}
+
+ /////
+ ///// 获取弹幕,不需要登录信息,只能获取3000条弹幕
+ /////
+ /////
+ /////
+ //public static List GetDanmaku(long cid, string referer)
+ //{
+ // string url = $"https://api.bilibili.com/x/v1/dm/list.so?oid={cid}";
+ // string response = Utils.RequestWeb(url, referer);
+
+ // if (response.Contains(""))
+ // {
+ // List danmakus = new List();
+
+ // XmlDocument doc = new XmlDocument();
+ // doc.LoadXml(response);
+ // // 取得节点名为d的XmlNode集合
+ // XmlNodeList danmuList = doc.GetElementsByTagName("d");
+ // foreach (XmlNode node in danmuList)
+ // {
+ // // 返回的是文字内容
+ // string nodeText = node.InnerText;
+ // // 节点p属性值
+ // string childValue = node.Attributes["p"].Value;
+ // // 拆分属性
+ // string[] attrs = childValue.Split(',');
+
+ // Danmaku danmaku = new Danmaku
+ // {
+ // Text = nodeText,
+ // Time = float.Parse(attrs[0]),
+ // Type = int.Parse(attrs[1]),
+ // Fontsize = int.Parse(attrs[2]),
+ // Color = long.Parse(attrs[3]),
+ // Timestamp = long.Parse(attrs[4]),
+ // Pool = int.Parse(attrs[5]),
+ // UserId = attrs[6],
+ // DanmuId = attrs[7]
+ // };
+ // danmakus.Add(danmaku);
+ // }
+
+ // return danmakus;
+ // }
+ // return null;
+ //}
+
+ }
+}
diff --git a/src/Core/Encryptor.cs b/src/Core/Encryptor.cs
new file mode 100644
index 0000000..7083606
--- /dev/null
+++ b/src/Core/Encryptor.cs
@@ -0,0 +1,316 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Core
+{
+ public static class Encryptor
+ {
+ private const ulong FC_TAG = 0xFC010203040506CF;
+ private const int BUFFER_SIZE = 128 * 1024;
+
+ ///
+ /// 加密文件
+ ///
+ /// 待加密文件
+ /// 加密后输入文件
+ /// 加密密码
+ public static void EncryptFile(string inFile, string outFile, string password)
+ {
+ using (FileStream fin = File.OpenRead(inFile), fout = File.OpenWrite(outFile))
+ {
+ long lSize = fin.Length; // 输入文件长度
+ int size = (int)lSize;
+ byte[] bytes = new byte[BUFFER_SIZE]; // 缓存
+ int read = -1; // 输入文件读取数量
+ int value = 0;
+
+ // 获取IV和salt
+ byte[] IV = GenerateRandomBytes(16);
+ byte[] salt = GenerateRandomBytes(16);
+
+ // 创建加密对象
+ SymmetricAlgorithm sma = CreateRijndael(password, salt);
+ sma.IV = IV;
+
+ // 在输出文件开始部分写入IV和salt
+ fout.Write(IV, 0, IV.Length);
+ fout.Write(salt, 0, salt.Length);
+
+ // 创建散列加密
+ HashAlgorithm hasher = SHA256.Create();
+ using (CryptoStream cout = new CryptoStream(fout, sma.CreateEncryptor(), CryptoStreamMode.Write),
+ chash = new CryptoStream(Stream.Null, hasher, CryptoStreamMode.Write))
+ {
+ BinaryWriter bw = new BinaryWriter(cout);
+ bw.Write(lSize);
+
+ bw.Write(FC_TAG);
+
+ // 读写字节块到加密流缓冲区
+ while ((read = fin.Read(bytes, 0, bytes.Length)) != 0)
+ {
+ cout.Write(bytes, 0, read);
+ chash.Write(bytes, 0, read);
+ value += read;
+ }
+ // 关闭加密流
+ chash.Flush();
+ chash.Close();
+
+ // 读取散列
+ byte[] hash = hasher.Hash;
+
+ // 输入文件写入散列
+ cout.Write(hash, 0, hash.Length);
+
+ // 关闭文件流
+ cout.Flush();
+ cout.Close();
+ }
+ }
+ }
+
+ ///
+ /// 解密文件
+ ///
+ /// 待解密文件
+ /// 解密后输出文件
+ /// 解密密码
+ public static void DecryptFile(string inFile, string outFile, string password)
+ {
+ // 创建打开文件流
+ using (FileStream fin = File.OpenRead(inFile), fout = File.OpenWrite(outFile))
+ {
+ int size = (int)fin.Length;
+ byte[] bytes = new byte[BUFFER_SIZE];
+ int read = -1;
+ int value = 0;
+ int outValue = 0;
+
+ byte[] IV = new byte[16];
+ fin.Read(IV, 0, 16);
+ byte[] salt = new byte[16];
+ fin.Read(salt, 0, 16);
+
+ SymmetricAlgorithm sma = CreateRijndael(password, salt);
+ sma.IV = IV;
+
+ value = 32;
+ long lSize = -1;
+
+ // 创建散列对象, 校验文件
+ HashAlgorithm hasher = SHA256.Create();
+
+ try
+ {
+ using (CryptoStream cin = new CryptoStream(fin, sma.CreateDecryptor(), CryptoStreamMode.Read),
+ chash = new CryptoStream(Stream.Null, hasher, CryptoStreamMode.Write))
+ {
+ // 读取文件长度
+ BinaryReader br = new BinaryReader(cin);
+ lSize = br.ReadInt64();
+ ulong tag = br.ReadUInt64();
+
+ if (FC_TAG != tag)
+ throw new CryptoHelpException("文件被破坏");
+
+ long numReads = lSize / BUFFER_SIZE;
+
+ long slack = (long)lSize % BUFFER_SIZE;
+
+ for (int i = 0; i < numReads; ++i)
+ {
+ read = cin.Read(bytes, 0, bytes.Length);
+ fout.Write(bytes, 0, read);
+ chash.Write(bytes, 0, read);
+ value += read;
+ outValue += read;
+ }
+
+ if (slack > 0)
+ {
+ read = cin.Read(bytes, 0, (int)slack);
+ fout.Write(bytes, 0, read);
+ chash.Write(bytes, 0, read);
+ value += read;
+ outValue += read;
+ }
+
+ chash.Flush();
+ chash.Close();
+
+ fout.Flush();
+ fout.Close();
+
+ byte[] curHash = hasher.Hash;
+
+ // 获取比较和旧的散列对象
+ byte[] oldHash = new byte[hasher.HashSize / 8];
+ read = cin.Read(oldHash, 0, oldHash.Length);
+ if ((oldHash.Length != read) || (!CheckByteArrays(oldHash, curHash)))
+ throw new CryptoHelpException("文件被破坏");
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("DecryptFile()发生异常: {0}", e);
+ }
+
+ if (outValue != lSize)
+ throw new CryptoHelpException("文件大小不匹配");
+ }
+ }
+
+ ///
+ /// 检验两个Byte数组是否相同
+ ///
+ /// Byte数组
+ /// Byte数组
+ /// true-相等
+ private static bool CheckByteArrays(byte[] b1, byte[] b2)
+ {
+ if (b1.Length == b2.Length)
+ {
+ for (int i = 0; i < b1.Length; ++i)
+ {
+ if (b1[i] != b2[i])
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 创建DebugLZQ ,http://www.cnblogs.com/DebugLZQ
+ ///
+ /// 密码
+ ///
+ /// 加密对象
+ private static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
+ {
+ PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, salt, "SHA256", 1000);
+
+ SymmetricAlgorithm sma = Rijndael.Create();
+ sma.KeySize = 256;
+ sma.Key = pdb.GetBytes(32);
+ sma.Padding = PaddingMode.PKCS7;
+ return sma;
+ }
+
+ ///
+ /// 生成指定长度的随机Byte数组
+ ///
+ /// Byte数组长度
+ /// 随机Byte数组
+ private static byte[] GenerateRandomBytes(int count)
+ {
+ // 加密文件随机数生成
+ RandomNumberGenerator rand = new RNGCryptoServiceProvider();
+
+ byte[] bytes = new byte[count];
+ rand.GetBytes(bytes);
+ return bytes;
+ }
+
+
+ ///
+ /// DES加密字符串
+ ///
+ /// 待加密的字符串
+ /// 加密密钥,要求为8位
+ /// 加密成功返回加密后的字符串,失败返回源串
+ public static string EncryptString(string encryptString, string encryptKey)
+ {
+ try
+ {
+ byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8));//转换为字节
+ byte[] rgbIV = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8));
+ byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
+ DESCryptoServiceProvider dCSP = new DESCryptoServiceProvider();//实例化数据加密标准
+ MemoryStream mStream = new MemoryStream();//实例化内存流
+ //将数据流链接到加密转换的流
+ CryptoStream cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
+ cStream.Write(inputByteArray, 0, inputByteArray.Length);
+ cStream.FlushFinalBlock();
+ // 转base64
+ return Convert.ToBase64String(mStream.ToArray());
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("EncryptString()发生异常: {0}", e);
+ return encryptString;
+ }
+ }
+
+ ///
+ /// DES解密字符串
+ ///
+ /// 待解密的字符串
+ /// 解密密钥,要求为8位,和加密密钥相同
+ /// 解密成功返回解密后的字符串,失败返源串
+ public static string DecryptString(string decryptString, string decryptKey)
+ {
+ try
+ {
+ byte[] rgbKey = Encoding.UTF8.GetBytes(decryptKey);
+ byte[] rgbIV = Encoding.UTF8.GetBytes(decryptKey);
+ byte[] inputByteArray = Convert.FromBase64String(decryptString);
+ DESCryptoServiceProvider DCSP = new DESCryptoServiceProvider();
+ MemoryStream mStream = new MemoryStream();
+ CryptoStream cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
+ cStream.Write(inputByteArray, 0, inputByteArray.Length);
+ cStream.FlushFinalBlock();
+ return Encoding.UTF8.GetString(mStream.ToArray());
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("DecryptString()发生异常: {0}", e);
+ return decryptString;
+ }
+ }
+
+ ///
+ /// 计算字符串MD5值
+ ///
+ ///
+ ///
+ public static string GetMd5Hash(string input)
+ {
+ if (input == null)
+ {
+ return null;
+ }
+
+ MD5 md5Hash = MD5.Create();
+
+ // 将输入字符串转换为字节数组并计算哈希数据
+ byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
+
+ // 创建一个 Stringbuilder 来收集字节并创建字符串
+ StringBuilder sBuilder = new StringBuilder();
+
+ // 循环遍历哈希数据的每一个字节并格式化为十六进制字符串
+ for (int i = 0; i < data.Length; i++)
+ {
+ sBuilder.Append(data[i].ToString("x2"));
+ }
+
+ // 返回十六进制字符串
+ return sBuilder.ToString();
+ }
+
+
+ }
+
+ ///
+ /// 异常处理类
+ ///
+ public class CryptoHelpException : ApplicationException
+ {
+ public CryptoHelpException(string msg) : base(msg) { }
+ }
+
+}
diff --git a/src/Core/FFmpegHelper.cs b/src/Core/FFmpegHelper.cs
new file mode 100644
index 0000000..adfb4d7
--- /dev/null
+++ b/src/Core/FFmpegHelper.cs
@@ -0,0 +1,254 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Core
+{
+ public static class FFmpegHelper
+ {
+
+ ///
+ /// 合并音频和视频
+ ///
+ ///
+ ///
+ ///
+ public static bool MergeVideo(string video1, string video2, string destVideo)
+ {
+ string param = $"-i \"{video1}\" -i \"{video2}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
+ if (video1 == null || !File.Exists(video1))
+ {
+ param = $"-i \"{video2}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
+ }
+ if (video2 == null || !File.Exists(video2))
+ {
+ param = $"-i \"{video1}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
+ }
+ if (!File.Exists(video1) && !File.Exists(video2)) { return false; }
+
+ // 如果存在
+ try { File.Delete(destVideo); }
+ catch (IOException e)
+ {
+ Console.WriteLine("MergeVideo()发生IO异常: {0}", e);
+ return false;
+ }
+
+ ExcuteProcess("ffmpeg.exe", param, null, (s, e) => Console.WriteLine(e.Data));
+
+ try
+ {
+ if (video1 != null) { File.Delete(video1); }
+ if (video2 != null) { File.Delete(video2); }
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("MergeVideo()发生IO异常: {0}", e);
+ }
+
+ return true;
+ }
+
+ ///
+ /// 拼接多个视频
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool ConcatVideo(string workingDirectory, List flvFiles, string destVideo)
+ {
+ // contact的文件名,不包含路径
+ string concatFileName = Guid.NewGuid().ToString("N") + "_concat.txt";
+ try
+ {
+ string contact = "";
+ foreach (string flv in flvFiles)
+ {
+ contact += $"file '{flv}'\n";
+ }
+
+ FileStream fileStream = new FileStream(workingDirectory + "/" + concatFileName, FileMode.Create);
+ StreamWriter streamWriter = new StreamWriter(fileStream);
+ //开始写入
+ streamWriter.Write(contact);
+ //清空缓冲区
+ streamWriter.Flush();
+ //关闭流
+ streamWriter.Close();
+ fileStream.Close();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("ConcatVideo()发生异常: {0}", e);
+ return false;
+ }
+
+ // ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mkv
+ // 加上-y,表示如果有同名文件,则默认覆盖
+ string param = $"-f concat -safe 0 -i {concatFileName} -c copy \"{destVideo}\" -y";
+ ExcuteProcess("ffmpeg.exe", param, workingDirectory, (s, e) => Console.WriteLine(e.Data));
+
+ // 删除临时文件
+ try
+ {
+ // 删除concat文件
+ File.Delete(workingDirectory + "/" + concatFileName);
+
+ foreach (string flv in flvFiles)
+ {
+ File.Delete(flv);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("ConcatVideo()发生异常: {0}", e);
+ }
+
+ return true;
+ }
+
+ ///
+ /// 去水印,非常消耗cpu资源
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Delogo(string video, string destVideo, int x, int y, int width, int height, TextBox output = null, Window window = null)
+ {
+ // ffmpeg -i "video.mp4" -vf delogo=x=1670:y=50:w=180:h=70:show=1 "delogo.mp4"
+ string param = $"-i \"{video}\" -vf delogo=x={x}:y={y}:w={width}:h={height} \"{destVideo}\" -hide_banner";
+ ExcuteProcess("ffmpeg.exe", param,
+ null, (s, e) =>
+ {
+ Console.WriteLine(e.Data);
+ if (output != null && window != null)
+ {
+ window.Dispatcher.Invoke(new Action(() =>
+ {
+ output.Text += e.Data + "\n";
+ output.ScrollToEnd();
+ }));
+ }
+ });
+ }
+
+ ///
+ /// 从一个视频中仅提取音频
+ ///
+ /// 源视频
+ /// 目标音频
+ /// 输出信息
+ ///
+ public static void ExtractAudio(string video, string audio, TextBox output = null, Window window = null)
+ {
+ // 抽取音频命令
+ // ffmpeg -i 3.mp4 -vn -y -acodec copy 3.aac
+ // ffmpeg -i 3.mp4 -vn -y -acodec copy 3.m4a
+ string param = $"-i \"{video}\" -vn -y -acodec copy \"{audio}\" -hide_banner";
+ ExcuteProcess("ffmpeg.exe", param,
+ null, (s, e) =>
+ {
+ Console.WriteLine(e.Data);
+ if (output != null && window != null)
+ {
+ window.Dispatcher.Invoke(new Action(() =>
+ {
+ output.Text += e.Data + "\n";
+ output.ScrollToEnd();
+ }));
+ }
+ });
+ }
+
+ ///
+ /// 从一个视频中仅提取视频
+ ///
+ /// 源视频
+ /// 目标视频
+ /// 输出信息
+ ///
+ public static void ExtractVideo(string video, string destVideo, TextBox output = null, Window window = null)
+ {
+ // 提取视频 (Extract Video)
+ // ffmpeg -i Life.of.Pi.has.subtitles.mkv -vcodec copy –an videoNoAudioSubtitle.mp4
+ string param = $"-i \"{video}\" -y -vcodec copy -an \"{destVideo}\" -hide_banner";
+ ExcuteProcess("ffmpeg.exe", param,
+ null, (s, e) =>
+ {
+ Console.WriteLine(e.Data);
+ if (output != null && window != null)
+ {
+ window.Dispatcher.Invoke(new Action(() =>
+ {
+ output.Text += e.Data + "\n";
+ output.ScrollToEnd();
+ }));
+ }
+ });
+ }
+
+ ///
+ /// 提取视频的帧,输出为图片
+ ///
+ ///
+ ///
+ ///
+ public static void ExtractFrame(string video, string image, uint number)
+ {
+ // 提取帧
+ // ffmpeg -i caiyilin.wmv -vframes 1 wm.bmp
+ string param = $"-i \"{video}\" -y -vframes {number} \"{image}\"";
+ ExcuteProcess("ffmpeg.exe", param, null, (s, e) => Console.WriteLine(e.Data));
+ }
+
+
+ ///
+ /// 执行一个控制台程序
+ ///
+ /// 程序名称
+ /// 参数
+ /// 工作路径
+ /// 输出重定向
+ private static void ExcuteProcess(string exe, string arg, string workingDirectory, DataReceivedEventHandler output)
+ {
+ using (var p = new Process())
+ {
+ p.StartInfo.FileName = exe;
+ p.StartInfo.Arguments = arg;
+
+ // 工作目录
+ if (workingDirectory != null)
+ {
+ p.StartInfo.WorkingDirectory = workingDirectory;
+ }
+
+ p.StartInfo.UseShellExecute = false; //输出信息重定向
+ p.StartInfo.CreateNoWindow = true;
+ p.StartInfo.RedirectStandardError = true;
+ p.StartInfo.RedirectStandardOutput = true;
+
+ // 将 StandardErrorEncoding 改为 UTF-8 才不会出现中文乱码
+ p.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
+ p.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8;
+
+ p.OutputDataReceived += output;
+ p.ErrorDataReceived += output;
+
+ p.Start(); //启动线程
+ p.BeginOutputReadLine();
+ p.BeginErrorReadLine();
+ p.WaitForExit(); //等待进程结束
+ }
+ }
+ }
+
+}
diff --git a/src/Core/FileDownloadUtil.cs b/src/Core/FileDownloadUtil.cs
new file mode 100644
index 0000000..5501720
--- /dev/null
+++ b/src/Core/FileDownloadUtil.cs
@@ -0,0 +1,198 @@
+using System;
+using System.IO;
+using System.Net;
+
+namespace Core
+{
+ // from https://www.jianshu.com/p/f31910ed8435
+ public class FileDownloadUtil
+ {
+ private string url; //文件下载网络地址
+ private string referer; //访问url的referer
+ private string path; //文件下载位置,如d:/download
+ private string filename; //文件名,如test.jpg
+ private string fileId; //文件ID,文件唯一标识,一般为UUID
+
+ private System.Threading.Timer FileTimer; // 定时器
+ private readonly int SPD_INTERVAL_SEC = 1; // 每隔多少秒计算一次速度
+ private long FileTemp = 0; // 临时储存长度
+ private float FileSpeed = 0; // //SPD_INTERVAL_SEC秒下载字节数
+
+ // 下载进度回调
+ public delegate void ProgressChangedHandler(int progress, string fileId);
+ public event ProgressChangedHandler ProgressChanged;
+ protected virtual void OnProgressChanged(int progress, string fileId)
+ {
+ ProgressChanged?.Invoke(progress, fileId);
+ }
+
+ public delegate void ProgressChanged2Handler(long totalBytes, long totalDownloadedByte, float speed, string fileId);
+ public event ProgressChanged2Handler ProgressChanged2;
+ protected virtual void OnProgressChanged2(long totalBytes, long totalDownloadedByte, float speed, string fileId)
+ {
+ ProgressChanged2?.Invoke(totalBytes, totalDownloadedByte, speed, fileId);
+ }
+
+ // 下载结果回调
+ public delegate void DownloadFinishHandler(bool isSuccess, string downloadPath, string fileId, string msg = null);
+ public event DownloadFinishHandler DownloadFinish;
+ protected virtual void OnDownloadFinish(bool isSuccess, string downloadPath, string fileId, string msg = null)
+ {
+ DownloadFinish?.Invoke(isSuccess, downloadPath, fileId, msg);
+ }
+
+ //通过网络链接直接下载任意文件
+ public FileDownloadUtil Init(string url, string referer, string path, string filename, string fileId)
+ {
+ this.url = url;
+ this.referer = referer;
+ this.path = path;
+ this.filename = filename;
+ this.fileId = fileId;
+
+ return this;
+ }
+
+ public void Download()
+ {
+ Download(url, path, filename, fileId);
+ }
+
+ private void Download(string url, string path, string filename, string fileId)
+ {
+
+ if (!Directory.Exists(path)) //判断文件夹是否存在
+ Directory.CreateDirectory(path);
+ path = path + "\\" + filename;
+
+ // 临时文件 "bilidownkyi\\"
+ string tempFile = Path.GetTempPath() + "downkyi." + Guid.NewGuid().ToString("N");
+ //string tempFile = path + ".temp";
+
+ try
+ {
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile); //存在则删除
+ }
+ if (File.Exists(path))
+ {
+ File.Delete(path); //存在则删除
+ }
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("Download()发生IO异常: {0}", e);
+ }
+
+ FileStream fs = null;
+ HttpWebRequest request = null;
+ HttpWebResponse response = null;
+ Stream responseStream = null;
+ try
+ {
+ //创建临时文件
+ fs = new FileStream(tempFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
+ request = WebRequest.Create(url) as HttpWebRequest;
+ request.Method = "GET";
+ request.Timeout = 60 * 1000;
+ request.UserAgent = Utils.GetUserAgent();
+ //request.ContentType = "text/html;charset=UTF-8";
+ request.Headers["accept-language"] = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7";
+ //request.Headers["accept-encoding"] = "gzip, deflate, br";
+ request.Headers["origin"] = "https://www.bilibili.com";
+ request.Referer = referer;
+
+ // 构造cookie
+ // web端请求视频链接时没有加入cookie
+ //if (!url.Contains("getLogin"))
+ //{
+ // CookieContainer cookies = Login.GetLoginInfoCookies();
+ // if (cookies != null)
+ // {
+ // request.CookieContainer = cookies;
+ // }
+ //}
+
+ //发送请求并获取相应回应数据
+ response = request.GetResponse() as HttpWebResponse;
+ //直到request.GetResponse()程序才开始向目标网页发送Post请求
+ responseStream = response.GetResponseStream();
+ byte[] bArr = new byte[1024];
+ long totalBytes = response.ContentLength; //通过响应头获取文件大小,前提是响应头有文件大小
+ int size = responseStream.Read(bArr, 0, (int)bArr.Length); //读取响应流到bArr,读取大小
+ float percent = 0; //用来保存计算好的百分比
+ long totalDownloadedByte = 0; //总共下载字节数
+
+ FileTimer = new System.Threading.Timer(SpeedTimer, null, 0, SPD_INTERVAL_SEC * 1000);
+ while (size > 0) //while (totalBytes > totalDownloadedByte) //while循环读取响应流
+ {
+ fs.Write(bArr, 0, size); //写到临时文件
+ // 定期刷新数据到磁盘,影响下载速度
+ //fs.Flush(true);
+
+ totalDownloadedByte += size;
+ FileTemp += size;
+ size = responseStream.Read(bArr, 0, (int)bArr.Length);
+ percent = (float)totalDownloadedByte / (float)totalBytes * 100;
+
+ // 下载进度回调
+ OnProgressChanged((int)percent, fileId);
+ OnProgressChanged2(totalBytes, totalDownloadedByte, FileSpeed, fileId);
+ }
+
+ try
+ {
+ if (File.Exists(path))
+ {
+ File.Delete(path); //存在则删除
+ }
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("Download()发生IO异常: {0}", e);
+ }
+
+ if (fs != null)
+ fs.Close();
+ File.Move(tempFile, path); //重命名为正式文件
+ OnDownloadFinish(true, path, fileId, null); //下载完成,成功回调
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Download()发生异常: {0}", ex);
+ OnDownloadFinish(false, null, fileId, ex.Message); //下载完成,失败回调
+ }
+ finally
+ {
+ if (fs != null)
+ fs.Close();
+ if (request != null)
+ request.Abort();
+ if (response != null)
+ response.Close();
+ if (responseStream != null)
+ responseStream.Close();
+
+ try
+ {
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile); //存在则删除
+ }
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("Download()发生IO异常: {0}", e);
+ }
+ }
+ }
+
+ private void SpeedTimer(object state)
+ {
+ FileSpeed = FileTemp / SPD_INTERVAL_SEC; //SPD_INTERVAL_SEC秒下载字节数,
+ FileTemp = 0; //清空临时储存
+ }
+
+ }
+}
diff --git a/src/Core/MachineCode.cs b/src/Core/MachineCode.cs
new file mode 100644
index 0000000..e265cfc
--- /dev/null
+++ b/src/Core/MachineCode.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Management;
+
+namespace Core
+{
+ public class MachineCode
+ {
+ private static MachineCode machineCode;
+
+ ///
+ /// 获取机器码
+ ///
+ ///
+ public static string GetMachineCodeString()
+ {
+ if (machineCode == null)
+ {
+ machineCode = new MachineCode();
+ }
+
+ string machineCodeString = "PC." +
+ machineCode.GetMainBordId() + "." +
+ machineCode.GetCpuInfo() + "." +
+ machineCode.GetDiskID();// + "." +
+ //machineCode.GetMoAddress();
+ return machineCodeString.Replace(" ", "");
+ }
+
+ ///
+ /// 获取主板ID
+ ///
+ ///
+ public string GetMainBordId()
+ {
+ string strId = "";
+ try
+ {
+ using (ManagementClass mc = new ManagementClass("Win32_BaseBoard"))
+ {
+ ManagementObjectCollection moc = mc.GetInstances();
+ foreach (ManagementObject mo in moc)
+ {
+ strId = mo.Properties["SerialNumber"].Value.ToString();
+ mo.Dispose();
+ break;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return "unknown";
+ //throw;
+ }
+ return strId;
+ }
+
+ ///
+ /// 获取cpu序列号
+ ///
+ ///
+ public string GetCpuInfo()
+ {
+ string cpuInfo = "";
+ try
+ {
+ using (ManagementClass cimobject = new ManagementClass("Win32_Processor"))
+ {
+ ManagementObjectCollection moc = cimobject.GetInstances();
+
+ foreach (ManagementObject mo in moc)
+ {
+ cpuInfo = mo.Properties["ProcessorId"].Value.ToString();
+ mo.Dispose();
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return "unknown";
+ //throw;
+ }
+ return cpuInfo;
+ }
+
+ ///
+ /// 获取硬盘ID
+ ///
+ ///
+ public string GetDiskID()
+ {
+ string diskName = "";
+ string diskID = "";
+ try
+ {
+ using (ManagementClass cimobject1 = new ManagementClass("Win32_DiskDrive"))
+ {
+ ManagementObjectCollection moc1 = cimobject1.GetInstances();
+ foreach (ManagementObject mo in moc1)
+ {
+ diskName = mo.Properties["Model"].Value.ToString();
+ diskID = mo.Properties["SerialNumber"].Value.ToString();
+ mo.Dispose();
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return "unknown";
+ //throw;
+ }
+ return diskName + diskID;
+ }
+
+ ///
+ /// 获取网卡硬件地址
+ ///
+ ///
+ public string GetMoAddress()
+ {
+ string MoAddress = "";
+ try
+ {
+ using (ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration"))
+ {
+ ManagementObjectCollection moc2 = mc.GetInstances();
+ foreach (ManagementObject mo in moc2)
+ {
+ if ((bool)mo["IPEnabled"] == true)
+ MoAddress = mo["MacAddress"].ToString();
+ mo.Dispose();
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return "unknown";
+ //throw;
+ }
+ return MoAddress;
+ }
+ }
+}
diff --git a/src/Core/UserSpaceOld.cs b/src/Core/UserSpaceOld.cs
new file mode 100644
index 0000000..88bd659
--- /dev/null
+++ b/src/Core/UserSpaceOld.cs
@@ -0,0 +1,148 @@
+using Core.entity;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+
+namespace Core
+{
+ public static class UserSpaceOld
+ {
+
+ ///
+ /// 获取我创建的收藏夹
+ ///
+ ///
+ ///
+ public static FavFolderData GetCreatedFavFolder(long mid)
+ {
+ string url = $"https://api.bilibili.com/x/v3/fav/folder/created/list?up_mid={mid}&ps=50";
+ return GetAllFavFolder(url);
+ }
+
+ ///
+ /// 获取我收藏的收藏夹
+ ///
+ ///
+ ///
+ public static FavFolderData GetCollectedFavFolder(long mid)
+ {
+ string url = $"https://api.bilibili.com/x/v3/fav/folder/collected/list?up_mid={mid}&ps=50";
+ return GetAllFavFolder(url);
+ }
+
+ private static FavFolderData GetAllFavFolder(string baseUrl)
+ {
+ FavFolderData userFavoriteData = new FavFolderData
+ {
+ count = 0,
+ list = new List()
+ };
+ int i = 0;
+ while (true)
+ {
+ i++;
+ string url = baseUrl + $"&pn={i}";
+
+ var data = GetFavFolder(url);
+ if (data == null)
+ { break; }
+ if (data.count == 0 || data.list == null)
+ { break; }
+
+ userFavoriteData.list.AddRange(data.list);
+ }
+ userFavoriteData.count = userFavoriteData.list.Count;
+ return userFavoriteData;
+ }
+
+ private static FavFolderData GetFavFolder(string url)
+ {
+ string referer = "https://www.bilibili.com";
+ string response = Utils.RequestWeb(url, referer);
+
+ try
+ {
+ FavFolder favFolder = JsonConvert.DeserializeObject(response);
+ if (favFolder == null || favFolder.data == null) { return null; }
+
+ return favFolder.data;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetFavFolder()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ ///
+ /// 获得某个收藏夹的内容
+ ///
+ ///
+ ///
+ public static List GetAllFavResource(long mediaId)
+ {
+ string baseUrl = $"https://api.bilibili.com/x/v3/fav/resource/list?media_id={mediaId}&ps=20";
+ List medias = new List();
+
+ int i = 0;
+ while (true)
+ {
+ i++;
+ string url = baseUrl + $"&pn={i}";
+
+ var data = GetFavResource(url);
+ if (data == null || data.Count == 0)
+ { break; }
+
+ medias.AddRange(data);
+ }
+ return medias;
+ }
+
+ private static List GetFavResource(string url)
+ {
+ string referer = "https://www.bilibili.com";
+ string response = Utils.RequestWeb(url, referer);
+
+ try
+ {
+ FavResource favResource = JsonConvert.DeserializeObject(response);
+ if (favResource == null || favResource.data == null) { return null; }
+ return favResource.data.medias;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetFavResource()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+
+ ///
+ /// 获取订阅番剧的数量
+ /// 废弃
+ ///
+ ///
+ ///
+ //public static int GetBangumiFollowList(long mid)
+ //{
+ // string url = $"https://space.bilibili.com/ajax/Bangumi/getList?mid={mid}";
+ // string referer = "https://www.bilibili.com";
+ // string response = Utils.RequestWeb(url, referer);
+
+ // try
+ // {
+ // BangumiList bangumiList = JsonConvert.DeserializeObject(response);
+ // if (bangumiList == null || bangumiList.data == null) { return -1; }
+
+ // return bangumiList.data.count;
+ // }
+ // catch (Exception e)
+ // {
+ // Console.WriteLine("发生异常: {0}", e);
+ // return 0;
+ // }
+ //}
+
+ }
+}
diff --git a/src/Core/Utils.cs b/src/Core/Utils.cs
new file mode 100644
index 0000000..f27fc64
--- /dev/null
+++ b/src/Core/Utils.cs
@@ -0,0 +1,430 @@
+using Brotli;
+using Core.api.login;
+using Core.history;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Net;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+
+namespace Core
+{
+ public static class Utils
+ {
+ ///
+ /// 随机的UserAgent
+ ///
+ /// userAgent
+ public static string GetUserAgent()
+ {
+ string[] userAgents = {
+ // Chrome
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
+
+ // 新版Edge
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66",
+
+ // IE 11
+ "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)",
+
+ // 火狐
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0",
+
+ // Opera
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 OPR/63.0.3368.43",
+
+ // MacOS Chrome
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36",
+
+ // MacOS Safari
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"
+ };
+
+ var time = DateTime.Now;
+
+ Random random = new Random(time.GetHashCode());
+ int number = random.Next(0, userAgents.Length - 1);
+
+ return userAgents[number];
+ }
+
+ ///
+ /// 发送get或post请求
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string RequestWeb(string url, string referer = null, string method = "GET", Dictionary parameters = null, int retry = 3)
+ {
+ // 重试次数
+ if (retry <= 0) { return ""; }
+
+ // post请求,发送参数
+ if (method == "POST" && parameters != null)
+ {
+ StringBuilder builder = new StringBuilder();
+ int i = 0;
+ foreach (var item in parameters)
+ {
+ if (i > 0) builder.Append("&");
+ builder.AppendFormat("{0}={1}", item.Key, item.Value);
+ i++;
+ }
+
+ url += "?" + builder.ToString();
+ }
+
+ try
+ {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
+ request.Method = method;
+ request.Timeout = 30 * 1000;
+ request.UserAgent = GetUserAgent();
+ //request.ContentType = "application/json,text/html,application/xhtml+xml,application/xml;charset=UTF-8";
+ request.Headers["accept-language"] = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7";
+ request.Headers["accept-encoding"] = "gzip, deflate, br";
+
+ //request.Headers["sec-fetch-dest"] = "empty";
+ //request.Headers["sec-fetch-mode"] = "cors";
+ //request.Headers["sec-fetch-site"] = "same-site";
+
+ // referer
+ if (referer != null)
+ {
+ request.Referer = referer;
+ }
+
+ // 构造cookie
+ if (!url.Contains("getLogin"))
+ {
+ request.Headers["origin"] = "https://www.bilibili.com";
+
+ CookieContainer cookies = LoginHelper.GetInstance().GetLoginInfoCookies();
+ if (cookies != null)
+ {
+ request.CookieContainer = cookies;
+ }
+ }
+
+ //// post请求,发送参数
+ //if (method == "POST" && parameters != null)
+ //{
+ // StringBuilder builder = new StringBuilder();
+ // int i = 0;
+ // foreach (var item in parameters)
+ // {
+ // if (i > 0) builder.Append("&");
+ // builder.AppendFormat("{0}={1}", item.Key, item.Value);
+ // i++;
+ // }
+ // byte[] data = Encoding.UTF8.GetBytes(builder.ToString());
+ // request.ContentLength = data.Length;
+
+ // Stream reqStream = request.GetRequestStream();
+ // reqStream.Write(data, 0, data.Length);
+ // reqStream.Close();
+
+ // Console.WriteLine("\n" + builder.ToString() + "\t" + data.Length + "\n");
+ //}
+
+ //HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ //Stream responseStream = response.GetResponseStream();
+ //StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
+ //string str = streamReader.ReadToEnd();
+ //streamReader.Close();
+ //responseStream.Close();
+
+ string html = string.Empty;
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ {
+ if (response.ContentEncoding.ToLower().Contains("gzip"))
+ {
+ using (GZipStream stream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress))
+ {
+ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ html = reader.ReadToEnd();
+ }
+ }
+ }
+ else if (response.ContentEncoding.ToLower().Contains("deflate"))
+ {
+ using (DeflateStream stream = new DeflateStream(response.GetResponseStream(), CompressionMode.Decompress))
+ {
+ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ html = reader.ReadToEnd();
+ }
+ }
+ }
+ else if (response.ContentEncoding.ToLower().Contains("br"))
+ {
+ using (BrotliStream stream = new BrotliStream(response.GetResponseStream(), CompressionMode.Decompress))
+ {
+ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ html = reader.ReadToEnd();
+ }
+ }
+ }
+ else
+ {
+ using (Stream stream = response.GetResponseStream())
+ {
+ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ html = reader.ReadToEnd();
+ }
+ }
+ }
+ }
+
+ return html;
+ }
+ catch (WebException e)
+ {
+ Console.WriteLine("RequestWeb()发生Web异常: {0}", e);
+ return RequestWeb(url, referer, method, parameters, retry - 1);
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("RequestWeb()发生IO异常: {0}", e);
+ return RequestWeb(url, referer, method, parameters, retry - 1);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("RequestWeb()发生其他异常: {0}", e);
+ return RequestWeb(url, referer, method, parameters, retry - 1);
+ }
+ }
+
+ ///
+ /// 解析二维码登录返回的url,用于设置cookie
+ ///
+ ///
+ ///
+ public static CookieContainer ParseCookie(string url)
+ {
+ CookieContainer cookieContainer = new CookieContainer();
+
+ if (url == null || url == "") { return cookieContainer; }
+
+ string[] strList = url.Split('?');
+ if (strList.Count() < 2) { return cookieContainer; }
+
+ string[] strList2 = strList[1].Split('&');
+ if (strList2.Count() == 0) { return cookieContainer; }
+
+ // 获取expires
+ string expires = strList2.FirstOrDefault(it => it.Contains("Expires")).Split('=')[1];
+ DateTime dateTime = DateTime.Now;
+ dateTime = dateTime.AddSeconds(int.Parse(expires));
+
+ foreach (var item in strList2)
+ {
+ string[] strList3 = item.Split('=');
+ if (strList3.Count() < 2) { continue; }
+
+ string name = strList3[0];
+ string value = strList3[1];
+
+ // 不需要
+ if (name == "Expires" || name == "gourl") { continue; }
+
+ // 添加cookie
+ cookieContainer.Add(new Cookie(name, value, "/", ".bilibili.com") { Expires = dateTime });
+#if DEBUG
+ Console.WriteLine(name + ": " + value + "\t" + cookieContainer.Count);
+#endif
+ }
+
+ return cookieContainer;
+ }
+
+ ///
+ /// 将CookieContainer中的所有的Cookie读出来
+ ///
+ ///
+ ///
+ public static List GetAllCookies(CookieContainer cc)
+ {
+ List lstCookies = new List();
+
+ Hashtable table = (Hashtable)cc.GetType().InvokeMember("m_domainTable",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField |
+ System.Reflection.BindingFlags.Instance, null, cc, new object[] { });
+
+ foreach (object pathList in table.Values)
+ {
+ SortedList lstCookieCol = (SortedList)pathList.GetType().InvokeMember("m_list",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField
+ | System.Reflection.BindingFlags.Instance, null, pathList, new object[] { });
+ foreach (CookieCollection colCookies in lstCookieCol.Values)
+ foreach (Cookie c in colCookies) lstCookies.Add(c);
+ }
+
+ return lstCookies;
+ }
+
+ ///
+ /// 写入cookies到磁盘
+ ///
+ ///
+ ///
+ ///
+ public static bool WriteCookiesToDisk(string file, CookieContainer cookieJar)
+ {
+ return WriteObjectToDisk(file, cookieJar);
+ }
+
+ ///
+ /// 从磁盘读取cookie
+ ///
+ ///
+ ///
+ public static CookieContainer ReadCookiesFromDisk(string file)
+ {
+ return (CookieContainer)ReadObjectFromDisk(file);
+ }
+
+ ///
+ /// 写入历史数据到磁盘
+ ///
+ ///
+ ///
+ ///
+ public static bool WriteHistoryToDisk(string file, HistoryEntity history)
+ {
+ return WriteObjectToDisk(file, history);
+ }
+
+ ///
+ /// 从磁盘读取历史数据
+ ///
+ ///
+ ///
+ public static HistoryEntity ReadHistoryFromDisk(string file)
+ {
+ return (HistoryEntity)ReadObjectFromDisk(file);
+ }
+
+ ///
+ /// 写入序列化对象到磁盘
+ ///
+ ///
+ ///
+ ///
+ public static bool WriteObjectToDisk(string file, object obj)
+ {
+ try
+ {
+ using (Stream stream = File.Create(file))
+ {
+#if DEBUG
+ Console.Out.Write("Writing object to disk... ");
+#endif
+ BinaryFormatter formatter = new BinaryFormatter();
+ formatter.Serialize(stream, obj);
+#if DEBUG
+ Console.Out.WriteLine("Done.");
+#endif
+ return true;
+ }
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("WriteObjectToDisk()发生IO异常: {0}", e);
+ return false;
+ }
+ catch (Exception e)
+ {
+ //Console.Out.WriteLine("Problem writing object to disk: " + e.GetType());
+ Console.WriteLine("WriteObjectToDisk()发生异常: {0}", e);
+ return false;
+ }
+ }
+
+ ///
+ /// 从磁盘读取序列化对象
+ ///
+ ///
+ ///
+ public static object ReadObjectFromDisk(string file)
+ {
+ try
+ {
+ using (Stream stream = File.Open(file, FileMode.Open))
+ {
+#if DEBUG
+ Console.Out.Write("Reading object from disk... ");
+#endif
+ BinaryFormatter formatter = new BinaryFormatter();
+#if DEBUG
+ Console.Out.WriteLine("Done.");
+#endif
+ return formatter.Deserialize(stream);
+ }
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("ReadObjectFromDisk()发生IO异常: {0}", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ //Console.Out.WriteLine("Problem reading object from disk: " + e.GetType());
+ Console.WriteLine("ReadObjectFromDisk()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ ///
+ /// 生成二维码
+ ///
+ /// 信息
+ /// 版本 1 ~ 40
+ /// 像素点大小
+ /// 图标路径
+ /// 图标尺寸
+ /// 图标边框厚度
+ /// 二维码白边
+ /// 位图
+ public static Bitmap EncodeQRCode(string msg, int version, int pixel, string icon_path, int icon_size, int icon_border, bool white_edge)
+ {
+ QRCoder.QRCodeGenerator code_generator = new QRCoder.QRCodeGenerator();
+
+ QRCoder.QRCodeData code_data = code_generator.CreateQrCode(msg, QRCoder.QRCodeGenerator.ECCLevel.H/* 这里设置容错率的一个级别 */, true, false, QRCoder.QRCodeGenerator.EciMode.Utf8, version);
+
+ QRCoder.QRCode code = new QRCoder.QRCode(code_data);
+
+ Bitmap icon;
+ if (icon_path == null || icon_path == "")
+ {
+ icon = null;
+ }
+ else
+ {
+ icon = new Bitmap(icon_path);
+ }
+
+ Bitmap bmp = code.GetGraphic(pixel, Color.Black, Color.White, icon, icon_size, icon_border, white_edge);
+ return bmp;
+ }
+
+ }
+
+}
diff --git a/src/Core/VideoZone.cs b/src/Core/VideoZone.cs
new file mode 100644
index 0000000..3a7a16d
--- /dev/null
+++ b/src/Core/VideoZone.cs
@@ -0,0 +1,209 @@
+using System.Collections.Generic;
+
+namespace Core
+{
+ public class VideoZone
+ {
+ private static VideoZone that;
+ private readonly List zones = new List();
+
+ ///
+ /// 使用单例模式获取分区,注意未搜索到的分区需要额外处理
+ ///
+ ///
+ public static VideoZone Instance()
+ {
+ if (that == null)
+ {
+ that = new VideoZone();
+ }
+ return that;
+ }
+
+ public List GetZone()
+ {
+ return zones;
+ }
+
+ private VideoZone()
+ {
+ //动画
+ zones.Add(new ZoneAttr(1, "douga", "动画")); // 主分区
+ zones.Add(new ZoneAttr(24, "mad", "MAD·AMV", 1)); //具有一定制作程度的动画或静画的二次创作视频
+ zones.Add(new ZoneAttr(25, "mmd", "MMD·3D", 1)); //使用MMD(MikuMikuDance)和其他3D建模类软件制作的视频
+ zones.Add(new ZoneAttr(47, "voice", "短片·手书·配音", 1)); //追求创新并具有强烈特色的短片、手书(绘)及ACG相关配音
+ zones.Add(new ZoneAttr(210, "garage_kit", "手办·模玩", 1)); //手办模玩的测评、改造或其他衍生内容
+ zones.Add(new ZoneAttr(86, "tokusatsu", "特摄", 1)); //特摄相关衍生视频
+ zones.Add(new ZoneAttr(27, "other", "综合", 1)); //以动画及动画相关内容为素材,包括但不仅限于音频替换、杂谈、排行榜等内容
+
+ //番剧
+ zones.Add(new ZoneAttr(13, "anime", "番剧")); // 主分区
+ zones.Add(new ZoneAttr(33, "serial", "连载动画", 13)); //当季连载的动画番剧
+ zones.Add(new ZoneAttr(32, "finish", "完结动画", 13)); //已完结的动画番剧合集
+ zones.Add(new ZoneAttr(51, "information", "资讯", 13)); //动画番剧相关资讯视频
+ zones.Add(new ZoneAttr(152, "offical", "官方延伸", 13)); //动画番剧为主题的宣传节目、采访视频,及声优相关视频
+
+ //国创
+ zones.Add(new ZoneAttr(167, "guochuang", "国创")); // 主分区
+ zones.Add(new ZoneAttr(153, "chinese", "国产动画", 167)); //我国出品的PGC动画
+ zones.Add(new ZoneAttr(168, "original", "国产原创相关", 167)); //
+ zones.Add(new ZoneAttr(169, "puppetry", "布袋戏", 167)); //
+ zones.Add(new ZoneAttr(195, "motioncomic", "动态漫·广播剧", 167)); //
+ zones.Add(new ZoneAttr(170, "information", "资讯", 167)); //
+
+ //音乐
+ zones.Add(new ZoneAttr(3, "music", "音乐")); // 主分区
+ zones.Add(new ZoneAttr(28, "original", "原创音乐", 3)); //个人或团队制作以音乐为主要原创因素的歌曲或纯音乐
+ zones.Add(new ZoneAttr(31, "cover", "翻唱", 3)); //一切非官方的人声再演绎歌曲作品
+ zones.Add(new ZoneAttr(30, "vocaloid", "VOCALOID·UTAU", 3)); //以雅马哈Vocaloid和UTAU引擎为基础,包含其他调教引擎,运用各类音源进行的歌曲创作内容
+ zones.Add(new ZoneAttr(194, "electronic", "电音", 3)); //以电子合成器、音乐软体等产生的电子声响制作的音乐
+ zones.Add(new ZoneAttr(59, "perform", "演奏", 3)); //传统或非传统乐器及器材的演奏作品
+ zones.Add(new ZoneAttr(193, "mv", "MV", 3)); //音乐录影带,为搭配音乐而拍摄的短片
+ zones.Add(new ZoneAttr(29, "live", "音乐现场", 3)); //音乐实况表演视频
+ zones.Add(new ZoneAttr(130, "other", "音乐综合", 3)); //收录无法定义到其他音乐子分区的音乐视频
+
+ //舞蹈
+ zones.Add(new ZoneAttr(129, "dance", "舞蹈")); // 主分区
+ zones.Add(new ZoneAttr(20, "otaku", "宅舞", 129)); //与ACG相关的翻跳、原创舞蹈
+ zones.Add(new ZoneAttr(198, "hiphop", "街舞", 129)); //收录街舞相关内容,包括赛事现场、舞室作品、个人翻跳、FREESTYLE等
+ zones.Add(new ZoneAttr(199, "star", "明星舞蹈", 129)); //国内外明星发布的官方舞蹈及其翻跳内容
+ zones.Add(new ZoneAttr(200, "china", "中国舞", 129)); //传承中国艺术文化的舞蹈内容,包括古典舞、民族民间舞、汉唐舞、古风舞等
+ zones.Add(new ZoneAttr(154, "three_d", "舞蹈综合", 129)); //收录无法定义到其他舞蹈子分区的舞蹈视频
+ zones.Add(new ZoneAttr(156, "demo", "舞蹈教程", 129)); //镜面慢速,动作分解,基础教程等具有教学意义的舞蹈视频
+
+ //游戏
+ zones.Add(new ZoneAttr(4, "game", "游戏")); // 主分区
+ zones.Add(new ZoneAttr(17, "stand_alone", "单机游戏", 4)); //以所有平台(PC、主机、移动端)的单机或联机游戏为主的视频内容,包括游戏预告、CG、实况解说及相关的评测、杂谈与视频剪辑等
+ zones.Add(new ZoneAttr(171, "esports", "电子竞技", 4)); //具有高对抗性的电子竞技游戏项目,其相关的赛事、实况、攻略、解说、短剧等视频。
+ zones.Add(new ZoneAttr(172, "mobile", "手机游戏", 4)); //以手机及平板设备为主要平台的游戏,其相关的实况、攻略、解说、短剧、演示等视频。
+ zones.Add(new ZoneAttr(65, "online", "网络游戏", 4)); //由网络运营商运营的多人在线游戏,以及电子竞技的相关游戏内容。包括赛事、攻略、实况、解说等相关视频
+ zones.Add(new ZoneAttr(173, "board", "桌游棋牌", 4)); //桌游、棋牌、卡牌对战等及其相关电子版游戏的实况、攻略、解说、演示等视频。
+ zones.Add(new ZoneAttr(121, "gmv", "GMV", 4)); //由游戏素材制作的MV视频。以游戏内容或CG为主制作的,具有一定创作程度的MV类型的视频
+ zones.Add(new ZoneAttr(136, "music", "音游", 4)); //各个平台上,通过配合音乐与节奏而进行的音乐类游戏视频
+ zones.Add(new ZoneAttr(19, "mugen", "Mugen", 4)); //以Mugen引擎为平台制作、或与Mugen相关的游戏视频
+
+ //知识
+ zones.Add(new ZoneAttr(36, "technology", "知识")); // 主分区
+ zones.Add(new ZoneAttr(201, "science", "科学科普", 36)); //回答你的十万个为什么
+ zones.Add(new ZoneAttr(124, "fun", "社科人文", 36)); //聊聊互联网社会法律,看看历史趣闻艺术,品品文化心理人物
+ zones.Add(new ZoneAttr(207, "finance", "财经", 36)); //宏观经济分析,证券市场动态,商业帝国故事,知识与财富齐飞~
+ zones.Add(new ZoneAttr(208, "campus", "校园学习", 36)); //老师很有趣,同学多人才,我们都爱搞学习
+ zones.Add(new ZoneAttr(209, "career", "职业职场", 36)); //职场加油站,成为最有料的职场人
+ zones.Add(new ZoneAttr(122, "wild", "野生技术协会", 36)); //炫酷技能大集合,是时候展现真正的技术了
+
+ //数码
+ zones.Add(new ZoneAttr(188, "digital", "数码")); // 主分区
+ zones.Add(new ZoneAttr(95, "mobile", "手机平板", 188)); //手机平板、app 和产品教程等相关视频
+ zones.Add(new ZoneAttr(189, "pc", "电脑装机", 188)); //电脑、笔记本、装机配件、外设和软件教程等相关视频
+ zones.Add(new ZoneAttr(190, "photography", "摄影摄像", 188)); //摄影摄像器材、拍摄剪辑技巧、拍摄作品分享等相关视频
+ zones.Add(new ZoneAttr(191, "intelligence_av", "影音智能", 188)); //影音设备、智能硬件、生活家电等相关视频
+
+ //汽车
+ zones.Add(new ZoneAttr(223, "car", "汽车")); // 主分区
+ zones.Add(new ZoneAttr(176, "life", "汽车生活", 223)); //分享汽车及出行相关的生活体验类视频
+ zones.Add(new ZoneAttr(224, "culture", "汽车文化", 223)); //车迷的精神圣地,包括汽车赛事、品牌历史、汽车改装、经典车型和汽车模型等
+ zones.Add(new ZoneAttr(225, "geek", "汽车极客", 223)); //汽车硬核达人聚集地,包括DIY造车、专业评测和技术知识分享
+ zones.Add(new ZoneAttr(226, "smart", "智能出行", 223)); //探索新能源汽车和未来智能出行的前沿阵地
+ zones.Add(new ZoneAttr(227, "strategy", "购车攻略", 223)); //丰富详实的购车建议和新车体验
+
+ //生活
+ zones.Add(new ZoneAttr(160, "life", "生活")); // 主分区
+ zones.Add(new ZoneAttr(138, "funny", "搞笑", 160)); //各种沙雕有趣的搞笑剪辑,挑战,表演,配音等视频
+ zones.Add(new ZoneAttr(21, "daily", "日常", 160)); //记录日常生活,分享生活故事
+ zones.Add(new ZoneAttr(161, "handmake", "手工", 160)); //手工制品的制作过程或成品展示、教程、测评类视频
+ zones.Add(new ZoneAttr(162, "painting", "绘画", 160)); //绘画过程或绘画教程,以及绘画相关的所有视频
+ zones.Add(new ZoneAttr(163, "sports", "运动", 160)); //运动相关的记录、教程、装备评测和精彩瞬间剪辑视频
+ zones.Add(new ZoneAttr(174, "other", "其他", 160)); //对分区归属不明的视频进行归纳整合的特定分区
+
+ //美食
+ zones.Add(new ZoneAttr(211, "food", "美食")); // 主分区
+ zones.Add(new ZoneAttr(76, "make", "美食制作", 211)); //学做人间美味,展示精湛厨艺
+ zones.Add(new ZoneAttr(212, "detective", "美食侦探", 211)); //寻找美味餐厅,发现街头美食
+ zones.Add(new ZoneAttr(213, "measurement", "美食测评", 211)); //吃货世界,品尝世间美味
+ zones.Add(new ZoneAttr(214, "rural", "田园美食", 211)); //品味乡野美食,寻找山与海的味道
+ zones.Add(new ZoneAttr(215, "record", "美食记录", 211)); //记录一日三餐,给生活添一点幸福感
+
+ //动物圈
+ zones.Add(new ZoneAttr(217, "animal", "动物圈")); // 主分区
+ zones.Add(new ZoneAttr(218, "cat", "喵星人", 217)); //喵喵喵喵喵
+ zones.Add(new ZoneAttr(219, "dog", "汪星人", 217)); //汪汪汪汪汪
+ zones.Add(new ZoneAttr(220, "panda", "大熊猫", 217)); //芝麻汤圆营业中
+ zones.Add(new ZoneAttr(221, "wild_animal", "野生动物", 217)); //内有“猛兽”出没
+ zones.Add(new ZoneAttr(222, "reptiles", "爬宠", 217)); //鳞甲有灵
+ zones.Add(new ZoneAttr(75, "animal_composite", "动物综合", 217)); //收录除上述子分区外,其余动物相关视频以及非动物主体或多个动物主体的动物相关延伸内容
+
+ //鬼畜
+ zones.Add(new ZoneAttr(119, "kichiku", "鬼畜")); // 主分区
+ zones.Add(new ZoneAttr(22, "guide", "鬼畜调教", 119)); //使用素材在音频、画面上做一定处理,达到与BGM一定的同步感
+ zones.Add(new ZoneAttr(26, "mad", "音MAD", 119)); //使用素材音频进行一定的二次创作来达到还原原曲的非商业性质稿件
+ zones.Add(new ZoneAttr(126, "manual_vocaloid", "人力VOCALOID", 119)); //将人物或者角色的无伴奏素材进行人工调音,使其就像VOCALOID一样歌唱的技术
+ zones.Add(new ZoneAttr(216, "theatre", "鬼畜剧场", 119)); //使用素材进行人工剪辑编排的有剧情的作品
+ zones.Add(new ZoneAttr(127, "course", "教程演示", 119)); //鬼畜相关的教程演示
+
+ //时尚
+ zones.Add(new ZoneAttr(155, "fashion", "时尚")); // 主分区
+ zones.Add(new ZoneAttr(157, "makeup", "美妆", 155)); //涵盖妆容、发型、美甲等教程,彩妆、护肤相关产品测评、分享等
+ zones.Add(new ZoneAttr(158, "clothing", "服饰", 155)); //服饰风格、搭配技巧相关的展示和教程视频
+ zones.Add(new ZoneAttr(164, "aerobics", "健身", 155)); //器械、有氧、拉伸运动等,以达到强身健体、减肥瘦身、形体塑造目的
+ zones.Add(new ZoneAttr(159, "catwalk", "T台", 155)); //发布会走秀现场及模特相关时尚片、采访、后台花絮
+ zones.Add(new ZoneAttr(192, "trends", "风尚标", 155)); //时尚明星专访、街拍、时尚购物相关知识科普
+
+ //资讯
+ zones.Add(new ZoneAttr(202, "information", "资讯")); // 主分区
+ zones.Add(new ZoneAttr(203, "hotspot", "热点", 202)); //全民关注的时政热门资讯
+ zones.Add(new ZoneAttr(204, "global", "环球", 202)); //全球范围内发生的具有重大影响力的事件动态
+ zones.Add(new ZoneAttr(205, "social", "社会", 202)); //日常生活的社会事件、社会问题、社会风貌的报道
+ zones.Add(new ZoneAttr(206, "multiple", "综合", 202)); //除上述领域外其它垂直领域的综合资讯
+
+ //娱乐
+ zones.Add(new ZoneAttr(5, "ent", "娱乐")); // 主分区
+ zones.Add(new ZoneAttr(71, "variety", "综艺", 5)); //国内外有趣的综艺和综艺相关精彩剪辑
+ zones.Add(new ZoneAttr(137, "star", "明星", 5)); //娱乐圈动态、明星资讯相关
+
+ //影视
+ zones.Add(new ZoneAttr(181, "cinephile", "影视")); // 主分区
+ zones.Add(new ZoneAttr(182, "cinecism", "影视杂谈", 181)); //影视评论、解说、吐槽、科普等
+ zones.Add(new ZoneAttr(183, "montage", "影视剪辑", 181)); //对影视素材进行剪辑再创作的视频
+ zones.Add(new ZoneAttr(85, "shortfilm", "短片", 181)); //追求自我表达且具有特色的短片
+ zones.Add(new ZoneAttr(184, "trailer_info", "预告·资讯", 181)); //影视类相关资讯,预告,花絮等视频
+
+ //纪录片
+ zones.Add(new ZoneAttr(177, "documentary", "纪录片")); // 主分区
+ zones.Add(new ZoneAttr(37, "history", "人文·历史", 177)); //
+ zones.Add(new ZoneAttr(178, "science", "科学·探索·自然", 177)); //
+ zones.Add(new ZoneAttr(179, "military", "军事", 177)); //
+ zones.Add(new ZoneAttr(180, "travel", "社会·美食·旅行", 177)); //
+
+ //电影
+ zones.Add(new ZoneAttr(23, "movie", "电影")); // 主分区
+ zones.Add(new ZoneAttr(147, "chinese", "华语电影", 23)); //
+ zones.Add(new ZoneAttr(145, "west", "欧美电影", 23)); //
+ zones.Add(new ZoneAttr(146, "japan", "日本电影", 23)); //
+ zones.Add(new ZoneAttr(83, "movie", "其他国家", 23)); //
+
+ //电视剧
+ zones.Add(new ZoneAttr(11, "tv", "电视剧")); // 主分区
+ zones.Add(new ZoneAttr(185, "mainland", "国产剧", 11)); //
+ zones.Add(new ZoneAttr(187, "overseas", "海外剧", 11)); //
+
+ }
+ }
+
+ public class ZoneAttr
+ {
+ public int Id { get; }
+ public string Type { get; }
+ public string Name { get; }
+ public int ParentId { get; }
+
+ public ZoneAttr(int id, string type, string name, int parentId = 0)
+ {
+ Id = id;
+ Type = type;
+ Name = name;
+ ParentId = parentId;
+ }
+
+ }
+
+}
diff --git a/src/Core/api/danmaku/BiliDanmaku.cs b/src/Core/api/danmaku/BiliDanmaku.cs
new file mode 100644
index 0000000..6218273
--- /dev/null
+++ b/src/Core/api/danmaku/BiliDanmaku.cs
@@ -0,0 +1,36 @@
+namespace Core.api.danmaku.entity
+{
+ public class BiliDanmaku
+ {
+ public long Id { get; set; } //弹幕dmID
+ public int Progress { get; set; } //出现时间
+ public int Mode { get; set; } //弹幕类型
+ public int Fontsize { get; set; } //文字大小
+ public uint Color { get; set; } //弹幕颜色
+ public string MidHash { get; set; } //发送者UID的HASH
+ public string Content { get; set; } //弹幕内容
+ public long Ctime { get; set; } //发送时间
+ public int Weight { get; set; } //权重
+ //public string Action { get; set; } //动作?
+ public int Pool { get; set; } //弹幕池
+
+ public override string ToString()
+ {
+ //return base.ToString();
+
+ string separator = "\n";
+ return $"id: {Id}{separator}" +
+ $"progress: {Progress}{separator}" +
+ $"mode: {Mode}{separator}" +
+ $"fontsize: {Fontsize}{separator}" +
+ $"color: {Color}{separator}" +
+ $"midHash: {MidHash}{separator}" +
+ $"content: {Content}{separator}" +
+ $"ctime: {Ctime}{separator}" +
+ $"weight: {Weight}{separator}" +
+ //$"action: {Action}{separator}" +
+ $"pool: {Pool}";
+ }
+
+ }
+}
diff --git a/src/Core/api/danmaku/DanmakuProtobuf.cs b/src/Core/api/danmaku/DanmakuProtobuf.cs
new file mode 100644
index 0000000..e2c8180
--- /dev/null
+++ b/src/Core/api/danmaku/DanmakuProtobuf.cs
@@ -0,0 +1,115 @@
+using Core.api.danmaku.entity;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Core.api.danmaku
+{
+ ///
+ /// protobuf弹幕
+ ///
+ public class DanmakuProtobuf
+ {
+ private static DanmakuProtobuf instance;
+
+ ///
+ /// 获取DanmakuProtobuf实例
+ ///
+ ///
+ public static DanmakuProtobuf GetInstance()
+ {
+ if (instance == null)
+ {
+ instance = new DanmakuProtobuf();
+ }
+ return instance;
+ }
+
+ ///
+ /// 隐藏DanmakuProtobuf()方法,必须使用单例模式
+ ///
+ private DanmakuProtobuf() { }
+
+ ///
+ /// 下载6分钟内的弹幕,返回弹幕列表
+ ///
+ /// 稿件avID
+ /// 视频CID
+ /// 分包,每6分钟一包
+ ///
+ public List GetDanmakuProto(long avid, long cid, int segmentIndex)
+ {
+ string url = $"https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid={cid}&pid={avid}&segment_index={segmentIndex}";
+ string referer = "https://www.bilibili.com";
+
+ FileDownloadUtil fileDownload = new FileDownloadUtil();
+ fileDownload.Init(url, referer, Path.GetTempPath() + "downkyi/danmaku", $"{cid}-{segmentIndex}.proto", "DanmakuProtobuf");
+ fileDownload.Download();
+
+ var danmakuList = new List();
+
+ DmSegMobileReply danmakus;
+ try
+ {
+ using (var input = File.OpenRead(Path.GetTempPath() + $"downkyi/danmaku/{cid}-{segmentIndex}.proto"))
+ {
+ danmakus = DmSegMobileReply.Parser.ParseFrom(input);
+ if (danmakus == null || danmakus.Elems == null)
+ {
+ return danmakuList;
+ }
+
+ foreach (var dm in danmakus.Elems)
+ {
+ var danmaku = new BiliDanmaku
+ {
+ Id = dm.Id,
+ Progress = dm.Progress,
+ Mode = dm.Mode,
+ Fontsize = dm.Fontsize,
+ Color = dm.Color,
+ MidHash = dm.MidHash,
+ Content = dm.Content,
+ Ctime = dm.Ctime,
+ Weight = dm.Weight,
+ //Action = dm.Action,
+ Pool = dm.Pool
+ };
+ danmakuList.Add(danmaku);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+#if DEBUG
+ Console.WriteLine("发生异常: {0}", e);
+#endif
+ return null;
+ }
+
+ return danmakuList;
+ }
+
+ ///
+ /// 下载所有弹幕,返回弹幕列表
+ ///
+ /// 稿件avID
+ /// 视频CID
+ ///
+ public List GetAllDanmakuProto(long avid, long cid)
+ {
+ var danmakuList = new List();
+
+ int segmentIndex = 0;
+ while (true)
+ {
+ segmentIndex += 1;
+ var danmakus = GetDanmakuProto(avid, cid, segmentIndex);
+ if (danmakus == null) { break; }
+ danmakuList.AddRange(danmakus);
+ }
+ return danmakuList;
+ }
+
+ }
+}
diff --git a/src/Core/api/danmaku/proto/Danmaku.cs b/src/Core/api/danmaku/proto/Danmaku.cs
new file mode 100644
index 0000000..75fb7b6
--- /dev/null
+++ b/src/Core/api/danmaku/proto/Danmaku.cs
@@ -0,0 +1,814 @@
+//
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: Danmaku.proto
+//
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+/// Holder for reflection information generated from Danmaku.proto
+public static partial class DanmakuReflection {
+
+ #region Descriptor
+ /// File descriptor for Danmaku.proto
+ public static pbr::FileDescriptor Descriptor {
+ get { return descriptor; }
+ }
+ private static pbr::FileDescriptor descriptor;
+
+ static DanmakuReflection() {
+ byte[] descriptorData = global::System.Convert.FromBase64String(
+ string.Concat(
+ "Cg1EYW5tYWt1LnByb3RvIsgBCgtEYW5tYWt1RWxlbRIKCgJpZBgBIAEoAxIQ",
+ "Cghwcm9ncmVzcxgCIAEoBRIMCgRtb2RlGAMgASgFEhAKCGZvbnRzaXplGAQg",
+ "ASgFEg0KBWNvbG9yGAUgASgNEg8KB21pZEhhc2gYBiABKAkSDwoHY29udGVu",
+ "dBgHIAEoCRINCgVjdGltZRgIIAEoAxIOCgZ3ZWlnaHQYCSABKAUSDgoGYWN0",
+ "aW9uGAogASgJEgwKBHBvb2wYCyABKAUSDQoFaWRTdHIYDCABKAkiLwoQRG1T",
+ "ZWdNb2JpbGVSZXBseRIbCgVlbGVtcxgBIAMoCzIMLkRhbm1ha3VFbGVtYgZw",
+ "cm90bzM="));
+ descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+ new pbr::FileDescriptor[] { },
+ new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
+ new pbr::GeneratedClrTypeInfo(typeof(global::DanmakuElem), global::DanmakuElem.Parser, new[]{ "Id", "Progress", "Mode", "Fontsize", "Color", "MidHash", "Content", "Ctime", "Weight", "Action", "Pool", "IdStr" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::DmSegMobileReply), global::DmSegMobileReply.Parser, new[]{ "Elems" }, null, null, null, null)
+ }));
+ }
+ #endregion
+
+}
+#region Messages
+public sealed partial class DanmakuElem : pb::IMessage
+#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+#endif
+{
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DanmakuElem());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::DanmakuReflection.Descriptor.MessageTypes[0]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public DanmakuElem() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public DanmakuElem(DanmakuElem other) : this() {
+ id_ = other.id_;
+ progress_ = other.progress_;
+ mode_ = other.mode_;
+ fontsize_ = other.fontsize_;
+ color_ = other.color_;
+ midHash_ = other.midHash_;
+ content_ = other.content_;
+ ctime_ = other.ctime_;
+ weight_ = other.weight_;
+ action_ = other.action_;
+ pool_ = other.pool_;
+ idStr_ = other.idStr_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public DanmakuElem Clone() {
+ return new DanmakuElem(this);
+ }
+
+ /// Field number for the "id" field.
+ public const int IdFieldNumber = 1;
+ private long id_;
+ ///
+ ///弹幕dmID
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public long Id {
+ get { return id_; }
+ set {
+ id_ = value;
+ }
+ }
+
+ /// Field number for the "progress" field.
+ public const int ProgressFieldNumber = 2;
+ private int progress_;
+ ///
+ ///出现时间
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int Progress {
+ get { return progress_; }
+ set {
+ progress_ = value;
+ }
+ }
+
+ /// Field number for the "mode" field.
+ public const int ModeFieldNumber = 3;
+ private int mode_;
+ ///
+ ///弹幕类型
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int Mode {
+ get { return mode_; }
+ set {
+ mode_ = value;
+ }
+ }
+
+ /// Field number for the "fontsize" field.
+ public const int FontsizeFieldNumber = 4;
+ private int fontsize_;
+ ///
+ ///文字大小
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int Fontsize {
+ get { return fontsize_; }
+ set {
+ fontsize_ = value;
+ }
+ }
+
+ /// Field number for the "color" field.
+ public const int ColorFieldNumber = 5;
+ private uint color_;
+ ///
+ ///弹幕颜色
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public uint Color {
+ get { return color_; }
+ set {
+ color_ = value;
+ }
+ }
+
+ /// Field number for the "midHash" field.
+ public const int MidHashFieldNumber = 6;
+ private string midHash_ = "";
+ ///
+ ///发送者UID的HASH
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string MidHash {
+ get { return midHash_; }
+ set {
+ midHash_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "content" field.
+ public const int ContentFieldNumber = 7;
+ private string content_ = "";
+ ///
+ ///弹幕内容
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string Content {
+ get { return content_; }
+ set {
+ content_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "ctime" field.
+ public const int CtimeFieldNumber = 8;
+ private long ctime_;
+ ///
+ ///发送时间
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public long Ctime {
+ get { return ctime_; }
+ set {
+ ctime_ = value;
+ }
+ }
+
+ /// Field number for the "weight" field.
+ public const int WeightFieldNumber = 9;
+ private int weight_;
+ ///
+ ///权重
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int Weight {
+ get { return weight_; }
+ set {
+ weight_ = value;
+ }
+ }
+
+ /// Field number for the "action" field.
+ public const int ActionFieldNumber = 10;
+ private string action_ = "";
+ ///
+ ///动作?
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string Action {
+ get { return action_; }
+ set {
+ action_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "pool" field.
+ public const int PoolFieldNumber = 11;
+ private int pool_;
+ ///
+ ///弹幕池
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int Pool {
+ get { return pool_; }
+ set {
+ pool_ = value;
+ }
+ }
+
+ /// Field number for the "idStr" field.
+ public const int IdStrFieldNumber = 12;
+ private string idStr_ = "";
+ ///
+ ///弹幕dmID(字串形式)
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string IdStr {
+ get { return idStr_; }
+ set {
+ idStr_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as DanmakuElem);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(DanmakuElem other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Id != other.Id) return false;
+ if (Progress != other.Progress) return false;
+ if (Mode != other.Mode) return false;
+ if (Fontsize != other.Fontsize) return false;
+ if (Color != other.Color) return false;
+ if (MidHash != other.MidHash) return false;
+ if (Content != other.Content) return false;
+ if (Ctime != other.Ctime) return false;
+ if (Weight != other.Weight) return false;
+ if (Action != other.Action) return false;
+ if (Pool != other.Pool) return false;
+ if (IdStr != other.IdStr) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Id != 0L) hash ^= Id.GetHashCode();
+ if (Progress != 0) hash ^= Progress.GetHashCode();
+ if (Mode != 0) hash ^= Mode.GetHashCode();
+ if (Fontsize != 0) hash ^= Fontsize.GetHashCode();
+ if (Color != 0) hash ^= Color.GetHashCode();
+ if (MidHash.Length != 0) hash ^= MidHash.GetHashCode();
+ if (Content.Length != 0) hash ^= Content.GetHashCode();
+ if (Ctime != 0L) hash ^= Ctime.GetHashCode();
+ if (Weight != 0) hash ^= Weight.GetHashCode();
+ if (Action.Length != 0) hash ^= Action.GetHashCode();
+ if (Pool != 0) hash ^= Pool.GetHashCode();
+ if (IdStr.Length != 0) hash ^= IdStr.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Id != 0L) {
+ output.WriteRawTag(8);
+ output.WriteInt64(Id);
+ }
+ if (Progress != 0) {
+ output.WriteRawTag(16);
+ output.WriteInt32(Progress);
+ }
+ if (Mode != 0) {
+ output.WriteRawTag(24);
+ output.WriteInt32(Mode);
+ }
+ if (Fontsize != 0) {
+ output.WriteRawTag(32);
+ output.WriteInt32(Fontsize);
+ }
+ if (Color != 0) {
+ output.WriteRawTag(40);
+ output.WriteUInt32(Color);
+ }
+ if (MidHash.Length != 0) {
+ output.WriteRawTag(50);
+ output.WriteString(MidHash);
+ }
+ if (Content.Length != 0) {
+ output.WriteRawTag(58);
+ output.WriteString(Content);
+ }
+ if (Ctime != 0L) {
+ output.WriteRawTag(64);
+ output.WriteInt64(Ctime);
+ }
+ if (Weight != 0) {
+ output.WriteRawTag(72);
+ output.WriteInt32(Weight);
+ }
+ if (Action.Length != 0) {
+ output.WriteRawTag(82);
+ output.WriteString(Action);
+ }
+ if (Pool != 0) {
+ output.WriteRawTag(88);
+ output.WriteInt32(Pool);
+ }
+ if (IdStr.Length != 0) {
+ output.WriteRawTag(98);
+ output.WriteString(IdStr);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Id != 0L) {
+ output.WriteRawTag(8);
+ output.WriteInt64(Id);
+ }
+ if (Progress != 0) {
+ output.WriteRawTag(16);
+ output.WriteInt32(Progress);
+ }
+ if (Mode != 0) {
+ output.WriteRawTag(24);
+ output.WriteInt32(Mode);
+ }
+ if (Fontsize != 0) {
+ output.WriteRawTag(32);
+ output.WriteInt32(Fontsize);
+ }
+ if (Color != 0) {
+ output.WriteRawTag(40);
+ output.WriteUInt32(Color);
+ }
+ if (MidHash.Length != 0) {
+ output.WriteRawTag(50);
+ output.WriteString(MidHash);
+ }
+ if (Content.Length != 0) {
+ output.WriteRawTag(58);
+ output.WriteString(Content);
+ }
+ if (Ctime != 0L) {
+ output.WriteRawTag(64);
+ output.WriteInt64(Ctime);
+ }
+ if (Weight != 0) {
+ output.WriteRawTag(72);
+ output.WriteInt32(Weight);
+ }
+ if (Action.Length != 0) {
+ output.WriteRawTag(82);
+ output.WriteString(Action);
+ }
+ if (Pool != 0) {
+ output.WriteRawTag(88);
+ output.WriteInt32(Pool);
+ }
+ if (IdStr.Length != 0) {
+ output.WriteRawTag(98);
+ output.WriteString(IdStr);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (Id != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(Id);
+ }
+ if (Progress != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Progress);
+ }
+ if (Mode != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Mode);
+ }
+ if (Fontsize != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Fontsize);
+ }
+ if (Color != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Color);
+ }
+ if (MidHash.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(MidHash);
+ }
+ if (Content.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Content);
+ }
+ if (Ctime != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(Ctime);
+ }
+ if (Weight != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Weight);
+ }
+ if (Action.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Action);
+ }
+ if (Pool != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Pool);
+ }
+ if (IdStr.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(IdStr);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(DanmakuElem other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Id != 0L) {
+ Id = other.Id;
+ }
+ if (other.Progress != 0) {
+ Progress = other.Progress;
+ }
+ if (other.Mode != 0) {
+ Mode = other.Mode;
+ }
+ if (other.Fontsize != 0) {
+ Fontsize = other.Fontsize;
+ }
+ if (other.Color != 0) {
+ Color = other.Color;
+ }
+ if (other.MidHash.Length != 0) {
+ MidHash = other.MidHash;
+ }
+ if (other.Content.Length != 0) {
+ Content = other.Content;
+ }
+ if (other.Ctime != 0L) {
+ Ctime = other.Ctime;
+ }
+ if (other.Weight != 0) {
+ Weight = other.Weight;
+ }
+ if (other.Action.Length != 0) {
+ Action = other.Action;
+ }
+ if (other.Pool != 0) {
+ Pool = other.Pool;
+ }
+ if (other.IdStr.Length != 0) {
+ IdStr = other.IdStr;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ Id = input.ReadInt64();
+ break;
+ }
+ case 16: {
+ Progress = input.ReadInt32();
+ break;
+ }
+ case 24: {
+ Mode = input.ReadInt32();
+ break;
+ }
+ case 32: {
+ Fontsize = input.ReadInt32();
+ break;
+ }
+ case 40: {
+ Color = input.ReadUInt32();
+ break;
+ }
+ case 50: {
+ MidHash = input.ReadString();
+ break;
+ }
+ case 58: {
+ Content = input.ReadString();
+ break;
+ }
+ case 64: {
+ Ctime = input.ReadInt64();
+ break;
+ }
+ case 72: {
+ Weight = input.ReadInt32();
+ break;
+ }
+ case 82: {
+ Action = input.ReadString();
+ break;
+ }
+ case 88: {
+ Pool = input.ReadInt32();
+ break;
+ }
+ case 98: {
+ IdStr = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ Id = input.ReadInt64();
+ break;
+ }
+ case 16: {
+ Progress = input.ReadInt32();
+ break;
+ }
+ case 24: {
+ Mode = input.ReadInt32();
+ break;
+ }
+ case 32: {
+ Fontsize = input.ReadInt32();
+ break;
+ }
+ case 40: {
+ Color = input.ReadUInt32();
+ break;
+ }
+ case 50: {
+ MidHash = input.ReadString();
+ break;
+ }
+ case 58: {
+ Content = input.ReadString();
+ break;
+ }
+ case 64: {
+ Ctime = input.ReadInt64();
+ break;
+ }
+ case 72: {
+ Weight = input.ReadInt32();
+ break;
+ }
+ case 82: {
+ Action = input.ReadString();
+ break;
+ }
+ case 88: {
+ Pool = input.ReadInt32();
+ break;
+ }
+ case 98: {
+ IdStr = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+}
+
+public sealed partial class DmSegMobileReply : pb::IMessage
+#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+#endif
+{
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DmSegMobileReply());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::DanmakuReflection.Descriptor.MessageTypes[1]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public DmSegMobileReply() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public DmSegMobileReply(DmSegMobileReply other) : this() {
+ elems_ = other.elems_.Clone();
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public DmSegMobileReply Clone() {
+ return new DmSegMobileReply(this);
+ }
+
+ /// Field number for the "elems" field.
+ public const int ElemsFieldNumber = 1;
+ private static readonly pb::FieldCodec _repeated_elems_codec
+ = pb::FieldCodec.ForMessage(10, global::DanmakuElem.Parser);
+ private readonly pbc::RepeatedField elems_ = new pbc::RepeatedField();
+ ///
+ ///弹幕条目
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public pbc::RepeatedField Elems {
+ get { return elems_; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as DmSegMobileReply);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(DmSegMobileReply other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if(!elems_.Equals(other.elems_)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ hash ^= elems_.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ elems_.WriteTo(output, _repeated_elems_codec);
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ elems_.WriteTo(ref output, _repeated_elems_codec);
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ size += elems_.CalculateSize(_repeated_elems_codec);
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(DmSegMobileReply other) {
+ if (other == null) {
+ return;
+ }
+ elems_.Add(other.elems_);
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ elems_.AddEntriesFrom(input, _repeated_elems_codec);
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ elems_.AddEntriesFrom(ref input, _repeated_elems_codec);
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+}
+
+#endregion
+
+
+#endregion Designer generated code
diff --git a/src/Core/api/danmaku/proto/Danmaku.proto b/src/Core/api/danmaku/proto/Danmaku.proto
new file mode 100644
index 0000000..71f4f54
--- /dev/null
+++ b/src/Core/api/danmaku/proto/Danmaku.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+message DanmakuElem {
+ int64 id = 1; //弹幕dmID
+ int32 progress = 2; //出现时间
+ int32 mode = 3; //弹幕类型
+ int32 fontsize = 4; //文字大小
+ uint32 color = 5; //弹幕颜色
+ string midHash = 6; //发送者UID的HASH
+ string content = 7; //弹幕内容
+ int64 ctime = 8; //发送时间
+ int32 weight = 9; //权重
+ string action = 10; //动作?
+ int32 pool = 11; //弹幕池
+ string idStr = 12; //弹幕dmID(字串形式)
+}
+
+message DmSegMobileReply {
+ repeated DanmakuElem elems = 1; //弹幕条目
+}
diff --git a/src/Core/api/fileDownload/FileDownloadConfig.cs b/src/Core/api/fileDownload/FileDownloadConfig.cs
new file mode 100644
index 0000000..7bea76d
--- /dev/null
+++ b/src/Core/api/fileDownload/FileDownloadConfig.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+
+namespace Core.api.fileDownload
+{
+ [Serializable]
+ internal class FileDownloadConfig
+ {
+ public long DownloadSize { get; set; }
+ public long TotalSize { get; set; }
+ public Queue DownloadQueue { get; set; }
+
+ ///
+ /// 保存文件下载的配置信息,包括总字节数、已下载字节数、下载队列信息
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void SaveConfig(string configFile, long downloadSize, long totalSize, Queue downloadQueue)
+ {
+ var config = new FileDownloadConfig()
+ {
+ DownloadSize = downloadSize,
+ TotalSize = totalSize,
+ DownloadQueue = downloadQueue
+ };
+ Utils.WriteObjectToDisk(configFile, config);
+ }
+
+ ///
+ /// 读取文件下载的配置信息,包括总字节数、已下载字节数、下载队列信息
+ ///
+ ///
+ ///
+ public static FileDownloadConfig ReadConfig(string configFile)
+ {
+ return (FileDownloadConfig)Utils.ReadObjectFromDisk(configFile);
+ }
+
+ }
+}
diff --git a/src/Core/api/fileDownload/FileDownloadEvent.cs b/src/Core/api/fileDownload/FileDownloadEvent.cs
new file mode 100644
index 0000000..d641257
--- /dev/null
+++ b/src/Core/api/fileDownload/FileDownloadEvent.cs
@@ -0,0 +1,24 @@
+namespace Core.api.fileDownload
+{
+ public class FileDownloadEvent
+ {
+
+ //private long _downloadSize;
+ //private long _totalSize;
+ public FileDownloadEvent() { }
+
+ // 每秒下载的速度 B/s
+ public float Speed { get; set; }
+
+ public float Percent
+ {
+ get { return DownloadSize * 100.0f / TotalSize; }
+ }
+
+ public long DownloadSize { get; set; }
+
+ public long TotalSize { get; set; }
+
+
+ }
+}
diff --git a/src/Core/api/fileDownload/FileDownloadHelper.cs b/src/Core/api/fileDownload/FileDownloadHelper.cs
new file mode 100644
index 0000000..de0ad49
--- /dev/null
+++ b/src/Core/api/fileDownload/FileDownloadHelper.cs
@@ -0,0 +1,317 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Core.api.fileDownload
+{
+ public class FileDownloadHelper
+ {
+ private const int DOWNLOAD_BUFFER_SIZE = 102400; //每次下载量 100KB
+ private const int THREAD_BUFFER_SIZE = 10485760; //每个线程每次最大下载大小 设为10MB 不能太小 否则会创建太多的request对象
+ public delegate void ErrorMakedEventHandler(string errorstring);
+ public event ErrorMakedEventHandler ErrorMakedEvent;
+ public delegate void DownloadEventHandler(FileDownloadEvent e);
+ public event DownloadEventHandler DownloadEvent;
+ public delegate void StopEventHandler();
+ public event StopEventHandler StopEvent;
+ private object locker = new object();
+ private long downloadSize = 0; //已经下载的字节
+ private CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
+ private ManualResetEvent mre = new ManualResetEvent(true); //初始化不等待
+ private AutoResetEvent eventFinished = new AutoResetEvent(false);
+
+ private void ThreadWork(string url, FileStream fs, Queue downQueue)
+ {
+ mre.WaitOne();
+ if (cancelTokenSource.IsCancellationRequested)
+ {
+ return;
+ }
+
+ Monitor.Enter(downQueue);
+ if (downQueue.Count == 0)
+ {
+ return;
+ }
+ ThreadDownloadInfo downInfo = downQueue.Dequeue();
+ Monitor.Exit(downQueue);
+
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
+ request.AddRange(downInfo.StartLength); //设置Range值
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ Stream ns = response.GetResponseStream();
+ byte[] nbytes = new byte[DOWNLOAD_BUFFER_SIZE];
+ int temp = 0;
+ int nReadSize = 0;
+ byte[] buffer = new byte[downInfo.Length]; //文件写入缓冲
+ nReadSize = ns.Read(nbytes, 0, Math.Min(DOWNLOAD_BUFFER_SIZE, downInfo.Length));
+ while (temp < downInfo.Length)
+ {
+ mre.WaitOne();
+ Buffer.BlockCopy(nbytes, 0, buffer, temp, nReadSize);
+ lock (locker)
+ {
+ downloadSize += nReadSize;
+ }
+ temp += nReadSize;
+ nReadSize = ns.Read(nbytes, 0, Math.Min(DOWNLOAD_BUFFER_SIZE, downInfo.Length - temp));
+ }
+
+ lock (locker)
+ {
+ fs.Seek(downInfo.StartLength, SeekOrigin.Begin);
+ fs.Write(buffer, 0, buffer.Length);
+ }
+
+ ns.Close();
+ ThreadWork(url, fs, downQueue);
+ }
+
+ public async void StartDownload(FileDownloadInfo info)
+ {
+ if (string.IsNullOrEmpty(info.DownLoadUrl))
+ { throw new Exception("下载地址不能为空!"); }
+ if (!info.DownLoadUrl.ToLower().StartsWith("http://") || !info.DownLoadUrl.ToLower().StartsWith("https://"))
+ { throw new Exception("非法的下载地址!"); }
+
+ downloadSize = 0;
+ await Task.Run(() =>
+ {
+ try
+ {
+ long totalSize = 0;
+ long threadInitedLength = 0; //分配线程任务的下载量
+
+ #region 获取文件信息
+ //打开网络连接
+ HttpWebRequest initRequest = (HttpWebRequest)WebRequest.Create(info.DownLoadUrl);
+ WebResponse initResponse = initRequest.GetResponse();
+ FileInfo fileMsg = FileInfo.GetFileMessage(initResponse);
+ totalSize = fileMsg.Length;
+ if ((!string.IsNullOrEmpty(fileMsg.FileName)) && info.LocalSaveFolder != null)
+ {
+ info.SavePath = Path.Combine(info.LocalSaveFolder, fileMsg.FileName);
+ }
+ //ReaderWriterLock readWriteLock = new ReaderWriterLock();
+ #endregion
+
+ #region 读取配置文件
+ string configPath = info.SavePath.Substring(0, info.SavePath.LastIndexOf(".")) + ".cfg";
+ FileDownloadConfig initInfo = null;
+ if (File.Exists(configPath) && (info.IsNew == false))
+ {
+ initInfo = FileDownloadConfig.ReadConfig(configPath);
+ downloadSize = (long)initInfo.DownloadSize;
+ totalSize = (long)initInfo.TotalSize;
+ }
+ #endregion
+
+ #region 计算速度
+ //Stopwatch MyStopWatch = new Stopwatch();
+ long lastDownloadSize = 0; //上次下载量
+ bool isSendCompleteEvent = false; //是否完成
+ Timer timer = new Timer(new TimerCallback((o) =>
+ {
+ if (!isSendCompleteEvent)
+ {
+ FileDownloadEvent e = new FileDownloadEvent();
+ e.DownloadSize = downloadSize;
+ e.TotalSize = totalSize;
+ if (totalSize > 0 && downloadSize == totalSize)
+ {
+ e.Speed = 0;
+ isSendCompleteEvent = true;
+ eventFinished.Set();
+ }
+ else
+ {
+ e.Speed = downloadSize - lastDownloadSize;
+ lastDownloadSize = downloadSize; //更新上次下载量
+ }
+
+ DownloadEvent(e);
+ }
+
+ }), null, 0, 1000);
+ #endregion
+
+ string tempPath = info.SavePath.Substring(0, info.SavePath.LastIndexOf(".")) + ".dat";
+
+ #region 多线程下载
+ //分配下载队列
+ Queue downQueue = null;
+ if (initInfo == null || info.IsNew)
+ {
+ downQueue = new Queue(); //下载信息队列
+ while (threadInitedLength < totalSize)
+ {
+ ThreadDownloadInfo downInfo = new ThreadDownloadInfo();
+ downInfo.StartLength = threadInitedLength;
+ downInfo.Length = (int)Math.Min(Math.Min(THREAD_BUFFER_SIZE, totalSize - threadInitedLength), totalSize / info.ThreadCount); //下载量
+ downQueue.Enqueue(downInfo);
+ threadInitedLength += downInfo.Length;
+ }
+ }
+ else
+ {
+ downQueue = initInfo.DownloadQueue;
+ }
+
+ FileStream fs = new FileStream(tempPath, FileMode.OpenOrCreate);
+ fs.SetLength(totalSize);
+ int threads = info.ThreadCount;
+
+ for (int i = 0; i < info.ThreadCount; i++)
+ {
+ ThreadPool.QueueUserWorkItem((state) =>
+ {
+ ThreadWork(info.DownLoadUrl, fs, downQueue);
+ if (Interlocked.Decrement(ref threads) == 0)
+ {
+ (state as AutoResetEvent).Set();
+ }
+ }, eventFinished);
+ }
+
+ //等待所有线程完成
+ eventFinished.WaitOne();
+ if (fs != null)
+ {
+ fs.Close();
+ }
+ fs = null;
+ if (File.Exists(info.SavePath))
+ {
+ File.Delete(info.SavePath);
+ }
+
+ if (downloadSize == totalSize)
+ {
+ File.Move(tempPath, info.SavePath);
+ File.Delete(configPath);
+ }
+
+ if (cancelTokenSource.IsCancellationRequested && StopEvent != null)
+ {
+ StopEvent();
+ //保存配置文件
+ FileDownloadConfig.SaveConfig(configPath, downloadSize, totalSize, downQueue);
+ }
+ #endregion
+
+ }
+ catch (Exception ex)
+ {
+ ErrorMakedEvent?.Invoke(ex.Message);
+ }
+ });
+
+ }
+
+ public void Stop()
+ {
+ cancelTokenSource.Cancel();
+ }
+
+ public void Suspend()
+ {
+ mre.Reset();
+ }
+
+ public void Resume()
+ {
+ mre.Set();
+ }
+
+ //#region 获取文件信息
+ //public class FileMessage
+ //{
+ // public long Length { get; set; }
+ // public string FileName { get; set; }
+ //}
+
+ //public FileMessage GetFileMessage(WebResponse response)
+ //{
+ // FileMessage info = new FileMessage();
+
+ // if (response.Headers["Content-Disposition"] != null)
+ // {
+ // Match match = Regex.Match(response.Headers["Content-Disposition"], "filename=(.*)");
+ // if (match.Success)
+ // {
+ // string fileName = match.Groups[1].Value;
+ // Encoding encoding = Encoding.UTF8;
+ // string str = (response as HttpWebResponse).CharacterSet;
+ // if (!string.IsNullOrEmpty(str))
+ // {
+ // encoding = Encoding.GetEncoding(str);
+ // }
+ // info.FileName = System.Web.HttpUtility.UrlDecode(fileName, encoding);
+ // }
+ // }
+
+ // if (response.Headers["Content-Length"] != null)
+ // {
+ // info.Length = long.Parse(response.Headers.Get("Content-Length"));
+ // }
+ // else
+ // {
+ // info.Length = response.ContentLength;
+ // }
+
+ // return info;
+ //}
+
+
+ //#endregion
+
+ //private void SaveConfig(string configPath, long downloadSize, long totalSize, Queue downQueue)
+ //{
+ // stringBuilder sb = new stringBuilder();
+ // sb.Append(downloadSize + ";" + totalSize + ";");
+ // foreach (ThreadDownloadInfo info in downQueue)
+ // {
+ // sb.Append("(" + info.startLength + ",");
+ // sb.Append(info.length + ");");
+ // }
+
+ // byte[] buffer = Encoding.UTF8.GetBytes(sb.Tostring());
+ // string str = Convert.ToBase64string(buffer);
+ // File.WriteAllText(configPath, str);
+ //}
+
+ //private List ReadConfig(string configPath)
+ //{
+ // List list = new List();
+ // string str = File.ReadAllText(configPath);
+ // byte[] buffer = Convert.FromBase64string(str);
+ // str =Encoding.UTF8.Getstring(buffer);
+ // lock (locker)
+ // {
+ // string[] split = str.Split(';');
+ // long downloadSize = Convert.ToInt64(split[0]);
+ // long totalSize = Convert.ToInt64(split[1]);
+ // Queue downQueue = new Queue(); //下载信息队列
+ // foreach (Match match in Regex.Matches(str, "\\((\\d+),(\\d+)\\);"))
+ // {
+ // ThreadDownloadInfo downInfo = new ThreadDownloadInfo();
+ // downInfo.startLength = Convert.ToInt64(match.Groups[1].Value);
+ // downInfo.length = Convert.ToInt32(match.Groups[2].Value);
+ // downQueue.Enqueue(downInfo);
+ // }
+
+ // list.Add(downloadSize);
+ // list.Add(totalSize);
+ // list.Add(downQueue);
+ // }
+
+ // return list;
+ //}
+
+
+ }
+
+}
diff --git a/src/Core/api/fileDownload/FileDownloadInfo.cs b/src/Core/api/fileDownload/FileDownloadInfo.cs
new file mode 100644
index 0000000..e40cbc6
--- /dev/null
+++ b/src/Core/api/fileDownload/FileDownloadInfo.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+
+namespace Core.api.fileDownload
+{
+ public class FileDownloadInfo
+ {
+
+
+ // 下载地址
+ public string DownLoadUrl { get; set; }
+
+ private string _localSaveFolder;
+ // 本地保存路径
+ public string LocalSaveFolder
+ {
+ get { return _localSaveFolder; }
+ set
+ { _localSaveFolder = value; }
+ }
+
+ // 包含文件名的完整保存路径
+ private string _savePath;
+ public string SavePath
+ {
+ get
+ {
+ if (_savePath == null)
+ {
+ if (_localSaveFolder == null)
+ {
+ throw new Exception("本地保存路径不能为空");
+ }
+
+ _savePath = Path.Combine(_localSaveFolder, Path.GetFileName(DownLoadUrl));
+
+ if (File.Exists(_savePath))
+ {
+ if (IsNew)
+ {
+ if (IsOver)
+ {
+ File.Delete(_savePath);
+ }
+ else
+ {
+ _savePath = _savePath.Substring(0, _savePath.LastIndexOf(".")) + "(2)" + _savePath.Substring(_savePath.LastIndexOf("."));
+ }
+ }
+ }
+ }
+
+ return _savePath;
+ }
+ set
+ {
+ _savePath = value;
+ }
+ }
+
+ private int _threadCount = 1;
+ ////// 线程数
+ public int ThreadCount
+ {
+ get { return _threadCount; }
+ set { _threadCount = value; }
+ }
+
+ ////// 是否覆盖已存在的文件
+ public bool IsOver { get; set; }
+
+ ////// 是否重新下载
+ public bool IsNew { get; set; }
+
+
+ }
+}
diff --git a/src/Core/api/fileDownload/FileInfo.cs b/src/Core/api/fileDownload/FileInfo.cs
new file mode 100644
index 0000000..934f84d
--- /dev/null
+++ b/src/Core/api/fileDownload/FileInfo.cs
@@ -0,0 +1,43 @@
+using System.Net;
+using System.Text.RegularExpressions;
+
+namespace Core.api.fileDownload
+{
+ internal class FileInfo
+ {
+ public long Length { get; set; }
+ public string FileName { get; set; }
+
+ ///
+ /// 获取远程文件的信息
+ ///
+ ///
+ ///
+ public static FileInfo GetFileMessage(WebResponse response)
+ {
+ FileInfo info = new FileInfo();
+
+ if (response.Headers["Content-Disposition"] != null)
+ {
+ Match match = Regex.Match(response.Headers["Content-Disposition"], "filename=(.*)");
+ if (match.Success)
+ {
+ string fileName = match.Groups[1].Value;
+ info.FileName = WebUtility.UrlDecode(fileName);
+ }
+ }
+
+ if (response.Headers["Content-Length"] != null)
+ {
+ info.Length = long.Parse(response.Headers.Get("Content-Length"));
+ }
+ else
+ {
+ info.Length = response.ContentLength;
+ }
+
+ return info;
+ }
+
+ }
+}
diff --git a/src/Core/api/fileDownload/ThreadDownloadInfo.cs b/src/Core/api/fileDownload/ThreadDownloadInfo.cs
new file mode 100644
index 0000000..f1e4364
--- /dev/null
+++ b/src/Core/api/fileDownload/ThreadDownloadInfo.cs
@@ -0,0 +1,11 @@
+namespace Core.api.fileDownload
+{
+ internal class ThreadDownloadInfo
+ {
+ // 开始位置
+ public long StartLength { get; set; }
+
+ // 下载量
+ public int Length { get; set; }
+ }
+}
diff --git a/src/Core/api/login/Login.cs b/src/Core/api/login/Login.cs
new file mode 100644
index 0000000..114ff90
--- /dev/null
+++ b/src/Core/api/login/Login.cs
@@ -0,0 +1,107 @@
+using Core.entity2.login;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+
+namespace Core.api.login
+{
+ public class Login
+ {
+ private static Login instance;
+
+ ///
+ /// 获取Login实例
+ ///
+ ///
+ public static Login GetInstance()
+ {
+ if (instance == null)
+ {
+ instance = new Login();
+ }
+ return instance;
+ }
+
+ ///
+ /// 隐藏Login()方法,必须使用单例模式
+ ///
+ private Login() { }
+
+ ///
+ /// 申请二维码URL及扫码密钥(web端)
+ ///
+ ///
+ public LoginUrlOrigin GetLoginUrl()
+ {
+ string getLoginUrl = "https://passport.bilibili.com/qrcode/getLoginUrl";
+ string referer = "https://passport.bilibili.com/login";
+ string response = Utils.RequestWeb(getLoginUrl, referer);
+
+ try
+ {
+ var loginUrl = JsonConvert.DeserializeObject(response);
+ return loginUrl;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetLoginUrl()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ ///
+ /// 使用扫码登录(web端)
+ ///
+ ///
+ ///
+ ///
+ public LoginStatus GetLoginStatus(string oauthKey, string goUrl = "https://www.bilibili.com")
+ {
+ string url = "https://passport.bilibili.com/qrcode/getLoginInfo";
+ string referer = "https://passport.bilibili.com/login";
+
+ Dictionary parameters = new Dictionary
+ {
+ { "oauthKey", oauthKey },
+ { "gourl", goUrl }
+ };
+
+ string response = Utils.RequestWeb(url, referer, "POST", parameters);
+ var loginInfo = new LoginStatus();
+
+ try
+ {
+ if (response.Contains("\"code\":0") || response.Contains("\"code\": 0"))
+ {
+ var ready = JsonConvert.DeserializeObject(response);
+ if (ready == null)
+ { return null; }
+
+ loginInfo.Code = ready.Code;
+ loginInfo.Status = ready.Status;
+ loginInfo.Message = "登录成功";
+ loginInfo.Url = ready.Data.Url;
+ }
+ else
+ {
+ var scanning = JsonConvert.DeserializeObject(response);
+ if (scanning == null)
+ { return null; }
+
+ loginInfo.Code = scanning.Data;
+ loginInfo.Status = scanning.Status;
+ loginInfo.Message = scanning.Message;
+ loginInfo.Url = "";
+ }
+
+ return loginInfo;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetLoginInfo()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ }
+}
diff --git a/src/Core/api/login/LoginHelper.cs b/src/Core/api/login/LoginHelper.cs
new file mode 100644
index 0000000..7d23378
--- /dev/null
+++ b/src/Core/api/login/LoginHelper.cs
@@ -0,0 +1,231 @@
+using Core.settings;
+using System;
+using System.Drawing;
+using System.IO;
+using System.Net;
+using System.Windows.Media.Imaging;
+
+namespace Core.api.login
+{
+ public class LoginHelper
+ {
+ private static LoginHelper instance;
+
+ ///
+ /// 获取LoginHelper实例
+ ///
+ ///
+ public static LoginHelper GetInstance()
+ {
+ if (instance == null)
+ {
+ instance = new LoginHelper();
+ }
+ return instance;
+ }
+
+ ///
+ /// 隐藏LoginHelper()方法,必须使用单例模式
+ ///
+ private LoginHelper() { }
+
+ private static readonly string LOCAL_LOGIN_INFO = Common.ConfigPath + "Login";
+ private static readonly string SecretKey = "Ps*rB$TGaM#&JvOe"; // 16位密码,ps:密码位数没有限制,可任意设置
+
+ ///
+ /// 获得登录二维码
+ ///
+ ///
+ public BitmapImage GetLoginQRCode()
+ {
+ try
+ {
+ string loginUrl = Login.GetInstance().GetLoginUrl().Data.Url;
+ return GetLoginQRCode(loginUrl);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetLoginQRCode()发生异常: {0}", e);
+ return null;
+ }
+ }
+
+ ///
+ /// 根据输入url生成二维码
+ ///
+ ///
+ ///
+ public BitmapImage GetLoginQRCode(string url)
+ {
+ // 设置的参数影响app能否成功扫码
+ Bitmap qrCode = Utils.EncodeQRCode(url, 10, 10, null, 0, 0, false);
+
+ MemoryStream ms = new MemoryStream();
+ qrCode.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
+ byte[] bytes = ms.GetBuffer();
+ ms.Close();
+
+ BitmapImage image = new BitmapImage();
+ image.BeginInit();
+ image.StreamSource = new MemoryStream(bytes);
+ image.EndInit();
+ return image;
+ }
+
+ ///
+ /// 保存登录的cookies到文件
+ ///
+ ///
+ ///
+ public bool SaveLoginInfoCookies(string url)
+ {
+ if (!Directory.Exists(Common.ConfigPath))
+ {
+ Directory.CreateDirectory(Common.ConfigPath);
+ }
+
+ string tempFile = LOCAL_LOGIN_INFO + "-" + Guid.NewGuid().ToString("N");
+ CookieContainer cookieContainer = Utils.ParseCookie(url);
+
+ bool isSucceed = Utils.WriteCookiesToDisk(tempFile, cookieContainer);
+ if (isSucceed)
+ {
+ // 加密密钥,增加机器码
+ string password = SecretKey + MachineCode.GetMachineCodeString();
+
+ try
+ {
+ Encryptor.EncryptFile(tempFile, LOCAL_LOGIN_INFO, password);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("SaveLoginInfoCookies()发生异常: {0}", e);
+ return false;
+ }
+ }
+
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ return isSucceed;
+ }
+
+ ///
+ /// 获得登录的cookies
+ ///
+ ///
+ public CookieContainer GetLoginInfoCookies()
+ {
+ string tempFile = LOCAL_LOGIN_INFO + "-" + Guid.NewGuid().ToString("N");
+
+ if (File.Exists(LOCAL_LOGIN_INFO))
+ {
+ try
+ {
+ // 加密密钥,增加机器码
+ string password = SecretKey + MachineCode.GetMachineCodeString();
+ Encryptor.DecryptFile(LOCAL_LOGIN_INFO, tempFile, password);
+ }
+ catch (CryptoHelpException e)
+ {
+ Console.WriteLine("GetLoginInfoCookies()发生异常: {0}", e);
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ return null;
+ }
+ catch (FileNotFoundException e)
+ {
+ Console.WriteLine("GetLoginInfoCookies()发生异常: {0}", e);
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ return null;
+ }
+ catch (DirectoryNotFoundException e)
+ {
+ Console.WriteLine("GetLoginInfoCookies()发生异常: {0}", e);
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ return null;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("GetLoginInfoCookies()发生异常: {0}", e);
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ return null;
+ }
+ }
+ else { return null; }
+
+ CookieContainer cookies = Utils.ReadCookiesFromDisk(tempFile);
+
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ return cookies;
+ }
+
+ ///
+ /// 返回登录信息的cookies的字符串
+ ///
+ ///
+ public string GetLoginInfoCookiesString()
+ {
+ var cookieContainer = GetLoginInfoCookies();
+ if (cookieContainer == null)
+ {
+ return "";
+ }
+
+ var cookies = Utils.GetAllCookies(cookieContainer);
+
+ string cookie = string.Empty;
+ foreach (var item in cookies)
+ {
+ cookie += item.ToString() + ";";
+ }
+ return cookie.TrimEnd(';');
+ }
+
+ ///
+ /// 注销登录
+ ///
+ ///
+ public bool Logout()
+ {
+ if (File.Exists(LOCAL_LOGIN_INFO))
+ {
+ try
+ {
+ File.Delete(LOCAL_LOGIN_INFO);
+
+ Settings.GetInstance().SetUserInfo(new UserInfoForSetting
+ {
+ Mid = -1,
+ Name = "",
+ IsLogin = false,
+ IsVip = false
+ });
+ return true;
+ }
+ catch (IOException e)
+ {
+ Console.WriteLine("Logout()发生异常: {0}", e);
+ return false;
+ }
+ }
+ return false;
+ }
+
+ }
+}
diff --git a/src/Core/api/users/UserSpace.cs b/src/Core/api/users/UserSpace.cs
index 84421b8..4661fda 100644
--- a/src/Core/api/users/UserSpace.cs
+++ b/src/Core/api/users/UserSpace.cs
@@ -3,6 +3,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace Core.api.users
{
@@ -99,24 +100,28 @@ namespace Core.api.users
/// 视频分区
/// 搜索关键词
///
- public List GetAllPublication(long mid, PublicationOrder order = PublicationOrder.PUBDATE, int tid = 0, string keyword = "")
+ public async Task> GetAllPublication(long mid, int tid = 0, PublicationOrder order = PublicationOrder.PUBDATE, string keyword = "")
{
List result = new List();
- int i = 0;
- while (true)
+ await Task.Run(() =>
{
- i++;
- int ps = 100;
+ int i = 0;
+ while (true)
+ {
+ i++;
+ int ps = 100;
- var data = GetPublication(mid, i, ps, tid, order, keyword);
- //if (data == null) { continue; }
+ var data = GetPublication(mid, i, ps, tid, order, keyword);
+ //if (data == null) { continue; }
- if (data == null || data.Vlist == null || data.Vlist.Count == 0)
- { break; }
+ if (data == null || data.Vlist == null || data.Vlist.Count == 0)
+ { break; }
+
+ result.AddRange(data.Vlist);
+ }
+ });
- result.AddRange(data.Vlist);
- }
return result;
}
@@ -138,7 +143,20 @@ namespace Core.api.users
try
{
- var spacePublication = JsonConvert.DeserializeObject(response);
+ // 忽略play的值为“--”时的类型错误
+ var settings = new JsonSerializerSettings
+ {
+ Error = (sender, args) =>
+ {
+ if (Equals(args.ErrorContext.Member, "play") &&
+ args.ErrorContext.OriginalObject.GetType() == typeof(SpacePublicationListVideo))
+ {
+ args.ErrorContext.Handled = true;
+ }
+ }
+ };
+
+ var spacePublication = JsonConvert.DeserializeObject(response, settings);
if (spacePublication == null || spacePublication.Data == null) { return null; }
return spacePublication.Data.List;
}
@@ -440,7 +458,7 @@ namespace Core.api.users
///
public BangumiFollowData GetBangumiFollow(long mid, BangumiType type, int pn, int ps)
{
- string url = $"https://api.bilibili.com/x/space/bangumi/follow/list?vmid={mid}&type={type.ToString("D")}&pn={pn}&ps={ps}";
+ string url = $"https://api.bilibili.com/x/space/bangumi/follow/list?vmid={mid}&type={type:D}&pn={pn}&ps={ps}";
string referer = "https://www.bilibili.com";
string response = Utils.RequestWeb(url, referer);
diff --git a/src/Core/aria2cNet/Aria2c.cs b/src/Core/aria2cNet/Aria2c.cs
new file mode 100644
index 0000000..6067942
--- /dev/null
+++ b/src/Core/aria2cNet/Aria2c.cs
@@ -0,0 +1,433 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+// 代码来源
+// https://blog.csdn.net/haibin258/article/details/87905628
+
+// Aria2详细信息请看
+// https://aria2.github.io/manual/en/html/aria2c.html#rpc-interface
+
+// 废弃
+namespace Core.aria2cNet
+{
+ //定义一个类用于转码成JSON字符串发送给Aria2 json-rpc接口
+ public class JsonClass
+ {
+ public string Jsonrpc { get; set; }
+ public string Id { get; set; }
+ public string Method { get; set; }
+ public List Params { get; set; }
+ }
+
+ public class Aria2
+ {
+ public static ClientWebSocket webSocket; // 用于连接到Aria2Rpc的客户端
+ public static CancellationToken cancellationToken; // 传播有关应取消操作的通知
+
+ ///
+ /// 连接Aria2Rpc服务器
+ ///
+ ///
+ ///
+ public static async Task ConnectServer(string uri)
+ {
+ webSocket = new ClientWebSocket(); // 用于连接到Aria2Rpc的客户端
+ cancellationToken = new CancellationToken(); // 传播有关应取消操作的通知
+ bool status = false; //储存连接状态
+ try
+ {
+ //连接服务器
+ await webSocket.ConnectAsync(new Uri(uri), cancellationToken);
+ }
+ catch
+ {
+ status = false;
+ }
+ //检查连接是否成功
+ if (webSocket.State == WebSocketState.Open)
+ {
+ status = true;
+ }
+ return status;
+ }
+
+
+ ///
+ /// JsonClass类转为json格式
+ ///
+ ///
+ ///
+ private static string ToJson(JsonClass json)
+ {
+ string str = "{" + "\"jsonrpc\":\"" + json.Jsonrpc + "\",\"id\":\"" + json.Id + "\",\"method\":\"" + json.Method + "\",\"params\":[";
+ for (int i = 0; i < json.Params.Count; i++)
+ {
+ if (json.Method == "aria2.addUri")
+ {
+ str += "[\"" + json.Params[i] + "\"]";
+ }
+ else if (json.Method == "aria2.tellStatus")
+ {
+ if (i == 0)
+ {
+ str += "\"" + json.Params[i] + "\"";
+ }
+ else
+ {
+ str += "[\"" + json.Params[i] + "\"]";
+ }
+ }
+ else
+ {
+ str += "\"" + json.Params[i] + "\"";
+ }
+ if (json.Params.Count - 1 > i)
+ {
+ str += ",";
+ }
+ }
+ str += "]}";
+ //最后得到类似
+ //{"jsonrpc":"2.0","id":"qwer","method":"aria2.addUri","params:"[["http://www.baidu.com"]]}
+ return str;
+ }
+
+
+ ///
+ /// 发送json并返回json消息
+ ///
+ ///
+ ///
+ private static async Task SendAndReceive(JsonClass json)
+ {
+ string str = ToJson(json);
+ try
+ {
+ //发送json数据
+ await webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(str)), WebSocketMessageType.Text, true, cancellationToken);
+ var result = new byte[1024];
+ //接收数据
+ await webSocket.ReceiveAsync(new ArraySegment(result), new CancellationToken());
+ str += "\r\n" + Encoding.UTF8.GetString(result, 0, result.Length) + "\r\n";
+ }
+ catch
+ {
+ str = "连接错误";
+ }
+ return str;
+ }
+
+
+ ///
+ /// 添加新的下载
+ ///
+ ///
+ ///
+ public static async Task AddUri(string uri)
+ {
+ var json = new JsonClass
+ {
+ Jsonrpc = "2.0",
+ Id = "qwer",
+ Method = "aria2.addUri"
+ };
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(uri);
+ json.Params = paramslist;
+ string str = await SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 上传“.torrent”文件添加BitTorrent下载
+ ///
+ ///
+ ///
+ public static async Task AddTorrent(string path)
+ {
+ string str = "";
+ string fsbase64 = "";
+ byte[] fs = File.ReadAllBytes(path);
+ fsbase64 = Convert.ToBase64String(fs); //转为Base64编码
+
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.addTorrent";
+ List paramslist = new List();
+ //添加“.torrent”文件本地地址
+ paramslist.Add(fsbase64);
+ json.Params = paramslist;
+ str = await SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 删除已经停止的任务,强制删除请使用ForceRemove方法
+ ///
+ ///
+ ///
+ public static async Task Remove(string gid)
+ {
+ string str = "";
+ var json = new JsonClass
+ {
+ Jsonrpc = "2.0",
+ Id = "qwer",
+ Method = "aria2.remove"
+ };
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 强制删除
+ ///
+ ///
+ ///
+ public static async Task ForceRemove(string gid)
+ {
+ string str = "";
+ var json = new JsonClass
+ {
+ Jsonrpc = "2.0",
+ Id = "qwer",
+ Method = "aria2.forceRemove"
+ };
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 暂停下载,强制暂停请使用ForcePause方法
+ ///
+ ///
+ ///
+ public static async Task Pause(string gid)
+ {
+ string str = "";
+ var json = new JsonClass
+ {
+ Jsonrpc = "2.0",
+ Id = "qwer",
+ Method = "aria2.pause"
+ };
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 暂停全部任务
+ ///
+ ///
+ ///
+ public static async Task PauseAll()
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.pauseAll";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add("");
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 强制暂停下载
+ ///
+ ///
+ ///
+ public static async Task ForcePause(string gid)
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.forcePause";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 强制暂停全部下载
+ ///
+ ///
+ ///
+ public static async Task ForcePauseAll()
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.forcePauseAll";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add("");
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 把正在下载的任务状态改为等待下载
+ ///
+ ///
+ ///
+ public static async Task PauseToWaiting(string gid)
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.unpause";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 把全部在下载的任务状态改为等待下载
+ ///
+ ///
+ ///
+ public static async Task PauseToWaitingAll()
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.unpauseAll";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add("");
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 返回下载进度
+ ///
+ ///
+ ///
+ public static async Task TellStatus(string gid)
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.tellStatus";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ paramslist.Add("completedLength\", \"totalLength\",\"downloadSpeed");
+
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 返回全局统计信息,例如整体下载和上载速度
+ ///
+ ///
+ ///
+ public static async Task GetGlobalStat(string gid)
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.getGlobalStat";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 返回由gid(字符串)表示的下载中使用的URI 。响应是一个结构数组
+ ///
+ ///
+ ///
+ public static async Task GetUris(string gid)
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.getUris";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+
+ ///
+ /// 返回由gid(字符串)表示的下载文件列表
+ ///
+ ///
+ ///
+ public static async Task GetFiles(string gid)
+ {
+ string str = "";
+ var json = new JsonClass();
+ json.Jsonrpc = "2.0";
+ json.Id = "qwer";
+ json.Method = "aria2.getFiles";
+ List paramslist = new List();
+ //添加下载地址
+ paramslist.Add(gid);
+ json.Params = paramslist;
+ str = await Aria2.SendAndReceive(json);
+ return str;
+ }
+
+ }
+}
diff --git a/src/Core/aria2cNet/AriaManager.cs b/src/Core/aria2cNet/AriaManager.cs
index 8dd9369..a3132ff 100644
--- a/src/Core/aria2cNet/AriaManager.cs
+++ b/src/Core/aria2cNet/AriaManager.cs
@@ -14,14 +14,6 @@ namespace Core.aria2cNet
TellStatus?.Invoke(totalLength, completedLength, speed, gid);
}
- // 全局下载状态
- public delegate void GetGlobalStatusHandler(long speed);
- public event GetGlobalStatusHandler GetGlobalStatus;
- protected virtual void OnGetGlobalStatus(long speed)
- {
- GetGlobalStatus?.Invoke(speed);
- }
-
// 下载结果回调
public delegate void DownloadFinishHandler(bool isSuccess, string downloadPath, string gid, string msg = null);
public event DownloadFinishHandler DownloadFinish;
@@ -30,6 +22,19 @@ namespace Core.aria2cNet
DownloadFinish?.Invoke(isSuccess, downloadPath, gid, msg);
}
+ // 全局下载状态
+ public delegate void GetGlobalStatusHandler(long speed);
+ public event GetGlobalStatusHandler GlobalStatus;
+ protected virtual void OnGlobalStatus(long speed)
+ {
+ GlobalStatus?.Invoke(speed);
+ }
+
+ ///
+ /// 获取gid下载项的状态
+ ///
+ ///
+ ///
public DownloadStatus GetDownloadStatus(string gid)
{
string filePath = "";
@@ -83,12 +88,32 @@ namespace Core.aria2cNet
}
// 降低CPU占用
- Thread.Sleep(500);
+ Thread.Sleep(100);
}
OnDownloadFinish(true, filePath, gid, null);
return DownloadStatus.SUCCESS;
}
+ ///
+ /// 获取全局下载速度
+ ///
+ public async void GetGlobalStatus()
+ {
+ while (true)
+ {
+ // 查询全局status
+ var globalStatus = await AriaClient.GetGlobalStatAsync();
+ if (globalStatus == null || globalStatus.Result == null) { continue; }
+
+ long globalSpeed = long.Parse(globalStatus.Result.DownloadSpeed);
+ // 回调
+ OnGlobalStatus(globalSpeed);
+
+ // 降低CPU占用
+ Thread.Sleep(100);
+ }
+ }
+
//private async void Poll()
//{
diff --git a/src/Core/aria2cNet/client/AriaClient.cs b/src/Core/aria2cNet/client/AriaClient.cs
index 795c3cd..0605cae 100644
--- a/src/Core/aria2cNet/client/AriaClient.cs
+++ b/src/Core/aria2cNet/client/AriaClient.cs
@@ -1035,8 +1035,10 @@ namespace Core.aria2cNet.client
///
private async static Task GetRpcResponseAsync(AriaSendData ariaSend)
{
+ // 去掉null
+ var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
// 转换为json字符串
- string sendJson = JsonConvert.SerializeObject(ariaSend);
+ string sendJson = JsonConvert.SerializeObject(ariaSend, Formatting.Indented, jsonSetting);
// 向服务器请求数据
string result = string.Empty;
await Task.Run(() =>
diff --git a/src/Core/aria2cNet/client/AriaClientWebSocket.cs b/src/Core/aria2cNet/client/AriaClientWebSocket.cs
new file mode 100644
index 0000000..71e88e8
--- /dev/null
+++ b/src/Core/aria2cNet/client/AriaClientWebSocket.cs
@@ -0,0 +1,111 @@
+using Core.aria2cNet.client.entity;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Core.aria2cNet.client
+{
+ public class AriaClientWebSocket
+ {
+ private static readonly string JSONRPC = "2.0";
+ private static readonly string TOKEN = "downkyi";
+
+ public static ClientWebSocket webSocket; // 用于连接到Aria2Rpc的客户端
+ public static CancellationToken cancellationToken; // 传播有关应取消操作的通知
+ public static bool Status; // 储存连接状态
+
+ ///
+ /// 连接Aria2Rpc服务器
+ ///
+ ///
+ ///
+ public static async Task ConnectServerAsync(string uri)
+ {
+ webSocket = new ClientWebSocket(); // 用于连接到Aria2Rpc的客户端
+ cancellationToken = new CancellationToken(); // 传播有关应取消操作的通知
+ Status = false; // 储存连接状态
+
+ try
+ {
+ //连接服务器
+ await webSocket.ConnectAsync(new Uri(uri), cancellationToken);
+ }
+ catch
+ {
+ Status = false;
+ }
+ //检查连接是否成功
+ if (webSocket.State == WebSocketState.Open)
+ {
+ Status = true;
+ }
+ return Status;
+ }
+
+ ///
+ /// add
+ ///
+ ///
+ ///
+ public static async Task AddUriAsync(List uris)
+ {
+ AriaSendOption option = new AriaSendOption
+ {
+ Out = "index测试.html",
+ Dir = "home/"
+ };
+
+ List