dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
1,210 lines (1,119 loc) • 37.2 kB
text/typescript
import { SenderGetter } from "./senderGetter";
import type { Entity, EntityLike } from "../../define";
import { Api } from "../api";
import type { TelegramClient } from "../..";
import { ChatGetter } from "./chatGetter";
import * as utils from "../../Utils";
import { Forward } from "./forward";
import { File } from "./file";
import {
EditMessageParams,
SendMessageParams,
UpdatePinMessageParams,
} from "../../client/messages";
import { DownloadMediaInterface } from "../../client/downloads";
import { betterConsoleLog, returnBigInt } from "../../Helpers";
import { _selfId } from "../../client/users";
import bigInt, { BigInteger } from "big-integer";
import { LogLevel } from "../../extensions/Logger";
import { MessageButton } from "./messageButton";
import { inspect } from "../../inspect";
interface MessageBaseInterface {
id: any;
peerId?: any;
date?: any;
out?: any;
mentioned?: any;
mediaUnread?: any;
silent?: any;
post?: any;
fromId?: any;
replyTo?: any;
message?: any;
fwdFrom?: any;
viaBotId?: any;
media?: any;
replyMarkup?: any;
entities?: any;
views?: any;
editDate?: any;
postAuthor?: any;
groupedId?: any;
fromScheduled?: any;
legacy?: any;
editHide?: any;
pinned?: any;
restrictionReason?: any;
forwards?: any;
ttlPeriod?: number;
replies?: any;
action?: any;
reactions?: any;
noforwards?: any;
_entities?: Map<string, Entity>;
}
/**
* Interface for clicking a message button.
* Calls `SendVote` with the specified poll option
* or `button.click <MessageButton.click>`
* on the specified button.
* Does nothing if the message is not a poll or has no buttons.
* @example
* ```ts
* # Click the first button
* await message.click(0)
*
* # Click some row/column
* await message.click(row, column)
*
* # Click by text
* await message.click(text='👍')
*
* # Click by data
* await message.click(data=b'payload')
*
* # Click on a button requesting a phone
* await message.click(0, share_phone=True)
* ```
*/
export interface ButtonClickParam {
/** Clicks the i'th button or poll option (starting from the index 0).
For multiple-choice polls, a list with the indices should be used.
Will ``raise IndexError`` if out of bounds. Example:
>>> message = ... # get the message somehow
>>> # Clicking the 3rd button
>>> # [button1] [button2]
>>> # [ button3 ]
>>> # [button4] [button5]
>>> await message.click(2) # index
. */
i?: number | number[];
/**
* Clicks the button at position (i, j), these being the
* indices for the (row, column) respectively. Example:
>>> # Clicking the 2nd button on the 1st row.
>>> # [button1] [button2]
>>> # [ button3 ]
>>> # [button4] [button5]
>>> await message.click(0, 1) # (row, column)
This is equivalent to ``message.buttons[0][1].click()``.
*/
j?: number;
/** Clicks the first button or poll option with the text "text". This may
also be a callable, like a ``new RegExp('foo*').test``,
and the text will be passed to it.
If you need to select multiple options in a poll,
pass a list of indices to the ``i`` parameter.
*/
text?: string | Function;
/** Clicks the first button or poll option for which the callable
returns `True`. The callable should accept a single
`MessageButton <messagebutton.MessageButton>`
or `PollAnswer <PollAnswer>` argument.
If you need to select multiple options in a poll,
pass a list of indices to the ``i`` parameter.
*/
filter?: Function;
/** This argument overrides the rest and will not search any
buttons. Instead, it will directly send the request to
behave as if it clicked a button with said data. Note
that if the message does not have this data, it will
``DATA_INVALID``.
*/
data?: Buffer;
/** When clicking on a keyboard button requesting a phone number
(`KeyboardButtonRequestPhone`), this argument must be
explicitly set to avoid accidentally sharing the number.
It can be `true` to automatically share the current user's
phone, a string to share a specific phone number, or a contact
media to specify all details.
If the button is pressed without this, `new Error("Value Error")` is raised.
*/
sharePhone?: boolean | string | Api.InputMediaContact;
/** When clicking on a keyboard button requesting a geo location
(`KeyboardButtonRequestGeoLocation`), this argument must
be explicitly set to avoid accidentally sharing the location.
It must be a `list` of `float` as ``(longitude, latitude)``,
or a :tl:`InputGeoPoint` instance to avoid accidentally using
the wrong roder.
If the button is pressed without this, `ValueError` is raised.
*/
shareGeo?: [number, number] | Api.InputMediaGeoPoint;
/**When clicking certain buttons (such as BotFather's confirmation
button to transfer ownership), if your account has 2FA enabled,
you need to provide your account's password. Otherwise,
`PASSWORD_HASH_INVALID` is raised.
*/
password?: string;
}
/**
* This custom class aggregates both {@link Api.Message} and {@link Api.MessageService} to ease accessing their members.<br/>
* <br/>
* Remember that this class implements {@link ChatGetter} and {@link SenderGetter}<br/>
* which means you have access to all their sender and chat properties and methods.
*/
export class CustomMessage extends SenderGetter {
static CONSTRUCTOR_ID: number;
static SUBCLASS_OF_ID: number;
CONSTRUCTOR_ID!: number;
SUBCLASS_OF_ID!: number;
/**
* Whether the message is outgoing (i.e. you sent it from
* another session) or incoming (i.e. someone else sent it).
* <br/>
* Note that messages in your own chat are always incoming,
* but this member will be `true` if you send a message
* to your own chat. Messages you forward to your chat are
* **not** considered outgoing, just like official clients
* display them.
*/
out?: boolean;
/**
* Whether you were mentioned in this message or not.
* Note that replies to your own messages also count as mentions.
*/
mentioned?: boolean;
/** Whether you have read the media in this message
* or not, e.g. listened to the voice note media.
*/
mediaUnread?: boolean;
/**
* Whether the message should notify people with sound or not.
* Previously used in channels, but since 9 August 2019, it can
* also be {@link https://telegram.org/blog/silent-messages-slow-mode|used in private chats}
*/
silent?: boolean;
/**
* Whether this message is a post in a broadcast
* channel or not.
*/
post?: boolean;
/**
* Whether this message was originated from a previously-scheduled
* message or not.
*/
fromScheduled?: boolean;
/**
* Whether this is a legacy message or not.
*/
legacy?: boolean;
/**
* Whether the edited mark of this message is edited
* should be hidden (e.g. in GUI clients) or shown.
*/
editHide?: boolean;
/**
* Whether this message is currently pinned or not.
*/
pinned?: boolean;
/**
* The ID of this message. This field is *always* present.
* Any other member is optional and may be `undefined`.
*/
id!: number;
/**
* The peer who sent this message, which is either
* {@link Api.PeerUser}, {@link Api.PeerChat} or {@link Api.PeerChannel}.
* This value will be `undefined` for anonymous messages.
*/
fromId?: Api.TypePeer;
/**
* The peer to which this message was sent, which is either
* {@link Api.PeerUser}, {@link Api.PeerChat} or {@link Api.PeerChannel}.
* This will always be present except for empty messages.
*/
peerId!: Api.TypePeer;
/**
* The original forward header if this message is a forward.
* You should probably use the `forward` property instead.
*/
fwdFrom?: Api.TypeMessageFwdHeader;
/**
* The ID of the bot used to send this message
* through its inline mode (e.g. "via @like").
*/
viaBotId?: bigInt.BigInteger;
/**
* The original reply header if this message is replying to another.
*/
replyTo?: Api.MessageReplyHeader;
/**
* The timestamp indicating when this message was sent.
* This will always be present except for empty messages.
*/
date!: number;
/**
* The string text of the message for {@link Api.Message} instances,
* which will be `undefined` for other types of messages.
*/
message!: string;
/**
* The media sent with this message if any (such as photos, videos, documents, gifs, stickers, etc.).
*
* You may want to access the `photo`, `document` etc. properties instead.
*
* If the media was not present or it was {@link Api.MessageMediaEmpty},
* this member will instead be `undefined` for convenience.
*/
media?: Api.TypeMessageMedia;
/**
* The reply markup for this message (which was sent either via a bot or by a bot).
* You probably want to access `buttons` instead.
*/
replyMarkup?: Api.TypeReplyMarkup;
/**
* The list of markup entities in this message,
* such as bold, italics, code, hyperlinks, etc.
*/
entities?: Api.TypeMessageEntity[];
/**
* The number of views this message from a broadcast channel has.
* This is also present in forwards.
*/
views?: number;
/**
* The number of times this message has been forwarded.
*/
forwards?: number;
/**
* The number of times another message has replied to this message.
*/
replies?: Api.TypeMessageReplies;
/**
* The date when this message was last edited.
*/
editDate?: number;
/**
* The display name of the message sender to show in messages sent to broadcast channels.
*/
postAuthor?: string;
/**
* If this message belongs to a group of messages (photo albums or video albums),
* all of them will have the same value here.
*/
groupedId?: BigInteger;
/**
* An optional list of reasons why this message was restricted.
* If the list is `undefined`, this message has not been restricted.
*/
restrictionReason?: Api.TypeRestrictionReason[];
/**
* The message action object of the message for {@link Api.MessageService}
* instances, which will be `undefined` for other types of messages.
*/
action!: Api.TypeMessageAction;
/**
* The Time To Live period configured for this message.
* The message should be erased from wherever it's stored (memory, a
* local database, etc.) when this threshold is met.
*/
ttlPeriod?: number;
reactions?: Api.MessageReactions;
noforwards?: boolean;
/** @hidden */
_actionEntities?: any;
/** @hidden */
_client?: TelegramClient;
/** @hidden */
_text?: string;
/** @hidden */
_file?: File;
/** @hidden */
_replyMessage?: Api.Message;
/** @hidden */
_buttons?: MessageButton[][];
/** @hidden */
_buttonsFlat?: MessageButton[];
/** @hidden */
_buttonsCount?: number;
/** @hidden */
_viaBot?: EntityLike;
/** @hidden */
_viaInputBot?: EntityLike;
/** @hidden */
_inputSender?: any;
/** @hidden */
_forward?: Forward;
/** @hidden */
_sender?: any;
/** @hidden */
_entities?: Map<string, Entity>;
/** @hidden */
/* @ts-ignore */
getBytes(): Buffer;
originalArgs: any;
patternMatch?: RegExpMatchArray;
[inspect.custom]() {
return betterConsoleLog(this);
}
init({
id,
peerId = undefined,
date = undefined,
out = undefined,
mentioned = undefined,
mediaUnread = undefined,
silent = undefined,
post = undefined,
fromId = undefined,
replyTo = undefined,
message = undefined,
fwdFrom = undefined,
viaBotId = undefined,
media = undefined,
replyMarkup = undefined,
entities = undefined,
views = undefined,
editDate = undefined,
postAuthor = undefined,
groupedId = undefined,
fromScheduled = undefined,
legacy = undefined,
editHide = undefined,
pinned = undefined,
restrictionReason = undefined,
forwards = undefined,
replies = undefined,
action = undefined,
reactions = undefined,
noforwards = undefined,
ttlPeriod = undefined,
_entities = new Map<string, Entity>(),
}: MessageBaseInterface) {
if (!id) throw new Error("id is a required attribute for Message");
let senderId = undefined;
if (fromId) {
senderId = utils.getPeerId(fromId);
} else if (peerId) {
if (post || (!out && peerId instanceof Api.PeerUser)) {
senderId = utils.getPeerId(peerId);
}
}
// Common properties to all messages
this._entities = _entities;
this.out = out;
this.mentioned = mentioned;
this.mediaUnread = mediaUnread;
this.silent = silent;
this.post = post;
this.post = post;
this.fromScheduled = fromScheduled;
this.legacy = legacy;
this.editHide = editHide;
this.ttlPeriod = ttlPeriod;
this.id = id;
this.fromId = fromId;
this.peerId = peerId;
this.fwdFrom = fwdFrom;
this.viaBotId = viaBotId;
this.replyTo = replyTo;
this.date = date;
this.message = message;
this.media = media instanceof Api.MessageMediaEmpty ? media : undefined;
this.replyMarkup = replyMarkup;
this.entities = entities;
this.views = views;
this.forwards = forwards;
this.replies = replies;
this.editDate = editDate;
this.pinned = pinned;
this.postAuthor = postAuthor;
this.groupedId = groupedId;
this.restrictionReason = restrictionReason;
this.action = action;
this.noforwards = noforwards;
this.reactions = reactions;
this._client = undefined;
this._text = undefined;
this._file = undefined;
this._replyMessage = undefined;
this._buttons = undefined;
this._buttonsFlat = undefined;
this._buttonsCount = 0;
this._viaBot = undefined;
this._viaInputBot = undefined;
this._actionEntities = undefined;
// Note: these calls would reset the client
ChatGetter.initChatClass(this, { chatPeer: peerId, broadcast: post });
SenderGetter.initSenderClass(this, {
senderId: senderId ? returnBigInt(senderId) : undefined,
});
this._forward = undefined;
}
constructor(args: MessageBaseInterface) {
super();
this.init(args);
}
_finishInit(
client: TelegramClient,
entities: Map<string, Entity>,
inputChat?: EntityLike
) {
this._client = client;
const cache = client._entityCache;
if (this.senderId) {
[this._sender, this._inputSender] = utils._getEntityPair(
this.senderId.toString(),
entities,
cache
);
}
if (this.chatId) {
[this._chat, this._inputChat] = utils._getEntityPair(
this.chatId.toString(),
entities,
cache
);
}
if (inputChat) {
// This has priority
this._inputChat = inputChat;
}
if (this.viaBotId) {
[this._viaBot, this._viaInputBot] = utils._getEntityPair(
this.viaBotId.toString(),
entities,
cache
);
}
if (this.fwdFrom) {
this._forward = new Forward(this._client, this.fwdFrom, entities);
}
if (this.action) {
if (
this.action instanceof Api.MessageActionChatAddUser ||
this.action instanceof Api.MessageActionChatCreate
) {
this._actionEntities = this.action.users.map((i) =>
entities.get(i.toString())
);
} else if (this.action instanceof Api.MessageActionChatDeleteUser) {
this._actionEntities = [
entities.get(this.action.userId.toString()),
];
} else if (
this.action instanceof Api.MessageActionChatJoinedByLink
) {
this._actionEntities = [
entities.get(
utils.getPeerId(
new Api.PeerChannel({
channelId: this.action.inviterId,
})
)
),
];
} else if (
this.action instanceof Api.MessageActionChannelMigrateFrom
) {
this._actionEntities = [
entities.get(
utils.getPeerId(
new Api.PeerChat({ chatId: this.action.chatId })
)
),
];
}
}
}
get client() {
return this._client;
}
get text() {
if (this._text === undefined && this._client) {
if (!this._client.parseMode) {
this._text = this.message;
} else {
this._text = this._client.parseMode.unparse(
this.message || "",
this.entities || []
);
}
}
return this._text || "";
}
set text(value: string) {
this._text = value;
if (this._client && this._client.parseMode) {
[this.message, this.entities] = this._client.parseMode.parse(value);
} else {
this.message = value;
this.entities = [];
}
}
get rawText() {
return this.message || "";
}
/**
* @param {string} value
*/
set rawText(value: string) {
this.message = value;
this.entities = [];
this._text = "";
}
get isReply(): boolean {
return !!this.replyTo;
}
get forward() {
return this._forward;
}
async _refetchSender() {
await this._reloadMessage();
}
/**
* Re-fetches this message to reload the sender and chat entities,
* along with their input versions.
* @private
*/
async _reloadMessage() {
if (!this._client) return;
let msg: CustomMessage | undefined = undefined;
try {
const chat = this.isChannel ? await this.getInputChat() : undefined;
let temp = await this._client.getMessages(chat, { ids: this.id });
if (temp) {
msg = temp[0] as CustomMessage;
}
} catch (e) {
this._client._log.error(
"Got error while trying to finish init message with id " +
this.id
);
if (this._client._log.canSend(LogLevel.ERROR)) {
console.error(e);
}
}
if (msg == undefined) return;
this._sender = msg._sender;
this._inputSender = msg._inputSender;
this._chat = msg._chat;
this._inputChat = msg._inputChat;
this._viaBot = msg._viaBot;
this._viaInputBot = msg._viaInputBot;
this._forward = msg._forward;
this._actionEntities = msg._actionEntities;
}
/**
* Returns a list of lists of `MessageButton <MessageButton>`, if any.
* Otherwise, it returns `undefined`.
*/
get buttons() {
if (!this._buttons && this.replyMarkup) {
if (!this.inputChat) {
return;
}
try {
const bot = this._neededMarkupBot();
this._setButtons(this.inputChat, bot);
} catch (e) {
return;
}
}
return this._buttons;
}
/**
* Returns `buttons` when that property fails (this is rarely needed).
*/
async getButtons() {
if (!this.buttons && this.replyMarkup) {
const chat = await this.getInputChat();
if (!chat) return;
let bot;
try {
bot = this._neededMarkupBot();
} catch (e) {
await this._reloadMessage();
bot = this._neededMarkupBot();
}
this._setButtons(chat, bot);
}
return this._buttons;
}
get buttonCount() {
if (!this._buttonsCount) {
if (
this.replyMarkup instanceof Api.ReplyInlineMarkup ||
this.replyMarkup instanceof Api.ReplyKeyboardMarkup
) {
this._buttonsCount = this.replyMarkup.rows
.map((r) => r.buttons.length)
.reduce(function (a, b) {
return a + b;
}, 0);
} else {
this._buttonsCount = 0;
}
}
return this._buttonsCount;
}
get file() {
if (!this._file) {
const media = this.photo || this.document;
if (media) {
this._file = new File(media);
}
}
return this._file;
}
get photo() {
if (this.media instanceof Api.MessageMediaPhoto) {
if (this.media.photo instanceof Api.Photo) return this.media.photo;
} else if (this.action instanceof Api.MessageActionChatEditPhoto) {
return this.action.photo;
} else {
return this.webPreview && this.webPreview.photo instanceof Api.Photo
? this.webPreview.photo
: undefined;
}
return undefined;
}
get document() {
if (this.media instanceof Api.MessageMediaDocument) {
if (this.media.document instanceof Api.Document)
return this.media.document;
} else {
const web = this.webPreview;
return web && web.document instanceof Api.Document
? web.document
: undefined;
}
return undefined;
}
get webPreview() {
if (this.media instanceof Api.MessageMediaWebPage) {
if (this.media.webpage instanceof Api.WebPage)
return this.media.webpage;
}
}
get audio() {
return this._documentByAttribute(
Api.DocumentAttributeAudio,
(attr: Api.DocumentAttributeAudio) => !attr.voice
);
}
get voice() {
return this._documentByAttribute(
Api.DocumentAttributeAudio,
(attr: Api.DocumentAttributeAudio) => !!attr.voice
);
}
get video() {
return this._documentByAttribute(Api.DocumentAttributeVideo);
}
get videoNote() {
return this._documentByAttribute(
Api.DocumentAttributeVideo,
(attr: Api.DocumentAttributeVideo) => !!attr.roundMessage
);
}
get gif() {
return this._documentByAttribute(Api.DocumentAttributeAnimated);
}
get sticker() {
return this._documentByAttribute(Api.DocumentAttributeSticker);
}
get contact() {
if (this.media instanceof Api.MessageMediaContact) {
return this.media;
}
}
get game() {
if (this.media instanceof Api.MessageMediaGame) {
return this.media.game;
}
}
get geo() {
if (
this.media instanceof Api.MessageMediaGeo ||
this.media instanceof Api.MessageMediaGeoLive ||
this.media instanceof Api.MessageMediaVenue
) {
return this.media.geo;
}
}
get invoice() {
if (this.media instanceof Api.MessageMediaInvoice) {
return this.media;
}
}
get poll() {
if (this.media instanceof Api.MessageMediaPoll) {
return this.media;
}
}
get venue() {
if (this.media instanceof Api.MessageMediaVenue) {
return this.media;
}
}
get dice() {
if (this.media instanceof Api.MessageMediaDice) {
return this.media;
}
}
get actionEntities() {
return this._actionEntities;
}
get viaBot() {
return this._viaBot;
}
get viaInputBot() {
return this._viaInputBot;
}
get replyToMsgId() {
return this.replyTo?.replyToMsgId;
}
get toId() {
if (this._client && !this.out && this.isPrivate) {
return new Api.PeerUser({
userId: _selfId(this._client)!,
});
}
return this.peerId;
}
getEntitiesText(cls?: Function) {
let ent = this.entities;
if (!ent || ent.length == 0) return;
if (cls) {
ent = ent.filter((v: any) => v instanceof cls);
}
const texts = utils.getInnerText(this.message || "", ent);
const zip = (rows: any[]) =>
rows[0].map((_: any, c: string | number) =>
rows.map((row) => row[c])
);
return zip([ent, texts]);
}
async getReplyMessage(): Promise<Api.Message | undefined> {
if (!this._replyMessage && this._client) {
if (!this.replyTo) return undefined;
// Bots cannot access other bots' messages by their ID.
// However they can access them through replies...
this._replyMessage = (
await this._client.getMessages(
this.isChannel ? await this.getInputChat() : undefined,
{
ids: new Api.InputMessageReplyTo({ id: this.id }),
}
)
)[0];
if (!this._replyMessage) {
// ...unless the current message got deleted.
//
// If that's the case, give it a second chance accessing
// directly by its ID.
this._replyMessage = (
await this._client.getMessages(
this.isChannel ? this._inputChat : undefined,
{
ids: this.replyToMsgId,
}
)
)[0];
}
}
return this._replyMessage;
}
async respond(params: SendMessageParams) {
if (this._client) {
return this._client.sendMessage(
(await this.getInputChat())!,
params
);
}
}
async reply(params: SendMessageParams) {
if (this._client) {
params.replyTo = this.id;
return this._client.sendMessage(
(await this.getInputChat())!,
params
);
}
}
async forwardTo(entity: EntityLike) {
if (this._client) {
entity = await this._client.getInputEntity(entity);
const params = {
messages: [this.id],
fromPeer: (await this.getInputChat())!,
};
return this._client.forwardMessages(entity, params);
}
}
async edit(params: Omit<EditMessageParams, "message">) {
const param = params as EditMessageParams;
if (this.fwdFrom || !this.out || !this._client) return undefined;
if (param.linkPreview == undefined) {
param.linkPreview = !!this.webPreview;
}
if (param.buttons == undefined) {
param.buttons = this.replyMarkup;
}
param.message = this.id;
return this._client.editMessage((await this.getInputChat())!, param);
}
async delete({ revoke } = { revoke: false }) {
if (this._client) {
return this._client.deleteMessages(
await this.getInputChat(),
[this.id],
{
revoke,
}
);
}
}
async pin(params?: UpdatePinMessageParams) {
if (this._client) {
const entity = await this.getInputChat();
if (entity === undefined) {
throw Error(
"Failed to pin message due to cannot get input chat."
);
}
return this._client.pinMessage(entity, this.id, params);
}
}
async unpin(params?: UpdatePinMessageParams) {
if (this._client) {
const entity = await this.getInputChat();
if (entity === undefined) {
throw Error(
"Failed to unpin message due to cannot get input chat."
);
}
return this._client.unpinMessage(entity, this.id, params);
}
}
async downloadMedia(params?: DownloadMediaInterface) {
// small hack for patched method
if (this._client)
return this._client.downloadMedia(this as any, params || {});
}
async markAsRead() {
if (this._client) {
const entity = await this.getInputChat();
if (entity === undefined) {
throw Error(
`Failed to mark message id ${this.id} as read due to cannot get input chat.`
);
}
return this._client.markAsRead(entity, this.id);
}
}
async click({
i,
j,
text,
filter,
data,
sharePhone,
shareGeo,
password,
}: ButtonClickParam) {
if (!this.client) {
return;
}
if (data) {
const chat = await this.getInputChat();
if (!chat) {
return;
}
const button = new Api.KeyboardButtonCallback({
text: "",
data: data,
});
return await new MessageButton(
this.client,
button,
chat,
undefined,
this.id
).click({
sharePhone: sharePhone,
shareGeo: shareGeo,
password: password,
});
}
if (this.poll) {
function findPoll(answers: Api.PollAnswer[]) {
if (i != undefined) {
if (Array.isArray(i)) {
const corrects = [];
for (let x = 0; x < i.length; x++) {
corrects.push(answers[x].option);
}
return corrects;
}
return [answers[i].option];
}
if (text != undefined) {
if (typeof text == "function") {
for (const answer of answers) {
if (text(answer.text)) {
return [answer.option];
}
}
} else {
for (const answer of answers) {
if (answer.text == text) {
return [answer.option];
}
}
}
return;
}
if (filter != undefined) {
for (const answer of answers) {
if (filter(answer)) {
return [answer.option];
}
}
return;
}
}
const options = findPoll(this.poll.poll.answers) || [];
return await this.client.invoke(
new Api.messages.SendVote({
peer: this.inputChat,
msgId: this.id,
options: options,
})
);
}
if (!(await this.getButtons())) {
return; // Accessing the property sets this._buttons[_flat]
}
function findButton(self: CustomMessage) {
if (!self._buttonsFlat || !self._buttons) {
return;
}
if (Array.isArray(i)) {
i = i[0];
}
if (text != undefined) {
if (typeof text == "function") {
for (const button of self._buttonsFlat) {
if (text(button.text)) {
return button;
}
}
} else {
for (const button of self._buttonsFlat) {
if (button.text == text) {
return button;
}
}
}
return;
}
if (filter != undefined) {
for (const button of self._buttonsFlat) {
if (filter(button)) {
return button;
}
}
return;
}
if (i == undefined) {
i = 0;
}
if (j == undefined) {
return self._buttonsFlat[i];
} else {
return self._buttons[i][j];
}
}
const button = findButton(this);
if (button) {
return await button.click({
sharePhone: sharePhone,
shareGeo: shareGeo,
password: password,
});
}
}
/**
* Helper methods to set the buttons given the input sender and chat.
*/
_setButtons(chat: EntityLike, bot?: EntityLike) {
if (
this.client &&
(this.replyMarkup instanceof Api.ReplyInlineMarkup ||
this.replyMarkup instanceof Api.ReplyKeyboardMarkup)
) {
this._buttons = [];
this._buttonsFlat = [];
for (const row of this.replyMarkup.rows) {
const tmp = [];
for (const button of row.buttons) {
const btn = new MessageButton(
this.client,
button,
chat,
bot,
this.id
);
tmp.push(btn);
this._buttonsFlat.push(btn);
}
this._buttons.push(tmp);
}
}
}
/**
*Returns the input peer of the bot that's needed for the reply markup.
This is necessary for `KeyboardButtonSwitchInline` since we need
to know what bot we want to start. Raises ``Error`` if the bot
cannot be found but is needed. Returns `None` if it's not needed.
*/
_neededMarkupBot() {
if (!this.client || this.replyMarkup == undefined) {
return;
}
if (
!(
this.replyMarkup instanceof Api.ReplyInlineMarkup ||
this.replyMarkup instanceof Api.ReplyKeyboardMarkup
)
) {
return;
}
for (const row of this.replyMarkup.rows) {
for (const button of row.buttons) {
if (button instanceof Api.KeyboardButtonSwitchInline) {
if (button.samePeer || !this.viaBotId) {
const bot = this._inputSender;
if (!bot) throw new Error("No input sender");
return bot;
} else {
const ent = this.client!._entityCache.get(
this.viaBotId
);
if (!ent) throw new Error("No input sender");
return ent;
}
}
}
}
}
// TODO fix this
_documentByAttribute(kind: Function, condition?: Function) {
const doc = this.document;
if (doc) {
for (const attr of doc.attributes) {
if (attr instanceof kind) {
if (
condition == undefined ||
(typeof condition == "function" && condition(attr))
) {
return doc;
}
return undefined;
}
}
}
}
}