Skip to content

feat: new handlers for bot#158

Merged
hmbanan666 merged 1 commit intomainfrom
telegram-file-handlers
Sep 16, 2025
Merged

feat: new handlers for bot#158
hmbanan666 merged 1 commit intomainfrom
telegram-file-handlers

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Sep 16, 2025

Summary by CodeRabbit

  • New Features
    • Wasabi Telegram bot now accepts photos, videos, and documents; these are captured and attached to your support ticket.
  • Improvements
    • Messages are automatically linked to the correct user and an open ticket is created if needed.
    • Clear confirmations are sent after your message or attachment is received.
    • More robust startup and error handling; the bot only starts when properly configured.

@hmbanan666 hmbanan666 self-assigned this Sep 16, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 16, 2025

Walkthrough

The Wasabi Telegram bot now initializes its token from the database, adds handlers for text, photos, videos, and files, and persists incoming content to user tickets. New helper functions map Telegram users to app users/tickets, build Telegram file download URLs, and centralize token retrieval. Bot start is wrapped with error logging.

Changes

Cohort / File(s) Summary
Telegram Wasabi bot enhancements
apps/web-app/server/services/telegram/wasabi-bot.ts
- Initialize bot token via getBotToken(); error if missing
- Add handlers: text, message:photo, message:video, message:document, message:file
- Add getUserAndTicket(telegramId) to resolve/create open ticket
- Persist messages/media JSON to ticket; reply to user; logging
- getFileDownloadUrl() builds direct Telegram file URL via API
- Wrap bot.start() with try/catch and logs
- Add Ticket and User type imports

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant TG as Telegram User
  participant Bot as Wasabi Bot
  participant Svc as WasabiBot Service
  participant Repo as repository.telegram / tickets
  participant TGAPI as Telegram API

  Note over Bot,Svc: Startup
  Bot->>Svc: start()
  Svc->>Repo: getBotToken(botId)
  alt token found
    Svc-->>Bot: init with token
  else no token
    Svc-->>Bot: throw "Wasabi bot is not configured"
  end

  rect rgba(220,245,255,0.5)
  Note over TG,Bot: Incoming message (text/photo/video/file)
  TG-->>Bot: message
  Bot->>Svc: handleMessage/Photo/Video/File(ctx)
  Svc->>Repo: getUserAndTicket(telegramId)
  alt user & open ticket
    opt media (photo/video/document)
      Svc->>TGAPI: getFile(fileId) (photo: choose best size)
      Svc->>Svc: getFileDownloadUrl(file.file_id, token)
      Svc-->>Repo: persist ticket message (JSON payload)
    end
    Svc-->>Repo: persist ticket message (text)
    Svc-->>TG: reply ack
  else not mapped
    Svc-->>TG: reply not configured/ignored
  end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A rabbit taps the chat with glee,
New bots now fetch their keys from DB.
Photos, vids, and files take flight—
Hopped into tickets, logged just right.
With careful paws and tidy tone,
Wasabi whispers, “Message known.” 🐇📨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The provided title "feat: new handlers for bot" accurately and concisely reflects the primary change in the diff—adding new Telegram message handlers to the bot—so it is relevant and clear to a reviewer, though it is somewhat generic and could be slightly more descriptive about which handler types were added.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch telegram-file-handlers

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

@hmbanan666 hmbanan666 merged commit b8a07a2 into main Sep 16, 2025
7 of 8 checks passed
@hmbanan666 hmbanan666 deleted the telegram-file-handlers branch September 16, 2025 10:37
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web-app/server/services/telegram/wasabi-bot.ts (2)

200-211: Guard for undefined list, and avoid duplicate ticket races.

  • listOpenedByUser may return undefined; you already used optional chaining, but the subsequent length access is unsafe.
  • Two concurrent messages from a user with no open ticket can create two tickets. Gate creation with a transaction or a unique partial index on (userId, status='opened').

Apply this diff to harden the null path:

-  const tickets = await repository.ticket.listOpenedByUser(telegramUser.user.id)
+  const tickets = (await repository.ticket.listOpenedByUser(telegramUser.user.id)) ?? []
   let ticket = tickets?.[0]
   if (!tickets.length || !ticket) {
     // Create ticket
     ticket = await repository.ticket.create({

Operational follow-up:

  • Add a DB constraint or repository-level “findOrCreateOpenedTicket(userId)” that retries on unique‑violation.

49-56: Do not await bot.start(); it stays pending during long-polling. apps/web-app/server/services/telegram/wasabi-bot.ts (lines 49–56): remove await bot.start() — call bot.start() (no await) and handle startup errors via bot.catch(...) or the onStart/start options.

🧹 Nitpick comments (4)
apps/web-app/server/services/telegram/wasabi-bot.ts (4)

104-117: Await replies to handle errors and ensure ordering.
These handlers are async; not awaiting ctx.reply may drop errors and race with process exit.

Apply this diff:

   logger.log('message', data.user.id, ctx.message.from.id, ctx.message.text)
-  ctx.reply('Сообщение передано в службу поддержки.')
+  await ctx.reply('Сообщение передано в службу поддержки.')

152-171: Await reply and consider persisting a compact subset of video fields.
Await the reply; storing the entire object may bloat the DB—prefer a minimal subset (file_id, file_unique_id, mime, size, caption).

Apply this minimal diff:

-  ctx.reply('Видео передано в службу поддержки.')
+  await ctx.reply('Видео передано в службу поддержки.')

Optional (example shape, adjust as needed):

-  text: JSON.stringify(ctx.message.video),
+  text: JSON.stringify({
+    fileId: ctx.message.video.file_id,
+    fileUniqueId: ctx.message.video.file_unique_id,
+    mimeType: ctx.message.video.mime_type,
+    fileSize: ctx.message.video.file_size,
+    width: ctx.message.video.width,
+    height: ctx.message.video.height,
+    duration: ctx.message.video.duration,
+    caption: ctx.message.caption,
+  }),

173-192: Await reply and trim stored document fields.
Await the reply; consider storing only file_id, mime_type, file_size, file_name, caption.

Apply this minimal diff:

-  ctx.reply('Файл передан в службу поддержки.')
+  await ctx.reply('Файл передан в службу поддержки.')

229-236: Avoid hot-path DB lookups for the bot token.
getBotToken() is invoked inside handlers in the current code. Either:

  • Fetch once at startup and reuse, or
  • Cache with a TTL and add an admin-triggered refresh/rotation path.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30b6c6b and ae53e6c.

📒 Files selected for processing (1)
  • apps/web-app/server/services/telegram/wasabi-bot.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web-app/server/services/telegram/wasabi-bot.ts (2)
packages/database/src/repository/index.ts (1)
  • repository (63-63)
packages/database/src/types.ts (2)
  • User (44-44)
  • Ticket (179-179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
apps/web-app/server/services/telegram/wasabi-bot.ts (3)

1-1: Types import looks good.
No issues with the added types import.


28-43: Do not remove the 'message:file' handler — grammY supports it.
message:file (or :file) is a valid shortcut that matches any message containing a file (photo, animation, audio, document, video, video_note, voice, sticker); keep it if you want to catch all file types, or use message:document only when you intend to handle documents exclusively.

Likely an incorrect or invalid review comment.


219-227: Return file_path instead of token-bearing URL.

Helpers must not construct or return URLs containing bot tokens; return the Telegram File.file_path and let the backend produce ephemeral/proxied links.

Apply this diff:

-async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string }) {
-  // https://api.telegram.org/file/bot<token>/<file_path>
-  const file = await data.ctx.api.getFile(data.fileId)
-  if (!file) {
-    return null
-  }
-
-  return `https://api.telegram.org/file/bot${data.botToken}/${file.file_path}`
-}
+async function getFilePath(data: { ctx: Context, fileId: string }) {
+  const file = await data.ctx.api.getFile(data.fileId)
+  if (!file?.file_path) {
+    return null
+  }
+  return file.file_path
+}

Reminder: update call sites accordingly (handlePhoto should call ctx.api.getFile or use the new getFilePath result as appropriate).

Comment on lines 12 to 19
export async function useCreateWasabiBot() {
const botInDb = await repository.telegram.findBot(telegram.wasabiBotId)
if (!botInDb?.token) {
const token = await getBotToken()
if (!token) {
throw new Error('Wasabi bot is not configured')
}

bot = new Bot(botInDb.token)
bot = new Bot(token)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Make bot initialization idempotent to prevent duplicate handlers and multiple start() calls.
If useCreateWasabiBot() can be called more than once (hot reload, startup race, health-checks), you'll re-register handlers and call start() again. Guard and early-return if already initialized.

Apply this diff:

 export async function useCreateWasabiBot() {
+  if (bot) {
+    logger.info('Wasabi bot is already initialized; skipping re-init')
+    return
+  }
   const token = await getBotToken()
   if (!token) {
     throw new Error('Wasabi bot is not configured')
   }

   bot = new Bot(token)
+  bot.catch((err) => logger.error('Unhandled Wasabi bot error:', err))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function useCreateWasabiBot() {
const botInDb = await repository.telegram.findBot(telegram.wasabiBotId)
if (!botInDb?.token) {
const token = await getBotToken()
if (!token) {
throw new Error('Wasabi bot is not configured')
}
bot = new Bot(botInDb.token)
bot = new Bot(token)
export async function useCreateWasabiBot() {
if (bot) {
logger.info('Wasabi bot is already initialized; skipping re-init')
return
}
const token = await getBotToken()
if (!token) {
throw new Error('Wasabi bot is not configured')
}
bot = new Bot(token)
bot.catch((err) => logger.error('Unhandled Wasabi bot error:', err))
🤖 Prompt for AI Agents
In apps/web-app/server/services/telegram/wasabi-bot.ts around lines 12 to 19,
the code always constructs a new Bot(token) and registers handlers/start which
can run multiple times on hot reloads; make initialization idempotent by
checking an existing bot instance and returning early if already initialized
(optionally verify the existing bot was created with the same token), only
create new Bot and register handlers/start when no bot exists, and ensure any
exported/shared state (bot variable) is set once so duplicate handlers and
start() calls are avoided.

Comment on lines +119 to +151
async function handlePhoto(ctx: Context) {
if (!ctx.message?.photo?.length) {
return
}

const data = await getUserAndTicket(ctx.message.from.id.toString())
if (!data) {
return
}

const bestQuality = ctx.message.photo.pop()
if (!bestQuality) {
return
}

const botToken = await getBotToken()
if (!botToken) {
return null
}

const downloadUrl = await getFileDownloadUrl({ ctx, fileId: bestQuality.file_id, botToken })

await repository.ticket.createMessage({
ticketId: data.ticket.id,
userId: data.user.id,
text: JSON.stringify({ downloadUrl, photo: ctx.message.photo }),
})

// Save photo?
logger.log('photo', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.photo, downloadUrl)
ctx.reply('Фото передано в службу поддержки.')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not persist or log Telegram download URLs (they embed the bot token).
Storing/logging downloadUrl leaks the bot token (a secret). Also, using Array.pop() mutates ctx.message.photo and drops the highest-quality size from the array you later persist. Fix both.

Apply this diff:

 async function handlePhoto(ctx: Context) {
   if (!ctx.message?.photo?.length) {
     return
   }

   const data = await getUserAndTicket(ctx.message.from.id.toString())
   if (!data) {
     return
   }

-  const bestQuality = ctx.message.photo.pop()
+  const bestQuality = ctx.message.photo[ctx.message.photo.length - 1]
   if (!bestQuality) {
     return
   }
-
-  const botToken = await getBotToken()
-  if (!botToken) {
-    return null
-  }
-
-  const downloadUrl = await getFileDownloadUrl({ ctx, fileId: bestQuality.file_id, botToken })
+  // Fetch file metadata without creating a URL that embeds the bot token
+  const file = await ctx.api.getFile(bestQuality.file_id)
+  if (!file?.file_path) {
+    logger.warn('photo:file_path missing for file_id', bestQuality.file_id)
+    return
+  }

   await repository.ticket.createMessage({
     ticketId: data.ticket.id,
     userId: data.user.id,
-    text: JSON.stringify({ downloadUrl, photo: ctx.message.photo }),
+    text: JSON.stringify({
+      fileId: bestQuality.file_id,
+      filePath: file.file_path,
+      sizes: ctx.message.photo,
+      caption: ctx.message.caption,
+    }),
   })

   // Save photo?
-  logger.log('photo', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.photo, downloadUrl)
-  ctx.reply('Фото передано в службу поддержки.')
+  logger.log('photo', {
+    userId: data.user.id,
+    tgUserId: ctx.message.from.id,
+    fileId: bestQuality.file_id,
+    filePath: file.file_path,
+  })
+  await ctx.reply('Фото передано в службу поддержки.')
 }

Follow-ups:

  • If you need a usable link for agents, generate it on-demand server-side (proxy endpoint) so the token never leaves the backend.
  • Scrub past logs/DB entries created by this branch that may already contain the token. Rotate the bot token after deploying the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant