register -> invite -> join -> send flow
This commit is contained in:
		
							parent
							
								
									3bc29def41
								
							
						
					
					
						commit
						1e7e66dc31
					
				
					 11 changed files with 150 additions and 34 deletions
				
			
		| 
						 | 
					@ -8,8 +8,8 @@ const api = sync.require("../../matrix/api")
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild
 | 
					 * @param {import("discord-api-types/v10").RESTGetAPIGuildResult} guild
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function createSpace(guild) {
 | 
					async function createSpace(guild) {
 | 
				
			||||||
	return api.createRoom({
 | 
						const roomID = api.createRoom({
 | 
				
			||||||
		name: guild.name,
 | 
							name: guild.name,
 | 
				
			||||||
		preset: "private_chat",
 | 
							preset: "private_chat",
 | 
				
			||||||
		visibility: "private",
 | 
							visibility: "private",
 | 
				
			||||||
| 
						 | 
					@ -37,10 +37,9 @@ function createSpace(guild) {
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		]
 | 
							]
 | 
				
			||||||
	}).then(root => {
 | 
					 | 
				
			||||||
		db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, root.room_id)
 | 
					 | 
				
			||||||
		return root
 | 
					 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
						db.prepare("INSERT INTO guild_space (guild_id, space_id) VALUES (?, ?)").run(guild.id, roomID)
 | 
				
			||||||
 | 
						return roomID
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.createSpace = createSpace
 | 
					module.exports.createSpace = createSpace
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
// @ts-check
 | 
					// @ts-check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const assert = require("assert")
 | 
					const assert = require("assert")
 | 
				
			||||||
 | 
					const reg = require("../../matrix/read-registration")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const passthrough = require("../../passthrough")
 | 
					const passthrough = require("../../passthrough")
 | 
				
			||||||
const { discord, sync, db } = passthrough
 | 
					const { discord, sync, db } = passthrough
 | 
				
			||||||
| 
						 | 
					@ -14,10 +15,58 @@ const userToMxid = sync.require("../converters/user-to-mxid")
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A sim is an account that is being simulated by the bridge to copy events from the other side.
 | 
					 * A sim is an account that is being simulated by the bridge to copy events from the other side.
 | 
				
			||||||
 * @param {import("discord-api-types/v10").APIUser} user
 | 
					 * @param {import("discord-api-types/v10").APIUser} user
 | 
				
			||||||
 | 
					 * @returns mxid
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
async function createSim(user) {
 | 
					async function createSim(user) {
 | 
				
			||||||
 | 
						// Choose sim name
 | 
				
			||||||
	const simName = userToMxid.userToSimName(user)
 | 
						const simName = userToMxid.userToSimName(user)
 | 
				
			||||||
	const appservicePrefix = "_ooye_"
 | 
						const localpart = reg.namespace_prefix + simName
 | 
				
			||||||
	const localpart = appservicePrefix + simName
 | 
						const mxid = "@" + localpart + ":cadence.moe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Save chosen name in the database forever
 | 
				
			||||||
 | 
						db.prepare("INSERT INTO sim (discord_id, sim_name, localpart, mxid) VALUES (?, ?, ?, ?)").run(user.id, simName, localpart, mxid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Register matrix user with that name
 | 
				
			||||||
	await api.register(localpart)
 | 
						await api.register(localpart)
 | 
				
			||||||
 | 
						return mxid
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Ensure a sim is registered for the user.
 | 
				
			||||||
 | 
					 * If there is already a sim, use that one. If there isn't one yet, register a new sim.
 | 
				
			||||||
 | 
					 * @returns mxid
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function ensureSim(user) {
 | 
				
			||||||
 | 
						let mxid = null
 | 
				
			||||||
 | 
						const existing = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(user.id)
 | 
				
			||||||
 | 
						if (existing) {
 | 
				
			||||||
 | 
							mxid = existing
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							mxid = await createSim(user)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mxid
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Ensure a sim is registered for the user and is joined to the room.
 | 
				
			||||||
 | 
					 * @returns mxid
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function ensureSimJoined(user, roomID) {
 | 
				
			||||||
 | 
						// Ensure room ID is really an ID, not an alias
 | 
				
			||||||
 | 
						assert.ok(roomID[0] === "!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure user
 | 
				
			||||||
 | 
						const mxid = await ensureSim(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure joined
 | 
				
			||||||
 | 
						const existing = db.prepare("SELECT * FROM sim_member WHERE room_id = ? and mxid = ?").get(roomID, mxid)
 | 
				
			||||||
 | 
						if (!existing) {
 | 
				
			||||||
 | 
							await api.inviteToRoom(roomID, mxid)
 | 
				
			||||||
 | 
							await api.joinRoom(roomID, mxid)
 | 
				
			||||||
 | 
							db.prepare("INSERT INTO sim_member (room_id, mxid) VALUES (?, ?)").run(roomID, mxid)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mxid
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.ensureSim = ensureSim
 | 
				
			||||||
 | 
					module.exports.ensureSimJoined = ensureSimJoined
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,27 +1,29 @@
 | 
				
			||||||
// @ts-check
 | 
					// @ts-check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const reg = require("../../matrix/read-registration.js")
 | 
					 | 
				
			||||||
const makeTxnId = require("../../matrix/txnid.js")
 | 
					 | 
				
			||||||
const fetch = require("node-fetch").default
 | 
					const fetch = require("node-fetch").default
 | 
				
			||||||
const messageToEvent = require("../converters/message-to-event.js")
 | 
					const reg = require("../../matrix/read-registration.js")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
 | 
					 * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function sendMessage(message) {
 | 
					async function sendMessage(message) {
 | 
				
			||||||
	const event = messageToEvent.messageToEvent(message)
 | 
						const event = messageToEvent.messageToEvent(message)
 | 
				
			||||||
	return fetch(`https://matrix.cadence.moe/_matrix/client/v3/rooms/!VwVlIAjOjejUpDhlbA:cadence.moe/send/m.room.message/${makeTxnId()}?user_id=@_ooye_example:cadence.moe`, {
 | 
						const roomID = "!VwVlIAjOjejUpDhlbA:cadence.moe"
 | 
				
			||||||
		method: "PUT",
 | 
						let senderMxid = null
 | 
				
			||||||
		body: JSON.stringify(event),
 | 
						if (!message.webhook_id) {
 | 
				
			||||||
		headers: {
 | 
							senderMxid = await registerUser.ensureSimJoined(message.author, roomID)
 | 
				
			||||||
			Authorization: `Bearer ${reg.as_token}`
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}).then(res => res.text()).then(text => {
 | 
						const eventID = api.sendEvent(roomID, "m.room.message", event, senderMxid)
 | 
				
			||||||
		// {"event_id":"$4Zxs0fMmYlbo-sTlMmSEvwIs9b4hcg6yORzK0Ems84Q"}
 | 
						return eventID
 | 
				
			||||||
		console.log(text)
 | 
					 | 
				
			||||||
	}).catch(err => {
 | 
					 | 
				
			||||||
		console.log(err)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.sendMessage = sendMessage
 | 
					module.exports.sendMessage = sendMessage
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ test("user2name: works on normal name", t => {
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test("user2name: works on emojis", t => {
 | 
					test("user2name: works on emojis", t => {
 | 
				
			||||||
   t.equal(userToSimName({username: "Cookie 🍪", discriminator: "0001"}), "cookie")
 | 
					   t.equal(userToSimName({username: "🍪 Cookie Monster 🍪", discriminator: "0001"}), "cookie_monster")
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test("user2name: works on crazy name", t => {
 | 
					test("user2name: works on crazy name", t => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,15 @@ const { discord, sync, db } = passthrough
 | 
				
			||||||
const mreq = sync.require("./mreq")
 | 
					const mreq = sync.require("./mreq")
 | 
				
			||||||
/** @type {import("./file")} */
 | 
					/** @type {import("./file")} */
 | 
				
			||||||
const file = sync.require("./file")
 | 
					const file = sync.require("./file")
 | 
				
			||||||
 | 
					/** @type {import("./txnid")} */
 | 
				
			||||||
 | 
					const makeTxnId = sync.require("./txnid")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function path(p, mxid = null) {
 | 
				
			||||||
 | 
					   if (!mxid) return p
 | 
				
			||||||
 | 
					   const u = new URL(p, "http://localhost")
 | 
				
			||||||
 | 
					   u.searchParams.set("user_id", mxid)
 | 
				
			||||||
 | 
					   return u.pathname + "?" + u.searchParams.toString()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {string} username
 | 
					 * @param {string} username
 | 
				
			||||||
| 
						 | 
					@ -21,10 +30,27 @@ function register(username) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @returns {Promise<import("../types").R.RoomCreated>}
 | 
					 * @returns {Promise<string>} room ID
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function createRoom(content) {
 | 
					async function createRoom(content) {
 | 
				
			||||||
   return mreq.mreq("POST", "/client/v3/createRoom", content)
 | 
					   /** @type {import("../types").R.RoomCreated} */
 | 
				
			||||||
 | 
					   const root = await mreq.mreq("POST", "/client/v3/createRoom", content)
 | 
				
			||||||
 | 
					   return root.room_id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @returns {Promise<string>} room ID
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function joinRoom(roomIDOrAlias, mxid) {
 | 
				
			||||||
 | 
					   /** @type {import("../types").R.RoomJoined} */
 | 
				
			||||||
 | 
					   const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid))
 | 
				
			||||||
 | 
					   return root.room_id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function inviteToRoom(roomID, mxidToInvite, mxid) {
 | 
				
			||||||
 | 
					   await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/invite`, mxid), {
 | 
				
			||||||
 | 
					      user_id: mxidToInvite
 | 
				
			||||||
 | 
					   })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -39,15 +65,27 @@ function getAllState(roomID) {
 | 
				
			||||||
 * @param {string} roomID
 | 
					 * @param {string} roomID
 | 
				
			||||||
 * @param {string} type
 | 
					 * @param {string} type
 | 
				
			||||||
 * @param {string} stateKey
 | 
					 * @param {string} stateKey
 | 
				
			||||||
 * @returns {Promise<import("../types").R.EventSent>}
 | 
					 * @returns {Promise<string>} event ID
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function sendState(roomID, type, stateKey, content) {
 | 
					async function sendState(roomID, type, stateKey, content, mxid) {
 | 
				
			||||||
   assert.ok(type)
 | 
					   assert.ok(type)
 | 
				
			||||||
   assert.ok(stateKey)
 | 
					   assert.ok(stateKey)
 | 
				
			||||||
   return mreq.mreq("PUT", `/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, content)
 | 
					   /** @type {import("../types").R.EventSent} */
 | 
				
			||||||
 | 
					   const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content)
 | 
				
			||||||
 | 
					   return root.event_id
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function sendEvent(roomID, type, content, mxid) {
 | 
				
			||||||
 | 
					   /** @type {import("../types").R.EventSent} */
 | 
				
			||||||
 | 
					   const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content)
 | 
				
			||||||
 | 
					   return root.event_id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.path = path
 | 
				
			||||||
module.exports.register = register
 | 
					module.exports.register = register
 | 
				
			||||||
module.exports.createRoom = createRoom
 | 
					module.exports.createRoom = createRoom
 | 
				
			||||||
 | 
					module.exports.joinRoom = joinRoom
 | 
				
			||||||
 | 
					module.exports.inviteToRoom = inviteToRoom
 | 
				
			||||||
module.exports.getAllState = getAllState
 | 
					module.exports.getAllState = getAllState
 | 
				
			||||||
module.exports.sendState = sendState
 | 
					module.exports.sendState = sendState
 | 
				
			||||||
 | 
					module.exports.sendEvent = sendEvent
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								matrix/api.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								matrix/api.test.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					const {test} = require("supertape")
 | 
				
			||||||
 | 
					const assert = require("assert")
 | 
				
			||||||
 | 
					const {path} = require("./api")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("api path: no change for plain path", t => {
 | 
				
			||||||
 | 
						t.equal(path("/hello/world"), "/hello/world")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("api path: add mxid to the URL", t => {
 | 
				
			||||||
 | 
						t.equal(path("/hello/world", "12345"), "/hello/world?user_id=12345")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("api path: empty path with mxid", t => {
 | 
				
			||||||
 | 
						t.equal(path("", "12345"), "/?user_id=12345")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("api path: existing query parameters with mxid", t => {
 | 
				
			||||||
 | 
						t.equal(path("/hello/world?foo=bar&baz=qux", "12345"), "/hello/world?foo=bar&baz=qux&user_id=12345")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("api path: real world mxid", t => {
 | 
				
			||||||
 | 
						t.equal(path("/hello/world", "@cookie_monster:cadence.moe"), "/hello/world?user_id=%40cookie_monster%3Acadence.moe")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
| 
						 | 
					@ -16,8 +16,6 @@ class MatrixServerError extends Error {
 | 
				
			||||||
		this.data = data
 | 
							this.data = data
 | 
				
			||||||
		/** @type {string} */
 | 
							/** @type {string} */
 | 
				
			||||||
		this.errcode = data.errcode
 | 
							this.errcode = data.errcode
 | 
				
			||||||
		/** @type {string} */
 | 
					 | 
				
			||||||
		this.error = data.error
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let now = Date.now()
 | 
					let now = Date.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = function makeTxnId() {
 | 
					module.exports.makeTxnId = function makeTxnId() {
 | 
				
			||||||
	return now++
 | 
						return now++
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								stdin.js
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								stdin.js
									
										
									
									
									
								
							| 
						 | 
					@ -8,6 +8,7 @@ const { discord, config, sync, db } = passthrough
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createSpace = sync.require("./d2m/actions/create-space")
 | 
					const createSpace = sync.require("./d2m/actions/create-space")
 | 
				
			||||||
const createRoom = sync.require("./d2m/actions/create-room")
 | 
					const createRoom = sync.require("./d2m/actions/create-room")
 | 
				
			||||||
 | 
					const registerUser = sync.require("./d2m/actions/register-user")
 | 
				
			||||||
const mreq = sync.require("./matrix/mreq")
 | 
					const mreq = sync.require("./matrix/mreq")
 | 
				
			||||||
const api = sync.require("./matrix/api")
 | 
					const api = sync.require("./matrix/api")
 | 
				
			||||||
const guildID = "112760669178241024"
 | 
					const guildID = "112760669178241024"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,3 +14,4 @@ Object.assign(passthrough, { config, sync, db })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require("../d2m/actions/create-room.test")
 | 
					require("../d2m/actions/create-room.test")
 | 
				
			||||||
require("../d2m/converters/user-to-mxid.test")
 | 
					require("../d2m/converters/user-to-mxid.test")
 | 
				
			||||||
 | 
					require("../matrix/api.test")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								types.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								types.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4,6 +4,7 @@ export type AppServiceRegistrationConfig = {
 | 
				
			||||||
	hs_token: string
 | 
						hs_token: string
 | 
				
			||||||
	url: string
 | 
						url: string
 | 
				
			||||||
	sender_localpart: string
 | 
						sender_localpart: string
 | 
				
			||||||
 | 
						namespace_prefix: string
 | 
				
			||||||
	protocols: [string]
 | 
						protocols: [string]
 | 
				
			||||||
	rate_limited: boolean
 | 
						rate_limited: boolean
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -43,6 +44,10 @@ namespace R {
 | 
				
			||||||
		room_id: string
 | 
							room_id: string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export type RoomJoined = {
 | 
				
			||||||
 | 
							room_id: string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export type FileUploaded = {
 | 
						export type FileUploaded = {
 | 
				
			||||||
		content_uri: string
 | 
							content_uri: string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue