forked from cadence/out-of-your-element
		
	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 {any} kstate
 | 
			
		||||
 */
 | 
			
		||||
function applyKStateDiffToRoom(roomID, kstate) {
 | 
			
		||||
	const events = ks.kstateToState(kstate)
 | 
			
		||||
async function applyKStateDiffToRoom(roomID, kstate) {
 | 
			
		||||
	const events = await ks.kstateToState(kstate)
 | 
			
		||||
	return Promise.all(events.map(({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
 | 
			
		||||
			visibility: PRIVACY_ENUMS.VISIBILITY[privacyLevel],
 | 
			
		||||
			invite: [],
 | 
			
		||||
			initial_state: ks.kstateToState(kstate),
 | 
			
		||||
			initial_state: await ks.kstateToState(kstate),
 | 
			
		||||
			...spaceCreationContent
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ async function createSpace(guild, kstate) {
 | 
			
		|||
			creation_content: {
 | 
			
		||||
				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)
 | 
			
		||||
| 
						 | 
				
			
			@ -57,15 +57,14 @@ async function createSpace(guild, kstate) {
 | 
			
		|||
 * @param {number} privacyLevel
 | 
			
		||||
 */
 | 
			
		||||
async function guildToKState(guild, privacyLevel) {
 | 
			
		||||
	const avatarEventContent = {}
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.equal(typeof privacyLevel, "number")
 | 
			
		||||
	const guildKState = {
 | 
			
		||||
		"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.history_visibility/": {history_visibility: createRoom.PRIVACY_ENUMS.SPACE_HISTORY_VISIBILITY[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
 | 
			
		||||
		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)
 | 
			
		||||
		}).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",
 | 
			
		||||
				body: "wrong body",
 | 
			
		||||
				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",
 | 
			
		||||
			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 => {
 | 
			
		||||
	t.deepEqual(
 | 
			
		||||
		await eventToMessage({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,12 @@ const assert = require("assert").strict
 | 
			
		|||
const mixin = require("mixin-deep")
 | 
			
		||||
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) {
 | 
			
		||||
	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.)
 | 
			
		||||
| 
						 | 
				
			
			@ -16,9 +21,33 @@ function kstateStripConditionals(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 = []
 | 
			
		||||
	kstateStripConditionals(kstate)
 | 
			
		||||
	await kstateUploadMxc(kstate)
 | 
			
		||||
	for (const [k, content] of Object.entries(kstate)) {
 | 
			
		||||
		const slashIndex = k.indexOf("/")
 | 
			
		||||
		assert(slashIndex > 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +103,7 @@ function diffKState(actual, target) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
module.exports.kstateStripConditionals = kstateStripConditionals
 | 
			
		||||
module.exports.kstateUploadMxc = kstateUploadMxc
 | 
			
		||||
module.exports.kstateToState = kstateToState
 | 
			
		||||
module.exports.stateToKState = stateToKState
 | 
			
		||||
module.exports.diffKState = diffKState
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
const assert = require("assert")
 | 
			
		||||
const {kstateToState, stateToKState, diffKState, kstateStripConditionals} = require("./kstate")
 | 
			
		||||
const {kstateToState, stateToKState, diffKState, kstateStripConditionals, kstateUploadMxc} = require("./kstate")
 | 
			
		||||
const {test} = require("supertape")
 | 
			
		||||
 | 
			
		||||
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 => {
 | 
			
		||||
	t.deepEqual(kstateToState({
 | 
			
		||||
test("kstateUploadMxc: sets the mxc", async t => {
 | 
			
		||||
	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.member/@cadence:cadence.moe": {membership: "join"},
 | 
			
		||||
		"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_name = "cadence.moe"
 | 
			
		||||
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})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,6 +120,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
 | 
			
		|||
	require("../matrix/read-registration.test")
 | 
			
		||||
	require("../matrix/txnid.test")
 | 
			
		||||
	require("../d2m/actions/create-room.test")
 | 
			
		||||
	require("../d2m/actions/create-space.test")
 | 
			
		||||
	require("../d2m/actions/register-user.test")
 | 
			
		||||
	require("../d2m/converters/edit-to-changes.test")
 | 
			
		||||
	require("../d2m/converters/emoji-to-key.test")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue