补齐群组详情:支持查看管理员与复制加入链接
This commit is contained in:
1 parent
34bda77560
commit
eb3c3ae978
4 files changed
+353
-7
No files matched your search
@@ -16,4 +16,14 @@ public interface IGroupService
|
||||
/// 获取群组详情
|
||||
/// </summary>
|
||||
Task<GroupInfo?> GetGroupInfoAsync(int accountId, long groupId);
|
||||
|
||||
/// <summary>
|
||||
/// 导出加入链接:公开群组返回 t.me 链接;否则导出邀请链接。
|
||||
/// </summary>
|
||||
Task<string> ExportJoinLinkAsync(int accountId, long groupId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取群组管理员列表(需要权限)
|
||||
/// </summary>
|
||||
Task<List<ChannelAdminInfo>> GetAdminsAsync(int accountId, long groupId);
|
||||
}
|
||||
@@ -111,6 +111,148 @@ public class GroupService : IGroupService
|
||||
return groups.FirstOrDefault(g => g.TelegramId == groupId);
|
||||
}
|
||||
|
||||
public async Task<string> ExportJoinLinkAsync(int accountId, long groupId)
|
||||
{
|
||||
var client = await GetOrCreateConnectedClientAsync(accountId);
|
||||
var dialogs = await client.Messages_GetAllDialogs();
|
||||
|
||||
// 基础群组(Chat)
|
||||
var basic = dialogs.chats.Values.OfType<Chat>().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<Channel>().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<List<ChannelAdminInfo>> GetAdminsAsync(int accountId, long groupId)
|
||||
{
|
||||
var client = await GetOrCreateConnectedClientAsync(accountId);
|
||||
var dialogs = await client.Messages_GetAllDialogs();
|
||||
|
||||
// 基础群组(Chat)
|
||||
var basic = dialogs.chats.Values.OfType<Chat>().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<ChannelAdminInfo>();
|
||||
|
||||
var list = new List<ChannelAdminInfo>();
|
||||
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<Channel>().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<ChannelAdminInfo>();
|
||||
|
||||
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<WTelegram.Client> GetOrCreateConnectedClientAsync(int accountId)
|
||||
{
|
||||
var existing = _clientPool.GetClient(accountId);
|
||||
|
||||
@@ -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
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (loading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else if (group == null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined">未找到群组数据</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">@group.Title</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudText Typo="Typo.body2"><strong>TelegramId:</strong>@group.TelegramId</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>用户名:</strong>@(string.IsNullOrWhiteSpace(group.Username) ? "(私密)" : "@" + group.Username)</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>成员数:</strong>@group.MemberCount</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudText Typo="Typo.body2"><strong>创建者:</strong>@creatorDisplay</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>创建时间:</strong>@(group.CreatedAt?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") ?? "(未知)")</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>最后同步:</strong>@group.SyncedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(group.About))
|
||||
{
|
||||
<MudText Typo="Typo.body2"><strong>简介:</strong></MudText>
|
||||
<MudPaper Class="pa-3" Elevation="0" Style="background: rgba(0,0,0,0.03);">
|
||||
<MudText Typo="Typo.body2" Style="white-space: pre-wrap;">@group.About</MudText>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudDivider />
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudText Typo="Typo.h6">管理员</MudText>
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
|
||||
Disabled="@adminsLoading" OnClick="LoadAdmins">
|
||||
刷新
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if (adminsLoading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" />
|
||||
}
|
||||
else if (admins.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">暂无管理员数据(或无权限获取)</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@admins" Dense="true" Hover="true" Breakpoint="Breakpoint.Sm">
|
||||
<HeaderContent>
|
||||
<MudTh>身份</MudTh>
|
||||
<MudTh>昵称</MudTh>
|
||||
<MudTh>用户名</MudTh>
|
||||
<MudTh>头衔</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="身份">
|
||||
<MudChip T="string" Size="Size.Small" Color="@(context.IsCreator ? Color.Success : Color.Default)">
|
||||
@(context.IsCreator ? "创建者" : "管理员")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="昵称">@context.DisplayName</MudTd>
|
||||
<MudTd DataLabel="用户名">@(!string.IsNullOrWhiteSpace(context.Username) ? "@" + context.Username : "-")</MudTd>
|
||||
<MudTd DataLabel="头衔">@(string.IsNullOrWhiteSpace(context.Rank) ? "-" : context.Rank)</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@if (group != null)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="CopyLink">复制链接</MudButton>
|
||||
}
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Close">关闭</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@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<ChannelAdminInfo> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
<PageTitle>群组列表 - Telegram Panel</PageTitle>
|
||||
|
||||
@@ -73,7 +76,7 @@
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Info" OnClick="@(() => ShowDetails(context))">查看详情</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PersonAdd" OnClick="@(() => InviteMembers(context))">邀请成员</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(() => CopyLink(context))">复制链接</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(async () => await CopyLink(context))">复制链接</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="@(() => DeleteGroup(context.Id))">删除群组</MudMenuItem>
|
||||
</MudMenu>
|
||||
@@ -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<GroupDetailsDialog>("群组详情", 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)
|
||||
|
||||
Reference in new issue
Block a user