X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/1e2df788bbb17653a6df44acd41da2edb109d403..522d7fd117285e4e4ce3ea5ec9b5a1722521b681:/telegram.go?ds=sidebyside diff --git a/telegram.go b/telegram.go index 9d7600f..2a9345a 100644 --- a/telegram.go +++ b/telegram.go @@ -73,8 +73,10 @@ func loadTelegramConfig(dir string) (TelegramConfig, bool, error) { } var config TelegramConfig + decoder := json.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() + err = decoder.Decode(&config) if err == nil && !errors.Is(decoder.Decode(&struct{}{}), io.EOF) { err = errors.New("data after the JSON object") @@ -180,6 +182,7 @@ func (b *TelegramBot) Notify(event Event) { } b.mutex.Lock() + b.queue = append(b.queue, event) b.mutex.Unlock() @@ -197,7 +200,9 @@ func (b *TelegramBot) Run(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) defer cancel() var sender sync.WaitGroup + sender.Add(1) + go func() { defer sender.Done() @@ -205,6 +210,7 @@ func (b *TelegramBot) Run(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) }() err := b.poll(ctx, l, whatsapp) + cancel() sender.Wait() @@ -237,6 +243,7 @@ func (b *TelegramBot) send(ctx context.Context) { switch event.Kind { case "busy": busy = true + b.typing(ctx) ticker.Reset(telegramTypingInterval) case "idle": @@ -262,8 +269,8 @@ func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) outage := &TelegramOutage{ logger: b.logger, - loss: "cannot get messages from Telegram", - back: "messages from Telegram come again", + loss: "receive failed", + back: "receive back", } wait := b.minWait @@ -281,6 +288,7 @@ func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) } outage.End() + wait = b.minWait for _, update := range updates { if ctx.Err() != nil { @@ -371,7 +379,8 @@ func (b *TelegramBot) pause(ctx context.Context, outage *TelegramOutage, err err func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp, update TelegramUpdate) error { message := update.Message if message == nil || message.Chat.Type != "private" || message.Chat.ID != b.owner || message.From == nil || message.From.ID != b.owner { - b.logger.Print("a Telegram message is ignored, because it is not from the owner in the private chat") + b.logger.Print("message ignored: not from the owner") + return nil } @@ -429,15 +438,18 @@ func (b *TelegramBot) deliver(ctx context.Context, part TelegramPart) { } var answer *TelegramError + if len(part.entities) > 0 && errors.As(err, &answer) && answer.Status == http.StatusBadRequest { - b.logger.Printf("a reply goes as plain text, because Telegram refused its formatting: %v", err) + b.logger.Printf("reply sent as plain text: %v", err) + part.entities = nil continue } if !retry { - b.logger.Printf("a reply to Telegram is lost: %v", err) + b.logger.Printf("reply lost: %v", err) + return } @@ -467,7 +479,8 @@ func (b *TelegramBot) deliverRich(ctx context.Context, markdown string) { } if telegramRejected(err) { - b.logger.Printf("a reply goes with simpler formatting, because Telegram refused its full formatting: %v", err) + b.logger.Printf("reply sent with simple formatting: %v", err) + for _, part := range telegramReplyParts(telegramUnescapeTags(markdown)) { b.deliver(ctx, part) } @@ -476,7 +489,8 @@ func (b *TelegramBot) deliverRich(ctx context.Context, markdown string) { } if !retry { - b.logger.Printf("a reply to Telegram is lost: %v", err) + b.logger.Printf("reply lost: %v", err) + return } @@ -488,8 +502,8 @@ func (b *TelegramBot) deliverRich(ctx context.Context, markdown string) { func (b *TelegramBot) sendOutage() *TelegramOutage { return &TelegramOutage{ logger: b.logger, - loss: "cannot send replies to Telegram", - back: "replies go to Telegram again", + loss: "send failed", + back: "send back", } } @@ -519,7 +533,7 @@ func (o *TelegramOutage) Fail(err error, delay time.Duration) { } o.start = time.Now() - o.logger.Printf("%s: %v (next try in %s)", o.loss, err, durationText(delay)) + o.logger.Printf("%s: %v (retry in %s)", o.loss, err, durationText(delay)) } // End logs the end of the outage that runs, with its length. @@ -528,7 +542,8 @@ func (o *TelegramOutage) End() { return } - o.logger.Printf("%s, after %s", o.back, durationText(time.Since(o.start))) + o.logger.Printf("%s after %s", o.back, durationText(time.Since(o.start))) + o.start = time.Time{} } @@ -557,6 +572,7 @@ func telegramLoadOffset(path string) (int64, error) { } var file TelegramOffsetFile + err = json.Unmarshal(data, &file) if err != nil { return 0, fmt.Errorf("%s: %w", path, err) @@ -588,6 +604,7 @@ func telegramSaveOffset(path string, offset int64) error { // never cuts a character. func telegramSplitMessage(text string, limit int) []string { var parts []string + for { end := telegramPrefixEnd(text, limit) if end == len(text) { @@ -637,6 +654,7 @@ func telegramText(event Event) string { // not cut an entity of telegramEscapeTags. It never cuts a character. func telegramSplitRich(markdown string, limit int) []string { var parts []string + for len(markdown) > limit { cut, fence, opening := telegramRichCut(markdown, limit) if fence == "" { @@ -692,7 +710,9 @@ func telegramPrefixEnd(text string, limit int) int { // so each cut makes the rest shorter. func telegramRichCut(markdown string, limit int) (int, string, string) { blank, line, code := 0, 0, 0 + var fence, opening, inFence, inOpening string + for start := 0; start < limit; { end := strings.IndexByte(markdown[start:], '\n') if end < 0 || start+end+1 > limit { @@ -751,10 +771,13 @@ func telegramRichCut(markdown string, limit int) (int, string, string) { // An entity that crosses a cut becomes one entity in each part. func telegramSplitEntities(text string, entities []TelegramEntity, limit int) []TelegramPart { var parts []TelegramPart + start := 0 for _, part := range telegramSplitMessage(text, limit) { end := start + telegramUTF16Units(part) + var kept []TelegramEntity + for _, entity := range entities { from := max(entity.Offset, start) to := min(entity.Offset+entity.Length, end)