From aa737895521bb08b71ab153dd26aedc6dd9c6f8d Mon Sep 17 00:00:00 2001 From: Polaris_cn Date: Sat, 2 Dec 2023 00:11:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=94=99=E8=AF=AF=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=8A=A0=E4=B8=93=E6=A0=8F=E6=8A=95=E5=B8=81?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=B8=8E=E9=A2=86=E5=8F=96=E5=A4=A7=E4=BC=9A?= =?UTF-8?q?=E5=91=98=E7=BB=8F=E9=AA=8C=E7=9A=84=E5=8A=9F=E8=83=BD=20(#617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加条件排除无法进行直播挂机的up主 * 为跳过的主播添加跳过提示 * 专栏投币基本框架已完成 * 专栏签到全流程基本完成,将开发环境硬编码调整为专栏投币模式开始进行挂机测试。 * 修复了一下Bug,目前专栏投币功能已经能正常运行 * 显式添加个性化配置IsDonateCoinForArticle * 添加对专栏投币同时点赞的功能,与视频点赞共享一个参数 * 修复硬币数量多的时候可投硬币数返回值错误 * 添加了一些log,方便调试 * 修改单词拼写错误 * 临时的windows环境构建脚本 * 删除未使用的参数 * 添加卡券状态查询相关api * 完成每日领取大会员经验任务,同时修复在大积分任务中的一些错误 * 修改调用错误 --- scripts/publish.ps1 | 16 + .../Dtos/Article/AddCoinForArticleRequest.cs | 22 + .../Dtos/Article/SearchArticleInfoResponse.cs | 10 + .../Article/SearchArticlesByUpIdFullFto.cs | 19 + .../Dtos/Article/SearchUpArticlesResponse.cs | 18 + .../Dtos/VipTask/VipExperienceRequest.cs | 6 + .../Dtos/VipTask/VouchersInfoResponse.cs | 28 ++ .../BiliBiliAgent/Interfaces/IArticleApi.cs | 45 ++ .../Interfaces/IVipBigPointApi.cs | 7 + .../Extensions/ServiceCollectionExtension.cs | 3 + .../IVipBigPointAppService.cs | 2 + .../DailyTaskAppService.cs | 30 +- .../VipBigPointAppService.cs | 71 +++- .../Options/DailyTaskOptions.cs | 5 + src/Ray.BiliBiliTool.Console/appsettings.json | 1 + .../ArticleDomainService.cs | 395 ++++++++++++++++++ .../DonateCoinDomainService.cs | 4 +- .../Interfaces/IArticleDomainService.cs | 12 + .../LiveDomainService.cs | 8 + test/AppServiceTest/VipServiceTest.cs | 21 + test/BiliAgentTest/VipApiTest.cs | 59 +++ .../ArticleDomainServiceTest.cs | 47 +++ 22 files changed, 818 insertions(+), 11 deletions(-) create mode 100644 scripts/publish.ps1 create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/AddCoinForArticleRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticleInfoResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticlesByUpIdFullFto.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchUpArticlesResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VipExperienceRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VouchersInfoResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs create mode 100644 src/Ray.BiliBiliTool.DomainService/ArticleDomainService.cs create mode 100644 src/Ray.BiliBiliTool.DomainService/Interfaces/IArticleDomainService.cs create mode 100644 test/AppServiceTest/VipServiceTest.cs create mode 100644 test/BiliAgentTest/VipApiTest.cs create mode 100644 test/DomainServiceTest/ArticleDomainServiceTest.cs diff --git a/scripts/publish.ps1 b/scripts/publish.ps1 new file mode 100644 index 000000000..e21417b8c --- /dev/null +++ b/scripts/publish.ps1 @@ -0,0 +1,16 @@ +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime win-x86 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/win-x86 +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime win-x64 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/win-x64 +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime win-arm64 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/win-arm64 +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime linux-x64 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/linux-x64 +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime linux-musl-x64 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/linux-musl-x64 +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime linux-arm64 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/linux-arm64 +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime linux-arm --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/linux-arm +dotnet.exe publish ../src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj --runtime osx-x64 --no-self-contained -c Release -p:PublishSingleFile=true -o ./bin/Publish/osx-x64 +Remove-Item ./bin/Publish/win-x86/*.pdb +Remove-Item ./bin/Publish/win-x64/*.pdb +Remove-Item ./bin/Publish/win-arm64/*.pdb +Remove-Item ./bin/Publish/linux-x64/*.pdb +Remove-Item ./bin/Publish/linux-musl-x64/*.pdb +Remove-Item ./bin/Publish/linux-arm64/*.pdb +Remove-Item ./bin/Publish/linux-arm/*.pdb +Remove-Item ./bin/Publish/osx-x64/*.pdb diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/AddCoinForArticleRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/AddCoinForArticleRequest.cs new file mode 100644 index 000000000..7fd3f3e9d --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/AddCoinForArticleRequest.cs @@ -0,0 +1,22 @@ +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; + +public class AddCoinForArticleRequest +{ + public AddCoinForArticleRequest(long cvid,long mid,string csrf) + { + Aid = cvid; + Upid = mid; + Csrf = csrf; + } + + public long Aid { get; set; } + + public long Upid { get; set; } + + public int Multiply { get; set; } = 1; + + // 必须为2 + public int Avtype { get; private set; } = 2; + + public string Csrf { get; set; } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticleInfoResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticleInfoResponse.cs new file mode 100644 index 000000000..22b9b37a6 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticleInfoResponse.cs @@ -0,0 +1,10 @@ +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; + +public class SearchArticleInfoResponse +{ + public int Like { get; set; } + + public int Coin { get; set; } + + public long Mid { get; set; } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticlesByUpIdFullFto.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticlesByUpIdFullFto.cs new file mode 100644 index 000000000..e4cae3b9b --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchArticlesByUpIdFullFto.cs @@ -0,0 +1,19 @@ +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; + +public class SearchArticlesByUpIdDto +{ + public long Mid { get; set; } + + public int Pn { get; set; } = 1; + + public int Ps { get; set; } = 30; + + public string Sort { get; set; } = "publish_time"; +} + +public class SearchArticlesByUpIdFullDto : SearchArticlesByUpIdDto +{ + public string w_rid { get; set; } + + public long wts { get; set; } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchUpArticlesResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchUpArticlesResponse.cs new file mode 100644 index 000000000..bef59b449 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Article/SearchUpArticlesResponse.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; + +public class SearchUpArticlesResponse +{ + public List Articles { get; set; } + public int Count { get; set; } + +} + +public class ArticleInfo +{ + public long Id { get; set; } + + public string Title { get; set; } + +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VipExperienceRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VipExperienceRequest.cs new file mode 100644 index 000000000..542e6b083 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VipExperienceRequest.cs @@ -0,0 +1,6 @@ +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.VipTask; + +public class VipExperienceRequest +{ + public string csrf { get; set; } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VouchersInfoResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VouchersInfoResponse.cs new file mode 100644 index 000000000..ae85e0fd8 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/VipTask/VouchersInfoResponse.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.VipTask; + +public class VouchersInfoResponse +{ + public List List { get; set; } + public bool IsShortVip { get; set; } + public bool IsFreightOpen { get; set; } + public int Level { get; set; } + public int CurExp { get; set; } + public int NextExp { get; set; } + public bool IsVip { get; set; } + public int IsSeniorMember { get; set; } + public int Format060102 { get; set; } +} + + +public class List +{ + public int Type { get; set; } + public int State { get; set; } + public int ExpireTime { get; set; } + public int VipType { get; set; } + public int NextReceiveDays { get; set; } + public int PeriodEndUnix { get; set; } +} + diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs new file mode 100644 index 000000000..2019bb33b --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Video; +using WebApiClientCore.Attributes; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +{ + + [Header("Host", "api.bilibili.com")] + public interface IArticleApi : IBiliBiliApi + { + [Header("Content-Type", "application/x-www-form-urlencoded")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/web-interface/coin/add")] + Task AddCoinForArticle([FormContent] AddCoinForArticleRequest request, [Header("referer")] string refer = "https://www.bilibili.com/read/cv5806746/?from=search&spm_id_from=333.337.0.0"); + + + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://space.bilibili.com")] + [HttpGet("/x/space/wbi/article")] + Task> SearchUpArticlesByUpId( + [PathQuery] SearchArticlesByUpIdFullDto request); + + /// + /// 获取专栏详情 + /// + /// + /// + [HttpGet("/x/article/viewinfo?id={cvid}")] + Task> SearchArticleInfo(long cvid); + + + [Header("Content-Type", "application/x-www-form-urlencoded")] + [Header("Referer", "https://www.bilibili.com/read/cv{cvid}/?from=search&spm_id_from=333.337.0.0")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/article/like?id={cvid}&type=1&csrf={csrf}")] + Task Like(long cvid, string csrf); + + } + + +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs index ecc0451da..f83122d74 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs @@ -31,5 +31,12 @@ public interface IVipBigPointApi [HttpPost("/pgc/activity/deliver/task/complete")] Task ViewComplete([FormContent] ViewRequest request); + + [HttpGet("/x/vip/privilege/my")] + Task> GetVouchersInfo(); + + [Header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")] + [HttpPost("/x/vip/experience/add")] + Task GetVipExperience([FormContent] VipExperienceRequest request); } } diff --git a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs index 64558e390..92b8831c4 100644 --- a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs +++ b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs @@ -73,6 +73,9 @@ public static IServiceCollection AddBiliBiliClientApi(this IServiceCollection se services.AddBiliBiliClientApi("https://live-trace.bilibili.com"); services.AddBiliBiliClientApi("https://www.bilibili.com", false); + // 添加注入 + services.AddBiliBiliClientApi("https://api.bilibili.com"); + //qinglong var qinglongHost = configuration["QL_URL"] ?? "http://localhost:5600"; services diff --git a/src/Ray.BiliBiliTool.Application.Contracts/IVipBigPointAppService.cs b/src/Ray.BiliBiliTool.Application.Contracts/IVipBigPointAppService.cs index abd10b07f..f78097ef3 100644 --- a/src/Ray.BiliBiliTool.Application.Contracts/IVipBigPointAppService.cs +++ b/src/Ray.BiliBiliTool.Application.Contracts/IVipBigPointAppService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Text; +using System.Threading.Tasks; namespace Ray.BiliBiliTool.Application.Contracts { @@ -12,5 +13,6 @@ namespace Ray.BiliBiliTool.Application.Contracts public interface IVipBigPointAppService : IAppService { + Task VipExpress(); } } diff --git a/src/Ray.BiliBiliTool.Application/DailyTaskAppService.cs b/src/Ray.BiliBiliTool.Application/DailyTaskAppService.cs index 6f82e6d59..db2ee02f9 100644 --- a/src/Ray.BiliBiliTool.Application/DailyTaskAppService.cs +++ b/src/Ray.BiliBiliTool.Application/DailyTaskAppService.cs @@ -23,6 +23,7 @@ public class DailyTaskAppService : AppService, IDailyTaskAppService private readonly ILogger _logger; private readonly IAccountDomainService _accountDomainService; private readonly IVideoDomainService _videoDomainService; + private readonly IArticleDomainService _articleDomainService; private readonly IDonateCoinDomainService _donateCoinDomainService; private readonly IMangaDomainService _mangaDomainService; private readonly ILiveDomainService _liveDomainService; @@ -41,6 +42,7 @@ public DailyTaskAppService( IOptionsMonitor> dicOptions, IAccountDomainService accountDomainService, IVideoDomainService videoDomainService, + IArticleDomainService articleDomainService, IDonateCoinDomainService donateCoinDomainService, IMangaDomainService mangaDomainService, ILiveDomainService liveDomainService, @@ -56,6 +58,7 @@ public DailyTaskAppService( _expDic = dicOptions.Get(Constants.OptionsNames.ExpDictionaryName); _accountDomainService = accountDomainService; _videoDomainService = videoDomainService; + _articleDomainService = articleDomainService; _donateCoinDomainService = donateCoinDomainService; _mangaDomainService = mangaDomainService; _liveDomainService = liveDomainService; @@ -79,7 +82,8 @@ public override async Task DoTaskAsync(CancellationToken cancellationToken) DailyTaskInfo dailyTaskInfo = await GetDailyTaskStatus(); await WatchAndShareVideo(dailyTaskInfo); - await AddCoinsForVideo(userInfo); + + await AddCoins(userInfo); //签到: await LiveSign(); @@ -91,6 +95,9 @@ public override async Task DoTaskAsync(CancellationToken cancellationToken) await ReceiveVipPrivilege(userInfo); await ReceiveMangaVipReward(userInfo); + //TODO 大会员领经验 + + await Charge(userInfo); } @@ -124,7 +131,7 @@ protected async Task SetCookiesAsync(BiliCookie biliCookie, Cancella private async Task Login() { UserInfo userInfo = await _accountDomainService.LoginByCookie(); - if (userInfo == null) throw new Exception("登录失败,请检查Cookie");//终止流程 + if (userInfo == null) throw new Exception("登录失败,请检查Cookie"); //终止流程 _expDic.TryGetValue("每日登录", out int exp); _logger.LogInformation("登录成功,经验+{exp} √", exp); @@ -153,6 +160,7 @@ private async Task WatchAndShareVideo(DailyTaskInfo dailyTaskInfo) _logger.LogInformation("已配置为关闭,跳过任务"); return; } + await _videoDomainService.WatchAndShareVideo(dailyTaskInfo); } @@ -160,14 +168,28 @@ private async Task WatchAndShareVideo(DailyTaskInfo dailyTaskInfo) /// 投币任务 /// [TaskInterceptor("投币", rethrowWhenException: false)] - private async Task AddCoinsForVideo(UserInfo userInfo) + private async Task AddCoins(UserInfo userInfo) { if (_dailyTaskOptions.SaveCoinsWhenLv6 && userInfo.Level_info.Current_level >= 6) { _logger.LogInformation("已经为LV6大佬,开始白嫖"); return; } - await _donateCoinDomainService.AddCoinsForVideos(); + + if (_dailyTaskOptions.IsDonateCoinForArticle) + { + _logger.LogInformation("专栏投币已开启"); + + if (!await _articleDomainService.AddCoinForArticles()) + { + _logger.LogInformation("专栏投币结束,转入视频投币"); + await _donateCoinDomainService.AddCoinsForVideos(); + } + } + else + { + await _donateCoinDomainService.AddCoinsForVideos(); + } } /// diff --git a/src/Ray.BiliBiliTool.Application/VipBigPointAppService.cs b/src/Ray.BiliBiliTool.Application/VipBigPointAppService.cs index fd7a8c197..7d3d038ee 100644 --- a/src/Ray.BiliBiliTool.Application/VipBigPointAppService.cs +++ b/src/Ray.BiliBiliTool.Application/VipBigPointAppService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Ray.BiliBiliTool.Agent; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.VipTask; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; @@ -19,23 +20,81 @@ public class VipBigPointAppService : AppService, IVipBigPointAppService private readonly IConfiguration _configuration; private readonly IVipBigPointApi _vipApi; private readonly IAccountDomainService _loginDomainService; + private readonly IVideoDomainService _videoDomainService; + private readonly IAccountDomainService _accountDomainService; + private readonly BiliCookie _biliCookie; public VipBigPointAppService( IConfiguration configuration, ILogger logger, IVipBigPointApi vipApi, - IAccountDomainService loginDomainService - ) + IAccountDomainService loginDomainService, + IVideoDomainService videoDomainService, + BiliCookie biliCookie, IAccountDomainService accountDomainService) { _configuration = configuration; _logger = logger; _vipApi = vipApi; _loginDomainService = loginDomainService; + _videoDomainService = videoDomainService; + _biliCookie = biliCookie; + _accountDomainService = accountDomainService; } + public async Task VipExpress() + { + _logger.LogInformation("大会员经验领取任务开始"); + var re = await _vipApi.GetVouchersInfo(); + if (re.Code == 0) + { + var state = re.Data.List.Find(x => x.Type == 9).State; + + switch (state) + { + case 2: + _logger.LogInformation("大会员经验观看任务未完成"); + _logger.LogInformation("开始观看视频"); + // 观看视频,暂时没有好办法解决,先这样使着 + DailyTaskInfo dailyTaskInfo = await _accountDomainService.GetDailyTaskStatus(); + await _videoDomainService.WatchAndShareVideo(dailyTaskInfo); + // 跳转到未兑换,执行兑换任务 + goto case 0; + + case 1: + _logger.LogInformation("大会员经验已兑换"); + break; + + case 0: + _logger.LogInformation("大会员经验未兑换"); + //兑换api + var response = await _vipApi.GetVipExperience(new VipExperienceRequest() + { + csrf = _biliCookie.BiliJct + }); + if (response.Code != 0) + { + _logger.LogInformation("大会员经验领取失败,错误信息:{message}", response.Message); + break; + } + _logger.LogInformation("领取成功,经验+10 √"); + break; + + default: + _logger.LogDebug("大会员经验领取失败,未知错误"); + break; + } + + } + + } + + [TaskInterceptor("大会员大积分", TaskLevel.One)] public override async Task DoTaskAsync(CancellationToken cancellationToken) { + await VipExpress(); + + // TODO 解决taskInfo在一个任务出错后,后续的任务均会报空引用错误 var ui = await GetUserInfo(); if (ui.GetVipType() == VipType.None) @@ -66,7 +125,7 @@ public override async Task DoTaskAsync(CancellationToken cancellationToken) taskInfo = await ViewAnimate(taskInfo); //浏览影视频道页10秒 - taskInfo = await ViewFilmChannel(taskInfo); + // taskInfo = await ViewFilmChannel(taskInfo); //浏览会员购页面10秒 taskInfo = ViewVipMall(taskInfo); @@ -76,10 +135,12 @@ public override async Task DoTaskAsync(CancellationToken cancellationToken) //领取购买任务 taskInfo = await BuyVipVideo(taskInfo); - taskInfo = await BuyVipProduct(taskInfo); + // taskInfo = await BuyVipProduct(taskInfo); taskInfo = await BuyVipMall(taskInfo); - + taskInfo.LogInfo(_logger); + + } [TaskInterceptor("测试Cookie")] diff --git a/src/Ray.BiliBiliTool.Config/Options/DailyTaskOptions.cs b/src/Ray.BiliBiliTool.Config/Options/DailyTaskOptions.cs index 2258edf00..810562797 100644 --- a/src/Ray.BiliBiliTool.Config/Options/DailyTaskOptions.cs +++ b/src/Ray.BiliBiliTool.Config/Options/DailyTaskOptions.cs @@ -18,6 +18,11 @@ public class DailyTaskOptions /// public bool IsShareVideo { get; set; } + /// + /// 是否开启专栏投币模式 + /// + public bool IsDonateCoinForArticle { get; set; } + /// /// 每日设定的投币数 [0,5] /// diff --git a/src/Ray.BiliBiliTool.Console/appsettings.json b/src/Ray.BiliBiliTool.Console/appsettings.json index a3f85f0cf..d92fc1e05 100644 --- a/src/Ray.BiliBiliTool.Console/appsettings.json +++ b/src/Ray.BiliBiliTool.Console/appsettings.json @@ -6,6 +6,7 @@ "Cron": "0 15 * * *", "IsWatchVideo": true, //是否观看视频 "IsShareVideo": true, //是否分享视频 + "IsDonateCoinForArticle": false, "NumberOfCoins": 5, //每日设定的投币数 [0,5] "NumberOfProtectedCoins": 0, // 要保留的硬币数量 [0,int_max],0 为不保留,int_max 通常取 (2^31)-1 "SaveCoinsWhenLv6": false, //达到六级后是否开始白嫖[false,true] diff --git a/src/Ray.BiliBiliTool.DomainService/ArticleDomainService.cs b/src/Ray.BiliBiliTool.DomainService/ArticleDomainService.cs new file mode 100644 index 000000000..bda778dba --- /dev/null +++ b/src/Ray.BiliBiliTool.DomainService/ArticleDomainService.cs @@ -0,0 +1,395 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Polly; +using Ray.BiliBiliTool.Agent; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; +using Ray.BiliBiliTool.Config.Options; +using Ray.BiliBiliTool.DomainService.Interfaces; + +namespace Ray.BiliBiliTool.DomainService; + +public class ArticleDomainService : IArticleDomainService +{ + private readonly IArticleApi _articleApi; + private readonly BiliCookie _biliCookie; + private readonly ILogger _logger; + private readonly DailyTaskOptions _dailyTaskOptions; + private readonly ICoinDomainService _coinDomainService; + private readonly IAccountApi _accountApi; + private readonly IWbiDomainService _wbiDomainService; + + + /// + /// up的专栏总数缓存 + /// + private readonly Dictionary _upArticleCountDicCatch = new(); + + /// + /// 已对投币数量缓存 + /// + private readonly Dictionary _alreadyDonatedCoinCountCatch = new(); + + public ArticleDomainService( + IArticleApi articleApi, + BiliCookie biliCookie, + ILogger logger, + IOptionsMonitor dailyTaskOptions, + ICoinDomainService coinDomainService, + IAccountApi accountApi, IWbiDomainService wbiDomainService) + { + _articleApi = articleApi; + _biliCookie = biliCookie; + _logger = logger; + _coinDomainService = coinDomainService; + _accountApi = accountApi; + _wbiDomainService = wbiDomainService; + _dailyTaskOptions = dailyTaskOptions.CurrentValue; + } + + + + public async Task LikeArticle(long cvid) + { + await _articleApi.Like(cvid, _biliCookie.BiliJct); + } + + /// + /// 投币专栏任务 + /// + /// + public async Task AddCoinForArticles() + { + + var donateCoinsCounts = await CalculateDonateCoinsCounts(); + + if (donateCoinsCounts == 0) + { + // 没有可投的币相当于投币任务全部完成 + return true; + } + + + int success = 0; + int tryCount = 10; + + for (int i = 0; i <= tryCount && success < donateCoinsCounts; i++) + { + _logger.LogDebug("开始尝试第{num}次", i); + + var upId = GetUpFromConfigUps(); + var cvid = await GetRandomArticleFromUp(upId); + if (upId == 0 || cvid == 0) + { + _logger.LogInformation("未添加支持的Up主,任务跳过"); + return false; + } + + if (await AddCoinForArticle(cvid, upId)) + { + // 点赞 + if (_dailyTaskOptions.SelectLike) + { + await LikeArticle(cvid); + _logger.LogInformation("文章点赞成功"); + } + success++; + } + + + } + + if (success == donateCoinsCounts) + _logger.LogInformation("专栏投币任务完成"); + else + { + _logger.LogInformation("投币尝试超过10次,已终止"); + return false; + } + + + _logger.LogInformation("【硬币余额】{coin}", (await _accountApi.GetCoinBalance()).Data.Money ?? 0); + + return true; + } + + + /// + /// 给某一篇专栏投币 + /// + /// 文章cvid + /// 文章作者mid + /// 投币是否成功(false 投币失败,true 投币成功) + public async Task AddCoinForArticle(long cvid, long mid) + { + BiliApiResponse result; + try + { + var refer = $"https://www.bilibili.com/read/cv{cvid}/?from=search&spm_id_from=333.337.0.0"; + result = await _articleApi.AddCoinForArticle(new AddCoinForArticleRequest(cvid, mid, _biliCookie.BiliJct), + refer); + } + catch (Exception) + { + return false; + } + + if (result.Code == 0) + { + _logger.LogInformation("投币成功,经验+10 √"); + return true; + } + else + { + _logger.LogError("投币错误 {message}", result.Message); + return false; + } + } + + + #region private + + /// + /// 从某个up主中随机挑选一个专栏 + /// + /// + /// 专栏的cvid + private async Task GetRandomArticleFromUp(long mid) + { + if (!_upArticleCountDicCatch.TryGetValue(mid, out int articleCount)) + { + articleCount = await GetArticleCountOfUp(mid); + _upArticleCountDicCatch.Add(mid, articleCount); + } + + // 专栏数为0时 + if (articleCount == 0) + { + return 0; + } + + var req = new SearchArticlesByUpIdDto() + { + Mid = mid, + Ps = 1, + Pn = new Random().Next(1, articleCount + 1) + }; + var w_ridDto = await _wbiDomainService.GetWridAsync(req); + + var fullDto = new SearchArticlesByUpIdFullDto() + { + Mid = mid, + Ps = req.Ps, + Pn = req.Pn, + w_rid = w_ridDto.w_rid, + wts = w_ridDto.wts + }; + + BiliApiResponse re = await _articleApi.SearchUpArticlesByUpId(fullDto); + + if (re.Code != 0) + { + throw new Exception(re.Message); + } + + ArticleInfo articleInfo = re.Data.Articles.FirstOrDefault(); + + _logger.LogDebug("获取到的专栏{cvid}({title})", articleInfo.Id, articleInfo.Title); + + // 检查是否可投 + if (!await IsCanDonate(articleInfo.Id)) + { + return 0; + } + + return articleInfo.Id; + } + + + // TODO 转变为异步代码 + /// + /// 从支持UP主列表中随机挑选一位 + /// + /// 被挑选up主的mid + private long GetUpFromConfigUps() + { + if (_dailyTaskOptions.SupportUpIdList == null || _dailyTaskOptions.SupportUpIdList.Count == 0) + { + return 0; + } + + try + { + long randomUpId = + _dailyTaskOptions.SupportUpIdList[new Random().Next(0, _dailyTaskOptions.SupportUpIdList.Count)]; + + if (randomUpId is 0 or long.MinValue) return 0; + + if (randomUpId.ToString() == _biliCookie.UserId) + { + _logger.LogDebug("不能为自己投币"); + return 0; + } + _logger.LogDebug("挑选出的up主为{UpId}",randomUpId); + return randomUpId; + } + catch (Exception e) + { + _logger.LogWarning("异常:{msg}", e); + } + + return 0; + } + + /// + /// 获取Up主专栏总数 + /// + /// up主mid + /// 专栏总数 + /// + private async Task GetArticleCountOfUp(long mid) + { + var req = new SearchArticlesByUpIdDto() + { + Mid = mid + }; + + var w_ridDto = await _wbiDomainService.GetWridAsync(req); + + var fullDto = new SearchArticlesByUpIdFullDto() + { + Mid = mid, + w_rid = w_ridDto.w_rid, + wts = w_ridDto.wts + }; + + BiliApiResponse re = await _articleApi.SearchUpArticlesByUpId(fullDto); + + if (re.Code != 0) + { + throw new Exception(re.Message); + } + + return re.Data.Count; + } + + /// + /// 计算所需要投的硬币数量 + /// + /// 硬币数量 + private async Task CalculateDonateCoinsCounts() + { + int needCoins = await GetNeedDonateCoinCounts(); + + int protectedCoins = _dailyTaskOptions.NumberOfProtectedCoins; + if (needCoins <= 0) return 0; + + //投币前硬币余额 + decimal coinBalance = await _coinDomainService.GetCoinBalance(); + _logger.LogInformation("【投币前余额】 : {coinBalance}", coinBalance); + _ = int.TryParse(decimal.Truncate(coinBalance - protectedCoins).ToString(), out int unprotectedCoins); + + if (coinBalance <= 0) + { + _logger.LogInformation("因硬币余额不足,今日暂不执行投币任务"); + return 0; + } + + if (coinBalance <= protectedCoins) + { + _logger.LogInformation("因硬币余额达到或低于保留值,今日暂不执行投币任务"); + return 0; + } + + //余额小于目标投币数,按余额投 + if (coinBalance < needCoins) + { + _ = int.TryParse(decimal.Truncate(coinBalance).ToString(), out needCoins); + _logger.LogInformation("因硬币余额不足,目标投币数调整为: {needCoins}", needCoins); + return needCoins; + } + + //投币后余额小于等于保护值,按保护值允许投 + if (coinBalance - needCoins <= protectedCoins) + { + //排除需投等于保护后可投数量相等时的情况 + if (unprotectedCoins != needCoins) + { + needCoins = unprotectedCoins; + _logger.LogInformation("因硬币余额投币后将达到或低于保留值,目标投币数调整为: {needCoins}", needCoins); + return needCoins; + } + } + + return needCoins; + } + + private async Task GetNeedDonateCoinCounts() + { + int configCoins = _dailyTaskOptions.NumberOfCoins; + + if (configCoins <= 0) + { + _logger.LogInformation("已配置为跳过投币任务"); + return configCoins; + } + + //已投的硬币 + int alreadyCoins = await _coinDomainService.GetDonatedCoins(); + + int targetCoins = configCoins; + + _logger.LogInformation("【今日已投】{already}枚", alreadyCoins); + _logger.LogInformation("【目标欲投】{already}枚", targetCoins); + + if (targetCoins > alreadyCoins) + { + int needCoins = targetCoins - alreadyCoins; + _logger.LogInformation("【还需再投】{need}枚", needCoins); + return needCoins; + } + + _logger.LogInformation("已完成投币任务,不需要再投啦~"); + return 0; + } + + + private async Task IsCanDonate(long cvid) + { + try + { + if (_alreadyDonatedCoinCountCatch.Any(x => x.Key == cvid.ToString())) + { + _logger.LogDebug("重复专栏,丢弃处理"); + return false; + } + + if (!_alreadyDonatedCoinCountCatch.TryGetValue(cvid.ToString(), out int multiply)) + { + multiply = (await _articleApi.SearchArticleInfo(cvid)).Data.Coin; + _alreadyDonatedCoinCountCatch.TryAdd(cvid.ToString(), multiply); + } + + // 在网页端我测试时只能投一枚硬币,暂时设置最多投一枚 + if (multiply >= 1) + { + return false; + } + + return true; + } + catch (Exception e) + { + _logger.LogWarning("异常:{mag}", e); + return false; + } + } + + #endregion +} diff --git a/src/Ray.BiliBiliTool.DomainService/DonateCoinDomainService.cs b/src/Ray.BiliBiliTool.DomainService/DonateCoinDomainService.cs index 8e5114749..c989c595f 100644 --- a/src/Ray.BiliBiliTool.DomainService/DonateCoinDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/DonateCoinDomainService.cs @@ -124,7 +124,7 @@ public async Task AddCoinsForVideos() } if (success == needCoins) - _logger.LogInformation("投币任务完成"); + _logger.LogInformation("视频投币任务完成"); else _logger.LogInformation("投币尝试超过10次,已终止"); @@ -419,7 +419,7 @@ private async Task IsCanDonate(string aid) //已经投满2个币的,不能再投 if (!await IsDonatedLessThenLimitCoinsForVideo(aid)) { - _logger.LogDebug("超出单个视频投币数量限制,丢弃处理", aid); + _logger.LogDebug("超出单个视频投币数量限制,丢弃处理"); return false; } diff --git a/src/Ray.BiliBiliTool.DomainService/Interfaces/IArticleDomainService.cs b/src/Ray.BiliBiliTool.DomainService/Interfaces/IArticleDomainService.cs new file mode 100644 index 000000000..f5a2f6d77 --- /dev/null +++ b/src/Ray.BiliBiliTool.DomainService/Interfaces/IArticleDomainService.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.DomainService.Interfaces; + +public interface IArticleDomainService : IDomainService +{ + Task AddCoinForArticle(long cvid, long mid); + + Task AddCoinForArticles(); + + Task LikeArticle(long cvid); +} diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index 71e9fe23f..6cf387596 100644 --- a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs @@ -614,6 +614,14 @@ private async Task> GetFansMedalInfoList() continue; } + // 用以排除有牌子无直播间的up主 + if (spaceInfo.Data.Live_room is null) + { + _logger.LogInformation("【主播】{name} 直播间id获取失败,已跳过",medal.Target_name); + continue; + } + + var roomId = spaceInfo.Data.Live_room.Roomid; // 获取直播间详细信息 diff --git a/test/AppServiceTest/VipServiceTest.cs b/test/AppServiceTest/VipServiceTest.cs new file mode 100644 index 000000000..f50bafe0d --- /dev/null +++ b/test/AppServiceTest/VipServiceTest.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using Ray.BiliBiliTool.Application.Contracts; +using Ray.BiliBiliTool.Infrastructure; + +namespace AppServiceTest; + +public class VipServiceTest +{ + public VipServiceTest() + { + Program.CreateHost(new[] { "--ENVIRONMENT=Development" }); + } + + [Fact] + public async Task VipExpressTest() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + var appService = scope.ServiceProvider.GetRequiredService(); + await appService.VipExpress(); + } +} diff --git a/test/BiliAgentTest/VipApiTest.cs b/test/BiliAgentTest/VipApiTest.cs new file mode 100644 index 000000000..3e540a0a2 --- /dev/null +++ b/test/BiliAgentTest/VipApiTest.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using Xunit; +using Microsoft.Extensions.DependencyInjection; +using Ray.BiliBiliTool.Agent; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.VipTask; +using Ray.BiliBiliTool.Console; +using Ray.BiliBiliTool.Infrastructure; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; +using Xunit.Abstractions; +namespace BiliAgentTest; + +public class VipApiTest +{ + private readonly ITestOutputHelper _output; + public VipApiTest(ITestOutputHelper output) + { + _output = output; + Program.CreateHost(new[] { "--ENVIRONMENT=Development" }); + } + + [Fact] + public async Task VipInfoTest() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + + var re = await api.GetVouchersInfo(); + if (re.Code == 0) + { + var info = re.Data.List.Find(x => x.Type == 9); + if (info != null) + { + _output.WriteLine(info.State.ToString()); + } + else + { + _output.WriteLine("error"); + } + } + } + + + [Fact] + public async Task GetVipExperienceTest() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + var re = await api.GetVipExperience(new VipExperienceRequest() + { + csrf = ck.BiliJct + }); + + _output.WriteLine(re.Message); + } +} diff --git a/test/DomainServiceTest/ArticleDomainServiceTest.cs b/test/DomainServiceTest/ArticleDomainServiceTest.cs new file mode 100644 index 000000000..6f9d268e4 --- /dev/null +++ b/test/DomainServiceTest/ArticleDomainServiceTest.cs @@ -0,0 +1,47 @@ +using Xunit.Abstractions; + +namespace DomainServiceTest; + +public class ArticleDomainServiceTest +{ + private readonly ITestOutputHelper _output; + public ArticleDomainServiceTest(ITestOutputHelper output) + { + _output = output; + Program.CreateHost(new[] { "--ENVIRONMENT=Development" }); + } + + [Fact] + public async Task LikeArticleTest() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + var config = Global.ConfigurationRoot; + var domainService = scope.ServiceProvider.GetRequiredService(); + await domainService.LikeArticle(5806746); + } + + + [Fact] + public async Task AddCoinForArticleTest() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + var config = Global.ConfigurationRoot; + var domainService = scope.ServiceProvider.GetRequiredService(); + + // 测试用的专栏:https://www.bilibili.com/read/cv5806746/?from=search&spm_id_from=333.337.0.0 + + await domainService.AddCoinForArticle(5806746, 486980924); + } + + + [Fact] + public async Task AddCoinForArticlesTest() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + var config = Global.ConfigurationRoot; + var domainService = scope.ServiceProvider.GetRequiredService(); + await domainService.AddCoinForArticles(); + } + + +}