From 0a0e79180a520fc4e8460894ec5f271ae84d2846 Mon Sep 17 00:00:00 2001 From: olcxja Date: Sat, 25 Jul 2026 01:06:59 +0200 Subject: [PATCH] add message pinning --- LarpixServer/Account/Requests.cs | 12 +++ LarpixServer/Room/Requests.cs | 148 +++++++++++++++++++++++++++++++ LarpixServer/Utils/Jsons.cs | 1 + 3 files changed, 161 insertions(+) diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index 17e0327..e0a2c47 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -753,6 +753,12 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec await Room.Requests.DmMessageDelete(id, serializedBody.string2) , password)); break; + case "dm/message/pin/toggle": + await context.Response.WriteAsync( + Encryption.Encryption.EncryptString( + await Room.Requests.DmMessagePinToggle(id, serializedBody.string2) + , password)); + break; case "dm/message/react/add": await context.Response.WriteAsync( Encryption.Encryption.EncryptString( @@ -774,6 +780,12 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Requests.GetDmMessages(id, msgGet.string1, msgGet.string3, msgGet.string2), password)); break; } + case "dm/messages/pinned": + await context.Response.WriteAsync( + Encryption.Encryption.EncryptString( + await Room.Requests.DmMessageGetPinned(id, serializedBody.string2) + , password)); + break; case "dm/read": { Universal2String readGet = JsonSerializer.Deserialize( diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index d1af6ce..59a1a4e 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -107,6 +107,11 @@ public class Requests msgObj.reactions = ""; } + if (Fs.Exists($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/pinned/{i}")) + { + msgObj.pinned = "true"; + } + sb.Append(JsonSerializer.Serialize(msgObj, AppJsonSerializerContext.Default.Message)); fetched++; } @@ -650,4 +655,147 @@ public class Requests } return "success:message.reaction.removed"; } + + public static async Task DmMessagePinToggle(string id, string body) + { + Universal2String serializedBody = JsonSerializer.Deserialize( + body, + AppJsonSerializerContext.Default.Universal2String + ); + string dmId = serializedBody.string1; + string msgId = serializedBody.string2; + + if (!IsSafeMsgId(msgId)) return "error:invalid.msg.id"; + if (!await IsMemberOfDm(id, dmId)) return "error:forbidden"; + + string msgPath = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/{msgId}"; + if (!Fs.Exists(msgPath)) return "error:message.not.found"; + + string pinnedDir = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/pinned"; + if (!System.IO.Directory.Exists(pinnedDir)) + { + System.IO.Directory.CreateDirectory(pinnedDir); + } + + string pinnedFilePath = $"{pinnedDir}/{msgId}"; + bool isNowPinned = false; + + if (Fs.Exists(pinnedFilePath)) + { + Fs.DeleteFile(pinnedFilePath); + isNowPinned = false; + } + else + { + await Fs.WriteFile(pinnedFilePath, new byte[0]); + isNowPinned = true; + } + + // Send larp.info.pin message + long nextId = 0; + await createLock.WaitAsync(); + try + { + if (Fs.Exists($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/last")) + { + string lastStr = Encoding.UTF8.GetString(await Fs.ReadFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/last")); + if (long.TryParse(lastStr, out long last)) + { + nextId = last + 1; + } + } + + Message infoMessage = new(); + infoMessage.attachment = ""; + infoMessage.author = id; + infoMessage.timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); + infoMessage.type = "larp.info.pin"; + infoMessage.content = isNowPinned ? "{blah(message.pinned.by)}" : "{blah(message.unpinned.by)}"; + infoMessage.key = ""; + infoMessage.pervious = msgId; + + string mPath = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/{nextId}"; + await Fs.WriteFile(mPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(infoMessage, AppJsonSerializerContext.Default.Message))); + await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/last", Encoding.UTF8.GetBytes(nextId.ToString())); + } + finally + { + createLock.Release(); + } + + string[] members = Fs.ReadDirectory($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/members"); + foreach(string memberWD in members) + { + bool isLocal = Account.Utils.IsUserLocal(memberWD, out string domain); + string memberId = Account.Utils.GetValidIdOrZero(memberWD); + if (isLocal) + { + await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}:update"); + } + } + return "success:message.pin.toggled"; + } + + public static async Task DmMessageGetPinned(string id, string dmId) + { + if (!await IsMemberOfDm(id, dmId)) return "error:forbidden"; + + string pinnedDir = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/pinned"; + if (!System.IO.Directory.Exists(pinnedDir)) return "{}"; + + string[] pinnedFiles = Fs.ReadDirectory(pinnedDir); + StringBuilder sb = new StringBuilder("{"); + int fetched = 0; + + foreach (string pFile in pinnedFiles) + { + string msgId = System.IO.Path.GetFileName(pFile); + string msgPath = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/{msgId}"; + + if (Fs.Exists(msgPath)) + { + if (fetched > 0) sb.Append(','); + sb.Append($"\"{msgId}\":"); + + string msgJson = Encoding.UTF8.GetString(await Fs.ReadFile(msgPath)); + Message msgObj = JsonSerializer.Deserialize(msgJson, AppJsonSerializerContext.Default.Message); + + string reactDir = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/reactions/{msgId}"; + if (Fs.Exists(reactDir)) + { + StringBuilder rxSb = new StringBuilder("["); + string[] reactFiles = Fs.ReadDirectory(reactDir); + int rxCount = 0; + foreach(string rf in reactFiles) + { + string rId = System.IO.Path.GetFileName(rf); + if (rId == "last") continue; + string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{reactDir}/{rId}")); + int nLineIdx = content.IndexOf('\n'); + if (nLineIdx != -1) + { + string u = content.Substring(0, nLineIdx).Trim(); + string r = content.Substring(nLineIdx + 1).Trim(); + if (rxCount > 0) rxSb.Append(','); + rxSb.Append($"{{\"id\":\"{rId}\",\"u\":\"{u}\",\"r\":\"{r}\"}}"); + rxCount++; + } + } + rxSb.Append("]"); + msgObj.reactions = rxCount > 0 ? rxSb.ToString() : ""; + } + else + { + msgObj.reactions = ""; + } + + msgObj.pinned = "true"; + sb.Append(JsonSerializer.Serialize(msgObj, AppJsonSerializerContext.Default.Message)); + fetched++; + } + } + + sb.Append("}"); + return sb.ToString(); + } } \ No newline at end of file diff --git a/LarpixServer/Utils/Jsons.cs b/LarpixServer/Utils/Jsons.cs index 7ee2e57..05301bc 100644 --- a/LarpixServer/Utils/Jsons.cs +++ b/LarpixServer/Utils/Jsons.cs @@ -60,4 +60,5 @@ public class Message public string key { get; set; } public string responded { get; set; } public string reactions { get; set; } + public string pinned { get; set; } } \ No newline at end of file