majesticdev / commandnet-discord-plugin
Multi-guild Discord bridge for the Command Net plugin suite: syncs Discord roles, usernames, slash commands and notifications across one connection per unit's Discord server.
Package info
github.com/Spearhead-Gaming/commandnet-discord-plugin
Type:forumify-plugin
pkg:composer/majesticdev/commandnet-discord-plugin
Requires
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpstan/phpstan-symfony: ^2.0
- slevomat/coding-standard: *
- squizlabs/php_codesniffer: ^3.13
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-23 11:03:15 UTC
README
A forumify plugin bridging the Command Net suite to Discord — across as many Discord servers as your community has, not just one. Every unit can get its own private server with its own role mapping and its own notification channels, instead of everyone sharing one community-wide bot connection.
Built for a specific MILSIM community's forumify install, using forumify's own forumify-discord-plugin as reference material while building this from scratch; not a general-purpose skeleton, and not a redistribution of that plugin.
Requirements
- PHP 8.4 or newer
- A forumify 1.2.x install
- MySQL (for the migration in
migrations/) commandnet-discord-botrunning somewhere reachable from this install — this plugin talks to it over HTTP, it doesn't run a Discord gateway connection itself
Install
composer require majesticdev/commandnet-discord-plugin
Then, from the forumify install:
bin/console forumify:plugins:refresh bin/console forumify:plugins:activate majesticdev/commandnet-discord-plugin bin/console doctrine:migrations:migrate
Entities
| Entity | Notes |
|---|---|
DiscordConnection |
One Discord server the bot has been invited to — the community server, or a unit's private one. Guild ID, invite link, ops-log/announcements channel IDs, active flag. |
DiscordRoleMapping |
"Grant/revoke this Discord role whenever a user gains/loses this forumify Role" — scoped to one DiscordConnection, so the same forumify Role can map to a different Discord role in each unit's server. |
Admin
- Admin → Command Net Discord → Connections — CRUD for
DiscordConnection, with an embedded, add/remove-able role-mapping table per connection. - Admin → Command Net Discord → Settings — bot-wide toggles only (force-connect Discord account, force users to join the server, sync display names, calendar cross-posting). Invite links and role mapping used to live here too; they moved to per-connection once a single server stopped being a safe assumption.
Permissions
Checked as discord.<area>.<action>.
| Permission | Grants |
|---|---|
discord.admin.connections.view / .manage |
View / create, edit, and delete Discord connections and their role mappings. |
Design notes
- One bot, many connections.
commandnet-discord-botis a single Discord application invited into every server; this plugin never assumes a guild ID, it always asks "which connections apply" and loops. BotService::updateRoles()/updateUsername()walk every active connection themselves rather than being told which one — callers (UpdateUserListener, etc.) never changed when this went from single-server to multi-server.BotService::postAnnouncement()is the generic "tell every server's announcements channel something happened" primitive.CalendarEventListeneruses it for calendar cross-posting today; it's the seam future plugins (id-card, a server manager, S3 tools) are meant to build on instead of talking to the bot directly.PatrolAnnouncementListenercross-posts a Command Net patrol the same way, on itsOperationpostPersist rather than a calendar event, so a patrol announces once whether it's posted from the web or from/command-net-patrol-create.DiscordPatrolReminderNotifierdecorates Command Net's ownPatrolReminderNotifier(a no-op there without this plugin) to also echo the AAR due/overdue reminder to Discord,@mention-ing the leader. Both referenceMajesticDev\CommandNet\*classes the same un-required wayCalendarEventListenerreferences the calendar plugin's - see phpstan.neon's matching ignore rule.- Discord role IDs are pasted, not fetched live.
DiscordRoleMappingTypeuses a plain text field with instructions (enable Developer Mode, right-click the role, copy ID) rather than a live dropdown from the bot — editing a mapping doesn't require the bot to be online.
Known gaps
- No automated test coverage. The
tests/harness boots (CI runs it green), buttests/Tests/only has the kernel bootstrap — no actual test classes exist yet. commandnet-discord-botself-registers viaPOST /api/discord/register-bot- the/apiprefix comes fromapi_platform's routing config (everyApiResource, including this plugin's, lives under it), not from anything in this plugin's own code, so it's easy to miss when readingDiscordRegistration'suriTemplatealone.- The connections form's role-mapping table has no add/remove UI wired up.
assets/dist/settings_form_controller.jsused to power exactly this for the old single-connection settings form; it's currently unused rather than repointed at the new per-connection form. JoinServerController's default connection is "whichever was created first" (no explicit "this is the community server" flag) — fine with one extra connection, would want a real default marker once several units have their own.
Works well with
commandnet-plugin—Unitcarries an optionaldiscordGuildIdfor future per-unit notifications (AWOL alerts, operation posts), matched against aDiscordConnectionhere by guild ID.commandnet-discord-bot— the bot process this plugin talks to. See its README for the full HTTP contract.forumify-id-card-plugin— reaches intoForumify\Discord's (nowMajesticDev\Discord's) command interface for its own Discord slash commands the same waycommandnet-plugindoes.