Skip to content

Commit

Permalink
Modernized samples
Browse files Browse the repository at this point in the history
  • Loading branch information
gehongyan committed Jan 28, 2024
1 parent c222f60 commit 0da3fb7
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 384 deletions.
22 changes: 22 additions & 0 deletions docs/guides/concepts/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ Kook.Net 中的事件采用与标准 .NET 事件模式类似的方式实现,
由于网关线程将等待所有被调用的事件处理程序完成,然后才会处理所有来自网关的任何其它数据,
这将导致一个无法恢复的死锁。

> [!WARNING]
> 如果您需要在网关线程之外的上下文中(下以事件处理线程指代)访问 Kook.Net 的缓存实体,可能会遇到线程安全性问题。
>
> 例如,当您在事件处理线程中访问缓存中的某频道实体时,与此同时,如果网关线程正在处理频道删除事件,
> 则可能会导致频道实体在缓存中被删除,从而导致事件处理线程中在访问频道实体时可能会抛出异常。
>
> 又如,当您在时间处理线程中访问频道消息时,与此同时,如果消息的作者编辑了该消息文本,网关线程可能会更新该消息实体,
> 从而导致事件处理线程中在访问消息实体时可能会取到不正确的消息文本。
>
> 因此建议您**在开启不在网关线程中等待的、可能会访问缓存实体中的数据的事件处理线程前,先将必要数据取值为局部变量,再进行后续操作**
>
> 相同的问题在队列模式或发布订阅模式下也可能会出现,因此,这些可能会被网关线程更新的数据都应包装为队列或发布订阅事件的参数。
> [!NOTE]
> **等待任务完成**`await Task.Run(() => { /* ... */ });``await Task.Run(async () => { /* ... await ... */ });`
> 此时,异常将会被正确地传播到事件上下文中,但这样做也会导致网关线程等待任务完成。
>
> **不等待任务完成**`_ = Task.Run(() => { /* ... */ });``_ = Task.Run(async () => { /* ... await ... */ });`
> 此时,异常不会被传播到事件上下文中,您需要在事件处理线程正确地捕获处理异常。
>
> 额外要注意的是,如果异常被抛出到 async void 所标记的方法中,如果异常没有被正确地处理,**将会导致程序退出**
## 常见模式

Kook.Net 中的事件签名都是形如 `Func<T1, ..., Task>` 的模式,没有额外定义名称,
Expand Down
1 change: 1 addition & 0 deletions docs/guides/concepts/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Kook.Net 中提供了一个日志事件,所有的日志消息都会通过此
> Kook.Net 中所有的日志实现处理程序都将在网关线程上同步执行,
> 如果您要将日志消息输出到 Web API 中(例如:Sentry、Stackdriver、KOOK 频道等),
> 建议将输出程序包装在 `Task.Run` 中,以避免网关线程在等待数据日志输出时阻塞。
> 更多有关网关线程的信息,请参阅 [事件](events.md#线程安全性) 章节。
## 在客户端中记录日志

Expand Down
19 changes: 7 additions & 12 deletions docs/guides/getting_started/first-bot.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,17 @@ Bot 无法通过访问服务器的邀请链接进入频道,因此需要 Bot
Kook.Net 广泛采用 .NET 的 [异步编程模型],绝大多数操作都是以异步方式完成,
请尽可能地在异步上下文中等待这些操作。

可以通过这样的方式创建异步的入口点函数,来建立一个在异步上下文中启动的程序:

[!code-csharp[Async Main](samples/async-main.cs)]
C# 9.0 引入了顶级语句,可以在不显式创建 Program 类和 Main 方法的情况下编写代码,
顶级语句中使用 await 来调用异步方法,会自动建立一个在异步上下文中启动的程序。

> [!WARNING]
> 应用程序中异步上下文抛出的任何异常都会被持续向上层抛出直到首个同步方法,
> 由于该程序的首个同步方法是程序的入口点函数 `Main`
> **所有**未经处理的异常都会被抛出到这里,这将导致程序退出。
> 抛出至顶级语句的**所有**未经处理的异常将导致程序退出。
>
> Kook.Net 会阻止事件处理函数中发生的异常,以避免该异常导致程序退出,
> `MainAsync` 中的任何其它未经处理的异常仍然会导致程序退出。
> Kook.Net 会捕获事件处理函数中发生的异常,以避免该异常导致程序退出,
> 但需要注意的是,如果异常发生在 async void 所标记的方法中,
> 尽管异常发生在异步上下文中,但该异常仍会导致程序退出。
> 因此,请合理使用 async Task 与 await 的组合。
[异步编程模型]: https://docs.microsoft.com/zh-cn/dotnet/csharp/async

Expand Down Expand Up @@ -129,13 +129,8 @@ Kook.Net 中的事件与 C# 中的任何其他事件的工作机制类似。

到这里,客户端应该可以连接到 KOOK 服务端,运行程序,等待片刻,应该可以在 KOOK 客户端中看到 Bot 上线。

> [!NOTE]
> 如需查看完整代码示例,可访问 [完整代码示例]
[KookSocketClient]: xref:Kook.WebSocket.KookSocketClient

[LoginAsync]: xref:Kook.Rest.BaseKookClient.LoginAsync*

[StartAsync]: xref:Kook.WebSocket.KookSocketClient.StartAsync*

[完整代码示例]: samples/simple-bot.cs
11 changes: 6 additions & 5 deletions docs/guides/getting_started/installing.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Kook.Net 通过 NuGet 分发,推荐通过 NuGet 包管理工具安装,

Kook.Net 目前支持的目标框架包括

- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
- [.NET 7.0](https://dotnet.microsoft.com/download/dotnet/7.0)
- [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0)
- [.NET Standard 2.1](https://learn.microsoft.com/dotnet/standard/net-standard?tabs=net-standard-2-1)
Expand Down Expand Up @@ -81,17 +82,17 @@ Kook.Net 目前支持的目标框架包括
### 使用 Visual Studio

- [Visual Studio 2022](https://visualstudio.microsoft.com/zh-hans/vs/) 或更新版本。
- [.NET 6 SDK]
- [.NET 8 SDK]

安装 Visual Studio 期间需选择 .NET 6 工作负载。
安装 Visual Studio 期间需选择 .NET 8 工作负载。

### 使用 JetBrains Rider

- [JetBrains 2021.3](https://www.jetbrains.com.cn/rider/) 或更新版本。
- [.NET 6 SDK]
- [.NET 8 SDK]

### 使用 Command Line

* [.NET 6 SDK]
* [.NET 8 SDK]

[.NET 6 SDK]: https://dotnet.microsoft.com/download
[.NET 8 SDK]: https://dotnet.microsoft.com/download
8 changes: 0 additions & 8 deletions docs/guides/getting_started/samples/async-main.cs

This file was deleted.

32 changes: 13 additions & 19 deletions docs/guides/getting_started/samples/bot-client.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
private KookSocketClient _client;
using KookSocketClient client = new();
client.Log += LogAsync;

public async Task MainAsync()
{
_client = new KookSocketClient();
// 将 Token 写入字符串变量,用于 Bot 登录过程的身份认证
// 这很不安全,尤其是在有公开源代码的情况下,不应该这么做
string token = "token";

_client.Log += Log;
// 一些其它存储 Token 的方案,如环境变量、文件等
// string token = Environment.GetEnvironmentVariable("NameOfYourEnvironmentVariable");
// string token = File.ReadAllText("token.txt");
// string token = JsonConvert.DeserializeObject<AConfigurationClass>(File.ReadAllText("config.json")).Token;

// 将 Token 写入字符串变量,用于 Bot 登录过程的身份认证
// 这很不安全,尤其是在有公开源代码的情况下,不应该这么做
var token = "token";
await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();

// 一些其它存储 Token 的方案,如环境变量、文件等
// var token = Environment.GetEnvironmentVariable("NameOfYourEnvironmentVariable");
// var token = File.ReadAllText("token.txt");
// var token = JsonConvert.DeserializeObject<AConfigurationClass>(File.ReadAllText("config.json")).Token;

await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();

// 阻止程序退出
await Task.Delay(Timeout.Infinite);
}
// 阻塞程序直到关闭
await Task.Delay(Timeout.Infinite);
16 changes: 0 additions & 16 deletions docs/guides/getting_started/samples/project.xml

This file was deleted.

22 changes: 0 additions & 22 deletions docs/guides/getting_started/samples/simple-bot.cs

This file was deleted.

7 changes: 4 additions & 3 deletions docs/guides/getting_started/samples/simple-logging.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
private Task Log(LogMessage msg)
// Log 事件,此处以直接输出到控制台为例
Task LogAsync(LogMessage log)
{
Console.WriteLine(msg.ToString());
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}
}
29 changes: 15 additions & 14 deletions samples/Kook.Net.Samples.Docker/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
};

// 在使用完 Kook.Net 的客户端后,建议在应用程序的生命周期结束时进行 Dispose 操作
KookSocketClient client = new(config);
using KookSocketClient client = new(config);

// 此处列举了 Kook.Net 的 KookSocketClient 的所有事件

Expand Down Expand Up @@ -89,6 +89,20 @@

#endregion

// 令牌(Tokens)应被视为机密数据,永远不应硬编码在代码中
// 在实际开发中,为了保护令牌的安全性,建议将令牌存储在安全的环境中
// 例如本地 .json、.yaml、.xml、.txt 文件、环境变量或密钥管理系统
// 这样可以避免将敏感信息直接暴露在代码中,以防止令牌被滥用或泄露
string token = Environment.GetEnvironmentVariable("KookDebugToken")
?? throw new ArgumentNullException("KookDebugToken");

await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();

// 阻塞程序直到关闭
await Task.Delay(Timeout.Infinite);
return;

// Log 事件,此处以直接输出到控制台为例
Task LogAsync(LogMessage log)
{
Expand Down Expand Up @@ -150,16 +164,3 @@ async Task MessageButtonClickedAsync(string value,
else
Console.WriteLine("接收到了一个没有对应处理程序的按钮值!");
}

// 令牌(Tokens)应被视为机密数据,永远不应硬编码在代码中
// 在实际开发中,为了保护令牌的安全性,建议将令牌存储在安全的环境中
// 例如本地 .json、.yaml、.xml、.txt 文件、环境变量或密钥管理系统
// 这样可以避免将敏感信息直接暴露在代码中,以防止令牌被滥用或泄露
string token = Environment.GetEnvironmentVariable("KookDebugToken")
?? throw new ArgumentNullException("KookDebugToken");

await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();

// 阻塞程序直到关闭
await Task.Delay(Timeout.Infinite);
2 changes: 2 additions & 0 deletions samples/Kook.Net.Samples.FSharp/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ let config =
)

// 在使用完 Kook.Net 的客户端后,建议在应用程序的生命周期结束时进行 Dispose 操作
// 由于 F# 中 use 绑定在模块中被视为 let 绑定,此处使用 let 来创建一个作用域
let client = new KookSocketClient(config)

// Log 事件,此处以直接输出到控制台为例
Expand Down Expand Up @@ -170,5 +171,6 @@ async {

// 阻塞程序直到关闭
do! Task.Delay(Timeout.Infinite) |> Async.AwaitTask
client.Dispose()
}
|> Async.RunSynchronously
102 changes: 45 additions & 57 deletions samples/Kook.Net.Samples.ReactionRoleBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,63 +8,51 @@
using Serilog;
using Serilog.Events;

internal class Program
{
private static async Task Main(string[] args)
{
Log.Information("Starting Kook.Net API Helper");

using IHost host = CreateHostBuilder(args).Build();

await host.RunAsync();
}

private static IHostBuilder CreateHostBuilder(string[] args)
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args)
//.AddGlobalErrorHandler()
.ConfigureServices(ConfigureServices)
.UseSerilog((hostingContext, services, loggerConfiguration) =>
{
loggerConfiguration
.MinimumLevel.Verbose()
//.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", Serilog.Events.LogEventLevel.Warning)
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Verbose);
});
return hostBuilder;
}

/// <summary>
/// 配置服务
/// </summary>
/// <param name="hostContext"></param>
/// <param name="services"></param>
private static void ConfigureServices(HostBuilderContext hostContext, IServiceCollection services)
{
// 根配置
IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false)
.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true, true)
.Build();
Log.Information("Starting Kook.Net API Helper");
using IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
return;

// KOOK Webhook 配置
KookBotConfigurations kookBotConfigurations = new();
configurationRoot.GetSection(nameof(KookBotConfigurations))
.Bind(kookBotConfigurations);

// 服务配置
services
// 静态配置
.AddSingleton(kookBotConfigurations)
static IHostBuilder CreateHostBuilder(string[] args)
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args)
//.AddGlobalErrorHandler()
.ConfigureServices(ConfigureServices)
.UseSerilog((hostingContext, services, loggerConfiguration) =>
{
loggerConfiguration
.MinimumLevel.Verbose()
//.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", Serilog.Events.LogEventLevel.Warning)
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Verbose);
});
return hostBuilder;
}

// KOOK 客户端程序
.AddSingleton<KookBotClientExtension>()
.AddHostedService(p => p.GetRequiredService<KookBotClientExtension>())
.AddSingleton(_ =>
new KookSocketClient(new KookSocketConfig { AlwaysDownloadUsers = true, LogLevel = LogSeverity.Debug }))
.AddHttpClient();
}
static void ConfigureServices(HostBuilderContext hostContext, IServiceCollection services)
{
// 根配置
IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false)
.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true, true)
.Build();

// KOOK Webhook 配置
KookBotConfigurations kookBotConfigurations = new();
configurationRoot.GetSection(nameof(KookBotConfigurations))
.Bind(kookBotConfigurations);

// 服务配置
services
// 静态配置
.AddSingleton(kookBotConfigurations)

// KOOK 客户端程序
.AddSingleton<KookBotClientExtension>()
.AddHostedService(p => p.GetRequiredService<KookBotClientExtension>())
.AddSingleton(_ =>
new KookSocketClient(new KookSocketConfig { AlwaysDownloadUsers = true, LogLevel = LogSeverity.Debug }))
.AddHttpClient();
}
Loading

0 comments on commit 0da3fb7

Please sign in to comment.