import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiBotApp, ApiBotPreviewMedia, ApiChat, ApiInputMessageReplyInfo, ApiPeer, ApiThemeParameters, ApiUser, OnApiUpdate, } from '../../types'; import { WEB_APP_PLATFORM } from '../../../config'; import { buildCollectionByKey } from '../../../util/iteratees'; import { buildApiAttachBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildApiMessagesBotApp, buildBotSwitchPm, buildBotSwitchWebview, } from '../apiBuilders/bots'; import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { omitVirtualClassFields } from '../apiBuilders/helpers'; import { buildMessageMediaContent } from '../apiBuilders/messageContent'; import { buildApiUrlAuthResult } from '../apiBuilders/misc'; import { buildApiUser } from '../apiBuilders/users'; import { buildInputBotApp, buildInputEntity, buildInputPeer, buildInputReplyTo, buildInputThemeParams, generateRandomBigInt, } from '../gramjsBuilders'; import { addDocumentToLocalDb, addEntitiesToLocalDb, addPhotoToLocalDb, addUserToLocalDb, addWebDocumentToLocalDb, deserializeBytes, } from '../helpers'; import { invokeRequest } from './client'; let onUpdate: OnApiUpdate; export function init(_onUpdate: OnApiUpdate) { onUpdate = _onUpdate; } export async function answerCallbackButton({ chatId, accessHash, messageId, data, isGame, }: { chatId: string; accessHash?: string; messageId: number; data?: string; isGame?: boolean; }) { const result = await invokeRequest(new GramJs.messages.GetBotCallbackAnswer({ peer: buildInputPeer(chatId, accessHash), msgId: messageId, data: data ? deserializeBytes(data) : undefined, game: isGame || undefined, })); return result ? omitVirtualClassFields(result) : undefined; } export async function fetchTopInlineBots() { const topPeers = await invokeRequest(new GramJs.contacts.GetTopPeers({ botsInline: true, })); if (!(topPeers instanceof GramJs.contacts.TopPeers)) { return undefined; } const users = topPeers.users.map(buildApiUser).filter(Boolean); const ids = users.map(({ id }) => id); return { ids, users, }; } export async function fetchTopBotApps() { const topPeers = await invokeRequest(new GramJs.contacts.GetTopPeers({ botsApp: true, })); if (!(topPeers instanceof GramJs.contacts.TopPeers)) { return undefined; } const users = topPeers.users.map(buildApiUser).filter(Boolean); const ids = users.map(({ id }) => id); return { ids, users, }; } export async function fetchInlineBot({ username }: { username: string }) { const resolvedPeer = await invokeRequest(new GramJs.contacts.ResolveUsername({ username })); if ( !resolvedPeer || !( resolvedPeer.users[0] instanceof GramJs.User && resolvedPeer.users[0].bot && resolvedPeer.users[0].botInlinePlaceholder ) ) { return undefined; } addUserToLocalDb(resolvedPeer.users[0]); return { user: buildApiUser(resolvedPeer.users[0]), chat: buildApiChatFromPreview(resolvedPeer.users[0]), }; } export async function fetchInlineBotResults({ bot, chat, query, offset = '', }: { bot: ApiUser; chat: ApiChat; query: string; offset?: string; }) { const result = await invokeRequest(new GramJs.messages.GetInlineBotResults({ bot: buildInputPeer(bot.id, bot.accessHash), peer: buildInputPeer(chat.id, chat.accessHash), query, offset, })); if (!result) { return undefined; } addEntitiesToLocalDb(result.users); return { isGallery: Boolean(result.gallery), help: bot.botPlaceholder, nextOffset: getInlineBotResultsNextOffset(bot.usernames![0].username, result.nextOffset), switchPm: buildBotSwitchPm(result.switchPm), switchWebview: buildBotSwitchWebview(result.switchWebview), users: result.users.map(buildApiUser).filter(Boolean), results: processInlineBotResult(String(result.queryId), result.results), cacheTime: result.cacheTime, }; } export async function sendInlineBotResult({ chat, replyInfo, resultId, queryId, sendAs, isSilent, scheduleDate, }: { chat: ApiChat; replyInfo?: ApiInputMessageReplyInfo; resultId: string; queryId: string; sendAs?: ApiPeer; isSilent?: boolean; scheduleDate?: number; }) { const randomId = generateRandomBigInt(); await invokeRequest(new GramJs.messages.SendInlineBotResult({ clearDraft: true, randomId, queryId: BigInt(queryId), peer: buildInputPeer(chat.id, chat.accessHash), id: resultId, scheduleDate, replyTo: replyInfo && buildInputReplyTo(replyInfo), ...(isSilent && { silent: true }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), })); } export async function startBot({ bot, startParam, }: { bot: ApiUser; startParam?: string; }) { const randomId = generateRandomBigInt(); await invokeRequest(new GramJs.messages.StartBot({ bot: buildInputPeer(bot.id, bot.accessHash), peer: buildInputPeer(bot.id, bot.accessHash), randomId, startParam, })); } export async function requestWebView({ isSilent, peer, bot, url, startParam, replyInfo, theme, sendAs, isFromBotMenu, }: { isSilent?: boolean; peer: ApiPeer; bot: ApiUser; url?: string; startParam?: string; replyInfo?: ApiInputMessageReplyInfo; theme?: ApiThemeParameters; sendAs?: ApiPeer; isFromBotMenu?: boolean; }) { const result = await invokeRequest(new GramJs.messages.RequestWebView({ silent: isSilent || undefined, peer: buildInputPeer(peer.id, peer.accessHash), bot: buildInputPeer(bot.id, bot.accessHash), url, startParam, themeParams: theme ? buildInputThemeParams(theme) : undefined, fromBotMenu: isFromBotMenu || undefined, platform: WEB_APP_PLATFORM, replyTo: replyInfo && buildInputReplyTo(replyInfo), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), })); if (result instanceof GramJs.WebViewResultUrl) { return { url: result.url, queryId: result.queryId?.toString(), }; } return undefined; } export async function requestMainWebView({ peer, bot, startParam, theme, }: { peer: ApiPeer; bot: ApiUser; startParam?: string; theme?: ApiThemeParameters; }) { const result = await invokeRequest(new GramJs.messages.RequestMainWebView({ peer: buildInputPeer(peer.id, peer.accessHash), bot: buildInputPeer(bot.id, bot.accessHash), startParam, themeParams: theme ? buildInputThemeParams(theme) : undefined, platform: WEB_APP_PLATFORM, })); if (!(result instanceof GramJs.WebViewResultUrl)) { return undefined; } return { url: result.url, queryId: result.queryId?.toString(), }; } export async function requestSimpleWebView({ bot, url, theme, startParam, isFromSwitchWebView, isFromSideMenu, }: { bot: ApiUser; url?: string; theme?: ApiThemeParameters; startParam?: string; isFromSwitchWebView?: boolean; isFromSideMenu?: boolean; }) { const result = await invokeRequest(new GramJs.messages.RequestSimpleWebView({ url, bot: buildInputPeer(bot.id, bot.accessHash), themeParams: theme ? buildInputThemeParams(theme) : undefined, platform: WEB_APP_PLATFORM, startParam, fromSwitchWebview: isFromSwitchWebView || undefined, fromSideMenu: isFromSideMenu || undefined, })); return result?.url; } export async function fetchBotApp({ bot, appName, }: { bot: ApiUser; appName: string; }) { const result = await invokeRequest(new GramJs.messages.GetBotApp({ app: new GramJs.InputBotAppShortName({ botId: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser, shortName: appName, }), })); if (!result || result instanceof GramJs.BotAppNotModified) { return undefined; } return buildApiMessagesBotApp(result); } export async function requestAppWebView({ peer, app, startParam, theme, isWriteAllowed, }: { peer: ApiPeer; app: ApiBotApp; startParam?: string; theme?: ApiThemeParameters; isWriteAllowed?: boolean; }) { const result = await invokeRequest(new GramJs.messages.RequestAppWebView({ peer: buildInputPeer(peer.id, peer.accessHash), app: buildInputBotApp(app), startParam, themeParams: theme ? buildInputThemeParams(theme) : undefined, platform: WEB_APP_PLATFORM, writeAllowed: isWriteAllowed || undefined, })); return result?.url; } export function prolongWebView({ isSilent, peer, bot, queryId, replyInfo, sendAs, }: { isSilent?: boolean; peer: ApiPeer; bot: ApiUser; queryId: string; replyInfo?: ApiInputMessageReplyInfo; sendAs?: ApiPeer; }) { return invokeRequest(new GramJs.messages.ProlongWebView({ silent: isSilent || undefined, peer: buildInputPeer(peer.id, peer.accessHash), bot: buildInputPeer(bot.id, bot.accessHash), queryId: BigInt(queryId), replyTo: replyInfo && buildInputReplyTo(replyInfo), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), })); } export async function sendWebViewData({ bot, buttonText, data, }: { bot: ApiUser; buttonText: string; data: string; }) { const randomId = generateRandomBigInt(); await invokeRequest(new GramJs.messages.SendWebViewData({ bot: buildInputPeer(bot.id, bot.accessHash), buttonText, data, randomId, })); } export async function loadAttachBots({ hash, }: { hash?: string; }) { const result = await invokeRequest(new GramJs.messages.GetAttachMenuBots({ hash: hash ? BigInt(hash) : undefined, })); if (result instanceof GramJs.AttachMenuBots) { addEntitiesToLocalDb(result.users); return { hash: result.hash.toString(), bots: buildCollectionByKey(result.bots.map(buildApiAttachBot), 'id'), users: result.users.map(buildApiUser).filter(Boolean), }; } return undefined; } export async function loadAttachBot({ bot, }: { bot: ApiUser; }) { const result = await invokeRequest(new GramJs.messages.GetAttachMenuBot({ bot: buildInputPeer(bot.id, bot.accessHash), })); if (result instanceof GramJs.AttachMenuBotsBot) { addEntitiesToLocalDb(result.users); return { bot: buildApiAttachBot(result.bot), users: result.users.map(buildApiUser).filter(Boolean), }; } return undefined; } export function toggleAttachBot({ bot, isWriteAllowed, isEnabled, }: { bot: ApiUser; isWriteAllowed?: boolean; isEnabled: boolean; }) { return invokeRequest(new GramJs.messages.ToggleBotInAttachMenu({ bot: buildInputPeer(bot.id, bot.accessHash), writeAllowed: isWriteAllowed || undefined, enabled: isEnabled, })); } export async function requestBotUrlAuth({ chat, buttonId, messageId, }: { chat: ApiChat; buttonId: number; messageId: number; }) { const result = await invokeRequest(new GramJs.messages.RequestUrlAuth({ peer: buildInputPeer(chat.id, chat.accessHash), buttonId, msgId: messageId, })); if (!result) return undefined; const authResult = buildApiUrlAuthResult(result); if (authResult?.type === 'request') { onUpdate({ '@type': 'updateUser', id: authResult.bot.id, user: authResult.bot, }); } return authResult; } export async function acceptBotUrlAuth({ chat, messageId, buttonId, isWriteAllowed, }: { chat: ApiChat; messageId: number; buttonId: number; isWriteAllowed?: boolean; }) { const result = await invokeRequest(new GramJs.messages.AcceptUrlAuth({ peer: buildInputPeer(chat.id, chat.accessHash), msgId: messageId, buttonId, writeAllowed: isWriteAllowed || undefined, })); if (!result) return undefined; const authResult = buildApiUrlAuthResult(result); if (authResult?.type === 'request') { onUpdate({ '@type': 'updateUser', id: authResult.bot.id, user: authResult.bot, }); } return authResult; } export async function requestLinkUrlAuth({ url }: { url: string }) { const result = await invokeRequest(new GramJs.messages.RequestUrlAuth({ url, })); if (!result) return undefined; const authResult = buildApiUrlAuthResult(result); if (authResult?.type === 'request') { onUpdate({ '@type': 'updateUser', id: authResult.bot.id, user: authResult.bot, }); } return authResult; } export async function acceptLinkUrlAuth({ url, isWriteAllowed }: { url: string; isWriteAllowed?: boolean }) { const result = await invokeRequest(new GramJs.messages.AcceptUrlAuth({ url, writeAllowed: isWriteAllowed || undefined, })); if (!result) return undefined; const authResult = buildApiUrlAuthResult(result); if (authResult?.type === 'request') { onUpdate({ '@type': 'updateUser', id: authResult.bot.id, user: authResult.bot, }); } return authResult; } export function fetchBotCanSendMessage({ bot } : { bot: ApiUser }) { return invokeRequest(new GramJs.bots.CanSendMessage({ bot: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser, })); } export function allowBotSendMessages({ bot } : { bot: ApiUser }) { return invokeRequest(new GramJs.bots.AllowSendMessage({ bot: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser, }), { shouldReturnTrue: true, }); } export async function invokeWebViewCustomMethod({ bot, customMethod, parameters, }: { bot: ApiUser; customMethod: string; parameters: string; }): Promise<{ result: object; } | { error: string; }> { try { const result = await invokeRequest(new GramJs.bots.InvokeWebViewCustomMethod({ bot: buildInputPeer(bot.id, bot.accessHash), params: new GramJs.DataJSON({ data: parameters, }), customMethod, }), { shouldThrow: true, }); return { result: JSON.parse(result!.data), }; } catch (e) { const error = e as Error; return { error: error.message, }; } } export async function fetchPreviewMedias({ bot } : { bot: ApiUser }) { const result = await invokeRequest(new GramJs.bots.GetPreviewMedias({ bot: buildInputPeer(bot.id, bot.accessHash), })); if (!result) return undefined; const previews: ApiBotPreviewMedia[] = result.map((preview) => { return { content: buildMessageMediaContent(preview.media)!, date: preview.date, }; }); return previews; } function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) { return results.map((result) => { if (result instanceof GramJs.BotInlineMediaResult) { if (result.document instanceof GramJs.Document) { addDocumentToLocalDb(result.document); } if (result.photo instanceof GramJs.Photo) { addPhotoToLocalDb(result.photo); } return buildApiBotInlineMediaResult(result, queryId); } if (result.thumb) { addWebDocumentToLocalDb(result.thumb); } return buildApiBotInlineResult(result, queryId); }); } function getInlineBotResultsNextOffset(username: string, nextOffset?: string) { return username === 'gif' && nextOffset === '0' ? '' : nextOffset; } export function setBotInfo({ bot, langCode, name, about, description, }: { bot: ApiUser; langCode: string; name?: string; about?: string; description?: string; }) { return invokeRequest(new GramJs.bots.SetBotInfo({ bot: buildInputPeer(bot.id, bot.accessHash), langCode, name: name || '', about: about || '', description: description || '', }), { shouldReturnTrue: true, }); } export async function fetchPopularAppBots({ offset = '', limit, }: { offset?: string; limit?: number; }) { const result = await invokeRequest(new GramJs.bots.GetPopularAppBots({ offset, limit, })); if (!result) { return undefined; } addEntitiesToLocalDb(result.users); const users = result.users.map(buildApiUser).filter(Boolean); const chats = result.users.map((c) => buildApiChatFromPreview(c)).filter(Boolean); return { users, chats, nextOffset: result.nextOffset, }; }