chore: avoid using namespaces in favor of esm

This commit is contained in:
alina 🌸 2024-03-07 18:11:02 +03:00
parent b172787da1
commit a0b3e9cc6e
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
38 changed files with 2662 additions and 2608 deletions

View file

@ -8,7 +8,7 @@ import { assertTypeIs } from '../../../utils/type-assertions.js'
import { ITelegramClient } from '../../client.types.js' import { ITelegramClient } from '../../client.types.js'
import { isUploadedFile } from '../../types/files/uploaded-file.js' import { isUploadedFile } from '../../types/files/uploaded-file.js'
import { UploadFileLike } from '../../types/files/utils.js' import { UploadFileLike } from '../../types/files/utils.js'
import { InputMediaLike } from '../../types/media/input-media.js' import { InputMediaLike } from '../../types/media/input-media/types.js'
import { fileIdToInputDocument, fileIdToInputPhoto } from '../../utils/convert-file-id.js' import { fileIdToInputDocument, fileIdToInputPhoto } from '../../utils/convert-file-id.js'
import { extractFileName } from '../../utils/file-utils.js' import { extractFileName } from '../../utils/file-utils.js'
import { normalizeDate } from '../../utils/misc-utils.js' import { normalizeDate } from '../../utils/misc-utils.js'

View file

@ -2,7 +2,7 @@ import { tl } from '@mtcute/tl'
import { randomLong } from '../../../utils/long-utils.js' import { randomLong } from '../../../utils/long-utils.js'
import { ITelegramClient } from '../../client.types.js' import { ITelegramClient } from '../../client.types.js'
import { InputMediaLike } from '../../types/media/input-media.js' import { InputMediaLike } from '../../types/media/input-media/types.js'
import { Message } from '../../types/messages/message.js' import { Message } from '../../types/messages/message.js'
import { InputPeerLike, PeersIndex } from '../../types/peers/index.js' import { InputPeerLike, PeersIndex } from '../../types/peers/index.js'
import { assertIsUpdatesGroup } from '../../updates/utils.js' import { assertIsUpdatesGroup } from '../../updates/utils.js'

View file

@ -1,7 +1,7 @@
import { randomLong } from '../../../utils/long-utils.js' import { randomLong } from '../../../utils/long-utils.js'
import { ITelegramClient } from '../../client.types.js' import { ITelegramClient } from '../../client.types.js'
import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards.js' import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards/index.js'
import { InputMediaLike } from '../../types/media/input-media.js' import { InputMediaLike } from '../../types/media/input-media/types.js'
import { Message } from '../../types/messages/message.js' import { Message } from '../../types/messages/message.js'
import { InputText } from '../../types/misc/entities.js' import { InputText } from '../../types/misc/entities.js'
import { InputPeerLike } from '../../types/peers/index.js' import { InputPeerLike } from '../../types/peers/index.js'

View file

@ -4,7 +4,7 @@ import { MtTypeAssertionError } from '../../../types/errors.js'
import { randomLong } from '../../../utils/long-utils.js' import { randomLong } from '../../../utils/long-utils.js'
import { getMarkedPeerId } from '../../../utils/peer-utils.js' import { getMarkedPeerId } from '../../../utils/peer-utils.js'
import { ITelegramClient } from '../../client.types.js' import { ITelegramClient } from '../../client.types.js'
import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards.js' import { BotKeyboard, ReplyMarkup } from '../../types/bots/keyboards/index.js'
import { Message } from '../../types/messages/message.js' import { Message } from '../../types/messages/message.js'
import { InputText } from '../../types/misc/entities.js' import { InputText } from '../../types/misc/entities.js'
import { InputPeerLike, PeersIndex } from '../../types/peers/index.js' import { InputPeerLike, PeersIndex } from '../../types/peers/index.js'

View file

@ -1,106 +0,0 @@
import { tl } from '@mtcute/tl'
import { InputPeerLike } from '../peers/index.js'
/**
* Helper constants and builder functions for methods
* related to bot commands.
*
* You can learn more about bot command scopes in
* [Bot API docs](https://core.telegram.org/bots/api#botcommandscope)
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BotCommands {
/**
* Intermediate bot scope, that is converted to
* TL type `BotCommandScope` by the respective functions.
*
* Used to avoid manually resolving peers.
*/
export type IntermediateScope =
| {
type: 'peer' | 'peer_admins'
peer: InputPeerLike
}
| {
type: 'member'
chat: InputPeerLike
user: InputPeerLike
}
/**
* Default commands scope.
*
* Used if no commands with a narrower scope are available.
*/
export const default_: tl.RawBotCommandScopeDefault = {
_: 'botCommandScopeDefault',
} as const
/**
* Scope that covers all private chats
*/
export const allPrivate: tl.RawBotCommandScopeUsers = {
_: 'botCommandScopeUsers',
} as const
/**
* Scope that covers all group chats (both legacy and supergroups)
*/
export const allGroups: tl.RawBotCommandScopeChats = {
_: 'botCommandScopeChats',
} as const
/**
* Scope that covers all group chat administrators (both legacy and supergroups)
*/
export const allGroupAdmins: tl.RawBotCommandScopeChatAdmins = {
_: 'botCommandScopeChatAdmins',
} as const
/**
* Scope that covers a specific peer (a single user in PMs,
* or all users of a legacy group or a supergroup)
*/
export function peer(peer: InputPeerLike): IntermediateScope {
return {
type: 'peer',
peer,
}
}
/**
* Scope that covers admins in a specific group
*/
export function groupAdmins(peer: InputPeerLike): IntermediateScope {
return {
type: 'peer_admins',
peer,
}
}
/**
* Scope that covers a specific user in a specific group
*/
export function groupMember(chat: InputPeerLike, user: InputPeerLike): IntermediateScope {
return {
type: 'member',
chat,
user,
}
}
/**
* Helper function to create a bot command object
*
* @param command Bot command (without slash)
* @param description Command description
*/
export function cmd(command: string, description: string): tl.RawBotCommand {
return {
_: 'botCommand',
command,
description,
}
}
}

View file

@ -0,0 +1,12 @@
import * as BotCommands from './inner.js'
export {
/**
* Helper constants and builder functions for methods
* related to bot commands.
*
* You can learn more about bot command scopes in
* [Bot API docs](https://core.telegram.org/bots/api#botcommandscope)
*/
BotCommands,
}

View file

@ -0,0 +1,96 @@
import { tl } from '@mtcute/tl'
import { InputPeerLike } from '../../peers/index.js'
/**
* Intermediate bot scope, that is converted to
* TL type `BotCommandScope` by the respective functions.
*
* Used to avoid manually resolving peers.
*/
export type IntermediateScope =
| {
type: 'peer' | 'peer_admins'
peer: InputPeerLike
}
| {
type: 'member'
chat: InputPeerLike
user: InputPeerLike
}
/**
* Default commands scope.
*
* Used if no commands with a narrower scope are available.
*/
export const default_: tl.RawBotCommandScopeDefault = {
_: 'botCommandScopeDefault',
} as const
/**
* Scope that covers all private chats
*/
export const allPrivate: tl.RawBotCommandScopeUsers = {
_: 'botCommandScopeUsers',
} as const
/**
* Scope that covers all group chats (both legacy and supergroups)
*/
export const allGroups: tl.RawBotCommandScopeChats = {
_: 'botCommandScopeChats',
} as const
/**
* Scope that covers all group chat administrators (both legacy and supergroups)
*/
export const allGroupAdmins: tl.RawBotCommandScopeChatAdmins = {
_: 'botCommandScopeChatAdmins',
} as const
/**
* Scope that covers a specific peer (a single user in PMs,
* or all users of a legacy group or a supergroup)
*/
export function peer(peer: InputPeerLike): IntermediateScope {
return {
type: 'peer',
peer,
}
}
/**
* Scope that covers admins in a specific group
*/
export function groupAdmins(peer: InputPeerLike): IntermediateScope {
return {
type: 'peer_admins',
peer,
}
}
/**
* Scope that covers a specific user in a specific group
*/
export function groupMember(chat: InputPeerLike, user: InputPeerLike): IntermediateScope {
return {
type: 'member',
chat,
user,
}
}
/**
* Helper function to create a bot command object
*
* @param command Bot command (without slash)
* @param description Command description
*/
export function cmd(command: string, description: string): tl.RawBotCommand {
return {
_: 'botCommand',
command,
description,
}
}

View file

@ -1,5 +1,5 @@
export * from './command-scope.js' export * from './command-scope/index.js'
export * from './game-high-score.js' export * from './game-high-score.js'
export * from './input/index.js' export * from './inline-message/index.js'
export * from './keyboard-builder.js' export * from './inline-result/index.js'
export * from './keyboards.js' export * from './keyboards/index.js'

View file

@ -0,0 +1,204 @@
import { tl } from '@mtcute/tl'
import { assertNever } from '../../../../types/utils.js'
import { ITelegramClient } from '../../../client.types.js'
import { _normalizeInputText } from '../../../methods/misc/normalize-text.js'
import { InputText } from '../../../types/misc/entities.js'
import { InputMediaGeoLive } from '../../media/index.js'
import { BotKeyboard } from '../keyboards/index.js'
import {
InputInlineMessage,
InputInlineMessageContact,
InputInlineMessageGame,
InputInlineMessageGeo,
InputInlineMessageGeoLive,
InputInlineMessageMedia,
InputInlineMessageText,
InputInlineMessageVenue,
InputInlineMessageWebpage,
} from './types.js'
/**
* Create a text inline message
*
* @param text Message text
* @param params
*/
export function text(
text: InputText,
params: Omit<InputInlineMessageText, 'type' | 'text'> = {},
): InputInlineMessageText {
const ret = params as tl.Mutable<InputInlineMessageText>
ret.type = 'text'
ret.text = text
return ret
}
/**
* Create an inline message containing
* media from the result
*/
export function media(params: Omit<InputInlineMessageMedia, 'type'> = {}): InputInlineMessageMedia {
const ret = params as tl.Mutable<InputInlineMessageMedia>
ret.type = 'media'
return ret
}
/**
* Create an inline message containing a geolocation
*
* @param params Additional parameters
*/
export function geo(params: Omit<InputInlineMessageGeo, 'type'>): InputInlineMessageGeo {
const ret = params as tl.Mutable<InputInlineMessageGeo>
ret.type = 'geo'
return ret
}
/**
* Create an inline message containing a live geolocation
*
* @param params Additional parameters
*/
export function geoLive(params: Omit<InputInlineMessageGeoLive, 'type'>): InputInlineMessageGeoLive {
const ret = params as tl.Mutable<InputInlineMessageGeoLive>
ret.type = 'geo_live'
return ret
}
/**
* Create an inline message containing a venue
*/
export function venue(params: Omit<InputInlineMessageVenue, 'type'>): InputInlineMessageVenue {
const ret = params as tl.Mutable<InputInlineMessageVenue>
ret.type = 'venue'
return ret
}
/**
* Create an inline message containing a game
* from the inline result
*/
export function game(params: Omit<InputInlineMessageGame, 'type'>): InputInlineMessageGame {
const ret = params as tl.Mutable<InputInlineMessageGame>
ret.type = 'game'
return ret
}
/**
* Create an inline message containing a contact
*/
export function contact(params: Omit<InputInlineMessageContact, 'type'>): InputInlineMessageContact {
const ret = params as tl.Mutable<InputInlineMessageContact>
ret.type = 'contact'
return ret
}
/**
* Create an inline message containing a webpage
*/
export function webpage(params: Omit<InputInlineMessageWebpage, 'type'>): InputInlineMessageWebpage {
const ret = params as tl.Mutable<InputInlineMessageWebpage>
ret.type = 'webpage'
return ret
}
/** @internal */
export async function _convertToTl(
client: ITelegramClient,
obj: InputInlineMessage,
): Promise<tl.TypeInputBotInlineMessage> {
switch (obj.type) {
case 'text': {
const [message, entities] = await _normalizeInputText(client, obj.text)
return {
_: 'inputBotInlineMessageText',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
}
}
case 'media': {
const [message, entities] = await _normalizeInputText(client, obj.text)
return {
_: 'inputBotInlineMessageMediaAuto',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
}
}
case 'geo':
case 'geo_live':
return {
_: 'inputBotInlineMessageMediaGeo',
geoPoint: {
_: 'inputGeoPoint',
lat: obj.latitude,
long: obj.longitude,
},
// 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),
}
case 'venue':
return {
_: 'inputBotInlineMessageMediaVenue',
geoPoint: {
_: 'inputGeoPoint',
lat: obj.latitude,
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),
}
case 'game':
return {
_: 'inputBotInlineMessageGame',
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
}
case 'contact':
return {
_: 'inputBotInlineMessageMediaContact',
phoneNumber: obj.phone,
firstName: obj.firstName,
lastName: obj.lastName ?? '',
vcard: obj.vcard ?? '',
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
}
case 'webpage': {
const [message, entities] = await _normalizeInputText(client, obj.text)
return {
_: 'inputBotInlineMessageMediaWebPage',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
forceLargeMedia: obj.size === 'large',
forceSmallMedia: obj.size === 'small',
optional: !obj.required,
url: obj.url,
}
}
default:
assertNever(obj)
}
}

View file

@ -0,0 +1,5 @@
export * from './types.js'
import * as BotInlineMessage from './factories.js'
export { BotInlineMessage }

View file

@ -0,0 +1,147 @@
import {
InputMediaContact,
InputMediaGeo,
InputMediaGeoLive,
InputMediaVenue,
InputMediaWebpage,
} from '../../media/index.js'
import { InputText } from '../../misc/entities.js'
import { ReplyMarkup } from '../index.js'
/**
* Inline message containing only text
*/
export interface InputInlineMessageText {
type: 'text'
/**
* Text of the message
*/
text: InputText
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to disable links preview in this message
*/
disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
/**
* Inline message containing media, which is automatically
* inferred from the result itself.
*/
export interface InputInlineMessageMedia {
type: 'media'
/**
* Caption for the media
*/
text?: InputText
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
/**
* Inline message containing a geolocation
*/
export interface InputInlineMessageGeo extends InputMediaGeo {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a live geolocation
*/
export interface InputInlineMessageGeoLive extends InputMediaGeoLive {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a venue
*/
export interface InputInlineMessageVenue extends InputMediaVenue {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a game
*/
export interface InputInlineMessageGame {
type: 'game'
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a contact
*/
export interface InputInlineMessageContact extends InputMediaContact {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
export interface InputInlineMessageWebpage extends InputMediaWebpage {
/**
* Text of the message
*/
text: InputText
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
export type InputInlineMessage =
| InputInlineMessageText
| InputInlineMessageMedia
| InputInlineMessageGeo
| InputInlineMessageGeoLive
| InputInlineMessageVenue
| InputInlineMessageGame
| InputInlineMessageContact
| InputInlineMessageWebpage

View file

@ -0,0 +1,552 @@
import { tl } from '@mtcute/tl'
import { MtArgumentError } from '../../../../types/errors.js'
import { ITelegramClient } from '../../../client.types.js'
import { fileIdToInputDocument, fileIdToInputPhoto } from '../../../utils/convert-file-id.js'
import { extractFileName } from '../../../utils/file-utils.js'
import { BotInlineMessage } from '../inline-message/index.js'
import {
InputInlineResult,
InputInlineResultArticle,
InputInlineResultAudio,
InputInlineResultContact,
InputInlineResultFile,
InputInlineResultGame,
InputInlineResultGeo,
InputInlineResultGif,
InputInlineResultPhoto,
InputInlineResultSticker,
InputInlineResultVenue,
InputInlineResultVideo,
InputInlineResultVoice,
} from './types.js'
/**
* Create an inline result containing an article
*
* @param id Inline result ID
* @param params Article
*/
export function article(id: string, params: Omit<InputInlineResultArticle, 'type' | 'id'>): InputInlineResultArticle {
const ret = params as tl.Mutable<InputInlineResultArticle>
ret.id = id
ret.type = 'article'
return ret
}
/**
* Create an inline result containing a GIF
*
* @param id Inline result ID
* @param media GIF animation
* @param params Additional parameters
*/
export function gif(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputDocument,
params: Omit<InputInlineResultGif, 'type' | 'id' | 'media'> = {},
): InputInlineResultGif {
const ret = params as tl.Mutable<InputInlineResultGif>
ret.id = id
ret.type = 'gif'
ret.media = media
return ret
}
/**
* Create an inline result containing a video
*
* @param id Inline result ID
* @param media Video
* @param params Additional parameters
*/
export function video(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputDocument,
params: Omit<InputInlineResultVideo, 'type' | 'id' | 'media'>,
): InputInlineResultVideo {
const ret = params as tl.Mutable<InputInlineResultVideo>
ret.id = id
ret.type = 'video'
ret.media = media
return ret
}
/**
* Create an inline result containing an audio file
*
* @param id Inline result ID
* @param media Audio file
* @param params Additional parameters
*/
export function audio(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputDocument,
params: Omit<InputInlineResultAudio, 'type' | 'id' | 'media'>,
): InputInlineResultAudio {
const ret = params as tl.Mutable<InputInlineResultAudio>
ret.id = id
ret.type = 'audio'
ret.media = media
return ret
}
/**
* Create an inline result containing a voice note
*
* @param id Inline result ID
* @param media Voice note
* @param params Additional parameters
*/
export function voice(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputDocument,
params: Omit<InputInlineResultVoice, 'type' | 'id' | 'media'>,
): InputInlineResultVoice {
const ret = params as tl.Mutable<InputInlineResultVoice>
ret.id = id
ret.type = 'voice'
ret.media = media
return ret
}
/**
* Create an inline result containing a photo
*
* @param id Inline result ID
* @param media Photo
* @param params Additional parameters
*/
export function photo(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputPhoto,
params: Omit<InputInlineResultPhoto, 'type' | 'id' | 'media'> = {},
): InputInlineResultPhoto {
const ret = params as tl.Mutable<InputInlineResultPhoto>
ret.id = id
ret.type = 'photo'
ret.media = media
return ret
}
/**
* Create an inline result containing a sticker
*
* @param id Inline result ID
* @param media Sticker
*/
export function sticker(id: string, media: string | tl.RawInputDocument): InputInlineResultSticker {
return {
id,
type: 'sticker',
media,
}
}
/**
* Create an inline result containing a document
* (only PDF and ZIP are supported when using URL)
*
* @param id Inline result ID
* @param media Document
* @param params Additional parameters
*/
export function file(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputDocument,
params: Omit<InputInlineResultFile, 'type' | 'id' | 'media'>,
): InputInlineResultFile {
const ret = params as tl.Mutable<InputInlineResultFile>
ret.id = id
ret.type = 'file'
ret.media = media
return ret
}
/**
* Create an inline result containing a geolocation
*
* @param id Inline result ID
* @param params Additional parameters
*/
export function geo(id: string, params: Omit<InputInlineResultGeo, 'type' | 'id'>): InputInlineResultGeo {
const ret = params as tl.Mutable<InputInlineResultGeo>
ret.id = id
ret.type = 'geo'
return ret
}
/**
* Create an inline result containing a venue
*
* @param id Inline result ID
* @param params Venue parameters
*/
export function venue(id: string, params: Omit<InputInlineResultVenue, 'type' | 'id'>): InputInlineResultVenue {
const ret = params as tl.Mutable<InputInlineResultVenue>
ret.id = id
ret.type = 'venue'
return ret
}
/**
* Create an inline result containing a contact
*
* @param id Inline result ID
* @param params Contact parameters
*/
export function contact(id: string, params: Omit<InputInlineResultContact, 'type' | 'id'>): InputInlineResultContact {
const ret = params as tl.Mutable<InputInlineResultContact>
ret.id = id
ret.type = 'contact'
return ret
}
/**
* Create an inline result containing a game
*
* @param id Inline result ID
* @param shortName Short name of the game
* @param params Additional parameters
*/
export function game(
id: string,
shortName: string,
params: Omit<InputInlineResultGame, 'type' | 'id' | 'shortName'> = {},
): InputInlineResultGame {
const ret = params as tl.Mutable<InputInlineResultGame>
ret.id = id
ret.type = 'game'
ret.shortName = shortName
return ret
}
/** @internal */
export async function _convertToTl(
client: ITelegramClient,
results: InputInlineResult[],
): Promise<[boolean, tl.TypeInputBotInlineResult[]]> {
const normalizeThumb = (obj: InputInlineResult, fallback?: string): tl.RawInputWebDocument | undefined => {
if (obj.type !== 'voice' && obj.type !== 'audio' && obj.type !== 'sticker' && obj.type !== 'game') {
if (!obj.thumb || typeof obj.thumb === 'string') {
if (!obj.thumb && !fallback) {
return undefined
}
return {
_: 'inputWebDocument',
size: 0,
url: obj.thumb || fallback!,
mimeType: obj.type === 'gif' ? obj.thumbMime ?? obj.mime ?? 'video/mp4' : 'image/jpeg',
attributes: [],
}
}
return obj.thumb
}
}
const items: tl.TypeInputBotInlineResult[] = []
let isGallery = false
let forceVertical = false
for (const obj of results) {
switch (obj.type) {
case 'article': {
forceVertical = true
let sendMessage: tl.TypeInputBotInlineMessage
if (obj.message) {
sendMessage = await BotInlineMessage._convertToTl(client, obj.message)
} else {
let message = obj.title
const entities: tl.TypeMessageEntity[] = [
{
_: 'messageEntityBold',
offset: 0,
length: message.length,
},
]
if (obj.url) {
entities.push({
_: 'messageEntityTextUrl',
url: obj.url,
offset: 0,
length: message.length,
})
}
if (obj.description) {
message += '\n' + obj.description
}
sendMessage = {
_: 'inputBotInlineMessageText',
message,
entities,
}
}
items.push({
_: 'inputBotInlineResult',
id: obj.id,
type: obj.type,
title: obj.title,
description: obj.description,
url: obj.hideUrl ? undefined : obj.url,
content:
obj.url && obj.hideUrl ?
{
_: 'inputWebDocument',
url: obj.url,
mimeType: 'text/html',
size: 0,
attributes: [],
} :
undefined,
thumb: typeof obj.thumb === 'string' ? normalizeThumb(obj) : obj.thumb,
sendMessage,
})
continue
}
case 'game': {
let sendMessage: tl.TypeInputBotInlineMessage
if (obj.message) {
sendMessage = await BotInlineMessage._convertToTl(client, obj.message)
if (sendMessage._ !== 'inputBotInlineMessageGame') {
throw new MtArgumentError('game inline result must contain a game inline message')
}
} else {
sendMessage = {
_: 'inputBotInlineMessageGame',
}
}
items.push({
_: 'inputBotInlineResultGame',
id: obj.id,
shortName: obj.shortName,
sendMessage,
})
continue
}
case 'gif':
case 'photo':
case 'sticker':
isGallery = true
break
case 'audio':
case 'contact':
case 'voice':
forceVertical = true
}
let sendMessage: tl.TypeInputBotInlineMessage
if (obj.message) {
sendMessage = await BotInlineMessage._convertToTl(client, obj.message)
} else if (obj.type === 'venue') {
if (obj.latitude && obj.longitude) {
sendMessage = {
_: 'inputBotInlineMessageMediaVenue',
title: obj.title,
address: obj.address,
geoPoint: {
_: 'inputGeoPoint',
lat: obj.latitude,
long: obj.longitude,
},
provider: '',
venueId: '',
venueType: '',
}
} else {
throw new MtArgumentError('message or location (lat&lon) bust be supplied for venue inline result')
}
} else if (obj.type === 'video' && obj.isEmbed && typeof obj.media === 'string') {
sendMessage = {
_: 'inputBotInlineMessageText',
message: obj.media,
}
} else if (obj.type === 'geo') {
sendMessage = {
_: 'inputBotInlineMessageMediaGeo',
geoPoint: {
_: 'inputGeoPoint',
lat: obj.latitude,
long: obj.longitude,
},
}
} else if (obj.type === 'contact') {
sendMessage = {
_: 'inputBotInlineMessageMediaContact',
phoneNumber: obj.phone,
firstName: obj.firstName,
lastName: obj.lastName ?? '',
vcard: '',
}
} else {
sendMessage = {
_: 'inputBotInlineMessageMediaAuto',
message: '',
}
}
let media: tl.TypeInputWebDocument | tl.TypeInputDocument | tl.TypeInputPhoto | undefined = undefined
if (obj.type !== 'geo' && obj.type !== 'venue' && obj.type !== 'contact') {
if (typeof obj.media === 'string') {
// file id or url
if (obj.media.match(/^https?:\/\//)) {
if (obj.type === 'sticker') {
throw new MtArgumentError('sticker inline result cannot contain a URL')
}
let mime: string
if (obj.type === 'video') mime = 'video/mp4'
else if (obj.type === 'audio') {
mime = obj.mime ?? 'audio/mpeg'
} else if (obj.type === 'gif') {
mime = obj.mime ?? 'video/mp4'
} else if (obj.type === 'voice') mime = 'audio/ogg'
else if (obj.type === 'file') {
if (!obj.mime) {
throw new MtArgumentError('MIME type must be specified for file inline result')
}
mime = obj.mime
} else mime = 'image/jpeg'
const attributes: tl.TypeDocumentAttribute[] = []
if (
(obj.type === 'video' || obj.type === 'gif' || obj.type === 'photo') &&
obj.width &&
obj.height
) {
if (obj.type !== 'photo' && obj.duration) {
attributes.push({
_: 'documentAttributeVideo',
w: obj.width,
h: obj.height,
duration: obj.duration,
})
} else {
attributes.push({
_: 'documentAttributeImageSize',
w: obj.width,
h: obj.height,
})
}
} else if (obj.type === 'audio' || obj.type === 'voice') {
attributes.push({
_: 'documentAttributeAudio',
voice: obj.type === 'voice',
duration: obj.duration ?? 0,
title: obj.type === 'audio' ? obj.title : '',
performer: obj.type === 'audio' ? obj.performer : '',
})
}
attributes.push({
_: 'documentAttributeFilename',
fileName: extractFileName(obj.media),
})
media = {
_: 'inputWebDocument',
url: obj.media,
mimeType: mime,
size: 0,
attributes,
}
} else if (obj.type === 'photo') {
media = fileIdToInputPhoto(obj.media)
} else {
media = fileIdToInputDocument(obj.media)
}
} else {
media = obj.media
}
}
let title: string | undefined = undefined
let description: string | undefined = undefined
// incredible hacks by durov team.
// i honestly don't understand why didn't they just
// make a bunch of types, as they normally do,
// but whatever.
// ref: https://github.com/tdlib/td/blob/master/td/telegram/InlineQueriesManager.cpp
if (obj.type === 'contact') {
title = obj.lastName?.length ? `${obj.firstName} ${obj.lastName}` : obj.firstName
} else if (obj.type !== 'sticker') {
title = obj.title
}
if (obj.type === 'audio') {
description = obj.performer
} else if (obj.type === 'geo') {
description = `${obj.latitude} ${obj.longitude}`
} else if (obj.type === 'venue') {
description = obj.address
} else if (obj.type === 'contact') {
description = obj.phone
} else if (obj.type !== 'voice' && obj.type !== 'sticker') {
description = obj.description
}
if (!media || media._ === 'inputWebDocument') {
items.push({
_: 'inputBotInlineResult',
id: obj.id,
type: obj.type,
title,
description,
content: media,
thumb: normalizeThumb(obj, media?.url),
sendMessage,
})
continue
}
if (media._ === 'inputPhoto') {
items.push({
_: 'inputBotInlineResultPhoto',
id: obj.id,
type: obj.type,
photo: media,
sendMessage,
})
continue
}
items.push({
_: 'inputBotInlineResultDocument',
id: obj.id,
type: obj.type,
title,
description,
document: media,
sendMessage,
})
}
return [isGallery && !forceVertical, items]
}

View file

@ -0,0 +1,4 @@
import * as BotInline from './factories.js'
export * from './types.js'
export { BotInline }

View file

@ -0,0 +1,501 @@
import { tl } from '@mtcute/tl'
import { InputInlineMessage } from '../inline-message/types.js'
export interface BaseInputInlineResult {
/**
* Unique ID of the result
*/
id: string
/**
* Message to send when the result is selected.
*
* By default, is automatically generated,
* and details about how it is generated can be found
* in subclasses' description
*/
message?: InputInlineMessage
}
/**
* Inline result containing an article.
*
* If `message` is not provided, a {@link InputInlineMessageText} is created
* with web preview enabled and text generated as follows:
* ```
* {{#if url}}
* <a href="{{url}}"><b>{{title}}</b></a>
* {{else}}
* <b>{{title}}</b>
* {{/if}}
* {{#if description}}
* {{description}}
* {{/if}}
* ```
* > Handlebars syntax is used. HTML tags are used to signify entities,
* > but in fact raw TL entity objects are created
*/
export interface InputInlineResultArticle extends BaseInputInlineResult {
type: 'article'
/**
* Title of the result (must not be empty)
*/
title: string
/**
* Description of the result
*/
description?: string
/**
* URL of the article
*/
url?: string
/**
* Whether to prevent article URL from
* displaying by the client
*
* @default `false`
*/
hideUrl?: boolean
/**
* Article thumbnail URL (must be jpeg).
*/
thumb?: string | tl.RawInputWebDocument
}
/**
* Inline result containing an animation (silent mp4 or gif).
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used
* with empty caption
*/
export interface InputInlineResultGif extends BaseInputInlineResult {
type: 'gif'
/**
* The animation itself.
*
* Can be a URL, a TDLib and Bot API compatible File ID,
* or a TL object representing either of them.
*/
media: string | tl.RawInputWebDocument | tl.RawInputDocument
/**
* Media MIME type, only applicable to URLs.
*
* Usually unnecessary, since Telegram automatically infers it.
*
* @default `video/mp4`
*/
mime?: string
/**
* Title of the result
*/
title?: string
/**
* Title of the result
*/
description?: string
/**
* Animation thumbnail URL, only applicable in case `media` is a URL
*
* @default `media`
*/
thumb?: string | tl.RawInputWebDocument
/**
* Thumbnail MIME type
*
* @default `image/jpeg`
*/
thumbMime?: string
/**
* Width of the animation in pixels
*/
width?: number
/**
* Height of the animation in pixels
*/
height?: number
/**
* Duration of the animation in seconds
*/
duration?: number
}
/**
* Inline result containing a video (only MP4)
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used
* with empty caption for non-embed videos, {@link InputInlineMessageText}
* is used with text containing the URL for embed videos.
*/
export interface InputInlineResultVideo extends BaseInputInlineResult {
type: 'video'
/**
* The video itself, or a page containing an embedded video
*
* Can be a URL, a TDLib and Bot API compatible File ID,
* or a TL object representing either of them.
*/
media: string | tl.RawInputWebDocument | tl.RawInputDocument
/**
* In case `media` is a URL, whether that URL is a link
* to an embedded video player.
*/
isEmbed?: boolean
/**
* Title of the result
*/
title: string
/**
* Description of the result
*/
description?: string
/**
* Video thumbnail URL (must be jpeg), only applicable in case `media` is a URL.
*
* Must be provided explicitly if this is a video loaded by URL.
*
* @default `media`
*/
thumb?: string | tl.RawInputWebDocument
/**
* Width of the video in pixels
*/
width?: number
/**
* Height of the video in pixels
*/
height?: number
/**
* Duration of the video in seconds
*/
duration?: number
}
/**
* Inline result containing an audio file
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used
* with empty caption.
*/
export interface InputInlineResultAudio extends BaseInputInlineResult {
type: 'audio'
/**
* The audio itself
*
* Can be a URL, a TDLib and Bot API compatible File ID,
* or a TL object representing either of them.
*/
media: string | tl.RawInputWebDocument | tl.RawInputDocument
/**
* MIME type of the audio file
*
* Usually unnecessary, since Telegram infers it automatically.
*
* @default `audio/mpeg`
*/
mime?: string
/**
* Title of the audio track
*/
title: string
/**
* Performer of the audio track
*/
performer?: string
/**
* Duration of the audio in seconds
*/
duration?: number
}
/**
* Inline result containing a voice note
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used
* with empty caption.
*/
export interface InputInlineResultVoice extends BaseInputInlineResult {
type: 'voice'
/**
* The voice itself (.ogg, preferably encoded with OPUS)
*
* Can be a URL, a TDLib and Bot API compatible File ID,
* or a TL object representing either of them.
*/
media: string | tl.RawInputWebDocument | tl.RawInputDocument
/**
* Title of the result
*/
title: string
/**
* Duration of the voice note in seconds
*/
duration?: number
}
/**
* Inline result containing a photo
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used
* with empty caption.
*/
export interface InputInlineResultPhoto extends BaseInputInlineResult {
type: 'photo'
/**
* The photo itself
*
* Can be a URL, a TDLib and Bot API compatible File ID,
* or a TL object representing either of them.
*/
media: string | tl.RawInputWebDocument | tl.RawInputPhoto
/**
* Title of the result
*/
title?: string
/**
* Description of the result
*/
description?: string
/**
* Width of the photo in pixels
*/
width?: number
/**
* Height of the photo in pixels
*/
height?: number
/**
* Photo thumbnail URL (must be jpeg), only applicable in case `media` is a URL
*
* @default `media`
*/
thumb?: string | tl.RawInputWebDocument
}
/**
* Inline result containing a sticker
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used.
*/
export interface InputInlineResultSticker extends BaseInputInlineResult {
type: 'sticker'
/**
* The sticker itself. Can't be a URL.
*/
media: string | tl.RawInputDocument
}
/**
* Inline result containing a document
*
* If `message` is not provided, {@link InputInlineMessageMedia} is used
* with empty caption.
*/
export interface InputInlineResultFile extends BaseInputInlineResult {
type: 'file'
/**
* The file itself. When using URL, only PDF and ZIP are supported.
*
* Can be a URL, a TDLib and Bot API compatible File ID,
* or a TL object representing either of them.
*/
media: string | tl.RawInputWebDocument | tl.RawInputDocument
/**
* MIME type of the file.
*
* Due to some Telegram limitation, you can only send
* PDF and ZIP files from URL
* (`application/pdf` and `application/zip` MIMEs respectively).
*
* Must be provided if `media` is a URL
*/
mime?: string
/**
* Title of the result
*/
title: string
/**
* Description of the result
*/
description?: string
/**
* Photo thumbnail URL (must be jpeg), only applicable in case `media` is a URL
*
* @default `media`
*/
thumb?: string | tl.RawInputWebDocument
}
/**
* Inline result containing a geolocation.
*
* If `message` is not passed, a {@link InputInlineMessageGeo} is
* used, with the `latitude` and `longitude` parameters set
* accordingly
*/
export interface InputInlineResultGeo extends BaseInputInlineResult {
type: 'geo'
/**
* Title of the result
*/
title: string
/**
* Latitude of the geolocation
*/
latitude: number
/**
* Longitude of the geolocation
*/
longitude: number
/**
* Location thumbnail URL (must be jpeg).
*
* By default, Telegram generates one based on
* the location set by `latitude` and `longitude`
*/
thumb?: string | tl.RawInputWebDocument
}
/**
* Inline result containing a venue.
*
* If `message` is not passed, {@link BotInlineMessage.venue} is used with
* given `latitude` and `longitude` were passed.
* If they weren't passed either, an error is thrown.
*/
export interface InputInlineResultVenue extends BaseInputInlineResult {
type: 'venue'
/**
* Title of the venue
*/
title: string
/**
* Address of the venue
*/
address: string
/**
* Latitude of the geolocation
*/
latitude?: number
/**
* Longitude of the geolocation
*/
longitude?: number
/**
* Venue thumbnail URL (must be jpeg).
*
* By default, Telegram generates one based on
* the location in the `message`
*/
thumb?: string | tl.RawInputWebDocument
}
/**
* Inline result containing a game.
*
* If `message` is not passed, {@link InputInlineMessageGame} is used.
*
* Note that `message` can only be {@link InputInlineMessageGame}
*/
export interface InputInlineResultGame extends BaseInputInlineResult {
type: 'game'
/**
* Short name of the game
*/
shortName: string
}
/**
* Inline result containing a contact.
*
* If `message` is not passed, {@link InputInlineMessageContact} is used.
*/
export interface InputInlineResultContact extends BaseInputInlineResult {
type: 'contact'
/**
* First name of the contact
*/
firstName: string
/**
* Last name of the contact
*/
lastName?: string
/**
* Phone number of the contact
*/
phone: string
/**
* Contact thumbnail URL (i.e. their avatar) (must be jpeg)
*/
thumb?: string | tl.RawInputWebDocument
}
export type InputInlineResult =
| InputInlineResultArticle
| InputInlineResultGif
| InputInlineResultVideo
| InputInlineResultAudio
| InputInlineResultVoice
| InputInlineResultPhoto
| InputInlineResultSticker
| InputInlineResultFile
| InputInlineResultGeo
| InputInlineResultVenue
| InputInlineResultGame
| InputInlineResultContact

View file

@ -1,2 +0,0 @@
export * from './input-inline-message.js'
export * from './input-inline-result.js'

View file

@ -1,340 +0,0 @@
import { tl } from '@mtcute/tl'
import { assertNever } from '../../../../types/utils.js'
import { ITelegramClient } from '../../../client.types.js'
import { _normalizeInputText } from '../../../methods/misc/normalize-text.js'
import { InputText } from '../../../types/misc/entities.js'
import {
InputMediaContact,
InputMediaGeo,
InputMediaGeoLive,
InputMediaVenue,
InputMediaWebpage,
} from '../../media/index.js'
import { BotKeyboard, ReplyMarkup } from '../keyboards.js'
/**
* Inline message containing only text
*/
export interface InputInlineMessageText {
type: 'text'
/**
* Text of the message
*/
text: InputText
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to disable links preview in this message
*/
disableWebPreview?: boolean
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
/**
* Inline message containing media, which is automatically
* inferred from the result itself.
*/
export interface InputInlineMessageMedia {
type: 'media'
/**
* Caption for the media
*/
text?: InputText
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
/**
* Inline message containing a geolocation
*/
export interface InputInlineMessageGeo extends InputMediaGeo {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a live geolocation
*/
export interface InputInlineMessageGeoLive extends InputMediaGeoLive {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a venue
*/
export interface InputInlineMessageVenue extends InputMediaVenue {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a game
*/
export interface InputInlineMessageGame {
type: 'game'
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
/**
* Inline message containing a contact
*/
export interface InputInlineMessageContact extends InputMediaContact {
/**
* Message's reply markup
*/
replyMarkup?: ReplyMarkup
}
export interface InputInlineMessageWebpage extends InputMediaWebpage {
/**
* Text of the message
*/
text: InputText
/**
* Message reply markup
*/
replyMarkup?: ReplyMarkup
/**
* Whether to invert media position.
*
* Currently only supported for web previews and makes the
* client render the preview above the caption and not below.
*/
invertMedia?: boolean
}
export type InputInlineMessage =
| InputInlineMessageText
| InputInlineMessageMedia
| InputInlineMessageGeo
| InputInlineMessageGeoLive
| InputInlineMessageVenue
| InputInlineMessageGame
| InputInlineMessageContact
| InputInlineMessageWebpage
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BotInlineMessage {
/**
* Create a text inline message
*
* @param text Message text
* @param params
*/
export function text(
text: InputText,
params: Omit<InputInlineMessageText, 'type' | 'text'> = {},
): InputInlineMessageText {
const ret = params as tl.Mutable<InputInlineMessageText>
ret.type = 'text'
ret.text = text
return ret
}
/**
* Create an inline message containing
* media from the result
*/
export function media(params: Omit<InputInlineMessageMedia, 'type'> = {}): InputInlineMessageMedia {
const ret = params as tl.Mutable<InputInlineMessageMedia>
ret.type = 'media'
return ret
}
/**
* Create an inline message containing a geolocation
*
* @param params Additional parameters
*/
export function geo(params: Omit<InputInlineMessageGeo, 'type'>): InputInlineMessageGeo {
const ret = params as tl.Mutable<InputInlineMessageGeo>
ret.type = 'geo'
return ret
}
/**
* Create an inline message containing a live geolocation
*
* @param params Additional parameters
*/
export function geoLive(params: Omit<InputInlineMessageGeoLive, 'type'>): InputInlineMessageGeoLive {
const ret = params as tl.Mutable<InputInlineMessageGeoLive>
ret.type = 'geo_live'
return ret
}
/**
* Create an inline message containing a venue
*/
export function venue(params: Omit<InputInlineMessageVenue, 'type'>): InputInlineMessageVenue {
const ret = params as tl.Mutable<InputInlineMessageVenue>
ret.type = 'venue'
return ret
}
/**
* Create an inline message containing a game
* from the inline result
*/
export function game(params: Omit<InputInlineMessageGame, 'type'>): InputInlineMessageGame {
const ret = params as tl.Mutable<InputInlineMessageGame>
ret.type = 'game'
return ret
}
/**
* Create an inline message containing a contact
*/
export function contact(params: Omit<InputInlineMessageContact, 'type'>): InputInlineMessageContact {
const ret = params as tl.Mutable<InputInlineMessageContact>
ret.type = 'contact'
return ret
}
/**
* Create an inline message containing a webpage
*/
export function webpage(params: Omit<InputInlineMessageWebpage, 'type'>): InputInlineMessageWebpage {
const ret = params as tl.Mutable<InputInlineMessageWebpage>
ret.type = 'webpage'
return ret
}
/** @internal */
export async function _convertToTl(
client: ITelegramClient,
obj: InputInlineMessage,
): Promise<tl.TypeInputBotInlineMessage> {
switch (obj.type) {
case 'text': {
const [message, entities] = await _normalizeInputText(client, obj.text)
return {
_: 'inputBotInlineMessageText',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
}
}
case 'media': {
const [message, entities] = await _normalizeInputText(client, obj.text)
return {
_: 'inputBotInlineMessageMediaAuto',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
}
}
case 'geo':
case 'geo_live':
return {
_: 'inputBotInlineMessageMediaGeo',
geoPoint: {
_: 'inputGeoPoint',
lat: obj.latitude,
long: obj.longitude,
},
// 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),
}
case 'venue':
return {
_: 'inputBotInlineMessageMediaVenue',
geoPoint: {
_: 'inputGeoPoint',
lat: obj.latitude,
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),
}
case 'game':
return {
_: 'inputBotInlineMessageGame',
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
}
case 'contact':
return {
_: 'inputBotInlineMessageMediaContact',
phoneNumber: obj.phone,
firstName: obj.firstName,
lastName: obj.lastName ?? '',
vcard: obj.vcard ?? '',
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
}
case 'webpage': {
const [message, entities] = await _normalizeInputText(client, obj.text)
return {
_: 'inputBotInlineMessageMediaWebPage',
message,
entities,
replyMarkup: BotKeyboard._convertToTl(obj.replyMarkup),
invertMedia: obj.invertMedia,
forceLargeMedia: obj.size === 'large',
forceSmallMedia: obj.size === 'small',
optional: !obj.required,
url: obj.url,
}
}
default:
assertNever(obj)
}
}
}

View file

@ -1,478 +0,0 @@
import { tl } from '@mtcute/tl'
import { getPlatform } from '../../../platform.js'
import { assertNever } from '../../../types/utils.js'
import { toInputUser } from '../../utils/peer-utils.js'
import { BotKeyboardBuilder } from './keyboard-builder.js'
/**
* Reply keyboard markup
*/
export interface ReplyKeyboardMarkup extends Omit<tl.RawReplyKeyboardMarkup, '_' | 'rows'> {
readonly type: 'reply'
/**
* Two-dimensional array of buttons
*/
readonly buttons: tl.TypeKeyboardButton[][]
}
/**
* Hide previously sent bot keyboard
*/
export interface ReplyKeyboardHide extends Omit<tl.RawReplyKeyboardHide, '_'> {
readonly type: 'reply_hide'
}
/**
* Force the user to send a reply
*/
export interface ReplyKeyboardForceReply extends Omit<tl.RawReplyKeyboardForceReply, '_'> {
readonly type: 'force_reply'
}
/**
* Inline keyboard markup
*/
export interface InlineKeyboardMarkup {
readonly type: 'inline'
/**
* Two-dimensional array of buttons
*/
readonly buttons: tl.TypeKeyboardButton[][]
}
export type ReplyMarkup =
| ReplyKeyboardMarkup
| ReplyKeyboardHide
| ReplyKeyboardForceReply
| InlineKeyboardMarkup
| tl.TypeReplyMarkup
/**
* Convenience methods wrapping TL
* objects creation for bot keyboard buttons.
*
* You can also use the type-discriminated objects directly.
*
* > **Note**: Button creation functions are intended to be used
* > with inline reply markup, unless stated otherwise
* > in the description.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BotKeyboard {
/** Create a keyboard builder */
export function builder(maxRowWidth?: number | null): BotKeyboardBuilder {
return new BotKeyboardBuilder(maxRowWidth)
}
/**
* Create an inline keyboard markup
*
* @param buttons Two-dimensional array of buttons
*/
export function inline(buttons: tl.TypeKeyboardButton[][]): InlineKeyboardMarkup {
return {
type: 'inline',
buttons,
}
}
/**
* Create a reply keyboard markup
*
* @param buttons Two-dimensional array of buttons
* @param params Additional parameters for the keyboard
*/
export function reply(
buttons: tl.TypeKeyboardButton[][],
params: Omit<ReplyKeyboardMarkup, 'type' | 'buttons'> = {},
): ReplyKeyboardMarkup {
const ret = params as tl.Mutable<ReplyKeyboardMarkup>
ret.type = 'reply'
ret.buttons = buttons
return ret
}
/**
* Hide the previously sent reply keyboard
*
* @param selective
* Whether to remove the keyboard for specific users only. Targets:
* - users that are @mentioned in the text of the Message
* - in case this is a reply, sender of the original message
*/
export function hideReply(selective?: boolean): ReplyKeyboardHide {
return {
type: 'reply_hide',
selective,
}
}
/**
* Force the user to send a reply
*/
export function forceReply(params: Omit<ReplyKeyboardForceReply, 'type'> = {}): ReplyKeyboardForceReply {
const ret = params as tl.Mutable<ReplyKeyboardForceReply>
ret.type = 'force_reply'
return ret
}
/**
* Create a text-only keyboard button.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
*/
export function text(text: string): tl.RawKeyboardButton {
return {
_: 'keyboardButton',
text,
}
}
/**
* Create a keyboard button requesting for user's contact.
* Available only for private chats.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
*/
export function requestContact(text: string): tl.RawKeyboardButtonRequestPhone {
return {
_: 'keyboardButtonRequestPhone',
text,
}
}
/**
* Create a keyboard button requesting for user's geo location.
* Available only for private chats.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
*/
export function requestGeo(text: string): tl.RawKeyboardButtonRequestGeoLocation {
return {
_: 'keyboardButtonRequestGeoLocation',
text,
}
}
/**
* Create a keyboard button requesting the user to create and send a poll.
* Available only for private chats.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
* @param quiz If set, only quiz polls can be sent
*/
export function requestPoll(text: string, quiz?: boolean): tl.RawKeyboardButtonRequestPoll {
return {
_: 'keyboardButtonRequestPoll',
text,
quiz,
}
}
/**
* Create a keyboard button with a link.
*
* Used for inline keyboards, not reply!
*
* @param text Button text
* @param url URL
*/
export function url(text: string, url: string): tl.RawKeyboardButtonUrl {
return {
_: 'keyboardButtonUrl',
text,
url,
}
}
/**
* Create a keyboard button with a link.
*
* Used for inline keyboards, not reply!
*
* @param text Button text
* @param data Callback data (1-64 bytes). String will be converted to `Buffer`
* @param requiresPassword
* Whether the user should verify their identity by entering 2FA password.
* See more: {@link tl.RawKeyboardButtonCallback#requiresPassword}
*/
export function callback(
text: string,
data: string | Uint8Array,
requiresPassword?: boolean,
): tl.RawKeyboardButtonCallback {
return {
_: 'keyboardButtonCallback',
text,
requiresPassword,
data: typeof data === 'string' ? getPlatform().utf8Encode(data) : data,
}
}
/**
* Button to force a user to switch to inline mode.
*
* Pressing the button will prompt the user to select
* one of their chats, open that chat and insert the bots
* username and the specified inline query (if any) in the input field.
*
* Used for inline keyboards, not reply!
*
* @param text Button text
* @param query Inline query (can be empty or omitted)
* @param currentChat
* If set, pressing the button will insert the bot's username
* and the specified inline query in the current chat's input field
*/
export function switchInline(text: string, query = '', currentChat?: boolean): tl.RawKeyboardButtonSwitchInline {
return {
_: 'keyboardButtonSwitchInline',
samePeer: currentChat,
text,
query,
}
}
/**
* Button to start a game
*
* Used for inline keyboards, not reply!
*
* **Note**: This type of button must always be
* the first button in the first row. ID of the
* game is inferred from {@link InputMedia.game},
* thus this button should only be used with it.
*/
export function game(text: string): tl.RawKeyboardButtonGame {
return { _: 'keyboardButtonGame', text }
}
/**
* Button to pay for a product.
*
* Used for inline keyboards, not reply!
*
* **Note**: This type of button must always be
* the first button in the first row. Related
* invoice is inferred from {@link InputMedia.invoice},
* thus this button should only be used with it.
*/
export function pay(text: string): tl.RawKeyboardButtonBuy {
return { _: 'keyboardButtonBuy', text }
}
/**
* Button to authorize a user
*
* Used for inline keyboards, not reply!
*
* @param text Button label
* @param url Authorization URL (see {@link tl.RawInputKeyboardButtonUrlAuth})
* @param params
*/
export function urlAuth(
text: string,
url: string,
params: {
/**
* Button label when forwarded
*/
fwdText?: string
/**
* Whether to request the permission for
* your bot to send messages to the user
*/
requestWriteAccess?: boolean
/**
* Bot, which will be used for user authorization.
* `url` domain must be the same as the domain linked
* with the bot.
*
* @default current bot
*/
bot?: tl.TypeInputUser
} = {},
): tl.RawInputKeyboardButtonUrlAuth {
return {
_: 'inputKeyboardButtonUrlAuth',
text,
url,
bot: params.bot ?? {
_: 'inputUserSelf',
},
fwdText: params.fwdText,
requestWriteAccess: params.requestWriteAccess,
}
}
/**
* Button to open webview
*
* Used for both inline keyboards and reply ones
*
* @param text Button label
* @param url WebView URL
*/
export function webView(text: string, url: string): tl.RawKeyboardButtonWebView {
return {
_: 'keyboardButtonWebView',
text,
url,
}
}
/**
* Button to open user profile
*
* @param text Text of the button
* @param user User to be opened (use {@link TelegramClient.resolvePeer})
*/
export function userProfile(text: string, user: tl.TypeInputPeer): tl.RawInputKeyboardButtonUserProfile {
return {
_: 'inputKeyboardButtonUserProfile',
text,
userId: toInputUser(user),
}
}
/**
* Button to request a peer from the user
*
* @param text Text of the button
* @param buttonId ID of the button that will later be passed to the service message
*/
export function requestPeer(
text: string,
buttonId: number,
params: {
/**
* Peer type, along with filters
*/
peerType: tl.TypeRequestPeerType
/**
* Maximum number of peers to be selected
*
* @default 1
*/
count?: number
},
): tl.RawKeyboardButtonRequestPeer {
return {
_: 'keyboardButtonRequestPeer',
text,
buttonId,
peerType: params.peerType,
maxQuantity: params.count ?? 1,
}
}
/**
* Find a button in the keyboard by its text or by predicate
*
* @param buttons Two-dimensional array of buttons
* @param predicate Button text or predicate function
*/
export function findButton(
buttons: tl.TypeKeyboardButton[][],
predicate: string | ((btn: tl.TypeKeyboardButton) => boolean),
): tl.TypeKeyboardButton | null {
if (typeof predicate === 'string') {
const text = predicate
predicate = (btn) => {
return 'text' in btn && btn.text === text
}
}
for (const row of buttons) {
for (const btn of row) {
if (predicate(btn)) {
return btn
}
}
}
return null
}
/** @internal */
export function _rowsTo2d(rows: tl.RawKeyboardButtonRow[]): tl.TypeKeyboardButton[][] {
return rows.map((it) => it.buttons)
}
/** @internal */
export function _2dToRows(arr: tl.TypeKeyboardButton[][], inline: boolean): tl.RawKeyboardButtonRow[] {
return arr.map((row) => {
if (!inline) {
// le cringe
row = row.map((btn) =>
btn._ === 'keyboardButtonWebView' ?
{
...btn,
_: 'keyboardButtonSimpleWebView',
} :
btn,
)
}
return {
_: 'keyboardButtonRow',
buttons: row,
}
})
}
/** @internal */
export function _convertToTl(obj?: ReplyMarkup): tl.TypeReplyMarkup | undefined {
if (!obj) return obj
if (tl.isAnyReplyMarkup(obj)) return obj
switch (obj.type) {
case 'reply':
return {
_: 'replyKeyboardMarkup',
resize: obj.resize,
singleUse: obj.singleUse,
selective: obj.selective,
persistent: obj.persistent,
placeholder: obj.placeholder,
rows: _2dToRows(obj.buttons, false),
}
case 'reply_hide':
return {
_: 'replyKeyboardHide',
selective: obj.selective,
}
case 'force_reply':
return {
_: 'replyKeyboardForceReply',
singleUse: obj.singleUse,
selective: obj.selective,
placeholder: obj.placeholder,
}
case 'inline':
return {
_: 'replyInlineMarkup',
rows: _2dToRows(obj.buttons, true),
}
default:
assertNever(obj)
}
}
}

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { BotKeyboardBuilder } from './keyboard-builder.js' import { BotKeyboardBuilder } from './builder.js'
describe('BotKeyboardBuilder', () => { describe('BotKeyboardBuilder', () => {
describe('#push', () => { describe('#push', () => {

View file

@ -1,6 +1,6 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import type { InlineKeyboardMarkup, ReplyKeyboardMarkup } from './keyboards.js' import type { InlineKeyboardMarkup, ReplyKeyboardMarkup } from './types.js'
export type ButtonLike = tl.TypeKeyboardButton | false | null | undefined | void export type ButtonLike = tl.TypeKeyboardButton | false | null | undefined | void

View file

@ -0,0 +1,427 @@
import { tl } from '@mtcute/tl'
import { getPlatform } from '../../../../platform.js'
import { assertNever } from '../../../../types/utils.js'
import { toInputUser } from '../../../utils/peer-utils.js'
import { BotKeyboardBuilder } from './builder.js'
import {
InlineKeyboardMarkup,
ReplyKeyboardForceReply,
ReplyKeyboardHide,
ReplyKeyboardMarkup,
ReplyMarkup,
} from './types.js'
/** Create a keyboard builder */
export function builder(maxRowWidth?: number | null): BotKeyboardBuilder {
return new BotKeyboardBuilder(maxRowWidth)
}
/**
* Create an inline keyboard markup
*
* @param buttons Two-dimensional array of buttons
*/
export function inline(buttons: tl.TypeKeyboardButton[][]): InlineKeyboardMarkup {
return {
type: 'inline',
buttons,
}
}
/**
* Create a reply keyboard markup
*
* @param buttons Two-dimensional array of buttons
* @param params Additional parameters for the keyboard
*/
export function reply(
buttons: tl.TypeKeyboardButton[][],
params: Omit<ReplyKeyboardMarkup, 'type' | 'buttons'> = {},
): ReplyKeyboardMarkup {
const ret = params as tl.Mutable<ReplyKeyboardMarkup>
ret.type = 'reply'
ret.buttons = buttons
return ret
}
/**
* Hide the previously sent reply keyboard
*
* @param selective
* Whether to remove the keyboard for specific users only. Targets:
* - users that are @mentioned in the text of the Message
* - in case this is a reply, sender of the original message
*/
export function hideReply(selective?: boolean): ReplyKeyboardHide {
return {
type: 'reply_hide',
selective,
}
}
/**
* Force the user to send a reply
*/
export function forceReply(params: Omit<ReplyKeyboardForceReply, 'type'> = {}): ReplyKeyboardForceReply {
const ret = params as tl.Mutable<ReplyKeyboardForceReply>
ret.type = 'force_reply'
return ret
}
/**
* Create a text-only keyboard button.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
*/
export function text(text: string): tl.RawKeyboardButton {
return {
_: 'keyboardButton',
text,
}
}
/**
* Create a keyboard button requesting for user's contact.
* Available only for private chats.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
*/
export function requestContact(text: string): tl.RawKeyboardButtonRequestPhone {
return {
_: 'keyboardButtonRequestPhone',
text,
}
}
/**
* Create a keyboard button requesting for user's geo location.
* Available only for private chats.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
*/
export function requestGeo(text: string): tl.RawKeyboardButtonRequestGeoLocation {
return {
_: 'keyboardButtonRequestGeoLocation',
text,
}
}
/**
* Create a keyboard button requesting the user to create and send a poll.
* Available only for private chats.
*
* Used for reply keyboards, not inline!
*
* @param text Button text
* @param quiz If set, only quiz polls can be sent
*/
export function requestPoll(text: string, quiz?: boolean): tl.RawKeyboardButtonRequestPoll {
return {
_: 'keyboardButtonRequestPoll',
text,
quiz,
}
}
/**
* Create a keyboard button with a link.
*
* Used for inline keyboards, not reply!
*
* @param text Button text
* @param url URL
*/
export function url(text: string, url: string): tl.RawKeyboardButtonUrl {
return {
_: 'keyboardButtonUrl',
text,
url,
}
}
/**
* Create a keyboard button with a link.
*
* Used for inline keyboards, not reply!
*
* @param text Button text
* @param data Callback data (1-64 bytes). String will be converted to `Buffer`
* @param requiresPassword
* Whether the user should verify their identity by entering 2FA password.
* See more: {@link tl.RawKeyboardButtonCallback#requiresPassword}
*/
export function callback(
text: string,
data: string | Uint8Array,
requiresPassword?: boolean,
): tl.RawKeyboardButtonCallback {
return {
_: 'keyboardButtonCallback',
text,
requiresPassword,
data: typeof data === 'string' ? getPlatform().utf8Encode(data) : data,
}
}
/**
* Button to force a user to switch to inline mode.
*
* Pressing the button will prompt the user to select
* one of their chats, open that chat and insert the bots
* username and the specified inline query (if any) in the input field.
*
* Used for inline keyboards, not reply!
*
* @param text Button text
* @param query Inline query (can be empty or omitted)
* @param currentChat
* If set, pressing the button will insert the bot's username
* and the specified inline query in the current chat's input field
*/
export function switchInline(text: string, query = '', currentChat?: boolean): tl.RawKeyboardButtonSwitchInline {
return {
_: 'keyboardButtonSwitchInline',
samePeer: currentChat,
text,
query,
}
}
/**
* Button to start a game
*
* Used for inline keyboards, not reply!
*
* **Note**: This type of button must always be
* the first button in the first row. ID of the
* game is inferred from {@link InputMedia.game},
* thus this button should only be used with it.
*/
export function game(text: string): tl.RawKeyboardButtonGame {
return { _: 'keyboardButtonGame', text }
}
/**
* Button to pay for a product.
*
* Used for inline keyboards, not reply!
*
* **Note**: This type of button must always be
* the first button in the first row. Related
* invoice is inferred from {@link InputMedia.invoice},
* thus this button should only be used with it.
*/
export function pay(text: string): tl.RawKeyboardButtonBuy {
return { _: 'keyboardButtonBuy', text }
}
/**
* Button to authorize a user
*
* Used for inline keyboards, not reply!
*
* @param text Button label
* @param url Authorization URL (see {@link tl.RawInputKeyboardButtonUrlAuth})
* @param params
*/
export function urlAuth(
text: string,
url: string,
params: {
/**
* Button label when forwarded
*/
fwdText?: string
/**
* Whether to request the permission for
* your bot to send messages to the user
*/
requestWriteAccess?: boolean
/**
* Bot, which will be used for user authorization.
* `url` domain must be the same as the domain linked
* with the bot.
*
* @default current bot
*/
bot?: tl.TypeInputUser
} = {},
): tl.RawInputKeyboardButtonUrlAuth {
return {
_: 'inputKeyboardButtonUrlAuth',
text,
url,
bot: params.bot ?? {
_: 'inputUserSelf',
},
fwdText: params.fwdText,
requestWriteAccess: params.requestWriteAccess,
}
}
/**
* Button to open webview
*
* Used for both inline keyboards and reply ones
*
* @param text Button label
* @param url WebView URL
*/
export function webView(text: string, url: string): tl.RawKeyboardButtonWebView {
return {
_: 'keyboardButtonWebView',
text,
url,
}
}
/**
* Button to open user profile
*
* @param text Text of the button
* @param user User to be opened (use {@link TelegramClient.resolvePeer})
*/
export function userProfile(text: string, user: tl.TypeInputPeer): tl.RawInputKeyboardButtonUserProfile {
return {
_: 'inputKeyboardButtonUserProfile',
text,
userId: toInputUser(user),
}
}
/**
* Button to request a peer from the user
*
* @param text Text of the button
* @param buttonId ID of the button that will later be passed to the service message
*/
export function requestPeer(
text: string,
buttonId: number,
params: {
/**
* Peer type, along with filters
*/
peerType: tl.TypeRequestPeerType
/**
* Maximum number of peers to be selected
*
* @default 1
*/
count?: number
},
): tl.RawKeyboardButtonRequestPeer {
return {
_: 'keyboardButtonRequestPeer',
text,
buttonId,
peerType: params.peerType,
maxQuantity: params.count ?? 1,
}
}
/**
* Find a button in the keyboard by its text or by predicate
*
* @param buttons Two-dimensional array of buttons
* @param predicate Button text or predicate function
*/
export function findButton(
buttons: tl.TypeKeyboardButton[][],
predicate: string | ((btn: tl.TypeKeyboardButton) => boolean),
): tl.TypeKeyboardButton | null {
if (typeof predicate === 'string') {
const text = predicate
predicate = (btn) => {
return 'text' in btn && btn.text === text
}
}
for (const row of buttons) {
for (const btn of row) {
if (predicate(btn)) {
return btn
}
}
}
return null
}
/** @internal */
export function _rowsTo2d(rows: tl.RawKeyboardButtonRow[]): tl.TypeKeyboardButton[][] {
return rows.map((it) => it.buttons)
}
/** @internal */
export function _2dToRows(arr: tl.TypeKeyboardButton[][], inline: boolean): tl.RawKeyboardButtonRow[] {
return arr.map((row) => {
if (!inline) {
// le cringe
row = row.map((btn) =>
btn._ === 'keyboardButtonWebView' ?
{
...btn,
_: 'keyboardButtonSimpleWebView',
} :
btn,
)
}
return {
_: 'keyboardButtonRow',
buttons: row,
}
})
}
/** @internal */
export function _convertToTl(obj?: ReplyMarkup): tl.TypeReplyMarkup | undefined {
if (!obj) return obj
if (tl.isAnyReplyMarkup(obj)) return obj
switch (obj.type) {
case 'reply':
return {
_: 'replyKeyboardMarkup',
resize: obj.resize,
singleUse: obj.singleUse,
selective: obj.selective,
persistent: obj.persistent,
placeholder: obj.placeholder,
rows: _2dToRows(obj.buttons, false),
}
case 'reply_hide':
return {
_: 'replyKeyboardHide',
selective: obj.selective,
}
case 'force_reply':
return {
_: 'replyKeyboardForceReply',
singleUse: obj.singleUse,
selective: obj.selective,
placeholder: obj.placeholder,
}
case 'inline':
return {
_: 'replyInlineMarkup',
rows: _2dToRows(obj.buttons, true),
}
default:
assertNever(obj)
}
}

View file

@ -0,0 +1,16 @@
export * from './types.js'
import * as BotKeyboard from './factories.js'
export {
/**
* Convenience methods wrapping TL
* objects creation for bot keyboard buttons.
*
* You can also use the type-discriminated objects directly.
*
* > **Note**: Button creation functions are intended to be used
* > with inline reply markup, unless stated otherwise
* > in the description.
*/
BotKeyboard,
}

View file

@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { BotKeyboard } from './keyboards.js' import { BotKeyboard } from './index.js'
describe('findButton', () => { describe('findButton', () => {
const kb: tl.TypeKeyboardButton[][] = [ const kb: tl.TypeKeyboardButton[][] = [

View file

@ -0,0 +1,46 @@
import { tl } from '@mtcute/tl'
/**
* Reply keyboard markup
*/
export interface ReplyKeyboardMarkup extends Omit<tl.RawReplyKeyboardMarkup, '_' | 'rows'> {
readonly type: 'reply'
/**
* Two-dimensional array of buttons
*/
readonly buttons: tl.TypeKeyboardButton[][]
}
/**
* Hide previously sent bot keyboard
*/
export interface ReplyKeyboardHide extends Omit<tl.RawReplyKeyboardHide, '_'> {
readonly type: 'reply_hide'
}
/**
* Force the user to send a reply
*/
export interface ReplyKeyboardForceReply extends Omit<tl.RawReplyKeyboardForceReply, '_'> {
readonly type: 'force_reply'
}
/**
* Inline keyboard markup
*/
export interface InlineKeyboardMarkup {
readonly type: 'inline'
/**
* Two-dimensional array of buttons
*/
readonly buttons: tl.TypeKeyboardButton[][]
}
export type ReplyMarkup =
| ReplyKeyboardMarkup
| ReplyKeyboardHide
| ReplyKeyboardForceReply
| InlineKeyboardMarkup
| tl.TypeReplyMarkup

View file

@ -3,7 +3,7 @@ export * from './contact.js'
export * from './dice.js' export * from './dice.js'
export * from './document.js' export * from './document.js'
export * from './game.js' export * from './game.js'
export * from './input-media.js' export * from './input-media/index.js'
export * from './invoice.js' export * from './invoice.js'
export * from './location.js' export * from './location.js'
export * from './photo.js' export * from './photo.js'

View file

@ -0,0 +1,300 @@
import { tl } from '@mtcute/tl'
import { InputFileLike } from '../../files/utils.js'
import {
CaptionMixin,
InputMediaAudio,
InputMediaAuto,
InputMediaContact,
InputMediaDice,
InputMediaDocument,
InputMediaGame,
InputMediaGeo,
InputMediaGeoLive,
InputMediaInvoice,
InputMediaLike,
InputMediaPhoto,
InputMediaPoll,
InputMediaQuiz,
InputMediaSticker,
InputMediaStory,
InputMediaVenue,
InputMediaVideo,
InputMediaVoice,
InputMediaWebpage,
} from './types.js'
/** Omit `type` and `file` from the given type */
export type OmitTypeAndFile<T extends InputMediaLike, K extends keyof T = never> = Omit<T, 'type' | 'file' | K>
/**
* Create an animation to be sent
*
* @param file Animation
* @param params Additional parameters
*/
export function animation(file: InputFileLike, params: OmitTypeAndFile<InputMediaVideo> = {}): InputMediaVideo {
const ret = params as tl.Mutable<InputMediaVideo>
ret.type = 'video'
ret.file = file
ret.isAnimated = true
return ret
}
/**
* Create an audio to be sent
*
* @param file Audio file
* @param params Additional parameters
*/
export function audio(file: InputFileLike, params: OmitTypeAndFile<InputMediaAudio> = {}): InputMediaAudio {
const ret = params as tl.Mutable<InputMediaAudio>
ret.type = 'audio'
ret.file = file
return ret
}
/**
* Create an document to be sent
*
* @param file Document
* @param params Additional parameters
*/
export function document(file: InputFileLike, params: OmitTypeAndFile<InputMediaDocument> = {}): InputMediaDocument {
const ret = params as tl.Mutable<InputMediaDocument>
ret.type = 'document'
ret.file = file
return ret
}
/**
* Create an photo to be sent
*
* @param file Photo
* @param params Additional parameters
*/
export function photo(file: InputFileLike, params: OmitTypeAndFile<InputMediaPhoto> = {}): InputMediaPhoto {
const ret = params as tl.Mutable<InputMediaPhoto>
ret.type = 'photo'
ret.file = file
return ret
}
/**
* Create an video to be sent
*
* @param file Video
* @param params Additional parameters
*/
export function video(file: InputFileLike, params: OmitTypeAndFile<InputMediaVideo> = {}): InputMediaVideo {
const ret = params as tl.Mutable<InputMediaVideo>
ret.type = 'video'
ret.file = file
return ret
}
/**
* Create a voice note to be sent
*
* @param file Voice note
* @param params Additional parameters
*/
export function voice(file: InputFileLike, params: OmitTypeAndFile<InputMediaVoice> = {}): InputMediaVoice {
const ret = params as tl.Mutable<InputMediaVoice>
ret.type = 'voice'
ret.file = file
return ret
}
/**
* Create a sticker to be sent
*
* @param file Sticker
* @param params Additional parameters
*/
export function sticker(file: InputFileLike, params: OmitTypeAndFile<InputMediaSticker> = {}): InputMediaSticker {
const ret = params as tl.Mutable<InputMediaSticker>
ret.type = 'sticker'
ret.file = file
return ret
}
/**
* Create a venue to be sent
*
* @param params Venue parameters
*/
export function venue(params: OmitTypeAndFile<InputMediaVenue>): InputMediaVenue {
const ret = params as tl.Mutable<InputMediaVenue>
ret.type = 'venue'
return ret
}
/**
* Create a geolocation to be sent
*
* @param latitude Latitude of the location
* @param longitude Longitude of the location
* @param params Additional parameters
*/
export function geo(
latitude: number,
longitude: number,
params: OmitTypeAndFile<InputMediaGeo, 'latitude' | 'longitude'> = {},
): InputMediaGeo {
const ret = params as tl.Mutable<InputMediaGeo>
ret.type = 'geo'
ret.latitude = latitude
ret.longitude = longitude
return ret
}
/**
* Create a live geolocation to be sent
*
* @param latitude Latitude of the current location
* @param longitude Longitude of the current location
* @param params Additional parameters
*/
export function geoLive(
latitude: number,
longitude: number,
params: OmitTypeAndFile<InputMediaGeoLive, 'latitude' | 'longitude'> = {},
): InputMediaGeoLive {
const ret = params as tl.Mutable<InputMediaGeoLive>
ret.type = 'geo_live'
ret.latitude = latitude
ret.longitude = longitude
return ret
}
/**
* Create a dice to be sent
*
* For convenience, known dice emojis are available
* as static members of {@link Dice}.
*
* @param emoji Emoji representing the dice
* @param params Additional parameters
*/
export function dice(emoji: string, params: CaptionMixin): InputMediaDice {
const ret = params as tl.Mutable<InputMediaDice>
ret.type = 'dice'
return ret
}
/**
* Create a contact to be sent
*
* @param params Contact parameters
*/
export function contact(params: OmitTypeAndFile<InputMediaContact>): InputMediaContact {
const ret = params as tl.Mutable<InputMediaContact>
ret.type = 'contact'
return ret
}
/**
* Create a game to be sent
*
* @param game Game short name or TL object representing one
*/
export function game(game: string | tl.TypeInputGame): InputMediaGame {
return {
type: 'game',
game,
}
}
/**
* Create an invoice to be sent
*
* @param params Invoice parameters
*/
export function invoice(params: OmitTypeAndFile<InputMediaInvoice>): InputMediaInvoice {
const ret = params as tl.Mutable<InputMediaInvoice>
ret.type = 'invoice'
return ret
}
/**
* Create a poll to be sent
*
* @param params Poll parameters
*/
export function poll(params: OmitTypeAndFile<InputMediaPoll>): InputMediaPoll {
const ret = params as tl.Mutable<InputMediaPoll>
ret.type = 'poll'
return ret
}
/**
* Create a quiz to be sent
*
* @param params Quiz parameters
*/
export function quiz(params: OmitTypeAndFile<InputMediaQuiz>): InputMediaQuiz {
const ret = params as tl.Mutable<InputMediaQuiz>
ret.type = 'quiz'
return ret
}
/**
* Create a story to be sent
*
* @param params Story parameters
*/
export function story(params: OmitTypeAndFile<InputMediaStory>): InputMediaStory {
const ret = params as tl.Mutable<InputMediaStory>
ret.type = 'story'
return ret
}
/**
* Create a webpage to be sent
*
* @param url Webpage URL
* @param params Additional parameters
*/
export function webpage(url: string, params: OmitTypeAndFile<InputMediaWebpage, 'url'> = {}): InputMediaWebpage {
const ret = params as tl.Mutable<InputMediaWebpage>
ret.type = 'webpage'
ret.url = url
return ret
}
/**
* Create a document to be sent, which subtype
* is inferred automatically by file contents.
*
* Photo type is only inferred for reused files,
* newly uploaded photos with `auto` will be
* uploaded as a document
*
* @param file The media file
* @param params Additional parameters
*/
export function auto(file: InputFileLike, params: OmitTypeAndFile<InputMediaAuto> = {}): InputMediaAuto {
const ret = params as tl.Mutable<InputMediaAuto>
ret.type = 'auto'
ret.file = file
return ret
}

View file

@ -0,0 +1,3 @@
import * as InputMedia from './factories.js'
export * from './types.js'
export { InputMedia }

View file

@ -1,10 +1,10 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { MaybeArray } from '../../../types/utils.js' import { MaybeArray } from '../../../../types/utils.js'
import { InputText } from '../../types/misc/entities.js' import { InputText } from '../../../types/misc/entities.js'
import { InputFileLike } from '../files/index.js' import { InputFileLike } from '../../files/index.js'
import { InputPeerLike } from '../peers/index.js' import { InputPeerLike } from '../../peers/index.js'
import { VenueSource } from './venue.js' import { VenueSource } from '../venue.js'
export interface CaptionMixin { export interface CaptionMixin {
/** /**
@ -607,284 +607,3 @@ export type InputMediaLike =
| InputMediaStory | InputMediaStory
| InputMediaWebpage | InputMediaWebpage
| tl.TypeInputMedia | tl.TypeInputMedia
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace InputMedia {
/** Omit `type` and `file` from the given type */
export type OmitTypeAndFile<T extends InputMediaLike, K extends keyof T = never> = Omit<T, 'type' | 'file' | K>
/**
* Create an animation to be sent
*
* @param file Animation
* @param params Additional parameters
*/
export function animation(file: InputFileLike, params: OmitTypeAndFile<InputMediaVideo> = {}): InputMediaVideo {
const ret = params as tl.Mutable<InputMediaVideo>
ret.type = 'video'
ret.file = file
ret.isAnimated = true
return ret
}
/**
* Create an audio to be sent
*
* @param file Audio file
* @param params Additional parameters
*/
export function audio(file: InputFileLike, params: OmitTypeAndFile<InputMediaAudio> = {}): InputMediaAudio {
const ret = params as tl.Mutable<InputMediaAudio>
ret.type = 'audio'
ret.file = file
return ret
}
/**
* Create an document to be sent
*
* @param file Document
* @param params Additional parameters
*/
export function document(
file: InputFileLike,
params: OmitTypeAndFile<InputMediaDocument> = {},
): InputMediaDocument {
const ret = params as tl.Mutable<InputMediaDocument>
ret.type = 'document'
ret.file = file
return ret
}
/**
* Create an photo to be sent
*
* @param file Photo
* @param params Additional parameters
*/
export function photo(file: InputFileLike, params: OmitTypeAndFile<InputMediaPhoto> = {}): InputMediaPhoto {
const ret = params as tl.Mutable<InputMediaPhoto>
ret.type = 'photo'
ret.file = file
return ret
}
/**
* Create an video to be sent
*
* @param file Video
* @param params Additional parameters
*/
export function video(file: InputFileLike, params: OmitTypeAndFile<InputMediaVideo> = {}): InputMediaVideo {
const ret = params as tl.Mutable<InputMediaVideo>
ret.type = 'video'
ret.file = file
return ret
}
/**
* Create a voice note to be sent
*
* @param file Voice note
* @param params Additional parameters
*/
export function voice(file: InputFileLike, params: OmitTypeAndFile<InputMediaVoice> = {}): InputMediaVoice {
const ret = params as tl.Mutable<InputMediaVoice>
ret.type = 'voice'
ret.file = file
return ret
}
/**
* Create a sticker to be sent
*
* @param file Sticker
* @param params Additional parameters
*/
export function sticker(file: InputFileLike, params: OmitTypeAndFile<InputMediaSticker> = {}): InputMediaSticker {
const ret = params as tl.Mutable<InputMediaSticker>
ret.type = 'sticker'
ret.file = file
return ret
}
/**
* Create a venue to be sent
*
* @param params Venue parameters
*/
export function venue(params: OmitTypeAndFile<InputMediaVenue>): InputMediaVenue {
const ret = params as tl.Mutable<InputMediaVenue>
ret.type = 'venue'
return ret
}
/**
* Create a geolocation to be sent
*
* @param latitude Latitude of the location
* @param longitude Longitude of the location
* @param params Additional parameters
*/
export function geo(
latitude: number,
longitude: number,
params: OmitTypeAndFile<InputMediaGeo, 'latitude' | 'longitude'> = {},
): InputMediaGeo {
const ret = params as tl.Mutable<InputMediaGeo>
ret.type = 'geo'
ret.latitude = latitude
ret.longitude = longitude
return ret
}
/**
* Create a live geolocation to be sent
*
* @param latitude Latitude of the current location
* @param longitude Longitude of the current location
* @param params Additional parameters
*/
export function geoLive(
latitude: number,
longitude: number,
params: OmitTypeAndFile<InputMediaGeoLive, 'latitude' | 'longitude'> = {},
): InputMediaGeoLive {
const ret = params as tl.Mutable<InputMediaGeoLive>
ret.type = 'geo_live'
ret.latitude = latitude
ret.longitude = longitude
return ret
}
/**
* Create a dice to be sent
*
* For convenience, known dice emojis are available
* as static members of {@link Dice}.
*
* @param emoji Emoji representing the dice
* @param params Additional parameters
*/
export function dice(emoji: string, params: CaptionMixin): InputMediaDice {
const ret = params as tl.Mutable<InputMediaDice>
ret.type = 'dice'
return ret
}
/**
* Create a contact to be sent
*
* @param params Contact parameters
*/
export function contact(params: OmitTypeAndFile<InputMediaContact>): InputMediaContact {
const ret = params as tl.Mutable<InputMediaContact>
ret.type = 'contact'
return ret
}
/**
* Create a game to be sent
*
* @param game Game short name or TL object representing one
*/
export function game(game: string | tl.TypeInputGame): InputMediaGame {
return {
type: 'game',
game,
}
}
/**
* Create an invoice to be sent
*
* @param params Invoice parameters
*/
export function invoice(params: OmitTypeAndFile<InputMediaInvoice>): InputMediaInvoice {
const ret = params as tl.Mutable<InputMediaInvoice>
ret.type = 'invoice'
return ret
}
/**
* Create a poll to be sent
*
* @param params Poll parameters
*/
export function poll(params: OmitTypeAndFile<InputMediaPoll>): InputMediaPoll {
const ret = params as tl.Mutable<InputMediaPoll>
ret.type = 'poll'
return ret
}
/**
* Create a quiz to be sent
*
* @param params Quiz parameters
*/
export function quiz(params: OmitTypeAndFile<InputMediaQuiz>): InputMediaQuiz {
const ret = params as tl.Mutable<InputMediaQuiz>
ret.type = 'quiz'
return ret
}
/**
* Create a story to be sent
*
* @param params Story parameters
*/
export function story(params: OmitTypeAndFile<InputMediaStory>): InputMediaStory {
const ret = params as tl.Mutable<InputMediaStory>
ret.type = 'story'
return ret
}
/**
* Create a webpage to be sent
*
* @param url Webpage URL
* @param params Additional parameters
*/
export function webpage(url: string, params: OmitTypeAndFile<InputMediaWebpage, 'url'> = {}): InputMediaWebpage {
const ret = params as tl.Mutable<InputMediaWebpage>
ret.type = 'webpage'
ret.url = url
return ret
}
/**
* Create a document to be sent, which subtype
* is inferred automatically by file contents.
*
* Photo type is only inferred for reused files,
* newly uploaded photos with `auto` will be
* uploaded as a document
*
* @param file The media file
* @param params Additional parameters
*/
export function auto(file: InputFileLike, params: OmitTypeAndFile<InputMediaAuto> = {}): InputMediaAuto {
const ret = params as tl.Mutable<InputMediaAuto>
ret.type = 'auto'
ret.file = file
return ret
}
}

View file

@ -6,7 +6,7 @@ import { getMarkedPeerId, toggleChannelIdMark } from '../../../utils/peer-utils.
import { assertTypeIsNot } from '../../../utils/type-assertions.js' import { assertTypeIsNot } from '../../../utils/type-assertions.js'
import { makeInspectable } from '../../utils/index.js' import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js' import { memoizeGetters } from '../../utils/memoize.js'
import { BotKeyboard, ReplyMarkup } from '../bots/keyboards.js' import { BotKeyboard, ReplyMarkup } from '../bots/keyboards/index.js'
import { TextWithEntities } from '../misc/index.js' import { TextWithEntities } from '../misc/index.js'
import { Chat } from '../peers/chat.js' import { Chat } from '../peers/chat.js'
import { parsePeer, Peer } from '../peers/peer.js' import { parsePeer, Peer } from '../peers/peer.js'

View file

@ -1,5 +1,5 @@
export * from './app-config.js' export * from './app-config.js'
export * from './entities.js' export * from './entities.js'
export * from './input-privacy-rule.js' export * from './input-privacy-rule/index.js'
export * from './sticker-set.js' export * from './sticker-set.js'
export * from './takeout-session.js' export * from './takeout-session.js'

View file

@ -1,97 +0,0 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { tl } from '@mtcute/tl'
import { MaybeArray } from '../../../types/utils.js'
import { InputPeerLike } from '../peers/index.js'
export interface InputPrivacyRuleUsers {
allow: boolean
users: InputPeerLike[]
}
export interface InputPrivacyRuleChatParticipants {
allow: boolean
chats: InputPeerLike[]
}
export type InputPrivacyRule = InputPrivacyRuleChatParticipants | InputPrivacyRuleUsers | tl.TypeInputPrivacyRule
/**
* Helpers for creating {@link InputPrivacyRule}s
*
* @example
* ```typescript
* const rules = [
* PrivacyRule.allow.all,
* PrivacyRule.disallow.users([123456789, 'username']),
* ]
* ```
*/
export namespace PrivacyRule {
export namespace allow {
/** Allow all users */
export const all: tl.RawInputPrivacyValueAllowAll = { _: 'inputPrivacyValueAllowAll' }
/** Allow only contacts */
export const contacts: tl.RawInputPrivacyValueAllowContacts = { _: 'inputPrivacyValueAllowContacts' }
/** Allow only "close friends" list */
export const closeFriends: tl.RawInputPrivacyValueAllowCloseFriends = {
_: 'inputPrivacyValueAllowCloseFriends',
}
/**
* Allow only users specified in `users`
*
* @param users Users to allow
*/
export function users(users: MaybeArray<InputPeerLike>): InputPrivacyRuleUsers {
return {
allow: true,
users: Array.isArray(users) ? users : [users],
}
}
/**
* Allow only participants of chats specified in `chats`
*
* @param chats Chats to allow
*/
export function chatParticipants(chats: MaybeArray<InputPeerLike>): InputPrivacyRuleChatParticipants {
return {
allow: true,
chats: Array.isArray(chats) ? chats : [chats],
}
}
}
export namespace disallow {
/** Disallow all users */
export const all: tl.RawInputPrivacyValueDisallowAll = { _: 'inputPrivacyValueDisallowAll' }
/** Disallow contacts */
export const contacts: tl.RawInputPrivacyValueDisallowContacts = { _: 'inputPrivacyValueDisallowContacts' }
/**
* Disallow users specified in `users`
*
* @param users Users to disallow
*/
export function users(users: MaybeArray<InputPeerLike>): InputPrivacyRuleUsers {
return {
allow: false,
users: Array.isArray(users) ? users : [users],
}
}
/**
* Disallow participants of chats specified in `chats`
*
* @param chats Chats to disallow
*/
export function chatParticipants(chats: MaybeArray<InputPeerLike>): InputPrivacyRuleChatParticipants {
return {
allow: false,
chats: Array.isArray(chats) ? chats : [chats],
}
}
}
}

View file

@ -0,0 +1,38 @@
import { tl } from '@mtcute/tl'
import { MaybeArray } from '../../../../types/utils.js'
import { InputPeerLike } from '../../peers/peer.js'
import { InputPrivacyRuleChatParticipants, InputPrivacyRuleUsers } from './types.js'
/** Allow all users */
export const all: tl.RawInputPrivacyValueAllowAll = { _: 'inputPrivacyValueAllowAll' }
/** Allow only contacts */
export const contacts: tl.RawInputPrivacyValueAllowContacts = { _: 'inputPrivacyValueAllowContacts' }
/** Allow only "close friends" list */
export const closeFriends: tl.RawInputPrivacyValueAllowCloseFriends = {
_: 'inputPrivacyValueAllowCloseFriends',
}
/**
* Allow only users specified in `users`
*
* @param users Users to allow
*/
export function users(users: MaybeArray<InputPeerLike>): InputPrivacyRuleUsers {
return {
allow: true,
users: Array.isArray(users) ? users : [users],
}
}
/**
* Allow only participants of chats specified in `chats`
*
* @param chats Chats to allow
*/
export function chatParticipants(chats: MaybeArray<InputPeerLike>): InputPrivacyRuleChatParticipants {
return {
allow: true,
chats: Array.isArray(chats) ? chats : [chats],
}
}

View file

@ -0,0 +1,4 @@
import * as allow from './allow.js'
import * as disallow from './disallow.js'
export { allow, disallow }

View file

@ -0,0 +1,34 @@
import { tl } from '@mtcute/tl'
import { MaybeArray } from '../../../../types/utils.js'
import { InputPeerLike } from '../../peers/peer.js'
import { InputPrivacyRuleChatParticipants, InputPrivacyRuleUsers } from './types.js'
/** Disallow all users */
export const all: tl.RawInputPrivacyValueDisallowAll = { _: 'inputPrivacyValueDisallowAll' }
/** Disallow contacts */
export const contacts: tl.RawInputPrivacyValueDisallowContacts = { _: 'inputPrivacyValueDisallowContacts' }
/**
* Disallow users specified in `users`
*
* @param users Users to disallow
*/
export function users(users: MaybeArray<InputPeerLike>): InputPrivacyRuleUsers {
return {
allow: false,
users: Array.isArray(users) ? users : [users],
}
}
/**
* Disallow participants of chats specified in `chats`
*
* @param chats Chats to disallow
*/
export function chatParticipants(chats: MaybeArray<InputPeerLike>): InputPrivacyRuleChatParticipants {
return {
allow: false,
chats: Array.isArray(chats) ? chats : [chats],
}
}

View file

@ -0,0 +1,4 @@
import * as PrivacyRule from './bundle.js'
export { PrivacyRule }
export * from './types.js'

View file

@ -0,0 +1,15 @@
import { tl } from '@mtcute/tl'
import { InputPeerLike } from '../../peers/peer.js'
export interface InputPrivacyRuleUsers {
allow: boolean
users: InputPeerLike[]
}
export interface InputPrivacyRuleChatParticipants {
allow: boolean
chats: InputPeerLike[]
}
export type InputPrivacyRule = InputPrivacyRuleChatParticipants | InputPrivacyRuleUsers | tl.TypeInputPrivacyRule

View file

@ -0,0 +1,232 @@
import Long from 'long'
export const PERSISTENT_ID_VERSION_OLD = 2
export const PERSISTENT_ID_VERSION = 4
export const WEB_LOCATION_FLAG = 1 << 24
export const FILE_REFERENCE_FLAG = 1 << 25
export const CURRENT_VERSION = 48
/**
* An error occurred while parsing or serializing a File ID
*/
export class FileIdError extends Error {}
/**
* A newer version of File ID is provided, which is
* currently not supported by the library.
*
* Feel free to open an issue on Github!
*/
export class UnsupportedError extends FileIdError {}
/**
* File ID was invalid, meaning that something did not
* add up while parsing the file ID, or the file ID object
* contained invalid data.
*/
export class InvalidFileIdError extends FileIdError {}
/**
* Provided File ID cannot be converted to that TL object.
*/
export class ConversionError extends FileIdError {
constructor(to: string) {
super(`Cannot convert given File ID to ${to}`)
}
}
export enum FileType {
Thumbnail,
ProfilePhoto,
Photo,
VoiceNote,
Video,
Document,
Encrypted,
Temp,
Sticker,
Audio,
Animation,
EncryptedThumbnail,
Wallpaper,
VideoNote,
SecureRaw,
Secure,
Background,
DocumentAsFile,
Size,
None,
}
// naming convention just like in @mtcute/tl
// additionally, `_` discriminator is used,
// so we can interoperate with normal TL objects
// like InputFile just by checking `_`
// for nested types, we don't bother with full type name
// for discriminator since it is really only used internally,
// so uniqueness is pretty much guaranteed
/**
* This photo is a legacy photo that is
* represented simply by a secret number
*/
export interface RawPhotoSizeSourceLegacy {
readonly _: 'legacy'
readonly secret: Long
}
/**
* This photo is a thumbnail, and its size
* is provided here as a one-letter string
*/
export interface RawPhotoSizeSourceThumbnail {
readonly _: 'thumbnail'
readonly fileType: FileType
readonly thumbnailType: string
}
/**
* This photo is a profile photo of
* some peer, and their ID and access
* hash are provided here.
*/
export interface RawPhotoSizeSourceDialogPhoto {
readonly _: 'dialogPhoto'
readonly big: boolean
readonly id: number
readonly accessHash: Long
}
/**
* This photo is a thumbnail for a a sticker set,
* and set's ID and access hash are provided here
*/
export interface RawPhotoSizeSourceStickerSetThumbnail {
readonly _: 'stickerSetThumbnail'
readonly id: Long
readonly accessHash: Long
}
/**
* This photo is a legacy photo containing
* volume_id, local_id and secret
*/
export interface RawPhotoSizeSourceFullLegacy {
readonly _: 'fullLegacy'
readonly volumeId: Long
readonly localId: number
readonly secret: Long
}
/**
* This photo is a legacy dialog photo
*/
export interface RawPhotoSizeSourceDialogPhotoLegacy extends Omit<RawPhotoSizeSourceDialogPhoto, '_'> {
readonly _: 'dialogPhotoLegacy'
readonly volumeId: Long
readonly localId: number
}
/**
* This photo is a legacy sticker set thumbnail
*/
export interface RawPhotoSizeSourceStickerSetThumbnailLegacy extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
readonly _: 'stickerSetThumbnailLegacy'
readonly volumeId: Long
readonly localId: number
}
/**
* This photo is a legacy sticker set identified by a version
*/
export interface RawPhotoSizeSourceStickerSetThumbnailVersion extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
readonly _: 'stickerSetThumbnailVersion'
readonly version: number
}
export type TypePhotoSizeSource =
| RawPhotoSizeSourceLegacy
| RawPhotoSizeSourceThumbnail
| RawPhotoSizeSourceDialogPhoto
| RawPhotoSizeSourceStickerSetThumbnail
| RawPhotoSizeSourceFullLegacy
| RawPhotoSizeSourceDialogPhotoLegacy
| RawPhotoSizeSourceStickerSetThumbnailLegacy
| RawPhotoSizeSourceStickerSetThumbnailVersion
/**
* An external web file
*/
export interface RawWebRemoteFileLocation {
readonly _: 'web'
readonly url: string
readonly accessHash: Long
}
/**
* A photo, that, in addition to ID and access
* hash, has its own `source` and detailed
* information about photo location on the
* servers.
*/
export interface RawPhotoRemoteFileLocation {
readonly _: 'photo'
readonly id: Long
readonly accessHash: Long
readonly source: TypePhotoSizeSource
}
/**
* A common file that is represented as a pair
* of ID and access hash
*/
export interface RawCommonRemoteFileLocation {
readonly _: 'common'
readonly id: Long
readonly accessHash: Long
}
export type TypeRemoteFileLocation = RawWebRemoteFileLocation | RawPhotoRemoteFileLocation | RawCommonRemoteFileLocation
/**
* An object representing information about
* file location, that was either parsed from
* TDLib compatible File ID, or will be parsed
* to one.
*
* This type is supposed to be an intermediate step
* between TL objects and string file IDs,
* and if you are using `@mtcute/client`, you don't
* really need to care about this type at all.
*/
export interface RawFullRemoteFileLocation {
readonly _: 'remoteFileLocation'
/**
* DC ID where this file is located
*/
readonly dcId: number
/**
* Type of the file
*/
readonly type: FileType
/**
* File reference (if any)
*/
readonly fileReference: Uint8Array | null
/**
* Context of the file location
*/
readonly location: TypeRemoteFileLocation
}
export function isFileIdLike(obj: unknown): obj is string | RawFullRemoteFileLocation {
return (
typeof obj === 'string' ||
(obj !== null && typeof obj === 'object' && (obj as { _: unknown })._ === 'remoteFileLocation')
)
}

View file

@ -1,240 +1,2 @@
import Long from 'long' import * as tdFileId from './types-inner.js'
export { tdFileId }
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace tdFileId {
export const PERSISTENT_ID_VERSION_OLD = 2
export const PERSISTENT_ID_VERSION = 4
export const WEB_LOCATION_FLAG = 1 << 24
export const FILE_REFERENCE_FLAG = 1 << 25
export const CURRENT_VERSION = 48
/**
* An error occurred while parsing or serializing a File ID
*/
export class FileIdError extends Error {}
/**
* A newer version of File ID is provided, which is
* currently not supported by the library.
*
* Feel free to open an issue on Github!
*/
export class UnsupportedError extends FileIdError {}
/**
* File ID was invalid, meaning that something did not
* add up while parsing the file ID, or the file ID object
* contained invalid data.
*/
export class InvalidFileIdError extends FileIdError {}
/**
* Provided File ID cannot be converted to that TL object.
*/
export class ConversionError extends FileIdError {
constructor(to: string) {
super(`Cannot convert given File ID to ${to}`)
}
}
export enum FileType {
Thumbnail,
ProfilePhoto,
Photo,
VoiceNote,
Video,
Document,
Encrypted,
Temp,
Sticker,
Audio,
Animation,
EncryptedThumbnail,
Wallpaper,
VideoNote,
SecureRaw,
Secure,
Background,
DocumentAsFile,
Size,
None,
}
// naming convention just like in @mtcute/tl
// additionally, `_` discriminator is used,
// so we can interoperate with normal TL objects
// like InputFile just by checking `_`
// for nested types, we don't bother with full type name
// for discriminator since it is really only used internally,
// so uniqueness is pretty much guaranteed
/**
* This photo is a legacy photo that is
* represented simply by a secret number
*/
export interface RawPhotoSizeSourceLegacy {
readonly _: 'legacy'
readonly secret: Long
}
/**
* This photo is a thumbnail, and its size
* is provided here as a one-letter string
*/
export interface RawPhotoSizeSourceThumbnail {
readonly _: 'thumbnail'
readonly fileType: FileType
readonly thumbnailType: string
}
/**
* This photo is a profile photo of
* some peer, and their ID and access
* hash are provided here.
*/
export interface RawPhotoSizeSourceDialogPhoto {
readonly _: 'dialogPhoto'
readonly big: boolean
readonly id: number
readonly accessHash: Long
}
/**
* This photo is a thumbnail for a a sticker set,
* and set's ID and access hash are provided here
*/
export interface RawPhotoSizeSourceStickerSetThumbnail {
readonly _: 'stickerSetThumbnail'
readonly id: Long
readonly accessHash: Long
}
/**
* This photo is a legacy photo containing
* volume_id, local_id and secret
*/
export interface RawPhotoSizeSourceFullLegacy {
readonly _: 'fullLegacy'
readonly volumeId: Long
readonly localId: number
readonly secret: Long
}
/**
* This photo is a legacy dialog photo
*/
export interface RawPhotoSizeSourceDialogPhotoLegacy extends Omit<RawPhotoSizeSourceDialogPhoto, '_'> {
readonly _: 'dialogPhotoLegacy'
readonly volumeId: Long
readonly localId: number
}
/**
* This photo is a legacy sticker set thumbnail
*/
export interface RawPhotoSizeSourceStickerSetThumbnailLegacy
extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
readonly _: 'stickerSetThumbnailLegacy'
readonly volumeId: Long
readonly localId: number
}
/**
* This photo is a legacy sticker set identified by a version
*/
export interface RawPhotoSizeSourceStickerSetThumbnailVersion
extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
readonly _: 'stickerSetThumbnailVersion'
readonly version: number
}
export type TypePhotoSizeSource =
| RawPhotoSizeSourceLegacy
| RawPhotoSizeSourceThumbnail
| RawPhotoSizeSourceDialogPhoto
| RawPhotoSizeSourceStickerSetThumbnail
| RawPhotoSizeSourceFullLegacy
| RawPhotoSizeSourceDialogPhotoLegacy
| RawPhotoSizeSourceStickerSetThumbnailLegacy
| RawPhotoSizeSourceStickerSetThumbnailVersion
/**
* An external web file
*/
export interface RawWebRemoteFileLocation {
readonly _: 'web'
readonly url: string
readonly accessHash: Long
}
/**
* A photo, that, in addition to ID and access
* hash, has its own `source` and detailed
* information about photo location on the
* servers.
*/
export interface RawPhotoRemoteFileLocation {
readonly _: 'photo'
readonly id: Long
readonly accessHash: Long
readonly source: TypePhotoSizeSource
}
/**
* A common file that is represented as a pair
* of ID and access hash
*/
export interface RawCommonRemoteFileLocation {
readonly _: 'common'
readonly id: Long
readonly accessHash: Long
}
export type TypeRemoteFileLocation =
| RawWebRemoteFileLocation
| RawPhotoRemoteFileLocation
| RawCommonRemoteFileLocation
/**
* An object representing information about
* file location, that was either parsed from
* TDLib compatible File ID, or will be parsed
* to one.
*
* This type is supposed to be an intermediate step
* between TL objects and string file IDs,
* and if you are using `@mtcute/client`, you don't
* really need to care about this type at all.
*/
export interface RawFullRemoteFileLocation {
readonly _: 'remoteFileLocation'
/**
* DC ID where this file is located
*/
readonly dcId: number
/**
* Type of the file
*/
readonly type: FileType
/**
* File reference (if any)
*/
readonly fileReference: Uint8Array | null
/**
* Context of the file location
*/
readonly location: TypeRemoteFileLocation
}
export function isFileIdLike(obj: unknown): obj is string | RawFullRemoteFileLocation {
return (
typeof obj === 'string' ||
(obj !== null && typeof obj === 'object' && (obj as { _: unknown })._ === 'remoteFileLocation')
)
}
}