94 lines
2.1 KiB
Go
94 lines
2.1 KiB
Go
package telegram
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
)
|
|
|
|
// ReadMessages runs a blocking getUpdates long-poll loop, emitting each
|
|
// normalized inbound message to emit until ctx is canceled or the adapter is
|
|
// disconnected. When running in webhook mode it does not poll; it simply waits
|
|
// for ctx to be canceled, since messages are delivered through the message
|
|
// handler via ProcessUpdate instead.
|
|
//
|
|
// The update offset is advanced only after a batch has been fully processed, so
|
|
// a canceled read does not lose the batch; acknowledgment is batched so the
|
|
// getUpdates call does not re-deliver the same update_id flood.
|
|
func (a *Adapter) ReadMessages(ctx context.Context, emit func(adapter.UnifiedMessage)) {
|
|
if emit == nil {
|
|
return
|
|
}
|
|
if a.isWebhookMode() {
|
|
<-ctx.Done()
|
|
return
|
|
}
|
|
if err := a.requireConnected(); err != nil {
|
|
a.log.Warn("read not started", "err", err)
|
|
return
|
|
}
|
|
|
|
pollCtx, cancel := context.WithCancel(ctx)
|
|
a.mu.Lock()
|
|
a.runID++
|
|
a.pollCancel = cancel
|
|
runID := a.runID
|
|
a.mu.Unlock()
|
|
defer func() {
|
|
a.mu.Lock()
|
|
if a.runID == runID {
|
|
a.pollCancel = nil
|
|
}
|
|
a.mu.Unlock()
|
|
cancel()
|
|
}()
|
|
|
|
for {
|
|
if err := pollCtx.Err(); err != nil {
|
|
return
|
|
}
|
|
updates, err := a.getUpdates(pollCtx)
|
|
if err != nil {
|
|
a.log.Warn("long poll failed", "err", err)
|
|
if !sleepWait(pollCtx, pollInterval) {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
if len(updates) == 0 {
|
|
if !sleepWait(pollCtx, pollInterval) {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
for _, u := range updates {
|
|
msg, ok := a.toUnifiedMessage(u)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if err := msg.Validate(); err != nil {
|
|
a.log.Warn("skip invalid message", "err", err)
|
|
continue
|
|
}
|
|
emit(msg)
|
|
}
|
|
a.mu.Lock()
|
|
a.offset = updates[len(updates)-1].UpdateID + 1
|
|
a.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// sleepWait blocks for d or until ctx is canceled, returning false when the
|
|
// context fires first.
|
|
func sleepWait(ctx context.Context, d time.Duration) bool {
|
|
t := time.NewTimer(d)
|
|
defer t.Stop()
|
|
select {
|
|
case <-ctx.Done():
|
|
return false
|
|
case <-t.C:
|
|
return true
|
|
}
|
|
}
|