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 { uploadFile } from './methods/files/upload-file'
import { deleteMessages } from './methods/messages/delete-messages'
import { editInlineMessage } from './methods/messages/edit-inline-message'
import { editMessage } from './methods/messages/edit-message'
import { _findMessageInUpdate } from './methods/messages/find-in-update'
import { forwardMessages } from './methods/messages/forward-messages'
@ -1136,6 +1137,67 @@ export interface TelegramClient extends BaseTelegramClient {
ids: MaybeArray<number>,
revoke?: 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.
*
@ -1959,6 +2021,7 @@ export class TelegramClient extends BaseTelegramClient {
protected _userId: number | null
protected _isBot: boolean
protected _downloadConnections: Record<number, TelegramConnection>
protected _connectionsForInline: Record<number, TelegramConnection>
protected _parseModes: Record<string, IMessageEntityParser>
protected _defaultParseMode: string | null
protected _updLock: Lock
@ -1970,6 +2033,7 @@ export class TelegramClient extends BaseTelegramClient {
this._userId = null
this._isBot = false
this._downloadConnections = {}
this._connectionsForInline = {}
this._parseModes = {}
this._defaultParseMode = null
this._updLock = new Lock()
@ -2041,6 +2105,7 @@ export class TelegramClient extends BaseTelegramClient {
protected _normalizeInputMedia = _normalizeInputMedia
uploadFile = uploadFile
deleteMessages = deleteMessages
editInlineMessage = editInlineMessage
editMessage = editMessage
protected _findMessageInUpdate = _findMessageInUpdate
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 (
text?: string,
params?: Omit<InputInlineMessageMedia, 'type' | 'text'>,
params?: Omit<InputInlineMessageMedia, 'type'>,
): InputInlineMessageMedia {
return {
type: 'media',
text,
...(
params || {}
),

View file

@ -546,13 +546,13 @@ export namespace BotInline {
export function photo(
id: string,
media: string | tl.RawInputWebDocument | tl.RawInputPhoto,
params: Omit<InputInlineResultPhoto, 'type' | 'id' | 'media'>
params?: Omit<InputInlineResultPhoto, 'type' | 'id' | 'media'>
): InputInlineResultPhoto {
return {
id,
type: 'photo',
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>(
message: T,
params?: {
throwFlood: boolean
throwFlood?: boolean
connection?: TelegramConnection
}
): Promise<tl.RpcCallReturn[T['_']]> {
if (!this._connected) {
@ -491,7 +492,7 @@ export class BaseTelegramClient {
for (let i = 0; i < this._rpcRetryCount; i++) {
try {
const res = await this.primaryConnection.sendForResult(
const res = await (params?.connection ?? this.primaryConnection).sendForResult(
message,
stack
)

View file

@ -1,5 +1,6 @@
import {
ChatMemberUpdateHandler,
ChosenInlineResultHandler,
InlineQueryHandler,
NewMessageHandler,
RawUpdateHandler,
@ -8,6 +9,7 @@ import {
import { filters, UpdateFilter } from './filters'
import { InlineQuery, Message } from '@mtcute/client'
import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result'
function _create<T extends UpdateHandler>(
type: T['type'],
@ -18,18 +20,17 @@ function _create<T extends UpdateHandler>(
return {
type,
check: filter,
callback: handler
callback: handler,
} as any
}
return {
type,
callback: filter
callback: filter,
} as any
}
export namespace handlers {
/**
* Create a {@link RawUpdateHandler}
*
@ -74,10 +75,7 @@ export namespace handlers {
handler: NewMessageHandler<filters.Modify<Message, Mod>>['callback']
): NewMessageHandler
export function newMessage(
filter: any,
handler?: any
): NewMessageHandler {
export function newMessage(filter: any, handler?: any): NewMessageHandler {
return _create('new_message', filter, handler)
}
@ -98,7 +96,9 @@ export namespace handlers {
*/
export function chatMemberUpdate<Mod>(
filter: UpdateFilter<ChatMemberUpdate, Mod>,
handler: ChatMemberUpdateHandler<filters.Modify<ChatMemberUpdate, Mod>>['callback']
handler: ChatMemberUpdateHandler<
filters.Modify<ChatMemberUpdate, Mod>
>['callback']
): ChatMemberUpdateHandler
export function chatMemberUpdate(
@ -125,7 +125,9 @@ export namespace handlers {
*/
export function inlineQuery<Mod>(
filter: UpdateFilter<InlineQuery, Mod>,
handler: InlineQueryHandler<filters.Modify<InlineQuery, Mod>>['callback']
handler: InlineQueryHandler<
filters.Modify<InlineQuery, Mod>
>['callback']
): InlineQueryHandler
export function inlineQuery(
@ -134,4 +136,33 @@ export namespace handlers {
): InlineQueryHandler {
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,
} from './propagation'
import {
ChatMemberUpdateHandler, InlineQueryHandler,
ChatMemberUpdateHandler,
ChosenInlineResultHandler,
InlineQueryHandler,
NewMessageHandler,
RawUpdateHandler,
UpdateHandler,
@ -20,6 +22,7 @@ import {
import { filters, UpdateFilter } from './filters'
import { handlers } from './builders'
import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result'
const noop = () => {}
@ -69,6 +72,11 @@ const PARSERS: Partial<
'inline_query',
(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
* @internal
*/
onInlineQuery(
handler: InlineQueryHandler['callback'],
group?: number
): void
onInlineQuery(handler: InlineQueryHandler['callback'], group?: number): void
/**
* Register an inline query handler with a given filter
@ -489,4 +494,36 @@ export class Dispatcher {
onInlineQuery(filter: any, handler?: any, group?: number): void {
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 { PropagationSymbol } from './propagation'
import { ChatMemberUpdate } from './updates'
import { ChosenInlineResult } from './updates/chosen-inline-result'
interface BaseUpdateHandler<Type, Handler, Checker> {
type: Type
@ -47,7 +53,13 @@ export type ChatMemberUpdateHandler<T = ChatMemberUpdate> = ParsedUpdateHandler<
'chat_member',
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 =
| RawUpdateHandler
@ -55,3 +67,4 @@ export type UpdateHandler =
| EditMessageHandler
| ChatMemberUpdateHandler
| 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)