diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index f5fe5ef..ff3c1de 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message, guild) + const events = await messageToEvent.messageToEvent(message, guild, api) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 9999df9..0e94a70 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const markdown = require("discord-markdown") +const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const { sync, db, discord } = passthrough @@ -39,10 +40,56 @@ function getDiscordParseCallbacks(message, useHTML) { /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild + * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API */ -async function messageToEvent(message, guild) { +async function messageToEvent(message, guild, api) { const events = [] + /** + @type {{room?: boolean, user_ids?: string[]}} + We should consider the following scenarios for mentions: + 1. TODO A discord user rich-replies to a matrix user with a text post + + The matrix user needs to be m.mentioned in the text event + + The matrix user needs to have their name/mxid/link in the text event (notification fallback) + - So prepend their `@name:` to the start of the plaintext body + 2. TODO A discord user rich-replies to a matrix user with an image event only + + The matrix user needs to be m.mentioned in the image event + + The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) + - So append their name to the filename body, I guess!!! + 3. TODO A discord user `@`s a matrix user in the text body of their text box + + The matrix user needs to be m.mentioned in the text event + + No change needed to the text event content: it already has their name + - So make sure we don't do anything in this case. + */ + const mentions = {} + let repliedToEventId = null + let repliedToEventRoomId = null + let repliedToEventSenderMxid = null + let repliedToEventOriginallyFromMatrix = false + + function addMention(mxid) { + if (!mentions.user_ids) mentions.user_ids = [] + mentions.user_ids.push(mxid) + } + + // Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions + // (Still need to do scenarios 1 and 2 part B, and scenario 3.) + if (message.type === DiscordTypes.MessageType.Reply && message.message_reference?.message_id) { + const row = db.prepare("SELECT event_id, room_id, source FROM event_message INNER JOIN channel_room USING (channel_id) WHERE message_id = ? AND part = 0").get(message.message_reference.message_id) + if (row) { + repliedToEventId = row.event_id + repliedToEventRoomId = row.room_id + repliedToEventOriginallyFromMatrix = row.source === 0 // source 0 = matrix + } + } + if (repliedToEventOriginallyFromMatrix) { + // Need to figure out who sent that event... + const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) + repliedToEventSenderMxid = event.sender + // Need to add the sender to m.mentions + addMention(repliedToEventSenderMxid) + } + // Text content appears first if (message.content) { let content = message.content @@ -55,33 +102,63 @@ async function messageToEvent(message, guild) { } }) - const html = markdown.toHTML(content, { + let html = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, true) }, null, null) - const body = markdown.toHTML(content, { + let body = markdown.toHTML(content, { discordCallback: getDiscordParseCallbacks(message, false), discordOnly: true, escapeHTML: false, }, null, null) + // Fallback body/formatted_body for replies + if (repliedToEventId) { + let repliedToDisplayName + let repliedToUserHtml + if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { + const match = repliedToEventSenderMxid.match(/^@([^:]*)/) + assert(match) + repliedToDisplayName = match[1] || "a Matrix user" // grab the localpart as the display name, whatever + repliedToUserHtml = `${repliedToDisplayName}` + } else { + repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" + repliedToUserHtml = repliedToDisplayName + } + const repliedToContent = message.referenced_message?.content || "[Replied-to message content wasn't provided by Discord]" + const repliedToHtml = markdown.toHTML(repliedToContent, { + discordCallback: getDiscordParseCallbacks(message, true) + }, null, null) + const repliedToBody = markdown.toHTML(repliedToContent, { + discordCallback: getDiscordParseCallbacks(message, false), + discordOnly: true, + escapeHTML: false, + }, null, null) + html = `
In reply to ${repliedToUserHtml}` + + `
${repliedToHtml}
` + + html + body = (`${repliedToDisplayName}: ` // scenario 1 part B for mentions + + repliedToBody).split("\n").map(line => "> " + line).join("\n") + + "\n\n" + body + } + + const newTextMessageEvent = { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: body + } + const isPlaintext = body === html - if (isPlaintext) { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body - }) - } else { - events.push({ - $type: "m.room.message", - msgtype: "m.text", - body: body, + if (!isPlaintext) { + Object.assign(newTextMessageEvent, { format: "org.matrix.custom.html", formatted_body: html }) } + + events.push(newTextMessageEvent) } // Then attachments @@ -90,6 +167,7 @@ async function messageToEvent(message, guild) { if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.image", url: await file.uploadDiscordFileToMxc(attachment.url), external_url: attachment.url, @@ -105,6 +183,7 @@ async function messageToEvent(message, guild) { } else { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.text", body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) } @@ -122,6 +201,7 @@ async function messageToEvent(message, guild) { if (sticker && sticker.description) body += ` - ${sticker.description}` return { $type: "m.sticker", + "m.mentions": mentions, body, info: { mimetype: format.mime @@ -131,6 +211,7 @@ async function messageToEvent(message, guild) { } else { return { $type: "m.room.message", + "m.mentions": mentions, msgtype: "m.text", body: "Unsupported sticker format. Name: " + stickerItem.name } @@ -139,6 +220,17 @@ async function messageToEvent(message, guild) { events.push(...stickerEvents) } + // Rich replies + if (repliedToEventId) { + Object.assign(events[0], { + "m.relates_to": { + "m.in_reply_to": { + event_id: repliedToEventId + } + } + }) + } + return events } diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index e3fcb06..5fa16f8 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -1,11 +1,39 @@ const {test} = require("supertape") const {messageToEvent} = require("./message-to-event") const data = require("../../test/data") +const Ty = require("../../types") + +/** + * @param {string} roomID + * @param {string} eventID + * @returns {(roomID: string, eventID: string) => Promise>} + */ +function mockGetEvent(t, roomID_in, eventID_in, outer) { + return async function(roomID, eventID) { + t.equal(roomID, roomID_in) + t.equal(eventID, eventID_in) + return new Promise(resolve => { + setTimeout(() => { + resolve({ + event_id: eventID_in, + room_id: roomID_in, + origin_server_ts: 1680000000000, + unsigned: { + age: 2245, + transaction_id: "$local.whatever" + }, + ...outer + }) + }) + }) + } +} test("message2event: simple plaintext", async t => { const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "ayy lmao" }]) @@ -15,6 +43,7 @@ test("message2event: simple user mention", async t => { const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "@crunch god: Tell me about Phil, renowned martial arts master and creator of the Chin Trick", format: "org.matrix.custom.html", @@ -26,6 +55,7 @@ test("message2event: simple room mention", async t => { const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "#main", format: "org.matrix.custom.html", @@ -37,6 +67,7 @@ test("message2event: simple message link", async t => { const events = await messageToEvent(data.message.simple_message_link, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "https://matrix.to/#/!kLRqKKUQXcibIMtOpl:cadence.moe/$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", format: "org.matrix.custom.html", @@ -48,6 +79,7 @@ test("message2event: attachment with no content", async t => { const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.image", url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", body: "image.png", @@ -65,10 +97,12 @@ test("message2event: stickers", async t => { const events = await messageToEvent(data.message.sticker, data.guild.general) t.deepEqual(events, [{ $type: "m.room.message", + "m.mentions": {}, msgtype: "m.text", body: "can have attachments too" }, { $type: "m.room.message", + "m.mentions": {}, msgtype: "m.image", url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", body: "image.png", @@ -81,6 +115,7 @@ test("message2event: stickers", async t => { }, }, { $type: "m.sticker", + "m.mentions": {}, body: "pomu puff - damn that tiny lil bitch really chuffing. puffing that fat ass dart", info: { mimetype: "image/png" @@ -90,3 +125,94 @@ test("message2event: stickers", async t => { url: "mxc://cadence.moe/UuUaLwXhkxFRwwWCXipDlBHn" }]) }) + +test("message2event: skull webp attachment with content", async t => { + const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "Image" + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.image", + body: "skull.webp", + info: { + w: 1200, + h: 628, + mimetype: "image/webp", + size: 74290 + }, + external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + url: "mxc://cadence.moe/sDxWmDErBhYBxtDcJQgBETes" + }]) +}) + +test("message2event: reply to skull webp attachment with content", async t => { + const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$oLyUTyZ_7e_SUzGNWZKz880ll9amLZvXGbArJCKai2Q" + } + }, + "m.mentions": {}, + msgtype: "m.text", + body: "> Extremity: Image\n\nReply", + format: "org.matrix.custom.html", + formatted_body: + '
In reply to Extremity' + + '
Image
' + + 'Reply' + }, { + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.image", + body: "RDT_20230704_0936184915846675925224905.jpg", + info: { + w: 2048, + h: 1536, + mimetype: "image/jpeg", + size: 85906 + }, + external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + url: "mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa" + }]) +}) + +test("message2event: simple reply to matrix user", async t => { + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + }, + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe" + ] + }, + msgtype: "m.text", + body: "> cadence: so can you reply to my webhook uwu\n\nReply", + format: "org.matrix.custom.html", + formatted_body: + '
In reply to cadence' + + '
so can you reply to my webhook uwu
' + + 'Reply' + }]) +}) + +// TODO: read "edits of replies" in the spec diff --git a/matrix/api.js b/matrix/api.js index cf22933..ed9980b 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -65,6 +65,17 @@ async function leaveRoom(roomID, mxid) { await mreq.mreq("POST", path(`/client/v3/rooms/${roomID}/leave`, mxid), {}) } +/** + * @param {string} roomID + * @param {string} eventID + * @template T + */ +async function getEvent(roomID, eventID) { + /** @type {Ty.Event.Outer} */ + const root = await mreq.mreq("GET", `/client/v3/rooms/${roomID}/event/${eventID}`) + return root +} + /** * @param {string} roomID * @returns {Promise} @@ -73,6 +84,15 @@ function getAllState(roomID) { return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) } +/** + * "Any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server." + * @param {string} roomID + * @returns {Promise<{joined: {[mxid: string]: Ty.R.RoomMember}}>} + */ +function getJoinedMembers(roomID) { + return mreq.mreq("GET", `/client/v3/rooms/${roomID}/joined_members`) +} + /** * @param {string} roomID * @param {string} type @@ -114,7 +134,9 @@ module.exports.createRoom = createRoom module.exports.joinRoom = joinRoom module.exports.inviteToRoom = inviteToRoom module.exports.leaveRoom = leaveRoom +module.exports.getEvent = getEvent module.exports.getAllState = getAllState +module.exports.getJoinedMembers = getJoinedMembers module.exports.sendState = sendState module.exports.sendEvent = sendEvent module.exports.profileSetDisplayname = profileSetDisplayname diff --git a/package.json b/package.json index 7fb8cc6..8604330 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,6 @@ "tap-dot": "github:cloudrac3r/tap-dot#223a4e67a6f7daf015506a12a7af74605f06c7f4" }, "scripts": { - "test": "cross-env FORCE_COLOR=true supertape --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" } } diff --git a/test/data.js b/test/data.js index d2c586a..7c8fadc 100644 --- a/test/data.js +++ b/test/data.js @@ -423,6 +423,434 @@ module.exports = { flags: 0, components: [] }, + skull_webp_attachment_with_content: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + referenced_message: null, + pinned: false, + nonce: "1128084721398448128", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ], + guild_id: "112760669178241024" + }, + reply_to_skull_webp_attachment_with_content: { + type: 19, + tts: false, + timestamp: "2023-07-10T22:06:27.348000+00:00", + referenced_message: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ] + }, + pinned: false, + nonce: "1128084845403045888", + message_reference: { + message_id: "1128084748338741392", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [ + { + username: "extremity", + public_flags: 768, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + } + ], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084851279536279", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Reply", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 2048, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + size: 85906, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + id: "1128084851023675515", + height: 1536, + filename: "RDT_20230704_0936184915846675925224905.jpg", + content_type: "image/jpeg" + } + ], + guild_id: "112760669178241024" + }, + simple_reply_to_matrix_user: { + type: 19, + tts: false, + timestamp: "2023-07-11T00:19:04.358000+00:00", + referenced_message: { + webhook_id: "703458020193206272", + type: 0, + tts: false, + timestamp: "2023-07-11T00:18:52.856000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128118177155526666", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "so can you reply to my webhook uwu", + components: [], + channel_id: "112760669178241024", + author: { + username: "cadence", + id: "703458020193206272", + discriminator: "0000", + bot: true, + avatar: "ea5413d310c85eb9edaa9db865e80155" + }, + attachments: [], + application_id: "684280192553844747" + }, + pinned: false, + nonce: "1128118222315323392", + message_reference: { + message_id: "1128118177155526666", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1128118225398407228", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Reply", + components: [], + channel_id: "112760669178241024", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" + }, + edit_of_reply_to_skull_webp_attachment_with_content: { + type: 19, + tts: false, + timestamp: "2023-07-10T22:06:27.348000+00:00", + referenced_message: { + type: 0, + tts: false, + timestamp: "2023-07-10T22:06:02.805000+00:00", + pinned: false, + mentions: [], + mention_roles: [], + mention_everyone: false, + id: "1128084748338741392", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "Image", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 1200, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + size: 74290, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084747910918195/skull.webp", + id: "1128084747910918195", + height: 628, + filename: "skull.webp", + content_type: "image/webp" + } + ] + }, + pinned: false, + message_reference: { + message_id: "1128084748338741392", + guild_id: "112760669178241024", + channel_id: "112760669178241024" + }, + mentions: [ + { + username: "extremity", + public_flags: 768, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + } + ], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", + "118924814567211009", + "199995902742626304", + "204427286542417920", + "222168467627835392", + "271173313575780353", + "392141548932038658", + "1040735082610167858", + "372954403902193689", + "1124134606514442300", + "585531096071012409" + ], + premium_since: "2022-04-20T21:11:14.016000+00:00", + pending: false, + nick: "Tap to add a nickname", + mute: false, + joined_at: "2022-04-20T20:16:02.828000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: "a_4ea72c7b058ad848c9d9d35479fac26e" + }, + id: "1128084851279536279", + flags: 0, + embeds: [], + edited_timestamp: "2023-07-10T22:08:57.442417+00:00", + content: "Edit", + components: [], + channel_id: "112760669178241024", + author: { + username: "extremity", + public_flags: 768, + id: "114147806469554185", + global_name: "Extremity", + discriminator: "0", + avatar_decoration: null, + avatar: "6628aaf6b27219c36e2d3b5cfd6d0ee6" + }, + attachments: [ + { + width: 2048, + url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + size: 85906, + proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + id: "1128084851023675515", + height: 1536, + filename: "RDT_20230704_0936184915846675925224905.jpg", + content_type: "image/jpeg" + } + ], + guild_id: "112760669178241024" + }, sticker: { id: "1106366167788044450", type: 0, diff --git a/types.d.ts b/types.d.ts index 01ff6a1..3ed3975 100644 --- a/types.d.ts +++ b/types.d.ts @@ -81,6 +81,11 @@ namespace R { room_id: string } + export type RoomMember = { + avatar_url: string + display_name: string + } + export type FileUploaded = { content_uri: string }