Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 0 additions & 55 deletions .eslintrc.cjs

This file was deleted.

42 changes: 0 additions & 42 deletions .github/workflows/bot.yml

This file was deleted.

53 changes: 0 additions & 53 deletions .github/workflows/deploy.yml

This file was deleted.

50 changes: 12 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,54 +110,28 @@ The app is built and deployed automatically using **Cloudflare Pages**.

## 💬 Bot

TeleOTP uses a helper bot to send user a link to the app and assist with account migration.
The bot is written in **Python** using [Python Telegram Bot library](https://github.com/python-telegram-bot/python-telegram-bot).
TeleOTP uses a serverless Telegram bot to send users a link to the Mini App and
assist with account migration. The bot lives in [`bot/`](bot/) and runs on
Telegram's serverless platform.

### Starting bot

To start the bot, you have to run the `main.py` script with environment variables.
Install the local deployment CLI and link the project to a bot:
```shell
python main.py
```

### Environment variables

* `TOKEN` - Telegram bot token provided by @BotFather
* `TG_APP` - A link to the Mini App in Telegram (e.g. https://t.me/TeleOTPAppBot/app)
* `WEBAPP_URL` - Deployed Mini App URL (e.g. https://uselessstudio.github.io/TeleOTP)

> [!NOTE]
> Make sure that `WEBAPP_URL` doesn't end with a `/`!
> It is added automatically by the bot.

### Running in Docker

We recommend running the bot inside the Docker container.
The latest image is available at `ghcr.io/uselessstudio/teleotp-bot:main`.

Example `docker-compose.yml` file:

```yaml
services:
bot:
image: ghcr.io/uselessstudio/teleotp-bot:main
restart: unless-stopped
environment:
- TG_APP=https://t.me/TeleOTPAppBot/app
- WEBAPP_URL=https://uselessstudio.github.io/TeleOTP
- TOKEN=<insert your token>
cd bot
npm install
npx tgcloud login
```

And running is as simple as:
Set the deployed Mini App URL in `bot/lib/config.js`, then deploy the modules:
```shell
docker compose up
npm run deploy
```

### 🔁 CI/CD
GitHub Actions is used to automate the building of the bot container.
The workflow is defined in the [`bot.yml` file](.github/workflows/bot.yml)
and ran on every push to `main`. After a successful build,
the container is published in the GitHub Container Registry.

Use `TGCLOUD_TOKEN` as the deployment credential when running the tgcloud CLI in
CI.


# 💻 Structure
Expand Down
46 changes: 46 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": [
"**",
"!dist",
"!src/migration/proto/generated",
"!src/assets/*_lottie.json"
],
"maxSize": 2097152
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 4
},
"linter": {
"enabled": true,
"rules": {
"preset": "recommended",
"correctness": {
"useExhaustiveDependencies": "warn"
},
"suspicious": {
"noDoubleEquals": "warn",
"noImplicitAnyLet": "warn",
"useIterableCallbackReturn": "warn"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"json": {
"formatter": {
"indentWidth": 2
}
}
}
2 changes: 2 additions & 0 deletions bot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.tgcloud/
node_modules/
95 changes: 95 additions & 0 deletions bot/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# AGENTS.md

Orientation for AI coding assistants (and humans) working in this project.
This file is auto-loaded by Claude Code, Cursor, and similar tools — keep it short
and true. For the full SDK reference (db, Bot API, fetch), see
[docs/tgcloud-sdk.md](docs/tgcloud-sdk.md).

## What this project is

A **Telegram Mini App bot** running on Telegram's serverless platform. You write
JavaScript modules (database schema, shared library code, update handlers); the
platform runs them in a V8 isolate. The `tgcloud` CLI syncs this local project
with the bot's cloud environment — think `wrangler`/`vercel` + `drizzle-kit`.

There is no server to run locally and no `node_modules` to import from at runtime:
the only things available inside a module are the platform SDK and other modules
in this project.

## Layout

| Path | What it is |
|-----------------|-------------------------------------------------------------------|
| `schema.js` | Database schema — tables as **named exports**. One file, at root. |
| `lib/` | Shared modules. Subdirectories allowed (`lib/internal/util.js`). |
| `handlers/` | Update handlers, **one level only**. Names match Telegram Bot API update types (`message`, `callback_query`, …). |
| `docs/` | Reference docs (this project's, for you). Not deployed. |
| `.tgcloud/` | CLI state (credentials, snapshot, cached layout). **Never edit or read from here** — it's gitignored machine state. |

Only `.js` files in `schema.js`, `lib/`, and `handlers/` are deployed. Everything
else (Markdown, config, `.tgcloud/`) is local-only.

## Module system — the rules that bite

- **Import by bare module name, never a relative path or file extension.**
The platform resolves modules by their name in the module space, not by the
filesystem.
- ✅ `import { users } from 'schema'`
- ✅ `import { addItem } from 'lib/cart'`
- ✅ `import { db, api, fetch } from 'sdk'` / `import { eq, sql } from 'sdk/db'`
- ❌ `import { users } from './schema'` or `'../schema'` → **won't compile**
- ❌ `import x from 'lib/cart.js'` → drop the `.js`
- **No filesystem, no npm packages** at runtime. Only `sdk` (and its submodules
like `sdk/db`) and your own project modules exist.
- A handler module's `export default` is what the platform invokes, with the
update's **payload** as the first argument — for `handlers/message` that's the
`Message` (i.e. `update.message`), for `handlers/callback_query` the
`CallbackQuery`, and so on. The full `Update` (with `update_id`) is on the
second argument: `ctx.update`.

## Platform SDK (`import … from 'sdk'`)

- **`db`** — the database (query builder + schema DSL). Full API: [docs/tgcloud-sdk.md](docs/tgcloud-sdk.md).
- **`api`** — the Telegram Bot API. `api.<method>({...})` (e.g. `api.sendMessage`,
`api.getMe`) returns the **unwrapped** result and **throws `BotApiError`** on
failure (`import { BotApiError } from 'sdk'`; it has `.code`/`.description`/`.parameters`).
- **`fetch`** — outbound HTTP, web-`fetch`-like (`res.status/ok`, `res.json()`,
`res.text()`, streaming via `for await`, redirects followed).

## Database — the rules that bite

Full API in [docs/tgcloud-sdk.md](docs/tgcloud-sdk.md). The non-obvious parts:

- **Every DB call is async — always `await`.** `.all()`, `.get()`, `.values()`,
`.run()`, `db.$count()` and the raw `db.run/all/get` all return Promises.
- **No foreign keys.** `.references()` and `foreignKey()` **throw at declaration**
— the runtime runs with FKs off, so they'd be silently inert. Enforce integrity
in application code (delete children before parents, etc.).
- **Drops happen only via `.deprecated('reason')`** on a column/table/index.
Deleting the declaration does *not* drop anything.
- **Type changes aren't automatic** — do them by hand with `db.run(...)`.

## Deploy & migrate workflow

**Deploying never touches the database.** Schema sync is a separate, explicit step.

The CLI is a local dev-dependency, so run it with `npx tgcloud <command>` (or use
the `npm run` scripts in package.json — e.g. `npm run deploy`):

```
npx tgcloud status # what changed locally vs the cloud
npx tgcloud push # deploy modules to the cloud
npx tgcloud migrate # apply schema.js changes to the database (interactive)
npx tgcloud run <module> [args] # execute a handler server-side
npx tgcloud pull # bring the local project in line with the cloud
npx tgcloud login # link this project to a bot
npx tgcloud webhook # show the bot's webhook and whether it matches your handlers
```

After you change `schema.js`, `push` reports what the DB *would* change but applies
nothing — run `npx tgcloud migrate` to actually apply it.

The platform manages the bot's webhook for you, derived from your deployed
`handlers/*`, and refreshes it on `push`. If it ever drifts — e.g. someone called
`setWebhook` with the raw bot token — `npx tgcloud webhook` shows the mismatch and
`npx tgcloud webhook sync` repairs it.
10 changes: 0 additions & 10 deletions bot/Dockerfile

This file was deleted.

1 change: 1 addition & 0 deletions bot/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "extends": "//" }
13 changes: 0 additions & 13 deletions bot/docker-compose.example.yml

This file was deleted.

Loading