Files

139 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
)
const (
maxYouTubeDurationSec = 5 * 60
maxYouTubeFileBytes = 250 * 1024 * 1024 // лимит MAX для video
)
var youtubeURLRe = regexp.MustCompile(`(?i)https?://(?:(?:www|m)\.)?(?:youtube\.com/(?:watch\?[^ \t\n]*v=|shorts/|embed/|live/)|youtu\.be/)[^\s<>"']+`)
func extractYouTubeURL(text string) string {
m := youtubeURLRe.FindString(text)
if m == "" {
return ""
}
return strings.TrimRight(m, ".,);]!?")
}
type ytDLPInfo struct {
Duration float64 `json:"duration"`
Title string `json:"title"`
ID string `json:"id"`
}
func getYouTubeInfo(videoURL string) (*ytDLPInfo, error) {
cmd := exec.Command("yt-dlp", "--dump-json", "--no-playlist", "--no-download", videoURL)
out, err := cmd.Output()
if err != nil {
if ee, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("yt-dlp info: %v: %s", err, string(ee.Stderr))
}
return nil, fmt.Errorf("yt-dlp не найден или ошибка: %w", err)
}
var info ytDLPInfo
if err := json.Unmarshal(out, &info); err != nil {
return nil, err
}
return &info, nil
}
// downloadYouTubeIfShort скачивает ролик, если он короче 5 минут и уложится в лимит MAX.
// Возвращает путь к временному файлу (caller должен удалить) или ошибку.
func downloadYouTubeIfShort(videoURL string) (filePath string, title string, err error) {
info, err := getYouTubeInfo(videoURL)
if err != nil {
return "", "", err
}
if info.Duration <= 0 {
return "", "", fmt.Errorf("не удалось определить длительность")
}
if info.Duration > float64(maxYouTubeDurationSec) {
return "", "", fmt.Errorf("видео длиннее 5 минут (%.0f с)", info.Duration)
}
tmpDir, err := os.MkdirTemp("", "bridge-yt-*")
if err != nil {
return "", "", err
}
outTmpl := filepath.Join(tmpDir, "video.%(ext)s")
// mp4 предпочтительнее для MAX; ограничение размера 250 МБ
cmd := exec.Command(
"yt-dlp",
"--no-playlist",
"--max-filesize", strconv.Itoa(maxYouTubeFileBytes),
"-f", "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/bv*+ba/b",
"--merge-output-format", "mp4",
"-o", outTmpl,
videoURL,
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
_ = os.RemoveAll(tmpDir)
return "", "", fmt.Errorf("yt-dlp download: %w", err)
}
entries, err := os.ReadDir(tmpDir)
if err != nil || len(entries) == 0 {
_ = os.RemoveAll(tmpDir)
return "", "", fmt.Errorf("файл после скачивания не найден")
}
filePath = filepath.Join(tmpDir, entries[0].Name())
st, err := os.Stat(filePath)
if err != nil {
_ = os.RemoveAll(tmpDir)
return "", "", err
}
if st.Size() > maxYouTubeFileBytes {
_ = os.RemoveAll(tmpDir)
return "", "", fmt.Errorf("файл больше лимита MAX 250 МБ (%d)", st.Size())
}
log.Printf("[YouTube] скачано: %s (%.0f с, %d байт)", info.Title, info.Duration, st.Size())
return filePath, info.Title, nil
}
func cleanupYouTubeFile(filePath string) {
if filePath == "" {
return
}
_ = os.RemoveAll(filepath.Dir(filePath))
}
// SendToMAXSmart: при YouTube-ссылке и ролике < 5 мин отправляет видео-вложение, иначе текст (со ссылкой).
func (b *Bridge) SendToMAXSmart(chatID int64, text string) error {
ytURL := extractYouTubeURL(text)
if ytURL == "" {
return b.SendToMAX(chatID, text)
}
filePath, title, err := downloadYouTubeIfShort(ytURL)
if err != nil {
log.Printf("[YouTube→MAX] не удалось скачать (%s), отправляю ссылкой: %v", ytURL, err)
return b.SendToMAX(chatID, text)
}
defer cleanupYouTubeFile(filePath)
caption := text
if title != "" && !strings.Contains(text, title) {
caption = text + "\n" + title
}
if err := b.SendVideoFileToMAX(chatID, filePath, caption); err != nil {
log.Printf("[YouTube→MAX] ошибка загрузки видео, отправляю ссылкой: %v", err)
return b.SendToMAX(chatID, text)
}
return nil
}