From 30ce5f12cd4af76e1272ceb3aa6302a87e51e957 Mon Sep 17 00:00:00 2001 From: meoacgx Date: Fri, 2 Jan 2026 13:50:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(bot):=20=E4=BF=AE=E5=A4=8D=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=90=8C=E6=AD=A5=E6=97=A0=E6=B3=95=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E6=96=B0=E9=A2=91=E9=81=93=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BotUpdateHub: 添加 QuickCollectMyChatMemberAsync 方法 - 在 BotPoller 恢复时也收集 my_chat_member updates - 解决 BotAutoSyncEnabled=false 时手动同步失效的问题 同时优化 .dockerignore: - 排除 artifacts 目录 (7.2GB) - 排除模块源码和文档目录 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .dockerignore | 27 ++++++- .../Services/Telegram/BotUpdateHub.cs | 71 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 41723e7..ec6e49a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,15 +1,40 @@ +# Git & IDE .git .serena -.tmp +.vs +.vscode +.idea + +# Build outputs **/bin **/obj +artifacts + +# Docker runtime data docker-data + +# Temporary & logs +.tmp logs sessions todolist + +# Database & cache *.db *.log *.user *.suo *.cache +# Documentation & screenshots (not needed for build) +docs +screenshot +README.md + +# Module source (development only) +模块源码 + +# Misc +*.md +!Directory.Build.props + diff --git a/src/TelegramPanel.Core/Services/Telegram/BotUpdateHub.cs b/src/TelegramPanel.Core/Services/Telegram/BotUpdateHub.cs index 0f463fe..f3566f4 100644 --- a/src/TelegramPanel.Core/Services/Telegram/BotUpdateHub.cs +++ b/src/TelegramPanel.Core/Services/Telegram/BotUpdateHub.cs @@ -189,6 +189,11 @@ public sealed class BotUpdateHub : IAsyncDisposable if (lastUpdateId.HasValue) { nextOffset = lastUpdateId.Value + 1; + + // 恢复时:从上次的 offset 开始收集 my_chat_member(用于发现新加入的频道)。 + // 这些 updates 稍后会被主循环重新拉取(幂等),但手动同步时可以立即使用缓存。 + pendingMyChatMember = await QuickCollectMyChatMemberAsync( + token, nextOffset, botApi, logger, cancellationToken); } else { @@ -201,6 +206,72 @@ public sealed class BotUpdateHub : IAsyncDisposable return new BotPoller(botId, token, nextOffset, pendingMyChatMember, scopeFactory, botApi, logger); } + /// + /// 快速收集从指定 offset 开始的 my_chat_member updates(不推进主循环 offset)。 + /// 用于 Panel 恢复/手动同步时发现新加入的频道。 + /// + private static async Task?> QuickCollectMyChatMemberAsync( + string token, + long startOffset, + TelegramBotApiClient botApi, + ILogger logger, + CancellationToken cancellationToken) + { + var pending = new List(); + long offset = startOffset; + + // 最多尝试 20 次(2000 条),避免无限循环 + for (var i = 0; i < 20; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var updates = await botApi.CallAsync(token, "getUpdates", new Dictionary + { + ["offset"] = offset.ToString(), + ["timeout"] = "0", // 快速返回,不长轮询 + ["limit"] = "100", + ["allowed_updates"] = AllowedMyChatMemberOnlyJson + }, cancellationToken); + + if (updates.ValueKind != JsonValueKind.Array) + break; + + long? maxUpdateId = null; + foreach (var u in updates.EnumerateArray()) + { + if (!TryGetUpdateId(u, out var id)) + continue; + + maxUpdateId = maxUpdateId.HasValue ? Math.Max(maxUpdateId.Value, id) : id; + + if (u.ValueKind == JsonValueKind.Object && u.TryGetProperty("my_chat_member", out _)) + { + pending.Add(u.Clone()); + if (pending.Count > PendingMyChatMemberMax) + pending.RemoveAt(0); + } + } + + if (!maxUpdateId.HasValue) + break; + + offset = maxUpdateId.Value + 1; + } + catch (Exception ex) + { + logger.LogWarning(ex, "QuickCollectMyChatMember failed at offset {Offset}", offset); + break; + } + } + + if (pending.Count > 0) + logger.LogInformation("QuickCollectMyChatMember collected {Count} my_chat_member updates", pending.Count); + + return pending.Count > 0 ? pending : null; + } + public BotUpdateSubscription Subscribe() { var id = Guid.NewGuid();