113 lines
4.4 KiB
Python
Executable File
113 lines
4.4 KiB
Python
Executable File
#!/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())
|