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) - 个人维护开源不易 如果觉得我写的程序对你小有帮助 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/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 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 2a238d382..ff6d47ecb 100644 --- a/docker/scripts/entry.sh +++ b/docker/scripts/entry.sh @@ -8,35 +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 fi fi if ! [ -z "$Ray_Crontab" ]; then echo "=>检测到自定义定时任务" - echo "$Ray_Crontab" >> $CRON_FILE + echo "$Ray_Crontab" >>$CRON_FILE fi cat $CRON_FILE @@ -57,5 +60,5 @@ 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 +touch /var/log/cron.log #todo:debian似乎并没有记录cron的日志。。。 +tail -f /var/log/cron.log # 追踪cron日志,避免当前脚本终止导致容器终止 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 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..e53b825ed --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/EnterRoomRequest.cs @@ -0,0 +1,56 @@ +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, + string device) + { + 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; } + + 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; } + + public string Device { 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..acdb9a995 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Dtos/Live/HeartBeatRequest.cs @@ -0,0 +1,76 @@ +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, + string device) + { + 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 + { + 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; } + + public string Device { get; } + } +} 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/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/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..c09372450 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,55 @@ 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); + + /// + /// 请求直播主页用于配置直播相关 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/BiliBiliAgent/Interfaces/ILiveTraceApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs new file mode 100644 index 000000000..c5a36904c --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs @@ -0,0 +1,22 @@ +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); + + [HttpPost("/xlive/data-interface/v1/x25Kn/E")] + Task> EnterRoom([FormContent] EnterRoomRequest request); + + [HttpPost("/xlive/data-interface/v1/x25Kn/X")] + Task> HeartBeat([FormContent] HeartBeatRequest request); + } +} 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..a60acaf07 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; } + /// /// 检查是否已配置 /// 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.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.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..427d2b8f5 --- /dev/null +++ b/src/Ray.BiliBiliTool.Application/LiveFansMedalAppService.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Logging; +using Ray.BiliBiliTool.Application.Attributes; +using Ray.BiliBiliTool.Application.Contracts; +using Ray.BiliBiliTool.DomainService.Interfaces; + +namespace Ray.BiliBiliTool.Application +{ + public class LiveFansMedalAppService : AppService, ILiveFansMedalAppService + { + private readonly ILogger _logger; + private readonly ILiveDomainService _liveDomainService; + + public LiveFansMedalAppService( + ILiveDomainService liveDomainService, + ILogger logger + ) + { + _liveDomainService = liveDomainService; + _logger = logger; + } + + [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(); + } + + [TaskInterceptor("直播时长挂机", TaskLevel.Two,false)] + private void HeartBeat() + { + _liveDomainService.SendHeartBeatToFansMedalLive(); + } + } +} diff --git a/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs b/src/Ray.BiliBiliTool.Application/LoginTaskAppService.cs index 81a09764a..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 { @@ -34,8 +36,7 @@ public LoginTaskAppService( ILogger logger, IPassportApi passportApi, IHostEnvironment hostingEnvironment, - IQingLongApi qingLongApi - ) + IQingLongApi qingLongApi) { _configuration = configuration; _logger = logger; 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.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..88abea4c3 --- /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..bee0b6c80 100644 --- a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj +++ b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj @@ -55,12 +55,12 @@ - - - + + + - - + + @@ -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/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/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..f7d724de9 100644 --- a/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs +++ b/src/Ray.BiliBiliTool.DomainService/Interfaces/ILiveDomainService.cs @@ -29,5 +29,20 @@ public interface ILiveDomainService : IDomainService void TryJoinTianXuan(ListItemDto target); void GroupFollowing(); + + /// + /// 发送弹幕 + /// + void SendDanmakuToFansMedalLive(); + + /// + /// 直播时长挂机 + /// + void SendHeartBeatToFansMedalLive(); + + /// + /// 点赞直播间 + /// + void LikeFansMedalLive(); } } diff --git a/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs b/src/Ray.BiliBiliTool.DomainService/LiveDomainService.cs index cd790d7e5..6c7214b3f 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,37 @@ 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 BiliCookie _biliCookie; + private readonly LiveFansMedalTaskOptions _liveFansMedalTaskOptions; private readonly DailyTaskOptions _dailyTaskOptions; + private readonly SecurityOptions _securityOptions; + private readonly BiliCookie _biliCookie; + public LiveDomainService(ILogger logger, ILiveApi liveApi, IRelationApi relationApi, + ILiveTraceApi liveTraceApi, + IUserInfoApi userInfoApi, IOptionsMonitor dailyTaskOptions, IOptionsMonitor liveLotteryTaskOptions, + IOptionsMonitor liveFansMedalTaskOptions, + IOptionsMonitor securityOptions, BiliCookie biliCookie) { _logger = logger; _liveApi = liveApi; _relationApi = relationApi; + _liveTraceApi = liveTraceApi; + _userInfoApi = userInfoApi; _liveLotteryTaskOptions = liveLotteryTaskOptions.CurrentValue; - _biliCookie = biliCookie; _dailyTaskOptions = dailyTaskOptions.CurrentValue; + _liveFansMedalTaskOptions = liveFansMedalTaskOptions.CurrentValue; + _securityOptions = securityOptions.CurrentValue; + _biliCookie = biliCookie; + } /// @@ -385,5 +405,264 @@ private long GetOrCreateTianXuanGroupId() return groupId; } #endregion + + public void SendDanmakuToFansMedalLive() + { + if (!CheckLiveCookie()) return; + + GetFansMedalInfoList().ForEach(info => + { + var medal = info.MedalInfo; + + _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); + return; + } + + // 发送弹幕 + 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); + return; + } + + _logger.LogInformation("【弹幕发送】成功~,你和主播 {name} 的亲密值增加了100!", spaceInfo.Data.Name); + }); + } + + public void SendHeartBeatToFansMedalLive() + { + if (!CheckLiveCookie()) return; + + var infoList = new List(); + GetFansMedalInfoList().ForEach((medal) => infoList.Add(new(medal.RoomId, medal.LiveRoomInfo, 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 + 5) * 1000) + { + int sleepTime = (int)((LiveFansMedalTaskOptions.HeartBeatInterval + 5) * 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, + _securityOptions.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, + _securityOptions.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); + } + + 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 配置 + /// + /// + /// 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.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/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/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/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); + } + } +} 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