More consistency for invite records table
- Autojoined child spaces are recorded as invited - Update entry when reinvited - Delete entry when uninvited or removed from room - Allow linking with spaces you moderate, even if you didn't invite - Store power levels immediately for new invited rooms - Mark members as missing profile in this case - Only delete from invite table if it left the space
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE member_cache ADD COLUMN missing_profile INTEGER;
|
||||
|
||||
COMMIT;
|
||||
3
src/db/orm-defs.d.ts
vendored
3
src/db/orm-defs.d.ts
vendored
@@ -90,6 +90,7 @@ export type Models = {
|
||||
displayname: string | null
|
||||
avatar_url: string | null,
|
||||
power_level: number
|
||||
missing_profile: number | null
|
||||
}
|
||||
|
||||
member_power: {
|
||||
@@ -146,7 +147,7 @@ export type Models = {
|
||||
question_text: string
|
||||
is_closed: number
|
||||
}
|
||||
|
||||
|
||||
poll_option: {
|
||||
message_id: string
|
||||
matrix_option: string
|
||||
|
||||
@@ -290,8 +290,8 @@ function convertEmoji(mxcUrl, nameForGuess, allowSpriteSheetIndicator, allowLink
|
||||
* @returns {Promise<{displayname?: string?, avatar_url?: string?}>}
|
||||
*/
|
||||
async function getMemberFromCacheOrHomeserver(roomID, mxid, api) {
|
||||
const row = select("member_cache", ["displayname", "avatar_url"], {room_id: roomID, mxid}).get()
|
||||
if (row) return row
|
||||
const row = select("member_cache", ["displayname", "avatar_url", "missing_profile"], {room_id: roomID, mxid}).get()
|
||||
if (row && !row.missing_profile) return row
|
||||
return api.getStateEvent(roomID, "m.room.member", mxid).then(event => {
|
||||
const room = select("channel_room", "room_id", {room_id: roomID}).get()
|
||||
if (room) {
|
||||
@@ -299,7 +299,7 @@ async function getMemberFromCacheOrHomeserver(roomID, mxid, api) {
|
||||
// the cache will be kept in sync by the `m.room.member` event listener
|
||||
const displayname = event?.displayname || null
|
||||
const avatar_url = event?.avatar_url || null
|
||||
db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?").run(
|
||||
db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?, missing_profile = NULL").run(
|
||||
roomID, mxid,
|
||||
displayname, avatar_url,
|
||||
displayname, avatar_url
|
||||
|
||||
@@ -371,7 +371,18 @@ sync.addTemporaryListener(as, "type:m.space.child", guard("m.space.child",
|
||||
*/
|
||||
async event => {
|
||||
if (Array.isArray(event.content.via) && event.content.via.length) { // space child is being added
|
||||
await api.joinRoom(event.state_key).catch(() => {}) // try to join if able, it's okay if it doesn't want, bot will still respond to invites
|
||||
try {
|
||||
// try to join if able, it's okay if it doesn't want, bot will still respond to invites
|
||||
await api.joinRoom(event.state_key)
|
||||
// if autojoined a child space, store it in invite (otherwise the child space will be impossible to use with self-service in the future)
|
||||
const hierarchy = await api.getHierarchy(event.state_key, {limit: 1})
|
||||
const roomProperties = hierarchy.rooms?.[0]
|
||||
if (roomProperties?.room_id === event.state_key && roomProperties.room_type === "m.space" && roomProperties.name) {
|
||||
db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
.run(event.sender, event.state_key, roomProperties.room_type, roomProperties.name, roomProperties.topic, roomProperties.avatar_url)
|
||||
await updateMemberCachePowerLevels(event.state_key) // store privileged users in member_cache so they are also allowed to perform self-service
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -404,22 +415,24 @@ async event => {
|
||||
}
|
||||
if (!inviteRoomState?.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.`)
|
||||
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, inviteRoomState.type, inviteRoomState.name, inviteRoomState.topic, inviteRoomState.avatar)
|
||||
db.prepare("REPLACE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, inviteRoomState.type, inviteRoomState.name, inviteRoomState.topic, inviteRoomState.avatar)
|
||||
if (inviteRoomState.avatar) utils.getPublicUrlForMxc(inviteRoomState.avatar) // make sure it's available in the media_proxy allowed URLs
|
||||
await updateMemberCachePowerLevels(event.room_id) // store privileged users in member_cache so they are also allowed to perform self-service
|
||||
}
|
||||
|
||||
if (utils.eventSenderIsFromDiscord(event.state_key)) return
|
||||
|
||||
if (event.content.membership === "leave" || event.content.membership === "ban") {
|
||||
// Member is gone
|
||||
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
|
||||
// Unregister room's use as a direct chat and/or an invite target if the bot itself left
|
||||
if (event.state_key === utils.bot) {
|
||||
db.prepare("DELETE FROM direct WHERE room_id = ?").run(event.room_id)
|
||||
db.prepare("DELETE FROM invite WHERE room_id = ?").run(event.room_id)
|
||||
}
|
||||
}
|
||||
|
||||
if (utils.eventSenderIsFromDiscord(event.state_key)) return
|
||||
|
||||
const exists = select("channel_room", "room_id", {room_id: event.room_id}) ?? select("guild_space", "space_id", {space_id: event.room_id})
|
||||
if (!exists) return // don't cache members in unbridged rooms
|
||||
|
||||
@@ -428,7 +441,7 @@ async event => {
|
||||
if (memberPower === Infinity) memberPower = tombstone // database storage compatibility
|
||||
const displayname = event.content.displayname || null
|
||||
const avatar_url = event.content.avatar_url
|
||||
db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url, power_level) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?, power_level = ?").run(
|
||||
db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url, power_level) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?, power_level = ?, missing_profile = NULL").run(
|
||||
event.room_id, event.state_key,
|
||||
displayname, avatar_url, memberPower,
|
||||
displayname, avatar_url, memberPower
|
||||
@@ -441,16 +454,25 @@ sync.addTemporaryListener(as, "type:m.room.power_levels", guard("m.room.power_le
|
||||
*/
|
||||
async event => {
|
||||
if (event.state_key !== "") return
|
||||
const existingPower = select("member_cache", "mxid", {room_id: event.room_id}).pluck().all()
|
||||
const {allCreators} = await utils.getEffectivePower(event.room_id, [], api)
|
||||
const newPower = event.content.users || {}
|
||||
for (const mxid of existingPower) {
|
||||
if (!allCreators.includes(mxid)) {
|
||||
db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(newPower[mxid] || 0, event.room_id, mxid)
|
||||
}
|
||||
}
|
||||
await updateMemberCachePowerLevels(event.room_id)
|
||||
}))
|
||||
|
||||
/**
|
||||
* @param {string} roomID
|
||||
*/
|
||||
async function updateMemberCachePowerLevels(roomID) {
|
||||
const existingPower = select("member_cache", "mxid", {room_id: roomID}).pluck().all()
|
||||
const {powerLevels, allCreators, tombstone} = await utils.getEffectivePower(roomID, [], api)
|
||||
const newPower = powerLevels.users || {}
|
||||
const newPowerUsers = Object.keys(newPower)
|
||||
const relevantUsers = existingPower.concat(newPowerUsers).concat(allCreators)
|
||||
for (const mxid of [...new Set(relevantUsers)]) {
|
||||
const level = allCreators.includes(mxid) ? tombstone : newPower[mxid] ?? powerLevels.users_default ?? 0
|
||||
db.prepare("INSERT INTO member_cache (room_id, mxid, power_level, missing_profile) VALUES (?, ?, ?, 1) ON CONFLICT DO UPDATE SET power_level = ?")
|
||||
.run(roomID, mxid, level, level)
|
||||
}
|
||||
}
|
||||
|
||||
sync.addTemporaryListener(as, "type:m.room.tombstone", guard("m.room.tombstone",
|
||||
/**
|
||||
* @param {Ty.Event.StateOuter<Ty.Event.M_Room_Tombstone>} event
|
||||
|
||||
@@ -148,7 +148,13 @@ as.router.get("/guild", defineEventHandler(async event => {
|
||||
|
||||
// Self-service guild that hasn't been linked yet - needs a special page encouraging the link flow
|
||||
if (!row.space_id && row.autocreate === 0) {
|
||||
const spaces = db.prepare("SELECT room_id, type, name, topic, avatar FROM invite LEFT JOIN guild_space ON invite.room_id = guild_space.space_id WHERE mxid = ? AND space_id IS NULL AND type = 'm.space'").all(session.data.mxid)
|
||||
let spaces =
|
||||
// invited spaces
|
||||
db.prepare("SELECT room_id, type, name, topic, avatar FROM invite LEFT JOIN guild_space ON invite.room_id = guild_space.space_id WHERE mxid = ? AND space_id IS NULL AND type = 'm.space'").all(session.data.mxid)
|
||||
// moderated spaces
|
||||
.concat(db.prepare("SELECT room_id, type, name, topic, avatar FROM invite LEFT JOIN guild_space ON invite.room_id = guild_space.space_id INNER JOIN member_cache USING (room_id) WHERE member_cache.mxid = ? AND power_level >= 50 AND space_id IS NULL AND type = 'm.space'").all(session.data.mxid))
|
||||
const seen = new Set(spaces.map(s => s.room_id))
|
||||
spaces = spaces.filter(s => seen.delete(s.room_id))
|
||||
return pugSync.render(event, "guild_not_linked.pug", {guild, guild_id, spaces})
|
||||
}
|
||||
|
||||
|
||||
@@ -282,11 +282,11 @@ as.router.post("/api/unlink-space", defineEventHandler(async event => {
|
||||
await utils.setUserPower(spaceID, utils.bot, 0, api)
|
||||
await api.leaveRoom(spaceID)
|
||||
db.prepare("DELETE FROM guild_space WHERE guild_id = ? AND space_id = ?").run(guild_id, spaceID)
|
||||
db.prepare("DELETE FROM invite WHERE room_id = ?").run(spaceID)
|
||||
}
|
||||
|
||||
// Mark as not considered for bridging
|
||||
db.prepare("DELETE FROM guild_active WHERE guild_id = ?").run(guild_id)
|
||||
db.prepare("DELETE FROM invite WHERE room_id = ?").run(spaceID)
|
||||
await snow.user.leaveGuild(guild_id)
|
||||
|
||||
setResponseHeader(event, "HX-Redirect", "/")
|
||||
|
||||
Reference in New Issue
Block a user