Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 11:16
· 99 commits to main since this release

Added

  • tg sql --account <id|@username|label> queries that account's cache;
    without it tg sql reads the active account's. tg sql print-schema needs
    no account and creates no file.
  • CACHE_UNAVAILABLE (exit 1), with details: { reason, path }, when an
    account's cache can't be opened yet: an older tg still holds the shared
    cache (shared_cache_in_use), the move failed (move_failed), or the file
    belongs to another account (owner_mismatch, from the new cache_meta
    stamp).
  • tg status has a storage section (legacy_caches: shared caches set
    aside; migration: a move that waits), and every item in sync.jobs,
    progress.sample_chats and rate_limits.active_flood_waits has
    account_id. With several accounts, pretty output tags jobs with
    acct:<id> and the retry commands add --account <id>.
  • tg status gives each entry in accounts.accounts its own cache,
    connection, sync, progress and rate_limits, and pretty output
    prints them on a line under each account. tg status --account <selector>
    reports only that account and sets the new top-level account_id (null
    for all); pretty output names that account under the title. tg daemon status adds accounts[] with each account's
    connection, last error, messages synced and cached, and chat counts, and
    the same storage as tg status, so a shared cache that an older daemon
    still holds explains why every account reads empty.

Changed

  • Breaking: each account has its own cache, accounts/<id>/cache.db:
    cached chats, users and messages, sync state, jobs, rate limits and
    api_activity. data.db keeps only the accounts. Stop the daemon before
    upgrading. The first command that needs a cache moves the shared
    cache.db of 0.3 and older, and so do tg auth login, login-qr,
    logout and tg accounts remove before they change the accounts: with
    one account it becomes that account's cache and everything carries over;
    with several (or none) its rows can't be split by account, so it is kept
    as shared-cache.db and each account syncs again from an empty cache; an
    empty one is deleted. While an older tg still has the file open, a single
    account's commands fail with CACHE_UNAVAILABLE until it exits. tg auth login, login-qr, logout and tg accounts remove fail the same way
    with any number of accounts and change none, so a second account can't
    force the first one's cache aside, and the account left after a removal
    (or the first one added) is never handed rows that other accounts wrote.
    A file that can't be moved yet (move_failed: a read-only file, a full
    disk) waits the same way; only a file that is not a database is set aside
    for a single account. A login after tg auth logout gives
    the same Telegram user a new account ID and an empty cache; the old
    accounts/<id>/ stays, and renamed to the new ID it is taken back, as the
    owner stamp matches the user. Going back to 0.3 after the move is not
    supported. Scripts that open cache.db directly must open
    accounts/<id>/cache.db or use tg sql --account. See "Upgrading to
    per-account caches" in docs/configuration.md.
  • Breaking: a command that reads a cache works on one account:
    --account, else the active account, else the only one. With no account
    it fails with AUTH_REQUIRED (exit 2), so tg chats get --id <ID> without
    an account is no longer the INVALID_ARGS cache miss of 0.3.0. With
    several accounts and none active it fails with NO_ACTIVE_ACCOUNT
    (exit 1) and lists them in details.accounts.
  • The daemon runs each account with its own cache, scheduler and sync
    worker, taking turns between accounts; each account waits 3 seconds
    between its own jobs. An account whose cache doesn't open is logged and
    left out; one whose scheduler fails its startup pass, or whose cache fails
    while its next job is picked, is logged and keeps syncing, and never stops
    the accounts after it. daemon_status rows are per account; tg status and
    tg daemon status sum them and count only rows the live daemon wrote.

Fixed

  • tg chats list, chats search, contacts list, messages search and
    tg sql mixed every account's data, and message IDs and basic group IDs
    that two accounts share collided in one cache (#17).
  • The daemon ran a sync job on the first connected account's client, with
    another account's access hashes; a job now runs only on its own account's
    client, and a disconnected account's jobs wait for it.
  • tg send resolved recipients with access hashes that another account
    cached.
  • A delete without chat context could mark another account's message with
    the same ID as deleted (the cross-account part of #7).
  • A FLOOD_WAIT that one account recorded (one longer than mtcute waits out:
    60 seconds in the CLI, 5 minutes in the daemon) blocked API calls of every
    account. A shorter one that mtcute sleeps through inside a daemon job
    still holds the job loop for every account (#60).
  • tg chats search, messages search and chats sync ignored --account.
  • Processes that opened a new cache or data.db at the same moment, such as
    the daemon and a command right after the first login, could fail with
    "database is locked": SQLite fails all but one switch to WAL at once,
    without waiting. They now try again for up to 5 seconds. The cache moved
    from a shared one is also in this state on its first open.
  • Processes that opened a new cache, or one with an outdated search index,
    at the same moment built the index together: one could drop the
    message_search table or its triggers while another was filling it, and
    fail with "no such table", "vtable constructor failed" or "database is
    locked". One process now builds it under the write lock while the others
    wait.
  • After a reconnect, a complete dialog pass now marks the account's chats it
    didn't list (left, deleted or migrated) caught up, as the startup pass
    does. Before, such a chat whose newest message an earlier failed pass had
    left unknown still cost a messages.getHistory call. A chat turned on with
    tg chats sync still gets its checking page.
  • The daemon could skip a delete in a private chat or basic group, or mark the
    wrong message deleted. Telegram names no chat for such a delete, and the
    daemon matched the ID across every cached chat of the account. When a
    channel or supergroup had a message with the same ID, the deleted message
    stayed unmarked; when the deleted message wasn't cached, that channel post
    was marked deleted instead and dropped out of tg messages list and
    messages search. The daemon now looks only among the account's private
    chats and basic groups, which share one ID sequence, through a new index on
    messages_cache.message_id instead of reading every cached message. An ID
    that two of them still have is left as it is and logged as a warning (#7).
  • An account whose first connect failed (login, getMe or the updates loop)
    kept its Telegram client, its connection and its session file open until the
    daemon stopped. Stopping the daemon (tg daemon stop, SIGTERM or SIGINT)
    also exited without closing any client's connection. The daemon now closes
    each client once, right after a failed connect and on shutdown (#34).
  • History pages stored no sender for an incoming private message, a channel
    post or a message in Saved Messages, because Telegram leaves the sender out
    of those, while the same message cached live had one. fromId and
    sender.id were null for them in tg messages search, list and get,
    sender had no name, and --sender missed them. A daemon catch-up or a
    --fresh page also erased the sender that a live update had stored. They
    now get the sender a live message gets: the other user in a private chat,
    your own user in Saved Messages and the channel's marked ID (-100…) for a
    channel post. The first run of the new version fills in the messages cached
    before. Stop the daemon before upgrading and start it again after, so the
    old daemon neither loses writes while that one-time fill holds the database
    nor stores new messages without a sender; any it caches after the upgrade
    stay without a sender until they are fetched again (tg messages list --fresh) (#44).
  • A Telegram community (a collection of linked chats) would have been cached
    as a basic group without its access hash, so tg chats list --type group
    would have listed it, and syncing it with tg chats sync would have reached
    Telegram with access hash 0. The cache now stores a community as type
    community with its access hash: type in tg chats output, chat_type
    in tg status, and chats_cache.type / chat_sync_state.chat_type in tg sql say community, tg chats list --type community filters on it, and
    like a channel it is off by default. tg doesn't cache joined communities
    yet, because mtcute's dialog list skips them (#58), so --type community
    finds none so far (#45).
  • tg status didn't say when the running daemon was a different version from
    tg, so after an upgrade a daemon left running kept writing the cache in
    the old format and nothing said to restart it. The daemon now records its
    version when it starts. tg status reports it as daemon.version and sets
    daemon.version_mismatch when it differs from this tg (cli_version).
    --format pretty shows a Version row and how to restart the daemon, or,
    when the daemon is the newer one, says to use the newer tg instead. A
    daemon started by 0.3.0 or older records no version and counts as older
    (#46).
  • tg send --to, tg messages list and get --chat took a positive number
    the cache doesn't know as basic group N, so a typo or an uncached user ID
    could send to that group or fail with PEER_ID_INVALID. Such a number is
    now user N and never a group: basic group N is -N. Without a cached access
    hash Telegram may still refuse the user; use its @username or +phone
    once and the ID works after that. A number of 10 or more digits is still
    read only as a phone number (#47).
  • A daemon stop closed the session files under running clients, so update
    state and peers that arrived between the signal and the shutdown were lost,
    the updates loop logged errors until the process exited, and "Received
    SIGTERM" appeared twice. The daemon now keeps the session files open until
    it closes its clients, so those writes are saved and the stop logs no
    updates-loop error or failed save. A stop during the dialog pass ends it at
    the next dialog; a FLOOD_WAIT the daemon is already waiting out still holds
    the stop until it ends (#64) (#56).
  • tg accounts switch lasted one command when config.json set
    activeAccount: the next command made that account active again and read
    its cache. The switch now writes the new ID to activeAccount when the key
    is set; without the key config.json is not created (#61).
  • The startup handling of config.json had four more gaps: -q skipped
    applying activeAccount and the file check that every other format runs, so
    a quiet command could use another account; -f <format> before tg config
    or tg skill ran the check, so tg config set couldn't repair an invalid
    file; a file that couldn't be read printed a stack trace instead of a
    GENERAL_ERROR with the path in details.path; and an invalid file stopped
    tg daemon start, though the daemon reads none of its keys (#61).