Chats: Various fixes (#3880)

This commit is contained in:
Alexander Zinchuk 2023-09-25 18:53:24 +02:00
parent e8c4b8f713
commit b89ef33aec
18 changed files with 134 additions and 70 deletions

View File

@ -17,6 +17,7 @@ import {
} from '../../types';
import { compact } from '../../../util/iteratees';
import localDb from '../localDb';
import { bytesToDataUri } from './helpers';
import { pathBytesToSvg } from './pathBytesToSvg';
import { buildApiPeerId } from './peers';
@ -154,6 +155,8 @@ export function buildPrivacyRules(rules: GramJs.TypePrivacyRule[]): ApiPrivacySe
let blockUserIds: string[] | undefined;
let blockChatIds: string[] | undefined;
const localChats = localDb.chats;
rules.forEach((rule) => {
if (rule instanceof GramJs.PrivacyValueAllowAll) {
visibility ||= 'everybody';
@ -170,9 +173,20 @@ export function buildPrivacyRules(rules: GramJs.TypePrivacyRule[]): ApiPrivacySe
} else if (rule instanceof GramJs.PrivacyValueDisallowUsers) {
blockUserIds = rule.users.map((chatId) => buildApiPeerId(chatId, 'user'));
} else if (rule instanceof GramJs.PrivacyValueAllowChatParticipants) {
allowChatIds = rule.chats.map((chatId) => buildApiPeerId(chatId, 'chat'));
// Server allows channel ids here, so we need to check
allowChatIds = rule.chats.map((chatId) => {
const dialogId = buildApiPeerId(chatId, 'chat');
const channelId = buildApiPeerId(chatId, 'channel');
if (localChats[dialogId]) return dialogId;
return channelId;
});
} else if (rule instanceof GramJs.PrivacyValueDisallowChatParticipants) {
blockChatIds = rule.chats.map((chatId) => buildApiPeerId(chatId, 'chat'));
blockChatIds = rule.chats.map((chatId) => {
const dialogId = buildApiPeerId(chatId, 'chat');
const channelId = buildApiPeerId(chatId, 'channel');
if (localChats[dialogId]) return dialogId;
return channelId;
});
}
});

View File

@ -31,19 +31,18 @@ import {
ApiMessageEntityTypes,
} from '../../types';
import { DEFAULT_STATUS_ICON_ID } from '../../../config';
import { CHANNEL_ID_LENGTH, DEFAULT_STATUS_ICON_ID } from '../../../config';
import { pick } from '../../../util/iteratees';
import { deserializeBytes } from '../helpers';
import localDb from '../localDb';
const CHANNEL_ID_MIN_LENGTH = 11; // Example: -1234567890
const CHANNEL_ID_NEW_FORMAT_MIN_LENGTH = 14; // Example: -1001234567890
const LEGACY_CHANNEL_ID_MIN_LENGTH = 11; // Example: -1234567890
function checkIfChannelId(id: string) {
if (id.length >= CHANNEL_ID_NEW_FORMAT_MIN_LENGTH) return id.startsWith('-100');
if (id.length >= CHANNEL_ID_LENGTH) return id.startsWith('-100');
// LEGACY Unprefixed channel id
if (id.length === CHANNEL_ID_MIN_LENGTH && id.startsWith('-4')) return false;
return id.length >= CHANNEL_ID_MIN_LENGTH;
if (id.length === LEGACY_CHANNEL_ID_MIN_LENGTH && id.startsWith('-4')) return false;
return id.length >= LEGACY_CHANNEL_ID_MIN_LENGTH;
}
export function getEntityTypeById(chatOrUserId: string) {
@ -543,7 +542,7 @@ export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') {
}
if (type === 'channel') {
if (id.length === CHANNEL_ID_NEW_FORMAT_MIN_LENGTH) {
if (id.length === CHANNEL_ID_LENGTH) {
return BigInt(id.slice(4));
}

View File

@ -12,7 +12,7 @@ import { IS_TEST } from '../../config';
import {
getChatAvatarHash,
getChatTitle,
getUserColorKey,
getPeerColorKey,
getUserFullName,
getUserStoryHtmlId,
isChatWithRepliesBot,
@ -208,7 +208,7 @@ const Avatar: FC<OwnProps> = ({
const fullClassName = buildClassName(
`Avatar size-${size}`,
className,
`color-bg-${getUserColorKey(peer)}`,
`color-bg-${getPeerColorKey(peer)}`,
isSavedMessages && 'saved-messages',
isDeleted && 'deleted-account',
isReplies && 'replies-bot-account',

View File

@ -12,8 +12,8 @@ import {
getMessageIsSpoiler,
getMessageMediaHash,
getMessageRoundVideo,
getPeerColorKey,
getSenderTitle,
getUserColorKey,
isActionMessage,
isMessageTranslatable,
} from '../../global/helpers';
@ -93,7 +93,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
className={buildClassName(
'EmbeddedMessage',
className,
sender && !noUserColors && `color-${getUserColorKey(sender)}`,
sender && !noUserColors && `color-${getPeerColorKey(sender)}`,
)}
onClick={message && handleClick}
onMouseDown={message && handleMouseDown}

View File

@ -6,9 +6,9 @@ import type { ApiChat, ApiTypeStory, ApiUser } from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import {
getPeerColorKey,
getSenderTitle,
getStoryMediaHash,
getUserColorKey,
} from '../../global/helpers';
import buildClassName from '../../util/buildClassName';
import { getPictogramDimensions } from './helpers/mediaDimensions';
@ -75,7 +75,7 @@ const EmbeddedStory: FC<OwnProps> = ({
ref={ref}
className={buildClassName(
'EmbeddedMessage',
sender && !noUserColors && `color-${getUserColorKey(sender)}`,
sender && !noUserColors && `color-${getPeerColorKey(sender)}`,
)}
onClick={handleClick}
onMouseDown={handleMouseDown}

View File

@ -6,7 +6,7 @@ import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
import {
getChatAvatarHash,
getChatTitle,
getUserColorKey,
getPeerColorKey,
getUserFullName,
getVideoAvatarMediaHash,
isChatWithRepliesBot,
@ -139,7 +139,7 @@ const ProfilePhoto: FC<OwnProps> = ({
const fullClassName = buildClassName(
'ProfilePhoto',
`color-bg-${getUserColorKey(user || chat)}`,
`color-bg-${getPeerColorKey(user || chat)}`,
isSavedMessages && 'saved-messages',
isDeleted && 'deleted-account',
isRepliesChat && 'replies-bot-account',

View File

@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../global';
import type { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types';
import type { MessageList } from '../../global/types';
import { getUserIdDividend } from '../../global/helpers';
import { getPeerIdDividend } from '../../global/helpers';
import { selectChat, selectCurrentMessageList } from '../../global/selectors';
import useLang from '../../hooks/useLang';
@ -94,7 +94,7 @@ const ContactGreeting: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { userId }): StateProps => {
const { stickers } = global.stickers.greeting;
const dividend = getUserIdDividend(userId) + getUserIdDividend(global.currentUserId!);
const dividend = getPeerIdDividend(userId) + getPeerIdDividend(global.currentUserId!);
const sticker = stickers?.length ? stickers[dividend % stickers.length] : undefined;
const chat = selectChat(global, userId);
if (!chat) {

View File

@ -54,12 +54,18 @@ const AttachBotItem: FC<OwnProps> = ({
});
const handleClick = useLastCallback(() => {
callAttachBot({
bot,
chatId: chatId!,
threadId,
isFromSideMenu: isInSideMenu,
});
if (isInSideMenu) {
callAttachBot({
bot,
isFromSideMenu: true,
});
} else {
callAttachBot({
bot,
chatId: chatId!,
threadId,
});
}
});
const handleCloseMenu = useLastCallback(() => {

View File

@ -38,8 +38,8 @@ import {
getMessageLocation,
getMessageSingleCustomEmoji,
getMessageSingleRegularEmoji,
getPeerColorKey,
getSenderTitle,
getUserColorKey,
hasMessageText,
isAnonymousOwnMessage,
isChatChannel,
@ -1209,7 +1209,7 @@ const Message: FC<OwnProps & StateProps> = ({
senderTitle = getSenderTitle(lang, senderPeer);
if (!asForwarded && !isOwn) {
senderColor = `color-${getUserColorKey(senderPeer)}`;
senderColor = `color-${getPeerColorKey(senderPeer)}`;
}
} else if (forwardInfo?.hiddenUserName) {
senderTitle = forwardInfo.hiddenUserName;

View File

@ -129,6 +129,7 @@ export const DRAFT_DEBOUNCE = 10000; // 10s
export const SEND_MESSAGE_ACTION_INTERVAL = 3000; // 3s
// 10000s from https://corefork.telegram.org/api/url-authorization#automatic-authorization
export const APP_CONFIG_REFETCH_INTERVAL = 10000 * 1000;
export const GENERAL_REFETCH_INTERVAL = 60 * 60 * 1000; // 1h
export const EDITABLE_INPUT_ID = 'editable-message-text';
export const EDITABLE_INPUT_MODAL_ID = 'editable-message-text-modal';
@ -282,6 +283,7 @@ export const API_CHAT_TYPES = ['bots', 'channels', 'chats', 'users'] as const;
export const SERVICE_NOTIFICATIONS_USER_ID = '777000';
export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must be equal to 708513
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
export const CHANNEL_ID_LENGTH = 14; // 14 symbols, including -100 prefix
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';
export const ALL_FOLDER_ID = 0;
export const ARCHIVED_FOLDER_ID = 1;

View File

@ -6,10 +6,12 @@ import type { RequiredGlobalActions } from '../../index';
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
import { MAIN_THREAD_ID } from '../../../api/types';
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey } from '../../../util/iteratees';
import { translate } from '../../../util/langProvider';
import PopupManager from '../../../util/PopupManager';
import requestActionTimeout from '../../../util/requestActionTimeout';
import { debounce } from '../../../util/schedulers';
import { getServerTime } from '../../../util/serverTime';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
@ -721,7 +723,12 @@ addActionHandler('markBotTrusted', (global, actions, payload): ActionReturnType
addActionHandler('loadAttachBots', async (global, actions, payload): Promise<void> => {
const { hash } = payload || {};
await loadAttachBots(global, hash);
const result = await loadAttachBots(global, hash);
requestActionTimeout({
action: 'loadAttachBots',
payload: { hash: result?.hash },
}, GENERAL_REFETCH_INTERVAL);
});
addActionHandler('toggleAttachBot', async (global, actions, payload): Promise<void> => {
@ -737,7 +744,7 @@ addActionHandler('toggleAttachBot', async (global, actions, payload): Promise<vo
async function loadAttachBots<T extends GlobalState>(global: T, hash?: string) {
const result = await callApi('loadAttachBots', { hash });
if (!result) {
return;
return undefined;
}
global = getGlobal();
@ -750,16 +757,20 @@ async function loadAttachBots<T extends GlobalState>(global: T, hash?: string) {
},
};
setGlobal(global);
return result;
}
addActionHandler('callAttachBot', (global, actions, payload): ActionReturnType => {
const {
chatId, bot, url, startParam, threadId, isFromSideMenu,
tabId = getCurrentTabId(),
bot, startParam, isFromConfirm, tabId = getCurrentTabId(),
} = payload;
const isFromSideMenu = 'isFromSideMenu' in payload && payload.isFromSideMenu;
const isFromBotMenu = !bot;
if ((!isFromBotMenu && !global.attachMenu.bots[bot.id])
|| (isFromSideMenu && (bot?.isInactive || bot?.isDisclaimerNeeded))) {
const shouldDisplayDisclaimer = (!isFromBotMenu && !global.attachMenu.bots[bot.id])
|| (isFromSideMenu && (bot?.isInactive || bot?.isDisclaimerNeeded));
if (!isFromConfirm && shouldDisplayDisclaimer) {
return updateTabState(global, {
requestedAttachBotInstall: {
bot,
@ -767,11 +778,7 @@ addActionHandler('callAttachBot', (global, actions, payload): ActionReturnType =
action: 'callAttachBot',
payload: {
...payload,
bot: {
...bot,
isInactive: false,
isDisclaimerNeeded: false,
},
isFromConfirm: true,
},
},
},
@ -784,15 +791,19 @@ addActionHandler('callAttachBot', (global, actions, payload): ActionReturnType =
botId: bot!.id,
buttonText: '',
isFromSideMenu: true,
startParam,
theme,
tabId,
});
} else {
}
if ('chatId' in payload) {
const { chatId, threadId, url } = payload;
actions.openChat({ id: chatId, threadId, tabId });
actions.requestWebView({
url,
peerId: chatId,
botId: isFromBotMenu ? chatId : bot.id,
peerId: chatId!,
botId: (isFromBotMenu ? chatId : bot.id)!,
theme,
buttonText: '',
isFromBotMenu,
@ -818,6 +829,7 @@ addActionHandler('confirmAttachBotInstall', async (global, actions, payload): Pr
const botUser = selectUser(global, bot.id);
if (!botUser) return;
actions.markBotTrusted({ botId: bot.id, isWriteAllowed, tabId });
await callApi('toggleAttachBot', { bot: botUser, isWriteAllowed, isEnabled: true });
if (onConfirm) {
const { action, payload: actionPayload } = onConfirm;

View File

@ -999,14 +999,15 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
hash = part2;
}
const startAttach = params.hasOwnProperty('startattach') && !params.startattach ? true : params.startattach;
const hasStartAttach = params.hasOwnProperty('startattach');
const hasStartApp = params.hasOwnProperty('startapp');
const choose = parseChooseParameter(params.choose);
const storyId = part2 === 's' && (Number(part3) || undefined);
if (part1.match(/^\+([0-9]+)(\?|$)/)) {
openChatByPhoneNumber({
phoneNumber: part1.substr(1, part1.length - 1),
startAttach,
startAttach: params.startattach,
attach: params.attach,
tabId,
});
@ -1087,11 +1088,11 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
slug: part2,
tabId,
});
} else if (startAttach && choose) {
} else if ((hasStartAttach && choose) || (!part2 && hasStartApp)) {
processAttachBotParameters({
username: part1,
filter: choose,
...(typeof startAttach === 'string' && { startParam: startAttach }),
startParam: params.startattach || params.startapp,
tabId,
});
} else {
@ -1101,7 +1102,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
threadId: messageId ? Number(chatOrChannelPostId) : undefined,
commentId,
startParam: params.start,
startAttach,
startAttach: params.startattach,
attach: params.attach,
startApp: params.startapp,
originalParts: [part1, part2, part3],
@ -1701,6 +1702,18 @@ addActionHandler('processAttachBotParameters', async (global, actions, payload):
const bot = await getAttachBotOrNotify(global, actions, username, tabId);
if (!bot) return;
const isForChat = Boolean(filter);
if (!isForChat) {
actions.callAttachBot({
isFromSideMenu: true,
bot,
startParam,
tabId,
});
return;
}
global = getGlobal();
const { attachMenu: { bots } } = global;
if (!bots[bot.id]) {
@ -1720,7 +1733,6 @@ addActionHandler('processAttachBotParameters', async (global, actions, payload):
setGlobal(global);
return;
}
actions.requestAttachBotInChat({
bot,
filter,

View File

@ -1,9 +1,11 @@
import type { ActionReturnType } from '../../types';
import { ApiMediaFormat } from '../../../api/types';
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey, omit } from '../../../util/iteratees';
import * as mediaLoader from '../../../util/mediaLoader';
import requestActionTimeout from '../../../util/requestActionTimeout';
import { callApi } from '../../../api/gramjs';
import {
getDocumentMediaHash,
@ -61,6 +63,11 @@ addActionHandler('loadAvailableReactions', async (global): Promise<void> => {
availableReactions: result,
};
setGlobal(global);
requestActionTimeout({
action: 'loadAvailableReactions',
payload: undefined,
}, GENERAL_REFETCH_INTERVAL);
});
addActionHandler('interactWithAnimatedEmoji', (global, actions, payload): ActionReturnType => {

View File

@ -13,7 +13,7 @@ import {
} from '../../api/types';
import {
ARCHIVED_FOLDER_ID, GENERAL_TOPIC_ID, REPLIES_USER_ID, TME_LINK_PREFIX,
ARCHIVED_FOLDER_ID, CHANNEL_ID_LENGTH, GENERAL_TOPIC_ID, REPLIES_USER_ID, TME_LINK_PREFIX,
} from '../../config';
import { formatDateToString, formatTime } from '../../util/dateFormat';
import { orderBy } from '../../util/iteratees';
@ -24,11 +24,16 @@ const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
const VERIFIED_PRIORITY_BASE = 3e9;
const PINNED_PRIORITY_BASE = 3e8;
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
export function isUserId(entityId: string) {
return !entityId.startsWith('-');
}
export function isChannelId(entityId: string) {
return entityId.length === CHANNEL_ID_LENGTH && entityId.startsWith('-100');
}
export function isChatGroup(chat: ApiChat) {
return isChatBasicGroup(chat) || isChatSuperGroup(chat);
}
@ -448,3 +453,18 @@ export function getOrderedTopics(
return [...pinnedOrdered, ...ordered, ...hidden];
}
}
export function getCleanPeerId(peerId: string) {
return isChannelId(peerId) ? peerId.replace('-100', '') : peerId.replace('-', '');
}
export function getPeerIdDividend(peerId: string) {
return Math.abs(Number(getCleanPeerId(peerId)));
}
// https://github.com/telegramdesktop/tdesktop/blob/371510cfe23b0bd226de8c076bc49248fbe40c26/Telegram/SourceFiles/data/data_peer.cpp#L53
export function getPeerColorKey(peer: ApiUser | ApiChat | undefined) {
const index = peer ? getPeerIdDividend(peer.id) % 7 : 0;
return USER_COLOR_KEYS[index];
}

View File

@ -8,8 +8,6 @@ import { formatPhoneNumber } from '../../util/phoneNumber';
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import { getServerTime, getServerTimeOffset } from '../../util/serverTime';
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
export function getUserFirstOrLastName(user?: ApiUser) {
if (!user) {
return undefined;
@ -261,17 +259,6 @@ export function filterUsersByName(
});
}
export function getUserIdDividend(peerId: string) {
return Math.abs(Number(peerId));
}
// https://github.com/telegramdesktop/tdesktop/blob/371510cfe23b0bd226de8c076bc49248fbe40c26/Telegram/SourceFiles/data/data_peer.cpp#L53
export function getUserColorKey(peer: ApiUser | ApiChat | undefined) {
const index = peer ? getUserIdDividend(peer.id) % 7 : 0;
return USER_COLOR_KEYS[index];
}
export function getMainUsername(userOrChat: ApiUser | ApiChat) {
return userOrChat.usernames?.find((u) => u.isActive)?.username;
}

View File

@ -2378,7 +2378,7 @@ export interface ActionPayloads {
processAttachBotParameters: {
username: string;
filter: ApiChatType[];
filter?: ApiChatType[];
startParam?: string;
} & WithTabId;
requestAttachBotInChat: {
@ -2404,13 +2404,16 @@ export interface ActionPayloads {
isEnabled: boolean;
};
callAttachBot: {
callAttachBot: ({
chatId: string;
threadId?: number;
bot?: ApiAttachBot;
url?: string;
} | {
isFromSideMenu: true;
}) & {
startParam?: string;
isFromSideMenu?: boolean;
bot?: ApiAttachBot;
isFromConfirm?: boolean;
} & WithTabId;
requestBotUrlAuth: {

View File

@ -41,7 +41,8 @@ export const processDeepLink = (url: string) => {
appname, startapp, story,
} = params;
const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach;
const hasStartAttach = params.hasOwnProperty('startattach');
const hasStartApp = params.hasOwnProperty('startapp');
const choose = parseChooseParameter(params.choose);
const threadId = Number(thread) || Number(topic) || undefined;
@ -52,11 +53,11 @@ export const processDeepLink = (url: string) => {
startApp: startapp,
originalParts: [domain, appname],
});
} else if (startAttach && choose) {
} else if ((hasStartAttach && choose) || (!appname && hasStartApp)) {
processAttachBotParameters({
username: domain,
filter: choose,
...(typeof startAttach === 'string' && { startParam: startAttach }),
startParam: startattach || startapp,
});
} else if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
joinVoiceChatByLink({
@ -64,7 +65,7 @@ export const processDeepLink = (url: string) => {
inviteHash: voicechat || livestream,
});
} else if (phone) {
openChatByPhoneNumber({ phoneNumber: phone, startAttach, attach });
openChatByPhoneNumber({ phoneNumber: phone, startAttach: startattach, attach });
} else if (story) {
openStoryViewerByUsername({ username: domain, storyId: Number(story) });
} else {
@ -73,7 +74,7 @@ export const processDeepLink = (url: string) => {
messageId: post ? Number(post) : undefined,
commentId: comment ? Number(comment) : undefined,
startParam: start,
startAttach,
startAttach: startattach,
attach,
threadId,
});

View File

@ -4,6 +4,7 @@ import type { CallbackAction } from '../global/types';
const callbacks = new Map<string, number>();
// TODO Pass callbacks to the master tab. Sync them on master change
export default function requestActionTimeout(action: CallbackAction, timeout: number) {
const name = action.action;
clearTimeout(callbacks.get(name));