diff --git a/scripts/setup.js b/scripts/setup.js index 4e6de0a..69b62a2 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -1,6 +1,7 @@ #!/usr/bin/env node // @ts-check +const Ty = require("../src/types") const assert = require("assert").strict const fs = require("fs") const sqlite = require("better-sqlite3") @@ -285,8 +286,8 @@ function defineEchoHandler() { console.log() // Done with user prompts, reg is now guaranteed to be valid + const mreq = require("../src/matrix/mreq") const api = require("../src/matrix/api") - const file = require("../src/matrix/file") const DiscordClient = require("../src/d2m/discord-client") const discord = new DiscordClient(reg.ooye.discord_token, "no") passthrough.discord = discord @@ -343,7 +344,13 @@ function defineEchoHandler() { await api.register(reg.sender_localpart) // upload initial images... - const avatarUrl = await file.uploadDiscordFileToMxc("https://cadence.moe/friends/out_of_your_element.png") + const avatarBuffer = await fs.promises.readFile(join(__dirname, "..", "docs", "img", "icon.png"), null) + /** @type {Ty.R.FileUploaded} */ + const root = await mreq.mreq("POST", "/media/v3/upload", avatarBuffer, { + headers: {"Content-Type": "image/png"} + }) + const avatarUrl = root.content_uri + assert(avatarUrl) console.log("✅ Matrix appservice login works...") @@ -352,8 +359,7 @@ function defineEchoHandler() { console.log("✅ Emojis are ready...") // set profile data on discord... - const avatarImageBuffer = await fetch("https://cadence.moe/friends/out_of_your_element.png").then(res => res.arrayBuffer()) - await discord.snow.user.updateSelf({avatar: "data:image/png;base64," + Buffer.from(avatarImageBuffer).toString("base64")}) + await discord.snow.user.updateSelf({avatar: "data:image/png;base64," + avatarBuffer.toString("base64")}) console.log("✅ Discord profile updated...") // set profile data on homeserver... diff --git a/src/m2d/actions/sticker.js b/src/m2d/actions/sticker.js new file mode 100644 index 0000000..341d8b0 --- /dev/null +++ b/src/m2d/actions/sticker.js @@ -0,0 +1,40 @@ +// @ts-check + +const {Readable} = require("stream") +const {ReadableStream} = require("stream/web") + +const {sync} = require("../../passthrough") +const sharp = require("sharp") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") +const streamMimeType = require("stream-mime-type") + +const WIDTH = 160 +const HEIGHT = 160 +/** + * Downloads the sticker from the web and converts to webp data. + * @param {string} mxc a single mxc:// URL + * @returns {Promise} sticker webp data, or undefined if the downloaded sticker is not valid + */ +async function getAndResizeSticker(mxc) { + const res = await api.getMedia(mxc) + if (res.status !== 200) { + const root = await res.json() + throw new mreq.MatrixServerError(root, {mxc}) + } + + const streamIn = Readable.fromWeb(res.body) + const { stream, mime } = await streamMimeType.getMimeType(streamIn) + const animated = ["image/gif", "image/webp"].includes(mime) + + const transformer = sharp({animated: animated}) + .resize(WIDTH, HEIGHT, {fit: "inside", background: {r: 0, g: 0, b: 0, alpha: 0}}) + .webp() + stream.pipe(transformer) + return Readable.toWeb(transformer) +} + + +module.exports.getAndResizeSticker = getAndResizeSticker diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index 7eee659..f4e3ba4 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -633,23 +633,10 @@ async function eventToMessage(event, guild, channel, di) { } if (event.type === "m.sticker") { - content = "" - let filename = event.content.body - if (event.type === "m.sticker") { - let mimetype - if (event.content.info?.mimetype?.includes("/")) { - mimetype = event.content.info.mimetype - } else { - const res = await di.api.getMedia(event.content.url, {method: "HEAD"}) - if (res.status === 200) { - mimetype = res.headers.get("content-type") - } - if (!mimetype) throw new Error(`Server error ${res.status} or missing content-type while detecting sticker mimetype`) - } - filename += "." + mimetype.split("/")[1] - } - attachments.push({id: "0", filename}) - pendingFiles.push({name: filename, mxc: event.content.url}) + const withoutMxc = mxUtils.makeMxcPublic(event.content.url) + assert(withoutMxc) + const url = `${reg.ooye.bridge_origin}/download/sticker/${withoutMxc}/_.webp` + content = `[${event.content.body || "\u2800"}](${url})` } else if (event.type === "org.matrix.msc3381.poll.start") { const pollContent = event.content["org.matrix.msc3381.poll.start"] // just for convenience diff --git a/src/web/routes/download-matrix.js b/src/web/routes/download-matrix.js index bb6b850..82e2f7e 100644 --- a/src/web/routes/download-matrix.js +++ b/src/web/routes/download-matrix.js @@ -16,6 +16,9 @@ const emojiSheet = sync.require("../../m2d/actions/emoji-sheet") /** @type {import("../../m2d/converters/emoji-sheet")} */ const emojiSheetConverter = sync.require("../../m2d/converters/emoji-sheet") +/** @type {import("../../m2d/actions/sticker")} */ +const sticker = sync.require("../../m2d/actions/sticker") + const schema = { params: z.object({ server_name: z.string(), @@ -23,6 +26,10 @@ const schema = { }), sheet: z.object({ e: z.array(z.string()).or(z.string()) + }), + sticker: z.object({ + server_name: z.string().regex(/^[^/]+$/), + media_id: z.string().regex(/^[A-Za-z0-9_-]+$/) }) } @@ -90,3 +97,14 @@ as.router.get(`/download/sheet`, defineEventHandler(async event => { setResponseHeader(event, "Content-Type", "image/png") return buffer })) + +as.router.get(`/download/sticker/:server_name/:media_id/_.webp`, defineEventHandler(async event => { + const {server_name, media_id} = await getValidatedRouterParams(event, schema.sticker.parse) + /** remember that this has no mxc:// protocol in the string */ + const mxc = server_name + "/" + media_id + verifyMediaHash(mxc) + + const stream = await sticker.getAndResizeSticker(`mxc://${mxc}`) + setResponseHeader(event, "Content-Type", "image/webp") + return stream +}))