mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat: add announcement channels, publishing and following (#3090)
This commit is contained in:
@@ -168,6 +168,7 @@ export default defineConfig({
|
||||
items: [
|
||||
'http-api/errors',
|
||||
'topics/rate-limits',
|
||||
'topics/announcement-channels',
|
||||
'http-api/permissions',
|
||||
'topics/captcha',
|
||||
'topics/uploads',
|
||||
|
||||
@@ -300,7 +300,7 @@ const ACCEPTED_TABLE_FINDINGS = new Map<string, Readonly<Partial<Record<TableRul
|
||||
['http-api/guilds.mdx', {'table-fit': 1, 'table-identifier': 4}],
|
||||
['http-api/instance.mdx', {'table-identifier': 6}],
|
||||
['http-api/invites.mdx', {'table-cell': 6}],
|
||||
['http-api/messages.mdx', {'table-fit': 1, 'table-cell': 20}],
|
||||
['http-api/messages.mdx', {'table-fit': 1, 'table-cell': 20, 'table-identifier': 1}],
|
||||
['http-api/permissions.mdx', {'table-cell': 8}],
|
||||
['http-api/premium.mdx', {'table-identifier': 5}],
|
||||
['http-api/read-states.mdx', {'table-cell': 2}],
|
||||
|
||||
@@ -11,7 +11,7 @@ These routes list recorded background jobs and request cancellation. Create jobs
|
||||
Reads require `jobs:view` and cancellation requires `jobs:cancel`. Every operation on this page shares the `admin:jobs:view` bucket. The three reads record no [Admin audit entry](/admin-api/#admin-audit-entry-object), and cancellation records one.
|
||||
|
||||
:::note[Job updates vary by task]
|
||||
Mention processing, link previews, message delete audit log batching, payment reconciliation when a user connects, and every scheduled task except `syncUrlBlocklists` and `syncFileShaBlocklists` run with no job record and never appear here. Fluxer deletes a job record about 90 days after the job was queued. A job also runs with no record when Fluxer fails to write that record. When a later write of status, progress or attempts fails, Fluxer logs the failure and the job continues, so the stored values can be behind the real run. Use the [archive routes](/admin-api/archives/) to track archive progress and failures.
|
||||
Mention processing, link previews, announcement delivery and copy updates, message delete audit log batching, payment reconciliation when a user connects, and every scheduled task except `syncUrlBlocklists` and `syncFileShaBlocklists` run with no job record and never appear here. Fluxer deletes a job record about 90 days after the job was queued. A job also runs with no record when Fluxer fails to write that record. When a later write of status, progress or attempts fails, Fluxer logs the failure and the job continues, so the stored values can be behind the real run. Use the [archive routes](/admin-api/archives/) to track archive progress and failures.
|
||||
:::
|
||||
|
||||
## Admin job object
|
||||
@@ -120,12 +120,13 @@ The queue drops a job 7 days after it was queued. A job still recorded as `queue
|
||||
| unfurl | Link previews |
|
||||
| lifecycle | Account, guild, moderation, archive and bulk work |
|
||||
| batch | Scheduled maintenance and index work |
|
||||
| crosspost | [Announcement channel](/topics/announcement-channels/) delivery, copy updates, and follower cleanup |
|
||||
|
||||
## Background job task types
|
||||
|
||||
Use the returned `task_type` value to filter [List jobs](#list-jobs).
|
||||
|
||||
The tasks queued by [Bulk jobs](/admin-api/bulk-jobs/) are `bulkUpdateUserFlags`, `bulkUpdateSuspiciousActivityFlags`, `bulkUpdateGuildFeatures`, `bulkAddGuildMembers`, and `bulkScheduleUserDeletion`. `harvestUserData` and `harvestGuildData` build [archives](/admin-api/archives/), `sendSystemDm` delivers a [system DM broadcast](/admin-api/system-dms/), and `refreshSearchIndex` rebuilds a [search index](/admin-api/search-indexes/).
|
||||
The tasks queued by [Bulk jobs](/admin-api/bulk-jobs/) are `bulkUpdateUserFlags`, `bulkUpdateSuspiciousActivityFlags`, `bulkUpdateGuildFeatures`, `bulkAddGuildMembers`, and `bulkScheduleUserDeletion`. `harvestUserData` and `harvestGuildData` build [archives](/admin-api/archives/), `sendSystemDm` delivers a [system DM broadcast](/admin-api/system-dms/), and `refreshSearchIndex` rebuilds a [search index](/admin-api/search-indexes/). `removeChannelFollowers` runs on the `crosspost` lane after an announcement channel is deleted or converted into a text channel, and removes the channel follower webhooks that follow it.
|
||||
|
||||
## List jobs
|
||||
|
||||
|
||||
@@ -478,7 +478,7 @@ A channel became visible to the session, whether newly created or newly permitte
|
||||
|
||||
### <span id="channel-update"></span>CHANNEL_UPDATE
|
||||
|
||||
A visible channel changed. The payload is the complete [channel object](/http-api/channels/#channel-object).
|
||||
A visible channel changed. The payload is the complete [channel object](/http-api/channels/#channel-object). Converting a text channel into an announcement channel, or back, emits it with the new `type`.
|
||||
|
||||
### <span id="channel-update-bulk"></span>CHANNEL_UPDATE_BULK
|
||||
|
||||
@@ -519,6 +519,8 @@ A user left a group direct message the session belongs to.
|
||||
|
||||
The webhook set of a guild channel changed. The event has no webhook data, so a client that needs the new set reads it over the HTTP API.
|
||||
|
||||
A [follow](/http-api/channels/#follow-announcement-channel) and an unfollow each emit it for the target channel. Moving a webhook emits it for the previous channel and again for the new one. Removing the follows of a deleted or converted announcement channel emits it once for each target channel.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| guild_id | snowflake | Guild the channel belongs to |
|
||||
@@ -744,6 +746,8 @@ A visible message was created. The payload is the complete [message object](/htt
|
||||
|
||||
<sup>1</sup> The `user` field is removed from it, and the account is in the message's `author`
|
||||
|
||||
Each copy of a [published message](/http-api/messages/#crosspost-message) arrives in its following channel as Message Create, with the `IS_CROSSPOST` flag, and so does the `CHANNEL_FOLLOW_ADD` system message of a new follow. A copy mentions nobody.
|
||||
|
||||
Message Create alone overrides both the passive filter and the `ignored_events` list, and the two use different tests. A direct mention, a mention of one of the user's roles, an everyone mention, or a here mention overrides the passive filter. A direct, everyone, or here mention alone overrides the `ignored_events` list.
|
||||
|
||||
### <span id="message-update"></span>MESSAGE_UPDATE
|
||||
@@ -752,6 +756,8 @@ A visible message changed. The payload is the complete current [message object](
|
||||
|
||||
Recipients must hold `READ_MESSAGE_HISTORY` on the channel, or the message must be newer than the guild's message history cutoff.
|
||||
|
||||
[Publishing](/http-api/messages/#crosspost-message) a message emits it in the announcement channel with the `CROSSPOSTED` flag set. An edit of a published message emits it for each copy once Fluxer has copied the change, and deleting the published message emits it for each copy with the `SOURCE_MESSAGE_DELETED` flag and the content removed.
|
||||
|
||||
### <span id="message-delete"></span>MESSAGE_DELETE
|
||||
|
||||
One visible message was deleted.
|
||||
@@ -765,7 +771,7 @@ One visible message was deleted.
|
||||
| guild_id? | snowflake | Guild the channel belongs to |
|
||||
| member?<sup>2</sup> | [guild member](/http-api/guild-members/#guild-member-object) object | The author's guild member object, present in a guild channel |
|
||||
|
||||
<sup>1</sup> Both fields are omitted when an instance administrator deleted the message through the Admin API, when Fluxer deleted it after a CSAM report, or when Fluxer deleted it because content moderation blocked a link preview in it, and `author_id` is also omitted for a message with no author
|
||||
<sup>1</sup> Both fields are omitted when an instance administrator deleted the message through the Admin API, when Fluxer deleted it after a CSAM report, when Fluxer deleted it because content moderation blocked a link preview in it, or when Fluxer removed a published message and its copies together, and `author_id` is also omitted for a message with no author
|
||||
|
||||
<sup>2</sup> The `user` field is removed from it, and the whole field is absent when `author_id` is absent or the author is no longer a member
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ description: Channel objects, settings, recipients, permission overwrites, slowm
|
||||
|
||||
import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
A channel is where a conversation happens, in text or in voice. A [guild](/http-api/guilds/) owns text, voice, category and link channels. Outside a guild, a channel is a direct message, a group direct message, or the personal notes channel.
|
||||
A channel is where a conversation happens, in text or in voice. A [guild](/http-api/guilds/) owns text, announcement, voice, category and link channels. Outside a guild, a channel is a direct message, a group direct message, or the personal notes channel.
|
||||
|
||||
[Messages](/http-api/messages/) defines message content, and [Guild channels](/http-api/guild-channels/) defines guild-scoped listing, creation and reordering. [Calls](/http-api/calls/) defines ringing, the region of a private call, and ending a call. [Streams](/http-api/streams/) defines the stream keys and previews of a Go Live screen share.
|
||||
|
||||
@@ -20,9 +20,9 @@ A channel object always has its identity and type. Every other field is present
|
||||
| --- | --- | --- |
|
||||
| id | snowflake | The ID of the channel |
|
||||
| type | integer | The [type of the channel](#channel-types) |
|
||||
| guild_id? | snowflake | The ID of the guild, present only for a guild text, voice, category, or link channel |
|
||||
| guild_id? | snowflake | The ID of the guild, present only for a guild channel |
|
||||
| name?<sup>1</sup> | string | The name of the channel, present for a guild channel and for a group direct message |
|
||||
| topic? | ?string | The topic of the channel, present only for a guild text or voice channel |
|
||||
| topic? | ?string | The topic of the channel, present only for a guild text, announcement, or voice channel |
|
||||
| url? | ?string | The destination URL, present only for a guild link channel |
|
||||
| icon? | ?string | The icon hash, present only for a group direct message |
|
||||
| owner_id? | ?snowflake | The ID of the owner, present only for a group direct message |
|
||||
@@ -40,7 +40,7 @@ A channel object always has its identity and type. Every other field is present
|
||||
| nsfw_override?<sup>9</sup> | ?boolean | Whether this channel overrides the inherited age restriction, or null when it inherits |
|
||||
| content_warning_level?<sup>10</sup> | integer | The [content warning level](#content-warning-levels) stored on this channel, present only for a guild channel |
|
||||
| content_warning_text?<sup>10</sup> | ?string | The content warning text stored on this channel, or null when the channel inherits |
|
||||
| rate_limit_per_user?<sup>11</sup> | integer | The slowmode interval in seconds, present only for a guild text or voice channel |
|
||||
| rate_limit_per_user?<sup>11</sup> | integer | The slowmode interval in seconds, present only for a guild text, announcement, or voice channel |
|
||||
| nicks?<sup>12</sup> | map[snowflake, string] | The group direct message nicknames keyed by the decimal user ID (each 1-32 characters) |
|
||||
|
||||
<sup>1</sup> Omitted when a group direct message stores no name, and never present on a direct message or on the personal notes channel
|
||||
@@ -110,11 +110,14 @@ An age restriction and a content warning both resolve through this channel first
|
||||
| 2 | GUILD_VOICE | Guild voice channel, which also has messages, pins, and slowmode |
|
||||
| 3 | GROUP_DM | Group direct message channel |
|
||||
| 4 | GUILD_CATEGORY | Guild category, which owns no parent and no messages |
|
||||
| 5 | GUILD_ANNOUNCEMENT<sup>2</sup> | Guild text channel whose messages can be published to the channels that follow it |
|
||||
| 998 | GUILD_LINK | Guild link channel, which has a destination URL and no messages |
|
||||
| 999 | DM_PERSONAL_NOTES<sup>1</sup> | Personal notes channel |
|
||||
|
||||
<sup>1</sup> Its channel ID is exactly the owning user ID, and it has no recipient and no name. Fluxer creates it on first authenticated access
|
||||
|
||||
<sup>2</sup> It has the fields and the slowmode of a guild text channel. [Announcement channels](/topics/announcement-channels/) describes publishing and following
|
||||
|
||||
## Permission overwrite object
|
||||
|
||||
A permission overwrite changes the effective guild permissions for one role or one member in one channel. `allow` and `deny` are decimal strings because a permission mask exceeds the range a JSON number preserves. [Permissions](/http-api/permissions/) defines the individual flags.
|
||||
@@ -209,6 +212,46 @@ An RTC region names one voice routing target the deployment operates. Its identi
|
||||
}
|
||||
```
|
||||
|
||||
## Followed channel object
|
||||
|
||||
A followed channel object names the announcement channel that was followed and the [channel follower webhook](/http-api/webhooks/#webhook-types) that now delivers its published messages.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the followed announcement channel |
|
||||
| webhook_id | snowflake | The ID of the channel follower webhook created in the target channel |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_id": "1501314428688998182",
|
||||
"webhook_id": "1501320000000000123"
|
||||
}
|
||||
```
|
||||
|
||||
## Channel follower stats object
|
||||
|
||||
The follower stats count the channels that follow one announcement channel and the guilds those channels belong to.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_count | integer | The number of channels that follow the announcement channel |
|
||||
| guild_count | integer | The number of distinct guilds those channels belong to |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_count": 12,
|
||||
"guild_count": 9
|
||||
}
|
||||
```
|
||||
|
||||
## Get channel
|
||||
|
||||
<RouteHeader method="GET" path="/v1/channels/{channel_id}" bot />
|
||||
@@ -221,7 +264,7 @@ Returns the [channel object](#channel-object) visible to the authenticated user.
|
||||
- A private channel requires current recipient access.
|
||||
- The personal notes channel requires ownership.
|
||||
|
||||
Fluxer enforces age verification only for a guild text, voice, or link channel. Fluxer never requires age verification for a guild category, even when the category has the override its children inherit.
|
||||
Fluxer enforces age verification only for a guild text, announcement, voice, or link channel. Fluxer never requires age verification for a guild category, even when the category has the override its children inherit.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -354,7 +397,7 @@ The submitted array becomes the complete overwrite collection of the channel.
|
||||
|
||||
### JSON body
|
||||
|
||||
The stored channel type selects the body variant, and a `type` field in the request is ignored. A direct message and the personal notes channel match no variant, and Fluxer rejects both with 400 `INVALID_FORM_BODY`.
|
||||
The stored channel type selects the body variant. A `type` field converts a guild text channel into an announcement channel or back, and a `type` equal to the stored type changes nothing. A direct message and the personal notes channel match no variant, and Fluxer rejects both with 400 `INVALID_FORM_BODY`.
|
||||
|
||||
#### Guild channel body
|
||||
|
||||
@@ -362,6 +405,7 @@ Every field is optional, and an omitted field preserves its current value. The g
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| type?<sup>14</sup> | integer | The [channel type](#channel-types) to convert to, either `0` or `5` |
|
||||
| name?<sup>1</sup> | ?string | The name of the channel (1-100 characters after normalisation) |
|
||||
| topic?<sup>2</sup> | ?string | The topic of the channel (1-1,024 characters), or null to clear it |
|
||||
| url?<sup>3</sup> | ?string | The destination of a guild link channel (1-2,048 characters, `http` or `https`), or null to clear it |
|
||||
@@ -374,10 +418,10 @@ Every field is optional, and an omitted field preserves its current value. The g
|
||||
| nsfw?<sup>8</sup> | ?boolean | Whether the channel is age restricted, a legacy switch superseded by `nsfw_override` |
|
||||
| content_warning_level?<sup>9</sup> | integer | The [content warning level](#content-warning-levels) to store on this channel |
|
||||
| content_warning_text?<sup>10</sup> | ?string | The content warning text (max 200 characters) |
|
||||
| rate_limit_per_user?<sup>11</sup> | ?integer | The slowmode interval in seconds (0-21,600) for a guild text or voice channel |
|
||||
| rate_limit_per_user?<sup>11</sup> | ?integer | The slowmode interval in seconds (0-21,600) for a guild text, announcement, or voice channel |
|
||||
| rtc_region?<sup>12</sup> | ?string | The ID of the RTC region (1-64 characters) for a guild voice channel, where null selects automatic routing |
|
||||
|
||||
<sup>1</sup> An explicit null preserves the current name. In a guild without flexible channel names, Fluxer lowercases a guild text channel name, replaces its whitespace with hyphens, and removes disallowed punctuation
|
||||
<sup>1</sup> An explicit null preserves the current name. In a guild without flexible channel names, Fluxer lowercases a guild text or announcement channel name, replaces its whitespace with hyphens, and removes disallowed punctuation
|
||||
|
||||
<sup>2</sup> An explicit null clears the stored topic
|
||||
|
||||
@@ -397,12 +441,16 @@ Every field is optional, and an omitted field preserves its current value. The g
|
||||
|
||||
<sup>10</sup> Trimmed. A value empty after trimming, or an explicit null, clears the stored text
|
||||
|
||||
<sup>11</sup> Applied only to a guild text or voice channel. An explicit null preserves the current interval
|
||||
<sup>11</sup> Applied only to a guild text, announcement, or voice channel. An explicit null preserves the current interval
|
||||
|
||||
<sup>12</sup> Applied only to a guild voice channel. Supplying the field at all, including as null, requires [UPDATE_RTC_REGION](/http-api/permissions/)
|
||||
|
||||
<sup>13</sup> The stored value is capped at 96,000 unless the guild holds an [audio bitrate feature](/http-api/guilds/#guild-features). A higher value is stored at the cap rather than rejected
|
||||
|
||||
<sup>14</sup> Absent, null, or equal to the stored type keeps the type. Any other conversion returns 400 `CHANNEL_TYPE_CONVERSION_NOT_SUPPORTED`
|
||||
|
||||
Converting a text channel into an announcement channel fails with 400 `CHANNEL_HAS_FOLLOWED_CHANNELS` while the text channel receives messages from any followed announcement channel. Delete its [channel follower webhooks](/http-api/webhooks/#webhook-types) first. Converting an announcement channel back into a text channel needs no preparation, and Fluxer removes every follow of it afterwards.
|
||||
|
||||
A `url` that is not an absolute `http` or `https` URL with a host returns 400 `INVALID_FORM_BODY` with the code `INVALID_URL_FORMAT` on the path `url`.
|
||||
|
||||
A parent that does not exist in the same guild returns 400 `INVALID_FORM_BODY` with the code `INVALID_PARENT_CHANNEL` on the path `parent_id`. A parent that is not a category returns `PARENT_MUST_BE_CATEGORY` the same way.
|
||||
@@ -441,6 +489,8 @@ Fluxer trims a stored nickname, and a value that is null or empty after trimming
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [channel](#channel-object) object | Channel was modified, or the submitted values already matched the stored state |
|
||||
| 400 | [error response](/http-api/#error-response) | The `type` conversion is not between `0` and `5`, returning `CHANNEL_TYPE_CONVERSION_NOT_SUPPORTED` |
|
||||
| 400 | [error response](/http-api/#error-response) | The text channel receives followed channels, returning `CHANNEL_HAS_FOLLOWED_CHANNELS` |
|
||||
| 403 | [error response](/http-api/#error-response) | A required permission, ownership, or permission bit authority is absent and the request returns `MISSING_PERMISSIONS` |
|
||||
| 403 | [error response](/http-api/#error-response) | A concurrent change removed the caller from the group direct message and the request returns `MISSING_ACCESS` |
|
||||
| 403 | [error response](/http-api/#error-response) | The guild exists but the caller's membership state cannot be resolved and the request returns `ACCESS_DENIED` |
|
||||
@@ -448,6 +498,7 @@ Fluxer trims a stored nickname, and a value that is null or empty after trimming
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist, or the caller is not a recipient of the group direct message, each returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The guild that owns the channel does not exist and the request returns `UNKNOWN_GUILD` |
|
||||
| 404 | [error response](/http-api/#error-response) | A referenced owner or nickname target is not a current recipient and the request returns `UNKNOWN_USER` |
|
||||
| 429 | [error response](/http-api/#error-response) | A concurrent follow holds the channel, returning `RESOURCE_LOCKED` |
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -455,7 +506,9 @@ A guild channel change emits [Channel Update](/gateway/events/#channel-update) t
|
||||
|
||||
Changing `rate_limit_per_user` clears the remaining slowmode delay of every user in the channel, so each user's next message is checked against the new interval. Changing `rtc_region` on a guild voice channel moves its active voice connections to the selected region.
|
||||
|
||||
A request that changes at least one field creates a channel update audit log entry with the supplied reason. Supplying `permission_overwrites` also creates one overwrite create, update, or delete audit log entry for each overwrite the array adds, changes, or removes.
|
||||
Converting an announcement channel into a text channel deletes every [channel follower webhook](/http-api/webhooks/#webhook-types) that follows it. The deletion runs in the background after the response, emits [Webhooks Update](/gateway/events/#webhooks-update) for each target channel, and writes no audit log entry. Copies already delivered stay in the target channels, and later edits and deletions of the published messages still reach them.
|
||||
|
||||
A request that changes at least one field creates a channel update audit log entry with the supplied reason, and a conversion records the `type` change in it. Supplying `permission_overwrites` also creates one overwrite create, update, or delete audit log entry for each overwrite the array adds, changes, or removes.
|
||||
|
||||
A group direct message change emits [Channel Update](/gateway/events/#channel-update) to every current recipient. A name or successful icon change creates a system message delivered with [Message Create](/gateway/events/#message-create), and replacing the icon permanently deletes the previous one. Ownership and nickname changes create no system message and no audit entry.
|
||||
|
||||
@@ -542,7 +595,7 @@ An account that stores no password, no TOTP secret, and no registered WebAuthn c
|
||||
|
||||
Deleting a guild category first clears the `parent_id` of every child channel and emits [Channel Update](/gateway/events/#channel-update) for each one. The category itself is then deleted like any other guild channel.
|
||||
|
||||
Deleting a guild channel permanently removes its messages, attachments, invites and webhooks. Guild subscribers receive [Channel Delete](/gateway/events/#channel-delete), and the deletion appears in the audit log. When the deleted channel is the guild's system, rules or AFK channel, Fluxer clears that guild setting and emits [Guild Update](/gateway/events/#guild-update).
|
||||
Deleting a guild channel permanently removes its messages, attachments, invites and webhooks. Deleting an announcement channel also deletes every [channel follower webhook](/http-api/webhooks/#webhook-types) that follows it and turns each copy of its published messages into a deleted-source placeholder, both in the background after the response. Guild subscribers receive [Channel Delete](/gateway/events/#channel-delete), and the deletion appears in the audit log. When the deleted channel is the guild's system, rules or AFK channel, Fluxer clears that guild setting and emits [Guild Update](/gateway/events/#guild-update).
|
||||
|
||||
Closing a direct message marks the channel closed for the caller alone and emits [Channel Delete](/gateway/events/#channel-delete) to that caller. The channel, its messages, and the other recipient's view are untouched.
|
||||
|
||||
@@ -776,3 +829,97 @@ Fluxer emits [Channel Update](/gateway/events/#channel-update) to every session
|
||||
### Rate limit
|
||||
|
||||
20 requests per 10 seconds for each authenticated user and channel ID, on the `channel:update::channel_id` bucket.
|
||||
|
||||
## Follow announcement channel
|
||||
|
||||
<RouteHeader method="POST" path="/v1/channels/{channel_id}/followers" bot auditReason />
|
||||
|
||||
Follows an announcement channel into a guild text channel and returns a [followed channel object](#followed-channel-object). Fluxer creates a [channel follower webhook](/http-api/webhooks/#webhook-types) in the target channel, and every message later [published](/http-api/messages/#crosspost-message) in the announcement channel is copied into the target through it. [Announcement channels](/topics/announcement-channels/) describes delivery.
|
||||
|
||||
### Limitations
|
||||
|
||||
- The caller needs [VIEW_CHANNEL](/http-api/permissions/) on the announcement channel, and a satisfied age verification when it resolves to an age restriction.
|
||||
- The target must be a guild text channel in any guild, including the announcement channel's own guild.
|
||||
- The caller needs [MANAGE_WEBHOOKS](/http-api/permissions/) at guild level in the target guild, and `VIEW_CHANNEL` and `MANAGE_WEBHOOKS` in the target channel.
|
||||
- A target channel follows one announcement channel at most once.
|
||||
- The guild that owns the announcement channel must not have [ANNOUNCEMENT_CHANNELS_DISABLED](/http-api/guilds/#guild-features).
|
||||
|
||||
`MANAGE_WEBHOOKS` is an [elevated permission](/http-api/permissions/#elevated-permissions). In a target guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated, a caller who is neither its owner nor enrolled in multi-factor authentication receives 400 [`TWO_FACTOR_REQUIRED`](/http-api/errors/).
|
||||
|
||||
Fluxer resolves the age restriction and the content warning of both channels through the channel, then its parent category, and then its guild. An age-restricted announcement channel can only be followed into an age-restricted target. An announcement channel with a content warning can only be followed into a target that has a content warning or an age restriction.
|
||||
|
||||
An announcement channel has no cap on its follows. The follower webhook takes one slot of the target's [`max_webhooks_per_channel`](/http-api/instance/#limit-keys) and `max_webhooks_per_guild` allowances, which default to 15 and 1000.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the announcement channel to follow |
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| webhook_channel_id | snowflake | The ID of the guild text channel that receives published messages |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [followed channel](#followed-channel-object) object | The follow was created |
|
||||
| 400 | [error response](/http-api/#error-response) | The followed channel is not an announcement channel, returning `ANNOUNCEMENT_CHANNEL_REQUIRED` |
|
||||
| 400 | [error response](/http-api/#error-response) | The target is not a guild text channel, returning `INVALID_FOLLOW_TARGET_CHANNEL` |
|
||||
| 400 | [error response](/http-api/#error-response) | The target already follows this announcement channel, returning `CHANNEL_ALREADY_FOLLOWED` |
|
||||
| 400<sup>1</sup> | [error response](/http-api/#error-response) | The target does not meet the age restriction or content warning rule |
|
||||
| 400<sup>2</sup> | [error response](/http-api/#error-response) | The webhook allowance of the target is already reached |
|
||||
| 400 | [error response](/http-api/#error-response) | The caller holds `MANAGE_WEBHOOKS` with no enrolled authenticator in an elevated-MFA guild |
|
||||
| 403 | [error response](/http-api/#error-response) | A permission on either channel is absent, returning `MISSING_PERMISSIONS` |
|
||||
| 403 | [error response](/http-api/#error-response) | The resolved age restriction is not satisfied, returning `NSFW_CONTENT_AGE_RESTRICTED` |
|
||||
| 403 | [error response](/http-api/#error-response) | The source guild has `ANNOUNCEMENT_CHANNELS_DISABLED`, returning `FEATURE_TEMPORARILY_DISABLED` |
|
||||
| 403 | [error response](/http-api/#error-response) | The generated webhook name is blocked, returning `CONTENT_BLOCKED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Either channel does not exist, returning `UNKNOWN_CHANNEL` |
|
||||
| 429 | [error response](/http-api/#error-response) | A concurrent follow or conversion holds the target channel, returning `RESOURCE_LOCKED` |
|
||||
|
||||
<sup>1</sup> `FOLLOW_TARGET_NOT_AGE_RESTRICTED` for an age-restricted announcement channel, and `FOLLOW_TARGET_CONTENT_WARNING_REQUIRED` for one with a content warning
|
||||
|
||||
<sup>2</sup> `MAX_WEBHOOKS_PER_CHANNEL` or `MAX_WEBHOOKS_PER_GUILD`, each with the allowance that was reached
|
||||
|
||||
### Side effects
|
||||
|
||||
The follow creates a channel follower webhook in the target channel. Its name is the source guild name and the announcement channel name, as in `Fluxer #updates`, cut to 80 characters. When that name fails validation or the blocklist scan, Fluxer uses the channel name alone. The webhook avatar is a copy of the source guild icon at the time of the follow, or null when the source guild has no icon. It does not change when the source guild icon changes, and [Update webhook](/http-api/webhooks/#update-webhook) cannot replace it. A member who manages the target channel's webhooks can rename it later. Each copy uses the source guild icon at the time of delivery as its author avatar.
|
||||
|
||||
It emits [Webhooks Update](/gateway/events/#webhooks-update) for the target channel, records a `WEBHOOK_CREATE` audit log entry in the target guild with the supplied reason, and posts a `CHANNEL_FOLLOW_ADD` [system message](/http-api/messages/#message-types) in the target channel through [Message Create](/gateway/events/#message-create). The follow takes effect for messages published after it, and earlier messages are never copied.
|
||||
|
||||
[Delete webhook](/http-api/webhooks/#delete-webhook) on the follower webhook removes the follow.
|
||||
|
||||
### Rate limit
|
||||
|
||||
5 requests per 10 seconds for each authenticated user and channel ID, on the `channel:follow::channel_id` bucket.
|
||||
|
||||
## Get channel follower stats
|
||||
|
||||
<RouteHeader method="GET" path="/v1/channels/{channel_id}/follower-stats" bot />
|
||||
|
||||
Returns the [channel follower stats object](#channel-follower-stats-object) of an announcement channel. The caller needs the same access as [Get channel](#get-channel).
|
||||
|
||||
The counts are cached for 60 seconds, so a follow or unfollow can take up to a minute to show.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the announcement channel |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [channel follower stats](#channel-follower-stats-object) object | Counts were returned |
|
||||
| 400 | [error response](/http-api/#error-response) | The channel is not an announcement channel, returning `ANNOUNCEMENT_CHANNEL_REQUIRED` |
|
||||
| 403 | [error response](/http-api/#error-response) | Caller is not a guild member or lacks `VIEW_CHANNEL`, each returning `MISSING_PERMISSIONS` |
|
||||
| 403 | [error response](/http-api/#error-response) | The resolved age restriction is not satisfied, returning `NSFW_CONTENT_AGE_RESTRICTED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist, returning `UNKNOWN_CHANNEL` |
|
||||
|
||||
### Rate limit
|
||||
|
||||
10 requests per 10 seconds for each authenticated user and channel ID, on the `channel:follower_stats::channel_id` bucket.
|
||||
|
||||
@@ -164,6 +164,10 @@ You've already completed age verification
|
||||
|
||||
You're already friends with this user
|
||||
|
||||
### `ANNOUNCEMENT_CHANNEL_REQUIRED`
|
||||
|
||||
This action is only available in announcement channels
|
||||
|
||||
### `APPLICATION_NOT_OWNED`
|
||||
|
||||
You don't own this application
|
||||
@@ -288,6 +292,18 @@ Community ownership can't be transferred to a bot
|
||||
|
||||
Verification required. Try again
|
||||
|
||||
### `CHANNEL_ALREADY_FOLLOWED`
|
||||
|
||||
This channel already receives updates from that announcement channel
|
||||
|
||||
### `CHANNEL_HAS_FOLLOWED_CHANNELS`
|
||||
|
||||
Remove the followed channels posting here before converting it to an announcement channel
|
||||
|
||||
### `CHANNEL_TYPE_CONVERSION_NOT_SUPPORTED`
|
||||
|
||||
Only text and announcement channels can be converted into each other
|
||||
|
||||
### `COMMUNICATION_DISABLED`
|
||||
|
||||
Communication is disabled
|
||||
@@ -404,6 +420,14 @@ This feature is temporarily disabled
|
||||
|
||||
File size is too large
|
||||
|
||||
### `FOLLOW_TARGET_CONTENT_WARNING_REQUIRED`
|
||||
|
||||
Updates from a channel with a content warning can only go to a channel with a content warning or an age restriction
|
||||
|
||||
### `FOLLOW_TARGET_NOT_AGE_RESTRICTED`
|
||||
|
||||
Updates from an age-restricted channel can only go to an age-restricted channel
|
||||
|
||||
### `FORBIDDEN`
|
||||
|
||||
Forbidden
|
||||
@@ -536,6 +560,10 @@ Invalid DSA verification code
|
||||
|
||||
Invalid flags format
|
||||
|
||||
### `INVALID_FOLLOW_TARGET_CHANNEL`
|
||||
|
||||
Followed channels can only post into text channels
|
||||
|
||||
### `INVALID_FORM_BODY`
|
||||
|
||||
Invalid form body
|
||||
@@ -704,6 +732,18 @@ You've reached the maximum of {count, plural, one {# webhook} other {# webhooks}
|
||||
|
||||
Media metadata error
|
||||
|
||||
### `MESSAGE_ALREADY_CROSSPOSTED`
|
||||
|
||||
This message has already been published
|
||||
|
||||
### `MESSAGE_CROSSPOST_RATE_LIMITED`
|
||||
|
||||
This channel has reached its publishing limit
|
||||
|
||||
### `MESSAGE_NOT_CROSSPOSTABLE`
|
||||
|
||||
This message cannot be published
|
||||
|
||||
### `METHOD_NOT_ALLOWED`
|
||||
|
||||
Method not allowed
|
||||
@@ -844,6 +884,10 @@ We couldn't process the request
|
||||
|
||||
Email verification is required for this action
|
||||
|
||||
### `PUBLISHED_MESSAGE_EDIT_RATE_LIMITED`
|
||||
|
||||
This published message has reached its editing limit
|
||||
|
||||
### `PURCHASE_EMAIL_VERIFICATION_REQUIRED`
|
||||
|
||||
Email verification is required for this action
|
||||
|
||||
@@ -129,9 +129,9 @@ The Value column is the numeric `action_type`, which is also the value the `acti
|
||||
| 40 | INVITE_CREATE | Invite was created. `target_id` is the invite code, and the action records the invite options |
|
||||
| 41 | INVITE_UPDATE<sup>8</sup> | Invite was changed. `target_id` is the invite code, and the action records no options |
|
||||
| 42 | INVITE_DELETE | Invite was deleted. `target_id` is the invite code, and the action records the invite options |
|
||||
| 50 | WEBHOOK_CREATE | Webhook was created. `target_id` is the webhook, and the action records `channel_id`<sup>7</sup> |
|
||||
| 51 | WEBHOOK_UPDATE | Webhook was changed. `target_id` is the webhook, and the action records `channel_id`<sup>7</sup> |
|
||||
| 52 | WEBHOOK_DELETE | Webhook was deleted. `target_id` is the webhook, and the action records `channel_id`<sup>7</sup> |
|
||||
| 50 | WEBHOOK_CREATE<sup>11</sup> | Webhook was created. `target_id` is the webhook, and the action records `channel_id` and `type`<sup>7</sup> |
|
||||
| 51 | WEBHOOK_UPDATE | Webhook was changed. `target_id` is the webhook, and the action records `channel_id` and `type`<sup>7</sup> |
|
||||
| 52 | WEBHOOK_DELETE<sup>11</sup> | Webhook was deleted. `target_id` is the webhook, and the action records `channel_id` and `type`<sup>7</sup> |
|
||||
| 60 | EMOJI_CREATE | Emoji was created. `target_id` is the emoji, and the action records no options |
|
||||
| 61 | EMOJI_UPDATE | Emoji was changed. `target_id` is the emoji, and the action records no options |
|
||||
| 62 | EMOJI_DELETE | Emoji was deleted. `target_id` is the emoji, and the action records no options |
|
||||
@@ -163,6 +163,8 @@ The Value column is the numeric `action_type`, which is also the value the `acti
|
||||
|
||||
<sup>10</sup> Recorded for an overwrite whose target is a role other than `@everyone`, when that role exists at the write
|
||||
|
||||
<sup>11</sup> [Follow announcement channel](/http-api/channels/#follow-announcement-channel) records `WEBHOOK_CREATE` in the following guild with `type` `2`, and deleting that webhook records `WEBHOOK_DELETE` the same way. Follower webhooks that Fluxer removes because the announcement channel was deleted or converted record no entry
|
||||
|
||||
:::note[An update that changes nothing records no entry]
|
||||
GUILD_UPDATE, CHANNEL_UPDATE, CHANNEL_OVERWRITE_UPDATE, MEMBER_UPDATE, MEMBER_ROLE_UPDATE, MEMBER_MOVE, ROLE_UPDATE, WEBHOOK_UPDATE, EMOJI_UPDATE, and STICKER_UPDATE need a change other than `ip`. GUILD_UPDATE also ignores `guild_id`, `member_count`, and the image width and height fields. A reason alone records nothing.
|
||||
:::
|
||||
@@ -184,7 +186,7 @@ Every field is present only for the actions listed against it in the [audit acti
|
||||
| message_id | string | The decimal ID of the single message the action concerns |
|
||||
| members_removed<sup>4</sup> | number | The number of memberships the action removed |
|
||||
| role_name<sup>5</sup> | string | The name of the role the action concerns, as it was when the entry was written |
|
||||
| type<sup>6</sup> | number | The channel type, or the permission overwrite type |
|
||||
| type<sup>6</sup> | number | The channel type, the permission overwrite type, or the webhook type |
|
||||
| inviter_id | string | The decimal ID of the user that created the invite |
|
||||
| max_age | number | The configured invite lifetime in seconds |
|
||||
| max_uses | number | The configured maximum use count |
|
||||
@@ -201,7 +203,7 @@ Every field is present only for the actions listed against it in the [audit acti
|
||||
|
||||
<sup>5</sup> Recorded by [MEMBER_ROLE_UPDATE](#audit-actions), [ROLE_UPDATE](#audit-actions), and an overwrite action whose target is a role other than `@everyone`
|
||||
|
||||
<sup>6</sup> The value is a [channel type](/http-api/channels/#channel-types) for the channel actions and a [permission overwrite type](/http-api/channels/#permission-overwrite-types) for the overwrite actions
|
||||
<sup>6</sup> The value is a [channel type](/http-api/channels/#channel-types) for the channel actions, a [permission overwrite type](/http-api/channels/#permission-overwrite-types) for the overwrite actions, and a [webhook type](/http-api/webhooks/#webhook-types) for the webhook actions
|
||||
|
||||
## Audit log change object
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ These are the [channel types](/http-api/channels/#channel-types) that can exist
|
||||
| --- | --- | --- |
|
||||
| 0 | GUILD_TEXT | Text channel that has messages and slowmode |
|
||||
| 2 | GUILD_VOICE | Voice channel that has messages, voice sessions, and Go Live streams |
|
||||
| 5 | GUILD_ANNOUNCEMENT | Text channel whose messages can be [published](/topics/announcement-channels/) to the channels that follow it |
|
||||
| 4 | GUILD_CATEGORY | Category that groups other guild channels and supplies inherited permission overwrites |
|
||||
| 998 | GUILD_LINK | Channel whose only content is an external destination URL |
|
||||
|
||||
@@ -169,7 +170,7 @@ Creates a guild channel and returns its [channel object](/http-api/channels/#cha
|
||||
|
||||
<sup>1</sup> Normalisation strips control, join, bidirectional, and tag code points, collapses whitespace runs to one space, and trims. An empty result is rejected with `NAME_EMPTY_AFTER_NORMALIZATION`
|
||||
|
||||
<sup>2</sup> A text channel name is also lowercased, its whitespace replaced with hyphens, and ASCII punctuation other than `-`, `.`, and `_` removed, unless the guild has [TEXT_CHANNEL_FLEXIBLE_NAMES](/http-api/guilds/#guild-features). That pass rewrites the stored name and never fails the request
|
||||
<sup>2</sup> A text or announcement channel name is also lowercased, its whitespace replaced with hyphens, and ASCII punctuation other than `-`, `.`, and `_` removed, unless the guild has [TEXT_CHANNEL_FLEXIBLE_NAMES](/http-api/guilds/#guild-features). That pass rewrites the stored name and never fails the request
|
||||
|
||||
<sup>3</sup> An `http` or `https` URL of at most 2048 characters with a host and no embedded credentials. It is stored for every channel type
|
||||
|
||||
@@ -193,7 +194,7 @@ A value longer than 10000 characters is rejected with `STRING_LENGTH_INVALID` be
|
||||
|
||||
The guild holds at most the instance-configured `max_guild_channels` [limit](/http-api/instance/#limit-keys), defaulting to 500, and a parent category holds at most `max_channels_per_category`, defaulting to 50. Reaching the guild limit returns 400 `MAX_GUILD_CHANNELS` and reaching the category limit returns 400 `MAX_CATEGORY_CHANNELS`. Each error message states the resolved limit. The new channel is created with no RTC region.
|
||||
|
||||
The new channel takes a position derived from its siblings. A category, and a channel created with no parent, is placed after the highest position in the guild. A voice channel created inside a category is placed after the last voice sibling, or after the last text or link sibling when the category holds no voice channel. A text or link channel is placed after the last text or link sibling, and a channel with no such sibling is placed directly after the category itself. The rest of the guild is not renumbered, so two channels can hold the same stored position until the next [hierarchy update](#modify-guild-channel-positions) renumbers them.
|
||||
The new channel takes a position derived from its siblings. A category, and a channel created with no parent, is placed after the highest position in the guild. A voice channel created inside a category is placed after the last voice sibling, or after the last text, announcement, or link sibling when the category holds no voice channel. A text, announcement, or link channel is placed after the last text, announcement, or link sibling, and a channel with no such sibling is placed directly after the category itself. The rest of the guild is not renumbered, so two channels can hold the same stored position until the next [hierarchy update](#modify-guild-channel-positions) renumbers them.
|
||||
|
||||
:::caution[Overwrites are checked against the parent category]
|
||||
Fluxer compares the allowed bits of every supplied overwrite against the caller's effective permissions in the parent category, or in the guild when no parent is supplied. A bit the caller does not hold there fails the entire request with 403 `MISSING_PERMISSIONS`.
|
||||
@@ -258,7 +259,7 @@ Renumbering can change positions of channels not named in the request. An entry
|
||||
The top-level body is an array of [channel position objects](#channel-position-object) of any length, including zero. Every channel, parent, and sibling an entry names must exist in this guild. A category cannot be given a parent, and a parent that is not a category is refused. A channel cannot be positioned relative to itself or to one of its own children. Moving a channel into a full category is refused with 400 `MAX_CATEGORY_CHANNELS`.
|
||||
|
||||
:::caution[Inside a category, voice channels come last]
|
||||
Placing a voice channel above a text or link sibling, or placing a text or link channel below a voice sibling, returns `VOICE_CHANNELS_CANNOT_BE_ABOVE_TEXT_CHANNELS`.
|
||||
Placing a voice channel above a text, announcement, or link sibling, or placing one of those below a voice sibling, returns `VOICE_CHANNELS_CANNOT_BE_ABOVE_TEXT_CHANNELS`.
|
||||
:::
|
||||
|
||||
Channels at the guild root are exempt from this restriction.
|
||||
|
||||
@@ -39,7 +39,7 @@ A guild object contains the guild's configuration. The operation that returns it
|
||||
| embed_splash_height<sup>3</sup> | ?integer | Embedded invite splash height in pixels |
|
||||
| vanity_url_code | ?string | Custom invite code |
|
||||
| owner_id | snowflake | The ID of the guild owner |
|
||||
| system_channel_id | ?snowflake | Text channel that receives system messages |
|
||||
| system_channel_id | ?snowflake | Text or announcement channel that receives system messages |
|
||||
| system_channel_flags | integer | [System channel flags](#system-channel-flags) |
|
||||
| rules_channel_id<sup>5</sup> | ?snowflake | The ID of the rules channel |
|
||||
| afk_channel_id | ?snowflake | Voice channel that inactive members are moved to |
|
||||
@@ -189,7 +189,7 @@ A creation template describes the roles and channels that [Create guild](#create
|
||||
| verification_level?<sup>2</sup> | integer | [Verification level](#verification-levels), defaulting to 0 |
|
||||
| default_message_notifications?<sup>2</sup> | integer | [Default message notification level](#default-message-notification-levels), defaulting to 0 |
|
||||
| explicit_content_filter?<sup>2</sup> | integer | [Guild explicit content filter level](#guild-explicit-content-filter-levels), defaulting to 0 |
|
||||
| system_channel_id?<sup>3</sup> | ?decimal string \| integer | Template-local ID of the text channel that becomes the system channel |
|
||||
| system_channel_id?<sup>3</sup> | ?decimal string \| integer | Template-local ID of the text or announcement channel that becomes the system channel |
|
||||
| afk_timeout?<sup>2</sup> | integer | AFK timeout in seconds, clamped to 60-3600 and defaulting to 300 |
|
||||
| system_channel_flags?<sup>4</sup> | integer | [System channel flags](#system-channel-flags), defaulting to 0 |
|
||||
| roles<sup>5</sup> | array[[guild template role](#guild-template-role-object) object] | Template roles (max 250) |
|
||||
@@ -199,7 +199,7 @@ A creation template describes the roles and channels that [Create guild](#create
|
||||
|
||||
<sup>2</sup> The value is truncated to an integer and clamped into its registry range, and a missing value becomes the stated default. A non-numeric value fails validation and returns 400 `INVALID_FORM_BODY`
|
||||
|
||||
<sup>3</sup> An identifier that resolves to no text channel in the same template, and an absent or null value, all fall back to the template's first text channel. A template with no text channel receives a root text channel named `general`, which becomes the system channel
|
||||
<sup>3</sup> An identifier that resolves to no text or announcement channel in the same template, and an absent or null value, all fall back to the template's first text or announcement channel. A template with neither receives a root text channel named `general`, which becomes the system channel
|
||||
|
||||
<sup>4</sup> Every bit outside the registry is discarded
|
||||
|
||||
@@ -251,7 +251,7 @@ A creation template describes the roles and channels that [Create guild](#create
|
||||
|
||||
<sup>1</sup> A duplicate identifier in the same template rejects creation with 400 `GUILD_TEMPLATE_INVALID`
|
||||
|
||||
<sup>2</sup> The value 0 creates a text channel, 2 a voice channel, and 4 a category. The value 5, the announcement channel type of another platform, creates a text channel. The value 13, the stage channel type of another platform, creates a voice channel. Fluxer skips every other value, so the channel is not created
|
||||
<sup>2</sup> The value 0 creates a text channel, 2 a voice channel, 4 a category, and 5 an announcement channel. The value 13, the stage channel type of another platform, creates a voice channel. Fluxer skips every other value, so the channel is not created
|
||||
|
||||
<sup>3</sup> The identifier is applied only when it resolves to a category in the same template, and every other value leaves the channel at the guild root
|
||||
|
||||
@@ -377,6 +377,7 @@ Each value in the guild's `features` array is a capability or availability flag.
|
||||
| Value | Description |
|
||||
| --- | --- |
|
||||
| ANIMATED_ICON | Guild can use an animated icon |
|
||||
| ANNOUNCEMENT_CHANNELS_DISABLED<sup>8</sup> | Publishing from and following the guild's announcement channels is disabled |
|
||||
| ANIMATED_BANNER | Guild can use an animated banner |
|
||||
| AUDIO_BITRATE_128_KBPS<sup>6</sup> | Voice channel bitrate ceiling is raised to 128000 |
|
||||
| AUDIO_BITRATE_256_KBPS<sup>6</sup> | Voice channel bitrate ceiling is raised to 256000 |
|
||||
@@ -424,6 +425,8 @@ Each value in the guild's `features` array is a capability or availability flag.
|
||||
|
||||
<sup>7</sup> The feature is deprecated and changes no behaviour. Fluxer still returns it for a guild that already holds it, and [Modify guild](#modify-guild) can neither add nor remove it
|
||||
|
||||
<sup>8</sup> Set by an instance administrator. [Crosspost message](/http-api/messages/#crosspost-message) and [Follow announcement channel](/http-api/channels/#follow-announcement-channel) return 403 `FEATURE_TEMPORARILY_DISABLED`, and copies of messages published earlier are no longer delivered or updated. Creating and converting announcement channels is unaffected
|
||||
|
||||
:::caution[Expression cloning is opt-in]
|
||||
Cloning requires `CLONE_EMOJI_ENABLED` or `CLONE_STICKER_ENABLED`. A member with `MANAGE_GUILD` can enable them through [Modify guild](#modify-guild). The deprecated `CLONE_EMOJI_DISABLED` and `CLONE_STICKER_DISABLED` features have no effect.
|
||||
:::
|
||||
@@ -665,7 +668,7 @@ Every field is optional. An omitted field preserves its current value, and a fie
|
||||
|
||||
<sup>2</sup> The accepted encoding, byte ceiling, and format set are the ones listed by [Create guild](#create-guild). No guild feature gates an animated icon on write, but the `a_` prefix is stripped from the returned hash while the guild lacks `ANIMATED_ICON`
|
||||
|
||||
<sup>3</sup> The channel must exist in this guild and be a text channel, and is otherwise rejected with `SYSTEM_CHANNEL_MUST_BE_IN_GUILD` or `SYSTEM_CHANNEL_MUST_BE_TEXT`
|
||||
<sup>3</sup> The channel must exist in this guild and be a text or announcement channel, and is otherwise rejected with `SYSTEM_CHANNEL_MUST_BE_IN_GUILD` or `SYSTEM_CHANNEL_MUST_BE_TEXT`
|
||||
|
||||
<sup>4</sup> Every bit outside the registry is discarded
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ A message object is the full stored form of one post in a channel.
|
||||
| stickers | array[[sticker item](#sticker-item-object) object] | The stickers sent with the message |
|
||||
| nsfw_emojis? | array[snowflake] | IDs of the custom emojis in the message that are classified as explicit |
|
||||
| reactions? | array[[reaction](#reaction-object) object] | Reaction summaries |
|
||||
| message_reference? | [message reference](#message-reference-object) object | Reply or forward reference |
|
||||
| message_reference? | [message reference](#message-reference-object) object | Reply, forward, or published message reference |
|
||||
| message_snapshots? | array[[message snapshot](#message-snapshot-object) object] | The immutable copies captured for a forward |
|
||||
| nonce?<sup>6</sup> | string | Caller-supplied message nonce, echoed to the sender as a string of 1 through 32 characters |
|
||||
| call? | [message call](#message-call-object) object | Call state attached to a call message |
|
||||
@@ -65,7 +65,7 @@ A message object is the full stored form of one post in a channel.
|
||||
|
||||
<sup>6</sup> Echoed only in the create message response and its originating [Message Create](/gateway/events/#message-create) Gateway Dispatch, and never stored on the message
|
||||
|
||||
<sup>7</sup> The key is absent when the message has no reply reference, present and null when the reply target no longer resolves, and present with the message when it does. A client must tell absent apart from null by key presence
|
||||
<sup>7</sup> The key is absent when the message has no reply reference, present and null when the reply target no longer resolves, and present with the message when it does. It is always absent on a copy of a [published message](#crosspost-message). A client must tell absent apart from null by key presence
|
||||
|
||||
A webhook-authored message has no stored author user. Fluxer builds its author from the webhook, with the webhook ID as `id`, the stored webhook name as `username`, the discriminator `0000`, the stored webhook avatar hash as `avatar`, and `bot` true. A message whose stored author ID no longer resolves to an account is served with a deleted-user placeholder. The placeholder has that ID, the discriminator `0000`, and no avatar.
|
||||
|
||||
@@ -115,17 +115,23 @@ A reply reference resolves into `referenced_message`, and a forward has none. Fl
|
||||
| 5 | CHANNEL_ICON_CHANGE | Group direct message icon-change system message |
|
||||
| 6 | CHANNEL_PINNED_MESSAGE<sup>2</sup> | Channel pin system message |
|
||||
| 7 | USER_JOIN<sup>2</sup> | User-join system message |
|
||||
| 12 | CHANNEL_FOLLOW_ADD<sup>2</sup> <sup>3</sup> | System message posted when a channel starts following an announcement channel |
|
||||
| 19 | REPLY<sup>1</sup> <sup>2</sup> | Message with a reply reference |
|
||||
|
||||
<sup>1</sup> Only `DEFAULT` and `REPLY` can be modified, pinned, unpinned, or used as the target of a reply reference. Any other type rejects modification, pinning, and unpinning with 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK`, and rejects being replied to with the field code `CANNOT_REPLY_TO_SYSTEM_MESSAGE`
|
||||
|
||||
<sup>2</sup> Only `DEFAULT`, `CHANNEL_PINNED_MESSAGE`, `USER_JOIN`, and `REPLY` can be deleted, and any other type rejects deletion with 403 `MISSING_PERMISSIONS`
|
||||
<sup>2</sup> Only `DEFAULT`, `CHANNEL_PINNED_MESSAGE`, `USER_JOIN`, `CHANNEL_FOLLOW_ADD`, and `REPLY` can be deleted, and any other type rejects deletion with 403 `MISSING_PERMISSIONS`
|
||||
|
||||
<sup>3</sup> Its `content` is the name of the new [channel follower webhook](/http-api/webhooks/#webhook-types). Its `message_reference` names the followed announcement channel and its guild, has `type` `0`, and has no `message_id`. It never sends a push notification
|
||||
|
||||
## Message flags
|
||||
|
||||
| Value | Name | Description |
|
||||
| --- | --- | --- |
|
||||
| 1 << 0 | CROSSPOSTED<sup>3</sup> | Message was published to the channels that follow its announcement channel |
|
||||
| 1 << 1 | IS_CROSSPOST<sup>3</sup> | Message is a copy delivered from a followed announcement channel |
|
||||
| 1 << 2 | SUPPRESS_EMBEDS<sup>1</sup> | Suppress rendering of embeds |
|
||||
| 1 << 3 | SOURCE_MESSAGE_DELETED<sup>3</sup> | The published message this copy came from was deleted |
|
||||
| 1 << 12 | SUPPRESS_NOTIFICATIONS | Do not generate ordinary mention notifications |
|
||||
| 1 << 13 | VOICE_MESSAGE<sup>2</sup> | Message has one voice recording attachment |
|
||||
|
||||
@@ -133,7 +139,9 @@ A reply reference resolves into `referenced_message`, and a forward has none. Fl
|
||||
|
||||
<sup>2</sup> A message with this flag has the voice message restrictions listed under [Create message](#create-message)
|
||||
|
||||
These bits are also the complete sendable set. A create masks the supplied value down to them, and a modify replaces only them and leaves every other stored bit untouched. A bit outside this table is discarded, and the request still succeeds.
|
||||
<sup>3</sup> Set by the server. [Announcement channels](/topics/announcement-channels/) describes when each is set
|
||||
|
||||
`SUPPRESS_EMBEDS`, `SUPPRESS_NOTIFICATIONS`, and `VOICE_MESSAGE` are the complete sendable set. A create masks the supplied value down to them, and a modify replaces only them and leaves every other stored bit untouched. Any other bit in the request is discarded, and the request still succeeds.
|
||||
|
||||
## Message attachment object
|
||||
|
||||
@@ -281,28 +289,32 @@ Each message has at most the resolved `max_reactions_per_message` distinct group
|
||||
|
||||
## Message reference object
|
||||
|
||||
A message reference names the message that a reply points at or that a forward was taken from.
|
||||
A message reference names the message that a reply points at, that a forward was taken from, or that a copy was published from.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the referenced channel |
|
||||
| message_id | snowflake | The ID of the referenced message |
|
||||
| message_id?<sup>1</sup> | snowflake | The ID of the referenced message |
|
||||
| guild_id? | snowflake | The ID of the referenced guild, present only for a guild message |
|
||||
| type | integer | [Message reference type](#message-reference-types) |
|
||||
|
||||
<sup>1</sup> Absent only on a `CHANNEL_FOLLOW_ADD` [system message](#message-types), whose reference names a channel and no message
|
||||
|
||||
## Message reference types
|
||||
|
||||
| Value | Name | Description |
|
||||
| --- | --- | --- |
|
||||
| 0 | DEFAULT<sup>1</sup> | Reply reference |
|
||||
| 0 | DEFAULT<sup>1</sup> <sup>3</sup> | Reply reference, or the source of a published copy |
|
||||
| 1 | FORWARD<sup>2</sup> | Forward reference represented by snapshots |
|
||||
|
||||
<sup>1</sup> The default when a create message request supplies `message_reference` without a `type`
|
||||
|
||||
<sup>2</sup> Requires `channel_id` in the create message request, and the created message has `message_snapshots` and no `referenced_message`
|
||||
|
||||
<sup>3</sup> On a message with the `IS_CROSSPOST` [flag](#message-flags), the reference names the published message in the announcement channel. The copy is not a reply and never has `referenced_message`
|
||||
|
||||
## Message reference input object
|
||||
|
||||
A message reference input names the source message a create request replies to or forwards.
|
||||
@@ -1302,6 +1314,9 @@ Modifies a message. Returns the updated [message](#message-object) object. Emits
|
||||
- A caller who does not hold the bit at all is treated as any other non-author and receives 403 `CANNOT_EDIT_OTHER_USER_MESSAGE`.
|
||||
- Embeds require [EMBED_LINKS](/http-api/permissions/) and adding an upload requires [ATTACH_FILES](/http-api/permissions/).
|
||||
- A concurrent edit can fail with 429 `RESOURCE_LOCKED` and `Retry-After: 1`.
|
||||
- A [published](#crosspost-message) message has its own edit allowance, described below.
|
||||
|
||||
Published messages: 3 edits in a row, then one every 20 minutes, code `PUBLISHED_MESSAGE_EDIT_RATE_LIMITED`. Edits by other members with Manage Messages are not limited. The allowance is kept for each message and counts author edits, [Edit webhook message](/http-api/webhooks/#edit-webhook-message) calls, and [Delete message attachment](#delete-message-attachment) calls.
|
||||
|
||||
The body is read exactly as it is for [Create message](#create-message), so a `multipart/form-data` request is accepted with the same `payload_json` and `files[N]` fields. A file that is not uploaded directly must first be planned through [Request attachment upload URLs](#request-attachment-upload-urls) and then referenced by its `upload_filename`.
|
||||
|
||||
@@ -1350,6 +1365,7 @@ An `id` that names no attachment on the message is skipped, and the edit still s
|
||||
| 403 | [error response](/http-api/#error-response) | The caller is age restricted from the channel and the request returns `NSFW_CONTENT_AGE_RESTRICTED` |
|
||||
| 403 | [error response](/http-api/#error-response) | Message sending is temporarily disabled for the guild and the request returns `FEATURE_TEMPORARILY_DISABLED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel or message does not exist |
|
||||
| 429 | [error response](/http-api/#error-response) | The published message edit allowance is used up, returning `PUBLISHED_MESSAGE_EDIT_RATE_LIMITED` |
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -1357,10 +1373,128 @@ The operation updates the message, advances its edit timestamp when `content` ch
|
||||
|
||||
It emits [Message Update](/gateway/events/#message-update) to every session that can see the channel. It writes no audit log entry, even for a moderator edit.
|
||||
|
||||
An edit of a published message, including a moderator edit, reaches every copy of it in the background. Fluxer batches the edits of one message into 10-second windows and copies the latest state once per window. A moderator in a following channel can set `SUPPRESS_EMBEDS` on a copy, and the next copied edit of the published message overwrites it.
|
||||
|
||||
### Rate limit
|
||||
|
||||
20 requests per 10 seconds for each authenticated user and channel ID, on the `channel:message:update::channel_id` bucket.
|
||||
|
||||
## Crosspost message
|
||||
|
||||
<RouteHeader method="POST" path="/v1/channels/{channel_id}/messages/{message_id}/crosspost" bot />
|
||||
|
||||
Publishes a message in an announcement channel to every channel that follows it, and returns the updated [message](#message-object) object with the `CROSSPOSTED` [flag](#message-flags). Emits a [Message Update](/gateway/events/#message-update) Gateway event. The copies are delivered in the background after the response. [Announcement channels](/topics/announcement-channels/) describes what is copied.
|
||||
|
||||
### Limitations
|
||||
|
||||
- The channel must be an announcement channel, and the caller needs [VIEW_CHANNEL](/http-api/permissions/) and [SEND_MESSAGES](/http-api/permissions/) in it.
|
||||
- The author can publish their own message, and a timed-out author is refused with 403 `COMMUNICATION_DISABLED`.
|
||||
- Publishing another member's message, or a webhook's, also requires [MANAGE_MESSAGES](/http-api/permissions/).
|
||||
- A caller who publishes another member's message without [READ_MESSAGE_HISTORY](/http-api/permissions/) can publish only a message newer than the guild's message history cutoff.
|
||||
- Only a `DEFAULT` message with no `message_snapshots` can be published. A reply, a forward, a system message, and a copy of a published message cannot.
|
||||
- A message is published once.
|
||||
- Content, rich embed text, and attachment hashes that match the instance blocklists are refused with 403 `CONTENT_BLOCKED`.
|
||||
|
||||
`MANAGE_MESSAGES` is an [elevated permission](/http-api/permissions/#elevated-permissions), so a caller who holds it with no enrolled authenticator receives 400 [`TWO_FACTOR_REQUIRED`](/http-api/errors/) in a guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated, unless they own the guild.
|
||||
|
||||
Each announcement channel can publish 10 messages in a row, then one every 6 minutes. The allowance is shared by every member, and Fluxer counts a publish only after every other check passes. A denial returns 429 `MESSAGE_CROSSPOST_RATE_LIMITED` with `X-RateLimit-Scope: shared`.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the announcement channel |
|
||||
| message_id | snowflake | The ID of the message to publish |
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [message](#message-object) object | Message was published |
|
||||
| 400 | [error response](/http-api/#error-response) | The channel is not an announcement channel, returning `ANNOUNCEMENT_CHANNEL_REQUIRED` |
|
||||
| 400 | [error response](/http-api/#error-response) | The message type or shape cannot be published, returning `MESSAGE_NOT_CROSSPOSTABLE` |
|
||||
| 400 | [error response](/http-api/#error-response) | The message was already published, returning `MESSAGE_ALREADY_CROSSPOSTED` |
|
||||
| 403 | [error response](/http-api/#error-response) | A required permission is absent, returning `MISSING_PERMISSIONS` |
|
||||
| 403 | [error response](/http-api/#error-response) | The author is timed out, returning `COMMUNICATION_DISABLED` |
|
||||
| 403 | [error response](/http-api/#error-response) | The message matches an instance blocklist, returning `CONTENT_BLOCKED` |
|
||||
| 403<sup>1</sup> | [error response](/http-api/#error-response) | Publishing is disabled for the guild, returning `FEATURE_TEMPORARILY_DISABLED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel or message does not exist, or is older than the caller's history cutoff |
|
||||
| 429<sup>2</sup> | [error response](/http-api/#error-response) | The channel publish allowance is used up |
|
||||
| 429 | [error response](/http-api/#error-response) | A concurrent change holds the message, returning `RESOURCE_LOCKED` |
|
||||
| 503 | [error response](/http-api/#error-response) | The delivery queue is full, returning `SERVICE_UNAVAILABLE` |
|
||||
|
||||
<sup>1</sup> Returned when message sending is disabled for the guild, or the guild has [ANNOUNCEMENT_CHANNELS_DISABLED](/http-api/guilds/#guild-features)
|
||||
|
||||
<sup>2</sup> `MESSAGE_CROSSPOST_RATE_LIMITED`, with the [rate limit response](/topics/rate-limits/#rate-limit-response-object) members
|
||||
|
||||
A 503 leaves the message unpublished, and the publish it counted stays counted.
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation sets `CROSSPOSTED` on the message without changing its `edited_timestamp`, and emits [Message Update](/gateway/events/#message-update) in the announcement channel. Fluxer then creates one copy in each following channel through its [channel follower webhook](/http-api/webhooks/#webhook-types), each announced by [Message Create](/gateway/events/#message-create) in that channel. A copy has the `IS_CROSSPOST` flag, a `DEFAULT` `message_reference` to the published message, and no mentions.
|
||||
|
||||
Publishing writes no audit log entry.
|
||||
|
||||
### Rate limit
|
||||
|
||||
5 requests per 5 seconds for each authenticated user and channel ID, on the `channel:message:crosspost::channel_id` bucket. The channel publish allowance above applies on top of it.
|
||||
|
||||
## Get message crosspost source
|
||||
|
||||
<RouteHeader method="GET" path="/v1/channels/{channel_id}/messages/{message_id}/crosspost-source" bot />
|
||||
|
||||
Returns the public profile of the guild a copy of a published message came from, as a [crosspost source](#crosspost-source-object) object. Clients use it to show the source guild of a copy to a reader who is not a member of that guild. Requires [VIEW_CHANNEL](/http-api/permissions/) in the channel of the copy.
|
||||
|
||||
### Limitations
|
||||
|
||||
- The caller needs the same access as [Get message](#get-message), so a message outside the caller's message history cutoff is reported as 404 `UNKNOWN_MESSAGE`.
|
||||
- The message must be a copy with the `IS_CROSSPOST` [flag](#message-flags), or the `CHANNEL_FOLLOW_ADD` [system message](#message-types) posted when the channel followed an announcement channel. Any other message returns 404 `UNKNOWN_MESSAGE`.
|
||||
- The response never says whether the caller is a member of the source guild, and it names no channel of that guild.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the channel that holds the copy |
|
||||
| message_id | snowflake | The ID of the copy or of the `CHANNEL_FOLLOW_ADD` message |
|
||||
|
||||
### Crosspost source object
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| guild | [crosspost source guild](#crosspost-source-guild-object) object | The guild the message was published from |
|
||||
|
||||
### Crosspost source guild object
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| id | snowflake | The ID of the source guild |
|
||||
| name | string | The name of the source guild |
|
||||
| icon | ?string | The hash of the source guild icon |
|
||||
| banner | ?string | The hash of the source guild banner |
|
||||
| description | ?string | The discovery description of the source guild, or null when the guild is not listed in discovery |
|
||||
| features | array of strings | The badge [features](/http-api/guilds/#guild-features) of the source guild, limited to `VERIFIED`, `PARTNERED`, and `DISCOVERABLE` |
|
||||
| approximate_member_count | ?integer | Approximate member count, or null when Fluxer cannot count the guild right now |
|
||||
| approximate_presence_count | ?integer | Approximate online member count, or null when Fluxer cannot count the guild right now |
|
||||
| discoverable | boolean | Whether the caller can join the guild through [Join discovery guild](/http-api/discovery/#join-discovery-guild) |
|
||||
|
||||
`discoverable` is true when discovery is on for the instance, the guild is listed in discovery, and its invites are not disabled. The counts are cached for 60 seconds per guild.
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [crosspost source](#crosspost-source-object) object | The source guild was returned |
|
||||
| 403 | [error response](/http-api/#error-response) | Caller lacks `VIEW_CHANNEL`, returning `MISSING_PERMISSIONS` |
|
||||
| 403 | [error response](/http-api/#error-response) | Caller lacks age verification for the channel, returning `NSFW_CONTENT_AGE_RESTRICTED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist or the caller cannot see it, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | Message does not exist, is outside the caller's history cutoff, or is not eligible, returning `UNKNOWN_MESSAGE` |
|
||||
| 404 | [error response](/http-api/#error-response) | The source guild was deleted, returning `UNKNOWN_GUILD` |
|
||||
|
||||
### Rate limit
|
||||
|
||||
20 requests per 10 seconds for each authenticated user and channel ID, on the `channel:message:crosspost_source::channel_id` bucket.
|
||||
|
||||
## Clear channel read state
|
||||
|
||||
<RouteHeader method="DELETE" path="/v1/channels/{channel_id}/messages/ack" bot />
|
||||
@@ -1431,6 +1565,8 @@ There is no grace period and no restore operation.
|
||||
|
||||
The operation permanently deletes the message, its reactions, and its attachments and removes it from search.
|
||||
|
||||
Deleting a [published](#crosspost-message) message keeps each copy of it in the following channels and turns it into a placeholder in the background. The content of the placeholder is `[Original message deleted]`, its attachments, embeds, and stickers are removed, its `edited_timestamp` is set, and it has the `SOURCE_MESSAGE_DELETED` [flag](#message-flags). Deleting a copy removes that copy alone.
|
||||
|
||||
It emits [Message Delete](/gateway/events/#message-delete) to every session that can see the channel. When the message was pinned, it also removes the channel pin entry and emits [Channel Pins Update](/gateway/events/#channel-pins-update) with the channel's unchanged last-pin timestamp.
|
||||
|
||||
Every deletion in a guild channel writes one `MESSAGE_DELETE` guild audit log entry with the supplied reason, including deletion of the caller's own message.
|
||||
@@ -1473,7 +1609,7 @@ The response is 204 whether the message survives or is deleted, so the emitted G
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation permanently deletes the stored attachment object, purges it from media delivery, removes it from the message, and advances the message's edit timestamp. It then emits [Message Update](/gateway/events/#message-update) to every session that can see the channel. When the removed attachment was the message's only attachment and the message has no content, embeds, or stickers, Fluxer deletes the whole message instead, with the complete [Delete message](#delete-message) effect set, including its Dispatches and guild audit log entry.
|
||||
The operation permanently deletes the stored attachment object, purges it from media delivery, removes it from the message, and advances the message's edit timestamp. On a [published](#crosspost-message) message it counts toward the published message edit allowance described under [Modify message](#modify-message), and a denial returns 429 `PUBLISHED_MESSAGE_EDIT_RATE_LIMITED`. It then emits [Message Update](/gateway/events/#message-update) to every session that can see the channel. When the removed attachment was the message's only attachment and the message has no content, embeds, or stickers, Fluxer deletes the whole message instead, with the complete [Delete message](#delete-message) effect set, including its Dispatches and guild audit log entry.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Every bit Fluxer defines appears below.
|
||||
| 1 << 8 | PRIORITY_SPEAKER | Use priority speaker in a guild voice channel, evaluated by the client |
|
||||
| 1 << 9 | STREAM<sup>2</sup> | Publish a screenshare track on a guild voice connection |
|
||||
| 1 << 10 | VIEW_CHANNEL<sup>3</sup> | View a guild channel |
|
||||
| 1 << 11 | SEND_MESSAGES | Send a message in a guild text or voice channel |
|
||||
| 1 << 11 | SEND_MESSAGES | Send a message in a guild text, announcement, or voice channel, and publish the caller's own announcement |
|
||||
| 1 << 12 | SEND_TTS_MESSAGES | Send a message that requests text-to-speech playback |
|
||||
| 1 << 13 | MANAGE_MESSAGES<sup>4</sup> | Delete another member's message, bulk delete messages, remove another member's reaction, and edit the flags and attachments of another member's message |
|
||||
| 1 << 14 | EMBED_LINKS | Have a link in the caller's own message expanded into an embed |
|
||||
@@ -46,7 +46,7 @@ Every bit Fluxer defines appears below.
|
||||
| 1 << 26 | CHANGE_NICKNAME | Change the caller's own nickname in the guild |
|
||||
| 1 << 27 | MANAGE_NICKNAMES | Change another member's nickname in the guild |
|
||||
| 1 << 28 | MANAGE_ROLES<sup>5</sup> | Create, modify, position, and delete a role, assign a role to a member, and set a channel permission overwrite |
|
||||
| 1 << 29 | MANAGE_WEBHOOKS | Create, modify, and delete a webhook on a guild channel |
|
||||
| 1 << 29 | MANAGE_WEBHOOKS | Create, modify, and delete a webhook on a guild channel, and follow an announcement channel into it |
|
||||
| 1 << 30 | MANAGE_EXPRESSIONS<sup>6</sup> | Modify and delete an emoji or sticker created by another member |
|
||||
| 1 << 37 | USE_EXTERNAL_STICKERS | Use a sticker owned by another guild |
|
||||
| 1 << 40 | MODERATE_MEMBERS | Apply and clear a member communication timeout |
|
||||
@@ -96,6 +96,14 @@ Fluxer trims and lowercases each name in the header before matching it. A name t
|
||||
|
||||
On [Create guild role](#create-guild-role), a request that supplies `permissions` without declaring the feature always creates the role with `VIEW_CHANNEL_MEMBERS` cleared. A request that omits `permissions` copies the everyone role mask without applying the gate.
|
||||
|
||||
## Announcement channel permissions
|
||||
|
||||
[Crosspost message](/http-api/messages/#crosspost-message) publishes a message from an announcement channel. The author needs `VIEW_CHANNEL` and `SEND_MESSAGES` in the channel. Publishing another member's message, or a webhook's, needs `MANAGE_MESSAGES` as well, and a caller without `READ_MESSAGE_HISTORY` can publish only a message newer than the guild's message history cutoff.
|
||||
|
||||
[Follow announcement channel](/http-api/channels/#follow-announcement-channel) needs `VIEW_CHANNEL` on the announcement channel alone, so any member who can read it can follow it. The follow is created in the target guild, which needs `MANAGE_WEBHOOKS` at guild level and `VIEW_CHANNEL` and `MANAGE_WEBHOOKS` in the target channel. A guild receives copies only in a channel that one of its webhook managers chose.
|
||||
|
||||
Delivery rechecks access. While the account that created a follow cannot view the announcement channel, no new copy is delivered through it.
|
||||
|
||||
## Elevated permissions
|
||||
|
||||
`KICK_MEMBERS`, `BAN_MEMBERS`, `ADMINISTRATOR`, `MANAGE_CHANNELS`, `MANAGE_GUILD`, `MANAGE_MESSAGES`, `MANAGE_ROLES`, `MANAGE_WEBHOOKS`, and `MODERATE_MEMBERS` are elevated permissions. In a guild whose [MFA level](/http-api/guilds/#mfa-levels) is elevated, only the guild owner uses them without an enrolled authenticator.
|
||||
@@ -138,6 +146,7 @@ While the timeout is in the future, these operations fail with 403 `COMMUNICATIO
|
||||
|
||||
- Sending a message
|
||||
- Editing the caller's own message
|
||||
- Publishing the caller's own message in an announcement channel
|
||||
- Adding a reaction
|
||||
- Requesting an attachment upload
|
||||
- Starting a typing indicator
|
||||
|
||||
@@ -82,6 +82,7 @@ An over-length string draws two entries for the one path.
|
||||
| chat_input? | [chat input settings](#chat-input-settings-object) object | Chat composer behaviour preferences |
|
||||
| save_camera_uploads_to_device?<sup>4</sup> | bool | Whether a camera upload is also written to the device |
|
||||
| double_tap_reaction? | [reaction emoji](#reaction-emoji-object) object | Emoji a double tap on a message adds as a reaction |
|
||||
| announcement_prompts? | [announcement prompts state](#announcement-prompts-state-object) object | Hidden announcement channel prompt state |
|
||||
|
||||
<sup>1</sup> The entries live inside the snapshot, and the account [memes](/http-api/memes/) collection holds none of them
|
||||
|
||||
@@ -761,6 +762,16 @@ The `voice_prompts` field stores which voice confirmation prompts the account ha
|
||||
| skip_hide_own_camera_confirm | bool | Whether hiding the caller's own camera skips confirmation |
|
||||
| skip_hide_own_screenshare_confirm | bool | Whether hiding the caller's own screen share skips confirmation |
|
||||
|
||||
## Announcement prompts state object
|
||||
|
||||
The `announcement_prompts` field stores which announcement channel prompts the account has hidden.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| hide_publish_nudge | bool | Whether the prompt to publish a new message in an [announcement channel](/topics/announcement-channels/) is hidden |
|
||||
|
||||
## Sudo prompt state object
|
||||
|
||||
The `sudo_prompt` field stores the verification method the account used most recently, so a client can preselect it the next time [sudo mode](/http-api/users/mfa/#sudo-mode) is required.
|
||||
|
||||
@@ -6,7 +6,7 @@ description: Webhook objects, management, token-authenticated execution, and the
|
||||
|
||||
import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
A webhook posts messages into one guild channel under a name and avatar of its own.
|
||||
A webhook posts messages into one guild channel under a name and avatar of its own. An incoming webhook posts what its token holder sends, and a channel follower webhook posts the messages [published](/http-api/messages/#crosspost-message) in an announcement channel the channel follows.
|
||||
|
||||
Management routes take a user session token or a bot token. Every other webhook operation uses the webhook ID and secret token in its own path as the complete credential. A sent `Authorization` header grants no access there, and Fluxer still resolves it, as [Rate limit keying](#rate-limit-keying) describes..
|
||||
|
||||
@@ -40,7 +40,7 @@ A request that sends no `Origin`, or any other `Origin` value, passes the check.
|
||||
|
||||
## Webhook object
|
||||
|
||||
A webhook belongs to exactly one guild and posts into exactly one guild text or voice channel of that guild. Fluxer generates its execution token once at creation and never rotates it, so the pair of ID and token is a bearer credential for the lifetime of the webhook.
|
||||
A webhook belongs to exactly one guild and posts into exactly one guild text, voice, or announcement channel of that guild. Fluxer generates the execution token of an incoming webhook once at creation and never rotates it, so the pair of ID and token is a bearer credential for the lifetime of the webhook. A channel follower webhook has no usable token.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -51,13 +51,18 @@ A webhook belongs to exactly one guild and posts into exactly one guild text or
|
||||
| channel_id | snowflake | The ID of the channel the webhook sends messages to |
|
||||
| name | string | The name the webhook posts under (1-80 characters) |
|
||||
| avatar | ?string | The avatar hash the webhook posts under, or null when no avatar is set |
|
||||
| token<sup>1</sup> | string | The secret token that authorises execution of this webhook |
|
||||
| type | integer | The [webhook type](#webhook-types) |
|
||||
| token?<sup>1</sup> | string | The secret token that authorises execution of this webhook |
|
||||
| user<sup>2</sup> | [partial user](/http-api/users/#partial-user-object) object | The account that created the webhook |
|
||||
| source_guild?<sup>3</sup> | [webhook source guild](#webhook-source-guild-object) object | The guild of the followed announcement channel |
|
||||
| source_channel?<sup>3</sup> | [webhook source channel](#webhook-source-channel-object) object | The followed announcement channel |
|
||||
|
||||
<sup>1</sup> 64 characters drawn from the 62-character alphanumeric alphabet. No operation rotates or reissues it
|
||||
<sup>1</sup> 64 characters drawn from the 62-character alphanumeric alphabet. No operation rotates or reissues it. Absent on a channel follower webhook
|
||||
|
||||
<sup>2</sup> Absent from the [token webhook object](#token-webhook-object). The value is the deleted user partial when the creating account no longer exists
|
||||
|
||||
<sup>3</sup> Present only on a channel follower webhook, and only while the account that created it can view the announcement channel
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
@@ -77,9 +82,47 @@ A webhook belongs to exactly one guild and posts into exactly one guild text or
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook types
|
||||
|
||||
| Value | Name | Description |
|
||||
| --- | --- | --- |
|
||||
| 1 | INCOMING | Webhook that posts the messages its token holder sends |
|
||||
| 2 | CHANNEL_FOLLOWER<sup>1</sup> | Webhook that posts copies of the messages published in a followed announcement channel |
|
||||
|
||||
<sup>1</sup> Created by [Follow announcement channel](/http-api/channels/#follow-announcement-channel), never by [Create webhook](#create-webhook)
|
||||
|
||||
The `avatar` of a channel follower webhook is a copy of the source guild icon at the time of the follow, or null when the source guild had none. It never changes after that. Copies it delivers use the source guild icon at the time of delivery as their author avatar, stored under the webhook ID, and a null avatar when the source guild has no icon.
|
||||
|
||||
A channel follower webhook is managed through [Get webhook](#get-webhook), [Update webhook](#update-webhook), and [Delete webhook](#delete-webhook) with the same permissions as an incoming webhook, and both list routes return it. Every [token route](#token-routes) returns 404 `UNKNOWN_WEBHOOK` for it, so nothing can post through it except Fluxer.
|
||||
|
||||
While the account that created a channel follower webhook cannot view the announcement channel, the webhook has no `source_guild` or `source_channel` and no new copies arrive through it. That covers a creator who left or was removed from the source guild, and one who lost `VIEW_CHANNEL` there. Delivery resumes for messages published after access returns. Deleting the webhook and following the channel again from an account that can view it also restores delivery.
|
||||
|
||||
## Webhook source guild object
|
||||
|
||||
The guild that owns the announcement channel a channel follower webhook follows.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| id | snowflake | The ID of the guild that owns the followed announcement channel |
|
||||
| name | string | The name of that guild |
|
||||
| icon? | ?string | The icon hash of that guild, or null when it has none |
|
||||
|
||||
## Webhook source channel object
|
||||
|
||||
The announcement channel a channel follower webhook follows.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| id | snowflake | The ID of the followed announcement channel |
|
||||
| name | string | The name of that channel |
|
||||
|
||||
## Token webhook object
|
||||
|
||||
Token-authenticated operations return this object. It has every field of the [webhook object](#webhook-object) except `user`.
|
||||
Token-authenticated operations return this object. It has every field of the [webhook object](#webhook-object) except `user`, `source_guild`, and `source_channel`, and its `token` is always present.
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -90,10 +133,11 @@ Token-authenticated operations return this object. It has every field of the [we
|
||||
| channel_id | snowflake | The ID of the channel the webhook sends messages to |
|
||||
| name | string | The name the webhook posts under (1-80 characters) |
|
||||
| avatar | ?string | The avatar hash the webhook posts under, or null when no avatar is set |
|
||||
| type | integer | The [webhook type](#webhook-types), always `1` here |
|
||||
| token | string | The secret token that authorises execution of this webhook |
|
||||
|
||||
:::danger[The token is returned in full]
|
||||
Every operation returning a [webhook object](#webhook-object) or a token webhook object includes the complete execution token, so its response body grants full execution and management authority over each webhook it names.
|
||||
Every operation returning a [webhook object](#webhook-object) of an incoming webhook or a token webhook object includes the complete execution token, so its response body grants full execution and management authority over each webhook it names.
|
||||
:::
|
||||
|
||||
## Webhook message body
|
||||
@@ -760,13 +804,13 @@ A webhook is returned only when the caller also holds both `VIEW_CHANNEL` and `M
|
||||
|
||||
<RouteHeader method="GET" path="/v1/channels/{channel_id}/webhooks" bot />
|
||||
|
||||
Returns an array of [webhook objects](#webhook-object) in a guild text or voice channel. The caller must be able to view the channel and hold the [MANAGE_WEBHOOKS](/http-api/permissions/) permission at guild level and in the channel.
|
||||
Returns an array of [webhook objects](#webhook-object) in a guild text, voice, or announcement channel. The caller must be able to view the channel and hold the [MANAGE_WEBHOOKS](/http-api/permissions/) permission at guild level and in the channel.
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the guild text or voice channel whose webhooks are returned |
|
||||
| channel_id | snowflake | The ID of the guild text, voice, or announcement channel whose webhooks are returned |
|
||||
|
||||
### Response
|
||||
|
||||
@@ -781,7 +825,7 @@ Returns an array of [webhook objects](#webhook-object) in a guild text or voice
|
||||
| 403<sup>2</sup> | [error response](/http-api/#error-response) | The caller is not a member of the channel's guild |
|
||||
| 403<sup>2</sup> | [error response](/http-api/#error-response) | The caller lacks channel access or `MANAGE_WEBHOOKS` |
|
||||
| 403<sup>2</sup> | [error response](/http-api/#error-response) | The channel is age restricted and the account is not age verified |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist or is not a guild text or voice channel, each returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist or is not a guild text, voice, or announcement channel, each returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | Its guild does not resolve, returning `UNKNOWN_GUILD` |
|
||||
|
||||
<sup>1</sup> The missing-authenticator code is `TWO_FACTOR_REQUIRED`, returned only after Fluxer confirms the permission itself
|
||||
@@ -796,7 +840,7 @@ Returns an array of [webhook objects](#webhook-object) in a guild text or voice
|
||||
|
||||
<RouteHeader method="POST" path="/v1/channels/{channel_id}/webhooks" bot auditReason />
|
||||
|
||||
Creates a webhook in a guild text or voice channel and returns the new [webhook object](#webhook-object) with its execution token. The caller must be able to view the channel and hold the [MANAGE_WEBHOOKS](/http-api/permissions/) permission at guild level and in the channel. The operation accepts an audit reason.
|
||||
Creates a webhook in a guild text, voice, or announcement channel and returns the new [webhook object](#webhook-object) with its execution token. The caller must be able to view the channel and hold the [MANAGE_WEBHOOKS](/http-api/permissions/) permission at guild level and in the channel. The operation accepts an audit reason.
|
||||
|
||||
Creation emits a [Webhooks Update](/gateway/events/#webhooks-update).
|
||||
|
||||
@@ -806,7 +850,7 @@ The guild allowance is the resolved [max_webhooks_per_guild](/http-api/instance/
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| channel_id | snowflake | The ID of the guild text or voice channel the webhook is created in |
|
||||
| channel_id | snowflake | The ID of the guild text, voice, or announcement channel the webhook is created in |
|
||||
|
||||
### JSON body
|
||||
|
||||
@@ -835,7 +879,7 @@ The decoded bytes must be at most the resolved [avatar_max_size](/http-api/insta
|
||||
| 403 | [error response](/http-api/#error-response) | The channel is age restricted and the account is not age verified |
|
||||
| 403 | [error response](/http-api/#error-response) | The name is blocked |
|
||||
| 403 | [error response](/http-api/#error-response) | The avatar hash is banned |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | Channel does not exist or is not a guild text, voice, or announcement channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | Its guild does not resolve, returning `UNKNOWN_GUILD` |
|
||||
|
||||
<sup>1</sup> An avatar failure names the `avatar` path with `BASE64_LENGTH_INVALID`, `INVALID_BASE64_FORMAT`, `IMAGE_SIZE_EXCEEDS_LIMIT`, or `INVALID_IMAGE_FORMAT`
|
||||
@@ -856,7 +900,7 @@ The decoded bytes must be at most the resolved [avatar_max_size](/http-api/insta
|
||||
|
||||
### Side effects
|
||||
|
||||
A successful call consumes one guild and channel webhook slot, generates a 64-character execution token, and stores the optional avatar. It records a `WEBHOOK_CREATE` guild audit entry naming the created webhook, with the target channel in its metadata and the supplied reason. A failure to write that entry does not fail the request, and the webhook still exists.
|
||||
A successful call consumes one guild and channel webhook slot, generates a 64-character execution token, and stores the optional avatar. It records a `WEBHOOK_CREATE` guild audit entry naming the created webhook, with the target channel and the [webhook type](#webhook-types) in its metadata and the supplied reason. A failure to write that entry does not fail the request, and the webhook still exists.
|
||||
|
||||
Guild sessions that can view the channel receive [Webhooks Update](/gateway/events/#webhooks-update) with the guild and channel IDs. A failed creation leaves no webhook and consumes no slot.
|
||||
|
||||
@@ -906,6 +950,8 @@ Updates a webhook and returns the modified [webhook object](#webhook-object). Em
|
||||
|
||||
- The caller is a member of the webhook's guild and holds [MANAGE_WEBHOOKS](/http-api/permissions/) at guild level and in the webhook's current channel.
|
||||
- Moving the webhook also requires channel access and that permission in the destination channel, which must belong to the same guild.
|
||||
- A channel follower webhook accepts only `name` and `channel_id`. Any `avatar` member, null included, returns 400 `INVALID_FORM_BODY` with an `INVALID_FORMAT` error at `avatar`, and nothing is updated.
|
||||
- A channel follower webhook moves only into a guild text channel that does not already follow the same announcement channel, and the destination must meet the same age restriction and content warning rules as [Follow announcement channel](/http-api/channels/#follow-announcement-channel).
|
||||
- The operation accepts an audit reason.
|
||||
|
||||
### Path parameters
|
||||
@@ -920,17 +966,13 @@ Updates a webhook and returns the modified [webhook object](#webhook-object). Em
|
||||
| --- | --- | --- |
|
||||
| name? | string | Replacement webhook name (1-80 characters) |
|
||||
| avatar?<sup>1</sup> | ?base64 string | Base64-encoded replacement avatar, or null to remove the current avatar |
|
||||
| channel_id?<sup>2</sup> | snowflake | Destination guild text or voice channel in the same guild |
|
||||
| channel_id?<sup>2</sup> | snowflake | Destination guild text, voice, or announcement channel in the same guild |
|
||||
|
||||
<sup>1</sup> An omitted avatar leaves the stored hash unchanged, and an explicit null clears it. The same encoding, size, and format rules as [create webhook](#create-webhook) apply
|
||||
<sup>1</sup> An omitted avatar leaves the stored hash unchanged, and an explicit null clears it. The same encoding, size, and format rules as [create webhook](#create-webhook) apply. A channel follower webhook rejects the member, null included
|
||||
|
||||
<sup>2</sup> A destination equal to the current channel is a no-op, and any other destination is subject to the destination channel's own [max_webhooks_per_channel](/http-api/instance/#limit-keys) allowance
|
||||
|
||||
:::caution[A move notifies the source channel only]
|
||||
One Dispatch is emitted, naming the webhook's channel as it stood before the update. Sessions watching the destination channel receive nothing, so a client tracking a channel's webhook set refreshes it itself.
|
||||
:::
|
||||
|
||||
The returned webhook object has the destination channel.
|
||||
The returned webhook object has the destination channel. Copies a channel follower webhook delivered before a move stay in the previous channel.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -939,6 +981,7 @@ The returned webhook object has the destination channel.
|
||||
| 200 | [webhook](#webhook-object) object | Webhook was updated |
|
||||
| 400 | [error response](/http-api/#error-response) | Body or avatar is invalid |
|
||||
| 400<sup>1</sup> | [error response](/http-api/#error-response) | The destination channel already holds its maximum webhooks |
|
||||
| 400<sup>3</sup> | [error response](/http-api/#error-response) | A channel follower webhook cannot move into the destination channel |
|
||||
| 400 | [error response](/http-api/#error-response) | The caller holds `MANAGE_WEBHOOKS` with no enrolled authenticator in an elevated-MFA guild |
|
||||
| 403 | [error response](/http-api/#error-response) | Credential is a bearer token |
|
||||
| 403 | [error response](/http-api/#error-response) | The account has an outstanding required action |
|
||||
@@ -948,7 +991,7 @@ The returned webhook object has the destination channel.
|
||||
| 403 | [error response](/http-api/#error-response) | The name is blocked |
|
||||
| 403 | [error response](/http-api/#error-response) | The avatar hash is banned |
|
||||
| 404 | [error response](/http-api/#error-response) | Webhook does not exist, returning `UNKNOWN_WEBHOOK` |
|
||||
| 404 | [error response](/http-api/#error-response) | The destination channel is missing or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The destination channel is missing or is not a guild text, voice, or announcement channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The destination channel belongs to another guild, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The webhook's guild does not resolve, returning `UNKNOWN_GUILD` |
|
||||
|
||||
@@ -956,11 +999,16 @@ The returned webhook object has the destination channel.
|
||||
|
||||
<sup>2</sup> The age restriction applies to the destination channel
|
||||
|
||||
<sup>3</sup> `INVALID_FOLLOW_TARGET_CHANNEL`, `CHANNEL_ALREADY_FOLLOWED`, `FOLLOW_TARGET_NOT_AGE_RESTRICTED`, or `FOLLOW_TARGET_CONTENT_WARNING_REQUIRED`
|
||||
|
||||
| Condition | Error |
|
||||
| --- | --- |
|
||||
| Destination channel already holds its maximum webhooks | 400 `MAX_WEBHOOKS_PER_CHANNEL` |
|
||||
| Follower webhook destination is not a guild text channel | 400 `INVALID_FOLLOW_TARGET_CHANNEL` |
|
||||
| Destination already follows the same announcement channel | 400 `CHANNEL_ALREADY_FOLLOWED` |
|
||||
| Destination fails the age restriction or content warning rule | 400 `FOLLOW_TARGET_NOT_AGE_RESTRICTED` or `FOLLOW_TARGET_CONTENT_WARNING_REQUIRED` |
|
||||
| Caller holds the permission but has no enrolled authenticator | 400 `TWO_FACTOR_REQUIRED` |
|
||||
| Schema or image failure | 400 `INVALID_FORM_BODY` |
|
||||
| Schema or image failure, or an avatar on a channel follower webhook | 400 `INVALID_FORM_BODY` |
|
||||
| Name is blocked, or the avatar hash is banned | 403 `CONTENT_BLOCKED` |
|
||||
| Account has an outstanding required action | 403 `ACCOUNT_SUSPICIOUS_ACTIVITY` |
|
||||
| Unverified account in an age restricted destination channel | 403 `NSFW_CONTENT_AGE_RESTRICTED` |
|
||||
@@ -969,9 +1017,9 @@ The returned webhook object has the destination channel.
|
||||
|
||||
### Side effects
|
||||
|
||||
An update replaces the supplied fields, uploading a new avatar when one is supplied and clearing the stored hash when avatar is null. It records a `WEBHOOK_UPDATE` guild audit entry containing the previous and next webhook snapshots, the webhook's channel in its metadata, and the supplied audit reason.
|
||||
An update replaces the supplied fields, uploading a new avatar when one is supplied and clearing the stored hash when avatar is null. It records a `WEBHOOK_UPDATE` guild audit entry containing the previous and next webhook snapshots, the webhook's channel and type in its metadata, and the supplied audit reason.
|
||||
|
||||
It emits [Webhooks Update](/gateway/events/#webhooks-update) with the guild ID and the webhook's previous channel ID to guild sessions that can view that channel.
|
||||
It emits [Webhooks Update](/gateway/events/#webhooks-update) with the guild ID and the webhook's previous channel ID to guild sessions that can view that channel. A move emits a second Webhooks Update for the destination channel.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -983,7 +1031,7 @@ It emits [Webhooks Update](/gateway/events/#webhooks-update) with the guild ID a
|
||||
|
||||
Permanently deletes a webhook and returns 204 with an empty body on success. The caller must be a member of the webhook's guild with [MANAGE_WEBHOOKS](/http-api/permissions/) at guild level and in the webhook's channel. The operation accepts an audit reason.
|
||||
|
||||
Deletion emits a [Webhooks Update](/gateway/events/#webhooks-update).
|
||||
Deletion emits a [Webhooks Update](/gateway/events/#webhooks-update). Deleting a channel follower webhook unfollows the announcement channel, and the copies it already delivered stay in the channel.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -1013,7 +1061,7 @@ No operation restores the webhook or reissues its token. Messages it already cre
|
||||
|
||||
### Side effects
|
||||
|
||||
The operation removes the webhook, frees its guild and channel webhook slot, and records a `WEBHOOK_DELETE` guild audit entry with the deleted webhook, its channel in the entry metadata, and the supplied reason. It emits [Webhooks Update](/gateway/events/#webhooks-update) with the guild and channel IDs to guild sessions that can view the channel.
|
||||
The operation removes the webhook, frees its guild and channel webhook slot, and records a `WEBHOOK_DELETE` guild audit entry with the deleted webhook, its channel and type in the entry metadata, and the supplied reason. It emits [Webhooks Update](/gateway/events/#webhooks-update) with the guild and channel IDs to guild sessions that can view the channel.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1021,7 +1069,7 @@ The operation removes the webhook, frees its guild and channel webhook slot, and
|
||||
|
||||
## Token routes
|
||||
|
||||
The routes below take the matching webhook ID and token in their paths as the complete credential. None of them requires the `Authorization` header, and a header sent to one grants no access. Fluxer still resolves it, as [Rate limit keying](#rate-limit-keying) describes.
|
||||
The routes below take the matching webhook ID and token in their paths as the complete credential. Each returns 404 `UNKNOWN_WEBHOOK` for a [channel follower webhook](#webhook-types). None of them requires the `Authorization` header, and a header sent to one grants no access. Fluxer still resolves it, as [Rate limit keying](#rate-limit-keying) describes.
|
||||
|
||||
## Get webhook with token
|
||||
|
||||
@@ -1190,7 +1238,7 @@ An attachment metadata entry whose `id` matches a supplied file index supplies t
|
||||
| 403 | [error response](/http-api/#error-response) | Request has a first-party web client `Origin`, returning `INVALID_API_ORIGIN` |
|
||||
| 403 | [error response](/http-api/#error-response) | The resolved content or embed text is blocked, returning `CONTENT_BLOCKED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text, voice, or announcement channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The referenced message does not exist in the webhook's channel, returning `UNKNOWN_MESSAGE` |
|
||||
|
||||
<sup>1</sup> `CANNOT_SEND_EMPTY_MESSAGE` is returned as the 400 error code itself. Every other code below, including `CONTENT_EXCEEDS_MAX_LENGTH`, sits inside an `INVALID_FORM_BODY` body
|
||||
@@ -1387,7 +1435,7 @@ A callback that renders nothing, and one whose message creation fails, both leav
|
||||
| 204 | empty | Callback was accepted, was not renderable, or repeated a delivery within the deduplication window |
|
||||
| 403 | [error response](/http-api/#error-response) | Rendered embed text is blocked, returning `CONTENT_BLOCKED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text, voice, or announcement channel, returning `UNKNOWN_CHANNEL` |
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -1429,7 +1477,7 @@ The body is a [Slack callback](#slack-callback-object) object.
|
||||
| 400<sup>1</sup> | [error response](/http-api/#error-response) | The converted embed count exceeds its effective ceiling |
|
||||
| 403 | [error response](/http-api/#error-response) | Converted content or embed text is blocked, returning `CONTENT_BLOCKED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text, voice, or announcement channel, returning `UNKNOWN_CHANNEL` |
|
||||
|
||||
<sup>1</sup> The [error code](/http-api/errors/) is `CANNOT_SEND_EMPTY_MESSAGE` for a conversion that yields nothing, `CONTENT_EXCEEDS_MAX_LENGTH` for content that is too long, and `INVALID_FORM_BODY` with the validation code `TOO_MANY_EMBEDS` for the embed ceiling
|
||||
|
||||
@@ -1481,7 +1529,7 @@ A callback that renders nothing, and one whose message creation fails, both leav
|
||||
| 204 | empty | Callback was accepted, was not renderable, or repeated a callback within the deduplication window |
|
||||
| 403 | [error response](/http-api/#error-response) | Rendered embed text is blocked, returning `CONTENT_BLOCKED` |
|
||||
| 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` |
|
||||
| 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text, voice, or announcement channel, returning `UNKNOWN_CHANNEL` |
|
||||
|
||||
### Side effects
|
||||
|
||||
|
||||
@@ -1242,7 +1242,7 @@ Default `all_lanes`. Which lanes the worker runs. `all_lanes`, `single_lane`, or
|
||||
|
||||
#### `FLUXER_API_WORKER_LANE`
|
||||
|
||||
No default. Which lane, under `single_lane`. `realtime`, `unfurl`, `lifecycle`, or `batch`. Compose does not forward it.
|
||||
No default. Which lane, under `single_lane`. `realtime`, `unfurl`, `lifecycle`, `batch`, or `crosspost`. Compose does not forward it. A deployment that splits the worker by lane needs a `crosspost` worker, or published announcements are never delivered.
|
||||
|
||||
#### `FLUXER_API_WORKER_TASK`
|
||||
|
||||
@@ -1254,7 +1254,7 @@ No default. Whether the cron scheduler runs. Compose sets `true`. Without it no
|
||||
|
||||
#### `FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES`
|
||||
|
||||
No default. Per-lane concurrency. JSON object keyed by lane. Each value must be an integer of at least 1 or startup fails. Built-in concurrency is realtime 10, unfurl 20, lifecycle 8, batch 12. Compose forwards it from `.env`.
|
||||
No default. Per-lane concurrency. JSON object keyed by lane, `realtime`, `unfurl`, `lifecycle`, `batch`, or `crosspost`. Each value must be an integer of at least 1 or startup fails. Built-in concurrency is realtime 10, unfurl 20, lifecycle 8, batch 12, crosspost 8. Compose forwards it from `.env`.
|
||||
|
||||
#### `FLUXER_NODE_EXTRA_CA_CERTS`
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
title: Announcement channels
|
||||
description: Publishing messages from an announcement channel into the channels that follow it.
|
||||
---
|
||||
|
||||
An announcement channel is a guild text channel, [type](/http-api/channels/#channel-types) `5`, whose messages can be published. Other channels follow it, and each published message is copied into every following channel. The copy is posted by a channel follower [webhook](/http-api/webhooks/#webhook-types) that lives in the following channel.
|
||||
|
||||
Every guild can create announcement channels. [Create guild channel](/http-api/guild-channels/#create-guild-channel) accepts `type` `5`, and an announcement channel has the fields, permissions, and slowmode of a text channel.
|
||||
|
||||
## Converting a channel
|
||||
|
||||
[Modify channel](/http-api/channels/#modify-channel) converts a guild text channel into an announcement channel and back through its `type` field. No other conversion exists. The caller needs `MANAGE_CHANNELS`, the same as for any other channel change.
|
||||
|
||||
A text channel that receives copies from a followed announcement channel cannot become an announcement channel, because an announcement channel cannot follow another one. Delete its channel follower webhooks first.
|
||||
|
||||
Converting an announcement channel into a text channel removes every follow of it. The copies already delivered stay, and they keep receiving later edits and deletions of the messages they came from.
|
||||
|
||||
## Publishing
|
||||
|
||||
[Crosspost message](/http-api/messages/#crosspost-message) publishes one message. The author needs `SEND_MESSAGES`. Publishing another member's message, or a webhook's, also needs `MANAGE_MESSAGES`. A message is published once, and only a `DEFAULT` message that is not a reply, a forward, or a copy can be published.
|
||||
|
||||
Publishing sets the `CROSSPOSTED` [message flag](/http-api/messages/#message-flags) and returns at once. The copies are created in the background, usually within seconds. A channel that follows the announcement channel after a message was published never receives that message.
|
||||
|
||||
## Following
|
||||
|
||||
[Follow announcement channel](/http-api/channels/#follow-announcement-channel) creates the channel follower webhook in a guild text channel of any guild, including the announcement channel's own guild. The caller needs `VIEW_CHANNEL` on the announcement channel and `MANAGE_WEBHOOKS` in the target, so a guild receives copies only in a channel one of its webhook managers chose. The follow posts a `CHANNEL_FOLLOW_ADD` [system message](/http-api/messages/#message-types) in the target channel.
|
||||
|
||||
[Get channel follower stats](/http-api/channels/#get-channel-follower-stats) counts the following channels and their guilds. Deleting the channel follower webhook with [Delete webhook](/http-api/webhooks/#delete-webhook) unfollows.
|
||||
|
||||
## What a copy has
|
||||
|
||||
A copy is an ordinary message in the following channel with the `IS_CROSSPOST` flag. Its `message_reference` has type `DEFAULT` and names the published message, its channel, and its guild. It never has `referenced_message`.
|
||||
|
||||
| Part | In the copy |
|
||||
| --- | --- |
|
||||
| Author | The channel follower webhook, with its name and the source guild icon at the time of delivery |
|
||||
| Content | The published content, character for character |
|
||||
| Embeds | Every embed, with the same URLs as in the published message |
|
||||
| Attachments | The attachments of the published message, in order, with the same IDs, file names, sizes, and metadata |
|
||||
| Stickers | The same stickers |
|
||||
| Flags | `IS_CROSSPOST` plus `SUPPRESS_EMBEDS`, `SUPPRESS_NOTIFICATIONS`, and `VOICE_MESSAGE` from the source |
|
||||
| Mentions | None, so an `@everyone` in the published message notifies nobody in a following guild |
|
||||
|
||||
A copy has no files of its own. The `url` and `proxy_url` of each attachment in a copy point at the file of the published message, in the announcement channel, so every copy serves the same file. An attachment of a copy expires when the attachment of the published message does. An attachment removed from the published message by an edit leaves every copy, and one added by an edit appears in every copy.
|
||||
|
||||
The follower webhook is named after the source guild and the announcement channel, as in `Fluxer #updates`, and its avatar is a copy of the source guild icon at the time of the follow. The following guild can rename it, and later copies use the new name. Its avatar cannot be changed and does not follow later icon changes.
|
||||
|
||||
A new copy uses the source guild icon at the time of delivery as its author avatar, not the webhook avatar. When the source guild has no icon, the copy has a null avatar and shows the default avatar for the webhook ID. Copies already delivered keep their name and avatar, edits included.
|
||||
|
||||
[Get message crosspost source](/http-api/messages/#get-message-crosspost-source) returns the public profile of the source guild of a copy, so a reader who is not a member of it can see where the copy came from.
|
||||
|
||||
## Content rules
|
||||
|
||||
An age restriction and a content warning each resolve through the channel, then its parent category, and then its guild.
|
||||
|
||||
- An age-restricted announcement channel can only be followed into an age-restricted channel.
|
||||
- An announcement channel with a content warning can only be followed into a channel that has a content warning or an age restriction.
|
||||
- An attachment or embed flagged as explicit media is left out of a copy when the following channel does not allow explicit media.
|
||||
|
||||
Fluxer checks the first two rules when a follow is created and when a channel follower webhook is moved, and again at every delivery and update. When a following channel stops meeting them, new copies are skipped, and an update turns an existing copy into a deleted-source placeholder.
|
||||
|
||||
A published message whose content, rich embed text, or attachment hash matches an instance blocklist cannot be published. A later edit that matches one turns every copy into a placeholder.
|
||||
|
||||
## Delivery
|
||||
|
||||
Delivery is asynchronous. Fluxer retries a failed delivery to one channel 8 times over about 80 minutes and then drops it. A channel that already has its copy is never given a second one.
|
||||
|
||||
A follow is paused while the account that created it cannot view the announcement channel, for example after leaving the source guild or losing `VIEW_CHANNEL` there. A paused follow receives no copies, and its webhook has no `source_guild` or `source_channel`. Messages published after access returns are delivered again. Messages published during the pause are not. An edit of a published message during the pause turns its copy in that channel into a deleted-source placeholder.
|
||||
|
||||
Nothing is delivered from a guild that has [ANNOUNCEMENT_CHANNELS_DISABLED](/http-api/guilds/#guild-features), from an unavailable guild, or from a guild whose message sending is disabled. A following guild that is unavailable or has message sending disabled receives nothing either.
|
||||
|
||||
## Edits and deletions
|
||||
|
||||
An edit of a published message reaches every copy that still exists, including copies in channels that have since unfollowed. Fluxer batches the edits of one message into 10-second windows and copies the latest state once per window, so a copy can briefly lag the published message. Each copy is announced by [Message Update](/gateway/events/#message-update).
|
||||
|
||||
Deleting a published message keeps each copy and edits it: the content becomes `[Original message deleted]`, attachments, embeds, and stickers are removed, `edited_timestamp` is set, and the `SOURCE_MESSAGE_DELETED` flag is added. The files of the published message are deleted with it. Deleting a copy removes that copy alone, and the files it showed stay with the published message.
|
||||
|
||||
When an instance administrator or a CSAM report removes a published message, every copy is deleted outright. When one of them removes a copy, the published message and every other copy are deleted too, so identical content leaves every guild at once. A published message that link preview moderation deletes takes its copies with it the same way.
|
||||
|
||||
## Cleanup
|
||||
|
||||
| Event | Follows | Copies |
|
||||
| --- | --- | --- |
|
||||
| Announcement channel deleted | Removed | Become deleted-source placeholders |
|
||||
| Announcement channel converted to text | Removed | Stay, and keep receiving edits and deletions |
|
||||
| Source guild deleted by its owner | Removed | Become deleted-source placeholders |
|
||||
| Source guild deleted by an administrator | Removed | Deleted |
|
||||
| Following channel deleted | Removed with the channel | Deleted with the channel |
|
||||
| Follower webhook deleted | Removed | Stay, and keep receiving edits and deletions |
|
||||
| Follower webhook moved | Kept | Stay in the previous channel |
|
||||
|
||||
The removal runs in the background after the triggering request. Each removed follow emits [Webhooks Update](/gateway/events/#webhooks-update) for its channel and writes no audit log entry. A follow or unfollow made through the API records `WEBHOOK_CREATE` or `WEBHOOK_DELETE` in the following guild's [audit log](/http-api/guild-audit-logs/).
|
||||
|
||||
## Limits
|
||||
|
||||
| Limit | Value |
|
||||
| --- | --- |
|
||||
| Publishes from one announcement channel | 10 in a row, then one every 6 minutes |
|
||||
| Edits of one published message | 3 in a row, then one every 20 minutes |
|
||||
| Webhooks in the following channel | 15, the `max_webhooks_per_channel` default, follower webhooks included |
|
||||
| Webhooks in the following guild | 1000, the `max_webhooks_per_guild` default, follower webhooks included |
|
||||
|
||||
An announcement channel has no cap on its follows, and a published message has no cap on its attachment size beyond the upload limits. The webhook limits are resolved against the following guild. The allowances are [enforced inside the handler](/topics/rate-limits/#limits-enforced-inside-a-handler) and answer 429 with their own codes. An edit by another member who holds `MANAGE_MESSAGES` draws on no edit allowance and still reaches the copies.
|
||||
@@ -57,7 +57,7 @@ The denial body has the members of the ordinary [error response](/http-api/#erro
|
||||
| global | boolean | Whether the global bucket produced the denial, present and false on a route denial |
|
||||
| retry_after<sup>3</sup> | number | The delay in fractional seconds before another request is admitted |
|
||||
|
||||
<sup>1</sup> A limit enforced outside the route bucket middleware can reuse this body with its own code. [Send phone verification](/http-api/users/phone-verification/#send-phone-verification) is the only live one, reporting `PHONE_RATE_LIMIT_EXCEEDED`
|
||||
<sup>1</sup> A limit enforced outside the route bucket middleware can reuse this body with its own code. [Allowances answering 429](#allowances-answering-429) and [Announcement channel allowances](#announcement-channel-allowances) list every live one
|
||||
|
||||
<sup>2</sup> The locale [resolved](/topics/locales/#negotiation) for the request, which the account setting selects ahead of [Accept-Language](/http-api/#standard-request-headers)
|
||||
|
||||
@@ -88,10 +88,12 @@ The `X-RateLimit-Scope` header is the scope that produced a denial.
|
||||
| global | The denial came from the global bucket |
|
||||
| shared<sup>1</sup> | The denial came from an allowance that several accounts can exhaust for each other |
|
||||
|
||||
<sup>1</sup> No route bucket declares a scope of its own, so every route bucket denial reports `user`. [Send phone verification](/http-api/users/phone-verification/#send-phone-verification) is the only live source of `shared`
|
||||
<sup>1</sup> No route bucket declares a scope of its own, so every route bucket denial reports `user`. Phone verification and the announcement channel allowances are the live sources of `shared`
|
||||
|
||||
Phone verification reports `shared` when the per-number send allowance or a number-scoped provider cooldown produced the denial. Both are keyed by the submitted number, so two accounts sending to one number share the allowance.
|
||||
|
||||
[Crosspost message](/http-api/messages/#crosspost-message) reports `shared` for its channel publish allowance, and an edit of a published message reports it for the per-message edit allowance. Every member who publishes or edits draws on the same allowance.
|
||||
|
||||
## Rate limit headers
|
||||
|
||||
These headers describe a rate limit decision. An operation that answers 429 returns this set.
|
||||
@@ -131,7 +133,7 @@ An allowance enforced inside a handler is keyed independently of the route bucke
|
||||
|
||||
The `disable_rate_limits` deployment switch turns off the login allowances along with both buckets. `relax_registration_rate_limits` turns off the registration allowances. Every other allowance below is enforced on every deployment.
|
||||
|
||||
A denial takes one of the shapes below. An allowance in [Allowances answering 429](#allowances-answering-429) answers 429 with the [rate limit response object](#rate-limit-response-object) and the [rate limit headers](#rate-limit-headers) minus `X-RateLimit-Bucket`. An allowance in [Allowances answering 400](#allowances-answering-400) answers 400 `INVALID_FORM_BODY` with one [validation error](/http-api/#validation-error-object) entry whose `code` names the exhausted allowance.
|
||||
A denial takes one of the shapes below. An allowance in [Allowances answering 429](#allowances-answering-429) or [Announcement channel allowances](#announcement-channel-allowances) answers 429 with the [rate limit response object](#rate-limit-response-object) and the [rate limit headers](#rate-limit-headers) minus `X-RateLimit-Bucket`. An allowance in [Allowances answering 400](#allowances-answering-400) answers 400 `INVALID_FORM_BODY` with one [validation error](/http-api/#validation-error-object) entry whose `code` names the exhausted allowance.
|
||||
|
||||
The 400 shape has no `retry_after` member, no `X-RateLimit-*` header, and no `Retry-After` header. The remaining delay appears only in the entry's localised `message`.
|
||||
|
||||
@@ -164,6 +166,17 @@ SMS provider throttling can impose an additional cooldown. It returns `PHONE_RAT
|
||||
|
||||
The Resend IP authorisation cooldown has no `X-RateLimit-*` header. It has a `Retry-After` header in whole seconds, and the body reports that delay again as a top-level `resend_available_in` and `retry_after`. A second resend on one ticket returns 400 `IP_AUTHORIZATION_RESEND_LIMIT_EXCEEDED`. The allowance never refills, and the ticket expires 15 minutes after it was issued.
|
||||
|
||||
### Announcement channel allowances
|
||||
|
||||
These allowances answer 429 the same way as the ones above, with `X-RateLimit-Scope` set to `shared`.
|
||||
|
||||
| Allowance | Code |
|
||||
| --- | --- |
|
||||
| Publishes from one announcement channel through [Crosspost message](/http-api/messages/#crosspost-message), 10 in a row, then one every 6 minutes | `MESSAGE_CROSSPOST_RATE_LIMITED` |
|
||||
| Edits of one published message through [Modify message](/http-api/messages/#modify-message) and the other edit routes, 3 in a row, then one every 20 minutes | `PUBLISHED_MESSAGE_EDIT_RATE_LIMITED` |
|
||||
|
||||
Each is a leaky bucket like the route buckets. It admits a burst of its full size and then refills one slot at the stated interval, so a caller that waits one interval can act once more. A moderator edit of another member's published message draws on no allowance. [Announcement channels](/topics/announcement-channels/#limits) lists them with the other announcement channel limits.
|
||||
|
||||
### Allowances answering 400
|
||||
|
||||
| Operation | Allowance | Validation code |
|
||||
|
||||
Reference in New Issue
Block a user