fix(channel-admin): bot/频道权限自动降级避免403

- 频道非forum自动移除 ManageTopics;目标为Bot自动移除 InviteUsers 等不适用权限

- 执行账号可授予范围内自动降级重试,减少 RIGHT_FORBIDDEN

- Bot频道批量设管理员仅要求执行账号具备添加管理员权限
This commit is contained in:
meoacgx committed 2026-01-03 13:35:18 +08:00
1 parent 95def3af1f
commit d6edc8cf95
2 files changed
+132 -17

No files matched your search

@@ -354,6 +354,9 @@ public class ChannelService : IChannelService
if (ex.Message.Contains("USER_ALREADY_PARTICIPANT", StringComparison.OrdinalIgnoreCase))
return new InviteResult(username, true);
if (ex.Message.Contains("RIGHT_FORBIDDEN", StringComparison.OrdinalIgnoreCase))
return new InviteResult(username, false, "RIGHT_FORBIDDEN:执行账号缺少邀请权限,或该频道限制邀请");
return new InviteResult(username, false, ex.Message);
}
catch (Exception ex)
@@ -394,15 +397,72 @@ public class ChannelService : IChannelService
var resolved = await client.Contacts_ResolveUsername(username);
var chatAdminRights = ConvertAdminRights(rights);
var requestedRights = SanitizeRequestedAdminRights(channel, resolved.User, rights);
var effectiveRights = requestedRights;
if (requestedRights != rights)
{
_logger.LogInformation(
"Admin rights sanitized for channel {ChannelId} target @{Username}: requested={Requested} sanitized={Sanitized}",
channelId, username, rights, requestedRights);
}
async Task PromoteAsync(Interfaces.AdminRights toGrant)
{
var chatAdminRights = ConvertAdminRights(toGrant);
await client.Channels_EditAdmin(channel, resolved.User, chatAdminRights, title);
}
async Task<Interfaces.AdminRights?> TryGetExecutorAdminRightsAsync()
{
try
{
var meId = client.User?.id ?? 0;
if (meId <= 0)
return null;
return await TryGetGrantedAdminRightsAsync(client, channel, meId);
}
catch
{
return null;
}
}
async Task<bool> TryPromoteWithFallbackAsync(Interfaces.AdminRights toGrant)
{
try
{
await PromoteAsync(toGrant);
effectiveRights = toGrant;
return true;
}
catch (RpcException ex) when (ex.Message.Contains("RIGHT_FORBIDDEN", StringComparison.OrdinalIgnoreCase))
{
// 如果执行账号能“提升管理员”,但不能授予某些权限(比如 add_admins/anonymous/manage_topics),则交集降级后重试一次
var executorRights = await TryGetExecutorAdminRightsAsync();
if (!executorRights.HasValue)
throw;
var reduced = toGrant & executorRights.Value;
if (reduced == toGrant || reduced == Interfaces.AdminRights.None)
throw;
_logger.LogInformation(
"Promote rights downgraded for channel {ChannelId}: requested={Requested} executor={Executor} effective={Effective}",
channelId, requestedRights, executorRights.Value, reduced);
await PromoteAsync(reduced);
effectiveRights = reduced;
return true;
}
}
try
{
await client.Channels_EditAdmin(channel, resolved.User, chatAdminRights, title);
await TryPromoteWithFallbackAsync(requestedRights);
}
catch (RpcException ex) when (ex.Message.Contains("USER_NOT_PARTICIPANT", StringComparison.OrdinalIgnoreCase))
{
// 目标用户不在频道:先尝试邀请进频道,再重试一次提升权限
// 目标用户不在频道:先尝试邀请进频道,再提升权限(邀请可能会因缺少 invite 权限而失败)
try
{
await client.Channels_InviteToChannel(channel, new InputUser(resolved.User.id, resolved.User.access_hash));
@@ -413,16 +473,33 @@ public class ChannelService : IChannelService
}
catch (RpcException inviteEx) when (inviteEx.Message.Contains("USER_BOT", StringComparison.OrdinalIgnoreCase))
{
// 目标是 Bot:邀请成员可能不被允许,继续尝试提升(部分频道/权限组合仍可直接提升)
// 目标是 Bot:邀请成员可能不被允许,继续尝试提升
_logger.LogDebug(inviteEx, "Invite bot as member is not allowed for channel {ChannelId}, continue promoting", channelId);
}
catch (RpcException inviteEx) when (inviteEx.Message.Contains("RIGHT_FORBIDDEN", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
"目标用户不在频道,且执行账号缺少“邀请用户”权限,无法先邀请再设置管理员。", inviteEx);
}
await client.Channels_EditAdmin(channel, resolved.User, chatAdminRights, title);
try
{
await TryPromoteWithFallbackAsync(requestedRights);
}
catch (RpcException ex2) when (ex2.Message.Contains("RIGHT_FORBIDDEN", StringComparison.OrdinalIgnoreCase))
{
var executor = await TryGetExecutorAdminRightsAsync();
var executorHint = executor.HasValue ? $"执行账号可授予:{FormatAdminRights(executor.Value)}。" : "";
throw new InvalidOperationException(
$"RIGHT_FORBIDDEN:执行账号无权设置管理员,或请求授予的权限超出可授予范围。{executorHint}", ex2);
}
}
catch (RpcException ex) when (ex.Message.Contains("RIGHT_FORBIDDEN", StringComparison.OrdinalIgnoreCase))
{
var executor = await TryGetExecutorAdminRightsAsync();
var executorHint = executor.HasValue ? $"执行账号可授予:{FormatAdminRights(executor.Value)}。" : "";
throw new InvalidOperationException(
"RIGHT_FORBIDDEN:执行账号缺少权限。请确认执行账号是频道创建者,或在频道里拥有“邀请用户/添加管理员”等权限。");
$"RIGHT_FORBIDDEN:执行账号无权设置管理员,或请求授予的权限超出可授予范围。{executorHint}", ex);
}
// 权限校验:Telegram 可能会静默“削减”你请求的权限(典型:执行账号本身没有 add_admins/promote 权限)
@@ -431,12 +508,12 @@ public class ChannelService : IChannelService
var granted = await TryGetGrantedAdminRightsAsync(client, channel, resolved.User.id);
if (granted.HasValue)
{
var missing = rights & ~granted.Value;
var missing = effectiveRights & ~granted.Value;
if (missing != Interfaces.AdminRights.None)
{
throw new InvalidOperationException(
$"已设置管理员,但部分权限未生效:{FormatAdminRights(missing)}。" +
"请确认:执行账号是该频道创建者,或执行账号在频道内拥有“添加管理员”权限。");
"请确认:执行账号是该频道创建者,或执行账号在频道内拥有足够的授权权限。");
}
}
}
@@ -454,6 +531,49 @@ public class ChannelService : IChannelService
return true;
}
private static Interfaces.AdminRights SanitizeRequestedAdminRights(Channel channel, User targetUser, Interfaces.AdminRights rights)
{
var sanitized = rights;
// 频道(broadcast)不支持“管理话题”;仅 forum 超级群才有话题
var isMegagroup = TryGetBoolMember(channel, "megagroup");
var isForum = TryGetBoolMember(channel, "forum");
if (!isMegagroup || !isForum)
sanitized &= ~Interfaces.AdminRights.ManageTopics;
// Bot 在频道内的可授予权限有额外限制(例如 invite_users 常见会被拒绝)
var isBot = TryGetBoolMember(targetUser, "bot");
if (isBot)
{
sanitized &= ~Interfaces.AdminRights.InviteUsers;
sanitized &= ~Interfaces.AdminRights.Anonymous;
sanitized &= ~Interfaces.AdminRights.ManageTopics;
}
return sanitized;
}
private static bool TryGetBoolMember(object obj, string memberName)
{
try
{
var t = obj.GetType();
var prop = t.GetProperty(memberName);
if (prop != null && prop.PropertyType == typeof(bool))
return (bool)(prop.GetValue(obj) ?? false);
var field = t.GetField(memberName);
if (field != null && field.FieldType == typeof(bool))
return (bool)(field.GetValue(obj) ?? false);
}
catch
{
// ignore
}
return false;
}
public async Task<List<SetAdminResult>> BatchSetAdminsAsync(int accountId, long channelId, List<AdminRequest> requests)
{
var results = new List<SetAdminResult>();
@@ -544,13 +544,8 @@
if (admin == null)
return (null, "所选执行账号不是该频道管理员");
if (!admin.IsCreator)
{
if (!admin.CanPromoteMembers)
return (null, "所选执行账号缺少“添加管理员”权限");
if (!admin.CanInviteUsers)
return (null, "所选执行账号缺少“邀请用户”权限(添加新管理员可能需要先邀请进频道)");
}
if (!admin.IsCreator && !admin.CanPromoteMembers)
return (null, "所选执行账号缺少“添加管理员”权限");
return (selected.Id, null);
}
@@ -561,14 +556,14 @@
foreach (var a in admins)
{
if (!a.IsCreator && (!a.CanPromoteMembers || !a.CanInviteUsers))
if (!a.IsCreator && !a.CanPromoteMembers)
continue;
if (accountsByUserId.TryGetValue(a.UserId, out var acc))
return (acc.Id, null);
}
return (null, "无可用执行账号(需要该频道管理员且拥有“邀请用户”+“添加管理员”权限,并且在系统中存在)");
return (null, "无可用执行账号(需要该频道管理员且拥有“添加管理员”权限,并且在系统中存在)");
}
private void CancelOrClose()