diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 00000000..9e9bc802 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,44 @@ +name: Upstream Sync + +permissions: + contents: write + +on: + schedule: + - cron: "0 0 * * *" # 每天 UTC 时间 0点 (北京时间早上8点) 自动执行 + workflow_dispatch: + +jobs: + sync_with_upstream: + name: Sync with Upstream + runs-on: ubuntu-latest + if: ${{ github.event.repository.fork }} # 仅在当前仓库是 Fork 的仓库时运行 + + steps: + - name: Checkout target branch + uses: actions/checkout@v3 + + - name: Sync Upstream + uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 + with: + + # 1. 设置上游仓库地址 (格式:用户名/仓库名) + upstream_sync_repo: LagrangeDev/LagrangeV2 + + # 2. 设置上游仓库的分支 (通常是 main 或 master) + upstream_sync_branch: main + + # 3. 设置您当前仓库需要更新的分支 + target_sync_branch: main + + # --- 配置结束 --- + + target_repo_token: ${{ secrets.GITHUB_TOKEN }} + test_mode: false + + - name: Check for Failure + if: failure() + run: | + echo "[Error] 由于存在冲突,自动同步失败。" + echo "请手动解决冲突:git pull upstream main 并在本地合并后推送。" + exit 1 diff --git a/Lagrange.Core/Common/Entity/BotGroup.cs b/Lagrange.Core/Common/Entity/BotGroup.cs index 20ad0c3d..4af526b8 100644 --- a/Lagrange.Core/Common/Entity/BotGroup.cs +++ b/Lagrange.Core/Common/Entity/BotGroup.cs @@ -8,7 +8,8 @@ public class BotGroup( long createTime, string? description, string? question, - string? announcement) : BotContact + string? announcement, + uint lastestSeq = 0) : BotContact { public long GroupUin { get; } = groupUin; @@ -25,6 +26,11 @@ public class BotGroup( public string? Question { get; } = question; public string? Announcement { get; } = announcement; + + /// + /// The latest message sequence number for this group. + /// + public uint LastestSeq { get; } = lastestSeq; public override long Uin => GroupUin; diff --git a/Lagrange.Core/Internal/Services/System/FetchGroupsService.cs b/Lagrange.Core/Internal/Services/System/FetchGroupsService.cs index 8ff696f5..36a35c50 100644 --- a/Lagrange.Core/Internal/Services/System/FetchGroupsService.cs +++ b/Lagrange.Core/Internal/Services/System/FetchGroupsService.cs @@ -38,7 +38,8 @@ private protected override Task ProcessResponse(FetchGroup raw.Info.CreatedTime, raw.Info.Description, raw.Info.Question, - raw.Info.Announcement + raw.Info.Announcement, + raw.CustomInfo?.LastestSeq ?? 0 ))])); } } \ No newline at end of file diff --git a/Lagrange.Core/Message/Entities/MultiMsgEntity.cs b/Lagrange.Core/Message/Entities/MultiMsgEntity.cs index 44be011c..d7aca564 100644 --- a/Lagrange.Core/Message/Entities/MultiMsgEntity.cs +++ b/Lagrange.Core/Message/Entities/MultiMsgEntity.cs @@ -26,9 +26,34 @@ public async Task Preprocess(BotContext context, BotMessage message) { if (string.IsNullOrEmpty(ResId)) { + // Recursively preprocess internal messages + Console.WriteLine($"[MultiMsgEntity] Preprocessing {Messages.Count} messages for forward chain..."); + + foreach (var innerMsg in Messages) + { + foreach (var entity in innerMsg.Entities) + { + try + { + await entity.Preprocess(context, message); + + if (entity is ImageEntity img) + { + if (img.MsgInfo != null) + Console.WriteLine($"[MultiMsgEntity] Image uploaded successfully. Size: {img.ImageSize}"); + else + Console.WriteLine("[MultiMsgEntity] WARNING: Image MsgInfo is NULL after preprocess!"); + } + } + catch (Exception ex) + { + Console.WriteLine($"[MultiMsgEntity] Error preprocessing entity: {ex.Message}"); + } + } + } + var result = await context.EventContext.SendEvent(new LongMsgSendEventReq(message.Receiver, Messages)); ResId = result.ResId; - } } diff --git a/Lagrange.Milky/Api/Handler/Message/GetHistoryMessagesHandler.cs b/Lagrange.Milky/Api/Handler/Message/GetHistoryMessagesHandler.cs index 5d64ced2..39eedc21 100644 --- a/Lagrange.Milky/Api/Handler/Message/GetHistoryMessagesHandler.cs +++ b/Lagrange.Milky/Api/Handler/Message/GetHistoryMessagesHandler.cs @@ -16,11 +16,39 @@ public class GetHistoryMessagesHandler(BotContext bot, EntityConvert convert) : public async Task HandleAsync(GetHistoryMessagesParameter parameter, CancellationToken token) { int start; - if (parameter.StartMessageSeq.HasValue) start = (int)(parameter.StartMessageSeq.Value - parameter.Limit); - // TODO: No start sequence - else throw new NotImplementedException(); + int end; - int end = start + parameter.Limit; + if (parameter.StartMessageSeq.HasValue) + { + start = (int)(parameter.StartMessageSeq.Value - parameter.Limit); + end = (int)parameter.StartMessageSeq.Value; + } + else + { + // No start sequence provided, try to get the latest sequence + switch (parameter.MessageScene) + { + case "group": + var groups = await _bot.FetchGroups(); + var group = groups.FirstOrDefault(g => g.GroupUin == parameter.PeerId) + ?? throw new ApiException(-1, $"Group {parameter.PeerId} not found"); + + if (group.LastestSeq == 0) + throw new ApiException(-1, $"Failed to get latest sequence for group {parameter.PeerId}"); + + end = (int)group.LastestSeq; + start = end - parameter.Limit; + break; + + case "friend": + throw new ApiException(-1, "Getting latest messages for friends without start_message_seq is not supported"); + + default: + throw new NotSupportedException($"Message scene '{parameter.MessageScene}' is not supported"); + } + } + + if (start < 0) start = 0; var messages = parameter.MessageScene switch { diff --git a/Lagrange.Proto.Test/CrashTest.cs b/Lagrange.Proto.Test/CrashTest.cs new file mode 100644 index 00000000..6b4c37f8 --- /dev/null +++ b/Lagrange.Proto.Test/CrashTest.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using Lagrange.Proto.Utility; + +namespace Lagrange.Proto.Test +{ + [TestFixture] + public class CrashTest + { + [Test] + public void TestNegativeInt32VarIntLength() + { + // This test is used to reproduce the IndexOutOfRangeException Bug + int value = -1; + + int length = ProtoHelper.GetVarIntLength(value); + + // Verify: For a 32-bit all-ones number (0xFFFF), VarInt encoding should be 5 bytes + Assert.That(length, Is.EqualTo(5)); + } + + [Test] + public void TestNegativeInt64VarIntLength() + { + long value = -1; + + // For 64-bit numbers with all 1s, VarInt encoding should be 10 bytes + int length = ProtoHelper.GetVarIntLength(value); + + Assert.That(length, Is.EqualTo(10)); + } + + [Test] + public void TestOtherNegativeValues() + { + // Test other negative values to ensure stability + int val1 = -100; + int len1 = ProtoHelper.GetVarIntLength(val1); + Assert.That(len1, Is.GreaterThan(0)); + + long val2 = long.MinValue; // 0x8000000000000000 + int len2 = ProtoHelper.GetVarIntLength(val2); + Assert.That(len2, Is.EqualTo(10)); + } + } +} \ No newline at end of file diff --git a/Lagrange.Proto/Primitives/ProtoWriter.cs b/Lagrange.Proto/Primitives/ProtoWriter.cs index 55b5da29..a59946b2 100644 --- a/Lagrange.Proto/Primitives/ProtoWriter.cs +++ b/Lagrange.Proto/Primitives/ProtoWriter.cs @@ -96,7 +96,8 @@ public void EncodeVarInt(T value) where T : unmanaged, INumber { if (_memory.Length - BytesPending >= 10) { - if (value < T.CreateTruncating(0x80)) + // For-1 (int), converting to ulong is 0xFF... FF, much greater than 0x80, will not mistakenly enter this branch. + if (ulong.CreateTruncating(value) < 0x80) { Unsafe.Add(ref MemoryMarshal.GetReference(_memory.Span), BytesPending++) = byte.CreateTruncating(value); return; diff --git a/Lagrange.Proto/Utility/ProtoHelper.cs b/Lagrange.Proto/Utility/ProtoHelper.cs index 01517c05..399db314 100644 --- a/Lagrange.Proto/Utility/ProtoHelper.cs +++ b/Lagrange.Proto/Utility/ProtoHelper.cs @@ -36,12 +36,12 @@ public static unsafe int GetVarIntLength(T value) where T : unmanaged, INumbe if (sizeof(T) <= 4) { - int leadingZeros = BitOperations.LeadingZeroCount(uint.CreateSaturating(value)); + int leadingZeros = BitOperations.LeadingZeroCount(uint.CreateTruncating(value)); return VarIntLengths32[leadingZeros]; } else { - int leadingZeros = BitOperations.LeadingZeroCount(ulong.CreateSaturating(value)); + int leadingZeros = BitOperations.LeadingZeroCount(ulong.CreateTruncating(value)); return VarIntLengths64[leadingZeros]; } } @@ -98,4 +98,4 @@ public static int CountBytes(ReadOnlySpan str) { return GetVarIntLength(str.Length) + str.Length; } -} \ No newline at end of file +}