- Rename 'Format' button to 'Quality' in settings (audio_quality/video_quality) - Add DefaultAudioContainer and DefaultVideoContainer to UserPreferences - Container settings now has Audio/Video sub-pickers (mp3/m4a/opus vs mp4/mkv/webm) - Add migration 003 for new container columns - Update repo layer and playlist flow for new fields - Update back routing: back_quality, back_container
280 lines
9.1 KiB
Go
280 lines
9.1 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 = "best"
|
|
}
|
|
videoFmt := prefs.DefaultVideoFormat
|
|
if videoFmt == "" {
|
|
videoFmt = "best"
|
|
}
|
|
audioCont := prefs.DefaultAudioContainer
|
|
if audioCont == "" {
|
|
audioCont = "mp3"
|
|
}
|
|
videoCont := prefs.DefaultVideoContainer
|
|
if videoCont == "" {
|
|
videoCont = "mp4"
|
|
}
|
|
langLabel := prefs.DefaultLanguage
|
|
if langLabel == "" {
|
|
langLabel = "Default"
|
|
}
|
|
|
|
rows := [][]models.InlineKeyboardButton{
|
|
{
|
|
{Text: fmt.Sprintf("Quality: %s/%s", audioFmt, videoFmt), CallbackData: "settings_quality"},
|
|
},
|
|
{
|
|
{Text: fmt.Sprintf("Container: %s/%s", audioCont, videoCont), CallbackData: "settings_container"},
|
|
{Text: fmt.Sprintf("Language: %s", langLabel), CallbackData: "settings_language"},
|
|
},
|
|
{{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 "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 "container":
|
|
text, kb := h.buildContainerAudioVideoPicker(prefs)
|
|
h.editText(ctx, chatID, msgID, text, &kb)
|
|
case "container_audio":
|
|
text, kb := h.buildAudioContainerPicker(prefs)
|
|
h.editText(ctx, chatID, msgID, text, &kb)
|
|
case "container_video":
|
|
text, kb := h.buildVideoContainerPicker(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{"best", "128k", "192k", "256k", "320k"}
|
|
rows := [][]models.InlineKeyboardButton{}
|
|
for _, b := range bitrates {
|
|
label := b
|
|
if 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{"best", "2160p", "1440p", "1080p", "720p", "480p", "360p"}
|
|
rows := [][]models.InlineKeyboardButton{}
|
|
row := []models.InlineKeyboardButton{}
|
|
for i, r := range resolutions {
|
|
label := r
|
|
if 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) buildContainerAudioVideoPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
|
|
rows := [][]models.InlineKeyboardButton{
|
|
{{Text: "Audio", CallbackData: "settings_container_audio"}},
|
|
{{Text: "Video", CallbackData: "settings_container_video"}},
|
|
{{Text: "Back to Settings", CallbackData: "back_settings"}},
|
|
}
|
|
return "Choose container type to set default:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
|
|
}
|
|
|
|
func (h *Handler) buildAudioContainerPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
|
|
containers := []string{"mp3", "m4a", "opus"}
|
|
rows := [][]models.InlineKeyboardButton{}
|
|
for _, c := range containers {
|
|
label := c
|
|
if c == prefs.DefaultAudioContainer {
|
|
label = "> " + label
|
|
}
|
|
rows = append(rows, []models.InlineKeyboardButton{
|
|
{Text: label, CallbackData: "settings_set_audiocontainer_" + c},
|
|
})
|
|
}
|
|
rows = append(rows, []models.InlineKeyboardButton{
|
|
{Text: "Back", CallbackData: "back_container"},
|
|
})
|
|
return "Select default audio container:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
|
|
}
|
|
|
|
func (h *Handler) buildVideoContainerPicker(prefs *domain.UserPreferences) (string, models.InlineKeyboardMarkup) {
|
|
containers := []string{"mp4", "mkv", "webm"}
|
|
rows := [][]models.InlineKeyboardButton{}
|
|
row := []models.InlineKeyboardButton{}
|
|
for i, c := range containers {
|
|
label := c
|
|
if c == prefs.DefaultVideoContainer {
|
|
label = "> " + label
|
|
}
|
|
row = append(row, models.InlineKeyboardButton{
|
|
Text: label, CallbackData: "settings_set_videocontainer_" + c,
|
|
})
|
|
if len(row) == 3 || i == len(containers)-1 {
|
|
rows = append(rows, row)
|
|
row = nil
|
|
}
|
|
}
|
|
rows = append(rows, []models.InlineKeyboardButton{
|
|
{Text: "Back", CallbackData: "back_container"},
|
|
})
|
|
return "Select default video container:", 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) 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 "audioquality":
|
|
prefs.DefaultAudioFormat = value
|
|
changed = true
|
|
case "videoquality":
|
|
prefs.DefaultVideoFormat = value
|
|
changed = true
|
|
case "audiocontainer":
|
|
prefs.DefaultAudioContainer = value
|
|
changed = true
|
|
case "videocontainer":
|
|
prefs.DefaultVideoContainer = 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)
|
|
}
|