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
|
config.js
|
||||||
registration.yaml
|
registration.yaml
|
||||||
coverage
|
coverage
|
||||||
db/ooye.db*
|
src/db/ooye.db*
|
||||||
test/res/*
|
test/res/*
|
||||||
!test/res/lottie*
|
!test/res/lottie*
|
||||||
|
|
|
@ -59,6 +59,6 @@
|
||||||
"addbot": "node addbot.js",
|
"addbot": "node addbot.js",
|
||||||
"test": "cross-env FORCE_COLOR=true supertape --no-check-assertions-count --format tap test/test.js | tap-dot",
|
"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",
|
"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 {channelToKState, _convertNameAndTopic} = require("./create-room")
|
||||||
const {kstateStripConditionals} = require("../../matrix/kstate")
|
const {kstateStripConditionals} = require("../../matrix/kstate")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const testData = require("../../test/data")
|
const testData = require("../../../test/data")
|
||||||
|
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
const {db} = passthrough
|
const {db} = passthrough
|
|
@ -4,7 +4,7 @@ const mixin = require("@cloudrac3r/mixin-deep")
|
||||||
const {guildToKState, ensureSpace} = require("./create-space")
|
const {guildToKState, ensureSpace} = require("./create-space")
|
||||||
const {kstateStripConditionals, kstateUploadMxc} = require("../../matrix/kstate")
|
const {kstateStripConditionals, kstateUploadMxc} = require("../../matrix/kstate")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const testData = require("../../test/data")
|
const testData = require("../../../test/data")
|
||||||
|
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
const {db} = passthrough
|
const {db} = passthrough
|
|
@ -66,7 +66,7 @@ async function stickersToState(stickers) {
|
||||||
while (shortcodes.includes(shortcode)) shortcode = shortcode + "~"
|
while (shortcodes.includes(shortcode)) shortcode = shortcode + "~"
|
||||||
shortcodes.push(shortcode)
|
shortcodes.push(shortcode)
|
||||||
|
|
||||||
result.images[shortcodes] = {
|
result.images[shortcode] = {
|
||||||
info: {
|
info: {
|
||||||
mimetype: file.stickerFormat.get(sticker.format_type)?.mime || "image/png"
|
mimetype: file.stickerFormat.get(sticker.format_type)?.mime || "image/png"
|
||||||
},
|
},
|
|
@ -1,6 +1,6 @@
|
||||||
const {_memberToStateContent} = require("./register-user")
|
const {_memberToStateContent} = require("./register-user")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const testData = require("../../test/data")
|
const testData = require("../../../test/data")
|
||||||
|
|
||||||
test("member2state: without member nick or avatar", async t => {
|
test("member2state: without member nick or avatar", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
|
@ -1,6 +1,6 @@
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {editToChanges} = require("./edit-to-changes")
|
const {editToChanges} = require("./edit-to-changes")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
|
||||||
test("edit2changes: edit by webhook", async t => {
|
test("edit2changes: edit by webhook", async t => {
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {emojiToKey} = require("./emoji-to-key")
|
const {emojiToKey} = require("./emoji-to-key")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
|
||||||
test("emoji2key: unicode emoji works", async t => {
|
test("emoji2key: unicode emoji works", async t => {
|
|
@ -1,6 +1,6 @@
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {messageToEvent} = require("./message-to-event")
|
const {messageToEvent} = require("./message-to-event")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
|
||||||
test("message2event embeds: nothing but a field", async t => {
|
test("message2event embeds: nothing but a field", async t => {
|
|
@ -1,6 +1,6 @@
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {messageToEvent} = require("./message-to-event")
|
const {messageToEvent} = require("./message-to-event")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,6 +1,6 @@
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {messageToEvent} = require("./message-to-event")
|
const {messageToEvent} = require("./message-to-event")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,5 +1,5 @@
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const {pinsToList} = require("./pins-to-list")
|
const {pinsToList} = require("./pins-to-list")
|
||||||
|
|
||||||
test("pins2list: converts known IDs, ignores unknown IDs", t => {
|
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 {test} = require("supertape")
|
||||||
const {threadToAnnouncement} = require("./thread-to-announcement")
|
const {threadToAnnouncement} = require("./thread-to-announcement")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,7 +1,7 @@
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const tryToCatch = require("try-to-catch")
|
const tryToCatch = require("try-to-catch")
|
||||||
const assert = require("assert")
|
const assert = require("assert")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const {userToSimName} = require("./user-to-mxid")
|
const {userToSimName} = require("./user-to-mxid")
|
||||||
|
|
||||||
test("user2name: cannot create user for a webhook", async t => {
|
test("user2name: cannot create user for a webhook", async t => {
|
|
@ -10,7 +10,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = async function(db) {
|
module.exports = async function(db) {
|
||||||
const config = require("../../config")
|
const config = require("../../../config")
|
||||||
const id = Buffer.from(config.discordToken.split(".")[0], "base64").toString()
|
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)
|
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
|
// @ts-check
|
||||||
|
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const data = require("../test/data")
|
const data = require("../../test/data")
|
||||||
|
|
||||||
const {db, select, from} = require("../passthrough")
|
const {db, select, from} = require("../passthrough")
|
||||||
|
|
|
@ -4,13 +4,15 @@ const assert = require("assert").strict
|
||||||
const util = require("util")
|
const util = require("util")
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
const {reg} = require("../matrix/read-registration")
|
const {reg} = require("../matrix/read-registration")
|
||||||
const {addbot} = require("../addbot")
|
const {addbot} = require("../../addbot")
|
||||||
|
|
||||||
const {discord, sync, db, select} = require("../passthrough")
|
const {discord, sync, db, select} = require("../passthrough")
|
||||||
/** @type {import("../matrix/api")}) */
|
/** @type {import("../matrix/api")}) */
|
||||||
const api = sync.require("../matrix/api")
|
const api = sync.require("../matrix/api")
|
||||||
/** @type {import("../matrix/file")} */
|
/** @type {import("../matrix/file")} */
|
||||||
const file = sync.require("../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")} */
|
/** @type {import("../d2m/actions/create-space")} */
|
||||||
const createSpace = sync.require("../d2m/actions/create-space")
|
const createSpace = sync.require("../d2m/actions/create-space")
|
||||||
/** @type {import("./utils")} */
|
/** @type {import("./utils")} */
|
||||||
|
@ -91,9 +93,8 @@ const commands = [{
|
||||||
|
|
||||||
// Current avatar
|
// Current avatar
|
||||||
const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "")
|
const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "")
|
||||||
const avatarURLParts = avatarEvent?.url.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
|
||||||
let currentAvatarMessage =
|
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.")
|
: "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
|
// Next potential avatar
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
const {discord, sync, db, select} = require("../passthrough")
|
const {discord, sync, db, select} = require("../passthrough")
|
||||||
const {id} = require("../addbot")
|
const {id} = require("../../addbot")
|
||||||
|
|
||||||
const matrixInfo = sync.require("./interactions/matrix-info.js")
|
const matrixInfo = sync.require("./interactions/matrix-info.js")
|
||||||
const invite = sync.require("./interactions/invite.js")
|
const invite = sync.require("./interactions/invite.js")
|
|
@ -1,6 +1,6 @@
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const data = require("../test/data")
|
const data = require("../../test/data")
|
||||||
const utils = require("./utils")
|
const utils = require("./utils")
|
||||||
|
|
||||||
test("is webhook message: identifies bot interaction response as not a message", t => {
|
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)
|
giframe.feed(chunk)
|
||||||
})
|
})
|
||||||
const frame = await giframe.getFrame()
|
const frame = await giframe.getFrame()
|
||||||
|
const pixels = Uint8Array.from(frame.pixels)
|
||||||
stopStream()
|
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}})
|
.resize(SIZE, SIZE, {fit: "contain", background: {r: 0, g: 0, b: 0, alpha: 0}})
|
||||||
.png({compressionLevel: 0})
|
.png({compressionLevel: 0})
|
||||||
.toBuffer({resolveWithObject: true})
|
.toBuffer({resolveWithObject: true})
|
|
@ -15,6 +15,8 @@ const {sync, db, discord, select, from} = passthrough
|
||||||
const mxUtils = sync.require("../converters/utils")
|
const mxUtils = sync.require("../converters/utils")
|
||||||
/** @type {import("../../discord/utils")} */
|
/** @type {import("../../discord/utils")} */
|
||||||
const dUtils = sync.require("../../discord/utils")
|
const dUtils = sync.require("../../discord/utils")
|
||||||
|
/** @type {import("../../matrix/file")} */
|
||||||
|
const file = sync.require("../../matrix/file")
|
||||||
/** @type {import("./emoji-sheet")} */
|
/** @type {import("./emoji-sheet")} */
|
||||||
const emojiSheet = sync.require("./emoji-sheet")
|
const emojiSheet = sync.require("./emoji-sheet")
|
||||||
|
|
|
@ -3,7 +3,7 @@ const fs = require("fs")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const {eventToMessage} = require("./event-to-message")
|
const {eventToMessage} = require("./event-to-message")
|
||||||
const {convertImageStream} = require("./emoji-sheet")
|
const {convertImageStream} = require("./emoji-sheet")
|
||||||
const data = require("../../test/data")
|
const data = require("../../../test/data")
|
||||||
const {MatrixServerError} = require("../../matrix/mreq")
|
const {MatrixServerError} = require("../../matrix/mreq")
|
||||||
const {select, discord} = require("../../passthrough")
|
const {select, discord} = require("../../passthrough")
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
const assert = require("assert").strict
|
||||||
|
|
||||||
|
const passthrough = require("../../passthrough")
|
||||||
|
const {db} = passthrough
|
||||||
|
|
||||||
const {reg} = require("../../matrix/read-registration")
|
const {reg} = require("../../matrix/read-registration")
|
||||||
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
|
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
|
||||||
const assert = require("assert").strict
|
|
||||||
/** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore
|
/** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore
|
||||||
let hasher = null
|
let hasher = null
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -35,16 +40,6 @@ function eventSenderIsFromDiscord(sender) {
|
||||||
return false
|
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.
|
* 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.
|
* 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
|
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.BLOCK_ELEMENTS = BLOCK_ELEMENTS
|
||||||
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
|
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
|
||||||
module.exports.getPublicUrlForMxc = getPublicUrlForMxc
|
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