From 00c0e91a0246d1f54527d9c949075e112ef6de37 Mon Sep 17 00:00:00 2001 From: mateofuentespombo Date: Fri, 25 Apr 2025 00:02:11 +0200 Subject: [PATCH 01/10] feat: add download grid from channels chore: bump versions --- TelegramDownloader/Data/ITelegramService.cs | 3 +- TelegramDownloader/Data/TelegramService.cs | 52 +++++++- TelegramDownloader/Pages/Config.razor | 1 - TelegramDownloader/Pages/FileGrid.razor | 114 ++++++++++++++++++ .../Pages/Modals/DowloadFromTelegram.razor | 8 ++ TelegramDownloader/Pages/_Layout.cshtml | 2 +- TelegramDownloader/Program.cs | 2 +- TelegramDownloader/Shared/NavMenu.razor | 10 +- TelegramDownloader/TelegramDownloader.csproj | 12 +- TelegramDownloader/wwwroot/css/site.css | 3 + 10 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 TelegramDownloader/Pages/FileGrid.razor diff --git a/TelegramDownloader/Data/ITelegramService.cs b/TelegramDownloader/Data/ITelegramService.cs index f636d87..a825d7d 100644 --- a/TelegramDownloader/Data/ITelegramService.cs +++ b/TelegramDownloader/Data/ITelegramService.cs @@ -22,11 +22,12 @@ public interface ITelegramService Task RemoveFavouriteChannel(long id); Task> getAllChats(); Task> getAllSavedChats(); - Task> getAllMessages(long id); + Task> getAllMessages(long id, Boolean onlyFiles = false); Task> getChatHistory(long id, int limit = 30, int addOffset = 0); string getChatName(long id); Task getMessageFile(string chatId, int idMessage); Task getPhotoThumb(ChatBase chat); + Task downloadPhotoThumb(Photo thumb); Task logOff(); Task sendVerificationCode(string vc); Task uploadFile(string chatId, Stream file, string fileName, string mimeType = null, UploadModel um = null, string caption = null); diff --git a/TelegramDownloader/Data/TelegramService.cs b/TelegramDownloader/Data/TelegramService.cs index a500e8c..65430e5 100644 --- a/TelegramDownloader/Data/TelegramService.cs +++ b/TelegramDownloader/Data/TelegramService.cs @@ -317,7 +317,46 @@ public async Task getMessageFile(string chatId, int idMessage) //return m; } - public async Task> getAllMessages(long id) + public async Task> getAllMessages(long id, Boolean onlyFiles = false) + { + List cm = new List(); + InputPeer peer = chats.chats[id]; + for (int offset_id = 0; ;) + { + var messages = await client.Messages_GetHistory(peer, offset_id); + if (messages.Messages.Length == 0) break; + foreach (MessageBase msgBase in messages.Messages) + { + if (msgBase is Message msg) + { + ChatMessages cm2 = new ChatMessages(); + cm2.htmlMessage = client.EntitiesToHtml(msg.message, msg.entities); + cm2.message = msg; + + cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); + cm2.isDocument = false; + if (msg.media is MessageMediaDocument { document: Document document }) + { + cm2.isDocument = true; + } + if (onlyFiles) + { + if (cm2.isDocument) + cm.Add(cm2); + } else + { + cm.Add(cm2); + } + + + } + } + offset_id = messages.Messages[^1].ID; + } + return cm; + } + + public async Task> getAllFileMessages(long id) { List cm = new List(); InputPeer peer = chats.chats[id]; @@ -395,6 +434,17 @@ public async Task getPhotoThumb(ChatBase chat) } + public async Task downloadPhotoThumb(Photo thumb) + { + MemoryStream ms = new MemoryStream(); + if (await client.DownloadFileAsync(thumb, ms) != 0) + { + return $"data:image/jpeg;base64,{Convert.ToBase64String(ms.ToArray())}"; + }; + return ""; + + } + public async Task AddFavouriteChannel(long id) { if (!GeneralConfigStatic.config.FavouriteChannels.Contains(id)) diff --git a/TelegramDownloader/Pages/Config.razor b/TelegramDownloader/Pages/Config.razor index 1ee4a3b..98ca7c5 100644 --- a/TelegramDownloader/Pages/Config.razor +++ b/TelegramDownloader/Pages/Config.razor @@ -71,7 +71,6 @@ -
@if (TelegramService.isPremium) diff --git a/TelegramDownloader/Pages/FileGrid.razor b/TelegramDownloader/Pages/FileGrid.razor new file mode 100644 index 0000000..61bb5cf --- /dev/null +++ b/TelegramDownloader/Pages/FileGrid.razor @@ -0,0 +1,114 @@ +@page "/filegrid/{id}" + +@using TL +@using TelegramDownloader.Data; +@using TelegramDownloader.Models; +@using TelegramDownloader.Services; + +@inject ITelegramService ts; +@inject PreloadService PreloadService; + +

File Grid

+ +@if (SelectedItems.Any()) +{ + +} + + + + + + @context.message.id + + + @context.message.message + + + @context.message.Date.ToString("dd/MM/yyyy HH:mm:ss") + + + @(context.message.edit_date.Year > 1900 + ? context.message.edit_date.ToString("dd/MM/yyyy HH:mm:ss") + : "") + + + @if (context.message.media is MessageMediaDocument { document: Document document }) + { + @document.Filename + } + + + @if (context.message.media is MessageMediaDocument { document: Document document }) + { + @HelperService.SizeSuffix(document.size) + } + + + @if (context.message.media is MessageMediaDocument { document: Document document }) + { + @document.mime_type + } + + + @context.videoThumb + @if (context.message.media is MessageMediaPhoto { photo: Photo photo }) + { + + } else { + + } + + + + +
+ Selected Items Count: @SelectedItems.Count +
+ + + + + +@code { + [Parameter] + public string id { get; set; } + public List messages = default!; + + private Modals.DowloadFromTelegram Modal { get; set; } + private HashSet SelectedItems { get; set; } = new(); + + private async Task> ChatsDataProvider(GridDataProviderRequest request) + { + if (messages is null) { + PreloadService.Show(SpinnerColor.Light, "Loading data..."); + messages = await ts.getAllMessages(Convert.ToInt64(id), true); + PreloadService.Hide(); + } + + return await Task.FromResult(request.ApplyTo(messages)); + } + + private void GetSelectedItems() + { + Modal.chatMessages = SelectedItems.ToList(); + Modal.isDownloadingChatMessages = true; + Modal.Open(); + // Aquí puedes manejar los elementos seleccionados + // Console.WriteLine($"Elementos seleccionados: {SelectedItems.Count}"); + // foreach (var item in SelectedItems) + // { + // Console.WriteLine($"Id: {item.message.id}, Name: {item.message.message}"); + // } + } +} diff --git a/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor b/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor index 38938b6..03df9ac 100644 --- a/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor +++ b/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor @@ -45,7 +45,10 @@ [Parameter] public BsonSharedInfoModel bsi { get; set; } public string filePath { get; set; } + public List fileList { get; set; } + public List chatMessages { get; set; } + public bool isDownloadingChatMessages { get; set; } = false; public string filename = ""; public string size = ""; @@ -79,6 +82,11 @@ private async Task Submit() { + if (isDownloadingChatMessages) + { + chatMessages.ForEach(message => fs.DownloadFileFromChat(message, null, ddtree.selectedNode == null ? null : ddtree.selectedNode.FirstOrDefault(), null)); + return; + } // ts. if (isShare) fs.downloadFile(DbService.SHARED_DB_NAME, fileList.ToList(), ddtree.selectedNode == null ? null : ddtree.selectedNode.FirstOrDefault(), bsi.CollectionId, bsi.ChannelId); diff --git a/TelegramDownloader/Pages/_Layout.cshtml b/TelegramDownloader/Pages/_Layout.cshtml index 2c703e6..c31aa84 100644 --- a/TelegramDownloader/Pages/_Layout.cshtml +++ b/TelegramDownloader/Pages/_Layout.cshtml @@ -45,7 +45,7 @@ - + diff --git a/TelegramDownloader/Program.cs b/TelegramDownloader/Program.cs index c3c8d15..98433bc 100644 --- a/TelegramDownloader/Program.cs +++ b/TelegramDownloader/Program.cs @@ -33,7 +33,7 @@ var builder = WebApplication.CreateBuilder(args); -Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("MzY0NjIwOUAzMjM4MmUzMDJlMzBZdngxNkEzSjRTQTczaDdlWkxEd1BIeUovNjh1eHByNEg5SkRVZ1lmWHc4PQ=="); +Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("MzgzMTc5MUAzMjM5MmUzMDJlMzAzYjMyMzkzYmw1ZDhSMmlTeTVTMFl0Ky9YaHBpZlRhd0NZS2RuQlVvenpnRVduRHFtZkE9"); builder.Services.AddSyncfusionBlazor(); if (!Directory.Exists(FileService.IMGDIR)) diff --git a/TelegramDownloader/Shared/NavMenu.razor b/TelegramDownloader/Shared/NavMenu.razor index 6f5ba76..0efb2db 100644 --- a/TelegramDownloader/Shared/NavMenu.razor +++ b/TelegramDownloader/Shared/NavMenu.razor @@ -76,6 +76,9 @@
+ @@ -109,6 +112,9 @@
+ @@ -191,7 +197,9 @@
- + diff --git a/TelegramDownloader/TelegramDownloader.csproj b/TelegramDownloader/TelegramDownloader.csproj index 1ba72ce..de1dc77 100644 --- a/TelegramDownloader/TelegramDownloader.csproj +++ b/TelegramDownloader/TelegramDownloader.csproj @@ -15,13 +15,13 @@ - + - - - - - + + + + + diff --git a/TelegramDownloader/wwwroot/css/site.css b/TelegramDownloader/wwwroot/css/site.css index 1d7b996..47b4982 100644 --- a/TelegramDownloader/wwwroot/css/site.css +++ b/TelegramDownloader/wwwroot/css/site.css @@ -103,3 +103,6 @@ a, .btn-link { padding-bottom: 5%; } +.download-btn { + margin-bottom: 2% !important; +} \ No newline at end of file From 8bad38f07ab3a6b715ed87eb7fc86e321ee975b8 Mon Sep 17 00:00:00 2001 From: mateofuentespombo Date: Sat, 24 May 2025 18:27:48 +0200 Subject: [PATCH 02/10] feat: show channel files --- TelegramDownloader/Data/ITelegramService.cs | 5 +- TelegramDownloader/Data/TelegramService.cs | 145 +++++++++++++++++- TelegramDownloader/Models/GeneralConfig.cs | 1 + TelegramDownloader/Pages/Config.razor | 9 ++ TelegramDownloader/Pages/FileGrid.razor | 31 ++-- .../Pages/Modals/DowloadFromTelegram.razor | 1 + .../Services/TransactionInfoService.cs | 7 +- TelegramDownloader/Shared/NavMenu.razor | 32 ++-- 8 files changed, 197 insertions(+), 34 deletions(-) diff --git a/TelegramDownloader/Data/ITelegramService.cs b/TelegramDownloader/Data/ITelegramService.cs index a825d7d..33436be 100644 --- a/TelegramDownloader/Data/ITelegramService.cs +++ b/TelegramDownloader/Data/ITelegramService.cs @@ -1,4 +1,5 @@ -using TelegramDownloader.Models; +using BlazorBootstrap; +using TelegramDownloader.Models; using TL; using WTelegram; @@ -23,6 +24,8 @@ public interface ITelegramService Task> getAllChats(); Task> getAllSavedChats(); Task> getAllMessages(long id, Boolean onlyFiles = false); + Task> getPaginatedMessages(long id, int page, int size, Boolean onlyFiles = false); + Task> getAllMediaMessages(long id, Boolean onlyFiles = false); Task> getChatHistory(long id, int limit = 30, int addOffset = 0); string getChatName(long id); Task getMessageFile(string chatId, int idMessage); diff --git a/TelegramDownloader/Data/TelegramService.cs b/TelegramDownloader/Data/TelegramService.cs index 65430e5..dc52f42 100644 --- a/TelegramDownloader/Data/TelegramService.cs +++ b/TelegramDownloader/Data/TelegramService.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Components; +using BlazorBootstrap; +using Microsoft.AspNetCore.Components; +using System.Collections.Generic; +using System.Threading.Tasks; using TelegramDownloader.Data.db; using TelegramDownloader.Models; using TelegramDownloader.Services; @@ -330,10 +333,7 @@ public async Task> getAllMessages(long id, Boolean onlyFiles if (msgBase is Message msg) { ChatMessages cm2 = new ChatMessages(); - cm2.htmlMessage = client.EntitiesToHtml(msg.message, msg.entities); cm2.message = msg; - - cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); cm2.isDocument = false; if (msg.media is MessageMediaDocument { document: Document document }) { @@ -345,6 +345,8 @@ public async Task> getAllMessages(long id, Boolean onlyFiles cm.Add(cm2); } else { + cm2.htmlMessage = client.EntitiesToHtml(msg.message, msg.entities); + cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); cm.Add(cm2); } @@ -356,6 +358,141 @@ public async Task> getAllMessages(long id, Boolean onlyFiles return cm; } + public async Task> getAllMediaMessages(long id, Boolean onlyFiles = false) + { + List cm = new List(); + InputPeer peer = chats.chats[id]; + int size = 100; + int page = 1; + + var messages = await client.Messages_GetHistory(peer, add_offset: page -1, limit: size); + if (messages.Messages.Length == 0) return await Task.FromResult(cm); + foreach (MessageBase msgBase in messages.Messages) + { + if (msgBase is Message msg) + { + ChatMessages cm2 = new ChatMessages(); + cm2.message = msg; + cm2.isDocument = false; + if (msg.media is MessageMediaDocument { document: Document document }) + { + cm2.isDocument = true; + } + if (onlyFiles) + { + if (cm2.isDocument) + cm.Add(cm2); + } + else + { + cm2.htmlMessage = client.EntitiesToHtml(msg.message, msg.entities); + cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); + cm.Add(cm2); + } + } + } + + int totalMessages = messages.Count; + int gettedMessages = messages.Messages.Length; + int maxParalellTasks = 50; + int totalTasks = 0; + page++; + List>> tasks = new List>>(); + + while (gettedMessages < totalMessages) + { + int sizeGetted = gettedMessages + size >= totalMessages ? totalMessages - gettedMessages : size; + + tasks.Add(getPaginatedMessagesAsync(peer, page, sizeGetted, onlyFiles)); + page++; + totalTasks++; + gettedMessages += sizeGetted; + + if (totalTasks == maxParalellTasks || gettedMessages == totalMessages) + { + var results = await Task.WhenAll(tasks); + totalTasks = 0; + + foreach (var result in results) + { + cm.AddRange(result); + } + } + } + + return await Task.FromResult(cm); + } + + private async Task> getPaginatedMessagesAsync(InputPeer peer, int page, int size, Boolean onlyFiles = false) + { + List cm = new List(); + var messages = await client.Messages_GetHistory(peer, add_offset: (page - 1) * 100, limit: size); + if (messages.Messages.Length == 0) return cm; + foreach (MessageBase msgBase in messages.Messages) + { + if (msgBase is Message msg) + { + ChatMessages cm2 = new ChatMessages(); + cm2.message = msg; + cm2.isDocument = false; + if (msg.media is MessageMediaDocument { document: Document document }) + { + cm2.isDocument = true; + } + if (onlyFiles) + { + if (cm2.isDocument) + cm.Add(cm2); + } + else + { + cm2.htmlMessage = client.EntitiesToHtml(msg.message, msg.entities); + cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); + cm.Add(cm2); + } + + + } + } + return cm; + } + + public async Task> getPaginatedMessages(long id, int page, int size, Boolean onlyFiles = false) + { + List cm = new List(); + InputPeer peer = chats.chats[id]; + + var messages = await client.Messages_GetHistory(peer, add_offset: (page - 1) * 100, limit: size); + if (messages.Messages.Length == 0) return await Task.FromResult(new GridDataProviderResult { Data = cm, TotalCount = messages.Count }); + foreach (MessageBase msgBase in messages.Messages) + { + if (msgBase is Message msg) + { + ChatMessages cm2 = new ChatMessages(); + cm2.message = msg; + cm2.isDocument = false; + if (msg.media is MessageMediaDocument { document: Document document }) + { + cm2.isDocument = true; + } + if (onlyFiles) + { + if (cm2.isDocument) + cm.Add(cm2); + } + else + { + cm2.htmlMessage = client.EntitiesToHtml(msg.message, msg.entities); + cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); + cm.Add(cm2); + } + + + } + } + return await Task.FromResult(new GridDataProviderResult { Data = cm, TotalCount = messages.Count }); + } + public async Task> getAllFileMessages(long id) { List cm = new List(); diff --git a/TelegramDownloader/Models/GeneralConfig.cs b/TelegramDownloader/Models/GeneralConfig.cs index 4bd4374..9b04aca 100644 --- a/TelegramDownloader/Models/GeneralConfig.cs +++ b/TelegramDownloader/Models/GeneralConfig.cs @@ -74,6 +74,7 @@ public class GeneralConfig public bool CheckHash { get; set; } = false; public bool ShouldShowCaptionPath { get; set; } = false; public bool ShouldShowLogInTerminal { get; set; } = false; + public bool ShouldShowPaginatedFileChannel { get; set; } = false; public List FavouriteChannels { get; set; } = new List(); } diff --git a/TelegramDownloader/Pages/Config.razor b/TelegramDownloader/Pages/Config.razor index 98ca7c5..0390990 100644 --- a/TelegramDownloader/Pages/Config.razor +++ b/TelegramDownloader/Pages/Config.razor @@ -53,6 +53,15 @@
+ + +
+
+ + +
+ +
diff --git a/TelegramDownloader/Pages/FileGrid.razor b/TelegramDownloader/Pages/FileGrid.razor index 61bb5cf..4f01531 100644 --- a/TelegramDownloader/Pages/FileGrid.razor +++ b/TelegramDownloader/Pages/FileGrid.razor @@ -19,48 +19,49 @@ Class="table table-hover table-bordered table-striped" DataProvider="ChatsDataProvider" AllowPaging="true" -PageSize="25" +PageSize="@pageSize" AllowSelection="true" SelectionMode="GridSelectionMode.Multiple" @bind-SelectedItems="@SelectedItems" -PageSizeSelectorVisible="true" +PageSizeSelectorVisible="false" PageSizeSelectorItems="@(new int[] { 25, 50, 100 })" +AllowFiltering="true" Responsive="true"> - + @context.message.id - + @context.message.message - + @context.message.Date.ToString("dd/MM/yyyy HH:mm:ss") - + @(context.message.edit_date.Year > 1900 - ? context.message.edit_date.ToString("dd/MM/yyyy HH:mm:ss") - : "") + ? context.message.edit_date.ToString("dd/MM/yyyy HH:mm:ss") + : "") - + @if (context.message.media is MessageMediaDocument { document: Document document }) { @document.Filename } - + @if (context.message.media is MessageMediaDocument { document: Document document }) { @HelperService.SizeSuffix(document.size) } - + @if (context.message.media is MessageMediaDocument { document: Document document }) { @document.mime_type } - + @context.videoThumb @if (context.message.media is MessageMediaPhoto { photo: Photo photo }) { @@ -84,15 +85,17 @@ Responsive="true"> [Parameter] public string id { get; set; } public List messages = default!; + private int pageSize = 100; private Modals.DowloadFromTelegram Modal { get; set; } private HashSet SelectedItems { get; set; } = new(); private async Task> ChatsDataProvider(GridDataProviderRequest request) { - if (messages is null) { + if (messages is null) + { PreloadService.Show(SpinnerColor.Light, "Loading data..."); - messages = await ts.getAllMessages(Convert.ToInt64(id), true); + messages = await ts.getAllMediaMessages(Convert.ToInt64(id), true); PreloadService.Hide(); } diff --git a/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor b/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor index 03df9ac..7a13315 100644 --- a/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor +++ b/TelegramDownloader/Pages/Modals/DowloadFromTelegram.razor @@ -85,6 +85,7 @@ if (isDownloadingChatMessages) { chatMessages.ForEach(message => fs.DownloadFileFromChat(message, null, ddtree.selectedNode == null ? null : ddtree.selectedNode.FirstOrDefault(), null)); + Close(); return; } // ts. diff --git a/TelegramDownloader/Services/TransactionInfoService.cs b/TelegramDownloader/Services/TransactionInfoService.cs index 73cede0..8f5a46e 100644 --- a/TelegramDownloader/Services/TransactionInfoService.cs +++ b/TelegramDownloader/Services/TransactionInfoService.cs @@ -85,8 +85,8 @@ public void stopTimer() private void OnTimedEvent(Object source, ElapsedEventArgs e) { - _logger.LogInformation("El evento Elapsed se disparó a las {0:HH:mm:ss.fff}", e.SignalTime); - _logger.LogInformation("Upload: {0}, download: {1}", bytesUploaded, bytesDownloaded); + //_logger.LogInformation("El evento Elapsed se disparó a las {0:HH:mm:ss.fff}", e.SignalTime); + //_logger.LogInformation("Upload: {0}, download: {1}", bytesUploaded, bytesDownloaded); if (bytesDownloaded > 0) setDownloadSpeed(HelperService.SizeSuffixPerTime(bytesDownloaded)); if (bytesUploaded > 0) @@ -104,7 +104,6 @@ public async Task addDownloadBytes(long bytes) public async Task addUploadBytes(long bytes) { - _logger.LogInformation("Add bytes to upload {0}", bytes); UploadBytesMutex.WaitOne(); bytesUploaded += bytes; UploadBytesMutex.ReleaseMutex(); @@ -132,7 +131,7 @@ public void setDownloadSpeed(String speed) public void setUploadSpeed(String speed) { - _logger.LogInformation("Set bytes speed upload {0}", speed); + //_logger.LogInformation("Set bytes speed upload {0}", speed); uploadSpeed = speed; TaskEventChanged.Invoke(null, EventArgs.Empty); } diff --git a/TelegramDownloader/Shared/NavMenu.razor b/TelegramDownloader/Shared/NavMenu.razor index 0efb2db..6993525 100644 --- a/TelegramDownloader/Shared/NavMenu.razor +++ b/TelegramDownloader/Shared/NavMenu.razor @@ -11,7 +11,7 @@ @inject IJSRuntime JS; - + @@ -76,9 +76,12 @@
- + @if (GeneralConfigStatic.config.ShouldShowPaginatedFileChannel) + { + + } @@ -112,9 +115,13 @@
- + @if(GeneralConfigStatic.config.ShouldShowPaginatedFileChannel) + { + + } + @@ -197,9 +204,12 @@
- + @if (GeneralConfigStatic.config.ShouldShowPaginatedFileChannel) + { + + } From b0b0cc639359f31136bb71c71619a4249be96f08 Mon Sep 17 00:00:00 2001 From: mateof Date: Sat, 6 Sep 2025 18:26:47 +0200 Subject: [PATCH 03/10] feat: add media streaming from telegram (#32) --- .../Controllers/FileController.cs | 234 +++++++++++++++++- TelegramDownloader/Data/FileService.cs | 2 +- TelegramDownloader/Data/ITelegramService.cs | 1 + TelegramDownloader/Data/TelegramService.cs | 106 +++++++- TelegramDownloader/Data/db/DbService.cs | 2 +- .../Pages/Partials/impl/FileManagerImpl.razor | 81 ++++-- TelegramDownloader/TelegramDownloader.csproj | 14 +- 7 files changed, 398 insertions(+), 42 deletions(-) diff --git a/TelegramDownloader/Controllers/FileController.cs b/TelegramDownloader/Controllers/FileController.cs index 560e883..59e0bfe 100644 --- a/TelegramDownloader/Controllers/FileController.cs +++ b/TelegramDownloader/Controllers/FileController.cs @@ -9,13 +9,16 @@ using Syncfusion.Blazor.Inputs; using Syncfusion.EJ2.FileManager.Base; using Syncfusion.EJ2.FileManager.PhysicalFileProvider; +using Syncfusion.EJ2.Notifications; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Linq; using System.Net; using System.Net.Http.Headers; using System.Runtime.Serialization.Formatters.Binary; +using System.Web; using System.Xml.Linq; using TelegramDownloader.Data; using TelegramDownloader.Data.db; @@ -234,7 +237,7 @@ public async Task GetImage(string path) } [Route("GetFile/{id}")] - public async Task GetFile(string id, string? idChannel, string? idFile ) + public async Task GetFile(string id, string? idChannel, string? idFile) { var fileName = id; var mimeType = FileService.getMimeType(id.Split(".").Last()); @@ -242,7 +245,7 @@ public async Task GetFile(string id, string? idChannel, string? i if ( file == null ) { // HttpResponseMessage fullResponse = new HttpResponseMessage(HttpStatusCode.OK); // Request.CreateResponse(HttpStatusCode.OK); - Message idM = await _ts.getMessageFile(idChannel, Convert.ToInt32(idFile)); + TL.Message idM = await _ts.getMessageFile(idChannel, Convert.ToInt32(idFile)); ChatMessages cm = new ChatMessages(); cm.message = idM; file = new FileStream(System.IO.Path.Combine(FileService.TEMPDIR, "_temp", $"{idChannel}-{idFile}-{id}"), FileMode.Create, FileAccess.ReadWrite); @@ -253,6 +256,8 @@ public async Task GetFile(string id, string? idChannel, string? i await _ts.DownloadFileAndReturn(cm, file, model: dm); file.Position = 0; } + var request = HttpContext.Request; + var rangeHeader = request.Headers["Range"].ToString(); return new FileStreamResult(file, mimeType) { @@ -260,6 +265,231 @@ public async Task GetFile(string id, string? idChannel, string? i EnableRangeProcessing = true }; + } + + [Route("GetFileStream/{idChannel}/{idFile}/{name}")] + public async Task GetFileStream(string idChannel, string idFile, string name) + { + var fileName = name; + var mimeType = FileService.getMimeType(name.Split(".").Last()); + + var request = HttpContext.Request; + var rangeHeader = request.Headers["Range"].ToString(); + + var file = await _fs.getItemById(idChannel, idFile); + + TL.Message idM = await _ts.getMessageFile(idChannel, file.MessageId ?? file.ListMessageId.FirstOrDefault()); + + long totalLength = 0; // (await _fs.getItemById(idChannel, idFile)).Size; + + if(idM.media is MessageMediaDocument { document: Document document }) + { + totalLength = document.size; + } + + long from = 0; + long initialFrom = 0; + long to = 0; + + if (!string.IsNullOrEmpty(rangeHeader) && rangeHeader.StartsWith("bytes=")) + { + var range = rangeHeader.Replace("bytes=", "").Split('-'); + from = long.Parse(range[0]); + initialFrom = from; + if (range.Length > 1 && !string.IsNullOrEmpty(range[1])) + to = long.Parse(range[1]); + } + + if (from > 0) + { + from = (from / 524288) * 524288; + } + + Console.WriteLine("Fom:" + from); + + if (to == 0) + if (string.IsNullOrEmpty(rangeHeader) || from == 0) + to = (12 * 524288) + from; + else + to = (5 * 524288) + from; + else + to = ((to + 524288) / 524288) * 524288; + + if (to > totalLength) + { + to = totalLength; + } + + if (totalLength == initialFrom) + { + Response.StatusCode = 416; // Range Not Satisfiable + Response.Headers["Content-Range"] = $"bytes */{totalLength}"; + return new EmptyResult(); + } + + long length = to - from; + //Console.WriteLine("To length: " + to); + //Console.WriteLine("future download length: " + length); + + byte[] data = await _ts.DownloadFileStream(idM, from, (int)length); + + //Console.WriteLine("Total download length: " + data.Length); + + long skipedBytes = initialFrom - from - (length - data.Length); + + if (skipedBytes < 0) skipedBytes = 0; + + if (skipedBytes > data.Length) + return StatusCode(500, "No hay suficientes bytes en el bloque descargado"); + + long dataLength = data.Length - skipedBytes; + Response.ContentLength = dataLength; //(dataLength + initialFrom) >= totalLength ? totalLength - initialFrom : dataLength; + // Response.StatusCode = (int)HttpStatusCode.PartialContent; // 206 + Response.StatusCode = StatusCodes.Status206PartialContent; // StatusCodes.Status206PartialContent; + Response.ContentType = mimeType; + Response.Headers["Content-Disposition"] = $"inline; filename=\"{HttpUtility.UrlEncode(fileName)}\""; + Response.Headers.Add("Content-Range", $"bytes {(initialFrom >= totalLength ? totalLength - 1 : initialFrom)}-{(initialFrom >= totalLength ? totalLength - 1 : initialFrom + Response.ContentLength - 1)}/{totalLength}"); + Response.Headers.Add("Accept-Ranges", "bytes"); + + //if (Request.Headers.ContainsKey("Range")) + //{ + // Console.WriteLine("Range header recibido:"); + // Console.WriteLine(Request.Headers["Range"]); + //} + + //foreach (var header in Response.Headers) + //{ + // Console.WriteLine($"{header.Key}: {header.Value}"); + //} + + + // Console.WriteLine("Skiped bytes: " + skipedBytes); + + var stream = new MemoryStream(data, (int)skipedBytes, (int)Response.ContentLength); + stream.Position = 0; + + // Console.WriteLine("Real Data length: " + stream.Length); + + // Console.WriteLine("---------------------------------------------------"); + + var cancellationToken = HttpContext.RequestAborted; + + try + { + await stream.CopyToAsync(Response.Body, 64 * 1024, cancellationToken); + await Response.Body.FlushAsync(cancellationToken); + + return new EmptyResult(); + } + catch (OperationCanceledException) + { + Console.WriteLine("❌ El cliente cerró la conexión."); + } + + return new EmptyResult(); + + } + + [Route("GetFileStream2/{idChannel}/{idFile}/{name}")] + public async Task GetFileStream2(string idChannel, string idFile, string name) + { + var fileName = name; + var mimeType = FileService.getMimeType(name.Split(".").Last()); + + var request = HttpContext.Request; + var rangeHeader = request.Headers["Range"].ToString(); + + var file = await _fs.getItemById(idChannel, idFile); + + // HttpResponseMessage fullResponse = new HttpResponseMessage(HttpStatusCode.OK); // Request.CreateResponse(HttpStatusCode.OK); + TL.Message idM = await _ts.getMessageFile(idChannel, file.MessageId ?? file.ListMessageId.FirstOrDefault()); + //byte[] data = await _ts.DownloadFileStream(idM, 0, 512); + //var stream = new MemoryStream(data); + + // Supón que puedes obtener el tamaño total del archivo remoto + long totalLength = (await _fs.getItemById(idChannel, idFile)).Size; + + long from = 0; + long initialFrom = 0; + long to = 0; + + + + if (!string.IsNullOrEmpty(rangeHeader) && rangeHeader.StartsWith("bytes=")) + { + var range = rangeHeader.Replace("bytes=", "").Split('-'); + from = long.Parse(range[0]); + initialFrom = from; + if (range.Length > 1 && !string.IsNullOrEmpty(range[1])) + to = long.Parse(range[1]); + } + + if (from > 0) + { + from = (from / 524288) * 524288; + } + + if (to == 0) + to = (25 * 524288) + from; + else + to = ((to + 524288) / 524288) * 524288; + + if (to > totalLength) + { + to = totalLength - 1; + } + + long length = to - from; + + byte[] data = await _ts.DownloadFileStream(idM, from, (int)length); + + long skipedBytes = initialFrom - from; // (data.Length + initialFrom) >= totalLength ? data.Length - (totalLength - initialFrom) : initialFrom - from; + + if (skipedBytes < 0) skipedBytes = 0; + + if (skipedBytes >= data.Length) + return StatusCode(500, "No hay suficientes bytes en el bloque descargado"); + + long dataLength = data.Length - skipedBytes; + Response.ContentLength = (dataLength + initialFrom) >= totalLength ? totalLength - initialFrom : dataLength; + // Response.StatusCode = (int)HttpStatusCode.PartialContent; // 206 + Response.StatusCode = StatusCodes.Status206PartialContent; + Response.ContentType = mimeType; + Response.Headers.Add("Content-Range", $"bytes {initialFrom}-{(initialFrom + Response.ContentLength - 1)}/{totalLength}"); + Response.Headers.Add("Accept-Ranges", "bytes"); + + if (Request.Headers.ContainsKey("Range")) + { + Console.WriteLine("Range header recibido:"); + Console.WriteLine(Request.Headers["Range"]); + } + + foreach (var header in Response.Headers) + { + Console.WriteLine($"{header.Key}: {header.Value}"); + } + + + + + var stream = new MemoryStream(data, (int)skipedBytes, (int)Response.ContentLength); + + Console.WriteLine("Real Data length: " + stream.Length); + + Console.WriteLine("---------------------------------------------------"); + + //return new FileStreamResult(stream, mimeType); + var cancellationToken = HttpContext.RequestAborted; + + await stream.CopyToAsync(Response.Body, 64 * 1024, cancellationToken); + await Response.Body.FlushAsync(cancellationToken); + + return new EmptyResult(); + // return File(stream, mimeType, enableRangeProcessing: false); + //return new FileStreamResult(stream, mimeType) + //{ + // EnableRangeProcessing = false + //}; } diff --git a/TelegramDownloader/Data/FileService.cs b/TelegramDownloader/Data/FileService.cs index eae4b4a..94c5b67 100644 --- a/TelegramDownloader/Data/FileService.cs +++ b/TelegramDownloader/Data/FileService.cs @@ -117,7 +117,7 @@ public class FileService : IFileService {"mid", "audio/midi"}, {"midi", "audio/midi"}, {"mif", "application/vnd.mif"}, - {"mkv", "video/webm" }, + {"mkv", "video/x-matroska" }, {"mov", "video/quicktime"}, {"movie", "video/x-sgi-movie"}, {"mp2", "audio/mpeg"}, diff --git a/TelegramDownloader/Data/ITelegramService.cs b/TelegramDownloader/Data/ITelegramService.cs index 33436be..71fbdf4 100644 --- a/TelegramDownloader/Data/ITelegramService.cs +++ b/TelegramDownloader/Data/ITelegramService.cs @@ -17,6 +17,7 @@ public interface ITelegramService Task joinChatInvitationHash(string? hash); Task CallQrGenerator(Action func, CancellationToken ct, bool logoutFirst = false); Task DownloadFile(ChatMessages message, string fileName = null, string folder = null, DownloadModel model = null, bool shouldAddToList = false); + Task DownloadFileStream(Message message, long offset, int limit); Task DownloadFileAndReturn(ChatMessages message, Stream ms = null, string fileName = null, string folder = null, DownloadModel model = null); Task> GetFouriteChannels(bool mustRefresh = true); Task AddFavouriteChannel(long id); diff --git a/TelegramDownloader/Data/TelegramService.cs b/TelegramDownloader/Data/TelegramService.cs index dc52f42..92f8c92 100644 --- a/TelegramDownloader/Data/TelegramService.cs +++ b/TelegramDownloader/Data/TelegramService.cs @@ -1,11 +1,19 @@ using BlazorBootstrap; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Syncfusion.Blazor.Inputs; +using Syncfusion.Blazor.Kanban.Internal; +using Syncfusion.Blazor.Sparkline.Internal; using System.Collections.Generic; +using System.Reflection.Metadata; using System.Threading.Tasks; using TelegramDownloader.Data.db; using TelegramDownloader.Models; using TelegramDownloader.Services; using TL; +using TL.Layer23; +using TL.Methods; +using WTelegram; namespace TelegramDownloader.Data { @@ -13,6 +21,7 @@ public class TelegramService : ITelegramService { public static bool isPremium = false; public static int splitSizeGB = 2; + private const int FILESPLITSIZE = 524288; // 512 * 1024; private TransactionInfoService _tis { get; set; } private IDbService _db { get; set; } private static WTelegram.Client client = null; @@ -335,7 +344,7 @@ public async Task> getAllMessages(long id, Boolean onlyFiles ChatMessages cm2 = new ChatMessages(); cm2.message = msg; cm2.isDocument = false; - if (msg.media is MessageMediaDocument { document: Document document }) + if (msg.media is MessageMediaDocument { document: TL.Document document }) { cm2.isDocument = true; } @@ -374,7 +383,7 @@ public async Task> getAllMediaMessages(long id, Boolean onlyF ChatMessages cm2 = new ChatMessages(); cm2.message = msg; cm2.isDocument = false; - if (msg.media is MessageMediaDocument { document: Document document }) + if (msg.media is MessageMediaDocument { document: TL.Document document }) { cm2.isDocument = true; } @@ -435,7 +444,7 @@ private async Task> getPaginatedMessagesAsync(InputPeer peer, ChatMessages cm2 = new ChatMessages(); cm2.message = msg; cm2.isDocument = false; - if (msg.media is MessageMediaDocument { document: Document document }) + if (msg.media is MessageMediaDocument { document: TL.Document document }) { cm2.isDocument = true; } @@ -471,7 +480,7 @@ public async Task> getPaginatedMessages(lon ChatMessages cm2 = new ChatMessages(); cm2.message = msg; cm2.isDocument = false; - if (msg.media is MessageMediaDocument { document: Document document }) + if (msg.media is MessageMediaDocument { document: TL.Document document }) { cm2.isDocument = true; } @@ -511,7 +520,7 @@ public async Task> getAllFileMessages(long id) cm2.user = messages.UserOrChat(msg.From ?? msg.Peer); cm2.isDocument = false; - if (msg.media is MessageMediaDocument { document: Document document }) + if (msg.media is MessageMediaDocument { document: TL.Document document }) { cm2.isDocument = true; } @@ -547,7 +556,7 @@ public async Task> getChatHistory(long id, int limit = 30, in cm2.user = mess.UserOrChat(mb.From ?? mb.Peer); cm2.isDocument = false; - if (msg.media is MessageMediaDocument { document: Document document }) + if (msg.media is MessageMediaDocument { document: TL.Document document }) { cm2.isDocument = true; } @@ -601,6 +610,84 @@ public async Task RemoveFavouriteChannel(long id) await GetFouriteChannels(); } } + public async Task DownloadFileStream(Message message, long offset, int limit) + { + int totalLimit = limit; + long currentOffset = offset; + Byte[] response; + if (message is Message msg && msg.media is MessageMediaDocument doc) + { + try + { + using (var memoryStream = new MemoryStream()) + { + while (totalLimit > 0) + { + int totalDownloadBytes = FILESPLITSIZE; + if (totalLimit >= FILESPLITSIZE) + { + totalDownloadBytes = FILESPLITSIZE; + + } + //else if (totalLimit >= 4096) + //{ + // // Busca el mayor múltiplo de 4096 menor o igual a totalLimit + // totalDownloadBytes = (totalLimit / 4096) * 4096; + //} + else + { + // Último trozo, menor de 4096 + totalDownloadBytes = FILESPLITSIZE; // totalLimit; + //totalDownloadBytes = totalLimit; + } + + InputDocument inputFile = doc.document; + var location = new InputDocumentFileLocation + { + id = inputFile.id, + access_hash = inputFile.access_hash, + file_reference = inputFile.file_reference, + thumb_size = "" + }; + + //var file = await client.Invoke(new Upload_GetFile + //{ + // location = new InputDocumentFileLocation + // { + // id = inputFile.id, + // access_hash = inputFile.access_hash, + // file_reference = inputFile.file_reference, + // thumb_size = "" + // }, + // offset = currentOffset, + // limit = totalDownloadBytes + //}); + + var file = await client.Upload_GetFile(location, currentOffset, limit: totalDownloadBytes); + + if (file is Upload_File uploadFile) + { + //uploadFile.WriteTL(new BinaryWriter(memoryStream)); + var fileBytes = uploadFile.bytes; + memoryStream.Write(fileBytes, 0, fileBytes.Length); + currentOffset += (totalDownloadBytes); + totalLimit -= (totalDownloadBytes); + continue; + } + throw new InvalidOperationException("Unexpected file type returned."); + } + + return memoryStream.ToArray(); + } + } + catch (Exception e) + { + throw new InvalidOperationException("Unexpected file type returned."); + } + } + + throw new ArgumentException("Invalid message or media type."); + } public async Task DownloadFileAndReturn(ChatMessages message, Stream ms = null, string fileName = null, string folder = null, DownloadModel model = null) { @@ -613,7 +700,7 @@ public async Task DownloadFileAndReturn(ChatMessages message, Stream ms model.m = message; model.channel = message.user; - if (message.message.media is MessageMediaDocument { document: Document document }) + if (message.message.media is MessageMediaDocument { document: TL.Document document }) { model._transmitted = 0; @@ -628,6 +715,7 @@ public async Task DownloadFileAndReturn(ChatMessages message, Stream ms MemoryStream dest = new MemoryStream(); // using var dest = new FileStream($"{Path.Combine(folder != null ? folder : Path.Combine(Environment.CurrentDirectory, "download"), filename)}", FileMode.Create, FileAccess.Write); await client.DownloadFileAsync(document, ms ?? dest, null, model.ProgressCallback); + return ms ?? dest; // fileStream.CopyTo(dest); Console.WriteLine("Download finished"); @@ -640,7 +728,7 @@ public async Task DownloadFileAndReturn(ChatMessages message, Stream ms Console.WriteLine("Downloading " + filename); MemoryStream dest = new MemoryStream(); // using var dest = new FileStream($"{Path.Combine(Environment.CurrentDirectory!, "wwwroot", "img", "telegram", filename)}", FileMode.Create, FileAccess.Write); - var type = await client.DownloadFileAsync(photo, ms ?? dest, null, model.ProgressCallback); + var type = await client.DownloadFileAsync(photo, ms ?? dest, (PhotoSizeBase)null, model.ProgressCallback); dest.Close(); // necessary for the renaming Console.WriteLine("Download finished"); //if (type is not Storage_FileType.unknown and not Storage_FileType.partial) @@ -666,7 +754,7 @@ public async Task DownloadFile(ChatMessages message, string fileName = n model.m = message; model.channel = message.user; - if (message.message.media is MessageMediaDocument { document: Document document }) + if (message.message.media is MessageMediaDocument { document: TL.Document document }) { model._transmitted = 0; diff --git a/TelegramDownloader/Data/db/DbService.cs b/TelegramDownloader/Data/db/DbService.cs index cc37686..85f6086 100644 --- a/TelegramDownloader/Data/db/DbService.cs +++ b/TelegramDownloader/Data/db/DbService.cs @@ -333,7 +333,7 @@ public async Task> getAllFilesInDirectoryPath2(string { if (collectionName == null) collectionName = "directory"; - var result = await (await getDatabase(dbName).GetCollection(collectionName).FindAsync(Builders.Filter.Where(x => x.FilePath + "/" == path || x.FilterPath == path))).ToListAsync(); + var result = await (await getDatabase(dbName).GetCollection(collectionName).FindAsync(Builders.Filter.Where(x => x.FilterId == path || x.FilePath + "/" == path || x.FilterPath == path))).ToListAsync(); return result; } diff --git a/TelegramDownloader/Pages/Partials/impl/FileManagerImpl.razor b/TelegramDownloader/Pages/Partials/impl/FileManagerImpl.razor index e7504b8..4833273 100644 --- a/TelegramDownloader/Pages/Partials/impl/FileManagerImpl.razor +++ b/TelegramDownloader/Pages/Partials/impl/FileManagerImpl.razor @@ -6,6 +6,8 @@ @using TelegramDownloader.Data @using TelegramDownloader.Data.db @using TelegramDownloader.Models +@using TelegramDownloader.Pages.Modals.InfoModals +@using TelegramDownloader.Pages.Modals @using TelegramDownloader.Pages.Partials @inject IFileService fs @inject ITelegramService ts @@ -18,18 +20,18 @@ + ToolbarItemClicked="toolBarClicked" + ItemsMoving="ItemsMovingAsync" + ItemRenaming="ItemRenamingAsync" + Searching="SearchingAsync" + OnFileOpen="fileOpen" + FileSelection="FileSelectionAsync" + FileSelected="FileSelectedAsync" + OnRead="OnReadAsync" + FolderCreating="FolderCreatingAsync" + BeforeDownload="BeforeDownload" + ItemsDeleting="ItemsDeletingAsync" + ItemsUploaded="ItemsUploadedAsync"> @@ -37,6 +39,8 @@ + + @@ -53,6 +57,9 @@ public static string[] selectedItems = new string[] { "" }; public static bool downloadToServer = true; LocalFileManager lfm { get; set; } + private VideoPlayerModal videoPlayer { get; set; } = default!; + + private MediaUrlModal mediaUrlModal { get; set; } = default!; public string[] ContextItems = new string[] { "Open", "Delete", "Download", "Rename", "Details"}; @@ -68,6 +75,8 @@ new ToolBarItemModel() { Name = "Selection" }, new ToolBarItemModel() { Name = "View" }, new ToolBarItemModel() { Name = "Details" }, + new ToolBarItemModel() { Name = "UrlMedia", Text = "Url Media", TooltipText = "Url Media", PrefixIcon = "bi bi-collection-play-fill", Visible = false } + }; // protected override async Task OnParametersSetAsync() @@ -81,6 +90,7 @@ } + protected override async Task OnInitializedAsync() { if (!isShared) @@ -121,23 +131,24 @@ // args.Response = await FileManagerService.Rename(args.Path, args.File.Name, args.NewName, false, args.ShowFileExtension, args.File); } - public async Task FileOpen(FileOpenEventArgs args) + public async Task fileOpen(FileOpenEventArgs args) { - if (!isShared && args != null && args.FileDetails != null && args.FileDetails.IsFile && new List { ".mp3", ".ogg", ".flac", ".aac", ".wav" }.Contains(args.FileDetails.Type.ToLower())) + if (args != null && args.FileDetails != null && args.FileDetails.IsFile && new List { ".mp3", ".ogg", ".flac", ".aac", ".wav" }.Contains(args.FileDetails.Type.ToLower())) { await playAudio(args); - // string localdir = "/" + FileService.STATICRELATIVELOCALDIR.Replace("\\", "/"); - // string path = Path.Combine(localdir, args.FileDetails.FilterPath.Substring(1).Replace("\\", "/"), args.FileDetails.Name); - // await JSRuntime.InvokeVoidAsync("open", path, "_blank"); + } + else + { + if (args != null && args.FileDetails != null && args.FileDetails.IsFile && new List { ".mov", ".mp4", ".m4v", ".ogv", ".webm", ".flv", ".f4v" }.Contains(args.FileDetails.Type.ToLower())) + { + videoPlayer.ShowModal(getMediaUrl(args.FileDetails.Id, args.FileDetails.Name)); + } } } private async Task playAudio(FileOpenEventArgs args) { - var file = await fs.getItemById(id, args.FileDetails.Id); - string path = MyNavigationManager.BaseUri + "api/file/getfile/" + args.FileDetails.Name + $"?idChannel={id}&idFile={file.MessageId}"; - // string path = new System.Uri((Path.Combine(localdir, args.FileDetails.FilterPath.Substring(1).Replace("\\", "/"), args.FileDetails.Name)).Replace("\\", "/")).AbsoluteUri; - await JSRuntime.InvokeVoidAsync("openAudioPlayerModal", path, FileService.getMimeType(args.FileDetails.Name.Split(".").Last()), args.FileDetails.Name); + await JSRuntime.InvokeVoidAsync("openAudioPlayerModal", getMediaUrl(args.FileDetails.Id, args.FileDetails.Name), null, args.FileDetails.Name); } public async Task SearchingAsync(SearchEventArgs args) @@ -188,6 +199,18 @@ Console.WriteLine(""); } + private async Task ShowMediaURLModal(string idFile, String name) + { + mediaUrlModal.url = getMediaUrl(idFile, name); + await mediaUrlModal.OnShowModalClick(); + } + + private string getMediaUrl(string idFile, String name) + { + string localdir = MyNavigationManager.BaseUri; + return new System.Uri(Path.Combine(localdir, "api/file/GetFileStream", id, idFile, name).Replace("\\", "/")).AbsoluteUri; + } + public async Task toolBarClicked(ToolbarClickEventArgs args) { if (args.Item.Text == "Download To Local") @@ -207,6 +230,10 @@ { MyNavigationManager.NavigateTo("/api/file/share/" + id + $"?bsonId={args.FileDetails.FirstOrDefault().Id}&fileName={args.FileDetails.FirstOrDefault().Name}", true); } + if (args.Item.Text == "Url Media" && args.FileDetails.Count() > 0) + { + await ShowMediaURLModal(args.FileDetails[0].Id, args.FileDetails[0].Name); + } Console.WriteLine(""); } @@ -226,7 +253,17 @@ public async Task FileSelectionAsync(FileSelectionEventArgs args) { - Console.WriteLine(""); + List selectedList = fm.GetSelectedFiles(); + if ((selectedList.Count() + (args.Action == "UnSelect" ? -1 : 1)) == 1) + { + if (args.FileDetails.IsFile) + { + Items.Where(x => x.Name == "UrlMedia").FirstOrDefault().Visible = true; + return; + } + + } + Items.Where(x => x.Name == "UrlMedia").FirstOrDefault().Visible = false; } public async Task FileSelectedAsync(FileSelectEventArgs args) diff --git a/TelegramDownloader/TelegramDownloader.csproj b/TelegramDownloader/TelegramDownloader.csproj index de1dc77..f3d1bac 100644 --- a/TelegramDownloader/TelegramDownloader.csproj +++ b/TelegramDownloader/TelegramDownloader.csproj @@ -13,15 +13,15 @@ - + - + - - - - - + + + + + From 7f9345019312018c7c56126951870852c261d043 Mon Sep 17 00:00:00 2001 From: mateof Date: Tue, 9 Sep 2025 19:00:09 +0200 Subject: [PATCH 04/10] feat: media stream for huge files (#34) --- .../Controllers/FileController.cs | 35 +++++++++++++++---- TelegramDownloader/TelegramDownloader.csproj | 6 ++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/TelegramDownloader/Controllers/FileController.cs b/TelegramDownloader/Controllers/FileController.cs index 59e0bfe..d431d35 100644 --- a/TelegramDownloader/Controllers/FileController.cs +++ b/TelegramDownloader/Controllers/FileController.cs @@ -277,29 +277,50 @@ public async Task GetFileStream(string idChannel, string idFile, var rangeHeader = request.Headers["Range"].ToString(); var file = await _fs.getItemById(idChannel, idFile); + long totalLength = file.Size; TL.Message idM = await _ts.getMessageFile(idChannel, file.MessageId ?? file.ListMessageId.FirstOrDefault()); - long totalLength = 0; // (await _fs.getItemById(idChannel, idFile)).Size; + long totalPartialFileLength = 0; - if(idM.media is MessageMediaDocument { document: Document document }) + if (idM.media is MessageMediaDocument { document: Document document }) { - totalLength = document.size; + totalPartialFileLength = document.size; } long from = 0; long initialFrom = 0; + long initialPartFrom = 0; long to = 0; + long accumulatePart = 0; if (!string.IsNullOrEmpty(rangeHeader) && rangeHeader.StartsWith("bytes=")) { var range = rangeHeader.Replace("bytes=", "").Split('-'); from = long.Parse(range[0]); initialFrom = from; + initialPartFrom = from; if (range.Length > 1 && !string.IsNullOrEmpty(range[1])) to = long.Parse(range[1]); } + if (from >= totalPartialFileLength) + { + Console.WriteLine("Take a file part: from: " + from + ", totalPartialFileLength: " + totalPartialFileLength); + int filePart = 0; + while(from >= totalPartialFileLength) + { + filePart++; + from -= totalPartialFileLength; + } + idM = await _ts.getMessageFile(idChannel, file.ListMessageId[filePart]); + if (idM.media is MessageMediaDocument { document: Document document2 }) + { + totalPartialFileLength = document2.size; + } + initialPartFrom = from; + } + if (from > 0) { from = (from / 524288) * 524288; @@ -315,12 +336,12 @@ public async Task GetFileStream(string idChannel, string idFile, else to = ((to + 524288) / 524288) * 524288; - if (to > totalLength) + if (to > totalPartialFileLength) { - to = totalLength; + to = totalPartialFileLength; } - if (totalLength == initialFrom) + if (totalPartialFileLength == initialPartFrom) { Response.StatusCode = 416; // Range Not Satisfiable Response.Headers["Content-Range"] = $"bytes */{totalLength}"; @@ -335,7 +356,7 @@ public async Task GetFileStream(string idChannel, string idFile, //Console.WriteLine("Total download length: " + data.Length); - long skipedBytes = initialFrom - from - (length - data.Length); + long skipedBytes = initialPartFrom - from - (length - data.Length); if (skipedBytes < 0) skipedBytes = 0; diff --git a/TelegramDownloader/TelegramDownloader.csproj b/TelegramDownloader/TelegramDownloader.csproj index f3d1bac..39718bc 100644 --- a/TelegramDownloader/TelegramDownloader.csproj +++ b/TelegramDownloader/TelegramDownloader.csproj @@ -15,13 +15,13 @@ - + - - + + From d1836227d22b50015802b585b8518aa38e75968e Mon Sep 17 00:00:00 2001 From: mateof Date: Tue, 23 Sep 2025 20:09:31 +0200 Subject: [PATCH 05/10] feat: add webdav and export files to strm (#39) feat: improves fix: error on download root files fix: error on download splitted files --- .../Controllers/FileController.cs | 14 + .../Controllers/WebDavController.cs | 81 +++++ TelegramDownloader/Data/FileService.cs | 298 +++++++++--------- TelegramDownloader/Data/IFileService.cs | 3 + TelegramDownloader/Data/ITelegramService.cs | 2 + TelegramDownloader/Data/TelegramService.cs | 142 ++++++++- TelegramDownloader/Data/db/DbService.cs | 59 +++- TelegramDownloader/Data/db/IDbService.cs | 2 + TelegramDownloader/Dockerfile | 12 + TelegramDownloader/Models/DownloadModel.cs | 6 +- TelegramDownloader/Models/FileModel.cs | 38 +++ TelegramDownloader/Models/GeneralConfig.cs | 26 ++ TelegramDownloader/Models/LoginModel.cs | 19 ++ TelegramDownloader/Pages/Config.razor | 11 + TelegramDownloader/Pages/FileManager.razor | 43 ++- .../Pages/Modals/AudioPlayerModal.razor | 2 +- .../Modals/InfoModals/MediaUrlModal.razor | 4 +- .../Pages/Partials/impl/FileManagerImpl.razor | 65 +++- .../Partials/impl/LocalFileManagerImpl.razor | 25 +- TelegramDownloader/Pages/WebDavInfo.razor | 125 ++++++++ TelegramDownloader/Services/HelperService.cs | 45 +++ .../Services/TransactionInfoService.cs | 1 + TelegramDownloader/Services/WebDavService.cs | 51 +++ TelegramDownloader/Shared/ConfigLayout.razor | 2 +- TelegramDownloader/Shared/MainLayout.razor | 1 + TelegramDownloader/TelegramDownloader.csproj | 2 +- TelegramDownloader/WebDav/requirements.txt | 3 + TelegramDownloader/WebDav/webdav_api_proxy.py | 199 ++++++++++++ 28 files changed, 1077 insertions(+), 204 deletions(-) create mode 100644 TelegramDownloader/Controllers/WebDavController.cs create mode 100644 TelegramDownloader/Pages/WebDavInfo.razor create mode 100644 TelegramDownloader/Services/WebDavService.cs create mode 100644 TelegramDownloader/WebDav/requirements.txt create mode 100644 TelegramDownloader/WebDav/webdav_api_proxy.py diff --git a/TelegramDownloader/Controllers/FileController.cs b/TelegramDownloader/Controllers/FileController.cs index 8405d82..4300b1f 100644 --- a/TelegramDownloader/Controllers/FileController.cs +++ b/TelegramDownloader/Controllers/FileController.cs @@ -267,6 +267,19 @@ public async Task GetFile(string id, string? idChannel, string? i }; } + [Route("strm")] + public async Task GetStrm([FromQuery] string idChannel, [FromQuery] string path, [FromQuery] string host) + { + String zip = await _fs.CreateStrmFiles(path, idChannel, host); + + FileStream fs = new FileStream(zip, FileMode.Open); + + return new FileStreamResult(fs, "application/octet-stream") + { + FileDownloadName = Path.GetFileName(zip) + }; + } + [Route("GetFileStream/{idChannel}/{idFile}/{name}")] public async Task GetFileStream(string idChannel, string idFile, string name) { @@ -275,6 +288,7 @@ public async Task GetFileStream(string idChannel, string idFile, var request = HttpContext.Request; var rangeHeader = request.Headers["Range"].ToString(); + Console.WriteLine("range: ", rangeHeader); var file = await _fs.getItemById(idChannel, idFile); long totalLength = file.Size; diff --git a/TelegramDownloader/Controllers/WebDavController.cs b/TelegramDownloader/Controllers/WebDavController.cs new file mode 100644 index 0000000..3c7e708 --- /dev/null +++ b/TelegramDownloader/Controllers/WebDavController.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Mvc; +using TelegramDownloader.Data.db; +using TelegramDownloader.Data; +using TelegramDownloader.Models; +using System.Text.Json; + +namespace TelegramDownloader.Controllers +{ + [Route("api/nodes")] + [ApiController] + public class WebDavController : ControllerBase + { + IDbService _db { get; set; } + + public WebDavController(IDbService db) + { + _db = db; + } + [HttpGet] + public async Task> webDavPaths([FromQuery] string path, [FromQuery] string depth) + { + Console.WriteLine("Peticion de webDavPaths: " + path); + var isFile = Path.HasExtension(path); + if (!path.EndsWith("/") && !isFile) + path = path + "/"; + if (String.IsNullOrEmpty(path)) + throw new BadHttpRequestException("Path is null or empty"); + string channel = path.Split("/")[1]; + if (String.IsNullOrEmpty(channel)) + throw new BadHttpRequestException("Channel is empty"); + path = path.Remove(0, path.IndexOf("/", 1)); + List files = new List(); + if (isFile) + { + var bsonFile = await _db.getFileByPath(channel, path); + if (!(bsonFile == null) && bsonFile.IsFile) + { + WebDavFileModel file = bsonFile.toWebDavFileModel(channel); + files = new List(); + files.Add(file); + } + } + if ((!isFile) || files.Count == 0) + { + if (depth == "0") + { + var bsonFile = await _db.getFileByPath(channel, path[..^1]); + if (bsonFile != null) + { + WebDavFileModel file = bsonFile.toWebDavFileModel(channel); + files = new List(); + files.Add(file); + } + + } + else + files = (await _db.getAllFilesInDirectoryPath(channel, path)).Select(file => file.toWebDavFileModel()).ToList(); + } + + // Console.WriteLine(JsonSerializer.Serialize(files)); + return files; + } + + [HttpGet("meta")] + public async Task webDavMetadata([FromQuery] string path) + { + Console.WriteLine("Peticion de webDavMetadata: " + path); + if (String.IsNullOrEmpty(path)) + throw new Exception("Path is null or empty"); + string channel = path.Split("/")[1]; + if (String.IsNullOrEmpty(channel)) + throw new Exception("Channel is empty"); + path = path.Remove(0, path.IndexOf("/", 1)); + var bsonFile = await _db.getFileByPath(channel, path); + if (bsonFile == null || !bsonFile.IsFile) + throw new FileNotFoundException(); + WebDavFileModel files = bsonFile.toWebDavFileModel(channel); + return files; + } + } +} diff --git a/TelegramDownloader/Data/FileService.cs b/TelegramDownloader/Data/FileService.cs index 94c5b67..5b039f8 100644 --- a/TelegramDownloader/Data/FileService.cs +++ b/TelegramDownloader/Data/FileService.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using MongoDB.Driver; using Newtonsoft.Json; +using SharpCompress.Archives; using SharpCompress.Common; using Syncfusion.Blazor.FileManager; using Syncfusion.Blazor.Inputs; @@ -17,6 +18,8 @@ using System.IO.Hashing; using System.Security.Cryptography; using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Channels; using System.Xml.Linq; using TelegramDownloader.Data.db; using TelegramDownloader.Models; @@ -28,7 +31,6 @@ namespace TelegramDownloader.Data public class FileService : IFileService { public static readonly List ImageExtensions = new List { ".JPG", ".JPEG", ".JPE", ".BMP", ".GIF", ".PNG" }; - public static readonly long MAXIMAGESIZE = 1024 * 1024 * 10; public static string IMGDIR = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "img", "telegram"); public static string LOCALDIR = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "local"); public static string RELATIVELOCALDIR = System.IO.Path.Combine("local"); @@ -230,6 +232,7 @@ public class FileService : IFileService {"xyz", "chemical/x-xyz"}, {"zip", "application/zip"} }; + public static List refreshChannelList = new List(); private PhysicalFileProvider operation = new PhysicalFileProvider(); private ITelegramService _ts { get; set; } @@ -237,6 +240,7 @@ public class FileService : IFileService private ILogger _logger { get; set; } private TransactionInfoService _tis { get; set; } private ToastService _toastService { get; set; } + private static Mutex refreshMutex = new Mutex(); const int MaxSize = 1024 * 1024 * 1000; // 1GB @@ -321,6 +325,7 @@ public async Task> ShareFile(string dbName, string bs public async Task> itemDeleteAsync(string dbName, ItemsDeleteEventArgs args) { string[] names = args.Files.Select(x => x.Name).ToArray(); + bool isMyChannel = _ts.isMyChat(Convert.ToInt64(dbName)); // args.Response = await FileManagerService.Delete(args.Path, names, args.Files.ToArray()); foreach (Syncfusion.Blazor.FileManager.FileManagerDirectoryContent File in args.Files) { @@ -339,13 +344,13 @@ public async Task> ShareFile(string dbName, string bs { foreach (int id in child.ListMessageId) { - if (!await _db.existItemByTelegramId(dbName, id)) + if (isMyChannel && !await _db.existItemByTelegramId(dbName, id)) await _ts.deleteFile(dbName, id); } } else { - if (!await _db.existItemByTelegramId(dbName, (int)child.MessageId)) + if (isMyChannel && !await _db.existItemByTelegramId(dbName, (int)child.MessageId)) await _ts.deleteFile(dbName, (int)child.MessageId); } } @@ -364,13 +369,13 @@ public async Task> ShareFile(string dbName, string bs { foreach (int id in entry.ListMessageId) { - if (!await _db.existItemByTelegramId(dbName, id)) + if (isMyChannel && !await _db.existItemByTelegramId(dbName, id)) await _ts.deleteFile(dbName, id); } } else { - if (!await _db.existItemByTelegramId(dbName, (int)entry.MessageId)) + if (isMyChannel && !await _db.existItemByTelegramId(dbName, (int)entry.MessageId)) await _ts.deleteFile(dbName, (int)entry.MessageId); } } @@ -712,8 +717,8 @@ public async Task downloadFile(string dbName, List // if (targetPath == null) targetPath = "/"; //path.Replace("\\", "/"); foreach (var itemFile in files) { - - BsonFileManagerModel file = collectionId == null ? _db.getFileByPathSync(dbName, itemFile.FilterPath.Replace("\\", "/") + itemFile.Name) : _db.getFileByPathSync(dbName, itemFile.FilterPath.Replace("\\", "/") + itemFile.Name, collectionId); + var filterPath = itemFile.FilterPath == "Files/" ? "/" : itemFile.FilterPath; + BsonFileManagerModel file = collectionId == null ? _db.getFileByPathSync(dbName, filterPath.Replace("\\", "/") + itemFile.Name) : _db.getFileByPathSync(dbName, filterPath.Replace("\\", "/") + itemFile.Name, collectionId); string currentFilePath = currentTargetPath; if (!itemFile.IsFile) { @@ -732,20 +737,7 @@ public async Task downloadFile(string dbName, List throw new Exception($"{currentTargetPath} does not exist"); if (file.isSplit) { - int i = 1; - List splitPaths = new List(); - foreach (int messageId in file.ListMessageId) - { - string filePathPart = System.IO.Path.Combine(currentFilePath, $"({i})" + itemFile.Name); - await downloadFromTelegram(channelId == null ? dbName : channelId, messageId, filePathPart, file); - splitPaths.Add(filePathPart); - i++; - } - await mergeFileStreamAsync(splitPaths, System.IO.Path.Combine(currentFilePath, itemFile.Name)); - foreach (string filePath in splitPaths) - { - File.Delete(filePath); - } + downloadSplitFiles(itemFile, file, currentFilePath, channelId == null ? dbName : channelId); } else { @@ -768,6 +760,24 @@ public async Task downloadFile(string dbName, List } + private async Task downloadSplitFiles(FileManagerDirectoryContent itemFile, BsonFileManagerModel file, string currentFilePath, string dbName) + { + int i = 1; + List splitPaths = new List(); + foreach (int messageId in file.ListMessageId) + { + string filePathPart = System.IO.Path.Combine(currentFilePath, $"({i})" + itemFile.Name); + await downloadFromTelegram(dbName, messageId, filePathPart, file, true); + splitPaths.Add(filePathPart); + i++; + } + await mergeFileStreamAsync(splitPaths, System.IO.Path.Combine(currentFilePath, itemFile.Name)); + foreach (string filePath in splitPaths) + { + File.Delete(filePath); + } + } + private async Task downloadFromTelegramAndReturn(string dbName, int messageId, string destPath, MemoryStream ms = null) { TL.Message m = await _ts.getMessageFile(dbName, messageId); @@ -793,7 +803,7 @@ public static void functionCalll(string dbName, int messageId, string destPath) { } - private async Task downloadFromTelegram(string dbName, int messageId, string destPath, BsonFileManagerModel file = null) + private async Task downloadFromTelegram(string dbName, int messageId, string destPath, BsonFileManagerModel file = null, bool shouldWait = false) { DownloadModel model = new DownloadModel(); model.tis = _tis; @@ -810,9 +820,24 @@ private async Task downloadFromTelegram(string dbName, int messageId, string des } catch(Exception ex) { model.channelName = "Public or Shared"; } - + var tcs = new TaskCompletionSource(); + + EventHandler handler = null; + handler = (sender, args) => + { + if (model.state == StateTask.Completed) + { + model.EventStatechanged -= handler; + tcs.SetResult(true); + } + }; + + model.EventStatechanged += handler; model.callbacks.callback = async () => await DownloadFileNow(dbName, messageId, destPath, model); - _tis.addToPendingDownloadList(model); + _tis.addToPendingDownloadList(model, atFirst: shouldWait); + // Espera hasta que el estado sea "Completed" + if (shouldWait) + await tcs.Task; } public async Task DownloadFileFromChat(ChatMessages message, string fileName = null, string folder = null, DownloadModel model = null) @@ -828,7 +853,7 @@ public async Task DownloadFileFromChat(ChatMessages message, string fileName = n { model._size = document.size; } - if (message.user is Channel channel) + if (message.user is TL.Channel channel) { model.channelName = channel.Title; } @@ -1145,7 +1170,43 @@ public async Task AddUploadFileFromServer(string dbName, string currentPath, Lis _tis.CheckPendingUploadInfoTasks(); } + public async Task CreateStrmFiles(string path, string dbName, string host) + { + String basePath = Path.Combine(TEMPDIR, "Strm", dbName); + try + { + Directory.Delete(basePath, true); + } + catch (Exception ex) + { + } + + Directory.CreateDirectory(basePath); + List filesAndFolders = await _db.getAllChildFilesInDirectory(dbName, path); + foreach (BsonFileManagerModel file in filesAndFolders) + { + String filePath = Path.Combine(basePath, file.FilePath.Substring(path.Length)); + if (file.IsFile) + { + if (FileExtensionTypeTest.isVideoExtension(file.Type) || FileExtensionTypeTest.isAudioExtension(file.Type)) + { + string contenido = Path.Combine(host, "api/file/GetFileStream", dbName, file.Id, "file" + file.Type).Replace("\\", "/"); + string pattern = $@"\.({file.Type.Replace(".", "")})$"; + File.WriteAllText(Regex.Replace(filePath, pattern, ".strm"), contenido); + } + } else + { + Directory.CreateDirectory(filePath); + } + } + string zipPath = $"{basePath}.zip"; + + File.Delete(zipPath); + ZipFile.CreateFromDirectory(basePath, zipPath, CompressionLevel.Fastest, true); + Directory.Delete(basePath, true); + return zipPath; + } public async Task UploadFileFromServer(string dbName, string currentPath, List files, InfoDownloadTaksModel dm = null) // ItemsUploadedEventArgs args) { @@ -1155,44 +1216,6 @@ public async Task UploadFileFromServer(string dbName, string currentPath, List await UploadFileFromServer(dbName, currentPath, files); - // foreach (var file in files) - // { - // var filePath = file.IsFile ? file.FilterPath.Replace("\\", "/") + file.Name : file.FilterPath.Replace("\\", "/") + file.Name + "/"; - // string currentFilePath = System.IO.Path.Combine(LOCALDIR, filePath[0] == '/' ? filePath.Substring(1) : filePath).Replace("\\", "/"); - - // var fileInfo = new System.IO.FileInfo(currentFilePath); - // if (!file.IsFile) - // { - // var allFiles = new DirectoryInfo(currentFilePath).GetFiles("*.*", SearchOption.AllDirectories).Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden)); - // idt.total = allFiles.Count(); - // idt.totalSize += allFiles.Sum(x => x.Length); - // } else - // { - // idt.total++; - // idt.totalSize += fileInfo.Length; - // } - - // } - // TransactionInfoService ti = new TransactionInfoService(); - // ti.addToInfoDownloadTaskList(idt); - - - //} nm.sendEvent(new Notification($"Uploading files from folder {currentPath} to Telegram", "Telegram Upload", NotificationTypes.Info)); foreach (var file in files) { @@ -1203,24 +1226,6 @@ public async Task UploadFileFromServer(string dbName, string currentPath, List !x.Attributes.HasFlag(FileAttributes.Hidden)); - // idt.total = allFiles.Count(); - // idt.totalSize = allFiles.Sum(x => x.Length); - // idt.fromPath = currentFilePath; - // idt.toPath = Path.Combine(currentPath, file.Name); - // idt.executed = 0; - // idt.executedSize = 0; - // idt.isUpload = true; - // idt.file = file; - // idt.thread = Thread.CurrentThread; - // TransactionInfoService ti = new TransactionInfoService(); - // ti.addToInfoDownloadTaskList(idt); - //} BsonFileManagerModel model = new BsonFileManagerModel(); if (file.IsFile) @@ -1322,7 +1327,7 @@ public async Task UploadFileFromServer(string dbName, string currentPath, List file.Name.ToUpper().EndsWith(x)) && file.Size >= MAXIMAGESIZE) + if (ImageExtensions.Any(x => file.Name.ToUpper().EndsWith(x)) && file.Size >= (1024 * 1024 * GeneralConfigStatic.config.MaxImageUploadSizeInMb)) { m = await _ts.uploadFile(dbName, ms, file.Name, "application/octet-stream", um, caption: getCaption(filePath)); } @@ -1384,7 +1389,7 @@ public async Task UploadFileFromServer(string dbName, string currentPath, List args) + public async Task refreshChannelFIles(string channelId) { - // string currentPath = args.Path; - try + refreshMutex.WaitOne(); + refreshChannelList.Add(channelId); + refreshMutex.ReleaseMutex(); + List telegramChatDocuments = (await _ts.searchAllChannelFiles(Convert.ToInt64(channelId))).Where(x => x.name != null).ToList(); + var nameCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var doc in telegramChatDocuments) { - //foreach (var file in args.Files) - //{ + var baseName = doc.name; + var name = baseName; + int count = 0; - //using (var filestream = new FileStream(Path.Combine(Environment.CurrentDirectory, "download","assss.jpg"), FileMode.Create, FileAccess.Write)) - //{ - // await file.File.OpenReadStream(maxAllowedSize: long.MaxValue).CopyToAsync(filestream); - //} - //MemoryStream ms = new MemoryStream(file.File.OpenReadStream(maxAllowedSize: long.MaxValue)); - //await file.File.OpenReadStream().CopyToAsync(ms); + while (nameCount.ContainsKey(name)) + { + count++; + name = $"{baseName}({count})"; + } + doc.name = name; + nameCount[name] = 1; + if (count == 0) + nameCount[baseName] = 1; + } + List presentIds = await _db.getAllIdsFromChannel(channelId); + var rootFolder = await _db.getRootFolder(channelId); + foreach (TelegramChatDocuments tcd in telegramChatDocuments) + { + if (!presentIds.Contains(tcd.id)) + { + BsonFileManagerModel model = new BsonFileManagerModel(); + model.Size = tcd.fileSize; + model.MessageId = tcd.id; + model.Name = tcd.name; + model.IsFile = true; + model.HasChild = false; + model.DateCreated = tcd.creationDate; + model.DateModified = tcd.modifiedDate; + model.FilterPath = "/"; + model.FilterId = rootFolder.Id + "/"; + model.ParentId = rootFolder.Id; + model.FilePath = "/" + tcd.name; + model.Type = tcd.extension; + model.isSplit = false; + model.isEncrypted = false; + await _db.createEntry(channelId, model); + } + } + refreshMutex.WaitOne(); + refreshChannelList.Remove(channelId); + refreshMutex.ReleaseMutex(); + } + + public bool isChannelRefreshing(string channelId) + { + return refreshChannelList.Contains(channelId); + } + + public async Task UploadFile(string dbName, string currentPath, UploadFiles file) // ItemsUploadedEventArgs args) + { + try + { string currentFilePath = System.IO.Path.Combine(TEMPDIR, dbName + currentPath); await SaveToFile(file.File, currentFilePath); @@ -1547,33 +1600,6 @@ public async Task UploadFile(string dbName, string currentPath, UploadFiles file await _db.addBytesToFolder(dbName, model.ParentId, model.Size); GC.Collect(); - - - - - // var folders = (file.FileInfo.Name).Split('/'); - // if (folders.Length > 1) - // { - // for (var i = 0; i < folders.Length - 1; i++) - // { - // string newDirectoryPath = Path.Combine(FileManagerService.basePath + currentPath, folders[i]); - // if (Path.GetFullPath(newDirectoryPath) != (Path.GetDirectoryName(newDirectoryPath) + Path.DirectorySeparatorChar + folders[i])) - // { - // throw new UnauthorizedAccessException("Access denied for Directory-traversal"); - // } - // if (!Directory.Exists(newDirectoryPath)) - // { - // await FileManagerService.Create(currentPath, folders[i]); - // } - // currentPath += folders[i] + "/"; - // } - // } - // var fullName = Path.Combine((FileManagerService.contentRootPath + currentPath), file.File.Name); - // using (var filestream = new FileStream(fullName, FileMode.Create, FileAccess.Write)) - // { - // await file.File.OpenReadStream(long.MaxValue).CopyToAsync(filestream); - // } - //} } catch (Exception ex) { @@ -1646,15 +1672,6 @@ public async Task> getTelegramFolders(string dbName, } } return result; - //var root = listFolders.Where(x => string.IsNullOrEmpty(x.ParentId)).FirstOrDefault(); - //FolderModel folder = new FolderModel(); - //folder.FolderName = root.Name; - //folder.Id = "/"; - //folder.Expanded = true; - //folder.Folders = await getTelegramSubfolders(root.Id, listFolders.Where(x => !string.IsNullOrEmpty(x.ParentId)).ToList(), folder.Id); - //node.Add(folder); - //return node; - } private async Task> getTelegramSubfolders(string id, List listFolders, string prevPath) @@ -1712,27 +1729,6 @@ private async Task> AddChildRecords(string id, string parent if (fileDetails == null) fileDetails = new List(); FileManagerResponse response = new FileManagerResponse(); - //if (path == "/") - //{ - // List Data = await _db.getAllFiles(); - // string ParentId = Data - // .Where(x => string.IsNullOrEmpty(x.ParentId)) - // .Select(x => x.Id).First(); - // response.CWD = Data - // .Where(x => string.IsNullOrEmpty(x.ParentId)).First().toFileManagerContent(); - // response.Files = Data - // .Where(x => x.ParentId == ParentId).Select(x => x.toFileManagerContent()).ToList(); - //} - //else - //{ - //List Data = await _db.getAllFilesInDirectoryPath(path); - // var childItem = fileDetails.Count > 0 && fileDetails[0] != null ? fileDetails[0] : Data - // .Where(x => x.FilterPath == path).First().toFileManagerContent(); - // response.CWD = childItem; - // response.Files = Data - // .Where(x => x.ParentId == childItem.Id).Select(x => x.toFileManagerContent()).ToList(); - - //} if (path == "/") { List Data = collectionName == null ? await _db.getAllFiles(dbName) : await _db.getAllFiles(dbName, collectionName); diff --git a/TelegramDownloader/Data/IFileService.cs b/TelegramDownloader/Data/IFileService.cs index faeda77..e3455ce 100644 --- a/TelegramDownloader/Data/IFileService.cs +++ b/TelegramDownloader/Data/IFileService.cs @@ -25,6 +25,7 @@ public interface IFileService Task getItemById(string dbName, string id); Task> getTelegramFolders(string dbName, string? parentId = null); Task> GetTelegramFoldersExpando(string id, string parentId); + Task CreateStrmFiles(string path, string dbName, string host); Task importData(string dbName, string path, GenericNotificationProgressModel gnp); Task importSharedData(ShareFilesModel sfm, GenericNotificationProgressModel gnp); Task> itemDeleteAsync(string dbName, ItemsDeleteEventArgs args); @@ -34,5 +35,7 @@ public interface IFileService Task UploadFile(string dbName, string currentPath, UploadFiles file); Task UploadFileFromServer(string dbName, string currentPath, List files, InfoDownloadTaksModel dm = null); Task AddUploadFileFromServer(string dbName, string currentPath, List files, InfoDownloadTaksModel idt = null); + Task refreshChannelFIles(string channelId); + bool isChannelRefreshing(string channelId); } } \ No newline at end of file diff --git a/TelegramDownloader/Data/ITelegramService.cs b/TelegramDownloader/Data/ITelegramService.cs index 71fbdf4..cc83b81 100644 --- a/TelegramDownloader/Data/ITelegramService.cs +++ b/TelegramDownloader/Data/ITelegramService.cs @@ -35,6 +35,8 @@ public interface ITelegramService Task logOff(); Task sendVerificationCode(string vc); Task uploadFile(string chatId, Stream file, string fileName, string mimeType = null, UploadModel um = null, string caption = null); + Task> searchAllChannelFiles(long id); + bool isMyChat(long id); } } \ No newline at end of file diff --git a/TelegramDownloader/Data/TelegramService.cs b/TelegramDownloader/Data/TelegramService.cs index 92f8c92..9c2411c 100644 --- a/TelegramDownloader/Data/TelegramService.cs +++ b/TelegramDownloader/Data/TelegramService.cs @@ -3,9 +3,11 @@ using Microsoft.AspNetCore.Components.Forms; using Syncfusion.Blazor.Inputs; using Syncfusion.Blazor.Kanban.Internal; +using Syncfusion.Blazor.Schedule; using Syncfusion.Blazor.Sparkline.Internal; using System.Collections.Generic; using System.Reflection.Metadata; +using System.Threading.Channels; using System.Threading.Tasks; using TelegramDownloader.Data.db; using TelegramDownloader.Models; @@ -28,13 +30,14 @@ public class TelegramService : ITelegramService private static Messages_Chats chats = null; private static List favouriteChannels = new List(); private static Mutex mut = new Mutex(); + private ILogger _logger { get; set; } - - public TelegramService(TransactionInfoService tis, IDbService db) + public TelegramService(TransactionInfoService tis, IDbService db, ILogger logger) { _tis = tis; _db = db; + _logger = logger; // createDownloadFolder(); mut.WaitOne(); if (client == null) @@ -234,11 +237,26 @@ public bool isInChat(long id) public string getChatName(long id) { - List cm = new List(); + if (chats == null) + return ""; var peer = chats.chats[id]; return peer.Title; } + public bool isMyChat(long id) + { + if (chats == null) + return false; + var peer = chats.chats[id]; + if (peer is TL.Channel channel) + { + // Si es un canal, checkea la propiedad 'creator' + bool canPost = channel.admin_rights != null; + return canPost; + } + return true; + } + public async Task> GetFouriteChannels(bool mustRefresh = true) { @@ -650,20 +668,26 @@ public async Task DownloadFileStream(Message message, long offset, int l thumb_size = "" }; - //var file = await client.Invoke(new Upload_GetFile - //{ - // location = new InputDocumentFileLocation - // { - // id = inputFile.id, - // access_hash = inputFile.access_hash, - // file_reference = inputFile.file_reference, - // thumb_size = "" - // }, - // offset = currentOffset, - // limit = totalDownloadBytes - //}); - - var file = await client.Upload_GetFile(location, currentOffset, limit: totalDownloadBytes); + Upload_FileBase file = null; + try + { + file = await client.Upload_GetFile(location, currentOffset, limit: totalDownloadBytes); + } + catch (RpcException ex) when (ex.Code == 303 && ex.Message == "FILE_MIGRATE_X") + { + client = await client.GetClientForDC(-ex.X, true); + file = await client.Upload_GetFile(location, currentOffset, limit: totalDownloadBytes); + } + catch (RpcException ex) when (ex.Code == 400 && ex.Message == "OFFSET_INVALID") + { + _logger.LogError("OFFSET_INVALID", ex); + throw; + } + catch (Exception ex) + { + _logger.LogError("Download Error", ex); + throw; + } if (file is Upload_File uploadFile) { @@ -794,5 +818,89 @@ public async Task DownloadFile(ChatMessages message, string fileName = n return null; } + public async Task> searchAllChannelFiles(long id) + { + List telegramChatDocuments = new List(); + List result = await getAllFileMessages(id); + + foreach (var msg in result) + if (msg.message is Message msgBase) + { + if (msgBase.media is MessageMediaDocument mediaDoc && + mediaDoc.document is TL.Document doc) + { + TelegramChatDocuments tcd = new TelegramChatDocuments(); + tcd.id = msgBase.id; + tcd.documentType = DocumentType.Document; + tcd.name = doc.Filename; + tcd.fileSize = doc.size; + tcd.extension = Path.GetExtension(doc.Filename); + tcd.creationDate = msgBase.date; + tcd.modifiedDate = msgBase.edit_date < msgBase.date ? msgBase.date : msgBase.edit_date; + telegramChatDocuments.Add(tcd); + } + } + return telegramChatDocuments; + } + + //public async Task> searchAllChannelFiles(long id) + //{ + // InputPeer channel = chats.chats[id]; + // List telegramChatDocuments = new List(); + // for (int offset_id = 0; ;) + // { + // Messages_MessagesBase result = await client.Messages_Search( + // peer: channel, + // text: "", + // offset_id: offset_id, + // limit: 0); + // if (result.Messages.Length == 0) break; + // foreach (var msgBase in result.Messages.OfType()) + // { + // if (msgBase.media is MessageMediaDocument mediaDoc && + // mediaDoc.document is TL.Document doc) + // { + // TelegramChatDocuments tcd = new TelegramChatDocuments(); + // tcd.id = msgBase.id; + // tcd.documentType = DocumentType.Document; + // tcd.name = doc.Filename; + // tcd.extension = Path.GetExtension(doc.Filename); + // tcd.creationDate = msgBase.date; + // tcd.modifiedDate = msgBase.edit_date; + // telegramChatDocuments.Add(tcd); + // } + // } + // offset_id = result.Messages[^1].ID; + // } + + // for (int offset_id = 0; ;) + // { + // Messages_MessagesBase result = await client.Messages_Search( + // peer: channel, + // text: "", + // offset_id: offset_id, + // limit: 0); + // if (result.Messages.Length == 0) break; + // foreach (var msgBase in result.Messages.OfType()) + // { + // if (msgBase.media is MessageMediaPhoto mediaPhoto && + // mediaPhoto.photo is Photo photo) + // { + // TelegramChatDocuments tcd = new TelegramChatDocuments(); + // tcd.documentType = DocumentType.Photo; + // tcd.id = msgBase.id; + // // Nombre "falso" porque la foto no trae filename + // tcd.name = $"{photo.id}.jpg"; + // tcd.extension = ".jpg"; + // tcd.creationDate = msgBase.date; + // tcd.modifiedDate = msgBase.edit_date; + // telegramChatDocuments.Add(tcd); + // } + // } + // offset_id = result.Messages[^1].ID; + // } + + // return telegramChatDocuments; + //} } } diff --git a/TelegramDownloader/Data/db/DbService.cs b/TelegramDownloader/Data/db/DbService.cs index 85f6086..42370a7 100644 --- a/TelegramDownloader/Data/db/DbService.cs +++ b/TelegramDownloader/Data/db/DbService.cs @@ -6,6 +6,8 @@ using MongoDB.Driver.Linq; using System.Collections; using Syncfusion.Blazor.PivotView; +using Microsoft.Extensions.Options; +using Syncfusion.EJ2.Layouts; namespace TelegramDownloader.Data.db { @@ -34,15 +36,33 @@ public async Task getSession() public IMongoDatabase getDatabase(string dbName) { // currentDatabase = client.GetDatabase(dbName); + return client.GetDatabase(dbName); } public async Task createIndex(string dbName, string collectionName = "directory") { if (collectionName == null) collectionName = "directory"; - var options = new CreateIndexOptions() { Unique = true, Name = "uniquefile" }; - var indexKeysDefinition = Builders.IndexKeys.Ascending(x => x.FilePath); - await getDatabase(dbName).GetCollection(collectionName).Indexes.CreateOneAsync(indexKeysDefinition, options); + var collection = getDatabase(dbName).GetCollection(collectionName); + + var indexes = await collection.Indexes.ListAsync(); + var indexList = await indexes.ToListAsync(); + + bool existsFilePath = indexList.Any(i => i["name"] == "uniquefile"); + bool existsFilterPath = indexList.Any(i => i["name"] == "uniquefilterpath"); + + if (!existsFilePath) + { + var options = new CreateIndexOptions() { Unique = true, Name = "uniquefile" }; + var indexKeysDefinition = Builders.IndexKeys.Ascending(x => x.FilePath); + await collection.Indexes.CreateOneAsync(indexKeysDefinition, options); + } + if (!existsFilterPath) + { + var options2 = new CreateIndexOptions() { Unique = false, Name = "uniquefilterpath" }; + var indexKeysDefinition2 = Builders.IndexKeys.Ascending(x => x.FilterPath); + await collection.Indexes.CreateOneAsync(indexKeysDefinition2, options2); + } } public async Task CreateDatabase(string dbName = "default", string collection = "directory", bool CreateDefaultEntry = true) @@ -294,7 +314,7 @@ public async Task> getAllFiles(string dbName, string { if (collectionName == null) collectionName = "directory"; - return await (await getDatabase(dbName).GetCollection(collectionName).FindAsync(Builders.Filter.Eq(x => x.FilterPath, "/") | Builders.Filter.Eq(x => x.FilterId, ""))).ToListAsync(); + return await (await getDatabase(dbName).GetCollection(collectionName).FindAsync(Builders.Filter.Eq(x => x.FilterPath, "/") | Builders.Filter.Eq(x => x.FilterPath, "Files/") | Builders.Filter.Eq(x => x.FilterId, ""))).ToListAsync(); } public async Task> getAllFilesInDirectory(string dbName, string path, string collectionName = "directory") @@ -349,6 +369,30 @@ public async Task> getAllFolders(string dbName, strin //return await (await getDatabase(dbName).GetCollection(collectionName).FindAsync(Builders.Filter.Where(x => !x.IsFile && x.ParentId == parentId))).ToListAsync(); } + public async Task> getAllIdsFromChannel(string dbName, string collectionName = "directory") + { + if (collectionName == null) + collectionName = "directory"; + var collection = getDatabase(dbName).GetCollection(collectionName); + + var items = await collection + .Find(x => x.IsFile) + .Project(x => x.MessageId) + .ToListAsync(); + + var allMessageIds = new HashSet(); + foreach (var item in items) + { + if (item != null) + allMessageIds.Add(item.Value); + + //if (item != null) + // allMessageIds.UnionWith(item); + } + + return allMessageIds.OrderBy(x => x).ToList(); + } + public async Task> getAllFilesInDirectoryById(string dbName, string idFolder, string collectionName = "directory") { if (collectionName == null) @@ -486,6 +530,13 @@ public async Task> createEntry(string dbName, BsonFil return null; } + public async Task getRootFolder(string dbName, string collectionName = "directory") + { + if (collectionName == null) + collectionName = "directory"; + return await (await getDatabase(dbName).GetCollection(collectionName).FindAsync(Builders.Filter.Where(x => x.ParentId == "" && x.Name == "Files"))).FirstOrDefaultAsync(); + } + public async Task toBasonFile(string Path, string FolderName, FileManagerDirectoryContent ParentFolder) diff --git a/TelegramDownloader/Data/db/IDbService.cs b/TelegramDownloader/Data/db/IDbService.cs index 88db984..243657f 100644 --- a/TelegramDownloader/Data/db/IDbService.cs +++ b/TelegramDownloader/Data/db/IDbService.cs @@ -17,6 +17,7 @@ public interface IDbService Task copyItem(string dbName, string sourceId, FileManagerDirectoryContent target, string targetPath, bool isFile, string collectionName = "directory"); Task CreateDatabase(string dbName = "default", string collection = "directory", bool CreateDefaultEntry = true); Task> createEntry(string dbName, BsonFileManagerModel file, string collectionName = "directory", IClientSessionHandle? session = null); + Task getRootFolder(string dbName, string collectionName = "directory"); Task createIndex(string dbName, string collectionName = "directory"); Task deleteDatabase(string dbName = "default"); Task deleteEntry(string dbName, string id, string collectionName = "directory"); @@ -30,6 +31,7 @@ public interface IDbService Task> getAllFilesInDirectoryPath(string dbName, string path, string collectionName = "directory"); Task> getAllFilesInDirectoryPath2(string dbName, string path, string collectionName = "directory"); Task> getAllFolders(string dbName, string? parentId = null, string collectionName = "directory"); + Task> getAllIdsFromChannel(string dbName, string collectionName = "directory"); IMongoDatabase getDatabase(string dbName); Task getEntry(string dbName, string filterId, string name, string collectionName = "directory"); Task getFileById(string dbName, string id, string collectionName = "directory"); diff --git a/TelegramDownloader/Dockerfile b/TelegramDownloader/Dockerfile index fca714e..7aeebd8 100644 --- a/TelegramDownloader/Dockerfile +++ b/TelegramDownloader/Dockerfile @@ -21,5 +21,17 @@ RUN dotnet publish "./TelegramDownloader.csproj" -c $BUILD_CONFIGURATION -o /app FROM base AS final WORKDIR /app + +# Instala Python y pip +RUN apt-get update && \ + apt-get install -y python3 python3-pip && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + rm -rf /var/lib/apt/lists/* + +# Copia y instala los requirements de WebDav +COPY WebDav /app/WebDav +RUN python3 -m pip install --upgrade pip && \ + python3 -m pip install -r /app/WebDav/requirements.txt + COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "TelegramDownloader.dll"] diff --git a/TelegramDownloader/Models/DownloadModel.cs b/TelegramDownloader/Models/DownloadModel.cs index c1dcd32..4ee5453 100644 --- a/TelegramDownloader/Models/DownloadModel.cs +++ b/TelegramDownloader/Models/DownloadModel.cs @@ -119,6 +119,7 @@ public class DownloadModel { public Mutex mutex = new Mutex(); public event EventHandler EventChanged; + public event EventHandler EventStatechanged; public string id = Guid.NewGuid().ToString(); public string action { get; set; } = "Download"; public StateTask state { get; set; } = StateTask.Working; @@ -145,13 +146,14 @@ public DownloadModel(string? name = null) id = Guid.NewGuid().ToString() + ":" + name; } + // Fix for CS7036: Ensure the required "sender" parameter is passed when invoking the EventHandler. + public void ProgressCallback(long transmitted, long totalSize) { if (state == StateTask.Canceled) throw new Exception($"Canceled {name}"); if (state == StateTask.Paused) { - state = StateTask.Working; tis.deleteDownloadInList(this); throw new Exception($"Paused {name}"); @@ -165,11 +167,11 @@ public void ProgressCallback(long transmitted, long totalSize) if (transmitted == totalSize) { state = StateTask.Completed; + EventStatechanged?.Invoke(this, EventArgs.Empty); NotificationModel nm = new NotificationModel(); nm.sendEvent(new Notification($"Download {name} completed", "Download Completed", NotificationTypes.Success)); tis.CheckPendingDownloads(); } - } public void Cancel() diff --git a/TelegramDownloader/Models/FileModel.cs b/TelegramDownloader/Models/FileModel.cs index 10f37b4..d74906c 100644 --- a/TelegramDownloader/Models/FileModel.cs +++ b/TelegramDownloader/Models/FileModel.cs @@ -4,6 +4,8 @@ using TelegramDownloader.Data.db; using Syncfusion.EJ2.Linq; using TL; +using System.Text.Json.Serialization; +using TelegramDownloader.Data; namespace TelegramDownloader.Models { @@ -168,6 +170,42 @@ public FileDetails toFileDetails() }; } + public WebDavFileModel toWebDavFileModel(String? channel = null) + { + return this.IsFile + ? new WebDavFileModel() + { + name = this.Name, + is_dir = false, + file_id = this.Id, + content_type = FileService.getMimeType(this.Type), + content_length = this.Size, + channel = channel, + last_modified = this.DateModified + } + : new WebDavFileModel() + { + name = this.Name, + is_dir = true, + last_modified = this.DateModified + }; + } + } + + public class WebDavFileModel + { + public string name { get; set; } + public bool is_dir { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string file_id { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string content_type { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long content_length { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? channel { get; set; } + public DateTime last_modified { get; set; } + } public class FileManagerModel diff --git a/TelegramDownloader/Models/GeneralConfig.cs b/TelegramDownloader/Models/GeneralConfig.cs index 9b04aca..9eb05f1 100644 --- a/TelegramDownloader/Models/GeneralConfig.cs +++ b/TelegramDownloader/Models/GeneralConfig.cs @@ -7,6 +7,8 @@ using Newtonsoft.Json; using Syncfusion.Blazor.Charts; using TelegramDownloader.Data; +using System.Diagnostics; +using TelegramDownloader.Services; namespace TelegramDownloader.Models { @@ -72,10 +74,15 @@ public class GeneralConfig public int SplitSize { get; set; } = 0; public int MaxSimultaneousDownloads = 1; public bool CheckHash { get; set; } = false; + /// + /// Maximum image upload size in megabytes. if 0, All images will be sent as a file + /// + public int MaxImageUploadSizeInMb { get; set; } = 10; public bool ShouldShowCaptionPath { get; set; } = false; public bool ShouldShowLogInTerminal { get; set; } = false; public bool ShouldShowPaginatedFileChannel { get; set; } = false; public List FavouriteChannels { get; set; } = new List(); + public WebDavModel webDav { get; set; } = new WebDavModel(); } @@ -86,4 +93,23 @@ public class TLConfig public string? mongo_connection_string { get; set; } public bool? avoid_checking_certificate { get; set; } } + + public class WebDavModel + { + public string Host { get; set; } = "127.0.0.1"; + public int PuertoEntrada { get; set; } = 8080; + public int PuertoSalida { get; set; } = 9081; + [BsonIgnore] + public WebbDavService? webDavService { get; set; } = new WebbDavService(); + + public void start() + { + webDavService.Start(port: PuertoEntrada, externalPort: PuertoSalida, host: Host); + } + + public void stop() + { + webDavService.Stop(); + } + } } diff --git a/TelegramDownloader/Models/LoginModel.cs b/TelegramDownloader/Models/LoginModel.cs index cad6ee7..7afeda8 100644 --- a/TelegramDownloader/Models/LoginModel.cs +++ b/TelegramDownloader/Models/LoginModel.cs @@ -32,4 +32,23 @@ public class ChatMessages public string videoThumb { get; set; } public bool isDocument { get; set; } } + + public class TelegramChatDocuments + { + public int id { get; set; } + public string name { get; set; } + public string extension { get; set; } + public DateTime creationDate { get; set; } + public DateTime modifiedDate { get; set; } + public long fileSize { get; set; } + public DocumentType documentType { get; set; } + } + + public enum DocumentType + { + Photo, + Video, + Audio, + Document + } } diff --git a/TelegramDownloader/Pages/Config.razor b/TelegramDownloader/Pages/Config.razor index 0390990..75eb010 100644 --- a/TelegramDownloader/Pages/Config.razor +++ b/TelegramDownloader/Pages/Config.razor @@ -80,6 +80,17 @@ +
+
+ +
+ + +
+ +
+
+
@if (TelegramService.isPremium) diff --git a/TelegramDownloader/Pages/FileManager.razor b/TelegramDownloader/Pages/FileManager.razor index 9bc58ef..a0b3f85 100644 --- a/TelegramDownloader/Pages/FileManager.razor +++ b/TelegramDownloader/Pages/FileManager.razor @@ -3,7 +3,9 @@ @using TelegramDownloader.Data @using TelegramDownloader.Data.db @using TelegramDownloader.Models +@using TelegramDownloader.Pages.Modals.InfoModals @using TelegramDownloader.Pages.Partials +@using TelegramDownloader.Services @inject IFileService fs @inject ITelegramService ts @inject ILogger Logger @@ -12,11 +14,11 @@ @@ -29,6 +31,16 @@ Import + @if (webDavService.IsRunning) + { + + } + + @if (!isMyChannel && showRefresh) + { + + } +