From f727df2cf4e22a2b7ff9dd7d59643fb38f651c2c Mon Sep 17 00:00:00 2001 From: June Date: Fri, 15 Aug 2025 18:05:14 +0200 Subject: [PATCH] add first version of mrvc (max room version checker) Add first version of mrvc (max room version checker) - a tool for determining how many members support which maximum room version for a given Matrix room. --- go.mod | 31 ++++++ go.sum | 88 +++++++++++++++ main.go | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 446 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f1cd74c --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module codeberg.org/june64/mrvc + +go 1.24.5 + +require ( + github.com/hashicorp/go-version v1.7.0 + github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744 + maunium.net/go/mautrix v0.24.2 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/hashicorp/go-set/v3 v3.0.0 // indirect + github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 // indirect + github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/oleiade/lane/v2 v2.0.0 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.mau.fi/util v0.8.8 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..18cb1fd --- /dev/null +++ b/go.sum @@ -0,0 +1,88 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/go-set/v3 v3.0.0 h1:CaJBQvQCOWoftrBcDt7Nwgo0kdpmrKxar/x2o6pV9JA= +github.com/hashicorp/go-set/v3 v3.0.0/go.mod h1:IEghM2MpE5IaNvL+D7X480dfNtxjRXZ6VMpK3C8s2ok= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U= +github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744 h1:5GvC2FD9O/PhuyY95iJQdNYHbDioEhMWdeMP9maDUL8= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= +github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= +github.com/matrix-org/util v0.0.0-20221111132719-399730281e66/go.mod h1:iBI1foelCqA09JJgPV0FYz4qA5dUXYOxMi57FxKBdd4= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= +github.com/oleiade/lane/v2 v2.0.0 h1:XW/ex/Inr+bPkLd3O240xrFOhUkTd4Wy176+Gv0E3Qw= +github.com/oleiade/lane/v2 v2.0.0/go.mod h1:i5FBPFAYSWCgLh58UkUGCChjcCzef/MI7PlQm2TKCeg= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ= +github.com/shoenig/test v1.11.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= +go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +maunium.net/go/mautrix v0.24.2 h1:+AVT5kbcA/QuT5svrJKp4ivwoUmz+RRplMp3DnfpheI= +maunium.net/go/mautrix v0.24.2/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..23baa9a --- /dev/null +++ b/main.go @@ -0,0 +1,327 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "sort" + "strings" + + "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" +) + +type ServerPath struct { + MaxRoomVersion string + Server string +} + +type VersionPath struct { + MaxRoomVersion string + Server string + Version string +} + +var unknownServerVersionInfo = fclient.Version{ + Server: struct { + Name string `json:"name"` + Version string `json:"version"` + }{ + Name: "unknown", + Version: "unknown", + }, +} + +// 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 main() { + userLocalpart := os.Getenv("MATRIX_USER_LOCALPART") + homeserver := os.Getenv("MATRIX_HOMESERVER") + token := os.Getenv("MATRIX_TOKEN") + room := os.Getenv("MATRIX_ROOM") + verbose := false + + client, err := mautrix.NewClient( + homeserver, + id.NewUserID(userLocalpart, homeserver), + token, + ) + if err != nil { + log.Fatal(err) + } + federationClient := fclient.NewClient( + fclient.WithWellKnownSRVLookups(true), + ) + + joinedMembers, err := client.JoinedMembers(context.Background(), id.RoomID(room)) + if err != nil { + log.Fatal(err) + } + + membersByHomeservers := make(map[string]uint) + for key := range joinedMembers.Joined { + membersByHomeservers[key.Homeserver()]++ + } + + // Info map tree. + maxRoomVersionInfo := make(map[string](map[string](map[string](map[string]uint)))) + // Member counters. + membersByMaxRoomVersion := make(map[string]uint) + membersByServerPath := make(map[ServerPath]uint) + membersByVersionPath := make(map[VersionPath]uint) + + for key, value := range membersByHomeservers { + serverVersionInfo, err := federationClient.GetVersion(context.Background(), spec.ServerName(key)) + if err != nil { + serverVersionInfo = unknownServerVersionInfo + } + + maxRoomVersion := getMaxRoomVersion(serverVersionInfo) + + // Add to counters. + membersByMaxRoomVersion[maxRoomVersion] += value + membersByServerPath[ServerPath{maxRoomVersion, serverVersionInfo.Server.Name}] += value + membersByVersionPath[VersionPath{maxRoomVersion, serverVersionInfo.Server.Name, serverVersionInfo.Server.Version}] += value + + // Sort into maxRoomVersionInfo map tree. + serverMap, ok := maxRoomVersionInfo[maxRoomVersion] + if !ok { + serverMap = make(map[string](map[string](map[string]uint))) + maxRoomVersionInfo[maxRoomVersion] = serverMap + } + versionMap, ok := serverMap[serverVersionInfo.Server.Name] + if !ok { + versionMap = make(map[string](map[string]uint)) + serverMap[serverVersionInfo.Server.Name] = versionMap + } + homeserverMap, ok := versionMap[serverVersionInfo.Server.Version] + if !ok { + homeserverMap = make(map[string]uint) + versionMap[serverVersionInfo.Server.Version] = homeserverMap + } + homeserverMap[key] = value + } + + fmt.Println("Room:") + fmt.Printf("%s -> %d\n\n", room, len(joinedMembers.Joined)) + + fmt.Println("Version Support:") + + maxRoomVersionKeys := make([]string, 0, len(maxRoomVersionInfo)) + for key := range maxRoomVersionInfo { + maxRoomVersionKeys = append(maxRoomVersionKeys, key) + } + sort.Strings(maxRoomVersionKeys) + for _, maxRoomVersionKey := range maxRoomVersionKeys { + maxRoomVersionValue := maxRoomVersionInfo[maxRoomVersionKey] + + fmt.Printf("%s -> %d\n", maxRoomVersionKey, membersByMaxRoomVersion[maxRoomVersionKey]) + + serverKeys := make([]string, 0, len(maxRoomVersionValue)) + for key := range maxRoomVersionValue { + serverKeys = append(serverKeys, key) + } + sort.Strings(serverKeys) + for _, serverKey := range serverKeys { + serverValue := maxRoomVersionValue[serverKey] + + fmt.Printf(" %s -> %d\n", serverKey, membersByServerPath[ServerPath{maxRoomVersionKey, serverKey}]) + + versionKeys := make([]string, 0, len(serverValue)) + for key := range serverValue { + versionKeys = append(versionKeys, key) + } + sort.Strings(versionKeys) + for _, versionKey := range versionKeys { + versionValue := serverValue[versionKey] + + fmt.Printf(" %s -> %d\n", versionKey, membersByVersionPath[VersionPath{maxRoomVersionKey, serverKey, versionKey}]) + + if verbose { + homeserverKeys := make([]string, 0, len(versionValue)) + for key := range versionValue { + homeserverKeys = append(homeserverKeys, key) + } + sort.Strings(homeserverKeys) + for _, homeserverKey := range homeserverKeys { + homeserverValue := versionValue[homeserverKey] + + fmt.Printf(" %s -> %d\n", homeserverKey, homeserverValue) + } + } + } + } + } +}