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
+}