# correspond.channels.discord

Discord: a bot reading and posting in channels over REST, through discorddol.

Reference: `discord:<channel id>`, a channel or a thread (Discord numbers both with
snowflakes). A bot reads only the channels its server roles let it see, and since Discord
made message content a privileged intent, it reads the text of a message only when the
Message Content intent is enabled for it (Developer Portal, Bot, Privileged Gateway
Intents; a bot in more than 100 servers needs Discord’s approval) or when the message
mentions it.

- `read` fetches the channel’s history (the latest 50 messages unless `limit` says).
- `listen` polls the channel for messages newer than the last one seen (the cursor is
  that message’s id; a first poll looks back a day): new messages, not edits or
  deletions. With `listen_via = "gateway"` it opens the gateway for a short window
  instead and reports new, edited and deleted messages; the cursor is the gateway session,
  which the next listen resumes (Discord replays what was missed) rather than identifying
  again, since 1,000 identifies in a day reset the bot token.
- `send` posts a message (2,000 characters at most); a title is prepended in bold. With
  `reply_to` it is a Discord reply to that message (its author is not pinged). Only
  the users it mentions as `<@id>` are pinged: `@everyone`, `@here` and role
  mentions stay text, through Discord’s allowed mentions.
- `verify` grades an HTTP interaction Discord posted to your endpoint: `crypto` when
  its Ed25519 signature (`X-Signature-Ed25519` over `X-Signature-Timestamp` + body)
  checks against the application’s public key, `forged` otherwise. Discord sends
  deliberately invalid signatures to test the endpoint; those must come back `forged`
  (answer them 401).

Senders are `platform`: Discord authenticated the account, and correspond fetched the
message from the API itself. A server member’s role ids are its `authority`. The bot token never appears in an error or a plan.

Needs the `discord` extra (discorddol, over discord.py in REST-only mode), imported on
first use, not when the channel is listed.

```pycon
>>> Discord().parse_ref("123456789012345678").encoded
'discord:123456789012345678'
```

### Classes

| [`Discord`](#correspond.channels.discord.Discord)(\*[, backend, http, run, clock, gateway])   | A Discord bot over REST: read a channel, poll it for new messages, post to it.   |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|

### *class* correspond.channels.discord.Discord(\*, backend=None, http=<function urllib_http>, run=<function run>, clock=<built-in function time>, gateway=<function gateway_window>)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A Discord bot over REST: read a channel, poll it for new messages, post to it.

#### audience(ref, , draft=None)

A DM’s two participants (or a group DM’s members), listed; otherwise the server’s members who can view the channel, never listed.

The channel is looked up once per reference. When it cannot be (no lookup in
this backend, or Discord refused), the answer is the wider server group.

* **Return type:**
  [`Audience`](correspond.model.html.md#correspond.model.Audience)

#### *property* backend *: [Any](https://docs.python.org/3/library/typing.html#typing.Any)*

discorddol’s REST backend with the configured bot token, or the one passed in.

#### *property* capabilities *: [Capabilities](correspond.model.html.md#correspond.model.Capabilities)*

A bot’s view of Discord, over REST.

#### parse_ref(id)

A channel or thread id.

* **Return type:**
  [`ConversationRef`](correspond.model.html.md#correspond.model.ConversationRef)

#### poll(ref, , cursor=None, limit=None)

Messages newer than `cursor` (a message id), oldest first; a first poll looks back a day.

With `listen_via = "gateway"`, one gateway window instead (see the module docstring).

#### read(ref, , since=None, limit=None)

The channel’s latest messages (`limit`, else 50), oldest first; with `since`, only those after it.

* **Return type:**
  [`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`Message`](correspond.model.html.md#correspond.model.Message)]

#### send(ref, draft, , dry_run=False)

Post `draft.text` (the title, if any, prepended in bold) to the channel.

* **Return type:**
  [`SendResult`](correspond.model.html.md#correspond.model.SendResult)

#### verify(headers, body)

`crypto` when `X-Signature-Ed25519` checks over a fresh `X-Signature-Timestamp` + body, `forged` otherwise.

Raises [`MissingRequirement`](correspond.errors.html.md#correspond.errors.MissingRequirement) when the public key is unset
or unusable (a configuration error, never a verdict on the request). Replay
protection is the five-minute window only: a captured request can be replayed
within it, so act idempotently on an interaction’s id.

* **Return type:**
  [`Authenticity`](correspond.model.html.md#correspond.model.Authenticity)
