gotd / td

Telegram client, in Go. (MTProto API)
MIT License
1.46k stars 131 forks source link

Bug: update postponed and handled only with next update #1382

Open sadfun opened 4 months ago

sadfun commented 4 months ago

What version of gotd are you using?

github.com/gotd/td v0.99.2

Can this issue be reproduced with the latest version?

Yes

Steps to reproduce

**1. Receive a message from another User

  1. Read it
  2. Wait until another user edited this message
  3. You will receive update about message edit only when another pts-changing update arrive (for example, new message, but not online status)**

As I understand from logs, it happens because of some attempt of the library to fix update sequence:

{"level":"debug","ts":1715867034.0408983,"logger":"pts","msg":"Gap detected","gap":[{"from":4416,"to":4417}]}
{"level":"debug","ts":1715867034.0409162,"logger":"pts","msg":"Out of gap range, postponed","upd_from":4418,"upd_to":4418,"gaps":[{"from":4416,"to":4417}]}

Here is the minimal example to reproduce it:

package main

import (
    "context"
    "fmt"
    boltstor "github.com/gotd/contrib/bbolt"
    "github.com/gotd/contrib/storage"
    "github.com/gotd/td/telegram/query/dialogs"
    bolt "go.etcd.io/bbolt"
    "os"
    "os/signal"

    "github.com/go-faster/errors"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"

    "github.com/gotd/td/examples"
    "github.com/gotd/td/telegram"
    "github.com/gotd/td/telegram/auth"
    "github.com/gotd/td/telegram/updates"
    updhook "github.com/gotd/td/telegram/updates/hook"
    "github.com/gotd/td/tg"
)

func main() {
    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
    defer cancel()
    if err := run(ctx); err != nil {
        panic(err)
    }
}

func run(ctx context.Context) error {
    log, _ := zap.NewDevelopment(zap.IncreaseLevel(zapcore.InfoLevel), zap.AddStacktrace(zapcore.FatalLevel))
    defer func() { _ = log.Sync() }()

    peerDB, err := bolt.Open("peers.db", 0600, nil)
    if err != nil {
        return fmt.Errorf("failed to open peer db: %w", err)
    }

    d := tg.NewUpdateDispatcher()
    gaps := updates.New(updates.Config{
        Handler: d,
        Logger:  log.Named("gaps"),
    })
    peerStorage := boltstor.NewPeerStorage(peerDB, []byte(fmt.Sprintf("peers")))
    hook := storage.UpdateHook(gaps, peerStorage)

    // Authentication flow handles authentication process, like prompting for code and 2FA password.
    flow := auth.NewFlow(examples.Terminal{}, auth.SendCodeOptions{})

    // Initializing client from environment.
    // Available environment variables:
    //  APP_ID:         app_id of Telegram app.
    //  APP_HASH:       app_hash of Telegram app.
    //  SESSION_FILE:   path to session file
    //  SESSION_DIR:    path to session directory, if SESSION_FILE is not set
    client, err := telegram.ClientFromEnvironment(telegram.Options{
        Logger:        log,
        UpdateHandler: gaps,
        Middlewares: []telegram.Middleware{
            updhook.UpdateHook(hook.Handle),
        },
    })
    if err != nil {
        return err
    }

    //
    d.OnNewMessage(func(ctx context.Context, e tg.Entities, update *tg.UpdateNewMessage) error {
        log.Info("New", zap.Any("message", update.Message))

        msg, ok := update.Message.(*tg.Message)
        if !ok {
            return nil
        }

        peerUser, ok := msg.PeerID.(*tg.PeerUser)
        if !ok {
            return nil
        }

        ah := findAccessHash(peerUser.UserID, e, client.API(), peerStorage)

        _, err = client.API().MessagesReadHistory(ctx, &tg.MessagesReadHistoryRequest{
            Peer:  &tg.InputPeerUser{UserID: peerUser.GetUserID(), AccessHash: ah},
            MaxID: msg.ID,
        })

        return err
    })

    // Edit message handler.
    d.OnEditMessage(func(ctx context.Context, e tg.Entities, update *tg.UpdateEditMessage) error {
        log.Info("Edit", zap.Any("message", update.Message))
        return nil
    })

    return client.Run(ctx, func(ctx context.Context) error {
        // Perform auth if no session is available.
        if err := client.Auth().IfNecessary(ctx, flow); err != nil {
            return errors.Wrap(err, "auth")
        }

        // Fetch user info.
        user, err := client.Self(ctx)
        if err != nil {
            return errors.Wrap(err, "call self")
        }

        return gaps.Run(ctx, client.API(), user.ID, updates.AuthOptions{
            OnStart: func(ctx context.Context) {
                log.Info("Gaps started")
            },
        })
    })
}

func findAccessHash(userID int64, e tg.Entities, client *tg.Client, peerStorage storage.PeerStorage) (ah int64) {
    if e.Users[userID] != nil {
        return e.Users[userID].AccessHash
    }

    // Find access hash for user.
    peer, err := peerStorage.Find(context.Background(), storage.PeerKey{
        Kind: dialogs.User,
        ID:   userID,
    })
    if err == nil && peer.User != nil && peer.User.AccessHash != 0 {
        return peer.User.AccessHash
    }

    collector := storage.CollectPeers(peerStorage)

    err = collector.Dialogs(context.Background(), dialogs.NewIterator(dialogs.NewQueryBuilder(client).GetDialogs(), 10))
    if err != nil {
        return
    }

    peer, err = peerStorage.Find(context.Background(), storage.PeerKey{
        Kind: dialogs.User,
        ID:   userID,
    })

    if err == nil && peer.User != nil && peer.User.AccessHash != 0 {
        return peer.User.AccessHash
    }

    return
}

What did you expect to see?

Update passed to handler immediately after arriving

What did you see instead?

Update passed to handler only when another update arrives

What Go version and environment are you using?

go version go1.21.1 darwin/arm64

go env
GO111MODULE='on'
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/sadfun/Library/Caches/go-build'
GOENV='/Users/sadfun/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/sadfun/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/sadfun/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.21.1'
GCCGO='gccgo'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/sadfun/Developer/octopus/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/4r/2ndv351j4yx4cw17y8dbgr6r0000gn/T/go-build1564177886=/tmp/go-build -gno-record-gcc-switches -fno-common'
Splatjov commented 4 months ago

I have the same issue, please fix it asap!!

NikBuka commented 4 months ago

@ernado we have this bug too, fix this please

NikBuka commented 4 months ago

@ernado heeelp, please (

NikBuka commented 2 months ago

@ernado heeelp, please (

NikBuka commented 2 weeks ago

@ernado heeelp, please (