From e3ce2772feb689603e4c080edbc46938542db4e2 Mon Sep 17 00:00:00 2001 From: Afiq <152358148+AfiqAqil@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:29:43 +0800 Subject: [PATCH] fix: preserve original filenames for Telegram inbound attachments (#15071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Telegram inbound attachments were stored under a generic, renamed filename (e.g. `file_5.pdf`) instead of the name the sender actually uploaded (e.g. `Quarterly-Report-2025.pdf`). **Root cause:** In `Telegram::IncomingMessageService#attach_files`, the attachment filename was taken from the `Down`-downloaded file's `original_filename`, which `Down` derives from the Telegram file-server download URL. That URL is built from Telegram's `getFile` response, whose `file_path` is Telegram's *internal* storage path — not the sender's chosen name. The real name is present in the webhook payload as `file_name` on the `document` / `audio` / `video` object, but it was never read. Because `Attachment#set_extension` derives the stored `extension` from the filename, this also corrupted the recorded extension for documents whose Telegram path lacked a proper one. **Fix:** Prefer the payload's `file_name`, falling back to the downloaded name when it's absent (photos, stickers, voice notes — which Telegram sends without a `file_name`). This mirrors how the Line channel (`message['fileName']`) and WhatsApp Cloud channel already handle inbound attachment filenames. ```ruby filename: file[:file_name].presence || attachment_file.original_filename, ``` Fixes #15070 ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Added/updated specs in `spec/services/telegram/incoming_message_service_spec.rb`: - Extended the existing document-message example to assert the stored attachment retains the original payload filename (`Screenshot 2021-09-27 at 2.01.14 PM.png`) rather than Telegram's internal download name. - Added a fallback example (a `photo` payload, which carries no `file_name`) asserting the attachment is still created and the filename falls back to the downloaded name — guarding the `.presence || …` branch and confirming no regression for media types without a `file_name`. > Note: my local machine does not have the Ruby/Redis toolchain to run the suite, so I have not run RSpec/RuboCop locally — I'm relying on CI to validate. Suggested command for reviewers: `bundle exec rspec spec/services/telegram/incoming_message_service_spec.rb`. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] I have added tests that prove my fix is effective - [ ] New and existing unit tests pass locally with my changes (unable to run locally — see note above; relying on CI) --- .../telegram/incoming_message_service.rb | 8 +++---- .../telegram/incoming_message_service_spec.rb | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb index 897db29c3..7c7e3701c 100644 --- a/app/services/telegram/incoming_message_service.rb +++ b/app/services/telegram/incoming_message_service.rb @@ -150,16 +150,16 @@ class Telegram::IncomingMessageService return end - attachment_file = Down.download( - inbox.channel.get_telegram_file_path(file[:file_id]) - ) + attachment_file = Down.download(file_download_path) @message.attachments.new( account_id: @message.account_id, file_type: file_content_type, file: { io: attachment_file, - filename: attachment_file.original_filename, + # Telegram's download URL uses an internal storage path, so Down derives a generic name from it. + # Prefer the original filename from the payload (present for document/audio/video) when available. + filename: file[:file_name].presence || attachment_file.original_filename, content_type: attachment_file.content_type } ) diff --git a/spec/services/telegram/incoming_message_service_spec.rb b/spec/services/telegram/incoming_message_service_spec.rb index b81b18756..da558e6fc 100644 --- a/spec/services/telegram/incoming_message_service_spec.rb +++ b/spec/services/telegram/incoming_message_service_spec.rb @@ -299,7 +299,29 @@ describe Telegram::IncomingMessageService do described_class.new(inbox: telegram_channel.inbox, params: params).perform expect(telegram_channel.inbox.conversations.count).not_to eq(0) expect(Contact.all.first.name).to eq('Sojan Jose') - expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('file') + attachment = telegram_channel.inbox.messages.first.attachments.first + expect(attachment.file_type).to eq('file') + # Retain the original filename from the payload instead of Telegram's internal download name + expect(attachment.file.filename.to_s).to eq('Screenshot 2021-09-27 at 2.01.14 PM.png') + end + end + + context 'when attachment params have no file_name' do + it 'falls back to the downloaded file name' do + allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.png') + params = { + 'update_id' => 2_342_342_343_242, + 'message' => { + 'photo' => [{ + 'file_id' => 'AgACAgUAAxkBAAODYV3aGZlD6vhzKsE2WNmblsr6zKwAAi-tMRvCoeBWNQ1ENVBzJdwBAAMCAANzAAMhBA', + 'file_unique_id' => 'AQADL60xG8Kh4FZ4', 'file_size' => 1883, 'width' => 90, 'height' => 67 + }] + }.merge(message_params) + }.with_indifferent_access + described_class.new(inbox: telegram_channel.inbox, params: params).perform + attachment = telegram_channel.inbox.messages.first.attachments.first + expect(attachment.file_type).to eq('image') + expect(attachment.file.filename.to_s).to eq('sample.png') end end