改进Bot设置管理员:检测权限并显示失败原因

This commit is contained in:
meoacgx committed 2025-12-19 19:18:34 +08:00
1 parent 0e08a18f51
commit ebabf72ac4
2 files changed
+132 -6

No files matched your search

@@ -305,6 +305,8 @@ public class BotTelegramService
if (userId <= 0)
throw new ArgumentException("userId 无效", nameof(userId));
var botUserId = await GetBotUserIdAsync(bot.Token, cancellationToken);
var ok = 0;
foreach (var chatId in channelTelegramIds.Distinct())
{
@@ -312,6 +314,10 @@ public class BotTelegramService
try
{
var (canPromote, reason) = await CanBotPromoteMembersAsync(bot.Token, chatId, botUserId, cancellationToken);
if (!canPromote)
throw new InvalidOperationException(reason);
var args = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
@@ -414,4 +420,108 @@ public class BotTelegramService
bool PinMessages,
bool PromoteMembers
);
public async Task<PromoteAdminsResult> PromoteChatMemberWithResultAsync(
int botId,
IReadOnlyList<long> channelTelegramIds,
long userId,
BotAdminRights rights,
CancellationToken cancellationToken)
{
var bot = await _botManagement.GetBotAsync(botId)
?? throw new InvalidOperationException($"机器人不存在:{botId}");
if (!bot.IsActive)
throw new InvalidOperationException("该机器人已停用");
if (userId <= 0)
throw new ArgumentException("userId 无效", nameof(userId));
var distinctIds = channelTelegramIds.Distinct().ToList();
var failures = new Dictionary<long, string>();
var botUserId = await GetBotUserIdAsync(bot.Token, cancellationToken);
var ok = 0;
foreach (var chatId in distinctIds)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var (canPromote, reason) = await CanBotPromoteMembersAsync(bot.Token, chatId, botUserId, cancellationToken);
if (!canPromote)
throw new InvalidOperationException(reason);
var args = new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = userId.ToString(),
["can_manage_chat"] = rights.ManageChat ? "true" : "false",
["can_post_messages"] = rights.PostMessages ? "true" : "false",
["can_edit_messages"] = rights.EditMessages ? "true" : "false",
["can_delete_messages"] = rights.DeleteMessages ? "true" : "false",
["can_invite_users"] = rights.InviteUsers ? "true" : "false",
["can_restrict_members"] = rights.RestrictMembers ? "true" : "false",
["can_pin_messages"] = rights.PinMessages ? "true" : "false",
["can_promote_members"] = rights.PromoteMembers ? "true" : "false"
};
await _api.CallAsync(bot.Token, "promoteChatMember", args, cancellationToken);
ok++;
}
catch (Exception ex)
{
var msg = ex.Message;
failures[chatId] = msg;
_logger.LogWarning(ex, "PromoteChatMember failed for bot {BotId} chat {ChatId} user {UserId}", botId, chatId, userId);
}
}
return new PromoteAdminsResult(ok, distinctIds.Count, failures);
}
public sealed record PromoteAdminsResult(int SuccessCount, int TotalCount, IReadOnlyDictionary<long, string> Failures);
private async Task<long> GetBotUserIdAsync(string token, CancellationToken cancellationToken)
{
var me = await _api.CallAsync(token, "getMe", new Dictionary<string, string?>(), cancellationToken);
if (me.ValueKind != JsonValueKind.Object)
throw new InvalidOperationException("Bot API getMe 返回格式异常");
if (!me.TryGetProperty("id", out var idEl) || !idEl.TryGetInt64(out var id))
throw new InvalidOperationException("Bot API getMe 缺少 id");
return id;
}
private async Task<(bool CanPromote, string Reason)> CanBotPromoteMembersAsync(string token, long chatId, long botUserId, CancellationToken cancellationToken)
{
var member = await _api.CallAsync(token, "getChatMember", new Dictionary<string, string?>
{
["chat_id"] = chatId.ToString(),
["user_id"] = botUserId.ToString()
}, cancellationToken);
if (member.ValueKind != JsonValueKind.Object)
return (false, "无法检测机器人权限(getChatMember 返回格式异常)");
var status = member.TryGetProperty("status", out var statusEl) ? (statusEl.GetString() ?? "") : "";
if (string.Equals(status, "creator", StringComparison.OrdinalIgnoreCase))
return (true, "");
if (!string.Equals(status, "administrator", StringComparison.OrdinalIgnoreCase))
return (false, "机器人不是管理员(请先把机器人设为管理员)");
var canPromote = ReadBool(member, "can_promote_members");
if (!canPromote)
return (false, "机器人缺少“添加管理员”权限(请在频道管理员设置里给机器人开启“添加管理员/添加新管理员”)");
return (true, "");
}
private static bool ReadBool(JsonElement obj, string name)
{
return obj.TryGetProperty(name, out var el)
&& (el.ValueKind == JsonValueKind.True || el.ValueKind == JsonValueKind.False)
&& el.GetBoolean();
}
}
@@ -1,12 +1,13 @@
@namespace TelegramPanel.Web.Components.Dialogs
@inject TelegramPanel.Core.Services.Telegram.BotTelegramService BotTelegram
@inject ISnackbar Snackbar
@using System.Linq
<MudDialog>
<DialogContent>
<MudStack Spacing="2">
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
请输入要设置为管理员的 Telegram 用户 ID(不是手机号/用户名)。机器人需要在目标频道具备“添加管理员”权限。
请输入要设置为管理员的 Telegram 用户 ID(不是手机号/用户名)。机器人需要在目标频道具备“添加管理员/添加新管理员”权限,否则会失败。
</MudAlert>
<MudNumericField T="long?" @bind-Value="userId" Label="用户 ID" Variant="Variant.Outlined" Min="1" Required="true" />
@@ -40,7 +41,7 @@
</MudGrid>
<MudText Typo="Typo.caption" Color="Color.Secondary">
将对已选频道批量执行 promoteChatMember;失败的频道会被跳过(你可以在日志中查看原因)。
将对已选频道批量执行 promoteChatMember;失败会给出原因(常见:机器人不是管理员/缺少“添加管理员”权限/目标用户不在频道内)。
</MudText>
</MudStack>
</DialogContent>
@@ -104,7 +105,7 @@
saving = true;
try
{
var ok = await BotTelegram.PromoteChatMemberAsync(
var result = await BotTelegram.PromoteChatMemberWithResultAsync(
botId: BotId,
channelTelegramIds: ChannelTelegramIds,
userId: userId.Value,
@@ -121,8 +122,24 @@
cancellationToken: CancellationToken.None
);
Snackbar.Add($"已设置管理员:成功 {ok}/{ChannelTelegramIds.Distinct().Count()} 个频道", ok > 0 ? Severity.Success : Severity.Warning);
MudDialog.Close(DialogResult.Ok(ok));
if (result.SuccessCount == 0)
{
var first = result.Failures.Values.FirstOrDefault() ?? "未知原因";
Snackbar.Add($"设置失败:{first}", Severity.Error);
return;
}
if (result.Failures.Count > 0)
{
var first = result.Failures.Values.FirstOrDefault() ?? "未知原因";
Snackbar.Add($"部分失败:成功 {result.SuccessCount}/{result.TotalCount},示例原因:{first}", Severity.Warning);
}
else
{
Snackbar.Add($"已设置管理员:成功 {result.SuccessCount}/{result.TotalCount} 个频道", Severity.Success);
}
MudDialog.Close(DialogResult.Ok(result.SuccessCount));
}
catch (Exception ex)
{
@@ -151,4 +168,3 @@
public bool PromoteMembers { get; set; }
}
}