Skip to content

fix(telegram): use static thumbnail for animated stickers - #9610

Merged
Soulter merged 1 commit into
AstrBotDevs:masterfrom
Trainingcqy:fix/telegram-animated-sticker
Aug 10, 2026
Merged

fix(telegram): use static thumbnail for animated stickers#9610
Soulter merged 1 commit into
AstrBotDevs:masterfrom
Trainingcqy:fix/telegram-animated-sticker

Conversation

@Trainingcqy

@Trainingcqy Trainingcqy commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Animated Telegram stickers never reach the model as images: ask what a sticker shows and the model describes the emoji it is tagged with rather than the artwork. The sticker branch in convert_message() does not distinguish sticker types, always calling sticker.get_file() and wrapping the result in Comp.Image, but an animated sticker's payload is .tgs (gzipped Lottie JSON) or .webm, neither of which is a bitmap. ensure_jpeg() in the preprocess stage therefore throws at PIL.Image.open() and the component stays on the message chain unchanged; the provider side then fails to detect a MIME type when converting it to base64 and drops the entry. Animated stickers are common on Telegram, so any feature that needs to read the artwork fails.

The Bot API already ships a static thumbnail for these stickers, Sticker.thumbnail, in .WEBP or .JPG. Reading that gives us the artwork and avoids adding a new dependency.

Modifications / 改动点

astrbot/core/platform/sources/telegram/tg_adapter.py:

  • Sticker branch: when is_animated or is_video is set, read sticker.thumbnail.get_file() instead. Static stickers keep using sticker.get_file() and behave exactly as before.
  • An animated sticker with no thumbnail no longer appends a Comp.Image.

tests/test_telegram_adapter.py: four new cases covering static stickers still using the sticker's own file, is_animated and is_video each switching to the thumbnail, and a missing thumbnail producing no Comp.Image while the text component survives.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

Before the fix

The sticker carries a thumbnail, but the .tgs payload is what gets fetched. An Image component does reach the chain ([图片] in the log below), and it fails twice: once in the preprocess stage, once on the provider side. The request fired when the sticker arrived carries no IMAGE entry.Asked to describe the sticker, the model answers "a clenched fist with the thumb pointing up" — the shape of the Unicode 👍, not the duck the sticker actually shows.

Log (before)
[14:52:32.860] [Core] [DBUG] [telegram.tg_adapter:427]: Telegram message: Message(channel_chat_created=False, chat=Chat(first_name='<name>', id=<user_id>, type=<ChatType.PRIVATE>), date=datetime.datetime(2026, 8, 9, 5, 52, 32, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='<name>', id=<user_id>, is_bot=False, language_code='zh-hans'), group_chat_created=False, message_id=46, sticker=Sticker(api_kwargs={'thumb': {'file_id': 'AAMCAgADGQEAAypqeBKgYJj99Lw1IjGGwghZfDU6AwAC_gADVp29CtoEYTAu-df_AQAHbQADPQQ', 'file_unique_id': 'AQAD_gADVp29CnI', 'file_size': 4764, 'width': 128, 'height': 128}}, emoji='👍', file_id='CAACAgIAAxkBAAMqangSoGCY_fS8NSIxhsIIWXw1OgMAAv4AA1advQraBGEwLvnX_z0E', file_size=7340, file_unique_id='AgAD_gADVp29Cg', height=512, is_animated=True, is_video=False, set_name='UtyaDuck', thumbnail=PhotoSize(file_id='AAMCAgADGQEAAypqeBKgYJj99Lw1IjGGwghZfDU6AwAC_gADVp29CtoEYTAu-df_AQAHbQADPQQ', file_size=4764, file_unique_id='AQAD_gADVp29CnI', height=128, width=128), type=<StickerType.REGULAR>, width=512), supergroup_chat_created=False)
[14:52:34.293] [Core] [INFO] [core.event_bus:74]: [default] [telegram(telegram)] Unknown/<user_id>: [图片] Sticker: 👍
[14:52:37.271] [Core] [WARN] [v4.27.2] [preprocess_stage.stage:126]: Image processing failed for https URL host='api.telegram.org' file='file_9.tgs' len=99: cannot identify image file 'C:\\Users\\<user>\\Desktop\\AstrBot-repro\\data\\temp\\media_image_20260809145234300_660b.bin'
[14:52:39.252] [Core] [WARN] [v4.27.2] [provider.entities:216]: 图片预处理结果为空,将忽略。
[14:52:39.252] [Core] [DBUG] [runners.tool_loop_agent_runner:621]: [BefCompact] messages -> [2] system,user
[14:52:42.732] [Core] [DBUG] [sources.gemini_source:623]: genai result: sdk_http_response=HttpResponse(
  headers=<dict len=12>
) candidates=[Candidate(
  content=Content(
    parts=[
      Part(
        text='👍 收到!如果有其他需要随时告诉我哦!',
        thought_signature=b'\x12\xdb\t\n\xd8\t\x01\x11M2\x0fY\xfc\x90\x9bWA\x13<\x93\xb0\xd7\x1d\xdd\xd9\xf8\x13k\xeb0\xc1\xf2\xc2\xd8\xc0hmO\x1b$\x86\x87\xf3\\\x93\r9\xd1\x93\xca\xcc\x15\x93;\xb1\xbd\xe3\xa0\x95D,\xb9\xe4\x89\t]\x9b\x0b{\x0e\x8b.\xae\x1cXP\tf\x9b+D\xb9/\x19O\xc8/9<\xc8\xadj\xf0\xf4W{\xf9...'
      ),
    ],
    role='model'
  ),
  finish_reason=<FinishReason.STOP: 'STOP'>,
  index=0,
  safety_ratings=[
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_HARASSMENT: 'HARM_CATEGORY_HARASSMENT'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_HATE_SPEECH: 'HARM_CATEGORY_HATE_SPEECH'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 'HARM_CATEGORY_SEXUALLY_EXPLICIT'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: 'HARM_CATEGORY_DANGEROUS_CONTENT'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
  ]
)] create_time=None model_version='gemini-3.6-flash' prompt_feedback=None response_id='pxV4aoCZNPu0vr0P__zl6Qs' usage_metadata=GenerateContentResponseUsageMetadata(
  candidates_token_count=11,
  prompt_token_count=3342,
  prompt_tokens_details=[
    ModalityTokenCount(
      modality=<MediaModality.TEXT: 'TEXT'>,
      token_count=3342
    ),
  ],
  thoughts_token_count=304,
  total_token_count=3657
) model_status=None automatic_function_calling_history=None parsed=None
before the fix

After the fix

Same sticker (file_unique_id matches the run above), both warnings gone, and the thumbnail makes it into the request. The comparison is limited to the request fired when the sticker arrived: the TEXT token count is identical (3342) and the only difference is the added IMAGE: 1089. Asked the same question, the model now describes the picture: a round cartoon duck whose right wing is drawn as an oversized thumbs-up.

Log (after)
[14:56:31.687] [Core] [DBUG] [telegram.tg_adapter:427]: Telegram message: Message(channel_chat_created=False, chat=Chat(first_name='<name>', id=<user_id>, type=<ChatType.PRIVATE>), date=datetime.datetime(2026, 8, 9, 5, 56, 31, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='<name>', id=<user_id>, is_bot=False, language_code='zh-hans'), group_chat_created=False, message_id=52, sticker=Sticker(api_kwargs={'thumb': {'file_id': 'AAMCAgADGQEAAypqeBKgYJj99Lw1IjGGwghZfDU6AwAC_gADVp29CtoEYTAu-df_AQAHbQADPQQ', 'file_unique_id': 'AQAD_gADVp29CnI', 'file_size': 4764, 'width': 128, 'height': 128}}, emoji='👍', file_id='CAACAgIAAxkBAAMqangSoGCY_fS8NSIxhsIIWXw1OgMAAv4AA1advQraBGEwLvnX_z0E', file_size=7340, file_unique_id='AgAD_gADVp29Cg', height=512, is_animated=True, is_video=False, set_name='UtyaDuck', thumbnail=PhotoSize(file_id='AAMCAgADGQEAAypqeBKgYJj99Lw1IjGGwghZfDU6AwAC_gADVp29CtoEYTAu-df_AQAHbQADPQQ', file_size=4764, file_unique_id='AQAD_gADVp29CnI', height=128, width=128), type=<StickerType.REGULAR>, width=512), supergroup_chat_created=False)
[14:56:32.803] [Core] [INFO] [core.event_bus:74]: [default] [telegram(telegram)] Unknown/<user_id>: [图片] Sticker: 👍
[14:56:35.943] [Core] [DBUG] [runners.tool_loop_agent_runner:621]: [BefCompact] messages -> [2] system,user
[14:56:38.807] [Core] [DBUG] [sources.gemini_source:623]: genai result: sdk_http_response=HttpResponse(
  headers=<dict len=12>
) candidates=[Candidate(
  content=Content(
    parts=[
      Part(
        text="""收到!这是一个超萌的小黄鸭点赞表情包 🐥👍

有什么需要我帮忙的吗?随时告诉我哦!""",
        thought_signature=b'\x12\xe5\x02\n\xe2\x02\x01\x11M2\x0f2\xa6G\xbf\xd4\xac\xaac{\x9fD$\xbb\xeby\xe5\xc5\x02b\xc4w\xe4Hf1\xe3KS\xa7y\xa5f%{\x8f\x9dxTX\xb6\xb3\xe2\x90\xd6\x80\xd6\xffov\x00-\x13$\xee)\x83]f\xd8i\x10\x86\xd4\x0b\xe9\xb4\x87\x84\x11 $\x9e#f\x89\x9fX\xfe8\xca\x7f\x9e\xb0b\xab#...'
      ),
    ],
    role='model'
  ),
  finish_reason=<FinishReason.STOP: 'STOP'>,
  index=0,
  safety_ratings=[
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_HARASSMENT: 'HARM_CATEGORY_HARASSMENT'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_HATE_SPEECH: 'HARM_CATEGORY_HATE_SPEECH'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 'HARM_CATEGORY_SEXUALLY_EXPLICIT'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
    SafetyRating(
      category=<HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: 'HARM_CATEGORY_DANGEROUS_CONTENT'>,
      probability=<HarmProbability.NEGLIGIBLE: 'NEGLIGIBLE'>
    ),
  ]
)] create_time=None model_version='gemini-3.6-flash' prompt_feedback=None response_id='lBZ4aqnKLLej1e8P5_C96Qo' usage_metadata=GenerateContentResponseUsageMetadata(
  candidates_token_count=28,
  prompt_token_count=4431,
  prompt_tokens_details=[
    ModalityTokenCount(
      modality=<MediaModality.TEXT: 'TEXT'>,
      token_count=3342
    ),
    ModalityTokenCount(
      modality=<MediaModality.IMAGE: 'IMAGE'>,
      token_count=1089
    ),
  ],
  thoughts_token_count=65,
  total_token_count=4524
) model_status=None automatic_function_calling_history=None parsed=None
after the fix

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Handle Telegram animated/video stickers using their static thumbnails so sticker artwork reaches image-aware features without errors.

Bug Fixes:

  • Prevent failures when processing animated or video Telegram stickers whose payloads are non-bitmap formats by using their static thumbnails instead.
  • Avoid creating image components for animated stickers that lack thumbnails, while preserving the text representation.

Tests:

  • Add tests verifying static stickers still use their own file, animated and video stickers use thumbnails, and animated stickers without thumbnails skip image creation.

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. labels Aug 9, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 10, 2026
@Soulter
Soulter merged commit 939bdce into AstrBotDevs:master Aug 10, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. lgtm This PR has been approved by a maintainer size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants