From 5b3df2a61935162359a9fb86a5cb9734ebcb9155 Mon Sep 17 00:00:00 2001 From: June Date: Sun, 17 Aug 2025 22:46:14 +0200 Subject: [PATCH] refactor RoomInfoTree building into separate package for better org. --- main.go | 327 +-------------------------------- roominfotree/maxroomversion.go | 178 ++++++++++++++++++ roominfotree/roominfotree.go | 169 +++++++++++++++++ 3 files changed, 351 insertions(+), 323 deletions(-) create mode 100644 roominfotree/maxroomversion.go create mode 100644 roominfotree/roominfotree.go diff --git a/main.go b/main.go index 706126b..1aaaa4b 100644 --- a/main.go +++ b/main.go @@ -7,266 +7,16 @@ import ( "slices" "sort" "strings" - "sync" "github.com/hashicorp/go-version" "github.com/matrix-org/gomatrixserverlib/fclient" - "github.com/matrix-org/gomatrixserverlib/spec" "maunium.net/go/mautrix" "maunium.net/go/mautrix/id" "codeberg.org/june64/mrvc/config" + "codeberg.org/june64/mrvc/roominfotree" ) -type HomeserverServerVersionInfo struct { - Homeserver string - ServerVersionInfo fclient.Version -} - -type RoomInfoTree map[id.RoomID](*RoomInfo) - -type RoomInfo struct { - MemberCount uint - MaxRoomVersions map[string](*MaxRoomVersionInfo) -} - -type MaxRoomVersionInfo struct { - MemberCount uint - Servers map[string](*ServerInfo) -} - -type ServerInfo struct { - MemberCount uint - Versions map[string](*VersionInfo) -} - -type VersionInfo struct { - MemberCount uint - Homeservers map[string](*HomeserverInfo) -} - -type HomeserverInfo struct { - MemberCount uint -} - -var unknownServerVersionInfo = fclient.Version{ - Server: struct { - Name string `json:"name"` - Version string `json:"version"` - }{ - Name: "unknown", - Version: "unknown", - }, -} - -func getServerVersionInfoByHomeserver(homeservers []string, federationClient *fclient.Client) map[string](fclient.Version) { - homeserverChannel := make(chan string) - go func() { - for _, hs := range homeservers { - homeserverChannel <- hs - } - close(homeserverChannel) - }() - - homeserverSVInfoChannel := make(chan HomeserverServerVersionInfo) - var wg sync.WaitGroup - const numHomeserverSVInfoReceivers = 100 - wg.Add(numHomeserverSVInfoReceivers) - for i := 0; i < numHomeserverSVInfoReceivers; i++ { - go func() { - for hs := range homeserverChannel { - serverVersionInfo, err := federationClient.GetVersion(context.Background(), spec.ServerName(hs)) - if err != nil { - serverVersionInfo = unknownServerVersionInfo - } - homeserverSVInfoChannel <- HomeserverServerVersionInfo{hs, serverVersionInfo} - } - wg.Done() - }() - } - - go func() { - wg.Wait() - close(homeserverSVInfoChannel) - }() - - serverVersionInfoByHomeserver := make(map[string](fclient.Version)) - for homeserverSVInfo := range homeserverSVInfoChannel { - serverVersionInfoByHomeserver[homeserverSVInfo.Homeserver] = homeserverSVInfo.ServerVersionInfo - } - return serverVersionInfoByHomeserver -} - -// Gets the maximum supported room version for the given server-version-pair. -func getMaxRoomVersion(serverVersionInfo fclient.Version) string { - // Use only the first part of the version string as sometimes there are suffixes after a space, like " ()", which then can't be parsed. - serverVersion, err := version.NewVersion(strings.Split(serverVersionInfo.Server.Version, " ")[0]) - if err != nil { - return "unkown" - } - - switch serverVersionInfo.Server.Name { - case "Conduit": - // https://conduit.rs/ - // https://conduit.rs/changelog/ - // https://gitlab.com/famedly/conduit - switch { - // https://conduit.rs/changelog/#v0-10-8-2025-08-11 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.10.8"))): - return "v12" - // https://conduit.rs/changelog/#v0-7-0-2024-04-25 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.7.0"))): - return "v11" - // https://conduit.rs/changelog/#v0-5-0-2022-12-21 - // Includes MR 400: - // https://gitlab.com/famedly/conduit/-/merge_requests/400/commits - // MR 400 includes a commit, which specifies v10 as stable. - // https://gitlab.com/famedly/conduit/-/commit/1e1a144dfa98429ef9f02d16045796b73013830d - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.5.0"))): - return "v10" - // https://conduit.rs/changelog/#v0-4-0-2022-06-23 - // Includes MR 257: - // https://gitlab.com/famedly/conduit/-/merge_requests/257/commits - // MR 257 includes a commit, which specifies added support v7 to v9. - // https://gitlab.com/famedly/conduit/-/commit/0ae39807a478370a769217d01fa33514299a2b35 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.4.0"))): - return "v9" - // https://conduit.rs/changelog/#v0-2-0-2021-09-01 - default: - return "v6" - } - case "conduwuit", "Conduwuit": - // Using continuwuitys source (history) as a reference: - // https://forgejo.ellis.link/continuwuation/continuwuity - - // History from first commit on "continuwuity": - // https://forgejo.ellis.link/continuwuation/continuwuity/commits/commit/e054a56b3286a6fb3091bedd5261089435ed26d1 - // Last commit on "conduwuit"/"Conduwuit": - // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/d8311a5ff672fdc4729d956af5e3af8646b0670d - // Last version was "0.5.0": - // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/d8311a5ff672fdc4729d956af5e3af8646b0670d/Cargo.toml - - // Matching both "conduwuit" and "Conduwuit" as the capitalization got changed. - // https://forgejo.ellis.link/continuwuation/continuwuity/commit/5b5ccba64e3d36a9235f4e0d449f40d859046dad - - switch { - // Since the version went from "0.7.0-" to "0.3.0" and the last version was "0.5.0", give all versions greater than "0.7.0" a lower max room version. - // https://forgejo.ellis.link/continuwuation/continuwuity/commit/af0b81f5fb737d3550cf22fbcf8d8b8eb1947408 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.7.0"))): - return "v10" - // v11 support (shows there as included in tag "v0.3.0"): - // https://forgejo.ellis.link/continuwuation/continuwuity/commit/223f05c922ea6f3085d386f0758df550459a25c3 - // Tag "v0.3.0": - // https://forgejo.ellis.link/continuwuation/continuwuity/src/tag/v0.3.0/src/service/globals/mod.rs#L130 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.3.0"))): - return "v11" - // First version named "Conduwuit"/"conduwuit": - // https://forgejo.ellis.link/continuwuation/continuwuity/commit/e71855cd0b59d9274e8528a4c1ee480f99006c3c - // v10 support at first version named "Conduwuit"/"conduwuit": - // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/e71855cd0b59d9274e8528a4c1ee480f99006c3c/src/service/globals/mod.rs#L187 - // And shows first version as "0.7.0-alpha+conduwuit-0.1.3": - // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/e71855cd0b59d9274e8528a4c1ee480f99006c3c/Cargo.toml - // Because of this versioning and the match on "0.7.0" before, this default should never match, but it is included for clarity. - default: - return "v10" - } - case "continuwuity": - // https://continuwuity.org/ - // https://forgejo.ellis.link/continuwuation/continuwuity - - // First version named "continuwuity": - // https://forgejo.ellis.link/continuwuation/continuwuity/commit/dc599db19c48ad3cbae15fc419c4a531d217ed05 - // v11 support at first version named "continuwuity": - // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/dc599db19c48ad3cbae15fc419c4a531d217ed05/src/core/info/room_version.rs - return "v11" - case "Dendrite": - // https://github.com/element-hq/dendrite - // https://element-hq.github.io/dendrite/ - // https://github.com/element-hq/dendrite/blob/main/CHANGES.md - switch { - // https://github.com/element-hq/dendrite/blob/v0.15.0/CHANGES.md#dendrite-0150-2025-08-12 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.15.0"))): - return "v12" - // https://github.com/matrix-org/dendrite/blob/v0.13.3/CHANGES.md#dendrite-0133-2023-09-28 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.13.3"))): - return "v11" - // https://github.com/matrix-org/dendrite/blob/v0.8.7/CHANGES.md#dendrite-087-2022-06-01 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.8.7"))): - return "v10" - // https://github.com/matrix-org/dendrite/blob/v0.8.6/CHANGES.md#dendrite-086-2022-05-26 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.8.6"))): - return "v9" - // https://github.com/matrix-org/dendrite/blob/v0.8.6/CHANGES.md#dendrite-086-2022-05-26 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.8.6"))): - return "v8" - // https://github.com/matrix-org/dendrite/blob/v0.4.1/CHANGES.md#dendrite-041-2021-07-26 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.4.1"))): - return "v7" - // https://github.com/matrix-org/dendrite/blob/v0.1.0/CHANGES.md#dendrite-010-2020-10-08 - default: - return "v6" - } - case "Synapse": - // https://github.com/element-hq/synapse/ - // https://element-hq.github.io/synapse/latest/ - // https://github.com/element-hq/synapse/blob/develop/CHANGES.md - // https://github.com/element-hq/synapse/blob/develop/docs/changelogs/ - switch { - // https://github.com/element-hq/synapse/blob/v1.135.1/CHANGES.md#synapse-11351-2025-08-11 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.135.1"))): - return "v12" - // https://github.com/element-hq/synapse/blob/v1.89.0rc1/CHANGES.md#synapse-1890rc1-2023-07-25 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.89.0rc1"))): - return "v11" - // https://github.com/element-hq/synapse/blob/v1.64.0rc1/CHANGES.md#synapse-1640rc1-2022-07-26 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.64.0rc1"))): - return "v10" - // https://github.com/element-hq/synapse/blob/v1.42.0rc2/CHANGES.md#synapse-1420rc2-2021-09-06 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.42.0rc2"))): - return "v9" - // https://github.com/element-hq/synapse/blob/v1.40.0rc3/CHANGES.md#synapse-1400rc3-2021-08-09 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.40.0rc3"))): - return "v8" - // https://github.com/element-hq/synapse/blob/v1.37.0rc1/CHANGES.md#synapse-1370rc1-2021-06-24 - // https://github.com/matrix-org/synapse/pull/10167 - // https://github.com/matrix-org/synapse/pull/10167/files#diff-8e36e2fc4dec5d84357ee3d140ad223b66fb36e630c25bbb45382ca59cdb0f40 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.37.0rc1"))): - return "v7" - // https://github.com/element-hq/synapse/blob/v1.14.0rc1/CHANGES.md#synapse-1140rc1-2020-05-26 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.14.0rc1"))): - return "v6" - // https://github.com/element-hq/synapse/blob/v1.0.0rc1/CHANGES.md#synapse-100rc1-2019-06-07 - // https://github.com/matrix-org/synapse/pull/5354 - // https://github.com/matrix-org/synapse/pull/5354/files#diff-8e36e2fc4dec5d84357ee3d140ad223b66fb36e630c25bbb45382ca59cdb0f40 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.0.0rc1"))): - return "v5" - // https://github.com/element-hq/synapse/blob/v0.99.5rc1/CHANGES.md#synapse-0995rc1-2019-05-21 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.99.5rc1"))): - return "v4" - // https://github.com/element-hq/synapse/blob/v0.99.0rc1/CHANGES.md#synapse-0990rc1-2019-01-30 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.99.0rc1"))): - return "v3" - // https://github.com/element-hq/synapse/blob/v0.34.1rc1/CHANGES.md#synapse-0341rc1-2019-01-08 - // https://github.com/matrix-org/synapse/pull/4307 - // https://github.com/matrix-org/synapse/pull/4307/files#diff-32e0f096397b0e3545054c51c42b5be4f0db953083b60d76a047cb1618f6b877 - case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.34.1rc1"))): - return "v2" - default: - return "v1" - } - case "Tuwunel": - // https://github.com/matrix-construct/tuwunel - - // First version named "Tuwunel": - // https://github.com/matrix-construct/tuwunel/commit/9b658d86b2f49d01ba8d2f5ae7f263a104e8f282#diff-d58f31e506570d0bcb7629251ec1b350c5dde4a205a338349f154f3fd3187e93 - // v11 support at first version named "Tuwunel": - // https://github.com/matrix-construct/tuwunel/blob/9b658d86b2f49d01ba8d2f5ae7f263a104e8f282/src/core/info/room_version.rs - return "v11" - default: - return "unkown" - } -} - func compareVersionStrings(a, b string) int { // Try to parse a and b as versions. // Use only the first part of the version string as sometimes there are suffixes after a space, like " ()", which then can't be parsed. @@ -337,81 +87,12 @@ func main() { } } - joinedMembersByRoomID := make(map[id.RoomID]*mautrix.RespJoinedMembers) + roomIDs := make([]id.RoomID, 0, len(givenRoomsByRoomID)) for roomID := range givenRoomsByRoomID { - joinedMembers, err := client.JoinedMembers(context.Background(), roomID) - if err != nil { - log.Fatal(err) - } - joinedMembersByRoomID[roomID] = joinedMembers + roomIDs = append(roomIDs, roomID) } - membersByHomeserverByRoomID := make(map[id.RoomID](map[string]uint)) - for roomID, joinedMembers := range joinedMembersByRoomID { - membersByHomeserver := make(map[string]uint) - for key := range joinedMembers.Joined { - membersByHomeserver[key.Homeserver()]++ - } - membersByHomeserverByRoomID[roomID] = membersByHomeserver - } - - homeserverSet := make(map[string]bool) - for _, membersByHomeserver := range membersByHomeserverByRoomID { - for hs := range membersByHomeserver { - homeserverSet[hs] = true - } - } - - homeservers := make([]string, 0, len(homeserverSet)) - for hs := range homeserverSet { - homeservers = append(homeservers, hs) - } - - serverVersionInfoByHomeserver := getServerVersionInfoByHomeserver(homeservers, federationClient) - - // Info map tree. - roomInfoTree := make(RoomInfoTree) - - for roomID, membersByHomeserver := range membersByHomeserverByRoomID { - for hs, members := range membersByHomeserver { - serverVersionInfo := serverVersionInfoByHomeserver[hs] - - maxRoomVersion := getMaxRoomVersion(serverVersionInfo) - - // Sort into roomInfoTree and add to counters. - roomInfo, ok := roomInfoTree[roomID] - if !ok { - roomInfo = &RoomInfo{MaxRoomVersions: make(map[string]*MaxRoomVersionInfo)} - roomInfoTree[roomID] = roomInfo - } - maxRoomVersionInfo, ok := roomInfo.MaxRoomVersions[maxRoomVersion] - if !ok { - maxRoomVersionInfo = &MaxRoomVersionInfo{Servers: make(map[string]*ServerInfo)} - roomInfo.MaxRoomVersions[maxRoomVersion] = maxRoomVersionInfo - } - serverInfo, ok := maxRoomVersionInfo.Servers[serverVersionInfo.Server.Name] - if !ok { - serverInfo = &ServerInfo{Versions: make(map[string]*VersionInfo)} - maxRoomVersionInfo.Servers[serverVersionInfo.Server.Name] = serverInfo - } - versionInfo, ok := serverInfo.Versions[serverVersionInfo.Server.Version] - if !ok { - versionInfo = &VersionInfo{Homeservers: make(map[string]*HomeserverInfo)} - serverInfo.Versions[serverVersionInfo.Server.Version] = versionInfo - } - homeserverInfo, ok := versionInfo.Homeservers[hs] - if !ok { - homeserverInfo = &HomeserverInfo{} - versionInfo.Homeservers[hs] = homeserverInfo - } - - homeserverInfo.MemberCount = members - versionInfo.MemberCount += members - serverInfo.MemberCount += members - maxRoomVersionInfo.MemberCount += members - roomInfo.MemberCount += members - } - } + roomInfoTree := roominfotree.Get(roomIDs, client, federationClient) for roomID, roomInfo := range roomInfoTree { fmt.Println("Room:") diff --git a/roominfotree/maxroomversion.go b/roominfotree/maxroomversion.go new file mode 100644 index 0000000..8b5bc14 --- /dev/null +++ b/roominfotree/maxroomversion.go @@ -0,0 +1,178 @@ +package roominfotree + +import ( + "strings" + + "github.com/hashicorp/go-version" + "github.com/matrix-org/gomatrixserverlib/fclient" +) + +// Gets the maximum supported room version for the given server-version-pair. +func getMaxRoomVersion(serverVersionInfo fclient.Version) string { + // Use only the first part of the version string as sometimes there are suffixes after a space, like " ()", which then can't be parsed. + serverVersion, err := version.NewVersion(strings.Split(serverVersionInfo.Server.Version, " ")[0]) + if err != nil { + return "unkown" + } + + switch serverVersionInfo.Server.Name { + case "Conduit": + // https://conduit.rs/ + // https://conduit.rs/changelog/ + // https://gitlab.com/famedly/conduit + switch { + // https://conduit.rs/changelog/#v0-10-8-2025-08-11 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.10.8"))): + return "v12" + // https://conduit.rs/changelog/#v0-7-0-2024-04-25 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.7.0"))): + return "v11" + // https://conduit.rs/changelog/#v0-5-0-2022-12-21 + // Includes MR 400: + // https://gitlab.com/famedly/conduit/-/merge_requests/400/commits + // MR 400 includes a commit, which specifies v10 as stable. + // https://gitlab.com/famedly/conduit/-/commit/1e1a144dfa98429ef9f02d16045796b73013830d + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.5.0"))): + return "v10" + // https://conduit.rs/changelog/#v0-4-0-2022-06-23 + // Includes MR 257: + // https://gitlab.com/famedly/conduit/-/merge_requests/257/commits + // MR 257 includes a commit, which specifies added support v7 to v9. + // https://gitlab.com/famedly/conduit/-/commit/0ae39807a478370a769217d01fa33514299a2b35 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.4.0"))): + return "v9" + // https://conduit.rs/changelog/#v0-2-0-2021-09-01 + default: + return "v6" + } + case "conduwuit", "Conduwuit": + // Using continuwuitys source (history) as a reference: + // https://forgejo.ellis.link/continuwuation/continuwuity + + // History from first commit on "continuwuity": + // https://forgejo.ellis.link/continuwuation/continuwuity/commits/commit/e054a56b3286a6fb3091bedd5261089435ed26d1 + // Last commit on "conduwuit"/"Conduwuit": + // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/d8311a5ff672fdc4729d956af5e3af8646b0670d + // Last version was "0.5.0": + // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/d8311a5ff672fdc4729d956af5e3af8646b0670d/Cargo.toml + + // Matching both "conduwuit" and "Conduwuit" as the capitalization got changed. + // https://forgejo.ellis.link/continuwuation/continuwuity/commit/5b5ccba64e3d36a9235f4e0d449f40d859046dad + + switch { + // Since the version went from "0.7.0-" to "0.3.0" and the last version was "0.5.0", give all versions greater than "0.7.0" a lower max room version. + // https://forgejo.ellis.link/continuwuation/continuwuity/commit/af0b81f5fb737d3550cf22fbcf8d8b8eb1947408 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.7.0"))): + return "v10" + // v11 support (shows there as included in tag "v0.3.0"): + // https://forgejo.ellis.link/continuwuation/continuwuity/commit/223f05c922ea6f3085d386f0758df550459a25c3 + // Tag "v0.3.0": + // https://forgejo.ellis.link/continuwuation/continuwuity/src/tag/v0.3.0/src/service/globals/mod.rs#L130 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.3.0"))): + return "v11" + // First version named "Conduwuit"/"conduwuit": + // https://forgejo.ellis.link/continuwuation/continuwuity/commit/e71855cd0b59d9274e8528a4c1ee480f99006c3c + // v10 support at first version named "Conduwuit"/"conduwuit": + // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/e71855cd0b59d9274e8528a4c1ee480f99006c3c/src/service/globals/mod.rs#L187 + // And shows first version as "0.7.0-alpha+conduwuit-0.1.3": + // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/e71855cd0b59d9274e8528a4c1ee480f99006c3c/Cargo.toml + // Because of this versioning and the match on "0.7.0" before, this default should never match, but it is included for clarity. + default: + return "v10" + } + case "continuwuity": + // https://continuwuity.org/ + // https://forgejo.ellis.link/continuwuation/continuwuity + + // First version named "continuwuity": + // https://forgejo.ellis.link/continuwuation/continuwuity/commit/dc599db19c48ad3cbae15fc419c4a531d217ed05 + // v11 support at first version named "continuwuity": + // https://forgejo.ellis.link/continuwuation/continuwuity/src/commit/dc599db19c48ad3cbae15fc419c4a531d217ed05/src/core/info/room_version.rs + return "v11" + case "Dendrite": + // https://github.com/element-hq/dendrite + // https://element-hq.github.io/dendrite/ + // https://github.com/element-hq/dendrite/blob/main/CHANGES.md + switch { + // https://github.com/element-hq/dendrite/blob/v0.15.0/CHANGES.md#dendrite-0150-2025-08-12 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.15.0"))): + return "v12" + // https://github.com/matrix-org/dendrite/blob/v0.13.3/CHANGES.md#dendrite-0133-2023-09-28 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.13.3"))): + return "v11" + // https://github.com/matrix-org/dendrite/blob/v0.8.7/CHANGES.md#dendrite-087-2022-06-01 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.8.7"))): + return "v10" + // https://github.com/matrix-org/dendrite/blob/v0.8.6/CHANGES.md#dendrite-086-2022-05-26 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.8.6"))): + return "v9" + // https://github.com/matrix-org/dendrite/blob/v0.8.6/CHANGES.md#dendrite-086-2022-05-26 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.8.6"))): + return "v8" + // https://github.com/matrix-org/dendrite/blob/v0.4.1/CHANGES.md#dendrite-041-2021-07-26 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.4.1"))): + return "v7" + // https://github.com/matrix-org/dendrite/blob/v0.1.0/CHANGES.md#dendrite-010-2020-10-08 + default: + return "v6" + } + case "Synapse": + // https://github.com/element-hq/synapse/ + // https://element-hq.github.io/synapse/latest/ + // https://github.com/element-hq/synapse/blob/develop/CHANGES.md + // https://github.com/element-hq/synapse/blob/develop/docs/changelogs/ + switch { + // https://github.com/element-hq/synapse/blob/v1.135.1/CHANGES.md#synapse-11351-2025-08-11 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.135.1"))): + return "v12" + // https://github.com/element-hq/synapse/blob/v1.89.0rc1/CHANGES.md#synapse-1890rc1-2023-07-25 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.89.0rc1"))): + return "v11" + // https://github.com/element-hq/synapse/blob/v1.64.0rc1/CHANGES.md#synapse-1640rc1-2022-07-26 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.64.0rc1"))): + return "v10" + // https://github.com/element-hq/synapse/blob/v1.42.0rc2/CHANGES.md#synapse-1420rc2-2021-09-06 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.42.0rc2"))): + return "v9" + // https://github.com/element-hq/synapse/blob/v1.40.0rc3/CHANGES.md#synapse-1400rc3-2021-08-09 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.40.0rc3"))): + return "v8" + // https://github.com/element-hq/synapse/blob/v1.37.0rc1/CHANGES.md#synapse-1370rc1-2021-06-24 + // https://github.com/matrix-org/synapse/pull/10167 + // https://github.com/matrix-org/synapse/pull/10167/files#diff-8e36e2fc4dec5d84357ee3d140ad223b66fb36e630c25bbb45382ca59cdb0f40 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.37.0rc1"))): + return "v7" + // https://github.com/element-hq/synapse/blob/v1.14.0rc1/CHANGES.md#synapse-1140rc1-2020-05-26 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.14.0rc1"))): + return "v6" + // https://github.com/element-hq/synapse/blob/v1.0.0rc1/CHANGES.md#synapse-100rc1-2019-06-07 + // https://github.com/matrix-org/synapse/pull/5354 + // https://github.com/matrix-org/synapse/pull/5354/files#diff-8e36e2fc4dec5d84357ee3d140ad223b66fb36e630c25bbb45382ca59cdb0f40 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("1.0.0rc1"))): + return "v5" + // https://github.com/element-hq/synapse/blob/v0.99.5rc1/CHANGES.md#synapse-0995rc1-2019-05-21 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.99.5rc1"))): + return "v4" + // https://github.com/element-hq/synapse/blob/v0.99.0rc1/CHANGES.md#synapse-0990rc1-2019-01-30 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.99.0rc1"))): + return "v3" + // https://github.com/element-hq/synapse/blob/v0.34.1rc1/CHANGES.md#synapse-0341rc1-2019-01-08 + // https://github.com/matrix-org/synapse/pull/4307 + // https://github.com/matrix-org/synapse/pull/4307/files#diff-32e0f096397b0e3545054c51c42b5be4f0db953083b60d76a047cb1618f6b877 + case serverVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.34.1rc1"))): + return "v2" + default: + return "v1" + } + case "Tuwunel": + // https://github.com/matrix-construct/tuwunel + + // First version named "Tuwunel": + // https://github.com/matrix-construct/tuwunel/commit/9b658d86b2f49d01ba8d2f5ae7f263a104e8f282#diff-d58f31e506570d0bcb7629251ec1b350c5dde4a205a338349f154f3fd3187e93 + // v11 support at first version named "Tuwunel": + // https://github.com/matrix-construct/tuwunel/blob/9b658d86b2f49d01ba8d2f5ae7f263a104e8f282/src/core/info/room_version.rs + return "v11" + default: + return "unkown" + } +} diff --git a/roominfotree/roominfotree.go b/roominfotree/roominfotree.go new file mode 100644 index 0000000..d5928a0 --- /dev/null +++ b/roominfotree/roominfotree.go @@ -0,0 +1,169 @@ +package roominfotree + +import ( + "context" + "log" + "sync" + + "github.com/matrix-org/gomatrixserverlib/fclient" + "github.com/matrix-org/gomatrixserverlib/spec" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/id" +) + +type homeserverServerVersionInfo struct { + homeserver string + serverVersionInfo fclient.Version +} + +type RoomInfoTree map[id.RoomID](*RoomInfo) + +type RoomInfo struct { + MemberCount uint + MaxRoomVersions map[string](*MaxRoomVersionInfo) +} + +type MaxRoomVersionInfo struct { + MemberCount uint + Servers map[string](*ServerInfo) +} + +type ServerInfo struct { + MemberCount uint + Versions map[string](*VersionInfo) +} + +type VersionInfo struct { + MemberCount uint + Homeservers map[string](*HomeserverInfo) +} + +type HomeserverInfo struct { + MemberCount uint +} + +var unknownServerVersionInfo = fclient.Version{ + Server: struct { + Name string `json:"name"` + Version string `json:"version"` + }{ + Name: "unknown", + Version: "unknown", + }, +} + +func getServerVersionInfoByHomeserver(homeservers []string, federationClient *fclient.Client) map[string](fclient.Version) { + homeserverChannel := make(chan string) + go func() { + for _, hs := range homeservers { + homeserverChannel <- hs + } + close(homeserverChannel) + }() + + homeserverSVInfoChannel := make(chan homeserverServerVersionInfo) + var wg sync.WaitGroup + const numHomeserverSVInfoReceivers = 100 + wg.Add(numHomeserverSVInfoReceivers) + for i := 0; i < numHomeserverSVInfoReceivers; i++ { + go func() { + for hs := range homeserverChannel { + serverVersionInfo, err := federationClient.GetVersion(context.Background(), spec.ServerName(hs)) + if err != nil { + serverVersionInfo = unknownServerVersionInfo + } + homeserverSVInfoChannel <- homeserverServerVersionInfo{hs, serverVersionInfo} + } + wg.Done() + }() + } + + go func() { + wg.Wait() + close(homeserverSVInfoChannel) + }() + + serverVersionInfoByHomeserver := make(map[string](fclient.Version)) + for homeserverSVInfo := range homeserverSVInfoChannel { + serverVersionInfoByHomeserver[homeserverSVInfo.homeserver] = homeserverSVInfo.serverVersionInfo + } + return serverVersionInfoByHomeserver +} + +func Get(roomIDs []id.RoomID, client *mautrix.Client, federationClient *fclient.Client) RoomInfoTree { + joinedMembersByRoomID := make(map[id.RoomID]*mautrix.RespJoinedMembers) + for _, roomID := range roomIDs { + joinedMembers, err := client.JoinedMembers(context.Background(), roomID) + if err != nil { + log.Fatal(err) + } + joinedMembersByRoomID[roomID] = joinedMembers + } + + membersByHomeserverByRoomID := make(map[id.RoomID](map[string]uint)) + for roomID, joinedMembers := range joinedMembersByRoomID { + membersByHomeserver := make(map[string]uint) + for key := range joinedMembers.Joined { + membersByHomeserver[key.Homeserver()]++ + } + membersByHomeserverByRoomID[roomID] = membersByHomeserver + } + + homeserverSet := make(map[string]bool) + for _, membersByHomeserver := range membersByHomeserverByRoomID { + for hs := range membersByHomeserver { + homeserverSet[hs] = true + } + } + homeservers := make([]string, 0, len(homeserverSet)) + for hs := range homeserverSet { + homeservers = append(homeservers, hs) + } + + serverVersionInfoByHomeserver := getServerVersionInfoByHomeserver(homeservers, federationClient) + + roomInfoTree := make(RoomInfoTree) + + for roomID, membersByHomeserver := range membersByHomeserverByRoomID { + for hs, members := range membersByHomeserver { + serverVersionInfo := serverVersionInfoByHomeserver[hs] + + maxRoomVersion := getMaxRoomVersion(serverVersionInfo) + + // Sort into roomInfoTree and add to counters. + roomInfo, ok := roomInfoTree[roomID] + if !ok { + roomInfo = &RoomInfo{MaxRoomVersions: make(map[string]*MaxRoomVersionInfo)} + roomInfoTree[roomID] = roomInfo + } + maxRoomVersionInfo, ok := roomInfo.MaxRoomVersions[maxRoomVersion] + if !ok { + maxRoomVersionInfo = &MaxRoomVersionInfo{Servers: make(map[string]*ServerInfo)} + roomInfo.MaxRoomVersions[maxRoomVersion] = maxRoomVersionInfo + } + serverInfo, ok := maxRoomVersionInfo.Servers[serverVersionInfo.Server.Name] + if !ok { + serverInfo = &ServerInfo{Versions: make(map[string]*VersionInfo)} + maxRoomVersionInfo.Servers[serverVersionInfo.Server.Name] = serverInfo + } + versionInfo, ok := serverInfo.Versions[serverVersionInfo.Server.Version] + if !ok { + versionInfo = &VersionInfo{Homeservers: make(map[string]*HomeserverInfo)} + serverInfo.Versions[serverVersionInfo.Server.Version] = versionInfo + } + homeserverInfo, ok := versionInfo.Homeservers[hs] + if !ok { + homeserverInfo = &HomeserverInfo{} + versionInfo.Homeservers[hs] = homeserverInfo + } + + homeserverInfo.MemberCount = members + versionInfo.MemberCount += members + serverInfo.MemberCount += members + maxRoomVersionInfo.MemberCount += members + roomInfo.MemberCount += members + } + } + + return roomInfoTree +}