diff --git a/packages/core/src/highlevel/client.ts b/packages/core/src/highlevel/client.ts index 4559d1f0..bd067425 100644 --- a/packages/core/src/highlevel/client.ts +++ b/packages/core/src/highlevel/client.ts @@ -132,6 +132,7 @@ import { editInlineMessage } from './methods/messages/edit-inline-message.js' import { editMessage } from './methods/messages/edit-message.js' import { ForwardMessageOptions, forwardMessages, forwardMessagesById } from './methods/messages/forward-messages.js' import { getAllScheduledMessages } from './methods/messages/get-all-scheduled-messages.js' +import { getAvailableMessageEffects } from './methods/messages/get-available-effects.js' import { getCallbackQueryMessage } from './methods/messages/get-callback-query-message.js' import { getDiscussionMessage } from './methods/messages/get-discussion-message.js' import { getFactCheck } from './methods/messages/get-fact-check.js' @@ -302,6 +303,7 @@ import { InputText, MaybeDynamic, Message, + MessageEffect, MessageMedia, MessageReactions, ParametersSkip2, @@ -3245,6 +3247,13 @@ export interface TelegramClient extends ITelegramClient { */ getAllScheduledMessages(chatId: InputPeerLike): Promise + /** + * Get a list of available message effects + * **Available**: 👤 users only + * + */ + getAvailableMessageEffects(): Promise + /** * Get the message containing the button being clicked * in the given callback query. @@ -5916,6 +5925,9 @@ TelegramClient.prototype.forwardMessages = function (...args) { TelegramClient.prototype.getAllScheduledMessages = function (...args) { return getAllScheduledMessages(this._client, ...args) } +TelegramClient.prototype.getAvailableMessageEffects = function (...args) { + return getAvailableMessageEffects(this._client, ...args) +} TelegramClient.prototype.getCallbackQueryMessage = function (...args) { return getCallbackQueryMessage(this._client, ...args) } diff --git a/packages/core/src/highlevel/methods.ts b/packages/core/src/highlevel/methods.ts index 1f96e19f..aa06c23e 100644 --- a/packages/core/src/highlevel/methods.ts +++ b/packages/core/src/highlevel/methods.ts @@ -130,6 +130,7 @@ export type { ForwardMessageOptions } from './methods/messages/forward-messages. export { forwardMessagesById } from './methods/messages/forward-messages.js' export { forwardMessages } from './methods/messages/forward-messages.js' export { getAllScheduledMessages } from './methods/messages/get-all-scheduled-messages.js' +export { getAvailableMessageEffects } from './methods/messages/get-available-effects.js' export { getCallbackQueryMessage } from './methods/messages/get-callback-query-message.js' export { getDiscussionMessage } from './methods/messages/get-discussion-message.js' export { getFactCheck } from './methods/messages/get-fact-check.js' diff --git a/packages/core/src/highlevel/methods/_imports.ts b/packages/core/src/highlevel/methods/_imports.ts index bd6f1405..76824dee 100644 --- a/packages/core/src/highlevel/methods/_imports.ts +++ b/packages/core/src/highlevel/methods/_imports.ts @@ -69,6 +69,7 @@ import { InputText, MaybeDynamic, Message, + MessageEffect, MessageMedia, MessageReactions, ParametersSkip2, diff --git a/packages/core/src/highlevel/methods/messages/_business-connection.ts b/packages/core/src/highlevel/methods/messages/_business-connection.ts index a992ee9c..0282e4f0 100644 --- a/packages/core/src/highlevel/methods/messages/_business-connection.ts +++ b/packages/core/src/highlevel/methods/messages/_business-connection.ts @@ -1,6 +1,7 @@ import { tl } from '@mtcute/tl' import { RpcCallOptions } from '../../../network/network-manager.js' +import { MustEqual } from '../../../types/utils.js' import { LruMap } from '../../../utils/lru-map.js' import { ITelegramClient } from '../../client.types.js' import { getBusinessConnection } from '../premium/get-business-connection.js' @@ -26,7 +27,7 @@ const getDcMap = (client: ITelegramClient): LruMap => { export async function _maybeInvokeWithBusinessConnection( client: ITelegramClient, businessConnectionId: string | undefined, - request: T, + request: MustEqual, params?: RpcCallOptions, ): Promise { if (!businessConnectionId) { diff --git a/packages/core/src/highlevel/methods/messages/get-available-effects.ts b/packages/core/src/highlevel/methods/messages/get-available-effects.ts new file mode 100644 index 00000000..d82c6df9 --- /dev/null +++ b/packages/core/src/highlevel/methods/messages/get-available-effects.ts @@ -0,0 +1,28 @@ +import { tl } from '@mtcute/tl' + +import { LongMap } from '../../../utils/long-utils.js' +import { assertTypeIsNot } from '../../../utils/type-assertions.js' +import { ITelegramClient } from '../../client.types.js' +import { MessageEffect } from '../../types/index.js' + +// @available=user +/** + * Get a list of available message effects + */ +export async function getAvailableMessageEffects(client: ITelegramClient): Promise { + const res = await client.call({ + _: 'messages.getAvailableEffects', + hash: 0, + }) + + assertTypeIsNot('getAvailableMessageEffects', res, 'messages.availableEffectsNotModified') + + const documentsMap = new LongMap() + + for (const doc of res.documents) { + if (doc._ !== 'document') continue + documentsMap.set(doc.id, doc) + } + + return res.effects.map((effect) => new MessageEffect(effect, documentsMap)) +} diff --git a/packages/core/src/highlevel/methods/messages/send-common.ts b/packages/core/src/highlevel/methods/messages/send-common.ts index 924decbd..f083511f 100644 --- a/packages/core/src/highlevel/methods/messages/send-common.ts +++ b/packages/core/src/highlevel/methods/messages/send-common.ts @@ -117,6 +117,13 @@ export interface CommonSendParams { * the message will be sent */ businessConnectionId?: string + + /** + * ID of a message effect to use when sending the message + * (see {@link TelegramClient.getAvailableMessageEffects}) + */ + effect?: tl.Long + // todo: once we have a caching layer, we can accept an emoji here } /** diff --git a/packages/core/src/highlevel/methods/messages/send-media-group.ts b/packages/core/src/highlevel/methods/messages/send-media-group.ts index ec2a2de9..fcf6fec3 100644 --- a/packages/core/src/highlevel/methods/messages/send-media-group.ts +++ b/packages/core/src/highlevel/methods/messages/send-media-group.ts @@ -114,6 +114,7 @@ export async function sendMediaGroup( sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, invertMedia: params.invertMedia, quickReplyShortcut, + effect: params.effect, }, { chainId }, ) diff --git a/packages/core/src/highlevel/methods/messages/send-media.ts b/packages/core/src/highlevel/methods/messages/send-media.ts index 254ef73e..e1e71bec 100644 --- a/packages/core/src/highlevel/methods/messages/send-media.ts +++ b/packages/core/src/highlevel/methods/messages/send-media.ts @@ -107,6 +107,7 @@ export async function sendMedia( sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, invertMedia: params.invert, quickReplyShortcut, + effect: params.effect, }, { chainId }, ) diff --git a/packages/core/src/highlevel/methods/messages/send-text.ts b/packages/core/src/highlevel/methods/messages/send-text.ts index d027b734..70291d43 100644 --- a/packages/core/src/highlevel/methods/messages/send-text.ts +++ b/packages/core/src/highlevel/methods/messages/send-text.ts @@ -81,6 +81,7 @@ export async function sendText( sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, invertMedia: params.invertMedia, quickReplyShortcut, + effect: params.effect, }, { chainId }, ) diff --git a/packages/core/src/highlevel/types/messages/index.ts b/packages/core/src/highlevel/types/messages/index.ts index fb09b82a..4c409463 100644 --- a/packages/core/src/highlevel/types/messages/index.ts +++ b/packages/core/src/highlevel/types/messages/index.ts @@ -4,6 +4,7 @@ export * from './fact-check.js' export * from './input-message-id.js' export * from './message.js' export * from './message-action.js' +export * from './message-effect.js' export * from './message-entity.js' export * from './message-forward.js' export * from './message-media.js' diff --git a/packages/core/src/highlevel/types/messages/message-effect.ts b/packages/core/src/highlevel/types/messages/message-effect.ts new file mode 100644 index 00000000..d7d63f62 --- /dev/null +++ b/packages/core/src/highlevel/types/messages/message-effect.ts @@ -0,0 +1,82 @@ +import { tl } from '@mtcute/tl' + +import { MtTypeAssertionError } from '../../../types/errors.js' +import { LongMap } from '../../../utils/long-utils.js' +import { makeInspectable } from '../../utils/inspectable.js' +import { memoizeGetters } from '../../utils/memoize.js' +import { parseSticker } from '../media/document-utils.js' +import { Sticker } from '../media/sticker.js' + +export class MessageEffect { + constructor( + readonly raw: tl.RawAvailableEffect, + readonly documentsMap: LongMap, + ) {} + + /** Whether Telegram Premium is required to use this effect */ + get isPremiumRequired(): boolean { + return this.raw.premiumRequired! + } + + /** ID of this effect */ + get id(): tl.Long { + return this.raw.id + } + + /** Emoji representint this reaction */ + get emoji(): string { + return this.raw.emoticon + } + + /** Sticker representing a static icon for this effect (if any) */ + get staticIcon(): Sticker | null { + if (!this.raw.staticIconId) return null + + const document = this.documentsMap.get(this.raw.staticIconId) + if (!document) return null + + const parsed = parseSticker(document) + + if (!parsed) { + throw new MtTypeAssertionError('MessageEffect.staticIcon', 'sticker', 'null') + } + + return parsed + } + + /** Animated icon representing the effect */ + get icon(): Sticker { + const document = this.documentsMap.get(this.raw.effectStickerId) + + if (!document) { + throw new MtTypeAssertionError('MessageEffect.effect', 'document', 'null') + } + + const parsed = parseSticker(document) + + if (!parsed) { + throw new MtTypeAssertionError('MessageEffect.effect', 'sticker', 'null') + } + + return parsed + } + + /** The animation itself */ + get animation(): Sticker | null { + if (!this.raw.effectAnimationId) return null + + const document = this.documentsMap.get(this.raw.effectAnimationId) + if (!document) return null + + const parsed = parseSticker(document) + + if (!parsed) { + throw new MtTypeAssertionError('MessageEffect.effectAnimation', 'sticker', 'null') + } + + return parsed + } +} + +memoizeGetters(MessageEffect, ['staticIcon', 'icon', 'animation']) +makeInspectable(MessageEffect) diff --git a/packages/core/src/highlevel/types/messages/message.ts b/packages/core/src/highlevel/types/messages/message.ts index c2763ad1..79c3a50e 100644 --- a/packages/core/src/highlevel/types/messages/message.ts +++ b/packages/core/src/highlevel/types/messages/message.ts @@ -484,6 +484,15 @@ export class Message { return new FactCheck(this.raw.factcheck) } + /** + * If this message was sent with a message effect, ID of the effect + */ + get effectId(): tl.Long | null { + if (this.raw._ === 'messageService') return null + + return this.raw.effect ?? null + } + /** * Generated permalink to this message, only for groups and channels *