feat(hamsterimg): add image uploader
This commit is contained in:
1 parent
6eb0ab7d5a
commit
0d97094092
2 files changed
+307
No files matched your search
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# dependencies = ["httpx"]
|
||||
# ///
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
@dataclass
|
||||
class UploadedImage:
|
||||
url: str
|
||||
thumb_url: str
|
||||
delete_url: str
|
||||
|
||||
|
||||
def upload_image_to_hamsterimg(api_key: str, image_paths: list[Path]) -> list[UploadedImage]:
|
||||
if not image_paths:
|
||||
return []
|
||||
if not api_key:
|
||||
raise ValueError("HamsterImg API key is required")
|
||||
for image_path in image_paths:
|
||||
if not image_path.is_file():
|
||||
raise FileNotFoundError(f"Image file not found: {image_path}")
|
||||
with httpx.Client(headers={"X-API-Key": api_key}, timeout=60) as client:
|
||||
|
||||
def upload(image_path: Path) -> UploadedImage:
|
||||
with image_path.open(mode="rb") as image_file:
|
||||
response = client.post(
|
||||
url="https://hamsterimg.net/api/1/upload",
|
||||
data={"format": "json"},
|
||||
files={"source": (image_path.name, image_file)},
|
||||
)
|
||||
try:
|
||||
result = response.json()
|
||||
except ValueError:
|
||||
result = {}
|
||||
if response.is_error or not isinstance(result, dict) or result.get("status_code") != 200:
|
||||
error = result.get("error", {}) if isinstance(result, dict) else {}
|
||||
message = error.get("message") if isinstance(error, dict) else str(error)
|
||||
status = result.get("status_txt") if isinstance(result, dict) else None
|
||||
reason = message or status or response.reason_phrase or "Unknown error"
|
||||
raise RuntimeError(f"HamsterImg upload failed for {image_path}: {reason}")
|
||||
try:
|
||||
image = result["image"]
|
||||
url = image["url"]
|
||||
thumb_url = image["thumb"]["url"]
|
||||
delete_url = image["delete_url"]
|
||||
except (KeyError, TypeError) as error:
|
||||
raise ValueError(f"HamsterImg returned incomplete image details for {image_path}") from error
|
||||
if not all(isinstance(value, str) and value for value in (url, thumb_url, delete_url)):
|
||||
raise ValueError(f"HamsterImg returned missing image URLs for {image_path}")
|
||||
return UploadedImage(url=url, thumb_url=thumb_url, delete_url=delete_url)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=min(8, len(image_paths))) as executor:
|
||||
futures = [executor.submit(upload, image_path=image_path) for image_path in image_paths]
|
||||
return [future.result() for future in futures]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Upload images to HamsterImg and print their URLs.",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument("image_paths", nargs="+", type=Path, help="Image files to upload")
|
||||
parser.add_argument("--api-key", help="HamsterImg API key; also reads HAMSTERIMG_API_KEY")
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
dest="output_format",
|
||||
choices=["text", "json", "bbcode"],
|
||||
default="text",
|
||||
help="Output format; BBCode shows thumbnails linked to full images",
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
arguments.api_key = arguments.api_key or os.getenv("HAMSTERIMG_API_KEY")
|
||||
if not arguments.api_key:
|
||||
parser.error("Provide --api-key or set HAMSTERIMG_API_KEY")
|
||||
for image_path in arguments.image_paths:
|
||||
if not image_path.is_file():
|
||||
parser.error(f"Image file not found: {image_path}")
|
||||
return arguments
|
||||
|
||||
|
||||
def main() -> int:
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
||||
arguments = parse_args()
|
||||
try:
|
||||
images = upload_image_to_hamsterimg(api_key=arguments.api_key, image_paths=arguments.image_paths)
|
||||
except (OSError, ValueError, RuntimeError, httpx.HTTPError) as error:
|
||||
logging.error(f"{error}")
|
||||
return 1
|
||||
if arguments.output_format == "json":
|
||||
print(json.dumps([asdict(obj=image) for image in images], indent=2, ensure_ascii=False))
|
||||
return 0
|
||||
for image in images:
|
||||
if arguments.output_format == "bbcode":
|
||||
print(f"[url={image.url}][img]{image.thumb_url}[/img][/url]")
|
||||
else:
|
||||
print(image.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user