{"_id":"@elizaos/plugin-discord-root","_rev":"4-8ea925593e75cc75fbfeb3cae8af5bd6","name":"@elizaos/plugin-discord-root","dist-tags":{"next":"2.0.0-alpha.1","latest":"2.0.0-alpha.1"},"versions":{"2.0.0-alpha.1":{"name":"@elizaos/plugin-discord-root","version":"2.0.0-alpha.1","_id":"@elizaos/plugin-discord-root@2.0.0-alpha.1","maintainers":[{"name":"shawticus","email":"shawmakesmagic@gmail.com"},{"name":"shakkernerd","email":"shakkerdroid@gmail.com"},{"name":"wtfsayo","email":"hi@sayo.wtf"},{"name":"odilitime","email":"janesmith@airmail.cc"},{"name":"cjft","email":"cjft@trimboli.io"}],"homepage":"https://github.com/elizaos-plugins/plugin-discord#readme","bugs":{"url":"https://github.com/elizaos-plugins/plugin-discord/issues"},"dist":{"shasum":"331820508f32d36a0dad5f8baa083d56167ba81a","tarball":"https://registry.npmjs.org/@elizaos/plugin-discord-root/-/plugin-discord-root-2.0.0-alpha.1.tgz","fileCount":4,"integrity":"sha512-HIXabAoYSCM+xYOPL9br8B3vnvNgFYeZUcuSj4p/0qSFIoos+H3F8HcWUJZCnwCAA3VEH1czMbrO0SzsdaT7KQ==","signatures":[{"sig":"MEYCIQD5J94s4bMosJLmyHfKVyRukKCxWn3nDGlVK8u0aUxP0gIhAI30v4rfnTwOPr02Jb3smeimUPhkxK+/6obLi7wqSthw","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":25138},"main":"typescript/dist/index.js","type":"module","types":"typescript/dist/index.d.ts","module":"typescript/dist/index.js","exports":{".":{"import":{"types":"./typescript/dist/index.d.ts","default":"./typescript/dist/index.js"}},"./rust":{"node":{"import":"./rust/pkg/node/elizaos_plugin_discord.js","default":"./rust/pkg/node/elizaos_plugin_discord.js"},"default":"./rust/pkg/node/elizaos_plugin_discord.js"},"./package.json":"./package.json"},"gitHead":"05d4ca11d769db8c7f54a722ee24b2ce2b951543","scripts":{"dev":"cd typescript && bun run build.ts --watch","lint":"bunx @biomejs/biome check --write typescript/","test":"bun run test:ts && bun run test:rust && bun run test:python","build":"npm run build:prompts && bun run build:ts && bun run build:rust && bun run build:python","clean":"rm -rf typescript/dist .turbo typescript/node_modules .turbo-tsconfig.json typescript/tsconfig.tsbuildinfo","format":"bunx @biomejs/biome format --write typescript/","test:ts":"vitest run typescript/__tests__/","build:ts":"cd typescript && bun run build.ts","lint:rust":"cd rust && cargo clippy --all-targets --fix --allow-dirty --allow-staged -- -D warnings && cargo fmt","test:rust":"test -d rust && cd rust && cargo test || echo 'Rust tests skipped'","test:unit":"vitest run typescript/__tests__/","typecheck":"cd typescript && tsc --noEmit -p tsconfig.json","build:rust":"test -d rust && cd rust && cargo build --release || echo 'Rust build skipped - no rust directory'","lint:check":"bunx @biomejs/biome check typescript/","lint:python":"cd python && ruff check --fix . && ruff format .","test:python":"test -d python && cd python && pytest -p no:anchorpy --asyncio-mode=auto || echo 'Python tests skipped'","build:python":"test -d python && cd python && (python3 -m build 2>/dev/null || pyproject-build) || echo 'Python build skipped - no python directory'","format:check":"bunx @biomejs/biome format typescript/","build:prompts":"node ../../packages/prompts/scripts/generate-plugin-prompts.js ./prompts ./typescript/generated/prompts --target all","typecheck:rust":"cd rust && cargo check --all-targets --all-features","lint:python:fix":"cd python && ruff check --fix . && ruff format .","test:integration":"elizaos test || echo 'Integration tests skipped - elizaos CLI not available'","typecheck:python":"cd python && python3 -m mypy elizaos_plugin_discord || echo 'mypy not installed, skipping typecheck'","build:rust:native":"cd rust && cargo build --release"},"_npmUser":{"name":"shawticus","email":"shawmakesmagic@gmail.com"},"repository":{"url":"git+https://github.com/elizaos-plugins/plugin-discord.git","type":"git"},"_npmVersion":"lerna/9.0.3/node@v22.20.0+arm64 (darwin)","agentConfig":{"pluginType":"elizaos:plugin:1.0.0","pluginParameters":{"CHANNEL_IDS":{"type":"string","required":false,"sensitive":false,"description":"Comma-separated list of Discord channel IDs that will be parsed into an array if provided."},"DISCORD_API_TOKEN":{"type":"string","required":true,"sensitive":true,"description":"Discord API token used to authenticate and log in the Discord client/service."},"DISCORD_APPLICATION_ID":{"type":"string","required":true,"sensitive":false,"description":"Discord application ID for the bot"},"DISCORD_TEST_CHANNEL_ID":{"type":"string","required":false,"sensitive":false,"description":"Discord channel ID used during test suite to locate the test channel for sending messages, voice interactions, and other test operations."},"DISCORD_VOICE_CHANNEL_ID":{"type":"string","required":false,"sensitive":false,"description":"ID of the Discord voice channel the bot should join when scanning a guild. If not supplied, the bot selects a channel based on member activity."},"DISCORD_LISTEN_CHANNEL_IDS":{"type":"string","required":false,"sensitive":false,"description":"Comma-separated list of Discord channel IDs where the bot will only listen (not respond)."},"DISCORD_SHOULD_IGNORE_BOT_MESSAGES":{"type":"boolean","required":false,"sensitive":false,"description":"If true, the bot will ignore messages from other bots. Can be overridden by character settings."},"DISCORD_SHOULD_IGNORE_DIRECT_MESSAGES":{"type":"boolean","required":false,"sensitive":false,"description":"If true, the bot will ignore direct messages. Can be overridden by character settings."},"DISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS":{"type":"boolean","required":false,"sensitive":false,"description":"If true, the bot will only respond when explicitly mentioned. Can be overridden by character settings."}}},"description":"A Discord plugin implementation for ElizaOS, enabling rich integration with Discord servers for managing interactions, voice, and message handling.","directories":{},"_nodeVersion":"22.20.0","dependencies":{"zod":"^4.3.5","discord.js":"14.18.0","opusscript":"^0.1.1","typescript":"^5.9.3","prism-media":"1.3.5","@elizaos/core":"2.0.0-alpha.1","fluent-ffmpeg":"^2.1.3","get-func-name":"^3.0.0","@discordjs/opus":"^0.10.0","@discordjs/rest":"2.4.3","@discordjs/voice":"0.18.0","fast-levenshtein":"^3.0.0","libsodium-wrappers":"^0.7.13"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/node":"^25.0.3","@biomejs/biome":"^2.3.11"},"peerDependencies":{"whatwg-url":"7.1.0"},"_npmOperationalInternal":{"tmp":"tmp/plugin-discord-root_2.0.0-alpha.1_1769033594868_0.5663289559458382","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-01-21T22:13:14.807Z","modified":"2026-06-16T05:14:19.464Z","2.0.0-alpha.1":"2026-01-21T22:13:15.079Z"},"bugs":{"url":"https://github.com/elizaos-plugins/plugin-discord/issues"},"homepage":"https://github.com/elizaos-plugins/plugin-discord#readme","repository":{"url":"git+https://github.com/elizaos-plugins/plugin-discord.git","type":"git"},"description":"A Discord plugin implementation for ElizaOS, enabling rich integration with Discord servers for managing interactions, voice, and message handling.","maintainers":[{"email":"shawmakesmagic@gmail.com","name":"shawticus"},{"email":"janesmith@airmail.cc","name":"odilitime"}],"readme":"# @elizaos/plugin-discord\n\nA Discord plugin implementation for ElizaOS, enabling rich integration with Discord servers for managing interactions, voice, and message handling.\n\n## Features\n\n- Handle server join events and manage initial configurations\n- Voice event management via the voice manager\n- Manage and process new messages with the message manager\n- Slash command registration and interaction handling\n- Support for Discord attachments and media files\n- Voice channel join/leave functionality\n- Conversation summarization\n- Media transcription capabilities\n- Channel state and voice state providers\n- Channel restriction support (limit bot to specific channels)\n- Robust permissions management and audit event tracking\n- Event-driven architecture with comprehensive event handling\n- History backfill with efficient batch processing\n\n## Installation\n\nAs this is a workspace package, it's installed as part of the ElizaOS monorepo:\n\n```bash\nbun install\n```\n\n## Configuration\n\nThe plugin requires the following environment variables:\n\n```bash\n# Discord API Credentials (Required)\nDISCORD_APPLICATION_ID=your_application_id\nDISCORD_API_TOKEN=your_api_token\n\n# Channel Restrictions (Optional)\n# Comma-separated list of Discord channel IDs to restrict the bot to.\n# If not set, the bot operates in all channels.\n# These channels cannot be removed via the leaveChannel action.\nCHANNEL_IDS=123456789012345678,987654321098765432\n\n# Listen-only channels (Optional)\n# Comma-separated list of channel IDs where the bot will only listen (not respond).\nDISCORD_LISTEN_CHANNEL_IDS=123456789012345678\n\n# Voice Channel (Optional)\n# ID of the voice channel the bot should auto-join when scanning a guild.\n# If not set, the bot selects based on member activity.\nDISCORD_VOICE_CHANNEL_ID=123456789012345678\n\n# Behavior Settings (Optional)\n# If true, ignore messages from other bots (default: false)\nDISCORD_SHOULD_IGNORE_BOT_MESSAGES=false\n\n# If true, ignore direct messages (default: false)\nDISCORD_SHOULD_IGNORE_DIRECT_MESSAGES=false\n\n# If true, only respond when explicitly @mentioned (default: false)\nDISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS=false\n\n# Testing (Optional)\nDISCORD_TEST_CHANNEL_ID=123456789012345678\n```\n\nSettings can also be configured in your character file under `settings.discord`:\n\n```json\n{\n  \"settings\": {\n    \"discord\": {\n      \"shouldIgnoreBotMessages\": false,\n      \"shouldIgnoreDirectMessages\": false,\n      \"shouldRespondOnlyToMentions\": false,\n      \"allowedChannelIds\": [\"123456789012345678\"]\n    }\n  }\n}\n```\n\n## Usage\n\n```json\n{\n  \"plugins\": [\"@elizaos/plugin-discord\"]\n}\n```\n\n## Slash Command Permissions\n\nThe plugin uses a hybrid permission system that combines Discord's native features with ElizaOS-specific controls.\n\n### Permission Layers\n\nCommands go through multiple permission checks in this order:\n\n1. **Discord Native Checks** (before interaction fires):\n   - User must have required Discord permissions\n   - Command must be available in the current context (guild vs DM)\n\n2. **ElizaOS Channel Whitelist** (if `CHANNEL_IDS` is set):\n   - Commands only work in whitelisted channels\n   - Unless command has `bypassChannelWhitelist: true`\n\n3. **Custom Validator** (if provided):\n   - Runs custom validation logic\n   - Full programmatic control\n\n### Registering Commands\n\n```typescript\nimport { PermissionFlagsBits } from \"discord.js\";\n\n// Simple command (works everywhere)\nconst helpCommand = {\n  name: \"help\",\n  description: \"Show help information\",\n};\n\n// Guild-only command\nconst serverInfoCommand = {\n  name: \"serverinfo\",\n  description: \"Show server information\",\n  guildOnly: true,\n};\n\n// Requires Discord permission\nconst configCommand = {\n  name: \"config\",\n  description: \"Configure bot settings\",\n  requiredPermissions: PermissionFlagsBits.ManageGuild,\n};\n\n// Bypasses channel whitelist\nconst utilityCommand = {\n  name: \"export\",\n  description: \"Export data\",\n  bypassChannelWhitelist: true,\n};\n\n// Advanced: custom validation\nconst adminCommand = {\n  name: \"admin\",\n  description: \"Admin-only command\",\n  validator: async (interaction, runtime) => {\n    const adminIds = runtime.getSetting(\"ADMIN_USER_IDS\")?.split(\",\") ?? [];\n    return adminIds.includes(interaction.user.id);\n  },\n};\n\n// Register commands\nawait runtime.emitEvent([\"DISCORD_REGISTER_COMMANDS\"], {\n  commands: [\n    helpCommand,\n    serverInfoCommand,\n    configCommand,\n    utilityCommand,\n    adminCommand,\n  ],\n});\n```\n\n### Permission Options\n\n| Option                   | Type               | Description                                                           |\n| ------------------------ | ------------------ | --------------------------------------------------------------------- |\n| `guildOnly`              | `boolean`          | If true, command only works in guilds (not DMs)                       |\n| `bypassChannelWhitelist` | `boolean`          | If true, bypasses `CHANNEL_IDS` restrictions                          |\n| `requiredPermissions`    | `bigint \\| string` | Discord permission bitfield (e.g., `PermissionFlagsBits.ManageGuild`) |\n| `contexts`               | `number[]`         | Raw Discord contexts (0=Guild, 1=BotDM, 2=PrivateChannel)             |\n| `guildIds`               | `string[]`         | Register only in specific guilds (instant updates)                    |\n| `validator`              | `function`         | Custom validation function for advanced logic                         |\n\n### Common Permission Values\n\nFrom Discord.js `PermissionFlagsBits`:\n\n- `ManageGuild` - Server settings\n- `ManageChannels` - Channel management\n- `ManageMessages` - Delete messages\n- `BanMembers` - Ban users\n- `KickMembers` - Kick users\n- `ModerateMembers` - Timeout users\n- `ManageRoles` - Role management\n- `Administrator` - Full access\n\n### Design Rationale\n\n**Why Hybrid Approach?**\n\n- Discord's native permissions are powerful but limited to role-based access\n- ElizaOS needs programmatic control for channel restrictions and custom logic\n- Combining both gives developers the best of both worlds\n\n**Why Simple Flags?**\n\n- `guildOnly: true` is clearer than `contexts: [0]`\n- Abstracts Discord API details\n- Sensible defaults: zero config should \"just work\"\n\n**Why Keep Channel Whitelist?**\n\n- Discord's channel permissions are UI-based (Server Settings > Integrations)\n- Programmatic control is better for developer experience\n- Allows dynamic, runtime-based channel restrictions\n\n### Available Actions\n\nThe plugin provides the following actions:\n\n| Action                  | Description                                |\n| ----------------------- | ------------------------------------------ |\n| **chatWithAttachments** | Handle messages with Discord attachments   |\n| **createPoll**          | Create a poll in a Discord channel         |\n| **downloadMedia**       | Download media files from Discord messages |\n| **getUserInfo**         | Get information about a Discord user       |\n| **joinVoice**           | Join a voice channel                       |\n| **leaveVoice**          | Leave a voice channel                      |\n| **listChannels**        | List channels in a Discord server          |\n| **pinMessage**          | Pin a message in a channel                 |\n| **reactToMessage**      | Add a reaction to a message                |\n| **readChannel**         | Read messages from a channel               |\n| **searchMessages**      | Search for messages in a channel           |\n| **sendDM**              | Send a direct message to a user            |\n| **serverInfo**          | Get information about the current server   |\n| **summarize**           | Summarize conversation history             |\n| **transcribeMedia**     | Transcribe audio/video media to text       |\n| **unpinMessage**        | Unpin a message from a channel             |\n\n### Providers\n\nThe plugin includes two state providers:\n\n1. **channelStateProvider** - Provides state information about Discord channels\n2. **voiceStateProvider** - Provides state information about voice channels and connection status\n\n### Event Types\n\nThe plugin emits the following Discord-specific events:\n\n| Event                                 | Description                               |\n| ------------------------------------- | ----------------------------------------- |\n| `DISCORD_MESSAGE_RECEIVED`            | When a message is received                |\n| `DISCORD_MESSAGE_SENT`                | When a message is sent                    |\n| `DISCORD_SLASH_COMMAND`               | When a slash command is invoked           |\n| `DISCORD_MODAL_SUBMIT`                | When a modal form is submitted            |\n| `DISCORD_REACTION_RECEIVED`           | When a reaction is added to a message     |\n| `DISCORD_REACTION_REMOVED`            | When a reaction is removed from a message |\n| `DISCORD_WORLD_JOINED`                | When the bot joins a guild                |\n| `DISCORD_SERVER_CONNECTED`            | When connected to a server                |\n| `DISCORD_USER_JOINED`                 | When a user joins a guild                 |\n| `DISCORD_USER_LEFT`                   | When a user leaves a guild                |\n| `DISCORD_VOICE_STATE_CHANGED`         | When voice state changes                  |\n| `DISCORD_CHANNEL_PERMISSIONS_CHANGED` | When channel permissions change           |\n| `DISCORD_ROLE_PERMISSIONS_CHANGED`    | When role permissions change              |\n| `DISCORD_MEMBER_ROLES_CHANGED`        | When a member's roles change              |\n| `DISCORD_ROLE_CREATED`                | When a role is created                    |\n| `DISCORD_ROLE_DELETED`                | When a role is deleted                    |\n\n## Key Components\n\n### DiscordService\n\nMain service class that extends ElizaOS Service:\n\n- Handles authentication and session management\n- Manages Discord client connection\n- Processes events and interactions\n- Supports channel history backfill with efficient batch processing\n\n### MessageManager\n\n- Processes incoming messages and responses\n- Handles attachments and media files\n- Supports message formatting and templating\n- Manages conversation context\n\n### VoiceManager\n\n- Manages voice channel interactions\n- Handles joining and leaving voice channels\n- Processes voice events and audio streams\n- Integrates with transcription services\n\n### Attachment Handler\n\n- Downloads and processes Discord attachments\n- Supports various media types\n- Integrates with media transcription\n\n## Developer Guide\n\n### Custom Slash Commands\n\nRegister slash commands via the `DISCORD_REGISTER_COMMANDS` event, then listen for interactions:\n\n```typescript\n// Register custom slash commands\nawait runtime.emitEvent([\"DISCORD_REGISTER_COMMANDS\"], {\n  commands: [\n    {\n      name: \"mycommand\",\n      description: \"My custom command\",\n      options: [\n        {\n          name: \"input\",\n          description: \"User input\",\n          type: 3, // STRING type\n          required: true,\n        },\n      ],\n    },\n    {\n      name: \"serverinfo\",\n      description: \"Get server information\",\n      guildOnly: true, // Only works in guilds, not DMs\n    },\n  ],\n});\n\n// Listen for slash command events to handle the interaction\nruntime.registerEvent({\n  name: \"DISCORD_SLASH_COMMAND\",\n  handler: async (payload) => {\n    const { interaction, client, commands } = payload;\n\n    if (interaction.commandName === \"mycommand\") {\n      const input = interaction.options.getString(\"input\");\n      await interaction.reply(`You said: ${input}`);\n    }\n  },\n});\n```\n\n### Building on the Listen System\n\nThe `DISCORD_LISTEN_CHANNEL_IDS` setting creates \"listen-only\" channels where the bot receives messages but doesn't respond. This is useful for:\n\n- **Monitoring channels** - Track activity without interrupting conversations\n- **Data collection** - Gather messages for analysis or training\n- **Conditional responses** - Build custom logic that decides when to respond\n\n```typescript\n// Check if a channel is listen-only\nconst listenChannels = runtime.getSetting(\"DISCORD_LISTEN_CHANNEL_IDS\");\nconst listenChannelIds = listenChannels?.split(\",\").map((s) => s.trim()) || [];\n\nruntime.registerEvent({\n  name: \"DISCORD_MESSAGE_RECEIVED\",\n  handler: async (payload) => {\n    const { message } = payload;\n    const channelId = message.content.channelId;\n\n    if (listenChannelIds.includes(channelId)) {\n      // This is a listen-only channel - process without responding\n      await processMessageSilently(message);\n    }\n  },\n});\n```\n\n### Handling Modal and Component Interactions\n\nModal submits and message components (buttons, select menus) bypass channel whitelists to support multi-step UI flows:\n\n```typescript\n// Listen for modal submissions\nruntime.registerEvent({\n  name: \"DISCORD_MODAL_SUBMIT\",\n  handler: async (payload) => {\n    const { interaction } = payload;\n    const fieldValue = interaction.fields.getTextInputValue(\"myField\");\n    await interaction.reply(`Received: ${fieldValue}`);\n  },\n});\n```\n\n### Permission Audit System\n\nThe plugin includes a comprehensive permission audit system that tracks all permission changes with full audit log integration. This is useful for:\n\n- **Security monitoring** - Detect unauthorized permission escalations\n- **Compliance logging** - Maintain records of who changed what and when\n- **Bot self-protection** - Detect when the bot's permissions are modified\n\n#### Event Payloads\n\n**DISCORD_CHANNEL_PERMISSIONS_CHANGED** - When channel overwrites change:\n\n```typescript\ninterface ChannelPermissionsChangedPayload {\n  runtime: IAgentRuntime;\n  guild: { id: string; name: string };\n  channel: { id: string; name: string };\n  target: { type: \"role\" | \"user\"; id: string; name: string };\n  action: \"CREATE\" | \"UPDATE\" | \"DELETE\";\n  changes: Array<{\n    permission: string; // e.g., 'ManageMessages', 'Administrator'\n    oldState: \"ALLOW\" | \"DENY\" | \"NEUTRAL\";\n    newState: \"ALLOW\" | \"DENY\" | \"NEUTRAL\";\n  }>;\n  audit: {\n    executorId: string;\n    executorTag: string;\n    reason: string | null;\n  } | null;\n}\n```\n\n**DISCORD_ROLE_PERMISSIONS_CHANGED** - When role permissions change:\n\n```typescript\ninterface RolePermissionsChangedPayload {\n  runtime: IAgentRuntime;\n  guild: { id: string; name: string };\n  role: { id: string; name: string };\n  changes: PermissionDiff[];\n  audit: AuditInfo | null;\n}\n```\n\n**DISCORD_MEMBER_ROLES_CHANGED** - When a member's roles change:\n\n```typescript\ninterface MemberRolesChangedPayload {\n  runtime: IAgentRuntime;\n  guild: { id: string; name: string };\n  member: { id: string; tag: string };\n  added: Array<{ id: string; name: string; permissions: string[] }>;\n  removed: Array<{ id: string; name: string; permissions: string[] }>;\n  audit: AuditInfo | null;\n}\n```\n\n**DISCORD_ROLE_CREATED / DISCORD_ROLE_DELETED** - Role lifecycle:\n\n```typescript\ninterface RoleLifecyclePayload {\n  runtime: IAgentRuntime;\n  guild: { id: string; name: string };\n  role: { id: string; name: string; permissions: string[] };\n  audit: AuditInfo | null;\n}\n```\n\n#### Example: Security Monitoring\n\n```typescript\nimport { DiscordEventTypes } from \"@elizaos/plugin-discord\";\n\n// Alert on dangerous permission grants\nruntime.registerEvent({\n  name: DiscordEventTypes.CHANNEL_PERMISSIONS_CHANGED,\n  handler: async (payload) => {\n    const dangerousPerms = [\"Administrator\", \"ManageGuild\", \"ManageRoles\"];\n\n    for (const change of payload.changes) {\n      if (\n        dangerousPerms.includes(change.permission) &&\n        change.newState === \"ALLOW\"\n      ) {\n        console.warn(`⚠️ Dangerous permission granted!`, {\n          channel: payload.channel.name,\n          target: payload.target.name,\n          permission: change.permission,\n          grantedBy: payload.audit?.executorTag || \"Unknown\",\n        });\n      }\n    }\n  },\n});\n\n// Track role escalations\nruntime.registerEvent({\n  name: DiscordEventTypes.MEMBER_ROLES_CHANGED,\n  handler: async (payload) => {\n    const adminRoles = payload.added.filter((r) =>\n      r.permissions.includes(\"Administrator\"),\n    );\n\n    if (adminRoles.length > 0) {\n      console.warn(`⚠️ Admin role granted to ${payload.member.tag}`, {\n        roles: adminRoles.map((r) => r.name),\n        grantedBy: payload.audit?.executorTag || \"Unknown\",\n      });\n    }\n  },\n});\n\n// Log all role creations\nruntime.registerEvent({\n  name: DiscordEventTypes.ROLE_CREATED,\n  handler: async (payload) => {\n    console.log(`New role created: ${payload.role.name}`, {\n      permissions: payload.role.permissions,\n      createdBy: payload.audit?.executorTag || \"Unknown\",\n    });\n  },\n});\n```\n\n#### Bot Self-Protection\n\nMonitor when the bot's own permissions change:\n\n```typescript\nruntime.registerEvent({\n  name: DiscordEventTypes.MEMBER_ROLES_CHANGED,\n  handler: async (payload) => {\n    const botId = runtime.getSetting(\"DISCORD_APPLICATION_ID\");\n\n    if (payload.member.id === botId && payload.removed.length > 0) {\n      console.error(`🚨 Bot roles removed!`, {\n        removed: payload.removed.map((r) => r.name),\n        by: payload.audit?.executorTag || \"Unknown\",\n      });\n      // Could trigger alerts, notifications, etc.\n    }\n  },\n});\n```\n\n## Cross-Core Compatibility\n\nThis plugin includes a compatibility layer (`compat.ts`) that allows it to work with both old and new versions of `@elizaos/core`. The compatibility layer:\n\n- Automatically handles `serverId` vs `messageServerId` differences\n- Uses a runtime proxy to intercept and adapt API calls\n- Requires no changes to existing code\n\nWhen migrating to a new core version, see the comments in `compat.ts` for removal instructions.\n\n## Testing\n\nThe plugin includes a test suite for validating functionality:\n\n```bash\nbun run test\n```\n\n## Notes\n\n- Ensure that your `.env` file includes the required `DISCORD_API_TOKEN`\n- The bot requires appropriate Discord permissions (send messages, connect to voice, etc.)\n- If no token is provided, the plugin will load but remain non-functional with appropriate warnings\n- The plugin uses Discord.js v14.18.0 with comprehensive intent support\n- Slash commands and modal/component interactions bypass channel whitelists\n","readmeFilename":"README.md"}