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 { pinMessage } from './methods/messages/pin-message'
|
||||||
import { searchGlobal } from './methods/messages/search-global'
|
import { searchGlobal } from './methods/messages/search-global'
|
||||||
import { searchMessages } from './methods/messages/search-messages'
|
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 { sendMediaGroup } from './methods/messages/send-media-group'
|
||||||
import { sendMedia } from './methods/messages/send-media'
|
import { sendMedia } from './methods/messages/send-media'
|
||||||
import { sendText } from './methods/messages/send-text'
|
import { sendText } from './methods/messages/send-text'
|
||||||
|
@ -1662,83 +1660,6 @@ export interface TelegramClient extends BaseTelegramClient {
|
||||||
chunkSize?: number
|
chunkSize?: number
|
||||||
}
|
}
|
||||||
): AsyncIterableIterator<Message>
|
): 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.
|
* Send a group of media.
|
||||||
*
|
*
|
||||||
|
@ -2322,8 +2243,6 @@ export class TelegramClient extends BaseTelegramClient {
|
||||||
pinMessage = pinMessage
|
pinMessage = pinMessage
|
||||||
searchGlobal = searchGlobal
|
searchGlobal = searchGlobal
|
||||||
searchMessages = searchMessages
|
searchMessages = searchMessages
|
||||||
sendDice = sendDice
|
|
||||||
sendLocation = sendLocation
|
|
||||||
sendMediaGroup = sendMediaGroup
|
sendMediaGroup = sendMediaGroup
|
||||||
sendMedia = sendMedia
|
sendMedia = sendMedia
|
||||||
sendText = sendText
|
sendText = sendText
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
FileDownloadParameters,
|
FileDownloadParameters,
|
||||||
FileLocation,
|
FileLocation,
|
||||||
} from '../../types'
|
} 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
|
* 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 dcId = params.dcId
|
||||||
let fileSize = params.fileSize
|
let fileSize = params.fileSize
|
||||||
|
|
||||||
let location = params.location
|
const input = params.location
|
||||||
if (location instanceof FileLocation) {
|
let location: tl.TypeInputFileLocation | tl.TypeInputWebFileLocation
|
||||||
if (typeof location.location === 'function') {
|
if (input instanceof FileLocation) {
|
||||||
;(location as tl.Mutable<FileLocation>).location = location.location()
|
if (typeof input.location === 'function') {
|
||||||
|
;(input as tl.Mutable<FileLocation>).location = input.location()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.location instanceof Buffer) {
|
if (input.location instanceof Buffer) {
|
||||||
yield location.location
|
yield input.location
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!dcId) dcId = location.dcId
|
if (!dcId) dcId = input.dcId
|
||||||
if (!fileSize) fileSize = location.fileSize
|
if (!fileSize) fileSize = input.fileSize
|
||||||
location = location.location as any
|
location = input.location as any
|
||||||
}
|
} else if (typeof input === 'string') {
|
||||||
if (typeof location === 'string') {
|
const parsed = parseFileId(input)
|
||||||
location = fileIdToInputFileLocation(location)
|
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
|
// we will receive a FileMigrateError in case this is invalid
|
||||||
if (!dcId) dcId = this._primaryDc.id
|
if (!dcId) dcId = this._primaryDc.id
|
||||||
|
|
||||||
const chunkSize = partSizeKb * 1024
|
const chunkSize = partSizeKb * 1024
|
||||||
|
|
||||||
const limit =
|
let limit =
|
||||||
params.limit ??
|
params.limit ??
|
||||||
(fileSize
|
(fileSize
|
||||||
? // derive limit from chunk size, file size and offset
|
? // derive limit from chunk size, file size and offset
|
||||||
|
@ -77,13 +84,13 @@ export async function* downloadAsIterable(
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestCurrent = async (): Promise<Buffer> => {
|
const requestCurrent = async (): Promise<Buffer> => {
|
||||||
let result: tl.RpcCallReturn['upload.getFile']
|
let result: tl.RpcCallReturn['upload.getFile'] | tl.RpcCallReturn['upload.getWebFile']
|
||||||
try {
|
try {
|
||||||
result = await connection.sendForResult({
|
result = await connection.sendForResult({
|
||||||
_: 'upload.getFile',
|
_: isWeb ? 'upload.getWebFile' : 'upload.getFile',
|
||||||
location: location as tl.TypeInputFileLocation,
|
location: location as any,
|
||||||
offset,
|
offset,
|
||||||
limit: chunkSize,
|
limit: chunkSize
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof FileMigrateError) {
|
if (e instanceof FileMigrateError) {
|
||||||
|
@ -94,18 +101,25 @@ export async function* downloadAsIterable(
|
||||||
}
|
}
|
||||||
return requestCurrent()
|
return requestCurrent()
|
||||||
} else if (e instanceof FilerefUpgradeNeededError) {
|
} 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
|
// see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99
|
||||||
throw new MtCuteUnsupportedError('File ref expired!')
|
throw new MtCuteUnsupportedError('File ref expired!')
|
||||||
} else throw e
|
} else throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result._ === 'upload.fileCdnRedirect') {
|
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(
|
throw new MtCuteUnsupportedError(
|
||||||
'Received CDN redirect, which is not supported (yet)'
|
'Received CDN redirect, which is not supported (yet)'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result._ === 'upload.webFile' && result.size && limit === Infinity) {
|
||||||
|
limit = result.size
|
||||||
|
}
|
||||||
|
|
||||||
return result.bytes
|
return result.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ import {
|
||||||
} from '@mtcute/file-id'
|
} from '@mtcute/file-id'
|
||||||
import { extractFileName } from '../../utils/file-utils'
|
import { extractFileName } from '../../utils/file-utils'
|
||||||
import { assertTypeIs } from '../../utils/type-assertion'
|
import { assertTypeIs } from '../../utils/type-assertion'
|
||||||
|
import bigInt from 'big-integer'
|
||||||
|
import { normalizeDate } from '../../utils/misc-utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize an {@link InputMediaLike} to `InputMedia`,
|
* Normalize an {@link InputMediaLike} to `InputMedia`,
|
||||||
|
@ -25,6 +27,7 @@ export async function _normalizeInputMedia(
|
||||||
this: TelegramClient,
|
this: TelegramClient,
|
||||||
media: InputMediaLike,
|
media: InputMediaLike,
|
||||||
params: {
|
params: {
|
||||||
|
parseMode?: string | null
|
||||||
progressCallback?: (uploaded: number, total: number) => void
|
progressCallback?: (uploaded: number, total: number) => void
|
||||||
},
|
},
|
||||||
uploadMedia = false
|
uploadMedia = false
|
||||||
|
@ -35,6 +38,160 @@ export async function _normalizeInputMedia(
|
||||||
|
|
||||||
if (tl.isAnyInputMedia(media)) return media
|
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 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'
|
||||||
|
@ -56,18 +213,29 @@ export async function _normalizeInputMedia(
|
||||||
mime = uploaded.mime
|
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
|
if (!uploadMedia) return inputMedia
|
||||||
|
|
||||||
const res = await this.call({
|
const res = await this.call({
|
||||||
_: 'messages.uploadMedia',
|
_: 'messages.uploadMedia',
|
||||||
peer: { _: 'inputPeerSelf' },
|
peer: { _: 'inputPeerSelf' },
|
||||||
media: inputMedia
|
media: inputMedia,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (photo) {
|
if (photo) {
|
||||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res, 'messageMediaPhoto')
|
assertTypeIs(
|
||||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res.photo!, 'photo')
|
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||||
|
res,
|
||||||
|
'messageMediaPhoto'
|
||||||
|
)
|
||||||
|
assertTypeIs(
|
||||||
|
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||||
|
res.photo!,
|
||||||
|
'photo'
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_: 'inputMediaPhoto',
|
_: 'inputMediaPhoto',
|
||||||
|
@ -75,13 +243,21 @@ export async function _normalizeInputMedia(
|
||||||
_: 'inputPhoto',
|
_: 'inputPhoto',
|
||||||
id: res.photo.id,
|
id: res.photo.id,
|
||||||
accessHash: res.photo.accessHash,
|
accessHash: res.photo.accessHash,
|
||||||
fileReference: res.photo.fileReference
|
fileReference: res.photo.fileReference,
|
||||||
},
|
},
|
||||||
ttlSeconds: media.ttlSeconds
|
ttlSeconds: media.ttlSeconds,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res, 'messageMediaDocument')
|
assertTypeIs(
|
||||||
assertTypeIs('normalizeInputMedia (@ messages.uploadMedia)', res.document!, 'document')
|
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||||
|
res,
|
||||||
|
'messageMediaDocument'
|
||||||
|
)
|
||||||
|
assertTypeIs(
|
||||||
|
'normalizeInputMedia (@ messages.uploadMedia)',
|
||||||
|
res.document!,
|
||||||
|
'document'
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_: 'inputMediaDocument',
|
_: 'inputMediaDocument',
|
||||||
|
@ -89,9 +265,9 @@ export async function _normalizeInputMedia(
|
||||||
_: 'inputDocument',
|
_: 'inputDocument',
|
||||||
id: res.document.id,
|
id: res.document.id,
|
||||||
accessHash: res.document.accessHash,
|
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
|
const input = media.file
|
||||||
if (tdFileId.isFileIdLike(input)) {
|
if (tdFileId.isFileIdLike(input)) {
|
||||||
if (typeof input === 'string' && input.match(/^https?:\/\//)) {
|
if (typeof input === 'string' && input.match(/^https?:\/\//)) {
|
||||||
return uploadMediaIfNeeded({
|
return uploadMediaIfNeeded(
|
||||||
|
{
|
||||||
_:
|
_:
|
||||||
media.type === 'photo'
|
media.type === 'photo'
|
||||||
? 'inputMediaPhotoExternal'
|
? 'inputMediaPhotoExternal'
|
||||||
: 'inputMediaDocumentExternal',
|
: 'inputMediaDocumentExternal',
|
||||||
url: input,
|
url: input,
|
||||||
}, media.type === 'photo')
|
},
|
||||||
|
media.type === 'photo'
|
||||||
|
)
|
||||||
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
||||||
await upload(input.substr(5))
|
await upload(input.substr(5))
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,13 +297,16 @@ export async function _normalizeInputMedia(
|
||||||
id: fileIdToInputPhoto(parsed),
|
id: fileIdToInputPhoto(parsed),
|
||||||
}
|
}
|
||||||
} else if (parsed.location._ === 'web') {
|
} else if (parsed.location._ === 'web') {
|
||||||
return uploadMediaIfNeeded({
|
return uploadMediaIfNeeded(
|
||||||
|
{
|
||||||
_:
|
_:
|
||||||
parsed.type === tdFileId.FileType.Photo
|
parsed.type === tdFileId.FileType.Photo
|
||||||
? 'inputMediaPhotoExternal'
|
? 'inputMediaPhotoExternal'
|
||||||
: 'inputMediaDocumentExternal',
|
: 'inputMediaDocumentExternal',
|
||||||
url: parsed.location.url,
|
url: parsed.location.url,
|
||||||
}, parsed.type === tdFileId.FileType.Photo)
|
},
|
||||||
|
parsed.type === tdFileId.FileType.Photo
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
_: 'inputMediaDocument',
|
_: 'inputMediaDocument',
|
||||||
|
@ -146,11 +328,14 @@ export async function _normalizeInputMedia(
|
||||||
if (!inputFile) throw new Error('should not happen')
|
if (!inputFile) throw new Error('should not happen')
|
||||||
|
|
||||||
if (media.type === 'photo') {
|
if (media.type === 'photo') {
|
||||||
return uploadMediaIfNeeded({
|
return uploadMediaIfNeeded(
|
||||||
|
{
|
||||||
_: 'inputMediaUploadedPhoto',
|
_: 'inputMediaUploadedPhoto',
|
||||||
file: inputFile,
|
file: inputFile,
|
||||||
ttlSeconds: media.ttlSeconds,
|
ttlSeconds: media.ttlSeconds,
|
||||||
}, true)
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('thumb' in media && media.thumb) {
|
if ('thumb' in media && media.thumb) {
|
||||||
|
@ -204,7 +389,8 @@ export async function _normalizeInputMedia(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadMediaIfNeeded({
|
return uploadMediaIfNeeded(
|
||||||
|
{
|
||||||
_: 'inputMediaUploadedDocument',
|
_: 'inputMediaUploadedDocument',
|
||||||
nosoundVideo: media.type === 'video' && media.isAnimated,
|
nosoundVideo: media.type === 'video' && media.isAnimated,
|
||||||
forceFile: media.type === 'document',
|
forceFile: media.type === 'document',
|
||||||
|
@ -212,6 +398,8 @@ export async function _normalizeInputMedia(
|
||||||
thumb,
|
thumb,
|
||||||
mimeType: mime,
|
mimeType: mime,
|
||||||
attributes,
|
attributes,
|
||||||
ttlSeconds: media.ttlSeconds
|
ttlSeconds: media.ttlSeconds,
|
||||||
}, false)
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,10 @@ export async function editInlineMessage(
|
||||||
|
|
||||||
if (params.media) {
|
if (params.media) {
|
||||||
media = await this._normalizeInputMedia(params.media, params, true)
|
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(
|
;[content, entities] = await this._parseEntities(
|
||||||
params.media.caption,
|
params.media.caption,
|
||||||
params.parseMode,
|
params.parseMode,
|
||||||
|
|
|
@ -84,11 +84,16 @@ export async function editMessage(
|
||||||
|
|
||||||
if (params.media) {
|
if (params.media) {
|
||||||
media = await this._normalizeInputMedia(params.media, params)
|
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(
|
;[content, entities] = await this._parseEntities(
|
||||||
params.media.caption,
|
params.media.caption,
|
||||||
params.parseMode,
|
params.parseMode,
|
||||||
params.media.entities
|
params.media.entities
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
;[content, entities] = await this._parseEntities(
|
;[content, entities] = await this._parseEntities(
|
||||||
params.text,
|
params.text,
|
||||||
|
@ -105,7 +110,7 @@ export async function editMessage(
|
||||||
replyMarkup: BotKeyboard._convertToTl(params.replyMarkup),
|
replyMarkup: BotKeyboard._convertToTl(params.replyMarkup),
|
||||||
message: content,
|
message: content,
|
||||||
entities,
|
entities,
|
||||||
media
|
media,
|
||||||
})
|
})
|
||||||
|
|
||||||
return this._findMessageInUpdate(res, true) as any
|
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 uploaded Number of bytes already uploaded
|
||||||
* @param total Total file size
|
* @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.
|
* Whether to clear draft after sending this message.
|
||||||
|
@ -83,13 +87,16 @@ export async function sendMediaGroup(
|
||||||
for (let i = 0; i < medias.length; i++) {
|
for (let i = 0; i < medias.length; i++) {
|
||||||
const media = medias[i]
|
const media = medias[i]
|
||||||
const inputMedia = await this._normalizeInputMedia(media, {
|
const inputMedia = await this._normalizeInputMedia(media, {
|
||||||
progressCallback: params.progressCallback?.bind(null, i)
|
progressCallback: params.progressCallback?.bind(null, i),
|
||||||
})
|
})
|
||||||
|
|
||||||
const [message, entities] = await this._parseEntities(
|
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,
|
params.parseMode,
|
||||||
media.entities
|
(media as any).entities
|
||||||
)
|
)
|
||||||
|
|
||||||
multiMedia.push({
|
multiMedia.push({
|
||||||
|
@ -97,7 +104,7 @@ export async function sendMediaGroup(
|
||||||
randomId: randomUlong(),
|
randomId: randomUlong(),
|
||||||
media: inputMedia,
|
media: inputMedia,
|
||||||
message,
|
message,
|
||||||
entities
|
entities,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,9 +86,12 @@ export async function sendMedia(
|
||||||
const inputMedia = await this._normalizeInputMedia(media, params)
|
const inputMedia = await this._normalizeInputMedia(media, params)
|
||||||
|
|
||||||
const [message, entities] = await this._parseEntities(
|
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,
|
params.parseMode,
|
||||||
media.entities
|
(media as any).entities
|
||||||
)
|
)
|
||||||
|
|
||||||
const peer = normalizeToInputPeer(await this.resolvePeer(chatId))
|
const peer = normalizeToInputPeer(await this.resolvePeer(chatId))
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { BotKeyboard, ReplyMarkup } from '../keyboards'
|
import { BotKeyboard, ReplyMarkup } from '../keyboards'
|
||||||
import { TelegramClient } from '../../../client'
|
import { TelegramClient } from '../../../client'
|
||||||
|
import {
|
||||||
|
InputMediaGeo,
|
||||||
|
InputMediaGeoLive,
|
||||||
|
InputMediaVenue,
|
||||||
|
Venue,
|
||||||
|
} from '../../media'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline message containing only text
|
* Inline message containing only text
|
||||||
|
@ -57,37 +63,17 @@ export interface InputInlineMessageMedia {
|
||||||
/**
|
/**
|
||||||
* Inline message containing a geolocation
|
* Inline message containing a geolocation
|
||||||
*/
|
*/
|
||||||
export interface InputInlineMessageGeo {
|
export interface InputInlineMessageGeo extends InputMediaGeo {
|
||||||
type: 'geo'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Latitude of the geolocation
|
* Message's reply markup
|
||||||
*/
|
*/
|
||||||
latitude: number
|
replyMarkup?: ReplyMarkup
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Longitude of the geolocation
|
* Inline message containing a live geolocation
|
||||||
*/
|
*/
|
||||||
longitude: number
|
export interface InputInlineMessageGeoLive extends InputMediaGeoLive {
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message's reply markup
|
* Message's reply markup
|
||||||
*/
|
*/
|
||||||
|
@ -97,54 +83,7 @@ export interface InputInlineMessageGeo {
|
||||||
/**
|
/**
|
||||||
* Inline message containing a venue
|
* Inline message containing a venue
|
||||||
*/
|
*/
|
||||||
export interface InputInlineMessageVenue {
|
export interface InputInlineMessageVenue extends 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?: {
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message's reply markup
|
* Message's reply markup
|
||||||
*/
|
*/
|
||||||
|
@ -167,51 +106,62 @@ export type InputInlineMessage =
|
||||||
| InputInlineMessageText
|
| InputInlineMessageText
|
||||||
| InputInlineMessageMedia
|
| InputInlineMessageMedia
|
||||||
| InputInlineMessageGeo
|
| InputInlineMessageGeo
|
||||||
|
| InputInlineMessageGeoLive
|
||||||
| InputInlineMessageVenue
|
| InputInlineMessageVenue
|
||||||
| InputInlineMessageGame
|
| InputInlineMessageGame
|
||||||
|
|
||||||
export namespace BotInlineMessage {
|
export namespace BotInlineMessage {
|
||||||
export function text (
|
export function text(
|
||||||
text: string,
|
text: string,
|
||||||
params?: Omit<InputInlineMessageText, 'type' | 'text'>,
|
params?: Omit<InputInlineMessageText, 'type' | 'text'>
|
||||||
): InputInlineMessageText {
|
): InputInlineMessageText {
|
||||||
return {
|
return {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text,
|
text,
|
||||||
...(
|
...(params || {}),
|
||||||
params || {}
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function media (
|
export function media(
|
||||||
params?: Omit<InputInlineMessageMedia, 'type'>,
|
params?: Omit<InputInlineMessageMedia, 'type'>
|
||||||
): InputInlineMessageMedia {
|
): InputInlineMessageMedia {
|
||||||
return {
|
return {
|
||||||
type: 'media',
|
type: 'media',
|
||||||
...(
|
...(params || {}),
|
||||||
params || {}
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function geo (
|
export function geo(
|
||||||
latitude: number,
|
latitude: number,
|
||||||
longitude: number,
|
longitude: number,
|
||||||
params?: Omit<InputInlineMessageGeo, 'type' | 'latitude' | 'longitude'>,
|
params?: Omit<InputInlineMessageGeo, 'type' | 'latitude' | 'longitude'>
|
||||||
): InputInlineMessageGeo {
|
): InputInlineMessageGeo {
|
||||||
return {
|
return {
|
||||||
type: 'geo',
|
type: 'geo',
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
...(
|
...(params || {}),
|
||||||
params || {}
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function venue (
|
export function geoLive(
|
||||||
params: Omit<InputInlineMessageVenue, 'type'>,
|
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 {
|
): InputInlineMessageVenue {
|
||||||
return {
|
return {
|
||||||
type: 'venue',
|
type: 'venue',
|
||||||
|
@ -219,8 +169,8 @@ export namespace BotInlineMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function game (
|
export function game(
|
||||||
params: Omit<InputInlineMessageGame, 'type'>,
|
params: Omit<InputInlineMessageGame, 'type'>
|
||||||
): InputInlineMessageGame {
|
): InputInlineMessageGame {
|
||||||
return {
|
return {
|
||||||
type: 'game',
|
type: 'game',
|
||||||
|
@ -228,45 +178,55 @@ export namespace BotInlineMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function _convertToTl (
|
export async function _convertToTl(
|
||||||
client: TelegramClient,
|
client: TelegramClient,
|
||||||
obj: InputInlineMessage,
|
obj: InputInlineMessage,
|
||||||
parseMode?: string | null,
|
parseMode?: string | null
|
||||||
): Promise<tl.TypeInputBotInlineMessage> {
|
): Promise<tl.TypeInputBotInlineMessage> {
|
||||||
if (obj.type === 'text') {
|
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 {
|
return {
|
||||||
_: 'inputBotInlineMessageText',
|
_: 'inputBotInlineMessageText',
|
||||||
message,
|
message,
|
||||||
entities,
|
entities,
|
||||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.type === 'media') {
|
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 {
|
return {
|
||||||
_: 'inputBotInlineMessageMediaAuto',
|
_: 'inputBotInlineMessageMediaAuto',
|
||||||
message,
|
message,
|
||||||
entities,
|
entities,
|
||||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.type === 'geo') {
|
if (obj.type === 'geo' || obj.type === 'geo_live') {
|
||||||
return {
|
return {
|
||||||
_: 'inputBotInlineMessageMediaGeo',
|
_: 'inputBotInlineMessageMediaGeo',
|
||||||
geoPoint: {
|
geoPoint: {
|
||||||
_: 'inputGeoPoint',
|
_: 'inputGeoPoint',
|
||||||
lat: obj.latitude,
|
lat: obj.latitude,
|
||||||
long: obj.longitude
|
long: obj.longitude,
|
||||||
},
|
},
|
||||||
heading: obj.heading,
|
// fields will be `undefined` if this is a `geo`
|
||||||
period: obj.period,
|
heading: (obj as InputMediaGeoLive).heading,
|
||||||
proximityNotificationRadius: obj.proximityNotificationRadius,
|
period: (obj as InputMediaGeoLive).period,
|
||||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
proximityNotificationRadius: (obj as InputMediaGeoLive)
|
||||||
|
.proximityNotificationRadius,
|
||||||
|
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,21 +236,21 @@ export namespace BotInlineMessage {
|
||||||
geoPoint: {
|
geoPoint: {
|
||||||
_: 'inputGeoPoint',
|
_: 'inputGeoPoint',
|
||||||
lat: obj.latitude,
|
lat: obj.latitude,
|
||||||
long: obj.longitude
|
long: obj.longitude,
|
||||||
},
|
},
|
||||||
title: obj.title,
|
title: obj.title,
|
||||||
address: obj.address,
|
address: obj.address,
|
||||||
provider: obj.source?.provider ?? '',
|
provider: obj.source?.provider ?? '',
|
||||||
venueId: obj.source?.id ?? '',
|
venueId: obj.source?.id ?? '',
|
||||||
venueType: obj.source?.type ?? '',
|
venueType: obj.source?.type ?? '',
|
||||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.type === 'game') {
|
if (obj.type === 'game') {
|
||||||
return {
|
return {
|
||||||
_: 'inputBotInlineMessageGame',
|
_: 'inputBotInlineMessageGame',
|
||||||
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup)
|
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,9 @@ export class FileLocation {
|
||||||
*/
|
*/
|
||||||
readonly location:
|
readonly location:
|
||||||
| tl.TypeInputFileLocation
|
| tl.TypeInputFileLocation
|
||||||
|
| tl.TypeInputWebFileLocation
|
||||||
| Buffer
|
| Buffer
|
||||||
| (() => tl.TypeInputFileLocation | Buffer)
|
| (() => tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | Buffer)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File size in bytes, when available
|
* File size in bytes, when available
|
||||||
|
@ -44,8 +45,9 @@ export class FileLocation {
|
||||||
client: TelegramClient,
|
client: TelegramClient,
|
||||||
location:
|
location:
|
||||||
| tl.TypeInputFileLocation
|
| tl.TypeInputFileLocation
|
||||||
|
| tl.TypeInputWebFileLocation
|
||||||
| Buffer
|
| Buffer
|
||||||
| (() => tl.TypeInputFileLocation | Buffer),
|
| (() => tl.TypeInputFileLocation | tl.TypeInputWebFileLocation | Buffer),
|
||||||
fileSize?: number,
|
fileSize?: number,
|
||||||
dcId?: number
|
dcId?: number
|
||||||
) {
|
) {
|
||||||
|
@ -82,7 +84,7 @@ export class FileLocation {
|
||||||
* in chunks of a given size. Order of the chunks is guaranteed to be
|
* in chunks of a given size. Order of the chunks is guaranteed to be
|
||||||
* consecutive.
|
* consecutive.
|
||||||
*
|
*
|
||||||
* Shorthand for `client.downloadAsStream({ location: this })`
|
* Shorthand for `client.downloadAsIterable({ location: this })`
|
||||||
*
|
*
|
||||||
* @link TelegramClient.downloadAsIterable
|
* @link TelegramClient.downloadAsIterable
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -52,7 +52,7 @@ export interface FileDownloadParameters {
|
||||||
* File location which should be downloaded.
|
* File location which should be downloaded.
|
||||||
* You can also provide TDLib and Bot API compatible File ID
|
* 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.
|
* 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 './voice'
|
||||||
export * from './sticker'
|
export * from './sticker'
|
||||||
export * from './input-media'
|
export * from './input-media'
|
||||||
|
export * from './venue'
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { InputFileLike } from '../files'
|
import { InputFileLike } from '../files'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { Venue } from './venue'
|
||||||
|
import { MaybeArray } from '@mtcute/core'
|
||||||
|
|
||||||
interface BaseInputMedia {
|
interface BaseInputMedia {
|
||||||
/**
|
/**
|
||||||
|
@ -233,6 +235,303 @@ export interface InputMediaVideo extends BaseInputMedia {
|
||||||
isRound?: boolean
|
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.
|
* Input media that can be sent somewhere.
|
||||||
*
|
*
|
||||||
|
@ -243,17 +542,24 @@ export interface InputMediaVideo extends BaseInputMedia {
|
||||||
* @link InputMedia
|
* @link InputMedia
|
||||||
*/
|
*/
|
||||||
export type InputMediaLike =
|
export type InputMediaLike =
|
||||||
| InputMediaAudio
|
| InputMediaWithCaption
|
||||||
| InputMediaVoice
|
|
||||||
| InputMediaDocument
|
|
||||||
| InputMediaPhoto
|
|
||||||
| InputMediaVideo
|
|
||||||
| InputMediaAuto
|
|
||||||
| InputMediaSticker
|
| InputMediaSticker
|
||||||
|
| InputMediaVenue
|
||||||
|
| InputMediaGeo
|
||||||
|
| InputMediaGeoLive
|
||||||
|
| InputMediaDice
|
||||||
|
| InputMediaContact
|
||||||
|
| InputMediaGame
|
||||||
|
| InputMediaInvoice
|
||||||
|
| InputMediaPoll
|
||||||
|
| InputMediaQuiz
|
||||||
| tl.TypeInputMedia
|
| tl.TypeInputMedia
|
||||||
|
|
||||||
export namespace InputMedia {
|
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
|
* 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
|
* Create a document to be sent, which subtype
|
||||||
* is inferred automatically by file contents.
|
* 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 { makeInspectable } from '../utils'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { FileLocation } from '../files'
|
||||||
|
import { TelegramClient } from '../../client'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A point on the map
|
* A point on the map
|
||||||
*/
|
*/
|
||||||
export class Location {
|
export class Location {
|
||||||
|
readonly client: TelegramClient
|
||||||
readonly geo: tl.RawGeoPoint
|
readonly geo: tl.RawGeoPoint
|
||||||
|
|
||||||
constructor(geo: tl.RawGeoPoint) {
|
constructor(client: TelegramClient, geo: tl.RawGeoPoint) {
|
||||||
|
this.client = client
|
||||||
this.geo = geo
|
this.geo = geo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +35,62 @@ export class Location {
|
||||||
get radius(): number {
|
get radius(): number {
|
||||||
return this.geo.accuracyRadius ?? 0
|
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 {
|
export class LiveLocation extends Location {
|
||||||
readonly live: tl.RawMessageMediaGeoLive
|
readonly live: tl.RawMessageMediaGeoLive
|
||||||
|
|
||||||
constructor(live: tl.RawMessageMediaGeoLive) {
|
constructor(client: TelegramClient, live: tl.RawMessageMediaGeoLive) {
|
||||||
super(live.geo as tl.RawGeoPoint)
|
super(client, live.geo as tl.RawGeoPoint)
|
||||||
this.live = live
|
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 { Message } from './message'
|
||||||
import { InputPeerLike } from '../peers'
|
import { InputPeerLike } from '../peers'
|
||||||
import { makeInspectable } from '../utils'
|
import { makeInspectable } from '../utils'
|
||||||
import { InputMediaLike } from '../media'
|
import { InputMediaWithCaption } from '../media'
|
||||||
|
|
||||||
export class DraftMessage {
|
export class DraftMessage {
|
||||||
readonly client: TelegramClient
|
readonly client: TelegramClient
|
||||||
|
@ -101,7 +101,7 @@ export class DraftMessage {
|
||||||
* @link TelegramClient.sendMedia
|
* @link TelegramClient.sendMedia
|
||||||
*/
|
*/
|
||||||
sendWithMedia(
|
sendWithMedia(
|
||||||
media: InputMediaLike,
|
media: InputMediaWithCaption,
|
||||||
params?: Parameters<TelegramClient['sendMedia']>[2]
|
params?: Parameters<TelegramClient['sendMedia']>[2]
|
||||||
): Promise<Message> {
|
): Promise<Message> {
|
||||||
if (!media.caption) {
|
if (!media.caption) {
|
||||||
|
|
|
@ -21,12 +21,14 @@ import {
|
||||||
LiveLocation,
|
LiveLocation,
|
||||||
Sticker,
|
Sticker,
|
||||||
Voice,
|
Voice,
|
||||||
InputMediaLike,
|
InputMediaLike, Venue,
|
||||||
} from '../media'
|
} from '../media'
|
||||||
import { parseDocument } from '../media/document-utils'
|
import { parseDocument } from '../media/document-utils'
|
||||||
import { Game } from '../media/game'
|
import { Game } from '../media/game'
|
||||||
import { WebPage } from '../media/web-page'
|
import { WebPage } from '../media/web-page'
|
||||||
import { InputFileLike } from '../files'
|
import { InputFileLike } from '../files'
|
||||||
|
import { Poll } from '../media/poll'
|
||||||
|
import { Invoice } from '../media/invoice'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message or a service message
|
* A message or a service message
|
||||||
|
@ -208,6 +210,9 @@ export namespace Message {
|
||||||
| LiveLocation
|
| LiveLocation
|
||||||
| Game
|
| Game
|
||||||
| WebPage
|
| WebPage
|
||||||
|
| Venue
|
||||||
|
| Poll
|
||||||
|
| Invoice
|
||||||
| null
|
| null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,12 +660,12 @@ export class Message {
|
||||||
m._ === 'messageMediaGeo' &&
|
m._ === 'messageMediaGeo' &&
|
||||||
m.geo._ === 'geoPoint'
|
m.geo._ === 'geoPoint'
|
||||||
) {
|
) {
|
||||||
media = new Location(m.geo)
|
media = new Location(this.client, m.geo)
|
||||||
} else if (
|
} else if (
|
||||||
m._ === 'messageMediaGeoLive' &&
|
m._ === 'messageMediaGeoLive' &&
|
||||||
m.geo._ === 'geoPoint'
|
m.geo._ === 'geoPoint'
|
||||||
) {
|
) {
|
||||||
media = new LiveLocation(m)
|
media = new LiveLocation(this.client, m)
|
||||||
} else if (m._ === 'messageMediaGame') {
|
} else if (m._ === 'messageMediaGame') {
|
||||||
media = new Game(this.client, m.game)
|
media = new Game(this.client, m.game)
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -668,6 +673,12 @@ export class Message {
|
||||||
m.webpage._ === 'webPage'
|
m.webpage._ === 'webPage'
|
||||||
) {
|
) {
|
||||||
media = new WebPage(this.client, m.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 {
|
} else {
|
||||||
media = null
|
media = null
|
||||||
}
|
}
|
||||||
|
@ -757,7 +768,6 @@ export class Message {
|
||||||
.unparse(this.text, this.entities)
|
.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.
|
* 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)
|
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.
|
* Send a media in reply to this message.
|
||||||
*
|
*
|
||||||
|
@ -842,66 +828,6 @@ export class Message {
|
||||||
return this.client.sendMedia(this.chat.inputPeer, media, params)
|
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.
|
* Delete this message.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
RawDocument,
|
RawDocument,
|
||||||
Sticker,
|
Sticker,
|
||||||
TelegramClient,
|
TelegramClient,
|
||||||
User,
|
User, Venue,
|
||||||
Video,
|
Video,
|
||||||
Voice,
|
Voice,
|
||||||
} from '@mtcute/client'
|
} from '@mtcute/client'
|
||||||
|
@ -23,6 +23,8 @@ import { WebPage } from '@mtcute/client/src/types/media/web-page'
|
||||||
import { MaybeArray } from '@mtcute/core'
|
import { MaybeArray } from '@mtcute/core'
|
||||||
import { ChatMemberUpdate } from './updates'
|
import { ChatMemberUpdate } from './updates'
|
||||||
import { ChosenInlineResult } from './updates/chosen-inline-result'
|
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`
|
* 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) =>
|
export const webpage: UpdateFilter<Message, { media: WebPage }> = (msg) =>
|
||||||
msg.media instanceof WebPage
|
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
|
* Filter objects that match a given regular expression
|
||||||
|
|
|
@ -66,7 +66,7 @@ export class ChosenInlineResult {
|
||||||
if (this.raw.geo?._ !== 'geoPoint') return null
|
if (this.raw.geo?._ !== 'geoPoint') return null
|
||||||
|
|
||||||
if (!this._location) {
|
if (!this._location) {
|
||||||
this._location = new Location(this.raw.geo)
|
this._location = new Location(this.client, this.raw.geo)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._location
|
return this._location
|
||||||
|
|
Loading…
Reference in a new issue