merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
403
docs/admin-panel-api.en.md
Normal file
403
docs/admin-panel-api.en.md
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
# Admin panel: all routes and API contracts
|
||||
|
||||
Documentation of every route in the built-in `telesrv` admin panel — the SPA
|
||||
page addresses and the API routes behind them. The panel server lives in
|
||||
`cmd/telesrv-admin`; routes are declared in `(*server).routes()`
|
||||
(`server.go:50`).
|
||||
|
||||
The panel is a plain HTTP server: it serves the built frontend (`/`) and a
|
||||
JSON API under the `/api/` prefix. Every API path requires a session (cookie),
|
||||
and every mutating request additionally requires a CSRF token. Many routes also
|
||||
check an operator permission (`permission`).
|
||||
|
||||
## How to use this document
|
||||
|
||||
- For each GET endpoint, the **request parameters** (query) and a short **response** shape are listed.
|
||||
- For each POST endpoint under `/api/actions/*` and the verification decisions, the **request body** (JSON) is given. They all share common fields (see below) plus their own specific fields.
|
||||
- `int64` in JSON may be sent either as a number or as a string (e.g. `"user_id": "123"` or `"user_id": 123`) — the server accepts both (`flexInt64`). `flexUnix` accepts Unix seconds or a date in `2006-01-02` / RFC3339 form.
|
||||
- All mutating requests go to the Admin API. A command's response is either `{"status":..., "message":..., "command_id":...}` on success (HTTP 200) or `{"error":..., "code":...}` on error. An optimistic-locking conflict is `409`.
|
||||
|
||||
## Authentication and session
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| POST | `/api/login` | Log in. Body: `{"secret": "..."}`. Validates the config secret, issues a signed session cookie and CSRF cookie. The only mutating route without a CSRF token (no session yet); only Origin is checked. |
|
||||
| POST | `/api/logout` | Log out. Destroys the session and CSRF cookie. |
|
||||
| GET | `/api/session` | Returns `{"actor": "...", "permissions": [...], "csrf_token": "..."}`. Called by the panel on startup. |
|
||||
|
||||
The session lives in a signed cookie (default TTL 12 hours); there is no
|
||||
server-side session store. Every request except `GET/HEAD/OPTIONS` and
|
||||
`/api/login` must present:
|
||||
|
||||
- the `telesrv_admin_csrf` cookie;
|
||||
- the `X-CSRF-Token` header with the same value;
|
||||
- an Origin matching the host (when Origin is present).
|
||||
|
||||
Violating any of the three — `403 Forbidden`.
|
||||
|
||||
## Permissions and their checks
|
||||
|
||||
The panel distinguishes plain authorization (`requireAuthAPI`) from a
|
||||
permission check (`requirePermission`). Permission names match the strings in
|
||||
`TELESRV_ADMIN_UI_PERMISSIONS`:
|
||||
|
||||
| Permission | What it grants |
|
||||
| --- | --- |
|
||||
| `*` | All permissions (wildcard). |
|
||||
| `premium.manage` | Managing the Premium plan catalog, granting Premium. |
|
||||
| `bots.token.read` | Exporting a bot token (`/api/actions/export-bot-token`). |
|
||||
| `verification.review` | Reading and deciding the official verification queue. |
|
||||
| `verification.revoke` | Stripping the official badge (in addition to `verification.review`). |
|
||||
| `botverification.review` | Reading and deciding the third-party (bot) verification queue. |
|
||||
| `botverification.manage` | Appointing verifiers, editing the icon catalog, stripping a third-party mark. |
|
||||
|
||||
Official and third-party verification rights are intentionally independent. The
|
||||
session's permission list is returned from `/api/session` so the UI can hide
|
||||
unavailable sections.
|
||||
|
||||
## Interface pages (SPA)
|
||||
|
||||
SPA routes are declarative: any section name is served as `/` (index.html), and
|
||||
the frontend decides what to render (`web/src/pages/Routes.tsx`).
|
||||
|
||||
| Path | Page |
|
||||
| --- | --- |
|
||||
| `/` | Dashboard (counters, storage stats, section links). |
|
||||
| `/accounts` | Account list (with search). |
|
||||
| `/accounts/{id}` | Account card: profile, actions. |
|
||||
| `/channels` | Supergroup and channel list. |
|
||||
| `/channels/{id}` | Channel card. |
|
||||
| `/bots` | Bot list. |
|
||||
| `/bots/{id}` | Bot card. |
|
||||
| `/broadcasts` | Broadcasts. |
|
||||
| `/monetization`, `/premium` | Stars and Premium: plans, grants. Requires `premium.manage`. |
|
||||
| `/moderation` | Complaints and moderation: case list. |
|
||||
| `/moderation/{id}` | Moderation case details. |
|
||||
| `/emoji` | Emoji set catalog. |
|
||||
| `/stickers` | Sticker pack catalog. |
|
||||
| `/gif-catalog` | GIF catalog. |
|
||||
| `/messages`, `/messages/private` | Private message audit. |
|
||||
| `/messages/detail`, `/messages/private/detail` | Private message detail (`?owner_user_id=&msg_id=`). |
|
||||
| `/messages/groups` | Group/channel message audit. |
|
||||
| `/messages/groups/detail` | Group message detail (`?channel_id=&msg_id=`). |
|
||||
| `/gifts` | Star gifts: catalog, collectibles, auctions. |
|
||||
| `/give-gifts` | Gift granting. |
|
||||
| `/collectible-usernames` | Collectible usernames. |
|
||||
| `/collectible-usernames/{id}` | Collectible username card. |
|
||||
| `/collectible-phones` | Anonymous numbers. |
|
||||
| `/account-ratings` | Account ratings. |
|
||||
| `/account-ratings/{user_id}` | Account rating card. |
|
||||
| `/storage` | Object storage: stats. |
|
||||
| `/verification` | Official verification: application queue. Requires `verification.review`. |
|
||||
| `/verification/{id}` | Official verification application details. Requires `verification.review`. |
|
||||
| `/bot-verification` | Third-party verification: verifiers, icons, marks, queue. Requires `botverification.review`. |
|
||||
| `/bot-verification/{id}` | Third-party verification request details. Requires `botverification.review`. |
|
||||
|
||||
An unknown API path returns `404 {"error":"api route not found"}`; any unknown
|
||||
frontend path is served as `/`.
|
||||
|
||||
## Response conventions
|
||||
|
||||
- Successful reads — `200` + JSON; files (avatars, animations, previews) — the file itself.
|
||||
- Errors — JSON of the form `{"error": "...", "code": "..."}` with the corresponding HTTP status.
|
||||
- `401` — missing/expired session; `403` — CSRF/Origin violation or missing permission (in `requirePermission` the body gets an added `permission` field); `409` — optimistic-locking conflict (moderation case, verification); `502` — Admin API unreachable.
|
||||
- Errors from commands sent to the Admin API are returned as `{"status": ..., "message": ..., "error": ...}`.
|
||||
|
||||
## Common command fields (POST `/api/actions/*`)
|
||||
|
||||
Every mutating request carries in its body:
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `command_id` | string | Idempotency key for the command. Repeating the same `command_id` does not execute the action twice. If empty — generated by the server. |
|
||||
| `reason` | string | Mandatory operation reason (audit). |
|
||||
| `confirm` | bool | Operator confirmation (`true`). |
|
||||
|
||||
---
|
||||
|
||||
## API: dashboard and storage
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/dashboard` | Summary: `counts`, `storage`, and optionally `host`. |
|
||||
| GET | `/api/storage/stats` | Object storage statistics. |
|
||||
|
||||
## API: accounts
|
||||
|
||||
| Method | Path | Parameters / response |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/accounts` | Params: `q` (search), `before_id` (int64), `before_active_us` (int64, microseconds), `limit` (int). Response: `query`, `limit`, `rows`, `has_more`, `next_before_id`, `next_before_active_us`, `listing`. |
|
||||
| GET | `/api/accounts/{id}` | Account card: profile, flags, statistics. |
|
||||
| GET | `/api/accounts/{id}/avatar` | Account avatar (file). |
|
||||
| GET | `/api/account-ratings` | Params: `q`, `min_level` (int), `user_id` (int64), `before_id` (int64), `limit` (int). Response: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/account-ratings/{user_id}` | Response: `rating`, `events`. |
|
||||
|
||||
## API: channels and supergroups
|
||||
|
||||
| Method | Path | Parameters / response |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/channels` | Params: `q`, `before_id` (int64), `before_updated_us` (int64, microseconds), `limit` (int). Response: `query`, `limit`, `rows`, `has_more`, `next_before_id`, `next_before_updated_us`, `listing`. |
|
||||
| GET | `/api/channels/{id}` | Channel card. |
|
||||
| GET | `/api/channels/{id}/avatar` | Channel avatar (file). |
|
||||
|
||||
## API: bots and broadcasts
|
||||
|
||||
| Method | Path | Parameters / response |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/bots` | Params: `q`, `before_id` (int64), `limit` (int). Response: `query`, `limit`, `rows`, `has_more`, `next_before_id`, `listing`. |
|
||||
| GET | `/api/bots/{id}` | Bot card. |
|
||||
| GET | `/api/broadcasts` | Params: `before_id` (int64), `limit` (int). Response: `limit`, `rows`, `has_more`, `next_before_id`. |
|
||||
|
||||
## API: media catalogs
|
||||
|
||||
| Method | Path | Parameters / response |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/emoji` | Params: `q`, `before_id` (int64), `limit` (int). Response: `query`, `rows`, `has_more`, `next_before_id`, `listing`. |
|
||||
| GET | `/api/emoji/{id}/animation` | Emoji Lottie animation (file). |
|
||||
| GET | `/api/stickers` | Param: `kind` (string, type filter). Response: `rows`, `max_items`. |
|
||||
| GET | `/api/stickers/{id}/documents` | Response: `document_ids`. |
|
||||
| GET | `/api/stickers/documents/{id}/animation` | Sticker animation (file). |
|
||||
| GET | `/api/gif-catalog` | Response: proxied from Admin API (`/v1/gif-catalog`). |
|
||||
| GET | `/api/gif-catalog/documents/{id}/preview` | GIF preview (file). |
|
||||
|
||||
## API: message audit
|
||||
|
||||
| Method | Path | Parameters / response |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/messages` | Params: `owner_user_id` (int64, required with `peer_id`), `peer_id` (int64), `before_date` (int64), `before_id` (int), `limit` (int). Response: `owner_user_id`, `peer_id`, `before_date`, `before_id`, `limit`, `rows`. |
|
||||
| GET | `/api/messages/detail` | Params: `owner_user_id` (int64, req.), `msg_id` (int, req.). Response: message card. |
|
||||
| GET | `/api/messages/groups` | Params: `channel_id` (int64, req.), `before_date` (int64), `before_id` (int), `limit` (int). Response: `channel_id`, `before_date`, `before_id`, `limit`, `rows`. |
|
||||
| GET | `/api/messages/groups/detail` | Params: `channel_id` (int64, req.), `msg_id` (int, req.). Response: message card. |
|
||||
|
||||
## API: star gifts and collectibles
|
||||
|
||||
| Method | Path | Parameters / response |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/gifts` | Response: `Gifts` (gift list). |
|
||||
| GET | `/api/auctions` | Response: `Auctions` — live state of all operator-authored auctions and scheduled drops. |
|
||||
| GET | `/api/official-gifts` | Response: proxied from Admin API (`/v1/official-gifts`). |
|
||||
| GET | `/api/official-gifts/{id}/animation` | Official gift animation (file). |
|
||||
| GET | `/api/gifts/{id}/animation` | Gift animation (file). |
|
||||
| GET | `/api/gifts/{id}/collectibles` | Response: proxied from Admin API (`/v1/gifts/{id}/collectibles`). |
|
||||
| GET | `/api/gifts/{id}/collectibles/{kind}/{attribute_id}/animation` | Collectible attribute animation (file). `kind` ∈ {`model`, `pattern`}. |
|
||||
| GET | `/api/collectible-usernames` | Params: `status` (`` | `vault` | `owned` | `burned`), `owner_user_id` (int64), `before_id` (int64), `limit` (int), `q`. Response: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/collectible-usernames/{id}` | Response: `asset`, `transfers`. |
|
||||
| GET | `/api/collectible-phones` | Params are passed through to the Admin API as-is (`/v1/collectible-phones?...`). |
|
||||
| GET | `/api/collectible-phones/{id}` | Params are passed through to the Admin API (`/v1/collectible-phones/{id}?...`). |
|
||||
|
||||
## API: Premium
|
||||
|
||||
All routes in this section require the `premium.manage` permission (checked both
|
||||
at the panel and at the Admin API).
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/premium/plans` | Premium plan catalog (proxied to Admin API `/v1/premium/plans`). |
|
||||
|
||||
## API: moderation
|
||||
|
||||
All reads pass query parameters through to the Admin API as-is
|
||||
(`/v1/moderation/...`). Decisions are `POST` (see below).
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/moderation/cases` | Moderation case list. |
|
||||
| GET | `/api/moderation/cases/{id}` | Moderation case. |
|
||||
| GET | `/api/moderation/reports/{id}` | Report. |
|
||||
| POST | `/api/moderation/cases/{id}/claim` | Claim the case. Body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/moderation/cases/{id}/decide` | Decide the case. Body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/moderation/cases/{id}/appeals/{appeal_id}/review` | Review an appeal. Body: common fields + `version` (int64), `internal_note` (string). |
|
||||
|
||||
## API: official verification
|
||||
|
||||
All routes require the `verification.review` permission. Reads go straight to
|
||||
PostgreSQL; decisions always go through the Admin API (command journal, state
|
||||
machine, optimistic locking).
|
||||
|
||||
| Method | Path | Parameters / body |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/verification/applications` | Params: `status`, `target_type`, `reviewer`, `q`, `before_id`, `limit`. Response: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/verification/applications/{id}` | Response: application, events, `applicant_controls_target`, `target_verified`. |
|
||||
| GET | `/api/verification/counts` | Application counts by status. |
|
||||
| POST | `/api/verification/applications/{id}/claim` | Decision body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/verification/applications/{id}/approve` | Body: common fields + `version` (int64), `internal_note` (string). Grants badge. |
|
||||
| POST | `/api/verification/applications/{id}/reject` | Body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/actions/revoke-verification` | Strip a badge. Requires `verification.review` **and** `verification.revoke`. Body — see actions section below. |
|
||||
|
||||
A "decided by another moderator" conflict is returned as `409 Conflict`.
|
||||
|
||||
## API: third-party (bot) verification
|
||||
|
||||
A separate mechanism with separate tables, permissions, and routes. Queue reads
|
||||
and decisions require `botverification.review`; managing verifiers, the icon
|
||||
catalog, and stripping marks requires `botverification.manage`.
|
||||
|
||||
| Method | Path | Parameters / body |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/botverification/verifiers` | Params: `enabled_only` (bool), `limit` (int). Response: `rows`. |
|
||||
| GET | `/api/botverification/icons` | Params: `active_only` (bool), `limit` (int). Response: `rows`. |
|
||||
| GET | `/api/botverification/marks` | Params: `peer_type`, `verifier_bot_id` (int64), `q`, `before_id` (int64), `limit` (int). Response: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/botverification/requests` | Params: `status`, `peer_type`, `verifier_bot_id` (int64), `q`, `before_id` (int64), `limit` (int). Response: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/botverification/requests/{id}` | Response: `request`, `verifier`, `mark_active`. |
|
||||
| GET | `/api/botverification/counts` | Request counts by status. Response: `counts`. |
|
||||
| POST | `/api/botverification/requests/{id}/approve` | Decision body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/botverification/requests/{id}/reject` | Body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/botverification/requests/{id}/revoke` | Body: common fields + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/actions/grant-bot-verifier` | Appoint a bot as verifier. Requires `botverification.manage`. Body — see below. |
|
||||
| POST | `/api/actions/set-bot-verifier-enabled` | Enable/disable a verifier. Requires `botverification.manage`. Body — see below. |
|
||||
| POST | `/api/actions/revoke-bot-verifier` | Strip a bot's verifier status. Requires `botverification.manage`. Body — see below. |
|
||||
| POST | `/api/actions/upsert-verification-icon` | Add/edit a catalog icon. Requires `botverification.manage`. Body — see below. |
|
||||
| POST | `/api/actions/set-verification-icon-active` | Enable/disable an icon. Requires `botverification.manage`. Body — see below. |
|
||||
| POST | `/api/actions/revoke-custom-verification` | Strip a third-party mark. Requires `botverification.manage`. Body — see below. |
|
||||
|
||||
---
|
||||
|
||||
## API: account actions
|
||||
|
||||
All routes are `POST /api/actions/...`, require a session and CSRF. The request
|
||||
body always contains the **common command fields** (`command_id`, `reason`,
|
||||
`confirm`) plus the fields from the table below.
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `set-frozen` | `user_id` (int64), `frozen` (bool), `freeze_until` (time, opt.), `freeze_appeal_url` (string, opt.) | — |
|
||||
| `grant-premium` | `user_id` (int64), `months` (int) | `premium.manage` |
|
||||
| `upsert-premium-plan` | `months` (int), `duration_days` (int), `amount_stars` (int64), `fiat_currency` (string), `fiat_amount` (int64), `store_product` (string), `store_quantity` (int), `enabled` (bool), `sort_order` (int), `label` (string), `expected_version` (int64) | `premium.manage` |
|
||||
| `grant-stars` | `user_id` (int64), `amount` (int64) | — |
|
||||
| `set-verified` | `user_id` (int64), `verified` (bool) | — |
|
||||
| `set-account-flags` | `user_id` (int64), `scam` (bool), `fake` (bool) | — |
|
||||
| `set-support` | `user_id` (int64), `support` (bool) | — |
|
||||
| `set-account-username` | `user_id` (int64), `username` (string) | — |
|
||||
| `set-account-profile` | `user_id` (int64), `first_name` (string), `last_name` (string) | — |
|
||||
| `set-account-phone` | `user_id` (int64), `phone` (string) | — |
|
||||
| `set-account-login-email` | `user_id` (int64), `email` (string) | — |
|
||||
| `set-account-avatar` | **multipart**: `metadata` field (JSON with common fields + `user_id` (int64)) and a file in `file` | — |
|
||||
| `set-account-color` | `user_id` (int64), `for_profile` (bool), `has_color` (bool), `color` (int), `background_emoji_id` (int64) | — |
|
||||
| `set-account-emoji-status` | `user_id` (int64), `document_id` (int64), `until` (int, seconds) | — |
|
||||
| `revoke-sessions` | `user_id` (int64), `hash` (int64, opt.), `keep_hash` (int64, opt.), `revoke_all` (bool) | — |
|
||||
|
||||
Example (granting Premium, the case from above):
|
||||
|
||||
```http
|
||||
POST /api/actions/grant-premium
|
||||
Content-Type: application/json
|
||||
X-CSRF-Token: <csrf from /api/session>
|
||||
|
||||
{
|
||||
"command_id": "grant-premium-001",
|
||||
"reason": "Incident compensation",
|
||||
"confirm": true,
|
||||
"user_id": 123456789,
|
||||
"months": 12
|
||||
}
|
||||
```
|
||||
|
||||
## API: channel actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `set-channel-flags` | `channel_id` (int64), `scam` (bool), `fake` (bool) | — |
|
||||
| `set-channel-settings` | `channel_id` (int64), `gigagroup` (*bool), `antispam` (*bool), `participants_hidden` (*bool), `noforwards` (*bool), `join_to_send` (*bool), `join_request` (*bool), `slowmode_seconds` (*int) — all optional pointers | — |
|
||||
| `set-channel-username` | `channel_id` (int64), `username` (string) | — |
|
||||
| `set-channel-color` | `channel_id` (int64), `for_profile` (bool), `has_color` (bool), `color` (int), `background_emoji_id` (int64) | — |
|
||||
| `set-channel-emoji-status` | `channel_id` (int64), `document_id` (int64), `until` (int) | — |
|
||||
| `set-channel-avatar` | **multipart**: `metadata` (JSON with common fields + `channel_id` (int64)) and a file `file` | — |
|
||||
| `set-channel-verified` | `channel_id` (int64), `verified` (bool) | — |
|
||||
|
||||
## API: bot actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `create-bot` | `owner_user_id` (int64), `name` (string), `username` (string) | — |
|
||||
| `delete-bot` | `bot_user_id` (int64) | — |
|
||||
| `export-bot-token` | `bot_user_id` (int64) | `bots.token.read` |
|
||||
| `create-broadcast` | `message` (string), `target_mode` (string), `user_ids` ([]int64, opt.) | — |
|
||||
|
||||
## API: sticker and emoji actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `create-sticker-set` | **multipart**: `metadata` (JSON: common fields + `title`, `short_name`, `kind`, `emoji`, `keywords`) and a file `file` | — |
|
||||
| `rename-sticker-set` | `set_id` (int64), `title` (string) | — |
|
||||
| `add-sticker-to-set` | **multipart**: `metadata` (JSON: common fields + `set_id` (string), `emoji`, `keywords`) and a file `file` | — |
|
||||
| `remove-sticker-from-set` | `set_id` (int64), `document_id` (int64) | — |
|
||||
| `set-sticker-set-archived` | `set_id` (int64), `archived` (bool) | — |
|
||||
| `set-sticker-set-sort-order` | `set_id` (int64), `sort_order` (int) | — |
|
||||
| `delete-sticker-set` | `set_id` (int64) | — |
|
||||
|
||||
## API: GIF catalog actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `create-gif-catalog-entry` | **multipart**: `metadata` (JSON: common fields + `title`) and a file `file` | — |
|
||||
| `set-gif-catalog-enabled` | `id` (int64, sent as string), `enabled` (bool) | — |
|
||||
| `set-gif-catalog-sort-order` | `id` (int64, string), `sort_order` (int) | — |
|
||||
| `delete-gif-catalog-entry` | `id` (int64, string) | — |
|
||||
|
||||
## API: gift and collectible actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `import-gift` | **multipart**: `metadata` (JSON: common fields + `gift_id` (int64), `title`, `stars` (int64), `convert_stars` (int64), `enabled` (bool), `sort_order` (int), `auction` (bool), `auction_slug`, `gifts_per_round` (int), `auction_start_date` (int), `auction_round_duration` (int), `availability_total` (int), `locked_until_date` (int)) and a file `file` | — |
|
||||
| `import-official-gift` | common fields + `source_gift_id` (string), `gift_id` (int64), `title`, `stars` (int64), `convert_stars` (int64), `enabled` (bool), `sort_order` (int), `include_collectible` (bool), `upgrade_stars` (int64), `supply_total` (int), `slug_prefix` (string), `locked_until_date` (int) | — |
|
||||
| `publish-gift-collectibles` | **multipart**, `gift_id` sent in query (`?gift_id=`): `metadata` (JSON: common fields + `upgrade_stars` (int64), `supply_total` (int), `slug_prefix` (string), `models` ([]object), `patterns` ([]object), `backdrops` ([]object)); animations are files keyed by the `models`/`patterns` entries | — |
|
||||
| `set-gift-enabled` | `gift_id` (int64), `enabled` (bool) | — |
|
||||
| `set-gift-sort-order` | `gift_id` (int64), `sort_order` (int) | — |
|
||||
| `give-gift` | common fields + `sender_user_id` (int64), `user_id` (int64), `channel_id` (int64), `gift_id` (int64), `hide_name` (bool), `message` (string), `upgrade` (bool), `model_attribute_id` (int64), `pattern_attribute_id` (int64), `backdrop_attribute_id` (int64) | — |
|
||||
|
||||
## API: collectible username actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `mint-collectible-username` | `username` (string), `owner_user_id` (int64), `owner_channel_id` (int64), `currency` (string), `amount` (int64), `crypto_currency` (string), `crypto_amount` (int64), `url` (string), `purchase_date` (int/unix or date) | — |
|
||||
| `transfer-collectible-username` | `username` (string), `to_user_id` (int64), `to_channel_id` (int64) | — |
|
||||
| `revoke-collectible-username` | `username` (string), `burn` (bool) | — |
|
||||
| `delete-collectible-username` | `username` (string) | — |
|
||||
|
||||
## API: anonymous number actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `mint-collectible-phone` | `phone` (string), `tier` (string), `owner_user_id` (int64), `currency` (string), `amount` (int64), `crypto_currency` (string), `crypto_amount` (int64), `url` (string), `purchase_date` (int/unix or date) | — |
|
||||
| `update-collectible-phone-price` | `phone` (string), `currency` (string), `amount` (int64), `crypto_currency` (string), `crypto_amount` (int64) | — |
|
||||
| `transfer-collectible-phone` | `phone` (string), `to_user_id` (int64) | — |
|
||||
| `revoke-collectible-phone` | `phone` (string), `burn` (bool) | — |
|
||||
| `delete-collectible-phone` | `phone` (string) | — |
|
||||
|
||||
## API: account rating actions
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `recompute-account-rating` | `user_id` (int64) | — |
|
||||
| `adjust-account-rating` | `user_id` (int64), `amount` (int64) | — |
|
||||
|
||||
## API: message deletion
|
||||
|
||||
| Path | Specific body fields | Permission |
|
||||
| --- | --- | --- |
|
||||
| `delete-messages` | `owner_user_id` (int64), `peer_id` (int64), `ids` ([]int), `revoke` (bool) | — |
|
||||
| `delete-history` | `owner_user_id` (int64), `peer_id` (int64), `max_id` (int), `min_date` (int), `max_date` (int), `max_batches` (int), `just_clear` (bool), `revoke` (bool) | — |
|
||||
|
||||
## API: verification decisions (request bodies)
|
||||
|
||||
Official verification — `revoke-verification`:
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `target_type` | string | Target type (`user` / `channel`, etc., validated by `domain.VerificationTargetType.Valid()`). |
|
||||
| `target_id` | int64 | Target identifier (not the application!). |
|
||||
| `internal_note` | string | Operator-only internal note (opt.). |
|
||||
| + common fields | | `command_id`, `reason`, `confirm`. |
|
||||
|
||||
Third-party verification — verifier/mark actions (`botverification.manage`):
|
||||
|
||||
| Path | Specific body fields |
|
||||
| --- | --- |
|
||||
| `grant-bot-verifier` | `bot_id` (int64), `icon_document_id` (int64), `company_name` (string, req.), `default_description` (string), `can_modify_custom_description` (bool), `version` (int64, 0 for new) |
|
||||
| `set-bot-verifier-enabled` | `bot_id` (int64), `enabled` (bool) |
|
||||
| `revoke-bot-verifier` | `bot_id` (int64) |
|
||||
| `upsert-verification-icon` | `document_id` (int64), `name` (string, req.), `owner_bot_id` (int64, opt., 0 = shared) |
|
||||
| `set-verification-icon-active` | `icon_id` (int64), `active` (bool) |
|
||||
| `revoke-custom-verification` | `verifier_bot_id` (int64), `peer_type` (string, validated), `peer_id` (int64) |
|
||||
|
||||
All of these also carry the common fields (`command_id`, `reason`, `confirm`).
|
||||
416
docs/admin-panel-api.ru.md
Normal file
416
docs/admin-panel-api.ru.md
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
# Админ-панель: все пути и контракты API
|
||||
|
||||
Документация по всем путям встроенной админ-панели `telesrv` — адресам
|
||||
страниц (SPA) и API-маршрутам, которые за ними стоят. Сервер панели живёт в
|
||||
`cmd/telesrv-admin`, маршруты объявлены в функции `(*server).routes()`
|
||||
(`server.go:50`).
|
||||
|
||||
Панель — это обычный HTTP-сервер: он отдаёт собранный фронтенд (`/`) и JSON-API
|
||||
под префиксом `/api/`. Все API-пути требуют сессию (cookie), а все
|
||||
изменяющие запросы — ещё и CSRF-токен. Многие маршруты дополнительно проверяют
|
||||
права оператора (`permission`).
|
||||
|
||||
## Как пользоваться этим документом
|
||||
|
||||
- Для каждого GET-эндпоинта указаны **параметры запроса** (query) и краткая
|
||||
форма **ответа**.
|
||||
- Для каждого POST-эндпоинта в блоке `/api/actions/*` и решений верификации
|
||||
указано **тело запроса** (JSON). Все они разделяют общие поля
|
||||
(см. ниже) плюс свои специфичные поля.
|
||||
- `int64` в JSON можно передавать и как число, и как строку (например,
|
||||
`"user_id": "123"` или `"user_id": 123`) — сервер принимает оба варианта
|
||||
(`flexInt64`). `flexUnix` принимает Unix-секунды или дату `2006-01-02` /
|
||||
RFC3339.
|
||||
- Все изменяющие запросы идут в Admin API. Ответ команды — либо
|
||||
`{"status":..., "message":..., "command_id":...}` при успехе (HTTP 200), либо
|
||||
`{"error":..., "code":...}` при ошибке. Конфликт оптимистичной блокировки —
|
||||
`409`.
|
||||
|
||||
## Аутентификация и сессия
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
| --- | --- | --- |
|
||||
| POST | `/api/login` | Вход. Тело: `{"secret": "..."}`. Проверяет секрет из конфига, выдаёт подписанную cookie-сессию и cookie CSRF. Единственный изменяющий маршрут без CSRF-токена; проверяется только Origin. |
|
||||
| POST | `/api/logout` | Выход. Уничтожает сессию и cookie CSRF. |
|
||||
| GET | `/api/session` | Возвращает `{"actor": "...", "permissions": [...], "csrf_token": "..."}`. Панель вызывает его при старте. |
|
||||
|
||||
Сессия живёт в подписанной cookie (TTL по умолчанию 12 часов), серверного
|
||||
хранилища сессий нет. Все запросы, кроме `GET/HEAD/OPTIONS` и `/api/login`,
|
||||
обязаны предъявить:
|
||||
|
||||
- cookie `telesrv_admin_csrf`;
|
||||
- заголовок `X-CSRF-Token` с тем же значением;
|
||||
- Origin, совпадающий с хостом (если Origin присутствует).
|
||||
|
||||
Нарушение любого из трёх условий — `403 Forbidden`.
|
||||
|
||||
## Права и их проверка
|
||||
|
||||
Панель различает обычную авторизацию (`requireAuthAPI`) и проверку права
|
||||
(`requirePermission`). Имена прав совпадают со строками в
|
||||
`TELESRV_ADMIN_UI_PERMISSIONS`:
|
||||
|
||||
| Право | Что даёт |
|
||||
| --- | --- |
|
||||
| `*` | Все права (wildcard). |
|
||||
| `premium.manage` | Управление каталогом Premium-планов, начисление Premium. |
|
||||
| `bots.token.read` | Экспорт токена бота (`/api/actions/export-bot-token`). |
|
||||
| `verification.review` | Чтение и решение очереди официальной верификации. |
|
||||
| `verification.revoke` | Снятие официального бейджа (в дополнение к `verification.review`). |
|
||||
| `botverification.review` | Чтение и решение очереди сторонней (ботовой) верификации. |
|
||||
| `botverification.manage` | Назначение верификаторов, редактирование каталога иконок, снятие метки сторонней верификации. |
|
||||
|
||||
Права официальной и сторонней верификации намеренно независимы. Список прав
|
||||
сессии возвращается в `/api/session`, чтобы интерфейс скрывал недоступные
|
||||
разделы.
|
||||
|
||||
## Страницы интерфейса (SPA)
|
||||
|
||||
SPA-роуты декларативны: любое имя раздела отдаётся сервером как `/` (index.html),
|
||||
а сам фронтенд решает, что рендерить (`web/src/pages/Routes.tsx`).
|
||||
|
||||
| Путь | Страница |
|
||||
| --- | --- |
|
||||
| `/` | Панель управления (счётчики, статистика хранилища, ссылки на разделы). |
|
||||
| `/accounts` | Список аккаунтов (с поиском). |
|
||||
| `/accounts/{id}` | Карточка аккаунта: профиль, действия. |
|
||||
| `/channels` | Список супергрупп и каналов. |
|
||||
| `/channels/{id}` | Карточка канала. |
|
||||
| `/bots` | Список ботов. |
|
||||
| `/bots/{id}` | Карточка бота. |
|
||||
| `/broadcasts` | Рассылки. |
|
||||
| `/monetization`, `/premium` | Звёзды и Premium: планы, начисление. Требует `premium.manage`. |
|
||||
| `/moderation` | Жалобы и модерация: список кейсов. |
|
||||
| `/moderation/{id}` | Детали кейса модерации. |
|
||||
| `/emoji` | Каталог emoji-наборов. |
|
||||
| `/stickers` | Каталог стикерпаков. |
|
||||
| `/gif-catalog` | Каталог GIF. |
|
||||
| `/messages`, `/messages/private` | Аудит личных сообщений. |
|
||||
| `/messages/detail`, `/messages/private/detail` | Детали личного сообщения (`?owner_user_id=&msg_id=`). |
|
||||
| `/messages/groups` | Аудит сообщений групп/каналов. |
|
||||
| `/messages/groups/detail` | Детали сообщения в группе (`?channel_id=&msg_id=`). |
|
||||
| `/gifts` | Звёздные подарки: каталог, коллекционки, аукционы. |
|
||||
| `/give-gifts` | Выдача подарков. |
|
||||
| `/collectible-usernames` | Коллекционные юзернеймы. |
|
||||
| `/collectible-usernames/{id}` | Карточка коллекционного юзернейма. |
|
||||
| `/collectible-phones` | Анонимные номера. |
|
||||
| `/account-ratings` | Рейтинг аккаунтов. |
|
||||
| `/account-ratings/{user_id}` | Карточка рейтинга аккаунта. |
|
||||
| `/storage` | Объектное хранилище: статистика. |
|
||||
| `/verification` | Официальная верификация: очередь заявок. Требует `verification.review`. |
|
||||
| `/verification/{id}` | Детали заявки на официальную верификацию. Требует `verification.review`. |
|
||||
| `/bot-verification` | Сторонняя верификация: верификаторы, иконки, метки, очередь. Требует `botverification.review`. |
|
||||
| `/bot-verification/{id}` | Детали запроса сторонней верификации. Требует `botverification.review`. |
|
||||
|
||||
Несуществующий API-путь возвращает `404 {"error":"api route not found"}`; любой
|
||||
неизвестный путь фронтенда отдаётся как `/`.
|
||||
|
||||
## Соглашения ответов
|
||||
|
||||
- Успешные чтения — `200` + JSON; файлы (аватары, анимации, превью) — сам файл.
|
||||
- Ошибки — JSON вида `{"error": "...", "code": "..."}` с соответствующим HTTP-статусом.
|
||||
- `401` — нет/просрочена сессия; `403` — нарушение CSRF/Origin или не хватает права
|
||||
(в `requirePermission` к телу добавляется поле `permission`);
|
||||
`409` — конфликт оптимистичной блокировки (кейс модерации, верификация);
|
||||
`502` — Admin API недоступен.
|
||||
- Ошибки команд, ушедших в Admin API, возвращаются как
|
||||
`{"status": ..., "message": ..., "error": ...}`.
|
||||
|
||||
## Общие поля команд (POST `/api/actions/*`)
|
||||
|
||||
Каждый изменяющий запрос несёт в теле:
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
| --- | --- | --- |
|
||||
| `command_id` | string | Идемпотентный ключ команды. Повтор с тем же `command_id` не выполняет действие дважды. Если пусто — генерируется сервером. |
|
||||
| `reason` | string | Обязательная причина операции (аудит). |
|
||||
| `confirm` | bool | Подтверждение оператором (`true`). |
|
||||
|
||||
---
|
||||
|
||||
## API: дашборд и хранилище
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/dashboard` | Сводка: `counts`, `storage`, при наличии — `host`. |
|
||||
| GET | `/api/storage/stats` | Статистика объектного хранилища. |
|
||||
|
||||
## API: аккаунты
|
||||
|
||||
| Метод | Путь | Параметры / ответ |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/accounts` | Параметры: `q` (поиск), `before_id` (int64), `before_active_us` (int64, микросекунды), `limit` (int). Ответ: `query`, `limit`, `rows`, `has_more`, `next_before_id`, `next_before_active_us`, `listing`. |
|
||||
| GET | `/api/accounts/{id}` | Карточка аккаунта: профиль, флаги, статистика (проксируется/читается из БД). |
|
||||
| GET | `/api/accounts/{id}/avatar` | Аватар аккаунта (файл). |
|
||||
| GET | `/api/account-ratings` | Параметры: `q`, `min_level` (int), `user_id` (int64), `before_id` (int64), `limit` (int). Ответ: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/account-ratings/{user_id}` | Ответ: `rating`, `events`. |
|
||||
|
||||
## API: каналы и супергруппы
|
||||
|
||||
| Метод | Путь | Параметры / ответ |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/channels` | Параметры: `q`, `before_id` (int64), `before_updated_us` (int64, микросекунды), `limit` (int). Ответ: `query`, `limit`, `rows`, `has_more`, `next_before_id`, `next_before_updated_us`, `listing`. |
|
||||
| GET | `/api/channels/{id}` | Карточка канала. |
|
||||
| GET | `/api/channels/{id}/avatar` | Аватар канала (файл). |
|
||||
|
||||
## API: боты и рассылки
|
||||
|
||||
| Метод | Путь | Параметры / ответ |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/bots` | Параметры: `q`, `before_id` (int64), `limit` (int). Ответ: `query`, `limit`, `rows`, `has_more`, `next_before_id`, `listing`. |
|
||||
| GET | `/api/bots/{id}` | Карточка бота. |
|
||||
| GET | `/api/broadcasts` | Параметры: `before_id` (int64), `limit` (int). Ответ: `limit`, `rows`, `has_more`, `next_before_id`. |
|
||||
|
||||
## API: медиа-каталоги
|
||||
|
||||
| Метод | Путь | Параметры / ответ |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/emoji` | Параметры: `q`, `before_id` (int64), `limit` (int). Ответ: `query`, `rows`, `has_more`, `next_before_id`, `listing`. |
|
||||
| GET | `/api/emoji/{id}/animation` | Lottie-анимация emoji (файл). |
|
||||
| GET | `/api/stickers` | Параметр: `kind` (string, фильтр по типу). Ответ: `rows`, `max_items`. |
|
||||
| GET | `/api/stickers/{id}/documents` | Ответ: `document_ids`. |
|
||||
| GET | `/api/stickers/documents/{id}/animation` | Анимация стикера (файл). |
|
||||
| GET | `/api/gif-catalog` | Ответ: проксируется из Admin API (`/v1/gif-catalog`). |
|
||||
| GET | `/api/gif-catalog/documents/{id}/preview` | Превью GIF (файл). |
|
||||
|
||||
## API: аудит сообщений
|
||||
|
||||
| Метод | Путь | Параметры / ответ |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/messages` | Параметры: `owner_user_id` (int64, обязателен с `peer_id`), `peer_id` (int64), `before_date` (int64), `before_id` (int), `limit` (int). Ответ: `owner_user_id`, `peer_id`, `before_date`, `before_id`, `limit`, `rows`. |
|
||||
| GET | `/api/messages/detail` | Параметры: `owner_user_id` (int64, обяз.), `msg_id` (int, обяз.). Ответ: карточка сообщения. |
|
||||
| GET | `/api/messages/groups` | Параметры: `channel_id` (int64, обяз.), `before_date` (int64), `before_id` (int), `limit` (int). Ответ: `channel_id`, `before_date`, `before_id`, `limit`, `rows`. |
|
||||
| GET | `/api/messages/groups/detail` | Параметры: `channel_id` (int64, обяз.), `msg_id` (int, обяз.). Ответ: карточка сообщения. |
|
||||
|
||||
## API: звёздные подарки и коллекционки
|
||||
|
||||
| Метод | Путь | Параметры / ответ |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/gifts` | Ответ: `Gifts` (список подарков). |
|
||||
| GET | `/api/auctions` | Ответ: `Auctions` — текущее состояние всех авторских аукционов и запланированных дропов. |
|
||||
| GET | `/api/official-gifts` | Ответ: проксируется из Admin API (`/v1/official-gifts`). |
|
||||
| GET | `/api/official-gifts/{id}/animation` | Анимация официального подарка (файл). |
|
||||
| GET | `/api/gifts/{id}/animation` | Анимация подарка (файл). |
|
||||
| GET | `/api/gifts/{id}/collectibles` | Ответ: проксируется из Admin API (`/v1/gifts/{id}/collectibles`). |
|
||||
| GET | `/api/gifts/{id}/collectibles/{kind}/{attribute_id}/animation` | Анимация атрибута коллекционки (файл). `kind` ∈ {`model`, `pattern`}. |
|
||||
| GET | `/api/collectible-usernames` | Параметры: `status` (`` | `vault` | `owned` | `burned`), `owner_user_id` (int64), `before_id` (int64), `limit` (int), `q`. Ответ: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/collectible-usernames/{id}` | Ответ: `asset`, `transfers`. |
|
||||
| GET | `/api/collectible-phones` | Параметры пробрасываются в Admin API как есть (`/v1/collectible-phones?...`). |
|
||||
| GET | `/api/collectible-phones/{id}` | Параметры пробрасываются в Admin API (`/v1/collectible-phones/{id}?...`). |
|
||||
|
||||
## API: Premium
|
||||
|
||||
Все маршруты раздела требуют права `premium.manage` (проверяется и на уровне
|
||||
панели, и на стороне Admin API).
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/premium/plans` | Каталог Premium-планов (проксируется в Admin API `/v1/premium/plans`). |
|
||||
|
||||
## API: модерация
|
||||
|
||||
Все чтения пробрасывают query-параметры в Admin API как есть
|
||||
(`/v1/moderation/...`). Решения — `POST` (см. ниже).
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/moderation/cases` | Список кейсов модерации. |
|
||||
| GET | `/api/moderation/cases/{id}` | Кейс модерации. |
|
||||
| GET | `/api/moderation/reports/{id}` | Жалоба. |
|
||||
| POST | `/api/moderation/cases/{id}/claim` | Взять кейс на себя. Тело: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/moderation/cases/{id}/decide` | Решение по кейсу. Тело: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/moderation/cases/{id}/appeals/{appeal_id}/review` | Рассмотрение апелляции. Тело: общие поля + `version` (int64), `internal_note` (string). |
|
||||
|
||||
## API: официальная верификация
|
||||
|
||||
Все маршруты требуют права `verification.review`. Чтения идут напрямую из
|
||||
PostgreSQL, решения — всегда через Admin API (журнал команд, статусная машина,
|
||||
оптимистичная блокировка).
|
||||
|
||||
| Метод | Путь | Параметры / тело |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/verification/applications` | Параметры: `status`, `target_type`, `reviewer`, `q`, `before_id`, `limit`. Ответ: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/verification/applications/{id}` | Ответ: заявка, события, `applicant_controls_target`, `target_verified`. |
|
||||
| GET | `/api/verification/counts` | Счётчики заявок по статусам. |
|
||||
| POST | `/api/verification/applications/{id}/claim` | Тело решения: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/verification/applications/{id}/approve` | Тело: общие поля + `version` (int64), `internal_note` (string). Выдаёт бейдж. |
|
||||
| POST | `/api/verification/applications/{id}/reject` | Тело: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/actions/revoke-verification` | Снять бейдж. Требует `verification.review` **и** `verification.revoke`. Тело — см. ниже в разделе действий. |
|
||||
|
||||
Конфликт «решил другой модератор» возвращается как `409 Conflict`.
|
||||
|
||||
## API: сторонняя (ботовая) верификация
|
||||
|
||||
Отдельный механизм с отдельными таблицами, правами и маршрутами. Чтения и
|
||||
решения очереди требуют `botverification.review`; управление верификаторами,
|
||||
каталогом иконок и снятие меток — `botverification.manage`.
|
||||
|
||||
| Метод | Путь | Параметры / тело |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/botverification/verifiers` | Параметры: `enabled_only` (bool), `limit` (int). Ответ: `rows`. |
|
||||
| GET | `/api/botverification/icons` | Параметры: `active_only` (bool), `limit` (int). Ответ: `rows`. |
|
||||
| GET | `/api/botverification/marks` | Параметры: `peer_type`, `verifier_bot_id` (int64), `q`, `before_id` (int64), `limit` (int). Ответ: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/botverification/requests` | Параметры: `status`, `peer_type`, `verifier_bot_id` (int64), `q`, `before_id` (int64), `limit` (int). Ответ: `rows`, `has_more`, `next_before_id`. |
|
||||
| GET | `/api/botverification/requests/{id}` | Ответ: `request`, `verifier`, `mark_active`. |
|
||||
| GET | `/api/botverification/counts` | Счётчики запросов по статусам. Ответ: `counts`. |
|
||||
| POST | `/api/botverification/requests/{id}/approve` | Тело решения: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/botverification/requests/{id}/reject` | Тело: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/botverification/requests/{id}/revoke` | Тело: общие поля + `version` (int64), `internal_note` (string). |
|
||||
| POST | `/api/actions/grant-bot-verifier` | Назначить бота верификатором. Требует `botverification.manage`. Тело — см. ниже. |
|
||||
| POST | `/api/actions/set-bot-verifier-enabled` | Включить/отключить верификатора. Требует `botverification.manage`. Тело — см. ниже. |
|
||||
| POST | `/api/actions/revoke-bot-verifier` | Лишить бота статуса верификатора. Требует `botverification.manage`. Тело — см. ниже. |
|
||||
| POST | `/api/actions/upsert-verification-icon` | Добавить/изменить иконку в каталоге. Требует `botverification.manage`. Тело — см. ниже. |
|
||||
| POST | `/api/actions/set-verification-icon-active` | Включить/отключить иконку. Требует `botverification.manage`. Тело — см. ниже. |
|
||||
| POST | `/api/actions/revoke-custom-verification` | Снять стороннюю метку. Требует `botverification.manage`. Тело — см. ниже. |
|
||||
|
||||
---
|
||||
|
||||
## API: действия над аккаунтами
|
||||
|
||||
Все маршруты — `POST /api/actions/...`, требуют сессию и CSRF. Тело запроса
|
||||
всегда содержит **общие поля команд** (`command_id`, `reason`, `confirm`) плюс
|
||||
поля из таблицы ниже.
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `set-frozen` | `user_id` (int64), `frozen` (bool), `freeze_until` (time, опц.), `freeze_appeal_url` (string, опц.) | — |
|
||||
| `grant-premium` | `user_id` (int64), `months` (int) | `premium.manage` |
|
||||
| `upsert-premium-plan` | `months` (int), `duration_days` (int), `amount_stars` (int64), `fiat_currency` (string), `fiat_amount` (int64), `store_product` (string), `store_quantity` (int), `enabled` (bool), `sort_order` (int), `label` (string), `expected_version` (int64) | `premium.manage` |
|
||||
| `grant-stars` | `user_id` (int64), `amount` (int64) | — |
|
||||
| `set-verified` | `user_id` (int64), `verified` (bool) | — |
|
||||
| `set-account-flags` | `user_id` (int64), `scam` (bool), `fake` (bool) | — |
|
||||
| `set-support` | `user_id` (int64), `support` (bool) | — |
|
||||
| `set-account-username` | `user_id` (int64), `username` (string) | — |
|
||||
| `set-account-profile` | `user_id` (int64), `first_name` (string), `last_name` (string) | — |
|
||||
| `set-account-phone` | `user_id` (int64), `phone` (string) | — |
|
||||
| `set-account-login-email` | `user_id` (int64), `email` (string) | — |
|
||||
| `set-account-avatar` | **multipart**: поле `metadata` (JSON с общими полями + `user_id` (int64)) и файл в поле `file` | — |
|
||||
| `set-account-color` | `user_id` (int64), `for_profile` (bool), `has_color` (bool), `color` (int), `background_emoji_id` (int64) | — |
|
||||
| `set-account-emoji-status` | `user_id` (int64), `document_id` (int64), `until` (int, секунды) | — |
|
||||
| `revoke-sessions` | `user_id` (int64), `hash` (int64, опц.), `keep_hash` (int64, опц.), `revoke_all` (bool) | — |
|
||||
|
||||
Пример (выдача Premium, как в вопросе выше):
|
||||
|
||||
```http
|
||||
POST /api/actions/grant-premium
|
||||
Content-Type: application/json
|
||||
X-CSRF-Token: <csrf из /api/session>
|
||||
|
||||
{
|
||||
"command_id": "grant-premium-001",
|
||||
"reason": "Компенсация за инцидент",
|
||||
"confirm": true,
|
||||
"user_id": 123456789,
|
||||
"months": 12
|
||||
}
|
||||
```
|
||||
|
||||
## API: действия над каналами
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `set-channel-flags` | `channel_id` (int64), `scam` (bool), `fake` (bool) | — |
|
||||
| `set-channel-settings` | `channel_id` (int64), `gigagroup` (*bool), `antispam` (*bool), `participants_hidden` (*bool), `noforwards` (*bool), `join_to_send` (*bool), `join_request` (*bool), `slowmode_seconds` (*int) — все опциональные указатели | — |
|
||||
| `set-channel-username` | `channel_id` (int64), `username` (string) | — |
|
||||
| `set-channel-color` | `channel_id` (int64), `for_profile` (bool), `has_color` (bool), `color` (int), `background_emoji_id` (int64) | — |
|
||||
| `set-channel-emoji-status` | `channel_id` (int64), `document_id` (int64), `until` (int) | — |
|
||||
| `set-channel-avatar` | **multipart**: `metadata` (JSON с общими полями + `channel_id` (int64)) и файл `file` | — |
|
||||
| `set-channel-verified` | `channel_id` (int64), `verified` (bool) | — |
|
||||
|
||||
## API: действия над ботами
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `create-bot` | `owner_user_id` (int64), `name` (string), `username` (string) | — |
|
||||
| `delete-bot` | `bot_user_id` (int64) | — |
|
||||
| `export-bot-token` | `bot_user_id` (int64) | `bots.token.read` |
|
||||
| `create-broadcast` | `message` (string), `target_mode` (string), `user_ids` ([]int64, опц.) | — |
|
||||
|
||||
## API: действия над стикерами и emoji
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `create-sticker-set` | **multipart**: `metadata` (JSON: общие поля + `title`, `short_name`, `kind`, `emoji`, `keywords`) и файл `file` | — |
|
||||
| `rename-sticker-set` | `set_id` (int64), `title` (string) | — |
|
||||
| `add-sticker-to-set` | **multipart**: `metadata` (JSON: общие поля + `set_id` (string), `emoji`, `keywords`) и файл `file` | — |
|
||||
| `remove-sticker-from-set` | `set_id` (int64), `document_id` (int64) | — |
|
||||
| `set-sticker-set-archived` | `set_id` (int64), `archived` (bool) | — |
|
||||
| `set-sticker-set-sort-order` | `set_id` (int64), `sort_order` (int) | — |
|
||||
| `delete-sticker-set` | `set_id` (int64) | — |
|
||||
|
||||
## API: действия над GIF-каталогом
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `create-gif-catalog-entry` | **multipart**: `metadata` (JSON: общие поля + `title`) и файл `file` | — |
|
||||
| `set-gif-catalog-enabled` | `id` (int64, передаётся как строка), `enabled` (bool) | — |
|
||||
| `set-gif-catalog-sort-order` | `id` (int64, строка), `sort_order` (int) | — |
|
||||
| `delete-gif-catalog-entry` | `id` (int64, строка) | — |
|
||||
|
||||
## API: действия над подарками и коллекционками
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `import-gift` | **multipart**: `metadata` (JSON: общие поля + `gift_id` (int64), `title`, `stars` (int64), `convert_stars` (int64), `enabled` (bool), `sort_order` (int), `auction` (bool), `auction_slug`, `gifts_per_round` (int), `auction_start_date` (int), `auction_round_duration` (int), `availability_total` (int), `locked_until_date` (int)) и файл `file` | — |
|
||||
| `import-official-gift` | общие поля + `source_gift_id` (string), `gift_id` (int64), `title`, `stars` (int64), `convert_stars` (int64), `enabled` (bool), `sort_order` (int), `include_collectible` (bool), `upgrade_stars` (int64), `supply_total` (int), `slug_prefix` (string), `locked_until_date` (int) | — |
|
||||
| `publish-gift-collectibles` | **multipart**, `gift_id` передаётся в query (`?gift_id=`): `metadata` (JSON: общие поля + `upgrade_stars` (int64), `supply_total` (int), `slug_prefix` (string), `models` ([]объект), `patterns` ([]объект), `backdrops` ([]объект)); анимации — файлы по ключам из `models`/`patterns` | — |
|
||||
| `set-gift-enabled` | `gift_id` (int64), `enabled` (bool) | — |
|
||||
| `set-gift-sort-order` | `gift_id` (int64), `sort_order` (int) | — |
|
||||
| `give-gift` | общие поля + `sender_user_id` (int64), `user_id` (int64), `channel_id` (int64), `gift_id` (int64), `hide_name` (bool), `message` (string), `upgrade` (bool), `model_attribute_id` (int64), `pattern_attribute_id` (int64), `backdrop_attribute_id` (int64) | — |
|
||||
|
||||
## API: действия над коллекционными юзернеймами
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `mint-collectible-username` | `username` (string), `owner_user_id` (int64), `owner_channel_id` (int64), `currency` (string), `amount` (int64), `crypto_currency` (string), `crypto_amount` (int64), `url` (string), `purchase_date` (int/unix или дата) | — |
|
||||
| `transfer-collectible-username` | `username` (string), `to_user_id` (int64), `to_channel_id` (int64) | — |
|
||||
| `revoke-collectible-username` | `username` (string), `burn` (bool) | — |
|
||||
| `delete-collectible-username` | `username` (string) | — |
|
||||
|
||||
## API: действия над анонимными номерами
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `mint-collectible-phone` | `phone` (string), `tier` (string), `owner_user_id` (int64), `currency` (string), `amount` (int64), `crypto_currency` (string), `crypto_amount` (int64), `url` (string), `purchase_date` (int/unix или дата) | — |
|
||||
| `update-collectible-phone-price` | `phone` (string), `currency` (string), `amount` (int64), `crypto_currency` (string), `crypto_amount` (int64) | — |
|
||||
| `transfer-collectible-phone` | `phone` (string), `to_user_id` (int64) | — |
|
||||
| `revoke-collectible-phone` | `phone` (string), `burn` (bool) | — |
|
||||
| `delete-collectible-phone` | `phone` (string) | — |
|
||||
|
||||
## API: действия над рейтингом аккаунтов
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `recompute-account-rating` | `user_id` (int64) | — |
|
||||
| `adjust-account-rating` | `user_id` (int64), `amount` (int64) | — |
|
||||
|
||||
## API: удаление сообщений
|
||||
|
||||
| Путь | Специфичные поля тела | Право |
|
||||
| --- | --- | --- |
|
||||
| `delete-messages` | `owner_user_id` (int64), `peer_id` (int64), `ids` ([]int), `revoke` (bool) | — |
|
||||
| `delete-history` | `owner_user_id` (int64), `peer_id` (int64), `max_id` (int), `min_date` (int), `max_date` (int), `max_batches` (int), `just_clear` (bool), `revoke` (bool) | — |
|
||||
|
||||
## API: решения верификации (тела запросов)
|
||||
|
||||
Официальная верификация — `revoke-verification`:
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
| --- | --- | --- |
|
||||
| `target_type` | string | Тип цели (`user` / `channel` и т.п., валидируется `domain.VerificationTargetType.Valid()`). |
|
||||
| `target_id` | int64 | Идентификатор цели (не заявки!). |
|
||||
| `internal_note` | string | Внутренняя заметка оператора (опц.). |
|
||||
| + общие поля | | `command_id`, `reason`, `confirm`. |
|
||||
|
||||
Сторонняя верификация — действия верификатора/меток (`botverification.manage`):
|
||||
|
||||
| Путь | Специфичные поля тела |
|
||||
| --- | --- |
|
||||
| `grant-bot-verifier` | `bot_id` (int64), `icon_document_id` (int64), `company_name` (string, обяз.), `default_description` (string), `can_modify_custom_description` (bool), `version` (int64, 0 для нового) |
|
||||
| `set-bot-verifier-enabled` | `bot_id` (int64), `enabled` (bool) |
|
||||
| `revoke-bot-verifier` | `bot_id` (int64) |
|
||||
| `upsert-verification-icon` | `document_id` (int64), `name` (string, обяз.), `owner_bot_id` (int64, опц., 0 = общая) |
|
||||
| `set-verification-icon-active` | `icon_id` (int64), `active` (bool) |
|
||||
| `revoke-custom-verification` | `verifier_bot_id` (int64), `peer_type` (string, валидируется), `peer_id` (int64) |
|
||||
|
||||
Все они также несут общие поля (`command_id`, `reason`, `confirm`).
|
||||
BIN
docs/assets/gramsrv-android.png
Normal file
BIN
docs/assets/gramsrv-android.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 285 KiB |
BIN
docs/assets/gramsrv-telegram-desktop.png
Normal file
BIN
docs/assets/gramsrv-telegram-desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 488 KiB |
83
docs/local-setup.md
Normal file
83
docs/local-setup.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Local setup
|
||||
|
||||
This guide shows the shortest safe path for running gramsrv on a development
|
||||
machine or a small test server.
|
||||
|
||||
## 1. Prepare local configuration
|
||||
|
||||
The repository intentionally tracks only `.env.example`. Your real `.env` is
|
||||
ignored by Git and must not be committed.
|
||||
|
||||
Linux / macOS:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
${EDITOR:-nano} .env
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
Copy-Item .env.example .env
|
||||
notepad .env
|
||||
```
|
||||
|
||||
If you prefer a different config filename, set `TELESRV_CONFIG` as a process
|
||||
environment variable before starting the server.
|
||||
|
||||
## 2. Set the network values
|
||||
|
||||
Review at least these values in `.env`:
|
||||
|
||||
- `TELESRV_LISTEN` is the MTProto bind address. Use `0.0.0.0:2398` when
|
||||
external clients must connect to this host, or `127.0.0.1:2398` for
|
||||
same-machine testing only.
|
||||
- `TELESRV_ADVERTISE_IP` must be a client-reachable IPv4 or IPv6 address, not a
|
||||
DNS name. Use `127.0.0.1` only when the patched client runs on the same
|
||||
machine. Use a LAN or public IP for phones, other computers, or remote tests.
|
||||
- `TELESRV_PUBLIC_BASE_URL` and `TELESRV_PUBLIC_WEB_BASE_URL` are HTTP(S) URLs
|
||||
used in generated public links. Put hostnames here, not in
|
||||
`TELESRV_ADVERTISE_IP`.
|
||||
- `TELESRV_DEV_AUTH_CODE=12345` is convenient for local development but must not
|
||||
be exposed as a production login code.
|
||||
|
||||
## 3. Start Postgres and Redis
|
||||
|
||||
The development compose file exposes Postgres on `127.0.0.1:5432` and Redis on
|
||||
`127.0.0.1:6399`, matching the defaults in `.env.example`.
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
If you use external Postgres or Redis, update `TELESRV_POSTGRES_DSN` and
|
||||
`TELESRV_REDIS_ADDR` in `.env`.
|
||||
|
||||
## 4. Build and run the server
|
||||
|
||||
Linux / macOS:
|
||||
|
||||
```bash
|
||||
go build -o bin/gramsrv ./cmd/telesrv
|
||||
./bin/gramsrv
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
go build -o bin/gramsrv.exe ./cmd/telesrv
|
||||
.\bin\gramsrv.exe
|
||||
```
|
||||
|
||||
## 5. First-start checklist
|
||||
|
||||
After startup, confirm:
|
||||
|
||||
- migrations completed successfully;
|
||||
- `data/server_rsa.pem` was created if it did not already exist;
|
||||
- MTProto is listening on `TELESRV_LISTEN`;
|
||||
- Postgres and Redis connections are healthy;
|
||||
- patched clients use the matching DC address, port, and server RSA key.
|
||||
|
||||
For the complete configuration reference, see
|
||||
[`docs/configuration.en.md`](configuration.en.md).
|
||||
175
docs/update-service.md
Normal file
175
docs/update-service.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# Native client update service
|
||||
|
||||
`cmd/telesrv-update` is a standalone HTTP service that provides two
|
||||
Telegram-compatible update surfaces:
|
||||
|
||||
- `/current4` and `/files/*` for the built-in Telegram Desktop updater;
|
||||
- `/v1/resolve` for the main `telesrv` process to answer
|
||||
`help.getAppUpdate` for Android, iOS, and other supported builds.
|
||||
|
||||
The service has no HTTP upload endpoint. Operators publish a release by placing
|
||||
an immutable artifact in the configured `files` directory and atomically
|
||||
replacing `manifest.json`. The catalog validates the file size and SHA-256
|
||||
before exposing it, so truncated or accidentally replaced packages fail closed.
|
||||
|
||||
## Quick start
|
||||
|
||||
Create the working directories and start with the disabled example catalog:
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force data\updates\files
|
||||
Copy-Item deploy\update\manifest.example.json data\updates\manifest.json
|
||||
|
||||
go run ./cmd/telesrv-update -check
|
||||
go run ./cmd/telesrv-update
|
||||
```
|
||||
|
||||
Check the local endpoints:
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod http://127.0.0.1:2402/readyz
|
||||
Invoke-RestMethod http://127.0.0.1:2402/current4
|
||||
```
|
||||
|
||||
Connect the main server:
|
||||
|
||||
```dotenv
|
||||
TELESRV_UPDATE_PUBLIC_URL=https://updates.example.test
|
||||
TELESRV_UPDATE_SERVICE_URL=http://127.0.0.1:2402
|
||||
TELESRV_UPDATE_REQUEST_TIMEOUT=2s
|
||||
```
|
||||
|
||||
`PUBLIC_URL` must be reachable by clients and is advertised as
|
||||
`help.getConfig.autoupdate_url_prefix`. `SERVICE_URL` may remain a loopback or
|
||||
private route. When both routes are identical, `SERVICE_URL` may be omitted.
|
||||
Production deployments should place an HTTPS reverse proxy in front of the
|
||||
service without rewriting `/current4` or `/files/*`.
|
||||
|
||||
Standalone service settings:
|
||||
|
||||
```dotenv
|
||||
TELESRV_UPDATE_LISTEN=127.0.0.1:2402
|
||||
TELESRV_UPDATE_MANIFEST=data/updates/manifest.json
|
||||
TELESRV_UPDATE_FILES_DIR=data/updates/files
|
||||
```
|
||||
|
||||
The manifest is reloaded automatically when its timestamp or size changes. A
|
||||
malformed replacement makes readiness and catalog requests return `503`; it is
|
||||
never combined with the previously validated snapshot. Validate a candidate
|
||||
before atomically replacing the active file:
|
||||
|
||||
```powershell
|
||||
go run ./cmd/telesrv-update `
|
||||
-manifest .\manifest.next.json `
|
||||
-files .\data\updates\files `
|
||||
-check
|
||||
```
|
||||
|
||||
## Telegram Desktop contract
|
||||
|
||||
TDesktop requests `<autoupdate_url_prefix>/current4`. A Windows x64 stable
|
||||
release is represented as:
|
||||
|
||||
```json
|
||||
{
|
||||
"win64": {
|
||||
"stable": {
|
||||
"released": 7000007,
|
||||
"link": "/files/tx64upd7000007"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The client compares `released` with its numeric `AppVersion`, downloads the
|
||||
artifact with HTTP Range support, verifies the embedded RSA signature, unpacks
|
||||
it, and only then exposes the normal update banner. A regular EXE or ZIP is not
|
||||
a valid update package.
|
||||
|
||||
Build packages with TDesktop's `Packer` target. The equivalent Windows x64
|
||||
command is:
|
||||
|
||||
```powershell
|
||||
Packer.exe `
|
||||
-version 7000007 `
|
||||
-path Telegram.exe `
|
||||
-path Updater.exe `
|
||||
-path "modules\x64\d3d\d3dcompiler_47.dll" `
|
||||
-target win64
|
||||
```
|
||||
|
||||
It produces `tx64upd7000007` and must report `Signature verified!` before the
|
||||
artifact is published.
|
||||
|
||||
| Platform | `/current4` key | Typical package name |
|
||||
|---|---|---|
|
||||
| Windows x64 | `win64` | `tx64upd<build>` |
|
||||
| Windows ARM64 | `winarm` | `tarm64upd<build>` |
|
||||
| Windows x86 | `win` | `tupdate<build>` |
|
||||
| macOS Intel | `mac` | `tmacupd<build>` |
|
||||
| macOS Apple Silicon | `armac` | `tarmacupd<build>` |
|
||||
| Linux | `linux` | `tlinuxupd<build>` |
|
||||
|
||||
## Update signing
|
||||
|
||||
The public TDesktop sources contain Telegram's public update key; the matching
|
||||
private key is not published. A custom deployment must establish its own update
|
||||
signing identity:
|
||||
|
||||
1. Generate a dedicated RSA-1024 key pair and keep the private key only in a
|
||||
protected build secret store.
|
||||
2. Provide the private key to TDesktop's local `DesktopPrivate/packer_private.h`.
|
||||
3. Embed the matching public key in both the client update verifier and Packer.
|
||||
4. Rebuild the bootstrap client and Packer before publishing updates.
|
||||
5. Do not rotate the key without a transition client that trusts both identities.
|
||||
|
||||
The update service never reads the private key and never creates signatures. It
|
||||
checks SHA-256 and serves an artifact that Packer has already signed. A stock
|
||||
TDesktop binary cannot install a package signed only by a custom key; the first
|
||||
custom client build must be distributed out of band.
|
||||
|
||||
## HTTP behavior
|
||||
|
||||
- `/healthz` reports process liveness.
|
||||
- `/readyz` validates the current catalog.
|
||||
- `/current`, `/current1` ... `/current4` return desktop metadata with
|
||||
`Cache-Control: no-cache`.
|
||||
- `/files/<name>` serves only artifacts referenced by the current validated
|
||||
catalog, supports GET/HEAD and Range, and emits an immutable cache policy and
|
||||
a SHA-256-based ETag.
|
||||
- `/v1/resolve` returns a newer application release or `204 No Content`.
|
||||
|
||||
Unknown package names are not exposed merely because a file exists in the
|
||||
directory. Published package names are immutable: changing an active file makes
|
||||
the endpoint return `503` until a matching manifest snapshot is loaded.
|
||||
|
||||
## Android and iOS
|
||||
|
||||
The main server forwards the client platform, current `app_version`, source,
|
||||
channel, and `lang_code` to `/v1/resolve`. The resolver selects localized notes,
|
||||
does not offer an equal or older version, and applies `url_by_source` when a
|
||||
matching installer/store source is configured.
|
||||
|
||||
- A standalone Android build may open or install an APK URL, but the APK must be
|
||||
signed with the same Android application signing key as the installed build.
|
||||
- Google Play and other store builds should use the corresponding store URL;
|
||||
this mechanism does not bypass store policy.
|
||||
- iOS may display information returned by `help.getAppUpdate`, but installation
|
||||
still happens through App Store, TestFlight, or MDM. A normal iOS application
|
||||
cannot replace itself from an arbitrary IPA URL.
|
||||
- Set `can_not_skip` only after confirming that the target release is actually
|
||||
available to every affected client.
|
||||
|
||||
## Manifest fields
|
||||
|
||||
- `desktop.<platform>.<channel>.build`: numeric TDesktop `AppVersion`.
|
||||
- `file`, `sha256`, `size`: immutable signed artifact and integrity metadata.
|
||||
- `apps.<platform>.<channel>.id`: stable positive release identifier.
|
||||
- `version`: value compared with the client's `initConnection.app_version`.
|
||||
- `notes`: localized text keyed by `en`, `ru`, `ru-ru`, and similar codes.
|
||||
- `url_by_source`: source-specific installer or store URL.
|
||||
- `can_not_skip`: whether the client may dismiss the application update.
|
||||
- `disabled`: keep a valid entry as a draft without publishing it.
|
||||
|
||||
Supported channels are `stable`, `beta`, and `alpha`. See
|
||||
`deploy/update/manifest.example.json` for a complete disabled example.
|
||||
Loading…
Add table
Add a link
Reference in a new issue