Импорт групповых чатом, мелкие правки, внешний вид чата, GIF тип
This commit is contained in:
@@ -62,7 +62,8 @@ public class AdminController : ControllerBase
|
||||
DisplayName = u.DisplayName,
|
||||
Avatar = u.Avatar,
|
||||
CreatedAt = u.CreatedAt,
|
||||
LastOnlineAt = u.CreatedAt // fallback
|
||||
IsOnline = Knot.Modules.Chats.Infrastructure.SignalR.ChatHub.IsUserOnline(u.Id.ToString()),
|
||||
LastOnlineAt = Knot.Modules.Chats.Infrastructure.SignalR.ChatHub.IsUserOnline(u.Id.ToString()) ? DateTime.UtcNow : u.CreatedAt
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -101,7 +102,8 @@ public class AdminController : ControllerBase
|
||||
Bio = targetUser.Bio,
|
||||
Avatar = targetUser.Avatar,
|
||||
CreatedAt = targetUser.CreatedAt,
|
||||
LastOnlineAt = targetUser.CreatedAt, // fallback
|
||||
IsOnline = Knot.Modules.Chats.Infrastructure.SignalR.ChatHub.IsUserOnline(targetUser.Id.ToString()),
|
||||
LastOnlineAt = Knot.Modules.Chats.Infrastructure.SignalR.ChatHub.IsUserOnline(targetUser.Id.ToString()) ? DateTime.UtcNow : targetUser.CreatedAt,
|
||||
Stats = new
|
||||
{
|
||||
MessagesCount = messagesCount,
|
||||
|
||||
@@ -93,8 +93,8 @@ public sealed class MessagesController : ControllerBase
|
||||
if (rUser != null) senders[r.UserId] = rUser;
|
||||
}
|
||||
var userObj = senders.TryGetValue(r.UserId, out var ru)
|
||||
? new { id = ru.Id, username = ru.Username, displayName = ru.DisplayName }
|
||||
: new { id = r.UserId, username = "unknown", displayName = "Unknown" };
|
||||
? new { id = ru.Id, username = ru.Username, displayName = ru.DisplayName, avatar = ru.Avatar }
|
||||
: new { id = r.UserId, username = "unknown", displayName = "Unknown", avatar = (string?)null };
|
||||
|
||||
reactionsWithUser.Add(new
|
||||
{
|
||||
@@ -141,7 +141,7 @@ public sealed class MessagesController : ControllerBase
|
||||
displayName = s.DisplayName,
|
||||
avatar = s.Avatar
|
||||
} : null,
|
||||
readBy = m.ReadBy.Select(r => r.UserId).ToList(),
|
||||
readBy = m.ReadBy.Select(r => new { userId = r.UserId }).ToList(),
|
||||
reactions = reactionsWithUser
|
||||
});
|
||||
}
|
||||
@@ -266,7 +266,10 @@ public sealed class MessagesController : ControllerBase
|
||||
|
||||
var filteredMedia = m.Media.Where(media => {
|
||||
var mediaType = media.Type?.ToLower() ?? "file";
|
||||
if (filterType == "media") return mediaType == "image" || mediaType == "video";
|
||||
var isGif = mediaType == "image" && (media.Url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) || media.Url.EndsWith(".gif", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (filterType == "media") return (mediaType == "image" || mediaType == "video") && !isGif;
|
||||
if (filterType == "gifs") return isGif;
|
||||
if (filterType == "files") return mediaType != "image" && mediaType != "video" && mediaType != "link";
|
||||
return true;
|
||||
}).ToList();
|
||||
|
||||
@@ -117,7 +117,7 @@ public sealed class TelegramImportController : ControllerBase
|
||||
});
|
||||
}
|
||||
|
||||
public sealed record ExecuteImportRequest(Guid Token, Dictionary<string, Guid> Mapping);
|
||||
public sealed record ExecuteImportRequest(Guid Token, Dictionary<string, Guid> Mapping, string? GroupName);
|
||||
|
||||
[HttpPost("execute")]
|
||||
public async Task<IActionResult> Execute([FromBody] ExecuteImportRequest req, CancellationToken ct)
|
||||
@@ -161,7 +161,7 @@ public sealed class TelegramImportController : ControllerBase
|
||||
else
|
||||
{
|
||||
// Create a group
|
||||
var command = new CreateChatCommand("Импортированный чат", ChatType.Group, chatMembers);
|
||||
var command = new CreateChatCommand(req.GroupName ?? "Импортированный чат", ChatType.Group, chatMembers);
|
||||
var res = await _sender.Send(command, ct);
|
||||
if (res.IsFailure) return BadRequest(res.Error);
|
||||
chatId = res.Value;
|
||||
@@ -224,15 +224,25 @@ public sealed class TelegramImportController : ControllerBase
|
||||
Guid senderGuid = lastSenderGuid;
|
||||
|
||||
string content = "";
|
||||
if (textNode != null)
|
||||
var mainBodyNode = node.QuerySelector(".body");
|
||||
var isForwarded = node.QuerySelector(".forwarded") != null;
|
||||
|
||||
// To avoid grabbing text from inside the forwarded block as main text,
|
||||
// we can look for .text that is a direct child of the main .body
|
||||
// The .forwarded block has its own .text
|
||||
var contentTextNode = isForwarded
|
||||
? (node.QuerySelector(".body > .text") ?? node.QuerySelector(".text:not(.forwarded .text)"))
|
||||
: node.QuerySelector(".text");
|
||||
|
||||
if (contentTextNode != null)
|
||||
{
|
||||
var html = textNode.InnerHtml
|
||||
var html = contentTextNode.InnerHtml
|
||||
.Replace("<br>", "\n", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("<br/>", "\n", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("<br />", "\n", StringComparison.OrdinalIgnoreCase);
|
||||
var tempParser = new AngleSharp.Html.Parser.HtmlParser();
|
||||
var tempDoc = tempParser.ParseDocument("<div>" + html + "</div>");
|
||||
content = tempDoc.Body.TextContent.Trim();
|
||||
content = tempDoc.Body?.TextContent.Trim() ?? "";
|
||||
}
|
||||
|
||||
DateTime createdAt = lastCreatedAt;
|
||||
@@ -282,6 +292,7 @@ public sealed class TelegramImportController : ControllerBase
|
||||
}
|
||||
|
||||
// Parse forwards
|
||||
Guid? forwardedFromId = null;
|
||||
var forwardedNode = node.QuerySelector(".forwarded.body");
|
||||
if (forwardedNode != null)
|
||||
{
|
||||
@@ -294,6 +305,15 @@ public sealed class TelegramImportController : ControllerBase
|
||||
}
|
||||
var fwdName = fwdNameText != null ? fwdNameText.TextContent.Trim() : "Неизвестного";
|
||||
|
||||
if (req.Mapping.TryGetValue(fwdName, out var mappedFwdId) && mappedFwdId != Guid.Empty)
|
||||
{
|
||||
forwardedFromId = mappedFwdId;
|
||||
}
|
||||
else if (fwdName == "Это я" || fwdName == req.Mapping.FirstOrDefault(x => x.Value == myId).Key)
|
||||
{
|
||||
forwardedFromId = myId;
|
||||
}
|
||||
|
||||
var fwdTextNode = forwardedNode.QuerySelector(".text");
|
||||
string fwdContent = "";
|
||||
if (fwdTextNode != null)
|
||||
@@ -304,12 +324,20 @@ public sealed class TelegramImportController : ControllerBase
|
||||
.Replace("<br />", "\n", StringComparison.OrdinalIgnoreCase);
|
||||
var tempParser = new AngleSharp.Html.Parser.HtmlParser();
|
||||
var tempDoc = tempParser.ParseDocument("<div>" + fHtml + "</div>");
|
||||
fwdContent = tempDoc.Body.TextContent.Trim();
|
||||
fwdContent = tempDoc.Body?.TextContent.Trim() ?? "";
|
||||
}
|
||||
|
||||
content = string.IsNullOrEmpty(content)
|
||||
? $"[Переслано от {fwdName}]:\n{fwdContent}"
|
||||
: $"{content}\n\n[Переслано от {fwdName}]:\n{fwdContent}";
|
||||
if (forwardedFromId == null)
|
||||
{
|
||||
// We don't have this user in the app, map to generic string
|
||||
content = string.IsNullOrEmpty(content)
|
||||
? $"[Переслано от {fwdName}]:\n{fwdContent}"
|
||||
: $"{content}\n\n[Переслано от {fwdName}]:\n{fwdContent}";
|
||||
}
|
||||
else if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
content = fwdContent;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse replies
|
||||
@@ -356,6 +384,15 @@ public sealed class TelegramImportController : ControllerBase
|
||||
if (firstHref != null)
|
||||
{
|
||||
messageType = GetMediaTypes(firstHref).mType;
|
||||
if (mediaNodes[0].ClassName?.Contains("animated") == true || firstHref.EndsWith(".mp4"))
|
||||
{
|
||||
// Treat telegram animated gifs as image format in app (we auto-loop mp4 images)
|
||||
// But web app specifically handles "image" and "video" and microlink crashes on mp4 video
|
||||
// Let's just keep "video" or "image". Actually, telegram exports gifs as mp4.
|
||||
// We should let our app player handle it.
|
||||
if (mediaNodes[0].ClassName?.Contains("animated") == true)
|
||||
messageType = "image";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +407,7 @@ public sealed class TelegramImportController : ControllerBase
|
||||
else
|
||||
{
|
||||
string finalContent = content;
|
||||
targetMessage = Message.Import(chatId, senderGuid, finalContent, messageType, createdAt, replyToId, null);
|
||||
targetMessage = Message.Import(chatId, senderGuid, finalContent, messageType, createdAt, replyToId, forwardedFromId);
|
||||
|
||||
var idAttr = node.GetAttribute("id");
|
||||
if (!string.IsNullOrEmpty(idAttr))
|
||||
@@ -396,8 +433,10 @@ public sealed class TelegramImportController : ControllerBase
|
||||
ms.Position = 0;
|
||||
|
||||
var types = GetMediaTypes(href);
|
||||
var finalMType = types.mType;
|
||||
if (mediaNode.ClassName?.Contains("animated") == true) finalMType = "image";
|
||||
var fileId = await _fileStorage.UploadFileAsync(ms, Path.GetFileName(href), types.cType);
|
||||
targetMessage.AddMedia(types.mType, $"/api/files/{fileId}", Path.GetFileName(href), zipEntry.Length);
|
||||
targetMessage.AddMedia(finalMType, $"/api/files/{fileId}", Path.GetFileName(href), zipEntry.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class ChatHub : Hub
|
||||
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, ParticipantInfo>> _groupCallParticipants = new();
|
||||
|
||||
public static int OnlineUsersCount => _userConnections.Count;
|
||||
public static bool IsUserOnline(string userId) => _userConnections.ContainsKey(userId);
|
||||
|
||||
private readonly ISender _sender;
|
||||
private readonly IUserContext _userContext;
|
||||
|
||||
@@ -8,14 +8,14 @@ public class SystemSettingsDto
|
||||
public int MaxFileSizeMb { get; set; } = 100;
|
||||
|
||||
// Calls
|
||||
public bool EnableCalls { get; set; } = true;
|
||||
public bool EnableCalls { get; set; } = false;
|
||||
public string TurnHost { get; set; } = string.Empty;
|
||||
public int TurnPort { get; set; } = 3478;
|
||||
public string TurnUser { get; set; } = string.Empty;
|
||||
public string TurnSecret { get; set; } = string.Empty;
|
||||
|
||||
// Klipy
|
||||
public bool EnableKlipy { get; set; } = true;
|
||||
public bool EnableKlipy { get; set; } = false;
|
||||
public string KlipyApiKey { get; set; } = string.Empty;
|
||||
public string KlipyCustomerId { get; set; } = string.Empty;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user