diff --git a/src/TelegramPanel.Web/Components/Layout/NavMenu.razor b/src/TelegramPanel.Web/Components/Layout/NavMenu.razor index 18458b7..ced23bb 100644 --- a/src/TelegramPanel.Web/Components/Layout/NavMenu.razor +++ b/src/TelegramPanel.Web/Components/Layout/NavMenu.razor @@ -22,11 +22,6 @@ 群组列表 - - 批量邀请 - 批量设管理 - - 机器人列表 Bot 频道 diff --git a/src/TelegramPanel.Web/Components/Pages/BatchAdmins.razor b/src/TelegramPanel.Web/Components/Pages/BatchAdmins.razor deleted file mode 100644 index 2127836..0000000 --- a/src/TelegramPanel.Web/Components/Pages/BatchAdmins.razor +++ /dev/null @@ -1,385 +0,0 @@ -@page "/batch/admins" -@inject IChannelService ChannelService -@inject AccountManagementService AccountManagement -@inject ChannelManagementService ChannelManagement -@inject ChannelGroupManagementService ChannelGroupManagement -@inject BatchTaskManagementService TaskManagement -@inject ISnackbar Snackbar -@using TelegramPanel.Data.Entities -@using TelegramPanel.Core.Interfaces - -批量设置管理员 - Telegram Panel - -批量设置管理员 - - - - - - 配置管理员 - - - - (默认:按频道创建账号执行) - @foreach (var account in accounts) - { - @FormatAccountLabel(account) - } - - - - 全部分组 - @foreach (var g in groups) - { - @g.Name - } - - - - - 全选当前筛选 - - - 选择该分组全部 - - - 清空选择 - - - - - - 频道名称 - 分组 - 创建账号 - 用户名 - - - @context.Title - @(context.Group?.Name ?? "未分组") - @(context.CreatorAccount?.Phone ?? (context.CreatorAccountId?.ToString() ?? "(非系统创建)")) - @(string.IsNullOrWhiteSpace(context.Username) ? "-" : "@" + context.Username) - - - 暂无频道数据 - - - - - - - 管理员权限 - - 全选 - 全不选 - - - - - - - - - - - - - - - - - - 清空 - - - @if (processing) - { - - 设置中... - } - else - { - 开始设置 - } - - - - - - - - - 设置结果 - - - - @foreach (var result in results) - { - -
- - @result.ChannelTitle:@("@" + result.Username) - - 执行账号:@result.ExecutedByAccountId - @if (!result.Success && result.Error != null) - { - @result.Error - } -
-
- } -
- @if (results.Any()) - { - - - 成功:@results.Count(r => r.Success) / 总计:@results.Count(频道:@results.Select(r => r.ChannelTelegramId).Distinct().Count()) - - } -
-
-
-
- -@code { - private List accounts = new(); - private List channels = new(); - private List groups = new(); - - private int selectedAccountId = 0; - private int filterGroupId = 0; - private bool channelsLoading = true; - private HashSet selectedChannels = new(); - - private string adminTitle = "Admin"; - private string usernames = ""; - private bool processing = false; - - private AdminPermissions permissions = new(); - private List results = new(); - - private IEnumerable filteredChannels => - channels.Where(c => filterGroupId <= 0 || c.GroupId == filterGroupId); - - protected override async Task OnInitializedAsync() - { - await LoadAccounts(); - await LoadGroups(); - await LoadChannels(); - } - - private async Task LoadAccounts() - { - try - { - var accountList = await AccountManagement.GetActiveAccountsAsync(); - accounts = accountList.ToList(); - } - catch (Exception ex) - { - Snackbar.Add($"加载账号列表失败:{ex.Message}", Severity.Error); - } - } - - private async Task LoadGroups() - { - try - { - var list = await ChannelGroupManagement.GetAllGroupsAsync(); - groups = list.ToList(); - } - catch (Exception ex) - { - Snackbar.Add($"加载频道分组失败:{ex.Message}", Severity.Error); - } - } - - private async Task LoadChannels() - { - channelsLoading = true; - try - { - var channelList = await ChannelManagement.GetAllChannelsAsync(); - channels = channelList.ToList(); - selectedChannels = selectedChannels.Where(c => channels.Any(x => x.Id == c.Id)).ToHashSet(); - } - catch (Exception ex) - { - Snackbar.Add($"加载频道列表失败:{ex.Message}", Severity.Error); - } - finally - { - channelsLoading = false; - } - } - - private void SelectAllFilteredChannels() - { - selectedChannels = filteredChannels.ToHashSet(); - } - - private void SelectCurrentGroupChannels() - { - if (filterGroupId <= 0) - return; - - selectedChannels = channels.Where(c => c.GroupId == filterGroupId).ToHashSet(); - } - - private void ClearSelectedChannels() - { - selectedChannels = new HashSet(); - } - - private void SelectAllPermissions() - { - permissions.ChangeInfo = true; - permissions.PostMessages = true; - permissions.EditMessages = true; - permissions.DeleteMessages = true; - permissions.BanUsers = true; - permissions.InviteUsers = true; - permissions.PinMessages = true; - permissions.ManageCall = true; - permissions.ManageTopics = true; - permissions.AddAdmins = true; - permissions.Anonymous = true; - } - - private void ClearAllPermissions() - { - permissions.ChangeInfo = false; - permissions.PostMessages = false; - permissions.EditMessages = false; - permissions.DeleteMessages = false; - permissions.BanUsers = false; - permissions.InviteUsers = false; - permissions.PinMessages = false; - permissions.ManageCall = false; - permissions.ManageTopics = false; - permissions.AddAdmins = false; - permissions.Anonymous = false; - } - - private async Task StartSetAdmins() - { - processing = true; - results.Clear(); - - try - { - var usernameList = usernames.Split('\n', StringSplitOptions.RemoveEmptyEntries) - .Select(u => u.Trim().TrimStart('@')) - .Where(u => !string.IsNullOrEmpty(u)) - .ToList(); - - if (usernameList.Count == 0) - { - Snackbar.Add("请输入至少一个用户名", Severity.Warning); - return; - } - - var targetChannels = selectedChannels.ToList(); - if (targetChannels.Count == 0) - { - Snackbar.Add("请先勾选至少一个频道", Severity.Warning); - return; - } - - var rights = BuildAdminRights(); - - var task = new BatchTask - { - TaskType = "set_admin", - Total = usernameList.Count * targetChannels.Count, - Completed = 0, - Failed = 0, - Config = System.Text.Json.JsonSerializer.Serialize(new - { - ChannelTelegramIds = targetChannels.Select(c => c.TelegramId).ToList(), - Usernames = usernameList, - Rights = (int)rights, - Title = adminTitle, - PreferredExecuteAccountId = selectedAccountId - }) - }; - var createdTask = await TaskManagement.CreateTaskAsync(task); - Snackbar.Add($"已提交后台任务 #{createdTask.Id}(批量设置管理员)。请到【任务中心】查看进度。", Severity.Success); - } - catch (Exception ex) - { - Snackbar.Add($"设置失败:{ex.Message}", Severity.Error); - } - finally - { - processing = false; - } - } - - private AdminRights BuildAdminRights() - { - var rights = AdminRights.None; - if (permissions.ChangeInfo) rights |= AdminRights.ChangeInfo; - if (permissions.PostMessages) rights |= AdminRights.PostMessages; - if (permissions.EditMessages) rights |= AdminRights.EditMessages; - if (permissions.DeleteMessages) rights |= AdminRights.DeleteMessages; - if (permissions.BanUsers) rights |= AdminRights.BanUsers; - if (permissions.InviteUsers) rights |= AdminRights.InviteUsers; - if (permissions.PinMessages) rights |= AdminRights.PinMessages; - if (permissions.ManageCall) rights |= AdminRights.ManageCall; - if (permissions.ManageTopics) rights |= AdminRights.ManageTopics; - if (permissions.AddAdmins) rights |= AdminRights.AddAdmins; - if (permissions.Anonymous) rights |= AdminRights.Anonymous; - return rights; - } - - private void ClearForm() - { - usernames = ""; - results.Clear(); - } - - private static string FormatAccountLabel(Account account) - { - var nickname = string.IsNullOrWhiteSpace(account.Nickname) ? null : account.Nickname.Trim(); - var username = string.IsNullOrWhiteSpace(account.Username) ? null : account.Username.Trim(); - - var namePart = nickname ?? account.Phone; - if (!string.IsNullOrWhiteSpace(username)) - return $"{namePart} (@{username})"; - return namePart; - } - - private class AdminPermissions - { - public bool ChangeInfo { get; set; } = true; - public bool PostMessages { get; set; } = true; - public bool EditMessages { get; set; } = true; - public bool DeleteMessages { get; set; } = true; - public bool BanUsers { get; set; } - public bool InviteUsers { get; set; } = true; - public bool PinMessages { get; set; } = true; - public bool ManageCall { get; set; } - public bool ManageTopics { get; set; } - public bool AddAdmins { get; set; } - public bool Anonymous { get; set; } - } - - private record AdminSetOutcome( - long ChannelTelegramId, - string ChannelTitle, - string Username, - bool Success, - string? Error, - int ExecutedByAccountId - ); -} diff --git a/src/TelegramPanel.Web/Components/Pages/BatchInvite.razor b/src/TelegramPanel.Web/Components/Pages/BatchInvite.razor deleted file mode 100644 index 565f0a4..0000000 --- a/src/TelegramPanel.Web/Components/Pages/BatchInvite.razor +++ /dev/null @@ -1,308 +0,0 @@ -@page "/batch/invite" -@inject IChannelService ChannelService -@inject AccountManagementService AccountManagement -@inject ChannelManagementService ChannelManagement -@inject ChannelGroupManagementService ChannelGroupManagement -@inject BatchTaskManagementService TaskManagement -@inject ISnackbar Snackbar -@using TelegramPanel.Data.Entities - -批量邀请 - Telegram Panel - -批量邀请用户 - - - - - - 配置邀请 - - - - (默认:按频道创建账号执行) - @foreach (var account in accounts) - { - @FormatAccountLabel(account) - } - - - - 全部分组 - @foreach (var g in groups) - { - @g.Name - } - - - - - 全选当前筛选 - - - 选择该分组全部 - - - 清空选择 - - - - - - 频道名称 - 分组 - 创建账号 - 用户名 - - - @context.Title - @(context.Group?.Name ?? "未分组") - @(context.CreatorAccount?.Phone ?? (context.CreatorAccountId?.ToString() ?? "(非系统创建)")) - @(string.IsNullOrWhiteSpace(context.Username) ? "-" : "@" + context.Username) - - - 暂无频道数据 - - - - - - - - - 清空 - - - @if (inviting) - { - - 邀请中... - } - else - { - 开始邀请 - } - - - - - - - - - 邀请结果 - - - - @foreach (var result in inviteResults) - { - -
- - @result.ChannelTitle:@("@" + result.Username) - - 执行账号:@result.ExecutedByAccountId - @if (!result.Success && result.Error != null) - { - @result.Error - } -
-
- } -
- @if (inviteResults.Any()) - { - - - 成功:@inviteResults.Count(r => r.Success) / 总计:@inviteResults.Count(频道:@inviteResults.Select(r => r.ChannelTelegramId).Distinct().Count()) - - } -
-
-
-
- -@code { - [SupplyParameterFromQuery(Name = "channelId")] public int? ChannelDbIdQuery { get; set; } - - private List accounts = new(); - private List channels = new(); - private List groups = new(); - - private int selectedAccountId = 0; - private int filterGroupId = 0; - private bool channelsLoading = true; - private HashSet selectedChannels = new(); - - private string usernames = ""; - private int delayMs = 2000; - private bool inviting = false; - - private List inviteResults = new(); - - private IEnumerable filteredChannels => - channels.Where(c => filterGroupId <= 0 || c.GroupId == filterGroupId); - - protected override async Task OnInitializedAsync() - { - await LoadAccounts(); - await LoadGroups(); - await LoadChannels(); - - if (ChannelDbIdQuery.HasValue && ChannelDbIdQuery.Value > 0) - { - var c = channels.FirstOrDefault(x => x.Id == ChannelDbIdQuery.Value); - if (c != null) - selectedChannels = new HashSet { c }; - } - } - - private async Task LoadAccounts() - { - try - { - var accountList = await AccountManagement.GetActiveAccountsAsync(); - accounts = accountList.ToList(); - } - catch (Exception ex) - { - Snackbar.Add($"加载账号列表失败:{ex.Message}", Severity.Error); - } - } - - private async Task LoadGroups() - { - try - { - var list = await ChannelGroupManagement.GetAllGroupsAsync(); - groups = list.ToList(); - } - catch (Exception ex) - { - Snackbar.Add($"加载频道分组失败:{ex.Message}", Severity.Error); - } - } - - private async Task LoadChannels() - { - channelsLoading = true; - try - { - var channelList = await ChannelManagement.GetAllChannelsAsync(); - channels = channelList.ToList(); - selectedChannels = selectedChannels.Where(c => channels.Any(x => x.Id == c.Id)).ToHashSet(); - } - catch (Exception ex) - { - Snackbar.Add($"加载频道列表失败:{ex.Message}", Severity.Error); - } - finally - { - channelsLoading = false; - } - } - - private void SelectAllFilteredChannels() - { - selectedChannels = filteredChannels.ToHashSet(); - } - - private void SelectCurrentGroupChannels() - { - if (filterGroupId <= 0) - return; - - selectedChannels = channels.Where(c => c.GroupId == filterGroupId).ToHashSet(); - } - - private void ClearSelectedChannels() - { - selectedChannels = new HashSet(); - } - - private async Task StartInvite() - { - inviting = true; - inviteResults.Clear(); - - try - { - var usernameList = usernames.Split('\n', StringSplitOptions.RemoveEmptyEntries) - .Select(u => u.Trim().TrimStart('@')) - .Where(u => !string.IsNullOrEmpty(u)) - .ToList(); - - if (usernameList.Count == 0) - { - Snackbar.Add("请输入至少一个用户名", Severity.Warning); - return; - } - - var targetChannels = selectedChannels.ToList(); - if (targetChannels.Count == 0) - { - Snackbar.Add("请先勾选至少一个频道", Severity.Warning); - return; - } - - var task = new BatchTask - { - TaskType = "invite", - Total = usernameList.Count * targetChannels.Count, - Completed = 0, - Failed = 0, - Config = System.Text.Json.JsonSerializer.Serialize(new - { - ChannelTelegramIds = targetChannels.Select(c => c.TelegramId).ToList(), - Usernames = usernameList, - DelayMs = delayMs, - PreferredExecuteAccountId = selectedAccountId - }) - }; - var createdTask = await TaskManagement.CreateTaskAsync(task); - Snackbar.Add($"已提交后台任务 #{createdTask.Id}(批量邀请)。请到【任务中心】查看进度。", Severity.Success); - } - catch (Exception ex) - { - Snackbar.Add($"邀请失败:{ex.Message}", Severity.Error); - } - finally - { - inviting = false; - } - } - - private void ClearForm() - { - usernames = ""; - inviteResults.Clear(); - } - - private static string FormatAccountLabel(Account account) - { - var nickname = string.IsNullOrWhiteSpace(account.Nickname) ? null : account.Nickname.Trim(); - var username = string.IsNullOrWhiteSpace(account.Username) ? null : account.Username.Trim(); - - var namePart = nickname ?? account.Phone; - if (!string.IsNullOrWhiteSpace(username)) - return $"{namePart} (@{username})"; - return namePart; - } - - private record InviteOutcome( - long ChannelTelegramId, - string ChannelTitle, - string Username, - bool Success, - string? Error, - int ExecutedByAccountId - ); -} diff --git a/src/TelegramPanel.Web/Components/Pages/Channels.razor b/src/TelegramPanel.Web/Components/Pages/Channels.razor index 2d8d559..32ed5a3 100644 --- a/src/TelegramPanel.Web/Components/Pages/Channels.razor +++ b/src/TelegramPanel.Web/Components/Pages/Channels.razor @@ -101,7 +101,6 @@ 查看详情 编辑频道 踢人 - 邀请用户 复制链接 删除频道 @@ -278,11 +277,6 @@ await ShowDetails(channel); } - private void InviteUsers(Channel channel) - { - Navigation.NavigateTo($"/batch/invite?channelId={channel.Id}"); - } - private async Task CopyLinkAsync(Channel channel) { try diff --git a/src/TelegramPanel.Web/Components/Pages/Groups.razor b/src/TelegramPanel.Web/Components/Pages/Groups.razor index 0d48c05..9188245 100644 --- a/src/TelegramPanel.Web/Components/Pages/Groups.razor +++ b/src/TelegramPanel.Web/Components/Pages/Groups.razor @@ -75,7 +75,6 @@ 查看详情 - 邀请成员 复制链接 删除群组 @@ -160,11 +159,6 @@ DialogService.Show("群组详情", parameters, options); } - private void InviteMembers(Group group) - { - Navigation.NavigateTo($"/batch/invite?groupId={group.Id}"); - } - private async Task CopyLink(Group group) { try diff --git a/src/TelegramPanel.Web/Components/Pages/Home.razor b/src/TelegramPanel.Web/Components/Pages/Home.razor index d58015c..ac1abf6 100644 --- a/src/TelegramPanel.Web/Components/Pages/Home.razor +++ b/src/TelegramPanel.Web/Components/Pages/Home.razor @@ -73,9 +73,6 @@ 创建频道 - - 批量邀请 - diff --git a/src/TelegramPanel.Web/Modules/BuiltIn/TaskCatalogModule.cs b/src/TelegramPanel.Web/Modules/BuiltIn/TaskCatalogModule.cs index ba2d2a2..ecf4222 100644 --- a/src/TelegramPanel.Web/Modules/BuiltIn/TaskCatalogModule.cs +++ b/src/TelegramPanel.Web/Modules/BuiltIn/TaskCatalogModule.cs @@ -32,28 +32,6 @@ public sealed class TaskCatalogModule : ITelegramPanelModule, IModuleTaskProvide public IEnumerable GetTasks(ModuleHostContext context) { - yield return new ModuleTaskDefinition - { - Category = "bot", - TaskType = BatchTaskTypes.Invite, - DisplayName = "批量邀请", - Description = "对所选频道批量邀请用户名列表(Bot/执行账号需具备权限)。", - Icon = Icons.Material.Filled.PersonAdd, - CreateRoute = "/batch/invite", - Order = 10 - }; - - yield return new ModuleTaskDefinition - { - Category = "bot", - TaskType = BatchTaskTypes.SetAdmin, - DisplayName = "设置管理员", - Description = "对所选频道批量设置管理员(Bot/执行账号需具备权限)。", - Icon = Icons.Material.Filled.AdminPanelSettings, - CreateRoute = "/batch/admins", - Order = 20 - }; - yield return new ModuleTaskDefinition { Category = "system", diff --git a/src/TelegramPanel.Web/Services/BatchTaskBackgroundService.cs b/src/TelegramPanel.Web/Services/BatchTaskBackgroundService.cs index a33c312..9ef34f2 100644 --- a/src/TelegramPanel.Web/Services/BatchTaskBackgroundService.cs +++ b/src/TelegramPanel.Web/Services/BatchTaskBackgroundService.cs @@ -2,8 +2,6 @@ using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using TelegramPanel.Core.BatchTasks; -using TelegramPanel.Core.Interfaces; using TelegramPanel.Core.Services; using TelegramPanel.Data.Entities; using TelegramPanel.Modules; @@ -71,8 +69,6 @@ public sealed class BatchTaskBackgroundService : BackgroundService { using var scope = _scopeFactory.CreateScope(); var taskManagement = scope.ServiceProvider.GetRequiredService(); - var channelManagement = scope.ServiceProvider.GetRequiredService(); - var channelService = scope.ServiceProvider.GetRequiredService(); var pending = (await taskManagement.GetTasksByStatusAsync("pending")) .OrderBy(t => t.CreatedAt) @@ -89,39 +85,28 @@ public sealed class BatchTaskBackgroundService : BackgroundService try { - switch (pending.TaskType) + // 模块扩展任务:从 DI 中查找对应 TaskType 的执行器 + var handler = scope.ServiceProvider + .GetServices() + .FirstOrDefault(h => string.Equals(h.TaskType, pending.TaskType, StringComparison.OrdinalIgnoreCase)); + + if (handler == null) { - case BatchTaskTypes.Invite: - (completed, failed) = await RunInviteAsync(pending, taskManagement, channelManagement, channelService, completed, failed, cancellationToken); - break; - case BatchTaskTypes.SetAdmin: - (completed, failed) = await RunSetAdminAsync(pending, taskManagement, channelManagement, channelService, completed, failed, cancellationToken); - break; - default: - // 模块扩展任务:从 DI 中查找对应 TaskType 的执行器 - var handler = scope.ServiceProvider - .GetServices() - .FirstOrDefault(h => string.Equals(h.TaskType, pending.TaskType, StringComparison.OrdinalIgnoreCase)); + failed = pending.Total == 0 ? 1 : pending.Total; + await taskManagement.UpdateTaskProgressAsync(pending.Id, completed, failed); + await taskManagement.CompleteTaskAsync(pending.Id, success: false); + _logger.LogWarning("Unsupported batch task type: {TaskType} (task {TaskId})", pending.TaskType, pending.Id); + return; + } - if (handler == null) - { - failed = pending.Total == 0 ? 1 : pending.Total; - await taskManagement.UpdateTaskProgressAsync(pending.Id, completed, failed); - await taskManagement.CompleteTaskAsync(pending.Id, success: false); - _logger.LogWarning("Unsupported batch task type: {TaskType} (task {TaskId})", pending.TaskType, pending.Id); - return; - } + var host = new DbBackedModuleTaskExecutionHost(pending, taskManagement, scope.ServiceProvider); + await handler.ExecuteAsync(host, cancellationToken); - var host = new DbBackedModuleTaskExecutionHost(pending, taskManagement, scope.ServiceProvider); - await handler.ExecuteAsync(host, cancellationToken); - - var after = await taskManagement.GetTaskAsync(pending.Id); - if (after != null) - { - completed = after.Completed; - failed = after.Failed; - } - break; + var after = await taskManagement.GetTaskAsync(pending.Id); + if (after != null) + { + completed = after.Completed; + failed = after.Failed; } // 如果任务被用户取消(当前实现:Cancel 会把状态写成 failed),则不覆盖它 @@ -148,140 +133,6 @@ public sealed class BatchTaskBackgroundService : BackgroundService } } - private static async Task<(int completed, int failed)> RunInviteAsync( - BatchTask task, - BatchTaskManagementService taskManagement, - ChannelManagementService channelManagement, - IChannelService channelService, - int completed, - int failed, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(task.Config)) - throw new InvalidOperationException("任务配置为空"); - - var cfg = JsonSerializer.Deserialize(task.Config); - if (cfg == null || cfg.ChannelTelegramIds.Count == 0 || cfg.Usernames.Count == 0) - throw new InvalidOperationException("任务配置缺少 ChannelTelegramIds 或 Usernames"); - - foreach (var channelTelegramId in cfg.ChannelTelegramIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var latest = await taskManagement.GetTaskAsync(task.Id); - if (latest != null && latest.Status != "running") - return (completed, failed); - - var channel = await channelManagement.GetChannelByTelegramIdAsync(channelTelegramId); - if (channel == null) - { - failed += cfg.Usernames.Count; - await taskManagement.UpdateTaskProgressAsync(task.Id, completed, failed); - continue; - } - - var executeAccountId = await channelManagement.ResolveExecuteAccountIdAsync(channel, cfg.PreferredExecuteAccountId > 0 ? cfg.PreferredExecuteAccountId : null); - if (executeAccountId is not > 0) - { - failed += cfg.Usernames.Count; - await taskManagement.UpdateTaskProgressAsync(task.Id, completed, failed); - continue; - } - - List results; - try - { - results = await channelService.BatchInviteUsersAsync(executeAccountId.Value, channelTelegramId, cfg.Usernames, cfg.DelayMs); - } - catch (Exception ex) - { - results = cfg.Usernames.Select(u => new InviteResult(u, false, ex.Message)).ToList(); - } - - completed += results.Count(r => r.Success); - failed += results.Count(r => !r.Success); - await taskManagement.UpdateTaskProgressAsync(task.Id, completed, failed); - } - - return (completed, failed); - } - - private static async Task<(int completed, int failed)> RunSetAdminAsync( - BatchTask task, - BatchTaskManagementService taskManagement, - ChannelManagementService channelManagement, - IChannelService channelService, - int completed, - int failed, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(task.Config)) - throw new InvalidOperationException("任务配置为空"); - - var cfg = JsonSerializer.Deserialize(task.Config); - if (cfg == null || cfg.ChannelTelegramIds.Count == 0 || cfg.Usernames.Count == 0) - throw new InvalidOperationException("任务配置缺少 ChannelTelegramIds 或 Usernames"); - - var rights = (AdminRights)cfg.Rights; - var adminRequests = cfg.Usernames.Select(u => new AdminRequest(u, rights, cfg.Title)).ToList(); - - foreach (var channelTelegramId in cfg.ChannelTelegramIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var latest = await taskManagement.GetTaskAsync(task.Id); - if (latest != null && latest.Status != "running") - return (completed, failed); - - var channel = await channelManagement.GetChannelByTelegramIdAsync(channelTelegramId); - if (channel == null) - { - failed += cfg.Usernames.Count; - await taskManagement.UpdateTaskProgressAsync(task.Id, completed, failed); - continue; - } - - var executeAccountId = await channelManagement.ResolveExecuteAccountIdAsync(channel, cfg.PreferredExecuteAccountId > 0 ? cfg.PreferredExecuteAccountId : null); - if (executeAccountId is not > 0) - { - failed += cfg.Usernames.Count; - await taskManagement.UpdateTaskProgressAsync(task.Id, completed, failed); - continue; - } - - List results; - try - { - results = await channelService.BatchSetAdminsAsync(executeAccountId.Value, channelTelegramId, adminRequests); - } - catch (Exception ex) - { - results = cfg.Usernames.Select(u => new SetAdminResult(u, false, ex.Message)).ToList(); - } - - completed += results.Count(r => r.Success); - failed += results.Count(r => !r.Success); - await taskManagement.UpdateTaskProgressAsync(task.Id, completed, failed); - } - - return (completed, failed); - } - - private sealed record InviteTaskConfig( - List ChannelTelegramIds, - List Usernames, - int DelayMs, - int PreferredExecuteAccountId - ); - - private sealed record SetAdminTaskConfig( - List ChannelTelegramIds, - List Usernames, - int Rights, - string Title, - int PreferredExecuteAccountId - ); - private sealed class DbBackedModuleTaskExecutionHost : IModuleTaskExecutionHost { private readonly BatchTask _task;