Telegram bot for downloading media from any site using yt-dlp. Supports audio/video/both, quality selection, language picker, container format selection, playlist pagination, progress updates, per-user quotas, user preferences, download history, cancel at any step, polling and webhook modes, proxy support, and SQLite persistence.
196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"uptodownBot/internal/domain"
|
|
)
|
|
|
|
func (h *Handler) handlePlaylist(ctx context.Context, chatID int64, userID int64, url string, info *domain.MediaInfo) {
|
|
state := &domain.PlaylistState{
|
|
Entries: info.PlaylistEntries,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
Selected: make(map[string]bool),
|
|
}
|
|
|
|
ss := &stepState{
|
|
URL: url,
|
|
MediaInfo: info,
|
|
PlaylistState: state,
|
|
}
|
|
h.setUserStepState(chatID, ss)
|
|
|
|
kb := playlistNavKeyboard(state, 0)
|
|
h.sendWithKB(ctx, chatID, fmt.Sprintf("Playlist: %s (%d videos)\n\nSelect videos to download:", info.Title, info.PlaylistCount), kb)
|
|
}
|
|
|
|
func (h *Handler) getPlaylistState(chatID int64) *domain.PlaylistState {
|
|
ss := h.getUserStepState(chatID)
|
|
if ss == nil {
|
|
return nil
|
|
}
|
|
return ss.PlaylistState
|
|
}
|
|
|
|
func (h *Handler) handlePlaylistPage(ctx context.Context, chatID int64, msgID int, userID int64, pageStr string) {
|
|
state := h.getPlaylistState(chatID)
|
|
if state == nil {
|
|
return
|
|
}
|
|
page, err := strconv.Atoi(pageStr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
totalPages := (len(state.Entries) + state.PerPage - 1) / state.PerPage
|
|
if page < 0 || page >= totalPages {
|
|
return
|
|
}
|
|
state.Page = page
|
|
|
|
selectedCount := countSelected(state)
|
|
kb := playlistNavKeyboard(state, selectedCount)
|
|
ss := h.getUserStepState(chatID)
|
|
title := ""
|
|
if ss != nil {
|
|
title = ss.MediaInfo.Title
|
|
}
|
|
h.editText(ctx, chatID, msgID, fmt.Sprintf("Playlist: %s (%d videos)", title, len(state.Entries)), &kb)
|
|
}
|
|
|
|
func (h *Handler) handlePlaylistEntry(ctx context.Context, chatID int64, msgID int, userID int64, entryID string) {
|
|
state := h.getPlaylistState(chatID)
|
|
if state == nil {
|
|
return
|
|
}
|
|
state.Selected[entryID] = !state.Selected[entryID]
|
|
|
|
selectedCount := countSelected(state)
|
|
kb := playlistNavKeyboard(state, selectedCount)
|
|
ss := h.getUserStepState(chatID)
|
|
title := ""
|
|
if ss != nil {
|
|
title = ss.MediaInfo.Title
|
|
}
|
|
h.editText(ctx, chatID, msgID, fmt.Sprintf("Playlist: %s (%d videos)", title, len(state.Entries)), &kb)
|
|
}
|
|
|
|
func (h *Handler) handlePlaylistSelectAll(ctx context.Context, chatID int64, msgID int, userID int64) {
|
|
state := h.getPlaylistState(chatID)
|
|
if state == nil {
|
|
return
|
|
}
|
|
|
|
allSelected := true
|
|
for _, e := range state.Entries {
|
|
if !state.Selected[e.ID] {
|
|
allSelected = false
|
|
break
|
|
}
|
|
}
|
|
for _, e := range state.Entries {
|
|
state.Selected[e.ID] = !allSelected
|
|
}
|
|
|
|
selectedCount := 0
|
|
if !allSelected {
|
|
selectedCount = len(state.Entries)
|
|
}
|
|
kb := playlistNavKeyboard(state, selectedCount)
|
|
ss := h.getUserStepState(chatID)
|
|
title := ""
|
|
if ss != nil {
|
|
title = ss.MediaInfo.Title
|
|
}
|
|
h.editText(ctx, chatID, msgID, fmt.Sprintf("Playlist: %s (%d videos)", title, len(state.Entries)), &kb)
|
|
}
|
|
|
|
func (h *Handler) handlePlaylistConfirm(ctx context.Context, chatID int64, msgID int, userID int64) {
|
|
state := h.getPlaylistState(chatID)
|
|
if state == nil {
|
|
return
|
|
}
|
|
|
|
var selected []domain.PlaylistEntry
|
|
for _, e := range state.Entries {
|
|
if state.Selected[e.ID] {
|
|
selected = append(selected, e)
|
|
}
|
|
}
|
|
if len(selected) == 0 {
|
|
h.editText(ctx, chatID, msgID, "No videos selected.", nil)
|
|
return
|
|
}
|
|
|
|
h.clearStepState(chatID)
|
|
h.editText(ctx, chatID, msgID, fmt.Sprintf("Downloading %d videos sequentially...", len(selected)), nil)
|
|
|
|
for i, entry := range selected {
|
|
h.sendText(ctx, chatID, fmt.Sprintf("Downloading (%d/%d): %s", i+1, len(selected), entry.Title))
|
|
|
|
info, err := h.YtDlp.GetMediaInfo(entry.URL)
|
|
if err != nil {
|
|
h.sendText(ctx, chatID, fmt.Sprintf("Failed: %s", err.Error()))
|
|
continue
|
|
}
|
|
|
|
prefs, prefErr := h.Repo.GetOrCreatePreferences(userID)
|
|
if prefErr != nil {
|
|
prefs = &domain.UserPreferences{
|
|
DefaultMediaType: domain.MediaTypeVideoAudio,
|
|
DefaultQuality: "best",
|
|
DefaultContainer: "mp4",
|
|
}
|
|
}
|
|
|
|
format := &domain.Format{ID: prefs.DefaultQuality}
|
|
for _, f := range info.Formats {
|
|
if f.ID == prefs.DefaultQuality {
|
|
format = &f
|
|
break
|
|
}
|
|
}
|
|
|
|
job := &domain.DownloadJob{
|
|
ID: h.generateJobID(),
|
|
UserID: userID,
|
|
ChatID: chatID,
|
|
URL: entry.URL,
|
|
MediaType: prefs.DefaultMediaType,
|
|
SelectedFormat: format,
|
|
Container: prefs.DefaultContainer,
|
|
Language: prefs.DefaultLanguage,
|
|
Status: domain.StatusDownloading,
|
|
Title: entry.Title,
|
|
FileSize: format.Filesize,
|
|
}
|
|
|
|
h.setActiveJob(userID, job)
|
|
h.runDownload(ctx, job, 0)
|
|
|
|
// Wait for active job to complete
|
|
for {
|
|
j := h.getActiveJob(userID)
|
|
if j == nil || j.Status == domain.StatusCompleted || j.Status == domain.StatusFailed || j.Status == domain.StatusCancelled {
|
|
break
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
h.sendText(ctx, chatID, "All playlist downloads completed.")
|
|
}
|
|
|
|
func countSelected(state *domain.PlaylistState) int {
|
|
count := 0
|
|
for _, s := range state.Selected {
|
|
if s {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|