feat(kick): add module page and queue /api/kick

This commit is contained in:
meoacgx committed 2026-01-22 04:06:21 +08:00
1 parent 0401312ee3
commit f551da0a8b
7 files changed
+551 -156

No files matched your search

@@ -0,0 +1,196 @@
@namespace TelegramPanel.Web.Components.ModulePages
@inject TelegramPanel.Core.Services.BotManagementService BotManagement
@inject TelegramPanel.Core.Services.BatchTaskManagementService TaskManagement
@inject ISnackbar Snackbar
@inject NavigationManager Navigation
@using System.Text.Json
@using TelegramPanel.Core.BatchTasks
@using TelegramPanel.Data.Entities
@using TelegramPanel.Web.ExternalApi
@using Microsoft.AspNetCore.Components
<MudText Typo="Typo.h4" Class="mb-4 tp-page-title">踢人/封禁</MudText>
<MudPaper Class="pa-4" Elevation="0" Outlined="true">
<MudStack Spacing="2">
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
提交后会在后台执行,可到【任务中心】查看进度与结果。频道数量较多时可能需要较长时间。
</MudAlert>
<MudSelect T="int" Value="@selectedBotId" ValueChanged="@(async (int id) => await OnSelectedBotChanged(id))"
Label="选择机器人" Variant="Variant.Outlined" Dense="true" Disabled="@loading">
<MudSelectItem Value="0">全部机器人(谨慎)</MudSelectItem>
@foreach (var b in bots)
{
<MudSelectItem Value="@b.Id">@b.Name</MudSelectItem>
}
</MudSelect>
<MudNumericField T="long" @bind-Value="userId" Label="用户 ID(user_id)" Variant="Variant.Outlined" Dense="true"
Min="1" Disabled="@loading" />
<MudSwitch @bind-Value="permanentBan" Color="Color.Error" Disabled="@loading"
Label="永久封禁(否则仅踢出,可重新加入)" />
<MudSwitch @bind-Value="useAllChats" Disabled="@(loading || selectedBotId == 0)"
Label="使用全部频道/群组(关闭后可手动输入 chat_id 列表)" />
@if (!useAllChats && selectedBotId != 0)
{
<MudTextField @bind-Value="chatIdsText" Label="chat_id 列表(可选)" Variant="Variant.Outlined" Lines="3"
Placeholder="支持逗号/空格/换行分隔,例如:123,456,789"
Disabled="@loading" />
}
<MudStack Row="true" Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.PersonRemove"
Disabled="@(!CanSubmit)" OnClick="SubmitAsync">
提交任务
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Default" Disabled="@loading"
OnClick="@(() => Navigation.NavigateTo("/tasks"))">
打开任务中心
</MudButton>
</MudStack>
</MudStack>
</MudPaper>
@code
{
[Parameter] public string ModuleId { get; set; } = "";
[Parameter] public string PageKey { get; set; } = "";
private bool loading = true;
private List<Bot> bots = new();
private int selectedBotId = 0;
private long userId;
private bool permanentBan;
private bool useAllChats = true;
private string chatIdsText = "";
private bool CanSubmit => !loading && userId > 0 && (selectedBotId == 0 || bots.Any(x => x.Id == selectedBotId));
protected override async Task OnInitializedAsync()
{
try
{
bots = (await BotManagement.GetAllBotsAsync())
.Where(b => b.IsActive)
.OrderBy(b => b.Id)
.ToList();
}
catch (Exception ex)
{
Snackbar.Add($"加载机器人失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task OnSelectedBotChanged(int id)
{
selectedBotId = id;
if (selectedBotId == 0)
useAllChats = true;
await Task.CompletedTask;
}
private async Task SubmitAsync()
{
if (!CanSubmit)
return;
var botId = selectedBotId;
var runUseAllChats = botId == 0 ? true : useAllChats;
var chatIds = runUseAllChats ? new List<long>() : ParseChatIds(chatIdsText);
loading = true;
try
{
var total = await CountTargetsAsync(botId, runUseAllChats, chatIds);
if (total <= 0)
{
Snackbar.Add("未匹配到任何可操作的频道/群组(请先同步 Bot 频道数据,或检查 chat_id 列表)", Severity.Warning);
return;
}
var log = new KickTaskLog
{
ApiName = "模块页面",
BotId = botId,
UseAllChats = runUseAllChats,
ChatIds = chatIds.Where(x => x != 0).Distinct().OrderBy(x => x).ToList(),
UserId = userId,
PermanentBan = permanentBan,
RequestedAtUtc = DateTime.UtcNow
};
var task = new BatchTask
{
TaskType = BatchTaskTypes.ExternalApiKick,
Total = total,
Completed = 0,
Failed = 0,
Config = JsonSerializer.Serialize(log, new JsonSerializerOptions { WriteIndented = true })
};
var created = await TaskManagement.CreateTaskAsync(task);
Snackbar.Add($"已提交后台任务 #{created.Id}。请到【任务中心】查看进度。", Severity.Success);
Navigation.NavigateTo("/tasks");
}
catch (Exception ex)
{
Snackbar.Add($"提交失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task<int> CountTargetsAsync(int botId, bool runUseAllChats, IReadOnlyCollection<long> chatIds)
{
if (botId > 0)
{
var chats = (await BotManagement.GetChatsAsync(botId)).ToList();
if (!runUseAllChats && chatIds.Count > 0)
chats = chats.Where(c => chatIds.Contains(c.TelegramId)).ToList();
return chats.Count;
}
// botId==0: all bots (force useAllChats)
var botsList = (await BotManagement.GetAllBotsAsync())
.Where(b => b.IsActive)
.OrderBy(b => b.Id)
.ToList();
var total = 0;
foreach (var b in botsList)
{
var chats = await BotManagement.GetChatsAsync(b.Id);
total += chats.Count();
}
return total;
}
private static List<long> ParseChatIds(string raw)
{
raw = (raw ?? "").Trim();
if (raw.Length == 0)
return new List<long>();
var parts = raw
.Replace("\r", "\n", StringComparison.Ordinal)
.Split(new[] { ',', ';', ' ', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var ids = new List<long>();
foreach (var p in parts)
{
if (long.TryParse(p, out var v) && v != 0)
ids.Add(v);
}
return ids;
}
}