Profile: GIFs & Main Tab (#6336)

This commit is contained in:
zubiden 2025-10-11 19:07:54 +02:00 committed by Alexander Zinchuk
parent 1be16bf765
commit 5e679218e2
51 changed files with 603 additions and 330 deletions

View File

@ -5,7 +5,7 @@ module.exports = {
fontTypes: ['woff2', 'woff'],
assetTypes: ['scss', 'ts'],
tag: '',
// Use a custom Handlebars template
normalize: true,
templates: {
scss: './dev/icons.scss.hbs',
},

View File

@ -48,6 +48,8 @@ type AnyToVoidFunction = (...args: any[]) => void;
type BooleanToVoidFunction = (value: boolean) => void;
type NoneToVoidFunction = () => void;
type StringAutocomplete<T> = T | (string & {});
type Complete<T> = {
[P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>> ? T[P] : (T[P] | undefined);
};

View File

@ -29,14 +29,17 @@ import { getServerTimeOffset } from '../../../util/serverTime';
import { addPhotoToLocalDb, addUserToLocalDb } from '../helpers/localDb';
import { serializeBytes } from '../helpers/misc';
import {
buildApiBotVerification, buildApiFormattedText, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId,
buildApiFormattedText, buildApiPhoto, buildApiUsernames,
} from './common';
import { omitVirtualClassFields } from './helpers';
import { buildApiPeerNotifySettings, buildApiRestrictionReasons } from './misc';
import { buildApiRestrictionReasons } from './misc';
import {
buildApiBotVerification,
buildApiEmojiStatus,
buildApiPeerColor,
buildApiPeerId,
buildApiPeerNotifySettings,
buildAvatarPhotoId,
getApiChatIdFromMtpPeer,
isMtpPeerChat,
isMtpPeerUser,

View File

@ -3,7 +3,6 @@ import type { Entity } from '../../../lib/gramjs/types';
import { strippedPhotoToJpg } from '../../../lib/gramjs/Utils';
import type {
ApiBotVerification,
ApiFormattedText,
ApiMessageEntity,
ApiMessageEntityDefault,
@ -293,19 +292,3 @@ export function buildApiMessageEntity(entity: GramJs.TypeMessageEntity): ApiMess
length,
};
}
export function buildAvatarPhotoId(photo: GramJs.TypeUserProfilePhoto | GramJs.TypeChatPhoto) {
if ('photoId' in photo) {
return photo.photoId.toString();
}
return undefined;
}
export function buildApiBotVerification(botVerification: GramJs.BotVerification): ApiBotVerification {
return {
botId: buildApiPeerId(botVerification.botId, 'user'),
iconId: botVerification.icon.toString(),
description: botVerification.description,
};
}

View File

@ -7,9 +7,6 @@ import type {
ApiCountry,
ApiLanguage,
ApiOldLangString,
ApiPeerColors,
ApiPeerNotifySettings,
ApiPeerProfileColorSet,
ApiPrivacyKey,
ApiRestrictionReason,
ApiSession,
@ -20,9 +17,8 @@ import type {
LangPackStringValue,
} from '../../types';
import { numberToHexColor } from '../../../util/colors';
import {
buildCollectionByCallback, omit, omitUndefined, pick,
omit, omitUndefined, pick,
} from '../../../util/iteratees';
import { toJSNumber } from '../../../util/numbers';
import { addUserToLocalDb } from '../helpers/localDb';
@ -111,23 +107,6 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und
return undefined;
}
export function buildApiPeerNotifySettings(
notifySettings: GramJs.TypePeerNotifySettings,
): ApiPeerNotifySettings {
const {
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = !(otherSound instanceof GramJs.NotificationSoundNone);
return {
hasSound,
isSilentPosting: silent,
mutedUntil: muteUntil,
shouldShowPreviews: showPreviews,
};
}
function buildApiCountry(country: GramJs.help.Country, code: GramJs.help.CountryCode) {
const {
hidden, iso2, defaultName, name,
@ -295,46 +274,6 @@ export function buildApiLanguage(lang: GramJs.TypeLangPackLanguage): ApiLanguage
};
}
function buildApiPeerColorSet(colorSet: GramJs.help.PeerColorSet) {
return colorSet.colors.map((color) => numberToHexColor(color));
}
function buildApiPeerProfileColorSet(colorSet: GramJs.help.PeerColorProfileSet): ApiPeerProfileColorSet {
return {
paletteColors: colorSet.paletteColors.map((color) => numberToHexColor(color)),
bgColors: colorSet.bgColors.map((color) => numberToHexColor(color)),
storyColors: colorSet.storyColors.map((color) => numberToHexColor(color)),
};
}
export function buildApiPeerColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['general'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiPeerProfileColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['profile'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiTimezone(timezone: GramJs.TypeTimezone): ApiTimezone {
const { id, name, utcOffset } = timezone;
return {

View File

@ -1,9 +1,18 @@
import { Api as GramJs } from '../../../lib/gramjs';
import type { ApiEmojiStatusType, ApiPeerColor } from '../../types';
import type {
ApiBotVerification,
ApiEmojiStatusType,
ApiPeerColor,
ApiPeerColors,
ApiPeerNotifySettings,
ApiPeerProfileColorSet,
ApiProfileTab,
} from '../../types';
import { CHANNEL_ID_BASE } from '../../../config';
import { numberToHexColor } from '../../../util/colors';
import { buildCollectionByCallback } from '../../../util/iteratees';
type TypePeerOrInput = GramJs.TypePeer | GramJs.TypeInputPeer | GramJs.TypeInputUser | GramJs.TypeInputChannel;
@ -49,6 +58,46 @@ export function buildApiPeerColor(peerColor: GramJs.TypePeerColor): ApiPeerColor
};
}
function buildApiPeerColorSet(colorSet: GramJs.help.PeerColorSet) {
return colorSet.colors.map((color) => numberToHexColor(color));
}
function buildApiPeerProfileColorSet(colorSet: GramJs.help.PeerColorProfileSet): ApiPeerProfileColorSet {
return {
paletteColors: colorSet.paletteColors.map((color) => numberToHexColor(color)),
bgColors: colorSet.bgColors.map((color) => numberToHexColor(color)),
storyColors: colorSet.storyColors.map((color) => numberToHexColor(color)),
};
}
export function buildApiPeerColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['general'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiPeerProfileColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['profile'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiEmojiStatus(mtpEmojiStatus: GramJs.TypeEmojiStatus):
ApiEmojiStatusType | undefined {
if (mtpEmojiStatus instanceof GramJs.EmojiStatus) {
@ -77,3 +126,61 @@ ApiEmojiStatusType | undefined {
return undefined;
}
export function buildAvatarPhotoId(photo: GramJs.TypeUserProfilePhoto | GramJs.TypeChatPhoto) {
if ('photoId' in photo) {
return photo.photoId.toString();
}
return undefined;
}
export function buildApiBotVerification(botVerification: GramJs.BotVerification): ApiBotVerification {
return {
botId: buildApiPeerId(botVerification.botId, 'user'),
iconId: botVerification.icon.toString(),
description: botVerification.description,
};
}
export function buildApiPeerNotifySettings(
notifySettings: GramJs.TypePeerNotifySettings,
): ApiPeerNotifySettings {
const {
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = !(otherSound instanceof GramJs.NotificationSoundNone);
return {
hasSound,
isSilentPosting: silent,
mutedUntil: muteUntil,
shouldShowPreviews: showPreviews,
};
}
export function buildApiProfileTab(profileTab: GramJs.TypeProfileTab): ApiProfileTab {
switch (profileTab.className) {
case 'ProfileTabPosts':
return 'stories';
case 'ProfileTabGifts':
return 'gifts';
case 'ProfileTabMedia':
return 'media';
case 'ProfileTabFiles':
return 'documents';
case 'ProfileTabMusic':
return 'audio';
case 'ProfileTabVoice':
return 'voice';
case 'ProfileTabLinks':
return 'links';
case 'ProfileTabGifs':
return 'gif';
default: {
const _exhaustiveCheck: never = profileTab;
return _exhaustiveCheck;
}
}
}

View File

@ -16,9 +16,9 @@ import type {
TypeStatisticsGraph,
} from '../../types';
import { buildApiUsernames, buildAvatarPhotoId } from './common';
import { buildApiUsernames } from './common';
import { buildApiCurrencyAmount } from './payments';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
import { buildApiPeerId, buildAvatarPhotoId, getApiChatIdFromMtpPeer } from './peers';
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
return {

View File

@ -14,11 +14,18 @@ import { toJSNumber } from '../../../util/numbers';
import { buildApiBotInfo } from './bots';
import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business';
import {
buildApiBotVerification, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId,
buildApiPhoto, buildApiUsernames,
} from './common';
import { buildApiDisallowedGiftsSettings } from './gifts';
import { omitVirtualClassFields } from './helpers';
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers';
import {
buildApiBotVerification,
buildApiEmojiStatus,
buildApiPeerColor,
buildApiPeerId,
buildApiProfileTab,
buildAvatarPhotoId,
} from './peers';
export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo {
const {
@ -29,7 +36,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts,
starsRating, starsMyPendingRating, starsMyPendingRatingDate,
starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab,
},
users,
} = mtpUserFull;
@ -68,6 +75,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
hasScheduledMessages: hasScheduled,
paidMessagesStars: toJSNumber(sendPaidMessagesStars),
settings: buildApiPeerSettings(settings),
mainTab: mainTab && buildApiProfileTab(mainTab),
};
}

View File

@ -23,6 +23,7 @@ import type {
ApiPoll,
ApiPremiumGiftCodeOption,
ApiPrivacyKey,
ApiProfileTab,
ApiReactionWithPaid,
ApiReportReason,
ApiRequestInputInvoice,
@ -980,3 +981,28 @@ export function buildInputSavedStarGift(inputGift: ApiRequestInputSavedStarGift)
savedId: BigInt(inputGift.savedId),
});
}
export function buildInputProfileTab(profileTab: ApiProfileTab) {
switch (profileTab) {
case 'stories':
return new GramJs.ProfileTabPosts();
case 'gifts':
return new GramJs.ProfileTabGifts();
case 'media':
return new GramJs.ProfileTabMedia();
case 'documents':
return new GramJs.ProfileTabFiles();
case 'audio':
return new GramJs.ProfileTabMusic();
case 'voice':
return new GramJs.ProfileTabVoice();
case 'links':
return new GramJs.ProfileTabLinks();
case 'gif':
return new GramJs.ProfileTabGifs();
default: {
const _exhaustiveCheck: never = profileTab;
return _exhaustiveCheck;
}
}
}

View File

@ -1,11 +1,17 @@
import { Api as GramJs } from '../../../lib/gramjs';
import type {
ApiPeer, ApiPhoto, ApiReportReason,
ApiPeer, ApiPhoto, ApiProfileTab, ApiReportReason,
} from '../../types';
import { buildApiChatLink } from '../apiBuilders/misc';
import { buildInputPeer, buildInputPhoto, buildInputReportReason, DEFAULT_PRIMITIVES } from '../gramjsBuilders';
import {
buildInputPeer,
buildInputPhoto,
buildInputProfileTab,
buildInputReportReason,
DEFAULT_PRIMITIVES,
} from '../gramjsBuilders';
import { invokeRequest } from './client';
export async function reportPeer({
@ -124,3 +130,11 @@ export function setAccountTTL({ days }: { days: number }) {
shouldReturnTrue: true,
});
}
export function setAccountMainProfileTab({ tab }: { tab: ApiProfileTab }) {
return invokeRequest(new GramJs.account.SetMainProfileTab({
tab: buildInputProfileTab(tab),
}), {
shouldReturnTrue: true,
});
}

View File

@ -17,6 +17,7 @@ import type {
ApiPeer,
ApiPeerNotifySettings,
ApiPhoto,
ApiProfileTab,
ApiTopic,
ApiUser,
ApiUserStatus,
@ -51,10 +52,15 @@ import {
buildChatMembers,
getPeerKey,
} from '../apiBuilders/chats';
import { buildApiBotVerification, buildApiPhoto } from '../apiBuilders/common';
import { buildApiPhoto } from '../apiBuilders/common';
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
import { buildApiPeerNotifySettings } from '../apiBuilders/misc';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import {
buildApiBotVerification,
buildApiPeerId,
buildApiPeerNotifySettings,
buildApiProfileTab,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import { buildStickerSet } from '../apiBuilders/symbols';
import { buildApiPeerSettings, buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
import {
@ -66,6 +72,7 @@ import {
buildInputChatReactions,
buildInputPeer,
buildInputPhoto,
buildInputProfileTab,
buildInputReplyTo,
buildInputSuggestedPost,
buildInputUser,
@ -654,6 +661,7 @@ async function getFullChannelInfo(
stargiftsCount,
stargiftsAvailable,
paidMessagesAvailable,
mainTab,
} = result.fullChat;
if (chatPhoto) {
@ -753,6 +761,7 @@ async function getFullChannelInfo(
starGiftCount: stargiftsCount,
areStarGiftsAvailable: Boolean(stargiftsAvailable),
arePaidMessagesAvailable: paidMessagesAvailable,
mainTab: mainTab && buildApiProfileTab(mainTab),
},
chats,
userStatusesById: statusesById,
@ -2092,3 +2101,12 @@ export function toggleAutoTranslation({
shouldReturnTrue: true,
});
}
export function setChannelMainProfileTab({ chat, tab }: { chat: ApiChat; tab: ApiProfileTab }) {
return invokeRequest(new GramJs.channels.SetMainProfileTab({
channel: buildInputChannel(chat.id, chat.accessHash),
tab: buildInputProfileTab(tab),
}), {
shouldReturnTrue: true,
});
}

View File

@ -1494,6 +1494,9 @@ export async function searchMessagesInChat({
case 'profilePhoto':
filter = new GramJs.InputMessagesFilterChatPhotos();
break;
case 'gif':
filter = new GramJs.InputMessagesFilterGif();
break;
case 'text':
default: {
filter = new GramJs.InputMessagesFilterEmpty();

View File

@ -31,9 +31,6 @@ import {
buildApiConfig,
buildApiCountryList,
buildApiLanguage,
buildApiPeerColors,
buildApiPeerNotifySettings,
buildApiPeerProfileColors,
buildApiSession,
buildApiTimezone,
buildApiWallpaper,
@ -41,7 +38,12 @@ import {
buildLangStrings,
oldBuildLangPack,
} from '../apiBuilders/misc';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import {
buildApiPeerColors,
buildApiPeerNotifySettings,
buildApiPeerProfileColors,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import {
buildDisallowedGiftsSettings,
buildInputChannel,

View File

@ -49,12 +49,16 @@ import {
buildMessageDraft,
} from '../apiBuilders/messages';
import {
buildApiPeerNotifySettings,
buildLangStrings,
buildPrivacyKey,
} from '../apiBuilders/misc';
import { buildApiCurrencyAmount } from '../apiBuilders/payments';
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import {
buildApiEmojiStatus,
buildApiPeerId,
buildApiPeerNotifySettings,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import {
buildApiPaidReactionPrivacy,
buildApiReaction,

View File

@ -2,9 +2,17 @@ import type { ApiBotCommand } from './bots';
import type {
ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiInputSuggestedPostInfo, ApiPhoto, ApiStickerSet,
} from './messages';
import type { ApiBotVerification, ApiChatInviteImporter, ApiPeerNotifySettings, ApiRestrictionReason } from './misc';
import type { ApiChatInviteImporter, ApiPeerNotifySettings, ApiRestrictionReason } from './misc';
import type {
ApiEmojiStatusType, ApiFakeType, ApiUser, ApiUsername,
ApiBotVerification,
ApiEmojiStatusType,
ApiFakeType,
ApiPeerColor,
ApiProfileTab,
ApiSendAsPeerId,
} from './peers';
import type {
ApiUser, ApiUsername,
} from './users';
type ApiChatType = (
@ -156,6 +164,7 @@ export interface ApiChatFullInfo {
boostsApplied?: number;
boostsToUnrestrict?: number;
botVerification?: ApiBotVerification;
mainTab?: ApiProfileTab;
}
export interface ApiChatMember {
@ -235,23 +244,6 @@ export interface ApiChatFolder {
hasMyInvites?: true;
}
export interface ApiPeerSettings {
isAutoArchived?: boolean;
canReportSpam?: boolean;
canAddContact?: boolean;
canBlockContact?: boolean;
chargedPaidMessageStars?: number;
registrationMonth?: string;
phoneCountry?: string;
nameChangeDate?: number;
photoChangeDate?: number;
}
export interface ApiSendAsPeerId {
id: string;
isPremium?: boolean;
}
export interface ApiTopic {
id: number;
isClosed?: boolean;
@ -296,11 +288,6 @@ export interface ApiChatlistExportedInvite {
peerIds: string[];
}
export interface ApiPeerColor {
color?: number;
backgroundEmojiId?: string;
}
export interface ApiMissingInvitedUser {
id: string;
isRequiringPremiumToInvite?: boolean;

View File

@ -12,3 +12,4 @@ export * from './statistics';
export * from './stories';
export * from './business';
export * from './stars';
export * from './peers';

View File

@ -4,12 +4,12 @@ import type {
ApiBotInlineResult,
ApiWebDocument,
} from './bots';
import type { ApiPeerColor } from './chats';
import type { ApiMessageAction } from './messageActions';
import type { ApiRestrictionReason } from './misc';
import type {
ApiLabeledPrice,
} from './payments';
import type { ApiPeerColor } from './peers';
import type { ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
import type {
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
@ -957,7 +957,8 @@ export type ApiTranscription = {
transcriptionId: string;
};
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'gif'
| 'profilePhoto';
export type ApiGlobalMessageSearchType = 'text' |
'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'publicPosts';
export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels';

View File

@ -5,6 +5,7 @@ import type { IconName } from '../../types/icons';
import type { RegularLangFnParameters } from '../../util/localization';
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
import type { ApiPremiumSection } from './payments';
import type { ApiBotVerification } from './peers';
import type { ApiStarsSubscriptionPricing } from './stars';
import type { ApiUser } from './users';
@ -287,26 +288,6 @@ export interface ApiConfig {
maxForwardedCount: number;
}
export type ApiPeerColorSet = string[];
export type ApiPeerProfileColorSet = {
paletteColors: string[];
bgColors: string[];
storyColors: string[];
};
export type ApiPeerColorOption<T extends ApiPeerColorSet | ApiPeerProfileColorSet> = {
isHidden?: true;
colors?: T;
darkColors?: T;
};
export interface ApiPeerColors {
general: Record<number, ApiPeerColorOption<ApiPeerColorSet>>;
generalHash?: number;
profile: Record<number, ApiPeerColorOption<ApiPeerProfileColorSet>>;
profileHash?: number;
}
export interface ApiTimezone {
id: string;
name: string;
@ -352,21 +333,6 @@ export interface ApiCollectibleInfo {
url: string;
}
export interface ApiPeerPhotos {
fallbackPhoto?: ApiPhoto;
personalPhoto?: ApiPhoto;
photos: ApiPhoto[];
count: number;
nextOffset?: number;
isLoading?: boolean;
}
export interface ApiBotVerification {
botId: string;
iconId: string;
description: string;
}
export type ApiLimitType =
| 'uploadMaxFileparts'
| 'stickersFaved'

84
src/api/types/peers.ts Normal file
View File

@ -0,0 +1,84 @@
import type { ApiPhoto } from './messages';
export interface ApiPeerPhotos {
fallbackPhoto?: ApiPhoto;
personalPhoto?: ApiPhoto;
photos: ApiPhoto[];
count: number;
nextOffset?: number;
isLoading?: boolean;
}
export type ApiFakeType = 'fake' | 'scam';
export interface ApiBotVerification {
botId: string;
iconId: string;
description: string;
}
export type ApiEmojiStatusType = ApiEmojiStatus | ApiEmojiStatusCollectible;
export interface ApiEmojiStatus {
type: 'regular';
documentId: string;
until?: number;
}
export interface ApiEmojiStatusCollectible {
type: 'collectible';
collectibleId: string;
documentId: string;
title: string;
slug: string;
patternDocumentId: string;
centerColor: string;
edgeColor: string;
patternColor: string;
textColor: string;
until?: number;
}
export interface ApiPeerSettings {
isAutoArchived?: boolean;
canReportSpam?: boolean;
canAddContact?: boolean;
canBlockContact?: boolean;
chargedPaidMessageStars?: number;
registrationMonth?: string;
phoneCountry?: string;
nameChangeDate?: number;
photoChangeDate?: number;
}
export interface ApiSendAsPeerId {
id: string;
isPremium?: boolean;
}
export interface ApiPeerColor {
color?: number;
backgroundEmojiId?: string;
}
export type ApiPeerColorSet = string[];
export type ApiPeerProfileColorSet = {
paletteColors: string[];
bgColors: string[];
storyColors: string[];
};
export type ApiPeerColorOption<T extends ApiPeerColorSet | ApiPeerProfileColorSet> = {
isHidden?: true;
colors?: T;
darkColors?: T;
};
export interface ApiPeerColors {
general: Record<number, ApiPeerColorOption<ApiPeerColorSet>>;
generalHash?: number;
profile: Record<number, ApiPeerColorOption<ApiPeerProfileColorSet>>;
profileHash?: number;
}
export type ApiProfileTab = 'stories' | 'gifts' | 'media' | 'documents' | 'audio' | 'voice' | 'links' | 'gif';

View File

@ -18,7 +18,6 @@ import type {
ApiChatFullInfo,
ApiChatMember,
ApiDraft,
ApiPeerSettings,
ApiTypingStatus,
} from './chats';
import type {
@ -43,11 +42,12 @@ import type {
ApiPeerNotifySettings,
ApiSessionData,
} from './misc';
import type { ApiEmojiStatusType, ApiPeerSettings } from './peers';
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
import type { ApiTypeCurrencyAmount } from './stars';
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
import type {
ApiEmojiStatusType, ApiUser, ApiUserFullInfo, ApiUserStatus,
ApiUser, ApiUserFullInfo, ApiUserStatus,
} from './users';
export type ApiUpdateReady = {

View File

@ -1,9 +1,15 @@
import type { API_CHAT_TYPES } from '../../config';
import type { ApiBotInfo } from './bots';
import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business';
import type { ApiPeerColor, ApiPeerSettings } from './chats';
import type { ApiDocument, ApiPhoto } from './messages';
import type { ApiBotVerification } from './misc';
import type {
ApiBotVerification,
ApiEmojiStatusType,
ApiFakeType,
ApiPeerColor,
ApiPeerSettings,
ApiProfileTab,
} from './peers';
import type { ApiSavedStarGift, ApiStarsRating } from './stars';
export interface ApiUser {
@ -75,10 +81,9 @@ export interface ApiUserFullInfo {
botVerification?: ApiBotVerification;
paidMessagesStars?: number;
settings?: ApiPeerSettings;
mainTab?: ApiProfileTab;
}
export type ApiFakeType = 'fake' | 'scam';
export type ApiUserType = 'userTypeBot' | 'userTypeRegular' | 'userTypeDeleted' | 'userTypeUnknown';
export interface ApiUserStatus {
@ -136,28 +141,6 @@ export interface ApiAttachBotIcon {
document: ApiDocument;
}
export type ApiEmojiStatusType = ApiEmojiStatus | ApiEmojiStatusCollectible;
export interface ApiEmojiStatus {
type: 'regular';
documentId: string;
until?: number;
}
export interface ApiEmojiStatusCollectible {
type: 'collectible';
collectibleId: string;
documentId: string;
title: string;
slug: string;
patternDocumentId: string;
centerColor: string;
edgeColor: string;
patternColor: string;
textColor: string;
until?: number;
}
export interface ApiBirthday {
day: number;
month: number;

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="32"><path d="M15.33 15.009a7.105 7.105 0 1 1 .001-14.21 7.105 7.105 0 0 1-.001 14.21zM1.122 27.236c0-1.363.339-2.725 1.306-3.686 1.817-1.804 5.722-4.482 12.901-4.482s11.085 2.678 12.901 4.482c.967.96 1.306 2.322 1.306 3.686a4.011 4.011 0 0 1-3.984 4.01H5.13a4.011 4.011 0 0 1-4.011-4.01zm30.601 4.01a7.346 7.346 0 0 0 1.184-4.01c0-1.839-.45-4.238-2.302-6.076l-.042-.041c.326-.013.66-.02 1.004-.02 6.145 0 9.493 2.226 11.052 3.729.83.8 1.126 1.952 1.126 3.105a3.314 3.314 0 0 1-3.314 3.314h-8.708zm-.156-14.207a6.089 6.089 0 1 1 0-12.178 6.089 6.089 0 0 1 0 12.178z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M11.002 15.286a5 5 0 1 1 0-10 5 5 0 0 1 0 10m-10 8.606c0-.96.239-1.918.92-2.595C3.2 20.027 5.947 18.143 11 18.143c5.052 0 7.802 1.885 9.08 3.154.68.676.919 1.635.919 2.595a2.823 2.823 0 0 1-2.804 2.822H3.823A2.823 2.823 0 0 1 1 23.892zm21.537 2.822a5.17 5.17 0 0 0 .833-2.822c0-1.295-.316-2.983-1.62-4.277l-.03-.028q.345-.015.707-.015c4.325 0 6.681 1.567 7.779 2.625.584.563.792 1.374.792 2.185a2.33 2.33 0 0 1-2.332 2.333zm-.11-9.999a4.285 4.285 0 1 1 0-8.57 4.285 4.285 0 0 1 0 8.57" /></svg>

Before

Width:  |  Height:  |  Size: 632 B

After

Width:  |  Height:  |  Size: 567 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M0 15C0 6.716 6.716 0 15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15Zm13.636-6.818a1.364 1.364 0 1 0 0 2.727h.682c.377 0 .682.305.682.682v8.864a1.364 1.364 0 1 0 2.727 0V11.59a3.41 3.41 0 0 0-3.409-3.41z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M1 16C1 7.716 7.716 1 16 1s15 6.716 15 15-6.716 15-15 15S1 24.284 1 16m13.636-6.818a1.364 1.364 0 1 0 0 2.727h.682c.377 0 .682.305.682.682v8.864a1.364 1.364 0 1 0 2.727 0V12.59a3.41 3.41 0 0 0-3.409-3.41Z"/></svg>

Before

Width:  |  Height:  |  Size: 335 B

After

Width:  |  Height:  |  Size: 309 B

View File

@ -1434,6 +1434,8 @@
"ProfileBirthdayValueYear" = "{date} ({age} years old)";
"ProfileBirthdayTodayValue" = "🎂 {date}";
"ProfileBirthdayTodayValueYear" = "🎂 {date} ({age} years old)";
"ProfileMenuSetMainTab" = "Set as Main Tab";
"ProfileItemSubscribers" = "Subscribers";
"MonetizationInfoTONTitle" = "What is 💎 TON?";
"ChannelEarnLearnCoinAbout" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
"MonetizationBalanceZeroInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by advertisers to pay for ads. {link}";
@ -1710,6 +1712,7 @@
"ProfileTabLinks" = "Links";
"ProfileTabMusic" = "Music";
"ProfileTabVoice" = "Voice";
"ProfileTabGifs" = "GIF";
"ProfileTabSharedGroups" = "Groups";
"ProfileTabSimilarChannels" = "Similar Channels";
"ProfileTabSimilarBots" = "Similar Bots";

View File

@ -3,6 +3,7 @@ import { memo, useEffect, useRef, useState } from '../../lib/teact/teact';
import type { ApiSticker } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { ANIMATION_LEVEL_MIN } from '../../config';
import buildClassName from '../../util/buildClassName';
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
@ -38,7 +39,7 @@ const AnimatedTabList = ({
const clipPathContainerRef = useRef<HTMLDivElement>();
const selectedIndex = items.findIndex((item) => item.id === selectedItemId) || 0;
const [clipPath, setClipPath] = useState<string>('');
const shouldAnimate = animationLevel > 0;
const shouldAnimate = animationLevel > ANIMATION_LEVEL_MIN;
useHorizontalScroll(containerRef, !items.length, true);

View File

@ -1,4 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import { memo, useRef } from '../../lib/teact/teact';
import type { ApiMessage } from '../../api/types';
@ -8,7 +7,9 @@ import {
getMessageHtmlId,
getMessageIsSpoiler,
getMessageVideo,
getVideoMediaHash,
} from '../../global/helpers';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { formatMediaDuration } from '../../util/dates/dateFormat';
import stopEvent from '../../util/stopEvent';
@ -21,6 +22,7 @@ import useLastCallback from '../../hooks/useLastCallback';
import useMedia from '../../hooks/useMedia';
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
import OptimizedVideo from '../ui/OptimizedVideo';
import MediaSpoiler from './MediaSpoiler';
import './Media.scss';
@ -29,26 +31,32 @@ type OwnProps = {
message: ApiMessage;
idPrefix?: string;
isProtected?: boolean;
canAutoPlay?: boolean;
observeIntersection?: ObserveFn;
onClick?: (messageId: number, chatId: string) => void;
};
const Media: FC<OwnProps> = ({
const Media = ({
message,
idPrefix = 'shared-media',
isProtected,
canAutoPlay,
observeIntersection,
onClick,
}) => {
}: OwnProps) => {
const ref = useRef<HTMLDivElement>();
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const [isHovering, markMouseOver, markMouseOut] = useFlag();
const thumbDataUri = useThumbnail(message);
const mediaHash = useMessageMediaHash(message, 'pictogram');
const mediaBlobUrl = useMedia(mediaHash, !isIntersecting);
const transitionClassNames = useMediaTransitionDeprecated(mediaBlobUrl);
const video = getMessageVideo(message);
const fullGiftHash = video?.isGif ? getVideoMediaHash(video, 'full') : undefined;
const fullGifBlobUrl = useMedia(fullGiftHash, !isIntersecting);
const hasSpoiler = getMessageIsSpoiler(message);
const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler);
@ -64,6 +72,8 @@ const Media: FC<OwnProps> = ({
id={`${idPrefix}${getMessageHtmlId(message.id)}`}
className="Media scroll-item"
onClick={onClick ? handleClick : undefined}
onMouseOver={!IS_TOUCH_ENV ? markMouseOver : undefined}
onMouseOut={!IS_TOUCH_ENV ? markMouseOut : undefined}
>
<img
src={thumbDataUri}
@ -73,14 +83,28 @@ const Media: FC<OwnProps> = ({
decoding="async"
onContextMenu={isProtected ? stopEvent : undefined}
/>
<img
src={mediaBlobUrl}
className={buildClassName('full-media', 'media-miniature', transitionClassNames)}
alt=""
draggable={!isProtected}
decoding="async"
onContextMenu={isProtected ? stopEvent : undefined}
/>
{fullGifBlobUrl ? (
<OptimizedVideo
canPlay={isIntersecting && !hasSpoiler && isHovering && Boolean(canAutoPlay)}
src={fullGifBlobUrl}
className={buildClassName('full-media', 'media-miniature', transitionClassNames)}
muted
loop
playsInline
draggable={false}
disablePictureInPicture
onContextMenu={isProtected ? stopEvent : undefined}
/>
) : (
<img
src={mediaBlobUrl}
className={buildClassName('full-media', 'media-miniature', transitionClassNames)}
alt=""
draggable={false}
decoding="async"
onContextMenu={isProtected ? stopEvent : undefined}
/>
)}
{hasSpoiler && (
<MediaSpoiler
thumbDataUri={mediaBlobUrl || thumbDataUri}

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../lib/teact/teact';
import {
memo, useMemo,
} from '../../../lib/teact/teact';
@ -12,8 +11,8 @@ import type {
ApiUserFullInfo,
ApiUsername,
} from '../../../api/types';
import type { BotAppPermissions } from '../../../types';
import { MAIN_THREAD_ID } from '../../../api/types';
import { type BotAppPermissions, ManagementScreens } from '../../../types';
import {
FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH, MUTE_INDEFINITE_TIMESTAMP, UNMUTE_TIMESTAMP,
@ -22,6 +21,7 @@ import {
buildStaticMapHash,
getChatLink,
getHasAdminRight,
isChatAdmin,
isChatChannel,
isUserRightBanned,
} from '../../../global/helpers';
@ -92,6 +92,7 @@ type StateProps = {
isBotCanManageEmojiStatus?: boolean;
botAppPermissions?: BotAppPermissions;
botVerification?: ApiBotVerification;
canViewSubscribers?: boolean;
};
const DEFAULT_MAP_CONFIG = {
@ -102,7 +103,7 @@ const DEFAULT_MAP_CONFIG = {
const BOT_VERIFICATION_ICON_SIZE = 16;
const ChatExtra: FC<OwnProps & StateProps> = ({
const ChatExtra = ({
chatOrUserId,
user,
chat,
@ -124,7 +125,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
className,
style,
isInSettings,
}) => {
canViewSubscribers,
}: OwnProps & StateProps) => {
const {
showNotification,
updateChatMutedState,
@ -136,6 +138,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
requestMainWebView,
toggleUserEmojiStatusPermission,
toggleUserLocationPermission,
requestNextManagementScreen,
} = getActions();
const {
@ -261,6 +264,10 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
copy(formatUsername(username.username, isChat), oldLang(isChat ? 'Link' : 'Username'));
});
const handleOpenSubscribers = useLastCallback(() => {
requestNextManagementScreen({ screen: ManagementScreens.ChannelSubscribers });
});
const handleOpenApp = useLastCallback(() => {
const botId = user?.id;
if (!botId) {
@ -476,6 +483,12 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
/>
</ListItem>
)}
{canViewSubscribers && (
<ListItem icon="group" narrow multiline ripple onClick={handleOpenSubscribers}>
<div className="title">{lang('ProfileItemSubscribers')}</div>
<span className="subtitle">{lang.number(chat?.membersCount || 0)}</span>
</ListItem>
)}
{botVerification && (
<div className={styles.botVerificationSection}>
<CustomEmoji
@ -510,6 +523,7 @@ export default memo(withGlobal<OwnProps>(
const chatInviteLink = chatFullInfo?.inviteLink;
const description = userFullInfo?.bio || chatFullInfo?.about;
const canViewSubscribers = chat && isChatChannel(chat) && isChatAdmin(chat);
const canInviteUsers = chat && !user && (
(!isChatChannel(chat) && !isUserRightBanned(chat, 'inviteUsers'))
|| getHasAdminRight(chat, 'inviteUsers')
@ -542,6 +556,7 @@ export default memo(withGlobal<OwnProps>(
hasMainMiniApp,
isBotCanManageEmojiStatus: userFullInfo?.isBotCanManageEmojiStatus,
botVerification,
canViewSubscribers,
};
},
)(ChatExtra));

View File

@ -18,7 +18,8 @@ import { type MediaViewerMedia, MediaViewerOrigin, type ThreadId } from '../../t
import { ANIMATION_END_DELAY } from '../../config';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
import {
getChatMediaMessageIds, getMessagePaidMedia, isChatAdmin,
getMessageContentIds,
getMessagePaidMedia, isChatAdmin,
} from '../../global/helpers';
import {
selectChatMessage,
@ -156,7 +157,7 @@ const MediaViewer = ({
bestData,
dimensions,
isGif,
isFromSharedMedia,
contentType,
} = useMediaProps({
media, isAvatar: Boolean(avatarOwner), origin, delay: isGhostAnimation && ANIMATION_DURATION,
});
@ -173,8 +174,8 @@ const MediaViewer = ({
const messageMediaIds = useMemo(() => {
return withDynamicLoading
? collectedMessageIds
: getChatMediaMessageIds(chatMessages || {}, collectedMessageIds || [], isFromSharedMedia);
}, [chatMessages, collectedMessageIds, isFromSharedMedia, withDynamicLoading]);
: getMessageContentIds(chatMessages || {}, collectedMessageIds || [], contentType);
}, [chatMessages, collectedMessageIds, contentType, withDynamicLoading]);
if (isOpen && (!prevSenderId || prevSenderId !== senderId || animationKey.current === undefined)) {
animationKey.current = isSingle ? 0 : (messageId || mediaIndex);
@ -520,7 +521,7 @@ export default memo(withGlobal(
const currentItem = getMediaViewerItem({
avatarOwner, standaloneMedia, profilePhotos, mediaIndex,
});
const viewableMedia = selectViewableMedia(global, currentItem);
const viewableMedia = selectViewableMedia(global, origin, currentItem);
return {
profilePhotos,
@ -565,6 +566,11 @@ export default memo(withGlobal(
}
}
const currentItem = getMediaViewerItem({
message, standaloneMedia, mediaIndex, sponsoredMessage,
});
const viewableMedia = selectViewableMedia(global, origin, currentItem);
let chatMessages: Record<number, ApiMessage> | undefined;
if (chatId) {
@ -588,7 +594,8 @@ export default memo(withGlobal(
collectedMessageIds = foundIds;
} else if (origin === MediaViewerOrigin.SharedMedia) {
const currentSearch = selectCurrentSharedMediaSearch(global);
const { foundIds } = (currentSearch && currentSearch.resultsByType && currentSearch.resultsByType.media) || {};
const resultsByType = currentSearch?.resultsByType;
const { foundIds } = (viewableMedia?.isGif ? resultsByType?.gif : resultsByType?.media) || {};
collectedMessageIds = foundIds;
} else if (isOriginInline || isOriginAlbum) {
const outlyingList = selectOutlyingListByMessageId(global, chatId, threadId, messageId);
@ -596,11 +603,6 @@ export default memo(withGlobal(
}
}
const currentItem = getMediaViewerItem({
message, standaloneMedia, mediaIndex, sponsoredMessage,
});
const viewableMedia = selectViewableMedia(global, currentItem);
return {
chatId,
threadId,

View File

@ -413,7 +413,7 @@ export default memo(withGlobal<OwnProps>(
const canDelete = canDeleteMessage || canDeleteAvatar;
const canUpdate = canUpdateMedia && Boolean(avatarPhoto) && !isCurrentAvatar;
const messageListType = currentMessageList?.type;
const viewableMedia = selectViewableMedia(global, item);
const viewableMedia = selectViewableMedia(global, origin, item);
return {
activeDownloads,

View File

@ -257,7 +257,7 @@ export default memo(withGlobal<OwnProps>(
const message = item.type === 'message' ? item.message : undefined;
const sponsoredMessage = item.type === 'sponsoredMessage' ? item.message : undefined;
const textMessage = message || sponsoredMessage;
const viewableMedia = selectViewableMedia(global, item);
const viewableMedia = selectViewableMedia(global, origin, item);
const maxTimestamp = message && selectMessageTimestampableDuration(global, message, true);

View File

@ -2,7 +2,7 @@ import type {
ApiMessage, ApiPeer, ApiPeerPhotos, ApiSponsoredMessage,
} from '../../../api/types';
import type { GlobalState } from '../../../global/types';
import type { MediaViewerMedia } from '../../../types';
import { type MediaViewerMedia, MediaViewerOrigin } from '../../../types';
import { getMessageContent, isDocumentPhoto, isDocumentVideo } from '../../../global/helpers';
import { selectWebPageFromMessage } from '../../../global/selectors';
@ -28,6 +28,7 @@ export type MediaViewerItem = {
export type ViewableMedia = {
media: MediaViewerMedia;
isGif?: boolean;
isSingle?: boolean;
};
@ -77,12 +78,16 @@ export function getMediaViewerItem({
return undefined;
}
export default function selectViewableMedia(global: GlobalState, params?: MediaViewerItem): ViewableMedia | undefined {
export default function selectViewableMedia(
global: GlobalState, origin?: MediaViewerOrigin, params?: MediaViewerItem,
): ViewableMedia | undefined {
if (!params) return undefined;
if (params.type === 'standalone') {
const media = params.media[params.mediaIndex];
return {
media: params.media[params.mediaIndex],
media,
isGif: media.mediaType === 'video' && media.isGif,
isSingle: params.media.length === 1,
};
}
@ -134,6 +139,7 @@ export default function selectViewableMedia(global: GlobalState, params?: MediaV
const { photo: extendedPhoto, video: extendedVideo } = extendedMedia;
return {
media: (extendedPhoto || extendedVideo)!,
isGif: extendedVideo?.isGif,
};
}
}
@ -143,7 +149,8 @@ export default function selectViewableMedia(global: GlobalState, params?: MediaV
if (media) {
return {
media,
isSingle: video?.isGif,
isGif: video?.isGif,
isSingle: video?.isGif && origin !== MediaViewerOrigin.SharedMedia,
};
}

View File

@ -1,7 +1,7 @@
import { useMemo } from '../../../lib/teact/teact';
import type { MediaViewerMedia } from '../../../types';
import { ApiMediaFormat } from '../../../api/types';
import { ApiMediaFormat, type ApiMessageSearchType } from '../../../api/types';
import { MediaViewerOrigin } from '../../../types';
import {
@ -46,6 +46,8 @@ export const useMediaProps = ({
const isFromSharedMedia = origin === MediaViewerOrigin.SharedMedia;
const isFromSearch = origin === MediaViewerOrigin.SearchResult;
const contentType: ApiMessageSearchType = isGif ? 'gif' : 'media';
const getMediaOrAvatarHash = useMemo(() => (isFull?: boolean) => {
if (!media) return undefined;
@ -133,7 +135,7 @@ export const useMediaProps = ({
bestImageData,
bestData,
dimensions,
isFromSharedMedia,
contentType,
isVideoAvatar,
isLocal,
loadProgress,

View File

@ -1,6 +1,8 @@
@use '../../styles/mixins';
.Profile {
scrollbar-gutter: stable;
overflow-x: hidden;
overflow-y: scroll;
display: flex;
@ -134,6 +136,7 @@
&.storiesArchive-list,
&.stories-list,
&.media-list,
&.gif-list,
&.previewMedia-list,
&.gifts-list {
display: grid;

View File

@ -6,6 +6,7 @@ import type {
ApiChat,
ApiChatMember,
ApiMessage,
ApiProfileTab,
ApiSavedStarGift,
ApiStarGiftCollection,
ApiStoryAlbum,
@ -29,7 +30,6 @@ import {
getIsDownloading,
getIsSavedDialog,
getMessageDocument,
isChatAdmin,
isChatChannel,
isChatGroup,
isUserBot,
@ -38,6 +38,7 @@ import {
import { getSavedGiftKey } from '../../global/helpers/stars';
import {
selectActiveDownloads,
selectCanUpdateMainTab,
selectChat,
selectChatFullInfo,
selectChatMessages,
@ -47,6 +48,7 @@ import {
selectIsRightColumnShown,
selectMonoforumChannel,
selectPeerStories,
selectPerformanceSettingsValue,
selectSimilarBotsIds,
selectSimilarChannelIds,
selectTabState,
@ -86,6 +88,7 @@ import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
import useSyncEffect from '../../hooks/useSyncEffect';
import useSyncEffectWithPrevDeps from '../../hooks/useSyncEffectWithPrevDeps.ts';
import useAsyncRendering from './hooks/useAsyncRendering';
import useProfileState from './hooks/useProfileState';
import useProfileViewportIds from './hooks/useProfileViewportIds';
@ -112,7 +115,7 @@ import InfiniteScroll from '../ui/InfiniteScroll';
import Link from '../ui/Link';
import ListItem, { type MenuItemContextAction } from '../ui/ListItem';
import Spinner from '../ui/Spinner';
import TabList from '../ui/TabList';
import TabList, { type TabWithProperties } from '../ui/TabList';
import Transition from '../ui/Transition';
import DeleteMemberModal from './DeleteMemberModal';
import StarGiftCollectionList from './gifts/StarGiftCollectionList';
@ -179,24 +182,40 @@ type StateProps = {
isSavedMessages?: boolean;
isSynced?: boolean;
hasAvatar?: boolean;
mainTab?: ApiProfileTab;
canUpdateMainTab?: boolean;
canAutoPlayGifs?: boolean;
};
type TabProps = {
type LocalTabProps = {
type: ProfileTabType;
key: RegularLangKey;
};
const TABS: TabProps[] = [
type TabWithPropertiesAndType = TabWithProperties & {
type: ProfileTabType;
};
const TABS: LocalTabProps[] = [
{ type: 'media', key: 'ProfileTabMedia' },
{ type: 'documents', key: 'ProfileTabFiles' },
{ type: 'links', key: 'ProfileTabLinks' },
{ type: 'audio', key: 'ProfileTabMusic' },
{ type: 'gif', key: 'ProfileTabGifs' },
];
const HIDDEN_RENDER_DELAY = 1000;
const INTERSECTION_THROTTLE = 500;
const SHARED_MEDIA_TYPES = new Set<string>(['media', 'documents', 'links', 'audio', 'voice']);
const VALID_CHANNEL_MAIN_TAB_TYPES = new Set<StringAutocomplete<ApiProfileTab>>([
'stories', 'gifts', 'media', 'documents', 'audio', 'voice', 'links', 'gif',
]);
const VALID_USER_MAIN_TAB_TYPES = new Set<StringAutocomplete<ApiProfileTab>>([
'stories', 'gifts',
]);
const SHARED_MEDIA_TYPES = new Set<StringAutocomplete<SharedMediaType>>([
'media', 'documents', 'links', 'audio', 'voice', 'gif',
]);
const Profile = ({
chatId,
@ -252,6 +271,9 @@ const Profile = ({
isSavedMessages,
isSynced,
hasAvatar,
mainTab,
canUpdateMainTab,
canAutoPlayGifs,
onProfileStateChange,
}: OwnProps & StateProps) => {
const {
@ -276,11 +298,14 @@ const Profile = ({
loadStoryAlbums,
resetSelectedStoryAlbum,
changeProfileTab,
setMainProfileTab,
} = getActions();
const containerRef = useRef<HTMLDivElement>();
const transitionRef = useRef<HTMLDivElement>();
const shouldSkipTransitionRef = useRef(false);
const oldLang = useOldLang();
const lang = useLang();
@ -296,8 +321,11 @@ const Profile = ({
const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0);
const isUser = isUserId(chatId);
const validMainTabTypes = isUser ? VALID_USER_MAIN_TAB_TYPES : VALID_CHANNEL_MAIN_TAB_TYPES;
const tabs = useMemo(() => {
const arr: TabProps[] = [];
const arr: LocalTabProps[] = [];
if (isGeneralSavedMessages) {
arr.push({ type: 'dialogs', key: 'ProfileTabSavedDialogs' });
}
@ -306,16 +334,16 @@ const Profile = ({
arr.push({ type: 'stories', key: 'ProfileTabStories' });
}
if (hasStoriesTab && isOwnProfile) {
arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' });
}
if (hasGiftsTab) {
arr.push({ type: 'gifts', key: 'ProfileTabGifts' });
}
if (hasStoriesTab && isOwnProfile) {
arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' });
}
if (hasMembersTab && !isOwnProfile) {
arr.push({ type: 'members', key: isChannel ? 'ProfileTabSubscribers' : 'ProfileTabMembers' });
arr.push({ type: 'members', key: 'ProfileTabMembers' });
}
if (hasPreviewMediaTab && !isOwnProfile) {
@ -349,13 +377,35 @@ const Profile = ({
arr.push(TABS[0]);
}
return arr.map((tab) => ({
type: tab.type,
title: lang(tab.key),
}));
if (mainTab) {
const mainTabIndex = arr.findIndex((tab) => tab.type === mainTab);
if (mainTabIndex !== -1) {
const newFirstTab = arr[mainTabIndex];
arr.splice(mainTabIndex, 1);
arr.unshift(newFirstTab);
}
}
return arr.map((tab) => {
const contextActions: MenuItemContextAction[] | undefined = canUpdateMainTab && mainTab !== tab.type
&& validMainTabTypes.has(tab.type) ? [{
title: lang('ProfileMenuSetMainTab'),
icon: 'replace',
handler: () => {
setMainProfileTab({ chatId, tab: tab.type as ApiProfileTab });
},
}] : undefined;
return {
type: tab.type,
title: lang(tab.key),
contextActions,
} satisfies TabWithPropertiesAndType;
});
}, [
isGeneralSavedMessages, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo,
hasCommonChatsTab, isChannel, isBot, similarChannels?.length, similarBots?.length, lang, isOwnProfile,
mainTab, chatId, canUpdateMainTab, validMainTabTypes,
]);
const [allowAutoScrollToTabs, startAutoScrollToTabsIfNeeded, stopAutoScrollToTabs] = useFlag(false);
@ -377,6 +427,11 @@ const Profile = ({
setActiveTab(tabs[0].type); // Set default tab
}, [isClosed, profileTab, tabs]);
useEffectWithPrevDeps(([prevMainTab]) => {
if (prevMainTab || !mainTab) return;
setActiveTab(mainTab); // Only focus when loading full info
}, [mainTab]);
const handleSwitchTab = useCallback((index: number) => {
startAutoScrollToTabsIfNeeded();
setActiveTab(tabs[index].type);
@ -430,6 +485,17 @@ const Profile = ({
return index === -1 ? 0 : index;
}, [profileTab, tabs]);
// Reset skip transition flag from previous render
if (shouldSkipTransitionRef.current) {
shouldSkipTransitionRef.current = false;
}
useSyncEffectWithPrevDeps(([prevProfileTab, prevActiveTabIndex]) => {
if (prevProfileTab === profileTab && prevActiveTabIndex !== activeTabIndex) {
shouldSkipTransitionRef.current = true;
}
}, [profileTab, activeTabIndex]);
const tabType = tabs[activeTabIndex].type;
const handleLoadCommonChats = useCallback(() => {
loadCommonChats({ userId: chatId });
@ -498,10 +564,7 @@ const Profile = ({
const shouldRenderProfileInfo = !noProfileInfo && !isSavedMessages;
const isFirstTab = (isGeneralSavedMessages && resultType === 'dialogs')
|| (hasStoriesTab && resultType === 'stories')
|| resultType === 'members'
|| (!hasMembersTab && resultType === 'media');
const isFirstTab = tabs[0].type === resultType;
const activeKey = tabs.findIndex(({ type }) => type === resultType);
const [isGiftCollectionsShowed, markGiftCollectionsShowed, unmarkGiftCollectionsShowed] = useFlag(false);
@ -561,7 +624,7 @@ const Profile = ({
handleStopAutoScrollToTabs,
});
const { applyTransitionFix, releaseTransitionFix } = useTransitionFixes(containerRef);
useTransitionFixes(containerRef);
const [cacheBuster, resetCacheBuster] = useCacheBuster();
@ -570,11 +633,6 @@ const Profile = ({
throttleMs: INTERSECTION_THROTTLE,
});
const handleTransitionStop = useLastCallback(() => {
releaseTransitionFix();
resetCacheBuster();
});
const handleNewMemberDialogOpen = useLastCallback(() => {
setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.InProgress });
});
@ -613,16 +671,6 @@ const Profile = ({
setDeletingUserId(undefined);
});
useEffectWithPrevDeps(([prevHasMemberTabs]) => {
if (prevHasMemberTabs === undefined || activeTabIndex === 0 || prevHasMemberTabs === hasMembersTab) {
return;
}
const newActiveTab = Math.min(activeTabIndex + (hasMembersTab ? 1 : -1), tabs.length - 1);
setActiveTab(tabs[newActiveTab].type);
}, [hasMembersTab, activeTabIndex, tabs]);
const handleResetGiftsFilter = useLastCallback(() => {
resetGiftProfileFilter({ peerId: chatId });
});
@ -800,6 +848,9 @@ const Profile = ({
case 'storiesArchive':
text = oldLang('StoryList.ArchivedEmptyState.Title');
break;
case 'gif':
text = oldLang('lng_media_gif_empty');
break;
default:
text = oldLang('SharedMedia.EmptyTitle');
}
@ -825,15 +876,16 @@ const Profile = ({
shouldShowContentPanel && 'showContentPanel',
noTransition && 'noTransition',
)}
dir={oldLang.isRtl && resultType === 'media' ? 'rtl' : undefined}
dir={lang.isRtl && (resultType === 'media' || resultType === 'gif') ? 'rtl' : undefined}
teactFastList
>
{resultType === 'media' ? (
{resultType === 'media' || resultType === 'gif' ? (
(viewportIds as number[]).map((id) => messagesById[id] && (
<Media
key={id}
message={messagesById[id]}
isProtected={isChatProtected || messagesById[id].isProtected}
canAutoPlay={canAutoPlayGifs}
observeIntersection={observeIntersectionForMedia}
onClick={handleSelectMedia}
/>
@ -988,17 +1040,10 @@ const Profile = ({
onClick={() => openChat({ id: userId })}
>
{isUserId(userId) ? (
<PrivateChatInfo
userId={userId}
avatarSize="medium"
/>
) : (
<GroupChatInfo
chatId={userId}
avatarSize="medium"
/>
)}
<PrivateChatInfo
userId={userId}
avatarSize="medium"
/>
</ListItem>
))}
{!isCurrentUserPremium && (
@ -1081,7 +1126,7 @@ const Profile = ({
<Transition
className={`${resultType}-list`}
activeKey={contentTransitionKey}
name={resolveTransitionName('slideOptimized', animationLevel, undefined, oldLang.isRtl)}
name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
shouldCleanup
shouldRestoreHeight
restoreHeightKey={restoreContentHeightKey}
@ -1149,13 +1194,13 @@ const Profile = ({
>
<Transition
ref={transitionRef}
name={resolveTransitionName('slideOptimized', animationLevel, undefined, oldLang.isRtl)}
name={shouldSkipTransitionRef.current ? 'none'
: resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
activeKey={activeKey}
renderCount={tabs.length}
shouldRestoreHeight
className="shared-media-transition"
onStart={applyTransitionFix}
onStop={handleTransitionStop}
onStop={resetCacheBuster}
restoreHeightKey={shouldUseTransitionForContent ? restoreContentHeightKey : undefined}
contentSelector={shouldUseTransitionForContent
? '.Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .content'
@ -1218,8 +1263,7 @@ export default memo(withGlobal<OwnProps>(
const isGroup = chat && isChatGroup(chat);
const isChannel = chat && isChatChannel(chat);
const isBot = user && isUserBot(user);
const hasMembersTab = !isTopicInfo && !isSavedDialog
&& (isGroup || (isChannel && isChatAdmin(chat))) && !chat?.isMonoforum;
const hasMembersTab = !isTopicInfo && !isSavedDialog && isGroup && !chat?.isMonoforum;
const members = chatFullInfo?.members;
const adminMembersById = chatFullInfo?.adminMembersById;
const areMembersHidden = hasMembersTab && chat
@ -1265,6 +1309,8 @@ export default memo(withGlobal<OwnProps>(
const isRestricted = chat && selectIsChatRestricted(global, chat.id);
const hasAvatar = Boolean(peer?.avatarPhotoId);
const canAutoPlayGifs = selectPerformanceSettingsValue(global, 'autoplayGifs');
return {
theme: selectTheme(global),
isChannel,
@ -1315,6 +1361,9 @@ export default memo(withGlobal<OwnProps>(
commonChatIds: commonChats?.ids,
monoforumChannel,
hasAvatar,
mainTab: peerFullInfo?.mainTab,
canUpdateMainTab: selectCanUpdateMainTab(global, chatId),
canAutoPlayGifs,
};
},
)(Profile));

View File

@ -89,6 +89,10 @@ export default function useProfileViewportIds({
'media', resultType, searchMessages, chatMessages, foundIds, threadId,
);
const [gifViewportIds, getMoreGifs, noProfileInfoForGifs] = useInfiniteScrollForSharedMedia(
'gif', resultType, searchMessages, chatMessages, foundIds, threadId,
);
const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia(
'documents', resultType, searchMessages, chatMessages, foundIds, threadId,
);
@ -153,6 +157,11 @@ export default function useProfileViewportIds({
getMore = getMoreMedia;
noProfileInfo = noProfileInfoForMedia;
break;
case 'gif':
viewportIds = gifViewportIds;
getMore = getMoreGifs;
noProfileInfo = noProfileInfoForGifs;
break;
case 'documents':
viewportIds = documentViewportIds;
getMore = getMoreDocuments;

View File

@ -1,9 +1,7 @@
import type { ElementRef } from '../../../lib/teact/teact';
import { useEffect } from '../../../lib/teact/teact';
import { requestMeasure, requestMutation } from '../../../lib/fasterdom/fasterdom';
import useLastCallback from '../../../hooks/useLastCallback';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
export default function useTransitionFixes(
containerRef: ElementRef<HTMLDivElement>,
@ -32,28 +30,4 @@ export default function useTransitionFixes(
window.removeEventListener('resize', setMinHeight, false);
};
}, [containerRef, transitionElSelector]);
// Workaround for scrollable content flickering during animation.
const applyTransitionFix = useLastCallback(() => {
// This callback is called from `Transition.onStart` which is "mutate" phase
requestMeasure(() => {
const container = containerRef.current!;
if (container.style.overflowY === 'hidden') return;
const scrollBarWidth = container.offsetWidth - container.clientWidth;
requestMutation(() => {
container.style.overflowY = 'hidden';
container.style.paddingRight = `${scrollBarWidth}px`;
});
});
});
const releaseTransitionFix = useLastCallback(() => {
const container = containerRef.current!;
container.style.overflowY = 'scroll';
container.style.paddingRight = '0';
});
return { applyTransitionFix, releaseTransitionFix };
}

View File

@ -1,5 +1,4 @@
import type { FC, TeactNode } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import type { TeactNode } from '../../lib/teact/teact';
import { useEffect, useLayoutEffect, useRef } from '../../lib/teact/teact';
import type { MenuItemContextAction } from './ListItem';
@ -40,7 +39,7 @@ const classNames = {
badgeActive: 'Tab__badge--active',
};
const Tab: FC<OwnProps> = ({
const Tab = ({
className,
title,
isActive,
@ -48,11 +47,11 @@ const Tab: FC<OwnProps> = ({
badgeCount,
isBadgeActive,
previousActiveTab,
onClick,
clickArg,
contextActions,
contextRootElementSelector,
}) => {
clickArg,
onClick,
}: OwnProps) => {
const tabRef = useRef<HTMLDivElement>();
useLayoutEffect(() => {

View File

@ -1,4 +1,4 @@
import type { FC, TeactNode } from '../../lib/teact/teact';
import type { TeactNode } from '../../lib/teact/teact';
import { memo, useEffect, useRef } from '../../lib/teact/teact';
import type { MenuItemContextAction } from './ListItem';
@ -37,10 +37,14 @@ const TAB_SCROLL_THRESHOLD_PX = 16;
// Should match duration from `--slide-transition` CSS variable
const SCROLL_DURATION = IS_IOS ? 450 : IS_ANDROID ? 400 : 300;
const TabList: FC<OwnProps> = ({
tabs, activeTab, onSwitchTab,
contextRootElementSelector, className, tabClassName,
}) => {
const TabList = ({
tabs,
activeTab,
className,
tabClassName,
contextRootElementSelector,
onSwitchTab,
}: OwnProps) => {
const containerRef = useRef<HTMLDivElement>();
const previousActiveTab = usePreviousDeprecated(activeTab);

View File

@ -118,6 +118,7 @@ import {
selectIsCurrentUserFrozen,
selectLastServiceNotification,
selectPeer,
selectPeerFullInfo,
selectSimilarChannelIds,
selectStickerSet,
selectSupportChat,
@ -3061,6 +3062,32 @@ addActionHandler('toggleAutoTranslation', async (global, actions, payload): Prom
setGlobal(global);
});
addActionHandler('setMainProfileTab', async (global, actions, payload): Promise<void> => {
const { chatId, tab } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
const peerFullInfo = selectPeerFullInfo(global, chatId);
const oldMainTab = peerFullInfo?.mainTab;
if (oldMainTab === tab) return;
global = updatePeerFullInfo(global, chatId, { mainTab: tab });
setGlobal(global);
let result;
if (chatId === global.currentUserId) {
result = await callApi('setAccountMainProfileTab', { tab });
} else {
result = await callApi('setChannelMainProfileTab', { chat, tab });
}
if (!result) {
global = getGlobal();
global = updatePeerFullInfo(global, chatId, { mainTab: oldMainTab });
setGlobal(global);
}
});
addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Promise<void> => {
const { slug, tabId = getCurrentTabId() } = payload;
const result = await callApi('resolveBusinessChatLink', { slug });

View File

@ -12,7 +12,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey, isInsideSortedArrayRange } from '../../../util/iteratees';
import { getSearchResultKey } from '../../../util/keys/searchResultKey';
import { callApi } from '../../../api/gramjs';
import { getChatMediaMessageIds, getIsSavedDialog, isSameReaction } from '../../helpers';
import { getIsSavedDialog, getMessageContentIds, isSameReaction } from '../../helpers';
import {
addActionHandler, getGlobal, setGlobal,
} from '../../index';
@ -479,7 +479,7 @@ async function searchChatMedia<T extends GlobalState>(
const loadingState = calcLoadingState(direction, limit, newFoundIds.length, currentSegment);
const filteredIds = getChatMediaMessageIds(byId, newFoundIds, false);
const filteredIds = getMessageContentIds(byId, newFoundIds, 'inlineMedia');
currentSegment = mergeWithChatMediaSearchSegment(
filteredIds,
loadingState,

View File

@ -429,12 +429,6 @@ export function hasMediaLocalBlobUrl(media: ApiPhoto | ApiVideo | ApiDocument) {
return false;
}
export function getChatMediaMessageIds(
messages: Record<number, ApiMessage>, listedIds: number[], isFromSharedMedia = false,
) {
return getMessageContentIds(messages, listedIds, isFromSharedMedia ? 'media' : 'inlineMedia');
}
export function getPhotoFullDimensions(photo: Pick<ApiPhoto, 'sizes' | 'thumbnail'>): ApiDimensions | undefined {
return (
photo.sizes.find((size) => size.type === 'w')
@ -488,6 +482,13 @@ export function getMessageContentIds(
validator = getMessageDocument;
break;
case 'gif':
validator = (message: ApiMessage) => {
const video = getMessageVideo(message);
return video?.isGif;
};
break;
case 'links':
validator = (message: ApiMessage) => getMessageWebPage(message) || matchLinkInMessageText(message);
break;

View File

@ -4,7 +4,7 @@ import type { GlobalState, TabArgs } from '../types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import { isChatAdmin, isDeletedUser } from '../helpers';
import { getHasAdminRight, isChatAdmin, isChatChannel, isDeletedUser } from '../helpers';
import { selectChat, selectChatFullInfo } from './chats';
import { type ProfileCollectionKey } from './payments';
import { selectTabState } from './tabs';
@ -74,3 +74,12 @@ export function selectPeerHasProfileBackground<T extends GlobalState>(global: T,
const peer = selectPeer(global, peerId);
return Boolean(peer?.profileColor || peer?.emojiStatus?.type === 'collectible');
}
export function selectCanUpdateMainTab<T extends GlobalState>(global: T, peerId: string) {
if (global.currentUserId === peerId) {
return true;
}
const chat = selectChat(global, peerId);
return Boolean(chat && isChatChannel(chat) && getHasAdminRight(chat, 'postMessages'));
}

View File

@ -37,6 +37,7 @@ import type {
ApiPreparedInlineMessage,
ApiPrivacyKey,
ApiPrivacySettings,
ApiProfileTab,
ApiReaction,
ApiReactionWithPaid,
ApiReportReason,
@ -1142,6 +1143,10 @@ export interface ActionPayloads {
chatId: string;
isEnabled: boolean;
} & WithTabId;
setMainProfileTab: {
chatId: string;
tab: ApiProfileTab;
};
updateChat: {
chatId: string;

View File

@ -10,6 +10,7 @@ import type {
ApiCheckedGiftCode,
ApiCollectibleInfo,
ApiContact,
ApiEmojiStatusCollectible,
ApiError,
ApiFormattedText,
ApiGeoPoint,
@ -54,7 +55,6 @@ import type {
ApiUser,
ApiVideo,
} from '../../api/types';
import type { ApiEmojiStatusCollectible } from '../../api/types/users';
import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer';
import type { ReducerAction } from '../../hooks/useReducer';
import type {

View File

@ -9,7 +9,7 @@ import { getGlobal } from '../../global';
import type { AnimationLevel } from '../../types';
import type { VTTypes } from '../../util/animations/viewTransitionTypes';
import { VT_CLASS_NAME, VT_TYPE_CLASS_PREFIX } from '../../config';
import { ANIMATION_LEVEL_MED, VT_CLASS_NAME, VT_TYPE_CLASS_PREFIX } from '../../config';
import { requestMutation, requestNextMutation } from '../../lib/fasterdom/fasterdom';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import { IS_VIEW_TRANSITION_SUPPORTED } from '../../util/browser/windowEnvironment';
@ -98,7 +98,7 @@ export function useViewTransition(): ViewTransitionController {
function startViewTransition(
types: VTTypes,
updateCallback?: TransitionFunction,
minimumAnimationLevel: AnimationLevel = 1,
minimumAnimationLevel: AnimationLevel = ANIMATION_LEVEL_MED,
): PromiseLike<void> | void {
const global = getGlobal();
const { animationLevel } = selectSharedSettings(global);

View File

@ -1537,6 +1537,7 @@ account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue;
account.toggleNoPaidMessagesException#fe2eda76 flags:# refund_charged:flags.0?true require_payment:flags.2?true parent_peer:flags.1?InputPeer user_id:InputUser = Bool;
account.setMainProfileTab#5dee78b0 tab:ProfileTab = Bool;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
@ -1770,6 +1771,7 @@ channels.searchPosts#f2c4f24d flags:# hashtag:flags.0?string query:flags.1?strin
channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates;
channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates;
channels.checkSearchPostsFlood#22567115 flags:# query:flags.0?string = SearchPostsFlood;
channels.setMainProfileTab#3583fcb1 channel:InputChannel tab:ProfileTab = Bool;
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;

View File

@ -66,6 +66,7 @@
"account.getPaidMessagesRevenue",
"account.getAccountTTL",
"account.setAccountTTL",
"account.setMainProfileTab",
"users.getUsers",
"users.getFullUser",
"contacts.getContacts",
@ -288,6 +289,7 @@
"channels.updatePaidMessagesPrice",
"channels.checkSearchPostsFlood",
"channels.toggleAutotranslation",
"channels.setMainProfileTab",
"bots.getBotRecommendations",
"bots.canSendMessage",
"bots.allowSendMessage",

Binary file not shown.

Binary file not shown.

View File

@ -394,13 +394,14 @@ export type ProfileTabType =
| 'links'
| 'audio'
| 'voice'
| 'gif'
| 'stories'
| 'storiesArchive'
| 'similarChannels'
| 'similarBots'
| 'dialogs'
| 'gifts';
export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice';
export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'gif';
export type MiddleSearchType = 'chat' | 'myChats' | 'channels';
export type MiddleSearchParams = {
requestedQuery?: string;

View File

@ -1222,6 +1222,8 @@ export interface LangPair {
'ProfileBotOpenAppInfoLink': undefined;
'ProfileBirthday': undefined;
'ProfileBirthdayToday': undefined;
'ProfileMenuSetMainTab': undefined;
'ProfileItemSubscribers': undefined;
'MonetizationInfoTONTitle': undefined;
'AriaSearchOlderResult': undefined;
'AriaSearchNewerResult': undefined;
@ -1395,6 +1397,7 @@ export interface LangPair {
'ProfileTabLinks': undefined;
'ProfileTabMusic': undefined;
'ProfileTabVoice': undefined;
'ProfileTabGifs': undefined;
'ProfileTabSharedGroups': undefined;
'ProfileTabSimilarChannels': undefined;
'ProfileTabSimilarBots': undefined;