> For the complete documentation index, see [llms.txt](https://tyrix.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tyrix.gitbook.io/docs/script-resources/tyrix-frames/discord-logging.md).

# Discord Logging

All configured in `config_sv.lua`, which is server-only and never sent to a client - the right place for webhook URLs.

## Setup

```lua
Config_SV.DiscordLogs = {
    enabled = true,

    webhooks = {
        player  = '',
        admin   = '',
        exploit = '',
    },
    webhook = '',       -- used for any category left empty above

    botName = 'Tyrix Frames',
    botAvatar = '',

    colors = {
        success = 3066993,   -- green
        error   = 15158332,  -- red
        warning = 15105570,  -- orange
        info    = 3447003,   -- blue
    },
}
```

Fill in the single `webhook` and everything goes to one channel. Fill in the category webhooks to split them up - any category left empty falls back to `webhook`.

| Category  | Carries                                                                                                                    |
| --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `player`  | frame\_placed, frame\_moved, frame\_pickup, photo\_set, photo\_transform, photo\_dead\_link, gallery\_add, gallery\_delete |
| `admin`   | admin\_wipe, admin\_delete, admin\_tp                                                                                      |
| `exploit` | exploit\_warning                                                                                                           |

{% hint style="info" %}
Splitting `exploit` out is worth doing even on a small server - it is the channel you want to notice, and it stays quiet unless something is actually wrong.
{% endhint %}

## Log types

Toggle each individually:

```lua
logTypes = {
    frame_placed    = true,
    frame_moved     = true,
    frame_pickup    = true,
    photo_set       = true,
    photo_transform = false,
    photo_dead_link = true,
    gallery_add     = false,
    gallery_delete  = false,
    admin_wipe      = true,
    admin_delete    = true,
    admin_tp        = true,
    exploit_warning = true,
}
```

| Type              | Fires when                                 | Default |
| ----------------- | ------------------------------------------ | ------- |
| `frame_placed`    | A canvas is placed                         | on      |
| `frame_moved`     | A canvas is repositioned                   | on      |
| `frame_pickup`    | A canvas is picked up                      | on      |
| `photo_set`       | A photo is applied - **embeds the image**  | on      |
| `photo_transform` | A photo's crop is adjusted                 | **off** |
| `photo_dead_link` | A canvas reports its image no longer loads | on      |
| `gallery_add`     | A photo is saved to a collection           | off     |
| `gallery_delete`  | A photo is removed from a collection       | off     |
| `admin_wipe`      | An admin clears someone's photo            | on      |
| `admin_delete`    | An admin deletes a canvas                  | on      |
| `admin_tp`        | An admin teleports to a canvas             | on      |
| `exploit_warning` | A forged or abusive request is rejected    | on      |

## photo\_set is the moderation backbone

It embeds the image itself, alongside the frame id, size, owner and coordinates. An admin scrolling that channel sees exactly what was hung on a wall, without going in-game.

This is how moderation works here - photos go live immediately and are reviewed after the fact. There is no approval queue. If you only enable one log type, enable this one.

## Why photo\_transform is off by default

It is by far the highest-volume event - a player nudging a crop fires it repeatedly - and it carries no moderation value, because the photo itself has not changed, only how it sits in the frame. Leave it off unless you are debugging.

## Rate limiting

Discord tolerates roughly five requests per second per webhook and returns 429 beyond that. A busy server generates far more log-worthy actions than that, so posts are **queued and drained at a safe rate** rather than fired inline.

The queue is bounded and drops the **oldest** entry when full - under a flood the recent events are the ones worth keeping. If that happens you get one console line per burst:

```
[tyrix_frames] discord log queue full - dropped 100 message(s); reduce enabled logTypes
```

Seeing that means too many log types are on for your population. Turn off the low-value ones first: `photo_transform`, `gallery_add`, `gallery_delete`.

## Admin actions

Every admin action logs with the **acting admin's** player block — name, identifiers, Discord id where available — plus the frame owner's identifier as a separate field. So an admin wiping someone's photo produces a record of both parties.

## Turning it off

```lua
Config_SV.DiscordLogs.enabled = false
```

Leaving every webhook empty has the same practical effect: nothing is sent, and no error is raised.
