group settings (TEST)

This commit is contained in:
olcxja 2026-08-09 16:51:07 +02:00
commit 97fbbc7b71
4 changed files with 194 additions and 13 deletions

View file

@ -666,7 +666,7 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
"dm/message/send", "dm/message/delete", "dm/message/react", "dm/key/update", "dm/message/pin/toggle",
"dm/message/react/add", "dm/message/react/remove",
"user/storage/public/update", "user/storage/private/update", "dm/read",
"group/create", "user/group/list", "group/info",
"group/create", "user/group/list", "group/info", "group/settings/update",
"user/invite/accept", "user/invite/decline", "user/invite/revoke", "user/dm/list", "user/group/list",
"user/storage/public/update", "user/storage/private/update", "group/storage/public/update", "group/storage/private/update", "group/storage/private/getentry", "dm/read", "group/read",
"dm/message/send", "dm/message/delete", "dm/message/react/add", "dm/message/react/remove", "dm/message/pin/toggle",
@ -806,16 +806,25 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
break;
case "group/create":
{
Universal2String groupCreate = JsonSerializer.Deserialize(
Universal3String groupCreate = JsonSerializer.Deserialize(
serializedBody.string2,
AppJsonSerializerContext.Default.Universal2String
AppJsonSerializerContext.Default.Universal3String
);
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupCreate(id, groupCreate.string1, groupCreate.string2), password));
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupCreate(id, groupCreate.string1, groupCreate.string2, groupCreate.string3), password));
break;
}
case "group/info":
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInfo(serializedBody.string2), password));
break;
case "group/settings/update":
{
Universal3String req = JsonSerializer.Deserialize(
serializedBody.string2,
AppJsonSerializerContext.Default.Universal3String
);
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupSettingsUpdate(id, req.string1, req.string2, req.string3), password));
break;
}
case "group/invite":
{
Universal2String payload = JsonSerializer.Deserialize(

View file

@ -53,7 +53,7 @@ public class Groups
await Fs.WriteFile(groupPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(fileGroup, AppJsonSerializerContext.Default.Universal3String)));
}
public static async Task<string> GroupCreate(string id, string name, string body)
public static async Task<string> GroupCreate(string id, string name, string body, string historyVisible = "false")
{
await Room.Requests.createLock.WaitAsync();
try
@ -84,6 +84,7 @@ public class Groups
await Fs.WriteFile($"{groupPath}/keys/last", Encoding.UTF8.GetBytes("0"));
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("false"));
await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name));
await Fs.WriteFile($"{groupPath}/history_visible", Encoding.UTF8.GetBytes(historyVisible == "true" ? "true" : "false"));
// body is the key encrypted for the creator
await Fs.WriteFile($"{groupPath}/keys/0/{id};{DOMAIN}", Encoding.UTF8.GetBytes(body));
@ -149,6 +150,102 @@ public class Groups
return $"{ROOMS_DIR}/groups/{domain}/{id}";
}
public static async Task<string> GroupSettingsUpdate(string userId, string groupId, string name, string historyVisible)
{
string groupPath = GetGroupPath(groupId);
if (!Fs.Exists(groupPath)) return "error:group.not.found";
string memberPath = $"{groupPath}/members/{userId};{Utils.Utils.DOMAIN}";
if (!Fs.Exists(memberPath)) return "error:not.a.member";
await Room.Requests.createLock.WaitAsync();
try
{
if (!string.IsNullOrEmpty(name))
{
if (string.IsNullOrWhiteSpace(name)) return "error:invalid.name";
await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name));
}
if (!string.IsNullOrEmpty(historyVisible))
{
bool currentState = false;
if (Fs.Exists($"{groupPath}/history_visible"))
{
currentState = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true";
}
bool newState = historyVisible == "true";
if (currentState != newState)
{
await Fs.WriteFile($"{groupPath}/history_visible", Encoding.UTF8.GetBytes(newState ? "true" : "false"));
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
Message infoMsg = new();
infoMsg.attachment = "";
infoMsg.author = userId;
infoMsg.timestamp = timestamp;
infoMsg.type = "larp.info.history";
infoMsg.content = newState ? "{blah(history.visible)}" : "{blah(history.hidden)}";
infoMsg.key = "";
ulong lastMessageId = 0;
if (Fs.Exists($"{groupPath}/messages/last"))
{
lastMessageId = ulong.Parse(Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/messages/last")));
}
ulong newMessageId = lastMessageId + 1;
infoMsg.pervious = lastMessageId.ToString();
await Fs.WriteFile($"{groupPath}/messages/{newMessageId}", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(infoMsg, AppJsonSerializerContext.Default.Message)));
await Fs.WriteFile($"{groupPath}/messages/last", Encoding.UTF8.GetBytes(newMessageId.ToString()));
string[] members = Fs.ReadDirectory($"{groupPath}/members");
foreach (string memberWD in members)
{
string memberStr = Path.GetFileName(memberWD);
bool isLocal = Account.Utils.IsUserLocal(memberStr, out string d);
string memberId = Account.Utils.GetValidIdOrZero(memberStr);
if (isLocal)
{
if (memberId == userId)
{
await UpdateUserGroup(memberId, groupId, "true", timestamp);
await Utils.Utils.SendWsMessage(memberId, $"group_message:{groupId}:read");
}
else
{
_ = Task.Run(async () =>
{
SemaphoreSlim userLock = Account.Utils.GetUserLock(memberId);
await userLock.WaitAsync();
try
{
await UpdateUserGroup(memberId, groupId, "false", timestamp);
}
finally
{
userLock.Release();
}
await Utils.Utils.SendWsMessage(memberId, $"group_message:{groupId}:unread");
});
}
}
}
}
}
}
finally
{
Room.Requests.createLock.Release();
}
return "success";
}
public static async Task<string> GroupInfo(string groupId)
{
string groupPath = GetGroupPath(groupId);
@ -181,16 +278,23 @@ public class Groups
newRequired = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/keys/newrequired"));
}
var info = new Universal5String
string historyVisible = "false";
if (Fs.Exists($"{groupPath}/history_visible"))
{
historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible"));
}
var info = new Universal6String
{
string1 = groupId,
string2 = name,
string3 = memberCount,
string4 = lastKeyIndex,
string5 = newRequired
string5 = newRequired,
string6 = historyVisible
};
return JsonSerializer.Serialize(info, AppJsonSerializerContext.Default.Universal5String);
return JsonSerializer.Serialize(info, AppJsonSerializerContext.Default.Universal6String);
}
public static async Task<string> GroupInvite(string id, string groupId, string targetId)
@ -319,9 +423,16 @@ public class Groups
// Add to members
await Fs.WriteFile($"{groupPath}/members/{id};{DOMAIN}", Array.Empty<byte>());
// Require new key
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
// Require new key if history is not visible
bool historyVisible = false;
if (Fs.Exists($"{groupPath}/history_visible"))
{
historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true";
}
if (!historyVisible)
{
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
}
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
Message joinMsg = new();
@ -520,6 +631,42 @@ public class Groups
return "success:storage.entry.updated";
}
public static async Task<string> GroupDelete(string groupId)
{
string[] parts = groupId.Split(';');
string id = parts[0];
string domain = parts.Length > 1 ? parts[1] : Utils.Utils.DOMAIN;
string groupPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}";
if (!Fs.Exists(groupPath)) return "error:group.not.found";
await Room.Requests.createLock.WaitAsync();
try
{
string membersDir = $"{groupPath}/members";
if (Fs.Exists(membersDir))
{
string[] memberFiles = Fs.ReadDirectory(membersDir);
foreach (string memberFile in memberFiles)
{
string memberId = memberFile.Split(';')[0];
string userGroupPath = $"{Utils.Utils.ACCOUNTS_DATA_DIR}/{memberId}/groups/{id};{domain}";
if (Fs.Exists(userGroupPath))
{
Fs.DeleteFile(userGroupPath);
}
}
}
Fs.DeleteDirectory(groupPath);
}
finally
{
Room.Requests.createLock.Release();
}
return "success";
}
public static async Task<string> GroupLeave(string userId, string groupId)
{
string[] parts = groupId.Split(';');
@ -547,8 +694,16 @@ public class Groups
Fs.DeleteFile(userGroupPath);
}
// Require new key
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
// Require new key if history is not visible
bool historyVisible = false;
if (Fs.Exists($"{groupPath}/history_visible"))
{
historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true";
}
if (!historyVisible)
{
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
}
// Add info message
Message msg = new();
@ -575,6 +730,13 @@ public class Groups
Room.Requests.createLock.Release();
}
// If members count == 0, delete group folder and we are done.
// Doing it outside the lock since GroupDelete takes the lock again.
if (Fs.ReadDirectory($"{groupPath}/members").Length == 0)
{
await GroupDelete(groupId);
}
return "success";
}
}

View file

@ -11,6 +11,7 @@ using System.Collections.Generic;
[JsonSerializable(typeof(Universal3String))]
[JsonSerializable(typeof(Universal4String))]
[JsonSerializable(typeof(Universal5String))]
[JsonSerializable(typeof(Universal6String))]
[JsonSerializable(typeof(ServerInfo))]
[JsonSerializable(typeof(Message))]
[JsonSerializable(typeof(Dictionary<string, string>))]

View file

@ -34,6 +34,15 @@ public class Universal5String
public string string5 { get; set; }
}
public class Universal6String
{
public string string1 { get; set; }
public string string2 { get; set; }
public string string3 { get; set; }
public string string4 { get; set; }
public string string5 { get; set; }
public string string6 { get; set; }
}
public class KeyExchangePayload
{