DeepLink: Open private message link (#4157)
This commit is contained in:
parent
a5c38e59cc
commit
b8f0607700
@ -28,6 +28,7 @@ import {
|
|||||||
TOPICS_SLICE_SECOND_LOAD,
|
TOPICS_SLICE_SECOND_LOAD,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
||||||
|
import { isDeepLink } from '../../../util/deepLinkParser';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { getOrderedIds } from '../../../util/folderManager';
|
import { getOrderedIds } from '../../../util/folderManager';
|
||||||
import { buildCollectionByKey, omit, pick } from '../../../util/iteratees';
|
import { buildCollectionByKey, omit, pick } from '../../../util/iteratees';
|
||||||
@ -1167,6 +1168,13 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
|
if (isDeepLink(url)) {
|
||||||
|
const isProcessed = processDeepLink(url);
|
||||||
|
if (isProcessed || url.match(RE_TG_LINK)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openChatByPhoneNumber,
|
openChatByPhoneNumber,
|
||||||
openChatByInvite,
|
openChatByInvite,
|
||||||
@ -1184,11 +1192,6 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
checkGiftCode,
|
checkGiftCode,
|
||||||
} = actions;
|
} = actions;
|
||||||
|
|
||||||
if (url.match(RE_TG_LINK)) {
|
|
||||||
processDeepLink(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = new URL(url.toLowerCase().startsWith('http') ? url : `https://${url}`);
|
const uri = new URL(url.toLowerCase().startsWith('http') ? url : `https://${url}`);
|
||||||
if (TME_WEB_DOMAINS.has(uri.hostname) && uri.pathname === '/') {
|
if (TME_WEB_DOMAINS.has(uri.hostname) && uri.pathname === '/') {
|
||||||
window.open(uri.toString(), '_blank', 'noopener');
|
window.open(uri.toString(), '_blank', 'noopener');
|
||||||
|
|||||||
@ -9,20 +9,22 @@ interface PublicMessageLink {
|
|||||||
type: 'publicMessageLink';
|
type: 'publicMessageLink';
|
||||||
username: string;
|
username: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
isSingle?: boolean;
|
isSingle: boolean;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
commentId?: number;
|
commentId?: number;
|
||||||
mediaTimestamp?: string;
|
mediaTimestamp?: string;
|
||||||
|
isBoost: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PrivateMessageLink {
|
export interface PrivateMessageLink {
|
||||||
type: 'privateMessageLink';
|
type: 'privateMessageLink';
|
||||||
channelId: string;
|
channelId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
isSingle?: boolean;
|
isSingle: boolean;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
commentId?: number;
|
commentId?: number;
|
||||||
mediaTimestamp?: string;
|
mediaTimestamp?: string;
|
||||||
|
isBoost: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShareLink {
|
interface ShareLink {
|
||||||
@ -63,6 +65,16 @@ type BuilderParams<T extends DeepLink> = Record<keyof Omit<T, 'type'>, string>;
|
|||||||
type BuilderReturnType<T extends DeepLink> = T | undefined;
|
type BuilderReturnType<T extends DeepLink> = T | undefined;
|
||||||
type DeepLinkType = DeepLink['type'] | 'unknown';
|
type DeepLinkType = DeepLink['type'] | 'unknown';
|
||||||
|
|
||||||
|
type PrivateMessageLinkBuilderParams = Omit<BuilderParams<PrivateMessageLink>, 'isSingle' | 'isBoost'> & {
|
||||||
|
single: string;
|
||||||
|
boost: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PublicMessageLinkBuilderParams = Omit<BuilderParams<PublicMessageLink>, 'isSingle' | 'isBoost'> & {
|
||||||
|
single: string;
|
||||||
|
boost: string;
|
||||||
|
};
|
||||||
|
|
||||||
const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']);
|
const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']);
|
||||||
|
|
||||||
export function isDeepLink(link: string): boolean {
|
export function isDeepLink(link: string): boolean {
|
||||||
@ -100,28 +112,30 @@ function handleTgLink(url: URL) {
|
|||||||
switch (deepLinkType) {
|
switch (deepLinkType) {
|
||||||
case 'publicMessageLink': {
|
case 'publicMessageLink': {
|
||||||
const {
|
const {
|
||||||
domain, post, single, thread, comment, t,
|
domain, post, single, thread, comment, t, boost,
|
||||||
} = queryParams;
|
} = queryParams;
|
||||||
return buildPublicMessageLink({
|
return buildPublicMessageLink({
|
||||||
username: domain,
|
username: domain,
|
||||||
messageId: post,
|
messageId: post,
|
||||||
isSingle: single,
|
single,
|
||||||
threadId: thread,
|
threadId: thread,
|
||||||
commentId: comment,
|
commentId: comment,
|
||||||
mediaTimestamp: t,
|
mediaTimestamp: t,
|
||||||
|
boost,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'privateMessageLink': {
|
case 'privateMessageLink': {
|
||||||
const {
|
const {
|
||||||
channel, post, single, thread, comment, t,
|
channel, post, single, thread, comment, t, boost,
|
||||||
} = queryParams;
|
} = queryParams;
|
||||||
return buildPrivateMessageLink({
|
return buildPrivateMessageLink({
|
||||||
channelId: channel,
|
channelId: channel,
|
||||||
messageId: post,
|
messageId: post,
|
||||||
isSingle: single,
|
single,
|
||||||
threadId: thread,
|
threadId: thread,
|
||||||
commentId: comment,
|
commentId: comment,
|
||||||
mediaTimestamp: t,
|
mediaTimestamp: t,
|
||||||
|
boost,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'shareLink':
|
case 'shareLink':
|
||||||
@ -156,7 +170,7 @@ function handleHttpLink(url: URL) {
|
|||||||
switch (deepLinkType) {
|
switch (deepLinkType) {
|
||||||
case 'publicMessageLink': {
|
case 'publicMessageLink': {
|
||||||
const {
|
const {
|
||||||
single, comment, t,
|
single, comment, t, boost,
|
||||||
} = queryParams;
|
} = queryParams;
|
||||||
const {
|
const {
|
||||||
username,
|
username,
|
||||||
@ -174,15 +188,16 @@ function handleHttpLink(url: URL) {
|
|||||||
return buildPublicMessageLink({
|
return buildPublicMessageLink({
|
||||||
username,
|
username,
|
||||||
messageId,
|
messageId,
|
||||||
isSingle: single,
|
single,
|
||||||
threadId: thread,
|
threadId: thread,
|
||||||
commentId: comment,
|
commentId: comment,
|
||||||
mediaTimestamp: t,
|
mediaTimestamp: t,
|
||||||
|
boost,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'privateMessageLink': {
|
case 'privateMessageLink': {
|
||||||
const {
|
const {
|
||||||
single, comment, t,
|
single, comment, t, boost,
|
||||||
} = queryParams;
|
} = queryParams;
|
||||||
const {
|
const {
|
||||||
channelId,
|
channelId,
|
||||||
@ -200,10 +215,11 @@ function handleHttpLink(url: URL) {
|
|||||||
return buildPrivateMessageLink({
|
return buildPrivateMessageLink({
|
||||||
channelId,
|
channelId,
|
||||||
messageId,
|
messageId,
|
||||||
isSingle: single,
|
single,
|
||||||
threadId: thread,
|
threadId: thread,
|
||||||
commentId: comment,
|
commentId: comment,
|
||||||
mediaTimestamp: t,
|
mediaTimestamp: t,
|
||||||
|
boost,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'shareLink': {
|
case 'shareLink': {
|
||||||
@ -306,9 +322,9 @@ function buildShareLink(params: BuilderParams<ShareLink>): BuilderReturnType<Sha
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPublicMessageLink(params: BuilderParams<PublicMessageLink>): BuilderReturnType<PublicMessageLink> {
|
function buildPublicMessageLink(params: PublicMessageLinkBuilderParams): BuilderReturnType<PublicMessageLink> {
|
||||||
const {
|
const {
|
||||||
messageId, threadId, commentId, username, isSingle, mediaTimestamp,
|
messageId, threadId, commentId, username, single, mediaTimestamp, boost,
|
||||||
} = params;
|
} = params;
|
||||||
if (!username || !isUsernameValid(username)) {
|
if (!username || !isUsernameValid(username)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -326,16 +342,17 @@ function buildPublicMessageLink(params: BuilderParams<PublicMessageLink>): Build
|
|||||||
type: 'publicMessageLink',
|
type: 'publicMessageLink',
|
||||||
username,
|
username,
|
||||||
messageId: Number(messageId),
|
messageId: Number(messageId),
|
||||||
isSingle: isSingle === '',
|
isSingle: single === '',
|
||||||
threadId: threadId ? Number(threadId) : undefined,
|
threadId: threadId ? Number(threadId) : undefined,
|
||||||
commentId: commentId ? Number(commentId) : undefined,
|
commentId: commentId ? Number(commentId) : undefined,
|
||||||
mediaTimestamp,
|
mediaTimestamp,
|
||||||
|
isBoost: boost === '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPrivateMessageLink(params: BuilderParams<PrivateMessageLink>): BuilderReturnType<PrivateMessageLink> {
|
function buildPrivateMessageLink(params: PrivateMessageLinkBuilderParams): BuilderReturnType<PrivateMessageLink> {
|
||||||
const {
|
const {
|
||||||
messageId, threadId, commentId, channelId, isSingle, mediaTimestamp,
|
messageId, threadId, commentId, channelId, single, mediaTimestamp, boost,
|
||||||
} = params;
|
} = params;
|
||||||
if (!channelId || !isNumber(channelId)) {
|
if (!channelId || !isNumber(channelId)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -353,10 +370,11 @@ function buildPrivateMessageLink(params: BuilderParams<PrivateMessageLink>): Bui
|
|||||||
type: 'privateMessageLink',
|
type: 'privateMessageLink',
|
||||||
channelId,
|
channelId,
|
||||||
messageId: Number(messageId),
|
messageId: Number(messageId),
|
||||||
isSingle: isSingle === '',
|
isSingle: single === '',
|
||||||
threadId: threadId ? Number(threadId) : undefined,
|
threadId: threadId ? Number(threadId) : undefined,
|
||||||
commentId: commentId ? Number(commentId) : undefined,
|
commentId: commentId ? Number(commentId) : undefined,
|
||||||
mediaTimestamp,
|
mediaTimestamp,
|
||||||
|
isBoost: boost === '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,42 @@
|
|||||||
import { getActions } from '../global';
|
import { getActions } from '../global';
|
||||||
|
|
||||||
import type { ApiChatType } from '../api/types';
|
import type { ApiChatType } from '../api/types';
|
||||||
import type { DeepLinkMethod } from './deepLinkParser';
|
import type { DeepLinkMethod, PrivateMessageLink } from './deepLinkParser';
|
||||||
|
|
||||||
import { API_CHAT_TYPES } from '../config';
|
import { API_CHAT_TYPES } from '../config';
|
||||||
|
import { toChannelId } from '../global/helpers';
|
||||||
|
import { tryParseDeepLink } from './deepLinkParser';
|
||||||
import { IS_SAFARI } from './windowEnvironment';
|
import { IS_SAFARI } from './windowEnvironment';
|
||||||
|
|
||||||
export const processDeepLink = (url: string) => {
|
export const processDeepLink = (url: string): boolean => {
|
||||||
|
const actions = getActions();
|
||||||
|
|
||||||
|
const parsedLink = tryParseDeepLink(url);
|
||||||
|
if (parsedLink) {
|
||||||
|
switch (parsedLink.type) {
|
||||||
|
case 'privateMessageLink':
|
||||||
|
handlePrivateMessageLink(parsedLink, actions);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
protocol, searchParams, pathname, hostname,
|
protocol, searchParams, pathname, hostname,
|
||||||
} = new URL(url);
|
} = new URL(url);
|
||||||
|
|
||||||
if (protocol !== 'tg:') return;
|
if (protocol !== 'tg:') return false;
|
||||||
|
|
||||||
|
// Safari thinks the path in tg://path links is hostname for some reason
|
||||||
|
const method = (IS_SAFARI ? hostname : pathname).replace(/^\/\//, '') as DeepLinkMethod;
|
||||||
|
const params = Object.fromEntries(searchParams);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openChatByInvite,
|
openChatByInvite,
|
||||||
openChatByUsername,
|
openChatByUsername,
|
||||||
openChatByPhoneNumber,
|
openChatByPhoneNumber,
|
||||||
openStickerSet,
|
openStickerSet,
|
||||||
focusMessage,
|
|
||||||
joinVoiceChatByLink,
|
joinVoiceChatByLink,
|
||||||
openInvoice,
|
openInvoice,
|
||||||
processAttachBotParameters,
|
processAttachBotParameters,
|
||||||
@ -27,11 +45,7 @@ export const processDeepLink = (url: string) => {
|
|||||||
openStoryViewerByUsername,
|
openStoryViewerByUsername,
|
||||||
processBoostParameters,
|
processBoostParameters,
|
||||||
checkGiftCode,
|
checkGiftCode,
|
||||||
} = getActions();
|
} = actions;
|
||||||
|
|
||||||
// Safari thinks the path in tg://path links is hostname for some reason
|
|
||||||
const method = (IS_SAFARI ? hostname : pathname).replace(/^\/\//, '') as DeepLinkMethod;
|
|
||||||
const params = Object.fromEntries(searchParams);
|
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'resolve': {
|
case 'resolve': {
|
||||||
@ -84,24 +98,6 @@ export const processDeepLink = (url: string) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'privatepost': {
|
|
||||||
const {
|
|
||||||
post, channel,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
const hasBoost = params.hasOwnProperty('boost');
|
|
||||||
|
|
||||||
if (hasBoost) {
|
|
||||||
processBoostParameters({ usernameOrId: channel, isPrivate: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
focusMessage({
|
|
||||||
chatId: `-${channel}`,
|
|
||||||
messageId: Number(post),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'bg': {
|
case 'bg': {
|
||||||
// const {
|
// const {
|
||||||
// slug, color, rotation, mode, intensity, bg_color: bgColor, gradient,
|
// slug, color, rotation, mode, intensity, bg_color: bgColor, gradient,
|
||||||
@ -163,9 +159,9 @@ export const processDeepLink = (url: string) => {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Unsupported deeplink
|
// Unsupported deeplink
|
||||||
|
return false;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseChooseParameter(choose?: string) {
|
export function parseChooseParameter(choose?: string) {
|
||||||
@ -177,3 +173,22 @@ export function parseChooseParameter(choose?: string) {
|
|||||||
export function formatShareText(url?: string, text?: string, title?: string): string {
|
export function formatShareText(url?: string, text?: string, title?: string): string {
|
||||||
return [url, title, text].filter(Boolean).join('\n');
|
return [url, title, text].filter(Boolean).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePrivateMessageLink(link: PrivateMessageLink, actions: ReturnType<typeof getActions>) {
|
||||||
|
const {
|
||||||
|
focusMessage,
|
||||||
|
processBoostParameters,
|
||||||
|
} = actions;
|
||||||
|
const {
|
||||||
|
isBoost, channelId, messageId, threadId,
|
||||||
|
} = link;
|
||||||
|
if (isBoost) {
|
||||||
|
processBoostParameters({ usernameOrId: channelId, isPrivate: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
focusMessage({
|
||||||
|
chatId: toChannelId(channelId),
|
||||||
|
threadId,
|
||||||
|
messageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user