From 8b2debb0aabefb0ed5fd7f9780c8923197014036 Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Sat, 2 Dec 2023 19:00:51 +0300 Subject: [PATCH] feat(client): support Story message media --- .../methods/files/normalize-input-media.ts | 13 +++- packages/client/src/types/media/index.ts | 1 + packages/client/src/types/media/story.ts | 61 +++++++++++++++++++ packages/client/src/types/media/web-page.ts | 2 +- .../src/types/messages/message-media.ts | 11 ++++ .../src/types/stories/interactive/location.ts | 2 +- .../src/types/stories/interactive/venue.ts | 3 +- .../src/types/stories/story-interactions.ts | 3 +- packages/client/src/types/stories/story.ts | 25 +++++--- 9 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 packages/client/src/types/media/story.ts diff --git a/packages/client/src/methods/files/normalize-input-media.ts b/packages/client/src/methods/files/normalize-input-media.ts index 7b3c9078..93747f42 100644 --- a/packages/client/src/methods/files/normalize-input-media.ts +++ b/packages/client/src/methods/files/normalize-input-media.ts @@ -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 { diff --git a/packages/client/src/types/media/index.ts b/packages/client/src/types/media/index.ts index a5bb7904..addee4ba 100644 --- a/packages/client/src/types/media/index.ts +++ b/packages/client/src/types/media/index.ts @@ -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' diff --git a/packages/client/src/types/media/story.ts b/packages/client/src/types/media/story.ts new file mode 100644 index 00000000..57749d0e --- /dev/null +++ b/packages/client/src/types/media/story.ts @@ -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']) diff --git a/packages/client/src/types/media/web-page.ts b/packages/client/src/types/media/web-page.ts index ce3d4e24..5ea177f9 100644 --- a/packages/client/src/types/media/web-page.ts +++ b/packages/client/src/types/media/web-page.ts @@ -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 diff --git a/packages/client/src/types/messages/message-media.ts b/packages/client/src/types/messages/message-media.ts index d51e3fb9..a07ee094 100644 --- a/packages/client/src/types/messages/message-media.ts +++ b/packages/client/src/types/messages/message-media.ts @@ -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['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 } diff --git a/packages/client/src/types/stories/interactive/location.ts b/packages/client/src/types/stories/interactive/location.ts index c826d2fa..ad846dea 100644 --- a/packages/client/src/types/stories/interactive/location.ts +++ b/packages/client/src/types/stories/interactive/location.ts @@ -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' /** diff --git a/packages/client/src/types/stories/interactive/venue.ts b/packages/client/src/types/stories/interactive/venue.ts index aeaaa5c3..0bc8aebb 100644 --- a/packages/client/src/types/stories/interactive/venue.ts +++ b/packages/client/src/types/stories/interactive/venue.ts @@ -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' /** diff --git a/packages/client/src/types/stories/story-interactions.ts b/packages/client/src/types/stories/story-interactions.ts index 62dfc0a8..cf1fcee3 100644 --- a/packages/client/src/types/stories/story-interactions.ts +++ b/packages/client/src/types/stories/story-interactions.ts @@ -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' /** diff --git a/packages/client/src/types/stories/story.ts b/packages/client/src/types/stories/story.ts index 6aba4dce..a715a333 100644 --- a/packages/client/src/types/stories/story.ts +++ b/packages/client/src/types/stories/story.ts @@ -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,15 +116,20 @@ 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') } /**