From eb3c3ae9787d56ab67a0551cb3a8e9deabbb45dc Mon Sep 17 00:00:00 2001 From: meoacgx Date: Fri, 19 Dec 2025 19:10:45 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E7=BE=A4=E7=BB=84=E8=AF=A6?= =?UTF-8?q?=E6=83=85=EF=BC=9A=E6=94=AF=E6=8C=81=E6=9F=A5=E7=9C=8B=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E4=B8=8E=E5=A4=8D=E5=88=B6=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interfaces/IGroupService.cs | 10 + .../Services/Telegram/GroupService.cs | 142 ++++++++++++++ .../Dialogs/GroupDetailsDialog.razor | 183 ++++++++++++++++++ .../Components/Pages/Groups.razor | 25 ++- 4 files changed, 353 insertions(+), 7 deletions(-) create mode 100644 src/TelegramPanel.Web/Components/Dialogs/GroupDetailsDialog.razor diff --git a/src/TelegramPanel.Core/Interfaces/IGroupService.cs b/src/TelegramPanel.Core/Interfaces/IGroupService.cs index e71eaab..9495c83 100644 --- a/src/TelegramPanel.Core/Interfaces/IGroupService.cs +++ b/src/TelegramPanel.Core/Interfaces/IGroupService.cs @@ -16,4 +16,14 @@ public interface IGroupService /// 获取群组详情 /// Task GetGroupInfoAsync(int accountId, long groupId); + + /// + /// 导出加入链接:公开群组返回 t.me 链接;否则导出邀请链接。 + /// + Task ExportJoinLinkAsync(int accountId, long groupId); + + /// + /// 获取群组管理员列表(需要权限) + /// + Task> GetAdminsAsync(int accountId, long groupId); } diff --git a/src/TelegramPanel.Core/Services/Telegram/GroupService.cs b/src/TelegramPanel.Core/Services/Telegram/GroupService.cs index 56128ae..9c24492 100644 --- a/src/TelegramPanel.Core/Services/Telegram/GroupService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/GroupService.cs @@ -111,6 +111,148 @@ public class GroupService : IGroupService return groups.FirstOrDefault(g => g.TelegramId == groupId); } + public async Task ExportJoinLinkAsync(int accountId, long groupId) + { + var client = await GetOrCreateConnectedClientAsync(accountId); + var dialogs = await client.Messages_GetAllDialogs(); + + // 基础群组(Chat) + var basic = dialogs.chats.Values.OfType().FirstOrDefault(c => c.IsActive && c.id == groupId); + if (basic != null) + { + var invite = await client.Messages_ExportChatInvite(basic); + var link = invite switch + { + ChatInviteExported e => e.link, + _ => null + }; + + if (string.IsNullOrWhiteSpace(link)) + throw new InvalidOperationException("无法导出群组邀请链接(可能无权限)"); + + return link; + } + + // 超级群组(Channel 但 megagroup=true,即 !IsChannel) + var mega = dialogs.chats.Values.OfType().FirstOrDefault(c => c.IsActive && !c.IsChannel && c.id == groupId); + if (mega != null) + { + if (!string.IsNullOrWhiteSpace(mega.MainUsername)) + return $"https://t.me/{mega.MainUsername}"; + + var invite = await client.Messages_ExportChatInvite(mega); + var link = invite switch + { + ChatInviteExported e => e.link, + _ => null + }; + + if (string.IsNullOrWhiteSpace(link)) + throw new InvalidOperationException("无法导出群组邀请链接(可能无权限)"); + + return link; + } + + throw new InvalidOperationException($"群组 {groupId} not found"); + } + + public async Task> GetAdminsAsync(int accountId, long groupId) + { + var client = await GetOrCreateConnectedClientAsync(accountId); + var dialogs = await client.Messages_GetAllDialogs(); + + // 基础群组(Chat) + var basic = dialogs.chats.Values.OfType().FirstOrDefault(c => c.IsActive && c.id == groupId); + if (basic != null) + { + var fullChat = await client.Messages_GetFullChat(basic.id); + if (fullChat.full_chat is not ChatFull cf || cf.participants is not ChatParticipants cp) + return new List(); + + var list = new List(); + foreach (var p in cp.participants) + { + long userId; + var isCreator = p is ChatParticipantCreator; + + if (p is ChatParticipantCreator creator) + { + userId = creator.user_id; + } + else if (p is ChatParticipantAdmin admin) + { + userId = admin.user_id; + } + else + { + continue; + } + + fullChat.users.TryGetValue(userId, out var u); + list.Add(new ChannelAdminInfo( + UserId: userId, + Username: u?.MainUsername, + FirstName: u?.first_name, + LastName: u?.last_name, + IsCreator: isCreator, + Rank: null + )); + } + + return list + .OrderByDescending(x => x.IsCreator) + .ThenBy(x => x.DisplayName, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + // 超级群组(Channel but !IsChannel) + var mega = dialogs.chats.Values.OfType().FirstOrDefault(c => c.IsActive && !c.IsChannel && c.id == groupId); + if (mega != null) + { + var participants = await client.Channels_GetParticipants(mega, new ChannelParticipantsAdmins()); + var list = new List(); + + foreach (var p in participants.participants) + { + long userId; + string? rank = null; + var isCreator = p is ChannelParticipantCreator; + + if (p is ChannelParticipantAdmin admin) + { + userId = admin.user_id; + rank = string.IsNullOrWhiteSpace(admin.rank) ? null : admin.rank; + } + else if (p is ChannelParticipantCreator creator) + { + userId = creator.user_id; + rank = string.IsNullOrWhiteSpace(creator.rank) ? null : creator.rank; + } + else + { + continue; + } + + participants.users.TryGetValue(userId, out var u); + list.Add(new ChannelAdminInfo( + UserId: userId, + Username: u?.MainUsername, + FirstName: u?.first_name, + LastName: u?.last_name, + IsCreator: isCreator, + Rank: rank + )); + } + + return list + .OrderByDescending(x => x.IsCreator) + .ThenBy(x => x.DisplayName, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + throw new InvalidOperationException($"群组 {groupId} not found"); + } + private async Task GetOrCreateConnectedClientAsync(int accountId) { var existing = _clientPool.GetClient(accountId); diff --git a/src/TelegramPanel.Web/Components/Dialogs/GroupDetailsDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/GroupDetailsDialog.razor new file mode 100644 index 0000000..0dbd3cd --- /dev/null +++ b/src/TelegramPanel.Web/Components/Dialogs/GroupDetailsDialog.razor @@ -0,0 +1,183 @@ +@namespace TelegramPanel.Web.Components.Dialogs +@inject GroupManagementService GroupManagement +@inject AccountManagementService AccountManagement +@inject TelegramPanel.Core.Interfaces.IGroupService GroupService +@inject ISnackbar Snackbar +@inject IJSRuntime JS +@using TelegramPanel.Data.Entities +@using TelegramPanel.Core.Models + + + + @if (loading) + { + + } + else if (group == null) + { + 未找到群组数据 + } + else + { + + @group.Title + + + + TelegramId:@group.TelegramId + 用户名:@(string.IsNullOrWhiteSpace(group.Username) ? "(私密)" : "@" + group.Username) + 成员数:@group.MemberCount + + + 创建者:@creatorDisplay + 创建时间:@(group.CreatedAt?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") ?? "(未知)") + 最后同步:@group.SyncedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") + + + + @if (!string.IsNullOrWhiteSpace(group.About)) + { + 简介: + + @group.About + + } + + + + + 管理员 + + + 刷新 + + + + @if (adminsLoading) + { + + } + else if (admins.Count == 0) + { + 暂无管理员数据(或无权限获取) + } + else + { + + + 身份 + 昵称 + 用户名 + 头衔 + + + + + @(context.IsCreator ? "创建者" : "管理员") + + + @context.DisplayName + @(!string.IsNullOrWhiteSpace(context.Username) ? "@" + context.Username : "-") + @(string.IsNullOrWhiteSpace(context.Rank) ? "-" : context.Rank) + + + } + + } + + + @if (group != null) + { + 复制链接 + } + 关闭 + + + +@code +{ + [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!; + [Parameter] public int GroupDbId { get; set; } + + private bool loading = true; + private Group? group; + private string creatorDisplay = "-"; + + private bool adminsLoading; + private List admins = new(); + + protected override async Task OnInitializedAsync() + { + await LoadGroup(); + await LoadAdmins(); + } + + private async Task LoadGroup() + { + loading = true; + try + { + group = await GroupManagement.GetGroupAsync(GroupDbId); + if (group == null) + return; + + var creator = await AccountManagement.GetAccountAsync(group.CreatorAccountId); + creatorDisplay = creator == null + ? $"账号 {group.CreatorAccountId}" + : $"{creator.Phone}({creator.Nickname ?? creator.Username ?? creator.UserId.ToString()})"; + } + catch (Exception ex) + { + Snackbar.Add($"加载群组失败:{ex.Message}", Severity.Error); + } + finally + { + loading = false; + } + } + + private async Task LoadAdmins() + { + if (group == null) + return; + + adminsLoading = true; + try + { + admins = await GroupService.GetAdminsAsync(group.CreatorAccountId, group.TelegramId); + } + catch (Exception ex) + { + admins.Clear(); + Snackbar.Add($"加载管理员失败:{ex.Message}", Severity.Error); + } + finally + { + adminsLoading = false; + } + } + + private async Task CopyLink() + { + if (group == null) + return; + + try + { + var link = await GroupService.ExportJoinLinkAsync(group.CreatorAccountId, group.TelegramId); + await JS.InvokeVoidAsync("telegramPanel.copyText", link); + Snackbar.Add("已复制链接", Severity.Success); + } + catch (Exception ex) + { + Snackbar.Add($"复制失败:{ex.Message}", Severity.Error); + } + } + + private void Close() + { + MudDialog.Close(); + } +} + diff --git a/src/TelegramPanel.Web/Components/Pages/Groups.razor b/src/TelegramPanel.Web/Components/Pages/Groups.razor index d347513..0d48c05 100644 --- a/src/TelegramPanel.Web/Components/Pages/Groups.razor +++ b/src/TelegramPanel.Web/Components/Pages/Groups.razor @@ -1,10 +1,13 @@ @page "/groups" @inject GroupManagementService GroupManagement @inject AccountManagementService AccountManagement +@inject IGroupService GroupService @inject ISnackbar Snackbar @inject IDialogService DialogService @inject NavigationManager Navigation +@inject IJSRuntime JS @using TelegramPanel.Data.Entities +@using TelegramPanel.Web.Components.Dialogs 群组列表 - Telegram Panel @@ -73,7 +76,7 @@ 查看详情 邀请成员 - 复制链接 + 复制链接 删除群组 @@ -152,7 +155,9 @@ private void ShowDetails(Group group) { - Snackbar.Add($"查看群组详情:{group.Title}", Severity.Info); + var parameters = new DialogParameters { ["GroupDbId"] = group.Id }; + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true }; + DialogService.Show("群组详情", parameters, options); } private void InviteMembers(Group group) @@ -160,12 +165,18 @@ Navigation.NavigateTo($"/batch/invite?groupId={group.Id}"); } - private void CopyLink(Group group) + private async Task CopyLink(Group group) { - var link = string.IsNullOrEmpty(group.Username) - ? $"私密群组(邀请链接待实现)" - : $"https://t.me/{group.Username}"; - Snackbar.Add($"链接:{link}", Severity.Info); + try + { + var link = await GroupService.ExportJoinLinkAsync(group.CreatorAccountId, group.TelegramId); + await JS.InvokeVoidAsync("telegramPanel.copyText", link); + Snackbar.Add("已复制链接", Severity.Success); + } + catch (Exception ex) + { + Snackbar.Add($"复制失败:{ex.Message}", Severity.Error); + } } private async Task DeleteGroup(int id)