diff --git a/README.md b/README.md
index c310413..01922eb 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,35 @@
- 📥 **多账号批量导入/登录**:支持 Session/压缩包导入;支持手机号验证码登录与 2FA 密码
- 🔁 **账号维度一键切换操作**:选择不同账号创建频道/群组、查看与管理账号创建的数据
- 👥 **批量运营能力**:批量邀请成员/机器人、批量设置管理员、导出链接等高频操作
+- 🧩 **用户任务:批量订阅/加群**:任务中心新建「用户任务 → 批量订阅/加群」,支持按账号分类筛选;账号列表支持单账号“进群/订阅”入口(多链接换行分隔)
- 🔐 **二级密码(2FA)与找回邮箱**:支持单个/批量修改二级密码;支持绑定/换绑 2FA 找回邮箱(验证码确认)
- 🧯 **忘记二级密码可申请重置**:支持单个或批量向 Telegram 提交“忘记密码重置”申请(通常等待 7 天后可重新设置)
- 🪪 **账号资料管理**:支持单账号编辑昵称/Bio/用户名/头像;支持批量修改昵称(自动追加手机号后 4 位便于区分)与批量修改 Bio
- 🤖 **Bot 频道管理**:用于管理“频道创建人不在系统中”的频道(把 Bot 设为管理员即可纳入管理),支持批量导出链接、批量设置管理员(踢人能力可扩展)
+## 批量订阅/加群(用户任务)
+
+说明:这是**用户账号**任务(Bot 无法主动加群/订阅)。
+
+### 入口 1:任务中心批量执行
+
+1. 进入「任务中心」→ 点击「新建任务」
+2. 选择:任务分类 =「用户任务」,任务类型 =「批量订阅/加群」
+3. 选择执行账号(支持按**账号分类**筛选 + 搜索)
+4. 粘贴链接/用户名(**换行分隔**),设置间隔(建议 1500–4000ms)
+5. 提交后到「任务中心」查看进度与结果
+
+### 入口 2:账号列表单账号执行
+
+在「账号列表」每行账号的操作区点击「进群/订阅」,输入多个链接/用户名(换行分隔)后确认即可提交后台任务。
+
+### 支持的链接/格式
+
+- `https://t.me/xxx`
+- `t.me/+hash`
+- `@username` / `username`
+- `tg://join?invite=hash`
+
## 🧊 防冻结指南(新号必看)
diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs
index f9b3564..61c70a0 100644
--- a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs
+++ b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs
@@ -659,8 +659,8 @@ public class AccountTelegramToolsService
var title = chat switch
{
- Channel c => c.title,
- Chat c => c.title,
+ TL.Channel c => c.title,
+ TL.Chat c => c.title,
_ => null
};
diff --git a/src/TelegramPanel.Web/Components/Dialogs/CreateBatchTaskDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/CreateBatchTaskDialog.razor
index d79026d..b5c3c60 100644
--- a/src/TelegramPanel.Web/Components/Dialogs/CreateBatchTaskDialog.razor
+++ b/src/TelegramPanel.Web/Components/Dialogs/CreateBatchTaskDialog.razor
@@ -1,5 +1,6 @@
@namespace TelegramPanel.Web.Components.Dialogs
@inject AccountManagementService AccountManagement
+@inject AccountCategoryManagementService CategoryManagement
@inject TelegramPanel.Core.Services.AccountRiskService RiskService
@inject BatchTaskManagementService TaskManagement
@inject ISnackbar Snackbar
@@ -43,7 +44,7 @@
- 批量订阅频道/加群(用户任务):支持输入多个链接/用户名,换行分隔。支持:https://t.me/xxx、t.me/+hash、@username、username、tg://join?invite=hash。
+ 批量订阅频道/加群(用户任务):支持输入多个链接/用户名,换行分隔。支持:https://t.me/xxx、t.me/+hash、@@username、username、tg://join?invite=hash。
选择执行账号:@selectedAccounts.Count / @accounts.Count
- 全选
+ 全选当前筛选
清空
-
+
+
+
+ 全部分类
+ 未分类
+ @foreach (var c in categories)
+ {
+ @c.Name
+ }
+
+
+
+
+
+
@@ -68,11 +83,13 @@
手机号
昵称
用户名
+ 分类
@context.Phone
@(string.IsNullOrWhiteSpace(context.Nickname) ? "-" : context.Nickname)
@(string.IsNullOrWhiteSpace(context.Username) ? "-" : "@" + context.Username)
+ @GetAccountCategoryName(context.CategoryId)
暂无账号
@@ -150,7 +167,9 @@
private bool loadingAccounts;
private List accounts = new();
+ private List categories = new();
private string accountSearch = "";
+ private int filterAccountCategoryId = 0;
private HashSet selectedAccounts = new();
private IEnumerable AvailableOptions => Options.Where(o => o.Category == selectedCategory);
@@ -159,6 +178,11 @@
private IEnumerable FilteredAccounts =>
accounts.Where(a =>
{
+ if (filterAccountCategoryId > 0 && a.CategoryId != filterAccountCategoryId)
+ return false;
+ if (filterAccountCategoryId == -1 && a.CategoryId != null)
+ return false;
+
if (string.IsNullOrWhiteSpace(accountSearch))
return true;
var q = accountSearch.Trim();
@@ -170,9 +194,23 @@
protected override async Task OnInitializedAsync()
{
EnsureTaskTypeValid();
+ await LoadCategories();
await LoadAccounts();
}
+ private async Task LoadCategories()
+ {
+ try
+ {
+ var list = await CategoryManagement.GetAllCategoriesAsync();
+ categories = list.OrderBy(c => c.Id).ToList();
+ }
+ catch
+ {
+ categories = new List();
+ }
+ }
+
private async Task LoadAccounts()
{
loadingAccounts = true;
@@ -201,9 +239,16 @@
}
}
- private void SelectAll() => selectedAccounts = accounts.ToHashSet();
+ private void SelectAll() => selectedAccounts = FilteredAccounts.ToHashSet();
private void ClearSelection() => selectedAccounts = new HashSet();
+ private string GetAccountCategoryName(int? categoryId)
+ {
+ if (categoryId is not > 0)
+ return "未分类";
+ return categories.FirstOrDefault(c => c.Id == categoryId)?.Name ?? $"#{categoryId}";
+ }
+
private async Task Create()
{
if (creating)
diff --git a/src/TelegramPanel.Web/Components/Dialogs/JoinSubscribeDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/JoinSubscribeDialog.razor
index ae6175d..0535d69 100644
--- a/src/TelegramPanel.Web/Components/Dialogs/JoinSubscribeDialog.razor
+++ b/src/TelegramPanel.Web/Components/Dialogs/JoinSubscribeDialog.razor
@@ -14,7 +14,7 @@
将使用指定账号订阅频道/加入群组。支持输入多个链接/用户名,换行分隔。
- 支持:https://t.me/xxx、t.me/+hash、@username、username、tg://join?invite=hash
+ 支持:https://t.me/xxx、t.me/+hash、@@username、username、tg://join?invite=hash
@@ -155,4 +155,3 @@
private void Cancel() => MudDialog.Close();
}
-