feat(client): support Story message media

This commit is contained in:
alina 🌸 2023-12-02 19:00:51 +03:00
parent 893a15d111
commit 8b2debb0aa
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
9 changed files with 107 additions and 14 deletions

View file

@ -30,7 +30,18 @@ export async function _normalizeInputMedia(
// thanks to @pacificescape for pointing out messages.uploadMedia method // thanks to @pacificescape for pointing out messages.uploadMedia method
if (tl.isAnyInputMedia(media)) return media if (tl.isAnyInputMedia(media)) {
// make sure the peers in the media are correctly resolved (i.e. mtcute.* ones are replaced with proper ones)
switch (media._) {
case 'inputMediaStory':
return {
...media,
peer: await resolvePeer(client, media.peer),
}
}
return media
}
if (media.type === 'venue') { if (media.type === 'venue') {
return { return {

View file

@ -9,6 +9,7 @@ export * from './location.js'
export * from './photo.js' export * from './photo.js'
export * from './poll.js' export * from './poll.js'
export * from './sticker.js' export * from './sticker.js'
export * from './story.js'
export * from './thumbnail.js' export * from './thumbnail.js'
export * from './venue.js' export * from './venue.js'
export * from './video.js' export * from './video.js'

View file

@ -0,0 +1,61 @@
import { tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/inspectable.js'
import { parsePeer, Peer } from '../peers/peer.js'
import { PeersIndex } from '../peers/peers-index.js'
import { Story } from '../stories/story.js'
/**
* Information about a "forwarded" story in a message,
* internally represented as a message media
*/
export class MediaStory {
readonly type = 'story' as const
constructor(
readonly raw: tl.RawMessageMediaStory,
readonly _peers: PeersIndex,
) {}
/**
* Whether this story was automatically forwarded to you
* because you were mentioned in it
*/
get isMention(): boolean {
return this.raw.viaMention!
}
/**
* Peer who has posted this story
*/
get peer(): Peer {
return parsePeer(this.raw.peer, this._peers)
}
/**
* ID of the story
*/
get storyId(): number {
return this.raw.id
}
/**
* Contents of the story. May not be available, in which case the story
* should be fetched using {@link getStoriesById}
*/
get story(): Story | null {
if (this.raw.story?._ !== 'storyItem') return null
return new Story(this.raw.story, this._peers)
}
get inputMedia(): tl.TypeInputMedia {
return {
_: 'inputMediaStory',
peer: this.peer.inputPeer,
id: this.raw.id,
}
}
}
makeInspectable(MediaStory, undefined, ['inputMedia'])

View file

@ -50,7 +50,7 @@ export class WebPage {
* Officially documented are: * Officially documented are:
* `article, photo, audio, video, document, profile, app`, * `article, photo, audio, video, document, profile, app`,
* but also these are encountered: * but also these are encountered:
* `telegram_user, telegram_bot, telegram_channel, telegram_megagroup`: * `telegram_user, telegram_bot, telegram_channel, telegram_megagroup, telegram_story`:
* *
* - `telegram_*` ones seem to be used for `t.me` links. * - `telegram_*` ones seem to be used for `t.me` links.
* - `article` seems to be used for almost all custom pages with `og:*` tags * - `article` seems to be used for almost all custom pages with `og:*` tags

View file

@ -11,6 +11,7 @@ import { LiveLocation, Location } from '../media/location.js'
import { Photo } from '../media/photo.js' import { Photo } from '../media/photo.js'
import { Poll } from '../media/poll.js' import { Poll } from '../media/poll.js'
import { Sticker } from '../media/sticker.js' import { Sticker } from '../media/sticker.js'
import { MediaStory } from '../media/story.js'
import { Venue } from '../media/venue.js' import { Venue } from '../media/venue.js'
import { Video } from '../media/video.js' import { Video } from '../media/video.js'
import { Voice } from '../media/voice.js' import { Voice } from '../media/voice.js'
@ -34,6 +35,7 @@ export type MessageMedia =
| Venue | Venue
| Poll | Poll
| Invoice | Invoice
| MediaStory
| null | null
export type MessageMediaType = Exclude<MessageMedia, null>['type'] export type MessageMediaType = Exclude<MessageMedia, null>['type']
@ -84,6 +86,15 @@ export function _messageMediaFromTl(peers: PeersIndex | null, m: tl.TypeMessageM
return new Invoice(m, extended) return new Invoice(m, extended)
} }
case 'messageMediaStory': {
if (!peers) {
// should only be possible in extended media
// (and afaik stories can't be there)
throw new MtTypeAssertionError("can't create story without peers index", 'PeersIndex', 'null')
}
return new MediaStory(m, peers)
}
default: default:
return null return null
} }

View file

@ -3,7 +3,7 @@ import { assertTypeIs } from '@mtcute/core/utils.js'
import { makeInspectable } from '../../../utils/index.js' import { makeInspectable } from '../../../utils/index.js'
import { memoizeGetters } from '../../../utils/memoize.js' import { memoizeGetters } from '../../../utils/memoize.js'
import { Location } from '../../media/index.js' import { Location } from '../../media/location.js'
import { StoryInteractiveArea } from './base.js' import { StoryInteractiveArea } from './base.js'
/** /**

View file

@ -3,7 +3,8 @@ import { assertTypeIs } from '@mtcute/core/utils.js'
import { makeInspectable } from '../../../utils/index.js' import { makeInspectable } from '../../../utils/index.js'
import { memoizeGetters } from '../../../utils/memoize.js' import { memoizeGetters } from '../../../utils/memoize.js'
import { Location, VenueSource } from '../../media/index.js' import { Location } from '../../media/location.js'
import { VenueSource } from '../../media/venue.js'
import { StoryInteractiveArea } from './base.js' import { StoryInteractiveArea } from './base.js'
/** /**

View file

@ -2,7 +2,8 @@ import { tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/index.js' import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js' import { memoizeGetters } from '../../utils/memoize.js'
import { PeersIndex, User } from '../peers/index.js' import { PeersIndex } from '../peers/peers-index.js'
import { User } from '../peers/user.js'
import { ReactionCount } from '../reactions/reaction-count.js' import { ReactionCount } from '../reactions/reaction-count.js'
/** /**

View file

@ -2,8 +2,10 @@ import { MtUnsupportedError, tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/index.js' import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js' import { memoizeGetters } from '../../utils/memoize.js'
import { Photo, Video } from '../media/index.js' import { parseDocument } from '../media/document-utils.js'
import { _messageMediaFromTl, MessageEntity } from '../messages/index.js' import { Photo } from '../media/photo.js'
import { Video } from '../media/video.js'
import { MessageEntity } from '../messages/message-entity.js'
import { PeersIndex } from '../peers/index.js' import { PeersIndex } from '../peers/index.js'
import { ReactionEmoji, toReactionEmoji } from '../reactions/index.js' import { ReactionEmoji, toReactionEmoji } from '../reactions/index.js'
import { _storyInteractiveElementFromTl, StoryInteractiveElement } from './interactive/index.js' import { _storyInteractiveElementFromTl, StoryInteractiveElement } from './interactive/index.js'
@ -114,17 +116,22 @@ export class Story {
* Currently, can only be {@link Photo} or {@link Video}. * Currently, can only be {@link Photo} or {@link Video}.
*/ */
get media(): StoryMedia { get media(): StoryMedia {
const media = _messageMediaFromTl(this._peers, this.raw.media) switch (this.raw.media._) {
case 'messageMediaPhoto':
if (this.raw.media.photo?._ !== 'photo') throw new MtUnsupportedError('Unsupported story media type')
switch (media?.type) { return new Photo(this.raw.media.photo, this.raw.media)
case 'photo': case 'messageMediaDocument': {
case 'video': if (this.raw.media.document?._ !== 'document') { throw new MtUnsupportedError('Unsupported story media type') }
return media
default: const doc = parseDocument(this.raw.media.document, this.raw.media)
throw new MtUnsupportedError('Unsupported story media type') if (doc.type === 'video') return doc
} }
} }
throw new MtUnsupportedError('Unsupported story media type')
}
/** /**
* Interactive elements of the story * Interactive elements of the story
*/ */