feat(core): message effects

This commit is contained in:
alina 🌸 2024-06-01 17:22:57 +03:00
parent f84da0dce8
commit 710ef3f211
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
12 changed files with 146 additions and 1 deletions

View file

@ -132,6 +132,7 @@ import { editInlineMessage } from './methods/messages/edit-inline-message.js'
import { editMessage } from './methods/messages/edit-message.js' import { editMessage } from './methods/messages/edit-message.js'
import { ForwardMessageOptions, forwardMessages, forwardMessagesById } from './methods/messages/forward-messages.js' import { ForwardMessageOptions, forwardMessages, forwardMessagesById } from './methods/messages/forward-messages.js'
import { getAllScheduledMessages } from './methods/messages/get-all-scheduled-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 { getCallbackQueryMessage } from './methods/messages/get-callback-query-message.js'
import { getDiscussionMessage } from './methods/messages/get-discussion-message.js' import { getDiscussionMessage } from './methods/messages/get-discussion-message.js'
import { getFactCheck } from './methods/messages/get-fact-check.js' import { getFactCheck } from './methods/messages/get-fact-check.js'
@ -302,6 +303,7 @@ import {
InputText, InputText,
MaybeDynamic, MaybeDynamic,
Message, Message,
MessageEffect,
MessageMedia, MessageMedia,
MessageReactions, MessageReactions,
ParametersSkip2, ParametersSkip2,
@ -3245,6 +3247,13 @@ export interface TelegramClient extends ITelegramClient {
*/ */
getAllScheduledMessages(chatId: InputPeerLike): Promise<Message[]> getAllScheduledMessages(chatId: InputPeerLike): Promise<Message[]>
/**
* Get a list of available message effects
* **Available**: 👤 users only
*
*/
getAvailableMessageEffects(): Promise<MessageEffect[]>
/** /**
* Get the message containing the button being clicked * Get the message containing the button being clicked
* in the given callback query. * in the given callback query.
@ -5916,6 +5925,9 @@ TelegramClient.prototype.forwardMessages = function (...args) {
TelegramClient.prototype.getAllScheduledMessages = function (...args) { TelegramClient.prototype.getAllScheduledMessages = function (...args) {
return getAllScheduledMessages(this._client, ...args) return getAllScheduledMessages(this._client, ...args)
} }
TelegramClient.prototype.getAvailableMessageEffects = function (...args) {
return getAvailableMessageEffects(this._client, ...args)
}
TelegramClient.prototype.getCallbackQueryMessage = function (...args) { TelegramClient.prototype.getCallbackQueryMessage = function (...args) {
return getCallbackQueryMessage(this._client, ...args) return getCallbackQueryMessage(this._client, ...args)
} }

View file

@ -130,6 +130,7 @@ export type { ForwardMessageOptions } from './methods/messages/forward-messages.
export { forwardMessagesById } from './methods/messages/forward-messages.js' export { forwardMessagesById } from './methods/messages/forward-messages.js'
export { forwardMessages } 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 { 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 { getCallbackQueryMessage } from './methods/messages/get-callback-query-message.js'
export { getDiscussionMessage } from './methods/messages/get-discussion-message.js' export { getDiscussionMessage } from './methods/messages/get-discussion-message.js'
export { getFactCheck } from './methods/messages/get-fact-check.js' export { getFactCheck } from './methods/messages/get-fact-check.js'

View file

@ -69,6 +69,7 @@ import {
InputText, InputText,
MaybeDynamic, MaybeDynamic,
Message, Message,
MessageEffect,
MessageMedia, MessageMedia,
MessageReactions, MessageReactions,
ParametersSkip2, ParametersSkip2,

View file

@ -1,6 +1,7 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { RpcCallOptions } from '../../../network/network-manager.js' import { RpcCallOptions } from '../../../network/network-manager.js'
import { MustEqual } from '../../../types/utils.js'
import { LruMap } from '../../../utils/lru-map.js' import { LruMap } from '../../../utils/lru-map.js'
import { ITelegramClient } from '../../client.types.js' import { ITelegramClient } from '../../client.types.js'
import { getBusinessConnection } from '../premium/get-business-connection.js' import { getBusinessConnection } from '../premium/get-business-connection.js'
@ -26,7 +27,7 @@ const getDcMap = (client: ITelegramClient): LruMap<string, number> => {
export async function _maybeInvokeWithBusinessConnection<T extends tl.RpcMethod>( export async function _maybeInvokeWithBusinessConnection<T extends tl.RpcMethod>(
client: ITelegramClient, client: ITelegramClient,
businessConnectionId: string | undefined, businessConnectionId: string | undefined,
request: T, request: MustEqual<T, tl.RpcMethod>,
params?: RpcCallOptions, params?: RpcCallOptions,
): Promise<tl.RpcCallReturn[T['_']]> { ): Promise<tl.RpcCallReturn[T['_']]> {
if (!businessConnectionId) { if (!businessConnectionId) {

View file

@ -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<MessageEffect[]> {
const res = await client.call({
_: 'messages.getAvailableEffects',
hash: 0,
})
assertTypeIsNot('getAvailableMessageEffects', res, 'messages.availableEffectsNotModified')
const documentsMap = new LongMap<tl.RawDocument>()
for (const doc of res.documents) {
if (doc._ !== 'document') continue
documentsMap.set(doc.id, doc)
}
return res.effects.map((effect) => new MessageEffect(effect, documentsMap))
}

View file

@ -117,6 +117,13 @@ export interface CommonSendParams {
* the message will be sent * the message will be sent
*/ */
businessConnectionId?: string 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
} }
/** /**

View file

@ -114,6 +114,7 @@ export async function sendMediaGroup(
sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined,
invertMedia: params.invertMedia, invertMedia: params.invertMedia,
quickReplyShortcut, quickReplyShortcut,
effect: params.effect,
}, },
{ chainId }, { chainId },
) )

View file

@ -107,6 +107,7 @@ export async function sendMedia(
sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined,
invertMedia: params.invert, invertMedia: params.invert,
quickReplyShortcut, quickReplyShortcut,
effect: params.effect,
}, },
{ chainId }, { chainId },
) )

View file

@ -81,6 +81,7 @@ export async function sendText(
sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined, sendAs: params.sendAs ? await resolvePeer(client, params.sendAs) : undefined,
invertMedia: params.invertMedia, invertMedia: params.invertMedia,
quickReplyShortcut, quickReplyShortcut,
effect: params.effect,
}, },
{ chainId }, { chainId },
) )

View file

@ -4,6 +4,7 @@ export * from './fact-check.js'
export * from './input-message-id.js' export * from './input-message-id.js'
export * from './message.js' export * from './message.js'
export * from './message-action.js' export * from './message-action.js'
export * from './message-effect.js'
export * from './message-entity.js' export * from './message-entity.js'
export * from './message-forward.js' export * from './message-forward.js'
export * from './message-media.js' export * from './message-media.js'

View file

@ -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<tl.RawDocument>,
) {}
/** 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)

View file

@ -484,6 +484,15 @@ export class Message {
return new FactCheck(this.raw.factcheck) 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 * Generated permalink to this message, only for groups and channels
* *