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.
125 lines
2.7 KiB
Go
125 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
tgbot "github.com/go-telegram/bot"
|
|
"github.com/go-telegram/bot/models"
|
|
"github.com/joho/godotenv"
|
|
|
|
"uptodownBot/internal/dl"
|
|
"uptodownBot/internal/handler"
|
|
"uptodownBot/internal/repo"
|
|
)
|
|
|
|
func main() {
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
|
AddSource: true,
|
|
Level: slog.LevelInfo,
|
|
})))
|
|
|
|
godotenv.Load()
|
|
|
|
token := os.Getenv("BOT_TOKEN")
|
|
dbPath := getEnvDefault("DB_PATH", "data/uptodown.db")
|
|
downloadDir := getEnvDefault("DOWNLOAD_DIR", "/tmp/uptodown")
|
|
maxFileSizeMB := getEnvIntDefault("MAX_FILE_SIZE_MB", 2000)
|
|
cookiesFile := os.Getenv("COOKIES_FILE")
|
|
|
|
if token == "" {
|
|
slog.Error("BOT_TOKEN is required")
|
|
os.Exit(1)
|
|
}
|
|
|
|
store, err := repo.NewStore(dbPath)
|
|
if err != nil {
|
|
slog.Error("failed to open database", "path", dbPath, "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ytdlp, err := dl.NewClient(cookiesFile)
|
|
if err != nil {
|
|
slog.Error("failed to init yt-dlp", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
allowedUsers := parseAllowedUsers(os.Getenv("BOT_ALLOWED_USERS"))
|
|
|
|
var h *handler.Handler
|
|
|
|
dispatch := func(ctx context.Context, b *tgbot.Bot, update *models.Update) {
|
|
if update.Message != nil {
|
|
h.HandleMessage(ctx, update.Message)
|
|
}
|
|
if update.CallbackQuery != nil {
|
|
h.HandleCallback(ctx, update.CallbackQuery)
|
|
}
|
|
}
|
|
|
|
b, err := tgbot.New(token, tgbot.WithDefaultHandler(dispatch))
|
|
if err != nil {
|
|
slog.Error("failed to create bot", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
h = handler.NewHandler(b, store, ytdlp, allowedUsers, downloadDir, int64(maxFileSizeMB)*1024*1024)
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
if url := os.Getenv("BOT_WEBHOOK_URL"); url != "" {
|
|
startWebhook(ctx, b, h, url)
|
|
} else {
|
|
if _, err := b.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{}); err != nil {
|
|
slog.Warn("failed to delete webhook", "error", err)
|
|
}
|
|
slog.Info("bot started",
|
|
"allowed_users", len(allowedUsers),
|
|
"mode", "polling",
|
|
)
|
|
b.Start(ctx)
|
|
}
|
|
|
|
store.Close()
|
|
}
|
|
|
|
func parseAllowedUsers(raw string) map[int64]bool {
|
|
m := make(map[int64]bool)
|
|
for _, s := range strings.Split(raw, ",") {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
continue
|
|
}
|
|
id, err := strconv.ParseInt(s, 10, 64)
|
|
if err == nil {
|
|
m[id] = true
|
|
} else {
|
|
slog.Warn("invalid user ID in BOT_ALLOWED_USERS", "value", s)
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func getEnvDefault(key, defaultValue string) string {
|
|
if val := os.Getenv(key); val != "" {
|
|
return val
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvIntDefault(key string, defaultVal int) int {
|
|
if val := os.Getenv(key); val != "" {
|
|
n, err := strconv.Atoi(val)
|
|
if err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return defaultVal
|
|
}
|