refactor: improved typings for MessageEntity
This commit is contained in:
parent
5600f292f7
commit
75021648eb
14 changed files with 184 additions and 180 deletions
|
@ -1,5 +1,3 @@
|
||||||
import { isPresent } from '@mtcute/core/utils'
|
|
||||||
|
|
||||||
import { TelegramClient } from '../../client'
|
import { TelegramClient } from '../../client'
|
||||||
import { InputPeerLike, MessageEntity } from '../../types'
|
import { InputPeerLike, MessageEntity } from '../../types'
|
||||||
|
|
||||||
|
@ -28,5 +26,5 @@ export async function translateMessage(
|
||||||
toLang: toLanguage,
|
toLang: toLanguage,
|
||||||
})
|
})
|
||||||
|
|
||||||
return [res.result[0].text, res.result[0].entities.map((it) => MessageEntity._parse(it)).filter(isPresent)]
|
return [res.result[0].text, res.result[0].entities.map((it) => new MessageEntity(it, res.result[0].text))]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { tl } from '@mtcute/core'
|
import { tl } from '@mtcute/core'
|
||||||
import { isPresent } from '@mtcute/core/utils'
|
|
||||||
|
|
||||||
import { makeInspectable } from '../../utils'
|
import { makeInspectable } from '../../utils'
|
||||||
import { MessageEntity } from '../messages'
|
import { MessageEntity } from '../messages'
|
||||||
|
@ -37,7 +36,7 @@ export class TermsOfService {
|
||||||
* Terms of Service entities text
|
* Terms of Service entities text
|
||||||
*/
|
*/
|
||||||
get entities(): ReadonlyArray<MessageEntity> {
|
get entities(): ReadonlyArray<MessageEntity> {
|
||||||
return (this._entities ??= this.tos.entities.map((it) => MessageEntity._parse(it)).filter(isPresent))
|
return (this._entities ??= this.tos.entities.map((it) => new MessageEntity(it, this.tos.text)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,8 +152,7 @@ export class Poll {
|
||||||
|
|
||||||
if (this.results.solutionEntities?.length) {
|
if (this.results.solutionEntities?.length) {
|
||||||
for (const ent of this.results.solutionEntities) {
|
for (const ent of this.results.solutionEntities) {
|
||||||
const parsed = MessageEntity._parse(ent)
|
this._entities.push(new MessageEntity(ent, this.results.solution))
|
||||||
if (parsed) this._entities.push(parsed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,9 +167,9 @@ export class Poll {
|
||||||
* @param parseMode Parse mode to use (`null` for default)
|
* @param parseMode Parse mode to use (`null` for default)
|
||||||
*/
|
*/
|
||||||
unparseSolution(parseMode?: string | null): string | null {
|
unparseSolution(parseMode?: string | null): string | null {
|
||||||
if (!this.solution) return null
|
if (!this.results?.solutionEntities) return null
|
||||||
|
|
||||||
return this.client.getParseMode(parseMode).unparse(this.solution, this.solutionEntities!)
|
return this.client.getParseMode(parseMode).unparse(this.results.solution!, this.results.solutionEntities)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,8 +48,7 @@ export class DraftMessage {
|
||||||
|
|
||||||
if (this.raw.entities?.length) {
|
if (this.raw.entities?.length) {
|
||||||
for (const ent of this.raw.entities) {
|
for (const ent of this.raw.entities) {
|
||||||
const parsed = MessageEntity._parse(ent)
|
this._entities.push(new MessageEntity(ent, this.raw.message))
|
||||||
if (parsed) this._entities.push(parsed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,34 +2,13 @@ import { tl } from '@mtcute/core'
|
||||||
|
|
||||||
import { makeInspectable } from '../../utils'
|
import { makeInspectable } from '../../utils'
|
||||||
|
|
||||||
const entityToType: Partial<Record<tl.TypeMessageEntity['_'], MessageEntityType>> = {
|
|
||||||
messageEntityBlockquote: 'blockquote',
|
|
||||||
messageEntityBold: 'bold',
|
|
||||||
messageEntityBotCommand: 'bot_command',
|
|
||||||
messageEntityCashtag: 'cashtag',
|
|
||||||
messageEntityCode: 'code',
|
|
||||||
messageEntityEmail: 'email',
|
|
||||||
messageEntityHashtag: 'hashtag',
|
|
||||||
messageEntityItalic: 'italic',
|
|
||||||
messageEntityMention: 'mention',
|
|
||||||
messageEntityMentionName: 'text_mention',
|
|
||||||
messageEntityPhone: 'phone_number',
|
|
||||||
messageEntityPre: 'pre',
|
|
||||||
messageEntityStrike: 'strikethrough',
|
|
||||||
messageEntitySpoiler: 'spoiler',
|
|
||||||
messageEntityTextUrl: 'text_link',
|
|
||||||
messageEntityUnderline: 'underline',
|
|
||||||
messageEntityUrl: 'url',
|
|
||||||
messageEntityCustomEmoji: 'emoji',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of the entity. Can be:
|
* Params of the entity. `.kind` can be:
|
||||||
* - 'mention': `@username`.
|
* - 'mention': `@username`.
|
||||||
* - 'hashtag': `#hashtag`.
|
* - 'hashtag': `#hashtag`.
|
||||||
* - 'cashtag': `$USD`.
|
* - 'cashtag': `$USD`.
|
||||||
* - 'bot_command': `/start`.
|
* - 'bot_command': `/start`.
|
||||||
* - 'url': `https://example.com` (see {@link MessageEntity.url}).
|
* - 'url': `https://example.com`
|
||||||
* - 'email': `example@example.com`.
|
* - 'email': `example@example.com`.
|
||||||
* - 'phone_number': `+42000`.
|
* - 'phone_number': `+42000`.
|
||||||
* - 'bold': **bold text**.
|
* - 'bold': **bold text**.
|
||||||
|
@ -37,96 +16,145 @@ const entityToType: Partial<Record<tl.TypeMessageEntity['_'], MessageEntityType>
|
||||||
* - 'underline': <u>underlined</u> text.
|
* - 'underline': <u>underlined</u> text.
|
||||||
* - 'strikethrough': <s>strikethrough</s> text.
|
* - 'strikethrough': <s>strikethrough</s> text.
|
||||||
* - 'code': `monospaced` string.
|
* - 'code': `monospaced` string.
|
||||||
* - 'pre': `monospaced` block (see {@link MessageEntity.language}).
|
* - 'pre': `monospaced` block. `.language` contains the language of the block (if available).
|
||||||
* - 'text_link': for clickable text URLs.
|
* - 'text_link': for clickable text URLs.
|
||||||
* - 'text_mention': for users without usernames (see {@link MessageEntity.user} below).
|
* - 'text_mention': for user mention by name. `.userId` contains the ID of the mentioned user.
|
||||||
* - 'blockquote': A blockquote
|
* - 'blockquote': A blockquote
|
||||||
* - 'emoji': A custom emoji
|
* - 'emoji': A custom emoji. `.emojiId` contains the emoji ID.
|
||||||
*/
|
*/
|
||||||
export type MessageEntityType =
|
export type MessageEntityParams =
|
||||||
| 'mention'
|
| {
|
||||||
| 'hashtag'
|
kind:
|
||||||
| 'cashtag'
|
| 'mention'
|
||||||
| 'bot_command'
|
| 'hashtag'
|
||||||
| 'url'
|
| 'cashtag'
|
||||||
| 'email'
|
| 'bot_command'
|
||||||
| 'phone_number'
|
| 'url'
|
||||||
| 'bold'
|
| 'email'
|
||||||
| 'italic'
|
| 'phone_number'
|
||||||
| 'underline'
|
| 'bold'
|
||||||
| 'strikethrough'
|
| 'italic'
|
||||||
| 'spoiler'
|
| 'underline'
|
||||||
| 'code'
|
| 'strikethrough'
|
||||||
| 'pre'
|
| 'spoiler'
|
||||||
| 'text_link'
|
| 'code'
|
||||||
| 'text_mention'
|
| 'blockquote'
|
||||||
| 'blockquote'
|
| 'bank_card'
|
||||||
| 'emoji'
|
| 'unknown'
|
||||||
|
}
|
||||||
|
| { kind: 'pre'; language?: string }
|
||||||
|
| { kind: 'text_link'; url: string }
|
||||||
|
| { kind: 'text_mention'; userId: number }
|
||||||
|
| { kind: 'emoji'; emojiId: tl.Long }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kind of the entity. For more information, see {@link MessageEntityParams}
|
||||||
|
*/
|
||||||
|
export type MessageEntityKind = MessageEntityParams['kind']
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One special entity in a text message (like mention, hashtag, URL, etc.)
|
* One special entity in a text message (like mention, hashtag, URL, etc.)
|
||||||
*/
|
*/
|
||||||
export class MessageEntity<Type extends MessageEntityType = MessageEntityType> {
|
export class MessageEntity {
|
||||||
/**
|
constructor(readonly raw: tl.TypeMessageEntity, readonly _text?: string) {}
|
||||||
* Underlying raw TL object
|
|
||||||
*/
|
|
||||||
readonly raw!: tl.TypeMessageEntity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of the entity. See {@link MessageEntity.Type} for a list of possible values
|
|
||||||
*/
|
|
||||||
readonly type!: MessageEntityType
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offset in UTF-16 code units to the start of the entity.
|
* Offset in UTF-16 code units to the start of the entity.
|
||||||
*
|
*
|
||||||
* Since JS strings are UTF-16, you can use this as-is
|
* Since JS strings are UTF-16, you can use this as-is
|
||||||
*/
|
*/
|
||||||
readonly offset!: number
|
get offset() {
|
||||||
|
return this.raw.offset
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of the entity in UTF-16 code units.
|
* Length of the entity in UTF-16 code units.
|
||||||
*
|
*
|
||||||
* Since JS strings are UTF-16, you can use this as-is
|
* Since JS strings are UTF-16, you can use this as-is
|
||||||
*/
|
*/
|
||||||
readonly length!: number
|
get length() {
|
||||||
|
return this.raw.length
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When `type=text_link`, contains the URL that would be opened if user taps on the text
|
* Kind of the entity (see {@link MessageEntityParams})
|
||||||
*/
|
*/
|
||||||
readonly url?: Type extends 'text_link' ? string : never
|
get kind(): MessageEntityKind {
|
||||||
|
return this.params.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
private _params?: MessageEntityParams
|
||||||
/**
|
/**
|
||||||
* When `type=text_mention`, contains the ID of the user mentioned.
|
* Params of the entity
|
||||||
*/
|
*/
|
||||||
readonly userId?: number
|
get params(): MessageEntityParams {
|
||||||
|
if (this._params) return this._params
|
||||||
|
|
||||||
/**
|
switch (this.raw._) {
|
||||||
* When `type=pre`, contains the programming language of the entity text
|
case 'messageEntityMention':
|
||||||
*/
|
return (this._params = { kind: 'mention' })
|
||||||
readonly language?: string
|
case 'messageEntityHashtag':
|
||||||
|
return (this._params = { kind: 'hashtag' })
|
||||||
/**
|
case 'messageEntityCashtag':
|
||||||
* When `type=emoji`, ID of the custom emoji.
|
return (this._params = { kind: 'cashtag' })
|
||||||
* The emoji itself must be loaded separately (and presumably cached)
|
case 'messageEntityBotCommand':
|
||||||
* using {@link TelegramClient#getCustomEmojis}
|
return (this._params = { kind: 'bot_command' })
|
||||||
*/
|
case 'messageEntityUrl':
|
||||||
readonly emojiId?: tl.Long
|
return (this._params = { kind: 'url' })
|
||||||
|
case 'messageEntityEmail':
|
||||||
static _parse(obj: tl.TypeMessageEntity): MessageEntity | null {
|
return (this._params = { kind: 'email' })
|
||||||
const type = entityToType[obj._]
|
case 'messageEntityPhone':
|
||||||
if (!type) return null
|
return (this._params = { kind: 'phone_number' })
|
||||||
|
case 'messageEntityBold':
|
||||||
return {
|
return (this._params = { kind: 'bold' })
|
||||||
raw: obj,
|
case 'messageEntityItalic':
|
||||||
type,
|
return (this._params = { kind: 'italic' })
|
||||||
offset: obj.offset,
|
case 'messageEntityUnderline':
|
||||||
length: obj.length,
|
return (this._params = { kind: 'underline' })
|
||||||
url: obj._ === 'messageEntityTextUrl' ? obj.url : undefined,
|
case 'messageEntityStrike':
|
||||||
userId: obj._ === 'messageEntityMentionName' ? obj.userId : undefined,
|
return (this._params = { kind: 'strikethrough' })
|
||||||
language: obj._ === 'messageEntityPre' ? obj.language : undefined,
|
case 'messageEntitySpoiler':
|
||||||
emojiId: obj._ === 'messageEntityCustomEmoji' ? obj.documentId : undefined,
|
return (this._params = { kind: 'spoiler' })
|
||||||
|
case 'messageEntityCode':
|
||||||
|
return (this._params = { kind: 'code' })
|
||||||
|
case 'messageEntityPre':
|
||||||
|
return (this._params = { kind: 'pre', language: this.raw.language })
|
||||||
|
case 'messageEntityTextUrl':
|
||||||
|
return (this._params = { kind: 'text_link', url: this.raw.url })
|
||||||
|
case 'messageEntityMentionName':
|
||||||
|
return (this._params = { kind: 'text_mention', userId: this.raw.userId })
|
||||||
|
case 'messageEntityBlockquote':
|
||||||
|
return (this._params = { kind: 'blockquote' })
|
||||||
|
case 'messageEntityCustomEmoji':
|
||||||
|
return (this._params = { kind: 'emoji', emojiId: this.raw.documentId })
|
||||||
|
case 'messageEntityBankCard':
|
||||||
|
return (this._params = { kind: 'bank_card' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (this._params = { kind: 'unknown' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text contained in this entity.
|
||||||
|
*
|
||||||
|
* > **Note**: This does not take into account that entities may overlap,
|
||||||
|
* > and is only useful for simple cases.
|
||||||
|
*/
|
||||||
|
get text(): string {
|
||||||
|
if (!this._text) return ''
|
||||||
|
|
||||||
|
return this._text.slice(this.raw.offset, this.raw.offset + this.raw.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this entity is of the given type, and adjusts the type accordingly.
|
||||||
|
* @param kind
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
is<const T extends MessageEntityKind>(
|
||||||
|
kind: T,
|
||||||
|
): this is MessageEntity & { content: Extract<MessageEntityParams, { kind: T }>; kind: T } {
|
||||||
|
return this.params.kind === kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -404,8 +404,7 @@ export class Message {
|
||||||
|
|
||||||
if (this.raw._ === 'message' && this.raw.entities?.length) {
|
if (this.raw._ === 'message' && this.raw.entities?.length) {
|
||||||
for (const ent of this.raw.entities) {
|
for (const ent of this.raw.entities) {
|
||||||
const parsed = MessageEntity._parse(ent)
|
this._entities.push(new MessageEntity(ent, this.raw.message))
|
||||||
if (parsed) this._entities.push(parsed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,7 +571,9 @@ export class Message {
|
||||||
* @param parseMode Parse mode to use (`null` for default)
|
* @param parseMode Parse mode to use (`null` for default)
|
||||||
*/
|
*/
|
||||||
unparse(parseMode?: string | null): string {
|
unparse(parseMode?: string | null): string {
|
||||||
return this.client.getParseMode(parseMode).unparse(this.text, this.entities)
|
if (this.raw._ === 'messageService') return ''
|
||||||
|
|
||||||
|
return this.client.getParseMode(parseMode).unparse(this.text, this.raw.entities ?? [])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { tl } from '@mtcute/core'
|
import { tl } from '@mtcute/core'
|
||||||
|
|
||||||
import { MessageEntity } from '../types'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface describing a message entity parser.
|
* Interface describing a message entity parser.
|
||||||
|
*
|
||||||
* mtcute comes with HTML parser inside `@mtcute/html-parser`
|
* mtcute comes with HTML parser inside `@mtcute/html-parser`
|
||||||
* and MarkdownV2 parser inside `@mtcute/markdown-parser`,
|
* and Markdown parser inside `@mtcute/markdown-parser`.
|
||||||
* implemented similar to how they are described
|
|
||||||
* in the [Bot API documentation](https://core.telegram.org/bots/api#formatting-options).
|
|
||||||
*
|
*
|
||||||
* You are also free to implement your own parser and register it with
|
* You are also free to implement your own parser and register it with
|
||||||
* {@link TelegramClient.registerParseMode}.
|
* {@link TelegramClient.registerParseMode}.
|
||||||
|
@ -35,7 +32,7 @@ export interface IMessageEntityParser {
|
||||||
* @param text Plain text
|
* @param text Plain text
|
||||||
* @param entities Message entities that should be added to the text
|
* @param entities Message entities that should be added to the text
|
||||||
*/
|
*/
|
||||||
unparse(text: string, entities: ReadonlyArray<MessageEntity>): string
|
unparse(text: string, entities: ReadonlyArray<tl.TypeMessageEntity>): string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -588,9 +588,7 @@ export class Chat {
|
||||||
return new FormattedString(
|
return new FormattedString(
|
||||||
this.client.getParseMode(parseMode).unparse(text, [
|
this.client.getParseMode(parseMode).unparse(text, [
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line
|
_: 'messageEntityTextUrl',
|
||||||
raw: undefined as any,
|
|
||||||
type: 'text_link',
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
length: text.length,
|
length: text.length,
|
||||||
url: `https://t.me/${this.username}`,
|
url: `https://t.me/${this.username}`,
|
||||||
|
|
|
@ -359,9 +359,7 @@ export class User {
|
||||||
return new FormattedString(
|
return new FormattedString(
|
||||||
this.client.getParseMode(parseMode).unparse(text, [
|
this.client.getParseMode(parseMode).unparse(text, [
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line
|
_: 'messageEntityMentionName',
|
||||||
raw: undefined as any,
|
|
||||||
type: 'text_mention',
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
length: text.length,
|
length: text.length,
|
||||||
userId: this.raw.id,
|
userId: this.raw.id,
|
||||||
|
@ -415,9 +413,7 @@ export class User {
|
||||||
return new FormattedString(
|
return new FormattedString(
|
||||||
this.client.getParseMode(parseMode).unparse(text, [
|
this.client.getParseMode(parseMode).unparse(text, [
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line
|
_: 'messageEntityTextUrl',
|
||||||
raw: undefined as any,
|
|
||||||
type: 'text_link',
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
length: text.length,
|
length: text.length,
|
||||||
url: `tg://user?id=${this.id}&hash=${this.raw.accessHash.toString(16)}`,
|
url: `tg://user?id=${this.id}&hash=${this.raw.accessHash.toString(16)}`,
|
||||||
|
|
|
@ -100,8 +100,7 @@ export class Story {
|
||||||
|
|
||||||
if (this.raw.entities?.length) {
|
if (this.raw.entities?.length) {
|
||||||
for (const ent of this.raw.entities) {
|
for (const ent of this.raw.entities) {
|
||||||
const parsed = MessageEntity._parse(ent)
|
this._entities.push(new MessageEntity(ent, this.raw.caption))
|
||||||
if (parsed) this._entities.push(parsed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Parser } from 'htmlparser2'
|
import { Parser } from 'htmlparser2'
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
|
||||||
import type { FormattedString, IMessageEntityParser, MessageEntity, tl } from '@mtcute/client'
|
import type { FormattedString, IMessageEntityParser, tl } from '@mtcute/client'
|
||||||
|
|
||||||
const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
|
const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
|
||||||
|
|
||||||
|
@ -276,14 +276,14 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
|
||||||
return [plainText.replace(/\u00A0/g, ' '), entities]
|
return [plainText.replace(/\u00A0/g, ' '), entities]
|
||||||
}
|
}
|
||||||
|
|
||||||
unparse(text: string, entities: ReadonlyArray<MessageEntity>): string {
|
unparse(text: string, entities: ReadonlyArray<tl.TypeMessageEntity>): string {
|
||||||
return this._unparse(text, entities)
|
return this._unparse(text, entities)
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal function that uses recursion to correctly process nested & overlapping entities
|
// internal function that uses recursion to correctly process nested & overlapping entities
|
||||||
private _unparse(
|
private _unparse(
|
||||||
text: string,
|
text: string,
|
||||||
entities: ReadonlyArray<MessageEntity>,
|
entities: ReadonlyArray<tl.TypeMessageEntity>,
|
||||||
entitiesOffset = 0,
|
entitiesOffset = 0,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
length = text.length,
|
length = text.length,
|
||||||
|
@ -334,59 +334,59 @@ export class HtmlMessageEntityParser implements IMessageEntityParser {
|
||||||
const substr = text.substr(relativeOffset, length)
|
const substr = text.substr(relativeOffset, length)
|
||||||
if (!substr) continue
|
if (!substr) continue
|
||||||
|
|
||||||
const type = entity.type
|
const type = entity._
|
||||||
|
|
||||||
let entityText
|
let entityText
|
||||||
|
|
||||||
if (type === 'pre') {
|
if (type === 'messageEntityPre') {
|
||||||
entityText = substr
|
entityText = substr
|
||||||
} else {
|
} else {
|
||||||
entityText = this._unparse(substr, entities, i + 1, offset + relativeOffset, length)
|
entityText = this._unparse(substr, entities, i + 1, offset + relativeOffset, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'bold':
|
case 'messageEntityBold':
|
||||||
case 'italic':
|
case 'messageEntityItalic':
|
||||||
case 'underline':
|
case 'messageEntityUnderline':
|
||||||
case 'strikethrough':
|
case 'messageEntityStrike':
|
||||||
html.push(`<${type[0]}>${entityText}</${type[0]}>`)
|
case 'messageEntityCode':
|
||||||
|
case 'messageEntityBlockquote':
|
||||||
|
case 'messageEntitySpoiler':
|
||||||
|
{
|
||||||
|
const tag = (
|
||||||
|
{
|
||||||
|
messageEntityBold: 'b',
|
||||||
|
messageEntityItalic: 'i',
|
||||||
|
messageEntityUnderline: 'u',
|
||||||
|
messageEntityStrike: 's',
|
||||||
|
messageEntityCode: 'code',
|
||||||
|
messageEntityBlockquote: 'blockquote',
|
||||||
|
messageEntitySpoiler: 'spoiler',
|
||||||
|
} as const
|
||||||
|
)[type]
|
||||||
|
html.push(`<${tag}>${entityText}</${tag}>`)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'code':
|
case 'messageEntityPre':
|
||||||
case 'pre':
|
|
||||||
html.push(
|
html.push(
|
||||||
`<${type}${entity.language ? ` language="${entity.language}"` : ''}>${
|
`<pre${entity.language ? ` language="${entity.language}"` : ''}>${
|
||||||
this._syntaxHighlighter && entity.language ?
|
this._syntaxHighlighter && entity.language ?
|
||||||
this._syntaxHighlighter(entityText, entity.language) :
|
this._syntaxHighlighter(entityText, entity.language) :
|
||||||
entityText
|
entityText
|
||||||
}</${type}>`,
|
}</pre>`,
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case 'blockquote':
|
case 'messageEntityEmail':
|
||||||
case 'spoiler':
|
|
||||||
html.push(`<${type}>${entityText}</${type}>`)
|
|
||||||
break
|
|
||||||
case 'email':
|
|
||||||
html.push(`<a href="mailto:${entityText}">${entityText}</a>`)
|
html.push(`<a href="mailto:${entityText}">${entityText}</a>`)
|
||||||
break
|
break
|
||||||
case 'url':
|
case 'messageEntityUrl':
|
||||||
html.push(`<a href="${entityText}">${entityText}</a>`)
|
html.push(`<a href="${entityText}">${entityText}</a>`)
|
||||||
break
|
break
|
||||||
case 'text_link':
|
case 'messageEntityTextUrl':
|
||||||
html.push(
|
html.push(`<a href="${HtmlMessageEntityParser.escape(entity.url, true)}">${entityText}</a>`)
|
||||||
`<a href="${HtmlMessageEntityParser.escape(
|
|
||||||
// todo improve typings
|
|
||||||
|
|
||||||
entity.url!,
|
|
||||||
true,
|
|
||||||
)}">${entityText}</a>`,
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
case 'text_mention':
|
case 'messageEntityMentionName':
|
||||||
html.push(
|
html.push(`<a href="tg://user?id=${entity.userId}">${entityText}</a>`)
|
||||||
// todo improve typings
|
|
||||||
|
|
||||||
`<a href="tg://user?id=${entity.userId!}">${entityText}</a>`,
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
skip = true
|
skip = true
|
||||||
|
|
|
@ -2,8 +2,7 @@ import { expect } from 'chai'
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
import { describe, it } from 'mocha'
|
import { describe, it } from 'mocha'
|
||||||
|
|
||||||
import { FormattedString, MessageEntity, tl } from '@mtcute/client'
|
import { FormattedString, tl } from '@mtcute/client'
|
||||||
import { isPresent } from '@mtcute/client/utils'
|
|
||||||
|
|
||||||
import { html, HtmlMessageEntityParser } from '../src'
|
import { html, HtmlMessageEntityParser } from '../src'
|
||||||
|
|
||||||
|
@ -21,16 +20,12 @@ const createEntity = <T extends tl.TypeMessageEntity['_']>(
|
||||||
} as tl.TypeMessageEntity
|
} as tl.TypeMessageEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEntities = (entities: tl.TypeMessageEntity[]): MessageEntity[] => {
|
|
||||||
return entities.map((it) => MessageEntity._parse(it)).filter(isPresent)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('HtmlMessageEntityParser', () => {
|
describe('HtmlMessageEntityParser', () => {
|
||||||
const parser = new HtmlMessageEntityParser()
|
const parser = new HtmlMessageEntityParser()
|
||||||
|
|
||||||
describe('unparse', () => {
|
describe('unparse', () => {
|
||||||
const test = (text: string, entities: tl.TypeMessageEntity[], expected: string, _parser = parser): void => {
|
const test = (text: string, entities: tl.TypeMessageEntity[], expected: string, _parser = parser): void => {
|
||||||
expect(_parser.unparse(text, createEntities(entities))).eq(expected)
|
expect(_parser.unparse(text, entities)).eq(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should return the same text if there are no entities or text', () => {
|
it('should return the same text if there are no entities or text', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
|
||||||
import type { FormattedString, IMessageEntityParser, MessageEntity, tl } from '@mtcute/client'
|
import type { FormattedString, IMessageEntityParser, tl } from '@mtcute/client'
|
||||||
|
|
||||||
const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
|
const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
|
||||||
const EMOJI_REGEX = /^tg:\/\/emoji\?id=(-?\d+)/
|
const EMOJI_REGEX = /^tg:\/\/emoji\?id=(-?\d+)/
|
||||||
|
@ -303,7 +303,7 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
return [result, entities]
|
return [result, entities]
|
||||||
}
|
}
|
||||||
|
|
||||||
unparse(text: string, entities: ReadonlyArray<MessageEntity>): string {
|
unparse(text: string, entities: ReadonlyArray<tl.TypeMessageEntity>): string {
|
||||||
// keep track of positions of inserted escape symbols
|
// keep track of positions of inserted escape symbols
|
||||||
const escaped: number[] = []
|
const escaped: number[] = []
|
||||||
text = text.replace(TO_BE_ESCAPED, (s, pos: number) => {
|
text = text.replace(TO_BE_ESCAPED, (s, pos: number) => {
|
||||||
|
@ -317,7 +317,7 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
const insert: InsertLater[] = []
|
const insert: InsertLater[] = []
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
const type = entity.type
|
const type = entity._
|
||||||
|
|
||||||
let start = entity.offset
|
let start = entity.offset
|
||||||
let end = start + entity.length
|
let end = start + entity.length
|
||||||
|
@ -345,25 +345,25 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
let endTag: string
|
let endTag: string
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'bold':
|
case 'messageEntityBold':
|
||||||
startTag = endTag = TAG_BOLD
|
startTag = endTag = TAG_BOLD
|
||||||
break
|
break
|
||||||
case 'italic':
|
case 'messageEntityItalic':
|
||||||
startTag = endTag = TAG_ITALIC
|
startTag = endTag = TAG_ITALIC
|
||||||
break
|
break
|
||||||
case 'underline':
|
case 'messageEntityUnderline':
|
||||||
startTag = endTag = TAG_UNDERLINE
|
startTag = endTag = TAG_UNDERLINE
|
||||||
break
|
break
|
||||||
case 'strikethrough':
|
case 'messageEntityStrike':
|
||||||
startTag = endTag = TAG_STRIKE
|
startTag = endTag = TAG_STRIKE
|
||||||
break
|
break
|
||||||
case 'spoiler':
|
case 'messageEntitySpoiler':
|
||||||
startTag = endTag = TAG_SPOILER
|
startTag = endTag = TAG_SPOILER
|
||||||
break
|
break
|
||||||
case 'code':
|
case 'messageEntityCode':
|
||||||
startTag = endTag = TAG_CODE
|
startTag = endTag = TAG_CODE
|
||||||
break
|
break
|
||||||
case 'pre':
|
case 'messageEntityPre':
|
||||||
startTag = TAG_PRE
|
startTag = TAG_PRE
|
||||||
|
|
||||||
if (entity.language) {
|
if (entity.language) {
|
||||||
|
@ -373,17 +373,17 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
startTag += '\n'
|
startTag += '\n'
|
||||||
endTag = '\n' + TAG_PRE
|
endTag = '\n' + TAG_PRE
|
||||||
break
|
break
|
||||||
case 'text_link':
|
case 'messageEntityTextUrl':
|
||||||
startTag = '['
|
startTag = '['
|
||||||
endTag = `](${entity.url})`
|
endTag = `](${entity.url})`
|
||||||
break
|
break
|
||||||
case 'text_mention':
|
case 'messageEntityMentionName':
|
||||||
startTag = '['
|
startTag = '['
|
||||||
endTag = `](tg://user?id=${entity.userId})`
|
endTag = `](tg://user?id=${entity.userId})`
|
||||||
break
|
break
|
||||||
case 'emoji':
|
case 'messageEntityCustomEmoji':
|
||||||
startTag = '['
|
startTag = '['
|
||||||
endTag = `](tg://emoji?id=${entity.emojiId!.toString()})`
|
endTag = `](tg://emoji?id=${entity.documentId.toString()})`
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -2,8 +2,7 @@ import { expect } from 'chai'
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
import { describe, it } from 'mocha'
|
import { describe, it } from 'mocha'
|
||||||
|
|
||||||
import { FormattedString, MessageEntity, tl } from '@mtcute/client'
|
import { FormattedString, tl } from '@mtcute/client'
|
||||||
import { isPresent } from '@mtcute/client/utils'
|
|
||||||
|
|
||||||
import { MarkdownMessageEntityParser, md } from '../src'
|
import { MarkdownMessageEntityParser, md } from '../src'
|
||||||
|
|
||||||
|
@ -21,10 +20,6 @@ const createEntity = <T extends tl.TypeMessageEntity['_']>(
|
||||||
} as tl.TypeMessageEntity // idc really, its not that important
|
} as tl.TypeMessageEntity // idc really, its not that important
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEntities = (entities: tl.TypeMessageEntity[]): MessageEntity[] => {
|
|
||||||
return entities.map((it) => MessageEntity._parse(it)).filter(isPresent)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('MarkdownMessageEntityParser', () => {
|
describe('MarkdownMessageEntityParser', () => {
|
||||||
const parser = new MarkdownMessageEntityParser()
|
const parser = new MarkdownMessageEntityParser()
|
||||||
|
|
||||||
|
@ -35,7 +30,7 @@ describe('MarkdownMessageEntityParser', () => {
|
||||||
expected: string | string[],
|
expected: string | string[],
|
||||||
_parser = parser,
|
_parser = parser,
|
||||||
): void => {
|
): void => {
|
||||||
const result = _parser.unparse(text, createEntities(entities))
|
const result = _parser.unparse(text, entities)
|
||||||
|
|
||||||
if (Array.isArray(expected)) {
|
if (Array.isArray(expected)) {
|
||||||
expect(expected).to.include(result)
|
expect(expected).to.include(result)
|
||||||
|
|
Loading…
Reference in a new issue