Skip to content

feat(adapter): add KOOK protocol adapter - #65

Open
gehongyan wants to merge 3 commits into
nbtca:mainfrom
gehongyan:add-kook
Open

gehongyan wants to merge 3 commits into
nbtca:mainfrom
gehongyan:add-kook

Conversation

@gehongyan

Copy link
Copy Markdown

I noticed that PR #10 was closed.

First off, thanks to @LazuliKao for taking a shot at bringing Kook.Net into HuaJiBot.NET. Since that PR was left unmaintained, I'm going to try implementing this adapter myself as the Kook.Net developer.


Summary

Adds a HuaJiBot.NET.Adapter.Kook adapter that connects KOOK through Kook.Net. It follows the existing adapter contract (BotServiceBase / IAdapterService / event senders) and reuses Kook.Net's strongly-typed SDK, so it carries no protocol DTOs of its own.


What's implemented

Receiving

  • Guild text channel messages go to OnGroupMessageReceived, with the channel ChannelId carried as GroupId.
  • Direct messages go to OnPrivateMessageReceived, with the DM channel ChatCode carried as GroupId.
  • The bot's own messages are ignored to avoid loops.

Sending

  • Text, mentions and links are merged into a single KMarkdown message; images are sent on their own.
  • SendRichMessageAsync sends RichContent.Markdown as raw KMarkdown, so bold, links, mentions and emoji render natively.
  • Images can be sent from either a local path or an HTTP(S) URI.
  • Message recall.

Message parsing / rendering

Parsing and rendering are kept apart because they serve different consumers:

  • KookCommandReader turns a message into structured reader entities for the command system. Mentions keep their user ID, and images are carried as their URL so a command can take one as a Uri argument.
  • KookMessageRenderer turns a message into human-readable text for TextMessage. Plain text and KMarkdown go through Resolve(), card messages are laid out as a Card -> Module -> Element tree, and media is listed with its basic info.

Channel management

  • Renaming a guild channel, plus member-type and nickname lookup.
  • KookChannelId models a channel as an explicit Kind (guild ulong vs direct Guid chatCode) instead of leaving callers to infer it from which parse succeeded. Callers switch on the kind and throw on anything unrecognized.

Questions for maintainers

Both are framework-level and left to core rather than worked around in the adapter.

Note

Question 1: Typed image reader entity.
Received images are currently carried as ReaderText(url), so there's no way to tell an image apart from a URL the user typed. A distinct ReaderImage entity (plus a CommandReader.Image(out url)) in CommonCommandReader would fix this, additive and transparent to other adapters. Worth adding?

Note

Question 2: Direct-message commands.
CommandService only subscribes to OnGroupMessageReceived and ProcessCommand only takes GroupMessageEventArgs, so DM commands don't reach the command system on any adapter. Having it also handle OnPrivateMessageReceived / PrivateMessageEventArgs would enable them everywhere at once. (PrivateMessageEventArgs.Reply is an empty stub, related to the same gap.)


If you have any suggestions for changes, please don't hesitate to raise them!

Copilot AI balanced review requested due to automatic review settings September 29, 2026 06:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Reply ordering, remote image URLs, and mention parsing currently produce incorrect behavior.

Review effort: Balanced
Findings: 3 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Adds a KOOK adapter using Kook.Net, integrating message handling, sending, channel operations, configuration, and CLI startup.

Changes:

  • Adds KOOK message parsing, rendering, sending, replies, images, and recall.
  • Adds guild/direct-channel identification and member lookup.
  • Registers the adapter in configuration, CLI, solution, and documentation.
File Description
src/​HuaJiBot.NET/​Config/​Config.cs Adds KOOK service and token configuration.
src/​HuaJiBot.NET.CLI/​HuaJiBot.NET.CLI.csproj References the adapter and config output settings.
src/​HuaJiBot.NET.CLI/​App.cs Creates and starts the KOOK adapter.
src/​HuaJiBot.NET.Adapter.Kook/​Messaging/​KookTextSegment.cs Models outbound KMarkdown text.
src/​HuaJiBot.NET.Adapter.Kook/​Messaging/​KookOutboundSegment.cs Defines the outbound segment base type.
src/​HuaJiBot.NET.Adapter.Kook/​Messaging/​KookOutboundBuilder.cs Builds text, image, and reply segments.
src/​HuaJiBot.NET.Adapter.Kook/​Messaging/​KookMessageRenderer.cs Renders received messages as readable text.
src/​HuaJiBot.NET.Adapter.Kook/​Messaging/​KookImageSegment.cs Models outbound images.
src/​HuaJiBot.NET.Adapter.Kook/​Messaging/​KookCommandReader.cs Converts KOOK messages into command entities.
src/​HuaJiBot.NET.Adapter.Kook/​KookAdapter.cs Implements lifecycle, events, messaging, and channel operations.
src/​HuaJiBot.NET.Adapter.Kook/​HuaJiBot.NET.Adapter.Kook.csproj Defines the adapter project and Kook.Net dependency.
src/​HuaJiBot.NET.Adapter.Kook/​Channels/​KookChannelKind.cs Distinguishes guild and direct channels.
src/​HuaJiBot.NET.Adapter.Kook/​Channels/​KookChannelId.cs Parses structured KOOK channel identifiers.
README.md Documents the KOOK adapter.
HuaJiBot.NET.slnx Adds the adapter to the solution.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/HuaJiBot.NET.Adapter.Kook/KookAdapter.cs Outdated
Comment on lines +58 to +61
var userMentionTags = message
.Tags.Where(x => x.Type is TagType.UserMention)
.OrderBy(x => x.Index)
.ToArray();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kook.Net 0.11.1 does not populate tags for direct gateway messages or Card messages

This isn't accurate. See:

https://github.com/gehongyan/Kook.Net/blob/5f36295ac99221c5e44932b6737100aa859583d1/src/Kook.Net.WebSocket/Entities/Messages/SocketUserMessage.cs#L131-L136

SocketUserMessage always parses tags for text and KMarkdown messages, whether they come from an ITextChannel or an IDMChannel. Card and attachment messages do not need parsing tags.

Comment on lines +49 to +52
case ReplyMessage { MessageId: var msgId }
when Guid.TryParse(msgId, out var quotedId):
quote = new MessageReference(quotedId);
break;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this point, we should first confirm: can a Reply appear in the middle of a message, meaning that only part of the text is a reply while the rest is not? As far as I understand, it can't. So the presence of a Reply simply means the whole message is a reply.

Please confirm.

/// </summary>
internal static class KookOutboundBuilder
{
public static IReadOnlyList<KookOutboundSegment> Build(IEnumerable<SendingMessageBase> messages)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related unit tests are added.

gehongyan and others added 2 commits September 29, 2026 14:40
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@Yuna-Celisse

Copy link
Copy Markdown
Contributor

We have noted your PR and appreciate your contribution. In my view, the primary issue right now is that our KOOK server is not yet being utilized extensively; its main function currently serves as a platform for voice chat among a subset of members—a use case that neither requires nor supports cross-platform content synchronization via a bot. Before merging this PR, I believe our priority should be to actively build up the server's usage and establish it as a primary communication channel. We welcome more developers to get involved in the operation and maintenance of the relevant code repositories, and I would like to thank you on behalf of NBTCA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants