Compare commits
4 commits
e6c30f80b5
...
c7313035a4
| Author | SHA1 | Date | |
|---|---|---|---|
| c7313035a4 | |||
| 493bc25602 | |||
| f176b547ce | |||
| 1758b7aa22 |
9 changed files with 445 additions and 184 deletions
|
|
@ -434,7 +434,7 @@ async function unbridgeChannel(channelID) {
|
|||
async function unbridgeDeletedChannel(channel, guildID) {
|
||||
const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get()
|
||||
assert.ok(roomID)
|
||||
const row = from("guild_space").join("guild_active", "guild_id").select("space_id", "autocreate").get()
|
||||
const row = from("guild_space").join("guild_active", "guild_id").select("space_id", "autocreate").where({guild_id: guildID}).get()
|
||||
assert.ok(row)
|
||||
|
||||
let botInRoom = true
|
||||
|
|
@ -458,7 +458,7 @@ async function unbridgeDeletedChannel(channel, guildID) {
|
|||
// delete webhook on discord
|
||||
const webhook = select("webhook", ["webhook_id", "webhook_token"], {channel_id: channel.id}).get()
|
||||
if (webhook) {
|
||||
await discord.snow.webhook.deleteWebhook(webhook.webhook_id, webhook.webhook_token)
|
||||
await discord.snow.webhook.deleteWebhook(webhook.webhook_id, webhook.webhook_token).catch(() => {})
|
||||
db.prepare("DELETE FROM webhook WHERE channel_id = ?").run(channel.id)
|
||||
}
|
||||
|
||||
|
|
@ -568,6 +568,7 @@ module.exports.createAllForGuild = createAllForGuild
|
|||
module.exports.channelToKState = channelToKState
|
||||
module.exports.postApplyPowerLevels = postApplyPowerLevels
|
||||
module.exports._convertNameAndTopic = convertNameAndTopic
|
||||
module.exports._syncSpaceMember = _syncSpaceMember
|
||||
module.exports.unbridgeChannel = unbridgeChannel
|
||||
module.exports.unbridgeDeletedChannel = unbridgeDeletedChannel
|
||||
module.exports.existsOrAutocreatable = existsOrAutocreatable
|
||||
|
|
|
|||
|
|
@ -151,16 +151,9 @@ async function syncUser(messageID, author, roomID, shouldActuallySync) {
|
|||
const mxid = await ensureSimJoined(pkMessage, roomID)
|
||||
|
||||
if (shouldActuallySync) {
|
||||
// Build current profile data
|
||||
// Build current profile data and sync if the hash has changed
|
||||
const content = await memberToStateContent(pkMessage, author)
|
||||
const currentHash = registerUser._hashProfileContent(content, 0)
|
||||
const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get()
|
||||
|
||||
// Only do the actual sync if the hash has changed since we last looked
|
||||
if (existingHash !== currentHash) {
|
||||
await api.sendState(roomID, "m.room.member", mxid, content, mxid)
|
||||
db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE room_id = ? AND mxid = ?").run(currentHash, roomID, mxid)
|
||||
}
|
||||
await registerUser._sendSyncUser(roomID, mxid, content, null)
|
||||
}
|
||||
|
||||
return mxid
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ let hasher = null
|
|||
// @ts-ignore
|
||||
require("xxhash-wasm")().then(h => hasher = h)
|
||||
|
||||
const supportsMsc4069 = api.versions().then(v => !!v?.unstable_features?.["org.matrix.msc4069"]).catch(() => false)
|
||||
|
||||
/**
|
||||
* A sim is an account that is being simulated by the bridge to copy events from the other side.
|
||||
* @param {DiscordTypes.APIUser} user
|
||||
|
|
@ -98,6 +100,23 @@ async function ensureSimJoined(user, roomID) {
|
|||
return mxid
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APIUser} user
|
||||
*/
|
||||
async function userToGlobalProfile(user) {
|
||||
const globalProfile = {}
|
||||
|
||||
globalProfile.displayname = user.username
|
||||
if (user.global_name) globalProfile.displayname = user.global_name
|
||||
|
||||
if (user.avatar) {
|
||||
const avatarPath = file.userAvatar(user) // the user avatar only
|
||||
globalProfile.avatar_url = await file.uploadDiscordFileToMxc(avatarPath)
|
||||
}
|
||||
|
||||
return globalProfile
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APIUser} user
|
||||
* @param {Omit<DiscordTypes.APIGuildMember, "user"> | undefined} member
|
||||
|
|
@ -201,21 +220,45 @@ async function syncUser(user, member, channel, guild, roomID) {
|
|||
const mxid = await ensureSimJoined(user, roomID)
|
||||
const content = await memberToStateContent(user, member, guild.id)
|
||||
const powerLevel = memberToPowerLevel(user, member, guild, channel)
|
||||
const currentHash = _hashProfileContent(content, powerLevel)
|
||||
await _sendSyncUser(roomID, mxid, content, powerLevel, {
|
||||
// do not overwrite pre-existing data if we already have data and `member` is not accessible, because this would replace good data with bad data
|
||||
allowOverwrite: !!member,
|
||||
globalProfile: await userToGlobalProfile(user)
|
||||
})
|
||||
return mxid
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} roomID
|
||||
* @param {string} mxid
|
||||
* @param {{displayname: string, avatar_url?: string}} content
|
||||
* @param {number | null} powerLevel
|
||||
* @param {{allowOverwrite?: boolean, globalProfile?: {displayname: string, avatar_url?: string}}} [options]
|
||||
*/
|
||||
async function _sendSyncUser(roomID, mxid, content, powerLevel, options) {
|
||||
const currentHash = _hashProfileContent(content, powerLevel ?? 0)
|
||||
const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get()
|
||||
// only do the actual sync if the hash has changed since we last looked
|
||||
const hashHasChanged = existingHash !== currentHash
|
||||
// however, do not overwrite pre-existing data if we already have data and `member` is not accessible, because this would replace good data with bad data
|
||||
const wouldOverwritePreExisting = existingHash && !member
|
||||
if (hashHasChanged && !wouldOverwritePreExisting) {
|
||||
// always okay to add new data. for overwriting, restrict based on options.allowOverwrite, if present
|
||||
const overwriteOkay = !existingHash || (options?.allowOverwrite ?? true)
|
||||
if (hashHasChanged && overwriteOkay) {
|
||||
const actions = []
|
||||
// Update room member state
|
||||
await api.sendState(roomID, "m.room.member", mxid, content, mxid)
|
||||
actions.push(api.sendState(roomID, "m.room.member", mxid, content, mxid))
|
||||
// Update power levels
|
||||
await api.setUserPower(roomID, mxid, powerLevel)
|
||||
if (powerLevel != null) {
|
||||
actions.push(api.setUserPower(roomID, mxid, powerLevel))
|
||||
}
|
||||
// Update global profile (if supported by server)
|
||||
if (await supportsMsc4069) {
|
||||
actions.push(api.profileSetDisplayname(mxid, options?.globalProfile?.displayname || content.displayname, true))
|
||||
actions.push(api.profileSetAvatarUrl(mxid, options?.globalProfile?.avatar_url || content.avatar_url, true))
|
||||
}
|
||||
await Promise.all(actions)
|
||||
// Update cached hash
|
||||
db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE room_id = ? AND mxid = ?").run(currentHash, roomID, mxid)
|
||||
}
|
||||
return mxid
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -254,5 +297,7 @@ module.exports._hashProfileContent = _hashProfileContent
|
|||
module.exports.ensureSim = ensureSim
|
||||
module.exports.ensureSimJoined = ensureSimJoined
|
||||
module.exports.syncUser = syncUser
|
||||
module.exports._sendSyncUser = _sendSyncUser
|
||||
module.exports.syncAllUsersInRoom = syncAllUsersInRoom
|
||||
module.exports._memberToPowerLevel = memberToPowerLevel
|
||||
module.exports.supportsMsc4069 = supportsMsc4069
|
||||
|
|
|
|||
|
|
@ -128,16 +128,9 @@ async function syncUser(author, roomID, shouldActuallySync) {
|
|||
const mxid = await ensureSimJoined(fakeUserID, author, roomID)
|
||||
|
||||
if (shouldActuallySync) {
|
||||
// Build current profile data
|
||||
// Build current profile data and sync if the hash has changed
|
||||
const content = await authorToStateContent(author)
|
||||
const currentHash = registerUser._hashProfileContent(content, 0)
|
||||
const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get()
|
||||
|
||||
// Only do the actual sync if the hash has changed since we last looked
|
||||
if (existingHash !== currentHash) {
|
||||
await api.sendState(roomID, "m.room.member", mxid, content, mxid)
|
||||
db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE room_id = ? AND mxid = ?").run(currentHash, roomID, mxid)
|
||||
}
|
||||
await registerUser._sendSyncUser(roomID, mxid, content, null)
|
||||
}
|
||||
|
||||
return mxid
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ async function eventToMessage(event, guild, di) {
|
|||
}
|
||||
attachments.push({id: "0", filename})
|
||||
pendingFiles.push({name: filename, mxc: event.content.url})
|
||||
} else if (shouldProcessTextEvent) {
|
||||
} else {
|
||||
// Handling edits. If the edit was an edit of a reply, edits do not include the reply reference, so we need to fetch up to 2 more events.
|
||||
// this event ---is an edit of--> original event ---is a reply to--> past event
|
||||
await (async () => {
|
||||
|
|
@ -738,6 +738,7 @@ async function eventToMessage(event, guild, di) {
|
|||
replyLine = `-# > ${replyLine}${contentPreview}\n`
|
||||
})()
|
||||
|
||||
if (shouldProcessTextEvent) {
|
||||
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
|
||||
let input = event.content.formatted_body
|
||||
if (event.content.msgtype === "m.emote") {
|
||||
|
|
@ -891,6 +892,7 @@ async function eventToMessage(event, guild, di) {
|
|||
content = turndownService.escape(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content = displayNameRunoff + replyLine + content
|
||||
|
||||
|
|
|
|||
|
|
@ -2671,6 +2671,99 @@ test("event2message: rich reply to a state event with no body", async t => {
|
|||
)
|
||||
})
|
||||
|
||||
test("event2message: rich reply with an image", async t => {
|
||||
let called = 0
|
||||
t.deepEqual(
|
||||
await eventToMessage({
|
||||
type: "m.room.message",
|
||||
sender: "@cadence:cadence.moe",
|
||||
content: {
|
||||
body: "image.png",
|
||||
info: {
|
||||
size: 470379,
|
||||
mimetype: "image/png",
|
||||
thumbnail_info: {
|
||||
w: 800,
|
||||
h: 450,
|
||||
mimetype: "image/png",
|
||||
size: 183014
|
||||
},
|
||||
w: 1920,
|
||||
h: 1080,
|
||||
"xyz.amorgan.blurhash": "L24_wtVt00xuxvR%NFX74Toz?waL",
|
||||
thumbnail_url: "mxc://cadence.moe/lPtnjlleowWCXGOHKVDyoXGn"
|
||||
},
|
||||
msgtype: "m.image",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4"
|
||||
}
|
||||
},
|
||||
url: "mxc://cadence.moe/yxMobQMbSqNHpajxgSHtaooG"
|
||||
},
|
||||
origin_server_ts: 1764127662631,
|
||||
unsigned: {
|
||||
membership: "join",
|
||||
age: 97,
|
||||
transaction_id: "m1764127662540.2"
|
||||
},
|
||||
event_id: "$QOxkw7u8vjTrrdKxEUO13JWSixV7UXAZU1freT1SkHc",
|
||||
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||
}, data.guild.general, {
|
||||
api: {
|
||||
getEvent(roomID, eventID) {
|
||||
called++
|
||||
t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe")
|
||||
t.equal(eventID, "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4")
|
||||
return {
|
||||
type: "m.room.message",
|
||||
sender: "@cadence:cadence.moe",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "you have to check every diff above insane on this set https://osu.ppy.sh/beatmapsets/2263303#osu/4826296"
|
||||
},
|
||||
origin_server_ts: 1763639396419,
|
||||
unsigned: {
|
||||
membership: "join",
|
||||
age: 486586696,
|
||||
transaction_id: "m1763639396324.578"
|
||||
},
|
||||
event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4",
|
||||
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
{
|
||||
ensureJoined: [],
|
||||
messagesToDelete: [],
|
||||
messagesToEdit: [],
|
||||
messagesToSend: [
|
||||
{
|
||||
content: "-# > <:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/112760669178241024/1128118177155526666 **Ⓜcadence [they]**: you have to check every diff above insane on this...",
|
||||
allowed_mentions: {
|
||||
parse: ["users", "roles"]
|
||||
},
|
||||
attachments: [
|
||||
{
|
||||
filename: "image.png",
|
||||
id: "0",
|
||||
},
|
||||
],
|
||||
avatar_url: undefined,
|
||||
pendingFiles: [
|
||||
{
|
||||
mxc: "mxc://cadence.moe/yxMobQMbSqNHpajxgSHtaooG",
|
||||
name: "image.png",
|
||||
},
|
||||
],
|
||||
username: "cadence [they]",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test("event2message: raw mentioning discord users in plaintext body works", async t => {
|
||||
t.deepEqual(
|
||||
await eventToMessage({
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
const util = require("util")
|
||||
const Ty = require("../types")
|
||||
const {discord, db, sync, as, select} = require("../passthrough")
|
||||
const {tag} = require("@cloudrac3r/html-template-tag")
|
||||
|
||||
/** @type {import("./actions/send-event")} */
|
||||
const sendEvent = sync.require("./actions/send-event")
|
||||
|
|
@ -121,10 +122,10 @@ async function sendError(roomID, source, type, e, payload) {
|
|||
|
||||
// Where
|
||||
const stack = stringifyErrorStack(e)
|
||||
builder.addLine(`Error trace:\n${stack}`, `<details><summary>Error trace</summary><pre>${stack}</pre></details>`)
|
||||
builder.addLine(`Error trace:\n${stack}`, tag`<details><summary>Error trace</summary><pre>${stack}</pre></details>`)
|
||||
|
||||
// How
|
||||
builder.addLine("", `<details><summary>Original payload</summary><pre>${util.inspect(payload, false, 4, false)}</pre></details>`)
|
||||
builder.addLine("", tag`<details><summary>Original payload</summary><pre>${util.inspect(payload, false, 4, false)}</pre></details>`)
|
||||
}
|
||||
|
||||
// Send
|
||||
|
|
@ -322,14 +323,25 @@ sync.addTemporaryListener(as, "type:m.room.member", guard("m.room.member",
|
|||
*/
|
||||
async event => {
|
||||
if (event.state_key[0] !== "@") return
|
||||
const bot = `@${reg.sender_localpart}:${reg.ooye.server_name}`
|
||||
|
||||
if (event.content.membership === "invite" && event.state_key === `@${reg.sender_localpart}:${reg.ooye.server_name}`) {
|
||||
if (event.content.membership === "invite" && event.state_key === bot) {
|
||||
// We were invited to a room. We should join, and register the invite details for future reference in web.
|
||||
let attemptedApiMessage = "According to unsigned invite data."
|
||||
let inviteRoomState = event.unsigned?.invite_room_state
|
||||
if (!Array.isArray(inviteRoomState) || inviteRoomState.length === 0) {
|
||||
try {
|
||||
inviteRoomState = await api.getInviteState(event.room_id)
|
||||
attemptedApiMessage = "According to SSS API."
|
||||
} catch (e) {
|
||||
attemptedApiMessage = "According to unsigned invite data. SSS API unavailable: " + e.toString()
|
||||
}
|
||||
}
|
||||
const name = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.name", "name")
|
||||
const topic = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.topic", "topic")
|
||||
const avatar = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.avatar", "url")
|
||||
const creationType = getFromInviteRoomState(event.unsigned?.invite_room_state, "m.room.create", "type")
|
||||
if (!name) return await api.leaveRoomWithReason(event.room_id, "Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite!")
|
||||
if (!name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite! (${attemptedApiMessage})`)
|
||||
await api.joinRoom(event.room_id)
|
||||
db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, creationType, name, topic, avatar)
|
||||
if (avatar) utils.getPublicUrlForMxc(avatar) // make sure it's available in the media_proxy allowed URLs
|
||||
|
|
@ -342,7 +354,6 @@ async event => {
|
|||
db.prepare("DELETE FROM member_cache WHERE room_id = ? and mxid = ?").run(event.room_id, event.state_key)
|
||||
|
||||
// Unregister room's use as a direct chat if the bot itself left
|
||||
const bot = `@${reg.sender_localpart}:${reg.ooye.server_name}`
|
||||
if (event.state_key === bot) {
|
||||
db.prepare("DELETE FROM direct WHERE room_id = ?").run(event.room_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,24 @@ function getStateEvent(roomID, type, key) {
|
|||
return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state/${type}/${key}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} roomID
|
||||
* @returns {Promise<Ty.Event.InviteStrippedState[]>}
|
||||
*/
|
||||
async function getInviteState(roomID) {
|
||||
/** @type {Ty.R.SSS} */
|
||||
const root = await mreq.mreq("POST", "/client/unstable/org.matrix.simplified_msc3575/sync", {
|
||||
room_subscriptions: {
|
||||
[roomID]: {
|
||||
timeline_limit: 0,
|
||||
required_state: []
|
||||
}
|
||||
}
|
||||
})
|
||||
const roomResponse = root.rooms[roomID]
|
||||
return "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state
|
||||
}
|
||||
|
||||
/**
|
||||
* "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
|
||||
|
|
@ -299,16 +317,34 @@ async function sendTyping(roomID, isTyping, mxid, duration) {
|
|||
})
|
||||
}
|
||||
|
||||
async function profileSetDisplayname(mxid, displayname) {
|
||||
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), {
|
||||
/**
|
||||
* @param {string} mxid
|
||||
* @param {string} displayname
|
||||
* @param {boolean} [inhibitPropagate]
|
||||
*/
|
||||
async function profileSetDisplayname(mxid, displayname, inhibitPropagate) {
|
||||
const params = {}
|
||||
if (inhibitPropagate) params["org.matrix.msc4069.propagate"] = false
|
||||
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid, params), {
|
||||
displayname
|
||||
})
|
||||
}
|
||||
|
||||
async function profileSetAvatarUrl(mxid, avatar_url) {
|
||||
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), {
|
||||
/**
|
||||
* @param {string} mxid
|
||||
* @param {string} avatar_url
|
||||
* @param {boolean} [inhibitPropagate]
|
||||
*/
|
||||
async function profileSetAvatarUrl(mxid, avatar_url, inhibitPropagate) {
|
||||
const params = {}
|
||||
if (inhibitPropagate) params["org.matrix.msc4069.propagate"] = false
|
||||
if (avatar_url) {
|
||||
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid, params), {
|
||||
avatar_url
|
||||
})
|
||||
} else {
|
||||
await mreq.mreq("DELETE", path(`/client/v3/profile/${mxid}/avatar_url`, mxid, params))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -472,6 +508,10 @@ function getProfile(mxid) {
|
|||
return mreq.mreq("GET", `/client/v3/profile/${mxid}`)
|
||||
}
|
||||
|
||||
function versions() {
|
||||
return mreq.mreq("GET", "/client/versions")
|
||||
}
|
||||
|
||||
module.exports.path = path
|
||||
module.exports.register = register
|
||||
module.exports.createRoom = createRoom
|
||||
|
|
@ -483,6 +523,7 @@ module.exports.getEvent = getEvent
|
|||
module.exports.getEventForTimestamp = getEventForTimestamp
|
||||
module.exports.getAllState = getAllState
|
||||
module.exports.getStateEvent = getStateEvent
|
||||
module.exports.getInviteState = getInviteState
|
||||
module.exports.getJoinedMembers = getJoinedMembers
|
||||
module.exports.getMembers = getMembers
|
||||
module.exports.getHierarchy = getHierarchy
|
||||
|
|
@ -507,3 +548,4 @@ module.exports.getAccountData = getAccountData
|
|||
module.exports.setAccountData = setAccountData
|
||||
module.exports.setPresence = setPresence
|
||||
module.exports.getProfile = getProfile
|
||||
module.exports.versions = versions
|
||||
|
|
|
|||
81
src/types.d.ts
vendored
81
src/types.d.ts
vendored
|
|
@ -166,6 +166,37 @@ export namespace Event {
|
|||
content: any
|
||||
}
|
||||
|
||||
export type InviteStrippedState = {
|
||||
type: string
|
||||
state_key: string
|
||||
sender: string
|
||||
content: Event.M_Room_Create | Event.M_Room_Name | Event.M_Room_Avatar | Event.M_Room_Topic | Event.M_Room_JoinRules | Event.M_Room_CanonicalAlias
|
||||
}
|
||||
|
||||
export type M_Room_Create = {
|
||||
additional_creators: string[]
|
||||
"m.federate"?: boolean
|
||||
room_version: string
|
||||
type?: string
|
||||
predecessor?: {
|
||||
room_id: string
|
||||
event_id?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type M_Room_JoinRules = {
|
||||
join_rule: "public" | "knock" | "invite" | "private" | "restricted" | "knock_restricted"
|
||||
allow?: {
|
||||
type: string
|
||||
room_id: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export type M_Room_CanonicalAlias = {
|
||||
alias?: string
|
||||
alt_aliases?: string[]
|
||||
}
|
||||
|
||||
export type M_Room_Message = {
|
||||
msgtype: "m.text" | "m.emote"
|
||||
body: string
|
||||
|
|
@ -375,7 +406,57 @@ export namespace R {
|
|||
room_id: string
|
||||
servers: string[]
|
||||
}
|
||||
|
||||
export type SSS = {
|
||||
pos: string
|
||||
lists: {
|
||||
[list_key: string]: {
|
||||
count: number
|
||||
}
|
||||
}
|
||||
rooms: {
|
||||
[room_id: string]: {
|
||||
bump_stamp: number
|
||||
/** Omitted if user not in room (peeking) */
|
||||
membership?: Membership
|
||||
/** Names of lists that match this room */
|
||||
lists: string[]
|
||||
}
|
||||
// If user has been in the room - at least, that's what the spec says. Synapse returns some of these, such as `name` and `avatar`, for invites as well. Go nuts.
|
||||
& {
|
||||
name?: string
|
||||
avatar?: string
|
||||
heroes?: any[]
|
||||
/** According to account data */
|
||||
is_dm?: boolean
|
||||
/** If false, omitted fields are unchanged from their previous value. If true, omitted fields means the fields are not set. */
|
||||
initial?: boolean
|
||||
expanded_timeline?: boolean
|
||||
required_state?: Event.StateOuter<any>[]
|
||||
timeline_events?: Event.Outer<any>[]
|
||||
prev_batch?: string
|
||||
limited?: boolean
|
||||
num_live?: number
|
||||
joined_count?: number
|
||||
invited_count?: number
|
||||
notification_count?: number
|
||||
highlight_count?: number
|
||||
}
|
||||
// If user is invited or knocked
|
||||
& ({
|
||||
/** @deprecated */
|
||||
invite_state: Event.InviteStrippedState[]
|
||||
} | {
|
||||
stripped_state: Event.InviteStrippedState[]
|
||||
})
|
||||
}
|
||||
extensions: {
|
||||
[extension_key: string]: any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Membership = "invite" | "knock" | "join" | "leave" | "ban"
|
||||
|
||||
export type Pagination<T> = {
|
||||
chunk: T[]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue