refactor: use type discrimination for media types in Message

this should result in cleaner code without the need to import everything
This commit is contained in:
teidesu 2021-07-01 22:20:16 +03:00
parent abbebeddf9
commit d0e3ebda80
16 changed files with 72 additions and 33 deletions

View file

@ -8,6 +8,8 @@ import { tdFileId } from '@mtcute/file-id'
* An audio file * An audio file
*/ */
export class Audio extends RawDocument { export class Audio extends RawDocument {
readonly type = 'audio' as const
readonly doc: tl.RawDocument readonly doc: tl.RawDocument
readonly attr: tl.RawDocumentAttributeAudio readonly attr: tl.RawDocumentAttributeAudio

View file

@ -5,6 +5,8 @@ import { tl } from '@mtcute/tl'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
export class Contact { export class Contact {
readonly type = 'contact' as const
readonly obj: tl.RawMessageMediaContact readonly obj: tl.RawMessageMediaContact
constructor(obj: tl.RawMessageMediaContact) { constructor(obj: tl.RawMessageMediaContact) {

View file

@ -5,6 +5,8 @@ import { makeInspectable } from '../utils'
* A dice or another interactive random emoji. * A dice or another interactive random emoji.
*/ */
export class Dice { export class Dice {
readonly type = 'dice' as const
readonly obj: tl.RawMessageMediaDice readonly obj: tl.RawMessageMediaDice
/** /**

View file

@ -169,6 +169,8 @@ export class RawDocument extends FileLocation {
* and only used for documents without any special * and only used for documents without any special
* attributes. * attributes.
*/ */
export class Document extends RawDocument {} export class Document extends RawDocument {
readonly type = 'document' as const
}
makeInspectable(Document, ['fileSize', 'dcId'], ['inputMedia', 'inputDocument']) makeInspectable(Document, ['fileSize', 'dcId'], ['inputMedia', 'inputDocument'])

View file

@ -5,6 +5,8 @@ import { TelegramClient } from '../../client'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
export class Game { export class Game {
readonly type = 'game' as const
readonly game: tl.RawGame readonly game: tl.RawGame
readonly client: TelegramClient readonly client: TelegramClient

View file

@ -8,6 +8,8 @@ import { MtCuteArgumentError } from '../errors'
* An invoice * An invoice
*/ */
export class Invoice { export class Invoice {
readonly type = 'invoice' as const
readonly client: TelegramClient readonly client: TelegramClient
readonly raw: tl.RawMessageMediaInvoice readonly raw: tl.RawMessageMediaInvoice

View file

@ -6,7 +6,7 @@ import { TelegramClient } from '../../client'
/** /**
* A point on the map * A point on the map
*/ */
export class Location { export class RawLocation {
readonly client: TelegramClient readonly client: TelegramClient
readonly geo: tl.RawGeoPoint 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 readonly live: tl.RawMessageMediaGeoLive
constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) { constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) {

View file

@ -9,6 +9,8 @@ import { makeInspectable } from '../utils'
* A photo * A photo
*/ */
export class Photo extends FileLocation { export class Photo extends FileLocation {
readonly type: 'photo'
/** Raw TL object */ /** Raw TL object */
readonly raw: tl.RawPhoto readonly raw: tl.RawPhoto
@ -78,6 +80,7 @@ export class Photo extends FileLocation {
this.raw = raw this.raw = raw
this.width = width this.width = width
this.height = height this.height = height
this.type = 'photo'
} }
/** Date this photo was sent */ /** Date this photo was sent */

View file

@ -38,6 +38,8 @@ export namespace Poll {
} }
export class Poll { export class Poll {
readonly type = 'poll' as const
readonly client: TelegramClient readonly client: TelegramClient
readonly raw: tl.TypePoll readonly raw: tl.TypePoll
readonly results?: tl.TypePollResults readonly results?: tl.TypePollResults

View file

@ -40,6 +40,8 @@ const MASK_POS = ['forehead', 'eyes', 'mouth', 'chin'] as const
* A sticker * A sticker
*/ */
export class Sticker extends RawDocument { export class Sticker extends RawDocument {
readonly type = 'sticker' as const
readonly attr: tl.RawDocumentAttributeSticker readonly attr: tl.RawDocumentAttributeSticker
readonly attrSize?: tl.RawDocumentAttributeImageSize readonly attrSize?: tl.RawDocumentAttributeImageSize

View file

@ -29,6 +29,8 @@ export namespace Venue {
} }
export class Venue { export class Venue {
readonly type = 'venue' as const
readonly client: TelegramClient readonly client: TelegramClient
readonly raw: tl.RawMessageMediaVenue readonly raw: tl.RawMessageMediaVenue

View file

@ -10,6 +10,8 @@ import { tdFileId } from '@mtcute/file-id'
* **Note:** Legacy GIF animations are also wrapped with this class. * **Note:** Legacy GIF animations are also wrapped with this class.
*/ */
export class Video extends RawDocument { export class Video extends RawDocument {
readonly type = 'video' as const
readonly attr: readonly attr:
| tl.RawDocumentAttributeVideo | tl.RawDocumentAttributeVideo
| tl.RawDocumentAttributeImageSize | tl.RawDocumentAttributeImageSize

View file

@ -8,6 +8,8 @@ import { tdFileId } from '@mtcute/file-id'
* An voice note. * An voice note.
*/ */
export class Voice extends RawDocument { export class Voice extends RawDocument {
readonly type = 'voice' as const
readonly doc: tl.RawDocument readonly doc: tl.RawDocument
readonly attr: tl.RawDocumentAttributeAudio readonly attr: tl.RawDocumentAttributeAudio

View file

@ -17,6 +17,8 @@ import { MtCuteArgumentError } from '../errors'
* of my own observations and experiments. * of my own observations and experiments.
*/ */
export class WebPage { export class WebPage {
readonly type = 'web_page' as const
readonly client: TelegramClient readonly client: TelegramClient
readonly raw: tl.RawWebPage readonly raw: tl.RawWebPage
@ -71,7 +73,7 @@ export class WebPage {
* *
* `unknown` is returned if no type is returned in the TL object. * `unknown` is returned if no type is returned in the TL object.
*/ */
get type(): string { get previewType(): string {
return this.raw.type || 'unknown' return this.raw.type || 'unknown'
} }

View file

@ -54,7 +54,7 @@ export function _messageMediaFromTl(
return new Contact(m) return new Contact(m)
case 'messageMediaDocument': case 'messageMediaDocument':
if (!(m.document?._ === 'document')) return null if (!(m.document?._ === 'document')) return null
return parseDocument(this.client, m.document) return parseDocument(this.client, m.document) as MessageMedia
case 'messageMediaGeo': case 'messageMediaGeo':
if (!(m.geo._ === 'geoPoint')) return null if (!(m.geo._ === 'geoPoint')) return null
return new Location(this.client, m.geo) return new Location(this.client, m.geo)

View file

@ -21,7 +21,7 @@ import {
Invoice, Invoice,
Game, Game,
WebPage, WebPage,
MessageAction MessageAction, RawLocation,
} from '@mtcute/client' } from '@mtcute/client'
import { MaybeArray } from '@mtcute/core' import { MaybeArray } from '@mtcute/core'
import { ChatMemberUpdate } from './updates' import { ChatMemberUpdate } from './updates'
@ -52,7 +52,7 @@ import { UserTypingUpdate } from './updates/user-typing-update'
* Example without type mod: * Example without type mod:
* ```typescript * ```typescript
* *
* const hasPhoto: UpdateFilter<Message> = msg => msg.media instanceof Photo * const hasPhoto: UpdateFilter<Message> = msg => msg.media?.type === 'photo'
* *
* // ..later.. * // ..later..
* tg.onNewMessage(hasPhoto, async (msg) => { * tg.onNewMessage(hasPhoto, async (msg) => {
@ -68,7 +68,7 @@ import { UserTypingUpdate } from './updates/user-typing-update'
* Example with type mod: * Example with type mod:
* ```typescript * ```typescript
* *
* const hasPhoto: UpdateFilter<Message, { media: Photo }> = msg => msg.media instanceof Photo * const hasPhoto: UpdateFilter<Message, { media: Photo }> = msg => msg.media?.type === 'photo'
* *
* // ..later.. * // ..later..
* tg.onNewMessage(hasPhoto, async (msg) => { * tg.onNewMessage(hasPhoto, async (msg) => {
@ -87,7 +87,7 @@ import { UserTypingUpdate } from './updates/user-typing-update'
* > Bad example: * > Bad example:
* > ```typescript * > ```typescript
* > // we check for `Photo`, but type contains `Audio`. this will be a problem! * > // we check for `Photo`, but type contains `Audio`. this will be a problem!
* > const hasPhoto: UpdateFilter<Message, { media: Audio }> = msg => msg.media instanceof Photo * > const hasPhoto: UpdateFilter<Message, { media: Audio }> = msg => msg.media?.type === 'photo'
* > * >
* > // ..later.. * > // ..later..
* > tg.onNewMessage(hasPhoto, async (msg) => { * > tg.onNewMessage(hasPhoto, async (msg) => {
@ -595,19 +595,19 @@ export namespace filters {
* Filter messages containing a photo * Filter messages containing a photo
*/ */
export const photo: UpdateFilter<Message, { media: Photo }> = (msg) => export const photo: UpdateFilter<Message, { media: Photo }> = (msg) =>
msg.media?.constructor === Photo msg.media?.type === 'photo'
/** /**
* Filter messages containing a dice * Filter messages containing a dice
*/ */
export const dice: UpdateFilter<Message, { media: Dice }> = (msg) => export const dice: UpdateFilter<Message, { media: Dice }> = (msg) =>
msg.media?.constructor === Dice msg.media?.type === 'dice'
/** /**
* Filter messages containing a contact * Filter messages containing a contact
*/ */
export const contact: UpdateFilter<Message, { media: Contact }> = (msg) => export const contact: UpdateFilter<Message, { media: Contact }> = (msg) =>
msg.media?.constructor === Contact msg.media?.type === 'contact'
/** /**
* Filter messages containing a document * Filter messages containing a document
@ -615,7 +615,7 @@ export namespace filters {
* This will also match media like audio, video, voice * This will also match media like audio, video, voice
* that also use Documents * that also use Documents
*/ */
export const rawDocument: UpdateFilter<Message, { media: RawDocument }> = ( export const anyDocument: UpdateFilter<Message, { media: RawDocument }> = (
msg msg
) => msg.media instanceof RawDocument ) => msg.media instanceof RawDocument
@ -625,33 +625,33 @@ export namespace filters {
* This will not match media like audio, video, voice * This will not match media like audio, video, voice
*/ */
export const document: UpdateFilter<Message, { media: Document }> = (msg) => export const document: UpdateFilter<Message, { media: Document }> = (msg) =>
msg.media?.constructor === Document msg.media?.type === 'document'
/** /**
* Filter messages containing an audio file * Filter messages containing an audio file
*/ */
export const audio: UpdateFilter<Message, { media: Audio }> = (msg) => export const audio: UpdateFilter<Message, { media: Audio }> = (msg) =>
msg.media?.constructor === Audio msg.media?.type === 'audio'
/** /**
* Filter messages containing a voice note * Filter messages containing a voice note
*/ */
export const voice: UpdateFilter<Message, { media: Voice }> = (msg) => export const voice: UpdateFilter<Message, { media: Voice }> = (msg) =>
msg.media?.constructor === Voice msg.media?.type === 'voice'
/** /**
* Filter messages containing a sticker * Filter messages containing a sticker
*/ */
export const sticker: UpdateFilter<Message, { media: Sticker }> = (msg) => export const sticker: UpdateFilter<Message, { media: Sticker }> = (msg) =>
msg.media?.constructor === Sticker msg.media?.type === 'sticker'
/** /**
* Filter messages containing a video. * Filter messages containing a video.
* *
* This includes videos, round messages and animations * This includes videos, round messages and animations
*/ */
export const rawVideo: UpdateFilter<Message, { media: Video }> = (msg) => export const anyVideo: UpdateFilter<Message, { media: Video }> = (msg) =>
msg.media?.constructor === Video msg.media?.type === 'video'
/** /**
* Filter messages containing a simple video. * Filter messages containing a simple video.
@ -670,7 +670,7 @@ export namespace filters {
> >
} }
> = (msg) => > = (msg) =>
msg.media?.constructor === Video && msg.media?.type === 'video' &&
!msg.media.isAnimation && !msg.media.isAnimation &&
!msg.media.isRound !msg.media.isRound
@ -692,7 +692,7 @@ export namespace filters {
> >
} }
> = (msg) => > = (msg) =>
msg.media?.constructor === Video && msg.media?.type === 'video' &&
msg.media.isAnimation && msg.media.isAnimation &&
!msg.media.isRound !msg.media.isRound
@ -711,17 +711,23 @@ export namespace filters {
> >
} }
> = (msg) => > = (msg) =>
msg.media?.constructor === Video && msg.media?.type === 'video' &&
!msg.media.isAnimation && !msg.media.isAnimation &&
msg.media.isRound msg.media.isRound
/** /**
* Filter messages containing a location. * Filter messages containing any location (live or static).
*
* This includes live locations
*/ */
export const location: UpdateFilter<Message, { media: Location }> = (msg) => export const anyLocation: UpdateFilter<Message, { media: Location }> = (msg) =>
msg.media instanceof Location 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. * Filter messages containing a live location.
@ -729,37 +735,37 @@ export namespace filters {
export const liveLocation: UpdateFilter< export const liveLocation: UpdateFilter<
Message, Message,
{ media: LiveLocation } { media: LiveLocation }
> = (msg) => msg.media?.constructor === LiveLocation > = (msg) => msg.media?.type === 'live_location'
/** /**
* Filter messages containing a game. * Filter messages containing a game.
*/ */
export const game: UpdateFilter<Message, { media: Game }> = (msg) => export const game: UpdateFilter<Message, { media: Game }> = (msg) =>
msg.media?.constructor === Game msg.media?.type === 'game'
/** /**
* Filter messages containing a webpage preview. * Filter messages containing a webpage preview.
*/ */
export const webpage: UpdateFilter<Message, { media: WebPage }> = (msg) => export const webpage: UpdateFilter<Message, { media: WebPage }> = (msg) =>
msg.media?.constructor === WebPage msg.media?.type === 'web_page'
/** /**
* Filter messages containing a venue. * Filter messages containing a venue.
*/ */
export const venue: UpdateFilter<Message, { media: Venue }> = (msg) => export const venue: UpdateFilter<Message, { media: Venue }> = (msg) =>
msg.media?.constructor === Venue msg.media?.type === 'venue'
/** /**
* Filter messages containing a poll. * Filter messages containing a poll.
*/ */
export const poll: UpdateFilter<Message, { media: Poll }> = (msg) => export const poll: UpdateFilter<Message, { media: Poll }> = (msg) =>
msg.media?.constructor === Poll msg.media?.type === 'poll'
/** /**
* Filter messages containing an invoice. * Filter messages containing an invoice.
*/ */
export const invoice: UpdateFilter<Message, { media: Invoice }> = (msg) => export const invoice: UpdateFilter<Message, { media: Invoice }> = (msg) =>
msg.media?.constructor === Invoice msg.media?.type === 'invoice'
/** /**
* Filter objects that match a given regular expression * Filter objects that match a given regular expression