Finish room diffing and syncing. All tests pass
This commit is contained in:
parent
f09eeccef3
commit
3fbe7eed6e
13 changed files with 658 additions and 529 deletions
|
@ -1,8 +1,6 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
const assert = require("assert").strict
|
const assert = require("assert").strict
|
||||||
const {test} = require("supertape")
|
|
||||||
const testData = require("../../test/data")
|
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
|
@ -12,37 +10,62 @@ const mreq = sync.require("../../matrix/mreq")
|
||||||
/** @type {import("../../matrix/file")} */
|
/** @type {import("../../matrix/file")} */
|
||||||
const file = sync.require("../../matrix/file")
|
const file = sync.require("../../matrix/file")
|
||||||
|
|
||||||
|
function kstateStripConditionals(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) {
|
function kstateToState(kstate) {
|
||||||
return Object.entries(kstate).map(([k, content]) => {
|
const events = []
|
||||||
console.log(k)
|
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("/")
|
const [type, state_key] = k.split("/")
|
||||||
assert.ok(typeof type === "string")
|
assert.ok(typeof type === "string")
|
||||||
assert.ok(typeof state_key === "string")
|
assert.ok(typeof state_key === "string")
|
||||||
return {type, state_key, content}
|
events.push({type, state_key, content})
|
||||||
})
|
}
|
||||||
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
test("kstate2state: general", t => {
|
/**
|
||||||
t.deepEqual(kstateToState({
|
* @param {import("../../types").Event.BaseStateEvent[]} events
|
||||||
"m.room.name/": {name: "test name"},
|
* @returns {any}
|
||||||
"m.room.member/@cadence:cadence.moe": {membership: "join"}
|
*/
|
||||||
}), [
|
function stateToKState(events) {
|
||||||
{
|
const kstate = {}
|
||||||
type: "m.room.name",
|
for (const event of events) {
|
||||||
state_key: "",
|
kstate[event.type + "/" + event.state_key] = event.content
|
||||||
content: {
|
|
||||||
name: "test name"
|
|
||||||
}
|
}
|
||||||
},
|
return kstate
|
||||||
{
|
}
|
||||||
type: "m.room.member",
|
|
||||||
state_key: "@cadence:cadence.moe",
|
/**
|
||||||
content: {
|
* @param {string} roomID
|
||||||
membership: "join"
|
*/
|
||||||
}
|
async function roomToKState(roomID) {
|
||||||
}
|
/** @type {import("../../types").Event.BaseStateEvent[]} */
|
||||||
])
|
const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`)
|
||||||
})
|
return stateToKState(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @params {string} roomID
|
||||||
|
* @params {any} kstate
|
||||||
|
*/
|
||||||
|
function applyKStateDiffToRoom(roomID, kstate) {
|
||||||
|
const events = kstateToState(kstate)
|
||||||
|
return Promise.all(events.map(({type, state_key, content}) =>
|
||||||
|
mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${state_key}`, content)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
function diffKState(actual, target) {
|
function diffKState(actual, target) {
|
||||||
const diff = {}
|
const diff = {}
|
||||||
|
@ -60,39 +83,11 @@ function diffKState(actual, target) {
|
||||||
// not present, needs to be added
|
// not present, needs to be added
|
||||||
diff[key] = target[key]
|
diff[key] = target[key]
|
||||||
}
|
}
|
||||||
|
// keys that are missing in "actual" will not be deleted on "target" (no action)
|
||||||
}
|
}
|
||||||
return diff
|
return diff
|
||||||
}
|
}
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
|
@ -107,14 +102,14 @@ async function channelToKState(channel, guild) {
|
||||||
avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path)
|
avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
const kstate = {
|
const channelKState = {
|
||||||
"m.room.name/": {name: channel.name},
|
"m.room.name/": {name: channel.name},
|
||||||
"m.room.topic/": {topic: channel.topic || undefined},
|
"m.room.topic/": {$if: channel.topic, topic: channel.topic},
|
||||||
"m.room.avatar/": avatarEventContent,
|
"m.room.avatar/": avatarEventContent,
|
||||||
"m.room.guest_access/": {guest_access: "can_join"},
|
"m.room.guest_access/": {guest_access: "can_join"},
|
||||||
"m.room.history_visibility/": {history_visibility: "invited"},
|
"m.room.history_visibility/": {history_visibility: "invited"},
|
||||||
[`m.space.parent/${spaceID}`]: { // TODO: put the proper server here
|
[`m.space.parent/${spaceID}`]: {
|
||||||
via: ["cadence.moe"],
|
via: ["cadence.moe"], // TODO: put the proper server here
|
||||||
canonical: true
|
canonical: true
|
||||||
},
|
},
|
||||||
"m.room.join_rules/": {
|
"m.room.join_rules/": {
|
||||||
|
@ -126,13 +121,9 @@ async function channelToKState(channel, guild) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {spaceID, kstate}
|
return {spaceID, channelKState}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("channel2room: general", async t => {
|
|
||||||
t.deepEqual(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.kstate), {expected: true, ...testData.room.general})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("discord-api-types/v10").APIGuildTextChannel} channel
|
* @param {import("discord-api-types/v10").APIGuildTextChannel} channel
|
||||||
* @param guild
|
* @param guild
|
||||||
|
@ -140,7 +131,7 @@ test("channel2room: general", async t => {
|
||||||
* @param {any} kstate
|
* @param {any} kstate
|
||||||
*/
|
*/
|
||||||
async function createRoom(channel, guild, spaceID, kstate) {
|
async function createRoom(channel, guild, spaceID, kstate) {
|
||||||
/** @type {import("../../types").R_RoomCreated} */
|
/** @type {import("../../types").R.RoomCreated} */
|
||||||
const root = await mreq.mreq("POST", "/client/v3/createRoom", {
|
const root = await mreq.mreq("POST", "/client/v3/createRoom", {
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
topic: channel.topic || undefined,
|
topic: channel.topic || undefined,
|
||||||
|
@ -159,20 +150,46 @@ async function createRoom(channel, guild, spaceID, kstate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("discord-api-types/v10").APIGuildTextChannel} channel
|
* @param {import("discord-api-types/v10").APIGuildChannel} channel
|
||||||
*/
|
*/
|
||||||
async function syncRoom(channel) {
|
function channelToGuild(channel) {
|
||||||
const guildID = channel.guild_id
|
const guildID = channel.guild_id
|
||||||
assert(guildID)
|
assert(guildID)
|
||||||
const guild = discord.guilds.get(guildID)
|
const guild = discord.guilds.get(guildID)
|
||||||
assert(guild)
|
assert(guild)
|
||||||
|
return guild
|
||||||
|
}
|
||||||
|
|
||||||
const {spaceID, kstate} = await channelToKState(channel, guild)
|
/**
|
||||||
|
* @param {string} channelID
|
||||||
|
*/
|
||||||
|
async function syncRoom(channelID) {
|
||||||
|
/** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */
|
||||||
|
const channel = discord.channels.get(channelID)
|
||||||
|
assert.ok(channel)
|
||||||
|
const guild = channelToGuild(channel)
|
||||||
|
|
||||||
|
const {spaceID, channelKState} = await channelToKState(channel, guild)
|
||||||
|
|
||||||
/** @type {string?} */
|
/** @type {string?} */
|
||||||
const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id)
|
const existing = db.prepare("SELECT room_id from channel_room WHERE channel_id = ?").pluck().get(channel.id)
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
createRoom(channel, guild, spaceID, kstate)
|
return createRoom(channel, guild, spaceID, channelKState)
|
||||||
|
} else {
|
||||||
|
// sync channel state to room
|
||||||
|
const roomKState = await roomToKState(existing)
|
||||||
|
const roomDiff = diffKState(roomKState, channelKState)
|
||||||
|
const roomApply = applyKStateDiffToRoom(existing, roomDiff)
|
||||||
|
|
||||||
|
// sync room as space member
|
||||||
|
const spaceKState = await roomToKState(spaceID)
|
||||||
|
const spaceDiff = diffKState(spaceKState, {
|
||||||
|
[`m.space.child/${existing}`]: {
|
||||||
|
via: ["cadence.moe"] // TODO: use the proper server
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const spaceApply = applyKStateDiffToRoom(spaceID, spaceDiff)
|
||||||
|
return Promise.all([roomApply, spaceApply])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,14 +197,15 @@ async function createAllForGuild(guildID) {
|
||||||
const channelIDs = discord.guildChannelMap.get(guildID)
|
const channelIDs = discord.guildChannelMap.get(guildID)
|
||||||
assert.ok(channelIDs)
|
assert.ok(channelIDs)
|
||||||
for (const channelID of channelIDs) {
|
for (const channelID of channelIDs) {
|
||||||
const channel = discord.channels.get(channelID)
|
await syncRoom(channelID).then(r => console.log(`synced ${channelID}:`, r))
|
||||||
assert.ok(channel)
|
|
||||||
const existing = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id)
|
|
||||||
if (channel.type === DiscordTypes.ChannelType.GuildText && !existing) {
|
|
||||||
await createRoom(channel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.createRoom = createRoom
|
module.exports.createRoom = createRoom
|
||||||
|
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.kstateStripConditionals = kstateStripConditionals
|
||||||
|
|
83
d2m/actions/create-room.test.js
Normal file
83
d2m/actions/create-room.test.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
const {kstateToState, stateToKState, diffKState, channelToKState, kstateStripConditionals} = require("./create-room")
|
||||||
|
const {test} = require("supertape")
|
||||||
|
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 => {
|
||||||
|
t.deepEqual(
|
||||||
|
kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)),
|
||||||
|
testData.room.general
|
||||||
|
)
|
||||||
|
})
|
|
@ -37,7 +37,7 @@ function createSpace(guild) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).then(/** @param {import("../../types").R_RoomCreated} root */ root => {
|
}).then(/** @param {import("../../types").R.RoomCreated} root */ root => {
|
||||||
db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id)
|
db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id)
|
||||||
return root
|
return root
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
const reg = require("../../matrix/read-registration.js")
|
const reg = require("../../matrix/read-registration.js")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch").default
|
||||||
|
|
||||||
fetch("https://matrix.cadence.moe/_matrix/client/v3/register", {
|
fetch("https://matrix.cadence.moe/_matrix/client/v3/register", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
3
index.js
3
index.js
|
@ -22,6 +22,3 @@ passthrough.discord = discord
|
||||||
|
|
||||||
require("./stdin")
|
require("./stdin")
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// process.on("unhandledRejection", console.error)
|
|
||||||
// process.on("uncaughtException", console.error)
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ async function uploadDiscordFileToMxc(path) {
|
||||||
const body = res.body
|
const body = res.body
|
||||||
|
|
||||||
// Upload to Matrix
|
// Upload to Matrix
|
||||||
/** @type {import("../types").R_FileUploaded} */
|
/** @type {import("../types").R.FileUploaded} */
|
||||||
const root = await mreq.mreq("POST", "/media/v3/upload", body, {
|
const root = await mreq.mreq("POST", "/media/v3/upload", body, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": res.headers.get("content-type")
|
"Content-Type": res.headers.get("content-type")
|
||||||
|
|
|
@ -10,8 +10,9 @@ const reg = sync.require("./read-registration.js")
|
||||||
|
|
||||||
const baseUrl = "https://matrix.cadence.moe/_matrix"
|
const baseUrl = "https://matrix.cadence.moe/_matrix"
|
||||||
|
|
||||||
class MatrixServerError {
|
class MatrixServerError extends Error {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
|
super(data.error || data.errcode)
|
||||||
this.data = data
|
this.data = data
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.errcode = data.errcode
|
this.errcode = data.errcode
|
||||||
|
@ -35,7 +36,7 @@ async function mreq(method, url, body, extra = {}) {
|
||||||
}
|
}
|
||||||
}, extra)
|
}, extra)
|
||||||
|
|
||||||
console.log(baseUrl + url, opts)
|
// console.log(baseUrl + url, opts)
|
||||||
const res = await fetch(baseUrl + url, opts)
|
const res = await fetch(baseUrl + url, opts)
|
||||||
const root = await res.json()
|
const root = await res.json()
|
||||||
|
|
||||||
|
|
857
package-lock.json
generated
857
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -24,13 +24,15 @@
|
||||||
"matrix-js-sdk": "^24.1.0",
|
"matrix-js-sdk": "^24.1.0",
|
||||||
"mixin-deep": "^2.0.1",
|
"mixin-deep": "^2.0.1",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"snowtransfer": "^0.7.0",
|
"snowtransfer": "^0.7.0"
|
||||||
"supertape": "^8.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.16.0"
|
"@types/node": "^18.16.0",
|
||||||
|
"@types/node-fetch": "^2.6.3",
|
||||||
|
"supertape": "^8.3.0",
|
||||||
|
"tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "supertape --format short test/test.js"
|
"test": "FORCE_COLOR=true supertape --format tap test/test.js | tap-dot"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
stdin.js
2
stdin.js
|
@ -43,7 +43,7 @@ async function customEval(input, _context, _filename, callback) {
|
||||||
const output = util.inspect(result, false, depth, true)
|
const output = util.inspect(result, false, depth, true)
|
||||||
return callback(null, output)
|
return callback(null, output)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(null, util.inspect(e, true, 100, true))
|
return callback(null, util.inspect(e, false, 100, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
"m.room.avatar/": {
|
"m.room.avatar/": {
|
||||||
discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024",
|
discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024",
|
||||||
url: "mxc://cadence.moe/sZtPwbfOIsvfSoWCWPrGnzql"
|
url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,4 +12,4 @@ const sync = new HeatSync({persistent: false})
|
||||||
|
|
||||||
Object.assign(passthrough, { config, sync, db })
|
Object.assign(passthrough, { config, sync, db })
|
||||||
|
|
||||||
require("../d2m/actions/create-room")
|
require("../d2m/actions/create-room.test")
|
||||||
|
|
33
types.d.ts
vendored
33
types.d.ts
vendored
|
@ -8,17 +8,42 @@ export type AppServiceRegistrationConfig = {
|
||||||
rate_limited: boolean
|
rate_limited: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type M_Room_Message_content = {
|
namespace Event {
|
||||||
|
export type BaseStateEvent = {
|
||||||
|
type: string
|
||||||
|
room_id: string
|
||||||
|
sender: string
|
||||||
|
content: any
|
||||||
|
state_key: string
|
||||||
|
origin_server_ts: number
|
||||||
|
unsigned: any
|
||||||
|
event_id: string
|
||||||
|
user_id: string
|
||||||
|
age: number
|
||||||
|
replaces_state: string
|
||||||
|
prev_content?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type M_Room_Message = {
|
||||||
msgtype: "m.text"
|
msgtype: "m.text"
|
||||||
body: string
|
body: string
|
||||||
formatted_body?: "org.matrix.custom.html"
|
formatted_body?: "org.matrix.custom.html"
|
||||||
format?: string
|
format?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type M_Room_Member = {
|
||||||
|
membership: string
|
||||||
|
display_name?: string
|
||||||
|
avatar_url?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type R_RoomCreated = {
|
namespace R {
|
||||||
|
export type RoomCreated = {
|
||||||
room_id: string
|
room_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type R_FileUploaded = {
|
export type FileUploaded = {
|
||||||
content_uri: string
|
content_uri: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue