Add getViaServers function
This commit is contained in:
parent
3fb2c983e0
commit
30afaa1e17
3 changed files with 146 additions and 2 deletions
|
@ -127,8 +127,74 @@ class MatrixStringBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room IDs are not routable on their own. Room permalinks need a list of servers to try. The client is responsible for coming up with a list of servers.
|
||||||
|
* https://spec.matrix.org/v1.9/appendices/#routing
|
||||||
|
* https://gitdab.com/cadence/out-of-your-element/issues/11
|
||||||
|
* @param {string} roomID
|
||||||
|
* @param {{[K in "getStateEvent" | "getJoinedMembers"]: import("../../matrix/api")[K]}} api
|
||||||
|
*/
|
||||||
|
async function getViaServers(roomID, api) {
|
||||||
|
const candidates = []
|
||||||
|
const {joined} = await api.getJoinedMembers(roomID)
|
||||||
|
// Candidate 0: The bot's own server name
|
||||||
|
candidates.push(reg.ooye.server_name)
|
||||||
|
// Candidate 1: Highest joined non-sim non-bot power level user in the room
|
||||||
|
// https://github.com/matrix-org/matrix-react-sdk/blob/552c65db98b59406fb49562e537a2721c8505517/src/utils/permalinks/Permalinks.ts#L172
|
||||||
|
try {
|
||||||
|
/** @type {{users?: {[mxid: string]: number}}} */
|
||||||
|
const powerLevels = await api.getStateEvent(roomID, "m.room.power_levels", "")
|
||||||
|
if (powerLevels.users) {
|
||||||
|
const sorted = Object.entries(powerLevels.users).sort((a, b) => b[1] - a[1]) // Highest...
|
||||||
|
for (const power of sorted) {
|
||||||
|
const mxid = power[0]
|
||||||
|
if (!(mxid in joined)) continue // joined...
|
||||||
|
if (userRegex.some(r => mxid.match(r))) continue // non-sim non-bot...
|
||||||
|
const match = mxid.match(/:(.*)/)
|
||||||
|
assert(match)
|
||||||
|
if (!candidates.includes(match[1])) {
|
||||||
|
candidates.push(match[1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// power levels event not found
|
||||||
|
}
|
||||||
|
// Candidates 2-3: Most popular servers in the room
|
||||||
|
/** @type {Map<string, number>} */
|
||||||
|
const servers = new Map()
|
||||||
|
// We can get the most popular servers if we know the members, so let's process those...
|
||||||
|
Object.keys(joined)
|
||||||
|
.filter(mxid => !mxid.startsWith("@_")) // Quick check
|
||||||
|
.filter(mxid => !userRegex.some(r => mxid.match(r))) // Full check
|
||||||
|
.slice(0, 1000) // Just sample the first thousand real members
|
||||||
|
.map(mxid => {
|
||||||
|
const match = mxid.match(/:(.*)/)
|
||||||
|
assert(match)
|
||||||
|
return match[1]
|
||||||
|
})
|
||||||
|
.filter(server => !server.match(/([a-f0-9:]+:+)+[a-f0-9]+/)) // No IPv6 servers
|
||||||
|
.filter(server => !server.match(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/)) // No IPv4 servers
|
||||||
|
// I don't care enough to check ACLs
|
||||||
|
.forEach(server => {
|
||||||
|
const existing = servers.get(server)
|
||||||
|
if (!existing) servers.set(server, 1)
|
||||||
|
else servers.set(server, existing + 1)
|
||||||
|
})
|
||||||
|
const serverList = [...servers.entries()].sort((a, b) => b[1] - a[1])
|
||||||
|
for (const server of serverList) {
|
||||||
|
if (!candidates.includes(server[0])) {
|
||||||
|
candidates.push(server[0])
|
||||||
|
if (candidates.length >= 4) break // Can have at most 4 candidate via servers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS
|
module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS
|
||||||
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
|
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
|
||||||
module.exports.getPublicUrlForMxc = getPublicUrlForMxc
|
module.exports.getPublicUrlForMxc = getPublicUrlForMxc
|
||||||
module.exports.getEventIDHash = getEventIDHash
|
module.exports.getEventIDHash = getEventIDHash
|
||||||
module.exports.MatrixStringBuilder = MatrixStringBuilder
|
module.exports.MatrixStringBuilder = MatrixStringBuilder
|
||||||
|
module.exports.getViaServers = getViaServers
|
||||||
|
|
|
@ -3,9 +3,22 @@
|
||||||
const e = new Error("Custom error")
|
const e = new Error("Custom error")
|
||||||
|
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder} = require("./utils")
|
const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder, getViaServers} = require("./utils")
|
||||||
const util = require("util")
|
const util = require("util")
|
||||||
|
|
||||||
|
/** @param {string[]} mxids */
|
||||||
|
function joinedList(mxids) {
|
||||||
|
/** @type {{[mxid: string]: {display_name: null, avatar_url: null}}} */
|
||||||
|
const joined = {}
|
||||||
|
for (const mxid of mxids) {
|
||||||
|
joined[mxid] = {
|
||||||
|
display_name: null,
|
||||||
|
avatar_url: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {joined}
|
||||||
|
}
|
||||||
|
|
||||||
test("sender type: matrix user", t => {
|
test("sender type: matrix user", t => {
|
||||||
t.notOk(eventSenderIsFromDiscord("@cadence:cadence.moe"))
|
t.notOk(eventSenderIsFromDiscord("@cadence:cadence.moe"))
|
||||||
})
|
})
|
||||||
|
@ -74,3 +87,68 @@ test("MatrixStringBuilder: complete code coverage", t => {
|
||||||
formatted_body: "Line 1<p>Line 2</p>Line 3<p>Line 4</p>"
|
formatted_body: "Line 1<p>Line 2</p>Line 3<p>Line 4</p>"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("getViaServers: returns the server name if the room only has sim users", async t => {
|
||||||
|
const result = await getViaServers("!baby", {
|
||||||
|
getStateEvent: async () => ({}),
|
||||||
|
getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe"])
|
||||||
|
})
|
||||||
|
t.deepEqual(result, ["cadence.moe"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("getViaServers: also returns the most popular servers in order", async t => {
|
||||||
|
const result = await getViaServers("!baby", {
|
||||||
|
getStateEvent: async () => ({}),
|
||||||
|
getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid"])
|
||||||
|
})
|
||||||
|
t.deepEqual(result, ["cadence.moe", "thecollective.invalid", "selfhosted.invalid"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("getViaServers: does not return IP address servers", async t => {
|
||||||
|
const result = await getViaServers("!baby", {
|
||||||
|
getStateEvent: async () => ({}),
|
||||||
|
getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:45.77.232.172:8443", "@cadence:[::1]:8443", "@cadence:123example.456example.invalid"])
|
||||||
|
})
|
||||||
|
t.deepEqual(result, ["cadence.moe", "123example.456example.invalid"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("getViaServers: also returns the highest power level user (100)", async t => {
|
||||||
|
const result = await getViaServers("!baby", {
|
||||||
|
getStateEvent: async () => ({
|
||||||
|
users: {
|
||||||
|
"@moderator:tractor.invalid": 50,
|
||||||
|
"@singleuser:selfhosted.invalid": 100,
|
||||||
|
"@_ooye_bot:cadence.moe": 100
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@moderator:tractor.invalid"])
|
||||||
|
})
|
||||||
|
t.deepEqual(result, ["cadence.moe", "selfhosted.invalid", "thecollective.invalid", "tractor.invalid"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("getViaServers: also returns the highest power level user (50)", async t => {
|
||||||
|
const result = await getViaServers("!baby", {
|
||||||
|
getStateEvent: async () => ({
|
||||||
|
users: {
|
||||||
|
"@moderator:tractor.invalid": 50,
|
||||||
|
"@_ooye_bot:cadence.moe": 100
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@hazel:thecollective.invalid", "@june:thecollective.invalid", "@singleuser:selfhosted.invalid"])
|
||||||
|
})
|
||||||
|
t.deepEqual(result, ["cadence.moe", "tractor.invalid", "thecollective.invalid", "selfhosted.invalid"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("getViaServers: returns at most 4 results", async t => {
|
||||||
|
const result = await getViaServers("!baby", {
|
||||||
|
getStateEvent: async () => ({
|
||||||
|
users: {
|
||||||
|
"@moderator:tractor.invalid": 50,
|
||||||
|
"@singleuser:selfhosted.invalid": 100,
|
||||||
|
"@_ooye_bot:cadence.moe": 100
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getJoinedMembers: async () => joinedList(["@_ooye_bot:cadence.moe", "@_ooye_hazel:cadence.moe", "@cadence:cadence.moe", "@moderator:tractor.invalid", "@singleuser:selfhosted.invalid", "@hazel:thecollective.invalid", "@cadence:123example.456example.invalid"])
|
||||||
|
})
|
||||||
|
t.deepEqual(result.length, 4)
|
||||||
|
})
|
||||||
|
|
|
@ -115,7 +115,7 @@ function getStateEvent(roomID, type, key) {
|
||||||
/**
|
/**
|
||||||
* "Any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server."
|
* "Any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server."
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
* @returns {Promise<{joined: {[mxid: string]: {avatar_url?: string, display_name?: string}}}>}
|
* @returns {Promise<{joined: {[mxid: string]: {avatar_url: string?, display_name: string?}}}>}
|
||||||
*/
|
*/
|
||||||
function getJoinedMembers(roomID) {
|
function getJoinedMembers(roomID) {
|
||||||
return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`)
|
return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`)
|
||||||
|
|
Loading…
Reference in a new issue