Skip to content

Commit 5860b66

Browse files
committed
feat: add channel icons
1 parent 4ce7fb6 commit 5860b66

7 files changed

Lines changed: 587 additions & 66 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using TelegramDownloader.Data;
3+
using TelegramDownloader.Models;
4+
5+
namespace TelegramDownloader.Controllers
6+
{
7+
[Route("api/channel")]
8+
[ApiController]
9+
public class ChannelImageController : ControllerBase
10+
{
11+
private readonly ITelegramService _ts;
12+
private readonly ILogger<ChannelImageController> _logger;
13+
private static readonly string CacheFolder = Path.Combine(Path.GetTempPath(), "TFM_ChannelImages");
14+
private static readonly SemaphoreSlim _downloadLock = new SemaphoreSlim(1, 1);
15+
16+
public ChannelImageController(ITelegramService ts, ILogger<ChannelImageController> logger)
17+
{
18+
_ts = ts;
19+
_logger = logger;
20+
21+
// Ensure cache folder exists
22+
if (!Directory.Exists(CacheFolder))
23+
{
24+
Directory.CreateDirectory(CacheFolder);
25+
}
26+
}
27+
28+
[HttpGet("image/{channelId}")]
29+
[ResponseCache(Duration = 86400)] // Cache for 24 hours
30+
public async Task<IActionResult> GetChannelImage(long channelId)
31+
{
32+
try
33+
{
34+
var cachedPath = Path.Combine(CacheFolder, $"{channelId}.jpg");
35+
36+
// Check if image is cached
37+
if (System.IO.File.Exists(cachedPath))
38+
{
39+
var fileBytes = await System.IO.File.ReadAllBytesAsync(cachedPath);
40+
return File(fileBytes, "image/jpeg");
41+
}
42+
43+
// Download and cache the image
44+
await _downloadLock.WaitAsync();
45+
try
46+
{
47+
// Double-check after acquiring lock
48+
if (System.IO.File.Exists(cachedPath))
49+
{
50+
var fileBytes = await System.IO.File.ReadAllBytesAsync(cachedPath);
51+
return File(fileBytes, "image/jpeg");
52+
}
53+
54+
var imageBytes = await _ts.DownloadChannelPhoto(channelId);
55+
if (imageBytes != null && imageBytes.Length > 0)
56+
{
57+
await System.IO.File.WriteAllBytesAsync(cachedPath, imageBytes);
58+
return File(imageBytes, "image/jpeg");
59+
}
60+
}
61+
finally
62+
{
63+
_downloadLock.Release();
64+
}
65+
66+
// Return 404 if no image
67+
return NotFound();
68+
}
69+
catch (Exception ex)
70+
{
71+
_logger.LogError(ex, "Error getting channel image for {ChannelId}", channelId);
72+
return NotFound();
73+
}
74+
}
75+
76+
[HttpDelete("image/cache")]
77+
public IActionResult ClearCache()
78+
{
79+
try
80+
{
81+
if (Directory.Exists(CacheFolder))
82+
{
83+
var files = Directory.GetFiles(CacheFolder, "*.jpg");
84+
foreach (var file in files)
85+
{
86+
System.IO.File.Delete(file);
87+
}
88+
}
89+
return Ok(new { message = "Cache cleared" });
90+
}
91+
catch (Exception ex)
92+
{
93+
_logger.LogError(ex, "Error clearing image cache");
94+
return StatusCode(500, new { error = "Failed to clear cache" });
95+
}
96+
}
97+
}
98+
}

TelegramDownloader/Data/ITelegramService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public interface ITelegramService
2525
Task RemoveFavouriteChannel(long id);
2626
Task<List<ChatViewBase>> getAllChats();
2727
Task<List<ChatViewBase>> getAllSavedChats();
28+
Task<ChatsWithFolders> getChatsWithFolders();
2829
Task<List<ChatMessages>> getAllMessages(long id, Boolean onlyFiles = false);
2930
Task<GridDataProviderResult<ChatMessages>> getPaginatedMessages(long id, int page, int size, Boolean onlyFiles = false);
3031
Task<List<ChatMessages>> getAllMediaMessages(long id, Boolean onlyFiles = false);
@@ -33,6 +34,7 @@ public interface ITelegramService
3334
Task<Message> getMessageFile(string chatId, int idMessage);
3435
Task<string> getPhotoThumb(ChatBase chat);
3536
Task<string> downloadPhotoThumb(Photo thumb);
37+
Task<byte[]?> DownloadChannelPhoto(long channelId);
3638
Task logOff();
3739
Task sendVerificationCode(string vc);
3840
Task<Message> uploadFile(string chatId, Stream file, string fileName, string mimeType = null, UploadModel um = null, string caption = null);

TelegramDownloader/Data/TelegramService.cs

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ public async Task<List<ChatViewBase>> getAllChats()
321321
{
322322
ChatViewBase cb = new ChatViewBase();
323323
cb.chat = chat;
324-
cb.img64 = ""; // chat.Photo == null ? "" : await getPhotoThumb(chat);
324+
cb.img64 = ""; // Images loaded via /api/channel/image/{id} endpoint
325325
allChats.Add(cb);
326326
}
327327
return allChats;
@@ -339,12 +339,104 @@ public async Task<List<ChatViewBase>> getAllSavedChats()
339339
{
340340
ChatViewBase cb = new ChatViewBase();
341341
cb.chat = chat;
342-
cb.img64 = ""; // chat.Photo == null ? "" : await getPhotoThumb(chat);
342+
cb.img64 = ""; // Images loaded via /api/channel/image/{id} endpoint
343343
allChats.Add(cb);
344344
}
345345
return allChats;
346346
}
347347

348+
/// <summary>
349+
/// Gets all chats organized by Telegram folders (dialog filters)
350+
/// </summary>
351+
public async Task<ChatsWithFolders> getChatsWithFolders()
352+
{
353+
var result = new ChatsWithFolders();
354+
355+
if (!checkUserLogin()) return result;
356+
357+
try
358+
{
359+
// Get all chats first
360+
var allChats = await getAllSavedChats();
361+
var chatDict = allChats.ToDictionary(c => c.chat.ID);
362+
var chatsInFolders = new HashSet<long>();
363+
364+
// Get dialog filters (folders)
365+
var dialogFilters = await client.Messages_GetDialogFilters();
366+
367+
if (dialogFilters?.filters != null)
368+
{
369+
foreach (var filter in dialogFilters.filters)
370+
{
371+
ChatFolderView folder = null;
372+
InputPeer[] includePeers = null;
373+
374+
// Handle regular folders (DialogFilter)
375+
if (filter is DialogFilter df)
376+
{
377+
folder = new ChatFolderView
378+
{
379+
Id = df.id,
380+
Title = df.title?.text ?? df.title?.ToString() ?? "Folder",
381+
IconEmoji = df.emoticon ?? "📁"
382+
};
383+
includePeers = df.include_peers;
384+
}
385+
// Handle shared folders (DialogFilterChatlist)
386+
else if (filter is DialogFilterChatlist dfc)
387+
{
388+
folder = new ChatFolderView
389+
{
390+
Id = dfc.id,
391+
Title = dfc.title?.text ?? dfc.title?.ToString() ?? "Shared Folder",
392+
IconEmoji = dfc.emoticon ?? "🔗"
393+
};
394+
includePeers = dfc.include_peers;
395+
}
396+
397+
if (folder != null && includePeers != null)
398+
{
399+
foreach (var peer in includePeers)
400+
{
401+
long peerId = peer switch
402+
{
403+
InputPeerChannel ipc => ipc.channel_id,
404+
InputPeerChat ipchat => ipchat.chat_id,
405+
InputPeerUser ipu => ipu.user_id,
406+
_ => 0
407+
};
408+
409+
if (peerId != 0 && chatDict.TryGetValue(peerId, out var chatView))
410+
{
411+
folder.Chats.Add(chatView);
412+
chatsInFolders.Add(peerId);
413+
}
414+
}
415+
416+
// Only add folders that have chats
417+
if (folder.Chats.Count > 0)
418+
{
419+
result.Folders.Add(folder);
420+
}
421+
}
422+
}
423+
}
424+
425+
// Add ungrouped chats (not in any folder)
426+
result.UngroupedChats = allChats
427+
.Where(c => !chatsInFolders.Contains(c.chat.ID))
428+
.ToList();
429+
}
430+
catch (Exception ex)
431+
{
432+
_logger.LogError(ex, "Error getting chat folders");
433+
// Fallback: return all chats as ungrouped
434+
result.UngroupedChats = await getAllSavedChats();
435+
}
436+
437+
return result;
438+
}
439+
348440
public async Task<Message> uploadFile(string chatId, Stream file, string fileName, string mimeType = null, UploadModel um = null, string caption = null)
349441
{
350442
_logger.LogInformation("Starting file upload - FileName: {FileName}, Size: {SizeMB:F2}MB, ChatId: {ChatId}",
@@ -677,6 +769,22 @@ public async Task<string> getPhotoThumb(ChatBase chat)
677769

678770
}
679771

772+
public async Task<byte[]?> DownloadChannelPhoto(long channelId)
773+
{
774+
if (!checkUserLogin() || chats == null) return null;
775+
776+
if (chats.chats.TryGetValue(channelId, out var chat) && chat.Photo != null)
777+
{
778+
using var ms = new MemoryStream();
779+
// big=true for full resolution, miniThumb=false to avoid tiny thumbnail
780+
if (await client.DownloadProfilePhotoAsync(chat, ms, big: true, miniThumb: false) != 0)
781+
{
782+
return ms.ToArray();
783+
}
784+
}
785+
return null;
786+
}
787+
680788
public async Task<string> downloadPhotoThumb(Photo thumb)
681789
{
682790
MemoryStream ms = new MemoryStream();

TelegramDownloader/Models/GeneralConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public class GeneralConfig
8888
public bool ShouldShowPaginatedFileChannel { get; set; } = false;
8989
public bool hasFileManagerVirtualScroll { get; set; } = false;
9090
public bool UseMobileFileManagerAlways { get; set; } = false;
91+
public bool ShowChannelImages { get; set; } = false;
9192
public List<long> FavouriteChannels { get; set; } = new List<long>();
9293
public WebDavModel webDav { get; set; } = new WebDavModel();
9394

TelegramDownloader/Models/LoginModel.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,25 @@ public enum DocumentType
5151
Audio,
5252
Document
5353
}
54+
55+
/// <summary>
56+
/// Represents a Telegram chat folder (filter)
57+
/// </summary>
58+
public class ChatFolderView
59+
{
60+
public int Id { get; set; }
61+
public string Title { get; set; }
62+
public string IconEmoji { get; set; }
63+
public List<ChatViewBase> Chats { get; set; } = new List<ChatViewBase>();
64+
public bool IsExpanded { get; set; } = false;
65+
}
66+
67+
/// <summary>
68+
/// Container for organized chats with folders
69+
/// </summary>
70+
public class ChatsWithFolders
71+
{
72+
public List<ChatFolderView> Folders { get; set; } = new List<ChatFolderView>();
73+
public List<ChatViewBase> UngroupedChats { get; set; } = new List<ChatViewBase>();
74+
}
5475
}

0 commit comments

Comments
 (0)