diff --git a/blog/2026-11-13-v0.166.0.md b/blog/2026-11-13-v0.166.0.md new file mode 100644 index 0000000..35461f8 --- /dev/null +++ b/blog/2026-11-13-v0.166.0.md @@ -0,0 +1,67 @@ +--- +slug: v0.166.0 +title: "gotd v0.166.0: a bootstrap for user accounts" +authors: [ernado] +tags: [release] +--- + +gotd v0.166.0 adds `telegram/user`, the user-account counterpart of `telegram/bot`: one +call connects, signs in only when the stored session isn't authorized, and starts the +updates and peer managers with their state on disk. It also updates the API docs and +error constants from the Telegram documentation. + +{/* truncate */} + +## `telegram/user` + +A long-running user client (a userbot, an archiver, a service on a user account) needs +the same wiring every time: a persistent session, a sign in that never runs twice, update +state and access hashes on disk so nothing is missed across restarts, and a peer cache. +`telegram/user` does it in one call: + +```go +d := tg.NewUpdateDispatcher() +u, err := user.Start(ctx, appID, appHash, user.Options{ + Dir: "state/me", + Handler: d, + Auth: user.Phone(authenticator, auth.SendCodeOptions{}), +}, telegram.WithLifetime(ctx)) +if err != nil { + return err +} +defer func() { _ = u.Close() }() + +return u.Wait(context.Background()) +``` + +- An authorized session in `Dir` is used as is. A bot session, or another account than + `Options.UserID`, fails with `ErrSessionMismatch` instead of running as it. +- An unauthorized session is signed in with `user.Phone` (code and 2FA), `user.QR` (login + token, imported on another DC if needed) or `user.AuthFunc`. Without `Auth`, `Start` + fails with `ErrNotAuthorized` and sends nothing. +- `FLOOD_WAIT` on sign in is waited out only where retrying can't send a second login + code: before the code was requested, for QR exports, never for `AuthFunc`. +- Updates sent while the client was stopped are delivered after the restart, and the + access hashes of channels and users are kept, so known channels need no extra + `getDifference`. + +See [User authentication](/docs/authentication/user#the-telegramuser-package) and the +[`userbot-echo`](https://github.com/gotd/td/tree/main/examples/userbot-echo) example. +`telegram/bot` now shares the same internals; its API and behaviour are unchanged. + +## API docs and errors + +The generated docs and error constants come from getdoc v0.54.0. 42 error constants are +new (e.g. `ErrURLExpired`, `ErrTopicNotModified`, `ErrBotCreateLimitExceeded`). Eight +that Telegram no longer documents are removed: + +`ErrAnonymousReactionsDisabled`, `ErrEncryptionDeclineAdminFailed`, +`ErrGroupcallAddParticipantsFailed`, `ErrNeedDocInvalid`, `ErrParticipantCallFailed`, +`ErrPasskeyAuthRestart`, `ErrSavedPeerInvalid`, `ErrVolumeMoveInvalid`. + +## Upgrading + +If your code uses one of the removed constants, compare with the string instead, e.g. +`tgerr.Is(err, "SAVED_PEER_INVALID")`. Nothing else changes for existing code. + +Full list of changes: [gotd/td v0.166.0](https://github.com/gotd/td/releases/tag/v0.166.0). diff --git a/docs/authentication/user.md b/docs/authentication/user.md index 0cc524f..abaf768 100644 --- a/docs/authentication/user.md +++ b/docs/authentication/user.md @@ -8,6 +8,61 @@ Signing in as a user means a phone number, a login code Telegram sends you, and account has it enabled — a 2FA password. gotd wraps this multi-step dance in [`auth.Flow`](https://ref.gotd.dev/pkg/github.com/gotd/td/telegram/auth.html). +## The `telegram/user` package + +For a user account that runs as a service (a userbot, an archiver), the +[`telegram/user`](https://ref.gotd.dev/pkg/github.com/gotd/td/telegram/user.html) package +connects, signs in only if needed, and starts the updates and peer managers in one call. +It is the user counterpart of [`telegram/bot`](./bot.md#the-telegrambot-package): + +```go +import "github.com/gotd/td/telegram/user" + +d := tg.NewUpdateDispatcher() +u, err := user.Start(ctx, appID, appHash, user.Options{ + Dir: "state/me", // session, update state and access hashes + Handler: d, + // Used only if the stored session is not authorized. + Auth: user.Phone(authenticator, auth.SendCodeOptions{}), +}, telegram.WithLifetime(ctx)) +if err != nil { + return err +} +defer func() { _ = u.Close() }() + +return u.Wait(context.Background()) +``` + +* **Session in `Dir`.** An authorized session (`session.json`) is used as is and never + signed in again. A bot session, or another account than `Options.UserID`, fails with + `user.ErrSessionMismatch`. +* **Signing in.** `Options.Auth` signs in an unauthorized session: `user.Phone` (code and + 2FA password through [`auth.Flow`](#the-flow)), `user.QR` (a [login token](./qr-login.md) + shown with `QROptions.Show`, imported on another DC if needed) or `user.AuthFunc` for a + custom sign in. With `Auth` nil, `Start` returns `user.ErrNotAuthorized` without sending + anything, for services that must only run with an existing session. +* **Flood waits** on sign in are waited out, up to `MaxLoginWait` (5 minutes by default), + but only where that can't send a new code: before the code was requested for + `user.Phone`, always for QR, never for `user.AuthFunc`. Otherwise the error is returned. +* **Updates while stopped.** The [updates manager](../helpers/updates-recovery.md) keeps + its state and the access hashes of channels and users in `Dir` (`updates.json`), so + updates sent while the client was down are delivered after the restart, and known + channels need no extra `getDifference`. Updates received during sign in are buffered. +* **Peers.** The peer manager is fed with the entities of every update, with its cache on. + +Register handlers on the dispatcher before `Start`; use `u.Sender()`, `u.API()`, +`u.Peers()`, `u.Self()` and `u.ID()` from them. Use `user.New` and `u.Start(ctx)` to set +things up between creating and starting. Like the client, a `User` has `Stop`, +`Shutdown`, `Close`, `Done` and `Wait` — see [Running without `Run`](../advanced/without-run.md). +Use one `Dir` per account, and don't share it between running processes. + +:::tip +The runnable [`userbot-echo`](https://github.com/gotd/td/tree/main/examples/userbot-echo) +example replies to private messages as a user account. +::: + +The rest of this page shows the sign in on a plain `telegram.Client`. + ## The flow You provide an [`auth.UserAuthenticator`](#the-userauthenticator-interface) — something diff --git a/docs/basics/handling-updates.md b/docs/basics/handling-updates.md index ef83665..4a6e439 100644 --- a/docs/basics/handling-updates.md +++ b/docs/basics/handling-updates.md @@ -134,6 +134,8 @@ Without a callback, start the client with `client.Start` and block in [Running without `Run`](../advanced/without-run.md). The [`telegram/bot`](../authentication/bot.md#the-telegrambot-package) package does this for bots, together with update recovery; the [Echo bot tutorial](./echo-bot.md) uses it. +[`telegram/user`](../authentication/user.md#the-telegramuser-package) does the same for +user accounts. ## Don't miss updates diff --git a/docs/helpers/updates-recovery.md b/docs/helpers/updates-recovery.md index a53b3f5..1d05235 100644 --- a/docs/helpers/updates-recovery.md +++ b/docs/helpers/updates-recovery.md @@ -183,8 +183,9 @@ gaps := updates.New(updates.Config{ }) ``` -The [`telegram/bot`](../authentication/bot.md#the-telegrambot-package) package sets this -up for bots. +The [`telegram/bot`](../authentication/bot.md#the-telegrambot-package) and +[`telegram/user`](../authentication/user.md#the-telegramuser-package) packages set this +up for bots and user accounts. ## Access hashes