Skip to content
Projects

Case study

kyuar

QR codes that live inside Telegram: made in any chat, in a conversation with the bot, or in a Mini App editor, and scanned right there too.

Year
2026
Role
Author
Stack
Next.jsTypeScriptgrammYRedis
Links
LiveSource
  • 29

    module shapes, each one passes a decoder test

  • 12

    ready-made themes, none below 4.5:1 contrast

Problem

Most QR generators give you black squares on white. The ones that make prettier codes often make codes that do not scan. And the link ends up in Telegram anyway, while the code is made in a separate app.

Solution

kyuar works without leaving Telegram:

  • In any chat: type @kyuarbot https://example.com and pick a color.
  • In the bot chat: send any text, phone number or email and get a code back.
  • In the Mini App editor: every part of the code is styled on its own: data modules, finder rings, finder eyes, background, margin, logo. Each takes a solid color or a gradient. Halftone mode draws a picture with the code itself.
  • Scanner: read through the Telegram camera, from a photo, or by sending the bot a picture. Risky links are flagged: javascript:, plain http, link shorteners, names that imitate another site.

All four render through one engine, so a code made inline looks the same as one made in the editor.

Architecture

request flow
Telegram ─┬─ inline / message ─→ /api/bot (webhook) ─→ grammY
          │                                            └─ image URL → /api/qr
          └─ Mini App ─→ editor (renders in a Web Worker)
                          ├─ POST /api/share  → savePreparedInlineMessage
                          └─ POST /api/render → Redis (10 min) → download
  • One renderer everywhere. packages/qr turns text into a styled SVG. The same function runs in the editor's Web Worker, in /api/qr and in /api/render.
  • URLs built for caching. The style travels in the URL as base64url JSON, holding only the parts that differ from the defaults. The same code always gets the same URL, and /api/qr answers with Cache-Control: immutable.
  • One deployment. In production the bot runs inside the web app as a webhook route. The Docker image has no build args: settings are read from .env at startup, so one image runs in any environment.
  • Security. Every request made on a user's behalf verifies initData on the server.

Codes that scan

Three layers catch a code that will not scan:

  • The renderer warns when any color is below 4.5:1 contrast against the background.
  • "Scan test" decodes the design in four conditions: normal, small (132 px), dim and dark.
  • In the tests every shape, gradient, logo and halftone is rendered and decoded again. A shape that looks good but does not scan counts as a bug and does not ship.

Halftone mode follows the paper Halftone QR Codes (SIGGRAPH Asia 2013). Each module is split into 3×3 parts. The centre part keeps the real value; the rest are taken from the picture and dithered with Floyd–Steinberg.

Telegram quirks

  • web_app buttons only work in private chats. Anything that can land in a group opens through a t.me/<bot>?startapp=… link instead.
  • Images in inline results have to be JPEG.
  • resvg runs with loadSystemFonts: false: QR SVGs have no text, and loading fonts would take seconds.

Next

Saved codes per user, backed by PostgreSQL.