feat: support for chosen inline query results

This commit is contained in:
teidesu 2021-05-04 14:07:40 +03:00
parent 97cbf10d3d
commit 23dcc4c1e5
10 changed files with 431 additions and 23 deletions

View file

@ -59,6 +59,7 @@ import { _normalizeInputFile } from './methods/files/normalize-input-file'
import { _normalizeInputMedia } from './methods/files/normalize-input-media' import { _normalizeInputMedia } from './methods/files/normalize-input-media'
import { uploadFile } from './methods/files/upload-file' import { uploadFile } from './methods/files/upload-file'
import { deleteMessages } from './methods/messages/delete-messages' import { deleteMessages } from './methods/messages/delete-messages'
import { editInlineMessage } from './methods/messages/edit-inline-message'
import { editMessage } from './methods/messages/edit-message' import { editMessage } from './methods/messages/edit-message'
import { _findMessageInUpdate } from './methods/messages/find-in-update' import { _findMessageInUpdate } from './methods/messages/find-in-update'
import { forwardMessages } from './methods/messages/forward-messages' import { forwardMessages } from './methods/messages/forward-messages'
@ -1136,6 +1137,67 @@ export interface TelegramClient extends BaseTelegramClient {
ids: MaybeArray<number>, ids: MaybeArray<number>,
revoke?: boolean revoke?: boolean
): Promise<boolean> ): Promise<boolean>
/**
* Edit sent inline message text, media and reply markup.
*
* @param id
* Inline message ID, either as a TL object, or as a
* TDLib and Bot API compatible string
* @param params
*/
editInlineMessage(
id: tl.TypeInputBotInlineMessageID | string,
params: {
/**
* New message text
*
* When `media` is passed, `media.caption` is used instead
*/
text?: string
/**
* Parse mode to use to parse entities before sending
* the message. Defaults to current default parse mode (if any).
*
* Passing `null` will explicitly disable formatting.
*/
parseMode?: string | null
/**
* List of formatting entities to use instead of parsing via a
* parse mode.
*
* **Note:** Passing this makes the method ignore {@link parseMode}
*
* When `media` is passed, `media.entities` is used instead
*/
entities?: tl.TypeMessageEntity[]
/**
* New message media
*/
media?: InputMediaLike
/**
* Whether to disable links preview in this message
*/
disableWebPreview?: boolean
/**
* For bots: new reply markup.
* If omitted, existing markup will be removed.
*/
replyMarkup?: ReplyMarkup
/**
* For media, upload progress callback.
*
* @param uploaded Number of bytes uploaded
* @param total Total file size in bytes
*/
progressCallback?: (uploaded: number, total: number) => void
}
): Promise<void>
/** /**
* Edit message text, media, reply markup and schedule date. * Edit message text, media, reply markup and schedule date.
* *
@ -1959,6 +2021,7 @@ export class TelegramClient extends BaseTelegramClient {
protected _userId: number | null protected _userId: number | null
protected _isBot: boolean protected _isBot: boolean
protected _downloadConnections: Record<number, TelegramConnection> protected _downloadConnections: Record<number, TelegramConnection>
protected _connectionsForInline: Record<number, TelegramConnection>
protected _parseModes: Record<string, IMessageEntityParser> protected _parseModes: Record<string, IMessageEntityParser>
protected _defaultParseMode: string | null protected _defaultParseMode: string | null
protected _updLock: Lock protected _updLock: Lock
@ -1970,6 +2033,7 @@ export class TelegramClient extends BaseTelegramClient {
this._userId = null this._userId = null
this._isBot = false this._isBot = false
this._downloadConnections = {} this._downloadConnections = {}
this._connectionsForInline = {}
this._parseModes = {} this._parseModes = {}
this._defaultParseMode = null this._defaultParseMode = null
this._updLock = new Lock() this._updLock = new Lock()
@ -2041,6 +2105,7 @@ export class TelegramClient extends BaseTelegramClient {
protected _normalizeInputMedia = _normalizeInputMedia protected _normalizeInputMedia = _normalizeInputMedia
uploadFile = uploadFile uploadFile = uploadFile
deleteMessages = deleteMessages deleteMessages = deleteMessages
editInlineMessage = editInlineMessage
editMessage = editMessage editMessage = editMessage
protected _findMessageInUpdate = _findMessageInUpdate protected _findMessageInUpdate = _findMessageInUpdate
forwardMessages = forwardMessages forwardMessages = forwardMessages

View file

@ -0,0 +1,125 @@
import { TelegramClient } from '../../client'
import { BotKeyboard, InputMediaLike, ReplyMarkup } from '../../types'
import { tl } from '@mtcute/tl'
import { parseInlineMessageId } from '../../utils/inline-utils'
import { TelegramConnection } from '@mtcute/core'
// @extension
interface EditInlineExtension {
_connectionsForInline: Record<number, TelegramConnection>
}
// @initialize
function _initializeEditInline(this: TelegramClient) {
this._connectionsForInline = {}
}
/**
* Edit sent inline message text, media and reply markup.
*
* @param id
* Inline message ID, either as a TL object, or as a
* TDLib and Bot API compatible string
* @param params
* @internal
*/
export async function editInlineMessage(
this: TelegramClient,
id: tl.TypeInputBotInlineMessageID | string,
params: {
/**
* New message text
*
* When `media` is passed, `media.caption` is used instead
*/
text?: string
/**
* Parse mode to use to parse entities before sending
* the message. Defaults to current default parse mode (if any).
*
* Passing `null` will explicitly disable formatting.
*/
parseMode?: string | null
/**
* List of formatting entities to use instead of parsing via a
* parse mode.
*
* **Note:** Passing this makes the method ignore {@link parseMode}
*
* When `media` is passed, `media.entities` is used instead
*/
entities?: tl.TypeMessageEntity[]
/**
* New message media
*/
media?: InputMediaLike
/**
* Whether to disable links preview in this message
*/
disableWebPreview?: boolean
/**
* For bots: new reply markup.
* If omitted, existing markup will be removed.
*/
replyMarkup?: ReplyMarkup
/**
* For media, upload progress callback.
*
* @param uploaded Number of bytes uploaded
* @param total Total file size in bytes
*/
progressCallback?: (uploaded: number, total: number) => void
}
): Promise<void> {
let content: string | undefined
let entities: tl.TypeMessageEntity[] | undefined
let media: tl.TypeInputMedia | undefined = undefined
if (params.media) {
media = await this._normalizeInputMedia(params.media, params)
;[content, entities] = await this._parseEntities(
params.media.caption,
params.parseMode,
params.media.entities
)
} else {
;[content, entities] = await this._parseEntities(
params.text,
params.parseMode,
params.entities
)
}
if (typeof id === 'string') {
id = parseInlineMessageId(id)
}
let connection = this.primaryConnection
if (id.dcId !== connection.params.dc.id) {
if (!(id.dcId in this._connectionsForInline)) {
this._connectionsForInline[
id.dcId
] = await this.createAdditionalConnection(id.dcId)
}
connection = this._connectionsForInline[id.dcId]
}
await this.call(
{
_: 'messages.editInlineBotMessage',
id,
noWebpage: params.disableWebPreview,
replyMarkup: BotKeyboard._convertToTl(params.replyMarkup),
message: content,
entities,
media,
},
{ connection }
)
}

View file

@ -185,12 +185,10 @@ export namespace BotInlineMessage {
} }
export function media ( export function media (
text?: string, params?: Omit<InputInlineMessageMedia, 'type'>,
params?: Omit<InputInlineMessageMedia, 'type' | 'text'>,
): InputInlineMessageMedia { ): InputInlineMessageMedia {
return { return {
type: 'media', type: 'media',
text,
...( ...(
params || {} params || {}
), ),

View file

@ -546,13 +546,13 @@ export namespace BotInline {
export function photo( export function photo(
id: string, id: string,
media: string | tl.RawInputWebDocument | tl.RawInputPhoto, media: string | tl.RawInputWebDocument | tl.RawInputPhoto,
params: Omit<InputInlineResultPhoto, 'type' | 'id' | 'media'> params?: Omit<InputInlineResultPhoto, 'type' | 'id' | 'media'>
): InputInlineResultPhoto { ): InputInlineResultPhoto {
return { return {
id, id,
type: 'photo', type: 'photo',
media, media,
...params, ...(params || {}),
} }
} }

View file

@ -0,0 +1,25 @@
import { tl } from '@mtcute/tl'
import { encodeUrlSafeBase64, parseUrlSafeBase64 } from '@mtcute/file-id/src/utils'
import { BinaryReader, BinaryWriter } from '@mtcute/core'
export function parseInlineMessageId(id: string): tl.RawInputBotInlineMessageID {
const buf = parseUrlSafeBase64(id)
const reader = new BinaryReader(buf)
return {
_: 'inputBotInlineMessageID',
dcId: reader.int32(),
id: reader.long(),
accessHash: reader.long()
}
}
export function encodeInlineMessageId(id: tl.RawInputBotInlineMessageID): string {
const writer = BinaryWriter.alloc(20) // int32, int64, int64
writer.int32(id.dcId)
writer.long(id.id)
writer.long(id.accessHash)
return encodeUrlSafeBase64(writer.result())
}

View file

@ -456,7 +456,8 @@ export class BaseTelegramClient {
async call<T extends tl.RpcMethod>( async call<T extends tl.RpcMethod>(
message: T, message: T,
params?: { params?: {
throwFlood: boolean throwFlood?: boolean
connection?: TelegramConnection
} }
): Promise<tl.RpcCallReturn[T['_']]> { ): Promise<tl.RpcCallReturn[T['_']]> {
if (!this._connected) { if (!this._connected) {
@ -491,7 +492,7 @@ export class BaseTelegramClient {
for (let i = 0; i < this._rpcRetryCount; i++) { for (let i = 0; i < this._rpcRetryCount; i++) {
try { try {
const res = await this.primaryConnection.sendForResult( const res = await (params?.connection ?? this.primaryConnection).sendForResult(
message, message,
stack stack
) )

View file

@ -1,5 +1,6 @@
import { import {
ChatMemberUpdateHandler, ChatMemberUpdateHandler,
ChosenInlineResultHandler,
InlineQueryHandler, InlineQueryHandler,
NewMessageHandler, NewMessageHandler,
RawUpdateHandler, RawUpdateHandler,
@ -8,6 +9,7 @@ import {
import { filters, UpdateFilter } from './filters' import { filters, UpdateFilter } from './filters'
import { InlineQuery, Message } from '@mtcute/client' import { InlineQuery, Message } from '@mtcute/client'
import { ChatMemberUpdate } from './updates' import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result'
function _create<T extends UpdateHandler>( function _create<T extends UpdateHandler>(
type: T['type'], type: T['type'],
@ -18,18 +20,17 @@ function _create<T extends UpdateHandler>(
return { return {
type, type,
check: filter, check: filter,
callback: handler callback: handler,
} as any } as any
} }
return { return {
type, type,
callback: filter callback: filter,
} as any } as any
} }
export namespace handlers { export namespace handlers {
/** /**
* Create a {@link RawUpdateHandler} * Create a {@link RawUpdateHandler}
* *
@ -74,10 +75,7 @@ export namespace handlers {
handler: NewMessageHandler<filters.Modify<Message, Mod>>['callback'] handler: NewMessageHandler<filters.Modify<Message, Mod>>['callback']
): NewMessageHandler ): NewMessageHandler
export function newMessage( export function newMessage(filter: any, handler?: any): NewMessageHandler {
filter: any,
handler?: any
): NewMessageHandler {
return _create('new_message', filter, handler) return _create('new_message', filter, handler)
} }
@ -98,7 +96,9 @@ export namespace handlers {
*/ */
export function chatMemberUpdate<Mod>( export function chatMemberUpdate<Mod>(
filter: UpdateFilter<ChatMemberUpdate, Mod>, filter: UpdateFilter<ChatMemberUpdate, Mod>,
handler: ChatMemberUpdateHandler<filters.Modify<ChatMemberUpdate, Mod>>['callback'] handler: ChatMemberUpdateHandler<
filters.Modify<ChatMemberUpdate, Mod>
>['callback']
): ChatMemberUpdateHandler ): ChatMemberUpdateHandler
export function chatMemberUpdate( export function chatMemberUpdate(
@ -125,7 +125,9 @@ export namespace handlers {
*/ */
export function inlineQuery<Mod>( export function inlineQuery<Mod>(
filter: UpdateFilter<InlineQuery, Mod>, filter: UpdateFilter<InlineQuery, Mod>,
handler: InlineQueryHandler<filters.Modify<InlineQuery, Mod>>['callback'] handler: InlineQueryHandler<
filters.Modify<InlineQuery, Mod>
>['callback']
): InlineQueryHandler ): InlineQueryHandler
export function inlineQuery( export function inlineQuery(
@ -134,4 +136,33 @@ export namespace handlers {
): InlineQueryHandler { ): InlineQueryHandler {
return _create('inline_query', filter, handler) return _create('inline_query', filter, handler)
} }
/**
* Create a chosen inline result handler
*
* @param handler Chosen inline result handler
*/
export function chosenInlineResult(
handler: ChosenInlineResultHandler['callback']
): ChosenInlineResultHandler
/**
* Create a chosen inline result handler with a filter
*
* @param filter Chosen inline result filter
* @param handler Chosen inline result handler
*/
export function chosenInlineResult<Mod>(
filter: UpdateFilter<ChosenInlineResult, Mod>,
handler: ChosenInlineResultHandler<
filters.Modify<ChosenInlineResult, Mod>
>['callback']
): ChosenInlineResultHandler
export function chosenInlineResult(
filter: any,
handler?: any
): ChosenInlineResultHandler {
return _create('chosen_inline_result', filter, handler)
}
} }

View file

@ -12,7 +12,9 @@ import {
StopPropagation, StopPropagation,
} from './propagation' } from './propagation'
import { import {
ChatMemberUpdateHandler, InlineQueryHandler, ChatMemberUpdateHandler,
ChosenInlineResultHandler,
InlineQueryHandler,
NewMessageHandler, NewMessageHandler,
RawUpdateHandler, RawUpdateHandler,
UpdateHandler, UpdateHandler,
@ -20,6 +22,7 @@ import {
import { filters, UpdateFilter } from './filters' import { filters, UpdateFilter } from './filters'
import { handlers } from './builders' import { handlers } from './builders'
import { ChatMemberUpdate } from './updates' import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result'
const noop = () => {} const noop = () => {}
@ -69,6 +72,11 @@ const PARSERS: Partial<
'inline_query', 'inline_query',
(client, upd, users) => new InlineQuery(client, upd as any, users), (client, upd, users) => new InlineQuery(client, upd as any, users),
], ],
updateBotInlineSend: [
'chosen_inline_result',
(client, upd, users) =>
new ChosenInlineResult(client, upd as any, users),
],
} }
/** /**
@ -465,10 +473,7 @@ export class Dispatcher {
* @param group Handler group index * @param group Handler group index
* @internal * @internal
*/ */
onInlineQuery( onInlineQuery(handler: InlineQueryHandler['callback'], group?: number): void
handler: InlineQueryHandler['callback'],
group?: number
): void
/** /**
* Register an inline query handler with a given filter * Register an inline query handler with a given filter
@ -489,4 +494,36 @@ export class Dispatcher {
onInlineQuery(filter: any, handler?: any, group?: number): void { onInlineQuery(filter: any, handler?: any, group?: number): void {
this._addKnownHandler('inlineQuery', filter, handler, group) this._addKnownHandler('inlineQuery', filter, handler, group)
} }
/**
* Register a chosen inline result handler without any filters.
*
* @param handler Update handler
* @param group Handler group index
* @internal
*/
onChosenInlineResult(
handler: ChosenInlineResultHandler['callback'],
group?: number
): void
/**
* Register an inline query handler with a given filter
*
* @param filter Update filter
* @param handler Update handler
* @param group Handler group index
*/
onChosenInlineResult<Mod>(
filter: UpdateFilter<ChosenInlineResult, Mod>,
handler: ChosenInlineResultHandler<
filters.Modify<ChosenInlineResult, Mod>
>['callback'],
group?: number
): void
/** @internal */
onChosenInlineResult(filter: any, handler?: any, group?: number): void {
this._addKnownHandler('chosenInlineResult', filter, handler, group)
}
} }

View file

@ -1,7 +1,13 @@
import { MaybeAsync, Message, TelegramClient, InlineQuery } from '@mtcute/client' import {
MaybeAsync,
Message,
TelegramClient,
InlineQuery,
} from '@mtcute/client'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { PropagationSymbol } from './propagation' import { PropagationSymbol } from './propagation'
import { ChatMemberUpdate } from './updates' import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result'
interface BaseUpdateHandler<Type, Handler, Checker> { interface BaseUpdateHandler<Type, Handler, Checker> {
type: Type type: Type
@ -47,7 +53,13 @@ export type ChatMemberUpdateHandler<T = ChatMemberUpdate> = ParsedUpdateHandler<
'chat_member', 'chat_member',
T T
> >
export type InlineQueryHandler<T = InlineQuery> = ParsedUpdateHandler<'inline_query', T> export type InlineQueryHandler<T = InlineQuery> = ParsedUpdateHandler<
'inline_query',
T
>
export type ChosenInlineResultHandler<
T = ChosenInlineResult
> = ParsedUpdateHandler<'chosen_inline_result', T>
export type UpdateHandler = export type UpdateHandler =
| RawUpdateHandler | RawUpdateHandler
@ -55,3 +67,4 @@ export type UpdateHandler =
| EditMessageHandler | EditMessageHandler
| ChatMemberUpdateHandler | ChatMemberUpdateHandler
| InlineQueryHandler | InlineQueryHandler
| ChosenInlineResultHandler

View file

@ -0,0 +1,113 @@
import { makeInspectable } from '@mtcute/client/src/types/utils'
import { tl } from '@mtcute/tl'
import {
TelegramClient,
User,
Location,
MtCuteArgumentError,
} from '@mtcute/client'
import { encodeInlineMessageId } from '@mtcute/client/src/utils/inline-utils'
/**
* An inline result was chosen by the user and sent to some chat
*
* > **Note**: To receive these updates, you must enable
* > Inline feedback in [@BotFather](//t.me/botfather)
*/
export class ChosenInlineResult {
readonly client: TelegramClient
readonly raw: tl.RawUpdateBotInlineSend
readonly _users: Record<number, tl.TypeUser>
constructor(
client: TelegramClient,
raw: tl.RawUpdateBotInlineSend,
users: Record<number, tl.TypeUser>
) {
this.client = client
this.raw = raw
this._users = users
}
/**
* Unique identifier of the chosen result,
* as set in `InputInlineResult.id`
*/
get id(): string {
return this.raw.id
}
private _user?: User
/**
* User who has chosen the query
*/
get user(): User {
if (!this._user) {
this._user = new User(this.client, this._users[this.raw.userId])
}
return this._user
}
/**
* The query that was previously sent by the user,
* which was used to obtain this result
*/
get query(): string {
return this.raw.query
}
private _location?: Location
/**
* Sender location, only applicable to bots that requested user location
*/
get location(): Location | null {
if (this.raw.geo?._ !== 'geoPoint') return null
if (!this._location) {
this._location = new Location(this.raw.geo)
}
return this._location
}
/**
* Identifier of the sent inline message,
* which can be used in `TelegramClient.editInlineMessage`
*
* > **Note**: this is only available in case the `InputInlineMessage`
* > contained a reply keyboard markup.
*/
get messageId(): tl.TypeInputBotInlineMessageID | null {
return this.raw.msgId ?? null
}
/**
* Identifier of the sent inline message
* as a TDLib and Bot API compatible string.
* Can be used instead of {@link messageId} in
* case you want to store it in some storage.
*
* > **Note**: this is only available in case the `InputInlineMessage`
* > contained a reply keyboard markup.
*/
get messageIdStr(): string | null {
if (!this.raw.msgId) return null
return encodeInlineMessageId(this.raw.msgId)
}
async editMessage(
params: Parameters<TelegramClient['editInlineMessage']>[1]
): Promise<void> {
if (!this.raw.msgId)
throw new MtCuteArgumentError(
'No message ID, make sure you have included reply markup!'
)
return this.client.editInlineMessage(this.raw.msgId, params)
}
}
makeInspectable(ChosenInlineResult)