forked from cadence/out-of-your-element
		
	Store invite in database and sync power on startup
				
					
				
			This commit is contained in:
		
							parent
							
								
									74632c671c
								
							
						
					
					
						commit
						df1296e579
					
				
					 8 changed files with 119 additions and 4 deletions
				
			
		
							
								
								
									
										14
									
								
								db/migrations/0012-add-member-power.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								db/migrations/0012-add-member-power.sql
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
BEGIN TRANSACTION;
 | 
			
		||||
 | 
			
		||||
-- the power we want them to have
 | 
			
		||||
CREATE TABLE IF NOT EXISTS member_power (
 | 
			
		||||
	mxid			TEXT NOT NULL,
 | 
			
		||||
	room_id		TEXT NOT NULL,
 | 
			
		||||
	power_level	INTEGER NOT NULL,
 | 
			
		||||
	PRIMARY KEY(mxid, room_id)
 | 
			
		||||
) WITHOUT ROWID;
 | 
			
		||||
 | 
			
		||||
-- the power they have
 | 
			
		||||
ALTER TABLE member_cache ADD COLUMN power_level INTEGER NOT NULL DEFAULT 0;
 | 
			
		||||
 | 
			
		||||
COMMIT;
 | 
			
		||||
							
								
								
									
										9
									
								
								db/orm-defs.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								db/orm-defs.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -42,7 +42,14 @@ export type Models = {
 | 
			
		|||
		room_id: string
 | 
			
		||||
		mxid: string
 | 
			
		||||
		displayname: string | null
 | 
			
		||||
		avatar_url: string | null
 | 
			
		||||
		avatar_url: string | null,
 | 
			
		||||
		power_level: number
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	member_power: {
 | 
			
		||||
		mxid: string
 | 
			
		||||
		room_id: string
 | 
			
		||||
		power_level: number
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message_channel: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								db/orm.js
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								db/orm.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -44,6 +44,8 @@ class From {
 | 
			
		|||
		/** @private */
 | 
			
		||||
		this.cols = []
 | 
			
		||||
		/** @private */
 | 
			
		||||
		this.makeColsSafe = true
 | 
			
		||||
		/** @private */
 | 
			
		||||
		this.using = []
 | 
			
		||||
		/** @private */
 | 
			
		||||
		this.isPluck = false
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +80,12 @@ class From {
 | 
			
		|||
		return r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	selectUnsafe(...cols) {
 | 
			
		||||
		this.cols = cols
 | 
			
		||||
		this.makeColsSafe = false
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @template {Col} Select
 | 
			
		||||
	 * @param {Select} col
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +120,8 @@ class From {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	prepare() {
 | 
			
		||||
		let sql = `SELECT ${this.cols.map(k => `"${k}"`).join(", ")} FROM ${this.tables[0]} `
 | 
			
		||||
		if (this.makeColsSafe) this.cols = this.cols.map(k => `"${k}"`)
 | 
			
		||||
		let sql = `SELECT ${this.cols.join(", ")} FROM ${this.tables[0]} `
 | 
			
		||||
		for (let i = 1; i < this.tables.length; i++) {
 | 
			
		||||
			const table = this.tables[i]
 | 
			
		||||
			const col = this.using[i-1]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
 | 
			
		||||
const util = require("util")
 | 
			
		||||
const Ty = require("../types")
 | 
			
		||||
const {discord, db, sync, as} = require("../passthrough")
 | 
			
		||||
const {discord, db, sync, as, select} = require("../passthrough")
 | 
			
		||||
 | 
			
		||||
/** @type {import("./actions/send-event")} */
 | 
			
		||||
const sendEvent = sync.require("./actions/send-event")
 | 
			
		||||
| 
						 | 
				
			
			@ -167,5 +167,29 @@ sync.addTemporaryListener(as, "type:m.room.member", guard("m.room.member",
 | 
			
		|||
async event => {
 | 
			
		||||
	if (event.state_key[0] !== "@") return
 | 
			
		||||
	if (utils.eventSenderIsFromDiscord(event.state_key)) return
 | 
			
		||||
	db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(event.room_id, event.state_key, event.content.displayname || null, event.content.avatar_url || null)
 | 
			
		||||
	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)
 | 
			
		||||
	} else {
 | 
			
		||||
		// Member is here
 | 
			
		||||
		db.prepare("INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?) ON CONFLICT DO UPDATE SET displayname = ?, avatar_url = ?")
 | 
			
		||||
			.run(
 | 
			
		||||
				event.room_id, event.state_key,
 | 
			
		||||
				event.content.displayname || null, event.content.avatar_url || null,
 | 
			
		||||
				event.content.displayname || null, event.content.avatar_url || null
 | 
			
		||||
			)
 | 
			
		||||
	}
 | 
			
		||||
}))
 | 
			
		||||
 | 
			
		||||
sync.addTemporaryListener(as, "type:m.room.power_levels", guard("m.room.power_levels",
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Ty.Event.StateOuter<Ty.Event.M_Power_Levels>} event
 | 
			
		||||
 */
 | 
			
		||||
async event => {
 | 
			
		||||
	if (event.state_key !== "") return
 | 
			
		||||
	const existingPower = select("member_cache", "mxid", {room_id: event.room_id}).pluck().all()
 | 
			
		||||
	const newPower = event.content.users || {}
 | 
			
		||||
	for (const mxid of existingPower) {
 | 
			
		||||
		db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(newPower[mxid] || 0, event.room_id, mxid)
 | 
			
		||||
	}
 | 
			
		||||
}))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,6 +70,7 @@ async function inviteToRoom(roomID, mxidToInvite, mxid) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
async function leaveRoom(roomID, mxid) {
 | 
			
		||||
	console.log(`[api] leave: ${roomID}: ${mxid}`)
 | 
			
		||||
	await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										29
									
								
								matrix/power.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								matrix/power.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
// @ts-check
 | 
			
		||||
 | 
			
		||||
const {db, from} = require("../passthrough")
 | 
			
		||||
const api = require("./api")
 | 
			
		||||
const reg = require("./read-registration")
 | 
			
		||||
const ks = require("./kstate")
 | 
			
		||||
const {applyKStateDiffToRoom, roomToKState} = require("../d2m/actions/create-room")
 | 
			
		||||
 | 
			
		||||
// Migrate reg.ooye.invite setting to database
 | 
			
		||||
for (const mxid of reg.ooye.invite) {
 | 
			
		||||
	db.prepare("INSERT OR IGNORE INTO member_power (mxid, room_id, power_level) VALUES (?, ?, 100)").run(mxid, "*")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Apply global power level requests across ALL rooms where the member cache entry exists but the power level has not been applied yet.
 | 
			
		||||
const rows = from("member_cache").join("member_power", "mxid")
 | 
			
		||||
	.and("where member_power.room_id = '*' and member_cache.power_level != member_power.power_level")
 | 
			
		||||
	.selectUnsafe("mxid", "member_cache.room_id", "member_power.power_level")
 | 
			
		||||
	.all()
 | 
			
		||||
 | 
			
		||||
;(async () => {
 | 
			
		||||
	for (const row of rows) {
 | 
			
		||||
		const kstate = await roomToKState(row.room_id)
 | 
			
		||||
		const diff = ks.diffKState(kstate, {"m.room.power_levels/": {users: {[row.mxid]: row.power_level}}})
 | 
			
		||||
		await applyKStateDiffToRoom(row.room_id, diff)
 | 
			
		||||
		// There is a listener on m.room.power_levels to do this same update,
 | 
			
		||||
		// but we update it here anyway since the homeserver does not always deliver the event round-trip.
 | 
			
		||||
		db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(row.power_level, row.room_id, row.mxid)
 | 
			
		||||
	}
 | 
			
		||||
})()
 | 
			
		||||
							
								
								
									
										1
									
								
								start.js
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								start.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -34,6 +34,7 @@ discord.snow.requestHandler.on("requestError", data => {
 | 
			
		|||
	await migrate.migrate(db)
 | 
			
		||||
	await discord.cloud.connect()
 | 
			
		||||
	console.log("Discord gateway started")
 | 
			
		||||
	require("./matrix/power.js")
 | 
			
		||||
 | 
			
		||||
	require("./stdin")
 | 
			
		||||
})()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								types.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								types.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -209,6 +209,36 @@ export namespace Event {
 | 
			
		|||
		name?: string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	export type M_Power_Levels = {
 | 
			
		||||
		/** The level required to ban a user. Defaults to 50 if unspecified. */
 | 
			
		||||
		ban?: number,
 | 
			
		||||
		/** The level required to send specific event types. This is a mapping from event type to power level required. */
 | 
			
		||||
		events?: {
 | 
			
		||||
			[event_id: string]: number
 | 
			
		||||
		},
 | 
			
		||||
		/** The default level required to send message events. Can be overridden by the `events` key. Defaults to 0 if unspecified. */
 | 
			
		||||
		events_default?: number,
 | 
			
		||||
		/** The level required to invite a user. Defaults to 0 if unspecified. */
 | 
			
		||||
		invite?: number,
 | 
			
		||||
		/** The level required to kick a user. Defaults to 50 if unspecified. */
 | 
			
		||||
		kick?: number,
 | 
			
		||||
		/** The power level requirements for specific notification types. This is a mapping from `key` to power level for that notifications key. */
 | 
			
		||||
		notifications?: {
 | 
			
		||||
			room: number,
 | 
			
		||||
			[key: string]: number
 | 
			
		||||
		},
 | 
			
		||||
		/** The level required to redact an event sent by another user. Defaults to 50 if unspecified. */
 | 
			
		||||
		redact?: number,
 | 
			
		||||
		/** The default level required to send state events. Can be overridden by the `events` key. Defaults to 50 if unspecified. */
 | 
			
		||||
		state_default?: number,
 | 
			
		||||
		/** The power levels for specific users. This is a mapping from `user_id` to power level for that user. */
 | 
			
		||||
		users?: {
 | 
			
		||||
			[mxid: string]: number
 | 
			
		||||
		},
 | 
			
		||||
		/**The power level for users in the room whose `user_id` is not mentioned in the `users` key. Defaults to 0 if unspecified. */
 | 
			
		||||
		users_default?: number
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	export type M_Reaction = {
 | 
			
		||||
		"m.relates_to": {
 | 
			
		||||
			rel_type: "m.annotation"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue