forked from cadence/out-of-your-element
		
	progress on edits
This commit is contained in:
		
							parent
							
								
									f16900553a
								
							
						
					
					
						commit
						96dd488e39
					
				
					 5 changed files with 166 additions and 5 deletions
				
			
		
							
								
								
									
										118
									
								
								d2m/actions/edit-message.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								d2m/actions/edit-message.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
// @ts-check
 | 
			
		||||
 | 
			
		||||
const assert = require("assert")
 | 
			
		||||
 | 
			
		||||
const passthrough = require("../../passthrough")
 | 
			
		||||
const { discord, sync, db } = passthrough
 | 
			
		||||
/** @type {import("../converters/message-to-event")} */
 | 
			
		||||
const messageToEvent = sync.require("../converters/message-to-event")
 | 
			
		||||
/** @type {import("../../matrix/api")} */
 | 
			
		||||
const api = sync.require("../../matrix/api")
 | 
			
		||||
/** @type {import("./register-user")} */
 | 
			
		||||
const registerUser = sync.require("./register-user")
 | 
			
		||||
/** @type {import("../actions/create-room")} */
 | 
			
		||||
const createRoom = sync.require("../actions/create-room")
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
 | 
			
		||||
 * @param {import("discord-api-types/v10").APIGuild} guild
 | 
			
		||||
 */
 | 
			
		||||
async function editMessage(message, guild) {
 | 
			
		||||
	// Figure out what events we will be replacing
 | 
			
		||||
 | 
			
		||||
	const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id)
 | 
			
		||||
	const senderMxid = await registerUser.ensureSimJoined(message.author, roomID)
 | 
			
		||||
	/** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */
 | 
			
		||||
   const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id)
 | 
			
		||||
 | 
			
		||||
	// Figure out what we will be replacing them with
 | 
			
		||||
 | 
			
		||||
	const newEvents = await messageToEvent.messageToEvent(message, guild, api)
 | 
			
		||||
 | 
			
		||||
	// Match the new events to the old events
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		Rules:
 | 
			
		||||
			+ The events must have the same type.
 | 
			
		||||
			+ The events must have the same subtype.
 | 
			
		||||
		Events will therefore be divided into three categories:
 | 
			
		||||
	*/
 | 
			
		||||
	/** 1. Events that are matched, and should be edited by sending another m.replace event */
 | 
			
		||||
	let eventsToReplace = []
 | 
			
		||||
	/** 2. Events that are present in the old version only, and should be blanked or redacted */
 | 
			
		||||
	let eventsToRedact = []
 | 
			
		||||
	/** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */
 | 
			
		||||
	let eventsToSend = []
 | 
			
		||||
 | 
			
		||||
	// For each old event...
 | 
			
		||||
	outer: while (newEvents.length) {
 | 
			
		||||
		const newe = newEvents[0]
 | 
			
		||||
		// Find a new event to pair it with...
 | 
			
		||||
		let handled = false
 | 
			
		||||
		for (let i = 0; i < oldEventRows.length; i++) {
 | 
			
		||||
			const olde = oldEventRows[i]
 | 
			
		||||
			if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) {
 | 
			
		||||
				// Found one!
 | 
			
		||||
				// Set up the pairing
 | 
			
		||||
				eventsToReplace.push({
 | 
			
		||||
					old: olde,
 | 
			
		||||
					new: newe
 | 
			
		||||
				})
 | 
			
		||||
				// These events have been handled now, so remove them from the source arrays
 | 
			
		||||
				newEvents.shift()
 | 
			
		||||
				oldEventRows.splice(i, 1)
 | 
			
		||||
				// Go all the way back to the start of the next iteration of the outer loop
 | 
			
		||||
				continue outer
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// If we got this far, we could not pair it to an existing event, so it'll have to be a new one
 | 
			
		||||
		eventsToSend.push(newe)
 | 
			
		||||
		newEvents.shift()
 | 
			
		||||
	}
 | 
			
		||||
	// Anything remaining in oldEventRows is present in the old version only and should be redacted.
 | 
			
		||||
	eventsToRedact = oldEventRows
 | 
			
		||||
 | 
			
		||||
	// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
 | 
			
		||||
	// (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
 | 
			
		||||
	// So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed.
 | 
			
		||||
	eventsToReplace = eventsToReplace.filter(ev => {
 | 
			
		||||
		// Discord does not allow files, images, attachments, or videos to be edited.
 | 
			
		||||
		if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		// Discord does not allow stickers to be edited.
 | 
			
		||||
		if (ev.old.event_type === "m.sticker") {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		// Anything else is fair game.
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Action time!
 | 
			
		||||
 | 
			
		||||
	// 1. Replace all the things.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// 2. Redact all the things.
 | 
			
		||||
 | 
			
		||||
	// 3. Send all the things.
 | 
			
		||||
 | 
			
		||||
	// old code lies here
 | 
			
		||||
	let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database?
 | 
			
		||||
	for (const event of events) {
 | 
			
		||||
		const eventType = event.$type
 | 
			
		||||
		/** @type {Pick<typeof event, Exclude<keyof event, "$type">> & { $type?: string }} */
 | 
			
		||||
		const eventWithoutType = {...event}
 | 
			
		||||
		delete eventWithoutType.$type
 | 
			
		||||
 | 
			
		||||
		const eventID = await api.sendEvent(roomID, eventType, event, senderMxid)
 | 
			
		||||
		db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord
 | 
			
		||||
 | 
			
		||||
		eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting
 | 
			
		||||
		eventIDs.push(eventID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return eventIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports.editMessage = editMessage
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ async function sendMessage(message, guild) {
 | 
			
		|||
		delete eventWithoutType.$type
 | 
			
		||||
 | 
			
		||||
		const eventID = await api.sendEvent(roomID, eventType, event, senderMxid)
 | 
			
		||||
		db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord
 | 
			
		||||
		db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord
 | 
			
		||||
 | 
			
		||||
		eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting
 | 
			
		||||
		eventIDs.push(eventID)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ async function sendEvent(event) {
 | 
			
		|||
	let eventPart = 0 // 0 is primary, 1 is supporting
 | 
			
		||||
	for (const message of messages) {
 | 
			
		||||
      const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message)
 | 
			
		||||
		db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 0)").run(event.event_id, messageResponse.id, channelID, eventPart) // source 0 = matrix
 | 
			
		||||
		db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix
 | 
			
		||||
 | 
			
		||||
		eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting?
 | 
			
		||||
		messageResponses.push(messageResponse)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,12 +15,18 @@ const makeTxnId = sync.require("./txnid")
 | 
			
		|||
/**
 | 
			
		||||
 * @param {string} p endpoint to access
 | 
			
		||||
 * @param {string} [mxid] optional: user to act as, for the ?user_id parameter
 | 
			
		||||
 * @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add
 | 
			
		||||
 * @returns {string} the new endpoint
 | 
			
		||||
 */
 | 
			
		||||
function path(p, mxid) {
 | 
			
		||||
function path(p, mxid, otherParams = {}) {
 | 
			
		||||
   if (!mxid) return p
 | 
			
		||||
   const u = new URL(p, "http://localhost")
 | 
			
		||||
   u.searchParams.set("user_id", mxid)
 | 
			
		||||
   for (const entry of Object.entries(otherParams)) {
 | 
			
		||||
      if (entry[1] != undefined) {
 | 
			
		||||
         u.searchParams.set(entry[0], entry[1])
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
   return u.pathname + "?" + u.searchParams.toString()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -109,10 +115,17 @@ async function sendState(roomID, type, stateKey, content, mxid) {
 | 
			
		|||
   return root.event_id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function sendEvent(roomID, type, content, mxid) {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} roomID
 | 
			
		||||
 * @param {string} type
 | 
			
		||||
 * @param {any} content
 | 
			
		||||
 * @param {string} [mxid]
 | 
			
		||||
 * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds
 | 
			
		||||
 */
 | 
			
		||||
async function sendEvent(roomID, type, content, mxid, timestamp) {
 | 
			
		||||
   console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`)
 | 
			
		||||
   /** @type {Ty.R.EventSent} */
 | 
			
		||||
   const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content)
 | 
			
		||||
   const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content)
 | 
			
		||||
   return root.event_id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								scripts/save-event-types-to-db.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								scripts/save-event-types-to-db.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
// @ts-check
 | 
			
		||||
 | 
			
		||||
const sqlite = require("better-sqlite3")
 | 
			
		||||
const HeatSync = require("heatsync")
 | 
			
		||||
 | 
			
		||||
const passthrough = require("../passthrough")
 | 
			
		||||
const db = new sqlite("db/ooye.db")
 | 
			
		||||
 | 
			
		||||
const sync = new HeatSync({watchFS: false})
 | 
			
		||||
 | 
			
		||||
Object.assign(passthrough, {sync, db})
 | 
			
		||||
 | 
			
		||||
const api = require("../matrix/api")
 | 
			
		||||
 | 
			
		||||
/** @type {{event_id: string, room_id: string, event_type: string}[]} */ // @ts-ignore
 | 
			
		||||
const rows = db.prepare("SELECT event_id, room_id, event_type FROM event_message INNER JOIN channel_room USING (channel_id)").all()
 | 
			
		||||
 | 
			
		||||
const preparedUpdate = db.prepare("UPDATE event_message SET event_type = ?, event_subtype = ? WHERE event_id = ?")
 | 
			
		||||
 | 
			
		||||
;(async () => {
 | 
			
		||||
   for (const row of rows) {
 | 
			
		||||
      if (row.event_type == null) {
 | 
			
		||||
         const event = await api.getEvent(row.room_id, row.event_id)
 | 
			
		||||
         const type = event.type
 | 
			
		||||
         const subtype = event.content.msgtype || null
 | 
			
		||||
         preparedUpdate.run(type, subtype, row.event_id)
 | 
			
		||||
         console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`)
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
})()
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue