Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
675eac310e | ||
|
|
caf0c3ee5c | ||
|
|
40d38661d1 | ||
|
|
8e167368a4 |
No files matched your search
@@ -9,7 +9,7 @@ bites you.
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
```
|
```
|
||||||
dpy_cache @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_cache.git@v1.0.0
|
dpy_cache @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_cache.git@v1.2.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "dpy_cache"
|
name = "dpy_cache"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
description = "Persist images/files to a Discord storage channel and get durable, re-resolvable CDN references — config-free, injectable, installable."
|
description = "Persist images/files to a Discord storage channel and get durable, re-resolvable CDN references — config-free, injectable, installable."
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from importlib.metadata import PackageNotFoundError, version
|
|||||||
|
|
||||||
from .dpy_cache import (
|
from .dpy_cache import (
|
||||||
MAX_ATTACHMENTS_PER_MESSAGE,
|
MAX_ATTACHMENTS_PER_MESSAGE,
|
||||||
|
MAX_CACHE_URL_BYTES,
|
||||||
CacheResult,
|
CacheResult,
|
||||||
DPYCache,
|
DPYCache,
|
||||||
DPYCacheError,
|
DPYCacheError,
|
||||||
@@ -25,5 +26,6 @@ __all__ = [
|
|||||||
"CacheResult",
|
"CacheResult",
|
||||||
"FileContent",
|
"FileContent",
|
||||||
"MAX_ATTACHMENTS_PER_MESSAGE",
|
"MAX_ATTACHMENTS_PER_MESSAGE",
|
||||||
|
"MAX_CACHE_URL_BYTES",
|
||||||
"__version__",
|
"__version__",
|
||||||
]
|
]
|
||||||
+21
-12
@@ -41,10 +41,11 @@ error contract (fail-loud)
|
|||||||
--------------------------
|
--------------------------
|
||||||
nothing is swallowed. lib-specific invariants raise :class:`DPYCacheError` (attachment-count
|
nothing is swallowed. lib-specific invariants raise :class:`DPYCacheError` (attachment-count
|
||||||
mismatch after a send, a duplicate filename in a list input to ``cache()``, ``cache_url``
|
mismatch after a send, a duplicate filename in a list input to ``cache()``, ``cache_url``
|
||||||
non-200, a ``lookup`` jump-url pointing at a different channel than the injected one, a
|
non-200 or a body over ``MAX_CACHE_URL_BYTES``, a ``lookup`` jump-url pointing at a
|
||||||
``discord.File`` source that can't be safely rebuilt for a repeat send). unnamed ``bytes`` in
|
different channel than the injected one, a ``discord.File`` source that can't be safely
|
||||||
a list input raises ``ValueError``. raw discord errors (``Forbidden`` / ``HTTPException`` /
|
rebuilt for a repeat send). unnamed ``bytes`` in a list input raises ``ValueError``. raw
|
||||||
``NotFound``) propagate UNWRAPPED so callers can still branch on discord's types.
|
discord errors (``Forbidden`` / ``HTTPException`` / ``NotFound``) propagate UNWRAPPED so
|
||||||
|
callers can still branch on discord's types.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -60,6 +61,10 @@ import discord
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_ATTACHMENTS_PER_MESSAGE = 10
|
MAX_ATTACHMENTS_PER_MESSAGE = 10
|
||||||
|
# cache_url's body cap: the largest upload a BOT can make (boost tier 3 guild,
|
||||||
|
# discord.py Guild.filesize_limit 104857600; bots can't have nitro), so nothing discord would
|
||||||
|
# accept from a bot is refused; it bounds memory, not the channel's own (tier) limit
|
||||||
|
MAX_CACHE_URL_BYTES = 100 * 1024 * 1024
|
||||||
|
|
||||||
FileContent = Union[bytes, bytearray, str, "os.PathLike[str]", discord.File]
|
FileContent = Union[bytes, bytearray, str, "os.PathLike[str]", discord.File]
|
||||||
|
|
||||||
@@ -135,12 +140,7 @@ class DPYCache:
|
|||||||
for start in range(0, len(items), MAX_ATTACHMENTS_PER_MESSAGE):
|
for start in range(0, len(items), MAX_ATTACHMENTS_PER_MESSAGE):
|
||||||
group = items[start:start + MAX_ATTACHMENTS_PER_MESSAGE]
|
group = items[start:start + MAX_ATTACHMENTS_PER_MESSAGE]
|
||||||
discord_files = [self._to_file(name, source) for name, source in group]
|
discord_files = [self._to_file(name, source) for name, source in group]
|
||||||
try:
|
message = await self._channel.send(content=content, files=discord_files)
|
||||||
message = await self._channel.send(content=content, files=discord_files)
|
|
||||||
except discord.HTTPException:
|
|
||||||
# raise XOR log: HTTPException propagates unwrapped (the documented contract),
|
|
||||||
# so the exception carries the failure to the caller - no error log here.
|
|
||||||
raise
|
|
||||||
|
|
||||||
attachments = list(message.attachments)
|
attachments = list(message.attachments)
|
||||||
if len(attachments) != len(group):
|
if len(attachments) != len(group):
|
||||||
@@ -178,12 +178,21 @@ class DPYCache:
|
|||||||
content: "str | None" = None,
|
content: "str | None" = None,
|
||||||
) -> FileRef:
|
) -> FileRef:
|
||||||
"""fetch a remote url then cache its bytes as ``name``; raises DPYCacheError on
|
"""fetch a remote url then cache its bytes as ``name``; raises DPYCacheError on
|
||||||
non-200"""
|
non-200, or when the body (declared or streamed) exceeds ``MAX_CACHE_URL_BYTES``"""
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as response:
|
async with session.get(url) as response:
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
raise DPYCacheError(f"cache_url: GET {url} returned status {response.status}")
|
raise DPYCacheError(f"cache_url: GET {url} returned status {response.status}")
|
||||||
data = await response.read()
|
if (response.content_length or 0) > MAX_CACHE_URL_BYTES:
|
||||||
|
raise DPYCacheError(
|
||||||
|
f"cache_url: GET {url} is {response.content_length} bytes, over the "
|
||||||
|
f"{MAX_CACHE_URL_BYTES}-byte cap"
|
||||||
|
)
|
||||||
|
data = bytearray()
|
||||||
|
async for chunk in response.content.iter_chunked(1 << 16):
|
||||||
|
data += chunk
|
||||||
|
if len(data) > MAX_CACHE_URL_BYTES:
|
||||||
|
raise DPYCacheError(f"cache_url: GET {url} body exceeds the {MAX_CACHE_URL_BYTES}-byte cap")
|
||||||
return await self.cache_one(name, data, content=content)
|
return await self.cache_one(name, data, content=content)
|
||||||
|
|
||||||
async def resolve(self, ref: FileRef) -> str:
|
async def resolve(self, ref: FileRef) -> str:
|
||||||
|
|||||||
Reference in new issue
Block a user