From d8b4747fce649778217c4db763ba95b52168c3c0 Mon Sep 17 00:00:00 2001 From: ZenonEl <165126589+ZenonEl@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:04:50 +0400 Subject: [PATCH 1/2] feat(download): replace manual yt-dlp with YoutubeDLSharp package closes #4 --- TelegramBot/MediaGet.cs | 126 +++++++++++++++++++---------------- TelegramMediaRelayBot.csproj | 1 + 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/TelegramBot/MediaGet.cs b/TelegramBot/MediaGet.cs index 49eb8c7..d7dfdbb 100644 --- a/TelegramBot/MediaGet.cs +++ b/TelegramBot/MediaGet.cs @@ -11,13 +11,13 @@ using System.Diagnostics; using System.Net; +using YoutubeDLSharp; +using YoutubeDLSharp.Options; namespace TelegramMediaRelayBot { public class MediaGet { - private static readonly string[] ColonSpaceSeparator = [": "]; - public static async Task?> DownloadMedia(ITelegramBotClient botClient, string videoUrl, Message statusMessage, CancellationToken cancellationToken) { try @@ -34,10 +34,10 @@ public class MediaGet } Log.Debug("Starting video download via yt-dlp..."); - + string tempDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDirPath); - + if (Config.torEnabled) { var proxy = new WebProxy($"socks5://{Config.torSocksHost}:{Config.torSocksPort}"); @@ -47,80 +47,88 @@ public class MediaGet Log.Debug("Tor IP: " + result); } - var startInfo = new ProcessStartInfo + var ytdl = new YoutubeDL { - FileName = "yt-dlp", - Arguments = $"--proxy \"{Config.proxy}\" -v -f mp4 --output \"{tempDirPath}/video.%(ext)s\" {videoUrl}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true + YoutubeDLPath = "yt-dlp", + OutputFolder = tempDirPath, + OutputFileTemplate = "video.%(ext)s", + OverwriteFiles = true }; - using (Process process = new Process { StartInfo = startInfo }) + var overrideOptions = new OptionSet { - process.Start(); - Log.Debug("Process started."); - - List outputLines = new List(); - List errorLines = new List(); + Format = "mp4", + Verbose = true + }; - Task readOutputTask = ReadLinesAsync(process.StandardOutput, outputLines, botClient, statusMessage, cancellationToken); - Task readErrorTask = ReadLinesAsync(process.StandardError, errorLines, botClient, statusMessage, cancellationToken); + if (!string.IsNullOrEmpty(Config.proxy)) + { + overrideOptions.Proxy = Config.proxy; + } - await process.WaitForExitAsync(); + DateTime lastProgressUpdate = DateTime.MinValue; + var progress = new Progress(p => + { + if (DateTime.UtcNow - lastProgressUpdate < TimeSpan.FromMilliseconds(Config.videoGetDelay)) + return; - await Task.WhenAll(readOutputTask, readErrorTask); + lastProgressUpdate = DateTime.UtcNow; + int percent = (int)(p.Progress * 100); + string statusText = $"[download] {percent}%"; + if (!string.IsNullOrEmpty(p.DownloadSpeed)) + statusText += $" at {p.DownloadSpeed}"; + if (!string.IsNullOrEmpty(p.ETA)) + statusText += $" ETA {p.ETA}"; - string output = string.Join("\n", outputLines); - string error = string.Join("\n", errorLines); + if (Config.showVideoDownloadProgress) + Log.Debug($"Video download progress: {statusText}"); - if (process.ExitCode == 0) + _ = Task.Run(async () => { try { - string? downloadLine = output.Split('\n').FirstOrDefault(line => line.StartsWith("[download] Destination:")); - if (downloadLine == null) - { - Log.Error("Could not find download destination in yt-dlp output."); - return null; - } - - string[] parts = downloadLine.Split(ColonSpaceSeparator, 2, StringSplitOptions.None); - if (parts.Length < 2) - { - Log.Error("Download destination not found in yt-dlp output."); - return null; - } - - string finalFilePath = parts[1].Trim(); - Log.Debug($"Final file path: {finalFilePath}"); - - if (System.IO.File.Exists(finalFilePath)) - { - List? videoBytes = new List { System.IO.File.ReadAllBytes(finalFilePath) }; - - System.IO.File.Delete(finalFilePath); - Directory.Delete(tempDirPath, recursive: true); - - return videoBytes; - } - - Log.Error($"Final file does not exist: {finalFilePath}"); + await botClient.EditMessageText( + statusMessage.Chat.Id, + statusMessage.MessageId, + statusText, + cancellationToken: cancellationToken); } catch (Exception ex) { - Log.Error(ex, $"Error reading file: {ex.Message}"); + Log.Debug(ex, "Error editing message."); } - } - else + }); + }); + + var downloadResult = await ytdl.RunVideoDownload( + videoUrl, + format: "mp4", + ct: cancellationToken, + progress: progress, + overrideOptions: overrideOptions); + + if (downloadResult.Success) + { + string filePath = downloadResult.Data; + Log.Debug($"Final file path: {filePath}"); + + if (System.IO.File.Exists(filePath)) { - Log.Error("Video download failed: " + error); + byte[] videoBytes = await System.IO.File.ReadAllBytesAsync(filePath, cancellationToken); + Directory.Delete(tempDirPath, recursive: true); + return new List { videoBytes }; } - - Directory.Delete(tempDirPath, recursive: true); - return null; + + Log.Error($"Final file does not exist: {filePath}"); + } + else + { + string errors = string.Join("\n", downloadResult.ErrorOutput); + Log.Error("Video download failed: " + errors); } + + Directory.Delete(tempDirPath, recursive: true); + return null; } catch (Exception ex) { diff --git a/TelegramMediaRelayBot.csproj b/TelegramMediaRelayBot.csproj index 2ba8f74..d6fe337 100644 --- a/TelegramMediaRelayBot.csproj +++ b/TelegramMediaRelayBot.csproj @@ -43,5 +43,6 @@ PreserveNewest + \ No newline at end of file From a5f3615d7a88de7ef9b1c05c9e55f80687f02018 Mon Sep 17 00:00:00 2001 From: ZenonEl <165126589+ZenonEl@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:09:31 +0400 Subject: [PATCH 2/2] fix(download): temp dir cleanup, cancellation propagation, Tor proxy auto-set - Wrap yt-dlp download in try/finally for guaranteed temp dir cleanup - Propagate OperationCanceledException instead of swallowing - Remove duplicate format argument from RunVideoDownload - Auto-set yt-dlp proxy to Tor SOCKS when torEnabled and proxy is empty --- TelegramBot/MediaGet.cs | 156 +++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 73 deletions(-) diff --git a/TelegramBot/MediaGet.cs b/TelegramBot/MediaGet.cs index d7dfdbb..1fd315e 100644 --- a/TelegramBot/MediaGet.cs +++ b/TelegramBot/MediaGet.cs @@ -37,98 +37,108 @@ public class MediaGet string tempDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDirPath); - - if (Config.torEnabled) + try { - var proxy = new WebProxy($"socks5://{Config.torSocksHost}:{Config.torSocksPort}"); - var handler = new HttpClientHandler { Proxy = proxy, UseProxy = true }; - using var httpClient = new HttpClient(handler); - var result = await httpClient.GetStringAsync("https://check.torproject.org/api/ip"); - Log.Debug("Tor IP: " + result); - } + if (Config.torEnabled) + { + var proxy = new WebProxy($"socks5://{Config.torSocksHost}:{Config.torSocksPort}"); + var handler = new HttpClientHandler { Proxy = proxy, UseProxy = true }; + using var httpClient = new HttpClient(handler); + var result = await httpClient.GetStringAsync("https://check.torproject.org/api/ip"); + Log.Debug("Tor IP: " + result); + } - var ytdl = new YoutubeDL - { - YoutubeDLPath = "yt-dlp", - OutputFolder = tempDirPath, - OutputFileTemplate = "video.%(ext)s", - OverwriteFiles = true - }; + var ytdl = new YoutubeDL + { + YoutubeDLPath = "yt-dlp", + OutputFolder = tempDirPath, + OutputFileTemplate = "video.%(ext)s", + OverwriteFiles = true + }; - var overrideOptions = new OptionSet - { - Format = "mp4", - Verbose = true - }; + var overrideOptions = new OptionSet(); - if (!string.IsNullOrEmpty(Config.proxy)) - { - overrideOptions.Proxy = Config.proxy; - } + string effectiveProxy = Config.proxy; + if (string.IsNullOrEmpty(effectiveProxy) && Config.torEnabled) + effectiveProxy = $"socks5://{Config.torSocksHost}:{Config.torSocksPort}"; - DateTime lastProgressUpdate = DateTime.MinValue; - var progress = new Progress(p => - { - if (DateTime.UtcNow - lastProgressUpdate < TimeSpan.FromMilliseconds(Config.videoGetDelay)) - return; + if (!string.IsNullOrEmpty(effectiveProxy)) + overrideOptions.Proxy = effectiveProxy; - lastProgressUpdate = DateTime.UtcNow; - int percent = (int)(p.Progress * 100); - string statusText = $"[download] {percent}%"; - if (!string.IsNullOrEmpty(p.DownloadSpeed)) - statusText += $" at {p.DownloadSpeed}"; - if (!string.IsNullOrEmpty(p.ETA)) - statusText += $" ETA {p.ETA}"; + DateTime lastProgressUpdate = DateTime.MinValue; + var progress = new Progress(p => + { + if (DateTime.UtcNow - lastProgressUpdate < TimeSpan.FromMilliseconds(Config.videoGetDelay)) + return; - if (Config.showVideoDownloadProgress) - Log.Debug($"Video download progress: {statusText}"); + lastProgressUpdate = DateTime.UtcNow; + int percent = (int)(p.Progress * 100); + string statusText = $"[download] {percent}%"; + if (!string.IsNullOrEmpty(p.DownloadSpeed)) + statusText += $" at {p.DownloadSpeed}"; + if (!string.IsNullOrEmpty(p.ETA)) + statusText += $" ETA {p.ETA}"; - _ = Task.Run(async () => - { - try - { - await botClient.EditMessageText( - statusMessage.Chat.Id, - statusMessage.MessageId, - statusText, - cancellationToken: cancellationToken); - } - catch (Exception ex) + if (Config.showVideoDownloadProgress) + Log.Debug($"Video download progress: {statusText}"); + + _ = Task.Run(async () => { - Log.Debug(ex, "Error editing message."); - } + try + { + await botClient.EditMessageText( + statusMessage.Chat.Id, + statusMessage.MessageId, + statusText, + cancellationToken: cancellationToken); + } + catch (Exception ex) + { + Log.Debug(ex, "Error editing message."); + } + }); }); - }); - var downloadResult = await ytdl.RunVideoDownload( - videoUrl, - format: "mp4", - ct: cancellationToken, - progress: progress, - overrideOptions: overrideOptions); + var downloadResult = await ytdl.RunVideoDownload( + videoUrl, + ct: cancellationToken, + progress: progress, + overrideOptions: overrideOptions); - if (downloadResult.Success) - { - string filePath = downloadResult.Data; - Log.Debug($"Final file path: {filePath}"); + if (downloadResult.Success) + { + string filePath = downloadResult.Data; + Log.Debug($"Final file path: {filePath}"); + + if (System.IO.File.Exists(filePath)) + { + byte[] videoBytes = await System.IO.File.ReadAllBytesAsync(filePath, cancellationToken); + return new List { videoBytes }; + } - if (System.IO.File.Exists(filePath)) + Log.Error($"Final file does not exist: {filePath}"); + } + else { - byte[] videoBytes = await System.IO.File.ReadAllBytesAsync(filePath, cancellationToken); - Directory.Delete(tempDirPath, recursive: true); - return new List { videoBytes }; + string errors = string.Join("\n", downloadResult.ErrorOutput); + Log.Error("Video download failed: " + errors); } - Log.Error($"Final file does not exist: {filePath}"); + return null; } - else + catch (OperationCanceledException) { - string errors = string.Join("\n", downloadResult.ErrorOutput); - Log.Error("Video download failed: " + errors); + throw; } - - Directory.Delete(tempDirPath, recursive: true); - return null; + finally + { + if (Directory.Exists(tempDirPath)) + Directory.Delete(tempDirPath, recursive: true); + } + } + catch (OperationCanceledException) + { + throw; } catch (Exception ex) {