What a hyperlink nav is
A hyperlink nav is a single Telegram text message built from premium (custom) emoji and centred hyperlinks. It carries no special message type — it is stored and sent exactly like any other text command.
Storage
Beta
Hyperlink Nav is currently in beta, served from the dashboard’s beta tab.
A hyperlink nav is one entry in a bot’s text commands: bots[].textCommands[], inside /var/www/official-assets/{folderId}/botconfigs/memebot.json. Structurally it is not a different kind of command — there is no type or kind field anywhere on it. A nav is a text command that happens to carry one extra field, hyperlinkNav; a command without that field is a plain text command.
A text command’s fields
| Setting | Value |
|---|---|
| trigger | The command word, stored without its leading slash. |
| response | The message HTML. For a nav, this is exactly the HTML the builder’s live preview rendered — nothing is generated again at send time. |
| isActive | Whether the command fires. false removes it from the bot’s runtime command map entirely, the same as for any other command. |
| hyperlinkNav | Present only on a nav; optional, and absent on a plain text command. |
hyperlinkNav holds the builder’s own settings, not the message text: the chosen preset and color, whether whiteLogos is on, the bucket used (if any), the selected links, and an optional discovery — the site scan’s raw results, kept so editing can reopen every original candidate without re-scanning (chapter 4.9).
What the bot does with it
The bot reads only trigger, response and isActive from a text command. hyperlinkNav is never read on the bot’s side — it is dropped entirely when the bot’s runtime command map is built. The field exists purely for the dashboard: reopening the nav in the builder, and marking the command non-clonable and non-convertible in the Commands tab (chapter 4.9).
A deactivated nav (isActive: false) does not exist in the bot’s command map — its trigger gets no reply, the same as any other deactivated command.
Matching and sending both use the ordinary text-command path: the trigger matches with the leading slash optional, an @botname suffix optional, and any trailing text after it, case-insensitive.
Matches for a nav saved under the trigger socials
| Input | Output |
|---|---|
/socials | matches |
socials | matches — the leading slash is optional |
/socials@yourbot | matches — the bot-username suffix is accepted |
/socials followed by more text | matches — trailing text is allowed |
/socialsx | does not match |
The response is sent as a reply to the triggering message, parse_mode HTML, with link previews disabled. The HTML the builder writes uses exactly four tags — <a href>, <b>, <i>, and <tg-emoji emoji-id="…"> — every one of which passes through untouched; anything else in a response is escaped rather than interpreted.
Message shape, row by row
The row order is read from a configuration file, not fixed in code. The order currently configured is: banner, divider, then one link-and-divider pair per selected link, then a footer row that is never actually sent.
Row order
| # | Row | Renders as |
|---|---|---|
| 1 | Banner | The preset’s banner emoji run, alone on its own line. |
| 2 | Divider | Before any link is placed, this is the top divider — the preset’s body divider is reused here whenever no separate top divider is configured. |
| 3 onward | Link, then Divider | One link row, then the body divider. This pair repeats once for every selected link, in order. |
| last | Footer | Configured but never sent — see below. |
The footer row is never sent
A preset can define a
footeremoji run, and it is still validated and stored, but nothing in the message-building step ever places it — the part of the builder that turns row names into actual lines has no case forfooter, so it is silently skipped every time. All three currently configured presets leavefooterempty regardless.
A link row, left to right
- Edge emoji — the platform’s own emoji if the preset defines one, else the preset’s
defaultemoji. If the preset has neither, the row has no edge emoji and no padding on either side. - A run of regular spaces.
- The link itself: the label text, hyperlinked to its URL. Bold and italic by default.
- A second run of regular spaces — see Centring for how the two runs are sized.
- The same edge emoji again.
One rendered link row, from the builder’s own test suite
<tg-emoji emoji-id="…">🐦</tg-emoji> <a href="https://x.com/toshi"><i><b>View Our X</b></i></a> <tg-emoji emoji-id="…">🐦</tg-emoji>21 spaces on the left, 22 on the right, in this example — see Centring for the rule that decides which side gets the extra one.
Centring
Padding is built from regular space characters only — never an em-space, never a spacer emoji.
The line a link centres inside is the divider’s own rendered width: its cell count times one emoji-cell width, capped to the bubble’s width. The divider currently configured is 12 cells, so a link centres inside a 12-emoji-wide line whenever the chat bubble itself is wide enough to show it.
That width is measured live against the real Telegram preview engine — actual rendered pixel widths for the font, size and letter-spacing in use — not estimated.
The number of spaces on each side is floored down to whatever fits without exceeding the line. When the total doesn’t split evenly between the two sides, the extra space goes on the right.
The odd space goes right (two independent tests)
| Input | Output |
|---|---|
| Direct centring test — a 3-character label, 336px of free space, 5px spaces | 67.2 spaces → 67 → 33 left, 34 right |
| Full message build — a 10-character link label (“View Our X”), 130px of free space, 3px spaces | 43.3 spaces → 43 → 21 left, 22 right |
Both are from the builder’s own test suite: centerPad tested directly, and the HTML a full build actually emits.