> ## Documentation Index
> Fetch the complete documentation index at: https://docs.awokenlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Discord webhooks

> Archive chat messages, audit moderation events, and log fun-command results to Discord.

Awoken Chat ships three independent webhook surfaces. Each takes a Discord webhook URL; leaving a URL empty silently disables that surface.

| Surface              | What it posts                                                        | Where it goes                   |
| -------------------- | -------------------------------------------------------------------- | ------------------------------- |
| **Channel archive**  | Every message sent in a configured channel.                          | `webhooks.channels.<channelId>` |
| **Violations log**   | Blocked-word hits, rate-limit hits, duplicate floods, admin deletes. | `webhooks.violations`           |
| **Fun-commands log** | Every `/roll`, `/coinflip`, `/8ball`, `/random` fire.                | `webhooks.funCommands`          |

All three are in `config.lua` under the same `webhooks` block:

```lua config.lua theme={"dark"}
webhooks = {
    enabled     = false,
    channels    = {
        -- ['ooc']   = 'https://discord.com/api/webhooks/...',
        -- ['staff'] = 'https://discord.com/api/webhooks/...',
    },
    violations  = '',  -- moderation log (blocked words, rate limits, admin deletes)
    funCommands = '',  -- /roll, /coinflip, /8ball, /random log
},
```

<Warning>
  Discord webhook URLs are de-facto secrets - anyone with the URL can post arbitrary embeds to that channel. Treat them like API keys. If one leaks (screenshot, support chat, PR), rotate it via Discord's channel settings → Integrations → Webhooks.
</Warning>

## The master switch

```lua theme={"dark"}
webhooks = {
    enabled = false,
    -- ...
}
```

`enabled = false` silences **every** webhook surface at once, regardless of whether URLs are configured. Useful for development - leave your live URLs in place and just flip the master switch off.

## Channel archives

Posts a Discord embed for every message sent in a channel that has a URL configured.

```lua theme={"dark"}
webhooks.channels = {
    ['ooc']   = 'https://discord.com/api/webhooks/...',
    ['staff'] = 'https://discord.com/api/webhooks/...',
    ['me']    = 'https://discord.com/api/webhooks/...',
},
```

Channels NOT listed here silently do nothing - no error, no log, no Discord post. Use this to selectively archive only the channels you care about (most servers don't need `/global` in Discord but always want `/staffchat`).

### Embed shape

| Block              | Content                                                                  |
| ------------------ | ------------------------------------------------------------------------ |
| **Author**         | `Character Name • [ID 12]` + avatar (if the player has a URL avatar set) |
| **Description**    | Message body                                                             |
| **Sidebar colour** | Channel's `color`                                                        |
| **Footer**         | Channel label (e.g. "OOC")                                               |
| **Timestamp**      | UTC, right-aligned                                                       |

Anonymous channels redact the author block to just `Anonymous` (no avatar, no `[ID N]`) when the channel has `anonymousInWebhook` enabled. Without that flag, the channel hides the sender in-game but **the webhook still shows the real character name** - default behaviour, staff audit trail wins over RP anonymity.

## Violations log

Posts a colour-coded embed every time a player trips a moderation rule.

```lua theme={"dark"}
webhooks.violations = 'https://discord.com/api/webhooks/...'
```

| Trigger                                                         | Title                   | Embed colour       |
| --------------------------------------------------------------- | ----------------------- | ------------------ |
| Blocked word match                                              | "Blocked word filtered" | Red (`#DC2626`)    |
| Rate-limit hit (burst > `maxMessages`)                          | "Rate limit hit"        | Yellow (`#EAB308`) |
| Duplicate flood (same message twice within `duplicateWindowMs`) | "Duplicate flood"       | Yellow             |
| Admin delete                                                    | "Admin delete"          | Grey (default)     |

### Fields included

Every violation embed carries:

* **Author** - the offending player's character name + server id + avatar.
* **Description** - the offending message in a code block (truncated to 1900 chars).
* **Channel** - the channel id and label (inline).
* **Matched** - the blocked-word entry that triggered the match (inline, blocked-word violations only).
* **License** - the player's `license:` identifier, full-width code block. Copy/paste for cross-session lookup.
* **Discord** - mention ping of the player's linked Discord account (inline, if linked).

<Tip>
  The Discord ping is the most useful field for moderators - clicking it jumps straight to the player's Discord profile so you can DM them, check their roles, or escalate.
</Tip>

## Fun-commands log

Audit trail for `/roll`, `/coinflip`, `/8ball`, `/random`. These commands render only as 3D floating text above the sender - they never enter chat - so the webhook IS the audit trail.

```lua theme={"dark"}
webhooks.funCommands = 'https://discord.com/api/webhooks/...'
```

| Command                 | Title           | Embed colour |
| ----------------------- | --------------- | ------------ |
| `/roll`                 | "Dice roll"     | Amber        |
| `/coinflip`             | "Coin flip"     | Cyan         |
| `/8ball` / `/eightball` | "8-Ball answer" | Purple       |
| `/random`               | "Random pick"   | Teal         |

Each embed carries:

* **Author** - real character name + server id + avatar (the audit point - chat saw none of this).
* **Command** + **Result** (both inline).
* **Question** (`/8ball` only) - the player's original question.
* **Options** (`/random` only) - the comma-joined option list.
* **License** + **Discord** ping fields.

### Why this matters

The 3D-only model is great for RP privacy ("only nearby players see my dice roll") but it leaves staff without a way to verify "did Brogan really roll a 20?". Set `webhooks.funCommands` and that audit trail comes back, without bringing the chat row back.

## Error handling

If a webhook URL is wrong (typo, deleted from Discord, geo-blocked), the request fails silently in production. Set `debug = true` in `config.lua` to log the HTTP status code:

```
[awoken_chat] webhook ooc -> http 404
[awoken_chat] violations webhook -> http 401
```

The chat send itself always succeeds regardless of webhook delivery - moderation and gameplay never block on Discord availability.

## Re-using URLs

You can point multiple surfaces at the same Discord channel if you want, e.g. one consolidated `#chat-audit` channel for both violations and fun-commands. The embed titles + colours keep them visually distinct.

```lua theme={"dark"}
webhooks = {
    enabled     = true,
    channels    = { ['staff'] = '<staff-archive-url>' },
    violations  = '<combined-audit-url>',
    funCommands = '<combined-audit-url>',   -- same URL as violations
},
```
