From d469b81a8502f2281b5a0bb640da6ef869f93d3b Mon Sep 17 00:00:00 2001 From: teidesu Date: Tue, 4 May 2021 13:08:20 +0300 Subject: [PATCH] feat(client): support all possible inline results and messages --- packages/client/src/client.ts | 4 +- .../src/methods/messages/send-media-group.ts | 2 +- .../client/src/methods/messages/send-media.ts | 2 +- .../types/bots/input/input-inline-message.ts | 236 +++++ .../types/bots/input/input-inline-result.ts | 837 +++++++++++++++++- .../client/src/types/files/file-location.ts | 8 +- .../client/src/types/media/input-media.ts | 2 +- packages/client/src/types/messages/message.ts | 4 +- 8 files changed, 1060 insertions(+), 35 deletions(-) diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 96a86e2d..06f85e60 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -1630,7 +1630,7 @@ export interface TelegramClient extends BaseTelegramClient { * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param medias Medias contained in the message. * @param params Additional sending parameters - * @see InputMedia + * @link InputMedia */ sendMediaGroup( chatId: InputPeerLike, @@ -1698,7 +1698,7 @@ export interface TelegramClient extends BaseTelegramClient { * and Bot API compatible File ID, which will be wrapped * in {@link InputMedia.auto} * @param params Additional sending parameters - * @see InputMedia + * @link InputMedia */ sendMedia( chatId: InputPeerLike, diff --git a/packages/client/src/methods/messages/send-media-group.ts b/packages/client/src/methods/messages/send-media-group.ts index b15ba43e..ea48748c 100644 --- a/packages/client/src/methods/messages/send-media-group.ts +++ b/packages/client/src/methods/messages/send-media-group.ts @@ -16,7 +16,7 @@ import { tl } from '@mtcute/tl' * @param chatId ID of the chat, its username, phone or `"me"` or `"self"` * @param medias Medias contained in the message. * @param params Additional sending parameters - * @see InputMedia + * @link InputMedia * @internal */ export async function sendMediaGroup( diff --git a/packages/client/src/methods/messages/send-media.ts b/packages/client/src/methods/messages/send-media.ts index dfdf435e..46380a5c 100644 --- a/packages/client/src/methods/messages/send-media.ts +++ b/packages/client/src/methods/messages/send-media.ts @@ -18,7 +18,7 @@ import { normalizeDate, randomUlong } from '../../utils/misc-utils' * and Bot API compatible File ID, which will be wrapped * in {@link InputMedia.auto} * @param params Additional sending parameters - * @see InputMedia + * @link InputMedia * @internal */ export async function sendMedia( diff --git a/packages/client/src/types/bots/input/input-inline-message.ts b/packages/client/src/types/bots/input/input-inline-message.ts index 71fbefe9..0e1f1f05 100644 --- a/packages/client/src/types/bots/input/input-inline-message.ts +++ b/packages/client/src/types/bots/input/input-inline-message.ts @@ -2,6 +2,9 @@ import { tl } from '@mtcute/tl' import { BotKeyboard, ReplyMarkup } from '../keyboards' import { TelegramClient } from '../../../client' +/** + * Inline message containing only text + */ export interface InputInlineMessageText { type: 'text' @@ -27,8 +30,145 @@ export interface InputInlineMessageText { disableWebPreview?: boolean } +/** + * Inline message containing media, which is automatically + * inferred from the result itself. + */ +export interface InputInlineMessageMedia { + type: 'media' + + /** + * Caption for the media + */ + text?: string + + /** + * Caption markup entities. + * If passed, parse mode is ignored + */ + entities?: tl.TypeMessageEntity[] + + /** + * Message reply markup + */ + replyMarkup?: ReplyMarkup +} + +/** + * Inline message containing a geolocation + */ +export interface InputInlineMessageGeo { + type: 'geo' + + /** + * Latitude of the geolocation + */ + latitude: number + + /** + * Longitude of the geolocation + */ + longitude: number + + /** + * For live locations, direction in which the location + * moves, in degrees (1-360) + */ + heading?: number + + /** + * For live locations, period for which this live location + * will be updated + */ + period?: number + + /** + * For live locations, a maximum distance to another + * chat member for proximity alerts, in meters (0-100000) + */ + proximityNotificationRadius?: number + + /** + * Message's reply markup + */ + replyMarkup?: ReplyMarkup +} + +/** + * Inline message containing a venue + */ +export interface InputInlineMessageVenue { + type: 'venue' + + /** + * Latitude of the geolocation + */ + latitude: number + + /** + * Longitude of the geolocation + */ + longitude: number + + /** + * Venue name + */ + title: string + + /** + * Venue address + */ + address: string + + /** + * When available, source from where this venue was acquired + */ + source?: { + /** + * Provider name (`foursquare` or `gplaces` for Google Places) + */ + provider?: 'foursquare' | 'gplaces' + + /** + * Venue ID in the provider's DB + */ + id: string + + /** + * Venue type in the provider's DB + * + * - [Supported types for Foursquare](https://developer.foursquare.com/docs/build-with-foursquare/categories/) + * (use names, lowercase them, replace spaces and " & " with `_` (underscore) and remove other symbols, + * and use `/` (slash) as hierarchy separator) + * - [Supported types for Google Places](https://developers.google.com/places/web-service/supported_types) + */ + type: string + } + + /** + * Message's reply markup + */ + replyMarkup?: ReplyMarkup +} + +/** + * Inline message containing a game + */ +export interface InputInlineMessageGame { + type: 'game' + + /** + * Message's reply markup + */ + replyMarkup?: ReplyMarkup +} + export type InputInlineMessage = | InputInlineMessageText + | InputInlineMessageMedia + | InputInlineMessageGeo + | InputInlineMessageVenue + | InputInlineMessageGame export namespace BotInlineMessage { export function text ( @@ -44,6 +184,52 @@ export namespace BotInlineMessage { } } + export function media ( + text?: string, + params?: Omit, + ): InputInlineMessageMedia { + return { + type: 'media', + text, + ...( + params || {} + ), + } + } + + export function geo ( + latitude: number, + longitude: number, + params?: Omit, + ): InputInlineMessageGeo { + return { + type: 'geo', + latitude, + longitude, + ...( + params || {} + ), + } + } + + export function venue ( + params: Omit, + ): InputInlineMessageVenue { + return { + type: 'venue', + ...params, + } + } + + export function game ( + params: Omit, + ): InputInlineMessageGame { + return { + type: 'game', + ...params, + } + } + export async function _convertToTl ( client: TelegramClient, obj: InputInlineMessage, @@ -60,6 +246,56 @@ export namespace BotInlineMessage { } } + if (obj.type === 'media') { + const [message, entities] = await client['_parseEntities'](obj.text, parseMode, obj.entities) + + return { + _: 'inputBotInlineMessageMediaAuto', + message, + entities, + replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup) + } + } + + if (obj.type === 'geo') { + return { + _: 'inputBotInlineMessageMediaGeo', + geoPoint: { + _: 'inputGeoPoint', + lat: obj.latitude, + long: obj.longitude + }, + heading: obj.heading, + period: obj.period, + proximityNotificationRadius: obj.proximityNotificationRadius, + replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup) + } + } + + if (obj.type === 'venue') { + return { + _: 'inputBotInlineMessageMediaVenue', + geoPoint: { + _: 'inputGeoPoint', + lat: obj.latitude, + long: obj.longitude + }, + title: obj.title, + address: obj.address, + provider: obj.source?.provider ?? '', + venueId: obj.source?.id ?? '', + venueType: obj.source?.type ?? '', + replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup) + } + } + + if (obj.type === 'game') { + return { + _: 'inputBotInlineMessageGame', + replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup) + } + } + return obj as never } } diff --git a/packages/client/src/types/bots/input/input-inline-result.ts b/packages/client/src/types/bots/input/input-inline-result.ts index 519b5458..ed12b61c 100644 --- a/packages/client/src/types/bots/input/input-inline-result.ts +++ b/packages/client/src/types/bots/input/input-inline-result.ts @@ -1,6 +1,9 @@ import { tl } from '@mtcute/tl' import { BotInlineMessage, InputInlineMessage } from './input-inline-message' import { TelegramClient } from '../../../client' +import { fileIdToInputDocument, fileIdToInputPhoto } from '@mtcute/file-id' +import { extractFileName } from '../../../utils/file-utils' +import { MtCuteArgumentError } from '../../errors' interface BaseInputInlineResult { /** @@ -19,7 +22,7 @@ interface BaseInputInlineResult { } /** - * Represents an input article. + * Inline result containing an article. * * If `message` is not provided, a {@link InputInlineMessageText} is created * with web preview enabled and text generated as follows: @@ -63,40 +66,615 @@ export interface InputInlineResultArticle extends BaseInputInlineResult { hideUrl?: boolean /** - * Article thumbnail URL (only jpeg). + * Article thumbnail URL (must be jpeg). */ thumb?: string | tl.RawInputWebDocument } -export type InputInlineResult = InputInlineResultArticle +/** + * Inline result containing an animation (silent mp4 or gif). + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used + * with empty caption + */ +export interface InputInlineResultGif extends BaseInputInlineResult { + type: 'gif' + + /** + * The animation itself. + * + * Can be a URL, a TDLib and Bot API compatible File ID, + * or a TL object representing either of them. + */ + media: string | tl.RawInputWebDocument | tl.RawInputDocument + + /** + * Media MIME type, defaults to `video/mp4`, only applicable + * to URLs. + * + * Usually unnecessary, since Telegram automatically infers it. + */ + mime?: string + + /** + * Title of the result + */ + title?: string + + /** + * Animation thumbnail URL. Defaults to `media`, + * only applicable in case `media` is a URL + */ + thumb?: string | tl.RawInputWebDocument + + /** + * Thumbnail MIME type (defaults to `image/jpeg`) + */ + thumbMime?: string + + /** + * Width of the animation in pixels + */ + width?: number + + /** + * Height of the animation in pixels + */ + height?: number + + /** + * Duration of the animation in seconds + */ + duration?: number +} + +/** + * Inline result containing a video (only MP4) + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used + * with empty caption for non-embed videos, {@link InputInlineMessageText} + * is used with text containing the URL for embed videos. + */ +export interface InputInlineResultVideo extends BaseInputInlineResult { + type: 'video' + + /** + * The video itself, or a page containing an embedded video + * + * Can be a URL, a TDLib and Bot API compatible File ID, + * or a TL object representing either of them. + */ + media: string | tl.RawInputWebDocument | tl.RawInputDocument + + /** + * In case `media` is a URL, whether that URL is a link + * to an embedded video player. + * + * In such case, thumbnail must be passed explicitly. + */ + isEmbed?: boolean + + /** + * Title of the result + */ + title: string + + /** + * Description of the result + */ + description?: string + + /** + * Video thumbnail URL (must be jpeg). Defaults to `media`, + * only applicable in case `media` is a URL. + * + * Must be provided explicitly if this is an embed video. + */ + thumb?: string | tl.RawInputWebDocument + + /** + * Width of the video in pixels + */ + width?: number + + /** + * Height of the video in pixels + */ + height?: number + + /** + * Duration of the video in seconds + */ + duration?: number +} + +/** + * Inline result containing an audio file + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used + * with empty caption. + */ +export interface InputInlineResultAudio extends BaseInputInlineResult { + type: 'audio' + + /** + * The audio itself + * + * Can be a URL, a TDLib and Bot API compatible File ID, + * or a TL object representing either of them. + */ + media: string | tl.RawInputWebDocument | tl.RawInputDocument + + /** + * MIME type of the audio file, defaults to `audio/mpeg` + * + * Usually unnecessary, since Telegram infers it automatically. + */ + mime?: string + + /** + * Title of the audio track + */ + title: string + + /** + * Performer of the audio track + */ + performer?: string + + /** + * Duration of the audio in seconds + */ + duration?: number +} + +/** + * Inline result containing a voice note + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used + * with empty caption. + */ +export interface InputInlineResultVoice extends BaseInputInlineResult { + type: 'voice' + + /** + * The voice itself (.ogg, preferably encoded with OPUS) + * + * Can be a URL, a TDLib and Bot API compatible File ID, + * or a TL object representing either of them. + */ + media: string | tl.RawInputWebDocument | tl.RawInputDocument + + /** + * Title of the result + */ + title: string + + /** + * Duration of the voice note in seconds + */ + duration?: number +} + +/** + * Inline result containing a photo + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used + * with empty caption. + */ +export interface InputInlineResultPhoto extends BaseInputInlineResult { + type: 'photo' + + /** + * The photo itself + * + * Can be a URL, a TDLib and Bot API compatible File ID, + * or a TL object representing either of them. + */ + media: string | tl.RawInputWebDocument | tl.RawInputPhoto + + /** + * Title of the result + */ + title?: string + + /** + * Description of the result + */ + description?: string + + /** + * Width of the photo in pixels + */ + width?: number + + /** + * Height of the photo in pixels + */ + height?: number + + /** + * Photo thumbnail URL (must be jpeg). Defaults to `media`, + * only applicable in case `media` is a URL + */ + thumb?: string | tl.RawInputWebDocument +} + +/** + * Inline result containing a sticker + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used. + */ +export interface InputInlineResultSticker extends BaseInputInlineResult { + type: 'sticker' + + /** + * The sticker itself. Can't be a URL. + */ + media: string | tl.RawInputDocument +} + +/** + * Inline result containing a document + * + * If `message` is not provided, {@link InputInlineMessageMedia} is used + * with empty caption. + */ +export interface InputInlineResultFile extends BaseInputInlineResult { + type: 'file' + + /** + * The file itself + * + * Can be a URL, a TDLib and Bot API compatible File ID, + * or a TL object representing either of them. + */ + media: string | tl.RawInputWebDocument | tl.RawInputDocument + + /** + * MIME type of the file. + * + * Due to some Telegram limitation, you can only send + * PDF and ZIP files (`application/pdf` and `application/zip` + * MIMEs respectively). + * + * Must be provided if `media` is a URL + */ + mime?: string + + /** + * Title of the result + */ + title?: string + + /** + * Description of the result + */ + description?: string + + /** + * Photo thumbnail URL (must be jpeg). Defaults to `media`, + * only applicable in case `media` is a URL + */ + thumb?: string | tl.RawInputWebDocument +} + +/** + * Inline result containing a geolocation. + * + * If `message` is not passed, a {@link InputInlineMessageGeo} is + * used, with the `latitude` and `longitude` parameters set + * accordingly + */ +export interface InputInlineResultGeo extends BaseInputInlineResult { + type: 'geo' + + /** + * Title of the result + */ + title: string + + /** + * Latitude of the geolocation + */ + latitude: number + + /** + * Longitude of the geolocation + */ + longitude: number + + /** + * Location thumbnail URL (must be jpeg). + * + * By default, Telegram generates one based on + * the location set by `latitude` and `longitude` + */ + thumb?: string | tl.RawInputWebDocument +} + +/** + * Inline result containing a venue. + * + * If `message` is not passed, an error is thrown. + */ +export interface InputInlineResultVenue extends BaseInputInlineResult { + type: 'venue' + + /** + * Title of the venue + */ + title: string + + /** + * Address of the venue + */ + address: string + + /** + * Venue thumbnail URL (must be jpeg). + * + * By default, Telegram generates one based on + * the location in the `message` + */ + thumb?: string | tl.RawInputWebDocument +} + +/** + * Inline result containing a game. + * + * If `message` is not passed, {@link InputInlineMessageGame} is used. + * + * Note that `message` can only be {@link InputInlineMessageGame} + */ +export interface InputInlineResultGame extends BaseInputInlineResult { + type: 'game' + + /** + * Short name of the game + */ + shortName: string +} + +/** + * Inline result containing a contact. + * + * If `message` is not passed, {@link InputInlineMessageContact} is used. + */ +export interface InputInlineResultContact extends BaseInputInlineResult { + type: 'contact' + + /** + * First name of the contact + */ + firstName: string + + /** + * Last name of the contact + */ + lastName?: string + + /** + * Phone number of the contact + */ + phone: string + + /** + * Contact thumbnail URL (i.e. their avatar) (must be jpeg) + */ + thumb?: string | tl.RawInputWebDocument +} + +export type InputInlineResult = + | InputInlineResultArticle + | InputInlineResultGif + | InputInlineResultVideo + | InputInlineResultAudio + | InputInlineResultVoice + | InputInlineResultPhoto + | InputInlineResultSticker + | InputInlineResultFile + | InputInlineResultGeo + | InputInlineResultVenue + | InputInlineResultGame + | InputInlineResultContact export namespace BotInline { export function article( - params: Omit + id: string, + params: Omit ): InputInlineResultArticle { return { + id, type: 'article', ...params, } } + export function gif( + id: string, + media: string | tl.RawInputWebDocument | tl.RawInputDocument, + params: Omit + ): InputInlineResultGif { + return { + id, + media, + type: 'gif', + ...params, + } + } + + export function video( + id: string, + media: string | tl.RawInputWebDocument | tl.RawInputDocument, + params: Omit + ): InputInlineResultVideo { + return { + id, + type: 'video', + media, + ...params, + } + } + + export function audio( + id: string, + media: string | tl.RawInputWebDocument | tl.RawInputDocument, + params: Omit + ): InputInlineResultAudio { + return { + id, + type: 'audio', + media, + ...params, + } + } + + export function voice( + id: string, + media: string | tl.RawInputWebDocument | tl.RawInputDocument, + params: Omit + ): InputInlineResultVoice { + return { + id, + type: 'voice', + media, + ...params, + } + } + + export function photo( + id: string, + media: string | tl.RawInputWebDocument | tl.RawInputPhoto, + params: Omit + ): InputInlineResultPhoto { + return { + id, + type: 'photo', + media, + ...params, + } + } + + export function sticker( + id: string, + media: string | tl.RawInputDocument + ): InputInlineResultSticker { + return { + id, + type: 'sticker', + media, + } + } + + export function file( + id: string, + media: string | tl.RawInputWebDocument | tl.RawInputDocument, + params: Omit + ): InputInlineResultFile { + return { + id, + type: 'file', + media, + ...params, + } + } + + export function geo( + latitude: number, + longitude: number, + params: Omit + ): InputInlineResultGeo { + return { + type: 'geo', + latitude, + longitude, + ...params, + } + } + + export function venue( + id: string, + params: Omit + ): InputInlineResultVenue { + return { + id, + type: 'venue', + ...params, + } + } + + export function contact( + id: string, + params: Omit + ): InputInlineResultContact { + return { + id, + type: 'contact', + ...params, + } + } + + export function game( + id: string, + shortName: string, + params?: Omit + ): InputInlineResultGame { + return { + id, + type: 'game', + shortName, + ...(params || {}) + } + } + export async function _convertToTl( client: TelegramClient, obj: InputInlineResult, parseMode?: string | null ): Promise { + const normalizeThumb = ( + obj: InputInlineResult, + fallback?: string + ): tl.RawInputWebDocument | undefined => { + if (obj.type !== 'voice' && obj.type !== 'audio' && obj.type !== 'sticker' && obj.type !== 'game') { + if (!obj.thumb || typeof obj.thumb === 'string') { + if (!obj.thumb && !fallback) { + return undefined + } + + return { + _: 'inputWebDocument', + size: 0, + url: obj.thumb || fallback!, + mimeType: + obj.type === 'gif' + ? obj.thumbMime ?? 'image/jpeg' + : 'image/jpeg', + attributes: [], + } + } else { + return obj.thumb + } + } + } + if (obj.type === 'article') { let sendMessage: tl.TypeInputBotInlineMessage if (obj.message) { - sendMessage = await BotInlineMessage._convertToTl(client, obj.message, parseMode) + sendMessage = await BotInlineMessage._convertToTl( + client, + obj.message, + parseMode + ) } else { let message = obj.title const entities: tl.TypeMessageEntity[] = [ { _: 'messageEntityBold', offset: 0, - length: message.length - } + length: message.length, + }, ] if (obj.url) { @@ -104,7 +682,7 @@ export namespace BotInline { _: 'messageEntityTextUrl', url: obj.url, offset: 0, - length: message.length + length: message.length, }) } @@ -115,7 +693,7 @@ export namespace BotInline { sendMessage = { _: 'inputBotInlineMessageText', message, - entities + entities, } } @@ -126,24 +704,235 @@ export namespace BotInline { title: obj.title, description: obj.description, url: obj.hideUrl ? undefined : obj.url, - content: obj.url && obj.hideUrl ? { - _: 'inputWebDocument', - url: obj.url, - mimeType: 'text/html', - size: 0, - attributes: [] - } : undefined, - thumb: typeof obj.thumb === 'string' ? { - _: 'inputWebDocument', - size: 0, - url: obj.thumb, - mimeType: 'image/jpeg', - attributes: [], - } : obj.thumb, + content: + obj.url && obj.hideUrl + ? { + _: 'inputWebDocument', + url: obj.url, + mimeType: 'text/html', + size: 0, + attributes: [], + } + : undefined, + thumb: + typeof obj.thumb === 'string' + ? normalizeThumb(obj) + : obj.thumb, + sendMessage, + } + } + + if (obj.type === 'game') { + let sendMessage: tl.TypeInputBotInlineMessage + if (obj.message) { + sendMessage = await BotInlineMessage._convertToTl( + client, + obj.message, + parseMode + ) + if (sendMessage._ !== 'inputBotInlineMessageGame') { + throw new MtCuteArgumentError('game inline result must contain a game inline message') + } + } else { + sendMessage = { + _: 'inputBotInlineMessageGame' + } + } + + return { + _: 'inputBotInlineResultGame', + id: obj.id, + shortName: obj.shortName, sendMessage } } - return obj as never + let sendMessage: tl.TypeInputBotInlineMessage + if (obj.message) { + sendMessage = await BotInlineMessage._convertToTl( + client, + obj.message, + parseMode + ) + } else { + if (obj.type === 'venue') + throw new MtCuteArgumentError('message bust be supplied for venue inline result') + + if ( + obj.type === 'video' && + obj.isEmbed && + typeof obj.media === 'string' + ) { + sendMessage = { + _: 'inputBotInlineMessageText', + message: obj.media, + } + } else if (obj.type === 'geo') { + sendMessage = { + _: 'inputBotInlineMessageMediaGeo', + geoPoint: { + _: 'inputGeoPoint', + lat: obj.latitude, + long: obj.longitude, + }, + } + } else if (obj.type === 'contact') { + sendMessage = { + _: 'inputBotInlineMessageMediaContact', + phoneNumber: obj.phone, + firstName: obj.firstName, + lastName: obj.lastName ?? '', + vcard: '' + + } + } else { + sendMessage = { + _: 'inputBotInlineMessageMediaAuto', + message: '', + } + } + } + + let media: + | tl.TypeInputWebDocument + | tl.TypeInputDocument + | tl.TypeInputPhoto + | undefined = undefined + if (obj.type !== 'geo' && obj.type !== 'venue' && obj.type !== 'contact') { + if (typeof obj.media === 'string') { + // file id or url + if (obj.media.match(/^https?:\/\//)) { + if (obj.type === 'sticker') + throw new MtCuteArgumentError('sticker inline result cannot contain a URL') + + let mime: string + if (obj.type === 'video') mime = 'video/mp4' + else if (obj.type === 'audio') + mime = obj.mime ?? 'audio/mpeg' + else if (obj.type === 'gif') mime = obj.mime ?? 'image/jpeg' + else if (obj.type === 'voice') mime = 'audio/ogg' + else if (obj.type === 'file') { + if (!obj.mime) + throw new MtCuteArgumentError( + 'MIME type must be specified for file inline result' + ) + + mime = obj.mime + } else mime = 'image/jpeg' + + const attributes: tl.TypeDocumentAttribute[] = [] + + if ( + (obj.type === 'video' || + obj.type === 'gif' || + obj.type === 'photo') && + obj.width && + obj.height + ) { + if (obj.type !== 'photo' && obj.duration) { + attributes.push({ + _: 'documentAttributeVideo', + w: obj.width, + h: obj.height, + duration: obj.duration, + }) + } else { + attributes.push({ + _: 'documentAttributeImageSize', + w: obj.width, + h: obj.height, + }) + } + } else if (obj.type === 'audio' || obj.type === 'voice') { + attributes.push({ + _: 'documentAttributeAudio', + voice: obj.type === 'voice', + duration: obj.duration ?? 0, + title: obj.type === 'audio' ? obj.title : '', + performer: + obj.type === 'audio' ? obj.performer : '', + }) + } + + attributes.push({ + _: 'documentAttributeFilename', + fileName: extractFileName(obj.media), + }) + + media = { + _: 'inputWebDocument', + url: obj.media, + mimeType: mime, + size: 0, + attributes, + } + } else if (obj.type === 'photo') { + media = fileIdToInputPhoto(obj.media) + } else { + media = fileIdToInputDocument(obj.media) + } + } else { + media = obj.media + } + } + + let title: string | undefined = undefined + let description: string | undefined = undefined + + // incredible hacks by durov team. + // i honestly don't understand why didn't they just + // make a bunch of types, as they normally do, + // but whatever. + // ref: https://github.com/tdlib/td/blob/master/td/telegram/InlineQueriesManager.cpp + if (obj.type === 'contact') { + title = obj.lastName?.length ? `${obj.firstName} ${obj.lastName}` : obj.firstName + } else if (obj.type !== 'sticker') { + title = obj.title + } + + if (obj.type === 'audio') { + description = obj.performer + } else if (obj.type === 'geo') { + description = `${obj.latitude} ${obj.longitude}` + } else if (obj.type === 'venue') { + description = obj.address + } else if (obj.type === 'contact') { + description = obj.phone + } else if (obj.type !== 'gif' && obj.type !== 'voice' && obj.type !== 'sticker') { + description = obj.description + } + + if (!media || media._ === 'inputWebDocument') { + return { + _: 'inputBotInlineResult', + id: obj.id, + type: obj.type, + title, + description, + content: media, + thumb: normalizeThumb(obj, media?.url), + sendMessage, + } + } + + if (media._ === 'inputPhoto') { + return { + _: 'inputBotInlineResultPhoto', + id: obj.id, + type: obj.type, + photo: media, + sendMessage, + } + } + + return { + _: 'inputBotInlineResultDocument', + id: obj.id, + type: obj.type, + title, + description, + document: media, + sendMessage, + } } } diff --git a/packages/client/src/types/files/file-location.ts b/packages/client/src/types/files/file-location.ts index 4cf24f57..713ca788 100644 --- a/packages/client/src/types/files/file-location.ts +++ b/packages/client/src/types/files/file-location.ts @@ -84,7 +84,7 @@ export class FileLocation { * * Shorthand for `client.downloadAsStream({ location: this })` * - * @see TelegramClient.downloadAsIterable + * @link TelegramClient.downloadAsIterable */ downloadIterable(): AsyncIterableIterator { return this.client.downloadAsIterable({ location: this }) @@ -96,7 +96,7 @@ export class FileLocation { * * Shorthand for `client.downloadAsStream({ location: this })` * - * @see TelegramClient.downloadAsStream + * @link TelegramClient.downloadAsStream */ downloadStream(): Readable { return this.client.downloadAsStream({ location: this }) @@ -107,7 +107,7 @@ export class FileLocation { * * Shorthand for `client.downloadAsBuffer({ location: this })` * - * @see TelegramClient.downloadAsBuffer + * @link TelegramClient.downloadAsBuffer */ downloadBuffer(): Promise { return this.client.downloadAsBuffer({ location: this }) @@ -120,7 +120,7 @@ export class FileLocation { * Shorthand for `client.downloadToFile(filename, { location: this })` * * @param filename Local file name - * @see TelegramClient.downloadToFile + * @link TelegramClient.downloadToFile */ downloadToFile(filename: string): Promise { return this.client.downloadToFile(filename, { location: this }) diff --git a/packages/client/src/types/media/input-media.ts b/packages/client/src/types/media/input-media.ts index 8091df54..5eee54cf 100644 --- a/packages/client/src/types/media/input-media.ts +++ b/packages/client/src/types/media/input-media.ts @@ -240,7 +240,7 @@ export interface InputMediaVideo extends BaseInputMedia { * applicable if `file` is {@link UploadFileLike}, * otherwise they are ignored. * - * @see InputMedia + * @link InputMedia */ export type InputMediaLike = | InputMediaAudio diff --git a/packages/client/src/types/messages/message.ts b/packages/client/src/types/messages/message.ts index 2806897d..0769876d 100644 --- a/packages/client/src/types/messages/message.ts +++ b/packages/client/src/types/messages/message.ts @@ -931,7 +931,7 @@ export class Message { /** * Edit this message's text and/or reply markup * - * @see TelegramClient.editMessage + * @link TelegramClient.editMessage */ edit( params: Parameters[2] @@ -947,7 +947,7 @@ export class Message { * * @param text New message text * @param params Additional parameters - * @see TelegramClient.editMessage + * @link TelegramClient.editMessage */ editText( text: string,