Introduce Web Bots (#1813)

This commit is contained in:
Alexander Zinchuk 2022-04-19 15:12:16 +02:00
parent 14b603991f
commit 7b253a4103
74 changed files with 2811 additions and 358 deletions

View File

@ -1,12 +1,24 @@
import { Api as GramJs } from '../../../lib/gramjs';
import {
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm, ApiInlineResultType, ApiWebDocument,
ApiAttachMenuBot,
ApiAttachMenuBotIcon,
ApiBotCommand,
ApiBotInfo,
ApiBotInlineMediaResult,
ApiBotInlineResult,
ApiBotInlineSwitchPm,
ApiBotMenuButton,
ApiInlineResultType,
ApiWebDocument,
} from '../../types';
import { pick } from '../../../util/iteratees';
import { buildApiPhoto, buildApiThumbnailFromStripped } from './common';
import { buildVideoFromDocument } from './messages';
import { buildApiDocument, buildVideoFromDocument } from './messages';
import { buildStickerFromDocument } from './symbols';
import localDb from '../localDb';
import { buildApiPeerId } from './peers';
import { omitVirtualClassFields } from './helpers';
export function buildApiBotInlineResult(result: GramJs.BotInlineResult, queryId: string): ApiBotInlineResult {
const {
@ -50,6 +62,66 @@ export function buildBotSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) {
return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined;
}
export function buildApiAttachMenuBot(bot: GramJs.AttachMenuBot): ApiAttachMenuBot {
return {
id: bot.botId.toString(),
shortName: bot.shortName,
icons: bot.icons.map(buildApiAttachMenuIcon).filter(Boolean),
};
}
function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachMenuBotIcon | undefined {
if (!(icon.icon instanceof GramJs.Document)) return undefined;
const document = buildApiDocument(icon.icon);
if (!document) return undefined;
localDb.documents[String(icon.icon.id)] = icon.icon;
return {
name: icon.name,
document,
};
}
function buildApiWebDocument(document?: GramJs.TypeWebDocument): ApiWebDocument | undefined {
return document ? pick(document, ['url', 'mimeType']) : undefined;
}
export function buildApiBotInfo(botInfo: GramJs.BotInfo): ApiBotInfo {
const {
description, userId, commands, menuButton,
} = botInfo;
const botId = buildApiPeerId(userId, 'user');
const commandsArray = commands.map((command) => buildApiBotCommand(botId, command));
return {
botId,
description,
menuButton: buildApiBotMenuButton(menuButton),
commands: commandsArray.length ? commandsArray : undefined,
};
}
function buildApiBotCommand(botId: string, command: GramJs.BotCommand): ApiBotCommand {
return {
botId,
...omitVirtualClassFields(command),
};
}
export function buildApiBotMenuButton(menuButton: GramJs.TypeBotMenuButton): ApiBotMenuButton {
if (menuButton instanceof GramJs.BotMenuButton) {
return {
type: 'webApp',
text: menuButton.text,
url: menuButton.url,
};
}
return {
type: 'commands',
};
}

View File

@ -149,7 +149,6 @@ function buildApiChatRestrictions(peerEntity: GramJs.TypeUser | GramJs.TypeChat)
if (peerEntity instanceof GramJs.Chat) {
Object.assign(restrictions, {
isNotJoined: peerEntity.left,
isForbidden: peerEntity.kicked,
});
}

View File

@ -924,6 +924,9 @@ function buildAction(
text = senderId === currentUserId ? 'ActionYouScoredInGame' : 'ActionUserScoredInGame';
translationValues.push('%score%');
score = action.score;
} else if (action instanceof GramJs.MessageActionWebViewDataSent) {
text = 'Notification.WebAppSentData';
translationValues.push(action.text);
} else {
text = 'ChatList.UnsupportedMessage';
}
@ -1067,6 +1070,22 @@ function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefi
};
}
if (button instanceof GramJs.KeyboardButtonSimpleWebView) {
return {
type: 'simpleWebView',
text,
url: button.url,
};
}
if (button instanceof GramJs.KeyboardButtonWebView) {
return {
type: 'webView',
text,
url: button.url,
};
}
return {
type: 'unsupported',
text,

View File

@ -105,13 +105,15 @@ export function buildApiNotifyException(
notifySettings: GramJs.TypePeerNotifySettings, peer: GramJs.TypePeer, serverTimeOffset: number,
) {
const {
silent, muteUntil, showPreviews, sound,
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = Boolean(otherSound && !(otherSound instanceof GramJs.NotificationSoundNone));
return {
chatId: getApiChatIdFromMtpPeer(peer),
isMuted: silent || (typeof muteUntil === 'number' && getServerTime(serverTimeOffset) < muteUntil),
...(sound === '' && { isSilent: true }),
...(!hasSound && { isSilent: true }),
...(showPreviews !== undefined && { shouldShowPreviews: Boolean(showPreviews) }),
};
}

View File

@ -1,8 +1,9 @@
import { Api as GramJs } from '../../../lib/gramjs';
import {
ApiBotCommand, ApiUser, ApiUserStatus, ApiUserType,
ApiUser, ApiUserStatus, ApiUserType,
} from '../../types';
import { buildApiPeerId } from './peers';
import { buildApiBotInfo } from './bots';
export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUser {
const {
@ -21,8 +22,7 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse
commonChatsCount,
pinnedMessageId: pinnedMsgId,
isBlocked: Boolean(blocked),
...(botInfo && { botDescription: botInfo.description }),
...(botInfo && botInfo.commands.length && { botCommands: buildApiBotCommands(user.id, botInfo) }),
...(botInfo && { botInfo: buildApiBotInfo(botInfo) }),
},
};
}
@ -57,6 +57,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
...(avatarHash && { avatarHash }),
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachMenuBot: mtpUser.botAttachMenu }),
};
}
@ -87,14 +88,6 @@ export function buildApiUserStatus(mtpStatus?: GramJs.TypeUserStatus): ApiUserSt
}
}
function buildApiBotCommands(botId: string, botInfo: GramJs.BotInfo) {
return botInfo.commands.map(({ command, description }) => ({
botId,
command,
description,
})) as ApiBotCommand[];
}
export function buildApiUsersAndStatuses(mtpUsers: GramJs.TypeUser[]) {
const userStatusesById: Record<string, ApiUserStatus> = {};
const users: ApiUser[] = [];

View File

@ -18,6 +18,7 @@ import {
ApiSendMessageAction,
ApiSticker,
ApiVideo,
ApiThemeParameters,
} from '../../types';
import localDb from '../localDb';
import { pick } from '../../../util/iteratees';
@ -466,6 +467,12 @@ export function buildSendMessageAction(action: ApiSendMessageAction) {
return undefined;
}
export function buildInputThemeParams(params: ApiThemeParameters) {
return new GramJs.DataJSON({
data: JSON.stringify(params),
});
}
export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') {
// Workaround for old-fashioned IDs stored locally
if (typeof id === 'number') {

View File

@ -1,16 +1,19 @@
import BigInt from 'big-integer';
import { Api as GramJs } from '../../../lib/gramjs';
import { ApiChat, ApiUser } from '../../types';
import { ApiChat, ApiThemeParameters, ApiUser } from '../../types';
import localDb from '../localDb';
import { invokeRequest } from './client';
import { buildInputPeer, generateRandomBigInt } from '../gramjsBuilders';
import { buildInputPeer, buildInputThemeParams, generateRandomBigInt } from '../gramjsBuilders';
import { buildApiUser } from '../apiBuilders/users';
import { buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm } from '../apiBuilders/bots';
import {
buildApiAttachMenuBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm,
} from '../apiBuilders/bots';
import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
import { omitVirtualClassFields } from '../apiBuilders/helpers';
import { buildCollectionByKey } from '../../../util/iteratees';
export function init() {
}
@ -140,6 +143,132 @@ export async function startBot({
}), true);
}
export async function requestWebView({
isSilent,
peer,
bot,
url,
startParam,
replyToMessageId,
theme,
isFromBotMenu,
}: {
isSilent?: boolean;
peer: ApiChat | ApiUser;
bot: ApiUser;
url?: string;
startParam?: string;
replyToMessageId?: number;
theme?: ApiThemeParameters;
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),
replyToMsgId: replyToMessageId,
url,
startParam,
themeParams: theme ? buildInputThemeParams(theme) : undefined,
fromBotMenu: isFromBotMenu || undefined,
}));
if (result instanceof GramJs.WebViewResultUrl) {
return {
url: result.url,
queryId: result.queryId.toString(),
};
}
return undefined;
}
export async function requestSimpleWebView({
bot, url, theme,
}: {
bot: ApiUser;
url: string;
theme?: ApiThemeParameters;
}) {
const result = await invokeRequest(new GramJs.messages.RequestSimpleWebView({
url,
bot: buildInputPeer(bot.id, bot.accessHash),
themeParams: theme ? buildInputThemeParams(theme) : undefined,
}));
return result?.url;
}
export function prolongWebView({
isSilent,
peer,
bot,
queryId,
replyToMessageId,
}: {
isSilent?: boolean;
peer: ApiChat | ApiUser;
bot: ApiUser;
queryId: string;
replyToMessageId?: number;
}) {
return invokeRequest(new GramJs.messages.ProlongWebView({
silent: isSilent || undefined,
peer: buildInputPeer(peer.id, peer.accessHash),
bot: buildInputPeer(bot.id, bot.accessHash),
queryId: BigInt(queryId),
replyToMsgId: replyToMessageId,
}));
}
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,
}), true);
}
export async function loadAttachMenuBots({
hash,
}: {
hash?: string;
}) {
const result = await invokeRequest(new GramJs.messages.GetAttachMenuBots({
hash: hash ? BigInt(hash) : undefined,
}));
if (result instanceof GramJs.AttachMenuBots) {
addEntitiesWithPhotosToLocalDb(result.users);
return {
hash: result.hash.toString(),
bots: buildCollectionByKey(result.bots.map(buildApiAttachMenuBot), 'id'),
};
}
return undefined;
}
export function toggleBotInAttachMenu({
bot,
isEnabled,
}: {
bot: ApiUser;
isEnabled: boolean;
}) {
return invokeRequest(new GramJs.messages.ToggleBotInAttachMenu({
bot: buildInputPeer(bot.id, bot.accessHash),
enabled: isEnabled,
}));
}
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
return results.map((result) => {
if (result instanceof GramJs.BotInlineMediaResult) {

View File

@ -64,6 +64,7 @@ export {
export {
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachMenuBots, toggleBotInAttachMenu,
} from './bots';
export {

View File

@ -248,6 +248,9 @@ async function parseMedia(
case ApiMediaFormat.Lottie: {
return new Blob([data], { type: mimeType });
}
case ApiMediaFormat.Text: {
return data.toString();
}
case ApiMediaFormat.Progressive: {
return data.buffer;
}

View File

@ -34,7 +34,7 @@ import { buildApiConfig } from '../apiBuilders/appConfig';
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
const MAX_INT_32 = 2 ** 31 - 1;
const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz'];
const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz', 'en'];
export function updateProfile({
firstName,

View File

@ -44,11 +44,13 @@ import { buildApiNotifyException, buildPrivacyKey, buildPrivacyRules } from './a
import { buildApiPhoto } from './apiBuilders/common';
import {
buildApiGroupCall,
buildApiGroupCallParticipant, buildPhoneCall,
buildApiGroupCallParticipant,
buildPhoneCall,
getGroupCallId,
} from './apiBuilders/calls';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './apiBuilders/peers';
import { buildApiEmojiInteraction } from './apiBuilders/symbols';
import { buildApiBotMenuButton } from './apiBuilders/bots';
type Update = (
(GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }
@ -915,6 +917,23 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
callId: update.phoneCallId.toString(),
data: Array.from(update.data),
});
} else if (update instanceof GramJs.UpdateWebViewResultSent) {
const { queryId } = update;
onUpdate({
'@type': 'updateWebViewResultSent',
queryId: queryId.toString(),
});
} else if (update instanceof GramJs.UpdateBotMenuButton) {
const { botId, button } = update;
const id = buildApiPeerId(botId, 'user');
onUpdate({
'@type': 'updateBotMenuButton',
botId: id,
button: buildApiBotMenuButton(button),
});
} else if (DEBUG) {
const params = typeof update === 'object' && 'className' in update ? update.className : update;
// eslint-disable-next-line no-console

View File

@ -44,3 +44,22 @@ export interface ApiBotCommand {
command: string;
description: string;
}
type ApiBotMenuButtonCommands = {
type: 'commands';
};
type ApiBotMenuButtonWebApp = {
type: 'webApp';
text: string;
url: string;
};
export type ApiBotMenuButton = ApiBotMenuButtonWebApp | ApiBotMenuButtonCommands;
export interface ApiBotInfo {
botId: string;
commands?: ApiBotCommand[];
description: string;
menuButton: ApiBotMenuButton;
}

View File

@ -6,6 +6,7 @@ export enum ApiMediaFormat {
Lottie,
Progressive,
Stream,
Text,
}
export type ApiParsedMedia = string | Blob | ArrayBuffer;

View File

@ -395,6 +395,18 @@ interface ApiKeyboardButtonUrl {
url: string;
}
interface ApiKeyboardButtonSimpleWebView {
type: 'simpleWebView';
text: string;
url: string;
}
interface ApiKeyboardButtonWebView {
type: 'webView';
text: string;
url: string;
}
interface ApiKeyboardButtonCallback {
type: 'callback';
text: string;
@ -407,7 +419,7 @@ interface ApiKeyboardButtonRequestPoll {
isQuiz?: boolean;
}
interface ApiKeyboardButtonSwitchInline {
interface ApiKeyboardButtonSwitchBotInline {
type: 'switchBotInline';
text: string;
query: string;
@ -426,8 +438,10 @@ export type ApiKeyboardButton = (
| ApiKeyboardButtonUrl
| ApiKeyboardButtonCallback
| ApiKeyboardButtonRequestPoll
| ApiKeyboardButtonSwitchInline
| ApiKeyboardButtonSwitchBotInline
| ApiKeyboardButtonUserProfile
| ApiKeyboardButtonWebView
| ApiKeyboardButtonSimpleWebView
);
export type ApiKeyboardButtons = ApiKeyboardButton[][];
@ -448,6 +462,15 @@ export type ApiSendMessageAction = {
type: 'cancel' | 'typing' | 'recordAudio' | 'chooseSticker' | 'playingGame';
};
export type ApiThemeParameters = {
bg_color: string;
text_color: string;
hint_color: string;
link_color: string;
button_color: string;
button_text_color: string;
};
export const MAIN_THREAD_ID = -1;
// `Symbol` can not be transferred from worker

View File

@ -22,6 +22,7 @@ import {
import {
ApiGroupCall, ApiPhoneCall,
} from './calls';
import { ApiBotMenuButton } from './bots';
export type ApiUpdateReady = {
'@type': 'updateApiReady';
@ -486,6 +487,17 @@ export type ApiUpdatePhoneCallConnectionState = {
connectionState: RTCPeerConnectionState;
};
export type ApiUpdateWebViewResultSent = {
'@type': 'updateWebViewResultSent';
queryId: string;
};
export type ApiUpdateBotMenuButton = {
'@type': 'updateBotMenuButton';
botId: string;
button: ApiBotMenuButton;
};
export type ApiUpdate = (
ApiUpdateReady | ApiUpdateSession |
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
@ -501,14 +513,14 @@ export type ApiUpdate = (
ApiUpdateFavoriteStickers | ApiUpdateStickerSet |
ApiUpdateNewScheduledMessage | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage |
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages |
ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode |
ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent |
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
ApiUpdateServerTimeOffset | ApiUpdateShowInvite | ApiUpdateMessageReactions |
ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams |
ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId |
ApiUpdatePendingJoinRequests | ApiUpdatePaymentVerificationNeeded | ApiUpdatePaymentStateCompleted |
ApiUpdatePhoneCall | ApiUpdatePhoneCallSignalingData | ApiUpdatePhoneCallMediaState |
ApiUpdatePhoneCallConnectionState
ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton
);
export type OnApiUpdate = (update: ApiUpdate) => void;

View File

@ -1,5 +1,5 @@
import { ApiPhoto } from './messages';
import { ApiBotCommand } from './bots';
import { ApiDocument, ApiPhoto } from './messages';
import { ApiBotInfo } from './bots';
export interface ApiUser {
id: string;
@ -24,6 +24,7 @@ export interface ApiUser {
isFullyLoaded: boolean;
};
fakeType?: ApiFakeType;
isAttachMenuBot?: boolean;
// Obtained from GetFullUser / UserFullInfo
fullInfo?: ApiUserFullInfo;
@ -33,9 +34,8 @@ export interface ApiUserFullInfo {
isBlocked?: boolean;
bio?: string;
commonChatsCount?: number;
botDescription?: string;
pinnedMessageId?: number;
botCommands?: ApiBotCommand[];
botInfo?: ApiBotInfo;
}
export type ApiFakeType = 'fake' | 'scam';
@ -50,3 +50,14 @@ export interface ApiUserStatus {
wasOnline?: number;
expires?: number;
}
export interface ApiAttachMenuBot {
id: string;
shortName: string;
icons: ApiAttachMenuBotIcon[];
}
export interface ApiAttachMenuBotIcon {
name: string;
document: ApiDocument;
}

Binary file not shown.

Binary file not shown.

View File

@ -6,6 +6,9 @@ export { default as Notifications } from '../components/main/Notifications';
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
export { default as NewContactModal } from '../components/main/NewContactModal';
export { default as WebAppModal } from '../components/main/WebAppModal';
export { default as BotTrustModal } from '../components/main/BotTrustModal';
export { default as BotAttachModal } from '../components/main/BotAttachModal';
export { default as CalendarModal } from '../components/common/CalendarModal';
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';

View File

@ -54,7 +54,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
const lang = useLang();
const senderTitle = message?.forwardInfo?.hiddenUserName || (sender && getSenderTitle(lang, sender));
const senderTitle = sender ? getSenderTitle(lang, sender) : message?.forwardInfo?.hiddenUserName;
return (
<div

View File

@ -0,0 +1,16 @@
import React, { FC, memo } from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import { OwnProps } from './BotAttachModal';
import useModuleLoader from '../../hooks/useModuleLoader';
const BotAttachModalAsync: FC<OwnProps> = (props) => {
const { bot } = props;
const BotAttachModal = useModuleLoader(Bundles.Extra, 'BotAttachModal', !bot);
// eslint-disable-next-line react/jsx-props-no-spreading
return BotAttachModal ? <BotAttachModal {...props} /> : undefined;
};
export default memo(BotAttachModalAsync);

View File

@ -0,0 +1,34 @@
import React, { FC } from '../../lib/teact/teact';
import { getActions } from '../../global';
import { ApiUser } from '../../api/types';
import useLang from '../../hooks/useLang';
import ConfirmDialog from '../ui/ConfirmDialog';
export type OwnProps = {
bot?: ApiUser;
};
const BotAttachModal: FC<OwnProps> = ({
bot,
}) => {
const { closeBotAttachRequestModal, confirmBotAttachRequest } = getActions();
const lang = useLang();
const name = bot?.firstName;
return (
<ConfirmDialog
isOpen={Boolean(bot)}
onClose={closeBotAttachRequestModal}
confirmHandler={confirmBotAttachRequest}
title={name}
textParts={lang('WebApp.AddToAttachmentText', name)}
/>
);
};
export default BotAttachModal;

View File

@ -0,0 +1,16 @@
import React, { FC, memo } from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import { OwnProps } from './BotTrustModal';
import useModuleLoader from '../../hooks/useModuleLoader';
const BotTrustModalAsync: FC<OwnProps> = (props) => {
const { bot } = props;
const BotTrustModal = useModuleLoader(Bundles.Extra, 'BotTrustModal', !bot);
// eslint-disable-next-line react/jsx-props-no-spreading
return BotTrustModal ? <BotTrustModal {...props} /> : undefined;
};
export default memo(BotTrustModalAsync);

View File

@ -0,0 +1,47 @@
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import { getActions } from '../../global';
import { ApiUser } from '../../api/types';
import { getUserFullName } from '../../global/helpers';
import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang';
import usePrevious from '../../hooks/usePrevious';
import ConfirmDialog from '../ui/ConfirmDialog';
export type OwnProps = {
bot?: ApiUser;
type?: 'game' | 'webApp';
};
const BotTrustModal: FC<OwnProps> = ({ bot, type }) => {
const { cancelBotTrustRequest, markBotTrusted } = getActions();
const lang = useLang();
// Keep props a little bit longer, to show correct text on closing animation
const previousBot = usePrevious(bot, false);
const previousType = usePrevious(type, false);
const currentBot = bot || previousBot;
const currentType = type || previousType;
const handleBotTrustAccept = useCallback(() => {
markBotTrusted({ botId: bot!.id });
}, [markBotTrusted, bot]);
const title = currentType === 'game' ? lang('AppName') : lang('BotOpenPageTitle');
const text = currentType === 'game' ? lang('BotPermissionGameAlert', getUserFullName(currentBot))
: lang('BotOpenPageMessage', getUserFullName(currentBot));
return (
<ConfirmDialog
isOpen={Boolean(bot)}
onClose={cancelBotTrustRequest}
confirmHandler={handleBotTrustAccept}
title={title}
textParts={renderText(text, ['br', 'simple_markdown'])}
/>
);
};
export default memo(BotTrustModal);

View File

@ -4,7 +4,9 @@ import React, {
import { getActions, withGlobal } from '../../global';
import { LangCode } from '../../types';
import { ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType } from '../../api/types';
import {
ApiChat, ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType,
} from '../../api/types';
import { GlobalState } from '../../global/types';
import '../../global/actions/all';
@ -54,10 +56,14 @@ import ActiveCallHeader from '../calls/ActiveCallHeader.async';
import PhoneCall from '../calls/phone/PhoneCall.async';
import NewContactModal from './NewContactModal.async';
import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async';
import WebAppModal from './WebAppModal.async';
import BotTrustModal from './BotTrustModal.async';
import BotAttachModal from './BotAttachModal.async';
import './Main.scss';
type StateProps = {
chat?: ApiChat;
connectionState?: ApiUpdateConnectionStateType;
authState?: ApiUpdateAuthorizationStateType;
lastSyncTime?: number;
@ -84,6 +90,9 @@ type StateProps = {
openedGame?: GlobalState['openedGame'];
gameTitle?: string;
isRatePhoneCallModalOpen?: boolean;
webApp?: GlobalState['webApp'];
botTrustRequest?: GlobalState['botTrustRequest'];
botAttachRequest?: GlobalState['botAttachRequest'];
};
const NOTIFICATION_INTERVAL = 1000;
@ -120,6 +129,9 @@ const Main: FC<StateProps> = ({
openedGame,
gameTitle,
isRatePhoneCallModalOpen,
botTrustRequest,
botAttachRequest,
webApp,
}) => {
const {
sync,
@ -138,6 +150,7 @@ const Main: FC<StateProps> = ({
openStickerSetShortName,
checkVersionNotification,
loadAppConfig,
loadAttachMenuBots,
} = getActions();
if (DEBUG && !DEBUG_isLogged) {
@ -163,10 +176,11 @@ const Main: FC<StateProps> = ({
loadNotificationExceptions();
loadTopInlineBots();
loadEmojiKeywords({ language: BASE_EMOJI_KEYWORD_LANG });
loadAttachMenuBots();
}
}, [
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig,
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachMenuBots,
]);
// Language-based API calls
@ -366,10 +380,13 @@ const Main: FC<StateProps> = ({
isByPhoneNumber={newContactByPhoneNumber}
/>
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
<WebAppModal webApp={webApp} />
<DownloadManager />
<PhoneCall isActive={isPhoneCallActive} />
<UnreadCount isForAppBadge />
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
<BotTrustModal bot={botTrustRequest?.bot} type={botTrustRequest?.type} />
<BotAttachModal bot={botAttachRequest?.bot} />
</div>
);
};
@ -433,6 +450,9 @@ export default memo(withGlobal(
openedGame,
gameTitle,
isRatePhoneCallModalOpen: Boolean(global.ratingPhoneCall),
botTrustRequest: global.botTrustRequest,
botAttachRequest: global.botAttachRequest,
webApp: global.webApp,
};
},
)(Main));

View File

@ -0,0 +1,16 @@
import React, { FC, memo } from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import { OwnProps } from './WebAppModal';
import useModuleLoader from '../../hooks/useModuleLoader';
const WebAppModalAsync: FC<OwnProps> = (props) => {
const { webApp } = props;
const WebAppModal = useModuleLoader(Bundles.Extra, 'WebAppModal', !webApp);
// eslint-disable-next-line react/jsx-props-no-spreading
return WebAppModal ? <WebAppModal {...props} /> : undefined;
};
export default memo(WebAppModalAsync);

View File

@ -0,0 +1,69 @@
.WebAppModal {
.modal-header {
border-bottom: 1px solid var(--color-dividers);
padding: 0.5rem;
}
.modal-dialog {
height: 75%;
justify-content: center;
border: none;
box-shadow: none;
margin: 0;
overflow: hidden;
}
.modal-content {
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0;
border-bottom-right-radius: var(--border-radius-default);
border-bottom-left-radius: var(--border-radius-default);
}
.web-app-frame {
width: 100%;
height: 100%;
border: 0;
&.with-button {
height: calc(100% - 56px);
}
}
.web-app-button {
position: absolute;
bottom: 0;
border-radius: 0;
transform: translateY(100%);
transition: 0.25s ease-in-out transform;
&.visible {
transform: translateY(0);
}
&.hidden {
visibility: hidden;
}
}
.Spinner {
position: absolute;
right: 1rem;
}
@media (max-width: 600px) {
.modal-dialog {
background-color: var(--color-background);
border-radius: 0;
height: 100%;
max-width: 100% !important;
}
.modal-content {
max-height: none;
border-radius: 0;
}
}
}

View File

@ -0,0 +1,292 @@
import React, {
FC, memo, useCallback, useEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import { ApiChat } from '../../api/types';
import { GlobalState } from '../../global/types';
import { ThemeKey } from '../../types';
import windowSize from '../../util/windowSize';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import { selectCurrentChat, selectTheme } from '../../global/selectors';
import buildClassName from '../../util/buildClassName';
import { extractCurrentThemeParams, validateHexColor } from '../../util/themeStyle';
import useInterval from '../../hooks/useInterval';
import useLang from '../../hooks/useLang';
import useOnChange from '../../hooks/useOnChange';
import useWebAppFrame, { WebAppInboundEvent } from './hooks/useWebAppFrame';
import usePrevious from '../../hooks/usePrevious';
import Modal from '../ui/Modal';
import Button from '../ui/Button';
import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
import Spinner from '../ui/Spinner';
import './WebAppModal.scss';
type WebAppButton = {
isVisible: boolean;
isActive: boolean;
text: string;
color: string;
textColor: string;
isProgressVisible: boolean;
};
export type OwnProps = {
webApp?: GlobalState['webApp'];
};
type StateProps = {
isInstalled?: boolean;
chat?: ApiChat;
theme?: ThemeKey;
};
const MAIN_BUTTON_ANIMATION_TIME = 250;
const PROLONG_INTERVAL = 45000; // 45s
const ANIMATION_WAIT = 400;
const WebAppModal: FC<OwnProps & StateProps> = ({
webApp,
chat,
isInstalled,
theme,
}) => {
const {
closeWebApp, sendWebViewData, prolongWebView, toggleBotInAttachMenu,
} = getActions();
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
const lang = useLang();
const {
url, bot, buttonText, queryId,
} = webApp || {};
const isOpen = Boolean(url);
const isSimple = !queryId;
const handleEvent = useCallback((event: WebAppInboundEvent) => {
const { eventType } = event;
if (eventType === 'web_app_close') {
closeWebApp();
}
if (eventType === 'web_app_data_send') {
const { eventData } = event;
closeWebApp();
sendWebViewData({
bot: bot!,
buttonText: buttonText!,
data: eventData.data,
});
}
if (eventType === 'web_app_setup_main_button') {
const { eventData } = event;
const themeParams = extractCurrentThemeParams();
// Validate colors if they are present
const color = !eventData.color || validateHexColor(eventData.color) ? eventData.color
: themeParams.button_color;
const textColor = !eventData.text_color || validateHexColor(eventData.text_color) ? eventData.text_color
: themeParams.text_color;
setMainButton({
isVisible: eventData.is_visible && Boolean(eventData.text?.trim().length),
isActive: eventData.is_active,
text: eventData.text || '',
color,
textColor,
isProgressVisible: eventData.is_progress_visible,
});
}
}, [bot, buttonText, closeWebApp, sendWebViewData]);
const {
ref, reloadFrame, sendEvent, sendViewport, sendTheme,
} = useWebAppFrame(isOpen, isSimple, handleEvent);
const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0;
useInterval(() => {
prolongWebView({
bot: bot!,
queryId: queryId!,
peer: chat!,
});
}, queryId ? PROLONG_INTERVAL : undefined, true);
const handleMainButtonClick = useCallback(() => {
sendEvent({
eventType: 'main_button_pressed',
});
}, [sendEvent]);
const handleRefreshClick = useCallback(() => {
reloadFrame(webApp!.url);
}, [reloadFrame, webApp]);
// Notify view that height changed
useOnChange(() => {
setTimeout(() => {
sendViewport();
}, ANIMATION_WAIT);
}, [mainButton?.isVisible, sendViewport]);
// Notify view that theme changed
useOnChange(() => {
setTimeout(() => {
sendTheme();
}, ANIMATION_WAIT);
}, [theme, sendTheme]);
// Prevent refresh when rotating device
useEffect(() => {
if (!isOpen) return undefined;
windowSize.disableRefresh();
return () => {
windowSize.enableRefresh();
};
}, [isOpen]);
const handleToggleClick = useCallback(() => {
toggleBotInAttachMenu({
botId: bot!.id,
isEnabled: !isInstalled,
});
}, [bot, isInstalled, toggleBotInAttachMenu]);
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
return ({ onTrigger, isOpen: isMenuOpen }) => (
<Button
round
ripple={!IS_SINGLE_COLUMN_LAYOUT}
size="smaller"
color="translucent"
className={isMenuOpen ? 'active' : ''}
onClick={onTrigger}
ariaLabel="More actions"
>
<i className="icon-more" />
</Button>
);
}, []);
const header = useMemo(() => {
return (
<div className="modal-header">
<Button
round
color="translucent"
size="smaller"
ariaLabel={lang('Close')}
onClick={closeWebApp}
>
<i className="icon-close" />
</Button>
<div className="modal-title">{bot?.firstName}</div>
<DropdownMenu
className="web-app-more-menu"
trigger={MoreMenuButton}
positionX="right"
>
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem>
{bot?.isAttachMenuBot && (
<MenuItem icon={isInstalled ? 'stop' : 'install'} onClick={handleToggleClick} destructive={isInstalled}>
{lang(isInstalled ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
</MenuItem>
)}
</DropdownMenu>
</div>
);
}, [
MoreMenuButton, bot, closeWebApp, handleRefreshClick, handleToggleClick, lang, isInstalled,
]);
const prevMainButtonColor = usePrevious(mainButton?.color, true);
const prevMainButtonTextColor = usePrevious(mainButton?.textColor, true);
const prevMainButtonIsActive = usePrevious(mainButton && Boolean(mainButton.isActive), true);
const prevMainButtonText = usePrevious(mainButton?.text, true);
const mainButtonCurrentColor = mainButton?.color || prevMainButtonColor;
const mainButtonCurrentTextColor = mainButton?.textColor || prevMainButtonTextColor;
const mainButtonCurrentIsActive = mainButton?.isActive !== undefined ? mainButton.isActive : prevMainButtonIsActive;
const mainButtonCurrentText = mainButton?.text || prevMainButtonText;
useEffect(() => {
if (!isOpen) setMainButton(undefined);
}, [isOpen]);
const [shouldDecreaseWebFrameSize, setShouldDecreaseWebFrameSize] = useState(false);
const [shouldHideButton, setShouldHideButton] = useState(true);
const buttonChangeTimeout = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => {
if (buttonChangeTimeout.current) clearTimeout(buttonChangeTimeout.current);
if (!shouldShowMainButton) {
setShouldDecreaseWebFrameSize(false);
buttonChangeTimeout.current = setTimeout(() => {
setShouldHideButton(true);
}, MAIN_BUTTON_ANIMATION_TIME);
} else {
setShouldHideButton(false);
buttonChangeTimeout.current = setTimeout(() => {
setShouldDecreaseWebFrameSize(true);
}, MAIN_BUTTON_ANIMATION_TIME);
}
}, [setShouldDecreaseWebFrameSize, shouldShowMainButton]);
return (
<Modal
className="WebAppModal"
isOpen={isOpen}
onClose={closeWebApp}
header={header}
hasCloseButton
>
{isOpen && (
<>
<iframe
ref={ref}
className={buildClassName('web-app-frame', shouldDecreaseWebFrameSize && 'with-button')}
src={url}
title={`${bot?.firstName} Web App`}
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
allow="camera; microphone; geolocation;"
allowFullScreen
/>
<Button
className={buildClassName(
'web-app-button',
shouldShowMainButton && 'visible',
shouldHideButton && 'hidden',
)}
style={`background-color: ${mainButtonCurrentColor}; color: ${mainButtonCurrentTextColor}`}
disabled={!mainButtonCurrentIsActive}
onClick={handleMainButtonClick}
>
{mainButtonCurrentText}
{mainButton?.isProgressVisible && <Spinner color="white" />}
</Button>
</>
)}
</Modal>
);
};
export default memo(withGlobal<OwnProps>(
(global, { webApp }): StateProps => {
const { bot } = webApp || {};
const isInstalled = Boolean(bot && global.attachMenu.bots[bot.id]);
const chat = selectCurrentChat(global);
const theme = selectTheme(global);
return {
isInstalled,
chat,
theme,
};
},
)(WebAppModal));

View File

@ -0,0 +1,148 @@
import useWindowSize from '../../../hooks/useWindowSize';
import { useCallback, useEffect, useRef } from '../../../lib/teact/teact';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
export type WebAppInboundEvent = {
eventType: 'web_app_data_send';
eventData: {
data: string;
};
} | {
eventType: 'web_app_setup_main_button';
eventData: {
is_visible: boolean;
is_active: boolean;
text: string;
color: string;
text_color: string;
is_progress_visible: boolean;
};
} | {
eventType: 'web_app_request_viewport';
} | {
eventType: 'web_app_request_theme';
} | {
eventType: 'web_app_ready';
} | {
eventType: 'web_app_expand';
} | {
eventType: 'web_app_close';
};
type WebAppOutboundEvent = {
eventType: 'viewport_changed';
eventData: {
height: number;
width?: number;
is_expanded?: boolean;
};
} | {
eventType: 'theme_changed';
eventData: {
theme_params: {
bg_color: string;
text_color: string;
hint_color: string;
link_color: string;
button_color: string;
button_text_color: string;
};
};
} | {
eventType: 'main_button_pressed';
};
const useWebAppFrame = (isOpen: boolean, isSimpleView: boolean, onEvent: (event: WebAppInboundEvent) => void) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLIFrameElement>(null);
const ignoreEventsRef = useRef<boolean>(false);
const windowSize = useWindowSize();
const reloadFrame = useCallback((url: string) => {
if (!ref.current) return;
const frame = ref.current;
frame.src = 'about:blank';
frame.addEventListener('load', () => {
frame.src = url;
}, { once: true });
}, []);
const sendEvent = useCallback((event: WebAppOutboundEvent) => {
if (!ref.current?.contentWindow) return;
ref.current.contentWindow.postMessage(JSON.stringify(event), '*');
}, []);
const sendViewport = useCallback(() => {
if (!ref.current) {
return;
}
const { width, height } = ref.current.getBoundingClientRect();
sendEvent({
eventType: 'viewport_changed',
eventData: {
width,
height,
is_expanded: true,
},
});
}, [sendEvent]);
const sendTheme = useCallback(() => {
sendEvent({
eventType: 'theme_changed',
eventData: {
theme_params: extractCurrentThemeParams(),
},
});
}, [sendEvent]);
const handleMessage = useCallback((event: MessageEvent<string>) => {
if (ignoreEventsRef.current) {
return;
}
try {
const data = JSON.parse(event.data) as WebAppInboundEvent;
// Handle some app requests here to simplify hook usage
if (data.eventType === 'web_app_request_viewport') {
sendViewport();
}
if (data.eventType === 'web_app_request_theme') {
sendTheme();
}
if (data.eventType === 'web_app_data_send') {
if (!isSimpleView) return; // Allowed only in simple view
ignoreEventsRef.current = true;
}
onEvent(data);
} catch (err) {
// Ignore other messages
}
}, [isSimpleView, onEvent, sendTheme, sendViewport]);
useEffect(() => {
if (windowSize) {
sendViewport();
}
}, [sendViewport, windowSize]);
useEffect(() => {
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [handleMessage]);
useEffect(() => {
if (isOpen && ref.current?.contentWindow) {
sendViewport();
ignoreEventsRef.current = false;
}
}, [isOpen, sendViewport]);
return {
ref, sendEvent, reloadFrame, sendViewport, sendTheme,
};
};
export default useWebAppFrame;

View File

@ -208,7 +208,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
color="translucent"
size="smaller"
ariaLabel="Volume"
withClickPropagation
noPreventDefault
>
<i className={volumeIcon} onClick={handleVolumeClick} />
{!IS_IOS && (

View File

@ -410,7 +410,7 @@ export default memo(withGlobal<OwnProps>(
canReportChat,
canDeleteChat: getCanDeleteChat(chat),
hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId),
botCommands: chatBot?.fullInfo?.botCommands,
botCommands: chatBot?.fullInfo?.botInfo?.commands,
};
},
)(HeaderMenuContainer));

View File

@ -1,20 +1,22 @@
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import { getActions } from '../../global';
import { ApiMessage } from '../../api/types';
import { getPictogramDimensions } from '../common/helpers/mediaDimensions';
import { getMessageMediaHash } from '../../global/helpers';
import { getMessageMediaHash, getMessageSingleInlineButton } from '../../global/helpers';
import { renderMessageSummary } from '../common/helpers/renderMessageText';
import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/environment';
import useMedia from '../../hooks/useMedia';
import useWebpThumbnail from '../../hooks/useWebpThumbnail';
import ConfirmDialog from '../ui/ConfirmDialog';
import Button from '../ui/Button';
import RippleEffect from '../ui/RippleEffect';
import buildClassName from '../../util/buildClassName';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import { renderMessageSummary } from '../common/helpers/renderMessageText';
import RippleEffect from '../ui/RippleEffect';
import ConfirmDialog from '../ui/ConfirmDialog';
import Button from '../ui/Button';
import PinnedMessageNavigation from './PinnedMessageNavigation';
type OwnProps = {
@ -31,6 +33,7 @@ type OwnProps = {
const HeaderPinnedMessage: FC<OwnProps> = ({
message, count, index, customTitle, className, onUnpinMessage, onClick, onAllPinnedClick,
}) => {
const { clickBotInlineButton } = getActions();
const lang = useLang();
const mediaThumbnail = useWebpThumbnail(message);
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram'));
@ -46,6 +49,16 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
}
}, [closeUnpinDialog, onUnpinMessage, message.id]);
const inlineButton = getMessageSingleInlineButton(message);
const handleInlineButtonClick = useCallback(() => {
if (inlineButton) {
clickBotInlineButton({ messageId: message.id, button: inlineButton });
}
}, [clickBotInlineButton, inlineButton, message.id]);
const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag();
return (
<div className={buildClassName('HeaderPinnedMessage-wrapper', className)}>
{count > 1 && (
@ -79,7 +92,11 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
confirmLabel="Unpin"
confirmHandler={handleUnpinMessage}
/>
<div className="HeaderPinnedMessage" onClick={onClick} dir={lang.isRtl ? 'rtl' : undefined}>
<div
className={buildClassName('HeaderPinnedMessage', noHoverColor && 'no-hover')}
onClick={onClick}
dir={lang.isRtl ? 'rtl' : undefined}
>
<PinnedMessageNavigation
count={count}
index={index}
@ -90,9 +107,20 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
{customTitle || `${lang('PinnedMessage')} ${index > 0 ? `#${count - index}` : ''}`}
</div>
<p dir="auto">{text}</p>
<RippleEffect />
</div>
<RippleEffect />
{inlineButton && (
<Button
size="tiny"
className="inline-button"
onClick={handleInlineButtonClick}
shouldStopPropagation
onMouseEnter={!IS_TOUCH_ENV ? markNoHoverColor : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkNoHoverColor : undefined}
>
{inlineButton.text}
</Button>
)}
</div>
</div>
);

View File

@ -591,7 +591,7 @@ export default memo(withGlobal<OwnProps>(
let botDescription: string | undefined;
if (selectIsChatBotNotStarted(global, chatId)) {
if (chatBot.fullInfo) {
botDescription = chatBot.fullInfo.botDescription || 'NoMessages';
botDescription = chatBot.fullInfo.botInfo?.description || 'NoMessages';
} else {
botDescription = 'Updating bot info...';
}

View File

@ -421,8 +421,9 @@
position: relative;
overflow: hidden;
cursor: pointer;
align-items: center;
&:hover {
&:hover:not(.no-hover) {
background-color: var(--color-interactive-element-hover);
}
@ -465,6 +466,8 @@
margin-inline-start: 0.375rem;
margin-top: 0.125rem;
max-width: 15rem;
min-width: 8rem;
flex-grow: 1;
@media (min-width: 1440px) and (max-width: 1500px) {
max-width: 14rem;
@ -500,6 +503,21 @@
}
}
.inline-button {
display: block;
width: auto;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border-radius: 1.5rem;
padding: 0 0.75rem;
font-weight: 500;
text-transform: none;
height: 2rem;
max-width: 10rem;
flex-shrink: 1;
}
.emoji-small {
width: 1rem;
height: 1rem;

View File

@ -15,6 +15,11 @@
top: -2.875rem;
}
.bubble {
max-height: 20rem;
overflow: auto;
}
.is-pointer-env & {
> .backdrop {
position: absolute;
@ -31,3 +36,7 @@
}
}
}
.bot-attach-context-menu {
position: absolute;
}

View File

@ -2,9 +2,13 @@ import React, {
FC, memo, useCallback, useEffect,
} from '../../../lib/teact/teact';
import { GlobalState } from '../../../global/types';
import { ISettings } from '../../../types';
import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config';
import { IS_TOUCH_ENV } from '../../../util/environment';
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
import useMouseInside from '../../../hooks/useMouseInside';
import useLang from '../../../hooks/useLang';
import useFlag from '../../../hooks/useFlag';
@ -12,23 +16,39 @@ import useFlag from '../../../hooks/useFlag';
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem';
import AttachmentMenuBotItem from './AttachmentMenuBotItem';
import './AttachMenu.scss';
export type OwnProps = {
chatId: string;
isButtonVisible: boolean;
canAttachMedia: boolean;
canAttachPolls: boolean;
isScheduled?: boolean;
isPrivateChat?: boolean;
attachMenuBots: GlobalState['attachMenu']['bots'];
onFileSelect: (files: File[], isQuick: boolean) => void;
onPollCreate: () => void;
theme: ISettings['theme'];
};
const AttachMenu: FC<OwnProps> = ({
isButtonVisible, canAttachMedia, canAttachPolls, onFileSelect, onPollCreate,
chatId,
isButtonVisible,
canAttachMedia,
canAttachPolls,
attachMenuBots,
isScheduled,
isPrivateChat,
onFileSelect,
onPollCreate,
theme,
}) => {
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu);
const [isAttachmentBotMenuOpen, markAttachmentBotMenuOpen, unmarkAttachmentBotMenuOpen] = useFlag();
useEffect(() => {
if (isAttachMenuOpen) {
markMouseInside();
@ -84,7 +104,7 @@ const AttachMenu: FC<OwnProps> = ({
</ResponsiveHoverButton>
<Menu
id="attach-menu-controls"
isOpen={isAttachMenuOpen}
isOpen={isAttachMenuOpen || isAttachmentBotMenuOpen}
autoClose
positionX="right"
positionY="bottom"
@ -105,15 +125,23 @@ const AttachMenu: FC<OwnProps> = ({
)}
{canAttachMedia && (
<>
<MenuItem icon="photo" onClick={handleQuickSelect}>
{lang('AttachmentMenu.PhotoOrVideo')}
</MenuItem>
<MenuItem icon="photo" onClick={handleQuickSelect}>{lang('AttachmentMenu.PhotoOrVideo')}</MenuItem>
<MenuItem icon="document" onClick={handleDocumentSelect}>{lang('AttachDocument')}</MenuItem>
</>
)}
{canAttachPolls && (
<MenuItem icon="poll" onClick={onPollCreate}>{lang('Poll')}</MenuItem>
)}
{canAttachMedia && !isScheduled && isPrivateChat && Object.values(attachMenuBots).map((bot) => (
<AttachmentMenuBotItem
bot={bot}
chatId={chatId}
theme={theme}
onMenuOpened={markAttachmentBotMenuOpen}
onMenuClosed={unmarkAttachmentBotMenuOpen}
/>
))}
</Menu>
</div>
);

View File

@ -0,0 +1,21 @@
.root {
width: 1.5rem;
height: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
&.compact {
width: 1.25rem;
height: 1.25rem;
}
}
.image {
width: 1.75rem;
position: absolute;
&.compact {
width: 1.5rem;
}
}

View File

@ -0,0 +1,49 @@
import React, { FC, memo, useMemo } from '../../../lib/teact/teact';
import { ISettings } from '../../../types';
import { ApiDocument, ApiMediaFormat } from '../../../api/types';
import { IS_COMPACT_MENU } from '../../../util/environment';
import useMedia from '../../../hooks/useMedia';
import { getDocumentMediaHash } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
import styles from './AttachmentMenuBotIcon.module.scss';
type OwnProps = {
icon: ApiDocument;
theme: ISettings['theme'];
};
const ADDITIONAL_STROKE_WIDTH = '0.5px';
const DARK_THEME_COLOR = 'rgb(170, 170, 170)';
const LIGHT_THEME_COLOR = 'rgb(112, 117, 121)';
const COLOR_REPLACE_PATTERN = /#fff/gi;
const AttachmentMenuBotIcon: FC<OwnProps> = ({
icon, theme,
}) => {
const mediaData = useMedia(getDocumentMediaHash(icon), false, ApiMediaFormat.Text);
const iconSvg = useMemo(() => {
if (!mediaData) return '';
const color = theme === 'dark' ? DARK_THEME_COLOR : LIGHT_THEME_COLOR;
const mediaDataWithReplacedColors = mediaData.replace(COLOR_REPLACE_PATTERN, color);
const doc = new DOMParser().parseFromString(mediaDataWithReplacedColors, 'image/svg+xml');
doc.querySelectorAll('path').forEach((l) => {
l.style.stroke = color;
l.style.strokeWidth = ADDITIONAL_STROKE_WIDTH;
});
return `data:image/svg+xml;utf8,${doc.documentElement.outerHTML}`;
}, [mediaData, theme]);
return (
<i className={buildClassName(styles.root, IS_COMPACT_MENU && styles.compact)}>
<img src={iconSvg} alt="" className={buildClassName(styles.image, IS_COMPACT_MENU && styles.compact)} />
</i>
);
};
export default memo(AttachmentMenuBotIcon);

View File

@ -0,0 +1,100 @@
import React, {
FC, memo, useCallback, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import { IAnchorPosition, ISettings } from '../../../types';
import { ApiAttachMenuBot } from '../../../api/types';
import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang';
import Portal from '../../ui/Portal';
import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem';
import AttachmentMenuBotIcon from './AttachmentMenuBotIcon';
type OwnProps = {
bot: ApiAttachMenuBot;
theme: ISettings['theme'];
chatId: string;
onMenuOpened: VoidFunction;
onMenuClosed: VoidFunction;
};
const AttachmentMenuBotItem: FC<OwnProps> = ({
bot,
theme,
chatId,
onMenuOpened,
onMenuClosed,
}) => {
const { callAttachMenuBot, toggleBotInAttachMenu } = getActions();
const lang = useLang();
const icon = useMemo(() => {
return bot.icons.find(({ name }) => name === 'default_static')?.document;
}, [bot.icons]);
const [isMenuOpen, openMenu, closeMenu] = useFlag();
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
const handleContextMenu = useCallback((e: React.UIEvent) => {
e.preventDefault();
const rect = e.currentTarget.getBoundingClientRect();
setMenuPosition({ x: rect.right, y: rect.bottom });
onMenuOpened();
openMenu();
}, [onMenuOpened, openMenu]);
const handleCloseMenu = useCallback(() => {
closeMenu();
onMenuClosed();
}, [closeMenu, onMenuClosed]);
const handleCloseAnimationEnd = useCallback(() => {
setMenuPosition(undefined);
}, []);
const handleRemoveBot = useCallback(() => {
toggleBotInAttachMenu({
botId: bot.id,
isEnabled: false,
});
}, [bot.id, toggleBotInAttachMenu]);
return (
<MenuItem
key={bot.id}
customIcon={icon && <AttachmentMenuBotIcon icon={icon} theme={theme} />}
icon={!icon ? 'bots' : undefined}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => callAttachMenuBot({
botId: bot.id,
chatId,
})}
onContextMenu={handleContextMenu}
>
{bot.shortName}
{menuPosition && (
<Portal>
<Menu
isOpen={isMenuOpen}
positionX="right"
style={`left: ${menuPosition.x}px;top: ${menuPosition.y}px;`}
className="bot-attach-context-menu"
autoClose
onClose={handleCloseMenu}
onCloseAnimationEnd={handleCloseAnimationEnd}
>
<MenuItem icon="stop" destructive onClick={handleRemoveBot}>{lang('WebApp.RemoveBot')}</MenuItem>
</Menu>
</Portal>
)}
</MenuItem>
);
};
export default memo(AttachmentMenuBotItem);

View File

@ -0,0 +1,61 @@
import React, {
FC, memo, useEffect, useRef,
} from '../../../lib/teact/teact';
import buildClassName from '../../../util/buildClassName';
import Button from '../../ui/Button';
type OwnProps = {
isOpen?: boolean;
onClick: VoidFunction;
text: string;
isDisabled?: boolean;
};
const BotMenuButton: FC<OwnProps> = ({
isOpen,
onClick,
text,
isDisabled,
}) => {
// eslint-disable-next-line no-null/no-null
const textRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const textEl = textRef.current;
if (!textEl) return;
const width = textEl.scrollWidth + 1; // Make width slightly bigger prevent ellipsis in some cases
const composerEl = textEl.closest('.Composer') as HTMLElement;
composerEl.style.setProperty('--bot-menu-text-width', `${width}px`);
}, [isOpen, text]);
useEffect(() => {
const textEl = textRef.current;
if (!textEl) return undefined;
const composerEl = textEl.closest('.Composer') as HTMLElement;
return () => {
composerEl.style.removeProperty('--bot-menu-text-width');
};
}, []);
return (
<Button
className={buildClassName('bot-menu', isOpen && 'open')}
round
color="translucent"
disabled={isDisabled}
onClick={onClick}
ariaLabel="Open bot command keyboard"
>
<i className={buildClassName('bot-menu-icon', 'icon-webapp', isOpen && 'open')} />
<span ref={textRef} className="bot-menu-text">{text}</span>
</Button>
);
};
export default memo(BotMenuButton);

View File

@ -277,6 +277,53 @@
}
}
&.bot-menu {
--icon-width: 1.25rem;
--icon-gap: 0.25rem;
--padding-sides: 0.5rem;
background: var(--color-primary) !important;
height: 2rem;
margin: 0 0.5rem 0.75rem;
color: white !important;
text-transform: none;
display: inline-flex;
padding: 0 var(--padding-sides);
width: 2rem;
max-width: clamp(0px, 12rem, 25vw);
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: 0.25s ease-out width, 0.25s ease-out border-radius;
border-radius: 0.5rem;
justify-content: flex-start;
&.open {
width: calc(var(--bot-menu-text-width) + var(--padding-sides) * 2 + var(--icon-gap) + var(--icon-width));
border-radius: 1rem;
}
@media (max-width: 600px) {
margin-bottom: 0.4375rem;
}
}
.bot-menu-icon {
font-size: 1.25rem;
margin-right: var(--icon-gap);
transition: 0.25s ease-out transform;
transform: translateX(-0.15rem);
&.open {
transform: translateX(0);
}
}
.bot-menu-text {
font-size: 0.875rem;
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
&.bot-commands {
color: var(--color-primary) !important;

View File

@ -18,11 +18,15 @@ import {
ApiUser,
MAIN_THREAD_ID,
ApiBotCommand,
ApiBotMenuButton,
} from '../../../api/types';
import { InlineBotSettings } from '../../../types';
import { InlineBotSettings, ISettings } from '../../../types';
import {
BASE_EMOJI_KEYWORD_LANG, EDITABLE_INPUT_ID, REPLIES_USER_ID, SEND_MESSAGE_ACTION_INTERVAL,
BASE_EMOJI_KEYWORD_LANG,
EDITABLE_INPUT_ID,
REPLIES_USER_ID,
SEND_MESSAGE_ACTION_INTERVAL,
EDITABLE_INPUT_CSS_SELECTOR,
} from '../../../config';
import { IS_VOICE_RECORDING_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, IS_IOS } from '../../../util/environment';
@ -43,6 +47,7 @@ import {
selectEditingScheduledDraft,
selectEditingDraft,
selectRequestedText,
selectTheme,
} from '../../../global/selectors';
import {
getAllowedAttachmentOptions,
@ -105,6 +110,7 @@ import PollModal from './PollModal.async';
import DropArea, { DropAreaState } from './DropArea.async';
import WebPagePreview from './WebPagePreview';
import SendAsMenu from './SendAsMenu.async';
import BotMenuButton from './BotMenuButton';
import './Composer.scss';
@ -147,12 +153,16 @@ type StateProps =
isInlineBotLoading: boolean;
inlineBots?: Record<string, false | InlineBotSettings>;
botCommands?: ApiBotCommand[] | false;
botMenuButton?: ApiBotMenuButton;
chatBotCommands?: ApiBotCommand[];
sendAsUser?: ApiUser;
sendAsChat?: ApiChat;
sendAsId?: string;
editingDraft?: ApiFormattedText;
requestedText?: string;
attachMenuBots: GlobalState['attachMenu']['bots'];
isPrivateChat?: boolean;
theme: ISettings['theme'];
}
& Pick<GlobalState, 'connectionState'>;
@ -221,6 +231,10 @@ const Composer: FC<OwnProps & StateProps> = ({
sendAsId,
editingDraft,
requestedText,
botMenuButton,
attachMenuBots,
isPrivateChat,
theme,
}) => {
const {
sendMessage,
@ -238,6 +252,7 @@ const Composer: FC<OwnProps & StateProps> = ({
loadSendAs,
loadFullChat,
resetOpenChatWithText,
callAttachMenuBot,
} = getActions();
const lang = useLang();
@ -616,6 +631,13 @@ const Composer: FC<OwnProps & StateProps> = ({
resetComposer, stopRecordingVoice, showDialog, slowMode, isAdmin, sendMessage, forwardMessages, lang, htmlRef,
]);
const handleClickBotMenu = useCallback(() => {
if (botMenuButton?.type !== 'webApp') return;
callAttachMenuBot({
botId: chatId, chatId, isFromBotMenu: true, url: botMenuButton.url,
});
}, [botMenuButton, callAttachMenuBot, chatId]);
const handleActivateBotCommandMenu = useCallback(() => {
closeSymbolMenu();
openBotCommandMenu();
@ -927,6 +949,8 @@ const Composer: FC<OwnProps & StateProps> = ({
: mainButtonState === MainButtonState.Schedule ? handleSendScheduled
: handleSend;
const isBotMenuButtonCommands = botMenuButton && botMenuButton?.type === 'commands';
return (
<div className={className}>
{canAttachMedia && isReady && (
@ -1016,7 +1040,17 @@ const Composer: FC<OwnProps & StateProps> = ({
disabled={!canAttachEmbedLinks}
/>
<div className="message-input-wrapper">
{isChatWithBot && botCommands !== false && !activeVoiceRecording && !editingMessage && (
{isChatWithBot && botMenuButton && botMenuButton.type === 'webApp' && !editingMessage
&& (
<BotMenuButton
isOpen={!html && !activeVoiceRecording}
onClick={handleClickBotMenu}
text={botMenuButton.text}
isDisabled={Boolean(activeVoiceRecording)}
/>
)}
{isChatWithBot && isBotMenuButtonCommands && botCommands !== false && !activeVoiceRecording
&& !editingMessage && (
<ResponsiveHoverButton
className={buildClassName('bot-commands', isBotCommandMenuOpen && 'activated')}
round
@ -1129,11 +1163,16 @@ const Composer: FC<OwnProps & StateProps> = ({
addRecentEmoji={addRecentEmoji}
/>
<AttachMenu
chatId={chatId}
isButtonVisible={!activeVoiceRecording && !editingMessage}
canAttachMedia={canAttachMedia}
canAttachPolls={canAttachPolls}
onFileSelect={handleFileSelect}
onPollCreate={openPollModal}
isScheduled={shouldSchedule}
isPrivateChat={isPrivateChat}
attachMenuBots={attachMenuBots}
theme={theme}
/>
{botKeyboardMessageId && (
<BotKeyboardMenu
@ -1215,6 +1254,7 @@ export default memo(withGlobal<OwnProps>(
const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined;
const isChatWithBot = Boolean(chatBot);
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
const isPrivateChat = Boolean(selectUser(global, chatId));
const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId);
const scheduledIds = selectScheduledIds(global, chatId);
const { language, shouldSuggestStickers } = global.settings.byKey;
@ -1242,6 +1282,7 @@ export default memo(withGlobal<OwnProps>(
chat,
isChatWithBot,
isChatWithSelf,
isPrivateChat,
canScheduleUntilOnline: selectCanScheduleUntilOnline(global, chatId),
isChannel: chat ? isChatChannel(chat) : undefined,
isRightColumnShown: selectIsRightColumnShown(global),
@ -1268,13 +1309,16 @@ export default memo(withGlobal<OwnProps>(
emojiKeywords: emojiKeywords?.keywords,
inlineBots: global.inlineBots.byUsername,
isInlineBotLoading: global.inlineBots.isLoading,
chatBotCommands: chat?.fullInfo?.botCommands,
botCommands: chatBot?.fullInfo ? (chatBot.fullInfo.botCommands || false) : undefined,
chatBotCommands: chat?.fullInfo && chat.fullInfo.botCommands,
botCommands: chatBot?.fullInfo ? (chatBot.fullInfo.botInfo?.commands || false) : undefined,
botMenuButton: chatBot?.fullInfo?.botInfo?.menuButton,
sendAsUser,
sendAsChat,
sendAsId,
editingDraft,
requestedText,
attachMenuBots: global.attachMenu.bots,
theme: selectTheme(global),
};
},
)(Composer));

View File

@ -96,6 +96,8 @@
}
}
// TODO Remove this monster with context menu refactor
.Button.bot-menu:not(.open) ~ &,
.Button.bot-commands ~ &,
.Button.send-as-button ~ & {
.is-pointer-env & > .backdrop {
@ -104,6 +106,12 @@
}
}
.Button.bot-menu.open ~ & {
.is-pointer-env & > .backdrop {
left: calc(var(--bot-menu-text-width, 0) + 3rem);
}
}
.bubble {
width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar
padding: 0;

View File

@ -1,6 +1,7 @@
.InlineButtons {
display: flex;
flex-direction: column;
max-width: var(--max-width);
.row {
display: flex;
@ -63,6 +64,12 @@
}
}
.inline-button-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.row:first-of-type .Button {
margin-top: 0.25rem !important;
}

View File

@ -29,10 +29,11 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onClick({ messageId: message.id, button })}
>
{renderText(lang(button.text))}
<span className="inline-button-text">{renderText(lang(button.text))}</span>
{['buy', 'receipt'].includes(button.type) && <i className="icon-card" />}
{button.type === 'url' && !RE_TME_LINK.test(button.url) && <i className="icon-arrow-right" />}
{button.type === 'switchBotInline' && <i className="icon-share-filled" />}
{['webView', 'simpleWebView'].includes(button.type) && <i className="icon-webapp" />}
</Button>
))}
</div>

View File

@ -53,6 +53,7 @@ import {
selectIsMessageProtected,
selectLocalAnimatedEmojiEffect,
selectDefaultReaction,
selectReplySender,
} from '../../../global/selectors';
import {
getMessageContent,
@ -992,7 +993,7 @@ export default memo(withGlobal<OwnProps>(
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup,
} = ownProps;
const {
id, chatId, viaBotId, replyToChatId, replyToMessageId, isOutgoing, threadInfo,
id, chatId, viaBotId, replyToChatId, replyToMessageId, isOutgoing, threadInfo, forwardInfo,
} = message;
const chat = selectChat(global, chatId);
@ -1014,7 +1015,7 @@ export default memo(withGlobal<OwnProps>(
const replyMessage = replyToMessageId && !shouldHideReply
? selectChatMessage(global, isRepliesChat && replyToChatId ? replyToChatId : chatId, replyToMessageId)
: undefined;
const replyMessageSender = replyMessage && selectSender(global, replyMessage);
const replyMessageSender = replyMessage && selectReplySender(global, replyMessage, Boolean(forwardInfo));
const uploadProgress = selectUploadProgress(global, message);
const isFocused = messageListType === 'thread' && (

View File

@ -37,7 +37,9 @@ export type OwnProps = {
faded?: boolean;
tabIndex?: number;
isRtl?: boolean;
withClickPropagation?: boolean;
noPreventDefault?: boolean;
shouldStopPropagation?: boolean;
style?: string;
onClick?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
onContextMenu?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement>) => void;
@ -79,7 +81,9 @@ const Button: FC<OwnProps> = ({
faded,
tabIndex,
isRtl,
withClickPropagation,
noPreventDefault,
shouldStopPropagation,
style,
}) => {
// eslint-disable-next-line no-null/no-null
let elementRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null);
@ -111,18 +115,21 @@ const Button: FC<OwnProps> = ({
onClick(e);
}
if (shouldStopPropagation) e.stopPropagation();
setIsClicked(true);
setTimeout(() => {
setIsClicked(false);
}, CLICKED_TIMEOUT);
}, [disabled, onClick]);
}, [disabled, onClick, shouldStopPropagation]);
const handleMouseDown = useCallback((e: ReactMouseEvent<HTMLButtonElement>) => {
if (!withClickPropagation) e.preventDefault();
if (!noPreventDefault) e.preventDefault();
if (!disabled && onMouseDown) {
onMouseDown(e);
}
}, [onMouseDown, disabled, withClickPropagation]);
}, [disabled, noPreventDefault, onMouseDown]);
if (href) {
return (
@ -137,6 +144,7 @@ const Button: FC<OwnProps> = ({
dir={isRtl ? 'rtl' : undefined}
aria-label={ariaLabel}
aria-controls={ariaControls}
style={style}
>
{children}
{!disabled && ripple && (
@ -164,7 +172,7 @@ const Button: FC<OwnProps> = ({
title={ariaLabel}
tabIndex={tabIndex}
dir={isRtl ? 'rtl' : undefined}
style={backgroundImage ? `background-image: url(${backgroundImage})` : undefined}
style={backgroundImage ? `background-image: url(${backgroundImage})` : style}
>
{isLoading ? (
<div>

View File

@ -67,7 +67,7 @@
}
}
body.has-open-dialog &:not(.CustomSendMenu) .bubble {
body.has-open-dialog &:not(.CustomSendMenu):not(.web-app-more-menu) .bubble {
transition: none !important;
}

View File

@ -10,9 +10,11 @@ type OnClickHandler = (e: React.SyntheticEvent<HTMLDivElement | HTMLAnchorElemen
type OwnProps = {
icon?: string;
customIcon?: React.ReactNode;
className?: string;
children: React.ReactNode;
onClick?: OnClickHandler;
onContextMenu?: (e: React.UIEvent) => void;
href?: string;
download?: string;
disabled?: boolean;
@ -23,6 +25,7 @@ type OwnProps = {
const MenuItem: FC<OwnProps> = (props) => {
const {
icon,
customIcon,
className,
children,
onClick,
@ -31,6 +34,7 @@ const MenuItem: FC<OwnProps> = (props) => {
disabled,
destructive,
ariaLabel,
onContextMenu,
} = props;
const lang = useLang();
@ -70,9 +74,10 @@ const MenuItem: FC<OwnProps> = (props) => {
const content = (
<>
{icon && (
{!customIcon && icon && (
<i className={`icon-${icon}`} data-char={icon.startsWith('char-') ? icon.replace('char-', '') : undefined} />
)}
{customIcon}
{children}
</>
);
@ -103,6 +108,7 @@ const MenuItem: FC<OwnProps> = (props) => {
className={fullClassName}
onClick={handleClick}
onKeyDown={handleKeyDown}
onContextMenu={onContextMenu}
aria-label={ariaLabel}
title={ariaLabel}
dir={lang.isRtl ? 'rtl' : undefined}

View File

@ -35,7 +35,7 @@ export const MEDIA_PROGRESSIVE_CACHE_DISABLED = false;
export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive';
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
export const LANG_CACHE_NAME = 'tt-lang-packs-v8';
export const LANG_CACHE_NAME = 'tt-lang-packs-v9';
export const ASSET_CACHE_NAME = 'tt-assets';
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];

View File

@ -11,13 +11,14 @@ import {
import { callApi } from '../../../api/gramjs';
import {
selectChat, selectChatBot, selectChatMessage, selectCurrentChat, selectCurrentMessageList,
selectReplyingToId, selectSendAs, selectUser,
selectIsTrustedBot, selectReplyingToId, selectSendAs, selectUser,
} from '../../selectors';
import { addChats, addUsers, removeBlockedContact } from '../../reducers';
import { buildCollectionByKey } from '../../../util/iteratees';
import { debounce } from '../../../util/schedulers';
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
import { getServerTime } from '../../../util/serverTime';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import PopupManager from '../../../util/PopupManager';
const GAMEE_URL = 'https://prizes.gamee.com/';
@ -107,6 +108,51 @@ addActionHandler('clickBotInlineButton', (global, actions, payload) => {
actions.openChatWithInfo({ id: userId });
break;
}
case 'simpleWebView': {
const { url } = button;
const { chatId } = selectCurrentMessageList(global) || {};
if (!chatId) {
return;
}
const bot = selectChatBot(global, chatId);
if (!bot) {
return;
}
const theme = extractCurrentThemeParams();
actions.requestSimpleWebView({
url, bot, theme, buttonText: button.text,
});
break;
}
case 'webView': {
const { url } = button;
const chat = selectCurrentChat(global);
if (!chat) {
return;
}
const message = selectChatMessage(global, chat.id, messageId);
if (!message) {
return;
}
if (!message.viaBotId && !message.senderId) {
return;
}
const bot = selectChatBot(global, message.viaBotId! || message.senderId!);
if (!bot) {
return;
}
const theme = extractCurrentThemeParams();
actions.requestWebView({
url,
bot,
peer: chat,
theme,
buttonText: button.text,
});
break;
}
}
});
@ -322,6 +368,252 @@ addActionHandler('startBot', async (global, actions, payload) => {
});
});
addActionHandler('requestSimpleWebView', async (global, actions, payload) => {
const {
url, bot, theme, buttonText,
} = payload;
if (!selectIsTrustedBot(global, bot)) {
setGlobal({
...global,
botTrustRequest: {
bot,
type: 'webApp',
onConfirm: {
action: 'requestSimpleWebView',
payload,
},
},
});
return;
}
const webViewUrl = await callApi('requestSimpleWebView', { url, bot, theme });
if (!webViewUrl) {
return;
}
global = getGlobal();
setGlobal({
...global,
webApp: {
url: webViewUrl,
bot,
buttonText,
},
});
});
addActionHandler('requestWebView', async (global, actions, payload) => {
const {
url, bot, peer, theme, isSilent, buttonText, isFromBotMenu, startParam,
} = payload;
if (!selectIsTrustedBot(global, bot)) {
setGlobal({
...global,
botTrustRequest: {
bot,
type: 'webApp',
onConfirm: {
action: 'requestWebView',
payload,
},
},
});
return;
}
const currentMessageList = selectCurrentMessageList(global);
if (!currentMessageList) {
return;
}
const { chatId, threadId } = currentMessageList;
const reply = chatId && selectReplyingToId(global, chatId, threadId);
const result = await callApi('requestWebView', {
url,
bot,
peer,
theme,
isSilent,
replyToMessageId: reply || undefined,
isFromBotMenu,
startParam,
});
if (!result) {
return;
}
const { url: webViewUrl, queryId } = result;
global = getGlobal();
setGlobal({
...global,
webApp: {
url: webViewUrl,
bot,
queryId,
buttonText,
},
});
});
addActionHandler('prolongWebView', (global, actions, payload) => {
const {
bot, peer, isSilent, replyToMessageId, queryId,
} = payload;
const result = callApi('prolongWebView', {
bot,
peer,
isSilent,
replyToMessageId,
queryId,
});
if (!result) {
actions.closeWebApp();
}
});
addActionHandler('sendWebViewData', (global, actions, payload) => {
const {
bot, data, buttonText,
} = payload;
callApi('sendWebViewData', {
bot,
data,
buttonText,
});
});
addActionHandler('closeWebApp', (global) => {
return {
...global,
webApp: undefined,
};
});
addActionHandler('cancelBotTrustRequest', (global) => {
return {
...global,
botTrustRequest: undefined,
};
});
addActionHandler('markBotTrusted', (global, actions, payload) => {
const { botId } = payload;
const { trustedBotIds } = global;
const newTrustedBotIds = new Set(trustedBotIds);
newTrustedBotIds.add(botId);
setGlobal({
...global,
botTrustRequest: undefined,
trustedBotIds: Array.from(newTrustedBotIds),
});
if (global.botTrustRequest?.onConfirm) {
const { action, payload: callbackPayload } = global.botTrustRequest.onConfirm;
actions[action](callbackPayload);
}
});
addActionHandler('loadAttachMenuBots', async (global, actions, payload) => {
const { hash } = payload || {};
await loadAttachMenuBots(hash);
});
addActionHandler('toggleBotInAttachMenu', async (global, actions, payload) => {
const { botId, isEnabled } = payload;
const bot = selectUser(global, botId);
if (!bot) return;
await toggleBotInAttachMenu(bot, isEnabled);
});
async function toggleBotInAttachMenu(bot: ApiUser, isEnabled: boolean) {
await callApi('toggleBotInAttachMenu', { bot, isEnabled });
await loadAttachMenuBots();
}
async function loadAttachMenuBots(hash?: string) {
const result = await callApi('loadAttachMenuBots', { hash });
if (!result) {
return;
}
const global = getGlobal();
setGlobal({
...global,
attachMenu: {
hash: result.hash,
bots: result.bots,
},
});
}
addActionHandler('callAttachMenuBot', (global, actions, payload) => {
const {
chatId, botId, isFromBotMenu, url, startParam,
} = payload;
const chat = selectChat(global, chatId);
const bot = selectChatBot(global, botId);
if (!chat || !bot) {
return undefined;
}
const { attachMenu: { bots } } = global;
if (!isFromBotMenu && !bots[botId]) {
return {
...global,
botAttachRequest: {
bot,
chatId,
startParam,
},
};
}
const theme = extractCurrentThemeParams();
actions.requestWebView({
url,
peer: chat,
bot,
theme,
buttonText: '',
isFromBotMenu,
startParam,
});
return undefined;
});
addActionHandler('confirmBotAttachRequest', async (global, actions) => {
const { botAttachRequest } = global;
if (!botAttachRequest) return;
const { bot, chatId, startParam } = botAttachRequest;
setGlobal({
...global,
botAttachRequest: undefined,
});
await toggleBotInAttachMenu(bot, true);
actions.callAttachMenuBot({ chatId, botId: bot.id, startParam });
});
addActionHandler('closeBotAttachRequestModal', (global) => {
return {
...global,
botAttachRequest: undefined,
};
});
async function searchInlineBot({
username,
inlineBotData,

View File

@ -34,7 +34,7 @@ import {
import { buildCollectionByKey, omit } from '../../../util/iteratees';
import { debounce, pause, throttle } from '../../../util/schedulers';
import {
isChatSummaryOnly, isChatArchived, isChatBasicGroup,
isChatSummaryOnly, isChatArchived, isChatBasicGroup, isUserBot,
} from '../../helpers';
import { processDeepLink } from '../../../util/deeplink';
import { updateGroupCall } from '../../reducers/calls';
@ -509,7 +509,7 @@ addActionHandler('openChatByInvite', async (global, actions, payload) => {
});
addActionHandler('openChatByPhoneNumber', async (global, actions, payload) => {
const { phoneNumber } = payload!;
const { phoneNumber, startAttach, attach } = payload!;
// Open temporary empty chat to make the click response feel faster
actions.openChat({ id: TMP_CHAT_ID });
@ -524,6 +524,10 @@ addActionHandler('openChatByPhoneNumber', async (global, actions, payload) => {
}
actions.openChat({ id: chat.id });
if (attach) {
openAttachMenuFromLink(actions, chat.id, attach, startAttach);
}
});
addActionHandler('openTelegramLink', (global, actions, payload) => {
@ -542,8 +546,14 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
hash = part2;
}
const startAttach = params.hasOwnProperty('startattach') && !params.startattach ? true : params.startattach;
if (part1.match(/^\+([0-9]+)(\?|$)/)) {
actions.openChatByPhoneNumber({ phoneNumber: part1.substr(1, part1.length - 1) });
actions.openChatByPhoneNumber({
phoneNumber: part1.substr(1, part1.length - 1),
startAttach,
attach: params.attach,
});
return;
}
@ -590,6 +600,8 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
messageId: messageId || Number(chatOrChannelPostId),
commentId,
startParam: params.start,
startAttach,
attach: params.attach,
});
}
});
@ -606,7 +618,7 @@ addActionHandler('acceptInviteConfirmation', async (global, actions, payload) =>
addActionHandler('openChatByUsername', async (global, actions, payload) => {
const {
username, messageId, commentId, startParam,
username, messageId, commentId, startParam, startAttach, attach,
} = payload!;
const chat = selectCurrentChat(global);
@ -616,7 +628,7 @@ addActionHandler('openChatByUsername', async (global, actions, payload) => {
actions.focusMessage({ chatId: chat.id, messageId });
return;
}
await openChatByUsername(actions, username, messageId, startParam);
await openChatByUsername(actions, username, messageId, startParam, startAttach, attach);
return;
}
@ -1330,7 +1342,35 @@ async function openChatByUsername(
username: string,
channelPostId?: number,
startParam?: string,
startAttach?: string | boolean,
attach?: string,
) {
// Attach in the current chat
if (startAttach && !attach) {
const chat = await fetchChatByUsername(username);
if (!chat) return;
const global = getGlobal();
const user = selectUser(global, chat.id);
if (!user) return;
const isBot = isUserBot(user);
if (!isBot || !user.isAttachMenuBot) {
actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
return;
}
const currentChat = selectCurrentChat(global);
if (!currentChat) return;
actions.callAttachMenuBot({
botId: user.id,
chatId: currentChat.id,
...(typeof startAttach === 'string' && { startParam: startAttach }),
});
return;
}
// Open temporary empty chat to make the click response feel faster
actions.openChat({ id: TMP_CHAT_ID });
@ -1350,6 +1390,29 @@ async function openChatByUsername(
if (startParam) {
actions.startBot({ botId: chat.id, param: startParam });
}
if (attach) {
openAttachMenuFromLink(actions, chat.id, attach, startAttach);
}
}
async function openAttachMenuFromLink(
actions: GlobalActions,
chatId: string, attach: string, startAttach?: string | boolean,
) {
const botChat = await fetchChatByUsername(attach);
if (!botChat) return;
const botUser = selectUser(getGlobal(), botChat.id);
if (!botUser || !botUser.isAttachMenuBot) {
actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
return;
}
actions.callAttachMenuBot({
botId: botUser.id,
chatId,
...(typeof startAttach === 'string' && { startParam: startAttach }),
});
}
async function openCommentsByUsername(

View File

@ -37,6 +37,13 @@ addActionHandler('apiUpdate', (global, actions, update) => {
global = setPaymentStep(global, PaymentStep.ConfirmPayment);
setGlobal(global);
break;
case 'updateWebViewResultSent':
if (global.webApp?.queryId === update.queryId) {
actions.setReplyingToId({ messageId: undefined });
actions.closeWebApp();
}
break;
}
return undefined;

View File

@ -4,6 +4,7 @@ import { ApiUserStatus } from '../../../api/types';
import { deleteContact, replaceUserStatuses, updateUser } from '../../reducers';
import { throttle } from '../../../util/schedulers';
import { selectUser } from '../../selectors';
const STATUS_UPDATE_THROTTLE = 3000;
@ -57,6 +58,25 @@ addActionHandler('apiUpdate', (global, actions, update) => {
},
});
}
case 'updateBotMenuButton': {
const { botId, button } = update;
const targetUser = selectUser(global, botId);
if (!targetUser?.fullInfo?.botInfo) {
return undefined;
}
return updateUser(global, botId, {
fullInfo: {
...targetUser.fullInfo,
botInfo: {
...targetUser.fullInfo.botInfo,
menuButton: button,
},
},
});
}
}
return undefined;

View File

@ -1,10 +1,12 @@
import { addActionHandler } from '../../index';
import { addActionHandler, setGlobal } from '../../index';
import { ApiError } from '../../../api/types';
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment';
import getReadableErrorText from '../../../util/getReadableErrorText';
import { selectCurrentMessageList } from '../../selectors';
import {
selectChatBot, selectChatMessage, selectCurrentMessageList, selectIsTrustedBot,
} from '../../selectors';
import generateIdFor from '../../../util/generateIdFor';
const MAX_STORED_EMOJIS = 18; // Represents two rows of recent emojis
@ -264,14 +266,37 @@ addActionHandler('closeHistoryCalendar', (global) => {
addActionHandler('openGame', (global, actions, payload) => {
const { url, chatId, messageId } = payload;
return {
const message = selectChatMessage(global, chatId, messageId);
if (!message) return;
const botId = message.viaBotId || message.senderId;
const bot = botId && selectChatBot(global, botId);
if (!bot) return;
if (!selectIsTrustedBot(global, bot)) {
setGlobal({
...global,
botTrustRequest: {
bot,
type: 'game',
onConfirm: {
action: 'openGame',
payload,
},
},
});
return;
}
setGlobal({
...global,
openedGame: {
url,
chatId,
messageId,
},
};
});
});
addActionHandler('closeGame', (global) => {

View File

@ -225,6 +225,16 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
isOpen: false,
};
}
if (!cached.attachMenu) {
cached.attachMenu = {
bots: {},
};
}
if (!cached.trustedBotIds) {
cached.trustedBotIds = [];
}
}
function updateCache() {
@ -277,6 +287,7 @@ function updateCache() {
groupCalls: reduceGroupCalls(global),
availableReactions: reduceAvailableReactions(global),
isCallPanelVisible: undefined,
trustedBotIds: global.trustedBotIds,
};
const json = JSON.stringify(reducedGlobal);

View File

@ -1,5 +1,14 @@
import {
ApiAudio, ApiMediaFormat, ApiMessage, ApiMessageSearchType, ApiPhoto, ApiVideo, ApiDimensions, ApiLocation, ApiGame,
ApiAudio,
ApiMediaFormat,
ApiMessage,
ApiMessageSearchType,
ApiPhoto,
ApiVideo,
ApiDimensions,
ApiLocation,
ApiGame,
ApiDocument,
} from '../../api/types';
import { IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED, IS_SAFARI } from '../../util/environment';
@ -127,6 +136,10 @@ export function getMessageMediaThumbDataUri(message: ApiMessage) {
return getMessageMediaThumbnail(message)?.dataUri;
}
export function getDocumentMediaHash(document: ApiDocument) {
return `document${document.id}`;
}
export function buildStaticMapHash(
geo: ApiLocation['geo'],
width: number,

View File

@ -227,3 +227,9 @@ export function isGeoLiveExpired(message: ApiMessage, timestamp = Date.now() / 1
if (location?.type !== 'geoLive') return false;
return (timestamp - (message.date || 0) >= location.period);
}
export function getMessageSingleInlineButton(message: ApiMessage) {
return message.inlineButtons?.length === 1
&& message.inlineButtons[0].length === 1
&& message.inlineButtons[0][0];
}

View File

@ -206,4 +206,10 @@ export const INITIAL_STATE: GlobalState = {
pollModal: {
isOpen: false,
},
trustedBotIds: [],
attachMenu: {
bots: {},
},
};

View File

@ -1,4 +1,4 @@
import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
import { ApiChat, ApiUser, MAIN_THREAD_ID } from '../../api/types';
import { GlobalState } from '../types';
import {
@ -68,6 +68,10 @@ export function selectChatBot(global: GlobalState, chatId: string) {
return user;
}
export function selectIsTrustedBot(global: GlobalState, bot: ApiUser) {
return bot.isVerified || global.trustedBotIds.includes(bot.id);
}
export function selectIsChatBotNotStarted(global: GlobalState, chatId: string) {
const chat = selectChat(global, chatId);
const bot = selectChatBot(global, chatId);

View File

@ -335,6 +335,23 @@ export function selectSender(global: GlobalState, message: ApiMessage): ApiUser
return isUserId(senderId) ? selectUser(global, senderId) : selectChat(global, senderId);
}
export function selectReplySender(global: GlobalState, message: ApiMessage, isForwarded = false) {
if (isForwarded) {
const { senderUserId, hiddenUserName } = message.forwardInfo || {};
if (senderUserId) {
return isUserId(senderUserId) ? selectUser(global, senderUserId) : selectChat(global, senderUserId);
}
if (hiddenUserName) return undefined;
}
const { senderId } = message;
if (!senderId) {
return undefined;
}
return isUserId(senderId) ? selectUser(global, senderId) : selectChat(global, senderId);
}
export function selectForwardedSender(global: GlobalState, message: ApiMessage): ApiUser | ApiChat | undefined {
const { forwardInfo } = message;
if (!forwardInfo) {

View File

@ -33,6 +33,8 @@ import {
ApiReportReason,
ApiPhoto,
ApiKeyboardButton,
ApiThemeParameters,
ApiAttachMenuBot,
ApiPhoneCall,
} from '../api/types';
import {
@ -543,6 +545,33 @@ export type GlobalState = {
isOpen: boolean;
isQuiz?: boolean;
};
webApp?: {
url: string;
bot: ApiUser;
buttonText: string;
queryId?: string;
};
trustedBotIds: string[];
botTrustRequest?: {
bot: ApiUser;
type: 'game' | 'webApp';
onConfirm?: {
action: keyof GlobalActions;
payload: any; // TODO add TS support
};
};
botAttachRequest?: {
bot: ApiUser;
chatId: string;
startParam?: string;
};
attachMenu: {
hash?: string;
bots: Record<string, ApiAttachMenuBot>;
};
};
export type CallSound = (
@ -682,7 +711,6 @@ export interface ActionPayloads {
resetSwitchBotInline: {};
// Misc
openGame: {
url: string;
chatId: string;
@ -690,6 +718,64 @@ export interface ActionPayloads {
};
closeGame: {};
requestWebView: {
url?: string;
bot: ApiUser;
peer: ApiChat | ApiUser;
theme?: ApiThemeParameters;
isSilent?: boolean;
buttonText: string;
isFromBotMenu?: boolean;
startParam?: string;
};
prolongWebView: {
bot: ApiUser;
peer: ApiChat | ApiUser;
queryId: string;
isSilent?: boolean;
replyToMessageId?: number;
};
requestSimpleWebView: {
url: string;
bot: ApiUser;
buttonText: string;
theme?: ApiThemeParameters;
};
closeWebApp: {};
cancelBotTrustRequest: {};
markBotTrusted: {
botId: string;
};
closeBotAttachRequestModal: never;
confirmBotAttachRequest: never;
sendWebViewData: {
bot: ApiUser;
data: string;
buttonText: string;
};
loadAttachMenuBots: {
hash?: string;
};
toggleBotInAttachMenu: {
botId: string;
isEnabled: boolean;
};
callAttachMenuBot: {
chatId: string;
botId: string;
isFromBotMenu?: boolean;
url?: string;
startParam?: string;
};
// Misc
openPollModal: {
isQuiz?: boolean;
};

View File

@ -1,6 +1,6 @@
const api = require('./api');
const LAYER = 139;
const LAYER = 140;
const tlobjects = {};
for (const tl of Object.values(api)) {

View File

@ -70,7 +70,7 @@ namespace Api {
export type TypeChatPhoto = ChatPhotoEmpty | ChatPhoto;
export type TypeMessage = MessageEmpty | Message | MessageService;
export type TypeMessageMedia = MessageMediaEmpty | MessageMediaPhoto | MessageMediaGeo | MessageMediaContact | MessageMediaUnsupported | MessageMediaDocument | MessageMediaWebPage | MessageMediaVenue | MessageMediaGame | MessageMediaInvoice | MessageMediaGeoLive | MessageMediaPoll | MessageMediaDice;
export type TypeMessageAction = MessageActionEmpty | MessageActionChatCreate | MessageActionChatEditTitle | MessageActionChatEditPhoto | MessageActionChatDeletePhoto | MessageActionChatAddUser | MessageActionChatDeleteUser | MessageActionChatJoinedByLink | MessageActionChannelCreate | MessageActionChatMigrateTo | MessageActionChannelMigrateFrom | MessageActionPinMessage | MessageActionHistoryClear | MessageActionGameScore | MessageActionPaymentSentMe | MessageActionPaymentSent | MessageActionPhoneCall | MessageActionScreenshotTaken | MessageActionCustomAction | MessageActionBotAllowed | MessageActionSecureValuesSentMe | MessageActionSecureValuesSent | MessageActionContactSignUp | MessageActionGeoProximityReached | MessageActionGroupCall | MessageActionInviteToGroupCall | MessageActionSetMessagesTTL | MessageActionGroupCallScheduled | MessageActionSetChatTheme | MessageActionChatJoinedByRequest;
export type TypeMessageAction = MessageActionEmpty | MessageActionChatCreate | MessageActionChatEditTitle | MessageActionChatEditPhoto | MessageActionChatDeletePhoto | MessageActionChatAddUser | MessageActionChatDeleteUser | MessageActionChatJoinedByLink | MessageActionChannelCreate | MessageActionChatMigrateTo | MessageActionChannelMigrateFrom | MessageActionPinMessage | MessageActionHistoryClear | MessageActionGameScore | MessageActionPaymentSentMe | MessageActionPaymentSent | MessageActionPhoneCall | MessageActionScreenshotTaken | MessageActionCustomAction | MessageActionBotAllowed | MessageActionSecureValuesSentMe | MessageActionSecureValuesSent | MessageActionContactSignUp | MessageActionGeoProximityReached | MessageActionGroupCall | MessageActionInviteToGroupCall | MessageActionSetMessagesTTL | MessageActionGroupCallScheduled | MessageActionSetChatTheme | MessageActionChatJoinedByRequest | MessageActionWebViewDataSentMe | MessageActionWebViewDataSent;
export type TypeDialog = Dialog | DialogFolder;
export type TypePhoto = PhotoEmpty | Photo;
export type TypePhotoSize = PhotoSizeEmpty | PhotoSize | PhotoCachedSize | PhotoStrippedSize | PhotoSizeProgressive | PhotoPathSize;
@ -86,7 +86,7 @@ namespace Api {
export type TypeImportedContact = ImportedContact;
export type TypeContactStatus = ContactStatus;
export type TypeMessagesFilter = InputMessagesFilterEmpty | InputMessagesFilterPhotos | InputMessagesFilterVideo | InputMessagesFilterPhotoVideo | InputMessagesFilterDocument | InputMessagesFilterUrl | InputMessagesFilterGif | InputMessagesFilterVoice | InputMessagesFilterMusic | InputMessagesFilterChatPhotos | InputMessagesFilterPhoneCalls | InputMessagesFilterRoundVoice | InputMessagesFilterRoundVideo | InputMessagesFilterMyMentions | InputMessagesFilterGeo | InputMessagesFilterContacts | InputMessagesFilterPinned;
export type TypeUpdate = UpdateNewMessage | UpdateMessageID | UpdateDeleteMessages | UpdateUserTyping | UpdateChatUserTyping | UpdateChatParticipants | UpdateUserStatus | UpdateUserName | UpdateUserPhoto | UpdateNewEncryptedMessage | UpdateEncryptedChatTyping | UpdateEncryption | UpdateEncryptedMessagesRead | UpdateChatParticipantAdd | UpdateChatParticipantDelete | UpdateDcOptions | UpdateNotifySettings | UpdateServiceNotification | UpdatePrivacy | UpdateUserPhone | UpdateReadHistoryInbox | UpdateReadHistoryOutbox | UpdateWebPage | UpdateReadMessagesContents | UpdateChannelTooLong | UpdateChannel | UpdateNewChannelMessage | UpdateReadChannelInbox | UpdateDeleteChannelMessages | UpdateChannelMessageViews | UpdateChatParticipantAdmin | UpdateNewStickerSet | UpdateStickerSetsOrder | UpdateStickerSets | UpdateSavedGifs | UpdateBotInlineQuery | UpdateBotInlineSend | UpdateEditChannelMessage | UpdateBotCallbackQuery | UpdateEditMessage | UpdateInlineBotCallbackQuery | UpdateReadChannelOutbox | UpdateDraftMessage | UpdateReadFeaturedStickers | UpdateRecentStickers | UpdateConfig | UpdatePtsChanged | UpdateChannelWebPage | UpdateDialogPinned | UpdatePinnedDialogs | UpdateBotWebhookJSON | UpdateBotWebhookJSONQuery | UpdateBotShippingQuery | UpdateBotPrecheckoutQuery | UpdatePhoneCall | UpdateLangPackTooLong | UpdateLangPack | UpdateFavedStickers | UpdateChannelReadMessagesContents | UpdateContactsReset | UpdateChannelAvailableMessages | UpdateDialogUnreadMark | UpdateMessagePoll | UpdateChatDefaultBannedRights | UpdateFolderPeers | UpdatePeerSettings | UpdatePeerLocated | UpdateNewScheduledMessage | UpdateDeleteScheduledMessages | UpdateTheme | UpdateGeoLiveViewed | UpdateLoginToken | UpdateMessagePollVote | UpdateDialogFilter | UpdateDialogFilterOrder | UpdateDialogFilters | UpdatePhoneCallSignalingData | UpdateChannelMessageForwards | UpdateReadChannelDiscussionInbox | UpdateReadChannelDiscussionOutbox | UpdatePeerBlocked | UpdateChannelUserTyping | UpdatePinnedMessages | UpdatePinnedChannelMessages | UpdateChat | UpdateGroupCallParticipants | UpdateGroupCall | UpdatePeerHistoryTTL | UpdateChatParticipant | UpdateChannelParticipant | UpdateBotStopped | UpdateGroupCallConnection | UpdateBotCommands | UpdatePendingJoinRequests | UpdateBotChatInviteRequester | UpdateMessageReactions;
export type TypeUpdate = UpdateNewMessage | UpdateMessageID | UpdateDeleteMessages | UpdateUserTyping | UpdateChatUserTyping | UpdateChatParticipants | UpdateUserStatus | UpdateUserName | UpdateUserPhoto | UpdateNewEncryptedMessage | UpdateEncryptedChatTyping | UpdateEncryption | UpdateEncryptedMessagesRead | UpdateChatParticipantAdd | UpdateChatParticipantDelete | UpdateDcOptions | UpdateNotifySettings | UpdateServiceNotification | UpdatePrivacy | UpdateUserPhone | UpdateReadHistoryInbox | UpdateReadHistoryOutbox | UpdateWebPage | UpdateReadMessagesContents | UpdateChannelTooLong | UpdateChannel | UpdateNewChannelMessage | UpdateReadChannelInbox | UpdateDeleteChannelMessages | UpdateChannelMessageViews | UpdateChatParticipantAdmin | UpdateNewStickerSet | UpdateStickerSetsOrder | UpdateStickerSets | UpdateSavedGifs | UpdateBotInlineQuery | UpdateBotInlineSend | UpdateEditChannelMessage | UpdateBotCallbackQuery | UpdateEditMessage | UpdateInlineBotCallbackQuery | UpdateReadChannelOutbox | UpdateDraftMessage | UpdateReadFeaturedStickers | UpdateRecentStickers | UpdateConfig | UpdatePtsChanged | UpdateChannelWebPage | UpdateDialogPinned | UpdatePinnedDialogs | UpdateBotWebhookJSON | UpdateBotWebhookJSONQuery | UpdateBotShippingQuery | UpdateBotPrecheckoutQuery | UpdatePhoneCall | UpdateLangPackTooLong | UpdateLangPack | UpdateFavedStickers | UpdateChannelReadMessagesContents | UpdateContactsReset | UpdateChannelAvailableMessages | UpdateDialogUnreadMark | UpdateMessagePoll | UpdateChatDefaultBannedRights | UpdateFolderPeers | UpdatePeerSettings | UpdatePeerLocated | UpdateNewScheduledMessage | UpdateDeleteScheduledMessages | UpdateTheme | UpdateGeoLiveViewed | UpdateLoginToken | UpdateMessagePollVote | UpdateDialogFilter | UpdateDialogFilterOrder | UpdateDialogFilters | UpdatePhoneCallSignalingData | UpdateChannelMessageForwards | UpdateReadChannelDiscussionInbox | UpdateReadChannelDiscussionOutbox | UpdatePeerBlocked | UpdateChannelUserTyping | UpdatePinnedMessages | UpdatePinnedChannelMessages | UpdateChat | UpdateGroupCallParticipants | UpdateGroupCall | UpdatePeerHistoryTTL | UpdateChatParticipant | UpdateChannelParticipant | UpdateBotStopped | UpdateGroupCallConnection | UpdateBotCommands | UpdatePendingJoinRequests | UpdateBotChatInviteRequester | UpdateMessageReactions | UpdateAttachMenuBots | UpdateWebViewResultSent | UpdateBotMenuButton | UpdateSavedRingtones;
export type TypeUpdates = UpdatesTooLong | UpdateShortMessage | UpdateShortChatMessage | UpdateShort | UpdatesCombined | Updates | UpdateShortSentMessage;
export type TypeDcOption = DcOption;
export type TypeConfig = Config;
@ -116,7 +116,7 @@ namespace Api {
export type TypeStickerSet = StickerSet;
export type TypeBotCommand = BotCommand;
export type TypeBotInfo = BotInfo;
export type TypeKeyboardButton = KeyboardButton | KeyboardButtonUrl | KeyboardButtonCallback | KeyboardButtonRequestPhone | KeyboardButtonRequestGeoLocation | KeyboardButtonSwitchInline | KeyboardButtonGame | KeyboardButtonBuy | KeyboardButtonUrlAuth | InputKeyboardButtonUrlAuth | KeyboardButtonRequestPoll | InputKeyboardButtonUserProfile | KeyboardButtonUserProfile;
export type TypeKeyboardButton = KeyboardButton | KeyboardButtonUrl | KeyboardButtonCallback | KeyboardButtonRequestPhone | KeyboardButtonRequestGeoLocation | KeyboardButtonSwitchInline | KeyboardButtonGame | KeyboardButtonBuy | KeyboardButtonUrlAuth | InputKeyboardButtonUrlAuth | KeyboardButtonRequestPoll | InputKeyboardButtonUserProfile | KeyboardButtonUserProfile | KeyboardButtonWebView | KeyboardButtonSimpleWebView;
export type TypeKeyboardButtonRow = KeyboardButtonRow;
export type TypeReplyMarkup = ReplyKeyboardHide | ReplyKeyboardForceReply | ReplyKeyboardMarkup | ReplyInlineMarkup;
export type TypeMessageEntity = MessageEntityUnknown | MessageEntityMention | MessageEntityHashtag | MessageEntityBotCommand | MessageEntityUrl | MessageEntityEmail | MessageEntityBold | MessageEntityItalic | MessageEntityCode | MessageEntityPre | MessageEntityTextUrl | MessageEntityMentionName | InputMessageEntityMentionName | MessageEntityPhone | MessageEntityCashtag | MessageEntityUnderline | MessageEntityStrike | MessageEntityBlockquote | MessageEntityBankCard | MessageEntitySpoiler;
@ -270,6 +270,16 @@ namespace Api {
export type TypeAvailableReaction = AvailableReaction;
export type TypeMessagePeerReaction = MessagePeerReaction;
export type TypeGroupCallStreamChannel = GroupCallStreamChannel;
export type TypeAttachMenuBotIconColor = AttachMenuBotIconColor;
export type TypeAttachMenuBotIcon = AttachMenuBotIcon;
export type TypeAttachMenuBot = AttachMenuBot;
export type TypeAttachMenuBots = AttachMenuBotsNotModified | AttachMenuBots;
export type TypeAttachMenuBotsBot = AttachMenuBotsBot;
export type TypeWebViewResult = WebViewResultUrl;
export type TypeSimpleWebViewResult = SimpleWebViewResultUrl;
export type TypeWebViewMessageSent = WebViewMessageSent;
export type TypeBotMenuButton = BotMenuButtonDefault | BotMenuButtonCommands | BotMenuButton;
export type TypeNotificationSound = NotificationSoundDefault | NotificationSoundNone | NotificationSoundLocal | NotificationSoundRingtone;
export type TypeResPQ = ResPQ;
export type TypeP_Q_inner_data = PQInnerData | PQInnerDataDc | PQInnerDataTemp | PQInnerDataTempDc;
export type TypeServer_DH_Params = ServerDHParamsFail | ServerDHParamsOk;
@ -419,6 +429,8 @@ namespace Api {
export type TypeThemes = account.ThemesNotModified | account.Themes;
export type TypeContentSettings = account.ContentSettings;
export type TypeResetPasswordResult = account.ResetPasswordFailedWait | account.ResetPasswordRequestedWait | account.ResetPasswordOk;
export type TypeSavedRingtones = account.SavedRingtonesNotModified | account.SavedRingtones;
export type TypeSavedRingtone = account.SavedRingtone | account.SavedRingtoneConverted;
}
export namespace channels {
@ -882,6 +894,7 @@ namespace Api {
scam?: true;
applyMinPhoto?: true;
fake?: true;
botAttachMenu?: true;
id: long;
accessHash?: long;
firstName?: string;
@ -911,6 +924,7 @@ namespace Api {
scam?: true;
applyMinPhoto?: true;
fake?: true;
botAttachMenu?: true;
id: long;
accessHash?: long;
firstName?: string;
@ -960,7 +974,6 @@ namespace Api {
export class Chat extends VirtualClass<{
// flags: undefined;
creator?: true;
kicked?: true;
left?: true;
deactivated?: true;
callActive?: true;
@ -978,7 +991,6 @@ namespace Api {
}> {
// flags: undefined;
creator?: true;
kicked?: true;
left?: true;
deactivated?: true;
callActive?: true;
@ -1130,6 +1142,8 @@ namespace Api {
hasScheduled?: true;
canViewStats?: true;
blocked?: true;
// flags2: undefined;
canDeleteChannel?: true;
id: long;
about: string;
participantsCount?: int;
@ -1175,6 +1189,8 @@ namespace Api {
hasScheduled?: true;
canViewStats?: true;
blocked?: true;
// flags2: undefined;
canDeleteChannel?: true;
id: long;
about: string;
participantsCount?: int;
@ -1648,6 +1664,18 @@ namespace Api {
emoticon: string;
};
export class MessageActionChatJoinedByRequest extends VirtualClass<void> {};
export class MessageActionWebViewDataSentMe extends VirtualClass<{
text: string;
data: string;
}> {
text: string;
data: string;
};
export class MessageActionWebViewDataSent extends VirtualClass<{
text: string;
}> {
text: string;
};
export class Dialog extends VirtualClass<{
// flags: undefined;
pinned?: true;
@ -1805,26 +1833,30 @@ namespace Api {
showPreviews?: Bool;
silent?: Bool;
muteUntil?: int;
sound?: string;
sound?: Api.TypeNotificationSound;
} | void> {
// flags: undefined;
showPreviews?: Bool;
silent?: Bool;
muteUntil?: int;
sound?: string;
sound?: Api.TypeNotificationSound;
};
export class PeerNotifySettings extends VirtualClass<{
// flags: undefined;
showPreviews?: Bool;
silent?: Bool;
muteUntil?: int;
sound?: string;
iosSound?: Api.TypeNotificationSound;
androidSound?: Api.TypeNotificationSound;
otherSound?: Api.TypeNotificationSound;
} | void> {
// flags: undefined;
showPreviews?: Bool;
silent?: Bool;
muteUntil?: int;
sound?: string;
iosSound?: Api.TypeNotificationSound;
androidSound?: Api.TypeNotificationSound;
otherSound?: Api.TypeNotificationSound;
};
export class PeerSettings extends VirtualClass<{
// flags: undefined;
@ -1921,6 +1953,8 @@ namespace Api {
ttlPeriod?: int;
themeEmoticon?: string;
privateForwardName?: string;
botGroupAdminRights?: Api.TypeChatAdminRights;
botBroadcastAdminRights?: Api.TypeChatAdminRights;
}> {
// flags: undefined;
blocked?: true;
@ -1941,6 +1975,8 @@ namespace Api {
ttlPeriod?: int;
themeEmoticon?: string;
privateForwardName?: string;
botGroupAdminRights?: Api.TypeChatAdminRights;
botBroadcastAdminRights?: Api.TypeChatAdminRights;
};
export class Contact extends VirtualClass<{
userId: long;
@ -2820,6 +2856,20 @@ namespace Api {
msgId: int;
reactions: Api.TypeMessageReactions;
};
export class UpdateAttachMenuBots extends VirtualClass<void> {};
export class UpdateWebViewResultSent extends VirtualClass<{
queryId: long;
}> {
queryId: long;
};
export class UpdateBotMenuButton extends VirtualClass<{
botId: long;
button: Api.TypeBotMenuButton;
}> {
botId: long;
button: Api.TypeBotMenuButton;
};
export class UpdateSavedRingtones extends VirtualClass<void> {};
export class UpdatesTooLong extends VirtualClass<void> {};
export class UpdateShortMessage extends VirtualClass<{
// flags: undefined;
@ -3697,10 +3747,12 @@ namespace Api {
userId: long;
description: string;
commands: Api.TypeBotCommand[];
menuButton: Api.TypeBotMenuButton;
}> {
userId: long;
description: string;
commands: Api.TypeBotCommand[];
menuButton: Api.TypeBotMenuButton;
};
export class KeyboardButton extends VirtualClass<{
text: string;
@ -3807,6 +3859,20 @@ namespace Api {
text: string;
userId: long;
};
export class KeyboardButtonWebView extends VirtualClass<{
text: string;
url: string;
}> {
text: string;
url: string;
};
export class KeyboardButtonSimpleWebView extends VirtualClass<{
text: string;
url: string;
}> {
text: string;
url: string;
};
export class KeyboardButtonRow extends VirtualClass<{
buttons: Api.TypeKeyboardButton[];
}> {
@ -7071,6 +7137,96 @@ namespace Api {
scale: int;
lastTimestampMs: long;
};
export class AttachMenuBotIconColor extends VirtualClass<{
name: string;
color: int;
}> {
name: string;
color: int;
};
export class AttachMenuBotIcon extends VirtualClass<{
// flags: undefined;
name: string;
icon: Api.TypeDocument;
colors?: Api.TypeAttachMenuBotIconColor[];
}> {
// flags: undefined;
name: string;
icon: Api.TypeDocument;
colors?: Api.TypeAttachMenuBotIconColor[];
};
export class AttachMenuBot extends VirtualClass<{
// flags: undefined;
inactive?: true;
botId: long;
shortName: string;
icons: Api.TypeAttachMenuBotIcon[];
}> {
// flags: undefined;
inactive?: true;
botId: long;
shortName: string;
icons: Api.TypeAttachMenuBotIcon[];
};
export class AttachMenuBotsNotModified extends VirtualClass<void> {};
export class AttachMenuBots extends VirtualClass<{
hash: long;
bots: Api.TypeAttachMenuBot[];
users: Api.TypeUser[];
}> {
hash: long;
bots: Api.TypeAttachMenuBot[];
users: Api.TypeUser[];
};
export class AttachMenuBotsBot extends VirtualClass<{
bot: Api.TypeAttachMenuBot;
users: Api.TypeUser[];
}> {
bot: Api.TypeAttachMenuBot;
users: Api.TypeUser[];
};
export class WebViewResultUrl extends VirtualClass<{
queryId: long;
url: string;
}> {
queryId: long;
url: string;
};
export class SimpleWebViewResultUrl extends VirtualClass<{
url: string;
}> {
url: string;
};
export class WebViewMessageSent extends VirtualClass<{
// flags: undefined;
msgId?: Api.TypeInputBotInlineMessageID;
} | void> {
// flags: undefined;
msgId?: Api.TypeInputBotInlineMessageID;
};
export class BotMenuButtonDefault extends VirtualClass<void> {};
export class BotMenuButtonCommands extends VirtualClass<void> {};
export class BotMenuButton extends VirtualClass<{
text: string;
url: string;
}> {
text: string;
url: string;
};
export class NotificationSoundDefault extends VirtualClass<void> {};
export class NotificationSoundNone extends VirtualClass<void> {};
export class NotificationSoundLocal extends VirtualClass<{
title: string;
data: string;
}> {
title: string;
data: string;
};
export class NotificationSoundRingtone extends VirtualClass<{
id: long;
}> {
id: long;
};
export class ResPQ extends VirtualClass<{
nonce: int128;
serverNonce: int128;
@ -8643,6 +8799,20 @@ namespace Api {
untilDate: int;
};
export class ResetPasswordOk extends VirtualClass<void> {};
export class SavedRingtonesNotModified extends VirtualClass<void> {};
export class SavedRingtones extends VirtualClass<{
hash: long;
ringtones: Api.TypeDocument[];
}> {
hash: long;
ringtones: Api.TypeDocument[];
};
export class SavedRingtone extends VirtualClass<void> {};
export class SavedRingtoneConverted extends VirtualClass<{
document: Api.TypeDocument;
}> {
document: Api.TypeDocument;
};
}
export namespace channels {
@ -9673,6 +9843,27 @@ namespace Api {
encryptedRequestsDisabled?: Bool;
callRequestsDisabled?: Bool;
};
export class GetSavedRingtones extends Request<Partial<{
hash: long;
}>, account.TypeSavedRingtones> {
hash: long;
};
export class SaveRingtone extends Request<Partial<{
id: Api.TypeInputDocument;
unsave: Bool;
}>, account.TypeSavedRingtone> {
id: Api.TypeInputDocument;
unsave: Bool;
};
export class UploadRingtone extends Request<Partial<{
file: Api.TypeInputFile;
fileName: string;
mimeType: string;
}>, Api.TypeDocument> {
file: Api.TypeInputFile;
fileName: string;
mimeType: string;
};
}
export namespace users {
@ -11365,6 +11556,88 @@ namespace Api {
filter: Api.TypeMessagesFilter;
limit: int;
};
export class GetAttachMenuBots extends Request<Partial<{
hash: long;
}>, Api.TypeAttachMenuBots> {
hash: long;
};
export class GetAttachMenuBot extends Request<Partial<{
bot: Api.TypeInputUser;
}>, Api.TypeAttachMenuBotsBot> {
bot: Api.TypeInputUser;
};
export class ToggleBotInAttachMenu extends Request<Partial<{
bot: Api.TypeInputUser;
enabled: Bool;
}>, Bool> {
bot: Api.TypeInputUser;
enabled: Bool;
};
export class RequestWebView extends Request<Partial<{
// flags: undefined;
fromBotMenu?: true;
silent?: true;
peer: Api.TypeInputPeer;
bot: Api.TypeInputUser;
url?: string;
startParam?: string;
themeParams?: Api.TypeDataJSON;
replyToMsgId?: int;
}>, Api.TypeWebViewResult> {
// flags: undefined;
fromBotMenu?: true;
silent?: true;
peer: Api.TypeInputPeer;
bot: Api.TypeInputUser;
url?: string;
startParam?: string;
themeParams?: Api.TypeDataJSON;
replyToMsgId?: int;
};
export class ProlongWebView extends Request<Partial<{
// flags: undefined;
silent?: true;
peer: Api.TypeInputPeer;
bot: Api.TypeInputUser;
queryId: long;
replyToMsgId?: int;
}>, Bool> {
// flags: undefined;
silent?: true;
peer: Api.TypeInputPeer;
bot: Api.TypeInputUser;
queryId: long;
replyToMsgId?: int;
};
export class RequestSimpleWebView extends Request<Partial<{
// flags: undefined;
bot: Api.TypeInputUser;
url: string;
themeParams?: Api.TypeDataJSON;
}>, Api.TypeSimpleWebViewResult> {
// flags: undefined;
bot: Api.TypeInputUser;
url: string;
themeParams?: Api.TypeDataJSON;
};
export class SendWebViewResultMessage extends Request<Partial<{
botQueryId: string;
result: Api.TypeInputBotInlineResult;
}>, Api.TypeWebViewMessageSent> {
botQueryId: string;
result: Api.TypeInputBotInlineResult;
};
export class SendWebViewData extends Request<Partial<{
bot: Api.TypeInputUser;
randomId: long;
buttonText: string;
data: string;
}>, Api.TypeUpdates> {
bot: Api.TypeInputUser;
randomId: long;
buttonText: string;
data: string;
};
}
export namespace updates {
@ -11811,9 +12084,13 @@ namespace Api {
id: int[];
};
export class DeleteHistory extends Request<Partial<{
// flags: undefined;
forEveryone?: true;
channel: Api.TypeInputChannel;
maxId: int;
}>, Bool> {
}>, Api.TypeUpdates> {
// flags: undefined;
forEveryone?: true;
channel: Api.TypeInputChannel;
maxId: int;
};
@ -11932,6 +12209,28 @@ namespace Api {
scope: Api.TypeBotCommandScope;
langCode: string;
};
export class SetBotMenuButton extends Request<Partial<{
userId: Api.TypeInputUser;
button: Api.TypeBotMenuButton;
}>, Bool> {
userId: Api.TypeInputUser;
button: Api.TypeBotMenuButton;
};
export class GetBotMenuButton extends Request<Partial<{
userId: Api.TypeInputUser;
}>, Api.TypeBotMenuButton> {
userId: Api.TypeInputUser;
};
export class SetBotBroadcastDefaultAdminRights extends Request<Partial<{
adminRights: Api.TypeChatAdminRights;
}>, Bool> {
adminRights: Api.TypeChatAdminRights;
};
export class SetBotGroupDefaultAdminRights extends Request<Partial<{
adminRights: Api.TypeChatAdminRights;
}>, Bool> {
adminRights: Api.TypeChatAdminRights;
};
}
export namespace payments {
@ -12450,16 +12749,16 @@ namespace Api {
export type AnyRequest = InvokeAfterMsg | InvokeAfterMsgs | InitConnection | InvokeWithLayer | InvokeWithoutUpdates | InvokeWithMessagesRange | InvokeWithTakeout | ReqPq | ReqPqMulti | ReqPqMultiNew | ReqDHParams | SetClientDHParams | DestroyAuthKey | RpcDropAnswer | GetFutureSalts | Ping | PingDelayDisconnect | DestroySession
| auth.SendCode | auth.SignUp | auth.SignIn | auth.LogOut | auth.ResetAuthorizations | auth.ExportAuthorization | auth.ImportAuthorization | auth.BindTempAuthKey | auth.ImportBotAuthorization | auth.CheckPassword | auth.RequestPasswordRecovery | auth.RecoverPassword | auth.ResendCode | auth.CancelCode | auth.DropTempAuthKeys | auth.ExportLoginToken | auth.ImportLoginToken | auth.AcceptLoginToken | auth.CheckRecoveryPassword
| account.RegisterDevice | account.UnregisterDevice | account.UpdateNotifySettings | account.GetNotifySettings | account.ResetNotifySettings | account.UpdateProfile | account.UpdateStatus | account.GetWallPapers | account.ReportPeer | account.CheckUsername | account.UpdateUsername | account.GetPrivacy | account.SetPrivacy | account.DeleteAccount | account.GetAccountTTL | account.SetAccountTTL | account.SendChangePhoneCode | account.ChangePhone | account.UpdateDeviceLocked | account.GetAuthorizations | account.ResetAuthorization | account.GetPassword | account.GetPasswordSettings | account.UpdatePasswordSettings | account.SendConfirmPhoneCode | account.ConfirmPhone | account.GetTmpPassword | account.GetWebAuthorizations | account.ResetWebAuthorization | account.ResetWebAuthorizations | account.GetAllSecureValues | account.GetSecureValue | account.SaveSecureValue | account.DeleteSecureValue | account.GetAuthorizationForm | account.AcceptAuthorization | account.SendVerifyPhoneCode | account.VerifyPhone | account.SendVerifyEmailCode | account.VerifyEmail | account.InitTakeoutSession | account.FinishTakeoutSession | account.ConfirmPasswordEmail | account.ResendPasswordEmail | account.CancelPasswordEmail | account.GetContactSignUpNotification | account.SetContactSignUpNotification | account.GetNotifyExceptions | account.GetWallPaper | account.UploadWallPaper | account.SaveWallPaper | account.InstallWallPaper | account.ResetWallPapers | account.GetAutoDownloadSettings | account.SaveAutoDownloadSettings | account.UploadTheme | account.CreateTheme | account.UpdateTheme | account.SaveTheme | account.InstallTheme | account.GetTheme | account.GetThemes | account.SetContentSettings | account.GetContentSettings | account.GetMultiWallPapers | account.GetGlobalPrivacySettings | account.SetGlobalPrivacySettings | account.ReportProfilePhoto | account.ResetPassword | account.DeclinePasswordReset | account.GetChatThemes | account.SetAuthorizationTTL | account.ChangeAuthorizationSettings
| account.RegisterDevice | account.UnregisterDevice | account.UpdateNotifySettings | account.GetNotifySettings | account.ResetNotifySettings | account.UpdateProfile | account.UpdateStatus | account.GetWallPapers | account.ReportPeer | account.CheckUsername | account.UpdateUsername | account.GetPrivacy | account.SetPrivacy | account.DeleteAccount | account.GetAccountTTL | account.SetAccountTTL | account.SendChangePhoneCode | account.ChangePhone | account.UpdateDeviceLocked | account.GetAuthorizations | account.ResetAuthorization | account.GetPassword | account.GetPasswordSettings | account.UpdatePasswordSettings | account.SendConfirmPhoneCode | account.ConfirmPhone | account.GetTmpPassword | account.GetWebAuthorizations | account.ResetWebAuthorization | account.ResetWebAuthorizations | account.GetAllSecureValues | account.GetSecureValue | account.SaveSecureValue | account.DeleteSecureValue | account.GetAuthorizationForm | account.AcceptAuthorization | account.SendVerifyPhoneCode | account.VerifyPhone | account.SendVerifyEmailCode | account.VerifyEmail | account.InitTakeoutSession | account.FinishTakeoutSession | account.ConfirmPasswordEmail | account.ResendPasswordEmail | account.CancelPasswordEmail | account.GetContactSignUpNotification | account.SetContactSignUpNotification | account.GetNotifyExceptions | account.GetWallPaper | account.UploadWallPaper | account.SaveWallPaper | account.InstallWallPaper | account.ResetWallPapers | account.GetAutoDownloadSettings | account.SaveAutoDownloadSettings | account.UploadTheme | account.CreateTheme | account.UpdateTheme | account.SaveTheme | account.InstallTheme | account.GetTheme | account.GetThemes | account.SetContentSettings | account.GetContentSettings | account.GetMultiWallPapers | account.GetGlobalPrivacySettings | account.SetGlobalPrivacySettings | account.ReportProfilePhoto | account.ResetPassword | account.DeclinePasswordReset | account.GetChatThemes | account.SetAuthorizationTTL | account.ChangeAuthorizationSettings | account.GetSavedRingtones | account.SaveRingtone | account.UploadRingtone
| users.GetUsers | users.GetFullUser | users.SetSecureValueErrors
| contacts.GetContactIDs | contacts.GetStatuses | contacts.GetContacts | contacts.ImportContacts | contacts.DeleteContacts | contacts.DeleteByPhones | contacts.Block | contacts.Unblock | contacts.GetBlocked | contacts.Search | contacts.ResolveUsername | contacts.GetTopPeers | contacts.ResetTopPeerRating | contacts.ResetSaved | contacts.GetSaved | contacts.ToggleTopPeers | contacts.AddContact | contacts.AcceptContact | contacts.GetLocated | contacts.BlockFromReplies | contacts.ResolvePhone
| messages.GetMessages | messages.GetDialogs | messages.GetHistory | messages.Search | messages.ReadHistory | messages.DeleteHistory | messages.DeleteMessages | messages.ReceivedMessages | messages.SetTyping | messages.SendMessage | messages.SendMedia | messages.ForwardMessages | messages.ReportSpam | messages.GetPeerSettings | messages.Report | messages.GetChats | messages.GetFullChat | messages.EditChatTitle | messages.EditChatPhoto | messages.AddChatUser | messages.DeleteChatUser | messages.CreateChat | messages.GetDhConfig | messages.RequestEncryption | messages.AcceptEncryption | messages.DiscardEncryption | messages.SetEncryptedTyping | messages.ReadEncryptedHistory | messages.SendEncrypted | messages.SendEncryptedFile | messages.SendEncryptedService | messages.ReceivedQueue | messages.ReportEncryptedSpam | messages.ReadMessageContents | messages.GetStickers | messages.GetAllStickers | messages.GetWebPagePreview | messages.ExportChatInvite | messages.CheckChatInvite | messages.ImportChatInvite | messages.GetStickerSet | messages.InstallStickerSet | messages.UninstallStickerSet | messages.StartBot | messages.GetMessagesViews | messages.EditChatAdmin | messages.MigrateChat | messages.SearchGlobal | messages.ReorderStickerSets | messages.GetDocumentByHash | messages.GetSavedGifs | messages.SaveGif | messages.GetInlineBotResults | messages.SetInlineBotResults | messages.SendInlineBotResult | messages.GetMessageEditData | messages.EditMessage | messages.EditInlineBotMessage | messages.GetBotCallbackAnswer | messages.SetBotCallbackAnswer | messages.GetPeerDialogs | messages.SaveDraft | messages.GetAllDrafts | messages.GetFeaturedStickers | messages.ReadFeaturedStickers | messages.GetRecentStickers | messages.SaveRecentSticker | messages.ClearRecentStickers | messages.GetArchivedStickers | messages.GetMaskStickers | messages.GetAttachedStickers | messages.SetGameScore | messages.SetInlineGameScore | messages.GetGameHighScores | messages.GetInlineGameHighScores | messages.GetCommonChats | messages.GetAllChats | messages.GetWebPage | messages.ToggleDialogPin | messages.ReorderPinnedDialogs | messages.GetPinnedDialogs | messages.SetBotShippingResults | messages.SetBotPrecheckoutResults | messages.UploadMedia | messages.SendScreenshotNotification | messages.GetFavedStickers | messages.FaveSticker | messages.GetUnreadMentions | messages.ReadMentions | messages.GetRecentLocations | messages.SendMultiMedia | messages.UploadEncryptedFile | messages.SearchStickerSets | messages.GetSplitRanges | messages.MarkDialogUnread | messages.GetDialogUnreadMarks | messages.ClearAllDrafts | messages.UpdatePinnedMessage | messages.SendVote | messages.GetPollResults | messages.GetOnlines | messages.EditChatAbout | messages.EditChatDefaultBannedRights | messages.GetEmojiKeywords | messages.GetEmojiKeywordsDifference | messages.GetEmojiKeywordsLanguages | messages.GetEmojiURL | messages.GetSearchCounters | messages.RequestUrlAuth | messages.AcceptUrlAuth | messages.HidePeerSettingsBar | messages.GetScheduledHistory | messages.GetScheduledMessages | messages.SendScheduledMessages | messages.DeleteScheduledMessages | messages.GetPollVotes | messages.ToggleStickerSets | messages.GetDialogFilters | messages.GetSuggestedDialogFilters | messages.UpdateDialogFilter | messages.UpdateDialogFiltersOrder | messages.GetOldFeaturedStickers | messages.GetReplies | messages.GetDiscussionMessage | messages.ReadDiscussion | messages.UnpinAllMessages | messages.DeleteChat | messages.DeletePhoneCallHistory | messages.CheckHistoryImport | messages.InitHistoryImport | messages.UploadImportedMedia | messages.StartHistoryImport | messages.GetExportedChatInvites | messages.GetExportedChatInvite | messages.EditExportedChatInvite | messages.DeleteRevokedExportedChatInvites | messages.DeleteExportedChatInvite | messages.GetAdminsWithInvites | messages.GetChatInviteImporters | messages.SetHistoryTTL | messages.CheckHistoryImportPeer | messages.SetChatTheme | messages.GetMessageReadParticipants | messages.GetSearchResultsCalendar | messages.GetSearchResultsPositions | messages.HideChatJoinRequest | messages.HideAllChatJoinRequests | messages.ToggleNoForwards | messages.SaveDefaultSendAs | messages.SendReaction | messages.GetMessagesReactions | messages.GetMessageReactionsList | messages.SetChatAvailableReactions | messages.GetAvailableReactions | messages.SetDefaultReaction | messages.TranslateText | messages.GetUnreadReactions | messages.ReadReactions | messages.SearchSentMedia
| messages.GetMessages | messages.GetDialogs | messages.GetHistory | messages.Search | messages.ReadHistory | messages.DeleteHistory | messages.DeleteMessages | messages.ReceivedMessages | messages.SetTyping | messages.SendMessage | messages.SendMedia | messages.ForwardMessages | messages.ReportSpam | messages.GetPeerSettings | messages.Report | messages.GetChats | messages.GetFullChat | messages.EditChatTitle | messages.EditChatPhoto | messages.AddChatUser | messages.DeleteChatUser | messages.CreateChat | messages.GetDhConfig | messages.RequestEncryption | messages.AcceptEncryption | messages.DiscardEncryption | messages.SetEncryptedTyping | messages.ReadEncryptedHistory | messages.SendEncrypted | messages.SendEncryptedFile | messages.SendEncryptedService | messages.ReceivedQueue | messages.ReportEncryptedSpam | messages.ReadMessageContents | messages.GetStickers | messages.GetAllStickers | messages.GetWebPagePreview | messages.ExportChatInvite | messages.CheckChatInvite | messages.ImportChatInvite | messages.GetStickerSet | messages.InstallStickerSet | messages.UninstallStickerSet | messages.StartBot | messages.GetMessagesViews | messages.EditChatAdmin | messages.MigrateChat | messages.SearchGlobal | messages.ReorderStickerSets | messages.GetDocumentByHash | messages.GetSavedGifs | messages.SaveGif | messages.GetInlineBotResults | messages.SetInlineBotResults | messages.SendInlineBotResult | messages.GetMessageEditData | messages.EditMessage | messages.EditInlineBotMessage | messages.GetBotCallbackAnswer | messages.SetBotCallbackAnswer | messages.GetPeerDialogs | messages.SaveDraft | messages.GetAllDrafts | messages.GetFeaturedStickers | messages.ReadFeaturedStickers | messages.GetRecentStickers | messages.SaveRecentSticker | messages.ClearRecentStickers | messages.GetArchivedStickers | messages.GetMaskStickers | messages.GetAttachedStickers | messages.SetGameScore | messages.SetInlineGameScore | messages.GetGameHighScores | messages.GetInlineGameHighScores | messages.GetCommonChats | messages.GetAllChats | messages.GetWebPage | messages.ToggleDialogPin | messages.ReorderPinnedDialogs | messages.GetPinnedDialogs | messages.SetBotShippingResults | messages.SetBotPrecheckoutResults | messages.UploadMedia | messages.SendScreenshotNotification | messages.GetFavedStickers | messages.FaveSticker | messages.GetUnreadMentions | messages.ReadMentions | messages.GetRecentLocations | messages.SendMultiMedia | messages.UploadEncryptedFile | messages.SearchStickerSets | messages.GetSplitRanges | messages.MarkDialogUnread | messages.GetDialogUnreadMarks | messages.ClearAllDrafts | messages.UpdatePinnedMessage | messages.SendVote | messages.GetPollResults | messages.GetOnlines | messages.EditChatAbout | messages.EditChatDefaultBannedRights | messages.GetEmojiKeywords | messages.GetEmojiKeywordsDifference | messages.GetEmojiKeywordsLanguages | messages.GetEmojiURL | messages.GetSearchCounters | messages.RequestUrlAuth | messages.AcceptUrlAuth | messages.HidePeerSettingsBar | messages.GetScheduledHistory | messages.GetScheduledMessages | messages.SendScheduledMessages | messages.DeleteScheduledMessages | messages.GetPollVotes | messages.ToggleStickerSets | messages.GetDialogFilters | messages.GetSuggestedDialogFilters | messages.UpdateDialogFilter | messages.UpdateDialogFiltersOrder | messages.GetOldFeaturedStickers | messages.GetReplies | messages.GetDiscussionMessage | messages.ReadDiscussion | messages.UnpinAllMessages | messages.DeleteChat | messages.DeletePhoneCallHistory | messages.CheckHistoryImport | messages.InitHistoryImport | messages.UploadImportedMedia | messages.StartHistoryImport | messages.GetExportedChatInvites | messages.GetExportedChatInvite | messages.EditExportedChatInvite | messages.DeleteRevokedExportedChatInvites | messages.DeleteExportedChatInvite | messages.GetAdminsWithInvites | messages.GetChatInviteImporters | messages.SetHistoryTTL | messages.CheckHistoryImportPeer | messages.SetChatTheme | messages.GetMessageReadParticipants | messages.GetSearchResultsCalendar | messages.GetSearchResultsPositions | messages.HideChatJoinRequest | messages.HideAllChatJoinRequests | messages.ToggleNoForwards | messages.SaveDefaultSendAs | messages.SendReaction | messages.GetMessagesReactions | messages.GetMessageReactionsList | messages.SetChatAvailableReactions | messages.GetAvailableReactions | messages.SetDefaultReaction | messages.TranslateText | messages.GetUnreadReactions | messages.ReadReactions | messages.SearchSentMedia | messages.GetAttachMenuBots | messages.GetAttachMenuBot | messages.ToggleBotInAttachMenu | messages.RequestWebView | messages.ProlongWebView | messages.RequestSimpleWebView | messages.SendWebViewResultMessage | messages.SendWebViewData
| updates.GetState | updates.GetDifference | updates.GetChannelDifference
| photos.UpdateProfilePhoto | photos.UploadProfilePhoto | photos.DeletePhotos | photos.GetUserPhotos
| upload.SaveFilePart | upload.GetFile | upload.SaveBigFilePart | upload.GetWebFile | upload.GetCdnFile | upload.ReuploadCdnFile | upload.GetCdnFileHashes | upload.GetFileHashes
| help.GetConfig | help.GetNearestDc | help.GetAppUpdate | help.GetInviteText | help.GetSupport | help.GetAppChangelog | help.SetBotUpdatesStatus | help.GetCdnConfig | help.GetRecentMeUrls | help.GetTermsOfServiceUpdate | help.AcceptTermsOfService | help.GetDeepLinkInfo | help.GetAppConfig | help.SaveAppLog | help.GetPassportConfig | help.GetSupportName | help.GetUserInfo | help.EditUserInfo | help.GetPromoData | help.HidePromoData | help.DismissSuggestion | help.GetCountriesList
| channels.ReadHistory | channels.DeleteMessages | channels.ReportSpam | channels.GetMessages | channels.GetParticipants | channels.GetParticipant | channels.GetChannels | channels.GetFullChannel | channels.CreateChannel | channels.EditAdmin | channels.EditTitle | channels.EditPhoto | channels.CheckUsername | channels.UpdateUsername | channels.JoinChannel | channels.LeaveChannel | channels.InviteToChannel | channels.DeleteChannel | channels.ExportMessageLink | channels.ToggleSignatures | channels.GetAdminedPublicChannels | channels.EditBanned | channels.GetAdminLog | channels.SetStickers | channels.ReadMessageContents | channels.DeleteHistory | channels.TogglePreHistoryHidden | channels.GetLeftChannels | channels.GetGroupsForDiscussion | channels.SetDiscussionGroup | channels.EditCreator | channels.EditLocation | channels.ToggleSlowMode | channels.GetInactiveChannels | channels.ConvertToGigagroup | channels.ViewSponsoredMessage | channels.GetSponsoredMessages | channels.GetSendAs | channels.DeleteParticipantHistory
| bots.SendCustomRequest | bots.AnswerWebhookJSONQuery | bots.SetBotCommands | bots.ResetBotCommands | bots.GetBotCommands
| bots.SendCustomRequest | bots.AnswerWebhookJSONQuery | bots.SetBotCommands | bots.ResetBotCommands | bots.GetBotCommands | bots.SetBotMenuButton | bots.GetBotMenuButton | bots.SetBotBroadcastDefaultAdminRights | bots.SetBotGroupDefaultAdminRights
| payments.GetPaymentForm | payments.GetPaymentReceipt | payments.ValidateRequestedInfo | payments.SendPaymentForm | payments.GetSavedInfo | payments.ClearSavedInfo | payments.GetBankCardData
| stickers.CreateStickerSet | stickers.RemoveStickerFromSet | stickers.ChangeStickerPosition | stickers.AddStickerToSet | stickers.SetStickerSetThumb | stickers.CheckShortName | stickers.SuggestShortName
| phone.GetCallConfig | phone.RequestCall | phone.AcceptCall | phone.ConfirmCall | phone.ReceivedCall | phone.DiscardCall | phone.SetCallRating | phone.SaveCallDebug | phone.SendSignalingData | phone.CreateGroupCall | phone.JoinGroupCall | phone.LeaveGroupCall | phone.InviteToGroupCall | phone.DiscardGroupCall | phone.ToggleGroupCallSettings | phone.GetGroupCall | phone.GetGroupParticipants | phone.CheckGroupCall | phone.ToggleGroupCallRecord | phone.EditGroupCallParticipant | phone.EditGroupCallTitle | phone.GetGroupCallJoinAs | phone.ExportGroupCallInvite | phone.ToggleGroupCallStartSubscription | phone.StartScheduledGroupCall | phone.SaveDefaultGroupCallJoinAs | phone.JoinGroupCallPresentation | phone.LeaveGroupCallPresentation | phone.GetGroupCallStreamChannels | phone.GetGroupCallStreamRtmpUrl

View File

@ -259,11 +259,13 @@ function createClasses(classesType, params) {
if (argsConfig.hasOwnProperty(argName)) {
const arg = argsConfig[argName];
if (arg.isFlag) {
const flagValue = arg.flagIndex > 30
? args.flags2 & (1 << (arg.flagIndex - 31)) : args.flags & (1 << arg.flagIndex);
if (arg.type === 'true') {
args[argName] = Boolean(args.flags & (1 << arg.flagIndex));
args[argName] = Boolean(flagValue);
continue;
}
if (args.flags & (1 << arg.flagIndex)) {
if (flagValue) {
args[argName] = getArgFromReader(reader, arg);
} else {
args[argName] = undefined;

View File

@ -64,7 +64,7 @@ storage.fileMov#4b09ebbc = storage.FileType;
storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
userEmpty#d3bc4b7a id:long = User;
user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
userStatusEmpty#9d05049 = UserStatus;
@ -74,12 +74,12 @@ userStatusRecently#e26f42f1 = UserStatus;
userStatusLastWeek#7bf09fc = UserStatus;
userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#29562865 id:long = Chat;
chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#6592a1a7 id:long title:string = Chat;
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?Vector<string> = ChatFull;
channelFull#e13c3d20 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?Vector<string> = ChatFull;
channelFull#ea68a619 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags.31?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?Vector<string> = ChatFull;
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
chatParticipantAdmin#a0933f5b user_id:long inviter_id:long date:int = ChatParticipant;
@ -133,6 +133,8 @@ messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
photoEmpty#2331b22d id:long = Photo;
@ -153,8 +155,8 @@ inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
inputNotifyUsers#193b4417 = InputNotifyPeer;
inputNotifyChats#4a95e84e = InputNotifyPeer;
inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings;
peerNotifySettings#a83b0426 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound = PeerNotifySettings;
peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings;
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
@ -168,7 +170,7 @@ inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
inputReportReasonFake#f5ddd6e7 = ReportReason;
inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
inputReportReasonPersonalDetails#9ec7863d = ReportReason;
userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull;
userFull#8c72ea81 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights = UserFull;
contact#145ade0b user_id:long mutual:Bool = Contact;
importedContact#c13e3c50 user_id:long client_id:long = ImportedContact;
contactStatus#16d9703b user_id:long status:UserStatus = ContactStatus;
@ -301,6 +303,10 @@ updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = U
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update;
updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update;
updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
updateSavedRingtones#74d8be99 = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference;
updates.difference#f49ca0 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> state:updates.State = updates.Difference;
@ -442,7 +448,7 @@ stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:fl
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
botCommand#c27ac8c7 command:string description:string = BotCommand;
botInfo#1b74b335 user_id:long description:string commands:Vector<BotCommand> = BotInfo;
botInfo#e4169b5d user_id:long description:string commands:Vector<BotCommand> menu_button:BotMenuButton = BotInfo;
keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton;
@ -456,6 +462,8 @@ inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true te
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton;
keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton;
keyboardButtonWebView#13767230 text:string url:string = KeyboardButton;
keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup;
@ -957,6 +965,26 @@ messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_i
groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel;
phone.groupCallStreamChannels#d0e482b2 channels:Vector<GroupCallStreamChannel> = phone.GroupCallStreamChannels;
phone.groupCallStreamRtmpUrl#2dbf3432 url:string key:string = phone.GroupCallStreamRtmpUrl;
attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor;
attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector<AttachMenuBotIconColor> = AttachMenuBotIcon;
attachMenuBot#e93cb772 flags:# inactive:flags.0?true bot_id:long short_name:string icons:Vector<AttachMenuBotIcon> = AttachMenuBot;
attachMenuBotsNotModified#f1d88a5c = AttachMenuBots;
attachMenuBots#3c4301c0 hash:long bots:Vector<AttachMenuBot> users:Vector<User> = AttachMenuBots;
attachMenuBotsBot#93bf667f bot:AttachMenuBot users:Vector<User> = AttachMenuBotsBot;
webViewResultUrl#c14557c query_id:long url:string = WebViewResult;
simpleWebViewResultUrl#882f76bb url:string = SimpleWebViewResult;
webViewMessageSent#c94511c flags:# msg_id:flags.0?InputBotInlineMessageID = WebViewMessageSent;
botMenuButtonDefault#7533a588 = BotMenuButton;
botMenuButtonCommands#4258c205 = BotMenuButton;
botMenuButton#c7b57ce6 text:string url:string = BotMenuButton;
account.savedRingtonesNotModified#fbf6e8b1 = account.SavedRingtones;
account.savedRingtones#c1e92cc5 hash:long ringtones:Vector<Document> = account.SavedRingtones;
notificationSoundDefault#97e8bebe = NotificationSound;
notificationSoundNone#6f0c34df = NotificationSound;
notificationSoundLocal#830b9ae4 title:string data:string = NotificationSound;
notificationSoundRingtone#ff6c8049 id:long = NotificationSound;
account.savedRingtone#b7263f6d = account.SavedRingtone;
account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone;
---functions---
initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
@ -1110,6 +1138,14 @@ messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction
messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector<string> = Updates;
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
messages.requestWebView#fa04dff flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int = WebViewResult;
messages.prolongWebView#d22ad148 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int = Bool;
messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult;
messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;

View File

@ -222,5 +222,13 @@
"help.getAppConfig",
"stats.getBroadcastStats",
"stats.getMegagroupStats",
"stats.loadAsyncGraph"
"stats.loadAsyncGraph",
"messages.getAttachMenuBots",
"messages.getAttachMenuBot",
"messages.toggleBotInAttachMenu",
"messages.requestWebView",
"messages.prolongWebView",
"messages.requestSimpleWebView",
"messages.sendWebViewResultMessage",
"messages.sendWebViewData"
]

View File

@ -1,33 +1,3 @@
///////////////////////////////
/////////////////// Layer cons
///////////////////////////////
//invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
//invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;
//invokeWithLayer1#53835315 query:!X = X;
//invokeWithLayer2#289dd1f6 query:!X = X;
//invokeWithLayer3#b7475268 query:!X = X;
//invokeWithLayer4#dea0d430 query:!X = X;
//invokeWithLayer5#417a57ae query:!X = X;
//invokeWithLayer6#3a64d54d query:!X = X;
//invokeWithLayer7#a5be56d3 query:!X = X;
//invokeWithLayer8#e9abd9fd query:!X = X;
//invokeWithLayer9#76715a63 query:!X = X;
//invokeWithLayer10#39620c41 query:!X = X;
//invokeWithLayer11#a6b88fdf query:!X = X;
//invokeWithLayer12#dda60d3c query:!X = X;
//invokeWithLayer13#427c8ea2 query:!X = X;
//invokeWithLayer14#2b9b08fa query:!X = X;
//invokeWithLayer15#b4418b64 query:!X = X;
//invokeWithLayer16#cf5f0987 query:!X = X;
//invokeWithLayer17#50858a19 query:!X = X;
//invokeWithLayer18#1c900537 query:!X = X;
//invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer
///////////////////////////////
///////// Main application API
///////////////////////////////
boolFalse#bc799737 = Bool;
boolTrue#997275b5 = Bool;
@ -110,7 +80,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
userEmpty#d3bc4b7a id:long = User;
user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
@ -123,13 +93,13 @@ userStatusLastWeek#7bf09fc = UserStatus;
userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#29562865 id:long = Chat;
chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#6592a1a7 id:long title:string = Chat;
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?Vector<string> = ChatFull;
channelFull#e13c3d20 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?Vector<string> = ChatFull;
channelFull#ea68a619 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags.31?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?Vector<string> = ChatFull;
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
@ -189,6 +159,8 @@ messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@ -218,9 +190,9 @@ inputNotifyUsers#193b4417 = InputNotifyPeer;
inputNotifyChats#4a95e84e = InputNotifyPeer;
inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings;
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
peerNotifySettings#a83b0426 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound = PeerNotifySettings;
peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings;
@ -238,7 +210,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason;
inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
inputReportReasonPersonalDetails#9ec7863d = ReportReason;
userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull;
userFull#8c72ea81 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights = UserFull;
contact#145ade0b user_id:long mutual:Bool = Contact;
@ -384,6 +356,10 @@ updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = U
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update;
updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update;
updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
updateSavedRingtones#74d8be99 = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -575,7 +551,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
botCommand#c27ac8c7 command:string description:string = BotCommand;
botInfo#1b74b335 user_id:long description:string commands:Vector<BotCommand> = BotInfo;
botInfo#e4169b5d user_id:long description:string commands:Vector<BotCommand> menu_button:BotMenuButton = BotInfo;
keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
@ -590,6 +566,8 @@ inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true te
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton;
keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton;
keyboardButtonWebView#13767230 text:string url:string = KeyboardButton;
keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
@ -1329,6 +1307,38 @@ phone.groupCallStreamChannels#d0e482b2 channels:Vector<GroupCallStreamChannel> =
phone.groupCallStreamRtmpUrl#2dbf3432 url:string key:string = phone.GroupCallStreamRtmpUrl;
attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor;
attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector<AttachMenuBotIconColor> = AttachMenuBotIcon;
attachMenuBot#e93cb772 flags:# inactive:flags.0?true bot_id:long short_name:string icons:Vector<AttachMenuBotIcon> = AttachMenuBot;
attachMenuBotsNotModified#f1d88a5c = AttachMenuBots;
attachMenuBots#3c4301c0 hash:long bots:Vector<AttachMenuBot> users:Vector<User> = AttachMenuBots;
attachMenuBotsBot#93bf667f bot:AttachMenuBot users:Vector<User> = AttachMenuBotsBot;
webViewResultUrl#c14557c query_id:long url:string = WebViewResult;
simpleWebViewResultUrl#882f76bb url:string = SimpleWebViewResult;
webViewMessageSent#c94511c flags:# msg_id:flags.0?InputBotInlineMessageID = WebViewMessageSent;
botMenuButtonDefault#7533a588 = BotMenuButton;
botMenuButtonCommands#4258c205 = BotMenuButton;
botMenuButton#c7b57ce6 text:string url:string = BotMenuButton;
account.savedRingtonesNotModified#fbf6e8b1 = account.SavedRingtones;
account.savedRingtones#c1e92cc5 hash:long ringtones:Vector<Document> = account.SavedRingtones;
notificationSoundDefault#97e8bebe = NotificationSound;
notificationSoundNone#6f0c34df = NotificationSound;
notificationSoundLocal#830b9ae4 title:string data:string = NotificationSound;
notificationSoundRingtone#ff6c8049 id:long = NotificationSound;
account.savedRingtone#b7263f6d = account.SavedRingtone;
account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1432,6 +1442,9 @@ account.declinePasswordReset#4c9409f6 = Bool;
account.getChatThemes#d638de89 hash:long = account.Themes;
account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool;
account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool;
account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones;
account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone;
account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
@ -1618,6 +1631,14 @@ messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?in
messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
messages.requestWebView#fa04dff flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int = WebViewResult;
messages.prolongWebView#d22ad148 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int = Bool;
messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult;
messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@ -1685,7 +1706,7 @@ channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_r
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
channels.deleteHistory#9baa9647 flags:# for_everyone:flags.0?true channel:InputChannel max_id:int = Updates;
channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
@ -1705,6 +1726,10 @@ bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector<BotCommand> = Bool;
bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool;
bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector<BotCommand>;
bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool;
bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton;
bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool;
bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool;
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
@ -1768,4 +1793,3 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 139

View File

@ -1,5 +1,6 @@
// Not sure what they are for.
const RAW_TYPES = new Set(['Bool', 'X'])
const FLAGS = ['flags', 'flags2'];
module.exports = ({ types, constructors, functions }) => {
function groupByKey(collection, key) {
@ -32,7 +33,7 @@ module.exports = ({ types, constructors, functions }) => {
return `export class ${upperFirst(name)} extends VirtualClass<void> {};`
}
let hasRequiredArgs = argKeys.some((argName) => argName !== 'flags' && !argsConfig[argName].isFlag)
let hasRequiredArgs = argKeys.some((argName) => !FLAGS.includes(argName) && !argsConfig[argName].isFlag)
return `
export class ${upperFirst(name)} extends VirtualClass<{
@ -61,7 +62,7 @@ ${indent}};`.trim()
return `export class ${upperFirst(name)} extends Request<void, ${renderedResult}> {};`
}
let hasRequiredArgs = argKeys.some((argName) => argName !== 'flags' && !argsConfig[argName].isFlag)
let hasRequiredArgs = argKeys.some((argName) => !FLAGS.includes(argName) && !argsConfig[argName].isFlag)
return `
export class ${upperFirst(name)} extends Request<Partial<{
@ -95,7 +96,7 @@ ${indent}};`.trim()
const valueType = renderValueType(type, isVector, !skipConstructorId)
return `${argName === 'flags' ? '// ' : ''}${argName}${isFlag ? '?' : ''}: ${valueType}`
return `${FLAGS.includes(argName) ? '// ' : ''}${argName}${isFlag ? '?' : ''}: ${valueType}`
}
function renderValueType(type, isVector, isTlType) {

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,21 @@
.icon-volume-3:before {
content: "\e991";
}
.icon-reactions:before {
content: "\e99a";
}
.icon-reaction-filled:before {
content: "\e99b";
}
.icon-webapp:before {
content: "\e993";
}
.icon-reload:before {
content: "\e994";
}
.icon-install:before {
content: "\e999";
}
.icon-favorite-filled:before {
content: "\e998";
}
@ -66,12 +81,6 @@
.icon-copy-media:before {
content: "\e995";
}
.icon-reaction-filled:before {
content: "\e994";
}
.icon-reactions:before {
content: "\e993";
}
.icon-sidebar:before {
content: "\e992";
}

View File

@ -30,9 +30,11 @@ export const processDeepLink = (url: string) => {
switch (method) {
case 'resolve': {
const {
domain, phone, post, comment, voicechat, livestream, start,
domain, phone, post, comment, voicechat, livestream, start, startattach, attach,
} = params;
const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach;
if (domain !== 'telegrampassport') {
if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
joinVoiceChatByLink({
@ -40,13 +42,15 @@ export const processDeepLink = (url: string) => {
inviteHash: voicechat || livestream,
});
} else if (phone) {
openChatByPhoneNumber({ phone });
openChatByPhoneNumber({ phone, startAttach, attach });
} else {
openChatByUsername({
username: domain,
messageId: Number(post),
commentId: Number(comment),
startParam: start,
startAttach,
attach,
});
}
}

View File

@ -18,6 +18,7 @@ import { webpToPng } from './webpToPng';
const asCacheApiType = {
[ApiMediaFormat.BlobUrl]: cacheApi.Type.Blob,
[ApiMediaFormat.Lottie]: cacheApi.Type.Blob,
[ApiMediaFormat.Text]: cacheApi.Type.Text,
[ApiMediaFormat.Progressive]: undefined,
[ApiMediaFormat.Stream]: undefined,
};

39
src/util/themeStyle.ts Normal file
View File

@ -0,0 +1,39 @@
import { ApiThemeParameters } from '../api/types';
export function extractCurrentThemeParams(): ApiThemeParameters {
const style = getComputedStyle(document.documentElement);
const backgroundColor = getPropertyWrapped(style, '--color-background');
const textColor = getPropertyWrapped(style, '--color-text');
const buttonColor = getPropertyWrapped(style, '--color-primary');
const buttonTextColor = getPropertyWrapped(style, '--color-white');
const linkColor = getPropertyWrapped(style, '--color-links');
const hintColor = getPropertyWrapped(style, '--color-text-secondary');
return {
bg_color: backgroundColor,
text_color: textColor,
hint_color: hintColor,
link_color: linkColor,
button_color: buttonColor,
button_text_color: buttonTextColor,
};
}
export function validateHexColor(color: string) {
return /^#[0-9A-F]{6}$/i.test(color);
}
function getPropertyWrapped(style: CSSStyleDeclaration, property: string) {
const value = style.getPropertyValue(property);
return wrapColor(value.trim());
}
function wrapColor(color: string) {
if (validateHexColor(color)) return color;
return `#${color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/)!
.slice(1)
.map((n: string, i: number) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n))
.toString(16)
.padStart(2, '0')
.replace('NaN', ''))
.join('')}`;
}