Code coverage for lottie
This commit is contained in:
parent
8987107685
commit
64beb6c996
8 changed files with 117 additions and 37 deletions
53
d2m/actions/lottie.js
Normal file
53
d2m/actions/lottie.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
// @ts-check
|
||||
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const Ty = require("../../types")
|
||||
const assert = require("assert").strict
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {sync, db, select} = passthrough
|
||||
/** @type {import("../../matrix/file")} */
|
||||
const file = sync.require("../../matrix/file")
|
||||
/** @type {import("../../matrix/mreq")} */
|
||||
const mreq = sync.require("../../matrix/mreq")
|
||||
/** @type {import("../converters/lottie")} */
|
||||
const convertLottie = sync.require("../converters/lottie")
|
||||
|
||||
const INFO = {
|
||||
mimetype: "image/png",
|
||||
w: convertLottie.SIZE,
|
||||
h: convertLottie.SIZE
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APIStickerItem} stickerItem
|
||||
* @returns {Promise<{mxc_url: string, info: typeof INFO}>}
|
||||
*/
|
||||
async function convert(stickerItem) {
|
||||
// Reuse sticker if already converted and uploaded
|
||||
const existingMxc = select("lottie", "mxc_url", {sticker_id: stickerItem.id}).pluck().get()
|
||||
if (existingMxc) return {mxc_url: existingMxc, info: INFO}
|
||||
|
||||
// Fetch sticker data from Discord
|
||||
const res = await fetch(file.DISCORD_IMAGES_BASE + file.sticker(stickerItem))
|
||||
if (res.status !== 200) throw new Error("Sticker data file not found.")
|
||||
const text = await res.text()
|
||||
|
||||
// Convert to PNG (readable stream)
|
||||
const readablePng = await convertLottie.convert(text)
|
||||
|
||||
// Upload to MXC
|
||||
/** @type {Ty.R.FileUploaded} */
|
||||
const root = await mreq.mreq("POST", "/media/v3/upload", readablePng, {
|
||||
headers: {
|
||||
"Content-Type": INFO.mimetype
|
||||
}
|
||||
})
|
||||
assert(root.content_uri)
|
||||
|
||||
// Save the link for next time
|
||||
db.prepare("INSERT INTO lottie (sticker_id, mxc_url) VALUES (?, ?)").run(stickerItem.id, root.content_uri)
|
||||
return {mxc_url: root.content_uri, info: INFO}
|
||||
}
|
||||
|
||||
module.exports.convert = convert
|
|
@ -1,25 +1,10 @@
|
|||
// @ts-check
|
||||
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const Ty = require("../../types")
|
||||
const assert = require("assert").strict
|
||||
const stream = require("stream")
|
||||
const {PNG} = require("pngjs")
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {sync, db, discord, select} = passthrough
|
||||
/** @type {import("../../matrix/file")} */
|
||||
const file = sync.require("../../matrix/file")
|
||||
//** @type {import("../../matrix/mreq")} */
|
||||
const mreq = sync.require("../../matrix/mreq")
|
||||
|
||||
const SIZE = 160 // Discord's display size on 1x displays is 160
|
||||
|
||||
const INFO = {
|
||||
mimetype: "image/png",
|
||||
w: SIZE,
|
||||
h: SIZE
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef RlottieWasm
|
||||
* @prop {(string) => boolean} load load lottie data from string of json
|
||||
|
@ -34,16 +19,11 @@ const Rlottie = (async () => {
|
|||
})()
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APIStickerItem} stickerItem
|
||||
* @returns {Promise<{mxc_url: string, info: typeof INFO}>}
|
||||
* @param {string} text
|
||||
* @returns {Promise<import("stream").Readable>}
|
||||
*/
|
||||
async function convert(stickerItem) {
|
||||
const existingMxc = select("lottie", "mxc_url", {sticker_id: stickerItem.id}).pluck().get()
|
||||
if (existingMxc) return {mxc_url: existingMxc, info: INFO}
|
||||
async function convert(text) {
|
||||
const r = await Rlottie
|
||||
const res = await fetch(file.DISCORD_IMAGES_BASE + file.sticker(stickerItem))
|
||||
if (res.status !== 200) throw new Error("Sticker data file not found.")
|
||||
const text = await res.text()
|
||||
/** @type RlottieWasm */
|
||||
const rh = new r.RlottieWasm()
|
||||
const status = rh.load(text)
|
||||
|
@ -58,17 +38,12 @@ async function convert(stickerItem) {
|
|||
inputHasAlpha: true,
|
||||
})
|
||||
png.data = Buffer.from(rendered)
|
||||
// @ts-ignore wrong type from pngjs
|
||||
const readablePng = png.pack()
|
||||
/** @type {Ty.R.FileUploaded} */
|
||||
const root = await mreq.mreq("POST", "/media/v3/upload", readablePng, {
|
||||
headers: {
|
||||
"Content-Type": INFO.mimetype
|
||||
}
|
||||
})
|
||||
assert(root.content_uri)
|
||||
db.prepare("INSERT INTO lottie (sticker_id, mxc_url) VALUES (?, ?)").run(stickerItem.id, root.content_uri)
|
||||
return {mxc_url: root.content_uri, info: INFO}
|
||||
// The transform stream is necessary because PNG requires me to pipe it somewhere before this event loop ends
|
||||
const resultStream = png.pack()
|
||||
const p = new stream.PassThrough()
|
||||
resultStream.pipe(p)
|
||||
return p
|
||||
}
|
||||
|
||||
module.exports.convert = convert
|
||||
module.exports.SIZE = SIZE
|
||||
|
|
34
d2m/converters/lottie.test.js
Normal file
34
d2m/converters/lottie.test.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
// @ts-check
|
||||
|
||||
const fs = require("fs")
|
||||
const stream = require("stream")
|
||||
const {test} = require("supertape")
|
||||
const {convert} = require("./lottie")
|
||||
|
||||
const WRITE_PNG = false
|
||||
|
||||
test("lottie: can convert and save PNG", async t => {
|
||||
const input = await fs.promises.readFile("test/res/lottie-bee.json", "utf8")
|
||||
const resultStream = await convert(input)
|
||||
/* c8 ignore next 3 */
|
||||
if (WRITE_PNG) {
|
||||
resultStream.pipe(fs.createWriteStream("test/res/lottie-bee.png"))
|
||||
t.fail("PNG written to /test/res/lottie-bee.png, please manually check it")
|
||||
} else {
|
||||
const expected = await fs.promises.readFile("test/res/lottie-bee.png")
|
||||
const actual = Buffer.alloc(expected.length)
|
||||
let i = 0
|
||||
await stream.promises.pipeline(
|
||||
resultStream,
|
||||
async function* (source) {
|
||||
for await (const chunk of source) {
|
||||
chunk.copy(actual, i)
|
||||
i += chunk.length
|
||||
}
|
||||
},
|
||||
new stream.PassThrough()
|
||||
)
|
||||
t.equal(i, actual.length, `allocated ${actual.length} bytes, but wrote ${i}`)
|
||||
t.deepEqual(actual, expected)
|
||||
}
|
||||
})
|
|
@ -12,8 +12,8 @@ const {sync, db, discord, select, from} = passthrough
|
|||
const file = sync.require("../../matrix/file")
|
||||
/** @type {import("./emoji-to-key")} */
|
||||
const emojiToKey = sync.require("./emoji-to-key")
|
||||
/** @type {import("./lottie")} */
|
||||
const lottie = sync.require("./lottie")
|
||||
/** @type {import("../actions/lottie")} */
|
||||
const lottie = sync.require("../actions/lottie")
|
||||
/** @type {import("../../m2d/converters/utils")} */
|
||||
const mxUtils = sync.require("../../m2d/converters/utils")
|
||||
/** @type {import("../../discord/utils")} */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue