feat: add thumbnail preview, audio-only detection, /download command, tests, and bugfixes

- Add thumbnail preview with metadata (title, uploader, duration) before download
- Auto-detect audio-only sites (Spotify, SoundCloud, etc.) and skip type selection
- Add /download command for feature parity with UI button
- Fix yt-dlp defer bug: StatusFailed was overwritten to StatusCompleted
- Fix handleURLInput using editText with msgID=0 (now uses sendWithKB)
- Fix filename detection: use job ID prefix for reliable file finding
- Fix double answerCb between handleBackStep and handleDownloadPrompt
- Fix runDownload unused msgID parameter
- Fix context for download goroutine (use context.Background)
- Remove unused startWebhook h parameter
- Remove dead buildToggleKeyboard function
- Add formatBytes, formatDuration, formatLabelForDisplay tests
- Add IsAudioOnlyContent, MediaType.String tests
- Add repo integration tests (users, preferences, quotas, history, delete)
- Add cmd/bot tests (parseAllowedUsers, env helpers)
- Add edge case tests for progress parser
- Add zero/negative safety guard in formatDuration
- Add .gitea, TODO.md, opencode.json to gitignore
This commit is contained in:
2026-06-25 15:07:31 +03:30
parent 05fcdf7458
commit cd27b4d28d
15 changed files with 631 additions and 64 deletions

View File

@@ -74,7 +74,7 @@ func main() {
defer cancel()
if url := os.Getenv("BOT_WEBHOOK_URL"); url != "" {
startWebhook(ctx, b, h, url)
startWebhook(ctx, b, url)
} else {
if _, err := b.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{}); err != nil {
slog.Warn("failed to delete webhook", "error", err)

55
cmd/bot/main_test.go Normal file
View File

@@ -0,0 +1,55 @@
package main
import (
"testing"
)
func TestParseAllowedUsers(t *testing.T) {
tests := []struct {
input string
want map[int64]bool
}{
{"", map[int64]bool{}},
{"12345", map[int64]bool{12345: true}},
{"12345,67890", map[int64]bool{12345: true, 67890: true}},
{" 12345 , 67890 ", map[int64]bool{12345: true, 67890: true}},
{"12345,", map[int64]bool{12345: true}},
{",12345", map[int64]bool{12345: true}},
{"invalid", map[int64]bool{}},
}
for _, tt := range tests {
got := parseAllowedUsers(tt.input)
if len(got) != len(tt.want) {
t.Errorf("parseAllowedUsers(%q) = %v, want %v", tt.input, got, tt.want)
continue
}
for k, v := range tt.want {
if got[k] != v {
t.Errorf("parseAllowedUsers(%q)[%d] = %v, want %v", tt.input, k, got[k], v)
}
}
}
}
func TestGetEnvDefault(t *testing.T) {
t.Setenv("TEST_EXISTS", "hello")
if got := getEnvDefault("TEST_EXISTS", "fallback"); got != "hello" {
t.Errorf("getEnvDefault() = %q, want %q", got, "hello")
}
if got := getEnvDefault("TEST_MISSING", "fallback"); got != "fallback" {
t.Errorf("getEnvDefault() = %q, want %q", got, "fallback")
}
}
func TestGetEnvIntDefault(t *testing.T) {
t.Setenv("TEST_INT", "42")
if got := getEnvIntDefault("TEST_INT", 0); got != 42 {
t.Errorf("getEnvIntDefault() = %d, want %d", got, 42)
}
if got := getEnvIntDefault("TEST_MISSING", 99); got != 99 {
t.Errorf("getEnvIntDefault() = %d, want %d", got, 99)
}
if got := getEnvIntDefault("TEST_INT", 0); got != 42 {
t.Errorf("getEnvIntDefault() cached = %d, want %d", got, 42)
}
}

View File

@@ -7,11 +7,9 @@ import (
"os"
tgbot "github.com/go-telegram/bot"
"uptodownBot/internal/handler"
)
func startWebhook(ctx context.Context, b *tgbot.Bot, h *handler.Handler, webhookURL string) {
func startWebhook(ctx context.Context, b *tgbot.Bot, webhookURL string) {
if _, err := b.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{}); err != nil {
slog.Warn("failed to delete webhook", "error", err)
}