Skip to content
Open
44 changes: 44 additions & 0 deletions .github/workflows/sync-upstream.yml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
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
8 changes: 7 additions & 1 deletion Lagrange.Core/Common/Entity/BotGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -25,6 +26,11 @@ public class BotGroup(
public string? Question { get; } = question;

public string? Announcement { get; } = announcement;

/// <summary>
/// The latest message sequence number for this group.
/// </summary>
public uint LastestSeq { get; } = lastestSeq;

public override long Uin => GroupUin;

Expand Down
3 changes: 2 additions & 1 deletion Lagrange.Core/Internal/Services/System/FetchGroupsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ private protected override Task<FetchGroupsEventResp> ProcessResponse(FetchGroup
raw.Info.CreatedTime,
raw.Info.Description,
raw.Info.Question,
raw.Info.Announcement
raw.Info.Announcement,
raw.CustomInfo?.LastestSeq ?? 0
))]));
}
}
27 changes: 26 additions & 1 deletion Lagrange.Core/Message/Entities/MultiMsgEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<LongMsgSendEventResp>(new LongMsgSendEventReq(message.Receiver, Messages));
ResId = result.ResId;

}
}

Expand Down
36 changes: 32 additions & 4 deletions Lagrange.Milky/Api/Handler/Message/GetHistoryMessagesHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,39 @@ public class GetHistoryMessagesHandler(BotContext bot, EntityConvert convert) :
public async Task<GetHistoryMessagesResult> 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
{
Expand Down
45 changes: 45 additions & 0 deletions Lagrange.Proto.Test/CrashTest.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
3 changes: 2 additions & 1 deletion Lagrange.Proto/Primitives/ProtoWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public void EncodeVarInt<T>(T value) where T : unmanaged, INumber<T>
{
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;
Expand Down
6 changes: 3 additions & 3 deletions Lagrange.Proto/Utility/ProtoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public static unsafe int GetVarIntLength<T>(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];
}
}
Expand Down Expand Up @@ -98,4 +98,4 @@ public static int CountBytes(ReadOnlySpan<byte> str)
{
return GetVarIntLength(str.Length) + str.Length;
}
}
}