diff options
6 files changed, 141 insertions, 8 deletions
diff --git a/ExampleBots/MediaModeratorPoC/Commands/BanMediaCommand.cs b/ExampleBots/MediaModeratorPoC/Commands/BanMediaCommand.cs index 69c0583..5dfa706 100644 --- a/ExampleBots/MediaModeratorPoC/Commands/BanMediaCommand.cs +++ b/ExampleBots/MediaModeratorPoC/Commands/BanMediaCommand.cs @@ -1,3 +1,4 @@ +using System.Buffers.Text; using System.Security.Cryptography; using ArcaneLibs.Extensions; using LibMatrix; @@ -87,7 +88,7 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic MediaPolicyEventContent policy; await policyRoom.SendStateEventAsync("gay.rory.media_moderator_poc.rule.media", Guid.NewGuid().ToString(), policy = new MediaPolicyEventContent { - Entity = uriHash, + // Entity = uriHash, FileHash = fileHash, Reason = string.Join(' ', ctx.Args[1..]), Recommendation = recommendation, diff --git a/ExampleBots/MediaModeratorPoC/StateEventTypes/BasePolicy.cs b/ExampleBots/MediaModeratorPoC/StateEventTypes/BasePolicy.cs index 048c1d0..7735314 100644 --- a/ExampleBots/MediaModeratorPoC/StateEventTypes/BasePolicy.cs +++ b/ExampleBots/MediaModeratorPoC/StateEventTypes/BasePolicy.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using LibMatrix; +using LibMatrix.Interfaces; namespace MediaModeratorPoC.StateEventTypes; -public abstract class BasePolicy : StateEvent { +public abstract class BasePolicy : EventContent { /// <summary> /// Entity this policy applies to /// </summary> diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs index bffd74a..d280ef3 100644 --- a/LibMatrix/Extensions/HttpClientExtensions.cs +++ b/LibMatrix/Extensions/HttpClientExtensions.cs @@ -111,6 +111,9 @@ public class MatrixHttpClient : HttpClient { options = GetJsonSerializerOptions(options); var request = new HttpRequestMessage(HttpMethod.Put, requestUri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + Console.WriteLine($"Sending PUT {requestUri}"); + Console.WriteLine($"Content: {value.ToJson()}"); + Console.WriteLine($"Type: {value.GetType().FullName}"); request.Content = new StringContent(JsonSerializer.Serialize(value, value.GetType(), options), Encoding.UTF8, "application/json"); return await SendAsync(request, cancellationToken); diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index 6bdf5ad..bfcd650 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -14,6 +14,8 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg public SyncFilter? Filter { get; set; } public bool FullState { get; set; } = false; + public bool IsInitialSync { get; set; } = true; + public async Task<SyncResponse?> SyncAsync(CancellationToken? cancellationToken = null) { var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}"; if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}"; @@ -39,14 +41,13 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg while (!cancellationToken?.IsCancellationRequested ?? true) { var sync = await SyncAsync(cancellationToken); if (sync is null) continue; - Since = sync.NextBatch ?? Since; + Since = string.IsNullOrWhiteSpace(sync?.NextBatch) ? Since : sync.NextBatch; yield return sync; } } public async Task RunSyncLoopAsync(bool skipInitialSyncEvents = true, CancellationToken? cancellationToken = null) { var sw = Stopwatch.StartNew(); - bool isInitialSync = true; int emptyInitialSyncCount = 0; var oldTimeout = Timeout; Timeout = 0; @@ -55,12 +56,12 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg if (sync?.ToJson(ignoreNull: true, indent: false).Length < 250) { emptyInitialSyncCount++; if (emptyInitialSyncCount > 5) { - isInitialSync = false; + IsInitialSync = false; Timeout = oldTimeout; } } - await RunSyncLoopCallbacksAsync(sync, isInitialSync && skipInitialSyncEvents); + await RunSyncLoopCallbacksAsync(sync, IsInitialSync && skipInitialSyncEvents); } } diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index 8a6e114..bc4ea1a 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -5,6 +5,8 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Filters; +using LibMatrix.Helpers; using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; @@ -163,4 +165,129 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) } #endregion -} + + public string? ResolveMediaUri(string? mxcUri) { + if (mxcUri is null) return null; + return $"{_httpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//", "/"); + } + + public async Task UpdateProfileAsync(ProfileResponseEventContent? newProfile, bool preserveCustomRoomProfile = true) { + Console.WriteLine($"Updating profile for {WhoAmI.UserId} to {newProfile.ToJson(ignoreNull: true)} (preserving room profiles: {preserveCustomRoomProfile})"); + if (newProfile is null) return; + var oldProfile = await GetProfileAsync(WhoAmI.UserId!); + Dictionary<string, RoomMemberEventContent> targetRoomProfileOverrides = new(); + var syncHelper = new SyncHelper(this) { + Filter = new SyncFilter { + AccountData = new SyncFilter.EventFilter() { + Types = new List<string> { + "m.room.member" + } + } + }, + Timeout = 250 + }; + int targetSyncCount = 0; + + if (preserveCustomRoomProfile) { + var rooms = await GetJoinedRooms(); + targetSyncCount = rooms.Count; + foreach (var room in rooms) { + try { + var currentRoomProfile = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", WhoAmI.UserId!); + //build new profiles + + if (currentRoomProfile.DisplayName == oldProfile.DisplayName) { + currentRoomProfile.DisplayName = newProfile.DisplayName; + } + + if (currentRoomProfile.AvatarUrl == oldProfile.AvatarUrl) { + currentRoomProfile.AvatarUrl = newProfile.AvatarUrl; + } + + targetRoomProfileOverrides.Add(room.RoomId, currentRoomProfile); + } + catch (Exception e) { } + } + + Console.WriteLine($"Rooms with custom profiles: {string.Join(',', targetRoomProfileOverrides.Keys)}"); + } + + if (oldProfile.DisplayName != newProfile.DisplayName) { + await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/profile/{WhoAmI.UserId}/displayname", new { displayname = newProfile.DisplayName }); + } + else { + Console.WriteLine($"Not updating display name because {oldProfile.DisplayName} == {newProfile.DisplayName}"); + } + + if (oldProfile.AvatarUrl != newProfile.AvatarUrl) { + await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/profile/{WhoAmI.UserId}/avatar_url", new { avatar_url = newProfile.AvatarUrl }); + } + else { + Console.WriteLine($"Not updating avatar URL because {newProfile.AvatarUrl} == {newProfile.AvatarUrl}"); + } + + if (!preserveCustomRoomProfile) return; + + int syncCount = 0; + await foreach (var sync in syncHelper.EnumerateSyncAsync()) { + if (sync.Rooms is null) break; + foreach (var (roomId, roomData) in sync.Rooms.Join) { + if (roomData.State is { Events: { Count: > 0 } }) { + var updatedRoomProfile = + roomData.State?.Events?.FirstOrDefault(x => x.Type == "m.room.member" && x.StateKey == WhoAmI.UserId)?.TypedContent as RoomMemberEventContent; + if (updatedRoomProfile is null) continue; + if (!targetRoomProfileOverrides.ContainsKey(roomId)) continue; + var targetRoomProfileOverride = targetRoomProfileOverrides[roomId]; + var room = GetRoom(roomId); + if (updatedRoomProfile.DisplayName != targetRoomProfileOverride.DisplayName || updatedRoomProfile.AvatarUrl != targetRoomProfileOverride.AvatarUrl) + await room.SendStateEventAsync("m.room.member", WhoAmI.UserId, targetRoomProfileOverride); + } + } + + var differenceFound = false; + if (syncCount++ >= targetSyncCount) { + var profiles = GetRoomProfilesAsync(); + await foreach ((string roomId, var profile) in profiles) { + if (!targetRoomProfileOverrides.ContainsKey(roomId)) continue; + var targetRoomProfileOverride = targetRoomProfileOverrides[roomId]; + if (profile.DisplayName != targetRoomProfileOverride.DisplayName || profile.AvatarUrl != targetRoomProfileOverride.AvatarUrl) { + differenceFound = true; + break; + } + } + // var rooms = await GetJoinedRooms(); + // List<ProfileResponseEventContent> currentProfiles = new(); + // foreach (var room in rooms) { + // try { + // var roomProfile = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", WhoAmI.UserId!); + // currentProfiles.Add(new ProfileResponseEventContent { + // AvatarUrl = roomProfile.AvatarUrl, + // DisplayName = roomProfile.DisplayName + // }); + // } + // catch (Exception e) { } + // } + // if (currentProfiles.All(x => x.DisplayName == newProfile.DisplayName) && currentProfiles.All(x => x.AvatarUrl == newProfile.AvatarUrl)) { + // Console.WriteLine("All rooms have been updated"); + // break; + // } + } + + if (!differenceFound) return; + } + } + + public async IAsyncEnumerable<KeyValuePair<string, RoomMemberEventContent>> GetRoomProfilesAsync() { + var rooms = await GetJoinedRooms(); + foreach (var room in rooms) { + RoomMemberEventContent? content = null; + try { + content = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", WhoAmI.UserId!); + } + catch (Exception e) { } + + if (content is not null) + yield return new KeyValuePair<string, RoomMemberEventContent>(room.RoomId, content!); + } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs index 6659081..c7fa5c3 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -54,7 +54,7 @@ public class HomeserverProviderService { public async Task<RemoteHomeServer> GetRemoteHomeserver(string homeserver, string? proxy = null) { var hs = await RemoteHomeServer.Create(proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)); // hs._httpClient.Dispose(); - // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; + // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.ServerName) }; // hs._httpClient.Timeout = TimeSpan.FromSeconds(120); return hs; } |