github.com/gotd/td v0.99.2
Yes
1. Receive a message from another User
2. Read it
3. Wait until another user edited this message
4. 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
}
Update passed to handler immediately after arriving
Update passed to handler only when another update arrives
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'
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too