Refactor kstate resource uploading
This commit is contained in:
parent
07a133eba9
commit
24a3b9b0f4
7 changed files with 196 additions and 18 deletions
|
@ -51,8 +51,8 @@ async function roomToKState(roomID) {
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
* @param {any} kstate
|
* @param {any} kstate
|
||||||
*/
|
*/
|
||||||
function applyKStateDiffToRoom(roomID, kstate) {
|
async function applyKStateDiffToRoom(roomID, kstate) {
|
||||||
const events = ks.kstateToState(kstate)
|
const events = await ks.kstateToState(kstate)
|
||||||
return Promise.all(events.map(({type, state_key, content}) =>
|
return Promise.all(events.map(({type, state_key, content}) =>
|
||||||
api.sendState(roomID, type, state_key, content)
|
api.sendState(roomID, type, state_key, content)
|
||||||
))
|
))
|
||||||
|
@ -220,7 +220,7 @@ async function createRoom(channel, guild, spaceID, kstate, privacyLevel) {
|
||||||
preset: PRIVACY_ENUMS.PRESET[privacyLevel], // This is closest to what we want, but properties from kstate override it anyway
|
preset: PRIVACY_ENUMS.PRESET[privacyLevel], // This is closest to what we want, but properties from kstate override it anyway
|
||||||
visibility: PRIVACY_ENUMS.VISIBILITY[privacyLevel],
|
visibility: PRIVACY_ENUMS.VISIBILITY[privacyLevel],
|
||||||
invite: [],
|
invite: [],
|
||||||
initial_state: ks.kstateToState(kstate),
|
initial_state: await ks.kstateToState(kstate),
|
||||||
...spaceCreationContent
|
...spaceCreationContent
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ async function createSpace(guild, kstate) {
|
||||||
creation_content: {
|
creation_content: {
|
||||||
type: "m.space"
|
type: "m.space"
|
||||||
},
|
},
|
||||||
initial_state: ks.kstateToState(kstate)
|
initial_state: await ks.kstateToState(kstate)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID)
|
db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID)
|
||||||
|
@ -57,15 +57,14 @@ async function createSpace(guild, kstate) {
|
||||||
* @param {number} privacyLevel
|
* @param {number} privacyLevel
|
||||||
*/
|
*/
|
||||||
async function guildToKState(guild, privacyLevel) {
|
async function guildToKState(guild, privacyLevel) {
|
||||||
const avatarEventContent = {}
|
assert.equal(typeof privacyLevel, "number")
|
||||||
if (guild.icon) {
|
|
||||||
avatarEventContent.discord_path = file.guildIcon(guild)
|
|
||||||
avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API
|
|
||||||
}
|
|
||||||
|
|
||||||
const guildKState = {
|
const guildKState = {
|
||||||
"m.room.name/": {name: guild.name},
|
"m.room.name/": {name: guild.name},
|
||||||
"m.room.avatar/": avatarEventContent,
|
"m.room.avatar/": {
|
||||||
|
$if: guild.icon,
|
||||||
|
discord_path: file.guildIcon(guild),
|
||||||
|
url: {$url: file.guildIcon(guild)}
|
||||||
|
},
|
||||||
"m.room.guest_access/": {guest_access: createRoom.PRIVACY_ENUMS.GUEST_ACCESS[privacyLevel]},
|
"m.room.guest_access/": {guest_access: createRoom.PRIVACY_ENUMS.GUEST_ACCESS[privacyLevel]},
|
||||||
"m.room.history_visibility/": {history_visibility: createRoom.PRIVACY_ENUMS.SPACE_HISTORY_VISIBILITY[privacyLevel]},
|
"m.room.history_visibility/": {history_visibility: createRoom.PRIVACY_ENUMS.SPACE_HISTORY_VISIBILITY[privacyLevel]},
|
||||||
"m.room.join_rules/": {join_rule: createRoom.PRIVACY_ENUMS.SPACE_JOIN_RULES[privacyLevel]},
|
"m.room.join_rules/": {join_rule: createRoom.PRIVACY_ENUMS.SPACE_JOIN_RULES[privacyLevel]},
|
||||||
|
@ -123,7 +122,8 @@ async function _syncSpace(guild, shouldActuallySync) {
|
||||||
// don't try to update rooms with custom avatars though
|
// don't try to update rooms with custom avatars though
|
||||||
const roomsWithCustomAvatars = select("channel_room", "room_id", {}, "WHERE custom_avatar IS NOT NULL").pluck().all()
|
const roomsWithCustomAvatars = select("channel_room", "room_id", {}, "WHERE custom_avatar IS NOT NULL").pluck().all()
|
||||||
|
|
||||||
const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => {
|
const state = await ks.kstateToState(spaceKState)
|
||||||
|
const childRooms = state.filter(({type, state_key, content}) => {
|
||||||
return type === "m.space.child" && "via" in content && !roomsWithCustomAvatars.includes(state_key)
|
return type === "m.space.child" && "via" in content && !roomsWithCustomAvatars.includes(state_key)
|
||||||
}).map(({state_key}) => state_key)
|
}).map(({state_key}) => state_key)
|
||||||
|
|
||||||
|
|
39
d2m/actions/create-space.test.js
Normal file
39
d2m/actions/create-space.test.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const mixin = require("mixin-deep")
|
||||||
|
const {guildToKState, ensureSpace} = require("./create-space")
|
||||||
|
const {kstateStripConditionals, kstateUploadMxc} = require("../../matrix/kstate")
|
||||||
|
const {test} = require("supertape")
|
||||||
|
const testData = require("../../test/data")
|
||||||
|
|
||||||
|
const passthrough = require("../../passthrough")
|
||||||
|
const {db} = passthrough
|
||||||
|
|
||||||
|
test("guild2space: can generate kstate for a guild, passing privacy level 0", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await kstateUploadMxc(kstateStripConditionals(await guildToKState(testData.guild.general, 0))),
|
||||||
|
{
|
||||||
|
"m.room.avatar/": {
|
||||||
|
discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024",
|
||||||
|
url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF"
|
||||||
|
},
|
||||||
|
"m.room.guest_access/": {
|
||||||
|
guest_access: "can_join"
|
||||||
|
},
|
||||||
|
"m.room.history_visibility/": {
|
||||||
|
history_visibility: "invited"
|
||||||
|
},
|
||||||
|
"m.room.join_rules/": {
|
||||||
|
join_rule: "invite"
|
||||||
|
},
|
||||||
|
"m.room.name/": {
|
||||||
|
name: "Psychonauts 3"
|
||||||
|
},
|
||||||
|
"m.room.power_levels/": {
|
||||||
|
users: {
|
||||||
|
"@test_auto_invite:example.org": 100
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
|
@ -584,7 +584,7 @@ test("event2message: code blocks work", async t => {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: "wrong body",
|
body: "wrong body",
|
||||||
format: "org.matrix.custom.html",
|
format: "org.matrix.custom.html",
|
||||||
formatted_body: "<p>preceding</p>\n<pre><code>code block\n</code></pre>\n<p>following <code>code</code> is inline</p>\n"
|
formatted_body: "<p>preceding</p>\n<pre><code>code block\n</code></pre>\n<p>following <code>code</code> is inline</p>"
|
||||||
},
|
},
|
||||||
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
origin_server_ts: 1688301929913,
|
origin_server_ts: 1688301929913,
|
||||||
|
@ -641,6 +641,66 @@ test("event2message: code block contents are formatted correctly and not escaped
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("event2message: code blocks use double backtick as delimiter when necessary", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "wrong body",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<code>backtick in ` the middle</code>, <code>backtick at the edge`</code>"
|
||||||
|
},
|
||||||
|
event_id: "$pGkWQuGVmrPNByrFELxhzI6MCBgJecr5I2J3z88Gc2s",
|
||||||
|
room_id: "!BpMdOUkWWhFxmTrENV:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "cadence [they]",
|
||||||
|
content: "``backtick in ` the middle``, `` backtick at the edge` ``",
|
||||||
|
avatar_url: undefined,
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event2message: inline code is converted to code block if it contains both delimiters", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "wrong body",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<code>` one two ``</code>"
|
||||||
|
},
|
||||||
|
event_id: "$pGkWQuGVmrPNByrFELxhzI6MCBgJecr5I2J3z88Gc2s",
|
||||||
|
room_id: "!BpMdOUkWWhFxmTrENV:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "cadence [they]",
|
||||||
|
content: "``` ` one two `` ```",
|
||||||
|
avatar_url: undefined,
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("event2message: code blocks are uploaded as attachments instead if they contain incompatible backticks", async t => {
|
test("event2message: code blocks are uploaded as attachments instead if they contain incompatible backticks", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
await eventToMessage({
|
await eventToMessage({
|
||||||
|
|
|
@ -4,7 +4,12 @@ const assert = require("assert").strict
|
||||||
const mixin = require("mixin-deep")
|
const mixin = require("mixin-deep")
|
||||||
const {isDeepStrictEqual} = require("util")
|
const {isDeepStrictEqual} = require("util")
|
||||||
|
|
||||||
/** Mutates the input. */
|
const passthrough = require("../passthrough")
|
||||||
|
const {sync} = passthrough
|
||||||
|
/** @type {import("./file")} */
|
||||||
|
const file = sync.require("./file")
|
||||||
|
|
||||||
|
/** Mutates the input. Not recursive - can only include or exclude entire state events. */
|
||||||
function kstateStripConditionals(kstate) {
|
function kstateStripConditionals(kstate) {
|
||||||
for (const [k, content] of Object.entries(kstate)) {
|
for (const [k, content] of Object.entries(kstate)) {
|
||||||
// conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.)
|
// conditional for whether a key is even part of the kstate (doing this declaratively on json is hard, so represent it as a property instead.)
|
||||||
|
@ -16,9 +21,33 @@ function kstateStripConditionals(kstate) {
|
||||||
return kstate
|
return kstate
|
||||||
}
|
}
|
||||||
|
|
||||||
function kstateToState(kstate) {
|
/** Mutates the input. Works recursively through object tree. */
|
||||||
|
async function kstateUploadMxc(obj) {
|
||||||
|
const promises = []
|
||||||
|
function inner(obj) {
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (v == null || typeof v !== "object") continue
|
||||||
|
|
||||||
|
if (v.$url) {
|
||||||
|
promises.push(
|
||||||
|
file.uploadDiscordFileToMxc(v.$url)
|
||||||
|
.then(mxc => obj[k] = mxc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner(obj)
|
||||||
|
await Promise.all(promises)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Automatically strips conditionals and uploads URLs to mxc. */
|
||||||
|
async function kstateToState(kstate) {
|
||||||
const events = []
|
const events = []
|
||||||
kstateStripConditionals(kstate)
|
kstateStripConditionals(kstate)
|
||||||
|
await kstateUploadMxc(kstate)
|
||||||
for (const [k, content] of Object.entries(kstate)) {
|
for (const [k, content] of Object.entries(kstate)) {
|
||||||
const slashIndex = k.indexOf("/")
|
const slashIndex = k.indexOf("/")
|
||||||
assert(slashIndex > 0)
|
assert(slashIndex > 0)
|
||||||
|
@ -74,6 +103,7 @@ function diffKState(actual, target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.kstateStripConditionals = kstateStripConditionals
|
module.exports.kstateStripConditionals = kstateStripConditionals
|
||||||
|
module.exports.kstateUploadMxc = kstateUploadMxc
|
||||||
module.exports.kstateToState = kstateToState
|
module.exports.kstateToState = kstateToState
|
||||||
module.exports.stateToKState = stateToKState
|
module.exports.stateToKState = stateToKState
|
||||||
module.exports.diffKState = diffKState
|
module.exports.diffKState = diffKState
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const assert = require("assert")
|
const assert = require("assert")
|
||||||
const {kstateToState, stateToKState, diffKState, kstateStripConditionals} = require("./kstate")
|
const {kstateToState, stateToKState, diffKState, kstateStripConditionals, kstateUploadMxc} = require("./kstate")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
|
|
||||||
test("kstate strip: strips false conditions", t => {
|
test("kstate strip: strips false conditions", t => {
|
||||||
|
@ -21,8 +21,53 @@ test("kstate strip: keeps true conditions while removing $if", t => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("kstate2state: general", t => {
|
test("kstateUploadMxc: sets the mxc", async t => {
|
||||||
t.deepEqual(kstateToState({
|
const input = {
|
||||||
|
"m.room.avatar/": {
|
||||||
|
url: {$url: "https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024"},
|
||||||
|
test1: {
|
||||||
|
test2: {
|
||||||
|
test3: {$url: "https://cdn.discordapp.com/attachments/176333891320283136/1157854643037163610/Screenshot_20231001_034036.jpg"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await kstateUploadMxc(input)
|
||||||
|
t.deepEqual(input, {
|
||||||
|
"m.room.avatar/": {
|
||||||
|
url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl",
|
||||||
|
test1: {
|
||||||
|
test2: {
|
||||||
|
test3: "mxc://cadence.moe/zAXdQriaJuLZohDDmacwWWDR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("kstateUploadMxc and strip: work together", async t => {
|
||||||
|
const input = {
|
||||||
|
"m.room.avatar/yes": {
|
||||||
|
$if: true,
|
||||||
|
url: {$url: "https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024"}
|
||||||
|
},
|
||||||
|
"m.room.avatar/no": {
|
||||||
|
$if: false,
|
||||||
|
url: {$url: "https://cdn.discordapp.com/avatars/320067006521147393/5fc4ad85c1ea876709e9a7d3374a78a1.png?size=1024"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
kstateStripConditionals(input)
|
||||||
|
await kstateUploadMxc(input)
|
||||||
|
t.deepEqual(input, {
|
||||||
|
"m.room.avatar/yes": {
|
||||||
|
url: "mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("kstate2state: general", async t => {
|
||||||
|
t.deepEqual(await kstateToState({
|
||||||
"m.room.name/": {name: "test name"},
|
"m.room.name/": {name: "test name"},
|
||||||
"m.room.member/@cadence:cadence.moe": {membership: "join"},
|
"m.room.member/@cadence:cadence.moe": {membership: "join"},
|
||||||
"uk.half-shot.bridge/org.matrix.appservice-irc://irc/epicord.net/#general": {creator: "@cadence:cadence.moe"}
|
"uk.half-shot.bridge/org.matrix.appservice-irc://irc/epicord.net/#general": {creator: "@cadence:cadence.moe"}
|
||||||
|
|
|
@ -21,6 +21,9 @@ const reg = require("../matrix/read-registration")
|
||||||
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
|
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
|
||||||
reg.ooye.server_name = "cadence.moe"
|
reg.ooye.server_name = "cadence.moe"
|
||||||
reg.ooye.invite = ["@test_auto_invite:example.org"]
|
reg.ooye.invite = ["@test_auto_invite:example.org"]
|
||||||
|
reg.id = "baby" // don't actually take authenticated actions on the server
|
||||||
|
reg.as_token = "baby"
|
||||||
|
reg.hs_token = "baby"
|
||||||
|
|
||||||
const sync = new HeatSync({watchFS: false})
|
const sync = new HeatSync({watchFS: false})
|
||||||
|
|
||||||
|
@ -117,6 +120,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
||||||
require("../matrix/read-registration.test")
|
require("../matrix/read-registration.test")
|
||||||
require("../matrix/txnid.test")
|
require("../matrix/txnid.test")
|
||||||
require("../d2m/actions/create-room.test")
|
require("../d2m/actions/create-room.test")
|
||||||
|
require("../d2m/actions/create-space.test")
|
||||||
require("../d2m/actions/register-user.test")
|
require("../d2m/actions/register-user.test")
|
||||||
require("../d2m/converters/edit-to-changes.test")
|
require("../d2m/converters/edit-to-changes.test")
|
||||||
require("../d2m/converters/emoji-to-key.test")
|
require("../d2m/converters/emoji-to-key.test")
|
||||||
|
|
Loading…
Reference in a new issue