diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 78985fa..5994ff3 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -86,6 +86,103 @@ const embedTitleParser = markdown.markdownEngine.parserFor({ link: undefined }) +/** + * @param {{room?: boolean, user_ids?: string[]}} mentions + * @param {DiscordTypes.APIAttachment} attachment + */ +async function attachmentToEvent(mentions, attachment) { + const emoji = + attachment.content_type?.startsWith("image/jp") ? "📸" + : attachment.content_type?.startsWith("image/") ? "🖼️" + : attachment.content_type?.startsWith("video/") ? "🎞️" + : attachment.content_type?.startsWith("text/") ? "📝" + : attachment.content_type?.startsWith("audio/") ? "🎶" + : "📄" + // no native media spoilers in Element, so we'll post a link instead, forcing it to not preview using a blockquote + if (attachment.filename.startsWith("SPOILER_")) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: `${emoji} Uploaded SPOILER file: ${attachment.url} (${pb(attachment.size)})`, + format: "org.matrix.custom.html", + formatted_body: `
${emoji} Uploaded SPOILER file: ${attachment.url} (${pb(attachment.size)})
` + } + } + // for large files, always link them instead of uploading so I don't use up all the space in the content repo + else if (attachment.size > reg.ooye.max_file_size) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.text", + body: `${emoji} Uploaded file: ${attachment.url} (${pb(attachment.size)})`, + format: "org.matrix.custom.html", + formatted_body: `${emoji} Uploaded file: ${attachment.filename} (${pb(attachment.size)})` + } + } else 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, + body: attachment.filename, + filename: attachment.filename, + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } + } else if (attachment.content_type?.startsWith("video/") && attachment.width && attachment.height) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.video", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.description || attachment.filename, + filename: attachment.filename, + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } + } else if (attachment.content_type?.startsWith("audio/")) { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.audio", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.description || attachment.filename, + filename: attachment.filename, + info: { + mimetype: attachment.content_type, + size: attachment.size, + duration: attachment.duration_secs ? attachment.duration_secs * 1000 : undefined + } + } + } else { + return { + $type: "m.room.message", + "m.mentions": mentions, + msgtype: "m.file", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.filename, + filename: attachment.filename, + info: { + mimetype: attachment.content_type, + size: attachment.size + } + } + } +} + /** * @param {import("discord-api-types/v10").APIMessage} message * @param {import("discord-api-types/v10").APIGuild} guild @@ -316,98 +413,7 @@ async function messageToEvent(message, guild, options = {}, di) { } // Then attachments - const attachmentEvents = await Promise.all(message.attachments.map(async attachment => { - const emoji = - attachment.content_type?.startsWith("image/jp") ? "📸" - : attachment.content_type?.startsWith("image/") ? "🖼️" - : attachment.content_type?.startsWith("video/") ? "🎞️" - : attachment.content_type?.startsWith("text/") ? "📝" - : attachment.content_type?.startsWith("audio/") ? "🎶" - : "📄" - // no native media spoilers in Element, so we'll post a link instead, forcing it to not preview using a blockquote - if (attachment.filename.startsWith("SPOILER_")) { - return { - $type: "m.room.message", - "m.mentions": mentions, - msgtype: "m.text", - body: `${emoji} Uploaded SPOILER file: ${attachment.url} (${pb(attachment.size)})`, - format: "org.matrix.custom.html", - formatted_body: `
${emoji} Uploaded SPOILER file: ${attachment.url} (${pb(attachment.size)})
` - } - } - // for large files, always link them instead of uploading so I don't use up all the space in the content repo - else if (attachment.size > reg.ooye.max_file_size) { - return { - $type: "m.room.message", - "m.mentions": mentions, - msgtype: "m.text", - body: `${emoji} Uploaded file: ${attachment.url} (${pb(attachment.size)})`, - format: "org.matrix.custom.html", - formatted_body: `${emoji} Uploaded file: ${attachment.filename} (${pb(attachment.size)})` - } - } else 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, - body: attachment.filename, - filename: attachment.filename, - info: { - mimetype: attachment.content_type, - w: attachment.width, - h: attachment.height, - size: attachment.size - } - } - } else if (attachment.content_type?.startsWith("video/") && attachment.width && attachment.height) { - return { - $type: "m.room.message", - "m.mentions": mentions, - msgtype: "m.video", - url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, - body: attachment.description || attachment.filename, - filename: attachment.filename, - info: { - mimetype: attachment.content_type, - w: attachment.width, - h: attachment.height, - size: attachment.size - } - } - } else if (attachment.content_type?.startsWith("audio/")) { - return { - $type: "m.room.message", - "m.mentions": mentions, - msgtype: "m.audio", - url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, - body: attachment.description || attachment.filename, - filename: attachment.filename, - info: { - mimetype: attachment.content_type, - size: attachment.size, - duration: attachment.duration_secs ? attachment.duration_secs * 1000 : undefined - } - } - } else { - return { - $type: "m.room.message", - "m.mentions": mentions, - msgtype: "m.file", - url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, - body: attachment.filename, - filename: attachment.filename, - info: { - mimetype: attachment.content_type, - size: attachment.size - } - } - } - })) + const attachmentEvents = await Promise.all(message.attachments.map(attachmentToEvent.bind(null, mentions))) events.push(...attachmentEvents) // Then embeds diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 9b3fbec..f26abb0 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -73,7 +73,7 @@ async function sendEvent(event) { // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it - let {messagesToEdit, messagesToSend, messagesToDelete} = await eventToMessage.eventToMessage(event, guild, {api}) + let {messagesToEdit, messagesToSend, messagesToDelete} = await eventToMessage.eventToMessage(event, guild, {api, snow: discord.snow}) messagesToEdit = await Promise.all(messagesToEdit.map(async e => { e.message = await resolvePendingFiles(e.message) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 7082fbb..ef913bb 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -122,7 +122,7 @@ turndownService.addRule("listItem", { if (parent.nodeName === "OL") { var start = parent.getAttribute("start") var index = Array.prototype.indexOf.call(parent.children, node) - prefix = (start ? Number(start) + index : index + 1) + ". " + prefix = (start ? Number(start) + index : index + 1) + ". " } return prefix + content + (node.nextSibling && !/\n$/.test(content) ? "\n" : "") } @@ -259,7 +259,7 @@ async function uploadEndOfMessageSpriteSheet(content, attachments, pendingFiles) /** * @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker | Ty.Event.Outer_M_Room_Message_Encrypted_File} event * @param {import("discord-api-types/v10").APIGuild} guild - * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API + * @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer}} di simple-as-nails dependency injection for the matrix API */ async function eventToMessage(event, guild, di) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | Readable}[]})[]} */ @@ -289,6 +289,8 @@ async function eventToMessage(event, guild, di) { const attachments = [] /** @type {({name: string, url: string} | {name: string, url: string, key: string, iv: string} | {name: string, buffer: Buffer})[]} */ const pendingFiles = [] + /** @type {DiscordTypes.APIUser[]} */ + const ensureJoined = [] // Convert content depending on what the message is if (event.type === "m.room.message" && (event.content.msgtype === "m.text" || event.content.msgtype === "m.emote")) { @@ -502,6 +504,17 @@ async function eventToMessage(event, guild, di) { content = displayNameRunoff + replyLine + content + // Handling written @mentions: we need to look for candidate Discord members to join to the room + let writtenMentionMatch = content.match(/(?:^|[^"<>/A-Za-z0-9])@([A-Za-z][A-Za-z0-9._\[\]\(\)-]+):?/d) // d flag requires Node 16+ + if (writtenMentionMatch) { + const results = await di.snow.guild.searchGuildMembers(guild.id, {query: writtenMentionMatch[1]}) + if (results[0]) { + assert(results[0].user) + content = content.slice(0, writtenMentionMatch.index) + `<@${results[0].user.id}>` + content.slice(writtenMentionMatch.index + writtenMentionMatch[0].length) + ensureJoined.push(results[0].user) + } + } + // Split into 2000 character chunks const chunks = chunk(content, 2000) messages = messages.concat(chunks.map(content => ({ @@ -543,7 +556,8 @@ async function eventToMessage(event, guild, di) { return { messagesToEdit, messagesToSend, - messagesToDelete: messageIDsToEdit + messagesToDelete: messageIDsToEdit, + ensureJoined } } diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index f564906..8bd4223 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -63,6 +63,7 @@ test("event2message: body is used when there is no formatted_body", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -89,8 +90,15 @@ test("event2message: any markdown in body is escaped, except strikethrough", asy unsigned: { age: 405299 } + }, {}, { + snow: { + guild: { + searchGuildMembers: () => [] + } + } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -122,6 +130,7 @@ test("event2message: links in formatted body are not broken", async t => { room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -151,6 +160,7 @@ test("event2message: links in plaintext body are not broken", async t => { room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -181,6 +191,7 @@ test("event2message: basic html is converted to markdown", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -211,6 +222,7 @@ test("event2message: spoilers work", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -241,6 +253,7 @@ test("event2message: markdown syntax is escaped", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -271,6 +284,7 @@ test("event2message: html lines are bridged correctly", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -301,6 +315,7 @@ test("event2message: html lines are bridged correctly", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -332,6 +347,7 @@ test("event2message: whitespace is collapsed", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -364,6 +380,7 @@ test("event2message: lists are bridged correctly", async t => { "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -392,6 +409,7 @@ test("event2message: long messages are split", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -426,6 +444,7 @@ test("event2message: code blocks work", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -457,6 +476,7 @@ test("event2message: code block contents are formatted correctly and not escaped "room_id": "!BpMdOUkWWhFxmTrENV:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -487,6 +507,7 @@ test("event2message: quotes have an appropriate amount of whitespace", async t = } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -528,6 +549,7 @@ test("event2message: lists have appropriate line breaks", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -539,6 +561,48 @@ test("event2message: lists have appropriate line breaks", async t => { ) }) +test("event2message: ordered list start attribute works", async t => { + t.deepEqual( + await eventToMessage({ + content: { + body: 'i am not certain what you mean by "already exists with as discord". my goals are\n' + + '1. bridgeing specific channels with existing matrix rooms\n' + + ' 2. optionally maybe entire "servers"\n' + + '3. offering the bridge as a public service ', + format: 'org.matrix.custom.html', + formatted_body: '

i am not certain what you mean by "already exists with as discord". my goals are

\n' + + '
    \n' + + '
  1. bridgeing specific channels with existing matrix rooms\n' + + '
      \n' + + '
    1. optionally maybe entire "servers"
    2. \n' + + '
    \n' + + '
  2. \n' + + '
  3. offering the bridge as a public service
  4. \n' + + '
\n', + 'm.mentions': {}, + msgtype: 'm.text' + }, + room_id: '!cBxtVRxDlZvSVhJXVK:cadence.moe', + sender: '@Milan:tchncs.de', + type: 'm.room.message', + }, {}, { + api: { + getStateEvent: async () => ({displayname: "Milan"}) + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "Milan", + content: `i am not certain what you mean by "already exists with as discord". my goals are\n\n1. bridgeing specific channels with existing matrix rooms\n 2. optionally maybe entire "servers"\n2. offering the bridge as a public service`, + avatar_url: undefined + }] + } + ) +}) + test("event2message: m.emote plaintext works", async t => { t.deepEqual( await eventToMessage({ @@ -556,6 +620,7 @@ test("event2message: m.emote plaintext works", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -586,6 +651,7 @@ test("event2message: m.emote markdown syntax is escaped", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -633,6 +699,7 @@ test("event2message: rich reply to a sim user", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -709,6 +776,7 @@ test("event2message: rich reply to an already-edited message will quote the new } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -756,6 +824,7 @@ test("event2message: should avoid using blockquote contents as reply preview in } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -841,6 +910,7 @@ test("event2message: should include a reply preview when message ends with a blo } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -921,6 +991,7 @@ test("event2message: should include a reply preview when replying to a descripti } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -970,6 +1041,7 @@ test("event2message: entities are not escaped in main message or reply preview", } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1048,6 +1120,7 @@ test("event2message: editing a rich reply to a sim user", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [{ id: "1144874214311067708", @@ -1102,6 +1175,7 @@ test("event2message: editing a plaintext body message", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [{ id: "1145688633186193479", @@ -1153,6 +1227,7 @@ test("event2message: editing a plaintext message to be longer", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [{ id: "1145688633186193479", @@ -1208,6 +1283,7 @@ test("event2message: editing a plaintext message to be shorter", async t => { } }), { + ensureJoined: [], messagesToDelete: ["1145688633186193481"], messagesToEdit: [{ id: "1145688633186193480", @@ -1265,6 +1341,7 @@ test("event2message: editing a formatted body message", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [{ id: "1145688633186193479", @@ -1317,6 +1394,7 @@ test("event2message: rich reply to a matrix user's long message with formatting" } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1376,6 +1454,7 @@ test("event2message: rich reply to an image", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1427,6 +1506,7 @@ test("event2message: rich reply to a spoiler should ensure the spoiler is hidden } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1478,6 +1558,7 @@ test("event2message: with layered rich replies, the preview should only be the r } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1508,6 +1589,7 @@ test("event2message: raw mentioning discord users in plaintext body works", asyn } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1538,6 +1620,7 @@ test("event2message: raw mentioning discord users in formatted body works", asyn } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1568,6 +1651,7 @@ test("event2message: mentioning discord users works", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1598,6 +1682,7 @@ test("event2message: mentioning matrix users works", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1628,6 +1713,7 @@ test("event2message: mentioning bridged rooms works", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1658,6 +1744,7 @@ test("event2message: colon after mentions is stripped", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1699,6 +1786,7 @@ test("event2message: caches the member if the member is not known", async t => { } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1743,6 +1831,7 @@ test("event2message: skips caching the member if the member does not exist, some } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1786,6 +1875,7 @@ test("event2message: overly long usernames are shifted into the message content" } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1818,6 +1908,7 @@ test("event2message: overly long usernames are not treated specially when the ms } }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1847,6 +1938,7 @@ test("event2message: text attachments work", async t => { room_id: "!BnKuBPCvyfOkhcUjEu:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1881,6 +1973,7 @@ test("event2message: image attachments work", async t => { room_id: "!BnKuBPCvyfOkhcUjEu:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1930,6 +2023,7 @@ test("event2message: encrypted image attachments work", async t => { room_id: "!BnKuBPCvyfOkhcUjEu:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -1974,6 +2068,7 @@ test("event2message: stickers work", async t => { room_id: "!BnKuBPCvyfOkhcUjEu:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -2002,6 +2097,7 @@ test("event2message: static emojis work", async t => { room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -2028,6 +2124,7 @@ test("event2message: animated emojis work", async t => { room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -2054,6 +2151,7 @@ test("event2message: unknown emojis in the middle are linked", async t => { room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" }), { + ensureJoined: [], messagesToDelete: [], messagesToEdit: [], messagesToSend: [{ @@ -2065,6 +2163,53 @@ test("event2message: unknown emojis in the middle are linked", async t => { ) }) +test("event2message: guessed @mentions may join members to mention", async t => { + let called = 0 + const subtext = { + user: { + id: "321876634777218072", + username: "subtext", + discriminator: "0" + } + } + t.deepEqual( + await eventToMessage({ + type: "m.room.message", + sender: "@cadence:cadence.moe", + content: { + msgtype: "m.text", + body: "@subtext: what food would you like to order?" + }, + event_id: "$u5gSwSzv_ZQS3eM00mnTBCor8nx_A_AwuQz7e59PZk8", + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe" + }, { + id: "112760669178241024" + }, { + snow: { + guild: { + async searchGuildMembers(guildID, options) { + called++ + t.equal(guildID, "112760669178241024") + t.deepEqual(options, {query: "subtext"}) + return [subtext] + } + } + } + }), + { + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "cadence [they]", + content: "<@321876634777218072> what food would you like to order?", + avatar_url: undefined + }], + ensureJoined: [subtext.user] + } + ) + t.equal(called, 1, "searchGuildMembers should be called once") +}) + slow()("event2message: unknown emoji in the end is reuploaded as a sprite sheet", async t => { const messages = await eventToMessage({ type: "m.room.message", diff --git a/readme.md b/readme.md index 224d681..9b111ad 100644 --- a/readme.md +++ b/readme.md @@ -15,7 +15,7 @@ This readme has the most important info. The rest is [in the docs folder.](https * Modern: Supports new Discord features like replies, threads and stickers, and new Matrix features like edits, spaces and space membership. * Efficient: Special attention has been given to memory usage, database indexes, disk footprint, runtime algorithms, and queries to the homeserver. * Reliable: Any errors on either side are notified on Matrix and can be retried. -* Tested: A test suite and code coverage make sure all the core logic works. +* Tested: A test suite and code coverage make sure all the logic and special cases work. * Simple development: No build step (it's JavaScript, not TypeScript), minimal/lightweight dependencies, and abstraction only where necessary so that less background knowledge is required. No need to learn about Intents or library functions. * No locking algorithm: Other bridges use a locking algorithm which is a source of frequent bugs. This bridge avoids the need for one. * Latest API: Being on the latest Discord API version lets it access all features, without the risk of deprecated API versions being removed. @@ -42,13 +42,14 @@ Most features you'd expect in both directions, plus a little extra spice: * Custom emojis in messages * Custom room names/avatars can be applied on Matrix-side * Larger files from Discord are linked instead of reuploaded to Matrix +* Simulated user accounts are named @the_persons_username rather than @112233445566778899 For more information about features, [see the user guide.](https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/user-guide.md) ## Caveats * This bridge is not designed for puppetting. -* Direct Messaging is not supported yet. +* Direct Messaging is not supported until I figure out a good way of doing it. ## Efficiency details