Farmstead Mods
EN

Discord-интеграция

Мосты SDLink / Mc2Discord / DCIntegration, вебхуки без мода-моста, синхронизация ролей в группы прав.

На этой странице

Как устроена интеграция#

NeoEssentials сам в Discord не ходит за ботом: нет своего токена и gateway. Есть четыре независимых куска:

  1. Мосты чата и событий — адаптеры SDLink, Mc2Discord, DCIntegration. Забирают уже связанный аккаунт у моста и шлют чат / join / leave / mute / AFK / advancement / личку в Discord.
  2. Вебхуки — WebhookAdapter: обычный HTTPS POST на URL вебхука канала. Мост не нужен. Только Minecraft → Discord.
  3. Роли Discord → группа Minecraft — discordrolesync.json и DCIntegration. Пишет во внутренний менеджер прав NeoEssentials.
  4. Вход в веб-панель по Discord — discord_auth.json (roleMapping на ADMIN/MODERATOR/VIEWER). Это роли панели, не игрока на сервере. Команды панели (/dashboardregister discord, /linkaccount) на этой странице не разбираются.

Команд линковки у NeoEssentials нет. Игрок связывает Minecraft и Discord командами моста.

Мосты#

Порядок регистрации: SDLink → Mc2Discord → DCIntegration → Generic Webhook. Событие рассылается всем готовым адаптерам.

Мост Чат и события Линк аккаунта Список ролей Discord
Simple Discord Link (SDLink) да да нет (публичное API не отдаёт)
Mc2Discord да да нет
DCIntegration да, в основном явный channelId да да
Generic Webhook да, если задан webhookUrl нет нет

Чат настраивается внутри канала NeoEssentials: chat.channels.<имя>.discord.enabled / channelId / webhookUrl. Не-чат — сверху config.json в discordEventChannels. Команды каналов (/g, /staff, …) принадлежат системе каналов чата, не этой странице.

modules.discordIntegrationEnabled: false на старте пропускает ChatIntegrationManager.initialize() — не будет ни мостов, ни вебхуков, пока не включите флаг и не перезапустите.

Вебхуки#

URL берётся из того же webhookUrl рядом с channelId. Вебхук, добавленный через /neoe reload, начинает работать без рестарта — адаптер не кеширует URL. Поставить сам мод-мост по-прежнему можно только рестартом.

Обратного направления нет, ролей и линка нет, sendToChannel по snowflake id вебхук не умеет (дашбордный «test message» через него не уйдёт).

Двойная отправка#

У SDLink и Mc2Discord есть свой релей, параллельный NeoEssentials. На старте NeoEssentials читает их toml и для default-route глушит свои дубли, оставляя WARN. Явный channelId не глушится. Смена toml без рестарта детектор не перечитывает.

Роли → группы#

Файл discordrolesync.json, задача DiscordRoleSyncTask. Snowflake роли → имя группы из permissions.json. Несколько ролей — побеждает группа с большим priority. Только повышение. Только внутренний PermissionManager. Только DCIntegration как источник ролей. Обход по таймеру + сразу на join.

Примеры#

Чат в Discord без бота: webhook на канал global#

Прокинуть глобальный чат в Discord, не ставя SDLink / Mc2Discord / DCIntegration.

/neoe reload

В config.json → chat.channels.global.discord поставьте enabled: true и webhookUrl (Server Settings → Integrations → Webhooks → Copy Webhook URL). channelId бота можно оставить пустым. /neoe reload достаточно: WebhookAdapter перечитывает URL на каждое событие и регистрируется при старте даже без URL. В Discord сообщение придёт от имени игрока с аватаром mc-heads.net. Обратного моста Discord → Minecraft у вебхука нет.

Заходы и выходы в отдельный канал-лог#

Чат оставить в одном Discord-канале, join/leave отправить в другой — без привязки к каналу NeoEssentials.

/neoe reload

Секция discordEventChannels в config.json: у join и leave поставьте enabled: true и либо channelId (нужен мост-бот), либо webhookUrl, либо оба — тогда сработают независимо. Ключи событий: join, leave, mute, afk, advancement, privateMessage. Пустой channelId при enabled: false означает «пусть мост шлёт в свой родной канал». После reload вебхук подхватывается сразу; смена самого моста (поставили SDLink) требует рестарта — адаптеры создаются только в ChatIntegrationManager.initialize() на старте.

Роль Discord → группа permissions.json#

Игрок с ролью Boosters на Discord должен попасть в группу vip внутреннего менеджера прав.

/neoe reload

В discordrolesync.json → discordRoleSync включите enabled, пропишите snowflake роли в roleGroupMap на имя группы из permissions.json. Нужны DCIntegration (только он отдаёт список ролей) и внутренний PermissionManager: при LuckPerms/FTB Ranks PermissionAPI.isUsingExternal() — синк молча выходит. Игрок должен быть связан с Discord командами DCIntegration, не NeoEssentials. На join синк идёт сразу; периодический обход (intervalSeconds, по умолчанию 300) стартует только если флаг был true в момент ServerStartedEvent. Если включили впервые уже на работающем сервере — перезайдите персонажем или перезапустите сервер, иначе таймер не поднимется.

Убрать удвоенный префикс и двойные join-сообщения#

В Discord [Owner] [Owner] Name или два «joined» подряд: родной релей моста плюс NeoEssentials.

/neoe reload

На старте адаптер читает config/simple-discord-link/simple-discord-link.toml или config/mc2discord.toml и пишет WARN в лог (и в GET /api/discord/status → nativeRelayWarnings): какие события он сам слать не будет. Явный channelId в chat.channels.*.discord или discordEventChannels всё равно уходит — считается, что это другой канал. Чтобы единственным источником был NeoEssentials, выключите родной релей моста (chat.playerMessages / playerJoin / … у SDLink; подписки канала у Mc2Discord) и перезапустите. /neoe reload конфиг моста заново не парсит.

Тонкие места#

Вебхук тоже требует `modules.discordIntegrationEnabled`#

Хотя вебхуку не нужен мод-мост, WebhookAdapter попадает в список только из ChatIntegrationManager.initialize(), а тот вызывается при старте лишь если modules.discordIntegrationEnabled в config.json истинен. При false не будет ни ботов, ни вебхуков. Сам адаптер при пустом URL просто idle и начнёт постить после /neoe reload, когда URL появится — но только если его уже зарегистрировали на старте.

Что делать: Оставляйте discordIntegrationEnabled: true, даже если стоите только на webhookUrl. Смену модуля — рестартом.

Два разных файла синхронизации ролей#

discordrolesync.json — DiscordRoleSyncTask: Discord role id → имя группы permissions.json, побеждает группа с большим priority, никогда не понижает, роли читает только DCIntegration.

discord_auth.json → permissionSync — DiscordPermissionSync на join (задержка 1 с): те же роли через ChatIntegrationManager.findDiscordRoleIds (опять DCIntegration), но маппинг permissionMappings в коде берёт первый элемент списка как имя группы, остальные элементы игнорируются. Комментарий файла обещает узлы вроде neoessentials.* / neoessentials.kick. Поставка как раз кладёт узлы в этот список — setGroup("neoessentials.*") запишет несуществующую группу.

roleMapping / roleHierarchy в том же файле — это роли веб-панели (ADMIN/MODERATOR/VIEWER), не Minecraft.

Что делать: Группы игроков на сервере настраивайте в discordrolesync.json. permissionMappings не используйте как список узлов — либо выключите permissionSync.enabled, либо поставьте первым элементом реальное имя группы. Два синка сразу не включайте: оба пишут user.setGroup на join.

Список ролей есть только у DCIntegration#

SDLinkAdapter.getDiscordRoleIds всегда возвращает пустой список (публичное API SDLink не отдаёт роли). Mc2Discord использует default адаптера — тоже пусто. ChatIntegrationManager.findDiscordRoleIds берёт первый непустой ответ. Без DCIntegration и role-sync, и permissionSync на join не увидят ни одной роли (линк аккаунта при этом SDLink/Mc2Discord отдать могут).

Что делать: Синхронизация ролей → группы: ставьте DCIntegration и линкуйте аккаунт его командой. Для одного только чата достаточно SDLink, Mc2Discord или вебхука.

Синк только повышает группу и не пишет во внешний плагин#

DiscordRoleSyncTask выходит, если PermissionAPI.isUsingExternal(). Игрока без совпавшей роли не трогает — админская группа не сбросится в default, когда буст в Discord кончится. Интервал таймера фиксируется в start() на старте сервера: правка intervalSeconds подхватывается после рестарта, карта ролей из файла читается каждый проход (после /neoe reload cache конфигов сброшен).

Что делать: Понижение делайте вручную. С LuckPerms синхронизируйте роли их Discord-плагином, не этим файлом. Смену периода обхода — рестартом.

`channelId` и `webhookUrl` на одном событии#

Ботовые адаптеры смотрят channelId, вебхук — webhookUrl. Если заполнены оба, уйдут оба сообщения. Явный channelId не подавляется детектором native relay: детектор глушит только default-route. discord.enabled: false у канала чата выключает и вебхук этого канала (resolveChatWebhook проверяет enabled).

Что делать: Для одного направления оставьте одно поле. Чтобы не дублировать join с родным релеем SDLink, либо положитесь на WARN и default-route, либо шлите join только явным discordEventChannels.join.channelId в канал, куда SDLink сам не пишет.

discordEmbedTemplate (и вложенные join/leave/…) применяются к явной отправке в channelId и только адаптером SDLink. Mc2Discord и DCIntegration шлют обычный текст. Любой пользовательский текст во все три моста обрезается до лимита Discord (~2000) до отправки. Вебхук чата/advancement подставляет username и avatar игрока; join/leave/mute/afk идут от имени Server без аватара.

Что делать: Цветные embed-join — SDLink + непустой discordEventChannels.join.channelId. Вебхук так не умеет.

Команд NeoEssentials на линковку нет#

В инвентаре мода нет /discord, /link, /syncperms. Линк делается командами моста. /dashboardregister discord принадлежит веб-панели и только создаёт аккаунт панели по уже существующему линку. Узлы neoessentials.discord.syncperms в комментарии discord_auth.json — примеры того, что можно было бы выдать, в PermissionRegistry их нет, отдельной команды нет.

Что делать: Не ищите синк в /neoe. Связку аккаунтов настраивайте в SDLink / Mc2Discord / DCIntegration, затем файлы NeoEssentials.

Известные проблемы#

`permissionSync.permissionMappings` не выдаёт узлы прав#

DiscordPermissionSync.mapDiscordRoleToPermissionGroup берёт permissions.get(0) и передаёт в user.setGroup. Существование группы не проверяется. Поставляемый discord_auth.json начинает списки с neoessentials.*, neoessentials.dashboard.temppass и т. д. — это не имена групп. Плюс reloadConfig() ниоткуда не вызывается: после правки файла синглтон продолжает старый маппинг до рестарта, хотя /neoe reload сбрасывает JSON-кеш.

Что делать: Выключите permissionSync.enabled и пользуйтесь discordrolesync.json. Если оставляете этот путь — первым элементом списка должно быть имя группы из permissions.json, и после правки нужен рестарт, не только /neoe reload.

Включили `discordRoleSync.enabled` на уже запущенном сервере — периодический обход молчит#

DiscordRoleSyncTask.start() вызывается из ServerStartedEvent и сразу выходит, если enabled тогда был false. Join-обработчик читает флаг каждый раз и после /neoe reload уже работает. ScheduledExecutorService при этом никто не поднимает — смена роли без перезахода подтянется только после рестарта (или не подтянется, пока игрок онлайн).

Что делать: Первое включение role-sync — рестартом. Дальше достаточно /neoe reload для карты ролей; для смены intervalSeconds снова рестарт.

Детектор native relay не обновляется с `/neoe reload`#

Разбор simple-discord-link.toml / mc2discord.toml делается в initialize() адаптера на старте. Правка родного конфига моста или переключение его native-ключей без рестарта оставит старый набор подавлений: либо снова пойдут дубли, либо NeoEssentials продолжит молчать, хотя native уже выключен.

Что делать: После правок конфига моста перезапускайте сервер. Смотрите WARN на старте и nativeRelayWarnings в статусе панели.

Родной релей моста по-прежнему может удвоить префикс#

Сообщения, которые шлёт сам SDLink из chat.playerMessages, читают Player.getDisplayName() (там уже префикс scoreboard-команды таблиста) и отдельно клеят ранг из LuckPerms/FTB Ranks. Это закрытый код моста. Сообщения адаптеров NeoEssentials с build 68 берут префикс из PermissionAPI и не удваиваются. Детектор как раз глушит default-route NeoEssentials, когда native включён — тогда в Discord остаётся именно удваивающий native.

Что делать: Выключите native-релей моста для этого события и перезапустите, чтобы в канал шёл только адаптер NeoEssentials. tablist.json → nametagSettings.enabled: false убирает префикс с таблиста/над головой целиком — это не точечный фикс Discord.

Конфигурация#

discord_auth.json#

КлючПо умолчаниюОписание
enabledfalseEnable/disable Discord-linked dashboard login. Requires a player to have already linked their account in-game via Simple Discord Link or Mc2Discord's own commands.
requireLinkedAccounttrueWhen true, dashboard login via Discord identity requires the player's Minecraft account to actually be linked to Discord (recommended: true).
allowAutoRegistrationtrueAutomatically create dashboard accounts for Discord users on first login (recommended: true)
defaultRole"VIEWER"Default role for users with no Discord role mapping (ADMIN | MODERATOR | VIEWER)
roleMapping
roleMapping.709352979668533341"ADMIN"—
roleMapping.709353103530655857"MODERATOR"—
roleMapping.709353464475680788"VIEWER"—
roleHierarchy — Define which dashboard roles each Discord role ID grants (for multi-role users)
roleHierarchy.709352979668533341["ADMIN", "MODERATOR", "VIEWER"]—
roleHierarchy.709353103530655857["MODERATOR", "VIEWER"]—
roleHierarchy.709353464475680788["VIEWER"]—
whitelistedRoles[]Only allow Discord users with these role IDs (empty = allow all)
blacklistedUsers[]Block specific Discord user IDs from logging in
sessionDuration86400000Session duration in milliseconds (86400000 = 24 hours)
permissionSync
permissionSync.enabledtrueEnable/disable Discord role → Minecraft permission synchronization
permissionSync.syncOnJointrueAutomatically sync permissions when a player joins the server (recommended: true)
permissionSync.permissionMappings
permissionSync.permissionMappings.709352979668533341
permissionSync.permissionMappings.709353103530655857
permissionSync.permissionMappings.709353464475680788

discordrolesync.json#

КлючПо умолчаниюОписание
discordRoleSync
discordRoleSync.enabledfalseMaster switch. Requires a Discord bridge mod that can read a linked player's current Discord role list (currently DCIntegration only — SDLink/Mc2Discord don't expose a public API for this) and NeoEssentials' OWN internal permission manager (not an external adapter like LuckPerms/FTB Ranks, since this can't write into another plugin's groups).
discordRoleSync.intervalSeconds300How often (seconds) the periodic sweep re-checks every online player's Discord roles, in case a role changed outside this server (a booster role expiring, a manual Discord edit, etc). Also runs once immediately whenever a player joins.
discordRoleSync.roleGroupMap — Maps a Discord role's snowflake ID to a NeoEssentials permission group name (permissions.json). If a player holds more than one mapped role at once, the group with the higher "priority" wins. A player holding NONE of the mapped roles is left alone — this only ever grants/upgrades a group, it never demotes a player back to a default group.