Browse Source

tg:// links

master
morethanwords 3 years ago
parent
commit
015c0b31c4
  1. 269
      src/lib/appManagers/appImManager.ts
  2. 48
      src/lib/appManagers/internalLink.ts
  3. 5
      src/lib/mtproto/telegramMeWebManager.ts
  4. 10
      src/lib/richtextprocessor.ts

269
src/lib/appManagers/appImManager.ts

@ -65,6 +65,8 @@ import { toast, toastNew } from '../../components/toast'; @@ -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 { @@ -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 { @@ -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<T extends INTERNAL_LINK_TYPE>(type: T, uriParams: Omit<InternalLinkTypeMap[T], '_'>) {
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 { @@ -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<Params extends {pathnameParams?: any, uriParams?: any}>(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 { @@ -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;

48
src/lib/appManagers/internalLink.ts

@ -0,0 +1,48 @@ @@ -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
};

5
src/lib/mtproto/telegramMeWebManager.ts

@ -39,9 +39,10 @@ export class TelegramMeWebManager { @@ -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 => {

10
src/lib/richtextprocessor.ts

@ -801,8 +801,7 @@ namespace RichTextProcessor { @@ -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 { @@ -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);
} */

Loading…
Cancel
Save