You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
discordbot: deliver discount approvals via bot API so buttons render
Interactive components (the Approve button) are silently dropped by Discord
when posted through a plain incoming webhook, so leadership only saw the embed
text. Switch the discount-approval notification to the bot REST API.
Generalize discordwebhook delivery: queue rows may now target a channel
(POST /channels/{id}/messages, authenticated with the configured bot token)
in addition to a webhook URL, via a new channel_id column and
QueueChannelMessage. Any module or SQL trigger can post to any channel the
bot can access by inserting a row.
Replace the discount bot's Leadership Channel Webhook URL config with a
Leadership Channel ID; the bot token is reused from the Discord bot config.
// Verify the values landed in discord_config (not a separate table).
2742
2742
varenabledint
2743
-
varwebhook, pubKeystring
2744
-
err=env.db.QueryRow(`SELECT approval_bot_enabled, leadership_channel_webhook_url, application_public_key FROM discord_config ORDER BY version DESC LIMIT 1`).
2745
-
Scan(&enabled, &webhook, &pubKey)
2743
+
varchannelID, pubKeystring
2744
+
err=env.db.QueryRow(`SELECT approval_bot_enabled, leadership_channel_id, application_public_key FROM discord_config ORDER BY version DESC LIMIT 1`).
Copy file name to clipboardExpand all lines: modules/discord/config.go
+3-3Lines changed: 3 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -22,9 +22,9 @@ type Config struct {
22
22
SyncIntervalHoursint`json:"sync_interval_hours" config:"label=Full Reconciliation Interval (hours),section=sync,default=24,min=1,max=168,help=How often to fully reconcile all Discord role assignments. Default: 24 hours."`
23
23
24
24
// Discount Approval Bot
25
-
ApprovalBotEnabledbool`json:"approval_bot_enabled" config:"label=Enabled,section=approvalbot,help=When on, a member requesting a discount posts a Discord message with an Approve button. Inbound interactions are still verified regardless."`
26
-
LeadershipChannelWebhookURLstring`json:"leadership_channel_webhook_url" config:"label=Leadership Channel Webhook URL,secret,section=approvalbot,help=Create a webhook on the leadership channel (Channel Settings → Integrations → Webhooks → New Webhook) and paste its URL here. Discount requests are posted here for approval."`
27
-
ApplicationPublicKeystring`json:"application_public_key" config:"label=Application Public Key,section=approvalbot,help=Hex-encoded Ed25519 public key from your Discord application's General Information page. Used to verify inbound button interactions."`
25
+
ApprovalBotEnabledbool`json:"approval_bot_enabled" config:"label=Enabled,section=approvalbot,help=When on, a member requesting a discount posts a Discord message with an Approve button. Inbound interactions are still verified regardless."`
26
+
LeadershipChannelIDstring`json:"leadership_channel_id" config:"label=Leadership Channel ID,section=approvalbot,help=Right-click the leadership channel in Discord (Developer Mode on) and choose Copy Channel ID. Discount requests are posted here by the bot. Requires the Bot Token above."`
27
+
ApplicationPublicKeystring`json:"application_public_key" config:"label=Application Public Key,section=approvalbot,help=Hex-encoded Ed25519 public key from your Discord application's General Information page. Used to verify inbound button interactions."`
The discount approval bot posts a message with an <strong>Approve</strong> button to the leadership channel whenever a member requests a membership discount. To wire it up:
21
+
The discount approval bot posts a message with an <strong>Approve</strong> button to the leadership channel whenever a member requests a membership discount. The message is sent through the bot using the <strong>Bot Token</strong> configured above, which is what lets Discord render the interactive button. To wire it up:
22
22
<olclass="mb-0 mt-2">
23
-
<li>Create a webhook on the leadership channel (Channel Settings → Integrations → Webhooks → New Webhook) and paste its URL into <strong>Leadership Channel Webhook URL</strong>.</li>
23
+
<li>Make sure the <strong>Bot Token</strong> (Bot Configuration section) is set and the bot has permission to post in the leadership channel.</li>
24
+
<li>Enable Developer Mode in Discord, right-click the leadership channel, choose <strong>Copy Channel ID</strong>, and paste it into <strong>Leadership Channel ID</strong>.</li>
24
25
<li>In the <ahref="https://discord.com/developers/applications"target="_blank"rel="noopener">Discord Developer Portal</a>, open your application and copy the <strong>Public Key</strong> from General Information into <strong>Application Public Key</strong>.</li>
25
26
<li>Set the application's <strong>Interactions Endpoint URL</strong> to <code>{ selfURL }/discord/interactions</code>. Enable the bot and save the correct public key first, or Discord's verification ping will fail.</li>
Copy file name to clipboardExpand all lines: modules/discordbot/README.md
+8-7Lines changed: 8 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@ Notifies leadership when a member requests a membership discount and lets any au
5
5
## Functionality
6
6
7
7
- AFTER UPDATE OF `discount_status` trigger on `members` enqueues a member ID into `discordbot_discount_request_queue` whenever `discount_status` transitions into `'requested'`. Nothing is enqueued on signup, on unrelated updates, or on the `requested`→`approved` transition.
8
-
- A 15s polling worker drains the queue, builds a rich Discord payload (an embed describing the request plus a single **Approve** button), and forwards it via the `discordwebhook` module's `MessageQueuer` for rate-limited delivery.
8
+
- A 15s polling worker drains the queue, builds a rich Discord payload (an embed describing the request plus a single **Approve** button), and forwards it via the `discordwebhook` module's `MessageQueuer.QueueChannelMessage` for rate-limited delivery. Delivery goes through the **bot REST API** (not an incoming webhook) because Discord only renders interactive components on messages posted by an application/bot.
9
9
-`POST /discord/interactions` receives Discord's signed callbacks. The handler verifies the Ed25519 signature, then atomically runs `UPDATE members SET discount_status='approved' WHERE id=? AND discount_status='requested'`, logs a `DiscountApprovedViaDiscord` audit event, and replies with `UPDATE_MESSAGE` (empty `components`) recording who approved and removing the button. If the request is no longer pending (the member withdrew it, or another leader already approved), it instead shows a "Discount request closed" message and changes nothing.
10
10
- No Conway authentication is required: the route is unauthenticated and identity is established purely by Discord's request signature against the configured `ApplicationPublicKey`.
11
11
@@ -21,15 +21,16 @@ Notifies leadership when a member requests a membership discount and lets any au
21
21
22
22
## Setup
23
23
24
-
1. Create a Discord application at <https://discord.com/developers/applications>.
25
-
2. Copy the application's **Public Key** (hex) into the Conway admin UI under **Integrations → Discord → Discount Approval Bot → Application Public Key**.
26
-
3. In the leadership channel, create a webhook and copy its URL into **Leadership Channel Webhook URL** (stored as a secret).
27
-
4. In the Discord application's **General Information** page, set **Interactions Endpoint URL** to `https://<your-conway-host>/discord/interactions`. Discord will immediately probe the endpoint with a signed PING; saving succeeds only if signature verification passes.
28
-
5. Toggle **Enabled** on.
24
+
1. Create a Discord application at <https://discord.com/developers/applications>, add a **Bot** to it, and copy the **Bot Token** into the Conway admin UI under **Integrations → Discord → Bot Configuration → Bot Token**.
25
+
2. Invite the bot to your server and give it permission to post in the leadership channel (the bot must be able to send messages there).
26
+
3. Copy the application's **Public Key** (hex) into **Integrations → Discord → Discount Approval Bot → Application Public Key**.
27
+
4. Enable Developer Mode in Discord, right-click the leadership channel, choose **Copy Channel ID**, and paste it into **Leadership Channel ID**.
28
+
5. In the Discord application's **General Information** page, set **Interactions Endpoint URL** to `https://<your-conway-host>/discord/interactions`. Discord will immediately probe the endpoint with a signed PING; saving succeeds only if signature verification passes.
29
+
6. Toggle **Enabled** on.
29
30
30
31
## Behavioral details
31
32
32
-
-The Discord application's botaccount does not need to be invited to the server; webhook delivery posts the message, and interaction callbacks are routed by Discord's infrastructure based on the application's configured endpoint URL.
33
+
-Unlike incoming-webhook delivery, the Approve button requires that the message be posted **by the bot**: the bot account must be in the server and able to post in the leadership channel. The notification is sent via `POST /channels/{LeadershipChannelID}/messages` authenticated with the configured **Bot Token**. Interaction callbacks are routed by Discord's infrastructure based on the application's configured endpoint URL.
33
34
- Inbound interactions must be acknowledged within 3 seconds, so the entire happy path (signature verify, DB lookup, UPDATE, response build) runs inline on the request goroutine.
34
35
- Approval is atomic via the `WHERE ... AND discount_status='requested'` clause, so two leaders clicking Approve at once cannot double-approve; the loser sees "Discount request closed".
35
36
-**Family** requests can be approved from Discord like any other tier, but the root-account linkage must still be completed in the admin panel; the request and approval messages call this out.
Copy file name to clipboardExpand all lines: modules/discordbot/config.go
+13-12Lines changed: 13 additions & 12 deletions
Original file line number
Diff line number
Diff line change
@@ -7,10 +7,10 @@ import (
7
7
8
8
// Config controls the Discord discount-approval bot.
9
9
//
10
-
// LeadershipChannelWebhookURL is the Discord webhook URL used to POST a
11
-
// notification when a member requests a membership discount. The message
12
-
// contains an Approve button that any leader in the channel can click to
13
-
// approve the request.
10
+
// LeadershipChannelID is the ID of the Discord channel where a notification is
11
+
// posted when a member requests a membership discount. The message is sent via
12
+
// the Discord bot REST API (authenticated with the configured bot token) so it
13
+
// can carry an Approve button that any leader in the channel can click.
14
14
//
15
15
// ApplicationPublicKey is the hex-encoded Ed25519 public key shown on the
16
16
// Discord application's "General Information" page. Discord signs every
@@ -19,19 +19,20 @@ import (
19
19
//
20
20
// To wire this up in Discord:
21
21
//
22
-
// 1. Create a webhook on the leadership channel (Channel Settings →
23
-
// Integrations → Webhooks → New Webhook), copy its URL into
24
-
// LeadershipChannelWebhookURL.
25
-
// 2. In the Discord Developer Portal, open your application and copy the
22
+
// 1. Create a bot for your application, give it permission to post in the
23
+
// leadership channel, and copy its Bot Token into the Discord bot config.
24
+
// 2. Enable Developer Mode in Discord, right-click the leadership channel,
25
+
// choose "Copy Channel ID", and paste it into LeadershipChannelID.
26
+
// 3. In the Discord Developer Portal, open your application and copy the
26
27
// "Public Key" from General Information into ApplicationPublicKey.
27
-
// 3. Set "Interactions Endpoint URL" to
28
+
// 4. Set "Interactions Endpoint URL" to
28
29
// https://<your-conway-host>/discord/interactions. Discord will PING the
29
30
// URL and refuse to save unless Conway responds correctly, so make sure
30
31
// Enabled=true and the public key is correct first.
31
32
typeConfigstruct {
32
-
Enabledbool`json:"enabled" config:"label=Enabled,help=When on, a member requesting a discount posts a Discord message with an Approve button. Inbound interactions are still verified regardless."`
33
-
LeadershipChannelWebhookURLstring`json:"leadership_channel_webhook_url" config:"label=Leadership Channel Webhook URL,secret,help=Create a webhook on the leadership channel (Channel Settings → Integrations → Webhooks → New Webhook) and paste its URL here. Discount requests are posted here for approval."`
34
-
ApplicationPublicKeystring`json:"application_public_key" config:"label=Application Public Key,help=Hex-encoded Ed25519 public key from your Discord application's General Information page. Used to verify inbound button interactions."`
33
+
Enabledbool`json:"enabled" config:"label=Enabled,help=When on, a member requesting a discount posts a Discord message with an Approve button. Inbound interactions are still verified regardless."`
34
+
LeadershipChannelIDstring`json:"leadership_channel_id" config:"label=Leadership Channel ID,help=Right-click the leadership channel in Discord (Developer Mode on) and choose Copy Channel ID. Discount requests are posted here by the bot."`
35
+
ApplicationPublicKeystring`json:"application_public_key" config:"label=Application Public Key,help=Hex-encoded Ed25519 public key from your Discord application's General Information page. Used to verify inbound button interactions."`
35
36
}
36
37
37
38
// Validate ensures the public key is a valid Ed25519 key when set. Other
0 commit comments