m->d: Message links are now guessed when unknown
This commit is contained in:
parent
024ff34cca
commit
2df7c665cb
4 changed files with 164 additions and 19 deletions
|
@ -269,12 +269,12 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||||
*/
|
*/
|
||||||
async function transformContentMessageLinks(content) {
|
async function transformContentMessageLinks(content) {
|
||||||
let offset = 0
|
let offset = 0
|
||||||
for (const match of [...content.matchAll(/https:\/\/(?:ptb\.|canary\.|www\.)?discord(?:app)?\.com\/channels\/([0-9]+)\/([0-9]+)\/([0-9]+)/g)]) {
|
for (const match of [...content.matchAll(/https:\/\/(?:ptb\.|canary\.|www\.)?discord(?:app)?\.com\/channels\/[0-9]+\/([0-9]+)\/([0-9]+)/g)]) {
|
||||||
assert(typeof match.index === "number")
|
assert(typeof match.index === "number")
|
||||||
const channelID = match[2]
|
const [_, channelID, messageID] = match
|
||||||
const messageID = match[3]
|
|
||||||
const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
|
|
||||||
let result
|
let result
|
||||||
|
|
||||||
|
const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
|
||||||
if (roomID) {
|
if (roomID) {
|
||||||
const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get()
|
const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get()
|
||||||
if (eventID && roomID) {
|
if (eventID && roomID) {
|
||||||
|
@ -287,6 +287,7 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||||
} else {
|
} else {
|
||||||
result = `${match[0]} [event is from another server]`
|
result = `${match[0]} [event is from another server]`
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.slice(0, match.index + offset) + result + content.slice(match.index + match[0].length + offset)
|
content = content.slice(0, match.index + offset) + result + content.slice(match.index + match[0].length + offset)
|
||||||
offset += result.length - match[0].length
|
offset += result.length - match[0].length
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ const entities = require("entities")
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
const {sync, db, discord, select, from} = passthrough
|
const {sync, db, discord, select, from} = passthrough
|
||||||
/** @type {import("../converters/utils")} */
|
/** @type {import("../converters/utils")} */
|
||||||
const utils = sync.require("../converters/utils")
|
const mxUtils = sync.require("../converters/utils")
|
||||||
|
/** @type {import("../../discord/utils")} */
|
||||||
|
const dUtils = sync.require("../../discord/utils")
|
||||||
/** @type {import("./emoji-sheet")} */
|
/** @type {import("./emoji-sheet")} */
|
||||||
const emojiSheet = sync.require("./emoji-sheet")
|
const emojiSheet = sync.require("./emoji-sheet")
|
||||||
|
|
||||||
|
@ -102,6 +104,7 @@ turndownService.addRule("inlineLink", {
|
||||||
|
|
||||||
replacement: function (content, node) {
|
replacement: function (content, node) {
|
||||||
if (node.getAttribute("data-user-id")) return `<@${node.getAttribute("data-user-id")}>`
|
if (node.getAttribute("data-user-id")) return `<@${node.getAttribute("data-user-id")}>`
|
||||||
|
if (node.getAttribute("data-message-id")) return `https://discord.com/channels/${node.getAttribute("data-guild-id")}/${node.getAttribute("data-channel-id")}/${node.getAttribute("data-message-id")}`
|
||||||
if (node.getAttribute("data-channel-id")) return `<#${node.getAttribute("data-channel-id")}>`
|
if (node.getAttribute("data-channel-id")) return `<#${node.getAttribute("data-channel-id")}>`
|
||||||
const href = node.getAttribute("href")
|
const href = node.getAttribute("href")
|
||||||
let brackets = ["", ""]
|
let brackets = ["", ""]
|
||||||
|
@ -162,7 +165,7 @@ turndownService.addRule("emoji", {
|
||||||
return `<::>`
|
return `<::>`
|
||||||
} else {
|
} else {
|
||||||
// We prefer not to upload this as a sprite sheet because the emoji is not at the end of the message, it is in the middle.
|
// We prefer not to upload this as a sprite sheet because the emoji is not at the end of the message, it is in the middle.
|
||||||
return `[${node.getAttribute("title")}](${utils.getPublicUrlForMxc(mxcUrl)})`
|
return `[${node.getAttribute("title")}](${mxUtils.getPublicUrlForMxc(mxcUrl)})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -276,7 +279,7 @@ async function eventToMessage(event, guild, di) {
|
||||||
// Try to extract an accurate display name and avatar URL from the member event
|
// Try to extract an accurate display name and avatar URL from the member event
|
||||||
const member = await getMemberFromCacheOrHomeserver(event.room_id, event.sender, di?.api)
|
const member = await getMemberFromCacheOrHomeserver(event.room_id, event.sender, di?.api)
|
||||||
if (member.displayname) displayName = member.displayname
|
if (member.displayname) displayName = member.displayname
|
||||||
if (member.avatar_url) avatarURL = utils.getPublicUrlForMxc(member.avatar_url) || undefined
|
if (member.avatar_url) avatarURL = mxUtils.getPublicUrlForMxc(member.avatar_url) || undefined
|
||||||
// If the display name is too long to be put into the webhook (80 characters is the maximum),
|
// If the display name is too long to be put into the webhook (80 characters is the maximum),
|
||||||
// put the excess characters into displayNameRunoff, later to be put at the top of the message
|
// put the excess characters into displayNameRunoff, later to be put at the top of the message
|
||||||
let [displayNameShortened, displayNameRunoff] = splitDisplayName(displayName)
|
let [displayNameShortened, displayNameRunoff] = splitDisplayName(displayName)
|
||||||
|
@ -390,7 +393,7 @@ async function eventToMessage(event, guild, di) {
|
||||||
|
|
||||||
// Handling mentions of Discord users
|
// Handling mentions of Discord users
|
||||||
input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => {
|
input = input.replace(/("https:\/\/matrix.to\/#\/(@[^"]+)")>/g, (whole, attributeValue, mxid) => {
|
||||||
if (utils.eventSenderIsFromDiscord(mxid)) {
|
if (mxUtils.eventSenderIsFromDiscord(mxid)) {
|
||||||
// Handle mention of an OOYE sim user by their mxid
|
// Handle mention of an OOYE sim user by their mxid
|
||||||
const userID = select("sim", "user_id", {mxid: mxid}).pluck().get()
|
const userID = select("sim", "user_id", {mxid: mxid}).pluck().get()
|
||||||
if (!userID) return whole
|
if (!userID) return whole
|
||||||
|
@ -405,12 +408,42 @@ async function eventToMessage(event, guild, di) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handling mentions of Discord rooms
|
// Handling mentions of rooms and room-messages
|
||||||
input = input.replace(/("https:\/\/matrix.to\/#\/(![^"]+)")>/g, (whole, attributeValue, roomID) => {
|
let offset = 0
|
||||||
|
for (const match of [...input.matchAll(/("https:\/\/matrix.to\/#\/(![^"/?]+)(?:\/(\$[^"/?]+))?(?:\?[^"]*)?")>/g)]) {
|
||||||
|
assert(typeof match.index === "number")
|
||||||
|
const [_, attributeValue, roomID, eventID] = match
|
||||||
|
let result
|
||||||
|
|
||||||
|
// Don't process links that are part of the reply fallback, they'll be removed entirely by turndown
|
||||||
|
if (input.slice(match.index + match[0].length + offset).startsWith("In reply to")) continue
|
||||||
|
|
||||||
const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get()
|
const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get()
|
||||||
if (!channelID) return whole
|
if (!channelID) continue
|
||||||
return `${attributeValue} data-channel-id="${channelID}">`
|
if (!eventID) {
|
||||||
})
|
// 1: It's a room link, so <#link> to the channel
|
||||||
|
result = `${attributeValue} data-channel-id="${channelID}">`
|
||||||
|
} else {
|
||||||
|
// Linking to a particular event with a discord.com/channels/guildID/channelID/messageID link
|
||||||
|
// Need to know the guildID and messageID
|
||||||
|
const guildID = discord.channels.get(channelID)?.["guild_id"]
|
||||||
|
if (!guildID) continue
|
||||||
|
const messageID = select("event_message", "message_id", {event_id: eventID}).pluck().get()
|
||||||
|
if (messageID) {
|
||||||
|
// 2: Linking to a known event
|
||||||
|
result = `${attributeValue} data-channel-id="${channelID}" data-guild-id="${guildID}" data-message-id="${messageID}">`
|
||||||
|
} else {
|
||||||
|
// 3: Linking to an unknown event that OOYE didn't originally bridge - we can guess messageID from the timestamp
|
||||||
|
const originalEvent = await di.api.getEvent(roomID, eventID)
|
||||||
|
if (!originalEvent) continue
|
||||||
|
const guessedMessageID = dUtils.timestampToSnowflakeInexact(originalEvent.origin_server_ts)
|
||||||
|
result = `${attributeValue} data-channel-id="${channelID}" data-guild-id="${guildID}" data-message-id="${guessedMessageID}">`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input = input.slice(0, match.index + offset) + result + input.slice(match.index + match[0].length + offset)
|
||||||
|
offset += result.length - match[0].length
|
||||||
|
}
|
||||||
|
|
||||||
// Stripping colons after mentions
|
// Stripping colons after mentions
|
||||||
input = input.replace(/( data-user-id.*?<\/a>):?/g, "$1")
|
input = input.replace(/( data-user-id.*?<\/a>):?/g, "$1")
|
||||||
|
@ -430,7 +463,7 @@ async function eventToMessage(event, guild, di) {
|
||||||
beforeTag = beforeTag || ""
|
beforeTag = beforeTag || ""
|
||||||
afterContext = afterContext || ""
|
afterContext = afterContext || ""
|
||||||
afterTag = afterTag || ""
|
afterTag = afterTag || ""
|
||||||
if (!utils.BLOCK_ELEMENTS.includes(beforeTag.toUpperCase()) && !utils.BLOCK_ELEMENTS.includes(afterTag.toUpperCase())) {
|
if (!mxUtils.BLOCK_ELEMENTS.includes(beforeTag.toUpperCase()) && !mxUtils.BLOCK_ELEMENTS.includes(afterTag.toUpperCase())) {
|
||||||
return beforeContext + "<br>" + afterContext
|
return beforeContext + "<br>" + afterContext
|
||||||
} else {
|
} else {
|
||||||
return whole
|
return whole
|
||||||
|
@ -480,13 +513,13 @@ async function eventToMessage(event, guild, di) {
|
||||||
const filename = event.content.body
|
const filename = event.content.body
|
||||||
if ("url" in event.content) {
|
if ("url" in event.content) {
|
||||||
// Unencrypted
|
// Unencrypted
|
||||||
const url = utils.getPublicUrlForMxc(event.content.url)
|
const url = mxUtils.getPublicUrlForMxc(event.content.url)
|
||||||
assert(url)
|
assert(url)
|
||||||
attachments.push({id: "0", filename})
|
attachments.push({id: "0", filename})
|
||||||
pendingFiles.push({name: filename, url})
|
pendingFiles.push({name: filename, url})
|
||||||
} else {
|
} else {
|
||||||
// Encrypted
|
// Encrypted
|
||||||
const url = utils.getPublicUrlForMxc(event.content.file.url)
|
const url = mxUtils.getPublicUrlForMxc(event.content.file.url)
|
||||||
assert(url)
|
assert(url)
|
||||||
assert.equal(event.content.file.key.alg, "A256CTR")
|
assert.equal(event.content.file.key.alg, "A256CTR")
|
||||||
attachments.push({id: "0", filename})
|
attachments.push({id: "0", filename})
|
||||||
|
@ -494,7 +527,7 @@ async function eventToMessage(event, guild, di) {
|
||||||
}
|
}
|
||||||
} else if (event.type === "m.sticker") {
|
} else if (event.type === "m.sticker") {
|
||||||
content = ""
|
content = ""
|
||||||
const url = utils.getPublicUrlForMxc(event.content.url)
|
const url = mxUtils.getPublicUrlForMxc(event.content.url)
|
||||||
assert(url)
|
assert(url)
|
||||||
let filename = event.content.body
|
let filename = event.content.body
|
||||||
if (event.type === "m.sticker") {
|
if (event.type === "m.sticker") {
|
||||||
|
|
|
@ -1701,7 +1701,7 @@ test("event2message: mentioning bridged rooms works", async t => {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: "wrong body",
|
body: "wrong body",
|
||||||
format: "org.matrix.custom.html",
|
format: "org.matrix.custom.html",
|
||||||
formatted_body: `I'm just <a href="https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe">worm-form</a> testing channel mentions`
|
formatted_body: `I'm just <a href="https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe?via=cadence.moe">worm-farm</a> testing channel mentions`
|
||||||
},
|
},
|
||||||
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
origin_server_ts: 1688301929913,
|
origin_server_ts: 1688301929913,
|
||||||
|
@ -1725,6 +1725,112 @@ test("event2message: mentioning bridged rooms works", async t => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("event2message: mentioning known bridged events works", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "wrong body",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: `it was uploaded earlier in <a href="https://matrix.to/#/!CzvdIdUQXgUjDVKxeU:cadence.moe/$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10?via=cadence.moe">amanda-spam</a>`
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
origin_server_ts: 1688301929913,
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
type: "m.room.message",
|
||||||
|
unsigned: {
|
||||||
|
age: 405299
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "cadence [they]",
|
||||||
|
content: "it was uploaded earlier in https://discord.com/channels/497159726455455754/497161350934560778/1141619794500649020",
|
||||||
|
avatar_url: undefined
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event2message: mentioning unknown bridged events works", async t => {
|
||||||
|
let called = 0
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "wrong body",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: `it was uploaded years ago in <a href="https://matrix.to/#/!CzvdIdUQXgUjDVKxeU:cadence.moe/$zpzx6ABetMl8BrpsFbdZ7AefVU1Y_-t97bJRJM2JyW0?via=cadence.moe">amanda-spam</a>`
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
origin_server_ts: 1688301929913,
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
type: "m.room.message",
|
||||||
|
unsigned: {
|
||||||
|
age: 405299
|
||||||
|
}
|
||||||
|
}, {}, {
|
||||||
|
api: {
|
||||||
|
async getEvent(roomID, eventID) {
|
||||||
|
called++
|
||||||
|
t.equal(roomID, "!CzvdIdUQXgUjDVKxeU:cadence.moe")
|
||||||
|
t.equal(eventID, "$zpzx6ABetMl8BrpsFbdZ7AefVU1Y_-t97bJRJM2JyW0")
|
||||||
|
return {
|
||||||
|
origin_server_ts: 1599813121000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "cadence [they]",
|
||||||
|
content: "it was uploaded years ago in https://discord.com/channels/497159726455455754/497161350934560778/753895613661184000",
|
||||||
|
avatar_url: undefined
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
t.equal(called, 1, "getEvent should be called once")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event2message: link to event in an unknown room", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "wrong body",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: 'ah yeah, here\'s where the bug was reported: <a href="https://matrix.to/#/!QtykxKocfZaZOUrTwp:matrix.org/$1542477546853947KGhZL:matrix.org">https://matrix.to/#/!QtykxKocfZaZOUrTwp:matrix.org/$1542477546853947KGhZL:matrix.org</a>'
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
origin_server_ts: 1688301929913,
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
type: "m.room.message",
|
||||||
|
unsigned: {
|
||||||
|
age: 405299
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "cadence [they]",
|
||||||
|
content: "ah yeah, here's where the bug was reported: [https://matrix.to/#/!QtykxKocfZaZOUrTwp:matrix.org/$1542477546853947KGhZL:matrix.org](<https://matrix.to/#/!QtykxKocfZaZOUrTwp:matrix.org/$1542477546853947KGhZL:matrix.org>)",
|
||||||
|
avatar_url: undefined
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("event2message: colon after mentions is stripped", async t => {
|
test("event2message: colon after mentions is stripped", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
await eventToMessage({
|
await eventToMessage({
|
||||||
|
|
|
@ -24,7 +24,12 @@ const discord = {
|
||||||
]),
|
]),
|
||||||
application: {
|
application: {
|
||||||
id: "684280192553844747"
|
id: "684280192553844747"
|
||||||
}
|
},
|
||||||
|
channels: new Map([
|
||||||
|
["497161350934560778", {
|
||||||
|
guild_id: "497159726455455754"
|
||||||
|
}]
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(passthrough, { discord, config, sync, db })
|
Object.assign(passthrough, { discord, config, sync, db })
|
||||||
|
|
Loading…
Reference in a new issue