diff --git a/packages/client/src/types/media/audio.ts b/packages/client/src/types/media/audio.ts index fd77e57c..228f4848 100644 --- a/packages/client/src/types/media/audio.ts +++ b/packages/client/src/types/media/audio.ts @@ -8,6 +8,8 @@ import { tdFileId } from '@mtcute/file-id' * An audio file */ export class Audio extends RawDocument { + readonly type = 'audio' as const + readonly doc: tl.RawDocument readonly attr: tl.RawDocumentAttributeAudio diff --git a/packages/client/src/types/media/contact.ts b/packages/client/src/types/media/contact.ts index 0cf2a19e..d0099064 100644 --- a/packages/client/src/types/media/contact.ts +++ b/packages/client/src/types/media/contact.ts @@ -5,6 +5,8 @@ import { tl } from '@mtcute/tl' import { makeInspectable } from '../utils' export class Contact { + readonly type = 'contact' as const + readonly obj: tl.RawMessageMediaContact constructor(obj: tl.RawMessageMediaContact) { diff --git a/packages/client/src/types/media/dice.ts b/packages/client/src/types/media/dice.ts index 0315c39d..e7cae761 100644 --- a/packages/client/src/types/media/dice.ts +++ b/packages/client/src/types/media/dice.ts @@ -5,6 +5,8 @@ import { makeInspectable } from '../utils' * A dice or another interactive random emoji. */ export class Dice { + readonly type = 'dice' as const + readonly obj: tl.RawMessageMediaDice /** diff --git a/packages/client/src/types/media/document.ts b/packages/client/src/types/media/document.ts index 891489e0..5aa7a4fd 100644 --- a/packages/client/src/types/media/document.ts +++ b/packages/client/src/types/media/document.ts @@ -169,6 +169,8 @@ export class RawDocument extends FileLocation { * and only used for documents without any special * attributes. */ -export class Document extends RawDocument {} +export class Document extends RawDocument { + readonly type = 'document' as const +} makeInspectable(Document, ['fileSize', 'dcId'], ['inputMedia', 'inputDocument']) diff --git a/packages/client/src/types/media/game.ts b/packages/client/src/types/media/game.ts index c74361a0..8e3948a0 100644 --- a/packages/client/src/types/media/game.ts +++ b/packages/client/src/types/media/game.ts @@ -5,6 +5,8 @@ import { TelegramClient } from '../../client' import { makeInspectable } from '../utils' export class Game { + readonly type = 'game' as const + readonly game: tl.RawGame readonly client: TelegramClient diff --git a/packages/client/src/types/media/invoice.ts b/packages/client/src/types/media/invoice.ts index e66b10ec..1567132b 100644 --- a/packages/client/src/types/media/invoice.ts +++ b/packages/client/src/types/media/invoice.ts @@ -8,6 +8,8 @@ import { MtCuteArgumentError } from '../errors' * An invoice */ export class Invoice { + readonly type = 'invoice' as const + readonly client: TelegramClient readonly raw: tl.RawMessageMediaInvoice diff --git a/packages/client/src/types/media/location.ts b/packages/client/src/types/media/location.ts index c0e3e923..e7e9da3c 100644 --- a/packages/client/src/types/media/location.ts +++ b/packages/client/src/types/media/location.ts @@ -6,7 +6,7 @@ import { TelegramClient } from '../../client' /** * A point on the map */ -export class Location { +export class RawLocation { readonly client: TelegramClient readonly geo: tl.RawGeoPoint @@ -105,7 +105,13 @@ export class Location { } } -export class LiveLocation extends Location { +export class Location extends RawLocation { + readonly type = 'location' as const +} + +export class LiveLocation extends RawLocation { + readonly type = 'live_location' as const + readonly live: tl.RawMessageMediaGeoLive constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) { diff --git a/packages/client/src/types/media/photo.ts b/packages/client/src/types/media/photo.ts index e23d2f9d..a1745fd0 100644 --- a/packages/client/src/types/media/photo.ts +++ b/packages/client/src/types/media/photo.ts @@ -9,6 +9,8 @@ import { makeInspectable } from '../utils' * A photo */ export class Photo extends FileLocation { + readonly type: 'photo' + /** Raw TL object */ readonly raw: tl.RawPhoto @@ -78,6 +80,7 @@ export class Photo extends FileLocation { this.raw = raw this.width = width this.height = height + this.type = 'photo' } /** Date this photo was sent */ diff --git a/packages/client/src/types/media/poll.ts b/packages/client/src/types/media/poll.ts index b49197f3..d3eb73ad 100644 --- a/packages/client/src/types/media/poll.ts +++ b/packages/client/src/types/media/poll.ts @@ -38,6 +38,8 @@ export namespace Poll { } export class Poll { + readonly type = 'poll' as const + readonly client: TelegramClient readonly raw: tl.TypePoll readonly results?: tl.TypePollResults diff --git a/packages/client/src/types/media/sticker.ts b/packages/client/src/types/media/sticker.ts index bf968fe9..956ec991 100644 --- a/packages/client/src/types/media/sticker.ts +++ b/packages/client/src/types/media/sticker.ts @@ -40,6 +40,8 @@ const MASK_POS = ['forehead', 'eyes', 'mouth', 'chin'] as const * A sticker */ export class Sticker extends RawDocument { + readonly type = 'sticker' as const + readonly attr: tl.RawDocumentAttributeSticker readonly attrSize?: tl.RawDocumentAttributeImageSize diff --git a/packages/client/src/types/media/venue.ts b/packages/client/src/types/media/venue.ts index ef5f11fb..bb573493 100644 --- a/packages/client/src/types/media/venue.ts +++ b/packages/client/src/types/media/venue.ts @@ -29,6 +29,8 @@ export namespace Venue { } export class Venue { + readonly type = 'venue' as const + readonly client: TelegramClient readonly raw: tl.RawMessageMediaVenue diff --git a/packages/client/src/types/media/video.ts b/packages/client/src/types/media/video.ts index d561ee73..d8eca2d7 100644 --- a/packages/client/src/types/media/video.ts +++ b/packages/client/src/types/media/video.ts @@ -10,6 +10,8 @@ import { tdFileId } from '@mtcute/file-id' * **Note:** Legacy GIF animations are also wrapped with this class. */ export class Video extends RawDocument { + readonly type = 'video' as const + readonly attr: | tl.RawDocumentAttributeVideo | tl.RawDocumentAttributeImageSize diff --git a/packages/client/src/types/media/voice.ts b/packages/client/src/types/media/voice.ts index 1163b0c5..d377b84b 100644 --- a/packages/client/src/types/media/voice.ts +++ b/packages/client/src/types/media/voice.ts @@ -8,6 +8,8 @@ import { tdFileId } from '@mtcute/file-id' * An voice note. */ export class Voice extends RawDocument { + readonly type = 'voice' as const + readonly doc: tl.RawDocument readonly attr: tl.RawDocumentAttributeAudio diff --git a/packages/client/src/types/media/web-page.ts b/packages/client/src/types/media/web-page.ts index dd017a3e..c4510211 100644 --- a/packages/client/src/types/media/web-page.ts +++ b/packages/client/src/types/media/web-page.ts @@ -17,6 +17,8 @@ import { MtCuteArgumentError } from '../errors' * of my own observations and experiments. */ export class WebPage { + readonly type = 'web_page' as const + readonly client: TelegramClient readonly raw: tl.RawWebPage @@ -71,7 +73,7 @@ export class WebPage { * * `unknown` is returned if no type is returned in the TL object. */ - get type(): string { + get previewType(): string { return this.raw.type || 'unknown' } diff --git a/packages/client/src/types/messages/message-media.ts b/packages/client/src/types/messages/message-media.ts index b5d664a9..2e0f9673 100644 --- a/packages/client/src/types/messages/message-media.ts +++ b/packages/client/src/types/messages/message-media.ts @@ -54,7 +54,7 @@ export function _messageMediaFromTl( return new Contact(m) case 'messageMediaDocument': if (!(m.document?._ === 'document')) return null - return parseDocument(this.client, m.document) + return parseDocument(this.client, m.document) as MessageMedia case 'messageMediaGeo': if (!(m.geo._ === 'geoPoint')) return null return new Location(this.client, m.geo) diff --git a/packages/dispatcher/src/filters.ts b/packages/dispatcher/src/filters.ts index 3c924cd9..03406669 100644 --- a/packages/dispatcher/src/filters.ts +++ b/packages/dispatcher/src/filters.ts @@ -21,7 +21,7 @@ import { Invoice, Game, WebPage, - MessageAction + MessageAction, RawLocation, } from '@mtcute/client' import { MaybeArray } from '@mtcute/core' import { ChatMemberUpdate } from './updates' @@ -52,7 +52,7 @@ import { UserTypingUpdate } from './updates/user-typing-update' * Example without type mod: * ```typescript * - * const hasPhoto: UpdateFilter = msg => msg.media instanceof Photo + * const hasPhoto: UpdateFilter = msg => msg.media?.type === 'photo' * * // ..later.. * tg.onNewMessage(hasPhoto, async (msg) => { @@ -68,7 +68,7 @@ import { UserTypingUpdate } from './updates/user-typing-update' * Example with type mod: * ```typescript * - * const hasPhoto: UpdateFilter = msg => msg.media instanceof Photo + * const hasPhoto: UpdateFilter = msg => msg.media?.type === 'photo' * * // ..later.. * tg.onNewMessage(hasPhoto, async (msg) => { @@ -87,7 +87,7 @@ import { UserTypingUpdate } from './updates/user-typing-update' * > Bad example: * > ```typescript * > // we check for `Photo`, but type contains `Audio`. this will be a problem! - * > const hasPhoto: UpdateFilter = msg => msg.media instanceof Photo + * > const hasPhoto: UpdateFilter = msg => msg.media?.type === 'photo' * > * > // ..later.. * > tg.onNewMessage(hasPhoto, async (msg) => { @@ -595,19 +595,19 @@ export namespace filters { * Filter messages containing a photo */ export const photo: UpdateFilter = (msg) => - msg.media?.constructor === Photo + msg.media?.type === 'photo' /** * Filter messages containing a dice */ export const dice: UpdateFilter = (msg) => - msg.media?.constructor === Dice + msg.media?.type === 'dice' /** * Filter messages containing a contact */ export const contact: UpdateFilter = (msg) => - msg.media?.constructor === Contact + msg.media?.type === 'contact' /** * Filter messages containing a document @@ -615,7 +615,7 @@ export namespace filters { * This will also match media like audio, video, voice * that also use Documents */ - export const rawDocument: UpdateFilter = ( + export const anyDocument: UpdateFilter = ( msg ) => msg.media instanceof RawDocument @@ -625,33 +625,33 @@ export namespace filters { * This will not match media like audio, video, voice */ export const document: UpdateFilter = (msg) => - msg.media?.constructor === Document + msg.media?.type === 'document' /** * Filter messages containing an audio file */ export const audio: UpdateFilter = (msg) => - msg.media?.constructor === Audio + msg.media?.type === 'audio' /** * Filter messages containing a voice note */ export const voice: UpdateFilter = (msg) => - msg.media?.constructor === Voice + msg.media?.type === 'voice' /** * Filter messages containing a sticker */ export const sticker: UpdateFilter = (msg) => - msg.media?.constructor === Sticker + msg.media?.type === 'sticker' /** * Filter messages containing a video. * * This includes videos, round messages and animations */ - export const rawVideo: UpdateFilter = (msg) => - msg.media?.constructor === Video + export const anyVideo: UpdateFilter = (msg) => + msg.media?.type === 'video' /** * Filter messages containing a simple video. @@ -670,7 +670,7 @@ export namespace filters { > } > = (msg) => - msg.media?.constructor === Video && + msg.media?.type === 'video' && !msg.media.isAnimation && !msg.media.isRound @@ -692,7 +692,7 @@ export namespace filters { > } > = (msg) => - msg.media?.constructor === Video && + msg.media?.type === 'video' && msg.media.isAnimation && !msg.media.isRound @@ -711,17 +711,23 @@ export namespace filters { > } > = (msg) => - msg.media?.constructor === Video && + msg.media?.type === 'video' && !msg.media.isAnimation && msg.media.isRound /** - * Filter messages containing a location. - * - * This includes live locations + * Filter messages containing any location (live or static). */ - export const location: UpdateFilter = (msg) => - msg.media instanceof Location + export const anyLocation: UpdateFilter = (msg) => + msg.media instanceof RawLocation + + /** + * Filter messages containing a static (non-live) location. + */ + export const location: UpdateFilter< + Message, + { media: LiveLocation } + > = (msg) => msg.media?.type === 'location' /** * Filter messages containing a live location. @@ -729,37 +735,37 @@ export namespace filters { export const liveLocation: UpdateFilter< Message, { media: LiveLocation } - > = (msg) => msg.media?.constructor === LiveLocation + > = (msg) => msg.media?.type === 'live_location' /** * Filter messages containing a game. */ export const game: UpdateFilter = (msg) => - msg.media?.constructor === Game + msg.media?.type === 'game' /** * Filter messages containing a webpage preview. */ export const webpage: UpdateFilter = (msg) => - msg.media?.constructor === WebPage + msg.media?.type === 'web_page' /** * Filter messages containing a venue. */ export const venue: UpdateFilter = (msg) => - msg.media?.constructor === Venue + msg.media?.type === 'venue' /** * Filter messages containing a poll. */ export const poll: UpdateFilter = (msg) => - msg.media?.constructor === Poll + msg.media?.type === 'poll' /** * Filter messages containing an invoice. */ export const invoice: UpdateFilter = (msg) => - msg.media?.constructor === Invoice + msg.media?.type === 'invoice' /** * Filter objects that match a given regular expression