Support tg:// schema and t.me for comments (#1385)
This commit is contained in:
parent
fc650222ee
commit
57913234dd
@ -718,12 +718,12 @@ export async function requestThreadInfoUpdate({
|
||||
]);
|
||||
|
||||
if (!topMessageResult || !topMessageResult.messages.length) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const discussionChatId = resolveMessageApiChatId(topMessageResult.messages[0]);
|
||||
if (!discussionChatId) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
@ -749,6 +749,10 @@ export async function requestThreadInfoUpdate({
|
||||
noTopChatsRequest: true,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
discussionChatId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function searchMessagesLocal({
|
||||
|
||||
@ -2,7 +2,9 @@ import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
||||
import { getDispatch } from '../../lib/teact/teactn';
|
||||
import convertPunycode from '../../lib/punycode';
|
||||
|
||||
import { DEBUG, RE_TME_INVITE_LINK, RE_TME_LINK } from '../../config';
|
||||
import {
|
||||
DEBUG, RE_TG_LINK, RE_TME_ADDSTICKERS_LINK, RE_TME_INVITE_LINK, RE_TME_LINK,
|
||||
} from '../../config';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
type OwnProps = {
|
||||
@ -28,7 +30,8 @@ const SafeLink: FC<OwnProps> = ({
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
if (
|
||||
e.ctrlKey || e.altKey || e.shiftKey || e.metaKey
|
||||
|| !url || (!url.match(RE_TME_LINK) && !url.match(RE_TME_INVITE_LINK))
|
||||
|| !url || (!url.match(RE_TME_LINK) && !url.match(RE_TME_INVITE_LINK) && !url.match(RE_TG_LINK)
|
||||
&& !url.match(RE_TME_ADDSTICKERS_LINK))
|
||||
) {
|
||||
if (isNotSafe) {
|
||||
toggleSafeLinkModal({ url });
|
||||
|
||||
@ -128,8 +128,11 @@ export const CONTENT_TYPES_FOR_QUICK_UPLOAD = new Set([
|
||||
// eslint-disable-next-line max-len
|
||||
export const RE_LINK_TEMPLATE = '((ftp|https?):\\/\\/)?((www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,63})\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)';
|
||||
export const RE_MENTION_TEMPLATE = '(@[\\w\\d_-]+)';
|
||||
export const RE_TME_LINK = /^(?:https?:\/\/)?(?:t\.me\/)([\d\w_]+)(?:\/([\d]+))?(?:\/([\d]+))?$/gm;
|
||||
export const RE_TG_LINK = /^tg:(\/\/)?([?=&\d\w_-]+)?/gm;
|
||||
// eslint-disable-next-line max-len
|
||||
export const RE_TME_LINK = /^(?:https?:\/\/)?(?:t\.me\/)([\d\w_]+)(?:\/([\d]+))?(?:\/([\d]+)(?:\?([\w]+)=([\d]+))?)?$/gm;
|
||||
export const RE_TME_INVITE_LINK = /^(?:https?:\/\/)?(?:t\.me\/joinchat\/)([\d\w_-]+)?$/gm;
|
||||
export const RE_TME_ADDSTICKERS_LINK = /^(?:https?:\/\/)?(?:t\.me\/addstickers\/)([\d\w_-]+)$/gm;
|
||||
|
||||
// MTProto constants
|
||||
export const SERVICE_NOTIFICATIONS_USER_ID = 777000;
|
||||
|
||||
@ -20,6 +20,8 @@ import { pick } from '../util/iteratees';
|
||||
import { selectCurrentMessageList } from '../modules/selectors';
|
||||
import { hasStoredSession } from '../util/sessions';
|
||||
import { INITIAL_STATE } from './initial';
|
||||
import { parseLocationHash } from '../util/routing';
|
||||
import { LOCATION_HASH } from '../hooks/useHistoryBack';
|
||||
|
||||
const UPDATE_THROTTLE = 5000;
|
||||
|
||||
@ -55,7 +57,7 @@ export function initCache() {
|
||||
});
|
||||
}
|
||||
|
||||
export function loadCache(initialState: GlobalState) {
|
||||
export function loadCache(initialState: GlobalState): GlobalState | undefined {
|
||||
if (GLOBAL_STATE_CACHE_DISABLED) {
|
||||
return undefined;
|
||||
}
|
||||
@ -87,7 +89,7 @@ function clearCaching() {
|
||||
}
|
||||
}
|
||||
|
||||
function readCache(initialState: GlobalState) {
|
||||
function readCache(initialState: GlobalState): GlobalState {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.time('global-state-cache-read');
|
||||
@ -116,19 +118,25 @@ function readCache(initialState: GlobalState) {
|
||||
...cached.chatFolders,
|
||||
};
|
||||
|
||||
if (!cached.messages.messageLists) {
|
||||
cached.messages.messageLists = initialState.messages.messageLists;
|
||||
}
|
||||
|
||||
if (!cached.stickers.greeting) {
|
||||
cached.stickers.greeting = initialState.stickers.greeting;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const newState = {
|
||||
...initialState,
|
||||
...cached,
|
||||
};
|
||||
|
||||
const parsedMessageList = !IS_SINGLE_COLUMN_LAYOUT ? parseLocationHash(LOCATION_HASH) : undefined;
|
||||
|
||||
return {
|
||||
...newState,
|
||||
messages: {
|
||||
...newState.messages,
|
||||
messageLists: parsedMessageList ? [parsedMessageList] : [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function updateCache() {
|
||||
@ -237,15 +245,9 @@ function reduceMessages(global: GlobalState): GlobalState['messages'] {
|
||||
};
|
||||
});
|
||||
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
|
||||
return {
|
||||
byChatId,
|
||||
messageLists: !currentMessageList || IS_SINGLE_COLUMN_LAYOUT ? [] : [{
|
||||
...currentMessageList,
|
||||
threadId: MAIN_THREAD_ID,
|
||||
type: 'thread',
|
||||
}],
|
||||
messageLists: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -467,7 +467,7 @@ export type ActionTypes = (
|
||||
'openTelegramLink' | 'openChatByUsername' | 'requestThreadInfoUpdate' | 'setScrollOffset' | 'unpinAllMessages' |
|
||||
'setReplyingToId' | 'setEditingId' | 'editLastMessage' | 'saveDraft' | 'clearDraft' | 'loadPinnedMessages' |
|
||||
'toggleMessageWebPage' | 'replyToNextMessage' | 'deleteChatUser' | 'deleteChat' |
|
||||
'reportMessages' | 'focusNextReply' |
|
||||
'reportMessages' | 'focusNextReply' | 'openChatByInvite' |
|
||||
// scheduled messages
|
||||
'loadScheduledHistory' | 'sendScheduledMessages' | 'rescheduleMessage' | 'deleteScheduledMessages' |
|
||||
// poll result
|
||||
|
||||
@ -5,7 +5,9 @@ import {
|
||||
import { ApiChat } from '../../../api/types';
|
||||
import { InlineBotSettings } from '../../../types';
|
||||
|
||||
import { RE_TME_INVITE_LINK, RE_TME_LINK } from '../../../config';
|
||||
import {
|
||||
RE_TG_LINK, RE_TME_ADDSTICKERS_LINK, RE_TME_INVITE_LINK, RE_TME_LINK,
|
||||
} from '../../../config';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import {
|
||||
selectChat, selectChatBot, selectChatMessage, selectCurrentChat, selectCurrentMessageList,
|
||||
@ -28,7 +30,8 @@ addReducer('clickInlineButton', (global, actions, payload) => {
|
||||
actions.sendBotCommand({ command: button.value });
|
||||
break;
|
||||
case 'url':
|
||||
if (button.value.match(RE_TME_INVITE_LINK) || button.value.match(RE_TME_LINK)) {
|
||||
if (button.value.match(RE_TME_INVITE_LINK) || button.value.match(RE_TME_LINK) || button.value.match(RE_TG_LINK)
|
||||
|| button.value.match(RE_TME_ADDSTICKERS_LINK)) {
|
||||
actions.openTelegramLink({ url: button.value });
|
||||
} else {
|
||||
actions.toggleSafeLinkModal({ url: button.value });
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
RE_TME_INVITE_LINK,
|
||||
RE_TME_LINK,
|
||||
TIPS_USERNAME,
|
||||
LOCALIZED_TIPS,
|
||||
LOCALIZED_TIPS, RE_TG_LINK, RE_TME_ADDSTICKERS_LINK,
|
||||
} from '../../../config';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import {
|
||||
@ -30,7 +30,6 @@ import {
|
||||
} from '../../reducers';
|
||||
import {
|
||||
selectChat,
|
||||
selectCurrentChat,
|
||||
selectUser,
|
||||
selectChatListType,
|
||||
selectIsChatPinned,
|
||||
@ -39,12 +38,14 @@ import {
|
||||
selectChatByUsername,
|
||||
selectThreadTopMessageId,
|
||||
selectCurrentMessageList,
|
||||
selectThreadInfo,
|
||||
} from '../../selectors';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { debounce, pause, throttle } from '../../../util/schedulers';
|
||||
import {
|
||||
isChatSummaryOnly, isChatArchived, prepareChatList, isChatBasicGroup,
|
||||
} from '../../helpers';
|
||||
import { processDeepLink } from '../../../util/deeplink';
|
||||
|
||||
const TOP_CHATS_PRELOAD_PAUSE = 100;
|
||||
// We expect this ID does not exist
|
||||
@ -432,33 +433,56 @@ addReducer('toggleChatUnread', (global, actions, payload) => {
|
||||
}
|
||||
});
|
||||
|
||||
addReducer('openChatByInvite', (global, actions, payload) => {
|
||||
const { hash } = payload!;
|
||||
|
||||
(async () => {
|
||||
const result = await callApi('openChatByInvite', hash);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
actions.openChat({ id: result.chatId });
|
||||
})();
|
||||
});
|
||||
|
||||
addReducer('openTelegramLink', (global, actions, payload) => {
|
||||
const { url } = payload!;
|
||||
let match = RE_TME_INVITE_LINK.exec(url);
|
||||
|
||||
if (match) {
|
||||
const hash = match[1];
|
||||
|
||||
(async () => {
|
||||
const result = await callApi('openChatByInvite', hash);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
actions.openChat({ id: result.chatId });
|
||||
})();
|
||||
const stickersMatch = RE_TME_ADDSTICKERS_LINK.exec(url);
|
||||
if (stickersMatch) {
|
||||
actions.openStickerSetShortName({
|
||||
stickerSetShortName: stickersMatch[1],
|
||||
});
|
||||
} else if (url.match(RE_TG_LINK)) {
|
||||
processDeepLink(url.match(RE_TG_LINK)[0]);
|
||||
} else {
|
||||
match = RE_TME_LINK.exec(url)!;
|
||||
let match = RE_TME_INVITE_LINK.exec(url);
|
||||
|
||||
const username = match[1];
|
||||
const chatOrChannelPostId = match[2] ? Number(match[2]) : undefined;
|
||||
const messageId = match[3] ? Number(match[3]) : undefined;
|
||||
if (match) {
|
||||
const hash = match[1];
|
||||
|
||||
// Open message in private chat
|
||||
if (username === 'c' && chatOrChannelPostId && messageId) {
|
||||
actions.focusMessage({ chatId: -chatOrChannelPostId, messageId });
|
||||
actions.openChatByInvite({ hash });
|
||||
} else {
|
||||
void openChatByUsername(actions, username, chatOrChannelPostId);
|
||||
match = RE_TME_LINK.exec(url)!;
|
||||
|
||||
const username = match[1];
|
||||
const chatOrChannelPostId = match[2] ? Number(match[2]) : undefined;
|
||||
const messageId = match[3] ? Number(match[3]) : undefined;
|
||||
const commentId = match[4] === 'comment' && match[5] ? Number(match[5]) : undefined;
|
||||
|
||||
// Open message in private group
|
||||
if (username === 'c' && chatOrChannelPostId && messageId) {
|
||||
actions.focusMessage({
|
||||
chatId: -chatOrChannelPostId,
|
||||
messageId,
|
||||
});
|
||||
} else {
|
||||
actions.openChatByUsername({
|
||||
username,
|
||||
messageId,
|
||||
commentId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -476,9 +500,18 @@ addReducer('acceptInviteConfirmation', (global, actions, payload) => {
|
||||
});
|
||||
|
||||
addReducer('openChatByUsername', (global, actions, payload) => {
|
||||
const { username } = payload!;
|
||||
const { username, messageId, commentId } = payload!;
|
||||
|
||||
void openChatByUsername(actions, username);
|
||||
(async () => {
|
||||
if (!commentId) {
|
||||
await openChatByUsername(actions, username, messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!messageId) return;
|
||||
|
||||
await openCommentsByUsername(actions, username, messageId, commentId);
|
||||
})();
|
||||
});
|
||||
|
||||
addReducer('togglePreHistoryHidden', (global, actions, payload) => {
|
||||
@ -1027,42 +1060,79 @@ async function deleteChatFolder(id: number) {
|
||||
await callApi('deleteChatFolder', id);
|
||||
}
|
||||
|
||||
async function fetchChatByUsername(
|
||||
username: string,
|
||||
) {
|
||||
const global = getGlobal();
|
||||
const localChat = selectChatByUsername(global, username);
|
||||
if (localChat && !localChat.isMin) {
|
||||
return localChat;
|
||||
}
|
||||
|
||||
const chat = await callApi('getChatByUsername', username);
|
||||
if (!chat) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setGlobal(updateChat(getGlobal(), chat.id, chat));
|
||||
|
||||
return chat;
|
||||
}
|
||||
|
||||
async function openChatByUsername(
|
||||
actions: GlobalActions,
|
||||
username: string,
|
||||
channelPostId?: number,
|
||||
) {
|
||||
const global = getGlobal();
|
||||
const localChat = selectChatByUsername(global, username);
|
||||
if (localChat && !localChat.isMin) {
|
||||
if (channelPostId) {
|
||||
actions.focusMessage({ chatId: localChat.id, messageId: channelPostId });
|
||||
} else {
|
||||
actions.openChat({ id: localChat.id });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const previousChat = selectCurrentChat(global);
|
||||
// Open temporary empty chat to make the click response feel faster
|
||||
actions.openChat({ id: TMP_CHAT_ID });
|
||||
|
||||
const chat = await callApi('getChatByUsername', username);
|
||||
const chat = await fetchChatByUsername(username);
|
||||
|
||||
if (!chat) {
|
||||
if (previousChat) {
|
||||
actions.openChat({ id: previousChat.id });
|
||||
}
|
||||
|
||||
actions.openPreviousChat();
|
||||
actions.showNotification({ message: 'User does not exist' });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setGlobal(updateChat(getGlobal(), chat.id, chat));
|
||||
|
||||
if (channelPostId) {
|
||||
actions.focusMessage({ chatId: chat.id, messageId: channelPostId });
|
||||
} else {
|
||||
actions.openChat({ id: chat.id });
|
||||
}
|
||||
}
|
||||
|
||||
async function openCommentsByUsername(
|
||||
actions: GlobalActions,
|
||||
username: string,
|
||||
messageId: number,
|
||||
commentId: number,
|
||||
) {
|
||||
actions.openChat({ id: TMP_CHAT_ID });
|
||||
|
||||
const chat = await fetchChatByUsername(username);
|
||||
|
||||
if (!chat) return;
|
||||
|
||||
const global = getGlobal();
|
||||
|
||||
const threadInfo = selectThreadInfo(global, chat.id, messageId);
|
||||
let discussionChatId: number | undefined;
|
||||
|
||||
if (!threadInfo) {
|
||||
const result = await callApi('requestThreadInfoUpdate', { chat, threadId: messageId });
|
||||
if (!result) return;
|
||||
|
||||
discussionChatId = result.discussionChatId;
|
||||
} else {
|
||||
discussionChatId = threadInfo.chatId;
|
||||
}
|
||||
|
||||
if (!discussionChatId) return;
|
||||
|
||||
actions.focusMessage({
|
||||
chatId: discussionChatId,
|
||||
threadId: messageId,
|
||||
messageId: Number(commentId),
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
import { getDispatch } from '../lib/teact/teactn';
|
||||
|
||||
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'setlanguage' |
|
||||
'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url';
|
||||
|
||||
export const processDeepLink = (url: string) => {
|
||||
const { protocol, searchParams, pathname } = new URL(url);
|
||||
|
||||
if (protocol !== 'tg:') return;
|
||||
|
||||
const { openChatByUsername, openStickerSetShortName } = getDispatch();
|
||||
const {
|
||||
openChatByInvite,
|
||||
openChatByUsername,
|
||||
openStickerSetShortName,
|
||||
focusMessage,
|
||||
} = getDispatch();
|
||||
|
||||
const method = pathname.replace(/^\/\//, '');
|
||||
const method = pathname.replace(/^\/\//, '') as DeepLinkMethod;
|
||||
const params: Record<string, string> = {};
|
||||
searchParams.forEach((value, key) => {
|
||||
params[key] = value;
|
||||
@ -15,26 +23,40 @@ export const processDeepLink = (url: string) => {
|
||||
|
||||
switch (method) {
|
||||
case 'resolve': {
|
||||
const {
|
||||
domain,
|
||||
} = params;
|
||||
const { domain, post, comment } = params;
|
||||
|
||||
if (domain !== 'telegrampassport') {
|
||||
openChatByUsername({
|
||||
username: domain,
|
||||
messageId: Number(post),
|
||||
commentId: Number(comment),
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'privatepost':
|
||||
case 'privatepost': {
|
||||
const {
|
||||
post, channel,
|
||||
} = params;
|
||||
|
||||
focusMessage({
|
||||
chatId: -Number(channel),
|
||||
id: post,
|
||||
});
|
||||
break;
|
||||
case 'bg':
|
||||
|
||||
break;
|
||||
case 'join':
|
||||
}
|
||||
case 'bg': {
|
||||
// const {
|
||||
// slug, color, rotation, mode, intensity, bg_color: bgColor, gradient,
|
||||
// } = params;
|
||||
break;
|
||||
}
|
||||
case 'join': {
|
||||
const { invite } = params;
|
||||
|
||||
openChatByInvite({ hash: invite });
|
||||
break;
|
||||
}
|
||||
case 'addstickers': {
|
||||
const { set } = params;
|
||||
|
||||
@ -43,9 +65,15 @@ export const processDeepLink = (url: string) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'msg':
|
||||
|
||||
case 'share':
|
||||
case 'msg': {
|
||||
// const { url, text } = params;
|
||||
break;
|
||||
}
|
||||
case 'login': {
|
||||
// const { code, token } = params;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Unsupported deeplink
|
||||
|
||||
|
||||
@ -7,8 +7,13 @@ export const createMessageHash = (messageList: MessageList): string => (
|
||||
: (messageList.threadId !== -1 ? `_${messageList.threadId}` : ''))
|
||||
);
|
||||
|
||||
export const parseMessageHash = (value: string): MessageList => {
|
||||
const [chatId, typeOrThreadId] = value.split('_');
|
||||
export const parseLocationHash = (value: string): MessageList | undefined => {
|
||||
if (!value) return undefined;
|
||||
|
||||
const [chatId, typeOrThreadId] = value.replace(/^#/, '').split('_');
|
||||
|
||||
if (!chatId) return undefined;
|
||||
|
||||
const isType = ['thread', 'pinned', 'scheduled'].includes(typeOrThreadId);
|
||||
|
||||
return {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user