Skip to main content

Using JSX for telegram bots

Image of the author

Vitali Haradkouvitalics

JSX <3 telegram

Idea

While I do my automation routine i was thinking about next thing: “why nobody use JSX for building messages, in most cases it’s just simple HTML or Markdown?“.

Yes, in mostly cases each chat application have possibility to send message as raw html. Also, I’m creator and maintainer [Telegraph](https://github.com/vitalics/Telegraph) repository.

This repo was created to simplify working with Telegram by simple wrapper. While I write [fmt](https://github.com/vitalics/Telegraph/tree/main/packages/fmt) package i was thinking about next thing: “wait a minute, context can be replying with html by replyWithHTML function. And also I’m familiar with React and JSX syntax, why nobody joining this ideas?”

POC

I start to think how to join JSX and formatting. For html it was simple: wrap each function to custom component which reuse built-in HTML tags.

Q: Why wrap?

A: Since each html tag may contains class, style, etc attributes, but in telegram it’s not applies, so attributes should be ignored.

HTML

Each component is very simple and primitive:

html/bold.tsx
import { h, JSX } from "preact";
type Props = {
children: string | JSX.Element;
};
export default function ({ children }: Props) {
return <b>{children}</b>;
}

As for render to HTML I found [preact-render-to-string](https://github.com/preactjs/preact-render-to-string) package from official preact team to create raw HTML nodes.

html/render.tsx
import { h, JSX, VNode } from "preact";
import toString from "preact-render-to-string";
export default function render(node: JSX.Element | VNode) {
return toString(node);
}

Markdown

As for markdown I found the issue about using tags.

But I also remember about special characters, e.g. for bold text I just can write *bold* and the text will be bolded.

But if i return just text, it will not the match to JSX.Element type.

The answer is comes to me unexpectedly - Fragment component - this just returns children element.

Using this knowledge I start to implement components. E.g. look at the bold component in markdown implentation:

markdown/bold.tsx
import { h, JSX, Fragment } from "preact";
type Props = {
children: string | JSX.Element;
};
export default function ({ children }: Props) {
return <Fragment>*{children}*</Fragment>; // returns just `*{children}*`
}

As for render function I use node-html-parser, since i should get text content of received raw html-like text:

markdown/render.tsx
export default function render(node: VNode | JSX.Element) {
return toString(node); // markdown-like text
}

Default format

What should i do when user would like to use reply function. This function applies raw text as a first parameter and text modifications as the second parameter.

Since fmt string function is working with stringified text - so i made function which reuse default fmt function for raw text, but i found inconsistency, and made decision do not break fmt functionality, some components are the same as for html realization, but some of the components has a differences with html realization

Similar with HTML:

default/bold.tsx
// imports
// props
export default function ({ children }: Props) {
return <b>{children}</b>;
}

Differences:

default/code.tsx
export default function (props: Props) {
return (
<code>
{props.language}||{props.children}
</code>
);
}

And Render function reuse default fmt function

export default function render(node: VNode | JSX.Element) {
let text = toString(node); // html-like string
return fmt(text); // returns [raw text, {modifications}]
}

That’s why If user would like to use default components - he needs to use rest/spread operator.

Result

Code

example.ts
import { Fragment, h } from "preact";
import { Telegraf } from "telegraf";
import { Bold, render } from "@tlgr/fmt/default";
const TOKEN = "<API TOKEN>";
const bot = new Telegraf(TOKEN);
bot.start((ctx) => {
ctx.reply(
...render(
<Fragment>
<Bold>Some text</Bold>
</Fragment>
)
);
});
bot.launch();

Image1. Result of the executed command

Pros

Cons

Conclusion

As for me, I do many non-trivial works, never working with preact before, but it was a great experience.

How to use this examples:

Important: use version 1.4.0 or higher since JSX was introduced in this release, You can also read release from official Telegraph repository.

P.S. Or You can read official package API and tutorial from docs page. P.P.S. This 1.4.0 release are also posted in Telegraph blog post page

Have a good day, Bye 👋

All my Links: