diff --git a/apps/sim/blocks/blocks/telegram.test.ts b/apps/sim/blocks/blocks/telegram.test.ts new file mode 100644 index 0000000000..6632ecea10 --- /dev/null +++ b/apps/sim/blocks/blocks/telegram.test.ts @@ -0,0 +1,57 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { TelegramBlock } from '@/blocks/blocks/telegram' + +describe('TelegramBlock tools.config.params', () => { + it('accepts public photo URLs for telegram_send_photo', () => { + const params = TelegramBlock.tools.config.params({ + operation: 'telegram_send_photo', + botToken: 'token', + chatId: ' 123 ', + photo: ' https://example.com/a.jpg ', + caption: 'hello', + } as any) + + expect(params).toEqual({ + botToken: 'token', + chatId: '123', + photo: 'https://example.com/a.jpg', + caption: 'hello', + }) + }) + + it('accepts stringified JSON photo objects from advanced-mode references', () => { + const params = TelegramBlock.tools.config.params({ + operation: 'telegram_send_photo', + botToken: 'token', + chatId: '123', + photo: '{"url":"https://example.com/a.jpg"}', + } as any) + + expect(params.photo).toBe('https://example.com/a.jpg') + }) + + it('supports legacy `withPhoto` alias', () => { + const params = TelegramBlock.tools.config.params({ + operation: 'telegram_send_photo', + botToken: 'token', + chatId: '123', + withPhoto: 'https://example.com/a.jpg', + } as any) + + expect(params.photo).toBe('https://example.com/a.jpg') + }) + + it('rejects multiple photo values', () => { + expect(() => + TelegramBlock.tools.config.params({ + operation: 'telegram_send_photo', + botToken: 'token', + chatId: '123', + photo: ['https://example.com/1.jpg', 'https://example.com/2.jpg'], + } as any) + ).toThrow('Photo reference must be a single item, not an array.') + }) +}) diff --git a/apps/sim/blocks/blocks/telegram.ts b/apps/sim/blocks/blocks/telegram.ts index ce4076d384..0488a84f07 100644 --- a/apps/sim/blocks/blocks/telegram.ts +++ b/apps/sim/blocks/blocks/telegram.ts @@ -2,6 +2,7 @@ import { TelegramIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' import { normalizeFileInput } from '@/blocks/utils' +import { normalizeTelegramMediaParam } from '@/tools/telegram/media' import type { TelegramResponse } from '@/tools/telegram/types' import { getTrigger } from '@/triggers' @@ -269,13 +270,14 @@ export const TelegramBlock: BlockConfig = { messageId: params.messageId, } case 'telegram_send_photo': { - // photo is the canonical param for both basic (photoFile) and advanced modes - const photoSource = normalizeFileInput(params.photo, { - single: true, + // photo supports both public URLs/file_ids and UserFile objects. + // Backwards-compatible aliases (e.g., `withPhoto`) are supported for older saved workflows. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawPhoto = params.photo ?? (params as any).withPhoto ?? (params as any).with_photo + const photoSource = normalizeTelegramMediaParam(rawPhoto, { + label: 'Photo', + errorMessage: 'Photo is required.', }) - if (!photoSource) { - throw new Error('Photo is required.') - } return { ...commonParams, photo: photoSource, @@ -283,13 +285,12 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_video': { - // video is the canonical param for both basic (videoFile) and advanced modes - const videoSource = normalizeFileInput(params.video, { - single: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawVideo = params.video ?? (params as any).withVideo ?? (params as any).with_video + const videoSource = normalizeTelegramMediaParam(rawVideo, { + label: 'Video', + errorMessage: 'Video is required.', }) - if (!videoSource) { - throw new Error('Video is required.') - } return { ...commonParams, video: videoSource, @@ -297,13 +298,12 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_audio': { - // audio is the canonical param for both basic (audioFile) and advanced modes - const audioSource = normalizeFileInput(params.audio, { - single: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawAudio = params.audio ?? (params as any).withAudio ?? (params as any).with_audio + const audioSource = normalizeTelegramMediaParam(rawAudio, { + label: 'Audio', + errorMessage: 'Audio is required.', }) - if (!audioSource) { - throw new Error('Audio is required.') - } return { ...commonParams, audio: audioSource, @@ -311,13 +311,13 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_animation': { - // animation is the canonical param for both basic (animationFile) and advanced modes - const animationSource = normalizeFileInput(params.animation, { - single: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawAnimation = + params.animation ?? (params as any).withAnimation ?? (params as any).with_animation + const animationSource = normalizeTelegramMediaParam(rawAnimation, { + label: 'Animation', + errorMessage: 'Animation is required.', }) - if (!animationSource) { - throw new Error('Animation is required.') - } return { ...commonParams, animation: animationSource, diff --git a/apps/sim/tools/telegram/media.test.ts b/apps/sim/tools/telegram/media.test.ts new file mode 100644 index 0000000000..26c8583ff0 --- /dev/null +++ b/apps/sim/tools/telegram/media.test.ts @@ -0,0 +1,63 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { normalizeTelegramMediaParam } from '@/tools/telegram/media' + +describe('normalizeTelegramMediaParam', () => { + it('accepts trimmed URL/file_id strings', () => { + expect(normalizeTelegramMediaParam(' https://example.com/a.jpg ', { label: 'Photo' })).toBe( + 'https://example.com/a.jpg' + ) + expect(normalizeTelegramMediaParam(' ABC123 ', { label: 'Photo' })).toBe('ABC123') + }) + + it('accepts URL instances', () => { + expect( + normalizeTelegramMediaParam(new URL('https://example.com/a.jpg'), { label: 'Photo' }) + ).toBe('https://example.com/a.jpg') + }) + + it('accepts object shapes with url/href/file_id', () => { + expect( + normalizeTelegramMediaParam({ url: 'https://example.com/a.jpg' }, { label: 'Photo' }) + ).toBe('https://example.com/a.jpg') + expect( + normalizeTelegramMediaParam({ href: 'https://example.com/a.jpg' }, { label: 'Photo' }) + ).toBe('https://example.com/a.jpg') + expect(normalizeTelegramMediaParam({ file_id: 'FILE_ID' }, { label: 'Photo' })).toBe('FILE_ID') + expect(normalizeTelegramMediaParam({ fileId: 'FILE_ID_2' }, { label: 'Photo' })).toBe( + 'FILE_ID_2' + ) + }) + + it('parses stringified JSON objects/arrays from advanced-mode inputs', () => { + expect( + normalizeTelegramMediaParam('{\"url\":\"https://example.com/a.jpg\"}', { label: 'Photo' }) + ).toBe('https://example.com/a.jpg') + + expect( + normalizeTelegramMediaParam('[{\"url\":\"https://example.com/a.jpg\"}]', { label: 'Photo' }) + ).toBe('https://example.com/a.jpg') + }) + + it('rejects missing values with a configurable message', () => { + expect(() => + normalizeTelegramMediaParam('', { label: 'Photo', errorMessage: 'Photo is required.' }) + ).toThrow('Photo is required.') + + expect(() => normalizeTelegramMediaParam(undefined, { label: 'Photo' })).toThrow( + 'Photo URL or file_id is required.' + ) + }) + + it('rejects multiple values when an array is provided', () => { + expect(() => + normalizeTelegramMediaParam([{ url: 'a' }, { url: 'b' }], { label: 'Photo' }) + ).toThrow('Photo reference must be a single item, not an array.') + + expect(() => + normalizeTelegramMediaParam('[{\"url\":\"a\"},{\"url\":\"b\"}]', { label: 'Photo' }) + ).toThrow('Photo reference must be a single item, not an array.') + }) +}) diff --git a/apps/sim/tools/telegram/media.ts b/apps/sim/tools/telegram/media.ts new file mode 100644 index 0000000000..aef1c7c458 --- /dev/null +++ b/apps/sim/tools/telegram/media.ts @@ -0,0 +1,84 @@ +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +export function normalizeTelegramMediaParam( + input: unknown, + opts: { + label: string + errorMessage?: string + multipleErrorMessage?: string + } +): string { + const missingMessage = opts.errorMessage ?? `${opts.label} URL or file_id is required.` + const multipleMessage = + opts.multipleErrorMessage ?? + `${opts.label} reference must be a single item, not an array. Select one item (e.g. ).` + + if (input === null || input === undefined) { + throw new Error(missingMessage) + } + + if (typeof input === 'string') { + const trimmed = input.trim() + if (trimmed.length === 0) { + throw new Error(missingMessage) + } + + // Support advanced-mode values that were JSON.stringify'd into short-input fields. + if (trimmed.startsWith('{') || trimmed.startsWith('[')) { + let parsed: unknown + try { + parsed = JSON.parse(trimmed) as unknown + } catch { + parsed = undefined + } + + if (parsed !== undefined) { + return normalizeTelegramMediaParam(parsed, opts) + } + } + + return trimmed + } + + if (input instanceof URL) { + const asString = input.toString().trim() + if (asString.length > 0) return asString + throw new Error(missingMessage) + } + + if (Array.isArray(input)) { + if (input.length === 0) { + throw new Error(missingMessage) + } + if (input.length > 1) { + throw new Error(multipleMessage) + } + return normalizeTelegramMediaParam(input[0], opts) + } + + if (isRecord(input)) { + if ('url' in input && typeof input.url === 'string') { + const url = input.url.trim() + if (url.length > 0) return url + } + + if ('href' in input && typeof input.href === 'string') { + const href = input.href.trim() + if (href.length > 0) return href + } + + if ('file_id' in input && typeof input.file_id === 'string') { + const fileId = input.file_id.trim() + if (fileId.length > 0) return fileId + } + + if ('fileId' in input && typeof input.fileId === 'string') { + const fileId = input.fileId.trim() + if (fileId.length > 0) return fileId + } + } + + throw new Error(missingMessage) +} diff --git a/apps/sim/tools/telegram/send_animation.ts b/apps/sim/tools/telegram/send_animation.ts index 191dc188d8..9418afae79 100644 --- a/apps/sim/tools/telegram/send_animation.ts +++ b/apps/sim/tools/telegram/send_animation.ts @@ -1,4 +1,5 @@ import { ErrorExtractorId } from '@/tools/error-extractors' +import { normalizeTelegramMediaParam } from '@/tools/telegram/media' import type { TelegramMedia, TelegramSendAnimationParams, @@ -52,9 +53,10 @@ export const telegramSendAnimationTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params: TelegramSendAnimationParams) => { + const animation = normalizeTelegramMediaParam(params.animation, { label: 'Animation' }) const body: Record = { chat_id: params.chatId, - animation: params.animation, + animation, } if (params.caption) { diff --git a/apps/sim/tools/telegram/send_audio.ts b/apps/sim/tools/telegram/send_audio.ts index d539f7f226..d136a2db9e 100644 --- a/apps/sim/tools/telegram/send_audio.ts +++ b/apps/sim/tools/telegram/send_audio.ts @@ -1,4 +1,5 @@ import { ErrorExtractorId } from '@/tools/error-extractors' +import { normalizeTelegramMediaParam } from '@/tools/telegram/media' import type { TelegramAudio, TelegramSendAudioParams, @@ -50,9 +51,10 @@ export const telegramSendAudioTool: ToolConfig { + const audio = normalizeTelegramMediaParam(params.audio, { label: 'Audio' }) const body: Record = { chat_id: params.chatId, - audio: params.audio, + audio, } if (params.caption) { diff --git a/apps/sim/tools/telegram/send_photo.ts b/apps/sim/tools/telegram/send_photo.ts index 7454925844..b64f0ea7f0 100644 --- a/apps/sim/tools/telegram/send_photo.ts +++ b/apps/sim/tools/telegram/send_photo.ts @@ -1,4 +1,5 @@ import { ErrorExtractorId } from '@/tools/error-extractors' +import { normalizeTelegramMediaParam } from '@/tools/telegram/media' import type { TelegramPhoto, TelegramSendPhotoParams, @@ -50,9 +51,10 @@ export const telegramSendPhotoTool: ToolConfig { + const photo = normalizeTelegramMediaParam(params.photo, { label: 'Photo' }) const body: Record = { chat_id: params.chatId, - photo: params.photo, + photo, } if (params.caption) { diff --git a/apps/sim/tools/telegram/send_video.ts b/apps/sim/tools/telegram/send_video.ts index 72ec45250d..d0bbb06907 100644 --- a/apps/sim/tools/telegram/send_video.ts +++ b/apps/sim/tools/telegram/send_video.ts @@ -1,4 +1,5 @@ import { ErrorExtractorId } from '@/tools/error-extractors' +import { normalizeTelegramMediaParam } from '@/tools/telegram/media' import type { TelegramMedia, TelegramSendMediaResponse, @@ -50,9 +51,10 @@ export const telegramSendVideoTool: ToolConfig { + const video = normalizeTelegramMediaParam(params.video, { label: 'Video' }) const body: Record = { chat_id: params.chatId, - video: params.video, + video, } if (params.caption) {