feat(client): a lot of changes
- support web documents - support previews for locations - unify media interfaces, merge everything into sendMedia - support invoices, polls, venues (both sending and receiving)
This commit is contained in:
parent
169d95d6ed
commit
6db771e3da
23 changed files with 1319 additions and 518 deletions
|
@ -72,8 +72,6 @@ import { _parseEntities } from './methods/messages/parse-entities'
|
|||
import { pinMessage } from './methods/messages/pin-message'
|
||||
import { searchGlobal } from './methods/messages/search-global'
|
||||
import { searchMessages } from './methods/messages/search-messages'
|
||||
import { sendDice } from './methods/messages/send-dice'
|
||||
import { sendLocation } from './methods/messages/send-location'
|
||||
import { sendMediaGroup } from './methods/messages/send-media-group'
|
||||
import { sendMedia } from './methods/messages/send-media'
|
||||
import { sendText } from './methods/messages/send-text'
|
||||
|
@ -1662,83 +1660,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
chunkSize?: number
|
||||
}
|
||||
): AsyncIterableIterator<Message>
|
||||
/**
|
||||
* Send an animated dice with a random value.
|
||||
*
|
||||
* For convenience, known dice emojis are available
|
||||
* as static members of {@link Dice}.
|
||||
*
|
||||
* Note that dice result value is generated randomly on the server,
|
||||
* you can't influence it in any way!
|
||||
*
|
||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
||||
* @param emoji Emoji representing a dice
|
||||
* @param params Additional sending parameters
|
||||
* @link Dice
|
||||
*/
|
||||
sendDice(
|
||||
chatId: InputPeerLike,
|
||||
emoji: string,
|
||||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
/**
|
||||
* Whether to send this message silently.
|
||||
*/
|
||||
silent?: boolean
|
||||
|
||||
/**
|
||||
* If set, the message will be scheduled to this date.
|
||||
* When passing a number, a UNIX time in ms is expected.
|
||||
*/
|
||||
schedule?: Date | number
|
||||
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
}
|
||||
): Promise<Message>
|
||||
/**
|
||||
* Send a static geo location.
|
||||
*
|
||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
||||
* @param latitude Latitude of the location
|
||||
* @param longitude Longitude of the location
|
||||
* @param params Additional sending parameters
|
||||
*/
|
||||
sendLocation(
|
||||
chatId: InputPeerLike,
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
/**
|
||||
* Whether to send this message silently.
|
||||
*/
|
||||
silent?: boolean
|
||||
|
||||
/**
|
||||
* If set, the message will be scheduled to this date.
|
||||
* When passing a number, a UNIX time in ms is expected.
|
||||
*/
|
||||
schedule?: Date | number
|
||||
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
}
|
||||
): Promise<Message>
|
||||
/**
|
||||
* Send a group of media.
|
||||
*
|
||||
|
@ -2322,8 +2243,6 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
pinMessage = pinMessage
|
||||
searchGlobal = searchGlobal
|
||||
searchMessages = searchMessages
|
||||
sendDice = sendDice
|
||||
sendLocation = sendLocation
|
||||
sendMediaGroup = sendMediaGroup
|
||||
sendMedia = sendMedia
|
||||
sendText = sendText
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
FileDownloadParameters,
|
||||
FileLocation,
|
||||
} from '../../types'
|
||||
import { fileIdToInputFileLocation } from '@mtcute/file-id'
|
||||
import { fileIdToInputFileLocation, fileIdToInputWebFileLocation, parseFileId } from '@mtcute/file-id'
|
||||
|
||||
/**
|
||||
* Download a file and return it as an iterable, which yields file contents
|
||||
|
@ -39,30 +39,37 @@ export async function* downloadAsIterable(
|
|||
let dcId = params.dcId
|
||||
let fileSize = params.fileSize
|
||||
|
||||
let location = params.location
|
||||
if (location instanceof FileLocation) {
|
||||
if (typeof location.location === 'function') {
|
||||
;(location as tl.Mutable<FileLocation>).location = location.location()
|
||||
const input = params.location
|
||||
let location: tl.TypeInputFileLocation | tl.TypeInputWebFileLocation
|
||||
if (input instanceof FileLocation) {
|
||||
if (typeof input.location === 'function') {
|
||||
;(input as tl.Mutable<FileLocation>).location = input.location()
|
||||
}
|
||||
|
||||
if (location.location instanceof Buffer) {
|
||||
yield location.location
|
||||
if (input.location instanceof Buffer) {
|
||||
yield input.location
|
||||
return
|
||||
}
|
||||
if (!dcId) dcId = location.dcId
|
||||
if (!fileSize) fileSize = location.fileSize
|
||||
location = location.location as any
|
||||
}
|
||||
if (typeof location === 'string') {
|
||||
location = fileIdToInputFileLocation(location)
|
||||
if (!dcId) dcId = input.dcId
|
||||
if (!fileSize) fileSize = input.fileSize
|
||||
location = input.location as any
|
||||
} else if (typeof input === 'string') {
|
||||
const parsed = parseFileId(input)
|
||||
if (parsed.location._ === 'web') {
|
||||
location = fileIdToInputWebFileLocation(parsed)
|
||||
} else {
|
||||
location = fileIdToInputFileLocation(parsed)
|
||||
}
|
||||
} else location = input
|
||||
|
||||
const isWeb = tl.isAnyInputWebFileLocation(location)
|
||||
|
||||
// we will receive a FileMigrateError in case this is invalid
|
||||
if (!dcId) dcId = this._primaryDc.id
|
||||
|
||||
const chunkSize = partSizeKb * 1024
|
||||
|
||||
const limit =
|
||||
let limit =
|
||||
params.limit ??
|
||||
(fileSize
|
||||
? // derive limit from chunk size, file size and offset
|
||||
|
@ -77,13 +84,13 @@ export async function* downloadAsIterable(
|
|||
}
|
||||
|
||||
const requestCurrent = async (): Promise<Buffer> => {
|
||||
let result: tl.RpcCallReturn['upload.getFile']
|
||||
let result: tl.RpcCallReturn['upload.getFile'] | tl.RpcCallReturn['upload.getWebFile']
|
||||
try {
|
||||
result = await connection.sendForResult({
|
||||
_: 'upload.getFile',
|
||||
location: location as tl.TypeInputFileLocation,
|
||||
_: isWeb ? 'upload.getWebFile' : 'upload.getFile',
|
||||
location: location as any,
|
||||
offset,
|
||||
limit: chunkSize,
|
||||
limit: chunkSize
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof FileMigrateError) {
|
||||
|
@ -94,18 +101,25 @@ export async function* downloadAsIterable(
|
|||
}
|
||||
return requestCurrent()
|
||||
} else if (e instanceof FilerefUpgradeNeededError) {
|
||||
// todo: implement once messages api is ready
|
||||
// todo: implement someday
|
||||
// see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99
|
||||
throw new MtCuteUnsupportedError('File ref expired!')
|
||||
} else throw e
|
||||
}
|
||||
|
||||
if (result._ === 'upload.fileCdnRedirect') {
|
||||
// we shouldnt receive them since cdnSupported is not set in the getFile request.
|
||||
// also, i couldnt find any media that would be downloaded from cdn, so even if
|
||||
// i implemented that, i wouldnt be able to test that, so :shrug:
|
||||
throw new MtCuteUnsupportedError(
|
||||
'Received CDN redirect, which is not supported (yet)'
|
||||
)
|
||||
}
|
||||
|
||||
if (result._ === 'upload.webFile' && result.size && limit === Infinity) {
|
||||
limit = result.size
|
||||
}
|
||||
|
||||
return result.bytes
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
} from '@mtcute/file-id'
|
||||
import { extractFileName } from '../../utils/file-utils'
|
||||
import { assertTypeIs } from '../../utils/type-assertion'
|
||||
import bigInt from 'big-integer'
|
||||
import { normalizeDate } from '../../utils/misc-utils'
|
||||
|
||||
/**
|
||||
* Normalize an {@link InputMediaLike} to `InputMedia`,
|
||||
|
@ -25,6 +27,7 @@ export async function _normalizeInputMedia(
|
|||
this: TelegramClient,
|
||||
media: InputMediaLike,
|
||||
params: {
|
||||
parseMode?: string | null
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
uploadMedia = false
|
||||
|
@ -35,6 +38,160 @@ export async function _normalizeInputMedia(
|
|||
|
||||
if (tl.isAnyInputMedia(media)) return media
|
||||
|
||||
if (media.type === 'venue') {
|
||||
return {
|
||||
_: 'inputMediaVenue',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: media.latitude,
|
||||
long: media.longitude,
|
||||
},
|
||||
title: media.title,
|
||||
address: media.address,
|
||||
provider: media.source?.provider ?? '',
|
||||
venueId: media.source?.id ?? '',
|
||||
venueType: media.source?.type ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'geo') {
|
||||
return {
|
||||
_: 'inputMediaGeoPoint',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: media.latitude,
|
||||
long: media.longitude,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'geo_live') {
|
||||
return {
|
||||
_: 'inputMediaGeoLive',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: media.latitude,
|
||||
long: media.longitude,
|
||||
},
|
||||
heading: media.heading,
|
||||
period: media.period,
|
||||
proximityNotificationRadius: media.proximityNotificationRadius,
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'dice') {
|
||||
return {
|
||||
_: 'inputMediaDice',
|
||||
emoticon: media.emoji,
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'contact') {
|
||||
return {
|
||||
_: 'inputMediaContact',
|
||||
phoneNumber: media.phone,
|
||||
firstName: media.firstName,
|
||||
lastName: media.lastName ?? '',
|
||||
vcard: media.vcard ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'game') {
|
||||
return {
|
||||
_: 'inputMediaGame',
|
||||
id:
|
||||
typeof media.game === 'string'
|
||||
? {
|
||||
_: 'inputGameShortName',
|
||||
botId: { _: 'inputUserSelf' },
|
||||
shortName: media.game,
|
||||
}
|
||||
: media.game,
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'invoice') {
|
||||
return {
|
||||
_: 'inputMediaInvoice',
|
||||
title: media.title,
|
||||
description: media.description,
|
||||
photo:
|
||||
typeof media.photo === 'string'
|
||||
? {
|
||||
_: 'inputWebDocument',
|
||||
url: media.photo,
|
||||
mimeType: 'image/jpeg',
|
||||
size: 0,
|
||||
attributes: [],
|
||||
}
|
||||
: media.photo,
|
||||
invoice: media.invoice,
|
||||
payload: media.payload,
|
||||
provider: media.token,
|
||||
providerData: {
|
||||
_: 'dataJSON',
|
||||
data: JSON.stringify(media.providerData),
|
||||
},
|
||||
startParam: media.startParam,
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type === 'poll' || media.type === 'quiz') {
|
||||
const answers: tl.TypePollAnswer[] = media.answers.map((ans, idx) => {
|
||||
if (typeof ans === 'string') {
|
||||
return {
|
||||
_: 'pollAnswer',
|
||||
text: ans,
|
||||
option: Buffer.from([idx]),
|
||||
}
|
||||
}
|
||||
|
||||
return ans
|
||||
})
|
||||
|
||||
let correct: Buffer[] | undefined = undefined
|
||||
let solution: string | undefined = undefined
|
||||
let solutionEntities: tl.TypeMessageEntity[] | undefined = undefined
|
||||
|
||||
if (media.type === 'quiz') {
|
||||
let input = media.correct
|
||||
if (!Array.isArray(input)) input = [input]
|
||||
correct = input.map((it) => {
|
||||
if (typeof it === 'number') {
|
||||
return answers[it].option
|
||||
}
|
||||
|
||||
return it
|
||||
})
|
||||
|
||||
if (media.solution) {
|
||||
;[solution, solutionEntities] = await this._parseEntities(
|
||||
media.solution,
|
||||
params.parseMode,
|
||||
media.solutionEntities
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'inputMediaPoll',
|
||||
poll: {
|
||||
_: 'poll',
|
||||
id: bigInt.zero,
|
||||
publicVoters: media.public,
|
||||
multipleChoice: media.multiple,
|
||||
quiz: media.type === 'quiz',
|
||||
question: media.question,
|
||||
answers,
|
||||
closePeriod: media.closePeriod,
|
||||
closeDate: normalizeDate(media.closeDate)
|
||||
},
|
||||
correctAnswers: correct,
|
||||
solution,
|
||||
solutionEntities
|
||||
}
|
||||
}
|
||||
|
||||
let inputFile: tl.TypeInputFile | undefined = undefined
|
||||
let thumb: tl.TypeInputFile | undefined = undefined
|
||||
let mime = 'application/octet-stream'
|
||||
|
@ -56,18 +213,29 @@ export async function _normalizeInputMedia(
|
|||
mime = uploaded.mime
|
||||
}
|
||||
|
||||
const uploadMediaIfNeeded = async (inputMedia: tl.TypeInputMedia, photo: boolean): Promise<tl.TypeInputMedia> => {
|
||||
const uploadMediaIfNeeded = async (
|
||||
inputMedia: tl.TypeInputMedia,
|
||||
photo: boolean
|
||||
): Promise<tl.TypeInputMedia> => {
|
||||
if (!uploadMedia) return inputMedia
|
||||
|
||||
const res = await this.call({
|
||||
_: 'messages.uploadMedia',
|
||||
peer: { _: 'inputPeerSelf' },
|
||||
media: inputMedia
|
||||
media: inputMedia,
|
||||
})
|
||||
|
||||
if (photo) {
|
||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res, 'messageMediaPhoto')
|
||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res.photo!, 'photo')
|
||||
assertTypeIs(
|
||||
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||
res,
|
||||
'messageMediaPhoto'
|
||||
)
|
||||
assertTypeIs(
|
||||
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||
res.photo!,
|
||||
'photo'
|
||||
)
|
||||
|
||||
return {
|
||||
_: 'inputMediaPhoto',
|
||||
|
@ -75,13 +243,21 @@ export async function _normalizeInputMedia(
|
|||
_: 'inputPhoto',
|
||||
id: res.photo.id,
|
||||
accessHash: res.photo.accessHash,
|
||||
fileReference: res.photo.fileReference
|
||||
fileReference: res.photo.fileReference,
|
||||
},
|
||||
ttlSeconds: media.ttlSeconds
|
||||
ttlSeconds: media.ttlSeconds,
|
||||
}
|
||||
} else {
|
||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res, 'messageMediaDocument')
|
||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res.document!, 'document')
|
||||
assertTypeIs(
|
||||
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||
res,
|
||||
'messageMediaDocument'
|
||||
)
|
||||
assertTypeIs(
|
||||
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||
res.document!,
|
||||
'document'
|
||||
)
|
||||
|
||||
return {
|
||||
_: 'inputMediaDocument',
|
||||
|
@ -89,9 +265,9 @@ export async function _normalizeInputMedia(
|
|||
_: 'inputDocument',
|
||||
id: res.document.id,
|
||||
accessHash: res.document.accessHash,
|
||||
fileReference: res.document.fileReference
|
||||
fileReference: res.document.fileReference,
|
||||
},
|
||||
ttlSeconds: media.ttlSeconds
|
||||
ttlSeconds: media.ttlSeconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,13 +275,16 @@ export async function _normalizeInputMedia(
|
|||
const input = media.file
|
||||
if (tdFileId.isFileIdLike(input)) {
|
||||
if (typeof input === 'string' && input.match(/^https?:\/\//)) {
|
||||
return uploadMediaIfNeeded({
|
||||
return uploadMediaIfNeeded(
|
||||
{
|
||||
_:
|
||||
media.type === 'photo'
|
||||
? 'inputMediaPhotoExternal'
|
||||
: 'inputMediaDocumentExternal',
|
||||
url: input,
|
||||
}, media.type === 'photo')
|
||||
},
|
||||
media.type === 'photo'
|
||||
)
|
||||
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
||||
await upload(input.substr(5))
|
||||
} else {
|
||||
|
@ -118,13 +297,16 @@ export async function _normalizeInputMedia(
|
|||
id: fileIdToInputPhoto(parsed),
|
||||
}
|
||||
} else if (parsed.location._ === 'web') {
|
||||
return uploadMediaIfNeeded({
|
||||
return uploadMediaIfNeeded(
|
||||
{
|
||||
_:
|
||||
parsed.type === tdFileId.FileType.Photo
|
||||
? 'inputMediaPhotoExternal'
|
||||
: 'inputMediaDocumentExternal',
|
||||
url: parsed.location.url,
|
||||
}, parsed.type === tdFileId.FileType.Photo)
|
||||
},
|
||||
parsed.type === tdFileId.FileType.Photo
|
||||
)
|
||||
} else {
|
||||
return {
|
||||
_: 'inputMediaDocument',
|
||||
|
@ -146,11 +328,14 @@ export async function _normalizeInputMedia(
|
|||
if (!inputFile) throw new Error('should not happen')
|
||||
|
||||
if (media.type === 'photo') {
|
||||
return uploadMediaIfNeeded({
|
||||
return uploadMediaIfNeeded(
|
||||
{
|
||||
_: 'inputMediaUploadedPhoto',
|
||||
file: inputFile,
|
||||
ttlSeconds: media.ttlSeconds,
|
||||
}, true)
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
if ('thumb' in media && media.thumb) {
|
||||
|
@ -204,7 +389,8 @@ export async function _normalizeInputMedia(
|
|||
})
|
||||
}
|
||||
|
||||
return uploadMediaIfNeeded({
|
||||
return uploadMediaIfNeeded(
|
||||
{
|
||||
_: 'inputMediaUploadedDocument',
|
||||
nosoundVideo: media.type === 'video' && media.isAnimated,
|
||||
forceFile: media.type === 'document',
|
||||
|
@ -212,6 +398,8 @@ export async function _normalizeInputMedia(
|
|||
thumb,
|
||||
mimeType: mime,
|
||||
attributes,
|
||||
ttlSeconds: media.ttlSeconds
|
||||
}, false)
|
||||
ttlSeconds: media.ttlSeconds,
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
|
|
|
@ -97,7 +97,10 @@ export async function editInlineMessage(
|
|||
|
||||
if (params.media) {
|
||||
media = await this._normalizeInputMedia(params.media, params, true)
|
||||
if ('caption' in params.media) {
|
||||
|
||||
// if there's no caption in input media (i.e. not present or undefined),
|
||||
// user wants to keep current caption, thus `content` needs to stay `undefined`
|
||||
if ('caption' in params.media && params.media.caption !== undefined) {
|
||||
;[content, entities] = await this._parseEntities(
|
||||
params.media.caption,
|
||||
params.parseMode,
|
||||
|
|
|
@ -84,11 +84,16 @@ export async function editMessage(
|
|||
|
||||
if (params.media) {
|
||||
media = await this._normalizeInputMedia(params.media, params)
|
||||
|
||||
// if there's no caption in input media (i.e. not present or undefined),
|
||||
// user wants to keep current caption, thus `content` needs to stay `undefined`
|
||||
if ('caption' in params.media && params.media.caption !== undefined) {
|
||||
;[content, entities] = await this._parseEntities(
|
||||
params.media.caption,
|
||||
params.parseMode,
|
||||
params.media.entities
|
||||
)
|
||||
}
|
||||
} else {
|
||||
;[content, entities] = await this._parseEntities(
|
||||
params.text,
|
||||
|
@ -105,7 +110,7 @@ export async function editMessage(
|
|||
replyMarkup: BotKeyboard._convertToTl(params.replyMarkup),
|
||||
message: content,
|
||||
entities,
|
||||
media
|
||||
media,
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res, true) as any
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { BotKeyboard, InputPeerLike, Message, ReplyMarkup } from '../../types'
|
||||
import { normalizeDate, randomUlong } from '../../utils/misc-utils'
|
||||
import { normalizeToInputPeer } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Send an animated dice with a random value.
|
||||
*
|
||||
* For convenience, known dice emojis are available
|
||||
* as static members of {@link Dice}.
|
||||
*
|
||||
* Note that dice result value is generated randomly on the server,
|
||||
* you can't influence it in any way!
|
||||
*
|
||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
||||
* @param emoji Emoji representing a dice
|
||||
* @param params Additional sending parameters
|
||||
* @link Dice
|
||||
* @internal
|
||||
*/
|
||||
export async function sendDice(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
emoji: string,
|
||||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
/**
|
||||
* Whether to send this message silently.
|
||||
*/
|
||||
silent?: boolean
|
||||
|
||||
/**
|
||||
* If set, the message will be scheduled to this date.
|
||||
* When passing a number, a UNIX time in ms is expected.
|
||||
*/
|
||||
schedule?: Date | number
|
||||
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
}
|
||||
): Promise<Message> {
|
||||
if (!params) params = {}
|
||||
|
||||
const peer = normalizeToInputPeer(await this.resolvePeer(chatId))
|
||||
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
||||
|
||||
const res = await this.call({
|
||||
_: 'messages.sendMedia',
|
||||
peer,
|
||||
media: {
|
||||
_: 'inputMediaDice',
|
||||
emoticon: emoji,
|
||||
},
|
||||
silent: params.silent,
|
||||
replyToMsgId: params.replyTo
|
||||
? typeof params.replyTo === 'number'
|
||||
? params.replyTo
|
||||
: params.replyTo.id
|
||||
: undefined,
|
||||
randomId: randomUlong(),
|
||||
scheduleDate: normalizeDate(params.schedule),
|
||||
replyMarkup,
|
||||
message: '',
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res)
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
import { BotKeyboard, InputPeerLike, Message, ReplyMarkup } from '../../types'
|
||||
import { TelegramClient } from '../../client'
|
||||
import { normalizeToInputPeer } from '../../utils/peer-utils'
|
||||
import { normalizeDate, randomUlong } from '../../utils/misc-utils'
|
||||
|
||||
/**
|
||||
* Send a static geo location.
|
||||
*
|
||||
* @param chatId ID of the chat, its username, phone or `"me"` or `"self"`
|
||||
* @param latitude Latitude of the location
|
||||
* @param longitude Longitude of the location
|
||||
* @param params Additional sending parameters
|
||||
* @internal
|
||||
*/
|
||||
export async function sendLocation(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
/**
|
||||
* Whether to send this message silently.
|
||||
*/
|
||||
silent?: boolean
|
||||
|
||||
/**
|
||||
* If set, the message will be scheduled to this date.
|
||||
* When passing a number, a UNIX time in ms is expected.
|
||||
*/
|
||||
schedule?: Date | number
|
||||
|
||||
/**
|
||||
* For bots: inline or reply markup or an instruction
|
||||
* to hide a reply keyboard or to force a reply.
|
||||
*/
|
||||
replyMarkup?: ReplyMarkup
|
||||
}
|
||||
): Promise<Message> {
|
||||
if (!params) params = {}
|
||||
|
||||
const peer = normalizeToInputPeer(await this.resolvePeer(chatId))
|
||||
const replyMarkup = BotKeyboard._convertToTl(params.replyMarkup)
|
||||
|
||||
const res = await this.call({
|
||||
_: 'messages.sendMedia',
|
||||
peer,
|
||||
media: {
|
||||
_: 'inputMediaGeoPoint',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: latitude,
|
||||
long: longitude
|
||||
}
|
||||
},
|
||||
silent: params.silent,
|
||||
replyToMsgId: params.replyTo
|
||||
? typeof params.replyTo === 'number'
|
||||
? params.replyTo
|
||||
: params.replyTo.id
|
||||
: undefined,
|
||||
randomId: randomUlong(),
|
||||
scheduleDate: normalizeDate(params.schedule),
|
||||
replyMarkup,
|
||||
message: '',
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res)
|
||||
}
|
|
@ -63,7 +63,11 @@ export async function sendMediaGroup(
|
|||
* @param uploaded Number of bytes already uploaded
|
||||
* @param total Total file size
|
||||
*/
|
||||
progressCallback?: (index: number, uploaded: number, total: number) => void
|
||||
progressCallback?: (
|
||||
index: number,
|
||||
uploaded: number,
|
||||
total: number
|
||||
) => void
|
||||
|
||||
/**
|
||||
* Whether to clear draft after sending this message.
|
||||
|
@ -83,13 +87,16 @@ export async function sendMediaGroup(
|
|||
for (let i = 0; i < medias.length; i++) {
|
||||
const media = medias[i]
|
||||
const inputMedia = await this._normalizeInputMedia(media, {
|
||||
progressCallback: params.progressCallback?.bind(null, i)
|
||||
progressCallback: params.progressCallback?.bind(null, i),
|
||||
})
|
||||
|
||||
const [message, entities] = await this._parseEntities(
|
||||
media.caption,
|
||||
// some types dont have `caption` field, and ts warns us,
|
||||
// but since it's JS, they'll just be `undefined` and properly
|
||||
// handled by _parseEntities method
|
||||
(media as any).caption,
|
||||
params.parseMode,
|
||||
media.entities
|
||||
(media as any).entities
|
||||
)
|
||||
|
||||
multiMedia.push({
|
||||
|
@ -97,7 +104,7 @@ export async function sendMediaGroup(
|
|||
randomId: randomUlong(),
|
||||
media: inputMedia,
|
||||
message,
|
||||
entities
|
||||
entities,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -86,9 +86,12 @@ export async function sendMedia(
|
|||
const inputMedia = await this._normalizeInputMedia(media, params)
|
||||
|
||||
const [message, entities] = await this._parseEntities(
|
||||
media.caption,
|
||||
// some types dont have `caption` field, and ts warns us,
|
||||
// but since it's JS, they'll just be `undefined` and properly
|
||||
// handled by _parseEntities method
|
||||
(media as any).caption,
|
||||
params.parseMode,
|
||||
media.entities
|
||||
(media as any).entities
|
||||
)
|
||||
|
||||
const peer = normalizeToInputPeer(await this.resolvePeer(chatId))
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { BotKeyboard, ReplyMarkup } from '../keyboards'
|
||||
import { TelegramClient } from '../../../client'
|
||||
import {
|
||||
InputMediaGeo,
|
||||
InputMediaGeoLive,
|
||||
InputMediaVenue,
|
||||
Venue,
|
||||
} from '../../media'
|
||||
|
||||
/**
|
||||
* Inline message containing only text
|
||||
|
@ -57,37 +63,17 @@ export interface InputInlineMessageMedia {
|
|||
/**
|
||||
* Inline message containing a geolocation
|
||||
*/
|
||||
export interface InputInlineMessageGeo {
|
||||
type: 'geo'
|
||||
|
||||
export interface InputInlineMessageGeo extends InputMediaGeo {
|
||||
/**
|
||||
* Latitude of the geolocation
|
||||
* Message's reply markup
|
||||
*/
|
||||
latitude: number
|
||||
replyMarkup?: ReplyMarkup
|
||||
}
|
||||
|
||||
/**
|
||||
* Longitude of the geolocation
|
||||
/**
|
||||
* Inline message containing a live geolocation
|
||||
*/
|
||||
longitude: number
|
||||
|
||||
/**
|
||||
* For live locations, direction in which the location
|
||||
* moves, in degrees (1-360)
|
||||
*/
|
||||
heading?: number
|
||||
|
||||
/**
|
||||
* For live locations, period for which this live location
|
||||
* will be updated
|
||||
*/
|
||||
period?: number
|
||||
|
||||
/**
|
||||
* For live locations, a maximum distance to another
|
||||
* chat member for proximity alerts, in meters (0-100000)
|
||||
*/
|
||||
proximityNotificationRadius?: number
|
||||
|
||||
export interface InputInlineMessageGeoLive extends InputMediaGeoLive {
|
||||
/**
|
||||
* Message's reply markup
|
||||
*/
|
||||
|
@ -97,54 +83,7 @@ export interface InputInlineMessageGeo {
|
|||
/**
|
||||
* Inline message containing a venue
|
||||
*/
|
||||
export interface InputInlineMessageVenue {
|
||||
type: 'venue'
|
||||
|
||||
/**
|
||||
* Latitude of the geolocation
|
||||
*/
|
||||
latitude: number
|
||||
|
||||
/**
|
||||
* Longitude of the geolocation
|
||||
*/
|
||||
longitude: number
|
||||
|
||||
/**
|
||||
* Venue name
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Venue address
|
||||
*/
|
||||
address: string
|
||||
|
||||
/**
|
||||
* When available, source from where this venue was acquired
|
||||
*/
|
||||
source?: {
|
||||
/**
|
||||
* Provider name (`foursquare` or `gplaces` for Google Places)
|
||||
*/
|
||||
provider?: 'foursquare' | 'gplaces'
|
||||
|
||||
/**
|
||||
* Venue ID in the provider's DB
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* Venue type in the provider's DB
|
||||
*
|
||||
* - [Supported types for Foursquare](https://developer.foursquare.com/docs/build-with-foursquare/categories/)
|
||||
* (use names, lowercase them, replace spaces and " & " with `_` (underscore) and remove other symbols,
|
||||
* and use `/` (slash) as hierarchy separator)
|
||||
* - [Supported types for Google Places](https://developers.google.com/places/web-service/supported_types)
|
||||
*/
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface InputInlineMessageVenue extends InputMediaVenue {
|
||||
/**
|
||||
* Message's reply markup
|
||||
*/
|
||||
|
@ -167,51 +106,62 @@ export type InputInlineMessage =
|
|||
| InputInlineMessageText
|
||||
| InputInlineMessageMedia
|
||||
| InputInlineMessageGeo
|
||||
| InputInlineMessageGeoLive
|
||||
| InputInlineMessageVenue
|
||||
| InputInlineMessageGame
|
||||
|
||||
export namespace BotInlineMessage {
|
||||
export function text (
|
||||
export function text(
|
||||
text: string,
|
||||
params?: Omit<InputInlineMessageText, 'type' | 'text'>,
|
||||
params?: Omit<InputInlineMessageText, 'type' | 'text'>
|
||||
): InputInlineMessageText {
|
||||
return {
|
||||
type: 'text',
|
||||
text,
|
||||
...(
|
||||
params || {}
|
||||
),
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function media (
|
||||
params?: Omit<InputInlineMessageMedia, 'type'>,
|
||||
export function media(
|
||||
params?: Omit<InputInlineMessageMedia, 'type'>
|
||||
): InputInlineMessageMedia {
|
||||
return {
|
||||
type: 'media',
|
||||
...(
|
||||
params || {}
|
||||
),
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function geo (
|
||||
export function geo(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params?: Omit<InputInlineMessageGeo, 'type' | 'latitude' | 'longitude'>,
|
||||
params?: Omit<InputInlineMessageGeo, 'type' | 'latitude' | 'longitude'>
|
||||
): InputInlineMessageGeo {
|
||||
return {
|
||||
type: 'geo',
|
||||
latitude,
|
||||
longitude,
|
||||
...(
|
||||
params || {}
|
||||
),
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function venue (
|
||||
params: Omit<InputInlineMessageVenue, 'type'>,
|
||||
export function geoLive(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params?: Omit<
|
||||
InputInlineMessageGeoLive,
|
||||
'type' | 'latitude' | 'longitude'
|
||||
>
|
||||
): InputInlineMessageGeoLive {
|
||||
return {
|
||||
type: 'geo_live',
|
||||
latitude,
|
||||
longitude,
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function venue(
|
||||
params: Omit<InputInlineMessageVenue, 'type'>
|
||||
): InputInlineMessageVenue {
|
||||
return {
|
||||
type: 'venue',
|
||||
|
@ -219,8 +169,8 @@ export namespace BotInlineMessage {
|
|||
}
|
||||
}
|
||||
|
||||
export function game (
|
||||
params: Omit<InputInlineMessageGame, 'type'>,
|
||||
export function game(
|
||||
params: Omit<InputInlineMessageGame, 'type'>
|
||||
): InputInlineMessageGame {
|
||||
return {
|
||||
type: 'game',
|
||||
|
@ -228,45 +178,55 @@ export namespace BotInlineMessage {
|
|||
}
|
||||
}
|
||||
|
||||
export async function _convertToTl (
|
||||
export async function _convertToTl(
|
||||
client: TelegramClient,
|
||||
obj: InputInlineMessage,
|
||||
parseMode?: string | null,
|
||||
parseMode?: string | null
|
||||
): Promise<tl.TypeInputBotInlineMessage> {
|
||||
if (obj.type === 'text') {
|
||||
const [message, entities] = await client['_parseEntities'](obj.text, parseMode, obj.entities)
|
||||
const [message, entities] = await client['_parseEntities'](
|
||||
obj.text,
|
||||
parseMode,
|
||||
obj.entities
|
||||
)
|
||||
|
||||
return {
|
||||
_: 'inputBotInlineMessageText',
|
||||
message,
|
||||
entities,
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.type === 'media') {
|
||||
const [message, entities] = await client['_parseEntities'](obj.text, parseMode, obj.entities)
|
||||
const [message, entities] = await client['_parseEntities'](
|
||||
obj.text,
|
||||
parseMode,
|
||||
obj.entities
|
||||
)
|
||||
|
||||
return {
|
||||
_: 'inputBotInlineMessageMediaAuto',
|
||||
message,
|
||||
entities,
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.type === 'geo') {
|
||||
if (obj.type === 'geo' || obj.type === 'geo_live') {
|
||||
return {
|
||||
_: 'inputBotInlineMessageMediaGeo',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: obj.latitude,
|
||||
long: obj.longitude
|
||||
long: obj.longitude,
|
||||
},
|
||||
heading: obj.heading,
|
||||
period: obj.period,
|
||||
proximityNotificationRadius: obj.proximityNotificationRadius,
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
||||
// fields will be `undefined` if this is a `geo`
|
||||
heading: (obj as InputMediaGeoLive).heading,
|
||||
period: (obj as InputMediaGeoLive).period,
|
||||
proximityNotificationRadius: (obj as InputMediaGeoLive)
|
||||
.proximityNotificationRadius,
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,21 +236,21 @@ export namespace BotInlineMessage {
|
|||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: obj.latitude,
|
||||
long: obj.longitude
|
||||
long: obj.longitude,
|
||||
},
|
||||
title: obj.title,
|
||||
address: obj.address,
|
||||
provider: obj.source?.provider ?? '',
|
||||
venueId: obj.source?.id ?? '',
|
||||
venueType: obj.source?.type ?? '',
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.type === 'game') {
|
||||
return {
|
||||
_: 'inputBotInlineMessageGame',
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,9 @@ export class FileLocation {
|
|||
*/
|
||||
readonly location:
|
||||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| Buffer
|
||||
| (() => tl.TypeInputFileLocation | Buffer)
|
||||
| (() => tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | Buffer)
|
||||
|
||||
/**
|
||||
* File size in bytes, when available
|
||||
|
@ -44,8 +45,9 @@ export class FileLocation {
|
|||
client: TelegramClient,
|
||||
location:
|
||||
| tl.TypeInputFileLocation
|
||||
| tl.TypeInputWebFileLocation
|
||||
| Buffer
|
||||
| (() => tl.TypeInputFileLocation | Buffer),
|
||||
| (() => tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | Buffer),
|
||||
fileSize?: number,
|
||||
dcId?: number
|
||||
) {
|
||||
|
@ -82,7 +84,7 @@ export class FileLocation {
|
|||
* in chunks of a given size. Order of the chunks is guaranteed to be
|
||||
* consecutive.
|
||||
*
|
||||
* Shorthand for `client.downloadAsStream({ location: this })`
|
||||
* Shorthand for `client.downloadAsIterable({ location: this })`
|
||||
*
|
||||
* @link TelegramClient.downloadAsIterable
|
||||
*/
|
||||
|
|
|
@ -52,7 +52,7 @@ export interface FileDownloadParameters {
|
|||
* File location which should be downloaded.
|
||||
* You can also provide TDLib and Bot API compatible File ID
|
||||
*/
|
||||
location: tl.TypeInputFileLocation | FileLocation | string
|
||||
location: tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | FileLocation | string
|
||||
|
||||
/**
|
||||
* Total file size, if known.
|
||||
|
|
67
packages/client/src/types/files/web-document.ts
Normal file
67
packages/client/src/types/files/web-document.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { FileLocation } from './file-location'
|
||||
import { MtCuteArgumentError } from '../errors'
|
||||
import { makeInspectable } from '../utils'
|
||||
|
||||
const STUB_LOCATION = () => {
|
||||
throw new MtCuteArgumentError(
|
||||
'This web document is not downloadable through Telegram'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An external web document, that is not
|
||||
* stored on Telegram severs, and is available
|
||||
* by a HTTP(s) url.
|
||||
*
|
||||
* > **Note**: not all web documents are downloadable
|
||||
* > through Telegram. Media files usually are,
|
||||
* > and web pages (i.e. `mimeType = text/html`) usually aren't.
|
||||
* > To be sure, check `isDownloadable` property.
|
||||
*/
|
||||
export class WebDocument extends FileLocation {
|
||||
readonly raw: tl.TypeWebDocument
|
||||
|
||||
constructor(client: TelegramClient, raw: tl.TypeWebDocument) {
|
||||
super(
|
||||
client,
|
||||
raw._ === 'webDocument'
|
||||
? {
|
||||
_: 'inputWebFileLocation',
|
||||
url: raw.url,
|
||||
accessHash: raw.accessHash,
|
||||
}
|
||||
: STUB_LOCATION,
|
||||
raw.size
|
||||
)
|
||||
this.raw = raw
|
||||
}
|
||||
|
||||
/**
|
||||
* URL to the file
|
||||
*/
|
||||
get url(): string {
|
||||
return this.raw.url
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME type of the file
|
||||
*/
|
||||
get mimeType(): string {
|
||||
return this.raw.mimeType
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this file can be downloaded through Telegram.
|
||||
*
|
||||
* If `false`, you should use {@link url} to manually
|
||||
* fetch data via HTTP(s), and trying to use `download*` methods
|
||||
* will result in an error
|
||||
*/
|
||||
get isDownloadable(): boolean {
|
||||
return this.raw._ === 'webDocument'
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(WebDocument)
|
|
@ -9,3 +9,4 @@ export * from './location'
|
|||
export * from './voice'
|
||||
export * from './sticker'
|
||||
export * from './input-media'
|
||||
export * from './venue'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { InputFileLike } from '../files'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { Venue } from './venue'
|
||||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
interface BaseInputMedia {
|
||||
/**
|
||||
|
@ -233,6 +235,303 @@ export interface InputMediaVideo extends BaseInputMedia {
|
|||
isRound?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* A geolocation to be sent
|
||||
*/
|
||||
export interface InputMediaGeo {
|
||||
type: 'geo'
|
||||
|
||||
/**
|
||||
* Latitude of the geolocation
|
||||
*/
|
||||
latitude: number
|
||||
|
||||
/**
|
||||
* Longitude of the geolocation
|
||||
*/
|
||||
longitude: number
|
||||
|
||||
/**
|
||||
* The estimated horizontal accuracy of the
|
||||
* geolocation, in meters (0-1500)
|
||||
*/
|
||||
accuracy?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* A live geolocation to be sent
|
||||
*/
|
||||
export interface InputMediaGeoLive extends Omit<InputMediaGeo, 'type'> {
|
||||
type: 'geo_live'
|
||||
|
||||
/**
|
||||
* Direction in which the location moves, in degrees (1-360)
|
||||
*/
|
||||
heading?: number
|
||||
|
||||
/**
|
||||
* Validity period of the live location
|
||||
*/
|
||||
period?: number
|
||||
|
||||
/**
|
||||
* Maximum distance to another chat member for proximity
|
||||
* alerts, in meters (0-100000)
|
||||
*/
|
||||
proximityNotificationRadius?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* An animated dice with a random value to be sent
|
||||
*
|
||||
* For convenience, known dice emojis are available
|
||||
* as static members of {@link Dice}.
|
||||
*
|
||||
* Note that dice result value is generated randomly on the server,
|
||||
* you can't influence it in any way!
|
||||
*/
|
||||
export interface InputMediaDice {
|
||||
type: 'dice'
|
||||
|
||||
/**
|
||||
* Emoji representing a dice
|
||||
*/
|
||||
emoji: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A venue to be sent
|
||||
*/
|
||||
export interface InputMediaVenue {
|
||||
type: 'venue'
|
||||
|
||||
/**
|
||||
* Latitude of the geolocation
|
||||
*/
|
||||
latitude: number
|
||||
|
||||
/**
|
||||
* Longitude of the geolocation
|
||||
*/
|
||||
longitude: number
|
||||
|
||||
/**
|
||||
* Venue name
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Venue address
|
||||
*/
|
||||
address: string
|
||||
|
||||
/**
|
||||
* When available, source from where this venue was acquired
|
||||
*/
|
||||
source?: Venue.VenueSource
|
||||
}
|
||||
|
||||
/**
|
||||
* A contact to be sent
|
||||
*/
|
||||
export interface InputMediaContact {
|
||||
type: 'contact'
|
||||
|
||||
/**
|
||||
* Contact's phone number
|
||||
*/
|
||||
phone: string
|
||||
|
||||
/**
|
||||
* Contact's first name
|
||||
*/
|
||||
firstName: string
|
||||
|
||||
/**
|
||||
* Contact's last name
|
||||
*/
|
||||
lastName?: string
|
||||
|
||||
/**
|
||||
* Additional data about the contact
|
||||
* as a vCard (0-2048 bytes)
|
||||
*/
|
||||
vcard?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A game to be sent
|
||||
*/
|
||||
export interface InputMediaGame {
|
||||
type: 'game'
|
||||
|
||||
/**
|
||||
* Game's short name, or a TL object with an input game
|
||||
*/
|
||||
game: string | tl.TypeInputGame
|
||||
}
|
||||
|
||||
/**
|
||||
* An invoice to be sent (see https://core.telegram.org/bots/payments)
|
||||
*/
|
||||
export interface InputMediaInvoice {
|
||||
type: 'invoice'
|
||||
|
||||
/**
|
||||
* Product name (1-32 chars)
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Product description (1-255 chars)
|
||||
*/
|
||||
description: string
|
||||
|
||||
/**
|
||||
* The invoice itself
|
||||
*/
|
||||
invoice: tl.TypeInvoice
|
||||
|
||||
/**
|
||||
* Bot-defined invoice payload (1-128 bytes).
|
||||
*
|
||||
* Will not be displayed to the user and can be used
|
||||
* for internal processes
|
||||
*/
|
||||
payload: Buffer
|
||||
|
||||
/**
|
||||
* Payments provider token, obtained from
|
||||
* [@BotFather](//t.me/botfather)
|
||||
*/
|
||||
token: string
|
||||
|
||||
/**
|
||||
* Data about the invoice as a plain JS object, which
|
||||
* will be shared with the payment provider. A detailed
|
||||
* description of required fields should be provided by
|
||||
* the payment provider.
|
||||
*/
|
||||
providerData: any
|
||||
|
||||
/**
|
||||
* Start parameter for the bot
|
||||
*/
|
||||
startParam: string
|
||||
|
||||
/**
|
||||
* Product photo. Can be a photo of the goods or a marketing image for a service.
|
||||
*
|
||||
* Can be a URL, or a TL object with input web document
|
||||
*/
|
||||
photo?: string | tl.TypeInputWebDocument
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple poll to be sent
|
||||
*/
|
||||
export interface InputMediaPoll {
|
||||
type: 'poll'
|
||||
|
||||
/**
|
||||
* Question of the poll (1-255 chars for users, 1-300 chars for bots)
|
||||
*/
|
||||
question: string
|
||||
|
||||
/**
|
||||
* Answers of the poll.
|
||||
*
|
||||
* You can either provide a string, or a
|
||||
* TL object representing an answer.
|
||||
* Strings will be transformed to TL
|
||||
* objects, with a single=byte incrementing
|
||||
* `option` value.
|
||||
*/
|
||||
answers: (string | tl.TypePollAnswer)[]
|
||||
|
||||
/**
|
||||
* Whether this is a public poll
|
||||
* (i.e. users who have voted are visible to everyone)
|
||||
*/
|
||||
public?: boolean
|
||||
|
||||
/**
|
||||
* Whether users can select multiple answers
|
||||
* as an answer
|
||||
*/
|
||||
multiple?: boolean
|
||||
|
||||
/**
|
||||
* Amount of time in seconds the poll will be active after creation (5-600).
|
||||
*
|
||||
* Can't be used together with `closeDate`.
|
||||
*/
|
||||
closePeriod?: number
|
||||
|
||||
/**
|
||||
* Point in time when the poll will be automatically closed.
|
||||
*
|
||||
* Must be at least 5 and no more than 600 seconds in the future,
|
||||
* can't be used together with `closePeriod`.
|
||||
*
|
||||
* When `number` is used, UNIX time in ms is expected
|
||||
*/
|
||||
closeDate?: number | Date
|
||||
}
|
||||
|
||||
/**
|
||||
* A quiz to be sent.
|
||||
*
|
||||
* Quiz is an extended version of a poll, but quizzes have
|
||||
* correct answers, and votes can't be retracted from them
|
||||
*/
|
||||
export interface InputMediaQuiz extends Omit<InputMediaPoll, 'type'> {
|
||||
type: 'quiz'
|
||||
|
||||
/**
|
||||
* Correct answer ID(s) or index(es).
|
||||
*
|
||||
* > **Note**: even though quizzes can actually
|
||||
* > only have exactly one correct answer,
|
||||
* > the API itself has the possibility to pass
|
||||
* > multiple or zero correct answers,
|
||||
* > but that would result in `QUIZ_CORRECT_ANSWERS_TOO_MUCH`
|
||||
* > and `QUIZ_CORRECT_ANSWERS_EMPTY` errors respectively.
|
||||
* >
|
||||
* > But since the API has that option, we also provide it,
|
||||
* > maybe to future-proof this :shrug:
|
||||
*/
|
||||
correct: MaybeArray<number | Buffer>
|
||||
|
||||
/**
|
||||
* Explanation of the quiz solution
|
||||
*/
|
||||
solution?: string
|
||||
|
||||
/**
|
||||
* Format entities for `solution`.
|
||||
* If used, parse mode is ignored.
|
||||
*/
|
||||
solutionEntities?: tl.TypeMessageEntity[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Input media that can have a caption.
|
||||
*
|
||||
* Note that meta-fields (like `duration`) are only
|
||||
* applicable if `file` is {@link UploadFileLike},
|
||||
* otherwise they are ignored.
|
||||
*
|
||||
* A subset of {@link InputMediaLike}
|
||||
*/
|
||||
export type InputMediaWithCaption =
|
||||
| InputMediaAudio
|
||||
| InputMediaVoice
|
||||
| InputMediaDocument
|
||||
| InputMediaPhoto
|
||||
| InputMediaVideo
|
||||
| InputMediaAuto
|
||||
|
||||
/**
|
||||
* Input media that can be sent somewhere.
|
||||
*
|
||||
|
@ -243,17 +542,24 @@ export interface InputMediaVideo extends BaseInputMedia {
|
|||
* @link InputMedia
|
||||
*/
|
||||
export type InputMediaLike =
|
||||
| InputMediaAudio
|
||||
| InputMediaVoice
|
||||
| InputMediaDocument
|
||||
| InputMediaPhoto
|
||||
| InputMediaVideo
|
||||
| InputMediaAuto
|
||||
| InputMediaWithCaption
|
||||
| InputMediaSticker
|
||||
| InputMediaVenue
|
||||
| InputMediaGeo
|
||||
| InputMediaGeoLive
|
||||
| InputMediaDice
|
||||
| InputMediaContact
|
||||
| InputMediaGame
|
||||
| InputMediaInvoice
|
||||
| InputMediaPoll
|
||||
| InputMediaQuiz
|
||||
| tl.TypeInputMedia
|
||||
|
||||
export namespace InputMedia {
|
||||
type OmitTypeAndFile<T extends InputMediaLike> = Omit<T, 'type' | 'file'>
|
||||
type OmitTypeAndFile<
|
||||
T extends InputMediaLike,
|
||||
K extends keyof T = never
|
||||
> = Omit<T, 'type' | 'file' | K>
|
||||
|
||||
/**
|
||||
* Create an animation to be sent
|
||||
|
@ -354,6 +660,121 @@ export namespace InputMedia {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a venue to be sent
|
||||
*/
|
||||
export function venue(
|
||||
params: OmitTypeAndFile<InputMediaVenue>
|
||||
): InputMediaVenue {
|
||||
return {
|
||||
type: 'venue',
|
||||
...params,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a geolocation to be sent
|
||||
*/
|
||||
export function geo(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params?: OmitTypeAndFile<InputMediaGeo, 'latitude' | 'longitude'>
|
||||
): InputMediaGeo {
|
||||
return {
|
||||
type: 'geo',
|
||||
latitude,
|
||||
longitude,
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a live geolocation to be sent
|
||||
*/
|
||||
export function geoLive(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
params?: OmitTypeAndFile<InputMediaGeoLive, 'latitude' | 'longitude'>
|
||||
): InputMediaGeoLive {
|
||||
return {
|
||||
type: 'geo_live',
|
||||
latitude,
|
||||
longitude,
|
||||
...(params || {}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dice to be sent
|
||||
*
|
||||
* For convenience, known dice emojis are available
|
||||
* as static members of {@link Dice}.
|
||||
*/
|
||||
export function dice(emoji: string): InputMediaDice {
|
||||
return {
|
||||
type: 'dice',
|
||||
emoji,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a contact to be sent
|
||||
*/
|
||||
export function contact(
|
||||
params: OmitTypeAndFile<InputMediaContact>
|
||||
): InputMediaContact {
|
||||
return {
|
||||
type: 'contact',
|
||||
...params,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a game to be sent
|
||||
*/
|
||||
export function game(game: string | tl.TypeInputGame): InputMediaGame {
|
||||
return {
|
||||
type: 'game',
|
||||
game,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an invoice to be sent
|
||||
*/
|
||||
export function invoice(
|
||||
params: OmitTypeAndFile<InputMediaInvoice>
|
||||
): InputMediaInvoice {
|
||||
return {
|
||||
type: 'invoice',
|
||||
...params,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a poll to be sent
|
||||
*/
|
||||
export function poll(
|
||||
params: OmitTypeAndFile<InputMediaPoll>
|
||||
): InputMediaPoll {
|
||||
return {
|
||||
type: 'poll',
|
||||
...params,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a quiz to be sent
|
||||
*/
|
||||
export function quiz(
|
||||
params: OmitTypeAndFile<InputMediaQuiz>
|
||||
): InputMediaQuiz {
|
||||
return {
|
||||
type: 'quiz',
|
||||
...params,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document to be sent, which subtype
|
||||
* is inferred automatically by file contents.
|
||||
|
|
95
packages/client/src/types/media/invoice.ts
Normal file
95
packages/client/src/types/media/invoice.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { WebDocument } from '../files/web-document'
|
||||
|
||||
/**
|
||||
* An invoice
|
||||
*/
|
||||
export class Invoice {
|
||||
readonly client: TelegramClient
|
||||
readonly raw: tl.RawMessageMediaInvoice
|
||||
|
||||
constructor (client: TelegramClient, raw: tl.RawMessageMediaInvoice) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the shipping address was requested
|
||||
*/
|
||||
isShippingAddressRequested(): boolean {
|
||||
return !!this.raw.shippingAddressRequested
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is an example (test) invoice
|
||||
*/
|
||||
isTest(): boolean {
|
||||
return !!this.raw.test
|
||||
}
|
||||
|
||||
/**
|
||||
* Product name, 1-32 characters
|
||||
*/
|
||||
get title(): string {
|
||||
return this.raw.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Product description, 1-255 characters
|
||||
*/
|
||||
get description(): string {
|
||||
return this.raw.description
|
||||
}
|
||||
|
||||
private _photo?: WebDocument
|
||||
/**
|
||||
* URL of the product photo for the invoice
|
||||
*/
|
||||
get photo(): WebDocument | null {
|
||||
if (!this.raw.photo) return null
|
||||
|
||||
if (!this._photo) {
|
||||
this._photo = new WebDocument(this.client, this.raw.photo)
|
||||
}
|
||||
|
||||
return this._photo
|
||||
}
|
||||
|
||||
/**
|
||||
* Message ID of receipt
|
||||
*/
|
||||
get receiptMessageId(): number | null {
|
||||
return this.raw.receiptMsgId ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Three-letter ISO 4217 currency code
|
||||
*/
|
||||
get currency(): string {
|
||||
return this.raw.currency
|
||||
}
|
||||
|
||||
/**
|
||||
* Total price in the smallest units of the currency
|
||||
* (integer, not float/double). For example, for a price
|
||||
* of `US$ 1.45` `amount = 145`.
|
||||
*
|
||||
* See the exp parameter in [currencies.json](https://core.telegram.org/bots/payments/currencies.json),
|
||||
* it shows the number of digits past the decimal point
|
||||
* for each currency (2 for the majority of currencies).
|
||||
*/
|
||||
get amount(): tl.Long {
|
||||
return this.raw.totalAmount
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique bot deep-linking parameter that can be used to generate this invoice
|
||||
*/
|
||||
get startParam(): string {
|
||||
return this.raw.startParam
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(Invoice)
|
|
@ -1,13 +1,17 @@
|
|||
import { makeInspectable } from '../utils'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { FileLocation } from '../files'
|
||||
import { TelegramClient } from '../../client'
|
||||
|
||||
/**
|
||||
* A point on the map
|
||||
*/
|
||||
export class Location {
|
||||
readonly client: TelegramClient
|
||||
readonly geo: tl.RawGeoPoint
|
||||
|
||||
constructor(geo: tl.RawGeoPoint) {
|
||||
constructor(client: TelegramClient, geo: tl.RawGeoPoint) {
|
||||
this.client = client
|
||||
this.geo = geo
|
||||
}
|
||||
|
||||
|
@ -31,13 +35,62 @@ export class Location {
|
|||
get radius(): number {
|
||||
return this.geo.accuracyRadius ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link FileLocation} containing
|
||||
* server-generated image with the map preview
|
||||
*/
|
||||
preview(params: {
|
||||
/**
|
||||
* Map width in pixels before applying scale (16-1024)
|
||||
*
|
||||
* Defaults to `128`
|
||||
*/
|
||||
width?: number
|
||||
|
||||
/**
|
||||
* Map height in pixels before applying scale (16-1024)
|
||||
*
|
||||
* Defaults to `128`
|
||||
*/
|
||||
height?: number
|
||||
|
||||
/**
|
||||
* Map zoom level (13-20)
|
||||
*
|
||||
* Defaults to `15`
|
||||
*/
|
||||
zoom?: number
|
||||
|
||||
/**
|
||||
* Map scale (1-3)
|
||||
*
|
||||
* Defaults to `1`
|
||||
*/
|
||||
scale?: number
|
||||
} = {}): FileLocation {
|
||||
return new FileLocation(this.client, {
|
||||
_: 'inputWebFileGeoPointLocation',
|
||||
geoPoint: {
|
||||
_: 'inputGeoPoint',
|
||||
lat: this.geo.lat,
|
||||
long: this.geo.long,
|
||||
accuracyRadius: this.geo.accuracyRadius
|
||||
},
|
||||
accessHash: this.geo.accessHash,
|
||||
w: params.width ?? 128,
|
||||
h: params.height ?? 128,
|
||||
zoom: params.zoom ?? 15,
|
||||
scale: params.scale ?? 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class LiveLocation extends Location {
|
||||
readonly live: tl.RawMessageMediaGeoLive
|
||||
|
||||
constructor(live: tl.RawMessageMediaGeoLive) {
|
||||
super(live.geo as tl.RawGeoPoint)
|
||||
constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) {
|
||||
super(client, live.geo as tl.RawGeoPoint)
|
||||
this.live = live
|
||||
}
|
||||
|
||||
|
|
185
packages/client/src/types/media/poll.ts
Normal file
185
packages/client/src/types/media/poll.ts
Normal file
|
@ -0,0 +1,185 @@
|
|||
import { makeInspectable } from '../utils'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { TelegramClient } from '../../client'
|
||||
import { MessageEntity } from '../messages'
|
||||
|
||||
export namespace Poll {
|
||||
export interface PollAnswer {
|
||||
/**
|
||||
* Answer text
|
||||
*/
|
||||
text: string
|
||||
|
||||
/**
|
||||
* Answer data, to be passed to
|
||||
* {@link TelegramClient.votePoll}
|
||||
*/
|
||||
data: Buffer
|
||||
|
||||
/**
|
||||
* Number of people who has chosen this result.
|
||||
* If not available (i.e. not voted yet), defaults to `0`
|
||||
*/
|
||||
voters: number
|
||||
|
||||
/**
|
||||
* Whether this answer was chosen by the current user
|
||||
*/
|
||||
chosen: boolean
|
||||
|
||||
/**
|
||||
* Whether this answer is correct (for quizzes).
|
||||
* Not available before choosing an answer, and defaults to `false`
|
||||
*/
|
||||
correct: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export class Poll {
|
||||
readonly client: TelegramClient
|
||||
readonly raw: tl.TypePoll
|
||||
readonly results?: tl.TypePollResults
|
||||
|
||||
readonly _users: Record<number, tl.TypeUser>
|
||||
|
||||
constructor(
|
||||
client: TelegramClient,
|
||||
raw: tl.TypePoll,
|
||||
users: Record<number, tl.TypeUser>,
|
||||
results?: tl.TypePollResults
|
||||
) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
this._users = users
|
||||
this.results = results
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifier of the poll
|
||||
*/
|
||||
get id(): tl.Long {
|
||||
return this.raw.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll question
|
||||
*/
|
||||
get question(): string {
|
||||
return this.raw.question
|
||||
}
|
||||
|
||||
private _answers?: Poll.PollAnswer[]
|
||||
/**
|
||||
* List of answers in this poll
|
||||
*/
|
||||
get answers(): Poll.PollAnswer[] {
|
||||
if (!this._answers) {
|
||||
const results = this.results?.results
|
||||
|
||||
this._answers = this.raw.answers.map((ans, idx) => {
|
||||
if (results) {
|
||||
const res = results[idx]
|
||||
return {
|
||||
text: ans.text,
|
||||
data: ans.option,
|
||||
voters: res.voters,
|
||||
chosen: !!res.chosen,
|
||||
correct: !!res.correct
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
text: ans.text,
|
||||
data: ans.option,
|
||||
voters: 0,
|
||||
chosen: false,
|
||||
correct: false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return this._answers
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of voters in this poll, if available
|
||||
*/
|
||||
get voters(): number {
|
||||
return this.results?.totalVoters ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this poll is closed, i.e. does not
|
||||
* accept votes anymore
|
||||
*/
|
||||
get isClosed(): boolean {
|
||||
return !!this.raw.closed
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this poll is public, i.e. you
|
||||
* list of voters is publicly available
|
||||
*/
|
||||
get isPublic(): boolean {
|
||||
return !!this.raw.publicVoters
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a quiz
|
||||
*/
|
||||
get isQuiz(): boolean {
|
||||
return !!this.raw.quiz
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this poll accepts multiple answers
|
||||
*/
|
||||
get isMultiple(): boolean {
|
||||
return !!this.raw.multipleChoice
|
||||
}
|
||||
|
||||
/**
|
||||
* Solution for the quiz, only available
|
||||
* in case you have already answered
|
||||
*/
|
||||
get solution(): string | null {
|
||||
return this.results?.solution ?? null
|
||||
}
|
||||
|
||||
private _entities?: MessageEntity[]
|
||||
/**
|
||||
* Format entities for {@link solution}, only available
|
||||
* in case you have already answered
|
||||
*/
|
||||
get solutionEntities(): MessageEntity[] | null {
|
||||
if (!this.results) return null
|
||||
|
||||
if (!this._entities) {
|
||||
this._entities = []
|
||||
if (this.results.solutionEntities?.length) {
|
||||
for (const ent of this.results.solutionEntities) {
|
||||
const parsed = MessageEntity._parse(ent)
|
||||
if (parsed) this._entities.push(parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._entities
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the solution text formatted with a given parse mode.
|
||||
* Returns `null` if solution is not available
|
||||
*
|
||||
* @param parseMode Parse mode to use (`null` for default)
|
||||
*/
|
||||
unparseSolution(parseMode?: string | null): string | null {
|
||||
if (!this.solution) return null
|
||||
|
||||
return this.client
|
||||
.getParseMode(parseMode)
|
||||
.unparse(this.solution, this.solutionEntities!)
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(Poll)
|
81
packages/client/src/types/media/venue.ts
Normal file
81
packages/client/src/types/media/venue.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { Location } from './location'
|
||||
import { assertTypeIs } from '../../utils/type-assertion'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { TelegramClient } from '../../client'
|
||||
|
||||
export namespace Venue {
|
||||
export interface VenueSource {
|
||||
/**
|
||||
* Provider name (`foursquare` or `gplaces` for Google Places)
|
||||
*/
|
||||
provider?: 'foursquare' | 'gplaces'
|
||||
|
||||
/**
|
||||
* Venue ID in the provider's DB
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* Venue type in the provider's DB
|
||||
*
|
||||
* - [Supported types for Foursquare](https://developer.foursquare.com/docs/build-with-foursquare/categories/)
|
||||
* (use names, lowercase them, replace spaces and " & " with `_` (underscore) and remove other symbols,
|
||||
* and use `/` (slash) as hierarchy separator)
|
||||
* - [Supported types for Google Places](https://developers.google.com/places/web-service/supported_types)
|
||||
*/
|
||||
type: string
|
||||
}
|
||||
}
|
||||
|
||||
export class Venue {
|
||||
readonly client: TelegramClient
|
||||
readonly raw: tl.RawMessageMediaVenue
|
||||
|
||||
constructor (client: TelegramClient, raw: tl.RawMessageMediaVenue) {
|
||||
this.client = client
|
||||
this.raw = raw
|
||||
}
|
||||
|
||||
private _location: Location
|
||||
/**
|
||||
* Geolocation of the venue
|
||||
*/
|
||||
get location(): Location {
|
||||
if (!this._location) {
|
||||
assertTypeIs('Venue#location', this.raw.geo, 'geoPoint')
|
||||
this._location = new Location(this.client, this.raw.geo)
|
||||
}
|
||||
|
||||
return this._location
|
||||
}
|
||||
|
||||
/**
|
||||
* Venue name
|
||||
*/
|
||||
get title(): string {
|
||||
return this.raw.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Venue address
|
||||
*/
|
||||
get address(): string {
|
||||
return this.raw.address
|
||||
}
|
||||
|
||||
/**
|
||||
* When available, source from where this venue was acquired
|
||||
*/
|
||||
get source(): Venue.VenueSource | null {
|
||||
if (!this.raw.provider) return null
|
||||
|
||||
return {
|
||||
provider: this.raw.provider as Venue.VenueSource['provider'],
|
||||
id: this.raw.venueId,
|
||||
type: this.raw.venueType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(Venue)
|
|
@ -7,7 +7,7 @@ import { MessageEntity } from './message-entity'
|
|||
import { Message } from './message'
|
||||
import { InputPeerLike } from '../peers'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { InputMediaLike } from '../media'
|
||||
import { InputMediaWithCaption } from '../media'
|
||||
|
||||
export class DraftMessage {
|
||||
readonly client: TelegramClient
|
||||
|
@ -101,7 +101,7 @@ export class DraftMessage {
|
|||
* @link TelegramClient.sendMedia
|
||||
*/
|
||||
sendWithMedia(
|
||||
media: InputMediaLike,
|
||||
media: InputMediaWithCaption,
|
||||
params?: Parameters<TelegramClient['sendMedia']>[2]
|
||||
): Promise<Message> {
|
||||
if (!media.caption) {
|
||||
|
|
|
@ -21,12 +21,14 @@ import {
|
|||
LiveLocation,
|
||||
Sticker,
|
||||
Voice,
|
||||
InputMediaLike,
|
||||
InputMediaLike, Venue,
|
||||
} from '../media'
|
||||
import { parseDocument } from '../media/document-utils'
|
||||
import { Game } from '../media/game'
|
||||
import { WebPage } from '../media/web-page'
|
||||
import { InputFileLike } from '../files'
|
||||
import { Poll } from '../media/poll'
|
||||
import { Invoice } from '../media/invoice'
|
||||
|
||||
/**
|
||||
* A message or a service message
|
||||
|
@ -208,6 +210,9 @@ export namespace Message {
|
|||
| LiveLocation
|
||||
| Game
|
||||
| WebPage
|
||||
| Venue
|
||||
| Poll
|
||||
| Invoice
|
||||
| null
|
||||
}
|
||||
|
||||
|
@ -655,12 +660,12 @@ export class Message {
|
|||
m._ === 'messageMediaGeo' &&
|
||||
m.geo._ === 'geoPoint'
|
||||
) {
|
||||
media = new Location(m.geo)
|
||||
media = new Location(this.client, m.geo)
|
||||
} else if (
|
||||
m._ === 'messageMediaGeoLive' &&
|
||||
m.geo._ === 'geoPoint'
|
||||
) {
|
||||
media = new LiveLocation(m)
|
||||
media = new LiveLocation(this.client, m)
|
||||
} else if (m._ === 'messageMediaGame') {
|
||||
media = new Game(this.client, m.game)
|
||||
} else if (
|
||||
|
@ -668,6 +673,12 @@ export class Message {
|
|||
m.webpage._ === 'webPage'
|
||||
) {
|
||||
media = new WebPage(this.client, m.webpage)
|
||||
} else if (m._ === 'messageMediaVenue') {
|
||||
media = new Venue(this.client, m)
|
||||
} else if (m._ === 'messageMediaPoll') {
|
||||
media = new Poll(this.client, m.poll, this._users, m.results)
|
||||
} else if (m._ === 'messageMediaInvoice') {
|
||||
media = new Invoice(this.client, m)
|
||||
} else {
|
||||
media = null
|
||||
}
|
||||
|
@ -757,7 +768,6 @@ export class Message {
|
|||
.unparse(this.text, this.entities)
|
||||
}
|
||||
|
||||
// todo: bound methods https://github.com/pyrogram/pyrogram/blob/701c1cde07af779ab18dbf79a3e626f04fa5d5d2/pyrogram/types/messages_and_media/message.py#L737
|
||||
/**
|
||||
* For replies, fetch the message that is being replied.
|
||||
*
|
||||
|
@ -794,30 +804,6 @@ export class Message {
|
|||
return this.client.sendText(this.chat.inputPeer, text, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a photo in reply to this message.
|
||||
*
|
||||
* By default just sends a message to the same chat,
|
||||
* to make the reply a "real" reply, pass `visible=true`
|
||||
*
|
||||
* @param photo Photo to send
|
||||
* @param visible Whether the reply should be visible
|
||||
* @param params
|
||||
*/
|
||||
replyPhoto(
|
||||
photo: InputFileLike,
|
||||
visible = false,
|
||||
params?: Parameters<TelegramClient['sendPhoto']>[2]
|
||||
): ReturnType<TelegramClient['sendPhoto']> {
|
||||
if (visible) {
|
||||
return this.client.sendPhoto(this.chat.inputPeer, photo, {
|
||||
...(params || {}),
|
||||
replyTo: this.id,
|
||||
})
|
||||
}
|
||||
return this.client.sendPhoto(this.chat.inputPeer, photo, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a media in reply to this message.
|
||||
*
|
||||
|
@ -842,66 +828,6 @@ export class Message {
|
|||
return this.client.sendMedia(this.chat.inputPeer, media, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a dice in reply to this message.
|
||||
*
|
||||
* By default just sends a message to the same chat,
|
||||
* to make the reply a "real" reply, pass `visible=true`
|
||||
*
|
||||
* @param emoji Emoji representing a dice to send
|
||||
* @param visible Whether the reply should be visible
|
||||
* @param params
|
||||
*/
|
||||
replyDice(
|
||||
emoji: string,
|
||||
visible = false,
|
||||
params?: Parameters<TelegramClient['sendDice']>[2]
|
||||
): ReturnType<TelegramClient['sendDice']> {
|
||||
if (visible) {
|
||||
return this.client.sendDice(this.chat.inputPeer, emoji, {
|
||||
...(params || {}),
|
||||
replyTo: this.id,
|
||||
})
|
||||
}
|
||||
return this.client.sendDice(this.chat.inputPeer, emoji, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a static geo location in reply to this message.
|
||||
*
|
||||
* By default just sends a message to the same chat,
|
||||
* to make the reply a "real" reply, pass `visible=true`
|
||||
*
|
||||
* @param latitude Latitude of the location
|
||||
* @param longitude Longitude of the location
|
||||
* @param visible Whether the reply should be visible
|
||||
* @param params
|
||||
*/
|
||||
replyLocation(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
visible = false,
|
||||
params?: Parameters<TelegramClient['sendLocation']>[3]
|
||||
): ReturnType<TelegramClient['sendLocation']> {
|
||||
if (visible) {
|
||||
return this.client.sendLocation(
|
||||
this.chat.inputPeer,
|
||||
latitude,
|
||||
longitude,
|
||||
{
|
||||
...(params || {}),
|
||||
replyTo: this.id,
|
||||
}
|
||||
)
|
||||
}
|
||||
return this.client.sendLocation(
|
||||
this.chat.inputPeer,
|
||||
latitude,
|
||||
longitude,
|
||||
params
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this message.
|
||||
*
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
RawDocument,
|
||||
Sticker,
|
||||
TelegramClient,
|
||||
User,
|
||||
User, Venue,
|
||||
Video,
|
||||
Voice,
|
||||
} from '@mtcute/client'
|
||||
|
@ -23,6 +23,8 @@ import { WebPage } from '@mtcute/client/src/types/media/web-page'
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
import { ChatMemberUpdate } from './updates'
|
||||
import { ChosenInlineResult } from './updates/chosen-inline-result'
|
||||
import { Poll } from '@mtcute/client/src/types/media/poll'
|
||||
import { Invoice } from '@mtcute/client/src/types/media/invoice'
|
||||
|
||||
/**
|
||||
* Type describing a primitive filter, which is a function taking some `Base`
|
||||
|
@ -482,7 +484,23 @@ export namespace filters {
|
|||
export const webpage: UpdateFilter<Message, { media: WebPage }> = (msg) =>
|
||||
msg.media instanceof WebPage
|
||||
|
||||
// todo: more filters, see https://github.com/pyrogram/pyrogram/blob/701c1cde07af779ab18dbf79a3e626f04fa5d5d2/pyrogram/filters.py#L191
|
||||
/**
|
||||
* Filter messages containing a venue.
|
||||
*/
|
||||
export const venue: UpdateFilter<Message, { media: Venue }> = (msg) =>
|
||||
msg.media instanceof Venue
|
||||
|
||||
/**
|
||||
* Filter messages containing a poll.
|
||||
*/
|
||||
export const poll: UpdateFilter<Message, { media: Poll }> = (msg) =>
|
||||
msg.media instanceof Poll
|
||||
|
||||
/**
|
||||
* Filter messages containing an invoice.
|
||||
*/
|
||||
export const invoice: UpdateFilter<Message, { media: Invoice }> = (msg) =>
|
||||
msg.media instanceof Invoice
|
||||
|
||||
/**
|
||||
* Filter objects that match a given regular expression
|
||||
|
|
|
@ -66,7 +66,7 @@ export class ChosenInlineResult {
|
|||
if (this.raw.geo?._ !== 'geoPoint') return null
|
||||
|
||||
if (!this._location) {
|
||||
this._location = new Location(this.raw.geo)
|
||||
this._location = new Location(this.client, this.raw.geo)
|
||||
}
|
||||
|
||||
return this._location
|
||||
|
|
Loading…
Reference in a new issue