From 015c0b31c45279dffced266dead8d5a68321893d Mon Sep 17 00:00:00 2001 From: morethanwords Date: Wed, 11 Aug 2021 22:24:13 +0300 Subject: [PATCH] tg:// links --- src/lib/appManagers/appImManager.ts | 269 ++++++++++++++++++------ src/lib/appManagers/internalLink.ts | 48 +++++ src/lib/mtproto/telegramMeWebManager.ts | 5 +- src/lib/richtextprocessor.ts | 10 +- 4 files changed, 265 insertions(+), 67 deletions(-) create mode 100644 src/lib/appManagers/internalLink.ts diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 85dff69d..8ba9bdea 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -65,6 +65,8 @@ import { toast, toastNew } from '../../components/toast'; import debounce from '../../helpers/schedulers/debounce'; import { pause } from '../../helpers/schedulers/pause'; import appMessagesIdsManager from './appMessagesIdsManager'; +import { InternalLink, InternalLinkTypeMap, INTERNAL_LINK_TYPE } from './internalLink'; +import RichTextProcessor from '../richtextprocessor'; //console.log('appImManager included33!'); @@ -265,8 +267,7 @@ export class AppImManager { appMessagesManager.sendText(this.chat.peerId, '/' + command + (bot ? '@' + bot : '')); //console.log(command, bot); - }, - parseUriParams: true + } }); this.addAnchorListener<{uriParams: {hashtag: string}}>({ @@ -278,23 +279,193 @@ export class AppImManager { } this.chat.initSearch('#' + hashtag + ' '); - }, - parseUriParams: true + } }); this.addAnchorListener<{pathnameParams: ['addstickers', string]}>({ name: 'addstickers', callback: ({pathnameParams}) => { - new PopupStickers({id: pathnameParams[1]}).show(); - }, - parsePathname: true + const link: InternalLink = { + _: INTERNAL_LINK_TYPE.STICKER_SET, + set: pathnameParams[1] + }; + + this.processInternalLink(link); + } }); this.addAnchorListener<{pathnameParams: ['joinchat', string]}>({ name: 'joinchat', callback: ({pathnameParams}) => { + const link: InternalLink = { + _: INTERNAL_LINK_TYPE.JOIN_CHAT, + invite: pathnameParams[1] + }; + + this.processInternalLink(link); + } + }); + + this.addAnchorListener<{ + // pathnameParams: ['c', string, string], + // uriParams: {thread?: number} + // } | { + // pathnameParams: [string, string?], + // uriParams: {comment?: number} + pathnameParams: ['c', string, string] | [string, string?], + uriParams: {thread?: string, comment?: string} | {comment?: string} + }>({ + name: 'im', + callback: async({pathnameParams, uriParams}) => { + let link: InternalLink; + if(pathnameParams[0] === 'c') { + link = { + _: INTERNAL_LINK_TYPE.PRIVATE_POST, + channel: pathnameParams[1], + post: pathnameParams[2], + thread: 'thread' in uriParams ? uriParams.thread : undefined, + comment: uriParams.comment + }; + } else { + link = { + _: INTERNAL_LINK_TYPE.MESSAGE, + domain: pathnameParams[0], + post: pathnameParams[1], + comment: uriParams.comment + }; + } + + this.processInternalLink(link); + } + }); + + this.addAnchorListener<{ + uriParams: { + domain: string, + + // telegrampassport + scope?: string, + nonce?: string, + payload?: string, + bot_id?: string, + public_key?: string, + callback_url?: string, + + // regular + start?: string, + startgroup?: string, + game?: string, + voicechat?: string, + post?: string, + thread?: string, + comment?: string + } + }>({ + name: 'resolve', + protocol: 'tg', + callback: ({uriParams}) => { + let link: InternalLink; + if(uriParams.domain === 'telegrampassport') { + + } else { + link = this.makeLink(INTERNAL_LINK_TYPE.MESSAGE, uriParams); + } + + this.processInternalLink(link); + } + }); + + this.addAnchorListener<{ + uriParams: { + channel: string, + post: string, + thread?: string, + comment?: string + } + }>({ + name: 'privatepost', + protocol: 'tg', + callback: ({uriParams}) => { + const link = this.makeLink(INTERNAL_LINK_TYPE.PRIVATE_POST, uriParams); + this.processInternalLink(link); + } + }); + + this.addAnchorListener<{ + uriParams: { + set: string + } + }>({ + name: 'addstickers', + protocol: 'tg', + callback: ({uriParams}) => { + const link = this.makeLink(INTERNAL_LINK_TYPE.STICKER_SET, uriParams); + this.processInternalLink(link); + } + }); + + this.addAnchorListener<{ + uriParams: { + invite: string + } + }>({ + name: 'joinchat', + protocol: 'tg', + callback: ({uriParams}) => { + const link = this.makeLink(INTERNAL_LINK_TYPE.JOIN_CHAT, uriParams); + this.processInternalLink(link); + } + }); + + this.onHashChange(); + } + + private makeLink(type: T, uriParams: Omit) { + return { + _: type, + ...uriParams + } as any as InternalLinkTypeMap[T]; + } + + public async processInternalLink(link: InternalLink) { + switch(link?._) { + case INTERNAL_LINK_TYPE.MESSAGE: { + const postId = link.post ? appMessagesIdsManager.generateMessageId(+link.post) : undefined; + const commentId = link.comment ? appMessagesIdsManager.generateMessageId(+link.comment) : undefined; + + this.openUsername(link.domain, postId, undefined, commentId); + break; + } + + case INTERNAL_LINK_TYPE.PRIVATE_POST: { + const peerId = -+link.channel; + + const chat = appChatsManager.getChat(-peerId); + if(chat.deleted) { + try { + await appChatsManager.resolveChannel(-peerId); + } catch(err) { + toastNew({langPackKey: 'LinkNotFound'}); + throw err; + } + } + + const postId = appMessagesIdsManager.generateMessageId(+link.post); + const threadId = link.thread ? appMessagesIdsManager.generateMessageId(+link.thread) : undefined; + + if(threadId) this.openThread(peerId, postId, threadId); + else this.setInnerPeer(peerId, postId); + break; + } + + case INTERNAL_LINK_TYPE.STICKER_SET: { + new PopupStickers({id: link.set}).show(); + break; + } + + case INTERNAL_LINK_TYPE.JOIN_CHAT: { apiManager.invokeApi('messages.checkChatInvite', { - hash: pathnameParams[1] + hash: link.invite }).then(chatInvite => { if((chatInvite as ChatInvite.chatInvitePeek).chat) { appChatsManager.saveApiChat((chatInvite as ChatInvite.chatInvitePeek).chat, true); @@ -308,76 +479,39 @@ export class AppImManager { return; } - new PopupJoinChatInvite(pathnameParams[1], chatInvite).show(); + new PopupJoinChatInvite(link.invite, chatInvite).show(); }, (err) => { if(err.type === 'INVITE_HASH_EXPIRED') { toast(i18n('InviteExpired')); } }); - }, - parsePathname: true - }); - - this.addAnchorListener<{ - // pathnameParams: ['c', string, string], - // uriParams: {thread?: number} - // } | { - // pathnameParams: [string, string?], - // uriParams: {comment?: number} - pathnameParams: ['c', string, string] | [string, string?], - uriParams: {thread?: number} | {comment?: number} - }>({ - name: 'im', - callback: async(params) => { - console.log(params); - - const {pathnameParams, uriParams} = params; - if(pathnameParams[0] === 'c') { - const peerId = -+pathnameParams[1]; - - const chat = appChatsManager.getChat(-peerId); - if(chat.deleted) { - try { - await appChatsManager.resolveChannel(-peerId); - } catch(err) { - toastNew({langPackKey: 'LinkNotFound'}); - throw err; - } - } - - const postId = appMessagesIdsManager.generateMessageId(+pathnameParams[2]); - const threadId = 'thread' in uriParams ? appMessagesIdsManager.generateMessageId(+uriParams.thread) : undefined; - - if(threadId) this.openThread(peerId, postId, threadId); - else this.setInnerPeer(peerId, postId); - } else { - const username = pathnameParams[0]; - const postId = pathnameParams[1] ? appMessagesIdsManager.generateMessageId(+pathnameParams[1]) : undefined; - const commentId = 'comment' in uriParams ? appMessagesIdsManager.generateMessageId(uriParams.comment) : undefined; + break; + } - this.openUsername(username, postId, undefined, commentId); - } - }, - parsePathname: true, - parseUriParams: true - }); + default: { + this.log.warn('Not supported internal link:', link); + break; + } + } } private addAnchorListener(options: { - name: 'showMaskedAlert' | 'execBotCommand' | 'searchByHashtag' | 'addstickers' | 'joinchat' | 'im', - callback: (params: Params, element: HTMLAnchorElement) => boolean | any, - parsePathname?: boolean, - parseUriParams?: boolean, + name: 'showMaskedAlert' | 'execBotCommand' | 'searchByHashtag' | 'addstickers' | 'joinchat' | 'im' | + 'resolve' | 'privatepost' | 'addstickers', + protocol?: 'tg', + callback: (params: Params, element?: HTMLAnchorElement) => boolean | any, + noPathnameParams?: boolean, + noUriParams?: boolean }) { - (window as any)[options.name] = (element: HTMLAnchorElement/* , e: Event */) => { + (window as any)[(options.protocol ? options.protocol + '_' : '') + options.name] = (element?: HTMLAnchorElement/* , e: Event */) => { cancelEvent(null); const href = element.href; let pathnameParams: any[]; let uriParams: any; - if(options.parsePathname) pathnameParams = new URL(element.href).pathname.split('/').slice(1); - if(options.parseUriParams) uriParams = this.parseUriParams(href); + if(!options.noPathnameParams) pathnameParams = new URL(element.href).pathname.split('/').slice(1); + if(!options.noUriParams) uriParams = this.parseUriParams(href); const res = options.callback({pathnameParams, uriParams} as Params, element); return res === undefined ? res : false; @@ -402,6 +536,17 @@ export class AppImManager { this.log('hashchange', hash, splitted[0], params); + if(params.tgaddr) { + appNavigationController.replaceState(); + const {onclick} = RichTextProcessor.wrapUrl(params.tgaddr); + if(onclick) { + const a = document.createElement('a'); + a.href = params.tgaddr; + (window as any)[onclick](a); + } + return; + } + switch(splitted[0]) { case '#/im': { const p = params.p; diff --git a/src/lib/appManagers/internalLink.ts b/src/lib/appManagers/internalLink.ts new file mode 100644 index 00000000..04a7bc1b --- /dev/null +++ b/src/lib/appManagers/internalLink.ts @@ -0,0 +1,48 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +export enum INTERNAL_LINK_TYPE { + MESSAGE, + PRIVATE_POST, + STICKER_SET, + JOIN_CHAT +}; + +export type InternalLink = InternalLink.InternalLinkMessage | InternalLink.InternalLinkPrivatePost | InternalLink.InternalLinkStickerSet | InternalLink.InternalLinkJoinChat; + +export namespace InternalLink { + export interface InternalLinkMessage { + _: INTERNAL_LINK_TYPE.MESSAGE, + domain: string, + post?: string, + comment?: string + } + + export interface InternalLinkPrivatePost { + _: INTERNAL_LINK_TYPE.PRIVATE_POST, + channel: string, + post: string, + thread?: string, + comment?: string + } + + export interface InternalLinkStickerSet { + _: INTERNAL_LINK_TYPE.STICKER_SET, + set: string + } + + export interface InternalLinkJoinChat { + _: INTERNAL_LINK_TYPE.JOIN_CHAT, + invite: string + } +} + +export type InternalLinkTypeMap = { + [INTERNAL_LINK_TYPE.MESSAGE]: InternalLink.InternalLinkMessage, + [INTERNAL_LINK_TYPE.PRIVATE_POST]: InternalLink.InternalLinkPrivatePost, + [INTERNAL_LINK_TYPE.STICKER_SET]: InternalLink.InternalLinkStickerSet, + [INTERNAL_LINK_TYPE.JOIN_CHAT]: InternalLink.InternalLinkJoinChat +}; diff --git a/src/lib/mtproto/telegramMeWebManager.ts b/src/lib/mtproto/telegramMeWebManager.ts index 65824bfa..a354ef51 100644 --- a/src/lib/mtproto/telegramMeWebManager.ts +++ b/src/lib/mtproto/telegramMeWebManager.ts @@ -39,9 +39,10 @@ export class TelegramMeWebManager { } }); + const path = `_websync_?authed=${canRedirect ? '1' : '0'}&version=${encodeURIComponent(App.version + ' ' + App.suffix)}`; const urls = [ - '//telegram.me/_websync_?authed=' + (canRedirect ? '1' : '0'), - '//t.me/_websync_?authed=' + (canRedirect ? '1' : '0') + '//telegram.me/' + path, + '//t.me/' + path ]; const promises = urls.map(url => { diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts index 3cde35c7..1d3aac93 100644 --- a/src/lib/richtextprocessor.ts +++ b/src/lib/richtextprocessor.ts @@ -801,8 +801,7 @@ namespace RichTextProcessor { url = 'https://' + url; } - let tgMeMatch; - let telescoPeMatch; + let tgMeMatch, telescoPeMatch, tgMatch; let onclick: string; /* if(unsafe === 2) { url = 'tg://unsafe_url?url=' + encodeURIComponent(url); @@ -824,7 +823,12 @@ namespace RichTextProcessor { break; } } else if((telescoPeMatch = url.match(/^(?:https?:\/\/)?telesco\.pe\/([^/?]+)\/(\d+)/))) { - url = 'tg://resolve?domain=' + telescoPeMatch[1] + '&post=' + telescoPeMatch[2]; + onclick = 'im'; + } else if((tgMatch = url.match(/tg:(?:\/\/)?(.+?)(?:\?|$)/))) { + onclick = 'tg_' + tgMatch[1]; + if(!(window as any)[onclick]) { + onclick = undefined; + } }/* else if(unsafe) { url = 'tg://unsafe_url?url=' + encodeURIComponent(url); } */