Repository navigation
Conversation
Opt-in F5 packets for the client side of the Discord integration (sven-n/MuMain#696): - DiscordIntegrationInfoRequest (F5 02) / DiscordIntegrationInfo (F5 03): link state, which chats are bridged, the Rich Presence application and image keys, and the invite link. The request also announces that the client understands ExternalChatMessage. - DiscordLinkCodeRequest (F5 04) / DiscordLinkCode (F5 05): the one-time code to link the account, without typing /discord link. - DiscordUnlinkRequest (F5 06): removes the link; answered with a new DiscordIntegrationInfo. - ExternalChatMessage (F5 07): a message written in Discord, with source, scope (guild, alliance, world) and a sender name which isn't a character. Generated code, docs and packet tests are regenerated from the XML. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
Server side of the new F5 packets: - New feature plugin "Discord integration of the client" with the values the client gets: invite link, Rich Presence application id and image keys. Deactivated, the clients get no integration info. - DiscordIntegrationAction collects the info for a player (configuration, account link, guild and alliance chat bindings, published world chat), creates link codes and unlinks. /discord unlink uses the same unlink. - Handlers for DiscordIntegrationInfoRequest, DiscordLinkCodeRequest and DiscordUnlinkRequest, and the DiscordIntegrationViewPlugIn which sends the answers. - Messages of senders bridged from Discord (the '@' prefix, now shared as BridgedChatSender) go to clients which asked for the integration info as ExternalChatMessage: guild and alliance chat in ChatViewPlugIn, world chat in GameServer. Other clients keep getting them as before. - Docs: the game client section of the Discord page and the plugin page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
Deploying openmudocs with
|
| Latest commit: |
b4b567d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0be7af58.openmudocs.pages.dev |
| Branch Preview URL: | https://claude-discord-presence-muma.openmudocs.pages.dev |
This was referenced Oct 10, 2026
sven-n
commented
Oct 10, 2026
sven-n
left a comment
Member
Author
There was a problem hiding this comment.
Part of a review pass over the whole Discord stack, including the four client PRs in sven-n/MuMain that consume these packets.
I cross-checked the wire format against the client implementation in sven-n/MuMain#709 (src/source/Network/Discord/DiscordPackets.cpp) and the two agree exactly — ExternalChatMessage at 5/6/7+48/55, DiscordIntegrationInfo at 4/5/6/7/8+20/28+32/60+32/92+100/192+32, DiscordLinkCode at 4/5+10/15. No offset drift.
A few format details I checked and am satisfied with:
ExternalChatMessagecorrectly usesC2HeaderWithSubCode, so a long bridged message isn't constrained to a fixed field.- The capability handshake — the client announcing support by sending
DiscordIntegrationInfoRequest, with older clients continuing to get prefixed normal chat messages — is a clean way to avoid a client version bump, andChatViewPlugInfalls back correctly when the view plug-in isn't present. LinkedUserNameat 32 bytes exactly matches Discord's maximum username length, so a full-length name leaves no room for a terminator and ends flush with the end of the 224-byte packet. I checked the client side and it's safe —ReadStringpasses an explicitmaxSourceLengthtoMultiByteToWideCharand terminates the buffer itself, so there's no overread. Worth being aware of if another client implementation ever reads these fields as C strings.
Three comments inline. The two about client-triggered database work are the ones I'd act on; the third is a shared invariant that also affects #1161.
Generated by Claude Code
sven-n
pushed a commit
to sven-n/MuMain
that referenced
this pull request
Oct 10, 2026
Says next to IsBridgedSender that the prefix is a convention of the server (enforced in character names by MUnique/OpenMU#1174), not something the client can verify, and that ExternalChatMessage is the authoritative signal where the server sends it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
- The guild and alliance chat bindings of a player are read with a
targeted query (IPlayerContext.GetGuildChatBindingsAsync) instead of
loading the whole binding table on every integration info request; the
login notice of M5 uses it too.
- A player gets a new link code at most every 30 seconds
(DiscordIntegrationAction.LinkCodeCooldown). Every code costs a database
write and replaces the previous one, so a client asking in a loop
neither loads the database nor keeps invalidating the code the player
looks at. DiscordLinkCode gets the result TooSoon for it.
- Character names can't contain the prefix of bridged senders ('@'),
independently of the configured name expression, so no character can
pass for a Discord user in the game or in the clients.
- The packet docs say what IsWorldChatBridged means: the world chat is
published; the server can't tell whether the bot has a channel for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
sven-n
pushed a commit
to sven-n/MuMain
that referenced
this pull request
Oct 10, 2026
OpenMU hands out a new link code at most every 30 seconds and answers in between with the new DiscordLinkCode result TooSoon (MUnique/OpenMU#1174). The client tells the player to wait a moment instead of claiming that linking isn't available; an unknown result still reads as not available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
…ce-mumain-597-l2q09d-packets Master moved the validation of character names to CharacterInitialization.IsValidCharacterName, which rejects the prefix of bridged senders now. The unlinking uses AccountLinkService.UnlinkAccountAsync, which publishes the AccountUnlinkedEvent itself since M5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Packet definitions and server side for the client part of the Discord integration (sven-n/MuMain#696: C2 integration info, C3 link dialog, C4 messages from Discord, C5 guild window). Stacked on #1161 (M5).
Packets (opt-in
F5group, like the chat command list)C1 F5 02DiscordIntegrationInfoRequestExternalChatMessage.C1 F5 03DiscordIntegrationInfoC1 F5 04DiscordLinkCodeRequestC1 F5 05DiscordLinkCodeSuccess/NotAvailable), the one-time code (ABCD-EFGH), minutes valid.C1 F5 06DiscordUnlinkRequestDiscordIntegrationInfo.C2 F5 07ExternalChatMessageDiscord), scope (Guild/Alliance/World), sender name (48 bytes, not a character), message (variable).The generated structs, connection extensions, packet docs and packet tests were regenerated from the XML; nothing outside the new packets changed.
Server
Feature plugin Discord integration of the client (
DiscordIntegrationFeaturePlugIn). Its configuration holds what the client gets:InviteUrl,RichPresenceApplicationId,RichPresenceLargeImageKey,RichPresenceSmallImageKey. These are no secrets, since every player can read them. When the plugin is deactivated, clients get an empty integration info and hide their Discord features.DiscordIntegrationActioncollects the info for a player:AccountLinkService(M4)GuildChatBridgeNoticePlugInIt also creates link codes and unlinks.
/discord unlinknow uses the same unlink, including theAccountUnlinkedEvent.Handlers and view: handlers for
F5 02/04/06in the existingF5group, plusDiscordIntegrationViewPlugIn. All haveMinimumClient(106, 3, Invariant), like the chat command list.Messages from Discord: senders bridged from Discord (the
@prefix of M5, now shared asBridgedChatSender;DiscordChatText.DiscordSenderPrefixrefers to it) are sent asExternalChatMessageto clients that asked for the integration info:ChatViewPlugIn, with the chat prefix@/@@removedGameServer.WorldChatMessageAsyncAll other clients get exactly what they got before.
Docs: a "The game client" section on the Discord page, and the plugin on the plugins page.
Limitations
ExternalChatMessageis the linked character, as in M5. The Discord display name isn't carried throughIEventPublisheryet. The field has room for it (48 bytes).GuildChatBindingdoesn't store it.Tests
DiscordIntegrationViewPlugInTest: packet contents; link code results; external chat only after the client asked; bridged guild message →ExternalChatMessagefor supporting clients and unchanged chat for others; character messages stay normal chat.DiscordIntegrationActionTest: deactivated plugin → no info; configuration and link state; unlink.dotnet test: all green (3,377 passed, 11 skipped).Not tested: against a real Discord bot, or a running MuMain client.
The MuMain side follows as a draft PR in sven-n/MuMain. It needs these packets released in
MUnique.OpenMU.Network.Packetsbefore it can build in CI.🤖 Generated with Claude Code
https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
Generated by Claude Code