Compare commits

..

2 commits

Author SHA1 Message Date
011889216b Add lottie sticker message test data 2024-01-18 17:04:00 +13:00
64beb6c996 Code coverage for lottie 2024-01-18 17:03:17 +13:00
10 changed files with 152 additions and 37 deletions

53
d2m/actions/lottie.js Normal file
View 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

View file

@ -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

View 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)
}
})

View file

@ -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")} */

View file

@ -1331,6 +1331,38 @@ module.exports = {
name: "pomu puff"
}]
},
lottie_sticker: {
id: "1106366167788044450",
type: 0,
content: "",
channel_id: "122155380120748034",
author: {
id: "113340068197859328",
username: "Cookie 🍪",
global_name: null,
display_name: null,
avatar: "b48302623a12bc7c59a71328f72ccb39",
discriminator: "7766",
public_flags: 128,
avatar_decoration: null
},
attachments: [],
embeds: [],
mentions: [],
mention_roles: [],
pinned: false,
mention_everyone: false,
tts: false,
timestamp: "2023-05-11T23:44:09.690000+00:00",
edited_timestamp: null,
flags: 0,
components: [],
sticker_items: [{
id: "860171525772279849",
format_type: 3,
name: "8"
}]
},
message_in_thread: {
type: 0,
tts: false,

View file

@ -103,6 +103,9 @@ INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES
('!maggESguZBqGBZtSnr:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'),
('!CzvdIdUQXgUjDVKxeU:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU');
INSERT INTO lottie (sticker_id, mxc_url) VALUES
('860171525772279849', 'mxc://cadence.moe/ZtvvVbwMIdUZeovWVyGVFCeR');
INSERT INTO "auto_emoji" ("name","emoji_id","guild_id") VALUES
('L1','1144820033948762203','529176156398682115'),
('L2','1144820084079087647','529176156398682115'),

1
test/res/lottie-bee.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
# Source
Flying Bee by Afif Ridwan
https://lottiefiles.com/animations/flying-bee-WkXvUiWkZ1
# License
Lottie Simple License (FL 9.13.21) https://lottiefiles.com/page/license
Copyright © 2021 Design Barn Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of the public animation files available for download at the LottieFiles site (“Files”) to download, reproduce, modify, publish, distribute, publicly display, and publicly digitally perform such Files, including for commercial purposes, provided that any display, publication, performance, or distribution of Files must contain (and be subject to) the same terms and conditions of this license. Modifications to Files are deemed derivative works and must also be expressly distributed under the same terms and conditions of this license. You may not purport to impose any additional or different terms or conditions on, or apply any technical measures that restrict exercise of, the rights granted under this license. This license does not include the right to collect or compile Files from LottieFiles to replicate or develop a similar or competing service.
Use of Files without attributing the creator(s) of the Files is permitted under this license, though attribution is strongly encouraged. If attributions are included, such attributions should be visible to the end user.
FILES ARE PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL THE CREATOR(S) OF FILES OR DESIGN BARN, INC. BE LIABLE ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF SUCH FILES.

BIN
test/res/lottie-bee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -60,6 +60,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../d2m/actions/register-user.test")
require("../d2m/converters/edit-to-changes.test")
require("../d2m/converters/emoji-to-key.test")
require("../d2m/converters/lottie.test")
require("../d2m/converters/message-to-event.test")
require("../d2m/converters/message-to-event.embeds.test")
require("../d2m/converters/pins-to-list.test")