Compare commits
5 commits
23a7394ce7
...
eaa3b87670
Author | SHA1 | Date | |
---|---|---|---|
eaa3b87670 | |||
915c34646a | |||
4247a3114a | |||
decc32f7e6 | |||
96fd046530 |
107 changed files with 109 additions and 75 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,6 +2,6 @@ node_modules
|
|||
config.js
|
||||
registration.yaml
|
||||
coverage
|
||||
db/ooye.db*
|
||||
src/db/ooye.db*
|
||||
test/res/*
|
||||
!test/res/lottie*
|
||||
|
|
|
@ -59,6 +59,6 @@
|
|||
"addbot": "node addbot.js",
|
||||
"test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot",
|
||||
"test-slow": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap --no-worker test/test.js -- --slow | tap-dot",
|
||||
"cover": "c8 --skip-full -x db/migrations -x matrix/file.js -x matrix/api.js -x matrix/mreq.js -x d2m/converters/rlottie-wasm.js -r html -r text supertape --no-check-assertions-count --format fail --no-worker test/test.js -- --slow"
|
||||
"cover": "c8 -o test/coverage --skip-full -x db/migrations -x matrix/file.js -x matrix/api.js -x matrix/mreq.js -x d2m/converters/rlottie-wasm.js -r html -r text supertape --no-check-assertions-count --format fail --no-worker test/test.js -- --slow"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ const mixin = require("@cloudrac3r/mixin-deep")
|
|||
const {channelToKState, _convertNameAndTopic} = require("./create-room")
|
||||
const {kstateStripConditionals} = require("../../matrix/kstate")
|
||||
const {test} = require("supertape")
|
||||
const testData = require("../../test/data")
|
||||
const testData = require("../../../test/data")
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {db} = passthrough
|
|
@ -4,7 +4,7 @@ const mixin = require("@cloudrac3r/mixin-deep")
|
|||
const {guildToKState, ensureSpace} = require("./create-space")
|
||||
const {kstateStripConditionals, kstateUploadMxc} = require("../../matrix/kstate")
|
||||
const {test} = require("supertape")
|
||||
const testData = require("../../test/data")
|
||||
const testData = require("../../../test/data")
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {db} = passthrough
|
|
@ -66,7 +66,7 @@ async function stickersToState(stickers) {
|
|||
while (shortcodes.includes(shortcode)) shortcode = shortcode + "~"
|
||||
shortcodes.push(shortcode)
|
||||
|
||||
result.images[shortcodes] = {
|
||||
result.images[shortcode] = {
|
||||
info: {
|
||||
mimetype: file.stickerFormat.get(sticker.format_type)?.mime || "image/png"
|
||||
},
|
|
@ -1,6 +1,6 @@
|
|||
const {_memberToStateContent} = require("./register-user")
|
||||
const {test} = require("supertape")
|
||||
const testData = require("../../test/data")
|
||||
const testData = require("../../../test/data")
|
||||
|
||||
test("member2state: without member nick or avatar", async t => {
|
||||
t.deepEqual(
|
|
@ -1,6 +1,6 @@
|
|||
const {test} = require("supertape")
|
||||
const {editToChanges} = require("./edit-to-changes")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
test("edit2changes: edit by webhook", async t => {
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const {test} = require("supertape")
|
||||
const {emojiToKey} = require("./emoji-to-key")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
test("emoji2key: unicode emoji works", async t => {
|
|
@ -1,6 +1,6 @@
|
|||
const {test} = require("supertape")
|
||||
const {messageToEvent} = require("./message-to-event")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
test("message2event embeds: nothing but a field", async t => {
|
|
@ -1,6 +1,6 @@
|
|||
const {test} = require("supertape")
|
||||
const {messageToEvent} = require("./message-to-event")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
/**
|
|
@ -1,6 +1,6 @@
|
|||
const {test} = require("supertape")
|
||||
const {messageToEvent} = require("./message-to-event")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
/**
|
|
@ -1,5 +1,5 @@
|
|||
const {test} = require("supertape")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const {pinsToList} = require("./pins-to-list")
|
||||
|
||||
test("pins2list: converts known IDs, ignores unknown IDs", t => {
|
0
d2m/converters/rlottie-wasm.wasm → src/d2m/converters/rlottie-wasm.wasm
Executable file → Normal file
0
d2m/converters/rlottie-wasm.wasm → src/d2m/converters/rlottie-wasm.wasm
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
|||
const {test} = require("supertape")
|
||||
const {threadToAnnouncement} = require("./thread-to-announcement")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
/**
|
|
@ -1,7 +1,7 @@
|
|||
const {test} = require("supertape")
|
||||
const tryToCatch = require("try-to-catch")
|
||||
const assert = require("assert")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const {userToSimName} = require("./user-to-mxid")
|
||||
|
||||
test("user2name: cannot create user for a webhook", async t => {
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
|
||||
module.exports = async function(db) {
|
||||
const config = require("../../config")
|
||||
const config = require("../../../config")
|
||||
const id = Buffer.from(config.discordToken.split(".")[0], "base64").toString()
|
||||
db.prepare("UPDATE OR REPLACE sim SET user_id = ? WHERE user_id = '0'").run(id)
|
||||
}
|
8
src/db/migrations/0013-media-proxy.sql
Normal file
8
src/db/migrations/0013-media-proxy.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE "media_proxy" (
|
||||
"permitted_hash" INTEGER NOT NULL,
|
||||
PRIMARY KEY("permitted_hash")
|
||||
) WITHOUT ROWID;
|
||||
|
||||
COMMIT;
|
0
db/orm-defs.d.ts → src/db/orm-defs.d.ts
vendored
0
db/orm-defs.d.ts → src/db/orm-defs.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
// @ts-check
|
||||
|
||||
const {test} = require("supertape")
|
||||
const data = require("../test/data")
|
||||
const data = require("../../test/data")
|
||||
|
||||
const {db, select, from} = require("../passthrough")
|
||||
|
|
@ -4,13 +4,15 @@ const assert = require("assert").strict
|
|||
const util = require("util")
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const {reg} = require("../matrix/read-registration")
|
||||
const {addbot} = require("../addbot")
|
||||
const {addbot} = require("../../addbot")
|
||||
|
||||
const {discord, sync, db, select} = require("../passthrough")
|
||||
/** @type {import("../matrix/api")}) */
|
||||
const api = sync.require("../matrix/api")
|
||||
/** @type {import("../matrix/file")} */
|
||||
const file = sync.require("../matrix/file")
|
||||
/** @type {import("../m2d/converters/utils")} */
|
||||
const mxUtils = sync.require("../m2d/converters/utils")
|
||||
/** @type {import("../d2m/actions/create-space")} */
|
||||
const createSpace = sync.require("../d2m/actions/create-space")
|
||||
/** @type {import("./utils")} */
|
||||
|
@ -91,9 +93,8 @@ const commands = [{
|
|||
|
||||
// Current avatar
|
||||
const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "")
|
||||
const avatarURLParts = avatarEvent?.url.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
let currentAvatarMessage =
|
||||
( avatarURLParts ? `Current room-specific avatar: ${reg.ooye.server_origin}/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}`
|
||||
( avatarEvent.url ? `Current room-specific avatar: ${mxUtils.getPublicUrlForMxc(avatarEvent.url)}`
|
||||
: "No avatar. Now's your time to strike. Use `//icon` again with a link or upload to set the room-specific avatar.")
|
||||
|
||||
// Next potential avatar
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const {discord, sync, db, select} = require("../passthrough")
|
||||
const {id} = require("../addbot")
|
||||
const {id} = require("../../addbot")
|
||||
|
||||
const matrixInfo = sync.require("./interactions/matrix-info.js")
|
||||
const invite = sync.require("./interactions/invite.js")
|
|
@ -1,6 +1,6 @@
|
|||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const {test} = require("supertape")
|
||||
const data = require("../test/data")
|
||||
const data = require("../../test/data")
|
||||
const utils = require("./utils")
|
||||
|
||||
test("is webhook message: identifies bot interaction response as not a message", t => {
|
|
@ -81,9 +81,10 @@ async function convertImageStream(streamIn, stopStream) {
|
|||
giframe.feed(chunk)
|
||||
})
|
||||
const frame = await giframe.getFrame()
|
||||
const pixels = Uint8Array.from(frame.pixels)
|
||||
stopStream()
|
||||
|
||||
const buffer = await sharp(frame.pixels, {raw: {width: frame.width, height: frame.height, channels: 4}})
|
||||
const buffer = await sharp(pixels, {raw: {width: frame.width, height: frame.height, channels: 4}})
|
||||
.resize(SIZE, SIZE, {fit: "contain", background: {r: 0, g: 0, b: 0, alpha: 0}})
|
||||
.png({compressionLevel: 0})
|
||||
.toBuffer({resolveWithObject: true})
|
|
@ -15,6 +15,8 @@ const {sync, db, discord, select, from} = passthrough
|
|||
const mxUtils = sync.require("../converters/utils")
|
||||
/** @type {import("../../discord/utils")} */
|
||||
const dUtils = sync.require("../../discord/utils")
|
||||
/** @type {import("../../matrix/file")} */
|
||||
const file = sync.require("../../matrix/file")
|
||||
/** @type {import("./emoji-sheet")} */
|
||||
const emojiSheet = sync.require("./emoji-sheet")
|
||||
|
|
@ -3,7 +3,7 @@ const fs = require("fs")
|
|||
const {test} = require("supertape")
|
||||
const {eventToMessage} = require("./event-to-message")
|
||||
const {convertImageStream} = require("./emoji-sheet")
|
||||
const data = require("../../test/data")
|
||||
const data = require("../../../test/data")
|
||||
const {MatrixServerError} = require("../../matrix/mreq")
|
||||
const {select, discord} = require("../../passthrough")
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
// @ts-check
|
||||
|
||||
const assert = require("assert").strict
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {db} = passthrough
|
||||
|
||||
const {reg} = require("../../matrix/read-registration")
|
||||
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
|
||||
const assert = require("assert").strict
|
||||
|
||||
/** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore
|
||||
let hasher = null
|
||||
// @ts-ignore
|
||||
|
@ -35,16 +40,6 @@ function eventSenderIsFromDiscord(sender) {
|
|||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} mxc
|
||||
* @returns {string?}
|
||||
*/
|
||||
function getPublicUrlForMxc(mxc) {
|
||||
const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
if (avatarURLParts) return `${reg.ooye.server_origin}/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}`
|
||||
else return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Event IDs are really big and have more entropy than we need.
|
||||
* If we want to store the event ID in the database, we can store a more compact version by hashing it with this.
|
||||
|
@ -213,6 +208,32 @@ async function getViaServersQuery(roomID, api) {
|
|||
return qs
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the introduction of authenticated media, this can no longer just be the /_matrix/media/r0/download URL
|
||||
* because Discord and Discord users cannot use those URLs. Media now has to be proxied through the bridge.
|
||||
* To avoid the bridge acting as a proxy for *any* media, there is a list of permitted media stored in the database.
|
||||
* (The other approach would be signing the URLs with a MAC (or similar) and adding the signature, but I'm not a
|
||||
* cryptographer, so I don't want to.) To reduce database disk space usage, instead of storing each permitted URL,
|
||||
* we just store its xxhash as a signed (as in +/-, not signature) 64-bit integer, which fits in an SQLite integer field.
|
||||
* @see https://matrix.org/blog/2024/06/26/sunsetting-unauthenticated-media/ background
|
||||
* @see https://matrix.org/blog/2024/06/20/matrix-v1.11-release/ implementation details
|
||||
* @see https://www.sqlite.org/fileformat2.html#record_format SQLite integer field size
|
||||
* @param {string} mxc
|
||||
* @returns {string?}
|
||||
*/
|
||||
function getPublicUrlForMxc(mxc) {
|
||||
assert(hasher, "xxhash is not ready yet")
|
||||
const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
if (!avatarURLParts) return null
|
||||
|
||||
const serverAndMediaID = `${avatarURLParts[1]}/${avatarURLParts[2]}`
|
||||
const unsignedHash = hasher.h64(serverAndMediaID)
|
||||
const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range
|
||||
db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash)
|
||||
|
||||
return `${reg.ooye.bridge_origin}/download/matrix/${serverAndMediaID}`
|
||||
}
|
||||
|
||||
module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS
|
||||
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
|
||||
module.exports.getPublicUrlForMxc = getPublicUrlForMxc
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue