package tgbot import ( "fmt" "path/filepath" "strconv" "strings" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "git.vakhrushev.me/av/jellybit/internal/store" "git.vakhrushev.me/av/jellybit/internal/worker" ) // renderCard строит текст и клавиатуру карточки по состоянию задачи. func (b *Bot) renderCard(rd *worker.ReviewData) (string, *tgbotapi.InlineKeyboardMarkup) { id := rd.Download.ID state := rd.Download.State switch state { case store.StateReview, store.StateDeferred: return b.reviewCard(rd) case store.StateRecognizing: return "⏳ Распознаю #" + itoa(id) + "…", b.webOnly(id) case store.StateLinking: return "⏳ Раскладываю #" + itoa(id) + "…", nil case store.StateDone: return b.renderDone(rd), b.webOnly(id) default: text := fmt.Sprintf("Задача #%d — %s.", id, state) if msg := rd.Download.ErrorMsg.String; msg != "" { text += "\n" + msg } return text, b.webOnly(id) } } func (b *Bot) reviewCard(rd *worker.ReviewData) (string, *tgbotapi.InlineKeyboardMarkup) { id := rd.Download.ID var sb strings.Builder fmt.Fprintf(&sb, "🟡 Нужно подтверждение #%d\n", id) if src := contextOrSource(rd); src != "" { fmt.Fprintf(&sb, "Источник: %s\n", shorten(src, 80)) } fmt.Fprintf(&sb, "Похоже на: %s\n", guessLine(rd)) if base := baseLine(rd.Recognition); base != "" { fmt.Fprintf(&sb, "База: %s\n", base) } if reasons := rd.Recognition.ReasonList(); len(reasons) > 0 { fmt.Fprintf(&sb, "Причины: %s\n", strings.Join(reasons, " · ")) } if n := len(rd.Preview); n > 0 { fmt.Fprintf(&sb, "План: %d файлов → %s", n, tailPath(rd.Preview[0].Dst)) } return strings.TrimRight(sb.String(), "\n"), b.reviewKeyboard(rd) } func (b *Bot) reviewKeyboard(rd *worker.ReviewData) *tgbotapi.InlineKeyboardMarkup { id := rd.Download.ID sid := itoa(id) var row1 []tgbotapi.InlineKeyboardButton if len(rd.Preview) > 0 { row1 = append(row1, tgbotapi.NewInlineKeyboardButtonData("✅ Применить", "apply:"+sid)) } row1 = append(row1, tgbotapi.NewInlineKeyboardButtonData("📺↔🎬 Тип", "type:"+sid+":"+oppositeType(string(rd.Plan.Type)))) row2 := tgbotapi.NewInlineKeyboardRow( tgbotapi.NewInlineKeyboardButtonData("🔁 Уточнить", "refine:"+sid), tgbotapi.NewInlineKeyboardButtonData("🕗 Позже", "defer:"+sid), ) var row3 []tgbotapi.InlineKeyboardButton if url := b.reviewURL(id); url != "" { row3 = append(row3, tgbotapi.NewInlineKeyboardButtonURL("🌐 В вебе", url)) } row3 = append(row3, tgbotapi.NewInlineKeyboardButtonData("❌ Отклонить", "reject:"+sid)) kb := tgbotapi.NewInlineKeyboardMarkup(row1, row2, row3) return &kb } // renderDone — короткое сообщение о готовности. func (b *Bot) renderDone(rd *worker.ReviewData) string { title := rd.Plan.Title if title == "" { title = "#" + itoa(rd.Download.ID) } n := len(rd.Preview) if n == 0 { return fmt.Sprintf("✅ Готово: «%s» разложен.", title) } return fmt.Sprintf("✅ Готово: «%s» — разложено файлов: %d.", title, n) } func (b *Bot) webOnly(id int64) *tgbotapi.InlineKeyboardMarkup { url := b.reviewURL(id) if url == "" { return nil } kb := tgbotapi.NewInlineKeyboardMarkup(tgbotapi.NewInlineKeyboardRow( tgbotapi.NewInlineKeyboardButtonURL("🌐 Открыть в вебе", url), )) return &kb } func (b *Bot) reviewURL(id int64) string { if b.webBase == "" { return "" } return b.webBase + "/review/" + itoa(id) } // --- мелкие хелперы --- func guessLine(rd *worker.ReviewData) string { emoji, kind := "🎬", "фильм" if rd.Plan.Type == "series" { emoji, kind = "📺", "сериал" } title := rd.Plan.Title if title == "" { title = "не распознано" } s := fmt.Sprintf("%s %s «%s»", emoji, kind, title) if rd.Plan.Year != 0 { s += fmt.Sprintf(" (%d)", rd.Plan.Year) } return s } func baseLine(rec *store.Recognition) string { if rec == nil || !rec.Provider.Valid || rec.Provider.String == "" || rec.Provider.String == "none" { return "нет матча" } if rec.ProviderID.Valid && rec.ProviderID.String != "" { return rec.Provider.String + " " + rec.ProviderID.String } return rec.Provider.String } func contextOrSource(rd *worker.ReviewData) string { if c := strings.TrimSpace(rd.Download.Context); c != "" { return firstLine(c) } return rd.Download.SourceRef } func oppositeType(t string) string { if t == "series" { return "movie" } return "series" } func firstLine(s string) string { if i := strings.IndexByte(s, '\n'); i >= 0 { return s[:i] } return s } func tailPath(p string) string { dir, file := filepath.Split(p) parent := filepath.Base(strings.TrimRight(dir, "/")) if parent == "." || parent == "/" || parent == "" { return file } return parent + "/" + file } func shorten(s string, n int) string { r := []rune(s) if len(r) <= n { return s } return string(r[:n]) + "…" } func itoa(n int64) string { return strconv.FormatInt(n, 10) }