diff --git a/internal/domain/types.go b/internal/domain/types.go index ddfe660..4fa5063 100644 --- a/internal/domain/types.go +++ b/internal/domain/types.go @@ -134,10 +134,11 @@ type DownloadJob struct { // UserPreferences holds per-user default preferences. type UserPreferences struct { - DefaultMediaType MediaType - DefaultQuality string - DefaultContainer string - DefaultLanguage string + DefaultMediaType MediaType + DefaultAudioFormat string + DefaultVideoFormat string + DefaultContainer string + DefaultLanguage string } // UserQuotas holds the quota usage and limits for a user. diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 5f6e885..7c68e04 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -232,6 +232,9 @@ func (h *Handler) HandleCallback(ctx context.Context, cb *models.CallbackQuery) h.answerCb(ctx, cb.ID) case data == "history": h.historyCallback(ctx, chatID, msgID, cb.ID, userID) + case data == "deleteaccount": + h.deleteAccountPrompt(ctx, chatID, msgID, userID) + h.answerCb(ctx, cb.ID) case data == "pending_cancel": h.backToMenu(ctx, chatID, msgID, cb.ID, userID) default: @@ -259,6 +262,12 @@ func (h *Handler) routePrefixedCallback(ctx context.Context, chatID int64, msgID h.handlePlaylistSelectAll(ctx, chatID, msgID, userID) case data == "pl_confirm": h.handlePlaylistConfirm(ctx, chatID, msgID, userID) + case strings.HasPrefix(data, "delaccount_"): + if uid, err := strconv.ParseInt(data[11:], 10, 64); err == nil && uid > 0 { + h.deleteAccountConfirm(ctx, chatID, msgID, uid) + } + case data == "back_format": + h.handleSettingsSelection(ctx, chatID, msgID, userID, "format") case data == "back_settings": h.backToSettings(ctx, chatID, msgID, userID) case strings.HasPrefix(data, "settings_set_"): diff --git a/internal/handler/playlist.go b/internal/handler/playlist.go index 3824f3e..66f30b7 100644 --- a/internal/handler/playlist.go +++ b/internal/handler/playlist.go @@ -142,15 +142,20 @@ func (h *Handler) handlePlaylistConfirm(ctx context.Context, chatID int64, msgID prefs, prefErr := h.Repo.GetOrCreatePreferences(userID) if prefErr != nil { prefs = &domain.UserPreferences{ - DefaultMediaType: domain.MediaTypeVideoAudio, - DefaultQuality: "best", - DefaultContainer: "mp4", + DefaultMediaType: domain.MediaTypeVideoAudio, + DefaultAudioFormat: "best", + DefaultVideoFormat: "best", + DefaultContainer: "mp4", } } - format := &domain.Format{ID: prefs.DefaultQuality} + formatID := prefs.DefaultVideoFormat + if prefs.DefaultMediaType == domain.MediaTypeAudio { + formatID = prefs.DefaultAudioFormat + } + format := &domain.Format{ID: formatID} for _, f := range info.Formats { - if f.ID == prefs.DefaultQuality { + if f.ID == formatID { format = &f break } diff --git a/internal/handler/settings.go b/internal/handler/settings.go index 943b23c..bd4fa2c 100644 --- a/internal/handler/settings.go +++ b/internal/handler/settings.go @@ -12,16 +12,13 @@ import ( ) func (h *Handler) buildSettingsKeyboard(userID int64, prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { - mediaTypeLabel := "Video+Audio" - switch prefs.DefaultMediaType { - case domain.MediaTypeAudio: - mediaTypeLabel = "Audio Only" - case domain.MediaTypeVideo: - mediaTypeLabel = "Video Only" + audioFmt := prefs.DefaultAudioFormat + if audioFmt == "" { + audioFmt = "best" } - qualityLabel := prefs.DefaultQuality - if qualityLabel == "" { - qualityLabel = "best" + videoFmt := prefs.DefaultVideoFormat + if videoFmt == "" { + videoFmt = "best" } containerLabel := prefs.DefaultContainer if containerLabel == "" { @@ -34,8 +31,7 @@ func (h *Handler) buildSettingsKeyboard(userID int64, prefs *domain.UserPreferen rows := [][]models.InlineKeyboardButton{ { - {Text: fmt.Sprintf("Type: %s", mediaTypeLabel), CallbackData: "settings_media_type"}, - {Text: fmt.Sprintf("Quality: %s", qualityLabel), CallbackData: "settings_quality"}, + {Text: fmt.Sprintf("Format: %s/%s", audioFmt, videoFmt), CallbackData: "settings_format"}, }, { {Text: fmt.Sprintf("Container: %s", containerLabel), CallbackData: "settings_container"}, @@ -73,11 +69,14 @@ func (h *Handler) handleSettingsSelection(ctx context.Context, chatID int64, msg } switch setting { - case "media_type": - text, kb := h.buildMediaTypePicker(prefs) + case "format": + text, kb := h.buildFormatPicker(prefs) h.editText(ctx, chatID, msgID, text, &kb) - case "quality": - text, kb := h.buildQualityPicker(prefs) + case "format_audio": + text, kb := h.buildAudioFormatPicker(prefs) + h.editText(ctx, chatID, msgID, text, &kb) + case "format_video": + text, kb := h.buildVideoFormatPicker(prefs) h.editText(ctx, chatID, msgID, text, &kb) case "container": text, kb := h.buildContainerPicker(prefs) @@ -88,53 +87,54 @@ func (h *Handler) handleSettingsSelection(ctx context.Context, chatID int64, msg } } -func (h *Handler) buildMediaTypePicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { - options := []struct { - label string - value domain.MediaType - data string - }{ - {"Audio Only", domain.MediaTypeAudio, "settings_set_type_audio"}, - {"Video Only", domain.MediaTypeVideo, "settings_set_type_video"}, - {"Video+Audio", domain.MediaTypeVideoAudio, "settings_set_type_both"}, +func (h *Handler) buildFormatPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { + rows := [][]models.InlineKeyboardButton{ + {{Text: "Audio", CallbackData: "settings_format_audio"}}, + {{Text: "Video", CallbackData: "settings_format_video"}}, + {{Text: "Back to Settings", CallbackData: "back_settings"}}, } + return "Choose format type to set default:", models.InlineKeyboardMarkup{InlineKeyboard: rows} +} + +func (h *Handler) buildAudioFormatPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { + bitrates := []string{"best", "128k", "192k", "256k", "320k"} rows := [][]models.InlineKeyboardButton{} - for _, opt := range options { - label := opt.label - if opt.value == prefs.DefaultMediaType { + for _, b := range bitrates { + label := b + if b == prefs.DefaultAudioFormat { label = "> " + label } rows = append(rows, []models.InlineKeyboardButton{ - {Text: label, CallbackData: opt.data}, + {Text: label, CallbackData: "settings_set_audioformat_" + b}, }) } rows = append(rows, []models.InlineKeyboardButton{ - {Text: "Back to Settings", CallbackData: "back_settings"}, + {Text: "Back", CallbackData: "back_format"}, }) - return "Select default media type:", models.InlineKeyboardMarkup{InlineKeyboard: rows} + return "Select default audio format:", models.InlineKeyboardMarkup{InlineKeyboard: rows} } -func (h *Handler) buildQualityPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { - qualities := []string{"best", "2160p", "1080p", "720p", "480p", "360p"} +func (h *Handler) buildVideoFormatPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { + resolutions := []string{"best", "2160p", "1440p", "1080p", "720p", "480p", "360p"} rows := [][]models.InlineKeyboardButton{} row := []models.InlineKeyboardButton{} - for i, q := range qualities { - label := q - if q == prefs.DefaultQuality { + for i, r := range resolutions { + label := r + if r == prefs.DefaultVideoFormat { label = "> " + label } row = append(row, models.InlineKeyboardButton{ - Text: label, CallbackData: "settings_set_quality_" + q, + Text: label, CallbackData: "settings_set_videoformat_" + r, }) - if len(row) == 3 || i == len(qualities)-1 { + if len(row) == 3 || i == len(resolutions)-1 { rows = append(rows, row) row = nil } } rows = append(rows, []models.InlineKeyboardButton{ - {Text: "Back to Settings", CallbackData: "back_settings"}, + {Text: "Back", CallbackData: "back_format"}, }) - return "Select default quality:", models.InlineKeyboardMarkup{InlineKeyboard: rows} + return "Select default video format:", models.InlineKeyboardMarkup{InlineKeyboard: rows} } func (h *Handler) buildContainerPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) { @@ -203,18 +203,11 @@ func (h *Handler) handleSettingsSet(ctx context.Context, chatID int64, msgID int changed := false switch action { - case "type": - switch value { - case "audio": - prefs.DefaultMediaType = domain.MediaTypeAudio - case "video": - prefs.DefaultMediaType = domain.MediaTypeVideo - case "both": - prefs.DefaultMediaType = domain.MediaTypeVideoAudio - } + case "audioformat": + prefs.DefaultAudioFormat = value changed = true - case "quality": - prefs.DefaultQuality = value + case "videoformat": + prefs.DefaultVideoFormat = value changed = true case "container": prefs.DefaultContainer = value diff --git a/internal/repo/migrations/002_preferences_format.sql b/internal/repo/migrations/002_preferences_format.sql new file mode 100644 index 0000000..f3c13a0 --- /dev/null +++ b/internal/repo/migrations/002_preferences_format.sql @@ -0,0 +1,7 @@ +-- +goose Up +ALTER TABLE user_preferences ADD COLUMN default_audio_format TEXT NOT NULL DEFAULT 'best'; +ALTER TABLE user_preferences ADD COLUMN default_video_format TEXT NOT NULL DEFAULT 'best'; + +-- +goose Down +ALTER TABLE user_preferences DROP COLUMN default_audio_format; +ALTER TABLE user_preferences DROP COLUMN default_video_format; diff --git a/internal/repo/store.go b/internal/repo/store.go index 15c23b0..11296f3 100644 --- a/internal/repo/store.go +++ b/internal/repo/store.go @@ -75,12 +75,14 @@ func (s *Store) GetOrCreatePreferences(userID int64) (*domain.UserPreferences, e func (s *Store) getPreferences(userID int64) (*domain.UserPreferences, error) { row := s.db.QueryRow(` - SELECT default_media_type, default_quality, default_container, default_language + SELECT default_media_type, default_audio_format, default_video_format, + default_container, default_language FROM user_preferences WHERE user_id = ? `, userID) var p domain.UserPreferences var mediaType string - if err := row.Scan(&mediaType, &p.DefaultQuality, &p.DefaultContainer, &p.DefaultLanguage); err != nil { + if err := row.Scan(&mediaType, &p.DefaultAudioFormat, &p.DefaultVideoFormat, + &p.DefaultContainer, &p.DefaultLanguage); err != nil { return nil, err } switch mediaType { @@ -107,11 +109,11 @@ func mediaTypeToString(mt domain.MediaType) string { func (s *Store) UpdatePreferences(userID int64, prefs *domain.UserPreferences) error { _, err := s.db.Exec(` - UPDATE user_preferences SET default_media_type=?, default_quality=?, - default_container=?, default_language=? + UPDATE user_preferences SET default_media_type=?, default_audio_format=?, + default_video_format=?, default_container=?, default_language=? WHERE user_id=? - `, mediaTypeToString(prefs.DefaultMediaType), prefs.DefaultQuality, - prefs.DefaultContainer, prefs.DefaultLanguage, userID) + `, mediaTypeToString(prefs.DefaultMediaType), prefs.DefaultAudioFormat, + prefs.DefaultVideoFormat, prefs.DefaultContainer, prefs.DefaultLanguage, userID) return err } diff --git a/internal/repo/store_test.go b/internal/repo/store_test.go index ee5c5bc..4b36a4e 100644 --- a/internal/repo/store_test.go +++ b/internal/repo/store_test.go @@ -64,7 +64,8 @@ func TestPreferences(t *testing.T) { } prefs.DefaultMediaType = domain.MediaTypeAudio - prefs.DefaultQuality = "best" + prefs.DefaultAudioFormat = "best" + prefs.DefaultVideoFormat = "1080p" prefs.DefaultContainer = "mp3" prefs.DefaultLanguage = "en" if err := s.UpdatePreferences(userID, prefs); err != nil { @@ -78,8 +79,11 @@ func TestPreferences(t *testing.T) { if got.DefaultMediaType != domain.MediaTypeAudio { t.Errorf("media type = %v, want Audio", got.DefaultMediaType) } - if got.DefaultQuality != "best" { - t.Errorf("quality = %q, want best", got.DefaultQuality) + if got.DefaultAudioFormat != "best" { + t.Errorf("audio format = %q, want best", got.DefaultAudioFormat) + } + if got.DefaultVideoFormat != "1080p" { + t.Errorf("video format = %q, want 1080p", got.DefaultVideoFormat) } if got.DefaultContainer != "mp3" { t.Errorf("container = %q, want mp3", got.DefaultContainer)