refactor kstate, add stub user syncing function
This commit is contained in:
parent
4d8b74f61f
commit
f418d51e55
6 changed files with 181 additions and 143 deletions
|
@ -9,50 +9,15 @@ const { discord, sync, db } = passthrough
|
||||||
const file = sync.require("../../matrix/file")
|
const file = sync.require("../../matrix/file")
|
||||||
/** @type {import("../../matrix/api")} */
|
/** @type {import("../../matrix/api")} */
|
||||||
const api = sync.require("../../matrix/api")
|
const api = sync.require("../../matrix/api")
|
||||||
|
/** @type {import("../../matrix/kstate")} */
|
||||||
function kstateStripConditionals(kstate) {
|
const ks = sync.require("../../matrix/kstate")
|
||||||
for (const [k, content] of Object.entries(kstate)) {
|
|
||||||
if ("$if" in content) {
|
|
||||||
if (content.$if) delete content.$if
|
|
||||||
else delete kstate[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return kstate
|
|
||||||
}
|
|
||||||
|
|
||||||
function kstateToState(kstate) {
|
|
||||||
const events = []
|
|
||||||
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.)
|
|
||||||
if ("$if" in content && !content.$if) continue
|
|
||||||
delete content.$if
|
|
||||||
|
|
||||||
const [type, state_key] = k.split("/")
|
|
||||||
assert.ok(typeof type === "string")
|
|
||||||
assert.ok(typeof state_key === "string")
|
|
||||||
events.push({type, state_key, content})
|
|
||||||
}
|
|
||||||
return events
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("../../types").Event.BaseStateEvent[]} events
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
function stateToKState(events) {
|
|
||||||
const kstate = {}
|
|
||||||
for (const event of events) {
|
|
||||||
kstate[event.type + "/" + event.state_key] = event.content
|
|
||||||
}
|
|
||||||
return kstate
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
*/
|
*/
|
||||||
async function roomToKState(roomID) {
|
async function roomToKState(roomID) {
|
||||||
const root = await api.getAllState(roomID)
|
const root = await api.getAllState(roomID)
|
||||||
return stateToKState(root)
|
return ks.stateToKState(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,33 +25,12 @@ async function roomToKState(roomID) {
|
||||||
* @params {any} kstate
|
* @params {any} kstate
|
||||||
*/
|
*/
|
||||||
function applyKStateDiffToRoom(roomID, kstate) {
|
function applyKStateDiffToRoom(roomID, kstate) {
|
||||||
const events = kstateToState(kstate)
|
const events = 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)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffKState(actual, target) {
|
|
||||||
const diff = {}
|
|
||||||
// go through each key that it should have
|
|
||||||
for (const key of Object.keys(target)) {
|
|
||||||
if (key in actual) {
|
|
||||||
// diff
|
|
||||||
try {
|
|
||||||
assert.deepEqual(actual[key], target[key])
|
|
||||||
} catch (e) {
|
|
||||||
// they differ. reassign the target
|
|
||||||
diff[key] = target[key]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// not present, needs to be added
|
|
||||||
diff[key] = target[key]
|
|
||||||
}
|
|
||||||
// keys that are missing in "actual" will not be deleted on "target" (no action)
|
|
||||||
}
|
|
||||||
return diff
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("discord-api-types/v10").APIGuildTextChannel} channel
|
* @param {import("discord-api-types/v10").APIGuildTextChannel} channel
|
||||||
* @param {import("discord-api-types/v10").APIGuild} guild
|
* @param {import("discord-api-types/v10").APIGuild} guild
|
||||||
|
@ -98,7 +42,7 @@ async function channelToKState(channel, guild) {
|
||||||
const avatarEventContent = {}
|
const avatarEventContent = {}
|
||||||
if (guild.icon) {
|
if (guild.icon) {
|
||||||
avatarEventContent.discord_path = file.guildIcon(guild)
|
avatarEventContent.discord_path = file.guildIcon(guild)
|
||||||
avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path)
|
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 channelKState = {
|
const channelKState = {
|
||||||
|
@ -138,7 +82,7 @@ async function createRoom(channel, guild, spaceID, kstate) {
|
||||||
preset: "private_chat",
|
preset: "private_chat",
|
||||||
visibility: "private",
|
visibility: "private",
|
||||||
invite: ["@cadence:cadence.moe"], // TODO
|
invite: ["@cadence:cadence.moe"], // TODO
|
||||||
initial_state: kstateToState(kstate)
|
initial_state: ks.kstateToState(kstate)
|
||||||
})
|
})
|
||||||
|
|
||||||
db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID)
|
db.prepare("INSERT INTO channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID)
|
||||||
|
@ -204,12 +148,12 @@ async function _syncRoom(channelID, shouldActuallySync) {
|
||||||
|
|
||||||
// sync channel state to room
|
// sync channel state to room
|
||||||
const roomKState = await roomToKState(existing)
|
const roomKState = await roomToKState(existing)
|
||||||
const roomDiff = diffKState(roomKState, channelKState)
|
const roomDiff = ks.diffKState(roomKState, channelKState)
|
||||||
const roomApply = applyKStateDiffToRoom(existing, roomDiff)
|
const roomApply = applyKStateDiffToRoom(existing, roomDiff)
|
||||||
|
|
||||||
// sync room as space member
|
// sync room as space member
|
||||||
const spaceKState = await roomToKState(spaceID)
|
const spaceKState = await roomToKState(spaceID)
|
||||||
const spaceDiff = diffKState(spaceKState, {
|
const spaceDiff = ks.diffKState(spaceKState, {
|
||||||
[`m.space.child/${existing}`]: {
|
[`m.space.child/${existing}`]: {
|
||||||
via: ["cadence.moe"] // TODO: use the proper server
|
via: ["cadence.moe"] // TODO: use the proper server
|
||||||
}
|
}
|
||||||
|
@ -241,8 +185,4 @@ module.exports.createRoom = createRoom
|
||||||
module.exports.ensureRoom = ensureRoom
|
module.exports.ensureRoom = ensureRoom
|
||||||
module.exports.syncRoom = syncRoom
|
module.exports.syncRoom = syncRoom
|
||||||
module.exports.createAllForGuild = createAllForGuild
|
module.exports.createAllForGuild = createAllForGuild
|
||||||
module.exports.kstateToState = kstateToState
|
|
||||||
module.exports.stateToKState = stateToKState
|
|
||||||
module.exports.diffKState = diffKState
|
|
||||||
module.exports.channelToKState = channelToKState
|
module.exports.channelToKState = channelToKState
|
||||||
module.exports.kstateStripConditionals = kstateStripConditionals
|
|
||||||
|
|
|
@ -1,80 +1,8 @@
|
||||||
const {kstateToState, stateToKState, diffKState, channelToKState, kstateStripConditionals} = require("./create-room")
|
const {channelToKState} = require("./create-room")
|
||||||
|
const {kstateStripConditionals} = require("../../matrix/kstate")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const testData = require("../../test/data")
|
const testData = require("../../test/data")
|
||||||
|
|
||||||
test("kstate2state: general", t => {
|
|
||||||
t.deepEqual(kstateToState({
|
|
||||||
"m.room.name/": {name: "test name"},
|
|
||||||
"m.room.member/@cadence:cadence.moe": {membership: "join"}
|
|
||||||
}), [
|
|
||||||
{
|
|
||||||
type: "m.room.name",
|
|
||||||
state_key: "",
|
|
||||||
content: {
|
|
||||||
name: "test name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "m.room.member",
|
|
||||||
state_key: "@cadence:cadence.moe",
|
|
||||||
content: {
|
|
||||||
membership: "join"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("state2kstate: general", t => {
|
|
||||||
t.deepEqual(stateToKState([
|
|
||||||
{
|
|
||||||
type: "m.room.name",
|
|
||||||
state_key: "",
|
|
||||||
content: {
|
|
||||||
name: "test name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "m.room.member",
|
|
||||||
state_key: "@cadence:cadence.moe",
|
|
||||||
content: {
|
|
||||||
membership: "join"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]), {
|
|
||||||
"m.room.name/": {name: "test name"},
|
|
||||||
"m.room.member/@cadence:cadence.moe": {membership: "join"}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("diffKState: detects edits", t => {
|
|
||||||
t.deepEqual(
|
|
||||||
diffKState({
|
|
||||||
"m.room.name/": {name: "test name"},
|
|
||||||
"same/": {a: 2}
|
|
||||||
}, {
|
|
||||||
"m.room.name/": {name: "edited name"},
|
|
||||||
"same/": {a: 2}
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
"m.room.name/": {name: "edited name"}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("diffKState: detects new properties", t => {
|
|
||||||
t.deepEqual(
|
|
||||||
diffKState({
|
|
||||||
"m.room.name/": {name: "test name"},
|
|
||||||
}, {
|
|
||||||
"m.room.name/": {name: "test name"},
|
|
||||||
"new/": {a: 2}
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
"new/": {a: 2}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("channel2room: general", async t => {
|
test("channel2room: general", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)),
|
kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)),
|
||||||
|
|
|
@ -78,5 +78,15 @@ async function ensureSimJoined(user, roomID) {
|
||||||
return mxid
|
return mxid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("discord-api-types/v10").APIUser} user
|
||||||
|
* @param {Required<Omit<import("discord-api-types/v10").APIGuildMember, "user">>} member
|
||||||
|
*/
|
||||||
|
async function memberToStateContent(user, member) {
|
||||||
|
return {
|
||||||
|
displayname: member.nick || user.username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.ensureSim = ensureSim
|
module.exports.ensureSim = ensureSim
|
||||||
module.exports.ensureSimJoined = ensureSimJoined
|
module.exports.ensureSimJoined = ensureSimJoined
|
||||||
|
|
65
matrix/kstate.js
Normal file
65
matrix/kstate.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const assert = require("assert")
|
||||||
|
|
||||||
|
/** Mutates the input. */
|
||||||
|
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.)
|
||||||
|
if ("$if" in content) {
|
||||||
|
if (content.$if) delete content.$if
|
||||||
|
else delete kstate[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kstate
|
||||||
|
}
|
||||||
|
|
||||||
|
function kstateToState(kstate) {
|
||||||
|
const events = []
|
||||||
|
kstateStripConditionals(kstate)
|
||||||
|
for (const [k, content] of Object.entries(kstate)) {
|
||||||
|
const [type, state_key] = k.split("/")
|
||||||
|
assert.ok(typeof type === "string")
|
||||||
|
assert.ok(typeof state_key === "string")
|
||||||
|
events.push({type, state_key, content})
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../types").Event.BaseStateEvent[]} events
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
function stateToKState(events) {
|
||||||
|
const kstate = {}
|
||||||
|
for (const event of events) {
|
||||||
|
kstate[event.type + "/" + event.state_key] = event.content
|
||||||
|
}
|
||||||
|
return kstate
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffKState(actual, target) {
|
||||||
|
const diff = {}
|
||||||
|
// go through each key that it should have
|
||||||
|
for (const key of Object.keys(target)) {
|
||||||
|
if (key in actual) {
|
||||||
|
// diff
|
||||||
|
try {
|
||||||
|
assert.deepEqual(actual[key], target[key])
|
||||||
|
} catch (e) {
|
||||||
|
// they differ. reassign the target
|
||||||
|
diff[key] = target[key]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// not present, needs to be added
|
||||||
|
diff[key] = target[key]
|
||||||
|
}
|
||||||
|
// keys that are missing in "actual" will not be deleted on "target" (no action)
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.kstateStripConditionals = kstateStripConditionals
|
||||||
|
module.exports.kstateToState = kstateToState
|
||||||
|
module.exports.stateToKState = stateToKState
|
||||||
|
module.exports.diffKState = diffKState
|
94
matrix/kstate.test.js
Normal file
94
matrix/kstate.test.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
const {kstateToState, stateToKState, diffKState, kstateStripConditionals} = require("./kstate")
|
||||||
|
const {test} = require("supertape")
|
||||||
|
|
||||||
|
test("kstate strip: strips false conditions", t => {
|
||||||
|
t.deepEqual(kstateStripConditionals({
|
||||||
|
a: {$if: false, value: 2},
|
||||||
|
b: {value: 4}
|
||||||
|
}), {
|
||||||
|
b: {value: 4}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("kstate strip: keeps true conditions while removing $if", t => {
|
||||||
|
t.deepEqual(kstateStripConditionals({
|
||||||
|
a: {$if: true, value: 2},
|
||||||
|
b: {value: 4}
|
||||||
|
}), {
|
||||||
|
a: {value: 2},
|
||||||
|
b: {value: 4}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("kstate2state: general", t => {
|
||||||
|
t.deepEqual(kstateToState({
|
||||||
|
"m.room.name/": {name: "test name"},
|
||||||
|
"m.room.member/@cadence:cadence.moe": {membership: "join"}
|
||||||
|
}), [
|
||||||
|
{
|
||||||
|
type: "m.room.name",
|
||||||
|
state_key: "",
|
||||||
|
content: {
|
||||||
|
name: "test name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "m.room.member",
|
||||||
|
state_key: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
membership: "join"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("state2kstate: general", t => {
|
||||||
|
t.deepEqual(stateToKState([
|
||||||
|
{
|
||||||
|
type: "m.room.name",
|
||||||
|
state_key: "",
|
||||||
|
content: {
|
||||||
|
name: "test name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "m.room.member",
|
||||||
|
state_key: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
membership: "join"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]), {
|
||||||
|
"m.room.name/": {name: "test name"},
|
||||||
|
"m.room.member/@cadence:cadence.moe": {membership: "join"}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("diffKState: detects edits", t => {
|
||||||
|
t.deepEqual(
|
||||||
|
diffKState({
|
||||||
|
"m.room.name/": {name: "test name"},
|
||||||
|
"same/": {a: 2}
|
||||||
|
}, {
|
||||||
|
"m.room.name/": {name: "edited name"},
|
||||||
|
"same/": {a: 2}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
"m.room.name/": {name: "edited name"}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("diffKState: detects new properties", t => {
|
||||||
|
t.deepEqual(
|
||||||
|
diffKState({
|
||||||
|
"m.room.name/": {name: "test name"},
|
||||||
|
}, {
|
||||||
|
"m.room.name/": {name: "test name"},
|
||||||
|
"new/": {a: 2}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
"new/": {a: 2}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
|
@ -12,7 +12,8 @@ const sync = new HeatSync({watchFS: false})
|
||||||
|
|
||||||
Object.assign(passthrough, { config, sync, db })
|
Object.assign(passthrough, { config, sync, db })
|
||||||
|
|
||||||
|
require("../matrix/kstate.test")
|
||||||
|
require("../matrix/api.test")
|
||||||
require("../matrix/read-registration.test")
|
require("../matrix/read-registration.test")
|
||||||
require("../d2m/actions/create-room.test")
|
require("../d2m/actions/create-room.test")
|
||||||
require("../d2m/converters/user-to-mxid.test")
|
require("../d2m/converters/user-to-mxid.test")
|
||||||
require("../matrix/api.test")
|
|
||||||
|
|
Loading…
Reference in a new issue