# tgbot

The third Node.js service. Posts a launch alert into the official basefun Telegram group on every new token. \~150 LOC, no Telegram long-polling, no webhook — just bot API + DB polling.

## What it posts

```
🚀 New token launched on basefun

GOLD · Base Gold
📈 5× Long · pair #21

[BasefunOfficialDescriptionIfAny]

🔗 X · Telegram · Website

[Buy $GOLD]   ← inline button → https://basefun.app/t/0xbf05...
```

With the token image if `imageURI` resolves; falls back to text-only if Telegram rejects the photo URL.

## Where it posts

| Variable | Value (production)                    |
| -------- | ------------------------------------- |
| Group    | <https://t.me/basefunapp>             |
| Topic    | "New Basefun Launches" (topic\_id 11) |

Configurable via env: `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, `TELEGRAM_TOPIC_ID`.

## How it decides what's new

Per tick (every `POLL_INTERVAL_MS` = 10s):

```sql
SELECT t.* FROM "Token" t
  LEFT JOIN "TgBotSent" s ON s."tokenAddress" = t.address
 WHERE s."tokenAddress" IS NULL
 ORDER BY t."createdAt" ASC
 LIMIT 25
```

Any token without a row in `TgBotSent` is treated as un-announced. After a successful `sendPhoto`/`sendMessage`, the bot inserts a row keyed by `tokenAddress` with the returned `messageId` so the same token never gets re-announced.

## Schema self-healing

`TgBotSent` and `TgBotCursor` live in the shared Postgres but **not** in the API's Prisma schema. If the API service's boot script (`prisma db push`) ever drops them, the next tgbot tick re-creates them via raw `CREATE TABLE IF NOT EXISTS` at the top of each tick.

To avoid spamming the chat after such a recreate, the first `ensureSchema` call per process boot:

1. Checks if `TgBotSent` is empty.
2. If empty: inserts a "suppress" row for every token whose `createdAt < PROCESS_STARTED_AT`. Those will never be announced retroactively.
3. Marks `suppressionApplied = true` in memory so subsequent ticks skip the check.

Tokens launched **after** the process boots still flow through the normal `findUnsentTokens` path and get announced.

## Failure handling

If both `sendPhoto` and the text-only `sendMessage` fallback fail, the bot still inserts a `TgBotSent` row with `messageId = NULL`. This deliberately suppresses retries on poisoned rows (malformed caption, broken image, banned bot, etc.) so the queue doesn't get stuck.

An operator can manually clear a `TgBotSent` row from SQL to force a re-announce.

## Telegram API call

Posted via plain `fetch` with `content-type: application/json; charset=utf-8`. The charset is important: Telegram falls back to latin-1 without it and rejects captions containing multi-byte UTF-8 codepoints (emojis, accented letters) with `text must be encoded in UTF-8`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://basefun.gitbook.io/basefun-docs/tgbot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
