feat(core): support paid media

This commit is contained in:
alina 🌸 2024-06-30 22:58:48 +03:00
parent 55f3e535c2
commit 323bdb2ad9
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
10 changed files with 182 additions and 46 deletions

View file

@ -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 thumb: tl.TypeInputFile | undefined = undefined
let mime = 'application/octet-stream'

View file

@ -50,6 +50,7 @@ export async function uploadMedia(
case 'inputMediaInvoice':
case 'inputMediaPoll':
case 'inputMediaDice':
case 'inputMediaPaidMedia':
throw new MtArgumentError("This media can't be uploaded")
}

View file

@ -70,8 +70,16 @@ export async function sendMedia(
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(
client,
@ -81,11 +89,6 @@ export async function sendMedia(
)
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
const { peer, replyTo, scheduleDate, chainId, quickReplyShortcut } = await _processCommonSendParameters(
client,
chatId,
params,
)
const randomId = randomLong()
const res = await _maybeInvokeWithBusinessConnection(

View 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)

View file

@ -2,10 +2,12 @@ export * from './audio.js'
export * from './contact.js'
export * from './dice.js'
export * from './document.js'
export * from './extended-media.js'
export * from './game.js'
export * from './input-media/index.js'
export * from './invoice.js'
export * from './location.js'
export * from './paid-media.js'
export * from './photo.js'
export * from './poll.js'
export * from './sticker.js'

View file

@ -13,6 +13,7 @@ import {
InputMediaGeoLive,
InputMediaInvoice,
InputMediaLike,
InputMediaPaidMedia,
InputMediaPhoto,
InputMediaPoll,
InputMediaQuiz,
@ -280,6 +281,13 @@ export function webpage(url: string, params: OmitTypeAndFile<InputMediaWebpage,
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
* is inferred automatically by file contents.

View file

@ -578,6 +578,20 @@ export interface InputMediaWebpage extends CaptionMixin {
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.
*
@ -606,4 +620,5 @@ export type InputMediaLike =
| InputMediaQuiz
| InputMediaStory
| InputMediaWebpage
| InputMediaPaidMedia
| tl.TypeInputMedia

View file

@ -5,7 +5,7 @@ import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js'
import { WebDocument } from '../files/web-document.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.
@ -15,43 +15,6 @@ import { Thumbnail } from './thumbnail.js'
*/
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
*/
@ -152,12 +115,12 @@ export class Invoice {
* Only available if {@link extendedMediaState} is `preview`.
* Otherwise, throws an error.
*/
get extendedMediaPreview(): InvoiceExtendedMediaPreview {
get extendedMediaPreview(): ExtendedMediaPreview {
if (this.raw.extendedMedia?._ !== 'messageExtendedMediaPreview') {
throw new MtArgumentError('No extended media preview available')
}
return new InvoiceExtendedMediaPreview(this.raw.extendedMedia)
return new ExtendedMediaPreview(this.raw.extendedMedia)
}
/**

View 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'])

View file

@ -9,6 +9,7 @@ import { parseDocument } from '../media/document-utils.js'
import { Game } from '../media/game.js'
import { Invoice } from '../media/invoice.js'
import { LiveLocation, Location } from '../media/location.js'
import { PaidMedia } from '../media/paid-media.js'
import { Photo } from '../media/photo.js'
import { Poll } from '../media/poll.js'
import { Sticker } from '../media/sticker.js'
@ -37,6 +38,7 @@ export type MessageMedia =
| Poll
| Invoice
| MediaStory
| PaidMedia
| null
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)
}
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:
return null
}