feat(core): support paid media
This commit is contained in:
parent
55f3e535c2
commit
323bdb2ad9
10 changed files with 182 additions and 46 deletions
|
@ -221,6 +221,22 @@ export async function _normalizeInputMedia(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media.type === 'paid') {
|
||||||
|
let medias: tl.TypeInputMedia[]
|
||||||
|
|
||||||
|
if (Array.isArray(media.media)) {
|
||||||
|
medias = await Promise.all(media.media.map((m) => _normalizeInputMedia(client, m, params)))
|
||||||
|
} else {
|
||||||
|
medias = [await _normalizeInputMedia(client, media.media, params)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_: 'inputMediaPaidMedia',
|
||||||
|
starsAmount: Long.isLong(media.starsAmount) ? media.starsAmount : Long.fromNumber(media.starsAmount),
|
||||||
|
extendedMedia: medias,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let inputFile: tl.TypeInputFile | undefined = undefined
|
let inputFile: tl.TypeInputFile | undefined = undefined
|
||||||
let thumb: tl.TypeInputFile | undefined = undefined
|
let thumb: tl.TypeInputFile | undefined = undefined
|
||||||
let mime = 'application/octet-stream'
|
let mime = 'application/octet-stream'
|
||||||
|
|
|
@ -50,6 +50,7 @@ export async function uploadMedia(
|
||||||
case 'inputMediaInvoice':
|
case 'inputMediaInvoice':
|
||||||
case 'inputMediaPoll':
|
case 'inputMediaPoll':
|
||||||
case 'inputMediaDice':
|
case 'inputMediaDice':
|
||||||
|
case 'inputMediaPaidMedia':
|
||||||
throw new MtArgumentError("This media can't be uploaded")
|
throw new MtArgumentError("This media can't be uploaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,8 +70,16 @@ export async function sendMedia(
|
||||||
file: media,
|
file: media,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const { peer, replyTo, scheduleDate, chainId, quickReplyShortcut } = await _processCommonSendParameters(
|
||||||
|
client,
|
||||||
|
chatId,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
const inputMedia = await _normalizeInputMedia(client, media, params)
|
const inputMedia = await _normalizeInputMedia(client, media, {
|
||||||
|
progressCallback: params.progressCallback,
|
||||||
|
uploadPeer: peer,
|
||||||
|
})
|
||||||
|
|
||||||
const [message, entities] = await _normalizeInputText(
|
const [message, entities] = await _normalizeInputText(
|
||||||
client,
|
client,
|
||||||
|
@ -81,11 +89,6 @@ export async function sendMedia(
|
||||||
)
|
)
|
||||||
|
|
||||||
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
||||||
const { peer, replyTo, scheduleDate, chainId, quickReplyShortcut } = await _processCommonSendParameters(
|
|
||||||
client,
|
|
||||||
chatId,
|
|
||||||
params,
|
|
||||||
)
|
|
||||||
|
|
||||||
const randomId = randomLong()
|
const randomId = randomLong()
|
||||||
const res = await _maybeInvokeWithBusinessConnection(
|
const res = await _maybeInvokeWithBusinessConnection(
|
||||||
|
|
42
packages/core/src/highlevel/types/media/extended-media.ts
Normal file
42
packages/core/src/highlevel/types/media/extended-media.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { makeInspectable } from '../../utils/inspectable.js'
|
||||||
|
import { memoizeGetters } from '../../utils/memoize.js'
|
||||||
|
import { Thumbnail } from './thumbnail.js'
|
||||||
|
|
||||||
|
export class ExtendedMediaPreview {
|
||||||
|
constructor(public readonly raw: tl.RawMessageExtendedMediaPreview) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Width of the preview, in pixels (if available, else 0)
|
||||||
|
*/
|
||||||
|
get width(): number {
|
||||||
|
return this.raw.w ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Height of the preview, in pixels (if available, else 0)
|
||||||
|
*/
|
||||||
|
get height(): number {
|
||||||
|
return this.raw.h ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get thumbnail(): Thumbnail | null {
|
||||||
|
if (!this.raw.thumb) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Thumbnail(this.raw, this.raw.thumb)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a video, the duration of the video,
|
||||||
|
* in seconds (if available, else 0)
|
||||||
|
*/
|
||||||
|
get videoDuration(): number {
|
||||||
|
return this.raw.videoDuration ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memoizeGetters(ExtendedMediaPreview, ['thumbnail'])
|
||||||
|
makeInspectable(ExtendedMediaPreview)
|
|
@ -2,10 +2,12 @@ export * from './audio.js'
|
||||||
export * from './contact.js'
|
export * from './contact.js'
|
||||||
export * from './dice.js'
|
export * from './dice.js'
|
||||||
export * from './document.js'
|
export * from './document.js'
|
||||||
|
export * from './extended-media.js'
|
||||||
export * from './game.js'
|
export * from './game.js'
|
||||||
export * from './input-media/index.js'
|
export * from './input-media/index.js'
|
||||||
export * from './invoice.js'
|
export * from './invoice.js'
|
||||||
export * from './location.js'
|
export * from './location.js'
|
||||||
|
export * from './paid-media.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'
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
InputMediaGeoLive,
|
InputMediaGeoLive,
|
||||||
InputMediaInvoice,
|
InputMediaInvoice,
|
||||||
InputMediaLike,
|
InputMediaLike,
|
||||||
|
InputMediaPaidMedia,
|
||||||
InputMediaPhoto,
|
InputMediaPhoto,
|
||||||
InputMediaPoll,
|
InputMediaPoll,
|
||||||
InputMediaQuiz,
|
InputMediaQuiz,
|
||||||
|
@ -280,6 +281,13 @@ export function webpage(url: string, params: OmitTypeAndFile<InputMediaWebpage,
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function paid(params: OmitTypeAndFile<InputMediaPaidMedia>): InputMediaPaidMedia {
|
||||||
|
const ret = params as tl.Mutable<InputMediaPaidMedia>
|
||||||
|
ret.type = 'paid'
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a document to be sent, which subtype
|
* Create a document to be sent, which subtype
|
||||||
* is inferred automatically by file contents.
|
* is inferred automatically by file contents.
|
||||||
|
|
|
@ -578,6 +578,20 @@ export interface InputMediaWebpage extends CaptionMixin {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InputMediaPaidMedia extends CaptionMixin {
|
||||||
|
type: 'paid'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media to be sent
|
||||||
|
*/
|
||||||
|
media: MaybeArray<InputMediaLike>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of stars that should be paid for the media
|
||||||
|
*/
|
||||||
|
starsAmount: number | tl.Long
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input media that can be sent somewhere.
|
* Input media that can be sent somewhere.
|
||||||
*
|
*
|
||||||
|
@ -606,4 +620,5 @@ export type InputMediaLike =
|
||||||
| InputMediaQuiz
|
| InputMediaQuiz
|
||||||
| InputMediaStory
|
| InputMediaStory
|
||||||
| InputMediaWebpage
|
| InputMediaWebpage
|
||||||
|
| InputMediaPaidMedia
|
||||||
| tl.TypeInputMedia
|
| tl.TypeInputMedia
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { makeInspectable } from '../../utils/index.js'
|
||||||
import { memoizeGetters } from '../../utils/memoize.js'
|
import { memoizeGetters } from '../../utils/memoize.js'
|
||||||
import { WebDocument } from '../files/web-document.js'
|
import { WebDocument } from '../files/web-document.js'
|
||||||
import type { MessageMedia } from '../messages/message-media.js'
|
import type { MessageMedia } from '../messages/message-media.js'
|
||||||
import { Thumbnail } from './thumbnail.js'
|
import { ExtendedMediaPreview } from './extended-media.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information about invoice's extended media.
|
* Information about invoice's extended media.
|
||||||
|
@ -15,43 +15,6 @@ import { Thumbnail } from './thumbnail.js'
|
||||||
*/
|
*/
|
||||||
export type InvoiceExtendedMediaState = 'none' | 'preview' | 'full'
|
export type InvoiceExtendedMediaState = 'none' | 'preview' | 'full'
|
||||||
|
|
||||||
export class InvoiceExtendedMediaPreview {
|
|
||||||
constructor(public readonly raw: tl.RawMessageExtendedMediaPreview) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Width of the preview, in pixels (if available, else 0)
|
|
||||||
*/
|
|
||||||
get width(): number {
|
|
||||||
return this.raw.w ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Height of the preview, in pixels (if available, else 0)
|
|
||||||
*/
|
|
||||||
get height(): number {
|
|
||||||
return this.raw.h ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
get thumbnail(): Thumbnail | null {
|
|
||||||
if (!this.raw.thumb) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Thumbnail(this.raw, this.raw.thumb)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is a video, the duration of the video,
|
|
||||||
* in seconds (if available, else 0)
|
|
||||||
*/
|
|
||||||
get videoDuration(): number {
|
|
||||||
return this.raw.videoDuration ?? 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memoizeGetters(InvoiceExtendedMediaPreview, ['thumbnail'])
|
|
||||||
makeInspectable(InvoiceExtendedMediaPreview)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An invoice
|
* An invoice
|
||||||
*/
|
*/
|
||||||
|
@ -152,12 +115,12 @@ export class Invoice {
|
||||||
* Only available if {@link extendedMediaState} is `preview`.
|
* Only available if {@link extendedMediaState} is `preview`.
|
||||||
* Otherwise, throws an error.
|
* Otherwise, throws an error.
|
||||||
*/
|
*/
|
||||||
get extendedMediaPreview(): InvoiceExtendedMediaPreview {
|
get extendedMediaPreview(): ExtendedMediaPreview {
|
||||||
if (this.raw.extendedMedia?._ !== 'messageExtendedMediaPreview') {
|
if (this.raw.extendedMedia?._ !== 'messageExtendedMediaPreview') {
|
||||||
throw new MtArgumentError('No extended media preview available')
|
throw new MtArgumentError('No extended media preview available')
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InvoiceExtendedMediaPreview(this.raw.extendedMedia)
|
return new ExtendedMediaPreview(this.raw.extendedMedia)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
73
packages/core/src/highlevel/types/media/paid-media.ts
Normal file
73
packages/core/src/highlevel/types/media/paid-media.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { makeInspectable } from '../../utils/inspectable.js'
|
||||||
|
import { memoizeGetters } from '../../utils/memoize.js'
|
||||||
|
import { MessageMedia } from '../messages/message-media.js'
|
||||||
|
import { ExtendedMediaPreview } from './extended-media.js'
|
||||||
|
|
||||||
|
export class PaidMedia {
|
||||||
|
readonly type = 'paid' as const
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly raw: tl.RawMessageMediaPaidMedia,
|
||||||
|
private readonly _extendedMedia: MessageMedia[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** Whether this media was paid for */
|
||||||
|
get isPaid(): boolean {
|
||||||
|
return this._extendedMedia !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Price of the media (in Telegram Stars) */
|
||||||
|
get price(): tl.Long {
|
||||||
|
return this.raw.starsAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the available media previews.
|
||||||
|
*
|
||||||
|
* If the media is already paid for, this will return an empty array.
|
||||||
|
*/
|
||||||
|
get previews(): ExtendedMediaPreview[] {
|
||||||
|
const res: ExtendedMediaPreview[] = []
|
||||||
|
|
||||||
|
this.raw.extendedMedia.forEach((m) => {
|
||||||
|
if (m._ !== 'messageExtendedMediaPreview') return
|
||||||
|
|
||||||
|
res.push(new ExtendedMediaPreview(m))
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the available full media.
|
||||||
|
*
|
||||||
|
* If the media is not paid for, this will return an empty array.
|
||||||
|
*/
|
||||||
|
get medias(): MessageMedia[] {
|
||||||
|
return this._extendedMedia
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input media TL object generated from this object,
|
||||||
|
* to be used inside {@link InputMediaLike} and
|
||||||
|
* {@link TelegramClient.sendMedia}.
|
||||||
|
*
|
||||||
|
* Will throw if the media is not paid for.
|
||||||
|
*/
|
||||||
|
get inputMedia(): tl.TypeInputMedia {
|
||||||
|
if (!this.isPaid) {
|
||||||
|
throw new Error('Cannot get input media for non-paid media')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_: 'inputMediaPaidMedia',
|
||||||
|
starsAmount: this.raw.starsAmount,
|
||||||
|
extendedMedia: this._extendedMedia.map((m) => m!.inputMedia),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeInspectable(PaidMedia, undefined, ['inputMedia'])
|
||||||
|
memoizeGetters(PaidMedia, ['previews', 'inputMedia'])
|
|
@ -9,6 +9,7 @@ import { parseDocument } from '../media/document-utils.js'
|
||||||
import { Game } from '../media/game.js'
|
import { Game } from '../media/game.js'
|
||||||
import { Invoice } from '../media/invoice.js'
|
import { Invoice } from '../media/invoice.js'
|
||||||
import { LiveLocation, Location } from '../media/location.js'
|
import { LiveLocation, Location } from '../media/location.js'
|
||||||
|
import { PaidMedia } from '../media/paid-media.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'
|
||||||
|
@ -37,6 +38,7 @@ export type MessageMedia =
|
||||||
| Poll
|
| Poll
|
||||||
| Invoice
|
| Invoice
|
||||||
| MediaStory
|
| MediaStory
|
||||||
|
| PaidMedia
|
||||||
| null
|
| null
|
||||||
export type MessageMediaType = Exclude<MessageMedia, null>['type']
|
export type MessageMediaType = Exclude<MessageMedia, null>['type']
|
||||||
|
|
||||||
|
@ -96,6 +98,17 @@ export function _messageMediaFromTl(peers: PeersIndex | null, m: tl.TypeMessageM
|
||||||
|
|
||||||
return new MediaStory(m, peers)
|
return new MediaStory(m, peers)
|
||||||
}
|
}
|
||||||
|
case 'messageMediaPaidMedia': {
|
||||||
|
const extended: MessageMedia[] = []
|
||||||
|
|
||||||
|
m.extendedMedia.forEach((e) => {
|
||||||
|
if (e._ !== 'messageExtendedMedia') return
|
||||||
|
|
||||||
|
extended.push(_messageMediaFromTl(peers, e.media))
|
||||||
|
})
|
||||||
|
|
||||||
|
return new PaidMedia(m, extended)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue