@namespace TelegramPanel.Web.Components.Dialogs @inject AccountManagementService AccountManagement @inject TelegramPanel.Core.Services.AccountRiskService RiskService @inject BatchTaskManagementService TaskManagement @inject ISnackbar Snackbar @inject IDialogService DialogService @inject NavigationManager Navigation @using System.Text.Json @using TelegramPanel.Core.BatchTasks @using TelegramPanel.Data.Entities 任务会提交到后台执行,可在【任务中心】查看进度与结果。 用户任务 Bot 任务 @foreach (var opt in AvailableOptions) { @opt.DisplayName } @if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.Description)) { @CurrentOption.Description } @if (selectedTaskType == BatchTaskTypes.UserJoinSubscribe) { 批量订阅频道/加群(用户任务):支持输入多个链接/用户名,换行分隔。支持:https://t.me/xxx、t.me/+hash、@username、username、tg://join?invite=hash。 选择执行账号:@selectedAccounts.Count / @accounts.Count 全选 清空 手机号 昵称 用户名 @context.Phone @(string.IsNullOrWhiteSpace(context.Nickname) ? "-" : context.Nickname) @(string.IsNullOrWhiteSpace(context.Username) ? "-" : "@" + context.Username) 暂无账号 } else if (selectedTaskType == BatchTaskTypes.Invite) { 当前版本暂未在任务中心内置“批量邀请”的创建表单,请使用“批量邀请”页面创建任务。 前往批量邀请 } else if (selectedTaskType == BatchTaskTypes.SetAdmin) { 当前版本暂未在任务中心内置“设置管理员”的创建表单,请使用“批量设置管理员”页面创建任务。 前往批量设置管理员 } 关闭 @if (creating) { 提交中... } else { 提交任务 } @code { [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!; private sealed record TaskOption(string Category, string TaskType, string DisplayName, string? Description); private static readonly List Options = new() { new("bot", BatchTaskTypes.Invite, "批量邀请", "对所选频道批量邀请用户名列表(Bot/执行账号需具备权限)。"), new("bot", BatchTaskTypes.SetAdmin, "设置管理员", "对所选频道批量设置管理员(Bot/执行账号需具备权限)。"), new("user", BatchTaskTypes.UserJoinSubscribe, "批量订阅/加群", "用户账号批量加入群组/订阅频道(Bot 无法主动加入)。") }; private string _selectedCategory = "user"; private string selectedCategory { get => _selectedCategory; set { if (string.Equals(_selectedCategory, value, StringComparison.Ordinal)) return; _selectedCategory = value; EnsureTaskTypeValid(); } } private string selectedTaskType = BatchTaskTypes.UserJoinSubscribe; private bool creating; // join/subscribe config private string linksText = ""; private int delayMs = 2000; private bool loadingAccounts; private List accounts = new(); private string accountSearch = ""; private HashSet selectedAccounts = new(); private IEnumerable AvailableOptions => Options.Where(o => o.Category == selectedCategory); private TaskOption? CurrentOption => Options.FirstOrDefault(o => o.TaskType == selectedTaskType); private IEnumerable FilteredAccounts => accounts.Where(a => { if (string.IsNullOrWhiteSpace(accountSearch)) return true; var q = accountSearch.Trim(); return a.Phone.Contains(q, StringComparison.OrdinalIgnoreCase) || (a.Nickname?.Contains(q, StringComparison.OrdinalIgnoreCase) ?? false) || (a.Username?.Contains(q, StringComparison.OrdinalIgnoreCase) ?? false); }); protected override async Task OnInitializedAsync() { EnsureTaskTypeValid(); await LoadAccounts(); } private async Task LoadAccounts() { loadingAccounts = true; try { var list = await AccountManagement.GetActiveAccountsAsync(); accounts = list.OrderBy(a => a.Id).ToList(); selectedAccounts = selectedAccounts.Where(a => accounts.Any(x => x.Id == a.Id)).ToHashSet(); } catch (Exception ex) { Snackbar.Add($"加载账号失败:{ex.Message}", Severity.Error); } finally { loadingAccounts = false; } } private void EnsureTaskTypeValid() { if (AvailableOptions.All(o => o.TaskType != selectedTaskType)) { selectedTaskType = AvailableOptions.FirstOrDefault()?.TaskType ?? Options.First().TaskType; } } private void SelectAll() => selectedAccounts = accounts.ToHashSet(); private void ClearSelection() => selectedAccounts = new HashSet(); private async Task Create() { if (creating) return; EnsureTaskTypeValid(); if (selectedTaskType != BatchTaskTypes.UserJoinSubscribe) { Snackbar.Add("当前任务类型暂不支持从任务中心直接创建", Severity.Info); return; } var links = ParseLines(linksText); if (links.Count == 0) { Snackbar.Add("请至少输入一个频道/群链接", Severity.Warning); return; } var selected = selectedAccounts.ToList(); if (selected.Count == 0) { Snackbar.Add("请至少选择一个执行账号", Severity.Warning); return; } // 批量风控检查:加入群/订阅频道是敏感操作 var batchCheck = RiskService.CheckBatchAccounts(selected); if (batchCheck.HasRiskyAccounts) { var parameters = new DialogParameters { ["Message"] = $"检测到 {batchCheck.RiskyCount} 个风险账号", ["DetailedMessage"] = batchCheck.GetRiskySummary(), ["ShowRecommendations"] = true, ["ShowExcludeOption"] = true, ["RiskyAccounts"] = batchCheck.RiskyAccounts.ToList() }; var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large }; var dialog = DialogService.Show("批量操作风控警告", parameters, options); var result = await dialog.Result; if (result.Canceled) return; if (result.Data is TelegramPanel.Core.Services.RiskWarningAction action && action == TelegramPanel.Core.Services.RiskWarningAction.ExcludeRisky) { selected = batchCheck.SafeAccounts.ToList(); if (selected.Count == 0) { Snackbar.Add("排除风险账号后无剩余账号", Severity.Info); return; } } } bool? confirm = await DialogService.ShowMessageBox( "确认提交任务", $"将对 {selected.Count} 个账号执行 {links.Count} 个链接的订阅/加群操作,总计 {selected.Count * links.Count} 次。是否继续?", yesText: "继续", cancelText: "取消"); if (confirm != true) return; creating = true; try { var ids = selected.Select(a => a.Id).Distinct().ToList(); var task = new BatchTask { TaskType = BatchTaskTypes.UserJoinSubscribe, Total = ids.Count * links.Count, Completed = 0, Failed = 0, Config = JsonSerializer.Serialize(new { AccountIds = ids, Links = links, DelayMs = delayMs }) }; var created = await TaskManagement.CreateTaskAsync(task); Snackbar.Add($"已提交后台任务 #{created.Id}(批量订阅/加群)。请到【任务中心】查看进度。", Severity.Success); MudDialog.Close(DialogResult.Ok(created.Id)); } catch (Exception ex) { Snackbar.Add($"提交失败:{ex.Message}", Severity.Error); } finally { creating = false; } } private void NavigateTo(string url) { MudDialog.Close(); Navigation.NavigateTo(url); } private static List ParseLines(string? text) { if (string.IsNullOrWhiteSpace(text)) return new List(); return text .Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries) .Select(x => (x ?? string.Empty).Trim()) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } private void Cancel() => MudDialog.Close(); }