feat(channels): add Discord as a social channel - #100
Merged
Conversation
Connect a Discord server via OAuth (bot authorization) and schedule/publish messages to its channels, with mentions and rich embeds. - Connect: custom Socialite Discord provider (bot scope) maps the authorized guild to a SocialAccount; throws if no server was authorized. - Publish: DiscordPublisher posts via the global bot token, validates the chosen channel belongs to the connected guild (anti cross-guild), optimizes media, builds allowed_mentions only from explicit mention chips (no accidental pings), and renders rich embeds. - Compose: per-post channel picker (live lookup), mention autocomplete and an embed editor, gated by a required-channel compliance rule; Discord post preview. - Enum/config/content-type wiring, ConnectionVerifier health check, throttled lookup endpoints, i18n (en/es/pt-BR), and tests. Operators must create a Discord application and set DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN and DISCORD_CLIENT_REDIRECT.
…a test - Add a placeholder discord.png so the brand mark isn't a broken image (operators can swap in the official logo). - Keep a saved Discord channel selectable before the live channel list loads (or if the lookup is unavailable), so editing a post never loses the channel. - Cover the media multipart upload path (payload_json + files[N]) with a test.
…election - Introduced caching for Discord channel lookups, reducing API calls and improving performance. - Updated the DiscordSettings component to use a SearchableSelect for better user experience when selecting channels. - Added new language strings for channel search and no channels found messages in English, Spanish, and Portuguese. - Updated tests to ensure channel list caching is isolated between runs.
Persist channel_name (display-only) so the preview header shows the chosen channel; render mentions as pills; restyle the preview to a light theme to match the rest of the app.
…an use Only text (0) and announcement (5) channels accept a direct message — forum (15) and media (16) require thread creation, so they're dropped from the picker (they returned 'non-text channel'). Channels are also filtered by the bot's effective VIEW_CHANNEL + SEND_MESSAGES, computed from role base perms and channel overwrites (Discord's documented algorithm), with a safe fallback to type-only filtering when the bot's roles can't be resolved.
Add per-post Discord metrics (server member count, thread replies as comments, and reaction counts) read live with the bot token via GET message + guild with_counts, wired into PostMetricsFetcher. Redesign the shared post metrics row: monochrome line icons for member/comment counts vs clean reaction pills, so audience stats read distinctly from reactions. Applies to Telegram too.
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.
What
Adds Discord as a connectable social channel: connect a server via OAuth (bot authorization) and schedule/publish messages to its text channels, with mentions and rich embeds.
Modeled like the existing platforms — connect mirrors the OAuth flow (custom Socialite provider, à la Instagram), publishing mirrors the bot-token pattern (à la Telegram). Account = server (guild); the channel is chosen per post (stored in
PostPlatform.meta).How it works
DiscordProvider(Socialite,bot+identify+guildsscope) authorizes adding the shared bot to the user's server; the authorized guild becomes theSocialAccount. Throws (no broken account) if no server was authorized.DiscordPublisherposts with the global bot token:channel_idbelongs to the connected guild (prevents cross-guild posting via the shared bot);MediaOptimizer) and uploads as attachments;allowed_mentionsonly from explicit mention chips (a literal@everyonetyped in shared content never pings);PlatformUnavailableExceptionso the job retries instead of failing permanently.Platform::Discord+ContentType::DiscordMessage(all enum arms),ConnectionVerifierhealth check (bot still in guild), throttled lookup endpoints, i18n in en/es/pt-BR.Operator setup
Create a Discord application + bot and set:
DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,DISCORD_BOT_TOKEN,DISCORD_CLIENT_REDIRECT(optional:DISCORD_PERMISSIONS,DISCORD_ENABLED). Add the brand assetpublic/images/accounts/discord.png.Testing
DiscordPublisherTest— text, mentions (allowed_mentions), embeds, channel-not-in-guild, missing channel, transient-retry, error mapping.DiscordLookupTest— channel filtering, mention targets, graceful degradation on API failure, cross-workspace 403.DiscordControllerTest— connect redirect, callback creates account, fails gracefully with no guild.ConnectionVerifierTest— connected/disconnected guild.UpdatePostRequestTest— channel required on publish, allowed on draft.Out of scope
Threads; richer per-post content overrides. The media-URL fetch follows the same pattern as every other byte-uploading publisher.