Mud.Feishu.Abstractions
1.2.1
See the version list below for details.
dotnet add package Mud.Feishu.Abstractions --version 1.2.1
NuGet\Install-Package Mud.Feishu.Abstractions -Version 1.2.1
<PackageReference Include="Mud.Feishu.Abstractions" Version="1.2.1" />
<PackageVersion Include="Mud.Feishu.Abstractions" Version="1.2.1" />
<PackageReference Include="Mud.Feishu.Abstractions" />
paket add Mud.Feishu.Abstractions --version 1.2.1
#r "nuget: Mud.Feishu.Abstractions, 1.2.1"
#:package Mud.Feishu.Abstractions@1.2.1
#addin nuget:?package=Mud.Feishu.Abstractions&version=1.2.1
#tool nuget:?package=Mud.Feishu.Abstractions&version=1.2.1
Mud.Feishu.Abstractions
Mud.Feishu.Abstractions 是 MudFeishu 库的 WebSocket 事件订阅组件和 HTTP 事件订阅组件抽象层,专用于处理飞书事件订阅。它提供了完整的事件订阅策略模式的事件处理机制,使开发人员能够轻松地在 .NET 应用程序中接收和处理飞书实时事件。
🚀 特性
- 📡 事件订阅抽象 - 提供完整的事件订阅和处理抽象层
- 🔧 策略模式 - 基于策略模式的事件处理器,支持多种事件类型
- 🏭 工厂模式 - 内置事件处理器工厂,支持动态注册和发现
- ⚡ 异步处理 - 完全异步的事件处理,支持并行处理
- 🎯 类型安全 - 强类型事件数据模型,避免运行时错误
- 📋 丰富事件类型 - 支持飞书所有主要事件类型
- 🔄 可扩展 - 易于扩展新的事件类型和处理器
- 🛡️ 内置基类 - 提供默认事件处理器基类,简化开发
- 📦 多框架支持 - 支持.NET4.6+、 .NET 6.0 - .NET 10.0
📦 安装
dotnet add package Mud.Feishu.Abstractions
🏛️ 核心架构
事件处理流程
飞书事件 → EventData → EventHandlerFactory → IFeishuEventHandler → 业务逻辑
核心组件
EventData- 事件数据模型,包含飞书事件的所有基本信息IFeishuEventHandler- 事件处理器接口,定义事件处理契约DefaultFeishuEventHandler<T>- 抽象事件处理器基类,提供默认的反序列化和错误处理DefaultFeishuObjectEventHandler<T>- 对象事件处理器基类,继承自DefaultFeishuEventHandlerIFeishuEventHandlerFactory- 事件处理器工厂,负责处理器的注册、发现和调用IEventResult- 事件结果接口,用于标识不同类型事件的结果ObjectEventResult<T>- 对象事件结果类,包装事件处理后返回的对象FeishuEventTypes- 事件类型常量,定义所有支持的飞书事件类型
🎯 支持的事件类型
组织管理事件
contact.user.created_v3- 员工入职事件contact.user.updated_v3- 用户更新事件contact.user.deleted_v3- 用户删除事件contact.custom_attr_event.updated_v3- 成员字段变更事件contact.department.created_v3- 部门创建事件contact.department.updated_v3- 部门更新事件contact.department.deleted_v3- 部门删除事件contact.employee_type_enum.created_v3- 人员类型创建事件contact.employee_type_enum.updated_v3- 人员类型更新事件contact.employee_type_enum.deleted_v3- 人员类型删除事件contact.employee_type_enum.actived_v3- 人员类型启用事件contact.employee_type_enum.deactivated_v3- 人员类型禁用事件
消息事件
im.message.receive_v1- 接收消息事件im.message.recalled_v1- 消息撤回事件im.message.message_read_v1- 消息已读事件im.message.reaction.created_v1- 新增消息表情回复事件im.message.reaction.deleted_v1- 删除消息表情回复事件
群聊事件
im.chat.disbanded_v1- 群解散事件im.chat.updated_v1- 群配置修改事件im.chat.member.user.added_v1- 用户进群事件im.chat.member.user.deleted_v1- 用户出群事件im.chat.member.user.withdrawn_v1- 撤销拉用户进群事件im.chat.member.bot.added_v1- 机器人进群事件im.chat.member.bot.deleted_v1- 机器人被移出群事件
审批事件
approval.approval.approved_v1- 审批通过事件approval.approval.rejected_v1- 审批拒绝事件
日程和会议事件
calendar.event.updated_v4- 日程事件meeting.meeting.started_v1- 会议开始事件meeting.meeting.ended_v1- 会议结束事件
📖 使用示例
1. 创建基础事件处理器(实现 IFeishuEventHandler 接口)
using Mud.Feishu.Abstractions;
using System.Text.Json;
namespace YourProject.Handlers;
/// <summary>
/// 演示用户事件处理器
/// </summary>
public class DemoUserEventHandler : IFeishuEventHandler
{
private readonly ILogger<DemoUserEventHandler> _logger;
private readonly YourEventService _eventService;
public DemoUserEventHandler(ILogger<DemoUserEventHandler> logger, YourEventService eventService)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
}
public string SupportedEventType => FeishuEventTypes.UserCreated;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
if (eventData == null)
throw new ArgumentNullException(nameof(eventData));
_logger.LogInformation("👤 [用户事件] 开始处理用户创建事件: {EventId}", eventData.EventId);
try
{
// 解析用户数据
var userData = ParseUserData(eventData);
// 记录事件到服务
await _eventService.RecordUserEventAsync(userData, cancellationToken);
// 模拟业务处理
await ProcessUserEventAsync(userData, cancellationToken);
_logger.LogInformation("✅ [用户事件] 用户创建事件处理完成: 用户ID {UserId}, 用户名 {UserName}",
userData.UserId, userData.UserName);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ [用户事件] 处理用户创建事件失败: {EventId}", eventData.EventId);
throw;
}
}
private UserData ParseUserData(EventData eventData)
{
try
{
var jsonElement = JsonSerializer.Deserialize<JsonElement>(eventData.Event?.ToString() ?? "{}");
var userElement = jsonElement.GetProperty("user");
return new UserData
{
UserId = userElement.GetProperty("user_id").GetString() ?? "",
UserName = userElement.GetProperty("name").GetString() ?? "",
Email = TryGetProperty(userElement, "email") ?? "",
Department = TryGetProperty(userElement, "department") ?? "",
CreatedAt = DateTime.UtcNow
};
}
catch (Exception ex)
{
_logger.LogError(ex, "解析用户数据失败");
throw new InvalidOperationException("无法解析用户数据", ex);
}
}
private async Task ProcessUserEventAsync(UserData userData, CancellationToken cancellationToken)
{
// 模拟异步业务操作
await Task.Delay(100, cancellationToken);
// 验证必要字段
if (string.IsNullOrWhiteSpace(userData.UserId))
{
throw new ArgumentException("用户ID不能为空");
}
// 模拟发送欢迎通知
_logger.LogInformation("📧 [用户事件] 发送欢迎通知给用户: {UserName} ({Email})",
userData.UserName, userData.Email);
await Task.CompletedTask;
}
private static string? TryGetProperty(JsonElement element, string propertyName)
{
return element.TryGetProperty(propertyName, out var value) ? value.GetString() : null;
}
}
2. 继承预定义事件处理器(推荐方式)
using Mud.Feishu.Abstractions;
using Mud.Feishu.Abstractions.DataModels.Organization;
using Mud.Feishu.Abstractions.EventHandlers;
namespace YourProject.Handlers;
/// <summary>
/// 演示部门事件处理器 - 继承预定义的部门创建事件处理器
/// </summary>
public class DemoDepartmentEventHandler : DepartmentCreatedEventHandler
{
private readonly YourEventService _eventService;
public DemoDepartmentEventHandler(ILogger<DemoDepartmentEventHandler> logger, YourEventService eventService) : base(logger)
{
_eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
}
protected override async Task ProcessBusinessLogicAsync(
EventData eventData,
ObjectEventResult<DepartmentCreatedResult>? departmentData,
CancellationToken cancellationToken = default)
{
if (eventData == null)
throw new ArgumentNullException(nameof(eventData));
_logger.LogInformation("[部门事件] 开始处理部门创建事件: {EventId}", eventData.EventId);
try
{
// 记录事件到服务
await _eventService.RecordDepartmentEventAsync(departmentData.Object, cancellationToken);
// 模拟业务处理
await ProcessDepartmentEventAsync(departmentData.Object, cancellationToken);
_logger.LogInformation("[部门事件] 部门创建事件处理完成: 部门ID {DepartmentId}, 部门名 {DepartmentName}",
departmentData.Object.DepartmentId, departmentData.Object.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "[部门事件] 处理部门创建事件失败: {EventId}", eventData.EventId);
throw;
}
}
private async Task ProcessDepartmentEventAsync(DepartmentCreatedResult departmentData, CancellationToken cancellationToken)
{
// 模拟异步业务操作
await Task.Delay(100, cancellationToken);
// 验证逻辑
if (string.IsNullOrWhiteSpace(departmentData.DepartmentId))
{
throw new ArgumentException("部门ID不能为空");
}
// 模拟权限初始化
_logger.LogInformation("[部门事件] 初始化部门权限: {DepartmentName}", departmentData.Name);
// 通知部门主管
if (!string.IsNullOrWhiteSpace(departmentData.LeaderUserId))
{
_logger.LogInformation("[部门事件] 通知部门主管: {LeaderUserId}", departmentData.LeaderUserId);
}
// 处理层级关系
if (!string.IsNullOrWhiteSpace(departmentData.ParentDepartmentId))
{
_logger.LogInformation("[部门事件] 建立层级关系: {DepartmentId} -> {ParentDepartmentId}",
departmentData.DepartmentId, departmentData.ParentDepartmentId);
}
await Task.CompletedTask;
}
}
3. 在 Program.cs 中配置服务和事件处理器
using Mud.Feishu.WebSocket;
using Mud.Feishu.WebSocket.Demo.Handlers;
using Mud.Feishu.WebSocket.Demo.Services;
using Mud.Feishu.WebSocket.Services;
var builder = WebApplication.CreateBuilder(args);
// 配置基础服务
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Microsoft.OpenApi.OpenApiInfo
{
Title = "飞书WebSocket测试API",
Version = "v1",
Description = "用于测试飞书WebSocket长连接功能的演示API"
});
});
// 配置飞书服务
builder.Services.CreateFeishuServicesBuilder(builder.Configuration)
.AddAuthenticationApi()
.AddTokenManagers()
.Build();
// 配置飞书WebSocket服务(推荐方式)
builder.Services.AddFeishuWebSocketBuilder()
.ConfigureFrom(builder.Configuration)
.UseMultiHandler() // 使用多处理器模式
.AddHandler<DemoDepartmentEventHandler>() // 添加部门创建事件处理器
.AddHandler<DemoDepartmentDeleteEventHandler>() // 添加部门删除事件处理器
.Build();
// 配置自定义服务
builder.Services.AddSingleton<DemoEventService>();
builder.Services.AddHostedService<DemoEventBackgroundService>();
// 配置CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
// 配置中间件
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.Run();
4. 创建自定义事件服务来处理事件数据
namespace YourProject.Services;
/// <summary>
/// 演示事件服务 - 用于记录和管理事件处理结果
/// </summary>
public class DemoEventService
{
private readonly ILogger<DemoEventService> _logger;
private readonly ConcurrentBag<UserData> _userEvents = new();
private readonly ConcurrentBag<DepartmentData> _departmentEvents = new();
private int _userCount = 0;
private int _departmentCount = 0;
public DemoEventService(ILogger<DemoEventService> logger)
{
_logger = logger;
}
public async Task RecordUserEventAsync(UserData userData, CancellationToken cancellationToken = default)
{
_logger.LogDebug("记录用户事件: {UserId}", userData.UserId);
_userEvents.Add(userData);
await Task.CompletedTask;
}
public async Task RecordDepartmentEventAsync(DepartmentData departmentData, CancellationToken cancellationToken = default)
{
_logger.LogDebug("记录部门事件: {DepartmentId}", departmentData.DepartmentId);
_departmentEvents.Add(departmentData);
await Task.CompletedTask;
}
public void IncrementUserCount()
{
Interlocked.Increment(ref _userCount);
_logger.LogInformation("用户计数更新: {Count}", _userCount);
}
public void IncrementDepartmentCount()
{
Interlocked.Increment(ref _departmentCount);
_logger.LogInformation("部门计数更新: {Count}", _departmentCount);
}
public IEnumerable<UserData> GetUserEvents() => _userEvents.ToList();
public IEnumerable<DepartmentData> GetDepartmentEvents() => _departmentEvents.ToList();
public int GetUserCount() => _userCount;
public int GetDepartmentCount() => _departmentCount;
}
🏗️ 高级用法
多处理器策略
public class MultiHandlerService
{
private readonly IFeishuEventHandlerFactory _factory;
public MultiHandlerService(IFeishuEventHandlerFactory factory)
{
_factory = factory;
}
public async Task HandleEventWithMultipleStrategies(EventData eventData)
{
// 获取所有匹配的处理器
var handlers = _factory.GetHandlers(eventData.EventType);
// 按优先级处理
foreach (var handler in handlers.OrderBy(h => h.GetType().Name))
{
try
{
await handler.HandleAsync(eventData);
}
catch (Exception ex)
{
// 记录错误但继续处理其他处理器
Console.WriteLine($"处理器 {handler.GetType().Name} 失败: {ex.Message}");
}
}
}
}
条件事件处理
public class ConditionalEventHandler : IFeishuEventHandler
{
public string SupportedEventType => FeishuEventTypes.ReceiveMessage;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
// 只处理特定类型的消息
if (eventData.Event is MessageReceiveEvent msgEvent)
{
if (msgEvent.Message.MessageType == "text")
{
await HandleTextMessage(msgEvent);
}
else if (msgEvent.Message.MessageType == "image")
{
await HandleImageMessage(msgEvent);
}
}
}
private async Task HandleTextMessage(MessageReceiveEvent msgEvent)
{
// 处理文本消息逻辑
}
private async Task HandleImageMessage(MessageReceiveEvent msgEvent)
{
// 处理图片消息逻辑
}
}
🔧 扩展新事件类型
1. 定义事件类型常量
public static class CustomEventTypes
{
public const string MyCustomEvent = "custom.my_event.v1";
}
2. 创建事件数据模型
public class MyCustomEvent : IEventResult
{
[JsonPropertyName("custom_data")]
public string CustomData { get; set; } = string.Empty;
}
3. 实现事件处理器
// 基础实现方式
public class MyCustomEventHandler : IFeishuEventHandler
{
public string SupportedEventType => CustomEventTypes.MyCustomEvent;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
if (eventData.Event is MyCustomEvent customEvent)
{
// 处理自定义事件
}
}
}
// 推荐使用基类
public class MyCustomEventHandler : DefaultFeishuEventHandler<MyCustomEvent>
{
public override string SupportedEventType => CustomEventTypes.MyCustomEvent;
public MyCustomEventHandler(ILogger<MyCustomEventHandler> logger) : base(logger)
{
}
protected override async Task ProcessBusinessLogicAsync(
EventData eventData,
MyCustomEvent? eventEntity,
CancellationToken cancellationToken = default)
{
if (eventEntity != null)
{
// 处理自定义事件,基类已自动反序列化
Console.WriteLine($"自定义数据: {eventEntity.CustomData}");
}
await Task.CompletedTask;
}
}
📊 选择对比
处理器选择策略
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
IEventHandler 直接实现 |
最大灵活性 | 需要手动反序列化 | 简单事件或特殊需求 |
DefaultFeishuEventHandler<T> |
自动反序列化、错误处理 | 继承层次增加 | 大多数标准事件 |
DefaultFeishuObjectEventHandler<T> |
专为对象结果优化 | 功能相对固定 | 返回对象的事件 |
性能建议
- ✅ 推荐 使用
DefaultFeishuEventHandler<T>基类 - ⚡ 优化 对高频事件使用
ValueTask - 🔄 并发 使用
HandleEventParallelAsync处理复杂事件 - 🛡️ 安全 基类内置了异常处理和日志记录
🛠️ 开发和构建
要求
- .NET 6.0 或更高版本
- Visual Studio 2022 或 Visual Studio Code
构建项目
# 克隆仓库
git clone https://gitee.com/mudtools/MudFeishu.git
cd MudFeishu/Mud.Feishu.Abstractions
# 还原依赖
dotnet restore
# 构建项目
dotnet build
# 运行测试
dotnet test
📚 相关项目
- Mud.Feishu - 主要的飞书SDK实现
- Mud.Feishu.WebSocket - WebSocket事件订阅实现
- Mud.Feishu.Test - 测试项目和使用示例
🤝 贡献
欢迎贡献!请查看 贡献指南 了解详情。
贡献流程
- Fork 项目
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request
Mud.Feishu.Abstractions - 让飞书事件处理变得简单而强大! 🚀
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Http.Polly (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- System.Text.Json (>= 10.0.2)
-
net10.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.2)
- Microsoft.Extensions.Http (>= 10.0.2)
- Microsoft.Extensions.Http.Polly (>= 10.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.2)
- Microsoft.Extensions.Options (>= 10.0.2)
-
net6.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Http.Polly (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.2)
- Microsoft.Extensions.Http (>= 10.0.2)
- Microsoft.Extensions.Http.Polly (>= 10.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.2)
- Microsoft.Extensions.Options (>= 10.0.2)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Mud.Feishu.Abstractions:
| Package | Downloads |
|---|---|
|
Mud.Feishu
飞书服务端 SDK 的 .NET 适配版,支持.NET4.6+、.NET 5 至 .NET 10 平台。提供类型安全的 API 封装,让开发者能够在 .NET 应用程序中便捷、高效地集成飞书服务端功能。 |
|
|
Mud.Feishu.WebSocket
适用于 .NET 全平台的飞书 WebSocket 长连接事件订阅组件,提供开箱即用的客户端实现,支持自动重连、心跳检测与事件分发,并基于策略模式构建可扩展的事件处理机制,帮助开发者在 .NET 应用中便捷、可靠地接收和处理飞书实时事件。 |
|
|
Mud.Feishu.Webhook
适用于 .NET 全平台的飞书 Webhook 事件订阅组件,提供开箱即用的客户端实现,基于策略模式构建可扩展的事件处理机制,帮助开发者在 .NET 应用中便捷、可靠地接收和处理飞书实时事件。 |
|
|
Mud.Feishu.Redis
飞书事件订阅组件 Redis 分布式去重扩展,提供基于 Redis 的事件去重、Nonce 去重和 SeqID 去重功能,适用于多实例分布式部署场景。 |
|
|
Mud.Feishu.Authentication
飞书用户认证中间件,提供用户上下文管理功能。基于 AsyncLocal 实现线程安全的用户信息存储,支持从 JWT Claims 中提取飞书用户信息。 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 3.0.0-preview3 | 96 | 5/22/2026 | |
| 3.0.0-preview2 | 354 | 5/11/2026 | |
| 3.0.0-preview1 | 391 | 5/1/2026 | |
| 2.1.3 | 141 | 5/22/2026 | |
| 2.1.2 | 354 | 5/12/2026 | |
| 2.1.1 | 357 | 5/11/2026 | |
| 2.1.0 | 371 | 5/1/2026 | |
| 2.0.9 | 414 | 4/24/2026 | |
| 2.0.8 | 382 | 4/12/2026 | |
| 2.0.7 | 354 | 4/7/2026 | |
| 2.0.6 | 311 | 4/5/2026 | |
| 2.0.5 | 401 | 3/28/2026 | |
| 2.0.4 | 256 | 3/19/2026 | |
| 2.0.3 | 282 | 2/26/2026 | |
| 2.0.2 | 296 | 1/30/2026 | |
| 2.0.1 | 317 | 1/27/2026 | |
| 1.2.2 | 294 | 1/19/2026 | |
| 1.2.1 | 303 | 1/16/2026 | |
| 1.2.0 | 293 | 1/14/2026 | |
| 1.1.2 | 274 | 1/11/2026 |