DeepLinkParser: Add new types of the links (#4064)

This commit is contained in:
Alexander Zinchuk 2023-12-23 22:15:18 +01:00
parent fdd5c31623
commit 275ff8d938
2 changed files with 121 additions and 43 deletions

View File

@ -5,7 +5,7 @@ import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
import { GLOBAL_SEARCH_SLICE, GLOBAL_TOPIC_SEARCH_SLICE } from '../../../config'; import { GLOBAL_SEARCH_SLICE, GLOBAL_TOPIC_SEARCH_SLICE } from '../../../config';
import { timestampPlusDay } from '../../../util/dateFormat'; import { timestampPlusDay } from '../../../util/dateFormat';
import { DeepLinkType, isDeepLink, tryParseDeepLink } from '../../../util/deepLinkParser'; import { isDeepLink, tryParseDeepLink } from '../../../util/deepLinkParser';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
@ -172,9 +172,9 @@ async function searchMessagesGlobal<T extends GlobalState>(
}); });
if (isDeepLink(query)) { if (isDeepLink(query)) {
const link = tryParseDeepLink(query); const link = tryParseDeepLink(query);
if (link?.type === DeepLinkType.PublicMessageLink) { if (link?.type === 'publicMessageLink') {
messageLink = await getMessageByPublicLink(global, link); messageLink = await getMessageByPublicLink(global, link);
} else if (link?.type === DeepLinkType.PrivateMessageLink) { } else if (link?.type === 'privateMessageLink') {
messageLink = await getMessageByPrivateLink(global, link); messageLink = await getMessageByPrivateLink(global, link);
} }
} }

View File

@ -5,16 +5,8 @@ export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'jo
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' | 'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
'invoice' | 'addlist' | 'boost' | 'giftcode'; 'invoice' | 'addlist' | 'boost' | 'giftcode';
export enum DeepLinkType {
PublicMessageLink = 'PublicMessageLink',
PrivateMessageLink = 'PrivateMessageLink',
ShareLink = 'ShareLink',
ChatFolderLink = 'ChatFolderLink',
Unknown = 'Unknown',
}
interface PublicMessageLink { interface PublicMessageLink {
type: DeepLinkType.PublicMessageLink; type: 'publicMessageLink';
username: string; username: string;
messageId: number; messageId: number;
isSingle?: boolean; isSingle?: boolean;
@ -24,7 +16,7 @@ interface PublicMessageLink {
} }
interface PrivateMessageLink { interface PrivateMessageLink {
type: DeepLinkType.PrivateMessageLink; type: 'privateMessageLink';
channelId: string; channelId: string;
messageId: number; messageId: number;
isSingle?: boolean; isSingle?: boolean;
@ -34,20 +26,42 @@ interface PrivateMessageLink {
} }
interface ShareLink { interface ShareLink {
type: DeepLinkType.ShareLink; type: 'shareLink';
url: string; url: string;
text?: string; text?: string;
} }
interface ChatFolderLink { interface ChatFolderLink {
type: DeepLinkType.ChatFolderLink; type: 'chatFolderLink';
slug: string; slug: string;
} }
type DeepLink = PublicMessageLink | PrivateMessageLink | ShareLink | ChatFolderLink; interface LoginCodeLink {
type: 'loginCodeLink';
code: string;
}
interface TelegramPassportLink {
type: 'telegramPassportLink';
botId: number;
scope: string;
publicKey: string;
nonce: string;
callbackUrl?: string;
payload?: string;
}
type DeepLink =
TelegramPassportLink |
LoginCodeLink |
PublicMessageLink |
PrivateMessageLink |
ShareLink |
ChatFolderLink;
type BuilderParams<T extends DeepLink> = Record<keyof Omit<T, 'type'>, string>; 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';
const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']); const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']);
@ -84,7 +98,7 @@ function handleTgLink(url: URL) {
const deepLinkType = getTgDeepLinkType(queryParams, pathParams, method); const deepLinkType = getTgDeepLinkType(queryParams, pathParams, method);
switch (deepLinkType) { switch (deepLinkType) {
case DeepLinkType.PublicMessageLink: { case 'publicMessageLink': {
const { const {
domain, post, single, thread, comment, t, domain, post, single, thread, comment, t,
} = queryParams; } = queryParams;
@ -97,7 +111,7 @@ function handleTgLink(url: URL) {
mediaTimestamp: t, mediaTimestamp: t,
}); });
} }
case DeepLinkType.PrivateMessageLink: { case 'privateMessageLink': {
const { const {
channel, post, single, thread, comment, t, channel, post, single, thread, comment, t,
} = queryParams; } = queryParams;
@ -110,10 +124,21 @@ function handleTgLink(url: URL) {
mediaTimestamp: t, mediaTimestamp: t,
}); });
} }
case DeepLinkType.ShareLink: case 'shareLink':
return buildShareLink({ text: queryParams.text, url: queryParams.url }); return buildShareLink({ text: queryParams.text, url: queryParams.url });
case DeepLinkType.ChatFolderLink: case 'chatFolderLink':
return buildChatFolderLink({ slug: queryParams.slug }); return buildChatFolderLink({ slug: queryParams.slug });
case 'loginCodeLink':
return buildLoginCodeLink({ code: queryParams.code });
case 'telegramPassportLink':
return buildTelegramPassportLink({
botId: queryParams.bot_id,
scope: queryParams.scope,
publicKey: queryParams.public_key,
nonce: queryParams.nonce,
callbackUrl: queryParams.callback_url,
payload: queryParams.payload,
});
default: default:
break; break;
} }
@ -129,7 +154,7 @@ function handleHttpLink(url: URL) {
const deepLinkType = getHttpDeepLinkType(queryParams, pathParams); const deepLinkType = getHttpDeepLinkType(queryParams, pathParams);
switch (deepLinkType) { switch (deepLinkType) {
case DeepLinkType.PublicMessageLink: { case 'publicMessageLink': {
const { const {
single, comment, t, single, comment, t,
} = queryParams; } = queryParams;
@ -155,7 +180,7 @@ function handleHttpLink(url: URL) {
mediaTimestamp: t, mediaTimestamp: t,
}); });
} }
case DeepLinkType.PrivateMessageLink: { case 'privateMessageLink': {
const { const {
single, comment, t, single, comment, t,
} = queryParams; } = queryParams;
@ -181,11 +206,13 @@ function handleHttpLink(url: URL) {
mediaTimestamp: t, mediaTimestamp: t,
}); });
} }
case DeepLinkType.ShareLink: { case 'shareLink': {
return buildShareLink({ text: queryParams.text, url: queryParams.url }); return buildShareLink({ text: queryParams.text, url: queryParams.url });
} }
case DeepLinkType.ChatFolderLink: case 'chatFolderLink':
return buildChatFolderLink({ slug: pathParams[1] }); return buildChatFolderLink({ slug: pathParams[1] });
case 'loginCodeLink':
return buildLoginCodeLink({ code: pathParams[1] });
default: default:
break; break;
} }
@ -195,63 +222,76 @@ function handleHttpLink(url: URL) {
function getHttpDeepLinkType( function getHttpDeepLinkType(
queryParams: Record<string, string>, queryParams: Record<string, string>,
pathParams: string[], pathParams: string[],
) { ): DeepLinkType {
const len = pathParams.length; const len = pathParams.length;
const method = pathParams[0]; const method = pathParams[0];
if (len === 1) { if (len === 1) {
if (method === 'share') { if (method === 'share') {
return DeepLinkType.ShareLink; return 'shareLink';
} }
} else if (len === 2) { } else if (len === 2) {
if (method === 'addlist') { if (method === 'addlist') {
return DeepLinkType.ChatFolderLink; return 'chatFolderLink';
}
if (method === 'login') {
return 'loginCodeLink';
} }
if (isUsernameValid(pathParams[0]) && isNumber(pathParams[1])) { if (isUsernameValid(pathParams[0]) && isNumber(pathParams[1])) {
return DeepLinkType.PublicMessageLink; return 'publicMessageLink';
} }
} else if (len === 3) { } else if (len === 3) {
if (method === 'c' && pathParams.slice(1).every(isNumber)) { if (method === 'c' && pathParams.slice(1).every(isNumber)) {
return DeepLinkType.PrivateMessageLink; return 'privateMessageLink';
} }
if (isUsernameValid(pathParams[0]) && pathParams.slice(1).every(isNumber)) { if (isUsernameValid(pathParams[0]) && pathParams.slice(1).every(isNumber)) {
return DeepLinkType.PublicMessageLink; return 'publicMessageLink';
} }
} else if (len === 4) { } else if (len === 4) {
if (method === 'c' && pathParams.slice(1).every(isNumber)) { if (method === 'c' && pathParams.slice(1).every(isNumber)) {
return DeepLinkType.PrivateMessageLink; return 'privateMessageLink';
} }
} }
return DeepLinkType.Unknown; return 'unknown';
} }
function getTgDeepLinkType( function getTgDeepLinkType(
queryParams: Record<string, string>, queryParams: Record<string, string>,
pathParams: string[], pathParams: string[],
method: DeepLinkMethod, method: DeepLinkMethod,
) { ): DeepLinkType {
switch (method) { switch (method) {
case 'resolve': { case 'resolve': {
const { domain, post } = queryParams; const {
// eslint-disable-next-line @typescript-eslint/naming-convention
domain, post, bot_id, scope, public_key, nonce,
} = queryParams;
if (domain === 'telegrampassport' && bot_id && scope && public_key && nonce) {
return 'telegramPassportLink';
}
if (domain && post) { if (domain && post) {
return DeepLinkType.PublicMessageLink; return 'publicMessageLink';
} }
break; break;
} }
case 'privatepost': { case 'privatepost': {
const { channel, post } = queryParams; const { channel, post } = queryParams;
if (channel && post) { if (channel && post) {
return DeepLinkType.PrivateMessageLink; return 'privateMessageLink';
} }
break; break;
} }
case 'msg_url': case 'msg_url':
return DeepLinkType.ShareLink; return 'shareLink';
case 'addlist': case 'addlist':
return DeepLinkType.ChatFolderLink; return 'chatFolderLink';
case 'login':
return 'loginCodeLink';
case 'passport':
return 'telegramPassportLink';
default: default:
break; break;
} }
return DeepLinkType.Unknown; return 'unknown';
} }
function buildShareLink(params: BuilderParams<ShareLink>): BuilderReturnType<ShareLink> { function buildShareLink(params: BuilderParams<ShareLink>): BuilderReturnType<ShareLink> {
@ -260,7 +300,7 @@ function buildShareLink(params: BuilderParams<ShareLink>): BuilderReturnType<Sha
return undefined; return undefined;
} }
return { return {
type: DeepLinkType.ShareLink, type: 'shareLink',
url, url,
text, text,
}; };
@ -283,7 +323,7 @@ function buildPublicMessageLink(params: BuilderParams<PublicMessageLink>): Build
return undefined; return undefined;
} }
return { return {
type: DeepLinkType.PublicMessageLink, type: 'publicMessageLink',
username, username,
messageId: Number(messageId), messageId: Number(messageId),
isSingle: isSingle === '', isSingle: isSingle === '',
@ -310,7 +350,7 @@ function buildPrivateMessageLink(params: BuilderParams<PrivateMessageLink>): Bui
return undefined; return undefined;
} }
return { return {
type: DeepLinkType.PrivateMessageLink, type: 'privateMessageLink',
channelId, channelId,
messageId: Number(messageId), messageId: Number(messageId),
isSingle: isSingle === '', isSingle: isSingle === '',
@ -328,11 +368,49 @@ function buildChatFolderLink(params: BuilderParams<ChatFolderLink>): BuilderRetu
return undefined; return undefined;
} }
return { return {
type: DeepLinkType.ChatFolderLink, type: 'chatFolderLink',
slug, slug,
}; };
} }
function buildLoginCodeLink(params: BuilderParams<LoginCodeLink>): BuilderReturnType<LoginCodeLink> {
const {
code,
} = params;
if (!code) {
return undefined;
}
return {
type: 'loginCodeLink',
code,
};
}
function buildTelegramPassportLink(
params: BuilderParams<TelegramPassportLink>,
): BuilderReturnType<TelegramPassportLink> {
const {
botId,
scope,
publicKey,
nonce,
callbackUrl,
payload,
} = params;
if (!botId || !isNumber(botId) || !scope || !publicKey || !nonce) {
return undefined;
}
return {
type: 'telegramPassportLink',
botId: Number(botId),
scope,
publicKey,
nonce,
callbackUrl,
payload,
};
}
function isNumber(s: string) { function isNumber(s: string) {
return /^-?\d+$/.test(s); return /^-?\d+$/.test(s);
} }