aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/utils/snekio.py
blob: 7c5fba648a387251d9676d0408e28115ba0aa98b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""I/O File protocols for snekbox."""
from __future__ import annotations

from base64 import b64decode, b64encode
from dataclasses import dataclass
from io import BytesIO
from pathlib import Path

from discord import File

# Note discord upload limit is 8 MB, or 50 MB for lvl 2 boosted servers
FILE_SIZE_LIMIT = 8 * 1024 * 1024  # 8 MiB


def sizeof_fmt(num: int, suffix: str = "B") -> str:
    """Return a human-readable file size."""
    for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"):
        if abs(num) < 1024:
            return f"{num:3.1f}{unit}{suffix}"
        num /= 1024
    return f"{num:.1f}Yi{suffix}"


@dataclass
class FileAttachment:
    """File Attachment from Snekbox eval."""

    path: str
    content: bytes

    def __repr__(self) -> str:
        """Return the content as a string."""
        content = f"{self.content[:10]}..." if len(self.content) > 10 else self.content
        return f"FileAttachment(path={self.path!r}, content={content})"

    @classmethod
    def from_dict(cls, data: dict, size_limit: int = FILE_SIZE_LIMIT) -> FileAttachment:
        """Create a FileAttachment from a dict response."""
        size = data.get("size")
        if (size and size > size_limit) or (len(data["content"]) > size_limit):
            raise ValueError("File size exceeds limit")

        content = b64decode(data["content"])

        if len(content) > size_limit:
            raise ValueError("File size exceeds limit")

        return cls(data["path"], content)

    def to_dict(self) -> dict[str, str]:
        """Convert the attachment to a json dict."""
        content = self.content
        if isinstance(content, str):
            content = content.encode("utf-8")

        return {
            "path": self.path,
            "content": b64encode(content).decode("ascii"),
        }

    def to_file(self) -> File:
        """Convert to a discord.File."""
        name = Path(self.path).name
        return File(BytesIO(self.content), filename=name)