From afdb0da0e65189ff517545d56480291ea4eb1464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9C=A87=E6=A5=BC?= <31154238+RayWangQvQ@users.noreply.github.com> Date: Wed, 1 May 2024 14:25:39 +0800 Subject: [PATCH 1/7] feat[#670]: add config of UserAgentApp for app client (#701) --- .editorconfig | 291 ++++++++++++++++-- CHANGELOG.md | 4 +- common.props | 2 +- docs/configuration.md | 55 ++-- .../BiliBiliAgent/Interfaces/IAccountApi.cs | 23 +- .../BiliBiliAgent/Interfaces/IArticleApi.cs | 64 ++-- .../BiliBiliAgent/Interfaces/IBiliBiliApi.cs | 25 +- .../BiliBiliAgent/Interfaces/IChargeApi.cs | 85 +++-- .../BiliBiliAgent/Interfaces/IDailyTaskApi.cs | 59 ++-- .../BiliBiliAgent/Interfaces/IHomeApi.cs | 17 +- .../BiliBiliAgent/Interfaces/ILiveApi.cs | 281 +++++++++-------- .../BiliBiliAgent/Interfaces/ILiveTraceApi.cs | 21 +- .../BiliBiliAgent/Interfaces/IMangaApi.cs | 57 ++-- .../BiliBiliAgent/Interfaces/IPassportApi.cs | 23 +- .../BiliBiliAgent/Interfaces/IRelationApi.cs | 171 +++++----- .../BiliBiliAgent/Interfaces/IUserInfoApi.cs | 41 ++- .../BiliBiliAgent/Interfaces/IVideoApi.cs | 201 ++++++------ .../Interfaces/IVipBigPointApi.cs | 98 +++--- src/Ray.BiliBiliTool.Agent/BiliHosts.cs | 13 + .../Extensions/ServiceCollectionExtension.cs | 64 ++-- .../Options/SecurityOptions.cs | 5 + src/Ray.BiliBiliTool.Console/appsettings.json | 1 + .../ArticleApiTests.cs | 8 +- .../VipBigPointApiTest.cs | 9 +- 24 files changed, 921 insertions(+), 697 deletions(-) create mode 100644 src/Ray.BiliBiliTool.Agent/BiliHosts.cs diff --git a/.editorconfig b/.editorconfig index 8616aec3e..65d1b6620 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,31 +1,260 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -# Default settings: -# A newline ending every file -# Use CRLF line break -# Use 4 spaces as indentation -[*] -end_of_line = crlf -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 4 - -# Json config files -[*.json] -indent_size = 2 - -# Yaml config files -[*.{yml,yaml}] -indent_size = 2 - -# Xml project files -[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] -indent_size = 2 - -# Xml config files -[*.{props,targets,config,nuspec}] -indent_size = 2 +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use CRLF line break +# Use 4 spaces as indentation +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +# Json config files +[*.json] +indent_size = 2 + +# Yaml config files +[*.{yml,yaml}] +indent_size = 2 + +# Xml project files +[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# c# 文件 +[*.cs] + +#### Core EditorConfig 选项 #### + +# 缩进和间距 +indent_size = 4 +indent_style = space +tab_width = 4 + +# 新行首选项 +end_of_line = lf +insert_final_newline = false + +#### .NET 编码约定 #### + +# 组织 Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. 和 Me. 首选项 +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# 语言关键字与 bcl 类型首选项 +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# 括号首选项 +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# 修饰符首选项 +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# 表达式级首选项 +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# 字段首选项 +dotnet_style_readonly_field = true + +# 参数首选项 +dotnet_code_quality_unused_parameters = all + +# 禁止显示首选项 +dotnet_remove_unnecessary_suppression_exclusions = none + +# 新行首选项 +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### c# 编码约定 #### + +# var 首选项 +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = true + +# Expression-bodied 成员 +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = true +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# 模式匹配首选项 +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null 检查首选项 +csharp_style_conditional_delegate_call = true + +# 修饰符首选项 +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# 代码块首选项 +csharp_prefer_braces = when_multiline +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = file_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# 表达式级首选项 +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# "using" 指令首选项 +csharp_using_directive_placement = outside_namespace + +# 新行首选项 +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# 格式规则 #### + +# 新行首选项 +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# 缩进首选项 +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# 空格键首选项 +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# 包装首选项 +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# 命名样式 + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/CHANGELOG.md b/CHANGELOG.md index c0722e0f7..5c14e7565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ +## 2.0.6 +- Feature[#670]: 新增针对App的AppUserAgent配置项 ## 2.0.5 -- Fix[#260]: 再次尝试修复大会员大积分“账号风险”异常 +- Fix[#260]: 再次尝试修复大会员大积分“账号风险”异常 ## 2.0.4 - Fix: 尝试修复大会员大积分“账号风险”异常 - Feature:为agent api创建集成测试 diff --git a/common.props b/common.props index 89ab5d059..438fbfc63 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ Ray - 2.0.5 + 2.0.6 $(NoWarn);CS1591;CS0436 diff --git a/docs/configuration.md b/docs/configuration.md index 1c72fe6fe..795343a4b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -8,8 +8,7 @@ - [1.1. 方式一:修改配置文件](#11-方式一修改配置文件) - [1.2. 方式二:命令启动时通过命令行参数配置](#12-方式二命令启动时通过命令行参数配置) - [1.3. 方式三:添加环境变量(推荐)](#13-方式三添加环境变量推荐) - - [1.4. ~~方式四:托管在GitHub Actions上,使用GitHub Secrets配置~~](#14-方式四托管在github-actions上使用github-secrets配置) - - [1.5. 方式五:托管在青龙面板上,使用面板的环境变量页或配置文件页进行配置](#15-方式五托管在青龙面板上使用面板的环境变量页或配置文件页进行配置) + - [1.4. 方式四:托管在青龙面板上,使用面板的环境变量页或配置文件页进行配置](#14-方式四托管在青龙面板上使用面板的环境变量页或配置文件页进行配置) - [2. 优先级](#2-优先级) - [3. 详细配置说明](#3-详细配置说明) - [3.1. Cookie字符串](#31-cookie字符串) @@ -19,7 +18,8 @@ - [3.2.3. 两次调用B站Api之间的间隔秒数](#323-两次调用b站api之间的间隔秒数) - [3.2.4. 间隔秒数所针对的HttpMethod](#324-间隔秒数所针对的httpmethod) - [3.2.5. 请求B站接口时头部传递的User-Agent](#325-请求b站接口时头部传递的user-agent) - - [3.2.6. WebProxy(代理)](#326-webproxy代理) + - [3.2.6. App请求B站接口时头部传递的User-Agent](#326-app请求b站接口时头部传递的user-agent) + - [3.2.7. WebProxy(代理)](#327-webproxy代理) - [3.3. 每日任务相关](#33-每日任务相关) - [3.3.1. 是否开启观看视频任务](#331-是否开启观看视频任务) - [3.3.2. 是否开启分享视频任务](#332-是否开启分享视频任务) @@ -141,29 +141,8 @@ dotnet Ray.BiliBiliTool.Console.dll 注意区分单下划线和双下划线,linux系统使用 `export` 关键字代替 `set` 。 - -### 1.4. ~~方式四:托管在GitHub Actions上,使用GitHub Secrets配置~~ - -已废除,当前不支持使用`GitHub Action`直接运行应用,`GitHub Action`只用于部署 - -
- -~~使用GitHub Actions,可以通过添加Secret实现配置。~~ - -~~比如,配置微信推送的SCKEY,可以添加如下Secret:~~ - -~~Secret Name:`PUSHSCKEY`~~ - -~~Secret Value:`123abc`~~ - -~~这些 Secrets 会通过 workflow 里的yml脚本映射为环境变量,在应用启动时作为环境变量配置源传入程序当中,所以使用 GitHub Secrets 配置的本质是使用环境变量配置。~~ - -![添加GitHub Secrets](imgs/git-secrets.png) - -
- - -### 1.5. 方式五:托管在青龙面板上,使用面板的环境变量页或配置文件页进行配置 + +### 1.4. 方式四:托管在青龙面板上,使用面板的环境变量页或配置文件页进行配置 青龙面板配置,其本质还是通过环境变量进行配置,有如下两种方式。 @@ -238,7 +217,6 @@ export Ray_Serilog__WriteTo__9__Args__token="abcde" | 值域 | [true,false] | | | 默认值 | false | | | 环境变量 | `Ray_Security__IsSkipDailyTask` | `set Ray_Security__IsSkipDailyTask=true` | -| GitHub Secrets | `ISSKIPDAILYTASK` | Name:`ISSKIPDAILYTASK` Value: `true`| #### 3.2.2. 随机睡眠的最大时长 @@ -254,7 +232,6 @@ export Ray_Serilog__WriteTo__9__Args__token="abcde" | 值域 | 数字 | | 默认值 | 20 | | 环境变量 | `Ray_Security__RandomSleepMaxMin` | -| GitHub Secrets | `RANDOMSLEEPMAXMIN`| #### 3.2.3. 两次调用B站Api之间的间隔秒数 @@ -267,8 +244,6 @@ export Ray_Serilog__WriteTo__9__Args__token="abcde" | 值域 | [0,+] | | 默认值 | 20 | | 环境变量 | `Ray_Security__IntervalSecondsBetweenRequestApi` | -| GitHub Secrets | `INTERVALSECONDSBETWEENREQUESTAPI` | - #### 3.2.4. 间隔秒数所针对的HttpMethod @@ -280,7 +255,6 @@ export Ray_Serilog__WriteTo__9__Args__token="abcde" | 值域 | [GET,POST],多个以英文逗号分隔 | | 默认值 | POST | | 环境变量 | `Ray_Security__IntervalMethodTypes` | -| GitHub Secrets | `INTERVALMETHODTYPES` | #### 3.2.5. 请求B站接口时头部传递的User-Agent @@ -291,15 +265,28 @@ export Ray_Serilog__WriteTo__9__Args__token="abcde" | 值域 | 字符串,可以F12从自己的浏览器获取 | | 默认值 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 Edg/87.0.664.41 | | 环境变量 | `Ray_Security__UserAgent` | -| GitHub Secrets | `USERAGENT`| + +获取浏览器中自己的UA的方法见下图: + +![获取User-Agent](imgs/get-user-agent.png) + + +#### 3.2.6. App请求B站接口时头部传递的User-Agent + +| TITLE | CONTENT | +| ---------- | -------------- | +| 配置Key | `Security:UserAgentApp` | +| 值域 | 字符串,可以F12从自己的浏览器获取 | +| 默认值 | Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 os/android model/SM-S9080 build/7760700 osVer/12 sdkInt/32 network/2 BiliApp/7760700 mobi_app/android channel/bili innerVer/7760710 c_locale/zh_CN s_locale/zh_CN disable_rcmd/0 7.76.0 os/android model/SM-S9080 mobi_app/android build/7760700 channel/bili innerVer/7760710 osVer/12 network/2 | +| 环境变量 | `Ray_Security__UserAgentApp` | 获取浏览器中自己的UA的方法见下图: ![获取User-Agent](imgs/get-user-agent.png) - -#### 3.2.6. WebProxy(代理) + +#### 3.2.7. WebProxy(代理) 支持需要账户密码的代理。 diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IAccountApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IAccountApi.cs index 42fa70eec..d0c620275 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IAccountApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IAccountApi.cs @@ -2,17 +2,16 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +[Header("Host", "account.bilibili.com")] +public interface IAccountApi : IBiliBiliApi { - [Header("Host", "account.bilibili.com")] - public interface IAccountApi : IBiliBiliApi - { - /// - /// 获取硬币余额 - /// - /// - [Header("Referer", "https://account.bilibili.com/account/coin")] - [HttpGet("/site/getCoin")] - Task> GetCoinBalanceAsync(); - } + /// + /// 获取硬币余额 + /// + /// + [Header("Referer", "https://account.bilibili.com/account/coin")] + [HttpGet("/site/getCoin")] + Task> GetCoinBalanceAsync(); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs index 88d993863..9ae88929d 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IArticleApi.cs @@ -3,34 +3,44 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Article; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces -{ - - [Header("Host", "api.bilibili.com")] - public interface IArticleApi : IBiliBiliApi - { - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://space.bilibili.com")] - [HttpGet("/x/space/wbi/article")] - Task> SearchUpArticlesByUpIdAsync([PathQuery] SearchArticlesByUpIdDto request); +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; - /// - /// 获取专栏详情 - /// - /// - /// - [HttpGet("/x/article/viewinfo?id={cvid}")] - Task> SearchArticleInfoAsync(long cvid); +[Header("Host", "api.bilibili.com")] +public interface IArticleApi : IBiliBiliApi +{ + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://space.bilibili.com")] + [HttpGet("/x/space/wbi/article")] + Task> SearchUpArticlesByUpIdAsync([PathQuery] SearchArticlesByUpIdDto request); - [Header("Content-Type", "application/x-www-form-urlencoded")] - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/web-interface/coin/add")] - Task AddCoinForArticleAsync([FormContent] AddCoinForArticleRequest request, [Header("referer")] string refer = "https://www.bilibili.com/read/cv5806746/?from=search&spm_id_from=333.337.0.0"); + /// + /// 获取专栏详情 + /// + /// + /// + [HttpGet("/x/article/viewinfo?id={cvid}")] + Task> SearchArticleInfoAsync(long cvid); - [Header("Content-Type", "application/x-www-form-urlencoded")] - [Header("Referer", "https://www.bilibili.com/read/cv{cvid}/?from=search&spm_id_from=333.337.0.0")] - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/article/like?id={cvid}&type=1&csrf={csrf}")] - Task LikeAsync(long cvid, string csrf); - } + /// + /// 为专栏文章投币 + /// + /// + /// + /// + [Header("Content-Type", "application/x-www-form-urlencoded")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/web-interface/coin/add")] + Task AddCoinForArticleAsync([FormContent] AddCoinForArticleRequest request, [Header("referer")] string refer = "https://www.bilibili.com/read/cv5806746/?from=search&spm_id_from=333.337.0.0"); + + /// + /// 为专栏文章点赞 + /// + /// + /// + /// + [Header("Content-Type", "application/x-www-form-urlencoded")] + [Header("Referer", "https://www.bilibili.com/read/cv{cvid}/?from=search&spm_id_from=333.337.0.0")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/article/like?id={cvid}&type=1&csrf={csrf}")] + Task LikeAsync(long cvid, string csrf); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IBiliBiliApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IBiliBiliApi.cs index 35af6941b..50586ca94 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IBiliBiliApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IBiliBiliApi.cs @@ -1,20 +1,19 @@ using Ray.BiliBiliTool.Agent.Attributes; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces -{ - [AppendHeader("Accept", "application/json, text/plain, */*", AppendHeaderType.AddIfNotExist)] - //[Header("Accept-Encoding", "gzip, deflate, br")] - [AppendHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", AppendHeaderType.AddIfNotExist)] +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +[AppendHeader("Accept", "application/json, text/plain, */*", AppendHeaderType.AddIfNotExist)] +//[Header("Accept-Encoding", "gzip, deflate, br")] +[AppendHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", AppendHeaderType.AddIfNotExist)] - [AppendHeader("Sec-Fetch-Dest", "empty", AppendHeaderType.AddIfNotExist)] - [AppendHeader("Sec-Fetch-Mode", "cors", AppendHeaderType.AddIfNotExist)] - [AppendHeader("Sec-Fetch-Site", "same-site", AppendHeaderType.AddIfNotExist)] +[AppendHeader("Sec-Fetch-Dest", "empty", AppendHeaderType.AddIfNotExist)] +[AppendHeader("Sec-Fetch-Mode", "cors", AppendHeaderType.AddIfNotExist)] +[AppendHeader("Sec-Fetch-Site", "same-site", AppendHeaderType.AddIfNotExist)] - [AppendHeader("Connection", "keep-alive", AppendHeaderType.AddIfNotExist)] +[AppendHeader("Connection", "keep-alive", AppendHeaderType.AddIfNotExist)] - [LogFilter] - public interface IBiliBiliApi - { - } +[LogFilter] +public interface IBiliBiliApi +{ } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IChargeApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IChargeApi.cs index 3a788381e..7673b63d6 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IChargeApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IChargeApi.cs @@ -4,53 +4,52 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 充电相关接口 +/// +[Header("Host", "api.bilibili.com")] +public interface IChargeApi : IBiliBiliApi { /// - /// 充电相关接口 + /// 充电 /// - [Header("Host", "api.bilibili.com")] - public interface IChargeApi : IBiliBiliApi - { - /// - /// 充电 - /// - /// 充电电池数量(B币*10),必须在20-99990之间 - /// 充电对象用户UID - /// 充电来源代码(空间充电:充电对象用户UID;视频充电:稿件avID) - /// - /// - [HttpPost("/x/ugcpay/trade/elec/pay/quick?elec_num={elec_num}&up_mid={up_mid}&otype=up&oid={oid}&csrf={csrf}")] - [Obsolete] - Task> Charge(int elec_num, string up_mid, string oid, string csrf); + /// 充电电池数量(B币*10),必须在20-99990之间 + /// 充电对象用户UID + /// 充电来源代码(空间充电:充电对象用户UID;视频充电:稿件avID) + /// + /// + [HttpPost("/x/ugcpay/trade/elec/pay/quick?elec_num={elec_num}&up_mid={up_mid}&otype=up&oid={oid}&csrf={csrf}")] + [Obsolete] + Task> Charge(int elec_num, string up_mid, string oid, string csrf); - /// - /// 充电V2 - /// - /// B币个数 - /// 对方Id - /// 对方来源代码(空间充电:充电对象用户UID;视频充电:稿件avID) - /// 自己的bili_jct - /// - [Header("Content-Type", "application/x-www-form-urlencoded")] - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/ugcpay/web/v2/trade/elec/pay/quick")] - Task> ChargeV2Async([FormContent] ChargeRequest request); + /// + /// 充电V2 + /// + /// B币个数 + /// 对方Id + /// 对方来源代码(空间充电:充电对象用户UID;视频充电:稿件avID) + /// 自己的bili_jct + /// + [Header("Content-Type", "application/x-www-form-urlencoded")] + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/ugcpay/web/v2/trade/elec/pay/quick")] + Task> ChargeV2Async([FormContent] ChargeRequest request); - /// - /// 充电后留言 - /// - /// - /// - /// - /// - /// - [Header("Content-Type", "application/x-www-form-urlencoded")] - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/ugcpay/trade/elec/message")] - Task> ChargeCommentAsync([FormContent] ChargeCommentRequest request); + /// + /// 充电后留言 + /// + /// + /// + /// + /// + /// + [Header("Content-Type", "application/x-www-form-urlencoded")] + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/ugcpay/trade/elec/message")] + Task> ChargeCommentAsync([FormContent] ChargeCommentRequest request); - } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IDailyTaskApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IDailyTaskApi.cs index 9a5f1ac1b..d685a19d5 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IDailyTaskApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IDailyTaskApi.cs @@ -3,39 +3,38 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// BiliBili每日任务相关接口 +/// +[Header("Host", "api.bilibili.com")] +public interface IDailyTaskApi : IBiliBiliApi { /// - /// BiliBili每日任务相关接口 + /// 获取每日任务的完成情况 /// - [Header("Host", "api.bilibili.com")] - public interface IDailyTaskApi : IBiliBiliApi - { - /// - /// 获取每日任务的完成情况 - /// - /// - [Header("Referer", "https://account.bilibili.com/account/home")] - [Header("Origin", "https://account.bilibili.com")] - [HttpGet("/x/member/web/exp/reward")] - Task> GetDailyTaskRewardInfoAsync(); + /// + [Header("Referer", "https://account.bilibili.com/account/home")] + [Header("Origin", "https://account.bilibili.com")] + [HttpGet("/x/member/web/exp/reward")] + Task> GetDailyTaskRewardInfoAsync(); - /// - /// 获取通过投币已获取的经验值 - /// - /// - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [HttpGet("/x/web-interface/coin/today/exp")] - Task> GetDonateCoinExpAsync(); + /// + /// 获取通过投币已获取的经验值 + /// + /// + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [HttpGet("/x/web-interface/coin/today/exp")] + Task> GetDonateCoinExpAsync(); - /// - /// 获取VIP特权 - /// - /// - /// - /// - [HttpPost("/x/vip/privilege/receive?type={type}&csrf={csrf}")] - Task ReceiveVipPrivilegeAsync(int type, string csrf); - } + /// + /// 获取VIP特权 + /// + /// + /// + /// + [HttpPost("/x/vip/privilege/receive?type={type}&csrf={csrf}")] + Task ReceiveVipPrivilegeAsync(int type, string csrf); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IHomeApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IHomeApi.cs index d4dea1694..9027019a6 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IHomeApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IHomeApi.cs @@ -5,14 +5,13 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 主站首页接口API +/// +public interface IHomeApi : IBiliBiliApi { - /// - /// 主站首页接口API - /// - public interface IHomeApi : IBiliBiliApi - { - [HttpGet("")] - Task GetHomePageAsync([Header("Cookie")] string ck); - } + [HttpGet("")] + Task GetHomePageAsync([Header("Cookie")] string ck); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs index 33b72cf6d..0db0ae863 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveApi.cs @@ -5,148 +5,147 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Live; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 直播相关接口 +/// +[Header("Host", "api.live.bilibili.com")] + +public interface ILiveApi : IBiliBiliApi { /// - /// 直播相关接口 + /// 直播签到 + /// + /// + [Header("Referer", "https://link.bilibili.com/")] + [Header("Origin", "https://link.bilibili.com")] + [HttpGet("/xlive/web-ucenter/v1/sign/DoSign")] + Task> Sign(); + + /// + /// 银瓜子兑换硬币 + /// + /// + [Header("Referer", "https://link.bilibili.com/")] + [Header("Origin", "https://link.bilibili.com")] + [Header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")] + [HttpGet("/pay/v1/Exchange/silver2coin")] + [Obsolete] + Task ExchangeSilver2Coin(); + + /// + /// 获取银瓜子余额 + /// + /// + [Header("Referer", "https://link.bilibili.com/")] + [Header("Origin", "https://link.bilibili.com")] + [HttpGet("/pay/v1/Exchange/getStatus")] + [Obsolete] + Task> GetExchangeSilverStatus(); + + /// + /// 银瓜子兑换硬币 + /// + /// + /// + //[Header("Referer", "https://link.bilibili.com/p/center/index?visit_id=1ddo4yl01q00")] + [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); + + /// + /// 获取直播中心钱包状态 + /// + /// + //[Header("Referer", "https://link.bilibili.com/p/center/index?visit_id=1ddo4yl01q00")] + [Header("Origin", "https://link.bilibili.com")] + [HttpGet("/xlive/revenue/v1/wallet/getStatus")] + Task> GetLiveWalletStatus(); + + [HttpGet("/xlive/web-interface/v1/index/getWebAreaList?source_id=2")] + Task> GetAreaList(); + + /// + /// 获取直播列表 + /// + /// + /// + /// + /// sort_type_124 + /// + [Header("Referer", "https://live.bilibili.com/")] + [Header("Origin", "https://live.bilibili.com")] + [HttpGet("/xlive/web-interface/v1/second/getList?platform=web&parent_area_id={parentAreaId}&area_id={areaId}&sort_type={sortType}&page={page}")] + Task> GetList(long parentAreaId, int page, int areaId = 0, string sortType = ""); + //todo:Cookie比nav接口多了两项:Hm_lvt_8a6e55dbd2870f0f5bc9194cddf32a02、Hm_lvt_9e2a88dc69e0e55c353597501d2a4bbc + + /// + /// 检查天选时刻抽奖 + /// + /// + /// + [Header("Referer", "https://live.bilibili.com/")] + [Header("Origin", "https://live.bilibili.com")] + [HttpGet("/xlive/lottery-interface/v1/Anchor/Check?roomid={roomId}")] + Task> CheckTianXuan(long roomId); + + /// + /// 参加天选时刻抽奖 + /// + /// + /// + [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(string 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(long roomId); + + /// + /// 请求直播主页用于配置直播相关 Cookie + /// + [HttpGet("/news/v1/notice/recom?product=live")] + Task GetLiveHome(); + + /// + /// 点赞直播间 /// - [Header("Host", "api.live.bilibili.com")] - - public interface ILiveApi : IBiliBiliApi - { - /// - /// 直播签到 - /// - /// - [Header("Referer", "https://link.bilibili.com/")] - [Header("Origin", "https://link.bilibili.com")] - [HttpGet("/xlive/web-ucenter/v1/sign/DoSign")] - Task> Sign(); - - /// - /// 银瓜子兑换硬币 - /// - /// - [Header("Referer", "https://link.bilibili.com/")] - [Header("Origin", "https://link.bilibili.com")] - [Header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")] - [HttpGet("/pay/v1/Exchange/silver2coin")] - [Obsolete] - Task ExchangeSilver2Coin(); - - /// - /// 获取银瓜子余额 - /// - /// - [Header("Referer", "https://link.bilibili.com/")] - [Header("Origin", "https://link.bilibili.com")] - [HttpGet("/pay/v1/Exchange/getStatus")] - [Obsolete] - Task> GetExchangeSilverStatus(); - - /// - /// 银瓜子兑换硬币 - /// - /// - /// - //[Header("Referer", "https://link.bilibili.com/p/center/index?visit_id=1ddo4yl01q00")] - [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); - - /// - /// 获取直播中心钱包状态 - /// - /// - //[Header("Referer", "https://link.bilibili.com/p/center/index?visit_id=1ddo4yl01q00")] - [Header("Origin", "https://link.bilibili.com")] - [HttpGet("/xlive/revenue/v1/wallet/getStatus")] - Task> GetLiveWalletStatus(); - - [HttpGet("/xlive/web-interface/v1/index/getWebAreaList?source_id=2")] - Task> GetAreaList(); - - /// - /// 获取直播列表 - /// - /// - /// - /// - /// sort_type_124 - /// - [Header("Referer", "https://live.bilibili.com/")] - [Header("Origin", "https://live.bilibili.com")] - [HttpGet("/xlive/web-interface/v1/second/getList?platform=web&parent_area_id={parentAreaId}&area_id={areaId}&sort_type={sortType}&page={page}")] - Task> GetList(long parentAreaId, int page, int areaId = 0, string sortType = ""); - //todo:Cookie比nav接口多了两项:Hm_lvt_8a6e55dbd2870f0f5bc9194cddf32a02、Hm_lvt_9e2a88dc69e0e55c353597501d2a4bbc - - /// - /// 检查天选时刻抽奖 - /// - /// - /// - [Header("Referer", "https://live.bilibili.com/")] - [Header("Origin", "https://live.bilibili.com")] - [HttpGet("/xlive/lottery-interface/v1/Anchor/Check?roomid={roomId}")] - Task> CheckTianXuan(long roomId); - - /// - /// 参加天选时刻抽奖 - /// - /// - /// - [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(string 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(long 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); - } + [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 index c5a36904c..90cb1a053 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/ILiveTraceApi.cs @@ -4,19 +4,18 @@ using System.Threading.Tasks; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +[Header("Host", "live-trace.bilibili.com")] +public interface ILiveTraceApi : IBiliBiliApi { - [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); + [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/E")] + Task> EnterRoom([FormContent] EnterRoomRequest request); - [HttpPost("/xlive/data-interface/v1/x25Kn/X")] - Task> HeartBeat([FormContent] HeartBeatRequest request); - } + [HttpPost("/xlive/data-interface/v1/x25Kn/X")] + Task> HeartBeat([FormContent] HeartBeatRequest request); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IMangaApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IMangaApi.cs index 7a851b2de..e86c3c0c2 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IMangaApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IMangaApi.cs @@ -3,38 +3,37 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 漫画相关接口 +/// +[Header("Origin", "https://manga.bilibili.com")] +[Header("Host", "manga.bilibili.com")] +public interface IMangaApi : IBiliBiliApi { /// - /// 漫画相关接口 + /// 漫画签到 /// - [Header("Origin", "https://manga.bilibili.com")] - [Header("Host", "manga.bilibili.com")] - public interface IMangaApi : IBiliBiliApi - { - /// - /// 漫画签到 - /// - /// - /// - [LogFilter(false)] - [HttpPost("/twirp/activity.v1.Activity/ClockIn?platform={platform}")] - Task ClockIn(string platform); + /// + /// + [LogFilter(false)] + [HttpPost("/twirp/activity.v1.Activity/ClockIn?platform={platform}")] + Task ClockIn(string platform); - /// - /// 漫画阅读 - /// - /// - /// - [HttpPost("/twirp/bookshelf.v1.Bookshelf/AddHistory?platform={platform}&comic_id={comic_id}&ep_id={ep_id}")] - Task ReadManga(string platform, long comic_id, long ep_id); + /// + /// 漫画阅读 + /// + /// + /// + [HttpPost("/twirp/bookshelf.v1.Bookshelf/AddHistory?platform={platform}&comic_id={comic_id}&ep_id={ep_id}")] + Task ReadManga(string platform, long comic_id, long ep_id); - /// - /// 获取会员漫画奖励 - /// - /// - /// - [HttpPost("/twirp/user.v1.User/GetVipReward?reason_id={reason_id}")] - Task> ReceiveMangaVipReward(int reason_id); - } + /// + /// 获取会员漫画奖励 + /// + /// + /// + [HttpPost("/twirp/user.v1.User/GetVipReward?reason_id={reason_id}")] + Task> ReceiveMangaVipReward(int reason_id); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IPassportApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IPassportApi.cs index f016705b1..f061ee0ba 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IPassportApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IPassportApi.cs @@ -4,19 +4,18 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Passport; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +[Header("Host", "passport.bilibili.com")] +public interface IPassportApi : IBiliBiliApi { - [Header("Host", "passport.bilibili.com")] - public interface IPassportApi : IBiliBiliApi - { - [HttpGet("/x/passport-login/web/qrcode/generate")] - Task> GenerateQrCode(); + [HttpGet("/x/passport-login/web/qrcode/generate")] + Task> GenerateQrCode(); - [HttpGet("/x/passport-login/web/qrcode/poll?qrcode_key={qrcode_key}&source=main_mini")] - //Task> CheckQrCodeHasScaned(string qrcode_key); - Task CheckQrCodeHasScaned(string qrcode_key); + [HttpGet("/x/passport-login/web/qrcode/poll?qrcode_key={qrcode_key}&source=main_mini")] + //Task> CheckQrCodeHasScaned(string qrcode_key); + Task CheckQrCodeHasScaned(string qrcode_key); - [HttpGet("/x/passport-login/web/sso/list?biliCSRF={csrf}")] - Task> GetSsoListAsync(string csrf); - } + [HttpGet("/x/passport-login/web/sso/list?biliCSRF={csrf}")] + Task> GetSsoListAsync(string csrf); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IRelationApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IRelationApi.cs index 6e306476d..d84b5c258 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IRelationApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IRelationApi.cs @@ -9,103 +9,102 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Relation; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 关注相关接口 +/// +[AppendHeader("Host", "api.bilibili.com", AppendHeaderType.AddIfNotExist)] +[AppendHeader("Referer", "https://space.bilibili.com/", AppendHeaderType.AddIfNotExist)] +public interface IRelationApi : IBiliBiliApi { /// - /// 关注相关接口 + /// 获取关注列表 /// - [AppendHeader("Host", "api.bilibili.com", AppendHeaderType.AddIfNotExist)] - [AppendHeader("Referer", "https://space.bilibili.com/", AppendHeaderType.AddIfNotExist)] - public interface IRelationApi : IBiliBiliApi - { - /// - /// 获取关注列表 - /// - /// - [HttpGet("/x/relation/followings")] - Task> GetFollowings(GetFollowingsRequest request); + /// + [HttpGet("/x/relation/followings")] + Task> GetFollowings(GetFollowingsRequest request); - /// - /// 获取特别关注列表 - /// - /// - [Header("Cache-Control", "no-cache")] - [Header("Pragma", "no-cache")] - [JsonReturn(EnsureMatchAcceptContentType = false)] - [HttpGet("/x/relation/tag")] - Task>> GetFollowingsByTag(GetSpecialFollowingsRequest request); + /// + /// 获取特别关注列表 + /// + /// + [Header("Cache-Control", "no-cache")] + [Header("Pragma", "no-cache")] + [JsonReturn(EnsureMatchAcceptContentType = false)] + [HttpGet("/x/relation/tag")] + Task>> GetFollowingsByTag(GetSpecialFollowingsRequest request); - /// - /// 获取关注分组 - /// - /// - [AppendHeader("Sec-Fetch-Mode", "no-cors")] - [AppendHeader("Sec-Fetch-Dest", "script")] - [HttpGet("/x/relation/tags?jsonp=jsonp")] - Task>> GetTags([AppendHeader("Referer")] string referer = RelationApiConstant.GetTagsReferer); + /// + /// 获取关注分组 + /// + /// + [AppendHeader("Sec-Fetch-Mode", "no-cors")] + [AppendHeader("Sec-Fetch-Dest", "script")] + [HttpGet("/x/relation/tags?jsonp=jsonp")] + Task>> GetTags([AppendHeader("Referer")] string referer = RelationApiConstant.GetTagsReferer); - /// - /// 添加关注分组(tag) - /// - /// - /// - [AppendHeader("Origin", "https://space.bilibili.com")] - [HttpPost("/x/relation/tag/create?cross_domain=true")] - Task> CreateTag([FormContent] CreateTagRequest request, - [AppendHeader("Referer")] string referer = RelationApiConstant.GetTagsReferer); + /// + /// 添加关注分组(tag) + /// + /// + /// + [AppendHeader("Origin", "https://space.bilibili.com")] + [HttpPost("/x/relation/tag/create?cross_domain=true")] + Task> CreateTag([FormContent] CreateTagRequest request, + [AppendHeader("Referer")] string referer = RelationApiConstant.GetTagsReferer); - /// - /// 批量拷贝关注up到某指定分组 - /// - /// - /// - [AppendHeader("Origin", "https://space.bilibili.com")] - [HttpPost("/x/relation/tags/copyUsers")] - Task CopyUpsToGroup([FormContent] CopyUserToGroupRequest request, - [AppendHeader("Referer")] string referer = RelationApiConstant.CopyReferer); + /// + /// 批量拷贝关注up到某指定分组 + /// + /// + /// + [AppendHeader("Origin", "https://space.bilibili.com")] + [HttpPost("/x/relation/tags/copyUsers")] + Task CopyUpsToGroup([FormContent] CopyUserToGroupRequest request, + [AppendHeader("Referer")] string referer = RelationApiConstant.CopyReferer); - /// - /// 修改关系 - /// - /// - [AppendHeader("Origin", "https://space.bilibili.com")] - [HttpPost("/x/relation/modify")] - Task ModifyRelation([FormContent] ModifyRelationRequest request, - [AppendHeader("Referer")] string referer = RelationApiConstant.ModifyReferer); - } + /// + /// 修改关系 + /// + /// + [AppendHeader("Origin", "https://space.bilibili.com")] + [HttpPost("/x/relation/modify")] + Task ModifyRelation([FormContent] ModifyRelationRequest request, + [AppendHeader("Referer")] string referer = RelationApiConstant.ModifyReferer); +} - public enum FollowingsOrderType - { - /// - /// 最常访问频率倒序 - /// - [DefaultValue("attention")] - AttentionDesc, +public enum FollowingsOrderType +{ + /// + /// 最常访问频率倒序 + /// + [DefaultValue("attention")] + AttentionDesc, - /// - /// 关注时间倒序 - /// - [DefaultValue("")] - TimeDesc - } + /// + /// 关注时间倒序 + /// + [DefaultValue("")] + TimeDesc +} - public class RelationApiConstant - { - /// - /// GetTags接口中的Referer - /// {0}为UserId - /// - public const string GetTagsReferer = "https://space.bilibili.com/{0}/fans/follow"; +public class RelationApiConstant +{ + /// + /// GetTags接口中的Referer + /// {0}为UserId + /// + public const string GetTagsReferer = "https://space.bilibili.com/{0}/fans/follow"; - /// - /// CopyUpsToGroup接口中的Referer - /// {0}为UserId - /// - public const string CopyReferer = "https://space.bilibili.com/{0}/fans/follow?tagid=-1"; + /// + /// CopyUpsToGroup接口中的Referer + /// {0}为UserId + /// + public const string CopyReferer = "https://space.bilibili.com/{0}/fans/follow?tagid=-1"; - /// - /// ModifyRelation接口种的Referer - /// - public const string ModifyReferer = "https://space.bilibili.com/{0}/fans/follow?tagid={1}"; - } + /// + /// ModifyRelation接口种的Referer + /// + public const string ModifyReferer = "https://space.bilibili.com/{0}/fans/follow?tagid={1}"; } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs index 11e717f02..53c6a24d8 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IUserInfoApi.cs @@ -4,29 +4,28 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 用户信息接口API +/// +[Header("Referer", "https://www.bilibili.com/")] +[Header("Origin", "https://www.bilibili.com")] +[Header("Host", "api.bilibili.com")] +public interface IUserInfoApi : IBiliBiliApi { /// - /// 用户信息接口API + /// 登录 /// - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [Header("Host", "api.bilibili.com")] - public interface IUserInfoApi : IBiliBiliApi - { - /// - /// 登录 - /// - /// - [HttpGet("/x/web-interface/nav")] - Task> LoginByCookie(); + /// + [HttpGet("/x/web-interface/nav")] + Task> LoginByCookie(); - /// - /// 获取用户空间信息 - /// - /// uid - /// - [HttpGet("/x/space/wbi/acc/info")] - Task> GetSpaceInfo([PathQuery] GetSpaceInfoDto request); - } + /// + /// 获取用户空间信息 + /// + /// uid + /// + [HttpGet("/x/space/wbi/acc/info")] + Task> GetSpaceInfo([PathQuery] GetSpaceInfoDto request); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVideoApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVideoApi.cs index 7a21a4215..ba266805b 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVideoApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVideoApi.cs @@ -5,120 +5,119 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.Video; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 视频相关接口 +/// +[Header("Host", "api.bilibili.com")] +public interface IVideoApi : IBiliBiliApi { /// - /// 视频相关接口 + /// 分享视频 /// - [Header("Host", "api.bilibili.com")] - public interface IVideoApi : IBiliBiliApi - { - /// - /// 分享视频 - /// - /// - /// ck中必须要有buvid3,否则几率性-403 - /// - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/web-interface/share/add")] - Task ShareVideo([FormContent] ShareVideoRequest request); + /// + /// ck中必须要有buvid3,否则几率性-403 + /// + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/web-interface/share/add")] + Task ShareVideo([FormContent] ShareVideoRequest request); - /// - /// 上传视频观看进度 - /// 每15秒上报一次 - /// - /// - //[Header("Content-Length", "186")] - [Header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")] - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/click-interface/web/heartbeat?aid={aid}&played_time={playedTime}")] - Task UploadVideoHeartbeat([FormContent] UploadVideoHeartbeatRequest request); + /// + /// 上传视频观看进度 + /// 每15秒上报一次 + /// + /// + //[Header("Content-Length", "186")] + [Header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")] + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/click-interface/web/heartbeat?aid={aid}&played_time={playedTime}")] + Task UploadVideoHeartbeat([FormContent] UploadVideoHeartbeatRequest request); - #region 投币相关 - /// - /// 为视频投币 - /// - /// - /// - /// - /// - /// - [Header("Content-Type", "application/x-www-form-urlencoded")] - //[Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [HttpPost("/x/web-interface/coin/add")] - Task AddCoinForVideo([FormContent] AddCoinRequest request,[Header("referer")]string refer= "https://www.bilibili.com/video/BV123456/?spm_id_from=333.1007.tianma.1-1-1.click&vd_source=80c1601a7003934e7a90709c18dfcffd"); + #region 投币相关 + /// + /// 为视频投币 + /// + /// + /// + /// + /// + /// + [Header("Content-Type", "application/x-www-form-urlencoded")] + //[Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [HttpPost("/x/web-interface/coin/add")] + Task AddCoinForVideo([FormContent] AddCoinRequest request,[Header("referer")]string refer= "https://www.bilibili.com/video/BV123456/?spm_id_from=333.1007.tianma.1-1-1.click&vd_source=80c1601a7003934e7a90709c18dfcffd"); - /// - /// 获取当前用户对视频的投币信息 - /// - /// - /// - [Header("Referer", "https://www.bilibili.com/")] - [HttpGet("/x/web-interface/archive/coins")] - Task> GetDonatedCoinsForVideo(GetAlreadyDonatedCoinsRequest request); - #endregion - - /// - /// 搜索指定Up的视频列表 - /// - /// - /// [1,100]验证不通过接口会报异常 - /// - /// - /// - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://space.bilibili.com")] - //[HttpGet("/x/space/wbi/arc/search?mid={upId}&ps={pageSize}&tid=0&pn={pageNumber}&keyword={keyword}&order=pubdate&platform=web&web_location=1550101&order_avoided=true&w_rid=5df06b1c48e2be86a96e9d0f99bf06f4&wts=1684854929")] - [HttpGet("/x/space/wbi/arc/search")] - Task> SearchVideosByUpId([PathQuery] SearchVideosByUpIdDto request); + /// + /// 获取当前用户对视频的投币信息 + /// + /// + /// + [Header("Referer", "https://www.bilibili.com/")] + [HttpGet("/x/web-interface/archive/coins")] + Task> GetDonatedCoinsForVideo(GetAlreadyDonatedCoinsRequest request); + #endregion + + /// + /// 搜索指定Up的视频列表 + /// + /// + /// [1,100]验证不通过接口会报异常 + /// + /// + /// + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://space.bilibili.com")] + //[HttpGet("/x/space/wbi/arc/search?mid={upId}&ps={pageSize}&tid=0&pn={pageNumber}&keyword={keyword}&order=pubdate&platform=web&web_location=1550101&order_avoided=true&w_rid=5df06b1c48e2be86a96e9d0f99bf06f4&wts=1684854929")] + [HttpGet("/x/space/wbi/arc/search")] + Task> SearchVideosByUpId([PathQuery] SearchVideosByUpIdDto request); - /// - /// 通过ssid获取番剧的具体信息 - /// - /// - /// - [HttpGet("/pgc/view/web/season?season_id={ssid}")] - Task GetBangumiBySsid(long ssid); + /// + /// 通过ssid获取番剧的具体信息 + /// + /// + /// + [HttpGet("/pgc/view/web/season?season_id={ssid}")] + Task GetBangumiBySsid(long ssid); - } +} +/// +/// 不需要传递Cookie的接口 +/// +public interface IVideoWithoutCookieApi : IVideoApi +{ /// - /// 不需要传递Cookie的接口 + /// 获取视频详情 /// - public interface IVideoWithoutCookieApi : IVideoApi - { - /// - /// 获取视频详情 - /// - /// - /// - [HttpGet("/x/web-interface/view?aid={aid}")] - Task> GetVideoDetail(string aid); + /// + /// + [HttpGet("/x/web-interface/view?aid={aid}")] + Task> GetVideoDetail(string aid); - /// - /// 获取某分区下X日内排行榜 - /// - /// - /// - /// - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [HttpGet("/x/web-interface/ranking/region?rid={rid}&day={day}")] - [Obsolete] - Task>> GetRegionRankingVideos(int rid, int day); + /// + /// 获取某分区下X日内排行榜 + /// + /// + /// + /// + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [HttpGet("/x/web-interface/ranking/region?rid={rid}&day={day}")] + [Obsolete] + Task>> GetRegionRankingVideos(int rid, int day); - /// - /// 获取排行榜 - /// - /// - [Header("Referer", "https://www.bilibili.com/")] - [Header("Origin", "https://www.bilibili.com")] - [Header("dnt", "1")] - [HttpGet("/x/web-interface/ranking/v2?rid=0&type=all")] - Task> GetRegionRankingVideosV2(); + /// + /// 获取排行榜 + /// + /// + [Header("Referer", "https://www.bilibili.com/")] + [Header("Origin", "https://www.bilibili.com")] + [Header("dnt", "1")] + [HttpGet("/x/web-interface/ranking/v2?rid=0&type=all")] + Task> GetRegionRankingVideosV2(); - } } diff --git a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs index 223fee0be..8199b316b 100644 --- a/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs +++ b/src/Ray.BiliBiliTool.Agent/BiliBiliAgent/Interfaces/IVipBigPointApi.cs @@ -4,71 +4,61 @@ using Ray.BiliBiliTool.Agent.BiliBiliAgent.Dtos.VipTask; using WebApiClientCore.Attributes; -namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces +namespace Ray.BiliBiliTool.Agent.BiliBiliAgent.Interfaces; + +/// +/// 大会员大积分 +/// +[Header("Host", "api.bilibili.com")] +[Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] +[LogFilter] +public interface IVipBigPointApi { /// - /// 大会员大积分 + /// 获取任务列表 /// - [Header("Host", "api.bilibili.com")] - [Header("User-Agent", "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 os/android model/SM-S9080 build/7760700 osVer/12 sdkInt/32 network/2 BiliApp/7760700 mobi_app/android channel/bili innerVer/7760710 c_locale/zh_CN s_locale/zh_CN disable_rcmd/0 7.76.0 os/android model/SM-S9080 mobi_app/android build/7760700 channel/bili innerVer/7760710 osVer/12 network/2")] - [LogFilter] - public interface IVipBigPointApi - { - /// - /// 获取任务列表 - /// - /// - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpGet("/x/vip_point/task/combine")] - Task> GetTaskListAsync(); + /// + [HttpGet("/x/vip_point/task/combine")] + Task> GetTaskListAsync(); - /// - /// 签到任务 - /// - /// - /// - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/pgc/activity/score/task/sign")] - Task SignAsync([FormContent] SignRequest request); + /// + /// 签到任务 + /// + /// + /// + [HttpPost("/pgc/activity/score/task/sign")] + Task SignAsync([FormContent] SignRequest request); - /// - /// 领取任务 - /// - /// - /// - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/pgc/activity/score/task/receive")] - Task Receive([JsonContent] ReceiveOrCompleteTaskRequest request); + /// + /// 领取任务 + /// + /// + /// + [HttpPost("/pgc/activity/score/task/receive")] + Task Receive([JsonContent] ReceiveOrCompleteTaskRequest request); - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/pgc/activity/score/task/receive/v2")] - Task ReceiveV2([FormContent] ReceiveOrCompleteTaskRequest request); + [HttpPost("/pgc/activity/score/task/receive/v2")] + Task ReceiveV2([FormContent] ReceiveOrCompleteTaskRequest request); - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/pgc/activity/score/task/complete")] - Task CompleteAsync([JsonContent] ReceiveOrCompleteTaskRequest request); + [HttpPost("/pgc/activity/score/task/complete")] + Task CompleteAsync([JsonContent] ReceiveOrCompleteTaskRequest request); - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/pgc/activity/score/task/complete/v2")] - Task CompleteV2([FormContent] ReceiveOrCompleteTaskRequest request); + [HttpPost("/pgc/activity/score/task/complete/v2")] + Task CompleteV2([FormContent] ReceiveOrCompleteTaskRequest request); - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/pgc/activity/deliver/task/complete")] - Task ViewComplete([FormContent] ViewRequest request); + [HttpPost("/pgc/activity/deliver/task/complete")] + Task ViewComplete([FormContent] ViewRequest request); - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpGet("/x/vip/privilege/my")] - Task> GetVouchersInfoAsync(); + [HttpGet("/x/vip/privilege/my")] + Task> GetVouchersInfoAsync(); - /// - /// 兑换大会员经验 - /// - /// - /// - [Header("Referer", "https://big.bilibili.com/mobile/bigPoint/task")] - [HttpPost("/x/vip/experience/add")] - Task ObtainVipExperienceAsync([FormContent] VipExperienceRequest request); - } + /// + /// 兑换大会员经验 + /// + /// + /// + [HttpPost("/x/vip/experience/add")] + Task ObtainVipExperienceAsync([FormContent] VipExperienceRequest request); } diff --git a/src/Ray.BiliBiliTool.Agent/BiliHosts.cs b/src/Ray.BiliBiliTool.Agent/BiliHosts.cs new file mode 100644 index 000000000..e70d8d275 --- /dev/null +++ b/src/Ray.BiliBiliTool.Agent/BiliHosts.cs @@ -0,0 +1,13 @@ +namespace Ray.BiliBiliTool.Agent; + +public static class BiliHosts +{ + public const string Api = "https://api.bilibili.com"; + public const string Show = "https://show.bilibili.com"; + public const string Passport = "http://passport.bilibili.com"; + public const string LiveTrace = "https://live-trace.bilibili.com"; + public const string Www = "https://www.bilibili.com"; + public const string Manga = "https://manga.bilibili.com"; + public const string Account = "https://account.bilibili.com"; + public const string Live = "https://api.live.bilibili.com"; +} diff --git a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs index 4c21ccb53..f0222671b 100644 --- a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs +++ b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs @@ -60,24 +60,34 @@ public static IServiceCollection AddBiliBiliClientApi(this IServiceCollection se //服务 services.AddScoped(); - //bilibli - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("https://manga.bilibili.com"); - services.AddBiliBiliClientApi("https://account.bilibili.com"); - services.AddBiliBiliClientApi("https://api.live.bilibili.com"); - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("https://api.bilibili.com", false); - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("http://passport.bilibili.com", false); - services.AddBiliBiliClientApi("https://live-trace.bilibili.com"); - services.AddBiliBiliClientApi("https://www.bilibili.com", false); - - // 添加注入 - services.AddBiliBiliClientApi("https://api.bilibili.com"); - services.AddBiliBiliClientApi("https://show.bilibili.com"); + //bilibli + Action config = (sp, c) => { + c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgent); + c.DefaultRequestHeaders.Add("Cookie", sp.GetRequiredService().ToString()); + }; + Action configApp = (sp, c) => { + c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgentApp); + c.DefaultRequestHeaders.Add("Cookie", sp.GetRequiredService().ToString()); + }; + + services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); + + services.AddBiliBiliClientApi(BiliHosts.Show, config); + services.AddBiliBiliClientApi(BiliHosts.Passport, config); + services.AddBiliBiliClientApi(BiliHosts.LiveTrace,config); + services.AddBiliBiliClientApi(BiliHosts.Www, config); + services.AddBiliBiliClientApi(BiliHosts.Manga, config); + services.AddBiliBiliClientApi(BiliHosts.Account, config); + services.AddBiliBiliClientApi(BiliHosts.Live, config); + + services.AddBiliBiliClientApi(BiliHosts.Api, configApp); + //qinglong var qinglongHost = configuration["QL_URL"] ?? "http://localhost:5600"; @@ -89,8 +99,7 @@ public static IServiceCollection AddBiliBiliClientApi(this IServiceCollection se }) .ConfigureHttpClient((sp, c) => { - c.DefaultRequestHeaders.Add("User-Agent", - sp.GetRequiredService>().CurrentValue.UserAgent); + c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgent); }) .AddPolicyHandler(GetRetryPolicy()); @@ -104,7 +113,7 @@ public static IServiceCollection AddBiliBiliClientApi(this IServiceCollection se /// /// /// - private static IServiceCollection AddBiliBiliClientApi(this IServiceCollection services, string host, bool withCookie = true) + private static IServiceCollection AddBiliBiliClientApi(this IServiceCollection services, string host, Action config) where TInterface : class { var uri = new Uri(host); @@ -114,21 +123,10 @@ private static IServiceCollection AddBiliBiliClientApi(this IService o.HttpHost = uri; o.UseDefaultUserAgent = false; }) - .ConfigureHttpClient((sp, c) => - { - c.DefaultRequestHeaders.Add("User-Agent", - sp.GetRequiredService>().CurrentValue.UserAgent); - }) + .ConfigureHttpClient(config) .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()); - if (withCookie) - httpClientBuilder.ConfigureHttpClient((sp, c) => - { - var ck = sp.GetRequiredService(); - c.DefaultRequestHeaders.Add("Cookie", ck.ToString()); - }); - return services; } diff --git a/src/Ray.BiliBiliTool.Config/Options/SecurityOptions.cs b/src/Ray.BiliBiliTool.Config/Options/SecurityOptions.cs index 389bbb83d..912d3518e 100644 --- a/src/Ray.BiliBiliTool.Config/Options/SecurityOptions.cs +++ b/src/Ray.BiliBiliTool.Config/Options/SecurityOptions.cs @@ -58,6 +58,11 @@ public List GetIntervalMethods() /// 请求B站接口时头部传递的User-Agent /// public string UserAgent { get; set; } = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 Edg/87.0.664.41"; + + /// + /// App请求B站接口时头部传递的User-Agent + /// + public string UserAgentApp { get; set; } = "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 os/android model/SM-S9080 build/7760700 osVer/12 sdkInt/32 network/2 BiliApp/7760700 mobi_app/android channel/bili innerVer/7760710 c_locale/zh_CN s_locale/zh_CN disable_rcmd/0 7.76.0 os/android model/SM-S9080 mobi_app/android build/7760700 channel/bili innerVer/7760710 osVer/12 network/2"; /// /// 代理 diff --git a/src/Ray.BiliBiliTool.Console/appsettings.json b/src/Ray.BiliBiliTool.Console/appsettings.json index a32a6fb5f..9474d8d46 100644 --- a/src/Ray.BiliBiliTool.Console/appsettings.json +++ b/src/Ray.BiliBiliTool.Console/appsettings.json @@ -57,6 +57,7 @@ "IntervalSecondsBetweenRequestApi": 20, //两次调用api之间的间隔[0,+](单位为秒)。因为有人担心在几秒内连续调用api会被b站安全机制发现,所以为不放心的朋友添加了间隔秒数配置,两次连续调用Api之间会大于该秒数 "IntervalMethodTypes": "GET,POST", //间隔秒数所针对的HttpMethod,多个用英文逗号隔开,当前有GET和POST两种,可配置如“GET,POST” "UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0", //请求B站接口时头部传递的User-Agent + "UserAgentApp": "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 os/android model/SM-S9080 build/7760700 osVer/12 sdkInt/32 network/2 BiliApp/7760700 mobi_app/android channel/bili innerVer/7760710 c_locale/zh_CN s_locale/zh_CN disable_rcmd/0 7.76.0 os/android model/SM-S9080 mobi_app/android build/7760700 channel/bili innerVer/7760710 osVer/12 network/2", //App请求B站接口时头部传递的User-Agent "WebProxy": "" //代理,格式为http://host:port,如果有鉴权则为user:password@http://host:port }, diff --git a/test/Ray.BiliBiliTool.Agent.FunctionalTests/ArticleApiTests.cs b/test/Ray.BiliBiliTool.Agent.FunctionalTests/ArticleApiTests.cs index 6ef931f55..6f72ea60a 100644 --- a/test/Ray.BiliBiliTool.Agent.FunctionalTests/ArticleApiTests.cs +++ b/test/Ray.BiliBiliTool.Agent.FunctionalTests/ArticleApiTests.cs @@ -135,10 +135,10 @@ public async Task LikeAsync_AlreadyLike_GetResultSuccess() var re = await _api.LikeAsync(cvid, _ck.BiliJct); // Assert - re.Code.Should().BeOneOf(new List - { - 0, - 65006, //已赞过 + re.Code.Should().BeOneOf(new List + { + 0, + 65006, //已赞过 }); } diff --git a/test/Ray.BiliBiliTool.Agent.FunctionalTests/VipBigPointApiTest.cs b/test/Ray.BiliBiliTool.Agent.FunctionalTests/VipBigPointApiTest.cs index a9107751d..8c2092afa 100644 --- a/test/Ray.BiliBiliTool.Agent.FunctionalTests/VipBigPointApiTest.cs +++ b/test/Ray.BiliBiliTool.Agent.FunctionalTests/VipBigPointApiTest.cs @@ -88,10 +88,11 @@ public async Task GetVipExperienceAsync_Normal_Success() BiliApiResponse re = await _api.ObtainVipExperienceAsync(req); // Assert - re.Code.Should().BeOneOf(new List - { - 0, - 69198, //用户经验已经领取 + re.Code.Should().BeOneOf(new List + { + 0, + 6034005, //任务未完成 + 69198, //用户经验已经领取 }); } From 1489dc6b2e3316ff5ba59be660affcf3d19a818f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9C=A87=E6=A5=BC?= <31154238+RayWangQvQ@users.noreply.github.com> Date: Thu, 2 May 2024 16:28:37 +0800 Subject: [PATCH 2/7] fix release script (#704) * fix: release scripts missing content * update chagelog --- .github/workflows/publish-image.yml | 2 +- .github/workflows/publish-release.yml | 4 ++-- CHANGELOG.md | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index b9716b380..a31f8c10f 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -12,7 +12,7 @@ on: default: true type: boolean release: - types: [published] + types: [created] env: DOCKERHUB_USERNAME : ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index a8fef3334..bcfae54d4 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -32,9 +32,9 @@ jobs: - name: Extract Release Notes id: release_notes run: | - content=$(grep -m1 "##" -A 1000 ./CHANGELOG.md) + content=$(sed -n '/^## /{p;:a;n;/^## /q;p;ba}' CHANGELOG.md) version=$(echo "$GITHUB_REF" | sed 's/refs\/tags\///') - echo "::set-output name=content::$content" + echo "::set-output name=content::$content" echo "::set-output name=version::$version" - name: Create Release diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c14e7565..742414804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 2.0.6 - Feature[#670]: 新增针对App的AppUserAgent配置项 +- Fix: 修复CICD发布脚本错误 ## 2.0.5 - Fix[#260]: 再次尝试修复大会员大积分“账号风险”异常 ## 2.0.4 From cc11f25f0d89f0daa872e57ceefdc25b56cffe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9C=A87=E6=A5=BC?= <31154238+RayWangQvQ@users.noreply.github.com> Date: Sun, 5 May 2024 19:28:03 +0800 Subject: [PATCH 3/7] Featuer[#691]: enhancement deployment in qinglong (#706) * update installation of qinglong * update dev branch * feat:[#691] fix and format codes * feat:[#691]update doc * format codes * chore: update changelog --- CHANGELOG.md | 5 +- common.props | 2 +- docs/imgs/qinglong-run-as-bilitool.png | Bin 0 -> 64687 bytes qinglong/DefaultTasks/bili_task_base.sh | 480 +++++++++++++++-- qinglong/DefaultTasks/bili_task_daily.sh | 6 +- .../DefaultTasks/bili_task_liveFansMedal.sh | 6 +- .../DefaultTasks/bili_task_liveLottery.sh | 6 +- qinglong/DefaultTasks/bili_task_login.sh | 6 +- qinglong/DefaultTasks/bili_task_test.sh | 6 +- .../DefaultTasks/bili_task_unfollowBatched.sh | 6 +- .../DefaultTasks/bili_task_vipBigPoint.sh | 6 +- .../DefaultTasks/dev/bili_dev_task_base.sh | 484 ++++++++++++++++-- .../DefaultTasks/dev/bili_dev_task_daily.sh | 6 +- .../dev/bili_dev_task_liveFansMedal.sh | 6 +- .../dev/bili_dev_task_liveLottery.sh | 6 +- .../DefaultTasks/dev/bili_dev_task_login.sh | 6 +- .../DefaultTasks/dev/bili_dev_task_test.sh | 6 +- .../dev/bili_dev_task_unfollowBatched.sh | 6 +- .../dev/bili_dev_task_vipBigPoint.sh | 6 +- qinglong/README.md | 67 ++- qinglong/dotnet-install.sh | 1 + qinglong/ray-dotnet-install.sh | 77 +-- scripts/publish.sh | 2 +- 23 files changed, 977 insertions(+), 225 deletions(-) create mode 100644 docs/imgs/qinglong-run-as-bilitool.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 742414804..fcb312047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -## 2.0.6 -- Feature[#670]: 新增针对App的AppUserAgent配置项 +## 2.1.0 +- Feature[#691]: 重构并优化基于qinglong的部署方式,尝试解决偶发的安装失败的问题 +- Feature[#670]: 新增针对App的AppUserAgent配置项,用于解决大会员大积分异常问题 - Fix: 修复CICD发布脚本错误 ## 2.0.5 - Fix[#260]: 再次尝试修复大会员大积分“账号风险”异常 diff --git a/common.props b/common.props index 438fbfc63..6f3db6b1e 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ Ray - 2.0.6 + 2.1.0 $(NoWarn);CS1591;CS0436 diff --git a/docs/imgs/qinglong-run-as-bilitool.png b/docs/imgs/qinglong-run-as-bilitool.png new file mode 100644 index 0000000000000000000000000000000000000000..6b7508aec4a0d97317963fd4097164c06760215c GIT binary patch literal 64687 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 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ] && command -v tput >/dev/null; then + # see if it supports colors + ncolors=$(tput colors || echo 0) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_warning() { + printf "%b\n" "${yellow:-}bilitool: Warning: $1${normal:-}" >&3 +} + +say_err() { + printf "%b\n" "${red:-}bilitool: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}bilitool:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +QL_DIR=${QL_DIR:-"/ql"} +QL_BRANCH=${QL_BRANCH:-"develop"} +DefaultCronRule=${DefaultCronRule:-""} +CpuWarn=${CpuWarn:-""} +MemoryWarn=${MemoryWarn:-""} +DiskWarn=${DiskWarn:-""} +dir_repo=${dir_repo:-"$QL_DIR/data/repo"} + +dir_shell=$QL_DIR/shell +. $dir_shell/env.sh +touch /root/.bashrc . /root/.bashrc -## 安装dotnet(如果未安装过) -dotnetVersion=$(dotnet --version) -if [[ $dotnetVersion == 6.* ]]; then - echo "已安装dotnet,当前版本:$dotnetVersion" -else - echo "which dotnet: $(which dotnet)" - echo "开始安装dotnet" - rayInstallShell="https://ghproxy.com/https://raw.githubusercontent.com/RayWangQvQ/BiliBiliToolPro/main/qinglong/ray-dotnet-install.sh" - { - echo "------尝试使用apk安装------" +# 目录 +say "青龙repo目录: $dir_repo" +qinglong_bili_repo="$(echo "$bili_repo" | sed 's/\//_/g')${bili_branch}" +qinglong_bili_repo_dir="$(find $dir_repo -type d -iname $qinglong_bili_repo | head -1)" +say "bili仓库目录: $qinglong_bili_repo_dir" + +current_linux_os="debian" # 或alpine +current_os="linux" # 或linux-musl +machine_architecture="x64" # 或arm、arm64 +dotnet_installed=false +bilitool_installed=false +bilitool_installed_version=0 + +# 以下操作仅在bilitool仓库的根bin文件下执行 +cd $qinglong_bili_repo_dir +mkdir -p bin +cd $qinglong_bili_repo_dir/bin + +# 判断是否存在某指令 +machine_has() { + eval $invocation + + command -v "$1" >/dev/null 2>&1 + return $? +} + +# 判断系统架构 +# 输出:arm、arm64、x64 +get_machine_architecture() { + eval $invocation + + if command -v uname >/dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv*l) + echo "arm" + return 0 + ;; + aarch64 | arm64) + echo "arm64" + return 0 + ;; + esac + fi + + # Always default to 'x64' + echo "x64" + return 0 +} + +# 获取linux系统名称 +# 输出:debian.10、debian.11、debian.12、ubuntu.20.04、ubuntu.22.04、alpine.3.4.3... +get_linux_platform_name() { + eval $invocation + + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID${VERSION_ID:+.${VERSION_ID}}" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(&1 || true) | grep -q musl +} + +# 获取当前系统名称 +# 输出:linux、linux-musl、osx、freebsd +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + say_warning "当前系统:osx" + echo "osx" + return 1 + elif [ "$uname" = "FreeBSD" ]; then + say_warning "当前系统:freebsd" + echo "freebsd" + return 1 + elif [ "$uname" = "Linux" ]; then + local linux_platform_name="" + linux_platform_name="$(get_linux_platform_name)" || true + say "当前系统发行版本:$linux_platform_name" + + if [ "$linux_platform_name" = "rhel.6" ]; then + echo $linux_platform_name + return 1 + elif is_musl_based_distro; then + echo "linux-musl" + return 0 + elif [ "$linux_platform_name" = "linux-musl" ]; then + echo "linux-musl" + return 0 + else + echo "linux" + return 0 + fi + fi + + say_err "OS name could not be detected: UName = $uname" + return 1 +} + +check_os() { + eval $invocation + + # 获取系统信息 + current_os="$(get_current_os_name)" + say "当前系统:$current_os" + machine_architecture="$(get_machine_architecture)" + say "当前架构:$machine_architecture" + + if [ "$current_os" = "linux" ]; then + current_linux_os="debian" # 当前青龙只有debian和aplpine两种 + if ! machine_has curl; then + say "curl未安装,开始安装依赖..." + apt-get update + apt-get install -y curl + fi + else + current_linux_os="alpine" + if ! machine_has curl; then + say "curl未安装,开始安装依赖..." + apk update + apk add -y curl + fi + fi + + if [ -f "./Ray.BiliBiliTool.Console" ]; then + prefer_mode="bilitool" + fi + say "当前选择的运行方式:$prefer_mode" +} + +check_jq() { + if [ "$current_linux_os" = "debian" ]; then + if ! machine_has jq; then + say "jq未安装,开始安装依赖..." + apt-get update + apt-get install -y jq + fi + else + if ! machine_has jq; then + say "jq未安装,开始安装依赖..." + apk update + apk add -y jq + fi + fi +} + +check_unzip() { + if [ "$current_linux_os" = "debian" ]; then + if ! machine_has unzip; then + say "unzip未安装,开始安装依赖..." + apt-get update + apt-get install -y unzip + fi + else + if ! machine_has unzip; then + say "jq未安装,开始安装依赖..." + apk update + apk add -y unzip + fi + fi +} + +# 检查dotnet +check_dotnet() { + eval $invocation + + dotnetVersion=$(dotnet --version) + if [[ $dotnetVersion == 6.* ]]; then + say "已安装dotnet,当前版本:$dotnetVersion" + say "which dotnet: $(which dotnet)" + return 0 + else + say "未安装" + return 1 + fi +} + +# 检查bilitool +check_bilitool() { + eval $invocation + + TAG_FILE="./tag.txt" + touch $TAG_FILE + local STORED_TAG=$(cat $TAG_FILE 2>/dev/null) + + #如果STORED_TAG为空,则返回1 + if [[ -z $STORED_TAG ]]; then + say "tag.txt为空,未安装过" + return 1 + fi + + say "tag.txt记录的版本:$STORED_TAG" + + # 查找当前目录下是否有叫Ray.BiliBiliTool.Console的文件 + if [ -f "./Ray.BiliBiliTool.Console" ]; then + say "bilitool已安装" + bilitool_installed_version=$STORED_TAG + return 0 + else + say "bilitool未安装" + return 1 + fi +} + +# 检查环境 +check() { + eval $invocation + + if [ "$prefer_mode" == "dotnet" ]; then + if check_dotnet; then + dotnet_installed=true + return 0 + else + dotnet_installed=true + return 1 + fi + fi + + if [ "$prefer_mode" == "bilitool" ]; then + if check_bilitool; then + bilitool_installed=true + return 0 + else + bilitool_installed=false + return 1 + fi + fi + + return 1 +} + +# 安装dotnet环境 +install_dotnet() { + eval $invocation + + say "开始安装dotnet" + say "当前系统:$current_linux_os" + if [[ $current_linux_os == "debian" ]]; then + say "使用apt安装" + + if ! (curl -s -m 5 www.google.com >/dev/nul); then + say "机器位于墙内,切换为包源为国内镜像源" + cp /etc/apt/sources.list /etc/apt/sources.list.bak + sed -i 's/https:\/\/deb.debian.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apt/sources.list + sed -i 's/http:\/\/deb.debian.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apt/sources.list + apt-get update + fi + + . /etc/os-release + wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update && apt-get install -y dotnet-sdk-6.0 + else + say "使用apk安装" + if ! (curl -s -m 5 www.google.com >/dev/nul); then + say "机器位于墙内,切换为包源为国内镜像源" + cp /etc/apk/repositories /etc/apk/repositories.bak + sed -i 's/https:\/\/dl-cdn.alpinelinux.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apk/repositories + sed -i 's/http:\/\/dl-cdn.alpinelinux.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apk/repositories + apk update + fi apk add dotnet6-sdk - dotnet --version && echo "安装成功" - } || { - echo "------再尝试使用官方脚本安装------" - curl -sSL $rayInstallShell | bash /dev/stdin - . /root/.bashrc - dotnet --version && echo "安装成功" - } || { - echo "------再尝试使用二进制包安装------" - curl -sSL $rayInstallShell | bash /dev/stdin --no-official - . /root/.bashrc - dotnet --version && echo "安装成功" - } || { - echo "安装失败,没办法了,毁灭吧,自己解决吧:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-alpine" - exit 1 - } -fi + fi + dotnet --version && say "which dotnet: $(which dotnet)" && say "安装成功" + return $? +} + +# 从github获取bilitool下载地址 +get_download_url() { + eval $invocation + + tag=$1 + url="${github_proxy}https://github.com/RayWangQvQ/BiliBiliToolPro/releases/download/$tag/bilibili-tool-pro-v$tag-$current_os-$machine_architecture.zip" + say "下载地址:$url" + echo $url + return 0 +} + +# 安装bilitool +install_bilitool() { + eval $invocation + + say "开始安装bilitool" + # 获取最新的release信息 + LATEST_RELEASE=$(curl -s https://api.github.com/repos/$bili_repo/releases/latest) + + # 解析最新的tag名称 + check_jq + LATEST_TAG=$(echo $LATEST_RELEASE | jq -r '.tag_name') + say "最新版本:$LATEST_TAG" + + # 读取之前存储的tag并比较 + if [ "$LATEST_TAG" != "$bilitool_installed_version" ]; then + # 如果不一样,则需要更新安装 + ASSET_URL=$(get_download_url $LATEST_TAG) + + # 使用curl下载文件到当前目录下的test.zip文件 + local zip_file_name="bilitool-$LATEST_TAG.zip" + curl -L -o "$zip_file_name" $ASSET_URL + + # 解压 + check_unzip + unzip -jo "$zip_file_name" -d ./ && + rm "$zip_file_name" && + rm -f appsettings.* + + # 更新tag.txt文件 + echo $LATEST_TAG >./tag.txt + else + say "已经是最新版本,无需下载。" + fi +} + +## 安装dotnet(如果未安装过) +install() { + eval $invocation + + # 调用check方法,如果通过则返回0,否则返回1 + if check; then + say "环境正常,本次无需安装" + return 0 + else + say "开始安装环境" + # 先尝试使用install_dotnet安装,如果失败,就再尝试使用install_bilitool安装 + if [ "$prefer_mode" == "dotnet" ]; then + install_dotnet || { + echo "安装失败,请根据文档自行在青龙容器中安装dotnet:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-$current_linux_os" + exit 1 + } + fi + + if [ "$prefer_mode" == "bilitool" ]; then + install_bilitool || { + echo "安装失败,请检查日志并重试" + exit 1 + } + fi + return $? + fi +} + +run_task() { + eval $invocation + + local target_code=$1 + cd $qinglong_bili_repo_dir/src/Ray.BiliBiliTool.Console -bili_repo="raywangqvq_bilibilitoolpro" + if [ "$prefer_mode" == "dotnet" ]; then + export Ray_RunTasks=$target_code && dotnet run + else + cp -f $qinglong_bili_repo_dir/bin/Ray.BiliBiliTool.Console . + export Ray_RunTasks=$target_code && ./Ray.BiliBiliTool.Console + fi +} -echo -e "\nrepo目录: $dir_repo" -bili_repo_dir="$(find $dir_repo -type d -iname $bili_repo | head -1)" -echo -e "bili仓库目录: $bili_repo_dir\n" +check_os +install -cd $bili_repo_dir export Ray_PlateformType=QingLong export DOTNET_ENVIRONMENT=Production diff --git a/qinglong/DefaultTasks/bili_task_daily.sh b/qinglong/DefaultTasks/bili_task_daily.sh index 9eacc6f69..fa7bd64f1 100644 --- a/qinglong/DefaultTasks/bili_task_daily.sh +++ b/qinglong/DefaultTasks/bili_task_daily.sh @@ -3,7 +3,5 @@ # cron 0 9 * * * bili_task_daily.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=Daily && \ -dotnet run +target_task_code="Daily" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/bili_task_liveFansMedal.sh b/qinglong/DefaultTasks/bili_task_liveFansMedal.sh index d0c46ae62..b0ea1668c 100644 --- a/qinglong/DefaultTasks/bili_task_liveFansMedal.sh +++ b/qinglong/DefaultTasks/bili_task_liveFansMedal.sh @@ -3,7 +3,5 @@ # cron 5 0 * * * bili_task_liveFansMedal.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=LiveFansMedal && \ -dotnet run +target_task_code="LiveFansMedal" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/bili_task_liveLottery.sh b/qinglong/DefaultTasks/bili_task_liveLottery.sh index 4eda72904..9da3917bc 100644 --- a/qinglong/DefaultTasks/bili_task_liveLottery.sh +++ b/qinglong/DefaultTasks/bili_task_liveLottery.sh @@ -3,7 +3,5 @@ # cron 0 13 * * * bili_task_liveLottery.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=LiveLottery && \ -dotnet run +target_task_code="LiveLottery" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/bili_task_login.sh b/qinglong/DefaultTasks/bili_task_login.sh index b385ddf7d..70c5f65e0 100644 --- a/qinglong/DefaultTasks/bili_task_login.sh +++ b/qinglong/DefaultTasks/bili_task_login.sh @@ -3,7 +3,5 @@ # cron 0 0 1 1 * bili_task_login.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=Login && \ -dotnet run +target_task_code="Login" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/bili_task_test.sh b/qinglong/DefaultTasks/bili_task_test.sh index 8ade4702d..2bbea0194 100644 --- a/qinglong/DefaultTasks/bili_task_test.sh +++ b/qinglong/DefaultTasks/bili_task_test.sh @@ -3,7 +3,5 @@ # cron 0 8 * * * bili_task_test.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=Test && \ -dotnet run +target_task_code="Test" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/bili_task_unfollowBatched.sh b/qinglong/DefaultTasks/bili_task_unfollowBatched.sh index 0541347e1..1ccc493ed 100644 --- a/qinglong/DefaultTasks/bili_task_unfollowBatched.sh +++ b/qinglong/DefaultTasks/bili_task_unfollowBatched.sh @@ -3,7 +3,5 @@ # cron 0 12 1 * * bili_task_unfollowBatched.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=UnfollowBatched && \ -dotnet run +target_task_code="UnfollowBatched" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/bili_task_vipBigPoint.sh b/qinglong/DefaultTasks/bili_task_vipBigPoint.sh index b7bb4782f..0470bbc03 100644 --- a/qinglong/DefaultTasks/bili_task_vipBigPoint.sh +++ b/qinglong/DefaultTasks/bili_task_vipBigPoint.sh @@ -3,7 +3,5 @@ # cron 7 1 * * * bili_task_vipBigPoint.sh . bili_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=VipBigPoint && \ -dotnet run +target_task_code="VipBigPoint" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh index 5b389e88f..77fe01c6b 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh @@ -2,44 +2,460 @@ # new Env("bili_dev_task_base") # cron 0 0 1 1 * bili_dev_task_base.sh -dir_shell=${QL_DIR-'/ql'}/shell -. $dir_shell/share.sh +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + + + +verbose=true # 开启debug日志 +bili_repo="raywangqvq/bilibilitoolpro" # 仓库地址 +bili_branch="_develop" # 分支名,空或_develop +prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 +github_proxy=${BILI_GITHUB_PROXY:-""} # 下载github release包时使用的代理,会拼在地址前面,需要通过环境变量配置 + + + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ] && command -v tput >/dev/null; then + # see if it supports colors + ncolors=$(tput colors || echo 0) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_warning() { + printf "%b\n" "${yellow:-}bilitool: Warning: $1${normal:-}" >&3 +} + +say_err() { + printf "%b\n" "${red:-}bilitool: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}bilitool:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +QL_DIR=${QL_DIR:-"/ql"} +QL_BRANCH=${QL_BRANCH:-"develop"} +DefaultCronRule=${DefaultCronRule:-""} +CpuWarn=${CpuWarn:-""} +MemoryWarn=${MemoryWarn:-""} +DiskWarn=${DiskWarn:-""} +dir_repo=${dir_repo:-"$QL_DIR/data/repo"} + +dir_shell=$QL_DIR/shell +. $dir_shell/env.sh +touch /root/.bashrc . /root/.bashrc -## 安装dotnet(如果未安装过) -dotnetVersion=$(dotnet --version) -if [[ $dotnetVersion == 6.* ]]; then - echo "已安装dotnet,当前版本:$dotnetVersion" -else - echo "which dotnet: $(which dotnet)" - echo "开始安装dotnet" - rayInstallShell="https://ghproxy.com/https://raw.githubusercontent.com/RayWangQvQ/BiliBiliToolPro/main/qinglong/ray-dotnet-install.sh" - { - echo "------尝试使用apk安装------" +# 目录 +say "青龙repo目录: $dir_repo" +qinglong_bili_repo="$(echo "$bili_repo" | sed 's/\//_/g')${bili_branch}" +qinglong_bili_repo_dir="$(find $dir_repo -type d -iname $qinglong_bili_repo | head -1)" +say "bili仓库目录: $qinglong_bili_repo_dir" + +current_linux_os="debian" # 或alpine +current_os="linux" # 或linux-musl +machine_architecture="x64" # 或arm、arm64 +dotnet_installed=false +bilitool_installed=false +bilitool_installed_version=0 + +# 以下操作仅在bilitool仓库的根bin文件下执行 +cd $qinglong_bili_repo_dir +mkdir -p bin +cd $qinglong_bili_repo_dir/bin + +# 判断是否存在某指令 +machine_has() { + eval $invocation + + command -v "$1" >/dev/null 2>&1 + return $? +} + +# 判断系统架构 +# 输出:arm、arm64、x64 +get_machine_architecture() { + eval $invocation + + if command -v uname >/dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv*l) + echo "arm" + return 0 + ;; + aarch64 | arm64) + echo "arm64" + return 0 + ;; + esac + fi + + # Always default to 'x64' + echo "x64" + return 0 +} + +# 获取linux系统名称 +# 输出:debian.10、debian.11、debian.12、ubuntu.20.04、ubuntu.22.04、alpine.3.4.3... +get_linux_platform_name() { + eval $invocation + + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID${VERSION_ID:+.${VERSION_ID}}" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(&1 || true) | grep -q musl +} + +# 获取当前系统名称 +# 输出:linux、linux-musl、osx、freebsd +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + say_warning "当前系统:osx" + echo "osx" + return 1 + elif [ "$uname" = "FreeBSD" ]; then + say_warning "当前系统:freebsd" + echo "freebsd" + return 1 + elif [ "$uname" = "Linux" ]; then + local linux_platform_name="" + linux_platform_name="$(get_linux_platform_name)" || true + say "当前系统发行版本:$linux_platform_name" + + if [ "$linux_platform_name" = "rhel.6" ]; then + echo $linux_platform_name + return 1 + elif is_musl_based_distro; then + echo "linux-musl" + return 0 + elif [ "$linux_platform_name" = "linux-musl" ]; then + echo "linux-musl" + return 0 + else + echo "linux" + return 0 + fi + fi + + say_err "OS name could not be detected: UName = $uname" + return 1 +} + +check_os() { + eval $invocation + + # 获取系统信息 + current_os="$(get_current_os_name)" + say "当前系统:$current_os" + machine_architecture="$(get_machine_architecture)" + say "当前架构:$machine_architecture" + + if [ "$current_os" = "linux" ]; then + current_linux_os="debian" # 当前青龙只有debian和aplpine两种 + if ! machine_has curl; then + say "curl未安装,开始安装依赖..." + apt-get update + apt-get install -y curl + fi + else + current_linux_os="alpine" + if ! machine_has curl; then + say "curl未安装,开始安装依赖..." + apk update + apk add -y curl + fi + fi + + if [ -f "./Ray.BiliBiliTool.Console" ]; then + prefer_mode="bilitool" + fi + say "当前选择的运行方式:$prefer_mode" +} + +check_jq() { + if [ "$current_linux_os" = "debian" ]; then + if ! machine_has jq; then + say "jq未安装,开始安装依赖..." + apt-get update + apt-get install -y jq + fi + else + if ! machine_has jq; then + say "jq未安装,开始安装依赖..." + apk update + apk add -y jq + fi + fi +} + +check_unzip() { + if [ "$current_linux_os" = "debian" ]; then + if ! machine_has unzip; then + say "unzip未安装,开始安装依赖..." + apt-get update + apt-get install -y unzip + fi + else + if ! machine_has unzip; then + say "jq未安装,开始安装依赖..." + apk update + apk add -y unzip + fi + fi +} + +# 检查dotnet +check_dotnet() { + eval $invocation + + dotnetVersion=$(dotnet --version) + if [[ $dotnetVersion == 6.* ]]; then + say "已安装dotnet,当前版本:$dotnetVersion" + say "which dotnet: $(which dotnet)" + return 0 + else + say "未安装" + return 1 + fi +} + +# 检查bilitool +check_bilitool() { + eval $invocation + + TAG_FILE="./tag.txt" + touch $TAG_FILE + local STORED_TAG=$(cat $TAG_FILE 2>/dev/null) + + #如果STORED_TAG为空,则返回1 + if [[ -z $STORED_TAG ]]; then + say "tag.txt为空,未安装过" + return 1 + fi + + say "tag.txt记录的版本:$STORED_TAG" + + # 查找当前目录下是否有叫Ray.BiliBiliTool.Console的文件 + if [ -f "./Ray.BiliBiliTool.Console" ]; then + say "bilitool已安装" + bilitool_installed_version=$STORED_TAG + return 0 + else + say "bilitool未安装" + return 1 + fi +} + +# 检查环境 +check() { + eval $invocation + + if [ "$prefer_mode" == "dotnet" ]; then + if check_dotnet; then + dotnet_installed=true + return 0 + else + dotnet_installed=true + return 1 + fi + fi + + if [ "$prefer_mode" == "bilitool" ]; then + if check_bilitool; then + bilitool_installed=true + return 0 + else + bilitool_installed=false + return 1 + fi + fi + + return 1 +} + +# 安装dotnet环境 +install_dotnet() { + eval $invocation + + say "开始安装dotnet" + say "当前系统:$current_linux_os" + if [[ $current_linux_os == "debian" ]]; then + say "使用apt安装" + + if ! (curl -s -m 5 www.google.com >/dev/nul); then + say "机器位于墙内,切换为包源为国内镜像源" + cp /etc/apt/sources.list /etc/apt/sources.list.bak + sed -i 's/https:\/\/deb.debian.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apt/sources.list + sed -i 's/http:\/\/deb.debian.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apt/sources.list + apt-get update + fi + + . /etc/os-release + wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update && apt-get install -y dotnet-sdk-6.0 + else + say "使用apk安装" + if ! (curl -s -m 5 www.google.com >/dev/nul); then + say "机器位于墙内,切换为包源为国内镜像源" + cp /etc/apk/repositories /etc/apk/repositories.bak + sed -i 's/https:\/\/dl-cdn.alpinelinux.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apk/repositories + sed -i 's/http:\/\/dl-cdn.alpinelinux.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apk/repositories + apk update + fi apk add dotnet6-sdk - dotnet --version && echo "安装成功" - } || { - echo "------再尝试使用官方脚本安装------" - curl -sSL $rayInstallShell | bash /dev/stdin - . /root/.bashrc - dotnet --version && echo "安装成功" - } || { - echo "------再尝试使用二进制包安装------" - curl -sSL $rayInstallShell | bash /dev/stdin --no-official - . /root/.bashrc - dotnet --version && echo "安装成功" - } || { - echo "安装失败,没办法了,毁灭吧,自己解决吧:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-alpine" - exit 1 - } -fi + fi + dotnet --version && say "which dotnet: $(which dotnet)" && say "安装成功" + return $? +} + +# 从github获取bilitool下载地址 +get_download_url() { + eval $invocation + + tag=$1 + url="${github_proxy}https://github.com/RayWangQvQ/BiliBiliToolPro/releases/download/$tag/bilibili-tool-pro-v$tag-$current_os-$machine_architecture.zip" + say "下载地址:$url" + echo $url + return 0 +} + +# 安装bilitool +install_bilitool() { + eval $invocation + + say "开始安装bilitool" + # 获取最新的release信息 + LATEST_RELEASE=$(curl -s https://api.github.com/repos/$bili_repo/releases/latest) + + # 解析最新的tag名称 + check_jq + LATEST_TAG=$(echo $LATEST_RELEASE | jq -r '.tag_name') + say "最新版本:$LATEST_TAG" + + # 读取之前存储的tag并比较 + if [ "$LATEST_TAG" != "$bilitool_installed_version" ]; then + # 如果不一样,则需要更新安装 + ASSET_URL=$(get_download_url $LATEST_TAG) + + # 使用curl下载文件到当前目录下的test.zip文件 + local zip_file_name="bilitool-$LATEST_TAG.zip" + curl -L -o "$zip_file_name" $ASSET_URL + + # 解压 + check_unzip + unzip -jo "$zip_file_name" -d ./ && + rm "$zip_file_name" && + rm -f appsettings.* + + # 更新tag.txt文件 + echo $LATEST_TAG >./tag.txt + else + say "已经是最新版本,无需下载。" + fi +} + +## 安装dotnet(如果未安装过) +install() { + eval $invocation + + # 调用check方法,如果通过则返回0,否则返回1 + if check; then + say "环境正常,本次无需安装" + return 0 + else + say "开始安装环境" + # 先尝试使用install_dotnet安装,如果失败,就再尝试使用install_bilitool安装 + if [ "$prefer_mode" == "dotnet" ]; then + install_dotnet || { + echo "安装失败,请根据文档自行在青龙容器中安装dotnet:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-$current_linux_os" + exit 1 + } + fi + + if [ "$prefer_mode" == "bilitool" ]; then + install_bilitool || { + echo "安装失败,请检查日志并重试" + exit 1 + } + fi + return $? + fi +} + +run_task() { + eval $invocation + + local target_code=$1 + cd $qinglong_bili_repo_dir/src/Ray.BiliBiliTool.Console -bili_repo="raywangqvq_bilibilitoolpro_develop" + if [ "$prefer_mode" == "dotnet" ]; then + export Ray_RunTasks=$target_code && dotnet run + else + cp -f $qinglong_bili_repo_dir/bin/Ray.BiliBiliTool.Console . + export Ray_RunTasks=$target_code && ./Ray.BiliBiliTool.Console + fi +} -echo -e "\nrepo目录: $dir_repo" -bili_repo_dir="$(find $dir_repo -type d -iname $bili_repo | head -1)" -echo -e "bili仓库目录: $bili_repo_dir\n" +check_os +install -cd $bili_repo_dir export Ray_PlateformType=QingLong -export DOTNET_ENVIRONMENT=Production \ No newline at end of file +export DOTNET_ENVIRONMENT=Production diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_daily.sh b/qinglong/DefaultTasks/dev/bili_dev_task_daily.sh index 6b30dd4e5..110b9f9fa 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_daily.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_daily.sh @@ -3,7 +3,5 @@ # cron 0 9 * * * bili_dev_task_daily.sh . bili_dev_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=Daily && \ -dotnet run --ENVIRONMENT=Production +target_task_code="Daily" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh b/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh index 54d9d76e6..f569d01e3 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_liveFansMedal.sh @@ -3,7 +3,5 @@ # 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 +target_task_code="LiveFansMedal" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_liveLottery.sh b/qinglong/DefaultTasks/dev/bili_dev_task_liveLottery.sh index eb2c20124..9287a8529 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_liveLottery.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_liveLottery.sh @@ -3,7 +3,5 @@ # cron 0 13 * * * bili_dev_task_liveLottery.sh . bili_dev_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=LiveLottery && \ -dotnet run --ENVIRONMENT=Production +target_task_code="LiveLottery" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_login.sh b/qinglong/DefaultTasks/dev/bili_dev_task_login.sh index 933e05cfa..88a901e5b 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_login.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_login.sh @@ -3,7 +3,5 @@ # cron 0 0 1 1 * bili_dev_task_login.sh . bili_dev_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=Login && \ -dotnet run --ENVIRONMENT=Production +target_task_code="Login" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_test.sh b/qinglong/DefaultTasks/dev/bili_dev_task_test.sh index 19c6f798e..29237d5d9 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_test.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_test.sh @@ -3,7 +3,5 @@ # cron 0 8 * * * bili_dev_task_test.sh . bili_dev_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=Test && \ -dotnet run --ENVIRONMENT=Production +target_task_code="Test" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_unfollowBatched.sh b/qinglong/DefaultTasks/dev/bili_dev_task_unfollowBatched.sh index 0f8311dba..0413c54e2 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_unfollowBatched.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_unfollowBatched.sh @@ -3,7 +3,5 @@ # cron 0 12 1 * * bili_dev_task_unfollowBatched.sh . bili_dev_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=UnfollowBatched && \ -dotnet run --ENVIRONMENT=Production +target_task_code="UnfollowBatched" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_vipBigPoint.sh b/qinglong/DefaultTasks/dev/bili_dev_task_vipBigPoint.sh index d6cc9d4de..21960394b 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_vipBigPoint.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_vipBigPoint.sh @@ -3,7 +3,5 @@ # cron 7 1 * * * bili_dev_task_vipBigPoint.sh . bili_dev_task_base.sh -cd ./src/Ray.BiliBiliTool.Console - -export Ray_RunTasks=VipBigPoint && \ -dotnet run --ENVIRONMENT=Production +target_task_code="VipBigPoint" +run_task "${target_task_code}" \ No newline at end of file diff --git a/qinglong/README.md b/qinglong/README.md index 8e0e8896c..67b3fcdc1 100644 --- a/qinglong/README.md +++ b/qinglong/README.md @@ -1,6 +1,6 @@ # 在青龙中运行 -总体思路是,在青龙容器中安装 dotnet 环境,利用青龙的拉库命令,拉取本仓库源码,添加cron定时任务,定时运行相应的Task。 +思路是,在青龙容器中安装`dotnet`环境或`bilitool`的二进制包,利用青龙的拉库命令,拉取本仓库源码,添加cron定时任务,定时运行相应的Task。 开始前,请先确保你的青龙面板是运行正常的。 @@ -9,15 +9,15 @@ - [1. 步骤](#1-步骤) - [1.1. 登录青龙面板并修改配置](#11-登录青龙面板并修改配置) - [1.2. 在青龙面板中添加拉库定时任务](#12-在青龙面板中添加拉库定时任务) - - [1.2.1. 订阅管理](#121-订阅管理) - - [1.2.2. 定时任务拉库](#122-定时任务拉库) - - [1.3. 登录](#13-登录) + - [1.2.1. 方式一:订阅管理](#121-方式一订阅管理) + - [1.2.2. 方式二:定时任务拉库](#122-方式二定时任务拉库) + - [1.3. 检查定时任务](#13-检查定时任务) + - [1.4. 登录](#14-登录) - [2. 先行版](#2-先行版) - - [2.1. 订阅管理](#21-订阅管理) - - [2.2. 定时任务拉库](#22-定时任务拉库) - [3. GitHub加速](#3-github加速) - [4. 常见问题](#4-常见问题) - - [4.1. Couldn't find a valid ICU package installed on the system](#41-couldnt-find-a-valid-icu-package-installed-on-the-system) + - [4.1. 安装dotnet失败怎么办法](#41-安装dotnet失败怎么办法) + - [4.2. Couldn't find a valid ICU package installed on the system](#42-couldnt-find-a-valid-icu-package-installed-on-the-system) @@ -32,8 +32,9 @@ ### 1.2. 在青龙面板中添加拉库定时任务 -两种方式: -#### 1.2.1. 订阅管理 +两种方式,任选其一即可: + +#### 1.2.1. 方式一:订阅管理 ``` 名称:Bilibili @@ -49,7 +50,7 @@ 保存后,点击运行按钮,运行拉库。 -#### 1.2.2. 定时任务拉库 +#### 1.2.2. 方式二:定时任务拉库 青龙面板,`定时任务`页,右上角`添加任务`,填入以下信息: ``` @@ -62,11 +63,13 @@ 保存成功后,找到该定时任务,点击运行按钮,运行拉库。 -如果正常,拉库成功后,同时也会自动添加bilibili相关的task任务。 +### 1.3. 检查定时任务 + +如果正常,拉库成功后,会自动添加bilibili相关的task任务。 ![qinglong-tasks.png](../docs/imgs/qinglong-tasks.png) -### 1.3. 登录 +### 1.4. 登录 在青龙定时任务中,点击运行`bili扫码登录`任务,查看运行日志,扫描日志中的二维码进行登录。 ![qinglong-login.png](../docs/imgs/qinglong-login.png) @@ -75,7 +78,7 @@ ![qinglong-env.png](../docs/imgs/qinglong-env.png) -首次运行会自动安装dotnet环境,时间久点,之后就不需要重复安装了。 +首次运行会自动安装环境,时间可能长一点,之后就不需要重复安装了。 ## 2. 先行版 @@ -83,10 +86,6 @@ 想提前体验新功能的朋友可以尝试切换先行版,但同时也意味着稳定性会相应降低(其实是相当于在帮我内测测试bug了~🤨)。 -方式有两种: - -### 2.1. 订阅管理 - ``` 分支:develop 白名单:bili_dev_task_.+\.sh @@ -94,16 +93,40 @@ 其他选项同上。 -### 2.2. 定时任务拉库 +## 3. GitHub加速 + +拉库时,如果服务器在国内,访问GitHub速度慢,可在仓库地址前加上加速代理进行加速。 -修改拉库命令为`ql repo https://github.com/RayWangQvQ/BiliBiliToolPro.git "bili_dev_task_" "" "" "develop"` +如: -## 3. GitHub加速 -拉库时,如果服务器在国内,访问GitHub速度慢,可以在仓库地址前加上 `https://ghproxy.com/` 进行加速, 如:`ql repo https://ghproxy.com/https://github.com/RayWangQvQ/BiliBiliToolPro.git "bili_task_"` +``` +https://github.moeyy.xyz/https://github.com/RayWangQvQ/BiliBiliToolPro.git +https://ghproxy.net/https://github.com/RayWangQvQ/BiliBiliToolPro.git +... +``` + +加速代理地址通常不能保证长期稳定,请自行查找使用。 ## 4. 常见问题 -### 4.1. Couldn't find a valid ICU package installed on the system +### 4.1. 安装dotnet失败怎么办法 + +先通过日志自行排查,不行就根据微软官方文档,进入qinglong容器后,手动安装。 + +如果还不行,那么可以切换到基于`bilitool`的二进制包运行方式,该方式不需要安装`dotnet`,方式: + +编辑青龙面板的`配置文件`,新增如下两行: + +``` +export BILI_MODE="bilitool" # bili运行模式,dotnet或bilitool +export BILI_GITHUB_PROXY="https://github.moeyy.xyz/" # 下载二进制包时使用的加速代理,不要的话则置空 +``` + +其中加速代理形如:`https://github.moeyy.xyz/`或`https://ghproxy.net/`,因通常不能保证长期稳定,请自行查找使用。 + +![qinglong-login.png](../docs/imgs/qinglong-run-as-bilitool.png) + +### 4.2. Couldn't find a valid ICU package installed on the system 如 #266 ,需要在青龙面板的环境变量添加如下环境变量: diff --git a/qinglong/dotnet-install.sh b/qinglong/dotnet-install.sh index f80355e5f..096348d34 100644 --- a/qinglong/dotnet-install.sh +++ b/qinglong/dotnet-install.sh @@ -1314,6 +1314,7 @@ calculate_vars() { script_name=$(basename "$0") normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" say_verbose "Normalized architecture: '$normalized_architecture'." + normalized_os="$(get_normalized_os "$user_defined_os")" say_verbose "Normalized OS: '$normalized_os'." normalized_quality="$(get_normalized_quality "$quality")" diff --git a/qinglong/ray-dotnet-install.sh b/qinglong/ray-dotnet-install.sh index b57cf3520..1310277b2 100644 --- a/qinglong/ray-dotnet-install.sh +++ b/qinglong/ray-dotnet-install.sh @@ -3,29 +3,6 @@ echo -e "\n-------set up dot net env-------" ## 安装dotnet -DOWNLOAD_X64=https://download.visualstudio.microsoft.com/download/pr/d74b9eb9-d60c-4b0d-8d53-f30a6e22b917/ef06d32d3b5206786eac8011798568aa/dotnet-sdk-6.0.405-linux-musl-x64.tar.gz -DOWNLOAD_ARM32=https://download.visualstudio.microsoft.com/download/pr/29682aff-7321-4034-9833-29f3ea435759/cf2fd8a078d3a6c106a1254cc2887ad3/dotnet-sdk-6.0.405-linux-musl-arm.tar.gz -DOWNLOAD_ARM64=https://download.visualstudio.microsoft.com/download/pr/207a3484-7524-4963-9c4e-dacf20ba3a66/4a3bc869dc7a93753022752aa5782989/dotnet-sdk-6.0.405-linux-musl-arm64.tar.gz - -get_download_url_by_machine_architecture() { - if command -v uname >/dev/null; then - CPUName=$(uname -m) - case $CPUName in - armv*l) - echo $DOWNLOAD_ARM32 - return 0 - ;; - aarch64 | arm64) - echo $DOWNLOAD_ARM64 - return 0 - ;; - esac - fi - # Always default to 'x64' - echo $DOWNLOAD_X64 - return 0 -} - # 安装依赖 install_dependency() { echo "安装依赖..." @@ -38,20 +15,6 @@ install_by_offical() { curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 6.0 --no-cdn --verbose } -# 通过vscode域自己下载二进制文件安装dotnet -# https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0 -install_by_binaries() { - echo "install by binaries..." - DOWNLOAD_URL="$(get_download_url_by_machine_architecture)" - DOTNET_FILE=dotnet-sdk.tar.gz - - wget -O $DOTNET_FILE $DOWNLOAD_URL - DOTNET_ROOT=~/.dotnet - mkdir -p "$DOTNET_ROOT" && tar zxf "$DOTNET_FILE" -C "$DOTNET_ROOT" - - export PATH=$PATH:$DOTNET_ROOT -} - # 创建软链接 create_soft_link() { # echo "创建软链接..." @@ -69,47 +32,9 @@ create_soft_link() { args=("$@") -# 不使用官方脚本 -no_official=false - -while [ $# -ne 0 ]; do - name="$1" - case "$name" in - --no-official | -[Nn]o[Oo]fficial) - no_official=true - ;; - -? | --? | -h | --help | -[Hh]elp) - script_name="$(basename "$0")" - echo ".NET Tools Installer" - echo "" - echo "$script_name is a simple command line interface for obtaining dotnet cli." - echo "" - echo " --no-official, -NoOfficial Do not use the official script, download sdk by visualstudio domain directly." - echo " -?,--?,-h,--help,-Help Shows this help message" - echo "" - echo "Install Location:" - echo " Location is chosen in following order:" - echo " - --install-dir option" - echo " - Environmental variable DOTNET_INSTALL_DIR" - echo " - $HOME/.dotnet" - exit 0 - ;; - *) - echo "Unknown argument \`$name\`" - exit 1 - ;; - esac - - shift -done - install_dependency -if [ "$no_official" = true ]; then - install_by_binaries -else - install_by_offical -fi +install_by_offical create_soft_link diff --git a/scripts/publish.sh b/scripts/publish.sh index 74811d8e7..99501c824 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -122,7 +122,7 @@ main() { # self contained # https://learn.microsoft.com/zh-cn/dotnet/core/rid-catalog - array=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-musl-x64" "linux-arm64" "linux-arm" "osx-x64") + array=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-musl-x64" "linux-arm64" "linux-arm" "linux-musl-arm64" "osx-x64") if [ "$runTime" != "all" ]; then array=("$runTime") fi From 4e405ba585f23240dbec73b39dd84a6357abc039 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 5 May 2024 21:26:18 +0800 Subject: [PATCH 4/7] fallback to dotnet official script when fial --- qinglong/DefaultTasks/bili_task_base.sh | 57 ++++++++++++------ .../DefaultTasks/dev/bili_dev_task_base.sh | 59 ++++++++++++------- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/qinglong/DefaultTasks/bili_task_base.sh b/qinglong/DefaultTasks/bili_task_base.sh index 7fb5318f4..1756ec9bb 100644 --- a/qinglong/DefaultTasks/bili_task_base.sh +++ b/qinglong/DefaultTasks/bili_task_base.sh @@ -10,13 +10,11 @@ set -u # This is causing it to fail set -o pipefail - -verbose=true # 开启debug日志 +verbose=true # 开启debug日志 bili_repo="raywangqvq/bilibilitoolpro" # 仓库地址 -bili_branch="" # 分支名,空或_develop -prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 -github_proxy=${BILI_GITHUB_PROXY:-""} # 下载github release包时使用的代理,会拼在地址前面,需要通过环境变量配置 - +bili_branch="" # 分支名,空或_develop +prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 +github_proxy=${BILI_GITHUB_PROXY:-""} # 下载github release包时使用的代理,会拼在地址前面,需要通过环境变量配置 # Use in the the functions: eval $invocation invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' @@ -220,9 +218,6 @@ check_os() { fi fi - if [ -f "./Ray.BiliBiliTool.Console" ]; then - prefer_mode="bilitool" - fi say "当前选择的运行方式:$prefer_mode" } @@ -327,6 +322,21 @@ check() { return 1 } +install_dotnet_by_script() { + eval $invocation + + say "再尝试使用官方脚本安装" + curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 6.0 --no-cdn --verbose + + say "添加到PATH" + local exportFile="/root/.bashrc" + touch $exportFile + echo '' >>$exportFile + echo 'export DOTNET_ROOT=$HOME/.dotnet' >>$exportFile + echo 'export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools' >>$exportFile + . $exportFile +} + # 安装dotnet环境 install_dotnet() { eval $invocation @@ -343,12 +353,15 @@ install_dotnet() { sed -i 's/http:\/\/deb.debian.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apt/sources.list apt-get update fi - - . /etc/os-release - wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb - dpkg -i packages-microsoft-prod.deb - rm packages-microsoft-prod.deb - apt-get update && apt-get install -y dotnet-sdk-6.0 + { + . /etc/os-release + wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update && apt-get install -y dotnet-sdk-6.0 + } || { + install_dotnet_by_script + } else say "使用apk安装" if ! (curl -s -m 5 www.google.com >/dev/nul); then @@ -358,7 +371,12 @@ install_dotnet() { sed -i 's/http:\/\/dl-cdn.alpinelinux.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apk/repositories apk update fi - apk add dotnet6-sdk + { + apk add dotnet6-sdk + + } || { + install_dotnet_by_script + } fi dotnet --version && say "which dotnet: $(which dotnet)" && say "安装成功" return $? @@ -423,14 +441,17 @@ install() { # 先尝试使用install_dotnet安装,如果失败,就再尝试使用install_bilitool安装 if [ "$prefer_mode" == "dotnet" ]; then install_dotnet || { - echo "安装失败,请根据文档自行在青龙容器中安装dotnet:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-$current_linux_os" + say_err "安装失败" + say_err "请根据文档自行在青龙容器中安装dotnet:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-$current_linux_os" + say_err "或者尝试切换运行模式为bilitool,它不需要安装dotnet:https://github.com/RayWangQvQ/BiliBiliToolPro/blob/develop/qinglong/README.md" exit 1 } fi if [ "$prefer_mode" == "bilitool" ]; then install_bilitool || { - echo "安装失败,请检查日志并重试" + say_err "安装失败,请检查日志并重试" + say_err "或者尝试切换运行模式为dotnet:https://github.com/RayWangQvQ/BiliBiliToolPro/blob/develop/qinglong/README.md" exit 1 } fi diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh index 77fe01c6b..f84942b39 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh @@ -10,15 +10,11 @@ set -u # This is causing it to fail set -o pipefail - - -verbose=true # 开启debug日志 +verbose=true # 开启debug日志 bili_repo="raywangqvq/bilibilitoolpro" # 仓库地址 -bili_branch="_develop" # 分支名,空或_develop -prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 -github_proxy=${BILI_GITHUB_PROXY:-""} # 下载github release包时使用的代理,会拼在地址前面,需要通过环境变量配置 - - +bili_branch="_develop" # 分支名,空或_develop +prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 +github_proxy=${BILI_GITHUB_PROXY:-""} # 下载github release包时使用的代理,会拼在地址前面,需要通过环境变量配置 # Use in the the functions: eval $invocation invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' @@ -222,9 +218,6 @@ check_os() { fi fi - if [ -f "./Ray.BiliBiliTool.Console" ]; then - prefer_mode="bilitool" - fi say "当前选择的运行方式:$prefer_mode" } @@ -329,6 +322,21 @@ check() { return 1 } +install_dotnet_by_script() { + eval $invocation + + say "再尝试使用官方脚本安装" + curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 6.0 --no-cdn --verbose + + say "添加到PATH" + local exportFile="/root/.bashrc" + touch $exportFile + echo '' >>$exportFile + echo 'export DOTNET_ROOT=$HOME/.dotnet' >>$exportFile + echo 'export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools' >>$exportFile + . $exportFile +} + # 安装dotnet环境 install_dotnet() { eval $invocation @@ -345,12 +353,15 @@ install_dotnet() { sed -i 's/http:\/\/deb.debian.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apt/sources.list apt-get update fi - - . /etc/os-release - wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb - dpkg -i packages-microsoft-prod.deb - rm packages-microsoft-prod.deb - apt-get update && apt-get install -y dotnet-sdk-6.0 + { + . /etc/os-release + wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update && apt-get install -y dotnet-sdk-6.0 + } || { + install_dotnet_by_script + } else say "使用apk安装" if ! (curl -s -m 5 www.google.com >/dev/nul); then @@ -360,7 +371,12 @@ install_dotnet() { sed -i 's/http:\/\/dl-cdn.alpinelinux.org/https:\/\/mirrors.ustc.edu.cn/g' /etc/apk/repositories apk update fi - apk add dotnet6-sdk + { + apk add dotnet6-sdk + + } || { + install_dotnet_by_script + } fi dotnet --version && say "which dotnet: $(which dotnet)" && say "安装成功" return $? @@ -425,14 +441,17 @@ install() { # 先尝试使用install_dotnet安装,如果失败,就再尝试使用install_bilitool安装 if [ "$prefer_mode" == "dotnet" ]; then install_dotnet || { - echo "安装失败,请根据文档自行在青龙容器中安装dotnet:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-$current_linux_os" + say_err "安装失败" + say_err "请根据文档自行在青龙容器中安装dotnet:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-$current_linux_os" + say_err "或者尝试切换运行模式为bilitool,它不需要安装dotnet:https://github.com/RayWangQvQ/BiliBiliToolPro/blob/develop/qinglong/README.md" exit 1 } fi if [ "$prefer_mode" == "bilitool" ]; then install_bilitool || { - echo "安装失败,请检查日志并重试" + say_err "安装失败,请检查日志并重试" + say_err "或者尝试切换运行模式为dotnet:https://github.com/RayWangQvQ/BiliBiliToolPro/blob/develop/qinglong/README.md" exit 1 } fi From 084af9eaaced74b05b868ba44b63d50b3c4207d5 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 6 May 2024 11:43:55 +0800 Subject: [PATCH 5/7] fix:[#707] login error --- .../Extensions/ServiceCollectionExtension.cs | 32 +++++++++---------- .../Cookie/CookieStrFactory.cs | 6 +++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs index f0222671b..f3edeb53f 100644 --- a/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs +++ b/src/Ray.BiliBiliTool.Agent/Extensions/ServiceCollectionExtension.cs @@ -55,39 +55,39 @@ public static IServiceCollection AddBiliBiliClientApi(this IServiceCollection se .AddClasses(classes => classes.AssignableTo()) .AsSelf() .WithTransientLifetime() - ); + ); - //服务 + //服务 services.AddScoped(); - //bilibli - Action config = (sp, c) => { - c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgent); - c.DefaultRequestHeaders.Add("Cookie", sp.GetRequiredService().ToString()); - }; - Action configApp = (sp, c) => { - c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgentApp); - c.DefaultRequestHeaders.Add("Cookie", sp.GetRequiredService().ToString()); + //bilibli + Action config = (sp, c) => { + c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgent); + c.DefaultRequestHeaders.Add("Cookie", sp.GetRequiredService().ToString()); }; - + Action configApp = (sp, c) => { + c.DefaultRequestHeaders.Add("User-Agent", sp.GetRequiredService>().CurrentValue.UserAgentApp); + c.DefaultRequestHeaders.Add("Cookie", sp.GetRequiredService().ToString()); + }; + services.AddBiliBiliClientApi(BiliHosts.Api, config); services.AddBiliBiliClientApi(BiliHosts.Api, config); services.AddBiliBiliClientApi(BiliHosts.Api, config); services.AddBiliBiliClientApi(BiliHosts.Api, config); services.AddBiliBiliClientApi(BiliHosts.Api, config); services.AddBiliBiliClientApi(BiliHosts.Api, config); - services.AddBiliBiliClientApi(BiliHosts.Api, config); + services.AddBiliBiliClientApi(BiliHosts.Api, config); - services.AddBiliBiliClientApi(BiliHosts.Show, config); + services.AddBiliBiliClientApi(BiliHosts.Show, config); services.AddBiliBiliClientApi(BiliHosts.Passport, config); services.AddBiliBiliClientApi(BiliHosts.LiveTrace,config); - services.AddBiliBiliClientApi(BiliHosts.Www, config); + services.AddBiliBiliClientApi(BiliHosts.Www, config); services.AddBiliBiliClientApi(BiliHosts.Manga, config); services.AddBiliBiliClientApi(BiliHosts.Account, config); services.AddBiliBiliClientApi(BiliHosts.Live, config); - + services.AddBiliBiliClientApi(BiliHosts.Api, configApp); - + //qinglong var qinglongHost = configuration["QL_URL"] ?? "http://localhost:5600"; diff --git a/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieStrFactory.cs b/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieStrFactory.cs index fe27534be..3224b33e4 100644 --- a/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieStrFactory.cs +++ b/src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieStrFactory.cs @@ -32,7 +32,11 @@ public bool Any() public Dictionary GetCurrentCookieDic() { - if (!Any()) throw new Exception($"第 {CurrentNum} 个cookie不存在"); + if (!Any()) + { + return new Dictionary(); //todo + //throw new Exception($"第 {CurrentNum} 个cookie不存在"); + } return _cookieDictionary[CurrentNum]; } From 132054962e1acb2e8364f4d3f932f87efe2c3046 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 7 May 2024 01:53:57 +0800 Subject: [PATCH 6/7] replace wget with curl --- qinglong/DefaultTasks/bili_task_base.sh | 45 +++++++----------- .../DefaultTasks/dev/bili_dev_task_base.sh | 47 +++++++------------ 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/qinglong/DefaultTasks/bili_task_base.sh b/qinglong/DefaultTasks/bili_task_base.sh index 1756ec9bb..9741e5825 100644 --- a/qinglong/DefaultTasks/bili_task_base.sh +++ b/qinglong/DefaultTasks/bili_task_base.sh @@ -70,12 +70,11 @@ DefaultCronRule=${DefaultCronRule:-""} CpuWarn=${CpuWarn:-""} MemoryWarn=${MemoryWarn:-""} DiskWarn=${DiskWarn:-""} -dir_repo=${dir_repo:-"$QL_DIR/data/repo"} +dir_repo=${dir_repo:-"$QL_DIR/data/repo"} dir_shell=$QL_DIR/shell . $dir_shell/env.sh -touch /root/.bashrc -. /root/.bashrc +touch /root/.bashrc && . /root/.bashrc # 目录 say "青龙repo目录: $dir_repo" @@ -86,14 +85,12 @@ say "bili仓库目录: $qinglong_bili_repo_dir" current_linux_os="debian" # 或alpine current_os="linux" # 或linux-musl machine_architecture="x64" # 或arm、arm64 -dotnet_installed=false -bilitool_installed=false + bilitool_installed_version=0 # 以下操作仅在bilitool仓库的根bin文件下执行 cd $qinglong_bili_repo_dir -mkdir -p bin -cd $qinglong_bili_repo_dir/bin +mkdir -p bin && cd $qinglong_bili_repo_dir/bin # 判断是否存在某指令 machine_has() { @@ -193,12 +190,13 @@ get_current_os_name() { return 1 } +# 检查操作系统 check_os() { eval $invocation - # 获取系统信息 current_os="$(get_current_os_name)" say "当前系统:$current_os" + machine_architecture="$(get_machine_architecture)" say "当前架构:$machine_architecture" @@ -221,6 +219,7 @@ check_os() { say "当前选择的运行方式:$prefer_mode" } +# 检查安装jq check_jq() { if [ "$current_linux_os" = "debian" ]; then if ! machine_has jq; then @@ -237,6 +236,7 @@ check_jq() { fi } +# 检查安装unzip check_unzip() { if [ "$current_linux_os" = "debian" ]; then if ! machine_has unzip; then @@ -296,32 +296,23 @@ check_bilitool() { } # 检查环境 -check() { +check_installed() { eval $invocation if [ "$prefer_mode" == "dotnet" ]; then - if check_dotnet; then - dotnet_installed=true - return 0 - else - dotnet_installed=true - return 1 - fi + check_dotnet + return $? fi if [ "$prefer_mode" == "bilitool" ]; then - if check_bilitool; then - bilitool_installed=true - return 0 - else - bilitool_installed=false - return 1 - fi + check_bilitool + return $? fi return 1 } +# 使用官方脚本安装dotnet install_dotnet_by_script() { eval $invocation @@ -355,7 +346,7 @@ install_dotnet() { fi { . /etc/os-release - wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + curl -o packages-microsoft-prod.deb https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb apt-get update && apt-get install -y dotnet-sdk-6.0 @@ -373,7 +364,6 @@ install_dotnet() { fi { apk add dotnet6-sdk - } || { install_dotnet_by_script } @@ -432,13 +422,11 @@ install_bilitool() { install() { eval $invocation - # 调用check方法,如果通过则返回0,否则返回1 - if check; then + if check_installed; then say "环境正常,本次无需安装" return 0 else say "开始安装环境" - # 先尝试使用install_dotnet安装,如果失败,就再尝试使用install_bilitool安装 if [ "$prefer_mode" == "dotnet" ]; then install_dotnet || { say_err "安装失败" @@ -459,6 +447,7 @@ install() { fi } +# 运行bilitool任务 run_task() { eval $invocation diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh index f84942b39..0bbfe7e38 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh @@ -12,7 +12,7 @@ set -o pipefail verbose=true # 开启debug日志 bili_repo="raywangqvq/bilibilitoolpro" # 仓库地址 -bili_branch="_develop" # 分支名,空或_develop +bili_branch="_develop" # 分支名,空或_develop prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 github_proxy=${BILI_GITHUB_PROXY:-""} # 下载github release包时使用的代理,会拼在地址前面,需要通过环境变量配置 @@ -70,12 +70,11 @@ DefaultCronRule=${DefaultCronRule:-""} CpuWarn=${CpuWarn:-""} MemoryWarn=${MemoryWarn:-""} DiskWarn=${DiskWarn:-""} -dir_repo=${dir_repo:-"$QL_DIR/data/repo"} +dir_repo=${dir_repo:-"$QL_DIR/data/repo"} dir_shell=$QL_DIR/shell . $dir_shell/env.sh -touch /root/.bashrc -. /root/.bashrc +touch /root/.bashrc && . /root/.bashrc # 目录 say "青龙repo目录: $dir_repo" @@ -86,14 +85,12 @@ say "bili仓库目录: $qinglong_bili_repo_dir" current_linux_os="debian" # 或alpine current_os="linux" # 或linux-musl machine_architecture="x64" # 或arm、arm64 -dotnet_installed=false -bilitool_installed=false + bilitool_installed_version=0 # 以下操作仅在bilitool仓库的根bin文件下执行 cd $qinglong_bili_repo_dir -mkdir -p bin -cd $qinglong_bili_repo_dir/bin +mkdir -p bin && cd $qinglong_bili_repo_dir/bin # 判断是否存在某指令 machine_has() { @@ -193,12 +190,13 @@ get_current_os_name() { return 1 } +# 检查操作系统 check_os() { eval $invocation - # 获取系统信息 current_os="$(get_current_os_name)" say "当前系统:$current_os" + machine_architecture="$(get_machine_architecture)" say "当前架构:$machine_architecture" @@ -221,6 +219,7 @@ check_os() { say "当前选择的运行方式:$prefer_mode" } +# 检查安装jq check_jq() { if [ "$current_linux_os" = "debian" ]; then if ! machine_has jq; then @@ -237,6 +236,7 @@ check_jq() { fi } +# 检查安装unzip check_unzip() { if [ "$current_linux_os" = "debian" ]; then if ! machine_has unzip; then @@ -296,32 +296,23 @@ check_bilitool() { } # 检查环境 -check() { +check_installed() { eval $invocation if [ "$prefer_mode" == "dotnet" ]; then - if check_dotnet; then - dotnet_installed=true - return 0 - else - dotnet_installed=true - return 1 - fi + check_dotnet + return $? fi if [ "$prefer_mode" == "bilitool" ]; then - if check_bilitool; then - bilitool_installed=true - return 0 - else - bilitool_installed=false - return 1 - fi + check_bilitool + return $? fi return 1 } +# 使用官方脚本安装dotnet install_dotnet_by_script() { eval $invocation @@ -355,7 +346,7 @@ install_dotnet() { fi { . /etc/os-release - wget https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + curl -o packages-microsoft-prod.deb https://packages.microsoft.com/config/debian/$VERSION_ID/packages-microsoft-prod.deb dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb apt-get update && apt-get install -y dotnet-sdk-6.0 @@ -373,7 +364,6 @@ install_dotnet() { fi { apk add dotnet6-sdk - } || { install_dotnet_by_script } @@ -432,13 +422,11 @@ install_bilitool() { install() { eval $invocation - # 调用check方法,如果通过则返回0,否则返回1 - if check; then + if check_installed; then say "环境正常,本次无需安装" return 0 else say "开始安装环境" - # 先尝试使用install_dotnet安装,如果失败,就再尝试使用install_bilitool安装 if [ "$prefer_mode" == "dotnet" ]; then install_dotnet || { say_err "安装失败" @@ -459,6 +447,7 @@ install() { fi } +# 运行bilitool任务 run_task() { eval $invocation From b5bd99024caca1b46235f32a4907afba790f159d Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 7 May 2024 21:19:19 +0800 Subject: [PATCH 7/7] format codes --- qinglong/DefaultTasks/bili_task_base.sh | 13 +++++++------ qinglong/DefaultTasks/dev/bili_dev_task_base.sh | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/qinglong/DefaultTasks/bili_task_base.sh b/qinglong/DefaultTasks/bili_task_base.sh index 9741e5825..657c3a60c 100644 --- a/qinglong/DefaultTasks/bili_task_base.sh +++ b/qinglong/DefaultTasks/bili_task_base.sh @@ -10,7 +10,7 @@ set -u # This is causing it to fail set -o pipefail -verbose=true # 开启debug日志 +verbose=false # 开启debug日志 bili_repo="raywangqvq/bilibilitoolpro" # 仓库地址 bili_branch="" # 分支名,空或_develop prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 @@ -452,18 +452,19 @@ run_task() { eval $invocation local target_code=$1 + + export Ray_PlateformType=QingLong + export Ray_RunTasks=$target_code + cd $qinglong_bili_repo_dir/src/Ray.BiliBiliTool.Console if [ "$prefer_mode" == "dotnet" ]; then - export Ray_RunTasks=$target_code && dotnet run + dotnet run --ENVIRONMENT=Production else cp -f $qinglong_bili_repo_dir/bin/Ray.BiliBiliTool.Console . - export Ray_RunTasks=$target_code && ./Ray.BiliBiliTool.Console + chmod +x ./Ray.BiliBiliTool.Console && ./Ray.BiliBiliTool.Console --ENVIRONMENT=Production fi } check_os install - -export Ray_PlateformType=QingLong -export DOTNET_ENVIRONMENT=Production diff --git a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh index 0bbfe7e38..80c9a4642 100644 --- a/qinglong/DefaultTasks/dev/bili_dev_task_base.sh +++ b/qinglong/DefaultTasks/dev/bili_dev_task_base.sh @@ -10,7 +10,7 @@ set -u # This is causing it to fail set -o pipefail -verbose=true # 开启debug日志 +verbose=false # 开启debug日志 bili_repo="raywangqvq/bilibilitoolpro" # 仓库地址 bili_branch="_develop" # 分支名,空或_develop prefer_mode=${BILI_MODE:-"dotnet"} # dotnet或bilitool,需要通过环境变量配置 @@ -452,18 +452,19 @@ run_task() { eval $invocation local target_code=$1 + + export Ray_PlateformType=QingLong + export Ray_RunTasks=$target_code + cd $qinglong_bili_repo_dir/src/Ray.BiliBiliTool.Console if [ "$prefer_mode" == "dotnet" ]; then - export Ray_RunTasks=$target_code && dotnet run + dotnet run --ENVIRONMENT=Production else cp -f $qinglong_bili_repo_dir/bin/Ray.BiliBiliTool.Console . - export Ray_RunTasks=$target_code && ./Ray.BiliBiliTool.Console + chmod +x ./Ray.BiliBiliTool.Console && ./Ray.BiliBiliTool.Console --ENVIRONMENT=Production fi } check_os install - -export Ray_PlateformType=QingLong -export DOTNET_ENVIRONMENT=Production