summary refs log tree commit diff
path: root/MxApiExtensions/Controllers/Extensions/JoinedRoomListController.cs
blob: 3c4161d4855da197a1fa7f9272812b53efc99500 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using System.Collections.Concurrent;
using System.Net.Http.Headers;
using ArcaneLibs.Extensions;
using LibMatrix.Homeservers;
using LibMatrix.MxApiExtensions;
using LibMatrix.RoomTypes;
using LibMatrix.StateEventTypes.Spec;
using Microsoft.AspNetCore.Mvc;
using MxApiExtensions.Services;

namespace MxApiExtensions.Controllers.Extensions;

[ApiController]
[Route("/_matrix/client/unstable/gay.rory.mxapiextensions")]
public class JoinedRoomListController : ControllerBase {
    private static ILogger _logger;
    private static MxApiExtensionsConfiguration _config;
    private readonly AuthenticationService _authenticationService;
    private readonly AuthenticatedHomeserverProviderService _authenticatedHomeserverProviderService;

    private static ConcurrentDictionary<string, RoomInfoEntry> _roomInfoCache = new();

    public JoinedRoomListController(ILogger<JoinedRoomListController> logger, MxApiExtensionsConfiguration config, AuthenticationService authenticationService,
        AuthenticatedHomeserverProviderService authenticatedHomeserverProviderService) {
        _logger = logger;
        _config = config;
        _authenticationService = authenticationService;
        _authenticatedHomeserverProviderService = authenticatedHomeserverProviderService;
    }

    [HttpGet("joined_rooms_with_info")]
    public async IAsyncEnumerable<RoomInfoEntry> GetJoinedRooms([FromQuery] string? access_token) {
        List<GenericRoom> rooms = new();
        AuthenticatedHomeserverGeneric? hs = null;
        try {
            hs = await _authenticatedHomeserverProviderService.GetHomeserver();
            _logger.LogInformation("Got room list with info request for {user} ({hs})", hs.UserId, hs.FullHomeServerDomain);
            rooms = await hs.GetJoinedRooms();
        }
        catch (MxApiMatrixException e) {
            _logger.LogError(e, "Matrix error");
            Response.StatusCode = StatusCodes.Status500InternalServerError;
            Response.ContentType = "application/json";

            await Response.WriteAsJsonAsync(e.GetAsJson());
            await Response.CompleteAsync();
        }
        catch (Exception e) {
            _logger.LogError(e, "Unhandled error");
            Response.StatusCode = StatusCodes.Status500InternalServerError;
            Response.ContentType = "text/plain";

            await Response.WriteAsJsonAsync(e.ToString());
            await Response.CompleteAsync();
        }

        if (hs is not null) {
            Response.ContentType = "application/json";
            Response.Headers.Add("Cache-Control", "public, max-age=60");
            Response.Headers.Add("Expires", DateTime.Now.AddMinutes(1).ToString("R"));
            Response.Headers.Add("Last-Modified", DateTime.Now.ToString("R"));
            Response.Headers.Add("X-Matrix-Server", hs.FullHomeServerDomain);
            Response.Headers.Add("X-Matrix-User", hs.UserId);
            // await Response.StartAsync();

            var cachedRooms = _roomInfoCache
                .Where(cr => rooms.Any(r => r.RoomId == cr.Key) && cr.Value.ExpiresAt > DateTime.Now)
                .ToList();
            rooms.RemoveAll(r => cachedRooms.Any(cr => cr.Key == r.RoomId));

            foreach (var room in cachedRooms) {
                yield return room.Value;
                _logger.LogInformation("Sent cached room info for {room} for {user} ({hs})", room.Key, hs.UserId, hs.FullHomeServerDomain);
            }

            var tasks = rooms.Select(r => GetRoomInfo(hs, r.RoomId)).ToAsyncEnumerable();

            await foreach (var result in tasks) {
                yield return result;
                _logger.LogInformation("Sent room info for {room} for {user} ({hs})", result.RoomId, hs.UserId, hs.FullHomeServerDomain);
            }
        }
    }

    private SemaphoreSlim _roomInfoSemaphore = new(100, 100);

    private async Task<RoomInfoEntry> GetRoomInfo(AuthenticatedHomeserverGeneric hs, string roomId) {
        _logger.LogInformation("Getting room info for {room} for {user} ({hs})", roomId, hs.UserId, hs.FullHomeServerDomain);
        var room = await hs.GetRoom(roomId);
        var state = room.GetFullStateAsync();
        var result = new RoomInfoEntry {
            RoomId = roomId,
            RoomState = new(),
            MemberCounts = new(),
            StateCount = 0,
            ExpiresAt = DateTime.Now.AddMinutes(5)
        };

        await foreach (var @event in state) {
            // result.ExpiresAt = result.ExpiresAt.AddMilliseconds(100);
            result.StateCount++;
            if (@event.Type != "m.room.member") result.RoomState.Add(@event);
            else {
                if(!result.MemberCounts.ContainsKey((@event.TypedContent as RoomMemberEventData)?.Membership)) result.MemberCounts.Add((@event.TypedContent as RoomMemberEventData)?.Membership, 0);
                    result.MemberCounts[(@event.TypedContent as RoomMemberEventData)?.Membership]++;
            }
        }

        result.ExpiresAt = result.ExpiresAt.AddMilliseconds(100 * result.StateCount);

        _logger.LogInformation("Got room info for {room} for {user} ({hs})", roomId, hs.UserId, hs.FullHomeServerDomain);
        while (!_roomInfoCache.TryAdd(roomId, result)) {
            _logger.LogWarning("Failed to add room info for {room} to cache, retrying...", roomId);
            await Task.Delay(100);
            if (_roomInfoCache.ContainsKey(roomId)) break;
        }

        return result;
    }

    [HttpGet("joined_rooms_with_info_cache")]
    public async Task<object> GetRoomInfoCache() {
        var mxid = await _authenticationService.GetMxidFromToken();
        if(!_config.Admins.Contains(mxid)) {
            Response.StatusCode = StatusCodes.Status403Forbidden;
            Response.ContentType = "application/json";

            await Response.WriteAsJsonAsync(new {
                ErrorCode = "M_FORBIDDEN",
                Error = "You are not an admin"
            });
            await Response.CompleteAsync();
            return null;
        }

        return _roomInfoCache.Select(x => new {
            x.Key,
            x.Value.ExpiresAt,
            ExpiresIn = x.Value.ExpiresAt - DateTime.Now,
            x.Value.MemberCounts,
            x.Value.StateCount
        }).OrderByDescending(x => x.ExpiresAt);
    }
}