Mini Apps: Implement Share Support (#5576)
This commit is contained in:
parent
c8f82b1f91
commit
87fc3a832f
@ -13,32 +13,280 @@ import type {
|
||||
ApiBotInlineSwitchPm,
|
||||
ApiBotInlineSwitchWebview,
|
||||
ApiBotMenuButton,
|
||||
ApiInlineQueryPeerType,
|
||||
ApiInlineResultType,
|
||||
ApiKeyboardButton,
|
||||
ApiMessagesBotApp,
|
||||
ApiReplyKeyboard,
|
||||
MediaContainer,
|
||||
MediaContent,
|
||||
} from '../../types';
|
||||
|
||||
import { numberToHexColor } from '../../../util/colors';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { generateRandomInt } from '../gramjsBuilders';
|
||||
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||
import { buildApiPhoto, buildApiThumbnailFromStripped } from './common';
|
||||
import { serializeBytes } from '../helpers/misc';
|
||||
import { buildApiMessageEntity, buildApiPhoto } from './common';
|
||||
import { omitVirtualClassFields } from './helpers';
|
||||
import { buildApiDocument, buildApiWebDocument, buildVideoFromDocument } from './messageContent';
|
||||
import {
|
||||
buildApiDocument,
|
||||
buildApiWebDocument,
|
||||
buildAudioFromDocument,
|
||||
buildGeoPoint,
|
||||
buildVideoFromDocument,
|
||||
} from './messageContent';
|
||||
import { buildSvgPath } from './pathBytesToSvg';
|
||||
import { buildApiPeerId } from './peers';
|
||||
import { buildStickerFromDocument } from './symbols';
|
||||
|
||||
export function buildReplyButtons(
|
||||
replyMarkup: GramJs.TypeReplyMarkup | undefined,
|
||||
receiptMessageId?: number,
|
||||
): ApiReplyKeyboard | undefined {
|
||||
if (!(replyMarkup instanceof GramJs.ReplyKeyboardMarkup || replyMarkup instanceof GramJs.ReplyInlineMarkup)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const markup = replyMarkup.rows.map(({ buttons }) => {
|
||||
return buttons.map((button): ApiKeyboardButton | undefined => {
|
||||
const { text } = button;
|
||||
|
||||
if (button instanceof GramJs.KeyboardButton) {
|
||||
return {
|
||||
type: 'command',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUrl) {
|
||||
if (button.url.includes('?startgroup=')) {
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'url',
|
||||
text,
|
||||
url: button.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonCallback) {
|
||||
if (button.requiresPassword) {
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'callback',
|
||||
text,
|
||||
data: serializeBytes(button.data),
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonRequestPoll) {
|
||||
return {
|
||||
type: 'requestPoll',
|
||||
text,
|
||||
isQuiz: button.quiz,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonRequestPhone) {
|
||||
return {
|
||||
type: 'requestPhone',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonBuy) {
|
||||
if (receiptMessageId) {
|
||||
return {
|
||||
type: 'receipt',
|
||||
receiptMessageId,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'buy',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonGame) {
|
||||
return {
|
||||
type: 'game',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonSwitchInline) {
|
||||
return {
|
||||
type: 'switchBotInline',
|
||||
text,
|
||||
query: button.query,
|
||||
isSamePeer: button.samePeer,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUserProfile) {
|
||||
return {
|
||||
type: 'userProfile',
|
||||
text,
|
||||
userId: button.userId.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonSimpleWebView) {
|
||||
return {
|
||||
type: 'simpleWebView',
|
||||
text,
|
||||
url: button.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonWebView) {
|
||||
return {
|
||||
type: 'webView',
|
||||
text,
|
||||
url: button.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUrlAuth) {
|
||||
return {
|
||||
type: 'urlAuth',
|
||||
text,
|
||||
url: button.url,
|
||||
buttonId: button.buttonId,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonCopy) {
|
||||
return {
|
||||
type: 'copy',
|
||||
text,
|
||||
copyText: button.copyText,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
});
|
||||
|
||||
if (markup.every((row) => !row.length)) return undefined;
|
||||
|
||||
return {
|
||||
[replyMarkup instanceof GramJs.ReplyKeyboardMarkup ? 'keyboardButtons' : 'inlineButtons']: markup,
|
||||
...(replyMarkup instanceof GramJs.ReplyKeyboardMarkup && {
|
||||
keyboardPlaceholder: replyMarkup.placeholder,
|
||||
isKeyboardSingleUse: replyMarkup.singleUse,
|
||||
isKeyboardSelective: replyMarkup.selective,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildBotInlineMessage(
|
||||
sendMessage: GramJs.TypeBotInlineMessage, type: string, document?: GramJs.TypeDocument, photo?: GramJs.TypePhoto,
|
||||
): MediaContainer & { replyMarkup?: ApiReplyKeyboard } {
|
||||
const content: MediaContent = {};
|
||||
|
||||
if (sendMessage instanceof GramJs.BotInlineMessageText) {
|
||||
content.text = {
|
||||
text: sendMessage.message,
|
||||
entities: sendMessage.entities?.map(buildApiMessageEntity),
|
||||
};
|
||||
} else if (sendMessage instanceof GramJs.BotInlineMessageMediaAuto) {
|
||||
if (type === 'photo' && photo instanceof GramJs.Photo) {
|
||||
content.photo = buildApiPhoto(photo);
|
||||
} else if (type === 'audio' && document instanceof GramJs.Document) {
|
||||
content.audio = buildAudioFromDocument(document);
|
||||
} else if (type === 'video' && document instanceof GramJs.Document) {
|
||||
content.video = buildVideoFromDocument(document);
|
||||
} else if (type === 'sticker' && document instanceof GramJs.Document) {
|
||||
content.sticker = buildStickerFromDocument(document);
|
||||
} else if (type === 'file' && document instanceof GramJs.Document) {
|
||||
content.document = buildApiDocument(document);
|
||||
} else if (type === 'gif' && document instanceof GramJs.Document) {
|
||||
content.video = buildVideoFromDocument(document);
|
||||
} else {
|
||||
content.text = {
|
||||
text: sendMessage.message,
|
||||
entities: sendMessage.entities?.map(buildApiMessageEntity),
|
||||
};
|
||||
}
|
||||
} else if (sendMessage instanceof GramJs.BotInlineMessageMediaGeo) {
|
||||
content.location = {
|
||||
mediaType: 'geo',
|
||||
geo: buildGeoPoint(sendMessage.geo)!,
|
||||
};
|
||||
} else if (sendMessage instanceof GramJs.BotInlineMessageMediaVenue) {
|
||||
content.location = {
|
||||
mediaType: 'venue',
|
||||
geo: buildGeoPoint(sendMessage.geo)!,
|
||||
title: sendMessage.title,
|
||||
address: sendMessage.address,
|
||||
provider: sendMessage.provider,
|
||||
venueId: sendMessage.venueId,
|
||||
venueType: sendMessage.venueType,
|
||||
};
|
||||
} else if (sendMessage instanceof GramJs.BotInlineMessageMediaContact) {
|
||||
content.contact = {
|
||||
mediaType: 'contact',
|
||||
phoneNumber: sendMessage.phoneNumber,
|
||||
firstName: sendMessage.firstName,
|
||||
lastName: sendMessage.lastName,
|
||||
userId: '0',
|
||||
};
|
||||
} else if (sendMessage instanceof GramJs.BotInlineMessageMediaInvoice) {
|
||||
content.invoice = {
|
||||
mediaType: 'invoice',
|
||||
isTest: sendMessage.test,
|
||||
title: sendMessage.title,
|
||||
description: sendMessage.description,
|
||||
photo: buildApiWebDocument(sendMessage.photo),
|
||||
currency: sendMessage.currency,
|
||||
amount: sendMessage.totalAmount.toJSNumber(),
|
||||
};
|
||||
} else {
|
||||
const mediaSize = sendMessage.forceSmallMedia ? 'small' : sendMessage.forceLargeMedia ? 'large' : undefined;
|
||||
|
||||
content.webPage = {
|
||||
mediaType: 'webpage',
|
||||
id: generateRandomInt(),
|
||||
mediaSize,
|
||||
url: sendMessage.url,
|
||||
displayUrl: sendMessage.url,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
replyMarkup: buildReplyButtons(sendMessage.replyMarkup) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiBotInlineResult(result: GramJs.BotInlineResult, queryId: string): ApiBotInlineResult {
|
||||
const {
|
||||
id, type, title, description, url, thumb,
|
||||
id, type, title, description, url, thumb, content, sendMessage,
|
||||
} = result;
|
||||
|
||||
return {
|
||||
id,
|
||||
queryId,
|
||||
type: type as ApiInlineResultType,
|
||||
sendMessage: buildBotInlineMessage(sendMessage, type),
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
content: buildApiWebDocument(content),
|
||||
webThumbnail: buildApiWebDocument(thumb),
|
||||
};
|
||||
}
|
||||
@ -47,7 +295,7 @@ export function buildApiBotInlineMediaResult(
|
||||
result: GramJs.BotInlineMediaResult, queryId: string,
|
||||
): ApiBotInlineMediaResult {
|
||||
const {
|
||||
id, type, title, description, photo, document,
|
||||
id, type, title, description, sendMessage, photo, document,
|
||||
} = result;
|
||||
|
||||
return {
|
||||
@ -59,9 +307,10 @@ export function buildApiBotInlineMediaResult(
|
||||
...(type === 'sticker' && document instanceof GramJs.Document && { sticker: buildStickerFromDocument(document) }),
|
||||
...(photo instanceof GramJs.Photo && { photo: buildApiPhoto(photo) }),
|
||||
...(type === 'gif' && document instanceof GramJs.Document && { gif: buildVideoFromDocument(document) }),
|
||||
...(type === 'video' && document instanceof GramJs.Document && {
|
||||
thumbnail: buildApiThumbnailFromStripped(document.thumbs),
|
||||
}),
|
||||
...(type === 'file' && document instanceof GramJs.Document && { document: buildApiDocument(document) }),
|
||||
...(type === 'audio' && document instanceof GramJs.Document && { audio: buildAudioFromDocument(document) }),
|
||||
...(type === 'video' && document instanceof GramJs.Document && { video: buildVideoFromDocument(document) }),
|
||||
sendMessage: buildBotInlineMessage(sendMessage, type, document, photo),
|
||||
};
|
||||
}
|
||||
|
||||
@ -80,20 +329,22 @@ export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
|
||||
shortName: bot.shortName,
|
||||
isForAttachMenu: bot.showInAttachMenu!,
|
||||
isForSideMenu: bot.showInSideMenu,
|
||||
attachMenuPeerTypes: bot.peerTypes?.map(buildApiAttachMenuPeerType)!,
|
||||
attachMenuPeerTypes: bot.peerTypes && buildApiAttachMenuPeerType(bot.peerTypes),
|
||||
icons: bot.icons.map(buildApiAttachMenuIcon).filter(Boolean),
|
||||
isInactive: bot.inactive,
|
||||
isDisclaimerNeeded: bot.sideMenuDisclaimerNeeded,
|
||||
};
|
||||
}
|
||||
|
||||
function buildApiAttachMenuPeerType(peerType: GramJs.TypeAttachMenuPeerType): ApiAttachMenuPeerType {
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return 'bots';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypePM) return 'users';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return 'chats';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return 'channels';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeSameBotPM) return 'self';
|
||||
return undefined!; // Never reached
|
||||
function buildApiAttachMenuPeerType(peerTypes: GramJs.TypeAttachMenuPeerType[]): ApiAttachMenuPeerType[] {
|
||||
return peerTypes.flatMap((peerType) => {
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return ['bots'];
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypePM) return ['users'];
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return ['chats', 'groups'];
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return ['channels'];
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeSameBotPM) return ['self'];
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachBotIcon | undefined {
|
||||
@ -200,3 +451,13 @@ export function buildApiMessagesBotApp(botApp: GramJs.messages.BotApp): ApiMessa
|
||||
shouldRequestWriteAccess: requestWriteAccess,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiInlineQueryPeerType(peerType: GramJs.TypeInlineQueryPeerType): ApiInlineQueryPeerType {
|
||||
if (peerType instanceof GramJs.InlineQueryPeerTypeBotPM) return 'bots';
|
||||
if (peerType instanceof GramJs.InlineQueryPeerTypePM) return 'users';
|
||||
if (peerType instanceof GramJs.InlineQueryPeerTypeChat) return 'chats';
|
||||
if (peerType instanceof GramJs.InlineQueryPeerTypeMegagroup) return 'supergroups';
|
||||
if (peerType instanceof GramJs.InlineQueryPeerTypeBroadcast) return 'channels';
|
||||
if (peerType instanceof GramJs.InlineQueryPeerTypeSameBotPM) return 'self';
|
||||
return undefined!; // Never reached
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import type {
|
||||
ApiFactCheck,
|
||||
ApiInputMessageReplyInfo,
|
||||
ApiInputReplyInfo,
|
||||
ApiKeyboardButton,
|
||||
ApiMessage,
|
||||
ApiMessageEntity,
|
||||
ApiMessageForwardInfo,
|
||||
@ -17,9 +16,9 @@ import type {
|
||||
ApiPeer,
|
||||
ApiPhoto,
|
||||
ApiPoll,
|
||||
ApiPreparedInlineMessage,
|
||||
ApiQuickReply,
|
||||
ApiReplyInfo,
|
||||
ApiReplyKeyboard,
|
||||
ApiSponsoredMessage,
|
||||
ApiSticker,
|
||||
ApiStory,
|
||||
@ -28,9 +27,7 @@ import type {
|
||||
ApiVideo,
|
||||
MediaContent,
|
||||
} from '../../types';
|
||||
import {
|
||||
ApiMessageEntityTypes, MAIN_THREAD_ID,
|
||||
} from '../../types';
|
||||
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../types';
|
||||
|
||||
import {
|
||||
DELETED_COMMENTS_CHANNEL_ID,
|
||||
@ -47,10 +44,18 @@ import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
||||
import { interpolateArray } from '../../../util/waveform';
|
||||
import { buildPeer } from '../gramjsBuilders';
|
||||
import {
|
||||
addDocumentToLocalDb,
|
||||
addPhotoToLocalDb,
|
||||
addWebDocumentToLocalDb,
|
||||
type MediaRepairContext,
|
||||
} from '../helpers/localDb';
|
||||
import { resolveMessageApiChatId, serializeBytes } from '../helpers/misc';
|
||||
import {
|
||||
buildApiBotInlineMediaResult,
|
||||
buildApiBotInlineResult,
|
||||
buildApiInlineQueryPeerType,
|
||||
buildReplyButtons,
|
||||
} from './bots';
|
||||
import {
|
||||
buildApiFormattedText,
|
||||
buildApiPhoto,
|
||||
@ -193,7 +198,7 @@ export function buildApiMessageWithChatId(
|
||||
const isEdited = Boolean(mtpMessage.editDate) && !mtpMessage.editHide;
|
||||
const {
|
||||
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
||||
} = buildReplyButtons(mtpMessage, isInvoiceMedia) || {};
|
||||
} = buildReplyButtons(mtpMessage.replyMarkup, mtpMessage.id) || {};
|
||||
const { mediaUnread: isMediaUnread, postAuthor } = mtpMessage;
|
||||
const groupedId = mtpMessage.groupedId && String(mtpMessage.groupedId);
|
||||
const isInAlbum = Boolean(groupedId) && !(content.document || content.audio || content.sticker);
|
||||
@ -357,159 +362,6 @@ export function buildApiFactCheck(factCheck: GramJs.FactCheck): ApiFactCheck {
|
||||
};
|
||||
}
|
||||
|
||||
function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: boolean): ApiReplyKeyboard | undefined {
|
||||
const { replyMarkup, media } = message;
|
||||
|
||||
if (!(replyMarkup instanceof GramJs.ReplyKeyboardMarkup || replyMarkup instanceof GramJs.ReplyInlineMarkup)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const markup = replyMarkup.rows.map(({ buttons }) => {
|
||||
return buttons.map((button): ApiKeyboardButton | undefined => {
|
||||
const { text } = button;
|
||||
|
||||
if (button instanceof GramJs.KeyboardButton) {
|
||||
return {
|
||||
type: 'command',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUrl) {
|
||||
if (button.url.includes('?startgroup=')) {
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'url',
|
||||
text,
|
||||
url: button.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonCallback) {
|
||||
if (button.requiresPassword) {
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'callback',
|
||||
text,
|
||||
data: serializeBytes(button.data),
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonRequestPoll) {
|
||||
return {
|
||||
type: 'requestPoll',
|
||||
text,
|
||||
isQuiz: button.quiz,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonRequestPhone) {
|
||||
return {
|
||||
type: 'requestPhone',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonBuy) {
|
||||
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
||||
return {
|
||||
type: 'receipt',
|
||||
receiptMessageId: media.receiptMsgId,
|
||||
};
|
||||
}
|
||||
if (shouldSkipBuyButton) return undefined;
|
||||
return {
|
||||
type: 'buy',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonGame) {
|
||||
return {
|
||||
type: 'game',
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonSwitchInline) {
|
||||
return {
|
||||
type: 'switchBotInline',
|
||||
text,
|
||||
query: button.query,
|
||||
isSamePeer: button.samePeer,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUserProfile) {
|
||||
return {
|
||||
type: 'userProfile',
|
||||
text,
|
||||
userId: button.userId.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonSimpleWebView) {
|
||||
return {
|
||||
type: 'simpleWebView',
|
||||
text,
|
||||
url: button.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonWebView) {
|
||||
return {
|
||||
type: 'webView',
|
||||
text,
|
||||
url: button.url,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonUrlAuth) {
|
||||
return {
|
||||
type: 'urlAuth',
|
||||
text,
|
||||
url: button.url,
|
||||
buttonId: button.buttonId,
|
||||
};
|
||||
}
|
||||
|
||||
if (button instanceof GramJs.KeyboardButtonCopy) {
|
||||
return {
|
||||
type: 'copy',
|
||||
text,
|
||||
copyText: button.copyText,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'unsupported',
|
||||
text,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
});
|
||||
|
||||
if (markup.every((row) => !row.length)) return undefined;
|
||||
|
||||
return {
|
||||
[replyMarkup instanceof GramJs.ReplyKeyboardMarkup ? 'keyboardButtons' : 'inlineButtons']: markup,
|
||||
...(replyMarkup instanceof GramJs.ReplyKeyboardMarkup && {
|
||||
keyboardPlaceholder: replyMarkup.placeholder,
|
||||
isKeyboardSingleUse: replyMarkup.singleUse,
|
||||
isKeyboardSelective: replyMarkup.selective,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function buildNewPoll(poll: ApiNewPoll, localId: number): ApiPoll {
|
||||
return {
|
||||
mediaType: 'poll',
|
||||
@ -879,3 +731,36 @@ export function buildApiReportResult(
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
function processInlineBotResult(queryId: string, result: GramJs.TypeBotInlineResult) {
|
||||
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);
|
||||
}
|
||||
|
||||
export function buildPreparedInlineMessage(
|
||||
result: GramJs.messages.TypePreparedInlineMessage,
|
||||
): ApiPreparedInlineMessage {
|
||||
const queryId = result.queryId.toString();
|
||||
|
||||
return {
|
||||
queryId,
|
||||
result: processInlineBotResult(queryId, result.result),
|
||||
peerTypes: result.peerTypes?.map(buildApiInlineQueryPeerType),
|
||||
cacheTime: result.cacheTime,
|
||||
};
|
||||
}
|
||||
|
||||
@ -24,11 +24,15 @@ import type {
|
||||
ApiSticker,
|
||||
ApiStory,
|
||||
ApiStorySkipped,
|
||||
ApiUser,
|
||||
ApiUserStatus,
|
||||
ApiVideo,
|
||||
MediaContent,
|
||||
} from '../../types';
|
||||
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../types';
|
||||
import {
|
||||
MAIN_THREAD_ID,
|
||||
MESSAGE_DELETED,
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
API_GENERAL_ID_LIMIT,
|
||||
@ -65,6 +69,7 @@ import {
|
||||
buildApiThreadInfo,
|
||||
buildLocalForwardedMessage,
|
||||
buildLocalMessage,
|
||||
buildPreparedInlineMessage,
|
||||
buildUploadingMedia,
|
||||
} from '../apiBuilders/messages';
|
||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
@ -2178,3 +2183,18 @@ export async function exportMessageLink({
|
||||
|
||||
return result?.link;
|
||||
}
|
||||
|
||||
export async function fetchPreparedInlineMessage({
|
||||
bot, id,
|
||||
}: {
|
||||
bot: ApiUser;
|
||||
id: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.GetPreparedInlineMessage({
|
||||
bot: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser,
|
||||
id,
|
||||
}));
|
||||
if (!result) return undefined;
|
||||
|
||||
return buildPreparedInlineMessage(result);
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import type {
|
||||
ApiDimensions,
|
||||
ApiPhoto, ApiSticker, ApiThumbnail, ApiVideo, MediaContainer,
|
||||
ApiDimensions, ApiDocument,
|
||||
ApiPhoto, ApiReplyKeyboard,
|
||||
ApiSticker, ApiThumbnail,
|
||||
ApiVideo, MediaContainer,
|
||||
MediaContent,
|
||||
} from './messages';
|
||||
|
||||
export type ApiInlineResultType = (
|
||||
'article' | 'audio' | 'contact' | 'document' | 'game' | 'gif' | 'location' | 'mpeg4_gif' |
|
||||
'photo' | 'sticker' | 'venue' | 'video' | 'voice' | 'file'
|
||||
'photo' | 'sticker' | 'venue' | 'video' | 'voice' | 'file' | 'geo'
|
||||
);
|
||||
|
||||
export interface ApiWebDocument {
|
||||
@ -17,6 +20,11 @@ export interface ApiWebDocument {
|
||||
dimensions?: ApiDimensions;
|
||||
}
|
||||
|
||||
export type ApiBotInlineMessage = {
|
||||
content: MediaContent;
|
||||
replyMarkup?: ApiReplyKeyboard;
|
||||
};
|
||||
|
||||
export interface ApiBotInlineResult {
|
||||
id: string;
|
||||
queryId: string;
|
||||
@ -24,7 +32,9 @@ export interface ApiBotInlineResult {
|
||||
title?: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
content?: ApiWebDocument;
|
||||
webThumbnail?: ApiWebDocument;
|
||||
sendMessage: ApiBotInlineMessage;
|
||||
}
|
||||
|
||||
export interface ApiBotInlineMediaResult {
|
||||
@ -34,9 +44,11 @@ export interface ApiBotInlineMediaResult {
|
||||
title?: string;
|
||||
description?: string;
|
||||
sticker?: ApiSticker;
|
||||
document?: ApiDocument;
|
||||
photo?: ApiPhoto;
|
||||
gif?: ApiVideo;
|
||||
thumbnail?: ApiThumbnail;
|
||||
sendMessage: ApiBotInlineMessage;
|
||||
}
|
||||
|
||||
export interface ApiBotInlineSwitchPm {
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import type { ThreadId, WebPageMediaSize } from '../../types';
|
||||
import type { ApiWebDocument } from './bots';
|
||||
import type {
|
||||
ApiBotInlineMediaResult,
|
||||
ApiBotInlineResult,
|
||||
ApiWebDocument,
|
||||
} from './bots';
|
||||
import type { ApiPeerColor } from './chats';
|
||||
import type { ApiMessageAction } from './messageActions';
|
||||
import type {
|
||||
@ -9,6 +13,7 @@ import type {
|
||||
import type {
|
||||
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
||||
} from './stories';
|
||||
import type { ApiInlineQueryPeerType } from './users';
|
||||
|
||||
export interface ApiDimensions {
|
||||
width: number;
|
||||
@ -255,12 +260,12 @@ export interface ApiGeoPoint {
|
||||
accuracyRadius?: number;
|
||||
}
|
||||
|
||||
interface ApiGeo {
|
||||
export interface ApiGeo {
|
||||
mediaType: 'geo';
|
||||
geo: ApiGeoPoint;
|
||||
}
|
||||
|
||||
interface ApiVenue {
|
||||
export interface ApiVenue {
|
||||
mediaType: 'venue';
|
||||
geo: ApiGeoPoint;
|
||||
title: string;
|
||||
@ -270,11 +275,11 @@ interface ApiVenue {
|
||||
venueType: string;
|
||||
}
|
||||
|
||||
interface ApiGeoLive {
|
||||
export interface ApiGeoLive {
|
||||
mediaType: 'geoLive';
|
||||
geo: ApiGeoPoint;
|
||||
heading?: number;
|
||||
period: number;
|
||||
period?: number;
|
||||
}
|
||||
|
||||
export type ApiLocation = ApiGeo | ApiVenue | ApiGeoLive;
|
||||
@ -915,6 +920,13 @@ export type ApiSponsoredMessageReportResult = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export type ApiPreparedInlineMessage = {
|
||||
queryId: string;
|
||||
result: ApiBotInlineResult | ApiBotInlineMediaResult;
|
||||
peerTypes: ApiInlineQueryPeerType[];
|
||||
cacheTime: number;
|
||||
};
|
||||
|
||||
export const MAIN_THREAD_ID = -1;
|
||||
|
||||
// `Symbol` can not be transferred from worker
|
||||
|
||||
@ -103,9 +103,11 @@ export interface ApiUsername {
|
||||
export type ApiChatType = typeof API_CHAT_TYPES[number];
|
||||
export type ApiAttachMenuPeerType = 'self' | ApiChatType;
|
||||
|
||||
export type ApiInlineQueryPeerType = 'self' | 'supergroups' | ApiChatType;
|
||||
|
||||
type ApiAttachBotForMenu = {
|
||||
isForAttachMenu: true;
|
||||
attachMenuPeerTypes: ApiAttachMenuPeerType[];
|
||||
attachMenuPeerTypes?: ApiAttachMenuPeerType[];
|
||||
};
|
||||
|
||||
type ApiAttachBotBase = {
|
||||
|
||||
@ -154,6 +154,8 @@
|
||||
"Code" = "Code";
|
||||
"Open" = "Open";
|
||||
"LoginHeaderPassword" = "Enter Password";
|
||||
"BotShareMessageShare" = "Share with...";
|
||||
"BotShareMessage" = "Share Message";
|
||||
"LoginEnterPasswordDescription" = "You have Two-Step Verification enabled, so your account is protected with an additional password.";
|
||||
"StartText" = "Please confirm your country code\nand enter your phone number.";
|
||||
"LoginPhonePlaceholder" = "Your phone number";
|
||||
@ -538,8 +540,10 @@
|
||||
"EditAdminTransferSetPassword" = "Set Password";
|
||||
"WebAppAddToAttachmentText" = "{bot} requests your permission to be added as an option to your attachment menu, so you can access it from any chat.";
|
||||
"BotOpenPageTitle" = "Open page";
|
||||
"WebAppShareMessageInfo" = "{user} mini app suggests you to send this message to a chat you select.";
|
||||
"BotPermissionGameAlert" = "Allow {bot} to pass your Telegram name and id (not your phone number) to pages you open with this bot?";
|
||||
"BotOpenPageMessage" = "**{bot}** would like to open its web app to proceed.\n\nIt will be able to access your **IP address** and basic device info.";
|
||||
"BotSharedToOne" = "Sent message to {peer}";
|
||||
"FilterDeleteAlert" = "Are you sure you want to remove this folder? Your chats will not be deleted.";
|
||||
"RequestToJoinChannelSentDescription" = "You will be added to the channel once its admins approve your request.";
|
||||
"RequestToJoinGroupSentDescription" = "You will be added to the group once an admin approves your request.";
|
||||
|
||||
@ -33,6 +33,9 @@ export { default as EmojiStatusAccessModal } from '../components/modals/emojiSta
|
||||
export { default as LocationAccessModal } from '../components/modals/locationAccess/LocationAccessModal';
|
||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||
export { default as ReportModal } from '../components/modals/reportModal/ReportModal';
|
||||
export { default as PreparedMessageModal } from '../components/modals/preparedMessage/PreparedMessageModal';
|
||||
export { default as SharePreparedMessageModal }
|
||||
from '../components/modals/sharePreparedMessage/SharePreparedMessageModal';
|
||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
||||
export { default as PinMessageModal } from '../components/common/PinMessageModal';
|
||||
|
||||
@ -1125,10 +1125,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const { id, queryId, isSilent } = args;
|
||||
sendInlineBotResult({
|
||||
id,
|
||||
chatId,
|
||||
threadId,
|
||||
queryId,
|
||||
scheduledAt,
|
||||
isSilent,
|
||||
messageList,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -1272,8 +1273,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
sendInlineBotResult({
|
||||
id: inlineResult.id,
|
||||
queryId: inlineResult.queryId,
|
||||
threadId,
|
||||
chatId,
|
||||
isSilent,
|
||||
messageList: currentMessageList!,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const chatFullInfo = selectChatFullInfo(global, id);
|
||||
|
||||
return chat && chatFullInfo && getCanPostInChat(chat, undefined, undefined, chatFullInfo);
|
||||
return chat && (!chatFullInfo || getCanPostInChat(chat, undefined, undefined, chatFullInfo));
|
||||
});
|
||||
|
||||
const sorted = sortChatIds(
|
||||
|
||||
@ -154,7 +154,8 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
return attachBots
|
||||
? Object.values(attachBots).filter((bot) => {
|
||||
if (!peerType || !bot.isForAttachMenu) return false;
|
||||
if (peerType === 'bots' && bot.id === chatId && bot.attachMenuPeerTypes.includes('self')) {
|
||||
if (peerType === 'bots' && bot.id === chatId
|
||||
&& bot.attachMenuPeerTypes && bot.attachMenuPeerTypes.includes('self')) {
|
||||
return true;
|
||||
}
|
||||
return bot.attachMenuPeerTypes!.includes(peerType);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { ApiBotInlineResult } from '../../../../api/types';
|
||||
import type { ApiBotInlineMediaResult, ApiBotInlineResult } from '../../../../api/types';
|
||||
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
@ -9,15 +9,18 @@ import BaseResult from './BaseResult';
|
||||
|
||||
export type OwnProps = {
|
||||
focus?: boolean;
|
||||
inlineResult: ApiBotInlineResult;
|
||||
onClick: (result: ApiBotInlineResult) => void;
|
||||
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult;
|
||||
onClick: (result: ApiBotInlineResult | ApiBotInlineMediaResult) => void;
|
||||
};
|
||||
|
||||
const ArticleResult: FC<OwnProps> = ({ focus, inlineResult, onClick }) => {
|
||||
const {
|
||||
title, url, description, webThumbnail,
|
||||
title, description,
|
||||
} = inlineResult;
|
||||
|
||||
const url = 'url' in inlineResult ? inlineResult.url : undefined;
|
||||
const webThumbnail = 'webThumbnail' in inlineResult ? inlineResult.webThumbnail : undefined;
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
onClick(inlineResult);
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { ApiBotInlineMediaResult, ApiBotInlineResult, ApiVideo } from '../../../../api/types';
|
||||
import type { ApiBotInlineMediaResult, ApiVideo } from '../../../../api/types';
|
||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
@ -13,7 +13,7 @@ type OwnProps = {
|
||||
isSavedMessages?: boolean;
|
||||
canSendGifs?: boolean;
|
||||
observeIntersection: ObserveFn;
|
||||
onClick: (result: ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
onClick: (result: ApiBotInlineMediaResult, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
};
|
||||
|
||||
const GifResult: FC<OwnProps> = ({
|
||||
|
||||
@ -20,7 +20,7 @@ export type OwnProps = {
|
||||
focus?: boolean;
|
||||
isForGallery?: boolean;
|
||||
inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult;
|
||||
onClick: (result: ApiBotInlineResult) => void;
|
||||
onClick: (result: ApiBotInlineMediaResult | ApiBotInlineResult) => void;
|
||||
};
|
||||
|
||||
const MediaResult: FC<OwnProps> = ({
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { ApiBotInlineMediaResult, ApiBotInlineResult } from '../../../../api/types';
|
||||
import type { ApiBotInlineMediaResult } from '../../../../api/types';
|
||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
|
||||
import { STICKER_SIZE_INLINE_BOT_RESULT } from '../../../../config';
|
||||
@ -12,7 +12,7 @@ type OwnProps = {
|
||||
inlineResult: ApiBotInlineMediaResult;
|
||||
isSavedMessages?: boolean;
|
||||
observeIntersection: ObserveFn;
|
||||
onClick: (result: ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
onClick: (result: ApiBotInlineMediaResult, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
isCurrentUserPremium?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@ -201,9 +201,9 @@ type MessagePositionProperties = {
|
||||
type OwnProps =
|
||||
{
|
||||
message: ApiMessage;
|
||||
observeIntersectionForBottom: ObserveFn;
|
||||
observeIntersectionForLoading: ObserveFn;
|
||||
observeIntersectionForPlaying: ObserveFn;
|
||||
observeIntersectionForBottom?: ObserveFn;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
album?: IAlbum;
|
||||
noAvatars?: boolean;
|
||||
withAvatar?: boolean;
|
||||
@ -214,9 +214,9 @@ type OwnProps =
|
||||
noReplies: boolean;
|
||||
appearanceOrder: number;
|
||||
isJustAdded: boolean;
|
||||
memoFirstUnreadIdRef: { current: number | undefined };
|
||||
getIsMessageListReady: Signal<boolean>;
|
||||
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||
memoFirstUnreadIdRef?: { current: number | undefined };
|
||||
getIsMessageListReady?: Signal<boolean>;
|
||||
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
||||
}
|
||||
& MessagePositionProperties;
|
||||
|
||||
@ -494,7 +494,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
useUnmountCleanup(() => {
|
||||
if (message.isPinned) {
|
||||
const id = album ? album.mainMessage.id : messageId;
|
||||
onIntersectPinnedMessage({ viewportPinnedIdsToRemove: [id] });
|
||||
onIntersectPinnedMessage?.({ viewportPinnedIdsToRemove: [id] });
|
||||
}
|
||||
});
|
||||
|
||||
@ -729,7 +729,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if ((sticker?.hasEffect || effect) && ((
|
||||
memoFirstUnreadIdRef.current && messageId >= memoFirstUnreadIdRef.current
|
||||
memoFirstUnreadIdRef?.current && messageId >= memoFirstUnreadIdRef.current
|
||||
) || isLocal)) {
|
||||
requestEffect();
|
||||
}
|
||||
@ -1105,7 +1105,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{sticker && (
|
||||
{sticker && observeIntersectionForLoading && observeIntersectionForPlaying && (
|
||||
<Sticker
|
||||
message={message}
|
||||
observeIntersection={observeIntersectionForLoading}
|
||||
@ -1383,7 +1383,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
function renderInvertibleMediaContent(hasCustomAppendix: boolean) {
|
||||
const content = (
|
||||
<>
|
||||
{isAlbum && (
|
||||
{isAlbum && observeIntersectionForLoading && (
|
||||
<Album
|
||||
album={album!}
|
||||
albumLayout={albumLayout!}
|
||||
|
||||
@ -36,7 +36,7 @@ export default function useOuterHandlers(
|
||||
isContextMenuShown: boolean,
|
||||
quickReactionRef: RefObject<HTMLDivElement>,
|
||||
shouldHandleMouseLeave: boolean,
|
||||
getIsMessageListReady: Signal<boolean>,
|
||||
getIsMessageListReady?: Signal<boolean>,
|
||||
) {
|
||||
const { updateDraftReplyInfo, sendDefaultReaction } = getActions();
|
||||
|
||||
@ -146,7 +146,7 @@ export default function useOuterHandlers(
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!IS_TOUCH_ENV || isInSelectMode || !canReply || isContextMenuShown || !getIsMessageListReady()) {
|
||||
if (!IS_TOUCH_ENV || isInSelectMode || !canReply || isContextMenuShown || !getIsMessageListReady?.()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -28,8 +28,10 @@ import LocationAccessModal from './locationAccess/LocationAccessModal.async';
|
||||
import MapModal from './map/MapModal.async';
|
||||
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
||||
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
||||
import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
|
||||
import ReportAdModal from './reportAd/ReportAdModal.async';
|
||||
import ReportModal from './reportModal/ReportModal.async';
|
||||
import SharePreparedMessageModal from './sharePreparedMessage/SharePreparedMessageModal.async';
|
||||
import StarsGiftModal from './stars/gift/StarsGiftModal.async';
|
||||
import StarsBalanceModal from './stars/StarsBalanceModal.async';
|
||||
import StarsPaymentModal from './stars/StarsPaymentModal.async';
|
||||
@ -72,6 +74,8 @@ type ModalKey = keyof Pick<TabState,
|
||||
'giftUpgradeModal' |
|
||||
'monetizationVerificationModal' |
|
||||
'giftWithdrawModal' |
|
||||
'preparedMessageModal' |
|
||||
'sharePreparedMessageModal' |
|
||||
'giftStatusInfoModal' |
|
||||
'giftTransferModal'
|
||||
>;
|
||||
@ -120,6 +124,8 @@ const MODALS: ModalRegistry = {
|
||||
monetizationVerificationModal: VerificationMonetizationModal,
|
||||
giftWithdrawModal: GiftWithdrawModal,
|
||||
giftStatusInfoModal: GiftStatusInfoModal,
|
||||
preparedMessageModal: PreparedMessageModal,
|
||||
sharePreparedMessageModal: SharePreparedMessageModal,
|
||||
giftTransferModal: GiftTransferModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './PreparedMessageModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const PreparedMessageModalAsync: FC<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const PreparedMessageModal = useModuleLoader(Bundles.Extra, 'PreparedMessageModal', !modal);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return PreparedMessageModal ? <PreparedMessageModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default PreparedMessageModalAsync;
|
||||
@ -0,0 +1,185 @@
|
||||
@use "../../../styles/mixins";
|
||||
|
||||
.root {
|
||||
:global(.modal-dialog) {
|
||||
max-width: 26rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1rem;
|
||||
background-color: var(--color-background-secondary);
|
||||
border-bottom-left-radius: var(--border-radius-modal);
|
||||
border-bottom-right-radius: var(--border-radius-modal);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
}
|
||||
|
||||
.actionMessageView {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
min-height: 22.5rem;
|
||||
height: 100%;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
|
||||
margin: 0.75rem;
|
||||
width: calc(100% - 1.5rem);
|
||||
|
||||
border-radius: var(--border-radius-default);
|
||||
|
||||
background-color: var(--theme-background-color);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
:global(.Message) {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
:global(.message-content) {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
:global(html.theme-light) & {
|
||||
background-image: url('../../../assets/chat-bg-br.png');
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-image: url('../../../assets/chat-bg-pattern-light.png');
|
||||
background-position: top right;
|
||||
background-size: 510px auto;
|
||||
background-repeat: repeat;
|
||||
mix-blend-mode: overlay;
|
||||
|
||||
:global(html.theme-dark) & {
|
||||
background-image: url('../../../assets/chat-bg-pattern-dark.png');
|
||||
mix-blend-mode: unset;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
bottom: auto;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
background-color: var(--theme-background-color);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
:global(html.theme-light) &:not(.customBgImage)::before {
|
||||
background-image: url('../../../assets/chat-bg-br.png');
|
||||
}
|
||||
|
||||
&:not(.customBgImage).customBgColor::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.customBgImage::before {
|
||||
background-image: var(--custom-background) !important;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
:global(body:not(.no-page-transitions)) &.withTransition {
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&.customBgImage::before {
|
||||
transition: background-image var(--layer-transition);
|
||||
}
|
||||
}
|
||||
|
||||
&.customBgImage.blurred::before {
|
||||
filter: blur(12px);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1276px) {
|
||||
:global(body:not(.no-page-transitions)) &:not(.customBgImage)::before {
|
||||
overflow: hidden;
|
||||
transform: scale(1);
|
||||
transform-origin: left center;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-light body:not(.no-page-transitions)) &:not(.customBgImage).withRightColumn::before {
|
||||
@media screen and (min-width: 1276px) {
|
||||
transform: scaleX(0.73) !important;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1921px) {
|
||||
transform: scaleX(0.8) !important;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 2600px) {
|
||||
transform: scaleX(0.95) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line @stylistic/max-line-length */
|
||||
:global(html.theme-light body:not(.no-page-transitions)) &:not(.customBgImage).withRightColumn.withTransition::before {
|
||||
transition: transform var(--layer-transition);
|
||||
}
|
||||
|
||||
&:not(.customBgImage):not(.customBgColor)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-image: url('../../../assets/chat-bg-pattern-light.png');
|
||||
background-position: top right;
|
||||
background-size: 510px auto;
|
||||
background-repeat: repeat;
|
||||
mix-blend-mode: overlay;
|
||||
|
||||
:global(html.theme-dark) & {
|
||||
background-image: url('../../../assets/chat-bg-pattern-dark.png');
|
||||
mix-blend-mode: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
198
src/components/modals/preparedMessage/PreparedMessageModal.tsx
Normal file
198
src/components/modals/preparedMessage/PreparedMessageModal.tsx
Normal file
@ -0,0 +1,198 @@
|
||||
import React, {
|
||||
type FC,
|
||||
memo, useMemo, useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
import type { TabState } from '../../../global/types';
|
||||
import type { ThemeKey } from '../../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { getMockPreparedMessageFromResult, getUserFullName } from '../../../global/helpers';
|
||||
import { selectTheme, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
|
||||
import useCustomBackground from '../../../hooks/useCustomBackground';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Message from '../../middle/message/Message';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
import styles from './PreparedMessageModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['preparedMessageModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
theme: ThemeKey;
|
||||
isBackgroundBlurred?: boolean;
|
||||
patternColor?: string;
|
||||
customBackground?: string;
|
||||
backgroundColor?: string;
|
||||
bot?: ApiUser;
|
||||
};
|
||||
|
||||
const PreparedMessageModal: FC<OwnProps & StateProps> = ({
|
||||
modal,
|
||||
theme,
|
||||
isBackgroundBlurred,
|
||||
patternColor,
|
||||
customBackground,
|
||||
backgroundColor,
|
||||
bot,
|
||||
}) => {
|
||||
const {
|
||||
closePreparedInlineMessageModal, sendWebAppEvent, openSharePreparedMessageModal,
|
||||
} = getActions();
|
||||
const lang = useLang();
|
||||
const isOpen = Boolean(modal);
|
||||
|
||||
const { webAppKey, message, botId } = modal || {};
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const customBackgroundValue = useCustomBackground(theme, customBackground);
|
||||
|
||||
const handleOpenClick = useLastCallback(() => {
|
||||
if (webAppKey && botId && message) {
|
||||
openSharePreparedMessageModal({
|
||||
webAppKey,
|
||||
message,
|
||||
});
|
||||
closePreparedInlineMessageModal();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCloseClick = useLastCallback(() => {
|
||||
closePreparedInlineMessageModal();
|
||||
if (webAppKey) {
|
||||
sendWebAppEvent({
|
||||
webAppKey,
|
||||
event: {
|
||||
eventType: 'prepared_message_failed',
|
||||
eventData: { error: 'USER_DECLINED' },
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const header = useMemo(() => {
|
||||
if (!modal) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<Button
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel={lang('Close')}
|
||||
onClick={handleCloseClick}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<h3 className={buildClassName('modal-title', styles.modalTitle)}>
|
||||
{lang('BotShareMessage')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}, [lang, modal]);
|
||||
|
||||
const localMessage = useMemo(() => {
|
||||
if (!botId || !message || !webAppKey) return undefined;
|
||||
return getMockPreparedMessageFromResult(botId, message);
|
||||
}, [botId, message, webAppKey]);
|
||||
|
||||
const bgClassName = buildClassName(
|
||||
styles.background,
|
||||
styles.withTransition,
|
||||
customBackground && styles.customBgImage,
|
||||
backgroundColor && styles.customBgColor,
|
||||
customBackground && isBackgroundBlurred && styles.blurred,
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogRef={containerRef}
|
||||
isOpen={isOpen}
|
||||
header={header}
|
||||
onClose={handleCloseClick}
|
||||
className={styles.root}
|
||||
contentClassName={styles.content}
|
||||
>
|
||||
<div
|
||||
className={buildClassName(styles.actionMessageView, 'MessageList')}
|
||||
// @ts-ignore -- FIXME: Find a way to disable interactions but keep a11y
|
||||
inert
|
||||
style={buildStyle(
|
||||
`--pattern-color: ${patternColor}`,
|
||||
backgroundColor && `--theme-background-color: ${backgroundColor}`,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={bgClassName}
|
||||
style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined}
|
||||
/>
|
||||
{localMessage && (
|
||||
<Message
|
||||
key={botId}
|
||||
message={localMessage}
|
||||
threadId={MAIN_THREAD_ID}
|
||||
messageListType="thread"
|
||||
noComments
|
||||
noReplies
|
||||
appearanceOrder={0}
|
||||
isJustAdded={false}
|
||||
isFirstInGroup
|
||||
isLastInGroup
|
||||
isLastInList={false}
|
||||
isFirstInDocumentGroup={false}
|
||||
isLastInDocumentGroup={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<p className={styles.info}>
|
||||
{lang('WebAppShareMessageInfo', { user: getUserFullName(bot) })}
|
||||
</p>
|
||||
<Button
|
||||
size="smaller"
|
||||
onClick={handleOpenClick}
|
||||
>
|
||||
{lang('BotShareMessageShare')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { modal }) => {
|
||||
const theme = selectTheme(global);
|
||||
const {
|
||||
isBlurred: isBackgroundBlurred,
|
||||
patternColor,
|
||||
background: customBackground,
|
||||
backgroundColor,
|
||||
} = global.settings.themes[theme] || {};
|
||||
const bot = modal ? selectUser(global, modal?.botId) : undefined;
|
||||
|
||||
return {
|
||||
theme,
|
||||
isBackgroundBlurred,
|
||||
patternColor,
|
||||
customBackground,
|
||||
backgroundColor,
|
||||
bot,
|
||||
currentUserId: global.currentUserId,
|
||||
};
|
||||
},
|
||||
)(PreparedMessageModal));
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './SharePreparedMessageModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const SharePreparedMessageModalAsync: FC<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const SharePreparedMessageModal = useModuleLoader(Bundles.Extra, 'SharePreparedMessageModal', !modal);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return SharePreparedMessageModal ? <SharePreparedMessageModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default SharePreparedMessageModalAsync;
|
||||
@ -0,0 +1,97 @@
|
||||
import React, {
|
||||
type FC,
|
||||
memo, useEffect,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal } from '../../../global';
|
||||
|
||||
import type { TabState } from '../../../global/types';
|
||||
import type { ThreadId } from '../../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { getPeerTitle } from '../../../global/helpers';
|
||||
import { selectPeer } from '../../../global/selectors';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import RecipientPicker from '../../common/RecipientPicker';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['sharePreparedMessageModal'];
|
||||
};
|
||||
|
||||
const SharePreparedMessageModal: FC<OwnProps> = ({
|
||||
modal,
|
||||
}) => {
|
||||
const {
|
||||
closeSharePreparedMessageModal,
|
||||
sendInlineBotResult,
|
||||
sendWebAppEvent,
|
||||
showNotification,
|
||||
} = getActions();
|
||||
const lang = useOldLang();
|
||||
const isOpen = Boolean(modal);
|
||||
|
||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
markIsShown();
|
||||
}
|
||||
}, [isOpen, markIsShown]);
|
||||
|
||||
const { message, filter, webAppKey } = modal || {};
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeSharePreparedMessageModal();
|
||||
if (webAppKey) {
|
||||
sendWebAppEvent({
|
||||
webAppKey,
|
||||
event: {
|
||||
eventType: 'prepared_message_failed',
|
||||
eventData: { error: 'USER_DECLINED' },
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleSelectRecipient = useLastCallback((id: string, threadId?: ThreadId) => {
|
||||
if (message && webAppKey) {
|
||||
const global = getGlobal();
|
||||
const peer = selectPeer(global, id);
|
||||
sendInlineBotResult({
|
||||
chatId: id,
|
||||
threadId: threadId || MAIN_THREAD_ID,
|
||||
id: message.result.id,
|
||||
queryId: message.result.queryId,
|
||||
});
|
||||
sendWebAppEvent({
|
||||
webAppKey,
|
||||
event: {
|
||||
eventType: 'prepared_message_sent',
|
||||
},
|
||||
});
|
||||
showNotification({
|
||||
message: lang('BotSharedToOne', getPeerTitle(lang, peer!)),
|
||||
});
|
||||
closeSharePreparedMessageModal();
|
||||
}
|
||||
});
|
||||
|
||||
if (!isOpen && !isShown) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecipientPicker
|
||||
isOpen={isOpen}
|
||||
searchPlaceholder={lang('Search')}
|
||||
filter={filter}
|
||||
onSelectRecipient={handleSelectRecipient}
|
||||
onClose={handleClose}
|
||||
onCloseAnimationEnd={unmarkIsShown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SharePreparedMessageModal);
|
||||
@ -148,6 +148,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
openLocationAccessModal,
|
||||
changeWebAppModalState,
|
||||
closeWebAppModal,
|
||||
openPreparedInlineMessageModal,
|
||||
} = getActions();
|
||||
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
||||
const [secondaryButton, setSecondaryButton] = useState<WebAppButton | undefined>();
|
||||
@ -698,6 +699,12 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
handleCheckDownloadFile(eventData.url, eventData.file_name);
|
||||
}
|
||||
|
||||
if (eventType === 'web_app_send_prepared_message') {
|
||||
if (!bot || !webAppKey) return;
|
||||
const { id } = eventData;
|
||||
openPreparedInlineMessageModal({ botId: bot.id, messageId: id, webAppKey });
|
||||
}
|
||||
|
||||
if (eventType === 'web_app_request_emoji_status_access') {
|
||||
if (!bot) return;
|
||||
openEmojiStatusAccessModal({ bot, webAppKey });
|
||||
|
||||
@ -312,7 +312,7 @@ export const LANG_PACK = 'weba';
|
||||
// eslint-disable-next-line max-len
|
||||
export const COUNTRIES_WITH_12H_TIME_FORMAT = new Set(['AU', 'BD', 'CA', 'CO', 'EG', 'HN', 'IE', 'IN', 'JO', 'MX', 'MY', 'NI', 'NZ', 'PH', 'PK', 'SA', 'SV', 'US']);
|
||||
|
||||
export const API_CHAT_TYPES = ['bots', 'channels', 'chats', 'users'] as const;
|
||||
export const API_CHAT_TYPES = ['bots', 'channels', 'chats', 'users', 'groups'] as const;
|
||||
|
||||
export const HEART_REACTION: ApiReactionEmoji = {
|
||||
type: 'emoji',
|
||||
|
||||
@ -381,14 +381,13 @@ addActionHandler('switchBotInline', (global, actions, payload): ActionReturnType
|
||||
|
||||
addActionHandler('sendInlineBotResult', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
id, queryId, isSilent, scheduledAt, messageList,
|
||||
id, queryId, isSilent, scheduledAt, threadId, chatId,
|
||||
tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { chatId, threadId } = messageList;
|
||||
const chat = selectChat(global, chatId)!;
|
||||
const draftReplyInfo = selectDraft(global, chatId, threadId)?.replyInfo;
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ApiAttachment,
|
||||
ApiChat,
|
||||
ApiChatType,
|
||||
ApiDraft,
|
||||
ApiError,
|
||||
ApiInputMessageReplyInfo,
|
||||
@ -2342,6 +2343,59 @@ addActionHandler('reportMessageDelivery', (global, actions, payload): ActionRetu
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('openPreparedInlineMessageModal', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
botId, messageId, webAppKey, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const bot = selectUser(global, botId);
|
||||
if (!bot) return;
|
||||
|
||||
const result = await callApi('fetchPreparedInlineMessage', {
|
||||
bot,
|
||||
id: messageId,
|
||||
});
|
||||
if (!result) {
|
||||
actions.sendWebAppEvent({
|
||||
webAppKey,
|
||||
event: {
|
||||
eventType: 'prepared_message_failed',
|
||||
eventData: { error: 'MESSAGE_EXPIRED' },
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
preparedMessageModal: {
|
||||
message: result,
|
||||
webAppKey,
|
||||
botId,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openSharePreparedMessageModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
webAppKey, message, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const supportedFilters = message.peerTypes?.filter((type): type is ApiChatType => type !== 'self');
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
sharePreparedMessageModal: {
|
||||
webAppKey,
|
||||
filter: supportedFilters,
|
||||
message,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
function countSortedIds(ids: number[], from: number, to: number) {
|
||||
// If ids are outside viewport, we cannot get correct count
|
||||
if (ids.length === 0 || from < ids[0] || to > ids[ids.length - 1]) return undefined;
|
||||
|
||||
@ -1077,3 +1077,19 @@ addActionHandler('closeAboutAdsModal', (global, actions, payload): ActionReturnT
|
||||
aboutAdsModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closePreparedInlineMessageModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
preparedMessageModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeSharePreparedMessageModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
sharePreparedMessageModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -5,7 +5,9 @@ import type {
|
||||
ApiChatFolder,
|
||||
ApiChatFullInfo,
|
||||
ApiChatInviteInfo,
|
||||
ApiMessage,
|
||||
ApiPeer,
|
||||
ApiPreparedInlineMessage,
|
||||
ApiTopic,
|
||||
ApiUser,
|
||||
} from '../../api/types';
|
||||
@ -22,6 +24,7 @@ import {
|
||||
VERIFICATION_CODES_USER_ID,
|
||||
} from '../../config';
|
||||
import { formatDateToString, formatTime } from '../../util/dates/dateFormat';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
import { getGlobal } from '..';
|
||||
import { isSystemBot } from './bots';
|
||||
import { getMainUsername, getUserFirstOrLastName } from './users';
|
||||
@ -474,3 +477,19 @@ export function getCustomPeerFromInvite(invite: ApiChatInviteInfo): CustomPeer {
|
||||
fakeType: isFake ? 'fake' : isScam ? 'scam' : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function getMockPreparedMessageFromResult(botId: string, preparedMessage: ApiPreparedInlineMessage) {
|
||||
const { result } = preparedMessage;
|
||||
|
||||
const inlineButtons = result?.sendMessage?.replyMarkup?.inlineButtons;
|
||||
|
||||
return {
|
||||
chatId: botId,
|
||||
content: result.sendMessage.content,
|
||||
date: getServerTime(),
|
||||
id: 0,
|
||||
isOutgoing: true,
|
||||
viaBotId: botId,
|
||||
inlineButtons,
|
||||
} satisfies ApiMessage;
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import type {
|
||||
ApiPaymentStatus,
|
||||
ApiPhoto,
|
||||
ApiPremiumSection,
|
||||
ApiPreparedInlineMessage,
|
||||
ApiPrivacyKey,
|
||||
ApiPrivacySettings,
|
||||
ApiReaction,
|
||||
@ -528,6 +529,17 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
closeAboutAdsModal: WithTabId | undefined;
|
||||
openPreparedInlineMessageModal: {
|
||||
botId: string;
|
||||
messageId: string;
|
||||
webAppKey: string;
|
||||
} & WithTabId;
|
||||
closePreparedInlineMessageModal: WithTabId | undefined;
|
||||
openSharePreparedMessageModal: {
|
||||
webAppKey: string;
|
||||
message: ApiPreparedInlineMessage;
|
||||
} & WithTabId;
|
||||
closeSharePreparedMessageModal: WithTabId | undefined;
|
||||
openPreviousReportAdModal: WithTabId | undefined;
|
||||
openPreviousReportModal: WithTabId | undefined;
|
||||
closeReportAdModal: WithTabId | undefined;
|
||||
@ -1872,7 +1884,8 @@ export interface ActionPayloads {
|
||||
sendInlineBotResult: {
|
||||
id: string;
|
||||
queryId: string;
|
||||
messageList: MessageList;
|
||||
chatId: string;
|
||||
threadId: ThreadId;
|
||||
isSilent?: boolean;
|
||||
scheduledAt?: number;
|
||||
} & WithTabId;
|
||||
|
||||
@ -30,6 +30,7 @@ import type {
|
||||
ApiPremiumGiftCodeOption,
|
||||
ApiPremiumPromo,
|
||||
ApiPremiumSection,
|
||||
ApiPreparedInlineMessage,
|
||||
ApiReactionWithPaid,
|
||||
ApiReceiptRegular,
|
||||
ApiSavedGifts,
|
||||
@ -499,6 +500,18 @@ export type TabState = {
|
||||
isQuiz?: boolean;
|
||||
};
|
||||
|
||||
preparedMessageModal?: {
|
||||
message: ApiPreparedInlineMessage;
|
||||
webAppKey: string;
|
||||
botId: string;
|
||||
};
|
||||
|
||||
sharePreparedMessageModal?: {
|
||||
webAppKey: string;
|
||||
message: ApiPreparedInlineMessage;
|
||||
filter: ApiChatType[];
|
||||
};
|
||||
|
||||
webApps: {
|
||||
activeWebAppKey?: string;
|
||||
openedOrderedKeys: string[];
|
||||
|
||||
@ -1624,6 +1624,7 @@ messages.viewSponsoredMessage#673ad8f1 peer:InputPeer random_id:bytes = Bool;
|
||||
messages.clickSponsoredMessage#f093465 flags:# media:flags.0?true fullscreen:flags.1?true peer:InputPeer random_id:bytes = Bool;
|
||||
messages.reportSponsoredMessage#1af3dbb8 peer:InputPeer random_id:bytes option:bytes = channels.SponsoredMessageReportResult;
|
||||
messages.getSponsoredMessages#9bd2f439 peer:InputPeer = messages.SponsoredMessages;
|
||||
messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage;
|
||||
messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector<int> = Bool;
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||
|
||||
@ -219,6 +219,7 @@
|
||||
"messages.reportSponsoredMessage",
|
||||
"messages.getSponsoredMessages",
|
||||
"messages.reportMessagesDelivery",
|
||||
"messages.getPreparedInlineMessage",
|
||||
"updates.getState",
|
||||
"updates.getDifference",
|
||||
"updates.getChannelDifference",
|
||||
|
||||
8
src/types/language.d.ts
vendored
8
src/types/language.d.ts
vendored
@ -483,6 +483,8 @@ export interface LangPair {
|
||||
'SetAdditionalPasswordInfo': undefined;
|
||||
'EditAdminTransferSetPassword': undefined;
|
||||
'BotOpenPageTitle': undefined;
|
||||
'BotShareMessageShare': undefined;
|
||||
'BotShareMessage': undefined;
|
||||
'FilterDeleteAlert': undefined;
|
||||
'RequestToJoinChannelSentDescription': undefined;
|
||||
'RequestToJoinGroupSentDescription': undefined;
|
||||
@ -1547,6 +1549,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'ConversationOpenBotLinkAllowMessages': {
|
||||
'bot': V;
|
||||
};
|
||||
'WebAppShareMessageInfo': {
|
||||
'user': V;
|
||||
};
|
||||
'BlockUserTitle': {
|
||||
'user': V;
|
||||
};
|
||||
@ -2216,6 +2221,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'UniqueStatusWearTitle': {
|
||||
'gift': V;
|
||||
};
|
||||
'BotSharedToOne': {
|
||||
'peer': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
|
||||
@ -137,6 +137,9 @@ export type WebAppInboundEvent =
|
||||
url: string;
|
||||
file_name: string;
|
||||
}> |
|
||||
WebAppEvent<'web_app_send_prepared_message', {
|
||||
id: string;
|
||||
}> |
|
||||
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
||||
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
||||
| 'web_app_request_write_access' | 'iframe_will_reload'
|
||||
@ -259,6 +262,10 @@ export type WebAppOutboundEvent =
|
||||
WebAppEvent<'file_download_requested', {
|
||||
status: 'cancelled' | 'downloading';
|
||||
}> |
|
||||
WebAppEvent<'prepared_message_failed', {
|
||||
error: 'UNSUPPORTED' | 'MESSAGE_EXPIRED' | 'MESSAGE_SEND_FAILED'
|
||||
| 'USER_DECLINED' | 'UNKNOWN_ERROR';
|
||||
}> |
|
||||
WebAppEvent<'main_button_pressed' |
|
||||
'secondary_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'
|
||||
| 'reload_iframe' | 'emoji_status_set', null>;
|
||||
| 'reload_iframe' | 'prepared_message_sent' | 'emoji_status_set', null>;
|
||||
|
||||
@ -231,6 +231,11 @@ export function formatShareText(url?: string, text?: string, title?: string): Ap
|
||||
|
||||
function parseChooseParameter(choose?: string) {
|
||||
if (!choose) return undefined;
|
||||
const types = choose.toLowerCase().split(' ');
|
||||
const types = choose.toLowerCase().split(' ').flatMap((type) => {
|
||||
if (type === 'groups') {
|
||||
return ['chats', 'groups'];
|
||||
}
|
||||
return [type];
|
||||
});
|
||||
return types.filter((type): type is ApiChatType => API_CHAT_TYPES.includes(type as ApiChatType));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user