From 7309f3bef7d7dd5d8d6aa4a98d977de7608ae7d2 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:03:55 -0700 Subject: [PATCH] fix(line): map inbound message types to the correct MessageType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LINE adapter classified every non-text inbound message as `MessageType.IMAGE`, which doesn't exist on the enum — so any image, video, audio, file, sticker, or location message raised AttributeError the moment it was constructed. Beyond fixing the crash, every non-text message was being collapsed onto a single type. The gateway routes on MessageType (voice → STT, files → document handling, etc.), so misclassification silently mishandled media. Replace the inline ternary with a `_LINE_MESSAGE_TYPES` lookup that maps each LINE webhook type to its proper enum member (audio → VOICE to match how Telegram/WhatsApp treat voice notes), falling back to TEXT for unknown types. Adds regression tests covering the mapping and the old AttributeError. Co-authored-by: Sahibzada Allahyar <94376830+sahibzada-allahyar@users.noreply.github.com> --- plugins/platforms/line/adapter.py | 17 +++++++++++++++- tests/gateway/test_line_plugin.py | 33 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/plugins/platforms/line/adapter.py b/plugins/platforms/line/adapter.py index 00663702e..130bb2e2c 100644 --- a/plugins/platforms/line/adapter.py +++ b/plugins/platforms/line/adapter.py @@ -133,6 +133,21 @@ MEDIA_TOKEN_TTL_SECONDS = 1800 # 30 minutes; LINE caches the URL aggressively LINE_IMAGE_MAX_BYTES = 10 * 1024 * 1024 # 10 MB per LINE docs LINE_AV_MAX_BYTES = 200 * 1024 * 1024 # 200 MB for voice/video +# Map LINE webhook message types to the normalized MessageType the gateway +# routes on. LINE has no separate "voice" type — audio messages are recorded +# voice clips, so they map to VOICE (which the gateway sends through STT), +# mirroring how Telegram/WhatsApp classify voice notes. Anything unknown +# falls back to TEXT. +_LINE_MESSAGE_TYPES = { + "text": MessageType.TEXT, + "image": MessageType.PHOTO, + "video": MessageType.VIDEO, + "audio": MessageType.VOICE, + "file": MessageType.DOCUMENT, + "location": MessageType.LOCATION, + "sticker": MessageType.STICKER, +} + # A 1×1 transparent PNG used as fallback video preview thumbnail when no # explicit preview is supplied — LINE requires ``previewImageUrl`` for # video messages. Sourced from the Python stdlib (no Pillow dependency). @@ -968,7 +983,7 @@ class LineAdapter(BasePlatformAdapter): event_obj = MessageEvent( text=text, - message_type=MessageType.TEXT if msg_type == "text" else MessageType.IMAGE, + message_type=_LINE_MESSAGE_TYPES.get(msg_type, MessageType.TEXT), source=source_obj, raw_message=event, message_id=message_id, diff --git a/tests/gateway/test_line_plugin.py b/tests/gateway/test_line_plugin.py index 4f42c0f08..9d897ce7a 100644 --- a/tests/gateway/test_line_plugin.py +++ b/tests/gateway/test_line_plugin.py @@ -641,3 +641,36 @@ class TestAdapterInit: assert asyncio.run(ad.get_chat_info("U123"))["type"] == "dm" assert asyncio.run(ad.get_chat_info("C123"))["type"] == "group" assert asyncio.run(ad.get_chat_info("R123"))["type"] == "channel" + + +# --------------------------------------------------------------------------- +# 9. Inbound message-type classification +# --------------------------------------------------------------------------- + +class TestMessageTypeMapping: + """LINE webhook message types must map to the right normalized + MessageType so the gateway routes media correctly (e.g. voice → STT, + files → document handling). Regression guard for the old code that + referenced the non-existent ``MessageType.IMAGE`` and collapsed every + non-text message onto a single type.""" + + def test_image_event_not_attributeerror_regression(self): + # The bug: MessageType.IMAGE doesn't exist on the enum. + MessageType = _line.MessageType + assert not hasattr(MessageType, "IMAGE") + + def test_every_line_type_maps_to_correct_enum(self): + MessageType = _line.MessageType + mapping = _line._LINE_MESSAGE_TYPES + assert mapping["text"] == MessageType.TEXT + assert mapping["image"] == MessageType.PHOTO + assert mapping["video"] == MessageType.VIDEO + # LINE has no separate voice type — audio clips are voice notes. + assert mapping["audio"] == MessageType.VOICE + assert mapping["file"] == MessageType.DOCUMENT + assert mapping["location"] == MessageType.LOCATION + assert mapping["sticker"] == MessageType.STICKER + + def test_unknown_type_falls_back_to_text(self): + MessageType = _line.MessageType + assert _line._LINE_MESSAGE_TYPES.get("flex", MessageType.TEXT) == MessageType.TEXT