From c3826657535a12c42fce9a52f5a0e5dc2d21c8e6 Mon Sep 17 00:00:00 2001 From: Mayorz Date: Wed, 4 Mar 2026 16:45:10 +0800 Subject: [PATCH] fix excel webp mimetype registration for openpyxl --- .../scripts/convert_excel_image_links.py | 98 ++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/remote_sync/skills/dingtalk-excel-image-return/scripts/convert_excel_image_links.py b/remote_sync/skills/dingtalk-excel-image-return/scripts/convert_excel_image_links.py index e322ca1..7e69a88 100755 --- a/remote_sync/skills/dingtalk-excel-image-return/scripts/convert_excel_image_links.py +++ b/remote_sync/skills/dingtalk-excel-image-return/scripts/convert_excel_image_links.py @@ -7,6 +7,7 @@ import mimetypes import re import ssl import tempfile +import zipfile import urllib.error import urllib.parse import urllib.request @@ -16,6 +17,7 @@ from typing import Iterable from openpyxl import load_workbook from openpyxl.drawing.image import Image as XLImage +from openpyxl.packaging import manifest as openpyxl_manifest from openpyxl.utils import column_index_from_string, get_column_letter try: from PIL import Image as PILImage @@ -44,8 +46,98 @@ IMAGE_HINT_HEADERS = ( "image url", "图片", ) -mimetypes.add_type("image/webp", ".webp", strict=True) -mimetypes.add_type("image/webp", ".webp", strict=False) + + +def _iter_mimetype_modules(): + yield mimetypes + compat = getattr(openpyxl_manifest, "mimetypes", None) + if compat is not None: + yield compat + + +def _resolve_type_maps(module) -> tuple[dict, dict]: + type_maps = getattr(module, "types_map", {}) + if isinstance(type_maps, tuple) and len(type_maps) >= 2: + loose_map, strict_map = type_maps[0], type_maps[1] + if isinstance(strict_map, dict) and isinstance(loose_map, dict): + return strict_map, loose_map + if ( + isinstance(type_maps, dict) + and isinstance(type_maps.get(True), dict) + and isinstance(type_maps.get(False), dict) + ): + return type_maps[True], type_maps[False] + if isinstance(type_maps, dict): + loose_map = getattr(module, "common_types", {}) + if not isinstance(loose_map, dict): + loose_map = type_maps + return type_maps, loose_map + return {}, {} + + +def _register_type(module, extension: str, mime_type: str) -> None: + add_type = getattr(module, "add_type", None) + if callable(add_type): + try: + add_type(mime_type, extension, strict=True) + except TypeError: + try: + add_type(mime_type, extension) + except Exception: + pass + except Exception: + pass + try: + add_type(mime_type, extension, strict=False) + except Exception: + pass + + strict_map, loose_map = _resolve_type_maps(module) + strict_map[extension] = mime_type + loose_map.setdefault(extension, mime_type) + + +def ensure_mimetype_for_ext(ext: str) -> None: + normalized_raw = str(ext or "").strip() + if not normalized_raw: + return + + if not normalized_raw.startswith("."): + normalized_raw = f".{normalized_raw}" + normalized = normalized_raw.lower() + + guessed = mimetypes.types_map.get(normalized) + if not guessed: + guessed = mimetypes.common_types.get(normalized) + if not guessed: + guessed, _ = mimetypes.guess_type(f"x{normalized}", strict=False) + if not guessed and normalized in {".webp", ".WEBP"}: + guessed = "image/webp" + if not guessed: + guessed = "application/octet-stream" + + variants = {normalized} + variants.add(normalized_raw) + if normalized_raw != normalized: + variants.add(normalized_raw.upper()) + + for module in _iter_mimetype_modules(): + for variant in variants: + _register_type(module, variant, guessed) + + +ensure_mimetype_for_ext(".webp") + + +def register_mimetypes_from_archive(path: Path) -> None: + try: + with zipfile.ZipFile(path, "r") as archive: + for name in archive.namelist(): + suffix = Path(name).suffix + if suffix: + ensure_mimetype_for_ext(suffix) + except Exception: + return @dataclass @@ -247,6 +339,7 @@ def download_image(url: str, timeout: int, ssl_context): def to_excel_safe_image(payload: bytes, extension: str) -> tuple[bytes, str]: ext = (extension or "").lower() + ensure_mimetype_for_ext(ext) if ext in EXCEL_SAFE_EXTENSIONS and ext not in UNSAFE_OR_UNKNOWN_EXTENSIONS: return payload, ext @@ -367,6 +460,7 @@ def main() -> int: print(f"ERROR: input does not exist: {input_path}") return 1 + register_mimetypes_from_archive(input_path) ssl_context = build_ssl_context(args.skip_tls_verify) workbook = load_workbook(input_path)