fix(bot): promote/ban 目标管理员处理

- 设置管理员前检测目标是否在群/频道,补充 CHAT_ADMIN_INVITE_REQUIRED 提示

- 踢出/封禁前若目标为管理员自动降权后重试
This commit is contained in:
meoacgx committed 2026-01-03 17:15:46 +08:00
1 parent 68abfdd645
commit efbe67791d
1 file changed
+135 -27
@@ -571,6 +571,13 @@ public class BotTelegramService
if (!canPromote)
throw new InvalidOperationException(reason);
var targetStatus = await GetChatMemberStatusAsync(bot.Token, chatId, userId, cancellationToken);
if (string.Equals(targetStatus, "left", StringComparison.OrdinalIgnoreCase)
|| string.Equals(targetStatus, "kicked", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("目标未加入该频道/群,无法设置管理员(请先把目标加入聊天后再设置管理员)");
}
var args = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
@@ -714,6 +721,13 @@ public class BotTelegramService
if (!canPromote)
throw new InvalidOperationException(reason);
var targetStatus = await GetChatMemberStatusAsync(bot.Token, chatId, userId, cancellationToken);
if (string.Equals(targetStatus, "left", StringComparison.OrdinalIgnoreCase)
|| string.Equals(targetStatus, "kicked", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("目标未加入该频道/群,无法设置管理员(请先把目标加入聊天后再设置管理员)");
}
var args = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
@@ -734,6 +748,10 @@ public class BotTelegramService
catch (Exception ex)
{
var msg = ex.Message;
if (IsChatAdminInviteRequiredError(msg))
{
msg = "目标未加入该频道/群(或需要管理员邀请加入),无法设置管理员:请先把目标加入聊天后再设置管理员";
}
failures[chatId] = msg;
_logger.LogWarning(ex, "PromoteChatMember failed for bot {BotId} chat {ChatId} user {UserId}", botId, chatId, userId);
}
@@ -822,34 +840,33 @@ public class BotTelegramService
if (!canBan)
throw new InvalidOperationException(reason);
if (permanentBan)
{
// 永久封禁:调用 banChatMember(默认永久封禁)
var banArgs = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString(),
["revoke_messages"] = "false" // 不撤回用户的历史消息
};
await _api.CallAsync(bot.Token, "banChatMember", banArgs, cancellationToken);
}
else
{
// 仅踢出:先封禁再立即解封
var banArgs = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString(),
["revoke_messages"] = "false"
};
await _api.CallAsync(bot.Token, "banChatMember", banArgs, cancellationToken);
// 目标如果是管理员:Telegram 不允许直接 ban/kick 管理员,需先取消管理员再踢出
var targetStatus = await GetChatMemberStatusAsync(bot.Token, chatId, userId, cancellationToken);
if (string.Equals(targetStatus, "creator", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("目标是频道创建者,无法踢出/封禁");
var unbanArgs = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString()
};
await _api.CallAsync(bot.Token, "unbanChatMember", unbanArgs, cancellationToken);
if (string.Equals(targetStatus, "administrator", StringComparison.OrdinalIgnoreCase))
{
var (canPromote, promoteReason) = await CanBotPromoteMembersAsync(bot.Token, chatId, botUserId, cancellationToken);
if (!canPromote)
throw new InvalidOperationException($"目标是管理员,且无法取消管理员权限:{promoteReason}");
await DemoteChatMemberAsync(bot.Token, chatId, userId, cancellationToken);
}
try
{
await BanOrKickInternalAsync(bot.Token, chatId, userId, permanentBan, cancellationToken);
}
catch (InvalidOperationException ex) when (IsUserIsAdministratorError(ex.Message))
{
// 兜底:即使前面没识别到/状态变化,也尝试“先降权再踢”
var (canPromote, promoteReason) = await CanBotPromoteMembersAsync(bot.Token, chatId, botUserId, cancellationToken);
if (!canPromote)
throw new InvalidOperationException($"目标是管理员,且无法取消管理员权限:{promoteReason}");
await DemoteChatMemberAsync(bot.Token, chatId, userId, cancellationToken);
await BanOrKickInternalAsync(bot.Token, chatId, userId, permanentBan, cancellationToken);
}
ok++;
@@ -891,4 +908,95 @@ public class BotTelegramService
return (true, "");
}
private static bool IsUserIsAdministratorError(string message)
{
if (string.IsNullOrWhiteSpace(message))
return false;
// 典型:Bot API 调用失败:banChatMember (400) Bad Request: user is an administrator of the chat
return message.IndexOf("administrator of the chat", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static bool IsChatAdminInviteRequiredError(string message)
{
if (string.IsNullOrWhiteSpace(message))
return false;
// 典型:Bot API 调用失败:promoteChatMember (400) Bad Request: CHAT_ADMIN_INVITE_REQUIRED
return message.IndexOf("CHAT_ADMIN_INVITE_REQUIRED", StringComparison.OrdinalIgnoreCase) >= 0;
}
private async Task<string?> GetChatMemberStatusAsync(string token, long chatId, long userId, CancellationToken cancellationToken)
{
var member = await _api.CallAsync(token, "getChatMember", new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString()
}, cancellationToken);
if (member.ValueKind != JsonValueKind.Object)
return null;
return member.TryGetProperty("status", out var statusEl) ? statusEl.GetString() : null;
}
private async Task DemoteChatMemberAsync(string token, long chatId, long userId, CancellationToken cancellationToken)
{
// promoteChatMember:把所有管理员权限设为 false,即可取消管理员身份(demote)
var args = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString(),
["is_anonymous"] = "false",
["can_manage_chat"] = "false",
["can_change_info"] = "false",
["can_post_messages"] = "false",
["can_edit_messages"] = "false",
["can_delete_messages"] = "false",
["can_invite_users"] = "false",
["can_restrict_members"] = "false",
["can_pin_messages"] = "false",
["can_promote_members"] = "false",
["can_manage_video_chats"] = "false",
["can_manage_topics"] = "false",
["can_post_stories"] = "false",
["can_edit_stories"] = "false",
["can_delete_stories"] = "false"
};
await _api.CallAsync(token, "promoteChatMember", args, cancellationToken);
}
private async Task BanOrKickInternalAsync(string token, long chatId, long userId, bool permanentBan, CancellationToken cancellationToken)
{
if (permanentBan)
{
// 永久封禁:调用 banChatMember(默认永久封禁)
var banArgs = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString(),
["revoke_messages"] = "false" // 不撤回用户的历史消息
};
await _api.CallAsync(token, "banChatMember", banArgs, cancellationToken);
return;
}
// 仅踢出:先封禁再立即解封
var kickArgs = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString(),
["revoke_messages"] = "false"
};
await _api.CallAsync(token, "banChatMember", kickArgs, cancellationToken);
var unbanArgs = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString()
};
await _api.CallAsync(token, "unbanChatMember", unbanArgs, cancellationToken);
}
}