diff --git a/src/d2m/actions/register-pk-user.js b/src/d2m/actions/register-pk-user.js index 281bfcb..d127f21 100644 --- a/src/d2m/actions/register-pk-user.js +++ b/src/d2m/actions/register-pk-user.js @@ -106,6 +106,7 @@ async function ensureSimJoined(pkMessage, roomID) { * Generate profile data based on webhook displayname and configured avatar. * @param {Ty.PkMessage} pkMessage * @param {Ty.WebhookAuthor} author + * @returns {Promise} */ async function memberToStateContent(pkMessage, author) { // We prefer to use the member's avatar URL data since the image upload can be cached across channels, diff --git a/src/d2m/actions/register-plu-ral-user.js b/src/d2m/actions/register-plu-ral-user.js index 4e1d347..5830ce8 100644 --- a/src/d2m/actions/register-plu-ral-user.js +++ b/src/d2m/actions/register-plu-ral-user.js @@ -111,6 +111,7 @@ async function ensureSimJoined(pluRalMessage, roomID) { * Generate profile data based on webhook displayname and configured avatar. * @param {Ty.PluRalWebhookMessage} pluRalMessage * @param {Ty.WebhookAuthor} author + * @returns {Promise} */ async function memberToStateContent(pluRalMessage, author) { // We prefer to use the member's avatar URL data since the image upload can be cached across channels, diff --git a/src/d2m/actions/register-user.js b/src/d2m/actions/register-user.js index 3427bbd..db04c8e 100644 --- a/src/d2m/actions/register-user.js +++ b/src/d2m/actions/register-user.js @@ -102,8 +102,8 @@ async function userToGlobalProfile(user) { globalProfile.displayname = user.username if (user.global_name) globalProfile.displayname = user.global_name - if (user.avatar) { - const avatarPath = file.userAvatar(user) // the user avatar only + const avatarPath = file.userAvatar(user) // the user avatar only + if (avatarPath) { globalProfile.avatar_url = await file.uploadDiscordFileToMxc(avatarPath) } @@ -113,6 +113,8 @@ async function userToGlobalProfile(user) { /** * @param {DiscordTypes.APIUser} user * @param {Omit | undefined} member + * @param {string} guildID + * @returns {Promise} */ async function memberToStateContent(user, member, guildID) { let displayname = user.username @@ -132,9 +134,9 @@ async function memberToStateContent(user, member, guildID) { } } - if (member?.avatar || user.avatar) { - // const avatarPath = file.userAvatar(user) // the user avatar only - const avatarPath = file.memberAvatar(guildID, user, member) // the member avatar or the user avatar + // const avatarPath = file.userAvatar(user) // the user avatar only + const avatarPath = file.memberAvatar(guildID, user, member) // the member avatar or the user avatar + if (avatarPath) { content["moe.cadence.ooye.member"].avatar = avatarPath content.avatar_url = await file.uploadDiscordFileToMxc(avatarPath) } diff --git a/src/d2m/actions/register-webhook-user.js b/src/d2m/actions/register-webhook-user.js index 5114a8e..4dc3c3f 100644 --- a/src/d2m/actions/register-webhook-user.js +++ b/src/d2m/actions/register-webhook-user.js @@ -87,6 +87,7 @@ async function ensureSimJoined(fakeUserID, author, roomID) { /** * Generate profile data based on webhook displayname and configured avatar. * @param {Ty.WebhookAuthor} author + * @returns {Promise} */ async function authorToStateContent(author) { const avatar = file.userAvatar(author) diff --git a/src/matrix/file.js b/src/matrix/file.js index 240999c..e1e6542 100644 --- a/src/matrix/file.js +++ b/src/matrix/file.js @@ -1,5 +1,6 @@ // @ts-check +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../passthrough") const {reg, writeRegistration} = require("./read-registration.js") const Ty = require("../types") @@ -138,14 +139,30 @@ async function _actuallyUploadDiscordFileToMxc(url) { } } +/** + * @param {DiscordTypes.APIGuild} guild + * @returns {string | null} + */ function guildIcon(guild) { + if (!guild.icon) return null return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } +/** + * @param {DiscordTypes.APIUser | Ty.WebhookAuthor} user + * @returns {string | null} + */ function userAvatar(user) { + if (!user.avatar) return null return `/avatars/${user.id}/${user.avatar}.png?size=${IMAGE_SIZE}` } +/** + * @param {string} guildID + * @param {DiscordTypes.APIUser} user + * @param {Omit | undefined} member + * @returns {string} + */ function memberAvatar(guildID, user, member) { if (!member?.avatar) return userAvatar(user) return `/guilds/${guildID}/users/${user.id}/avatars/${member?.avatar}.png?size=${IMAGE_SIZE}` diff --git a/src/matrix/file.test.js b/src/matrix/file.test.js index aa7e6cb..6d66ed7 100644 --- a/src/matrix/file.test.js +++ b/src/matrix/file.test.js @@ -26,3 +26,33 @@ test("removeExpiryParams: works on media proxy and keeps quality", t => { const result = file._removeExpiryParams(url) t.equal(result, "https://media.discordapp.net/attachments/1160894080998461480/1548665030898094240/855064e9-bdae-487a-a497-c662bd510487.png?format=webp") }) + +test("guildIcon: returns icon path", t => { + t.equal( + // @ts-ignore + file.guildIcon({id: "112760669178241024", icon: "a_f83622e09ead74f0c5c527fe241f8f8c"}), + "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024" + ) +}) + +test("guildIcon: returns null if guild has no icon", t => { + t.equal( + // @ts-ignore + file.guildIcon({id: "112760669178241024", icon: null}), + null + ) +}) + +test("userAvatar: returns avatar path", t => { + t.equal( + file.userAvatar({id: "113340068197859328", username: "kumaccino", avatar: "b48302623a12bc7c59a71328f72ccb39"}), + "/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024" + ) +}) + +test("userAvatar: returns null if user has no avatar", t => { + t.equal( + file.userAvatar({id: "113340068197859328", username: "kumaccino", avatar: null}), + null + ) +}) diff --git a/src/matrix/kstate.js b/src/matrix/kstate.js index 3648f2d..c110fdf 100644 --- a/src/matrix/kstate.js +++ b/src/matrix/kstate.js @@ -32,7 +32,11 @@ async function kstateUploadMxc(obj) { for (const [k, v] of Object.entries(obj)) { if (v == null || typeof v !== "object") continue - if (v.$url) { + if ("$url" in v) { + if (!v.$url) { + delete obj[k] + continue + } promises.push( file.uploadDiscordFileToMxc(v.$url) .then(mxc => obj[k] = mxc) diff --git a/src/matrix/kstate.test.js b/src/matrix/kstate.test.js index b67a725..1cc600b 100644 --- a/src/matrix/kstate.test.js +++ b/src/matrix/kstate.test.js @@ -21,6 +21,18 @@ test("kstate strip: keeps true conditions while removing $if", t => { }) }) +test("kstateUploadMxc: removes keys where $url is null", async t => { + const input = { + "m.room.avatar/": { + url: {$url: null} + } + } + await kstateUploadMxc(input) + t.deepEqual(input, { + "m.room.avatar/": {} + }) +}) + test("kstateUploadMxc: sets the mxc", async t => { const input = { "m.room.avatar/": {