Repository navigation
Conversation
/world sends a message to all players of all game servers. The game event publisher plugin publishes the guild, alliance and world chat as ChatMessageEvent, if it's enabled in its configuration (off by default). Players are told when they enter the game, if the chat of their guild or alliance is mirrored to Discord. The Dapr event publisher moves to the common project, so that the Discord host can send chat messages, too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Guild masters bind the chat of their guild or alliance to a channel: on the Discord server of the game server with /guildchat create, or on their own Discord server with /guildchat bind. Linked users write into the game with /say, or with normal messages when the Message Content intent is enabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Deploying openmudocs with
|
| Latest commit: |
946d179
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d159a393.openmudocs.pages.dev |
| Branch Preview URL: | https://claude-sweet-cray-w8crms-m5.openmudocs.pages.dev |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
sven-n
pushed a commit
to sven-n/MuMain
that referenced
this pull request
Oct 10, 2026
OpenMU's chat bridge (MUnique/OpenMU#1161) sends messages written in Discord through the ordinary guild and alliance chat, as the linked character with an '@' prefix that no character name can contain. Such lines are now shown through UI::Chat::External as messages from Discord: "[Discord] <name>" in the guild or alliance colours, in the Discord view, and not whisperable. This makes the C4 display path work against OpenMU today, without waiting for a dedicated packet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
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. This is the biggest PR of the set and the one with the most to go wrong, so there are more comments here than elsewhere — most are about load rather than correctness.
What I checked specifically and found sound:
- No feedback loop. Discord → game goes through
IEventPublisher, which has noPlayer, so it can't re-triggerIChatMessageSentPlugInand bounce back to the channel. I went looking for this because the echo inSayAsyncplus the mirror inOnChatMessageAsyncis exactly the shape that loops, and it doesn't. - Authorisation on binding.
GetBindableTargetAsyncrequires the selected character of the linked account to be the guild master, and the alliance case additionally requires the guild to be the alliance master. Combined with M4's ownership check inSelectCharacterAsync, a user can't bind a guild they don't lead. - Posting permission. Linked account, character still on that account, chat ban honoured, and guild/alliance membership verified against the binding. The world-chat case skips the membership check, which is correct for world chat.
- Mention and markdown handling.
ToGamestrips mentions including@everyone/@here, converts custom emoji to:name:, drops markdown and collapses whitespace;ToDiscordescapes in the other direction and every send usesAllowedMentions.None. - Degradation without the privileged intent.
ReadMessagesdefaults off and/saycovers the same ground, which is the right default given Discord only grantsMessageContentto verified bots.
The one finding I'd treat as blocking is the IChatMessageReceivedPlugIn bypass — a server's own chat filter not applying to the Discord side seems like something an operator would consider a bug. The flood-guard ordering is a close second because it's a two-line move for a real saving.
Generated by Claude Code
…rms-m5 The migration AddGuildChatBinding is generated again, so that it follows the migrations of master and of M4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
…e chat bridge - Messages from Discord go through the new plugin point IExternalChatMessageReceivedPlugIn, e.g. for filters, because the IChatMessageReceivedPlugIn needs a sending player. - The flood limit is checked first, so spamming doesn't cause database queries, and inactive users are forgotten. - The linked members of a guild are determined with two queries instead of two per member (new batched queries in IPlayerContext). - A hosted channel respects the limit of 500 permission overwrites and tells when not all members fit. - The bindings are reloaded every tenth tick instead of every tick, and removing bindings without channel or server is refused. - /say echoes exactly the message which the game got. - The distributed admin panel publishes the unlinking of accounts. - The intents are documented correctly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
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.
Milestone M5 of the Discord integration (#1103). Stacked on #1160 (M4).
What
/world <message>reaches all players of all game servers. The chat ban andIChatMessageReceivedPlugInapply.IEventPublisher.WorldChatMessageAsyncandIGameServer.WorldChatMessageAsync, implemented in-memory, as a Dapr topic and inGameServerClient.[World] Name: text.isWorldChat(#world-chat).GameEventPublisherPlugInpublishes a newChatMessageEventfor guild, alliance and world chat.IEventPublisher.GuildMessageAsync/AllianceMessageAsync/WorldChatMessageAsync.EventPublishermoved from GameServer.Host to Dapr.Common, so the Discord host can use it. In the all-in-one deployment,GameServerContainer.EventPublisheris registered asIEventPublisherand resolved lazily, because the publisher depends on the bot.GuildChatBindingwith persistent guild id, scope (guild/alliance), provider, Discord server and channel id, hosted flag, bound by and bound at. It has a migration and compiled models, and an FK toGuildwith cascade, so disbanding a guild removes its bindings (M5.8). Operator settingsBot__ChatBridge__*:BindingMode(Both/HostedOnly/GuildOwnedOnly),AllowedDiscordServerIds,ReadMessages,MaximumMessageLength,MaximumMessagesPerMinute./guildchat create [scope]creates#guild-<name>/#alliance-<name>in the new Guilds category of the layout. Only the bot and linked guild members can see it. Visibility uses per-user channel overwrites, synchronized after linking and every 10 status ticks./guildchat bind [scope]in the channel. That requires Manage Channel on that channel; the allowlist applies. Players get a localized in-game notice at login when their guild or alliance chat is mirrored (newGuildChatBridgeNoticePlugIn).Account.ChatBanUntilapplies, plus a flood limit (10 messages per minute by default).<:name:id>becomes:name:, markdown and line breaks are stripped, and the length is capped. The sender is@plus the character name, at most 10 characters, so Discord users can't impersonate characters./say <message>works in any bridged channel.ReadMessagesthe bot uses the Message Content intent and reads normal messages, and reacts with ❌ when one is rejected./guildchat unbindis used (by the guild master or by users who manage the channel), or the guild is disbanded. When the guild master changes, the binding stays, but only the current master can change it.IGuildServerContext.GetGuildMembershipAsync(characterName)andIPlayerContext.GetAccountChatBanUntilAsync(accountId), for EF and in-memory.Deviations from the issue, for discussion
/discord bindguild+/bind <code>became/guildchat bind./sayinstead of/gand/w. It works in any bridged channel./guildchat create.Tests
DiscordChatBridgeTest, against the in-memory DB with guilds, an alliance and linked users. It covers:DiscordChatTextTest(sanitizing),DiscordChatQueueTest(batching).GameEventPublisherPlugInTest: chat publishing on and off, the/worldcommand, and the chat ban for it.GuildMembershipQueryTest: EF query translation.Not tested: against a real Discord bot or server, and the migration against a real PostgreSQL database.
🤖 Generated with Claude Code
https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Generated by Claude Code