From da596a2692261cca185874630aab49c0785f7e45 Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Mon, 8 Jun 2026 01:27:08 +0300 Subject: [PATCH] feat: Add PlayerReputation handling and WebSocket handshake for 2018 client --- DorkNet.Models/Auth/Login2018.cs | 24 +++++++++++++++++++ DorkNet.Server/Compat2018/Player2018Mapper.cs | 1 + .../Notification/V2/NotificationController.cs | 11 +++++++++ 3 files changed, 36 insertions(+) diff --git a/DorkNet.Models/Auth/Login2018.cs b/DorkNet.Models/Auth/Login2018.cs index 231e9ab..62ccbd6 100644 --- a/DorkNet.Models/Auth/Login2018.cs +++ b/DorkNet.Models/Auth/Login2018.cs @@ -102,6 +102,30 @@ public class Player2018 [JsonPropertyName("AvoidJuniors")] public bool AvoidJuniors { get; set; } + + // REQUIRED to be non-null: the post-login analytics Identify dereferences + // localPlayer.PlayerReputation. (RecNet.cs ~NECCOFKHCAA) — a null + // here NREs the boot coroutine and the client hangs on splash_screen. + [JsonPropertyName("PlayerReputation")] + public PlayerReputation2018 PlayerReputation { get; set; } = new(); +} + +/// +/// 2018 PlayerReputation (RecNet.HOLNFPNKNGB.Deserialize) — all int. Keys exact +/// incl. the "Noteriety" misspelling. Must be present (non-null) on the player. +/// +public class PlayerReputation2018 +{ + [JsonPropertyName("Noteriety")] public int Noteriety { get; set; } + [JsonPropertyName("CheerGeneral")] public int CheerGeneral { get; set; } + [JsonPropertyName("CheerHelpful")] public int CheerHelpful { get; set; } + [JsonPropertyName("CheerGreatHost")] public int CheerGreatHost { get; set; } + [JsonPropertyName("CheerSportsman")] public int CheerSportsman { get; set; } + [JsonPropertyName("CheerCreative")] public int CheerCreative { get; set; } + [JsonPropertyName("CheerCredit")] public int CheerCredit { get; set; } + [JsonPropertyName("SubscriberCount")] public int SubscriberCount { get; set; } + [JsonPropertyName("SubscribedCount")] public int SubscribedCount { get; set; } + [JsonPropertyName("SelectedCheer")] public int SelectedCheer { get; set; } } /// diff --git a/DorkNet.Server/Compat2018/Player2018Mapper.cs b/DorkNet.Server/Compat2018/Player2018Mapper.cs index 1669ca1..8bae642 100644 --- a/DorkNet.Server/Compat2018/Player2018Mapper.cs +++ b/DorkNet.Server/Compat2018/Player2018Mapper.cs @@ -26,5 +26,6 @@ public static class Player2018Mapper ProfileImageName = p.ProfileImageName ?? string.Empty, JuniorProfile = p.IsJunior, HasBirthday = p.Birthday.HasValue, + PlayerReputation = new PlayerReputation2018 { Noteriety = p.Reputation }, }; } diff --git a/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs b/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs index 5cf23cb..aebe36f 100644 --- a/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs +++ b/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs @@ -37,6 +37,17 @@ public class NotificationController( using var ws = await HttpContext.WebSockets.AcceptWebSocketAsync(); log.LogInformation("[notify] 2018 client opened notification WebSocket"); + + // HANDSHAKE: on connect the 2018 client enters PerformingHandshake and + // blocks ("Connecting to RecNet") until the SERVER sends a handshake + // message — any valid JSON object marks the channel Connected + // (RecNet.cs:49616-49620, JFBBMEIDBNA just needs dict != null). Without + // it both sides wait for each other ~35s, then the client aborts the + // channel and shows "Failed to connect to RecNet". Send {} immediately. + var handshake = System.Text.Encoding.UTF8.GetBytes("{}"); + await ws.SendAsync(handshake, WebSocketMessageType.Text, endOfMessage: true, + HttpContext.RequestAborted); + var buf = new byte[4096]; try {