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.eventSenderIsFromDiscord = eventSenderIsFromDiscord
|
||||
module.exports.getPublicUrlForMxc = getPublicUrlForMxc
|
||||
module.exports.getEventIDHash = getEventIDHash
|
||||
module.exports.MatrixStringBuilder = MatrixStringBuilder
|
||||
module.exports.getViaServers = getViaServers
|
||||
|
|
|
@ -3,9 +3,22 @@
|
|||
const e = new Error("Custom error")
|
||||
|
||||
const {test} = require("supertape")
|
||||
const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder} = require("./utils")
|
||||
const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder, getViaServers} = require("./utils")
|
||||
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 => {
|
||||
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>"
|
||||
})
|
||||
})
|
||||
|
||||
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."
|
||||
* @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) {
|
||||
return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`)
|
||||
|
|
Loading…
Reference in a new issue