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
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') {
return {

View file

@ -9,6 +9,7 @@ export * from './location.js'
export * from './photo.js'
export * from './poll.js'
export * from './sticker.js'
export * from './story.js'
export * from './thumbnail.js'
export * from './venue.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:
* `article, photo, audio, video, document, profile, app`,
* 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.
* - `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 { Poll } from '../media/poll.js'
import { Sticker } from '../media/sticker.js'
import { MediaStory } from '../media/story.js'
import { Venue } from '../media/venue.js'
import { Video } from '../media/video.js'
import { Voice } from '../media/voice.js'
@ -34,6 +35,7 @@ export type MessageMedia =
| Venue
| Poll
| Invoice
| MediaStory
| null
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)
}
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:
return null
}

View file

@ -3,7 +3,7 @@ import { assertTypeIs } from '@mtcute/core/utils.js'
import { makeInspectable } from '../../../utils/index.js'
import { memoizeGetters } from '../../../utils/memoize.js'
import { Location } from '../../media/index.js'
import { Location } from '../../media/location.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 { 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'
/**

View file

@ -2,7 +2,8 @@ import { tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/index.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'
/**

View file

@ -2,8 +2,10 @@ import { MtUnsupportedError, tl } from '@mtcute/core'
import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js'
import { Photo, Video } from '../media/index.js'
import { _messageMediaFromTl, MessageEntity } from '../messages/index.js'
import { parseDocument } from '../media/document-utils.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 { ReactionEmoji, toReactionEmoji } from '../reactions/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}.
*/
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) {
case 'photo':
case 'video':
return media
default:
throw new MtUnsupportedError('Unsupported story media type')
return new Photo(this.raw.media.photo, this.raw.media)
case 'messageMediaDocument': {
if (this.raw.media.document?._ !== 'document') { throw new MtUnsupportedError('Unsupported story media type') }
const doc = parseDocument(this.raw.media.document, this.raw.media)
if (doc.type === 'video') return doc
}
}
throw new MtUnsupportedError('Unsupported story media type')
}
/**
* Interactive elements of the story
*/