mirror of
https://github.com/BEARlogin/max-telegram-bridge-bot.git
synced 2026-04-28 03:39:46 +00:00
Surface TG→MAX upload errors in chat (not only in log)
Requested by user Victor via bridged group.
- New helper uploadErrHint maps common upload failures to a short
Russian hint ("файл слишком большой", "Telegram не нашёл файл",
"MAX CDN не успел обработать файл", etc) and falls back to a truncated
raw error for the unknown case.
- Every TG→MAX upload failure path now tells the user what happened
(photo, gif, sticker, video, video note, document, voice, audio,
edit-with-media, media group).
- Edit path previously failed silently and media group dropped the
error on the floor — both now notify.
- Media group notifies once with the first failure when no photos
survived upload (avoids N messages per album).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
02e2ea4f88
commit
825bfee473
3 changed files with 60 additions and 11 deletions
27
bridge.go
27
bridge.go
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/hex"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -203,6 +204,32 @@ func (b *Bridge) isSelfTgBot(from *UserInfo) bool {
|
|||
return from != nil && from.IsBot && from.UserName == b.tg.BotUsername()
|
||||
}
|
||||
|
||||
// uploadErrHint превращает техническую ошибку загрузки в короткий русский текст
|
||||
// для отправки в чат. Для неизвестных ошибок отдаёт усечённое сырое сообщение.
|
||||
func uploadErrHint(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
s := err.Error()
|
||||
switch {
|
||||
case strings.Contains(s, "file is too big"):
|
||||
return "файл слишком большой для Telegram Bot API (лимит без локального сервера — 20 МБ)"
|
||||
case strings.Contains(s, "file is not found") || strings.Contains(s, "FILE_REFERENCE_EXPIRED"):
|
||||
return "Telegram не нашёл файл (возможно, он удалён или ссылка протухла)"
|
||||
case strings.Contains(s, "wrong file id") || strings.Contains(s, "Wrong file"):
|
||||
return "некорректный file_id"
|
||||
case strings.Contains(s, "attachment.not.ready"):
|
||||
return "MAX CDN не успел обработать файл — попробуйте ещё раз"
|
||||
case strings.Contains(s, "chat.denied"):
|
||||
return "MAX отклонил отправку: бот не добавлен в чат или не имеет прав"
|
||||
}
|
||||
const maxLen = 180
|
||||
if len(s) > maxLen {
|
||||
s = s[:maxLen] + "…"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *Bridge) tgWebhookPath() string {
|
||||
return "/tg-webhook-" + b.whSecret
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,12 +130,14 @@ func (b *Bridge) flushMediaGroup(ctx context.Context, groupID string) {
|
|||
|
||||
// Загружаем и добавляем все фото
|
||||
photosSent := 0
|
||||
var photoFailErr error
|
||||
for _, it := range items {
|
||||
if len(it.photoSizes) > 0 {
|
||||
photo := it.photoSizes[len(it.photoSizes)-1]
|
||||
fileURL, err := b.tgFileURL(ctx, photo.FileID)
|
||||
if err != nil {
|
||||
slog.Error("media group: tgFileURL failed", "err", err)
|
||||
photoFailErr = err
|
||||
continue
|
||||
}
|
||||
// Если custom TG API — MAX не может скачать по URL, скачиваем сами
|
||||
|
|
@ -143,6 +145,7 @@ func (b *Bridge) flushMediaGroup(ctx context.Context, groupID string) {
|
|||
uploaded, err := b.uploadTgPhotoToMax(ctx, photo.FileID)
|
||||
if err != nil {
|
||||
slog.Error("media group: photo upload failed", "err", err)
|
||||
photoFailErr = err
|
||||
continue
|
||||
}
|
||||
m.AddPhoto(uploaded)
|
||||
|
|
@ -150,6 +153,7 @@ func (b *Bridge) flushMediaGroup(ctx context.Context, groupID string) {
|
|||
uploaded, err := b.maxApi.Uploads.UploadPhotoFromUrl(ctx, fileURL)
|
||||
if err != nil {
|
||||
slog.Error("media group: photo upload failed", "err", err)
|
||||
photoFailErr = err
|
||||
continue
|
||||
}
|
||||
m.AddPhoto(uploaded)
|
||||
|
|
@ -157,6 +161,10 @@ func (b *Bridge) flushMediaGroup(ctx context.Context, groupID string) {
|
|||
photosSent++
|
||||
}
|
||||
}
|
||||
if photoFailErr != nil && photosSent == 0 {
|
||||
b.tg.SendMessage(ctx, items[0].msg.Chat.ID,
|
||||
fmt.Sprintf("Не удалось отправить альбом в MAX: %s", uploadErrHint(photoFailErr)), nil)
|
||||
}
|
||||
|
||||
// Загружаем видео из альбома через direct API
|
||||
videosSent := 0
|
||||
|
|
|
|||
36
telegram.go
36
telegram.go
|
|
@ -620,7 +620,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
m.AddPhoto(uploaded)
|
||||
} else {
|
||||
slog.Error("TG→MAX photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить фото в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить фото в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if fileURL, err := b.tgFileURL(ctx, photo.FileID); err == nil {
|
||||
|
|
@ -628,9 +628,13 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
m.AddPhoto(uploaded)
|
||||
} else {
|
||||
slog.Error("TG→MAX photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить фото в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить фото в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
slog.Error("TG→MAX photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить фото в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
if msg.ReplyToMessage != nil {
|
||||
if maxReplyID, ok := b.repo.LookupMaxMsgID(msg.Chat.ID, msg.ReplyToMessage.MessageID); ok {
|
||||
|
|
@ -665,7 +669,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
mediaAttType = "video"
|
||||
} else {
|
||||
slog.Error("TG→MAX gif upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить GIF \"%s\" в MAX.", name), nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить GIF \"%s\" в MAX: %s", name, uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if msg.Sticker != nil {
|
||||
|
|
@ -679,7 +683,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
mediaAttType = "video"
|
||||
} else {
|
||||
slog.Error("TG→MAX sticker upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить стикер в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить стикер в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
|
@ -697,7 +701,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
result, err := b.maxApi.Messages.SendWithResult(ctx, m)
|
||||
if err != nil {
|
||||
slog.Error("TG→MAX sticker send failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить стикер в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить стикер в MAX: %s", uploadErrHint(err)), nil)
|
||||
} else {
|
||||
slog.Info("TG→MAX sent", "mid", result.Body.Mid)
|
||||
b.repo.SaveMsg(msg.Chat.ID, msg.MessageID, maxChatID, result.Body.Mid, msg.MessageThreadID)
|
||||
|
|
@ -705,9 +709,13 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
return
|
||||
} else {
|
||||
slog.Error("TG→MAX sticker photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить стикер в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить стикер в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
slog.Error("TG→MAX sticker getFileURL failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить стикер в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if msg.Video != nil {
|
||||
|
|
@ -723,7 +731,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
mediaAttType = "video"
|
||||
} else {
|
||||
slog.Error("TG→MAX video upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить видео \"%s\" в MAX.", name), nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить видео \"%s\" в MAX: %s", name, uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if msg.VideoNote != nil {
|
||||
|
|
@ -735,7 +743,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
mediaAttType = "video"
|
||||
} else {
|
||||
slog.Error("TG→MAX video note upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить кружок в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить кружок в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if msg.Document != nil {
|
||||
|
|
@ -777,7 +785,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
}
|
||||
slog.Error("TG→MAX file upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID,
|
||||
fmt.Sprintf("Не удалось отправить файл \"%s\" в MAX.", name), nil)
|
||||
fmt.Sprintf("Не удалось отправить файл \"%s\" в MAX: %s", name, uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if msg.Voice != nil {
|
||||
|
|
@ -795,7 +803,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
return
|
||||
}
|
||||
slog.Error("TG→MAX voice upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, "Не удалось отправить голосовое сообщение в MAX.", nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить голосовое сообщение в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if msg.Audio != nil {
|
||||
|
|
@ -826,7 +834,7 @@ func (b *Bridge) forwardTgToMax(ctx context.Context, msg *TGMessage, maxChatID i
|
|||
return
|
||||
}
|
||||
slog.Error("TG→MAX audio upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить аудио \"%s\" в MAX.", name), nil)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось отправить аудио \"%s\" в MAX: %s", name, uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -957,6 +965,7 @@ func (b *Bridge) editTgMediaInMax(ctx context.Context, msg *TGMessage, maxChatID
|
|||
m.AddPhoto(uploaded)
|
||||
} else {
|
||||
slog.Error("TG→MAX edit photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось обновить фото в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else if fileURL, err := b.tgFileURL(ctx, photo.FileID); err == nil {
|
||||
|
|
@ -964,8 +973,13 @@ func (b *Bridge) editTgMediaInMax(ctx context.Context, msg *TGMessage, maxChatID
|
|||
m.AddPhoto(uploaded)
|
||||
} else {
|
||||
slog.Error("TG→MAX edit photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось обновить фото в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
slog.Error("TG→MAX edit photo upload failed", "err", err)
|
||||
b.tg.SendMessage(ctx, msg.Chat.ID, fmt.Sprintf("Не удалось обновить фото в MAX: %s", uploadErrHint(err)), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue