Files
uptodownbot/internal/handler/settings.go
db123 3099244614
All checks were successful
CI / build (push) Successful in 47s
feat: add 'Ask' quality option, language auto-select, and m4a audio remux
- Add 'ask' option to audio/video quality settings (stored as '' in DB)
- Auto-select when both media type AND quality are set; show filtered
  format picker when quality is Ask
- Auto-skip language picker when user has a matching language preference
- Remux audio-only downloads to m4a instead of webm for Telegram compat
- Update README to document the new flow and m4a audio output
2026-06-28 12:17:08 +03:30

263 lines
8.2 KiB
Go

package handler
import (
"context"
"fmt"
"log/slog"
"strings"
"github.com/go-telegram/bot/models"
"uptodownBot/internal/domain"
)
func (h *Handler) buildSettingsKeyboard(userID int64, prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
audioFmt := prefs.DefaultAudioFormat
if audioFmt == "" {
audioFmt = "ask"
}
videoFmt := prefs.DefaultVideoFormat
if videoFmt == "" {
videoFmt = "ask"
}
langLabel := prefs.DefaultLanguage
if langLabel == "" {
langLabel = "Default"
}
mediaTypeLabel := "Ask"
switch prefs.DefaultMediaType {
case domain.MediaTypeAudio:
mediaTypeLabel = "Audio"
case domain.MediaTypeVideo:
mediaTypeLabel = "Video"
}
rows := [][]models.InlineKeyboardButton{
{
{Text: fmt.Sprintf("Media Type: %s", mediaTypeLabel), CallbackData: "settings_mediatype"},
{Text: fmt.Sprintf("Language: %s", langLabel), CallbackData: "settings_language"},
},
{
{Text: fmt.Sprintf("Quality: %s/%s", audioFmt, videoFmt), CallbackData: "settings_quality"},
},
{{Text: "Delete all data", CallbackData: "deleteaccount", Style: "danger"}},
{{Text: "Back to Menu", CallbackData: "back_menu"}},
}
return "Settings:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (h *Handler) settingsCallback(ctx context.Context, chatID int64, msgID int, cbID string, userID int64) {
defer h.answerCb(ctx, cbID)
prefs, err := h.Repo.GetOrCreatePreferences(userID)
if err != nil {
return
}
text, kb := h.buildSettingsKeyboard(userID, prefs)
h.editText(ctx, chatID, msgID, text, &kb)
}
func (h *Handler) backToSettings(ctx context.Context, chatID int64, msgID int, userID int64) {
prefs, err := h.Repo.GetOrCreatePreferences(userID)
if err != nil {
return
}
text, kb := h.buildSettingsKeyboard(userID, prefs)
h.editText(ctx, chatID, msgID, text, &kb)
}
func (h *Handler) handleSettingsSelection(ctx context.Context, chatID int64, msgID int, userID int64, setting string) {
prefs, err := h.Repo.GetOrCreatePreferences(userID)
if err != nil {
return
}
switch setting {
case "mediatype":
text, kb := h.buildMediaTypePicker(prefs)
h.editText(ctx, chatID, msgID, text, &kb)
case "quality":
text, kb := h.buildQualityAudioVideoPicker(prefs)
h.editText(ctx, chatID, msgID, text, &kb)
case "quality_audio":
text, kb := h.buildAudioQualityPicker(prefs)
h.editText(ctx, chatID, msgID, text, &kb)
case "quality_video":
text, kb := h.buildVideoQualityPicker(prefs)
h.editText(ctx, chatID, msgID, text, &kb)
case "language":
text, kb := h.buildLanguagePicker(prefs)
h.editText(ctx, chatID, msgID, text, &kb)
}
}
func (h *Handler) buildQualityAudioVideoPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
rows := [][]models.InlineKeyboardButton{
{{Text: "Audio", CallbackData: "settings_quality_audio"}},
{{Text: "Video", CallbackData: "settings_quality_video"}},
{{Text: "Back to Settings", CallbackData: "back_settings"}},
}
return "Choose quality type to set default:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (h *Handler) buildAudioQualityPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
bitrates := []string{"ask", "best", "128k", "192k", "256k", "320k"}
rows := [][]models.InlineKeyboardButton{}
for _, b := range bitrates {
label := b
if b == "ask" && prefs.DefaultAudioFormat == "" {
label = "> " + label
} else if b != "ask" && b == prefs.DefaultAudioFormat {
label = "> " + label
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: label, CallbackData: "settings_set_audioquality_" + b},
})
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: "Back", CallbackData: "back_quality"},
})
return "Select default audio quality:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (h *Handler) buildVideoQualityPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
resolutions := []string{"ask", "best", "2160p", "1440p", "1080p", "720p", "480p", "360p"}
rows := [][]models.InlineKeyboardButton{}
row := []models.InlineKeyboardButton{}
for i, r := range resolutions {
label := r
if r == "ask" && prefs.DefaultVideoFormat == "" {
label = "> " + label
} else if r != "ask" && r == prefs.DefaultVideoFormat {
label = "> " + label
}
row = append(row, models.InlineKeyboardButton{
Text: label, CallbackData: "settings_set_videoquality_" + r,
})
if len(row) == 3 || i == len(resolutions)-1 {
rows = append(rows, row)
row = nil
}
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: "Back", CallbackData: "back_quality"},
})
return "Select default video quality:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (h *Handler) buildLanguagePicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
languages := []string{"", "en", "es", "ja", "ko", "fr", "de", "zh", "ar", "pt", "ru"}
langLabels := map[string]string{
"": "Default", "en": "English", "es": "Spanish", "ja": "Japanese",
"ko": "Korean", "fr": "French", "de": "German", "zh": "Chinese",
"ar": "Arabic", "pt": "Portuguese", "ru": "Russian",
}
rows := [][]models.InlineKeyboardButton{}
row := []models.InlineKeyboardButton{}
for i, l := range languages {
label := langLabels[l]
if l == prefs.DefaultLanguage {
label = "> " + label
}
row = append(row, models.InlineKeyboardButton{
Text: label, CallbackData: "settings_set_language_" + l,
})
if len(row) == 2 || i == len(languages)-1 {
rows = append(rows, row)
row = nil
}
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: "Back to Settings", CallbackData: "back_settings"},
})
return "Select default language:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (h *Handler) buildMediaTypePicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
options := []struct {
mt domain.MediaType
label string
}{
{domain.MediaTypeAsk, "Ask"},
{domain.MediaTypeVideo, "Video"},
{domain.MediaTypeAudio, "Audio"},
}
rows := [][]models.InlineKeyboardButton{}
for _, opt := range options {
label := opt.label
if opt.mt == prefs.DefaultMediaType {
label = "> " + label
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: label, CallbackData: "settings_set_mediatype_" + opt.mt.String()},
})
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: "Back to Settings", CallbackData: "back_settings"},
})
return "Select default media type:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (h *Handler) handleSettingsSet(ctx context.Context, chatID int64, msgID int, userID int64, setting string) {
parts := strings.SplitN(setting, "_", 2)
if len(parts) < 2 {
return
}
action := parts[0]
value := parts[1]
prefs, err := h.Repo.GetOrCreatePreferences(userID)
if err != nil {
return
}
changed := false
switch action {
case "mediatype":
switch value {
case "ask":
prefs.DefaultMediaType = domain.MediaTypeAsk
case "audio":
prefs.DefaultMediaType = domain.MediaTypeAudio
case "video":
prefs.DefaultMediaType = domain.MediaTypeVideo
}
changed = true
case "audioquality":
if value == "ask" {
value = ""
}
prefs.DefaultAudioFormat = value
changed = true
case "videoquality":
if value == "ask" {
value = ""
}
prefs.DefaultVideoFormat = value
changed = true
case "language":
prefs.DefaultLanguage = value
changed = true
}
if changed {
_ = h.Repo.UpdatePreferences(userID, prefs)
}
h.backToSettings(ctx, chatID, msgID, userID)
}
// deleteAccountPrompt shows the deletion confirmation prompt.
func (h *Handler) deleteAccountPrompt(ctx context.Context, chatID int64, msgID int, userID int64) {
kb := deleteConfirmKeyboard(userID)
h.editText(ctx, chatID, msgID, "This will permanently delete ALL your data (preferences, quotas, history).\nAre you sure?", &kb)
}
// deleteAccountConfirm permanently deletes all user data.
func (h *Handler) deleteAccountConfirm(ctx context.Context, chatID int64, msgID int, userID int64) {
if err := h.Repo.DeleteUserData(userID); err != nil {
slog.Error("delete user data", "user_id", userID, "error", err)
h.editText(ctx, chatID, msgID, "Error deleting data. Please try again.", nil)
return
}
h.editText(ctx, chatID, msgID, "All your data has been deleted. You can start fresh by sending /start.", nil)
}