Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

928 lines
30 KiB

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import Config from './config';
import emojiRegExp from '../vendor/emoji/regex';
import { encodeEmoji, toCodePoints } from '../vendor/emoji';
import { MessageEntity } from '../layer';
import { encodeEntities } from '../helpers/string';
import { IS_SAFARI } from '../environment/userAgent';
import { MOUNT_CLASS_TO } from '../config/debug';
import IS_EMOJI_SUPPORTED from '../environment/emojiSupport';
const EmojiHelper = {
emojiMap: (code: string) => { return code; },
shortcuts: [] as any,
emojis: [] as any
};
const emojiData = Config.Emoji;
const alphaCharsRegExp = 'a-z' +
'\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u00ff' + // Latin-1
'\\u0100-\\u024f' + // Latin Extended A and B
'\\u0253\\u0254\\u0256\\u0257\\u0259\\u025b\\u0263\\u0268\\u026f\\u0272\\u0289\\u028b' + // IPA Extensions
'\\u02bb' + // Hawaiian
'\\u0300-\\u036f' + // Combining diacritics
'\\u1e00-\\u1eff' + // Latin Extended Additional (mostly for Vietnamese)
'\\u0400-\\u04ff\\u0500-\\u0527' + // Cyrillic
'\\u2de0-\\u2dff\\ua640-\\ua69f' + // Cyrillic Extended A/B
'\\u0591-\\u05bf\\u05c1-\\u05c2\\u05c4-\\u05c5\\u05c7' +
'\\u05d0-\\u05ea\\u05f0-\\u05f4' + // Hebrew
'\\ufb1d-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40-\\ufb41' +
'\\ufb43-\\ufb44\\ufb46-\\ufb4f' + // Hebrew Pres. Forms
'\\u0610-\\u061a\\u0620-\\u065f\\u066e-\\u06d3\\u06d5-\\u06dc' +
'\\u06de-\\u06e8\\u06ea-\\u06ef\\u06fa-\\u06fc\\u06ff' + // Arabic
'\\u0750-\\u077f\\u08a0\\u08a2-\\u08ac\\u08e4-\\u08fe' + // Arabic Supplement and Extended A
'\\ufb50-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb' + // Pres. Forms A
'\\ufe70-\\ufe74\\ufe76-\\ufefc' + // Pres. Forms B
'\\u200c' + // Zero-Width Non-Joiner
'\\u0e01-\\u0e3a\\u0e40-\\u0e4e' + // Thai
'\\u1100-\\u11ff\\u3130-\\u3185\\uA960-\\uA97F\\uAC00-\\uD7AF\\uD7B0-\\uD7FF' + // Hangul (Korean)
'\\u3003\\u3005\\u303b' + // Kanji/Han iteration marks
'\\uff21-\\uff3a\\uff41-\\uff5a' + // full width Alphabet
'\\uff66-\\uff9f' + // half width Katakana
'\\uffa1-\\uffdc'; // half width Hangul (Korean)
const alphaNumericRegExp = '0-9\_' + alphaCharsRegExp;
const domainAddChars = '\u00b7';
// Based on Regular Expression for URL validation by Diego Perini
const urlAlphanumericRegExpPart = '[' + alphaCharsRegExp + '0-9]';
const urlProtocolRegExpPart = '((?:https?|ftp)://|mailto:)?';
const urlRegExp = urlProtocolRegExpPart +
// user:pass authentication
'(?:' + urlAlphanumericRegExpPart + '{1,64}(?::' + urlAlphanumericRegExpPart + '{0,64})?@)?' +
'(?:' +
// sindresorhus/ip-regexp
'(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}' +
'|' +
// host name
urlAlphanumericRegExpPart + '[' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}' +
// domain name
'(?:\\.' + urlAlphanumericRegExpPart + '[' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}){0,10}' +
// TLD identifier
'(?:\\.(xn--[0-9a-z]{2,16}|[' + alphaCharsRegExp + ']{2,24}))' +
')' +
// port number
'(?::\\d{2,5})?' +
// resource path
'(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>"\'])?)?';
const urlProtocolRegExp = new RegExp('^' + urlProtocolRegExpPart.slice(0, -1), 'i');
const urlAnyProtocolRegExp = /^((?:[^\/]+?):\/\/|mailto:)/;
const usernameRegExp = '[a-zA-Z\\d_]{5,32}';
const botCommandRegExp = '\\/([a-zA-Z\\d_]{1,32})(?:@(' + usernameRegExp + '))?(\\b|$)';
const fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|[\\s\\(\\]])(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i');
const emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
//const markdownTestRegExp = /[`_*@~]/;
const markdownRegExp = /(^|\s|\n)(````?)([\s\S]+?)(````?)([\s\n\.,:?!;]|$)|(^|\s|\x01)(`|~~|\*\*|__|_-_)([^\n]+?)\7([\x01\s\.,:?!;]|$)|@(\d+)\s*\((.+?)\)|(\[(.+?)\]\((.+?)\))/m;
const siteHashtags: {[siteName: string]: string} = {
Telegram: 'tg://search_hashtag?hashtag={1}',
Twitter: 'https://twitter.com/hashtag/{1}',
Instagram: 'https://instagram.com/explore/tags/{1}/',
'Google Plus': 'https://plus.google.com/explore/{1}'
};
const siteMentions: {[siteName: string]: string} = {
Telegram: '#/im?p=%40{1}',
Twitter: 'https://twitter.com/{1}',
Instagram: 'https://instagram.com/{1}/',
GitHub: 'https://github.com/{1}'
};
const markdownEntities: {[markdown: string]: MessageEntity['_']} = {
'`': 'messageEntityCode',
'``': 'messageEntityPre',
'**': 'messageEntityBold',
'__': 'messageEntityItalic',
'~~': 'messageEntityStrike',
'_-_': 'messageEntityUnderline'
};
const passConflictingEntities: Set<MessageEntity['_']> = new Set([
'messageEntityEmoji',
'messageEntityLinebreak',
'messageEntityCaret'
]);
for(let i in markdownEntities) {
passConflictingEntities.add(markdownEntities[i]);
}
namespace RichTextProcessor {
export function getEmojiSpritesheetCoords(emojiCode: string) {
let unified = encodeEmoji(emojiCode).replace(/-?fe0f/g, '');
/* if(unified === '1f441-200d-1f5e8') {
//unified = '1f441-fe0f-200d-1f5e8-fe0f';
unified = '1f441-fe0f-200d-1f5e8';
} */
if(!emojiData.hasOwnProperty(unified)
// && !emojiData.hasOwnProperty(unified.replace(/-?fe0f$/, ''))
) {
//console.error('lol', unified);
return null;
}
return unified;
}
export function parseEntities(text: string) {
let match: any;
let raw = text;
const entities: MessageEntity[] = [];
let matchIndex;
let rawOffset = 0;
// var start = tsNow()
fullRegExp.lastIndex = 0;
while((match = raw.match(fullRegExp))) {
matchIndex = rawOffset + match.index;
//console.log('parseEntities match:', match);
if(match[3]) { // mentions
entities.push({
_: 'messageEntityMention',
offset: matchIndex + match[1].length,
length: match[2].length + match[3].length
});
} else if(match[4]) {
if(emailRegExp.test(match[4])) { // email
entities.push({
_: 'messageEntityEmail',
offset: matchIndex,
length: match[4].length
});
} else {
let url: string;
let protocol = match[5];
const tld = match[6];
// let excluded = '';
if(tld) { // URL
if(!protocol && (tld.substr(0, 4) === 'xn--' || Config.TLD.indexOf(tld.toLowerCase()) !== -1)) {
protocol = 'http://';
}
if(protocol) {
const balanced = checkBrackets(match[4]);
if(balanced.length !== match[4].length) {
// excluded = match[4].substring(balanced.length);
match[4] = balanced;
}
url = (match[5] ? '' : protocol) + match[4];
}
} else { // IP address
url = (match[5] ? '' : 'http://') + match[4];
}
if(url) {
entities.push({
_: 'messageEntityUrl',
offset: matchIndex,
length: match[4].length
});
}
}
} else if(match[7]) { // New line
entities.push({
_: 'messageEntityLinebreak',
offset: matchIndex,
length: 1
});
} else if(match[8]) { // Emoji
//console.log('hit', match[8]);
const emojiCoords = getEmojiSpritesheetCoords(match[8]);
if(emojiCoords) {
entities.push({
_: 'messageEntityEmoji',
offset: matchIndex,
length: match[8].length,
unicode: emojiCoords
});
}
} else if(match[11]) { // Hashtag
entities.push({
_: 'messageEntityHashtag',
offset: matchIndex + (match[10] ? match[10].length : 0),
length: match[11].length
});
} else if(match[13]) { // Bot command
entities.push({
_: 'messageEntityBotCommand',
offset: matchIndex + (match[11] ? match[11].length : 0) + (match[12] ? match[12].length : 0),
length: 1 + match[13].length + (match[14] ? 1 + match[14].length : 0),
unsafe: true
});
}
raw = raw.substr(match.index + match[0].length);
rawOffset += match.index + match[0].length;
}
// if (entities.length) {
// console.log('parse entities', text, entities.slice())
// }
return entities;
}
/* export function parseEmojis(text: string) {
return text.replace(/:([a-z0-9\-\+\*_]+?):/gi, function (all, shortcut) {
var emojiCode = EmojiHelper.shortcuts[shortcut]
if (emojiCode !== undefined) {
return EmojiHelper.emojis[emojiCode][0]
}
return all
})
} */
export function parseMarkdown(raw: string, currentEntities: MessageEntity[], noTrim?: boolean): string {
  /* if(!markdownTestRegExp.test(text)) {
return noTrim ? text : text.trim();
} */
const entities: MessageEntity[] = [];
let pushedEntity = false;
const pushEntity = (entity: MessageEntity) => !findConflictingEntity(currentEntities, entity) ? (entities.push(entity), pushedEntity = true) : pushedEntity = false;
const newTextParts: string[] = [];
let rawOffset = 0, match;
while(match = raw.match(markdownRegExp)) {
const matchIndex = rawOffset + match.index;
newTextParts.push(raw.substr(0, match.index));
const text = (match[3] || match[8] || match[11] || match[13]);
rawOffset -= text.length;
//text = text.replace(/^\s+|\s+$/g, '');
rawOffset += text.length;
let entity: MessageEntity;
pushedEntity = false;
if(text.match(/^`*$/)) {
newTextParts.push(match[0]);
} else if(match[3]) { // pre
entity = {
_: 'messageEntityPre',
language: '',
offset: matchIndex + match[1].length,
length: text.length
};
if(pushEntity(entity)) {
if(match[5] === '\n') {
match[5] = '';
rawOffset -= 1;
}
newTextParts.push(match[1] + text + match[5]);
rawOffset -= match[2].length + match[4].length;
}
} else if(match[7]) { // code|italic|bold
const isSOH = match[6] === '\x01';
entity = {
_: markdownEntities[match[7]] as (MessageEntity.messageEntityBold | MessageEntity.messageEntityCode | MessageEntity.messageEntityItalic)['_'],
//offset: matchIndex + match[6].length,
offset: matchIndex + (isSOH ? 0 : match[6].length),
length: text.length
};
if(pushEntity(entity)) {
if(!isSOH) {
newTextParts.push(match[6] + text + match[9]);
} else {
newTextParts.push(text);
}
rawOffset -= match[7].length * 2 + (isSOH ? 2 : 0);
}
} else if(match[11]) { // custom mention
entity = {
_: 'messageEntityMentionName',
user_id: +match[10],
offset: matchIndex,
length: text.length
};
if(pushEntity(entity)) {
newTextParts.push(text);
rawOffset -= match[0].length - text.length;
}
} else if(match[12]) { // text url
entity = {
_: 'messageEntityTextUrl',
url: match[14],
offset: matchIndex,
length: text.length
};
if(pushEntity(entity)) {
newTextParts.push(text);
rawOffset -= match[12].length - text.length;
}
}
if(!pushedEntity) {
newTextParts.push(match[0]);
}
raw = raw.substr(match.index + match[0].length);
rawOffset += match.index + match[0].length;
}
newTextParts.push(raw);
let newText = newTextParts.join('');
if(!newText.replace(/\s+/g, '').length) {
newText = raw;
entities.splice(0, entities.length);
}
if(!entities.length && !noTrim) {
newText = newText.trim();
}
mergeEntities(currentEntities, entities);
combineSameEntities(currentEntities);
let length = newText.length;
if(!noTrim) {
// trim left
newText = newText.replace(/^\s*/, '');
let diff = length - newText.length;
if(diff) {
currentEntities.forEach(entity => {
entity.offset = Math.max(0, entity.offset - diff);
});
}
// trim right
newText = newText.replace(/\s*$/, '');
diff = length - newText.length;
length = newText.length;
if(diff) {
currentEntities.forEach(entity => {
if((entity.offset + entity.length) > length) {
entity.length = length - entity.offset;
}
});
}
}
return newText;
}
export function findConflictingEntity(currentEntities: MessageEntity[], newEntity: MessageEntity) {
return currentEntities.find(currentEntity => {
const isConflictingTypes = newEntity._ === currentEntity._ ||
(!passConflictingEntities.has(newEntity._) && !passConflictingEntities.has(currentEntity._));
if(!isConflictingTypes) {
return false;
}
const isConflictingOffset = newEntity.offset >= currentEntity.offset &&
(newEntity.length + newEntity.offset) <= (currentEntity.length + currentEntity.offset);
return isConflictingOffset;
});
}
export function mergeEntities(currentEntities: MessageEntity[], newEntities: MessageEntity[]) {
const filtered = newEntities.filter(e => {
return !findConflictingEntity(currentEntities, e);
});
currentEntities.push(...filtered);
currentEntities.sort((a, b) => a.offset - b.offset);
if(!IS_EMOJI_SUPPORTED) { // fix splitted emoji. messageEntityTextUrl can split the emoji if starts before its end (e.g. on fe0f)
for(let i = 0; i < currentEntities.length; ++i) {
const entity = currentEntities[i];
if(entity._ === 'messageEntityEmoji') {
const nextEntity = currentEntities[i + 1];
if(nextEntity && nextEntity.offset < (entity.offset + entity.length)) {
entity.length = nextEntity.offset - entity.offset;
}
}
}
}
return currentEntities;
}
export function combineSameEntities(entities: MessageEntity[]) {
//entities = entities.slice();
for(let i = 0; i < entities.length; ++i) {
const entity = entities[i];
let nextEntityIdx = -1;
do {
nextEntityIdx = entities.findIndex((e, _i) => _i !== i && e._ === entity._ && (e.offset - entity.length) === entity.offset);
if(nextEntityIdx !== -1) {
const nextEntity = entities[nextEntityIdx];
entity.length += nextEntity.length;
entities.splice(nextEntityIdx, 1);
}
} while(nextEntityIdx !== -1);
}
//return entities;
}
export function wrapRichText(text: string, options: Partial<{
entities: MessageEntity[],
contextSite: string,
highlightUsername: string,
noLinks: true,
noLinebreaks: true,
noCommands: true,
wrappingDraft: boolean,
//mustWrapEmoji: boolean,
fromBot: boolean,
noTextFormat: true,
passEntities: Partial<{
[_ in MessageEntity['_']]: boolean
}>,
contextHashtag?: string,
}> = {}) {
if(!text) {
return '';
}
const lol: {
part: string,
offset: number
}[] = [];
const entities = options.entities || parseEntities(text);
const passEntities: typeof options.passEntities = options.passEntities || {};
const contextSite = options.contextSite || 'Telegram';
const contextExternal = contextSite !== 'Telegram';
const insertPart = (entity: MessageEntity, startPart: string, endPart?: string) => {
lol.push({part: startPart, offset: entity.offset});
if(endPart) {
lol.unshift({part: endPart, offset: entity.offset + entity.length});
}
};
for(let i = 0, length = entities.length; i < length; ++i) {
const entity = entities[i];
switch(entity._) {
case 'messageEntityBold': {
if(!options.noTextFormat) {
if(options.wrappingDraft) {
insertPart(entity, '<span style="font-weight: bold;">', '</span>');
} else {
insertPart(entity, '<strong>', '</strong>');
}
}
break;
}
case 'messageEntityItalic': {
if(!options.noTextFormat) {
if(options.wrappingDraft) {
insertPart(entity, '<span style="font-style: italic;">', '</span>');
} else {
insertPart(entity, '<em>', '</em>');
}
}
break;
}
case 'messageEntityStrike': {
if(options.wrappingDraft) {
const styleName = IS_SAFARI ? 'text-decoration' : 'text-decoration-line';
insertPart(entity, `<span style="${styleName}: line-through;">`, '</span>');
} else {
insertPart(entity, '<del>', '</del>');
}
break;
}
case 'messageEntityUnderline': {
if(options.wrappingDraft) {
const styleName = IS_SAFARI ? 'text-decoration' : 'text-decoration-line';
insertPart(entity, `<span style="${styleName}: underline;">`, '</span>');
} else {
insertPart(entity, '<u>', '</u>');
}
break;
}
case 'messageEntityCode': {
if(options.wrappingDraft) {
insertPart(entity, '<span style="font-family: monospace;">', '</span>');
} else {
insertPart(entity, '<code>', '</code>');
}
break;
}
case 'messageEntityPre': {
if(!options.noTextFormat) {
insertPart(entity, `<pre><code${entity.language ? ' class="language-' + encodeEntities(entity.language) + '"' : ''}>`, '</code></pre>');
}
break;
}
case 'messageEntityHighlight': {
insertPart(entity, '<i class="text-highlight">', '</i>');
break;
}
case 'messageEntityBotCommand': {
// if(!(options.noLinks || options.noCommands || contextExternal)/* && !entity.unsafe */) {
if(!options.noLinks && passEntities[entity._]) {
const entityText = text.substr(entity.offset, entity.length);
let command = entityText.substr(1);
let bot: string | boolean;
let atPos: number;
if((atPos = command.indexOf('@')) !== -1) {
bot = command.substr(atPos + 1);
command = command.substr(0, atPos);
} else {
bot = options.fromBot;
}
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}" ${contextExternal ? '' : 'onclick="execBotCommand(this)"'}>`, `</a>`);
}
break;
}
case 'messageEntityEmoji': {
//if(!(options.wrappingDraft && IS_EMOJI_SUPPORTED)) { // * fix safari emoji
if(!IS_EMOJI_SUPPORTED) { // no wrapping needed
// if(IS_EMOJI_SUPPORTED) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
// insertPart(entity, '<span class="emoji">', '</span>');
// } else {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
// }
//} else if(options.mustWrapEmoji) {
} else if(!options.wrappingDraft) {
insertPart(entity, '<span class="emoji">', '</span>');
} else if(!IS_SAFARI) {
insertPart(entity, '<span class="emoji" contenteditable="false">', '</span>');
}
/* if(!IS_EMOJI_SUPPORTED) {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
} */
break;
}
case 'messageEntityCaret': {
insertPart(entity, '<span class="composer-sel"></span>');
break;
}
/* case 'messageEntityLinebreak': {
if(options.noLinebreaks) {
insertPart(entity, ' ');
} else {
insertPart(entity, '<br/>');
}
break;
} */
case 'messageEntityUrl':
case 'messageEntityTextUrl': {
if(!(options.noLinks && !passEntities[entity._])) {
const entityText = text.substr(entity.offset, entity.length);
// let inner: string;
let url: string = (entity as MessageEntity.messageEntityTextUrl).url || entityText;
let masked = false;
let onclick: string;
const wrapped = wrapUrl(url, true);
url = wrapped.url;
onclick = wrapped.onclick;
if(entity._ === 'messageEntityTextUrl') {
const nextEntity = entities[i + 1];
if(nextEntity?._ === 'messageEntityUrl' &&
nextEntity.length === entity.length &&
nextEntity.offset === entity.offset) {
i++;
}
if(url !== entityText) {
masked = true;
}
} else {
//inner = encodeEntities(replaceUrlEncodings(entityText));
}
const currentContext = !!onclick;
if(!onclick && masked && !currentContext) {
onclick = 'showMaskedAlert';
}
if(options.wrappingDraft) {
onclick = undefined;
}
const href = (currentContext || typeof electronHelpers === 'undefined')
? encodeEntities(url)
: `javascript:electronHelpers.openExternal('${encodeEntities(url)}');`;
const target = (currentContext || typeof electronHelpers !== 'undefined')
? '' : ' target="_blank" rel="noopener noreferrer"';
insertPart(entity, `<a class="anchor-url" href="${href}"${target}${onclick ? `onclick="${onclick}(this)"` : ''}>`, '</a>');
}
break;
}
case 'messageEntityEmail': {
if(!options.noLinks) {
const entityText = text.substr(entity.offset, entity.length);
insertPart(entity, `<a href="${encodeEntities('mailto:' + entityText)}" target="_blank" rel="noopener noreferrer">`, '</a>');
}
break;
}
case 'messageEntityHashtag': {
const contextUrl = !options.noLinks && siteHashtags[contextSite];
if(contextUrl) {
const entityText = text.substr(entity.offset, entity.length);
const hashtag = entityText.substr(1);
insertPart(entity, `<a class="anchor-hashtag" href="${contextUrl.replace('{1}', encodeURIComponent(hashtag))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ' onclick="searchByHashtag(this)"'}>`, '</a>');
}
break;
}
case 'messageEntityMentionName': {
if(!(options.noLinks && !passEntities[entity._])) {
insertPart(entity, `<a href="#/im?p=${encodeURIComponent(entity.user_id)}" class="follow" data-follow="${entity.user_id}">`, '</a>');
}
break;
}
case 'messageEntityMention': {
// const contextUrl = !options.noLinks && siteMentions[contextSite];
if(!options.noLinks) {
const entityText = text.substr(entity.offset, entity.length);
const username = entityText.substr(1);
const {url, onclick} = wrapUrl('t.me/' + username);
// insertPart(entity, `<a class="mention" href="${contextUrl.replace('{1}', encodeURIComponent(username))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ''}>`, '</a>');
insertPart(entity, `<a class="mention" href="${url}" ${onclick ? `onclick=${onclick}(this)` : ''}>`, '</a>');
}
break;
}
}
}
lol.sort((a, b) => a.offset - b.offset);
const arr: string[] = [];
let usedLength = 0;
for(const {part, offset} of lol) {
if(offset > usedLength) {
arr.push(encodeEntities(text.slice(usedLength, offset)));
usedLength = offset;
}
arr.push(part);
}
if(usedLength < text.length) {
arr.push(encodeEntities(text.slice(usedLength)));
}
return arr.join('');
}
export function fixEmoji(text: string, entities?: MessageEntity[]) {
/* if(!IS_EMOJI_SUPPORTED) {
return text;
} */
// '$`\ufe0f'
text = text.replace(/[\u2640\u2642\u2764](?!\ufe0f)/g, (match, offset, string) => {
if(entities) {
const length = match.length;
offset += length;
entities.forEach(entity => {
const end = entity.offset + entity.length;
if(end === offset) { // current entity
entity.length += length;
} else if(end > offset) {
entity.offset += length;
}
});
}
// console.log([match, offset, string]);
return match + '\ufe0f';
});
return text;
}
export function wrapDraftText(text: string, options: Partial<{
entities: MessageEntity[]
}> = {}) {
if(!text) {
return '';
}
return wrapRichText(text, {
entities: options.entities,
noLinks: true,
wrappingDraft: true,
passEntities: {
messageEntityTextUrl: true,
messageEntityMentionName: true
}
});
}
export function checkBrackets(url: string) {
var urlLength = url.length;
var urlOpenBrackets = url.split('(').length - 1;
var urlCloseBrackets = url.split(')').length - 1;
while(urlCloseBrackets > urlOpenBrackets &&
url.charAt(urlLength - 1) === ')') {
url = url.substr(0, urlLength - 1)
urlCloseBrackets--;
urlLength--;
}
if(urlOpenBrackets > urlCloseBrackets) {
url = url.replace(/\)+$/, '');
}
return url;
}
export function replaceUrlEncodings(urlWithEncoded: string) {
return urlWithEncoded.replace(/(%[A-Z\d]{2})+/g, (str) => {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
});
}
export function wrapPlainText(text: string) {
if(IS_EMOJI_SUPPORTED) {
return text;
}
if(!text || !text.length) {
return '';
}
text = text.replace(/\ufe0f/g, '');
var match;
var raw = text;
const arr: string[] = [];
let emojiTitle;
fullRegExp.lastIndex = 0;
while((match = raw.match(fullRegExp))) {
arr.push(raw.substr(0, match.index))
if(match[8]) {
// @ts-ignore
const emojiCode = EmojiHelper.emojiMap[match[8]];
if(emojiCode &&
// @ts-ignore
(emojiTitle = emojiData[emojiCode][1][0])) {
arr.push(':' + emojiTitle + ':');
} else {
arr.push(match[0]);
}
} else {
arr.push(match[0]);
}
raw = raw.substr(match.index + match[0].length);
}
arr.push(raw);
return arr.join('');
}
export function wrapEmojiText(text: string, isDraft = false) {
if(!text) return '';
let entities = parseEntities(text).filter(e => e._ === 'messageEntityEmoji');
return wrapRichText(text, {entities, wrappingDraft: isDraft});
}
export function wrapUrl(url: string, unsafe?: number | boolean): {url: string, onclick: string} {
if(!matchUrlProtocol(url)) {
url = 'https://' + url;
}
let tgMeMatch, telescoPeMatch, tgMatch;
let onclick: string;
/* if(unsafe === 2) {
url = 'tg://unsafe_url?url=' + encodeURIComponent(url);
} else */if((tgMeMatch = url.match(/^(?:https?:\/\/)?t(?:elegram)?\.me\/(.+)/))) {
const fullPath = tgMeMatch[1];
const path = fullPath.split('/');
switch(path[0]) {
case 'joinchat':
case 'addstickers':
onclick = path[0];
break;
default:
if((path[1] && path[1].match(/^\d+(?:\?(?:comment|thread)=\d+)?$/)) || path.length === 1) {
onclick = 'im';
break;
}
break;
}
} else if((telescoPeMatch = url.match(/^(?:https?:\/\/)?telesco\.pe\/([^/?]+)\/(\d+)/))) {
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);
} */
return {url, onclick};
}
export function matchUrlProtocol(text: string) {
return !text ? null : text.match(urlAnyProtocolRegExp);
}
export function matchUrl(text: string) {
return !text ? null : text.match(urlRegExp);
}
export function matchEmail(text: string) {
return !text ? null : text.match(emailRegExp);
}
export function getAbbreviation(str: string, onlyFirst = false) {
const splitted = str.trim().split(' ');
if(!splitted[0]) return '';
const first = [...splitted[0]][0];
if(onlyFirst || splitted.length === 1) return wrapEmojiText(first);
const last = [...splitted[splitted.length - 1]][0];
return wrapEmojiText(first + last);
}
export function isUsernameValid(username: string) {
return ((username.length >= 5 && username.length <= 32) || !username.length) && /^[a-zA-Z0-9_]*$/.test(username);
}
export function getEmojiEntityFromEmoji(emoji: string): MessageEntity.messageEntityEmoji {
return {
_: 'messageEntityEmoji',
offset: 0,
length: emoji.length,
unicode: toCodePoints(emoji).join('-').replace(/-?fe0f/g, '')
};
}
export function wrapSingleEmoji(emoji: string) {
return wrapRichText(emoji, {
entities: [getEmojiEntityFromEmoji(emoji)]
});
}
}
MOUNT_CLASS_TO.RichTextProcessor = RichTextProcessor;
export {RichTextProcessor};
export default RichTextProcessor;