diff --git a/samples/Kook.Net.Samples.Docker/.dockerignore b/samples/Kook.Net.Samples.Docker/.dockerignore
new file mode 100644
index 00000000..2f32bfe4
--- /dev/null
+++ b/samples/Kook.Net.Samples.Docker/.dockerignore
@@ -0,0 +1,25 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
diff --git a/samples/Kook.Net.Samples.Docker/Dockerfile b/samples/Kook.Net.Samples.Docker/Dockerfile
new file mode 100644
index 00000000..0f85e7c0
--- /dev/null
+++ b/samples/Kook.Net.Samples.Docker/Dockerfile
@@ -0,0 +1,18 @@
+FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY ["Kook.Net.Samples.Docker/Kook.Net.Samples.Docker.csproj", "Kook.Net.Samples.Docker/"]
+RUN dotnet restore "Kook.Net.Samples.Docker/Kook.Net.Samples.Docker.csproj"
+COPY . .
+WORKDIR "/src/Kook.Net.Samples.Docker"
+RUN dotnet build "Kook.Net.Samples.Docker.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "Kook.Net.Samples.Docker.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "Kook.Net.Samples.Docker.dll"]
diff --git a/samples/Kook.Net.Samples.Docker/Kook.Net.Samples.Docker.csproj b/samples/Kook.Net.Samples.Docker/Kook.Net.Samples.Docker.csproj
new file mode 100644
index 00000000..5d79da3e
--- /dev/null
+++ b/samples/Kook.Net.Samples.Docker/Kook.Net.Samples.Docker.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
diff --git a/samples/Kook.Net.Samples.Docker/Program.cs b/samples/Kook.Net.Samples.Docker/Program.cs
new file mode 100644
index 00000000..3f6f6822
--- /dev/null
+++ b/samples/Kook.Net.Samples.Docker/Program.cs
@@ -0,0 +1,165 @@
+// See https://aka.ms/new-console-template for more information
+
+// KookSocketConfig 是 KookSocketClient 的配置类
+
+using Kook;
+using Kook.WebSocket;
+
+KookSocketConfig config = new()
+{
+ AlwaysDownloadUsers = false,
+ AlwaysDownloadVoiceStates = false,
+ AlwaysDownloadBoostSubscriptions = false,
+ MessageCacheSize = 100,
+ LogLevel = LogSeverity.Debug
+};
+
+// 在使用完 Kook.Net 的客户端后,建议在应用程序的生命周期结束时进行 Dispose 操作
+KookSocketClient client = new(config);
+
+// 此处列举了 Kook.Net 的 KookSocketClient 的所有事件
+
+#region BaseKookClient
+
+client.Log += LogAsync;
+client.LoggedIn += () => Task.CompletedTask;
+client.LoggedOut += () => Task.CompletedTask;
+
+#endregion
+
+#region KookSocketClient
+
+client.Connected += () => Task.CompletedTask;
+client.Disconnected += exception => Task.CompletedTask;
+client.Ready += ReadyAsync;
+client.LatencyUpdated += (before, after) => Task.CompletedTask;
+
+#endregion
+
+#region BaseSocketClient
+
+client.ChannelCreated += channel => Task.CompletedTask;
+client.ChannelDestroyed += channel => Task.CompletedTask;
+client.ChannelUpdated += (before, after) => Task.CompletedTask;
+
+client.ReactionAdded += (message, channel, user, reaction) => Task.CompletedTask;
+client.ReactionRemoved += (message, channel, user, reaction) => Task.CompletedTask;
+client.DirectReactionAdded += (message, channel, user, reaction) => Task.CompletedTask;
+client.DirectReactionRemoved += (message, channel, user, reaction) => Task.CompletedTask;
+
+client.MessageReceived += MessageReceivedAsync;
+client.MessageDeleted += (message, channel) => Task.CompletedTask;
+client.MessageUpdated += (before, after, channel) => Task.CompletedTask;
+client.MessagePinned += (before, after, channel, @operator) => Task.CompletedTask;
+client.MessageUnpinned += (before, after, channel, @operator) => Task.CompletedTask;
+
+client.DirectMessageReceived += (message, author, channel) => Task.CompletedTask;
+client.DirectMessageDeleted += (message, author, channel) => Task.CompletedTask;
+client.DirectMessageUpdated += (before, after, author, channel) => Task.CompletedTask;
+
+client.UserJoined += (user, time) => Task.CompletedTask;
+client.UserLeft += (guild, user, time) => Task.CompletedTask;
+client.UserBanned += (users, @operator, guild, reason) => Task.CompletedTask;
+client.UserUnbanned += (users, @operator, guild) => Task.CompletedTask;
+client.UserUpdated += (before, after) => Task.CompletedTask;
+client.CurrentUserUpdated += (before, after) => Task.CompletedTask;
+client.GuildMemberUpdated += (before, after) => Task.CompletedTask;
+client.GuildMemberOnline += (users, time) => Task.CompletedTask;
+client.GuildMemberOffline += (users, time) => Task.CompletedTask;
+
+client.UserConnected += (user, channel, time) => Task.CompletedTask;
+client.UserDisconnected += (user, channel, time) => Task.CompletedTask;
+
+client.RoleCreated += role => Task.CompletedTask;
+client.RoleDeleted += role => Task.CompletedTask;
+client.RoleUpdated += (before, after) => Task.CompletedTask;
+
+client.EmoteCreated += (emote, guild) => Task.CompletedTask;
+client.EmoteDeleted += (emote, guild) => Task.CompletedTask;
+client.EmoteUpdated += (before, after, guild) => Task.CompletedTask;
+
+client.JoinedGuild += guild => Task.CompletedTask;
+client.LeftGuild += guild => Task.CompletedTask;
+client.GuildUpdated += (before, after) => Task.CompletedTask;
+client.GuildAvailable += guild => Task.CompletedTask;
+client.GuildUnavailable += guild => Task.CompletedTask;
+
+client.MessageButtonClicked += MessageButtonClickedAsync;
+client.DirectMessageButtonClicked += (value, user, message, channel) => Task.CompletedTask;
+
+#endregion
+
+// Log 事件,此处以直接输出到控制台为例
+Task LogAsync(LogMessage log)
+{
+ Console.WriteLine(log.ToString());
+ return Task.CompletedTask;
+}
+
+// Ready 事件表示客户端已经建立了连接,现在可以安全地访问缓存
+Task ReadyAsync()
+{
+ Console.WriteLine($"{client.CurrentUser} 已连接!");
+ return Task.CompletedTask;
+}
+
+// 并不建议以这样的方式实现 Bot 的命令交互功能
+// 请参阅 Kook.Net.Samples.TextCommands 示例项目及其文档
+async Task MessageReceivedAsync(SocketMessage message,
+ SocketGuildUser author,
+ SocketTextChannel channel)
+{
+ // Bot 永远不应该响应自己的消息
+ if (author.Id == client.CurrentUser.Id)
+ return;
+
+
+ if (message.Content == "!ping")
+ {
+ // 创建一个 CardBuilder,卡片将会包含一个文本模块和一个按钮模块
+ CardBuilder builder = new CardBuilder()
+ .AddModule(s =>
+ s.WithText("pong!"))
+ .AddModule(a => a
+ .AddElement(b => b
+ .WithClick(ButtonClickEventType.ReturnValue)
+ .WithText("点我!")
+ .WithValue("unique-id")
+ .WithTheme(ButtonTheme.Primary)));
+
+ // 发送一条卡片形式的消息,内容包含文本 pong!,以及一个按钮
+ // 在调用时,需要先调用 .Build() 方法来构建卡片
+ await channel.SendCardAsync(builder.Build());
+ }
+}
+
+// 当按钮被点击时,会触发 MessageButtonClicked 事件
+async Task MessageButtonClickedAsync(string value,
+ Cacheable user,
+ Cacheable message,
+ SocketTextChannel channel)
+{
+ // 检查按钮的值是否为之前的代码中设置的值
+ if (value == "unique-id")
+ {
+ IMessage messageEntity = await message.GetOrDownloadAsync();
+ if (messageEntity is IUserMessage userMessage)
+ await userMessage.ReplyTextAsync("按钮被点击了!", isQuote: true);
+ }
+
+ 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);
diff --git a/src/Kook.Net.sln b/src/Kook.Net.sln
index 1ec11487..961f47f0 100644
--- a/src/Kook.Net.sln
+++ b/src/Kook.Net.sln
@@ -32,7 +32,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Experimental", "Experimenta
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kook.Net.Experimental", "Kook.Net.Experimental\Kook.Net.Experimental.csproj", "{95881FD6-BAF5-43A6-9D75-9F9181FC9D21}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{55F21D65-BA1A-4A4B-B370-FD2CA0204EB2}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "SolutionItems", "{55F21D65-BA1A-4A4B-B370-FD2CA0204EB2}"
ProjectSection(SolutionItems) = preProject
Kook.Net.targets = Kook.Net.targets
EndProjectSection
@@ -41,6 +41,8 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Kook.Net.Samples.VisualBasi
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Kook.Net.Samples.FSharp", "..\samples\Kook.Net.Samples.FSharp\Kook.Net.Samples.FSharp.fsproj", "{DCD692BE-9D91-4DE1-8603-CD8923C59B6A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kook.Net.Samples.Docker", "..\samples\Kook.Net.Samples.Docker\Kook.Net.Samples.Docker.csproj", "{B8A50094-A959-4807-AB36-6256A1EEEE61}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -99,6 +101,10 @@ Global
{DCD692BE-9D91-4DE1-8603-CD8923C59B6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCD692BE-9D91-4DE1-8603-CD8923C59B6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCD692BE-9D91-4DE1-8603-CD8923C59B6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B8A50094-A959-4807-AB36-6256A1EEEE61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B8A50094-A959-4807-AB36-6256A1EEEE61}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B8A50094-A959-4807-AB36-6256A1EEEE61}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B8A50094-A959-4807-AB36-6256A1EEEE61}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57F76D67-0E55-4609-A61C-3A4C3073CA0E} = {58B166AB-82DD-472F-AC70-9318DA4F881E}
@@ -112,5 +118,6 @@ Global
{95881FD6-BAF5-43A6-9D75-9F9181FC9D21} = {BB39595B-5DCE-4B1C-8740-A013C261E317}
{43C91115-7D09-4A28-95CD-380C3B00EAE0} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
{DCD692BE-9D91-4DE1-8603-CD8923C59B6A} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
+ {B8A50094-A959-4807-AB36-6256A1EEEE61} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
EndGlobalSection
EndGlobal