# Saving Media

Each platform's `Save` method downloads a received file to a directory and returns the saved path.

## Signatures

```go
// core/telegram — fileID from Input.Photo[i].FileID or Input.Document.FileID
func (b *Bot) Save(ctx context.Context, fileID, dir string) (string, error)

// core/discord — one entry of Input.Attachments
func (b *Bot) Save(ctx context.Context, att *discordgo.MessageAttachment, dir string) (string, error)

// core/line — Input.MessageID of an image, video, audio, or file message
func (b *Bot) Save(ctx context.Context, messageID, dir string) (string, error)
```

## Per-platform behavior

| | Telegram | Discord | LINE |
|---|---|---|---|
| Size cap | 20 MiB (Bot API `getFile` limit) | 25 MiB | 50 MiB (library limit) |
| Size known before download | `GetFile` → `FileSize` | `att.Size` | `ContentLength` |
| Download | `FileDownloadLink` over `http.DefaultClient` | `att.URL` over `http.DefaultClient` | `GetMessageContent` stream |
| Extension | From Telegram's `file_path` | From `att.Filename` | From the response `Content-Type` |

## Shared steps

1. Reject empty arguments (`fileID` / `att` / `att.URL` / `messageID`, and `dir`).
2. Reject the file when the reported size exceeds the cap, before downloading.
3. On a non-200 HTTP status (Telegram, Discord), return `http <code>: <body>` with at most 8 KiB of the body.
4. Create `dir` with `os.MkdirAll(dir, 0755)`.
5. Stream into a temporary file `<uuid>.*.tmp` inside `dir`, reading at most cap + 1 bytes. If more than the cap arrives (the reported size was missing or wrong), delete the temp file and return an error. Oversized files are never truncated.
6. Rename the temp file to `<uuid><ext>` and return the full path.

The file name is a v4 UUID from `go-pkg/utils`; an empty UUID (random source failure) is returned as an error. The original Telegram/Discord file name is not kept; for LINE file messages the original name is in `Input.FileName`.

## LINE extension mapping

`image/jpeg` → `.jpg`, `image/png` → `.png`, `image/gif` → `.gif`, `image/webp` → `.webp`, `video/mp4` → `.mp4`, `audio/mp4` and `audio/x-m4a` → `.m4a`, `audio/aac` → `.aac`, `application/pdf` → `.pdf`. Other types use the first extension from `mime.ExtensionsByType`; unknown types get no extension.

## Example

```go
bot.Reply(func(ctx context.Context, in discord.Input) string {
    for _, att := range in.Attachments {
        path, err := bot.Save(ctx, att, "./uploads")
        if err != nil {
            return "save failed: " + err.Error()
        }
        log.Println("saved", path)
    }
    return ""
})
```

## Related pages

- [Telegram Setup](/telegram) (`Input.Photo`, `Input.Document`)
- [Discord Setup](/discord) (`Input.Attachments`)
- [LINE Messages](/line-messages) (media message events)
