Починка импорта, плеер для аудио

This commit is contained in:
Халимов Рустам
2026-04-06 23:35:45 +03:00
parent d96e4ec7d4
commit 32c9bc43cf
10 changed files with 375 additions and 196 deletions

View File

@@ -15,5 +15,5 @@ public record TelegramMessage(
long OrderIndex = 0
);
public record TelegramMedia(string FilePath, string FileName, string MimeType);
public record TelegramMedia(string FilePath, string FileName, string MimeType, string? Duration = null);
public record TelegramReaction(string Emoji, List<string> UserNames);

View File

@@ -175,7 +175,7 @@ public class TelegramImportWorker : BackgroundService
else if (mediaItem.MimeType.Contains("voice") || mediaItem.MimeType.Contains("audio")) itemType = "audio";
var fileId = await fileStorage.UploadFileAsync(mediaStream, mediaItem.FileName, mimeToUpload);
mediaMsg.AddMedia(itemType, $"/api/files/{fileId}", mediaItem.FileName, zipEntry.Length);
mediaMsg.AddMedia(itemType, $"/api/files/{fileId}", mediaItem.FileName, zipEntry.Length, mediaItem.Duration);
}
else
{

View File

@@ -186,16 +186,82 @@ public sealed class TelegramHtmlParser : ITelegramHtmlParser
var fullPath = BuildPath(baseDir, href);
if (mediaList.Any(m => m.FilePath == fullPath)) continue;
var fileName = Path.GetFileName(href);
// Priority 1: Extract real filename from .title or .name or .description or specific body part
string fileName = "";
var titleNode = link.QuerySelector(".title") ?? link.QuerySelector(".name") ?? link.QuerySelector(".description");
if (titleNode != null)
{
fileName = titleNode.TextContent.Trim();
}
else
{
// Try body but exclude status
var bodyNode = link.QuerySelector(".body");
if (bodyNode != null)
{
var clone = (IElement)bodyNode.Clone();
foreach (var s in clone.QuerySelectorAll(".status, .details, .pull_right")) s.Remove();
fileName = clone.TextContent.Trim();
}
// Final desperate attempt: link's own content excluding status tags
if (string.IsNullOrWhiteSpace(fileName))
{
var clone = (IElement)link.Clone();
foreach (var s in clone.QuerySelectorAll(".status, .details, .pull_right, .details_icon")) s.Remove();
fileName = clone.TextContent.Trim();
}
}
// Cleanup & Russian Support
if (!string.IsNullOrWhiteSpace(fileName))
{
// Decrypt potential HTML entities
fileName = System.Net.WebUtility.HtmlDecode(fileName);
// Remove extra lines if any
var lines = fileName.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
fileName = lines.Length > 0 ? lines[0].Trim() : "";
// If the "name" is just "Animation" or generic, we might want to fallback to path
if (fileName.Equals("Animation", StringComparison.OrdinalIgnoreCase)) fileName = "";
}
// Priority 2: Fallback to Path filename (e.g. file_1.mp3)
if (string.IsNullOrWhiteSpace(fileName) || fileName.Length < 2)
{
var rawName = Path.GetFileName(href);
fileName = System.Net.WebUtility.UrlDecode(rawName);
}
var mimeType = GetMimeType(href);
bool hasGifClass = link.ClassList.Contains("animated_wrap") || link.ClassList.Contains("media_video");
bool hasAnimationText = link.TextContent.Contains("Animation", StringComparison.OrdinalIgnoreCase);
bool isVoice = link.ClassList.Contains("media_voice_message") || href.Contains("voice_messages");
bool isAudio = link.ClassList.Contains("media_audio_file") || (mimeType.StartsWith("audio") && !isVoice);
bool isVideo = link.ClassList.Contains("media_video") || mimeType.StartsWith("video");
bool isGif = link.ClassList.Contains("animated_wrap") || link.TextContent.Contains("Animation", StringComparison.OrdinalIgnoreCase);
if (hasGifClass || hasAnimationText) mimeType = "image/gif";
if (link.ClassList.Contains("media_voice_message") || href.Contains("voice_messages")) mimeType = "audio/ogg";
if (isGif) mimeType = "image/gif";
else if (isVoice) mimeType = "audio/ogg";
else if (isAudio && mimeType == "application/octet-stream") mimeType = "audio/mpeg";
mediaList.Add(new TelegramMedia(fullPath, fileName, mimeType));
// Extract Duration from .status
string? duration = null;
var statusNode = link.QuerySelector(".status") ?? link.QuerySelector(".details");
if (statusNode != null)
{
var text = statusNode.TextContent.Trim();
// Status is often: "01:23, 1.2 MB" or just "01:23"
var parts = text.Split(',');
var first = parts[0].Trim();
if (first.Contains(":") && first.All(c => char.IsDigit(c) || c == ':'))
{
duration = first;
}
}
mediaList.Add(new TelegramMedia(fullPath, fileName, mimeType, duration));
}
foreach (var audioEl in node.QuerySelectorAll("audio[src]"))
@@ -204,7 +270,7 @@ public sealed class TelegramHtmlParser : ITelegramHtmlParser
if (href == null) continue;
var fullPath = BuildPath(baseDir, href);
if (!mediaList.Any(m => m.FilePath == fullPath))
mediaList.Add(new TelegramMedia(fullPath, Path.GetFileName(href), "audio/ogg"));
mediaList.Add(new TelegramMedia(fullPath, System.Net.WebUtility.UrlDecode(Path.GetFileName(href)), "audio/ogg"));
}
return mediaList;
@@ -244,6 +310,11 @@ public sealed class TelegramHtmlParser : ITelegramHtmlParser
".mp4" => "video/mp4",
".mov" => "video/quicktime",
".webm" => "video/webm",
".mp3" => "audio/mpeg",
".ogg" => "audio/ogg",
".wav" => "audio/wav",
".m4a" => "audio/mp4",
".aac" => "audio/aac",
_ => "application/octet-stream"
};
}