From 040030fd3a2b57f29b652dc4ea062afeb9cf3412 Mon Sep 17 00:00:00 2001 From: bakapiano Date: Wed, 4 Jan 2023 23:39:51 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20=E6=AF=8F=E6=97=A5?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E9=97=B4=E8=87=AA=E5=8A=A8=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=BC=B9=E5=B9=95=20&&=20=E6=8C=82=E6=9C=BA=E5=88=B7=E8=A7=82?= =?UTF-8?q?=E7=9C=8B=E6=97=B6=E9=95=BF=20=E6=9D=A5=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=9B=B4=E6=92=AD=E7=B2=89=E4=B8=9D=E5=8B=8B?= =?UTF-8?q?=E7=AB=A0=E7=BB=8F=E9=AA=8C=20(#381)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init * 加入配置选项 && 优化代码 * 添加 docker cron 配置 && 修改默认 option Co-authored-by: bakapiano <9417325+bakapiano@user.noreply.gitee.com> --- Ray.BiliBiliTool.sln | 4 +- docker/scripts/entry.sh | 5 +- .../Dtos/GetSpaceInfoResponse.cs | 18 ++ .../Dtos/Live/EnterRoomRequest.cs | 52 ++++ .../Dtos/Live/GetLiveRoomInfoResponse.cs | 19 ++ .../Dtos/Live/HeartBeatRequest.cs | 73 +++++ .../Dtos/Live/HeartBeatResponse.cs | 19 ++ .../BiliBiliAgent/Dtos/Live/MedalWallDto.cs | 35 +++ .../Dtos/Live/SendLiveDanmukuRequest.cs | 40 +++ .../Dtos/Live/WearMedalWallRequest.cs | 43 +++ .../Dtos/Live/WebHeartBeatRequest.cs | 27 ++ .../Dtos/Live/WebHeartBeatResponse.cs | 13 + .../BiliBiliAgent/Interfaces/ILiveApi.cs | 42 ++- .../BiliBiliAgent/Interfaces/ILiveTraceApi.cs | 30 +++ .../BiliBiliAgent/Interfaces/IUserInfoApi.cs | 8 + .../Utils/LiveHeartBeatCrypto.cs | 50 ++++ src/Ray.BiliBiliTool.Agent/BiliCookie.cs | 13 + .../Extensions/ServiceCollectionExtension.cs | 2 + .../IntervalDelegatingHandler.cs | 4 +- .../ILiveFansMedalAppService.cs | 12 + .../LiveFansMedalAppService.cs | 50 ++++ .../LoginTaskAppService.cs | 21 +- .../Extensions/ServiceCollectionExtension.cs | 1 + .../Options/LiveFansMedalTaskOptions.cs | 39 +++ .../Properties/launchSettings.json | 2 +- .../Ray.BiliBiliTool.Console.csproj | 2 +- src/Ray.BiliBiliTool.Console/appsettings.json | 10 +- .../Dtos/HeartBeatIterationInfoDto.cs | 40 +++ .../Interfaces/ILiveDomainService.cs | 10 + .../LiveDomainService.cs | 254 ++++++++++++++++++ .../Cookie/CookieInfo.cs | 2 +- test/BiliAgentTest/LiveApiTest.cs | 78 +++++- test/BiliAgentTest/LiveTraceApiTest.cs | 36 +++ 33 files changed, 1041 insertions(+), 13 deletions(-) create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/GetSpaceInfoResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/GetLiveRoomInfoResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/MedalWallDto.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/SendLiveDanmukuRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WearMedalWallRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatRequest.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatResponse.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Utils/LiveHeartBeatCrypto.cs create mode 100644 src/Ray.BiliBiliTool.Application.Contracts/ILiveFansMedalAppService.cs create mode 100644 src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs create mode 100644 src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs create mode 100644 src/Ray.BiliBiliTool.DomainService/Dtos/HeartBeatIterationInfoDto.cs create mode 100644 test/BiliAgentTest/LiveTraceApiTest.cs diff --git a/Ray.BiliBiliTool.sln b/Ray.BiliBiliTool.sln index 274a807e1..ec70453b6 100644 --- a/Ray.BiliBiliTool.sln +++ b/Ray.BiliBiliTool.sln @@ -151,7 +151,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ray.Serilog.Sinks.WorkWeiXi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ray.Serilog.Sinks.MicrosoftTeamsBatched", "src\Ray.Serilog.Sinks\Ray.Serilog.Sinks.MicrosoftTeamsBatched\Ray.Serilog.Sinks.MicrosoftTeamsBatched.csproj", "{FB9A43DE-00F0-42C4-BF92-AF61D752CCA2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ray.Serilog.Sinks.GotifyBatched", "src\Ray.Serilog.Sinks\Ray.Serilog.Sinks.GotifyBatched\Ray.Serilog.Sinks.GotifyBatched.csproj", "{B00FF75D-4C48-45ED-9A24-5C0D383317EE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ray.Serilog.Sinks.GotifyBatched", "src\Ray.Serilog.Sinks\Ray.Serilog.Sinks.GotifyBatched\Ray.Serilog.Sinks.GotifyBatched.csproj", "{B00FF75D-4C48-45ED-9A24-5C0D383317EE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -254,7 +254,7 @@ Global {DB227D60-0737-45C2-8CEA-F55FDA711301} = {38736647-2196-417E-8519-C48A012A63D9} {114D45C8-E4BB-47EE-89AC-BD1DC6FA3BAD} = {E9BDDCBE-A57D-4E3B-8252-708088386ADF} {2039BF6A-5EC4-439C-8D2E-77313075843A} = {E9BDDCBE-A57D-4E3B-8252-708088386ADF} - {110D3D21-8E9B-45AB-9667-6DA1DB3862AC} = {AF21E067-3307-4E7F-8CE8-C695E6B61876} + {110D3D21-8E9B-45AB-9667-6DA1DB3862AC} = {120917DC-474C-448B-9EBB-1B3BA3A20B9D} {7188698C-0A9A-43B2-B3E2-5136B14FDE13} = {110D3D21-8E9B-45AB-9667-6DA1DB3862AC} {3388A58D-91CC-4875-A29F-3E6FC3B44BF5} = {98051127-2868-4F5C-9B2C-2150975E05F3} {B6AEDD60-9C06-4391-9171-65EBD5E9D77A} = {98051127-2868-4F5C-9B2C-2150975E05F3} diff --git a/docker/scripts/entry.sh b/docker/scripts/entry.sh index 2a238d382..fdd0487b5 100644 --- a/docker/scripts/entry.sh +++ b/docker/scripts/entry.sh @@ -32,6 +32,9 @@ else if ! [ -z "$Ray_VipBigPointConfig__Cron" ]; then echo "$Ray_VipBigPointConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=VipBigPoint" >> $CRON_FILE fi + if ! [ -z "$Ray_LiveFansMedalConfig__Cron" ]; then + echo "$Ray_LiveFansMedalConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=LiveFansMedal" >> $CRON_FILE + fi fi if ! [ -z "$Ray_Crontab" ]; then @@ -58,4 +61,4 @@ echo -e "[step 全部已完成]\n" . /app/scripts/entry_after.sh touch /var/log/cron.log #todo:debian似乎并没有记录cron的日志。。。 -tail -f /var/log/cron.log # 追踪cron日志,避免当前脚本终止导致容器终止 \ No newline at end of file +tail -f /var/log/cron.log # 追踪cron日志,避免当前脚本终止导致容器终止 diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/GetSpaceInfoResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/GetSpaceInfoResponse.cs new file mode 100644 index 000000000..478a00482 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/GetSpaceInfoResponse.cs @@ -0,0 +1,18 @@ +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos +{ + public class GetSpaceInfoResponse + { + public int Mid { get; set; } + + public string Name { get; set; } + + public SpaceLiveRoomInfoDto Live_room { get; set; } + } + + public class SpaceLiveRoomInfoDto + { + public string Title { get; set; } + + public int Roomid { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs new file mode 100644 index 000000000..5c3fd3c2d --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class EnterRoomRequest + { + public EnterRoomRequest( + int roomId, + int parentId, + int areaID, + int seqNumber, // 心跳包编号 + long timestamp, + string userAgent, + string csrf, + int ruid) + { + this.Id = JsonConvert.SerializeObject(new[] { parentId, areaID, seqNumber, roomId }); + this.Ts = timestamp; + this.Ua = userAgent; + this.Csrf = csrf; + this.Ruid = ruid; + + this.Is_patch = 0; + this.Heart_beat = "[]"; + this.Visit_id = ""; + } + public string Id { get; set; } + + public int Ruid { get; set; } + + public long Ts { get; set; } + + public int Is_patch { get; set; } + + public string Heart_beat { get; set; } + + public string Ua { get; set; } + + public string Csrf_token => Csrf; + + public string Csrf { get; set; } + + public string Visit_id { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/GetLiveRoomInfoResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/GetLiveRoomInfoResponse.cs new file mode 100644 index 000000000..7883dbcc6 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/GetLiveRoomInfoResponse.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class GetLiveRoomInfoResponse + { + public int Room_id { get; set; } + + public int Area_id { get; set; } + + public int Parent_area_id { get; set; } + + public int Uid { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs new file mode 100644 index 000000000..1f158a754 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs @@ -0,0 +1,73 @@ +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Utils; +using System; +using System.Collections.Generic; +using System.Text.Json; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class HeartBeatRequest + { + public HeartBeatRequest( + int roomId, + int parentId, + int areaID, + int seqNumber, // 心跳包编号 + string buvid, // cookie['LIVE_BUVID'] + long timestamp, + long ets, // 由后端返回的 timestamp + string userAgent, + ICollection secretRule, + string secretKey, + string csrf, + string uuid) + { + this.Id = JsonSerializer.Serialize(new[] { parentId, areaID, seqNumber, roomId }); + this.Ets = ets; + this.Benchmark = secretKey; + this.Time = 60; + this.Ts = timestamp; + this.Ua = userAgent; + this.Csrf = csrf; + + // 构造哈希值 + var json = new + { + platform = "web", + parent_id = parentId, + area_id = areaID, + seq_id = seqNumber, + room_id = roomId, + buvid, + uuid, + ets, + time = 60, + ts = timestamp, + }; + string jsonString = JsonSerializer.Serialize(json); + this.S = LiveHeartBeatCrypto.Sypder(jsonString, secretRule, secretKey); + + this.Visit_id = ""; + } + + public string S { get; set; } + + public string Id { get; set; } + + public long Ets { get; set; } + + public string Benchmark { get; set; } + + public long Time { get; set; } + + public long Ts { get; set; } + + public string Ua { get; set; } + + public string Csrf_token => Csrf; + + public string Csrf { get; set; } + + public string Visit_id { get; set; } + + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatResponse.cs new file mode 100644 index 000000000..fc72a2da6 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatResponse.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class HeartBeatResponse + { + public int Heartbeat_interval { get; set; } + + public string Secret_key { get; set; } + + public List Secret_rule { get; set;} + + public long Timestamp { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/MedalWallDto.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/MedalWallDto.cs new file mode 100644 index 000000000..bfd5bb547 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/MedalWallDto.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class MedalWallResponse + { + public List List { get; set; } + } + + public class MedalWallDto + { + public int Live_status { get; set; } + + public string Target_name { get; set; } + + public string Link { get; set; } + + public MedalInfoDto Medal_info { get; set; } + } + + public class MedalInfoDto + { + public string Medal_name { get; set; } + + public int Medal_id { get; set; } + + public int Target_id { get; set; } + + public int Level { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/SendLiveDanmukuRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/SendLiveDanmukuRequest.cs new file mode 100644 index 000000000..b9c0094bb --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/SendLiveDanmukuRequest.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class SendLiveDanmukuRequest + { + public SendLiveDanmukuRequest(string csrf, int room_id, string message) + { + this.Csrf = csrf; + this.Msg= message; + this.Roomid= room_id; + this.Bubble = "0"; + this.Mode = "1"; + this.Fontsize = "25"; + this.Rnd = "1672305761"; + this.Color = "16777215"; + } + public string Bubble { get; set; } + + public string Msg { get; set; } + + public string Color { get; set; } + + public string Mode { get; set; } + + public string Fontsize { get; set; } + + public string Rnd { get; set; } + + public int Roomid { get; set; } + + public string Csrf { get; set; } + + public string Csrf_token => Csrf; + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WearMedalWallRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WearMedalWallRequest.cs new file mode 100644 index 000000000..97e76a62f --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WearMedalWallRequest.cs @@ -0,0 +1,43 @@ +using Ray.BiliBiliTool.Infrastructure.Helpers; +using System; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class WearMedalWallRequest + { + public WearMedalWallRequest(string csrf, int medal_id) + { + Csrf = csrf; + Medal_id = medal_id; + } + + public int Medal_id { get; set; } + + public string Csrf { get; set; } + + public string Csrf_token => Csrf; + + /// + /// + /// + /// 8u0w3cesz1o0 + /// 33moy4vugle0 + /// 9zys612vo0c0 + /// 3uu2mkxt21c0 + /// 8orqn5vf4i00 + public string Visit_id { get; set; } = _visitId;//todo + + public static string GetRandomVisitId() + { + var ran = new Random(); + int first = ran.Next(1, 10); + int last = 0; + + var s = new RandomHelper().GenerateCode(10).ToLower(); + + return $"{first}{s}{last}"; + } + + private static string _visitId = GetRandomVisitId(); + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatRequest.cs new file mode 100644 index 000000000..56caa1faa --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatRequest.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class WebHeartBeatRequest + { + public WebHeartBeatRequest(int room_id, int next_interval) + { + this.RoomId = room_id; + this.NextInterval = next_interval; + } + + public int RoomId { set; get; } + + public int NextInterval { set; get; } + + public override string ToString() + { + string arg = $"{this.NextInterval}|{this.RoomId}|1|0"; + return Convert.ToBase64String(Encoding.UTF8.GetBytes(arg)); + } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatResponse.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatResponse.cs new file mode 100644 index 000000000..14e706488 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/WebHeartBeatResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class WebHeartBeatResponse + { + public int Next_interval { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs index f7036ca1e..0e4ac59fb 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using System.Threading.Tasks; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; @@ -52,7 +53,7 @@ public interface ILiveApi : IBiliBiliApi [Header("Content-Type", "application/x-www-form-urlencoded")] [Header("Origin", "https://link.bilibili.com")] [HttpPost("/xlive/revenue/v1/wallet/silver2coin")] - Task> Silver2Coin([FormContent]Silver2CoinRequest request); + Task> Silver2Coin([FormContent] Silver2CoinRequest request); /// /// 获取直播中心钱包状态 @@ -97,5 +98,44 @@ public interface ILiveApi : IBiliBiliApi /// [HttpPost("/xlive/lottery-interface/v1/Anchor/Join")] Task> Join([FormContent] JoinTianXuanRequest request); + + /// + /// 获取用户的粉丝勋章 + /// + /// uid + /// + [Header("Referer", "https://live.bilibili.com/")] + [Header("Origin", "https://live.bilibili.com")] + [HttpGet("/xlive/web-ucenter/user/MedalWall?target_id={userId}")] + Task> GetMedalWall(int userId); + + /// + /// 佩戴粉丝勋章 + /// + /// uid + /// + [Header("Referer", "https://live.bilibili.com/")] + [Header("Origin", "https://live.bilibili.com")] + [HttpPost("/xlive/app-ucenter/v1/fansMedal/wear")] + Task WearMedalWall([FormContent] WearMedalWallRequest request); + + /// + /// 发送弹幕 + /// + /// request + /// + [HttpPost("/msg/send")] + Task SendLiveDanmuku([FormContent] SendLiveDanmukuRequest request); + + /// + /// 获取直播间信息 + /// + /// roomId + /// + [HttpGet("/room/v1/Room/get_info?room_id={roomId}&from=room")] + Task> GetLiveRoomInfo(int roomId); + + [HttpGet("/news/v1/notice/recom?product=live")] + Task GetLiveHome(); } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs new file mode 100644 index 000000000..4bfab5391 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs @@ -0,0 +1,30 @@ +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; +using Ray.BiliBiliTool.Config.Options; +using System.Threading.Tasks; +using WebApiClientCore.Attributes; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +{ + [Header("Host", "live-trace.bilibili.com")] + public interface ILiveTraceApi : IBiliBiliApi + { + + [HttpGet("/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb={request}&pf=web")] + Task> WebHeartBeat(WebHeartBeatRequest request); + + /* + 单独引入 device 参数的原因: + WebApiClientCore 库的 FormContent 有个已知 issue https://github.com/dotnetcore/WebApiClient/issues/211 + 会将表单中的双引号自动加入反斜杠转义... + 如 ["key":"value"] => [\"key\":\"value\"] + */ + [Header("User-Agent", LiveFansMedalTaskOptions.UserAgent)] + [HttpPost("/xlive/data-interface/v1/x25Kn/E")] + Task> EnterRoom([FormContent] EnterRoomRequest request, [FormField] string device); + + [Header("User-Agent", LiveFansMedalTaskOptions.UserAgent)] + [HttpPost("/xlive/data-interface/v1/x25Kn/X")] + Task> HeartBeat([FormContent] HeartBeatRequest request, [FormField] string device); + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs index 2687a4d57..45f425118 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs @@ -19,5 +19,13 @@ public interface IUserInfoApi : IBiliBiliApi /// [HttpGet("/x/web-interface/nav")] Task> LoginByCookie(); + + /// + /// 获取用户空间信息 + /// + /// uid + /// + [HttpGet("/x/space/wbi/acc/info?mid={userId}")] + Task> GetSpaceInfo(int userId); } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Utils/LiveHeartBeatCrypto.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Utils/LiveHeartBeatCrypto.cs new file mode 100644 index 000000000..08ea68316 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Utils/LiveHeartBeatCrypto.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Utils +{ + public class LiveHeartBeatCrypto + { + public static string Sypder(string text, ICollection rules, string key) + { + string result = text; + foreach(var rule in rules) + { + switch (rule) + { + case 0: + result = Hash(result, key, "HMACMD5"); + break; + case 1: + result = Hash(result, key, "HMACSHA1"); + break; + case 2: + result = Hash(result, key, "HMACSHA256"); + break; + case 3: + result = Hash(result, key, "HMACSHA224"); + break; + case 4: + result = Hash(result, key, "HMACSHA512"); + break; + case 5: + result = Hash(result, key, "HMACSHA384"); + break; + default: + break; + } + } + return result; + } + + private static string Hash(string text, string key, string algorithmName) + { + var hamc = HMAC.Create(algorithmName); + hamc.Key = Encoding.UTF8.GetBytes(key); + byte[] inArray = hamc.ComputeHash(Encoding.UTF8.GetBytes(text)); + return BitConverter.ToString(inArray).Replace("-", "").ToLower(); + } + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliCookie.cs b/src/Ray.BiliBiliTool.Agent/BiliCookie.cs index 46d324c81..37f406464 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliCookie.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliCookie.cs @@ -40,6 +40,10 @@ private BiliCookie(ILogger logger, string ckStr) { SessData = sess; } + if (CookieItemDictionary.TryGetValue(GetPropertyDescription(nameof(LiveBuvid)), out string liveBuvid)) + { + LiveBuvid = liveBuvid; + } } [Description("DedeUserID")] @@ -54,6 +58,9 @@ private BiliCookie(ILogger logger, string ckStr) [Description("bili_jct")] public string BiliJct { get; set; } + [Description("LIVE_BUVID")] + public string LiveBuvid { get; set; } + /// /// 检查是否已配置 /// @@ -93,6 +100,12 @@ public override void Check() result = false; } + // LiveBuvid 为空时发出警告 + if (string.IsNullOrWhiteSpace(LiveBuvid)) + { + _logger.LogWarning("直播Cookie {cookie}未正确配置,将在执行相关任务时尝试自动获取", GetPropertyDescription(nameof(LiveBuvid))); + } + if (!result) throw new Exception($"请正确配置Cookie后再运行,配置方式见 {Constants.SourceCodeUrl}"); } diff --git a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs index 36d17512e..ec9b0d0e5 100644 --- a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs +++ b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs @@ -72,6 +72,8 @@ public static IServiceCollection AddBiliBiliClientApi(this IServiceCollection se services.AddBiliBiliClientApi("http://passport.bilibili.com", false); + services.AddBiliBiliClientApi("https://live-trace.bilibili.com"); + //qinglong var qinglongHost = configuration["QL_URL"]?? "http://localhost:5600"; services diff --git a/src/Ray.BiliBiliTool.Agent/HttpClientDelegatingHandlers/IntervalDelegatingHandler.cs b/src/Ray.BiliBiliTool.Agent/HttpClientDelegatingHandlers/IntervalDelegatingHandler.cs index 6089d6e7c..952ebac34 100644 --- a/src/Ray.BiliBiliTool.Agent/HttpClientDelegatingHandlers/IntervalDelegatingHandler.cs +++ b/src/Ray.BiliBiliTool.Agent/HttpClientDelegatingHandlers/IntervalDelegatingHandler.cs @@ -16,7 +16,9 @@ public class IntervalDelegatingHandler : DelegatingHandler private readonly SecurityOptions _securityOptions; private readonly Dictionary _special = new Dictionary() { - {"/xlive/lottery-interface/v1/Anchor/Join",3 }//天选抽奖,有时效,不能间隔过久,使用默认3秒 + {"/xlive/lottery-interface/v1/Anchor/Join",3 },//天选抽奖,有时效,不能间隔过久,使用默认3秒 + {"/xlive/data-interface/v1/x25Kn/E", 1}, + {"/xlive/data-interface/v1/x25Kn/X", 1}, }; public IntervalDelegatingHandler(IOptionsMonitor securityOptions) diff --git a/src/Ray.BiliBiliTool.Application.Contracts/ILiveFansMedalAppService.cs b/src/Ray.BiliBiliTool.Application.Contracts/ILiveFansMedalAppService.cs new file mode 100644 index 000000000..acf205004 --- /dev/null +++ b/src/Ray.BiliBiliTool.Application.Contracts/ILiveFansMedalAppService.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace Ray.BiliBiliTool.Application.Contracts +{ + /// + /// 直播粉丝牌任务 + /// + [Description("LiveFansMedal")] + public interface ILiveFansMedalAppService : IAppService + { + } +} diff --git a/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs new file mode 100644 index 000000000..902981a21 --- /dev/null +++ b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Ray.BiliBiliTool.Application.Attributes; +using Ray.BiliBiliTool.Application.Contracts; +using Ray.BiliBiliTool.Config.Options; +using Ray.BiliBiliTool.DomainService.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Application +{ + public class LiveFansMedalAppService : AppService, ILiveFansMedalAppService + { + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly ILiveDomainService _liveDomainService; + private readonly LiveLotteryTaskOptions _liveLotteryTaskOptions; + private readonly SecurityOptions _securityOptions; + private readonly IAccountDomainService _accountDomainService; + + public LiveFansMedalAppService( + IConfiguration configuration, + ILiveDomainService liveDomainService, + IOptionsMonitor securityOptions, + IOptionsMonitor liveLotteryTaskOptions, + ILogger logger, + IAccountDomainService accountDomainService + ) + { + _configuration = configuration; + _liveDomainService = liveDomainService; + _liveLotteryTaskOptions = liveLotteryTaskOptions.CurrentValue; + _securityOptions = securityOptions.CurrentValue; + _logger = logger; + _accountDomainService = accountDomainService; + } + + [TaskInterceptor("直播间互动", TaskLevel.One)] + public override void DoTask() + { + _liveDomainService.SendDanmakuToFansMedalLive(); + _liveDomainService.SendHeartBeatToFansMdealLive(); + } + + } +} diff --git a/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs b/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs index 81a09764a..066093dec 100644 --- a/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs +++ b/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs @@ -27,6 +27,7 @@ public class LoginTaskAppService : AppService, ILoginTaskAppService private readonly IPassportApi _passportApi; private readonly IHostEnvironment _hostingEnvironment; private readonly IQingLongApi _qingLongApi; + private readonly ILiveApi _liveApi; private readonly IConfiguration _configuration; public LoginTaskAppService( @@ -34,14 +35,15 @@ public LoginTaskAppService( ILogger logger, IPassportApi passportApi, IHostEnvironment hostingEnvironment, - IQingLongApi qingLongApi - ) + IQingLongApi qingLongApi, + ILiveApi liveApi) { _configuration = configuration; _logger = logger; _passportApi = passportApi; _hostingEnvironment = hostingEnvironment; _qingLongApi = qingLongApi; + _liveApi = liveApi; } [TaskInterceptor("扫码登录", TaskLevel.One)] @@ -118,6 +120,21 @@ protected bool QrCodeLogin(out BiliCookie cookieInfo) _logger.LogInformation("扫描成功!"); IEnumerable cookies = check.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value; + // 请求主播主页来正确配置 cookie + var liveHome = _liveApi.GetLiveHome().Result; + var liveHomeContent = JsonConvert.DeserializeObject(liveHome.Content.ReadAsStringAsync().Result); + if (liveHomeContent.Code == 0) + { + // 合并 cookie + IEnumerable liveCookies = liveHome.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value; + cookies = cookies.Union(liveCookies); + } + else + { + _logger.LogWarning("获取直播 cookie 时出现错误"); + _logger.LogWarning("{msg}", liveHomeContent.Message); + } + cookieInfo = GetCookie(cookies); result = true; diff --git a/src/Ray.BiliBiliTool.Config/Extensions/ServiceCollectionExtension.cs b/src/Ray.BiliBiliTool.Config/Extensions/ServiceCollectionExtension.cs index f62ac0c30..9169b6d06 100644 --- a/src/Ray.BiliBiliTool.Config/Extensions/ServiceCollectionExtension.cs +++ b/src/Ray.BiliBiliTool.Config/Extensions/ServiceCollectionExtension.cs @@ -25,6 +25,7 @@ public static IServiceCollection AddBiliBiliConfigs(this IServiceCollection serv .Configure(configuration.GetSection("UnfollowBatchedTaskConfig")) .Configure(configuration.GetSection("Security")) .Configure(configuration.GetSection("ReceiveVipPrivilegeConfig")) + .Configure(configuration.GetSection("LiveFansMedalTaskOptions")) .Configure>(Constants.OptionsNames.ExpDictionaryName, configuration.GetSection("Exp")) .Configure>(Constants.OptionsNames.DonateCoinCanContinueStatusDictionaryName, configuration.GetSection("DonateCoinCanContinueStatus")); diff --git a/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs b/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs new file mode 100644 index 000000000..c48eb0898 --- /dev/null +++ b/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Config.Options +{ + /// + /// 粉丝牌等级任务相关配置 + /// + public class LiveFansMedalTaskOptions + { + /// + /// 自定义发送弹幕内容,如 “打卡” 等来触发直播间内机器人关键词 + /// + public string DanmakuContent { get; set; } = "OvO"; + + /// + /// 心跳包发送的个数 / 挂机的时间,单位为分钟 + /// + public int HeartBeatNumber { get; set; } = 70; + + /// + /// 当心跳包发送连续失败多少次时放弃 + /// + public int HeartBeatSendGiveUpThreshold { get; set; } = 5; + + /// + /// 对于直播时长任务是否跳过粉丝牌等级大于等于 20 的 + /// + public bool IsSkipLevel20Medal { get; set; } = true; + + public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; + + public const int HeartBeatInterval = 60; + } +} diff --git a/src/Ray.BiliBiliTool.Console/Properties/launchSettings.json b/src/Ray.BiliBiliTool.Console/Properties/launchSettings.json index 7d719996c..bd1534eba 100644 --- a/src/Ray.BiliBiliTool.Console/Properties/launchSettings.json +++ b/src/Ray.BiliBiliTool.Console/Properties/launchSettings.json @@ -10,4 +10,4 @@ "commandName": "Docker" } } -} \ No newline at end of file +} diff --git a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj index cc6d544eb..8ccad8c92 100644 --- a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj +++ b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj @@ -95,5 +95,5 @@ - + \ No newline at end of file diff --git a/src/Ray.BiliBiliTool.Console/appsettings.json b/src/Ray.BiliBiliTool.Console/appsettings.json index a3f9a2999..9b1e4c3d6 100644 --- a/src/Ray.BiliBiliTool.Console/appsettings.json +++ b/src/Ray.BiliBiliTool.Console/appsettings.json @@ -1,7 +1,7 @@ { //Cookie集合,取自浏览器,必填 "BiliBiliCookies": [ //Cookie字符串集合,登录bilibili后F12获取,形如"_uuid=abcd; buvid3=1234; sid=abc123" - "", + "" ], "RunTasks": "Daily", //要运行的任务名称[Daily,LiveLottery,UnfollowBatched,VipBigPoint,Test],多个使用&分隔,如“Daily&LiveLottery”,建议使用命令行参数指定 @@ -42,6 +42,14 @@ "Cron": "7 1 * * *" }, + "LiveFansMedalTaskConfig": { + "Cron": "5 0 * * *", + "DanmakuContent": "OvO", + "HeartBeatNumber": 70, //直播间观看的时长,单位为分钟", + "HeartBeatSendGiveUpThreshold": 5, //当心跳包发送连续失败多少次时放弃 + "IsSkipLevel20Medal": true // 是否跳过粉丝牌等级 >=0 的 + }, + //安全相关配置 "Security": { "IsSkipDailyTask": false, //是否跳过执行任务,用于特殊情况下,通过配置灵活的开启和关闭任务 diff --git a/src/Ray.BiliBiliTool.DomainService/Dtos/HeartBeatIterationInfoDto.cs b/src/Ray.BiliBiliTool.DomainService/Dtos/HeartBeatIterationInfoDto.cs new file mode 100644 index 000000000..e7202eff5 --- /dev/null +++ b/src/Ray.BiliBiliTool.DomainService/Dtos/HeartBeatIterationInfoDto.cs @@ -0,0 +1,40 @@ +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.DomainService.Dtos +{ + public class HeartBeatIterationInfoDto + { + public HeartBeatIterationInfoDto( + int roomId, + GetLiveRoomInfoResponse roomInfo, + HeartBeatResponse heartBeatInfo, + int heartBeatCount, + long lastBeatTime) + { + RoomId = roomId; + RoomInfo = roomInfo; + HeartBeatInfo = heartBeatInfo; + HeartBeatCount = heartBeatCount; + LastBeatTime = lastBeatTime; + } + + public int RoomId { get; set; } = 0; + + public GetLiveRoomInfoResponse RoomInfo { get; set; } = new(); + + public HeartBeatResponse HeartBeatInfo { get; set; } = new(); + + // 成功发送的心跳包个数 + public int HeartBeatCount { get; set; } = 0; + + public long LastBeatTime { get; set; } = 0; + + // 连续失败的次数 + public int FailedTimes { get; set; } = 0; + } +} diff --git a/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs index 1355e4eb2..e3c6d6b64 100644 --- a/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs @@ -29,5 +29,15 @@ public interface ILiveDomainService : IDomainService void TryJoinTianXuan(ListItemDto target); void GroupFollowing(); + + /// + /// 发送弹幕 + /// + void SendDanmakuToFansMedalLive(); + + /// + /// 直播时长挂机 + /// + void SendHeartBeatToFansMdealLive(); } } diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index cd790d7e5..5fc522938 100644 --- a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs @@ -1,16 +1,22 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Text; +using System.Threading; +using System.Timers; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using Ray.BiliBiliTool.Agent; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Relation; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; using Ray.BiliBiliTool.Config.Options; +using Ray.BiliBiliTool.DomainService.Dtos; using Ray.BiliBiliTool.DomainService.Interfaces; +using Ray.BiliBiliTool.Infrastructure; namespace Ray.BiliBiliTool.DomainService { @@ -22,23 +28,32 @@ public class LiveDomainService : ILiveDomainService private readonly ILogger _logger; private readonly ILiveApi _liveApi; private readonly IRelationApi _relationApi; + private readonly ILiveTraceApi _liveTraceApi; + private readonly IUserInfoApi _userInfoApi; private readonly LiveLotteryTaskOptions _liveLotteryTaskOptions; + private readonly LiveFansMedalTaskOptions _liveFansMedalTaskOptions; private readonly BiliCookie _biliCookie; private readonly DailyTaskOptions _dailyTaskOptions; public LiveDomainService(ILogger logger, ILiveApi liveApi, IRelationApi relationApi, + ILiveTraceApi liveTraceApi, + IUserInfoApi userInfoApi, IOptionsMonitor dailyTaskOptions, IOptionsMonitor liveLotteryTaskOptions, + IOptionsMonitor liveFansMedalTaskOptions, BiliCookie biliCookie) { _logger = logger; _liveApi = liveApi; _relationApi = relationApi; + _liveTraceApi = liveTraceApi; + _userInfoApi = userInfoApi; _liveLotteryTaskOptions = liveLotteryTaskOptions.CurrentValue; _biliCookie = biliCookie; _dailyTaskOptions = dailyTaskOptions.CurrentValue; + _liveFansMedalTaskOptions = liveFansMedalTaskOptions.CurrentValue; } /// @@ -385,5 +400,244 @@ private long GetOrCreateTianXuanGroupId() return groupId; } #endregion + + public void SendDanmakuToFansMedalLive() + { + if (!CheckLiveCookie()) return; + + _logger.LogInformation("【获取直播列表】获取拥有粉丝牌的直播列表"); + var result = this._liveApi.GetMedalWall(int.Parse(this._biliCookie.UserId)).Result; + + if (result.Code != 0) + { + _logger.LogError("【获取直播列表】失败"); + _logger.LogError("【原因】{message}", result.Message); + return; + } + + // 遍历粉丝牌 + foreach (var medal in result.Data.List) + { + _logger.LogInformation("【直播间】{liveRoomName}", medal.Target_name); + _logger.LogInformation("【粉丝牌】{medalName}", medal.Medal_info.Medal_name); + + _logger.LogInformation("正在发送弹幕..."); + + // 通过空间主页信息获取直播间 id + int liveHostUserId = medal.Medal_info.Target_id; + var spaceInfo = _userInfoApi.GetSpaceInfo(liveHostUserId).Result; + if (spaceInfo.Code != 0) + { + _logger.LogError("【获取直播间信息】失败"); + _logger.LogError("【原因】{message}", spaceInfo.Message); + continue; + } + + // 发送弹幕 + var sendResult = _liveApi.SendLiveDanmuku(new SendLiveDanmukuRequest( + _biliCookie.BiliJct, + spaceInfo.Data.Live_room.Roomid, + _liveFansMedalTaskOptions.DanmakuContent)).Result; + + if (sendResult.Code != 0) + { + _logger.LogError("【弹幕发送】失败"); + _logger.LogError("【原因】{message}", sendResult.Message); + continue; + } + + _logger.LogInformation("【弹幕发送】成功~,你和主播 {name} 的亲密值增加了100!", spaceInfo.Data.Name); + } + } + + public void SendHeartBeatToFansMdealLive() + { + if (!CheckLiveCookie()) return; + + _logger.LogInformation("【获取直播列表】获取拥有粉丝牌的直播列表"); + var medalWallInfo = this._liveApi.GetMedalWall(int.Parse(this._biliCookie.UserId)).Result; + + if (medalWallInfo.Code != 0) + { + _logger.LogError("【获取直播列表】失败"); + _logger.LogError("【原因】{message}", medalWallInfo.Message); + return; + } + + var infoList = new List(); + foreach (var medal in medalWallInfo.Data.List) + { + _logger.LogInformation("【主播】{name} ", medal.Target_name); + if (_liveFansMedalTaskOptions.IsSkipLevel20Medal && medal.Medal_info.Level >= 20) + { + _logger.LogInformation("粉丝牌等级为 {level},观看将不再增长亲密度,跳过", medal.Medal_info.Level); + continue; + } + + // 通过空间主页信息获取直播间 id + int liveHostUserId = medal.Medal_info.Target_id; + var spaceInfo = _userInfoApi.GetSpaceInfo(liveHostUserId).Result; + if (spaceInfo.Code != 0) + { + _logger.LogError("【获取空间信息】失败"); + _logger.LogError("【原因】{message}", spaceInfo.Message); + continue; + } + + var roomId = spaceInfo.Data.Live_room.Roomid; + + // 获取直播间详细信息 + var liveRoomInfo = _liveApi.GetLiveRoomInfo(roomId).Result; + if (liveRoomInfo.Code != 0) + { + _logger.LogError("【获取直播间信息】失败"); + _logger.LogError("【原因】{message}", liveRoomInfo.Message); + continue; + } + + infoList.Add(new(roomId, liveRoomInfo.Data, new(), 0, 0)); + } + + if (infoList.Count == 0) + { + _logger.LogInformation("【直播观看时长】跳过,未检测到符合条件的主播"); + return; + } + + var Now = () => new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds(); + + while (infoList.Min( + info => + info.FailedTimes >= _liveFansMedalTaskOptions.HeartBeatSendGiveUpThreshold ? int.MaxValue : + info.HeartBeatCount) + < _liveFansMedalTaskOptions.HeartBeatNumber) + { + foreach (var info in infoList) + { + // 忽略连续失败超过上限的直播间 + if (info.FailedTimes >= _liveFansMedalTaskOptions.HeartBeatSendGiveUpThreshold) continue; + + string uuid = Guid.NewGuid().ToString(); + var current = Now(); + if (current - info.LastBeatTime <= LiveFansMedalTaskOptions.HeartBeatInterval * 1000) + { + int sleepTime = (int)(LiveFansMedalTaskOptions.HeartBeatInterval * 1000 - (current - info.LastBeatTime)); + _logger.LogDebug("【休眠】{time} 毫秒", sleepTime); + Thread.Sleep(sleepTime); + } + + // Heart Beat 接口 + var timestamp = Now(); + BiliApiResponse heartBeatResult = null; + if (info.HeartBeatCount == 0) + { + heartBeatResult = _liveTraceApi.EnterRoom( + new EnterRoomRequest( + info.RoomId, + info.RoomInfo.Parent_area_id, + info.RoomInfo.Area_id, + info.HeartBeatCount, + timestamp, + LiveFansMedalTaskOptions.UserAgent, + _biliCookie.BiliJct, + info.RoomInfo.Uid), + $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") + .Result; + } + else + { + heartBeatResult = _liveTraceApi.HeartBeat( + new HeartBeatRequest( + info.RoomId, + info.RoomInfo.Parent_area_id, + info.RoomInfo.Area_id, + info.HeartBeatCount, + _biliCookie.LiveBuvid, + timestamp, + info.HeartBeatInfo.Timestamp, + LiveFansMedalTaskOptions.UserAgent, + info.HeartBeatInfo.Secret_rule, + info.HeartBeatInfo.Secret_key, + _biliCookie.BiliJct, + uuid), + $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") + .Result; + } + + info.LastBeatTime = Now(); + + if (heartBeatResult != null && heartBeatResult.Data != null) + { + info.HeartBeatInfo.Secret_key = heartBeatResult.Data.Secret_key; + info.HeartBeatInfo.Secret_rule = heartBeatResult.Data.Secret_rule; + info.HeartBeatInfo.Timestamp = heartBeatResult.Data.Timestamp; + } + + if (heartBeatResult == null || heartBeatResult.Code != 0) + { + _logger.LogError("【心跳包】直播间 {room} 发送失败", info.RoomId); + _logger.LogError("【原因】{message}", heartBeatResult != null ? heartBeatResult.Message : ""); + info.FailedTimes += 1; + continue; + } + + info.HeartBeatCount += 1; + info.FailedTimes = 0; + + _logger.LogInformation("【直播间】{roomId} 的第 {index} 个心跳包发送成功", info.RoomId, info.HeartBeatCount); + } + } + + var successCount = infoList.Count(info => info.HeartBeatCount >= _liveFansMedalTaskOptions.HeartBeatNumber); + _logger.LogInformation("【直播观看时长】完成情况:{success}/{total} ", successCount, infoList.Count); + } + + /// + /// 自动配置直播相关 Cookie,来兼容较低版本中保存的 Cookie 配置 + /// + /// + /// bool 成功配置 or not + /// + private bool CheckLiveCookie() + { + // 检测 _biliCookie 是否正确配置 + if (string.IsNullOrWhiteSpace(_biliCookie.LiveBuvid)) + { + try + { + _logger.LogInformation("检测到直播 Cookie 未正确配置,尝试自动配置中..."); + + // 请求主播主页来正确配置 cookie + var liveHome = _liveApi.GetLiveHome().Result; + var liveHomeContent = JsonConvert.DeserializeObject(liveHome.Content.ReadAsStringAsync().Result); + if (liveHomeContent.Code != 0) + { + throw new Exception(liveHomeContent.Message); + } + + IEnumerable liveCookies = liveHome.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value; + var ckItemList = new List(); + foreach (var item in liveCookies) + { + ckItemList.Add(item.Split(';').FirstOrDefault()); + } + _biliCookie.LiveBuvid = CookieInfo.BuildCookieItemDictionaryByCookieItemList( + ckItemList, + null, + v => v.Contains(',') ? Uri.EscapeDataString(v) : v) + [_biliCookie.GetType().GetPropertyDescription(nameof(BiliCookie.LiveBuvid))]; + + _logger.LogDebug("LiveBuvid {value}", _biliCookie.LiveBuvid); + _logger.LogInformation("直播 Cookie 配置成功!"); + } + catch (Exception exception) + { + _logger.LogError("【配置直播Cookie】失败,放弃执行后续任务..."); + _logger.LogError("【原因】{message}", exception.Message); + return false; + } + } + return true; + } } } diff --git a/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieInfo.cs b/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieInfo.cs index f26bbb625..2abc29a57 100644 --- a/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieInfo.cs +++ b/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieInfo.cs @@ -29,7 +29,7 @@ public virtual void Check() if (string.IsNullOrWhiteSpace(CookieStr)) throw new Exception("Cookie字符串为空"); } - private static Dictionary BuildCookieItemDictionaryByCookieItemList(IEnumerable cookieItemList, Func nameBuilder = null, Func valueBuilder = null) + public static Dictionary BuildCookieItemDictionaryByCookieItemList(IEnumerable cookieItemList, Func nameBuilder = null, Func valueBuilder = null) { var re = new Dictionary(); foreach (var item in cookieItemList ?? new List()) diff --git a/test/BiliAgentTest/LiveApiTest.cs b/test/BiliAgentTest/LiveApiTest.cs index f600c2643..48acde41b 100644 --- a/test/BiliAgentTest/LiveApiTest.cs +++ b/test/BiliAgentTest/LiveApiTest.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Ray.BiliBiliTool.Agent; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; @@ -16,7 +16,7 @@ public class LiveApiTest { public LiveApiTest() { - Program.CreateHost(new[] { "--ENVIRONMENT=Development" });//ĬPrdָΪDev󣬿Զȡû + Program.CreateHost(new[] { "--ENVIRONMENT=Development" });//ĬÈÏPrd»·¾³£¬ÕâÀïÖ¸¶¨ÎªDevºó£¬¿ÉÒÔ¶ÁÈ¡µ½Óû§»úÃÜÅäÖà } [Fact] @@ -82,5 +82,79 @@ public void GetLiveWalletStatus_Normal_Success() Assert.False(re.Code != 0); } } + + [Fact] + public void GetMedalWall_Normal_Success() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + + BiliApiResponse re = api.GetMedalWall(919174).Result; + + Assert.NotEmpty(re.Data.List); + + var md = re.Data.List[0]; + Assert.NotNull(md); + Assert.False(String.IsNullOrEmpty(md.Link)); + Assert.False(String.IsNullOrEmpty(md.Target_name)); + Assert.NotNull(md.Medal_info); + Assert.False(String.IsNullOrEmpty(md.Medal_info.Medal_name)); + Assert.True(md.Medal_info.Medal_id > 0); + } + + [Fact] + public void WearMedalWall_Normal_Success() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + var biliCookie = scope.ServiceProvider.GetRequiredService(); + + // 猫雷粉丝牌 + var request = new WearMedalWallRequest(biliCookie.BiliJct, 365421); + + BiliApiResponse re = api.WearMedalWall(request).Result; + + Assert.True(re.Code == 0); + } + + [Fact] + public void GetSpaceInfo_Normal_Success() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + var biliCookie = scope.ServiceProvider.GetRequiredService(); + + BiliApiResponse re = api.GetSpaceInfo(919174).Result; + + Assert.True(re.Code == 0); + Assert.NotNull(re.Data); + Assert.Equal(919174, re.Data.Mid); + Assert.NotNull(re.Data.Live_room); + Assert.Equal(3115258, re.Data.Live_room.Roomid); + Assert.False(String.IsNullOrEmpty(re.Data.Name)); + Assert.False(String.IsNullOrEmpty(re.Data.Live_room.Title)); + } + + [Fact] + public void SendLiveDanmuku_Normal_Success() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + var biliCookie = scope.ServiceProvider.GetRequiredService(); + + var request = new SendLiveDanmukuRequest(biliCookie.BiliJct, 63666, "63666"); + + BiliApiResponse re = api.SendLiveDanmuku(request).Result; + + Assert.True(re.Code == 0); + } } } diff --git a/test/BiliAgentTest/LiveTraceApiTest.cs b/test/BiliAgentTest/LiveTraceApiTest.cs new file mode 100644 index 000000000..32332957b --- /dev/null +++ b/test/BiliAgentTest/LiveTraceApiTest.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using Ray.BiliBiliTool.Agent; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; +using Ray.BiliBiliTool.Console; +using Ray.BiliBiliTool.Infrastructure; +using Xunit; + +namespace BiliAgentTest +{ + public class LiveTraceApiTest + { + public LiveTraceApiTest() + { + Program.CreateHost(new[] { "--ENVIRONMENT=Development" }); + } + + [Fact] + public void WebHeartBeat_Normal_Success() + { + using var scope = Global.ServiceProviderRoot.CreateScope(); + + var ck = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); + + var request = new WebHeartBeatRequest(63666, 60); + + var re = api.WebHeartBeat(request).Result; + + Assert.Equal(0, re.Code); + Assert.Equal("0", re.Message); + Assert.Equal(60, re.Data.Next_interval); + } + } +} From 39b2088fdee84964af92cab8d23f9cbe3ee5c0c9 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 5 Jan 2023 00:28:30 +0800 Subject: [PATCH 02/11] feat: add qinglong task for liveFansMedal --- qinglong/DefaultTasks/bili_task_liveFansMedal.sh | 9 +++++++++ qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 qinglong/DefaultTasks/bili_task_liveFansMedal.sh create mode 100644 qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh diff --git a/qinglong/DefaultTasks/bili_task_liveFansMedal.sh b/qinglong/DefaultTasks/bili_task_liveFansMedal.sh new file mode 100644 index 000000000..aea011d95 --- /dev/null +++ b/qinglong/DefaultTasks/bili_task_liveFansMedal.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# new Env("bili直播粉丝牌") +# cron 5 0 * * * bili_task_liveFansMedal.sh +. bili_task_base.sh + +cd ./src/Ray.BiliBiliTool.Console + +export Ray_RunTasks=LiveFansMedal && \ +dotnet run --ENVIRONMENT=Production diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh b/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh new file mode 100644 index 000000000..54d9d76e6 --- /dev/null +++ b/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# new Env("bili直播粉丝牌[dev先行版]") +# cron 5 0 * * * bili_dev_task_liveFansMedal.sh +. bili_dev_task_base.sh + +cd ./src/Ray.BiliBiliTool.Console + +export Ray_RunTasks=LiveFansMedal && \ +dotnet run --ENVIRONMENT=Production From 9652030e6d831c637c137dffcb585c2c579329c1 Mon Sep 17 00:00:00 2001 From: bakapiano Date: Wed, 18 Jan 2023 21:17:12 +0800 Subject: [PATCH 03/11] =?UTF-8?q?1.=20=E5=88=A0=E9=99=A4=20Login=20Task=20?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=20ILiveApi=20=E4=BE=9D=E8=B5=96=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E7=84=B6=E4=BC=9A=E5=AF=BC=E8=87=B4=20Cookie=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=87=BA=E7=8E=B0=E9=97=AE=E9=A2=98=20(#383)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2. 实现直播间点赞接口 3. 为心跳包发送增加 5s 的延迟 --- .../Dtos/Live/LikeLiveRoomRequest.cs | 23 +++ .../BiliBiliAgent/Interfaces/ILiveApi.cs | 11 ++ src/Ray.BiliBiliTool.Agent/BiliCookie.cs | 6 - .../LiveFansMedalAppService.cs | 3 +- .../LoginTaskAppService.cs | 22 +-- .../Dtos/FansMedalInfoDto.cs | 28 ++++ .../Interfaces/ILiveDomainService.cs | 7 +- .../LiveDomainService.cs | 138 ++++++++++-------- 8 files changed, 151 insertions(+), 87 deletions(-) create mode 100644 src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/LikeLiveRoomRequest.cs create mode 100644 src/Ray.BiliBiliTool.DomainService/Dtos/FansMedalInfoDto.cs diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/LikeLiveRoomRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/LikeLiveRoomRequest.cs new file mode 100644 index 000000000..fe55938c6 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/LikeLiveRoomRequest.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live +{ + public class LikeLiveRoomRequest + { + public LikeLiveRoomRequest(int roomid, string csrf) + { + Roomid = roomid; + Csrf= csrf; + } + + public int Roomid { get; set; } + + public string Csrf { get; set; } + + public string Csrf_token => Csrf; + } +} diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs index 0e4ac59fb..c09372450 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs @@ -135,7 +135,18 @@ public interface ILiveApi : IBiliBiliApi [HttpGet("/room/v1/Room/get_info?room_id={roomId}&from=room")] Task> GetLiveRoomInfo(int roomId); + /// + /// 请求直播主页用于配置直播相关 Cookie + /// [HttpGet("/news/v1/notice/recom?product=live")] Task GetLiveHome(); + + /// + /// 点赞直播间 + /// + [HttpPost("/xlive/web-ucenter/v1/interact/likeInteract")] + [Header("Referer", "https://live.bilibili.com/")] + [Header("Origin", "https://live.bilibili.com")] + Task LikeLiveRoom([FormContent] LikeLiveRoomRequest request); } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliCookie.cs b/src/Ray.BiliBiliTool.Agent/BiliCookie.cs index 37f406464..a60acaf07 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliCookie.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliCookie.cs @@ -100,12 +100,6 @@ public override void Check() result = false; } - // LiveBuvid 为空时发出警告 - if (string.IsNullOrWhiteSpace(LiveBuvid)) - { - _logger.LogWarning("直播Cookie {cookie}未正确配置,将在执行相关任务时尝试自动获取", GetPropertyDescription(nameof(LiveBuvid))); - } - if (!result) throw new Exception($"请正确配置Cookie后再运行,配置方式见 {Constants.SourceCodeUrl}"); } diff --git a/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs index 902981a21..9f0be8b75 100644 --- a/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs +++ b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs @@ -43,7 +43,8 @@ IAccountDomainService accountDomainService public override void DoTask() { _liveDomainService.SendDanmakuToFansMedalLive(); - _liveDomainService.SendHeartBeatToFansMdealLive(); + _liveDomainService.LikeFansMedalLive(); + _liveDomainService.SendHeartBeatToFansMedalLive(); } } diff --git a/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs b/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs index 066093dec..e7dd5967a 100644 --- a/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs +++ b/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs @@ -18,6 +18,8 @@ using Ray.BiliBiliTool.Application.Attributes; using Ray.BiliBiliTool.Application.Contracts; using Ray.BiliBiliTool.Infrastructure.Enums; +using Ray.BiliBiliTool.Infrastructure; +using Microsoft.Extensions.DependencyInjection; namespace Ray.BiliBiliTool.Application { @@ -27,7 +29,6 @@ public class LoginTaskAppService : AppService, ILoginTaskAppService private readonly IPassportApi _passportApi; private readonly IHostEnvironment _hostingEnvironment; private readonly IQingLongApi _qingLongApi; - private readonly ILiveApi _liveApi; private readonly IConfiguration _configuration; public LoginTaskAppService( @@ -35,15 +36,13 @@ public LoginTaskAppService( ILogger logger, IPassportApi passportApi, IHostEnvironment hostingEnvironment, - IQingLongApi qingLongApi, - ILiveApi liveApi) + IQingLongApi qingLongApi) { _configuration = configuration; _logger = logger; _passportApi = passportApi; _hostingEnvironment = hostingEnvironment; _qingLongApi = qingLongApi; - _liveApi = liveApi; } [TaskInterceptor("扫码登录", TaskLevel.One)] @@ -120,21 +119,6 @@ protected bool QrCodeLogin(out BiliCookie cookieInfo) _logger.LogInformation("扫描成功!"); IEnumerable cookies = check.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value; - // 请求主播主页来正确配置 cookie - var liveHome = _liveApi.GetLiveHome().Result; - var liveHomeContent = JsonConvert.DeserializeObject(liveHome.Content.ReadAsStringAsync().Result); - if (liveHomeContent.Code == 0) - { - // 合并 cookie - IEnumerable liveCookies = liveHome.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value; - cookies = cookies.Union(liveCookies); - } - else - { - _logger.LogWarning("获取直播 cookie 时出现错误"); - _logger.LogWarning("{msg}", liveHomeContent.Message); - } - cookieInfo = GetCookie(cookies); result = true; diff --git a/src/Ray.BiliBiliTool.DomainService/Dtos/FansMedalInfoDto.cs b/src/Ray.BiliBiliTool.DomainService/Dtos/FansMedalInfoDto.cs new file mode 100644 index 000000000..f0f66385d --- /dev/null +++ b/src/Ray.BiliBiliTool.DomainService/Dtos/FansMedalInfoDto.cs @@ -0,0 +1,28 @@ +using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ray.BiliBiliTool.DomainService.Dtos +{ + public class FansMedalInfoDto + { + public FansMedalInfoDto(int roomId, MedalWallDto medalInfo, GetLiveRoomInfoResponse liveRoomInfo) + { + this.RoomId = roomId; + this.MedalInfo = medalInfo; + this.LiveRoomInfo = liveRoomInfo; + } + + // 直播间 id + public int RoomId { get; set; } + + // 粉丝牌信息 + public MedalWallDto MedalInfo { get; set; } + + // 直播间信息 + public GetLiveRoomInfoResponse LiveRoomInfo { get; set; } + } +} diff --git a/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs index e3c6d6b64..f7d724de9 100644 --- a/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs @@ -38,6 +38,11 @@ public interface ILiveDomainService : IDomainService /// /// 直播时长挂机 /// - void SendHeartBeatToFansMdealLive(); + void SendHeartBeatToFansMedalLive(); + + /// + /// 点赞直播间 + /// + void LikeFansMedalLive(); } } diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index 5fc522938..5768cc91c 100644 --- a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs @@ -405,19 +405,10 @@ public void SendDanmakuToFansMedalLive() { if (!CheckLiveCookie()) return; - _logger.LogInformation("【获取直播列表】获取拥有粉丝牌的直播列表"); - var result = this._liveApi.GetMedalWall(int.Parse(this._biliCookie.UserId)).Result; - - if (result.Code != 0) + GetFansMedalInfoList().ForEach(info => { - _logger.LogError("【获取直播列表】失败"); - _logger.LogError("【原因】{message}", result.Message); - return; - } + var medal = info.MedalInfo; - // 遍历粉丝牌 - foreach (var medal in result.Data.List) - { _logger.LogInformation("【直播间】{liveRoomName}", medal.Target_name); _logger.LogInformation("【粉丝牌】{medalName}", medal.Medal_info.Medal_name); @@ -430,7 +421,7 @@ public void SendDanmakuToFansMedalLive() { _logger.LogError("【获取直播间信息】失败"); _logger.LogError("【原因】{message}", spaceInfo.Message); - continue; + return; } // 发送弹幕 @@ -443,60 +434,19 @@ public void SendDanmakuToFansMedalLive() { _logger.LogError("【弹幕发送】失败"); _logger.LogError("【原因】{message}", sendResult.Message); - continue; + return; } _logger.LogInformation("【弹幕发送】成功~,你和主播 {name} 的亲密值增加了100!", spaceInfo.Data.Name); - } - } + }); + } - public void SendHeartBeatToFansMdealLive() + public void SendHeartBeatToFansMedalLive() { if (!CheckLiveCookie()) return; - _logger.LogInformation("【获取直播列表】获取拥有粉丝牌的直播列表"); - var medalWallInfo = this._liveApi.GetMedalWall(int.Parse(this._biliCookie.UserId)).Result; - - if (medalWallInfo.Code != 0) - { - _logger.LogError("【获取直播列表】失败"); - _logger.LogError("【原因】{message}", medalWallInfo.Message); - return; - } - var infoList = new List(); - foreach (var medal in medalWallInfo.Data.List) - { - _logger.LogInformation("【主播】{name} ", medal.Target_name); - if (_liveFansMedalTaskOptions.IsSkipLevel20Medal && medal.Medal_info.Level >= 20) - { - _logger.LogInformation("粉丝牌等级为 {level},观看将不再增长亲密度,跳过", medal.Medal_info.Level); - continue; - } - - // 通过空间主页信息获取直播间 id - int liveHostUserId = medal.Medal_info.Target_id; - var spaceInfo = _userInfoApi.GetSpaceInfo(liveHostUserId).Result; - if (spaceInfo.Code != 0) - { - _logger.LogError("【获取空间信息】失败"); - _logger.LogError("【原因】{message}", spaceInfo.Message); - continue; - } - - var roomId = spaceInfo.Data.Live_room.Roomid; - - // 获取直播间详细信息 - var liveRoomInfo = _liveApi.GetLiveRoomInfo(roomId).Result; - if (liveRoomInfo.Code != 0) - { - _logger.LogError("【获取直播间信息】失败"); - _logger.LogError("【原因】{message}", liveRoomInfo.Message); - continue; - } - - infoList.Add(new(roomId, liveRoomInfo.Data, new(), 0, 0)); - } + GetFansMedalInfoList().ForEach((medal) => infoList.Add(new(medal.RoomId, medal.LiveRoomInfo, new(), 0, 0))); if (infoList.Count == 0) { @@ -519,9 +469,9 @@ public void SendHeartBeatToFansMdealLive() string uuid = Guid.NewGuid().ToString(); var current = Now(); - if (current - info.LastBeatTime <= LiveFansMedalTaskOptions.HeartBeatInterval * 1000) + if (current - info.LastBeatTime <= (LiveFansMedalTaskOptions.HeartBeatInterval + 5) * 1000) { - int sleepTime = (int)(LiveFansMedalTaskOptions.HeartBeatInterval * 1000 - (current - info.LastBeatTime)); + int sleepTime = (int)((LiveFansMedalTaskOptions.HeartBeatInterval + 5) * 1000 - (current - info.LastBeatTime)); _logger.LogDebug("【休眠】{time} 毫秒", sleepTime); Thread.Sleep(sleepTime); } @@ -592,6 +542,74 @@ public void SendHeartBeatToFansMdealLive() _logger.LogInformation("【直播观看时长】完成情况:{success}/{total} ", successCount, infoList.Count); } + public void LikeFansMedalLive() + { + if (!CheckLiveCookie()) return; + + GetFansMedalInfoList().ForEach(info => + { + var result = _liveApi.LikeLiveRoom(new LikeLiveRoomRequest(info.RoomId, _biliCookie.BiliJct)).Result; + if (result.Code == 0) + { + _logger.LogInformation("【点赞直播间】{roomId} 完成", info.RoomId); + } + else + { + _logger.LogError("【点赞直播间】{roomId} 时候出现错误", info.RoomId); + _logger.LogError("【原因】{message}", result.Message); + } + }); + } + + private List GetFansMedalInfoList() + { + _logger.LogInformation("【获取直播列表】获取拥有粉丝牌的直播列表"); + var medalWallInfo = this._liveApi.GetMedalWall(int.Parse(this._biliCookie.UserId)).Result; + + if (medalWallInfo.Code != 0) + { + _logger.LogError("【获取直播列表】失败"); + _logger.LogError("【原因】{message}", medalWallInfo.Message); + return null; + } + + var infoList = new List(); + foreach (var medal in medalWallInfo.Data.List) + { + _logger.LogInformation("【主播】{name} ", medal.Target_name); + if (_liveFansMedalTaskOptions.IsSkipLevel20Medal && medal.Medal_info.Level >= 20) + { + _logger.LogInformation("粉丝牌等级为 {level},观看将不再增长亲密度,跳过", medal.Medal_info.Level); + continue; + } + + // 通过空间主页信息获取直播间 id + int liveHostUserId = medal.Medal_info.Target_id; + var spaceInfo = _userInfoApi.GetSpaceInfo(liveHostUserId).Result; + if (spaceInfo.Code != 0) + { + _logger.LogError("【获取空间信息】失败"); + _logger.LogError("【原因】{message}", spaceInfo.Message); + continue; + } + + var roomId = spaceInfo.Data.Live_room.Roomid; + + // 获取直播间详细信息 + var liveRoomInfo = _liveApi.GetLiveRoomInfo(roomId).Result; + if (liveRoomInfo.Code != 0) + { + _logger.LogError("【获取直播间信息】失败"); + _logger.LogError("【原因】{message}", liveRoomInfo.Message); + continue; + } + + infoList.Add(new FansMedalInfoDto(roomId, medal, liveRoomInfo.Data)); + } + + return infoList; + } + /// /// 自动配置直播相关 Cookie,来兼容较低版本中保存的 Cookie 配置 /// From 100c01106234a49eec602945e5b56924fa2729e7 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 22:08:24 +0800 Subject: [PATCH 04/11] chore: remove useless static UserAgent --- .../BiliBiliAgent/Interfaces/ILiveTraceApi.cs | 2 -- .../Options/LiveFansMedalTaskOptions.cs | 2 +- .../LiveDomainService.cs | 19 ++++++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs index 4bfab5391..689d6f36b 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs @@ -19,11 +19,9 @@ 单独引入 device 参数的原因: 会将表单中的双引号自动加入反斜杠转义... 如 ["key":"value"] => [\"key\":\"value\"] */ - [Header("User-Agent", LiveFansMedalTaskOptions.UserAgent)] [HttpPost("/xlive/data-interface/v1/x25Kn/E")] Task> EnterRoom([FormContent] EnterRoomRequest request, [FormField] string device); - [Header("User-Agent", LiveFansMedalTaskOptions.UserAgent)] [HttpPost("/xlive/data-interface/v1/x25Kn/X")] Task> HeartBeat([FormContent] HeartBeatRequest request, [FormField] string device); } diff --git a/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs b/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs index c48eb0898..88abea4c3 100644 --- a/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs +++ b/src/Ray.BiliBiliTool.Config/Options/LiveFansMedalTaskOptions.cs @@ -32,7 +32,7 @@ public class LiveFansMedalTaskOptions /// public bool IsSkipLevel20Medal { get; set; } = true; - public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; + //public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; public const int HeartBeatInterval = 60; } diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index 5768cc91c..999ce4629 100644 --- a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs @@ -34,6 +34,7 @@ public class LiveDomainService : ILiveDomainService private readonly LiveFansMedalTaskOptions _liveFansMedalTaskOptions; private readonly BiliCookie _biliCookie; private readonly DailyTaskOptions _dailyTaskOptions; + private readonly SecurityOptions _securityOptions; public LiveDomainService(ILogger logger, ILiveApi liveApi, @@ -42,7 +43,7 @@ public LiveDomainService(ILogger logger, IUserInfoApi userInfoApi, IOptionsMonitor dailyTaskOptions, IOptionsMonitor liveLotteryTaskOptions, - IOptionsMonitor liveFansMedalTaskOptions, + IOptionsMonitor securityOptions, BiliCookie biliCookie) { _logger = logger; @@ -53,7 +54,7 @@ public LiveDomainService(ILogger logger, _liveLotteryTaskOptions = liveLotteryTaskOptions.CurrentValue; _biliCookie = biliCookie; _dailyTaskOptions = dailyTaskOptions.CurrentValue; - _liveFansMedalTaskOptions = liveFansMedalTaskOptions.CurrentValue; + _securityOptions = securityOptions.CurrentValue; } /// @@ -439,7 +440,7 @@ public void SendDanmakuToFansMedalLive() _logger.LogInformation("【弹幕发送】成功~,你和主播 {name} 的亲密值增加了100!", spaceInfo.Data.Name); }); - } + } public void SendHeartBeatToFansMedalLive() { @@ -457,10 +458,10 @@ public void SendHeartBeatToFansMedalLive() var Now = () => new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds(); while (infoList.Min( - info => - info.FailedTimes >= _liveFansMedalTaskOptions.HeartBeatSendGiveUpThreshold ? int.MaxValue : - info.HeartBeatCount) - < _liveFansMedalTaskOptions.HeartBeatNumber) + info => info.FailedTimes >= _liveFansMedalTaskOptions.HeartBeatSendGiveUpThreshold + ? int.MaxValue : + info.HeartBeatCount) + < _liveFansMedalTaskOptions.HeartBeatNumber) { foreach (var info in infoList) { @@ -488,7 +489,7 @@ public void SendHeartBeatToFansMedalLive() info.RoomInfo.Area_id, info.HeartBeatCount, timestamp, - LiveFansMedalTaskOptions.UserAgent, + _securityOptions.UserAgent, _biliCookie.BiliJct, info.RoomInfo.Uid), $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") @@ -505,7 +506,7 @@ public void SendHeartBeatToFansMedalLive() _biliCookie.LiveBuvid, timestamp, info.HeartBeatInfo.Timestamp, - LiveFansMedalTaskOptions.UserAgent, + _securityOptions.UserAgent, info.HeartBeatInfo.Secret_rule, info.HeartBeatInfo.Secret_key, _biliCookie.BiliJct, From 8c53f35d9c9dbb7b65830a30feb32e841383bd19 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 22:12:45 +0800 Subject: [PATCH 05/11] fix: wrong delete --- src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index 999ce4629..9a2dd1edd 100644 --- a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs @@ -32,9 +32,10 @@ public class LiveDomainService : ILiveDomainService private readonly IUserInfoApi _userInfoApi; private readonly LiveLotteryTaskOptions _liveLotteryTaskOptions; private readonly LiveFansMedalTaskOptions _liveFansMedalTaskOptions; - private readonly BiliCookie _biliCookie; private readonly DailyTaskOptions _dailyTaskOptions; private readonly SecurityOptions _securityOptions; + private readonly BiliCookie _biliCookie; + public LiveDomainService(ILogger logger, ILiveApi liveApi, @@ -43,6 +44,7 @@ public LiveDomainService(ILogger logger, IUserInfoApi userInfoApi, IOptionsMonitor dailyTaskOptions, IOptionsMonitor liveLotteryTaskOptions, + IOptionsMonitor liveFansMedalTaskOptions, IOptionsMonitor securityOptions, BiliCookie biliCookie) { @@ -52,9 +54,11 @@ public LiveDomainService(ILogger logger, _liveTraceApi = liveTraceApi; _userInfoApi = userInfoApi; _liveLotteryTaskOptions = liveLotteryTaskOptions.CurrentValue; - _biliCookie = biliCookie; _dailyTaskOptions = dailyTaskOptions.CurrentValue; + _liveFansMedalTaskOptions = liveFansMedalTaskOptions.CurrentValue; _securityOptions = securityOptions.CurrentValue; + _biliCookie = biliCookie; + } /// From e51f6a25ca83e94a62b9995723d976e7b6e91f9a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 22:28:00 +0800 Subject: [PATCH 06/11] chore: upgrade nuget --- .../Ray.BiliBiliTool.Agent.csproj | 4 ++-- .../Ray.BiliBiliTool.Application.csproj | 2 +- .../Ray.BiliBiliTool.Console.csproj | 10 +++++----- .../Ray.BiliBiliTool.DomainService.csproj | 4 ++-- .../Ray.BiliBiliTool.Infrastructure.csproj | 2 +- .../Ray.Serilog.Sinks.Batched.csproj | 2 +- .../Ray.Serilog.Sinks.ServerChanBatched.csproj | 2 +- .../Ray.Serilog.Sinks.TelegramBatched.csproj | 2 +- .../Ray.Serilog.Sinks.WorkWeiXinBatched.csproj | 2 +- test/BiliAgentTest/BiliAgentTest.csproj | 8 ++++---- test/ConfigTest/ConfigTest.csproj | 8 ++++---- test/LogTest/LogTest.csproj | 8 ++++---- 12 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Ray.BiliBiliTool.Agent/Ray.BiliBiliTool.Agent.csproj b/src/Ray.BiliBiliTool.Agent/Ray.BiliBiliTool.Agent.csproj index 42789ea9c..8c3c6d1fd 100644 --- a/src/Ray.BiliBiliTool.Agent/Ray.BiliBiliTool.Agent.csproj +++ b/src/Ray.BiliBiliTool.Agent/Ray.BiliBiliTool.Agent.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/src/Ray.BiliBiliTool.Application/Ray.BiliBiliTool.Application.csproj b/src/Ray.BiliBiliTool.Application/Ray.BiliBiliTool.Application.csproj index 65607513b..6ae26800a 100644 --- a/src/Ray.BiliBiliTool.Application/Ray.BiliBiliTool.Application.csproj +++ b/src/Ray.BiliBiliTool.Application/Ray.BiliBiliTool.Application.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj index 8ccad8c92..bee0b6c80 100644 --- a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj +++ b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj @@ -55,12 +55,12 @@ - - - + + + - - + + diff --git a/src/Ray.BiliBiliTool.DomainService/Ray.BiliBiliTool.DomainService.csproj b/src/Ray.BiliBiliTool.DomainService/Ray.BiliBiliTool.DomainService.csproj index 675e9aafd..560a330c7 100644 --- a/src/Ray.BiliBiliTool.DomainService/Ray.BiliBiliTool.DomainService.csproj +++ b/src/Ray.BiliBiliTool.DomainService/Ray.BiliBiliTool.DomainService.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/Ray.BiliBiliTool.Infrastructure/Ray.BiliBiliTool.Infrastructure.csproj b/src/Ray.BiliBiliTool.Infrastructure/Ray.BiliBiliTool.Infrastructure.csproj index ea1bccbe6..25976c9a6 100644 --- a/src/Ray.BiliBiliTool.Infrastructure/Ray.BiliBiliTool.Infrastructure.csproj +++ b/src/Ray.BiliBiliTool.Infrastructure/Ray.BiliBiliTool.Infrastructure.csproj @@ -8,6 +8,6 @@ - + \ No newline at end of file diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.Batched/Ray.Serilog.Sinks.Batched.csproj b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.Batched/Ray.Serilog.Sinks.Batched.csproj index 6aa4c6ea2..a08684877 100644 --- a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.Batched/Ray.Serilog.Sinks.Batched.csproj +++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.Batched/Ray.Serilog.Sinks.Batched.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.ServerChanBatched/Ray.Serilog.Sinks.ServerChanBatched.csproj b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.ServerChanBatched/Ray.Serilog.Sinks.ServerChanBatched.csproj index 837a219a8..f945703ea 100644 --- a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.ServerChanBatched/Ray.Serilog.Sinks.ServerChanBatched.csproj +++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.ServerChanBatched/Ray.Serilog.Sinks.ServerChanBatched.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.TelegramBatched/Ray.Serilog.Sinks.TelegramBatched.csproj b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.TelegramBatched/Ray.Serilog.Sinks.TelegramBatched.csproj index f970b9bf2..b9a3aa956 100644 --- a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.TelegramBatched/Ray.Serilog.Sinks.TelegramBatched.csproj +++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.TelegramBatched/Ray.Serilog.Sinks.TelegramBatched.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.WorkWeiXinBatched/Ray.Serilog.Sinks.WorkWeiXinBatched.csproj b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.WorkWeiXinBatched/Ray.Serilog.Sinks.WorkWeiXinBatched.csproj index 837a219a8..f945703ea 100644 --- a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.WorkWeiXinBatched/Ray.Serilog.Sinks.WorkWeiXinBatched.csproj +++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.WorkWeiXinBatched/Ray.Serilog.Sinks.WorkWeiXinBatched.csproj @@ -5,7 +5,7 @@ - + diff --git a/test/BiliAgentTest/BiliAgentTest.csproj b/test/BiliAgentTest/BiliAgentTest.csproj index 0a8272606..5f8cefe40 100644 --- a/test/BiliAgentTest/BiliAgentTest.csproj +++ b/test/BiliAgentTest/BiliAgentTest.csproj @@ -8,13 +8,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/ConfigTest/ConfigTest.csproj b/test/ConfigTest/ConfigTest.csproj index 59cead1c9..2355e2c7b 100644 --- a/test/ConfigTest/ConfigTest.csproj +++ b/test/ConfigTest/ConfigTest.csproj @@ -6,13 +6,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/LogTest/LogTest.csproj b/test/LogTest/LogTest.csproj index 6c443a99b..4be2b3b3a 100644 --- a/test/LogTest/LogTest.csproj +++ b/test/LogTest/LogTest.csproj @@ -7,13 +7,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From bc892faad6e0fdaf4f14baafd681df7c553dc1e0 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 22:40:43 +0800 Subject: [PATCH 07/11] feat: update codes after upgrade nuget --- .../Dtos/Live/EnterRoomRequest.cs | 24 +++++++++++-------- .../Dtos/Live/HeartBeatRequest.cs | 19 ++++++++------- .../BiliBiliAgent/Interfaces/ILiveTraceApi.cs | 10 ++------ .../LiveDomainService.cs | 10 ++++---- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs index 5c3fd3c2d..e53b825ed 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs @@ -19,17 +19,19 @@ public EnterRoomRequest( long timestamp, string userAgent, string csrf, - int ruid) + int ruid, + string device) { - this.Id = JsonConvert.SerializeObject(new[] { parentId, areaID, seqNumber, roomId }); - this.Ts = timestamp; - this.Ua = userAgent; - this.Csrf = csrf; - this.Ruid = ruid; - - this.Is_patch = 0; - this.Heart_beat = "[]"; - this.Visit_id = ""; + Id = JsonConvert.SerializeObject(new[] { parentId, areaID, seqNumber, roomId }); + Ts = timestamp; + Ua = userAgent; + Csrf = csrf; + Ruid = ruid; + + Is_patch = 0; + Heart_beat = "[]"; + Visit_id = ""; + Device = device; } public string Id { get; set; } @@ -48,5 +50,7 @@ public EnterRoomRequest( public string Csrf { get; set; } public string Visit_id { get; set; } + + public string Device { get; set; } } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs index 1f158a754..acdb9a995 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs @@ -19,15 +19,17 @@ public HeartBeatRequest( ICollection secretRule, string secretKey, string csrf, - string uuid) + string uuid, + string device) { - this.Id = JsonSerializer.Serialize(new[] { parentId, areaID, seqNumber, roomId }); - this.Ets = ets; - this.Benchmark = secretKey; - this.Time = 60; - this.Ts = timestamp; - this.Ua = userAgent; - this.Csrf = csrf; + Id = JsonSerializer.Serialize(new[] { parentId, areaID, seqNumber, roomId }); + Ets = ets; + Benchmark = secretKey; + Time = 60; + Ts = timestamp; + Ua = userAgent; + Csrf = csrf; + Device = device; // 构造哈希值 var json = new @@ -69,5 +71,6 @@ public HeartBeatRequest( public string Visit_id { get; set; } + public string Device { get; } } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs index 689d6f36b..c5a36904c 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs @@ -13,16 +13,10 @@ public interface ILiveTraceApi : IBiliBiliApi [HttpGet("/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb={request}&pf=web")] Task> WebHeartBeat(WebHeartBeatRequest request); - /* - 单独引入 device 参数的原因: - WebApiClientCore 库的 FormContent 有个已知 issue https://github.com/dotnetcore/WebApiClient/issues/211 - 会将表单中的双引号自动加入反斜杠转义... - 如 ["key":"value"] => [\"key\":\"value\"] - */ [HttpPost("/xlive/data-interface/v1/x25Kn/E")] - Task> EnterRoom([FormContent] EnterRoomRequest request, [FormField] string device); + Task> EnterRoom([FormContent] EnterRoomRequest request); [HttpPost("/xlive/data-interface/v1/x25Kn/X")] - Task> HeartBeat([FormContent] HeartBeatRequest request, [FormField] string device); + Task> HeartBeat([FormContent] HeartBeatRequest request); } } diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index 9a2dd1edd..6c7214b3f 100644 --- a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs @@ -495,8 +495,9 @@ public void SendHeartBeatToFansMedalLive() timestamp, _securityOptions.UserAgent, _biliCookie.BiliJct, - info.RoomInfo.Uid), - $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") + info.RoomInfo.Uid, + $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") + ) .Result; } else @@ -514,8 +515,9 @@ public void SendHeartBeatToFansMedalLive() info.HeartBeatInfo.Secret_rule, info.HeartBeatInfo.Secret_key, _biliCookie.BiliJct, - uuid), - $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") + uuid, + $"[\"{_biliCookie.LiveBuvid}\",\"{uuid}\"]") + ) .Result; } From 98a60edbd917f961409b9a282f60882a870b5c30 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 22:54:33 +0800 Subject: [PATCH 08/11] chore: update --- .../LiveFansMedalAppService.cs | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs index 9f0be8b75..427d2b8f5 100644 --- a/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs +++ b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs @@ -1,51 +1,48 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; using Ray.BiliBiliTool.Application.Attributes; using Ray.BiliBiliTool.Application.Contracts; -using Ray.BiliBiliTool.Config.Options; using Ray.BiliBiliTool.DomainService.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Ray.BiliBiliTool.Application { public class LiveFansMedalAppService : AppService, ILiveFansMedalAppService { private readonly ILogger _logger; - private readonly IConfiguration _configuration; private readonly ILiveDomainService _liveDomainService; - private readonly LiveLotteryTaskOptions _liveLotteryTaskOptions; - private readonly SecurityOptions _securityOptions; - private readonly IAccountDomainService _accountDomainService; public LiveFansMedalAppService( - IConfiguration configuration, ILiveDomainService liveDomainService, - IOptionsMonitor securityOptions, - IOptionsMonitor liveLotteryTaskOptions, - ILogger logger, - IAccountDomainService accountDomainService + ILogger logger ) { - _configuration = configuration; _liveDomainService = liveDomainService; - _liveLotteryTaskOptions = liveLotteryTaskOptions.CurrentValue; - _securityOptions = securityOptions.CurrentValue; _logger = logger; - _accountDomainService = accountDomainService; } [TaskInterceptor("直播间互动", TaskLevel.One)] public override void DoTask() + { + SendDanmaku(); + Like(); + HeartBeat(); + } + + [TaskInterceptor("发送弹幕", TaskLevel.Two,false)] + private void SendDanmaku() { _liveDomainService.SendDanmakuToFansMedalLive(); + } + + [TaskInterceptor("点赞直播间", TaskLevel.Two,false)] + private void Like() + { _liveDomainService.LikeFansMedalLive(); - _liveDomainService.SendHeartBeatToFansMedalLive(); } + [TaskInterceptor("直播时长挂机", TaskLevel.Two,false)] + private void HeartBeat() + { + _liveDomainService.SendHeartBeatToFansMedalLive(); + } } } From a77b90f0ef6ffdc3909791c79d48e91deba2e015 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 23:23:43 +0800 Subject: [PATCH 09/11] chore: update --- docker/crontab | 1 + docker/sample/docker-compose.yml | 1 + docker/scripts/entry.sh | 28 ++++++++++++++-------------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/docker/crontab b/docker/crontab index de72028a6..4b39fc56f 100644 --- a/docker/crontab +++ b/docker/crontab @@ -1,3 +1,4 @@ 0 15 * * * dotnet /app/Ray.BiliBiliTool.Console.dll --runTasks=Daily >> /var/log/cron.log 0 22 * * * dotnet /app/Ray.BiliBiliTool.Console.dll --runTasks=LiveLottery >> /var/log/cron.log 7 1 * * * dotnet /app/Ray.BiliBiliTool.Console.dll --runTasks=VipBigPoint >> /var/log/cron.log +5 0 * * * dotnet /app/Ray.BiliBiliTool.Console.dll --runTasks=LiveFansMedal >> /var/log/cron.log diff --git a/docker/sample/docker-compose.yml b/docker/sample/docker-compose.yml index bca5efa0e..c6b7a4f2a 100644 --- a/docker/sample/docker-compose.yml +++ b/docker/sample/docker-compose.yml @@ -19,6 +19,7 @@ services: - Ray_LiveLotteryTaskConfig__Cron=0 22 * * * - Ray_UnfollowBatchedTaskConfig__Cron=0 6 1 * * - Ray_VipBigPointConfig__Cron=7 1 * * * + - Ray_LiveFansMedalTaskConfig__Cron=5 0 * * * # UA: - Ray_Security__UserAgent= diff --git a/docker/scripts/entry.sh b/docker/scripts/entry.sh index fdd0487b5..ff6d47ecb 100644 --- a/docker/scripts/entry.sh +++ b/docker/scripts/entry.sh @@ -8,38 +8,38 @@ CRON_FILE="/etc/cron.d/bilicron" # https://stackoverflow.com/questions/27771781/how-can-i-access-docker-set-environment-variables-from-a-cron-job echo "[step 1/4]导入环境变量" -printenv | grep -v "no_proxy" > /etc/environment -declare -p | grep -v "no_proxy" > /etc/cron.env +printenv | grep -v "no_proxy" >/etc/environment +declare -p | grep -v "no_proxy" >/etc/cron.env echo -e "=>完成\n" echo "[step 2/4]配置cron定时任务" -echo "SHELL=/bin/bash" > $CRON_FILE -echo "BASH_ENV=/etc/cron.env" >> $CRON_FILE -if [ -z "$Ray_LiveLotteryTaskConfig__Cron$Ray_UnfollowBatchedTaskConfig__Cron$Ray_VipBigPointConfig__Cron" ]; then +echo "SHELL=/bin/bash" >$CRON_FILE +echo "BASH_ENV=/etc/cron.env" >>$CRON_FILE +if [ -z "$Ray_LiveLotteryTaskConfig__Cron$Ray_UnfollowBatchedTaskConfig__Cron$Ray_VipBigPointConfig__Cron$Ray_LiveFansMedalTaskConfig__Cron" ]; then echo "=>使用默认的定时任务配置" - cat /app/scripts/crontab >> $CRON_FILE + cat /app/scripts/crontab >>$CRON_FILE else echo "=>使用用户指定的定时任务配置" if ! [ -z "$Ray_DailyTaskConfig__Cron" ]; then - echo "$Ray_DailyTaskConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=Daily" >> $CRON_FILE + echo "$Ray_DailyTaskConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=Daily" >>$CRON_FILE fi if ! [ -z "$Ray_LiveLotteryTaskConfig__Cron" ]; then - echo "$Ray_LiveLotteryTaskConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=LiveLottery" >> $CRON_FILE + echo "$Ray_LiveLotteryTaskConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=LiveLottery" >>$CRON_FILE fi if ! [ -z "$Ray_UnfollowBatchedTaskConfig__Cron" ]; then - echo "$Ray_UnfollowBatchedTaskConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=UnfollowBatched" >> $CRON_FILE + echo "$Ray_UnfollowBatchedTaskConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=UnfollowBatched" >>$CRON_FILE fi if ! [ -z "$Ray_VipBigPointConfig__Cron" ]; then - echo "$Ray_VipBigPointConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=VipBigPoint" >> $CRON_FILE + echo "$Ray_VipBigPointConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=VipBigPoint" >>$CRON_FILE fi - if ! [ -z "$Ray_LiveFansMedalConfig__Cron" ]; then - echo "$Ray_LiveFansMedalConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=LiveFansMedal" >> $CRON_FILE + if ! [ -z "$Ray_LiveFansMedalConfig__Cron" ]; then + echo "$Ray_LiveFansMedalConfig__Cron cd /app && dotnet $CONSOLE_DLL --runTasks=LiveFansMedal" >>$CRON_FILE fi fi if ! [ -z "$Ray_Crontab" ]; then echo "=>检测到自定义定时任务" - echo "$Ray_Crontab" >> $CRON_FILE + echo "$Ray_Crontab" >>$CRON_FILE fi cat $CRON_FILE @@ -60,5 +60,5 @@ echo -e "[step 全部已完成]\n" . /app/scripts/entry_after.sh -touch /var/log/cron.log #todo:debian似乎并没有记录cron的日志。。。 +touch /var/log/cron.log #todo:debian似乎并没有记录cron的日志。。。 tail -f /var/log/cron.log # 追踪cron日志,避免当前脚本终止导致容器终止 From 7030e6c3bf718ff58a99933afb66437d920b27fc Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 23:23:53 +0800 Subject: [PATCH 10/11] chore: update change log --- CHANGELOG.md | 2 ++ README.md | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0757b5b9..65bde9d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,3 +78,5 @@ - Fix( #364 ),兼容青龙异形response数据类型 - Fix( #366 #361 ),修复一些低级bug - Feature( #359 ),兼容读取不到`$QL_DIR`的情况 +## 0.4.0 +- 合并PR( #381 #383 ),新增直播间挂机功能,感谢@bakapiano \ No newline at end of file diff --git a/README.md b/README.md index b0a26ee7d..d6076935c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ BiliBiliTool - **扫码登录,自动更新cookie** - **每日获取满额升级经验(登录、投币、点赞、分享视频)(支持指定支持up主)** +- **直播间挂机** - **每天漫画签到** - **每天直播签到** - **直播中心银瓜子兑换为硬币** @@ -69,7 +70,7 @@ BiliBiliTool - **本应用仅用于学习和测试,作者本人并不对其负责,请于运行测试完成后自行删除,请勿滥用!** - **所有代码都是开源且透明的,任何人均可查看,程序不会保存或滥用任何用户的个人信息** -- **应用内几乎所有功能都开放为了配置(如任务开关、日期、id等),详细信息可阅读配置文档,请自己对配置负责** +- **应用内几乎所有功能都开放为了配置(如任务开关、日期、id等),详细信息可阅读配置文档,请对自己配置负责** 本地运行图示: @@ -111,9 +112,7 @@ BiliBiliTool 实现自动完成任务的原理,是通过调用一系列开放 #### 1.1.5. 方式五:~~GitHub Actions~~ -暂时删掉该方式避避风头。 - -**建议所有使用该方式运行的朋友,暂时先替换其他运行方式,避免造成不必要的损失。** +GitHub官方反对并抵制对Actions的滥用,建议所有使用该方式运行的朋友,暂时先替换其他运行方式,避免封号。 #### 1.1.6. 方式六:Chart部署 @@ -146,15 +145,15 @@ dotnet Ray.BiliBiliTool.Console.dll --runTasks=Daily&LiveLottery 任务列表如下: -| 任务名 | Code | 功能 | 默认WorkFlow文件 | GithHub Environments | 推荐运行频率 | 备注 | -| :----: | :----: | :----: | :----: | :----: | :----: | :----: | -| 扫码登录 | Login | 试用bili app扫码登录,用于第一次运行时初始化cookie,或cookie过期时的更新。不同平台会将cookie存储到不同地方,青龙存储到环境变量中,其他会存储到cookies。json中 | | Production | 手动 | | -| 每日任务 | Daily | 完成每日任务获取满额65点经验(登录、观看视频、分享视频、投币),以及签到、领福利和充电等附属功能 | bilibili-daily-task.yml | Production | 每天一次 | | -| 天选时刻抽奖 | LiveLottery | 直播中心天选时刻抽奖 | live-lottery-task.yml | LiveLottery | 建议每天运行0-4次内 | 对应Actions工作流默认是关闭的,需要添加key为`ISOPENLIVELOTTERYTASK`、值为`true`的secret来手动开启;大部分抽奖都需要关注主播,介意的不要开启 | -| 批量取关 | UnfollowBatched | 批量取关指定分组下的所有关注(主要用于清理天选抽奖而产生的关注) | unfollow-batched-task.yml | 无 | 需要时手动运行 | 需要通过配置指定2个参数:`GroupName`(分组名称,如`天选时刻`)和`Count`(目标取关个数,-1表示全部),应用会倒序从后往前取关指定个数 | -| 大会员大积分 | VipBigPoint | 大会员大积分任务(签到、浏览、观看) | 无 | 无 | 每天凌晨一点运行 | | -| 测试Cookie | Test | 测试Cookie是否正常 | 无,可以使用empty-task.yml来运行 | 无 | 需要时手动运行 | 主要用于调试 | -| 空模板 | 无(只用于 GitHub Actions ) | 用于 GitHub Actions 运行指定的任意任务 | empty-task.yml | 无 | 需要时手动运行 | 需要通过配置指定要运行的任务Code(多个使用英文逗号分隔),主要用于调试 | +| 任务名 | Code | 功能 | 推荐运行频率 | 备注 | +| :----: | :----: | :----: | :----: | :----: | +| 扫码登录 | Login | 试用bili app扫码登录,用于第一次运行时初始化cookie,或cookie过期时的更新。不同平台会将cookie存储到不同地方,青龙存储到环境变量中,其他会存储到cookies。json中 | 手动 | | +| 每日任务 | Daily | 完成每日任务获取满额65点经验(登录、观看视频、分享视频、投币),以及签到、领福利和充电等附属功能 | 每天一次 | | +| 天选时刻抽奖 | LiveLottery | 直播中心天选时刻抽奖 | 建议每天运行0-4次 | 对应Actions工作流默认是关闭的,需要添加key为`ISOPENLIVELOTTERYTASK`、值为`true`的secret来手动开启;大部分抽奖都需要关注主播,介意的不要开启 | +| 批量取关 | UnfollowBatched | 批量取关指定分组下的所有关注(主要用于清理天选抽奖而产生的关注) | 需要时手动运行 | 需要通过配置指定2个参数:`GroupName`(分组名称,如`天选时刻`)和`Count`(目标取关个数,-1表示全部),应用会倒序从后往前取关指定个数 | +| 大会员大积分 | VipBigPoint | 大会员大积分任务(签到、浏览、观看) | 每天凌晨一点运行 | | +| 直播间挂机 | LiveFansMedal | 直播间挂机 | 每天一次 | | +| 测试Cookie | Test | 测试Cookie是否正常 | 需要时手动运行 | 主要用于调试 | ## 3. 个性化自定义配置 @@ -210,9 +209,9 @@ dotnet Ray.BiliBiliTool.Console.dll --runTasks=Daily&LiveLottery * 搜索查看 Issue,确定是否已有人提过同类问题 -* 确认没有同类 Issue 后,自己可新建 Issue,描述问题或建议 +* 对于不确定的主题,为避免code结束后PR不被接受,可以先新建 Issue,描述问题或建议,讨论清楚后再动手编码 -* 如果想自己解决,请 Fork 仓库后,在**develop 分支**进行编码开发,完成后**提交 PR 到 develop 分支**,并标注解决的 Issue 编号 +* 如果确认自己可以解决,请 Fork 仓库后,在**develop 分支**进行编码开发,完成后**提交 PR 到 develop 分支** 我会尽快进行代码审核,测试成功后会合并入 main 主分支,提前感谢您的贡献。 @@ -221,8 +220,6 @@ dotnet Ray.BiliBiliTool.Console.dll --runTasks=Daily&LiveLottery ## 8. 捐赠支持 -[>>捐赠留言及回复](docs/donate-list.md) - 个人维护开源不易 如果觉得我写的程序对你小有帮助 From ab9cf1125608380d9f6e16f8a9f9ef6e2d5794be Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 18 Jan 2023 23:24:34 +0800 Subject: [PATCH 11/11] chore: update --- common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.props b/common.props index bb7c57498..8693d4eb0 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ Ray - 0.3.2 + 0.4.0 $(NoWarn);CS1591;CS0436