feat: 落地 V1 全部源码 — WebSocket 通知 + 应用内历史查看
17 个源文件 + .gitignore + 默认 config.json 模板,严格按方案文档第 6 节实现。
用户核心场景:
- Windows 接收 Gotify 通知(WS /stream → AppNotificationBuilder)
- 应用内查看历史消息(打开 MainWindow 触发 GetRecentMessagesAsync,实时通过 MessageReceived 事件追加)
- 列表条目双击打开 extras.client::notification.click.url
关键变更点:
- GotifyMessage.Extras 类型由 Dictionary<string,object> 改为 Dictionary<string,JsonElement>,避免 System.Text.Json 反序列化时丢失嵌套结构
- 新增 HistoryConfig.ViewerLimit(默认 50,UI 硬上限 200)
- GotifyApiService.GetRecentMessagesAsync:since=0 拉最近 N 条,OrderByDescending(Id) 保证顺序
- GotifyWebSocketService 新增 MessageReceived 事件,MainWindow 用 Dispatcher.Invoke 切回 UI 线程追加
- NotificationService.AddArgument('url=...') 透传 click URL 给 OnNotificationInvoked
- MainWindow.xaml 增加 ListView + 刷新按钮,使用 ObservableCollection<GotifyMessage>
- 默认 config.json 用 https://gotify.example.com 占位 URL,ClientToken 留空,首启动自动写入 %LOCALAPPDATA%
依赖:Microsoft.WindowsAppSDK 2.4.0(方案文档固定版本)。
构建:本机 .NET 8 SDK 未安装,build 待用户在装好 SDK 后执行: dotnet restore && dotnet build -c Release
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
namespace WindowsGotify.Models;
|
||||
|
||||
public sealed class AppConfig
|
||||
{
|
||||
public GotifyConfig Gotify { get; set; } = new();
|
||||
public NotificationConfig Notification { get; set; } = new();
|
||||
public ReconnectConfig Reconnect { get; set; } = new();
|
||||
public HistoryConfig History { get; set; } = new();
|
||||
public StartupConfig Startup { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class GotifyConfig
|
||||
{
|
||||
public string Url { get; set; } = "http://127.0.0.1:8080";
|
||||
public string ClientToken { get; set; } = "";
|
||||
}
|
||||
|
||||
public sealed class NotificationConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public bool Sound { get; set; } = true;
|
||||
public int MaxMessageLength { get; set; } = 2000;
|
||||
}
|
||||
|
||||
public sealed class ReconnectConfig
|
||||
{
|
||||
public int InitialDelaySeconds { get; set; } = 2;
|
||||
public int MaxDelaySeconds { get; set; } = 60;
|
||||
}
|
||||
|
||||
public sealed class HistoryConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int StartupSyncCount { get; set; } = 20;
|
||||
public int ViewerLimit { get; set; } = 50;
|
||||
}
|
||||
|
||||
public sealed class StartupConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WindowsGotify.Models;
|
||||
|
||||
public sealed class GotifyMessage
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonPropertyName("appid")]
|
||||
public long AppId { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public int Priority { get; set; }
|
||||
|
||||
[JsonPropertyName("date")]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[JsonPropertyName("extras")]
|
||||
public Dictionary<string, JsonElement>? Extras { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string TitleDisplay =>
|
||||
string.IsNullOrWhiteSpace(Title) ? $"(App {AppId})" : Title!;
|
||||
|
||||
[JsonIgnore]
|
||||
public string DateDisplay =>
|
||||
Date.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
[JsonIgnore]
|
||||
public string PriorityDisplay => $"P{Priority}";
|
||||
|
||||
/// <summary>
|
||||
/// 提取 Gotify extras 中的 client::notification.click.url(用于点击跳转)。
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ClickUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Extras is null) return null;
|
||||
try
|
||||
{
|
||||
if (!Extras.TryGetValue("client::notification", out var notif)) return null;
|
||||
if (notif.ValueKind != JsonValueKind.Object) return null;
|
||||
if (!notif.TryGetProperty("click", out var click)) return null;
|
||||
if (click.ValueKind != JsonValueKind.Object) return null;
|
||||
if (!click.TryGetProperty("url", out var url)) return null;
|
||||
if (url.ValueKind != JsonValueKind.String) return null;
|
||||
return url.GetString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GotifyMessagePage
|
||||
{
|
||||
[JsonPropertyName("messages")]
|
||||
public List<GotifyMessage> Messages { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("paging")]
|
||||
public PagingInfo? Paging { get; set; }
|
||||
}
|
||||
|
||||
public sealed class PagingInfo
|
||||
{
|
||||
[JsonPropertyName("size")]
|
||||
public int Size { get; set; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; set; }
|
||||
|
||||
[JsonPropertyName("next")]
|
||||
public string? Next { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user