Skip to content

Commit

Permalink
Added MarkdownV2 support (#580)
Browse files Browse the repository at this point in the history
* Added MarkdownV2 support

* Added MarkdownV2 test, fixed 'code' and 'pre' conflict

* Add type for markdownv2 to client.setParseMode
  • Loading branch information
ScribeSavant authored Sep 15, 2023
1 parent b51435d commit a3ddb35
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 4 deletions.
96 changes: 96 additions & 0 deletions __tests__/extensions/MarkdownV2.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { MarkdownV2Parser } from "../../gramjs/extensions/markdownv2";
import { Api as types } from "../../gramjs/tl/api";

describe("MarkdownV2Parser", () => {
describe(".parse", () => {
test("it should parse bold entities", () => {
const [text, entities] = MarkdownV2Parser.parse("Hello *world*");
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityBold);
});

test("it should parse italic entities", () => {
const [text, entities] = MarkdownV2Parser.parse("Hello -world-");
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityItalic);
});

test("it should parse code entities", () => {
const [text, entities] = MarkdownV2Parser.parse("Hello `world`");
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityCode);
});

test("it should parse pre entities", () => {
const [text, entities] = MarkdownV2Parser.parse("Hello ```world```");
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityPre);
});

test("it should parse strike entities", () => {
const [text, entities] = MarkdownV2Parser.parse("Hello ~world~");
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityStrike);
});

test("it should parse link entities", () => {
const [text, entities] = MarkdownV2Parser.parse(
"Hello [world](https://hello.world)"
);
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityTextUrl);
expect((entities[0] as types.MessageEntityTextUrl).url).toEqual(
"https://hello.world"
);
});

test("it should parse custom emoji", () =>{
const [text, entities] = MarkdownV2Parser.parse(
"![👍](tg://emoji?id=5368324170671202286)"
);
expect(text).toEqual("👍");
expect(entities.length).toEqual(1);
expect(entities[0]).toBeInstanceOf(types.MessageEntityCustomEmoji);
expect((entities[0] as types.MessageEntityCustomEmoji).documentId).toEqual(
"5368324170671202286"
);
} )

test("it should parse multiple entities", () => {
const [text, entities] = MarkdownV2Parser.parse("-Hello- *world*");
expect(text).toEqual("Hello world");
expect(entities.length).toEqual(2);
expect(entities[0]).toBeInstanceOf(types.MessageEntityItalic);
expect(entities[1]).toBeInstanceOf(types.MessageEntityBold);
});
});

describe(".unparse", () => {
// skipped until MarkDownV2
test.skip("it should create a markdown string from raw text and entities", () => {
const unparsed =
"*hello* -hello- ~hello~ `hello` ```hello``` [hello](https://hello.world)";
const strippedText = "hello hello hello hello hello hello";
const rawEntities = [
new types.MessageEntityBold({ offset: 0, length: 5 }),
new types.MessageEntityItalic({ offset: 6, length: 5 }),
new types.MessageEntityStrike({ offset: 12, length: 5 }),
new types.MessageEntityCode({ offset: 18, length: 5 }),
new types.MessageEntityPre({ offset: 24, length: 5, language: "" }),
new types.MessageEntityTextUrl({
offset: 30,
length: 5,
url: "https://hello.world",
}),
];
const text = MarkdownV2Parser.unparse(strippedText, rawEntities);
expect(text).toEqual(unparsed);
});
});
});
8 changes: 7 additions & 1 deletion gramjs/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import mime from "mime";
import type { ParseInterface } from "./client/messageParse";
import { MarkdownParser } from "./extensions/markdown";
import { CustomFile } from "./client/uploads";
import { MarkdownV2Parser } from "./extensions/markdownv2";

export function getFileInfo(
fileLocation:
Expand Down Expand Up @@ -1104,6 +1105,10 @@ export function sanitizeParseMode(
if (mode === "md" || mode === "markdown") {
return MarkdownParser;
}

if (mode === "md2" || mode === "markdownv2") {
return MarkdownV2Parser;
}
if (mode == "html") {
return HTMLParser;
}
Expand All @@ -1115,6 +1120,7 @@ export function sanitizeParseMode(
throw new Error(`Invalid parse mode type ${mode}`);
}


/**
Convert the given peer into its marked ID by default.
Expand Down Expand Up @@ -1356,4 +1362,4 @@ export function isListLike(item) {
)
)
}
*/
*/
2 changes: 2 additions & 0 deletions gramjs/client/TelegramClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,9 @@ export class TelegramClient extends TelegramBaseClient {
setParseMode(
mode:
| "md"
| "md2"
| "markdown"
| "markdownv2"
| "html"
| parseMethods.ParseInterface
| undefined
Expand Down
2 changes: 1 addition & 1 deletion gramjs/client/updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export async function _updateLoop(client: TelegramClient) {
PING_FAIL_INTERVAL
);
} else {
let wakeUpWarningTimeout: Timeout | undefined = setTimeout(
let wakeUpWarningTimeout: Timeout | undefined | number = setTimeout(
() => {
_handleUpdate(
client,
Expand Down
5 changes: 4 additions & 1 deletion gramjs/extensions/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,10 @@ export class HTMLParser {
html.push(
`<a href="tg://user?id=${entity.userId}">${entityText}</a>`
);
} else {
} else if (entity instanceof Api.MessageEntityCustomEmoji) {
html.push(`<tg-emoji emoji-id="${entity.documentId}">${entityText}</tg-emoji>`);
}
else {
skipEntity = true;
}
lastOffset = relativeOffset + (skipEntity ? 0 : length);
Expand Down
70 changes: 70 additions & 0 deletions gramjs/extensions/markdownv2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Api } from "../tl";
import { HTMLParser } from "./html";

export class MarkdownV2Parser {
static parse(message: string): [string, Api.TypeMessageEntity[]] {
// Bold
message = message.replace(/\*(.*?)\*/g, '<b>$1</b>');

// underline
message = message.replace(/__(.*?)__/g, '<u>$1</u>');

// strikethrough
message = message.replace(/~(.*?)~/g, '<s>$1</s>');

// italic
message = message.replace(/-(.*?)-/g, '<i>$1</i>');

// pre
message = message.replace(/```(.*?)```/g, '<pre>$1</pre>');

// code
message = message.replace(/`(.*?)`/g, '<code>$1</code>');

// Spoiler
message = message.replace(/\|\|(.*?)\|\|/g, '<spoiler>$1</spoiler>');

// Inline URL
message = message.replace(/(?<!\!)\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');

// Emoji
message = message.replace(/!\[([^\]]+)\]\(tg:\/\/emoji\?id=(\d+)\)/g, '<tg-emoji emoji-id="$2">$1</tg-emoji>');
return HTMLParser.parse(message)
}

static unparse(
text: string,
entities: Api.TypeMessageEntity[] | undefined
) {
text = HTMLParser.unparse(text, entities)

// Bold
text = text.replace(/<b>(.*?)<\/b>/g, '*$1*');

// Underline
text = text.replace(/<u>(.*?)<\/u>/g, '__$1__');

// Code
text = text.replace(/<code>(.*?)<\/code>/g, '`$1`');

// Pre
text = text.replace(/<pre>(.*?)<\/pre>/g, '```$1```');

// strikethrough
text = text.replace(/<s>(.*?)<\/s>/g, '~$1~');

// Italic
text = text.replace(/<i>(.*?)<\/i>/g, '-$1-');

// Spoiler
text = text.replace(/<spoiler>(.*?)<\/spoiler>/g, '||$1||');

// Inline URL
text = text.replace(/<a href="([^"]+)">([^<]+)<\/a>/g, '[$2]($1)');

// Emoji
text = text.replace(/<tg-emoji emoji-id="(\d+)">([^<]+)<\/tg-emoji>/g, '![$2](tg://emoji?id=$1)');

return text
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"lib": ["dom", "es7", "ES2019"],
"lib": ["dom", "es7", "ES2019", "ES2020"],
"sourceMap": false,
"downlevelIteration": true,
"allowJs": true,
Expand Down

0 comments on commit a3ddb35

Please sign in to comment.