Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd1bf3801b | |||
| 0f8fee2d33 | |||
| e19703ef03 | |||
| 42b331135d |
32 changed files with 103 additions and 297 deletions
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2024",
|
||||
"module": "nodenext",
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"useUnknownInCatchVariables": false
|
||||
}
|
||||
}
|
||||
36
package-lock.json
generated
36
package-lock.json
generated
|
|
@ -10,7 +10,7 @@
|
|||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@chriscdn/promise-semaphore": "^3.0.1",
|
||||
"@cloudrac3r/discord-markdown": "^2.6.7",
|
||||
"@cloudrac3r/discord-markdown": "^2.6.6",
|
||||
"@cloudrac3r/giframe": "^0.4.3",
|
||||
"@cloudrac3r/html-template-tag": "^5.0.1",
|
||||
"@cloudrac3r/in-your-element": "^1.1.1",
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"lru-cache": "^11.0.2",
|
||||
"prettier-bytes": "^1.0.4",
|
||||
"sharp": "^0.33.4",
|
||||
"snowtransfer": "^0.14.2",
|
||||
"snowtransfer": "^0.15.0",
|
||||
"stream-mime-type": "^1.0.2",
|
||||
"try-to-catch": "^3.0.1",
|
||||
"uqr": "^0.1.2",
|
||||
|
|
@ -225,9 +225,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@cloudrac3r/discord-markdown": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.7.tgz",
|
||||
"integrity": "sha512-bWLmBYWaNEDcQfZHDz4jaAxLKA9161ruEnHo3ms6kfRw8uYku/Uz7U1xTmQ2dQF/q1PiuBvM9I37pLiotlQj8A==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.6.tgz",
|
||||
"integrity": "sha512-4FNO7WmACPvcTrQjeLQLr9WRuP7JDUVUGFrRJvmAjiMs2UlUAsShfSRuU2SCqz3QqmX8vyJ06wy2hkjTTyRtbw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"simple-markdown": "^0.7.3"
|
||||
|
|
@ -1464,18 +1464,6 @@
|
|||
"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": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
|
|
@ -2731,12 +2719,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/snowtransfer": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.14.2.tgz",
|
||||
"integrity": "sha512-Fi8OdRmaIgeCj58oVej+tQAoY2I+Xp/6PAYV8X93jE/2E6Anc87SbTbDV6WZXCnuzTQz3gty8JOGz02qI7Qs9A==",
|
||||
"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.8"
|
||||
"discord-api-types": "^0.38.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.15.0"
|
||||
|
|
@ -3088,9 +3076,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
|
||||
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@chriscdn/promise-semaphore": "^3.0.1",
|
||||
"@cloudrac3r/discord-markdown": "^2.6.7",
|
||||
"@cloudrac3r/discord-markdown": "^2.6.6",
|
||||
"@cloudrac3r/giframe": "^0.4.3",
|
||||
"@cloudrac3r/html-template-tag": "^5.0.1",
|
||||
"@cloudrac3r/in-your-element": "^1.1.1",
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"lru-cache": "^11.0.2",
|
||||
"prettier-bytes": "^1.0.4",
|
||||
"sharp": "^0.33.4",
|
||||
"snowtransfer": "^0.14.2",
|
||||
"snowtransfer": "^0.15.0",
|
||||
"stream-mime-type": "^1.0.2",
|
||||
"try-to-catch": "^3.0.1",
|
||||
"uqr": "^0.1.2",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ if (!channelID) {
|
|||
|
||||
const assert = require("assert/strict")
|
||||
const sqlite = require("better-sqlite3")
|
||||
const path = require("path")
|
||||
const backfill = new sqlite("scripts/backfill.db")
|
||||
backfill.prepare("CREATE TABLE IF NOT EXISTS backfill (channel_id TEXT NOT NULL, message_id INTEGER NOT NULL, PRIMARY KEY (channel_id, message_id))").run()
|
||||
|
||||
|
|
@ -19,9 +20,10 @@ const HeatSync = require("heatsync")
|
|||
|
||||
const {reg} = require("../src/matrix/read-registration")
|
||||
const passthrough = require("../src/passthrough")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
const db = new sqlite("ooye.db")
|
||||
const db = getDatabase()
|
||||
Object.assign(passthrough, {sync, db})
|
||||
|
||||
const DiscordClient = require("../src/d2m/discord-client")
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
// Trigger the database migration flow and exit after committing.
|
||||
// You can use this to run migrations locally and check the result using sqlitebrowser.
|
||||
|
||||
const sqlite = require("better-sqlite3")
|
||||
|
||||
const passthrough = require("../src/passthrough")
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
const migrate = require("../src/db/migrate")
|
||||
|
||||
Object.assign(passthrough, {db})
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ const oldAT = reg.old_bridge.as_token
|
|||
const newAT = reg.as_token
|
||||
|
||||
const oldDB = new sqlite(reg.old_bridge.database)
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS half_shot_migration (
|
||||
discord_channel TEXT NOT NULL,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
const HeatSync = require("heatsync")
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
|
||||
const sqlite = require("better-sqlite3")
|
||||
const db = new sqlite("db/ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
|
||||
const passthrough = require("../src/passthrough")
|
||||
Object.assign(passthrough, {db, sync})
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env node
|
||||
// @ts-check
|
||||
|
||||
const sqlite = require("better-sqlite3")
|
||||
const HeatSync = require("heatsync")
|
||||
|
||||
const {reg} = require("../src/matrix/read-registration")
|
||||
const passthrough = require("../src/passthrough")
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
// @ts-check
|
||||
|
||||
const sqlite = require("better-sqlite3")
|
||||
const HeatSync = require("heatsync")
|
||||
|
||||
const passthrough = require("../src/passthrough")
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ if (fs.existsSync("db")) {
|
|||
}
|
||||
|
||||
const passthrough = require("../src/passthrough")
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
const migrate = require("../src/db/migrate")
|
||||
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
|
|
@ -120,28 +121,16 @@ function defineEchoHandler() {
|
|||
/** @type {string} */ // @ts-ignore
|
||||
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()
|
||||
app.use(defineEchoHandler())
|
||||
const server = createServer(toNodeListener(app))
|
||||
await server.listen(portResponse.socket)
|
||||
await server.listen(6693)
|
||||
|
||||
console.log("Now you need to enter a public URL that OOYE's web server will live on.")
|
||||
console.log("Set up your reverse proxy so that this URL accesses OOYE.")
|
||||
console.log("OOYE has its own web server. It needs to be accessible on the public internet.")
|
||||
console.log("You need to enter a public URL where you will be able to host this web server.")
|
||||
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")
|
||||
if (typeof portResponse.socket === "number") {
|
||||
console.log(`Now listening on http://localhost:${portResponse.socket}. Feel free to send some test requests.`)
|
||||
}
|
||||
console.log("Now listening on port 6693. Feel free to send some test requests.")
|
||||
/** @type {{bridge_origin: string}} */
|
||||
const bridgeOriginResponse = await prompt({
|
||||
type: "input",
|
||||
|
|
@ -267,7 +256,6 @@ function defineEchoHandler() {
|
|||
reg = {
|
||||
...template,
|
||||
url: bridgeOriginResponse.bridge_origin,
|
||||
...portResponse,
|
||||
ooye: {
|
||||
...template.ooye,
|
||||
...bridgeOriginResponse,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
const {createServer} = require("http")
|
||||
const EventEmitter = require("events")
|
||||
const {createApp, createRouter, toNodeListener} = require("h3")
|
||||
const sqlite = require("better-sqlite3")
|
||||
const migrate = require("../src/db/migrate")
|
||||
const HeatSync = require("heatsync")
|
||||
|
||||
const {reg} = require("../src/matrix/read-registration")
|
||||
const passthrough = require("../src/passthrough")
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase()
|
||||
|
||||
const sync = new HeatSync()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
// @ts-check
|
||||
|
||||
const sqlite = require("better-sqlite3")
|
||||
const db = new sqlite("ooye.db", {fileMustExist: true})
|
||||
const {getDatabase} = require("../src/db/database")
|
||||
const db = getDatabase({fileMustExist: true})
|
||||
db.pragma("journal_mode = wal")
|
||||
db.close()
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ const PRIVACY_ENUMS = {
|
|||
|
||||
const DEFAULT_PRIVACY_LEVEL = 0
|
||||
|
||||
const READ_ONLY_ROOM_EVENTS_DEFAULT_POWER = 50
|
||||
function getReadOnlyRoomEventsDefaultPower() {
|
||||
return reg.ooye.read_only_room_events_default_power ?? 50
|
||||
}
|
||||
|
||||
/** @type {Map<string, Promise<string>>} channel ID -> Promise<room ID> */
|
||||
const inflightRoomCreate = new Map()
|
||||
|
|
@ -148,7 +150,7 @@ async function channelToKState(channel, guild, di) {
|
|||
"m.room.join_rules/": join_rules,
|
||||
/** @type {Ty.Event.M_Power_Levels} */
|
||||
"m.room.power_levels/": {
|
||||
events_default: everyoneCanSend ? 0 : READ_ONLY_ROOM_EVENTS_DEFAULT_POWER,
|
||||
events_default: everyoneCanSend ? 0 : getReadOnlyRoomEventsDefaultPower(),
|
||||
events: {
|
||||
"m.reaction": 0,
|
||||
"m.room.redaction": 0 // only affects redactions of own events, required to be able to un-react
|
||||
|
|
@ -559,7 +561,7 @@ async function createAllForGuild(guildID) {
|
|||
}
|
||||
|
||||
module.exports.DEFAULT_PRIVACY_LEVEL = DEFAULT_PRIVACY_LEVEL
|
||||
module.exports.READ_ONLY_ROOM_EVENTS_DEFAULT_POWER = READ_ONLY_ROOM_EVENTS_DEFAULT_POWER
|
||||
module.exports.getReadOnlyRoomEventsDefaultPower = getReadOnlyRoomEventsDefaultPower
|
||||
module.exports.PRIVACY_ENUMS = PRIVACY_ENUMS
|
||||
module.exports.createRoom = createRoom
|
||||
module.exports.ensureRoom = ensureRoom
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ function memberToPowerLevel(user, member, guild, channel) {
|
|||
/* PL 50 = if room is read-only but the user has been specially allowed to send messages */
|
||||
const everyoneCanSend = utils.hasPermission(everyonePermissions, DiscordTypes.PermissionFlagsBits.SendMessages)
|
||||
const userCanSend = utils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.SendMessages)
|
||||
if (!everyoneCanSend && userCanSend) return createRoom.READ_ONLY_ROOM_EVENTS_DEFAULT_POWER
|
||||
if (!everyoneCanSend && userCanSend) return createRoom.getReadOnlyRoomEventsDefaultPower()
|
||||
/* PL 20 = Mention Everyone for technical reasons. */
|
||||
const everyoneCanMentionEveryone = utils.hasPermission(everyonePermissions, DiscordTypes.PermissionFlagsBits.MentionEveryone)
|
||||
const userCanMentionEveryone = utils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.MentionEveryone)
|
||||
|
|
|
|||
|
|
@ -207,7 +207,6 @@ async function attachmentToEvent(mentions, attachment) {
|
|||
* - alwaysReturnFormattedBody: false - formatted_body will be skipped if it is the same as body because the message is plaintext. if you want the formatted_body to be returned anyway, for example to merge it with another message, then set this to true.
|
||||
* - scanTextForMentions: true - needs to be set to false when converting forwarded messages etc which may be from a different channel that can't be scanned.
|
||||
* @param {{api: import("../../matrix/api"), snow?: import("snowtransfer").SnowTransfer}} di simple-as-nails dependency injection for the matrix API
|
||||
* @returns {Promise<{$type: string, $sender?: string, [x: string]: any}[]>}
|
||||
*/
|
||||
async function messageToEvent(message, guild, options = {}, di) {
|
||||
const events = []
|
||||
|
|
@ -409,13 +408,13 @@ async function messageToEvent(message, guild, options = {}, di) {
|
|||
|
||||
async function transformParsedVia(parsed) {
|
||||
for (const node of parsed) {
|
||||
if (node.type === "discordChannel" || node.type === "discordChannelLink") {
|
||||
if (node.type === "discordChannel") {
|
||||
node.row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get()
|
||||
if (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)) {
|
||||
await transformParsedVia(maybeChildNodesArray)
|
||||
}
|
||||
|
|
@ -612,7 +611,7 @@ async function messageToEvent(message, guild, options = {}, di) {
|
|||
const event = invite.guild_scheduled_event
|
||||
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", timeZone: reg.ooye.time_zone}) // 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"}) // 9 June at 3:00 pm NZT
|
||||
const rep = new mxUtils.MatrixStringBuilder()
|
||||
|
||||
// Add time
|
||||
|
|
|
|||
|
|
@ -100,44 +100,6 @@ test("message2event: simple room mention", async t => {
|
|||
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 => {
|
||||
let called = 0
|
||||
const events = await messageToEvent(data.message.nicked_room_mention, data.guild.general, {}, {
|
||||
|
|
|
|||
|
|
@ -32,10 +32,13 @@ async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thr
|
|||
const template = creatorMxid ? "started a thread:" : "Thread started:"
|
||||
const via = await mxUtils.getViaServersQuery(threadRoomID, di.api)
|
||||
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 {
|
||||
msgtype,
|
||||
body,
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: html,
|
||||
"m.mentions": {},
|
||||
...context
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ test("thread2announcement: no known creator, no branched from event", async t =>
|
|||
t.deepEqual(content, {
|
||||
msgtype: "m.text",
|
||||
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": {}
|
||||
})
|
||||
})
|
||||
|
|
@ -67,6 +69,8 @@ test("thread2announcement: known creator, no branched from event", async t => {
|
|||
t.deepEqual(content, {
|
||||
msgtype: "m.emote",
|
||||
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": {}
|
||||
})
|
||||
})
|
||||
|
|
@ -91,6 +95,8 @@ test("thread2announcement: no known creator, branched from discord event", async
|
|||
t.deepEqual(content, {
|
||||
msgtype: "m.text",
|
||||
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.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
|
|
@ -120,6 +126,8 @@ test("thread2announcement: known creator, branched from discord event", async t
|
|||
t.deepEqual(content, {
|
||||
msgtype: "m.emote",
|
||||
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.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
|
|
@ -149,6 +157,8 @@ test("thread2announcement: no known creator, branched from matrix event", async
|
|||
t.deepEqual(content, {
|
||||
msgtype: "m.text",
|
||||
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": {
|
||||
user_ids: ["@cadence:cadence.moe"]
|
||||
},
|
||||
|
|
|
|||
16
src/db/database.js
Normal file
16
src/db/database.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// @ts-check
|
||||
|
||||
const sqlite = require("better-sqlite3")
|
||||
const path = require("path")
|
||||
|
||||
/**
|
||||
* Create a new SQLite database instance
|
||||
* @param {import("better-sqlite3").Options} [options] - SQLite options
|
||||
* @returns {import("better-sqlite3").Database} Database instance
|
||||
*/
|
||||
function getDatabase(options = {}) {
|
||||
const dataDir = process.env.OOYE_DATA_DIR || process.cwd()
|
||||
return new sqlite(path.join(dataDir, "ooye.db"), options)
|
||||
}
|
||||
|
||||
module.exports = {getDatabase}
|
||||
|
|
@ -7,8 +7,6 @@ const {sync} = require("../../passthrough")
|
|||
const emojiSheetConverter = sync.require("../converters/emoji-sheet")
|
||||
/** @type {import("../../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.
|
||||
|
|
@ -21,10 +19,6 @@ async function getAndConvertEmoji(mxc) {
|
|||
// 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.
|
||||
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)
|
||||
return emojiSheetConverter.convertImageStream(readable, () => {
|
||||
abortController.abort()
|
||||
|
|
|
|||
|
|
@ -13,12 +13,10 @@ const utils = sync.require("../converters/utils")
|
|||
*/
|
||||
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()
|
||||
if (!rows.length) return
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ const {join} = require("path")
|
|||
|
||||
const passthrough = require("../../passthrough")
|
||||
|
||||
const {id} = require("../../../addbot")
|
||||
|
||||
async function setupEmojis() {
|
||||
const {id} = require("../../../addbot")
|
||||
const {discord, db} = passthrough
|
||||
const emojis = await discord.snow.assets.getAppEmojis(id)
|
||||
for (const name of ["L1", "L2"]) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ const entities = require("entities")
|
|||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {sync, db, discord, select, from} = passthrough
|
||||
const {reg} = require("../../matrix/read-registration")
|
||||
/** @type {import("../converters/utils")} */
|
||||
const mxUtils = sync.require("../converters/utils")
|
||||
/** @type {import("../../discord/utils")} */
|
||||
|
|
@ -239,8 +238,7 @@ function convertEmoji(mxcUrl, nameForGuess, allowSpriteSheetIndicator, allowLink
|
|||
if (!found) row = null
|
||||
}
|
||||
// Or, if we don't have an emoji right now, we search for the name instead.
|
||||
const isLocalMxc = mxcUrl?.match(/^mxc:\/\/([^/]+)/)?.[1] === reg.ooye.server_name
|
||||
if (!row && nameForGuess && isLocalMxc) {
|
||||
if (!row && nameForGuess) {
|
||||
const nameForGuessLower = nameForGuess.toLowerCase()
|
||||
for (const guild of discord.guilds.values()) {
|
||||
/** @type {{name: string, id: string, animated: number}[]} */
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ function path(p, mxid, otherParams = {}) {
|
|||
const u = new URL(p, "http://localhost")
|
||||
if (mxid) u.searchParams.set("user_id", mxid)
|
||||
for (const entry of Object.entries(otherParams)) {
|
||||
if (Array.isArray(entry[1])) {
|
||||
for (const element of entry[1]) {
|
||||
u.searchParams.append(entry[0], element)
|
||||
}
|
||||
} else if (entry[1] != undefined) {
|
||||
if (entry[1] != undefined) {
|
||||
u.searchParams.set(entry[0], entry[1])
|
||||
}
|
||||
}
|
||||
|
|
@ -66,14 +62,11 @@ async function createRoom(content) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {string} roomIDOrAlias
|
||||
* @param {string?} [mxid]
|
||||
* @param {string[]?} [via]
|
||||
* @returns {Promise<string>} room ID
|
||||
*/
|
||||
async function joinRoom(roomIDOrAlias, mxid, via) {
|
||||
async function joinRoom(roomIDOrAlias, mxid) {
|
||||
/** @type {Ty.R.RoomJoined} */
|
||||
const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid, {via}), {})
|
||||
const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid), {})
|
||||
return root.room_id
|
||||
}
|
||||
|
||||
|
|
@ -391,9 +384,7 @@ async function getMedia(mxc, init = {}) {
|
|||
},
|
||||
...init
|
||||
})
|
||||
if (init.method !== "HEAD") {
|
||||
assert(res.body)
|
||||
}
|
||||
assert(res.body)
|
||||
// @ts-ignore
|
||||
return res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,3 @@ test("api path: real world mxid", t => {
|
|||
test("api path: extras number works", t => {
|
||||
t.equal(path(`/client/v3/rooms/!example/timestamp_to_event`, null, {ts: 1687324651120}), "/client/v3/rooms/!example/timestamp_to_event?ts=1687324651120")
|
||||
})
|
||||
|
||||
test("api path: multiple via params", t => {
|
||||
t.equal(path(`/client/v3/rooms/!example/join`, null, {via: ["cadence.moe", "matrix.org"], ts: 1687324651120}), "/client/v3/rooms/!example/join?via=cadence.moe&via=matrix.org&ts=1687324651120")
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ const crypto = require("crypto")
|
|||
const assert = require("assert").strict
|
||||
const path = require("path")
|
||||
|
||||
const registrationFilePath = path.join(process.cwd(), "registration.yaml")
|
||||
const dataDir = process.env.OOYE_DATA_DIR || process.cwd()
|
||||
const registrationFilePath = path.join(dataDir, "registration.yaml")
|
||||
|
||||
/** @param {import("../types").AppServiceRegistrationConfig} reg */
|
||||
function checkRegistration(reg) {
|
||||
|
|
|
|||
12
src/types.d.ts
vendored
12
src/types.d.ts
vendored
|
|
@ -31,7 +31,7 @@ export type AppServiceRegistrationConfig = {
|
|||
discord_origin?: string
|
||||
discord_cdn_origin?: string,
|
||||
web_password: string
|
||||
time_zone?: string
|
||||
read_only_room_events_default_power?: number
|
||||
}
|
||||
old_bridge?: {
|
||||
as_token: string
|
||||
|
|
@ -149,14 +149,6 @@ export namespace Event {
|
|||
prev_content?: any
|
||||
}
|
||||
|
||||
export type StrippedChildStateEvent = {
|
||||
type: string
|
||||
state_key: string
|
||||
sender: string
|
||||
origin_server_ts: number
|
||||
content: any
|
||||
}
|
||||
|
||||
export type M_Room_Message = {
|
||||
msgtype: "m.text" | "m.emote"
|
||||
body: string
|
||||
|
|
@ -353,7 +345,7 @@ export namespace R {
|
|||
export type Hierarchy = {
|
||||
avatar_url?: string
|
||||
canonical_alias?: string
|
||||
children_state: Event.StrippedChildStateEvent[]
|
||||
children_state: {}
|
||||
guest_can_join: boolean
|
||||
join_rule?: string
|
||||
name?: string
|
||||
|
|
|
|||
|
|
@ -75,15 +75,11 @@ as.router.post("/api/link-space", defineEventHandler(async event => {
|
|||
const existing = select("guild_space", "guild_id", {}, "WHERE guild_id = ? OR space_id = ?").get(guildID, spaceID)
|
||||
if (existing) throw createError({status: 400, message: "Bad Request", data: `Guild ID ${guildID} or space ID ${spaceID} are already bridged and cannot be reused`})
|
||||
|
||||
const inviteSender = select("invite", "mxid", {mxid: session.data.mxid, room_id: spaceID}).pluck().get()
|
||||
const inviteSenderServer = inviteSender?.match(/:(.*)/)?.[1]
|
||||
const via = [inviteSenderServer || ""]
|
||||
|
||||
// Check space exists and bridge is joined
|
||||
try {
|
||||
await api.joinRoom(parsedBody.space_id, null, via)
|
||||
await api.joinRoom(parsedBody.space_id)
|
||||
} catch (e) {
|
||||
throw createError({status: 400, message: "Unable To Join", data: `Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: ${e.errcode} - ${e.message})`})
|
||||
throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`})
|
||||
}
|
||||
|
||||
// Check bridge has PL 100
|
||||
|
|
@ -138,33 +134,19 @@ as.router.post("/api/link", defineEventHandler(async event => {
|
|||
if (row) throw createError({status: 400, message: "Bad Request", data: `Channel ID ${row.channel_id} or room ID ${parsedBody.matrix} are already bridged and cannot be reused`})
|
||||
|
||||
// Check room is part of the guild's space
|
||||
let foundRoom = false
|
||||
/** @type {string[]?} */
|
||||
let foundVia = null
|
||||
let found = false
|
||||
for await (const room of api.generateFullHierarchy(spaceID)) {
|
||||
// When finding a space during iteration, look at space's children state, because we need a `via` to join the room (when we find it later)
|
||||
for (const state of room.children_state) {
|
||||
if (state.type === "m.space.child" && state.state_key === parsedBody.matrix) {
|
||||
foundVia = state.content.via
|
||||
}
|
||||
}
|
||||
|
||||
// When finding a room during iteration, see if it was the requested room (to confirm that the room is in the space)
|
||||
if (room.room_id === parsedBody.matrix && !room.room_type) {
|
||||
foundRoom = true
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
if (foundRoom && foundVia) break
|
||||
}
|
||||
if (!foundRoom) throw createError({status: 400, message: "Bad Request", data: "Matrix room needs to be part of the bridged space"})
|
||||
if (!found) throw createError({status: 400, message: "Bad Request", data: "Matrix room needs to be part of the bridged space"})
|
||||
|
||||
// Check room exists and bridge is joined
|
||||
try {
|
||||
await api.joinRoom(parsedBody.matrix, null, foundVia)
|
||||
await api.joinRoom(parsedBody.matrix)
|
||||
} catch (e) {
|
||||
if (!foundVia) {
|
||||
throw createError({status: 400, message: "Unable To Join", data: `Unable to join the requested Matrix room. Please invite the bridge to the room and try again. (Server said: ${e.errcode} - ${e.message})`})
|
||||
}
|
||||
throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ test("web link space: check that OOYE is joined", async t => {
|
|||
}
|
||||
}
|
||||
}))
|
||||
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(error.data, "M_FORBIDDEN - not allowed to join I guess")
|
||||
t.equal(called, 1)
|
||||
})
|
||||
|
||||
|
|
@ -360,7 +360,7 @@ test("web link room: check that room is part of space (not in hierarchy)", async
|
|||
t.equal(called, 1)
|
||||
})
|
||||
|
||||
test("web link room: check that bridge can join room (notices lack of via and asks for invite instead)", async t => {
|
||||
test("web link room: check that bridge can join room", async t => {
|
||||
let called = 0
|
||||
const [error] = await tryToCatch(() => router.test("post", "/api/link", {
|
||||
sessionData: {
|
||||
|
|
@ -381,55 +381,7 @@ test("web link room: check that bridge can join room (notices lack of via and as
|
|||
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
|
||||
yield {
|
||||
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
children_state: [],
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
/* c8 ignore next */
|
||||
}
|
||||
}
|
||||
}))
|
||||
t.equal(error.data, "Unable to join the requested Matrix room. Please invite the bridge to the room and try again. (Server said: M_FORBIDDEN - not allowed to join I guess)")
|
||||
t.equal(called, 2)
|
||||
})
|
||||
|
||||
test("web link room: check that bridge can join room (uses via for join attempt)", async t => {
|
||||
let called = 0
|
||||
const [error] = await tryToCatch(() => router.test("post", "/api/link", {
|
||||
sessionData: {
|
||||
managedGuilds: ["665289423482519565"]
|
||||
},
|
||||
body: {
|
||||
discord: "665310973967597573",
|
||||
matrix: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
guild_id: "665289423482519565"
|
||||
},
|
||||
api: {
|
||||
async joinRoom(roomID, _, via) {
|
||||
called++
|
||||
t.deepEqual(via, ["cadence.moe", "hashi.re"])
|
||||
throw new MatrixServerError({errcode: "M_FORBIDDEN", error: "not allowed to join I guess"})
|
||||
},
|
||||
async *generateFullHierarchy(spaceID) {
|
||||
called++
|
||||
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
|
||||
yield {
|
||||
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
children_state: [],
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
yield {
|
||||
room_id: "!zTMspHVUBhFLLSdmnS:cadence.moe",
|
||||
children_state: [{
|
||||
type: "m.space.child",
|
||||
state_key: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
sender: "@elliu:hashi.re",
|
||||
content: {
|
||||
via: ["cadence.moe", "hashi.re"]
|
||||
},
|
||||
origin_server_ts: 0
|
||||
}],
|
||||
children_state: {},
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
|
|
@ -462,7 +414,7 @@ test("web link room: check that bridge has PL 100 in target room (event missing)
|
|||
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
|
||||
yield {
|
||||
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
children_state: [],
|
||||
children_state: {},
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
|
|
@ -502,7 +454,7 @@ test("web link room: check that bridge has PL 100 in target room (users default)
|
|||
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
|
||||
yield {
|
||||
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
children_state: [],
|
||||
children_state: {},
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
|
|
@ -542,7 +494,7 @@ test("web link room: successfully calls createRoom", async t => {
|
|||
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
|
||||
yield {
|
||||
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
|
||||
children_state: [],
|
||||
children_state: {},
|
||||
guest_can_join: false,
|
||||
num_joined_members: 2
|
||||
}
|
||||
|
|
|
|||
4
start.js
4
start.js
|
|
@ -2,13 +2,13 @@
|
|||
// @ts-check
|
||||
|
||||
const fs = require("fs")
|
||||
const sqlite = require("better-sqlite3")
|
||||
const migrate = require("./src/db/migrate")
|
||||
const HeatSync = require("heatsync")
|
||||
|
||||
const {reg} = require("./src/matrix/read-registration")
|
||||
const passthrough = require("./src/passthrough")
|
||||
const db = new sqlite("ooye.db")
|
||||
const {getDatabase} = require("./src/db/database")
|
||||
const db = getDatabase()
|
||||
|
||||
const sync = new HeatSync({watchFunction: fs.watchFile})
|
||||
|
||||
|
|
|
|||
57
test/data.js
57
test/data.js
|
|
@ -1398,63 +1398,6 @@ module.exports = {
|
|||
attachments: [],
|
||||
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: {
|
||||
type: 0,
|
||||
tts: false,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ const {reg} = require("../src/matrix/read-registration")
|
|||
reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby"
|
||||
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
|
||||
reg.ooye.server_name = "cadence.moe"
|
||||
reg.ooye.namespace_prefix = "_ooye_"
|
||||
reg.sender_localpart = "_ooye_bot"
|
||||
reg.id = "baby"
|
||||
reg.as_token = "don't actually take authenticated actions on the server"
|
||||
reg.hs_token = "don't actually take authenticated actions on the server"
|
||||
|
|
@ -27,7 +25,6 @@ reg.namespaces = {
|
|||
aliases: [{regex: "#_ooye_.*:cadence.moe", exclusive: true}]
|
||||
}
|
||||
reg.ooye.bridge_origin = "https://bridge.example.org"
|
||||
reg.ooye.time_zone = "Pacific/Auckland"
|
||||
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue