Compare commits

..

19 commits

Author SHA1 Message Date
ab69eab8a4 Fix tests for new link space error message 2025-11-01 21:01:15 +09:00
717dc185e5 Misc. fixes for remote join 2025-11-01 20:50:36 +09:00
7932f8af85 Add "please try invite" message when joinRoom in /api/link-space fails 2025-11-01 20:50:36 +09:00
0776cc6ccd Fill in more of reg for other people to test with 2025-11-01 20:50:36 +09:00
e7b4dfea9c Fix /api/link-space joinRoom() for remote spaces 2025-11-01 20:50:36 +09:00
ea08e16963 Update tests for new types and code path 2025-11-01 20:50:36 +09:00
1efd301e1d Cleanup 2025-11-01 20:50:36 +09:00
dc7b444086 Fix matrix api joinRoom() for remote rooms
When using self-service mode and trying to link with a remote matrix
room (room not in the same HS as the bridge user), then we need to add
the "via" HSs to join the room with, or else it fails.

We get it from the "m.space.child" in the "children_state" of the space
hierarchy.

It seems like the "via" information can also be stored in the
"m.space.parent" in the states of the room, but hopefully this shouldn't
be needed in sane implementations
2025-11-01 20:50:36 +09:00
255e166e8c Better message when remote emojis unavailable 2025-10-31 16:22:32 +13:00
d4f4664c25 Fix retrying m->d message deletions 2025-10-23 23:09:14 +11:00
3de762d428 Fix stickers that don't provide content type 2025-10-12 12:17:20 -06:00
cffd3c9f2e Fix converting discord channel links 2025-10-10 12:26:01 -06:00
5b7433de32 Make tests time zone independent 2025-10-07 14:09:50 -05:00
7916f82b55 Change thread started message (closes #61) 2025-10-07 14:09:42 -05:00
7905802825 Allow customising port in setup 2025-10-07 00:48:06 -05:00
3891506163 Roll back snowtransfer to avoid issue with pins 2025-10-07 00:46:44 -05:00
d8e6de62e5 Keep sim_proxy profile data up to date 2025-09-08 16:26:16 +12:00
5a152b87b8 I guess mentions is an optional property too 2025-09-08 12:37:19 +12:00
a968bacffd Update discord-markdown
Interpret channel URLs the same as a channel #mention
2025-09-03 00:00:02 +12:00
18 changed files with 181 additions and 60 deletions

66
package-lock.json generated
View file

@ -10,7 +10,7 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@chriscdn/promise-semaphore": "^3.0.1", "@chriscdn/promise-semaphore": "^3.0.1",
"@cloudrac3r/discord-markdown": "^2.6.5", "@cloudrac3r/discord-markdown": "^2.6.7",
"@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/giframe": "^0.4.3",
"@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/html-template-tag": "^5.0.1",
"@cloudrac3r/in-your-element": "^1.1.1", "@cloudrac3r/in-your-element": "^1.1.1",
@ -119,9 +119,9 @@
} }
}, },
"node_modules/@chriscdn/promise-semaphore": { "node_modules/@chriscdn/promise-semaphore": {
"version": "3.0.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-3.1.1.tgz",
"integrity": "sha512-fVlCnoYE4hDzpcYRPtmN7dmcpmd2zxyPWjyfjIKI9Y+gsI7rwZSkjtuwMi8HFtlkSmNh8L7Zr37hdqeL13sYrw==", "integrity": "sha512-ALLLLYlPfd/QZLptcVi6HQRK1zaCDWZoqYYw+axLmCatFs4gVTSZ5nqlyxwFe4qwR/K84HvOMa9hxda881FqMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@cloudcmd/stub": { "node_modules/@cloudcmd/stub": {
@ -225,9 +225,9 @@
} }
}, },
"node_modules/@cloudrac3r/discord-markdown": { "node_modules/@cloudrac3r/discord-markdown": {
"version": "2.6.5", "version": "2.6.7",
"resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.5.tgz", "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.7.tgz",
"integrity": "sha512-B4uQNsyva5JNW0CVYkcunMQwWfrok1Hd5FYww/cWcvb98zp/pJdJfE3hoRl9EbnxNK2l62IJQ9j8HmssMFHJ9Q==", "integrity": "sha512-bWLmBYWaNEDcQfZHDz4jaAxLKA9161ruEnHo3ms6kfRw8uYku/Uz7U1xTmQ2dQF/q1PiuBvM9I37pLiotlQj8A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"simple-markdown": "^0.7.3" "simple-markdown": "^0.7.3"
@ -949,9 +949,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@stackoverflow/stacks": { "node_modules/@stackoverflow/stacks": {
"version": "2.8.3", "version": "2.8.4",
"resolved": "https://registry.npmjs.org/@stackoverflow/stacks/-/stacks-2.8.3.tgz", "resolved": "https://registry.npmjs.org/@stackoverflow/stacks/-/stacks-2.8.4.tgz",
"integrity": "sha512-ZGBeuXJC7moK/f+lgl2dCAW85etD/RO0DNubocdH2qzpJMuuGXX0GMeEAfrTOe+B00I8E1OqTnS1cpkqGdHBdQ==", "integrity": "sha512-FfA7Bw7a0AQrMw3/bG6G4BUrZ698F7Cdk6HkR9T7jdaufORkiX5d16wI4j4b5Sqm1FwkaZAF+ZSKLL1w0tAsew==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@hotwired/stimulus": "^3.2.2", "@hotwired/stimulus": "^3.2.2",
@ -1107,9 +1107,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.17.1", "version": "22.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz",
"integrity": "sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==", "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1452,18 +1452,30 @@
} }
}, },
"node_modules/cloudstorm": { "node_modules/cloudstorm": {
"version": "0.14.0", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.14.0.tgz", "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.14.1.tgz",
"integrity": "sha512-EgjMGxb2Z+L6Acti6DzL/bEbR495AIqPThyW4DaG6Jpvd0ZuM5eC13EiyxV8wlqAME612QO2LjqbhkdXn/327Q==", "integrity": "sha512-x95WCKg818E1rE1Ru45NPD3RoIq0pg3WxwvF0GE7Eq07pAeLcjSRqM1lUmbmfjdOqZrWdSRYA1NETVZ8QhVrIA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"discord-api-types": "^0.38.12", "discord-api-types": "^0.38.21",
"snowtransfer": "^0.14.2" "snowtransfer": "^0.15.0"
}, },
"engines": { "engines": {
"node": ">=22.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/cloudstorm/node_modules/snowtransfer": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.15.0.tgz",
"integrity": "sha512-kEDGKtFiH5nSkHsDZonEUuDx99lUasJoZ7AGrgvE8HzVG59vjvqc//C+pjWj4DuJqTj4Q+Z1L/M/MYNim8F2VA==",
"license": "MIT",
"dependencies": {
"discord-api-types": "^0.38.21"
},
"engines": {
"node": ">=16.15.0"
}
},
"node_modules/color": { "node_modules/color": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@ -1616,9 +1628,9 @@
} }
}, },
"node_modules/discord-api-types": { "node_modules/discord-api-types": {
"version": "0.38.19", "version": "0.38.22",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.19.tgz", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.22.tgz",
"integrity": "sha512-NUNMTgjYrgxt7wrTNEqnEez4hIAYbfyBpsjxT5gW7+82GjQCPDZvN+em6t+4/P5kGWnnwDa4ci070BV7eI6GbA==", "integrity": "sha512-2gnYrgXN3yTlv2cKBISI/A8btZwsSZLwKpIQXeI1cS8a7W7wP3sFVQOm3mPuuinTD8jJCKGPGNH399zE7Un1kA==",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"scripts/actions/documentation" "scripts/actions/documentation"
@ -3076,9 +3088,9 @@
} }
}, },
"node_modules/tar-fs": { "node_modules/tar-fs": {
"version": "2.1.3", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chownr": "^1.1.1", "chownr": "^1.1.1",
@ -3447,9 +3459,9 @@
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "4.0.17", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz",
"integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"

View file

@ -19,7 +19,7 @@
}, },
"dependencies": { "dependencies": {
"@chriscdn/promise-semaphore": "^3.0.1", "@chriscdn/promise-semaphore": "^3.0.1",
"@cloudrac3r/discord-markdown": "^2.6.5", "@cloudrac3r/discord-markdown": "^2.6.7",
"@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/giframe": "^0.4.3",
"@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/html-template-tag": "^5.0.1",
"@cloudrac3r/in-your-element": "^1.1.1", "@cloudrac3r/in-your-element": "^1.1.1",

View file

@ -120,16 +120,28 @@ function defineEchoHandler() {
/** @type {string} */ // @ts-ignore /** @type {string} */ // @ts-ignore
const serverOrigin = await serverOriginPrompt.run() const serverOrigin = await serverOriginPrompt.run()
console.log("OOYE has its own web server. It needs to be accessible on the public internet.")
console.log("What port would you like OOYE to use? You can connect your reverse proxy to this port later.")
/** @type {{socket: string | number}} */
const portResponse = await prompt({
type: "input",
name: "socket",
message: "Web server port",
initial: "6693"
})
portResponse.socket = +portResponse.socket || portResponse.socket // convert to number if numeric
const app = createApp() const app = createApp()
app.use(defineEchoHandler()) app.use(defineEchoHandler())
const server = createServer(toNodeListener(app)) const server = createServer(toNodeListener(app))
await server.listen(6693) await server.listen(portResponse.socket)
console.log("OOYE has its own web server. It needs to be accessible on the public internet.") console.log("Now you need to enter a public URL that OOYE's web server will live on.")
console.log("You need to enter a public URL where you will be able to host this web server.") console.log("Set up your reverse proxy so that this URL accesses OOYE.")
console.log("OOYE listens on localhost:6693, so you will probably have to set up a reverse proxy.")
console.log("Examples: https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md#appendix") console.log("Examples: https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md#appendix")
console.log("Now listening on port 6693. Feel free to send some test requests.") if (typeof portResponse.socket === "number") {
console.log(`Now listening on http://localhost:${portResponse.socket}. Feel free to send some test requests.`)
}
/** @type {{bridge_origin: string}} */ /** @type {{bridge_origin: string}} */
const bridgeOriginResponse = await prompt({ const bridgeOriginResponse = await prompt({
type: "input", type: "input",
@ -255,6 +267,7 @@ function defineEchoHandler() {
reg = { reg = {
...template, ...template,
url: bridgeOriginResponse.bridge_origin, url: bridgeOriginResponse.bridge_origin,
...portResponse,
ooye: { ooye: {
...template.ooye, ...template.ooye,
...bridgeOriginResponse, ...bridgeOriginResponse,

View file

@ -146,7 +146,7 @@ async function syncUser(messageID, author, roomID, shouldActuallySync) {
try { try {
// API lookup // API lookup
var pkMessage = await fetchMessage(messageID) var pkMessage = await fetchMessage(messageID)
db.prepare("INSERT OR IGNORE INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES (?, ?, ?)").run(pkMessage.member.uuid, pkMessage.sender, author.username) db.prepare("REPLACE INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES (?, ?, ?)").run(pkMessage.member.uuid, pkMessage.sender, author.username)
} catch (e) { } catch (e) {
// Fall back to offline cache // Fall back to offline cache
const senderMxid = from("sim_proxy").join("sim", "user_id").join("sim_member", "mxid").where({displayname: author.username, room_id: roomID}).pluck("mxid").get() const senderMxid = from("sim_proxy").join("sim", "user_id").join("sim_member", "mxid").where({displayname: author.username, room_id: roomID}).pluck("mxid").get()

View file

@ -33,9 +33,10 @@ function getDiscordParseCallbacks(message, guild, useHTML) {
user: node => { user: node => {
const mxid = select("sim", "mxid", {user_id: node.id}).pluck().get() const mxid = select("sim", "mxid", {user_id: node.id}).pluck().get()
const interaction = message.interaction_metadata || message.interaction const interaction = message.interaction_metadata || message.interaction
const username = message.mentions.find(ment => ment.id === node.id)?.username const username = message.mentions?.find(ment => ment.id === node.id)?.username
|| message.referenced_message?.mentions.find(ment => ment.id === node.id)?.username || message.referenced_message?.mentions?.find(ment => ment.id === node.id)?.username
|| (interaction?.user.id === node.id ? interaction.user.username : null) || (interaction?.user.id === node.id ? interaction.user.username : null)
|| (message.author.id === node.id ? message.author.username : null)
|| node.id || node.id
if (mxid && useHTML) { if (mxid && useHTML) {
return `<a href="https://matrix.to/#/${mxid}">@${username}</a>` return `<a href="https://matrix.to/#/${mxid}">@${username}</a>`
@ -407,13 +408,13 @@ async function messageToEvent(message, guild, options = {}, di) {
async function transformParsedVia(parsed) { async function transformParsedVia(parsed) {
for (const node of parsed) { for (const node of parsed) {
if (node.type === "discordChannel") { if (node.type === "discordChannel" || node.type === "discordChannelLink") {
node.row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get() node.row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get()
if (node.row?.room_id) { if (node.row?.room_id) {
node.via = await getViaServersMemo(node.row.room_id) node.via = await getViaServersMemo(node.row.room_id)
} }
} }
;for (const maybeChildNodesArray of [node, node.content, node.items]) { for (const maybeChildNodesArray of [node, node.content, node.items]) {
if (Array.isArray(maybeChildNodesArray)) { if (Array.isArray(maybeChildNodesArray)) {
await transformParsedVia(maybeChildNodesArray) await transformParsedVia(maybeChildNodesArray)
} }
@ -610,7 +611,7 @@ async function messageToEvent(message, guild, options = {}, di) {
const event = invite.guild_scheduled_event const event = invite.guild_scheduled_event
if (!event) continue // the event ID provided was not valid if (!event) continue // the event ID provided was not valid
const formatter = new Intl.DateTimeFormat("en-NZ", {month: "long", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "shortGeneric"}) // 9 June at 3:00 pm NZT const formatter = new Intl.DateTimeFormat("en-NZ", {month: "long", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "shortGeneric", timeZone: reg.ooye.time_zone}) // 9 June at 3:00 pm NZT
const rep = new mxUtils.MatrixStringBuilder() const rep = new mxUtils.MatrixStringBuilder()
// Add time // Add time

View file

@ -100,6 +100,44 @@ test("message2event: simple room mention", async t => {
t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each")
}) })
test("message2event: simple room link", async t => {
let called = 0
const events = await messageToEvent(data.message.simple_room_link, data.guild.general, {}, {
api: {
async getStateEvent(roomID, type, key) {
called++
t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe")
t.equal(type, "m.room.power_levels")
t.equal(key, "")
return {
users: {
"@_ooye_bot:cadence.moe": 100
}
}
},
async getJoinedMembers(roomID) {
called++
t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe")
return {
joined: {
"@_ooye_bot:cadence.moe": {display_name: null, avatar_url: null},
"@user:matrix.org": {display_name: null, avatar_url: null}
}
}
}
}
})
t.deepEqual(events, [{
$type: "m.room.message",
"m.mentions": {},
msgtype: "m.text",
body: "#worm-farm",
format: "org.matrix.custom.html",
formatted_body: '<a href="https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe?via=cadence.moe&via=matrix.org">#worm-farm</a>'
}])
t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each")
})
test("message2event: nicked room mention", async t => { test("message2event: nicked room mention", async t => {
let called = 0 let called = 0
const events = await messageToEvent(data.message.nicked_room_mention, data.guild.general, {}, { const events = await messageToEvent(data.message.nicked_room_mention, data.guild.general, {}, {

View file

@ -32,13 +32,10 @@ async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thr
const template = creatorMxid ? "started a thread:" : "Thread started:" const template = creatorMxid ? "started a thread:" : "Thread started:"
const via = await mxUtils.getViaServersQuery(threadRoomID, di.api) const via = await mxUtils.getViaServersQuery(threadRoomID, di.api)
let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}?${via.toString()}` let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}?${via.toString()}`
let html = `${template} <a href="https://matrix.to/#/${threadRoomID}?${via.toString()}">${thread.name}</a>`
return { return {
msgtype, msgtype,
body, body,
format: "org.matrix.custom.html",
formatted_body: html,
"m.mentions": {}, "m.mentions": {},
...context ...context
} }

View file

@ -55,8 +55,6 @@ test("thread2announcement: no known creator, no branched from event", async t =>
t.deepEqual(content, { t.deepEqual(content, {
msgtype: "m.text", msgtype: "m.text",
body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org",
format: "org.matrix.custom.html",
formatted_body: `Thread started: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"m.mentions": {} "m.mentions": {}
}) })
}) })
@ -69,8 +67,6 @@ test("thread2announcement: known creator, no branched from event", async t => {
t.deepEqual(content, { t.deepEqual(content, {
msgtype: "m.emote", msgtype: "m.emote",
body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org",
format: "org.matrix.custom.html",
formatted_body: `started a thread: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"m.mentions": {} "m.mentions": {}
}) })
}) })
@ -95,8 +91,6 @@ test("thread2announcement: no known creator, branched from discord event", async
t.deepEqual(content, { t.deepEqual(content, {
msgtype: "m.text", msgtype: "m.text",
body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org",
format: "org.matrix.custom.html",
formatted_body: `Thread started: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"m.mentions": {}, "m.mentions": {},
"m.relates_to": { "m.relates_to": {
"m.in_reply_to": { "m.in_reply_to": {
@ -126,8 +120,6 @@ test("thread2announcement: known creator, branched from discord event", async t
t.deepEqual(content, { t.deepEqual(content, {
msgtype: "m.emote", msgtype: "m.emote",
body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org",
format: "org.matrix.custom.html",
formatted_body: `started a thread: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"m.mentions": {}, "m.mentions": {},
"m.relates_to": { "m.relates_to": {
"m.in_reply_to": { "m.in_reply_to": {
@ -157,8 +149,6 @@ test("thread2announcement: no known creator, branched from matrix event", async
t.deepEqual(content, { t.deepEqual(content, {
msgtype: "m.text", msgtype: "m.text",
body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org",
format: "org.matrix.custom.html",
formatted_body: `Thread started: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"m.mentions": { "m.mentions": {
user_ids: ["@cadence:cadence.moe"] user_ids: ["@cadence:cadence.moe"]
}, },

View file

@ -7,6 +7,8 @@ const {sync} = require("../../passthrough")
const emojiSheetConverter = sync.require("../converters/emoji-sheet") const emojiSheetConverter = sync.require("../converters/emoji-sheet")
/** @type {import("../../matrix/api")} */ /** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api") const api = sync.require("../../matrix/api")
/** @type {import("../../matrix/mreq")} */
const mreq = sync.require("../../matrix/mreq")
/** /**
* Downloads the emoji from the web and converts to uncompressed PNG data. * Downloads the emoji from the web and converts to uncompressed PNG data.
@ -19,6 +21,10 @@ async function getAndConvertEmoji(mxc) {
// If we were using connection pooling, we would be forced to download the entire GIF. // If we were using connection pooling, we would be forced to download the entire GIF.
// So we set no agent to ensure we are not connection pooling. // So we set no agent to ensure we are not connection pooling.
const res = await api.getMedia(mxc, {signal: abortController.signal}) const res = await api.getMedia(mxc, {signal: abortController.signal})
if (res.status !== 200) {
const root = await res.json()
throw new mreq.MatrixServerError(root, {mxc})
}
const readable = stream.Readable.fromWeb(res.body) const readable = stream.Readable.fromWeb(res.body)
return emojiSheetConverter.convertImageStream(readable, () => { return emojiSheetConverter.convertImageStream(readable, () => {
abortController.abort() abortController.abort()

View file

@ -13,10 +13,12 @@ const utils = sync.require("../converters/utils")
*/ */
async function deleteMessage(event) { async function deleteMessage(event) {
const rows = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").where({event_id: event.redacts}).all() const rows = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").where({event_id: event.redacts}).all()
if (!rows.length) return
for (const row of rows) { for (const row of rows) {
db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(row.message_id)
await discord.snow.channel.deleteMessage(row.channel_id, row.message_id, event.content.reason) await discord.snow.channel.deleteMessage(row.channel_id, row.message_id, event.content.reason)
db.prepare("DELETE FROM event_message WHERE message_id = ?").run(row.message_id)
} }
db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(rows[0].message_id)
} }
/** /**

View file

@ -5,9 +5,8 @@ const {join} = require("path")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {id} = require("../../../addbot")
async function setupEmojis() { async function setupEmojis() {
const {id} = require("../../../addbot")
const {discord, db} = passthrough const {discord, db} = passthrough
const emojis = await discord.snow.assets.getAppEmojis(id) const emojis = await discord.snow.assets.getAppEmojis(id)
for (const name of ["L1", "L2"]) { for (const name of ["L1", "L2"]) {

View file

@ -11,6 +11,7 @@ const entities = require("entities")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {sync, db, discord, select, from} = passthrough const {sync, db, discord, select, from} = passthrough
const {reg} = require("../../matrix/read-registration")
/** @type {import("../converters/utils")} */ /** @type {import("../converters/utils")} */
const mxUtils = sync.require("../converters/utils") const mxUtils = sync.require("../converters/utils")
/** @type {import("../../discord/utils")} */ /** @type {import("../../discord/utils")} */
@ -238,7 +239,8 @@ function convertEmoji(mxcUrl, nameForGuess, allowSpriteSheetIndicator, allowLink
if (!found) row = null if (!found) row = null
} }
// Or, if we don't have an emoji right now, we search for the name instead. // Or, if we don't have an emoji right now, we search for the name instead.
if (!row && nameForGuess) { const isLocalMxc = mxcUrl?.match(/^mxc:\/\/([^/]+)/)?.[1] === reg.ooye.server_name
if (!row && nameForGuess && isLocalMxc) {
const nameForGuessLower = nameForGuess.toLowerCase() const nameForGuessLower = nameForGuess.toLowerCase()
for (const guild of discord.guilds.values()) { for (const guild of discord.guilds.values()) {
/** @type {{name: string, id: string, animated: number}[]} */ /** @type {{name: string, id: string, animated: number}[]} */

View file

@ -391,7 +391,9 @@ async function getMedia(mxc, init = {}) {
}, },
...init ...init
}) })
if (init.method !== "HEAD") {
assert(res.body) assert(res.body)
}
// @ts-ignore // @ts-ignore
return res return res
} }

1
src/types.d.ts vendored
View file

@ -31,6 +31,7 @@ export type AppServiceRegistrationConfig = {
discord_origin?: string discord_origin?: string
discord_cdn_origin?: string, discord_cdn_origin?: string,
web_password: string web_password: string
time_zone?: string
} }
old_bridge?: { old_bridge?: {
as_token: string as_token: string

View file

@ -77,7 +77,7 @@ test("web link space: check that OOYE is joined", async t => {
} }
} }
})) }))
t.equal(error.data, "M_FORBIDDEN - not allowed to join I guess") t.equal(error.data, "Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: M_FORBIDDEN - not allowed to join I guess)")
t.equal(called, 1) t.equal(called, 1)
}) })

View file

@ -1398,6 +1398,63 @@ module.exports = {
attachments: [], attachments: [],
guild_id: "112760669178241024" guild_id: "112760669178241024"
}, },
simple_room_link: {
type: 0,
tts: false,
timestamp: "2023-07-10T20:04:25.939000+00:00",
referenced_message: null,
pinned: false,
nonce: "1128054139385806848",
mentions: [],
mention_roles: [],
mention_everyone: false,
member: {
roles: [
"112767366235959296", "118924814567211009",
"204427286542417920", "199995902742626304",
"222168467627835392", "238028326281805825",
"259806643414499328", "265239342648131584",
"271173313575780353", "287733611912757249",
"225744901915148298", "305775031223320577",
"318243902521868288", "348651574924541953",
"349185088157777920", "378402925128712193",
"392141548932038658", "393912152173576203",
"482860581670486028", "495384759074160642",
"638988388740890635", "373336013109461013",
"530220455085473813", "454567553738473472",
"790724320824655873", "1123518980456452097",
"1040735082610167858", "695946570482450442",
"1123460940935991296", "849737964090556488"
],
premium_since: null,
pending: false,
nick: null,
mute: false,
joined_at: "2015-11-11T09:55:40.321000+00:00",
flags: 0,
deaf: false,
communication_disabled_until: null,
avatar: null
},
id: "1128054143064494233",
flags: 0,
embeds: [],
edited_timestamp: null,
content: "https://discord.com/channels/112760669178241024/1100319550446252084",
components: [],
channel_id: "266767590641238027",
author: {
username: "kumaccino",
public_flags: 128,
id: "113340068197859328",
global_name: "kumaccino",
discriminator: "0",
avatar_decoration: null,
avatar: "b48302623a12bc7c59a71328f72ccb39"
},
attachments: [],
guild_id: "112760669178241024"
},
nicked_room_mention: { nicked_room_mention: {
type: 0, type: 0,
tts: false, tts: false,

View file

@ -27,6 +27,7 @@ reg.namespaces = {
aliases: [{regex: "#_ooye_.*:cadence.moe", exclusive: true}] aliases: [{regex: "#_ooye_.*:cadence.moe", exclusive: true}]
} }
reg.ooye.bridge_origin = "https://bridge.example.org" reg.ooye.bridge_origin = "https://bridge.example.org"
reg.ooye.time_zone = "Pacific/Auckland"
const sync = new HeatSync({watchFS: false}) const sync = new HeatSync({watchFS: false})