Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 019f3f2ffb | |||
| 87fcdb18ab | |||
| 015bedab69 | |||
| e25f788738 | |||
| cfa319eaa3 | |||
| 714e990bef |
4 changed files with 306 additions and 3 deletions
|
|
@ -62,7 +62,29 @@ async function _interact({guild_id, data}, {api}) {
|
||||||
.sort((a, b) => webGuild._getPosition(a, discord.channels) - webGuild._getPosition(b, discord.channels))
|
.sort((a, b) => webGuild._getPosition(a, discord.channels) - webGuild._getPosition(b, discord.channels))
|
||||||
.filter(channel => from("channel_room").join("member_cache", "room_id").select("mxid").where({channel_id: channel.id, mxid: event.sender}).get())
|
.filter(channel => from("channel_room").join("member_cache", "room_id").select("mxid").where({channel_id: channel.id, mxid: event.sender}).get())
|
||||||
const matrixMember = select("member_cache", ["displayname", "avatar_url"], {room_id: message.room_id, mxid: event.sender}).get()
|
const matrixMember = select("member_cache", ["displayname", "avatar_url"], {room_id: message.room_id, mxid: event.sender}).get()
|
||||||
const name = matrixMember?.displayname || event.sender
|
let name = matrixMember?.displayname || event.sender
|
||||||
|
let avatar = utils.getPublicUrlForMxc(matrixMember?.avatar_url)
|
||||||
|
|
||||||
|
// Check for per-message profile
|
||||||
|
const perMessageProfile = event.content?.["com.beeper.per_message_profile"]
|
||||||
|
let profileNote = ""
|
||||||
|
if (perMessageProfile) {
|
||||||
|
if (perMessageProfile.displayname) {
|
||||||
|
name = perMessageProfile.displayname
|
||||||
|
}
|
||||||
|
if ("avatar_url" in perMessageProfile) {
|
||||||
|
if (perMessageProfile.avatar_url) {
|
||||||
|
// use provided avatar_url
|
||||||
|
avatar = utils.getPublicUrlForMxc(perMessageProfile.avatar_url)
|
||||||
|
} else if (perMessageProfile.avatar_url === "") {
|
||||||
|
// empty string avatar_url clears the avatar
|
||||||
|
avatar = undefined
|
||||||
|
}
|
||||||
|
// else, omitted/null falls back to member avatar
|
||||||
|
}
|
||||||
|
profileNote = "Sent with a per-message profile.\n"
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -70,9 +92,9 @@ async function _interact({guild_id, data}, {api}) {
|
||||||
author: {
|
author: {
|
||||||
name,
|
name,
|
||||||
url: `https://matrix.to/#/${event.sender}`,
|
url: `https://matrix.to/#/${event.sender}`,
|
||||||
icon_url: utils.getPublicUrlForMxc(matrixMember?.avatar_url)
|
icon_url: avatar
|
||||||
},
|
},
|
||||||
description: `This Matrix message was delivered to Discord by **Out Of Your Element**.\n[View on Matrix →](<https://matrix.to/#/${message.room_id}/${message.event_id}?${via}>)\n\n**User ID**: [${event.sender}](<https://matrix.to/#/${event.sender}>)`,
|
description: `This Matrix message was delivered to Discord by **Out Of Your Element**.\n[View on Matrix →](<https://matrix.to/#/${message.room_id}/${message.event_id}?${via}>)\n\n${profileNote}**User ID**: [${event.sender}](<https://matrix.to/#/${event.sender}>)`,
|
||||||
color: 0x0dbd8b,
|
color: 0x0dbd8b,
|
||||||
fields: [{
|
fields: [{
|
||||||
name: "In Channels",
|
name: "In Channels",
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,118 @@ test("matrix info: shows info for matrix source message", async t => {
|
||||||
)
|
)
|
||||||
t.equal(called, 1)
|
t.equal(called, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("matrix info: shows username for per-message profile", async t => {
|
||||||
|
let called = 0
|
||||||
|
const msg = await _interact({
|
||||||
|
data: {
|
||||||
|
target_id: "1128118177155526666",
|
||||||
|
resolved: {
|
||||||
|
messages: {
|
||||||
|
"1141501302736695316": data.message.simple_reply_to_matrix_user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
guild_id: "112760669178241024"
|
||||||
|
}, {
|
||||||
|
api: {
|
||||||
|
async getEvent(roomID, eventID) {
|
||||||
|
called++
|
||||||
|
t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe")
|
||||||
|
t.equal(eventID, "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4")
|
||||||
|
return {
|
||||||
|
event_id: eventID,
|
||||||
|
room_id: roomID,
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "master chief: i like the halo",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<strong>master chief: </strong>i like the halo",
|
||||||
|
"com.beeper.per_message_profile": {
|
||||||
|
has_fallback: true,
|
||||||
|
displayname: "master chief",
|
||||||
|
avatar_url: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sender: "@cadence:cadence.moe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getJoinedMembers(roomID) {
|
||||||
|
return {
|
||||||
|
joined: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getStateEventOuter(roomID, type, key) {
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
room_version: "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getStateEvent(roomID, type, key) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.equal(msg.data.embeds[0].author.name, "master chief")
|
||||||
|
t.match(msg.data.embeds[0].description, "Sent with a per-message profile")
|
||||||
|
t.equal(called, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("matrix info: shows avatar for per-message profile", async t => {
|
||||||
|
let called = 0
|
||||||
|
const msg = await _interact({
|
||||||
|
data: {
|
||||||
|
target_id: "1128118177155526666",
|
||||||
|
resolved: {
|
||||||
|
messages: {
|
||||||
|
"1141501302736695316": data.message.simple_reply_to_matrix_user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
guild_id: "112760669178241024"
|
||||||
|
}, {
|
||||||
|
api: {
|
||||||
|
async getEvent(roomID, eventID) {
|
||||||
|
called++
|
||||||
|
t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe")
|
||||||
|
t.equal(eventID, "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4")
|
||||||
|
return {
|
||||||
|
event_id: eventID,
|
||||||
|
room_id: roomID,
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "?",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "?",
|
||||||
|
"com.beeper.per_message_profile": {
|
||||||
|
avatar_url: "mxc://cadence.moe/HXfFuougamkURPPMflTJRxGc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sender: "@mystery:cadence.moe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getJoinedMembers(roomID) {
|
||||||
|
return {
|
||||||
|
joined: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getStateEventOuter(roomID, type, key) {
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
room_version: "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getStateEvent(roomID, type, key) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.equal(msg.data.embeds[0].author.name, "@mystery:cadence.moe")
|
||||||
|
t.equal(msg.data.embeds[0].author.icon_url, "https://bridge.example.org/download/matrix/cadence.moe/HXfFuougamkURPPMflTJRxGc")
|
||||||
|
t.match(msg.data.embeds[0].description, "Sent with a per-message profile")
|
||||||
|
t.equal(called, 1)
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -550,13 +550,30 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
let messageIDsToEdit = []
|
let messageIDsToEdit = []
|
||||||
let replyLine = ""
|
let replyLine = ""
|
||||||
|
|
||||||
// Extract a basic display name from the sender
|
// Extract a basic display name from the sender
|
||||||
const match = event.sender.match(/^@(.*?):/)
|
const match = event.sender.match(/^@(.*?):/)
|
||||||
if (match) displayName = match[1]
|
if (match) displayName = match[1]
|
||||||
|
|
||||||
// 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 = mxUtils.getPublicUrlForMxc(member.avatar_url)
|
if (member.avatar_url) avatarURL = mxUtils.getPublicUrlForMxc(member.avatar_url)
|
||||||
|
|
||||||
|
// MSC4144: Override display name and avatar from per-message profile if present
|
||||||
|
const perMessageProfile = event.content["com.beeper.per_message_profile"]
|
||||||
|
if (perMessageProfile?.displayname) displayName = perMessageProfile.displayname
|
||||||
|
if (perMessageProfile && "avatar_url" in perMessageProfile) {
|
||||||
|
if (perMessageProfile.avatar_url) {
|
||||||
|
// use provided avatar_url
|
||||||
|
avatarURL = mxUtils.getPublicUrlForMxc(perMessageProfile.avatar_url)
|
||||||
|
} else if (perMessageProfile.avatar_url === "") {
|
||||||
|
// empty string avatar_url clears the avatar
|
||||||
|
avatarURL = undefined
|
||||||
|
}
|
||||||
|
// else, omitted/null falls back to member avatar
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
@ -799,6 +816,16 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
if (shouldProcessTextEvent) {
|
if (shouldProcessTextEvent) {
|
||||||
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
|
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
|
||||||
let input = event.content.formatted_body
|
let input = event.content.formatted_body
|
||||||
|
if (perMessageProfile?.has_fallback) {
|
||||||
|
// Strip fallback elements added for clients that don't support per-message profiles.
|
||||||
|
// Deviates from recommended regexp in MSC to be less strict. Avoiding an HTML parser for performance reasons.
|
||||||
|
// ┌────A────┐ Opening HTML tag: capture tag name and stay within tag
|
||||||
|
// ┆ ┆┌─────────────B────────────┐ This text in the tag somewhere, presumably an attribute name
|
||||||
|
// ┆ ┆┆ ┆┌─C──┐ Rest of the opening tag
|
||||||
|
// ┆ ┆┆ ┆┆ ┆┌─D─┐ Tag content (no more tags allowed within)
|
||||||
|
// ┆ ┆┆ ┆┆ ┆┆ ┆┌─E──┐ Closing tag matching opening tag name
|
||||||
|
input = input.replace(/<(\w+)[^>]*\bdata-mx-profile-fallback\b[^>]*>[^<]*<\/\1>/g, "")
|
||||||
|
}
|
||||||
if (event.content.msgtype === "m.emote") {
|
if (event.content.msgtype === "m.emote") {
|
||||||
input = `* ${displayName} ${input}`
|
input = `* ${displayName} ${input}`
|
||||||
}
|
}
|
||||||
|
|
@ -944,6 +971,10 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
} else {
|
} else {
|
||||||
// Looks like we're using the plaintext body!
|
// Looks like we're using the plaintext body!
|
||||||
content = event.content.body
|
content = event.content.body
|
||||||
|
if (perMessageProfile?.has_fallback && perMessageProfile.displayname && content.startsWith(perMessageProfile.displayname + ": ")) {
|
||||||
|
// Strip the display name prefix fallback added for clients that don't support per-message profiles
|
||||||
|
content = content.slice(perMessageProfile.displayname.length + 2)
|
||||||
|
}
|
||||||
|
|
||||||
if (event.content.msgtype === "m.emote") {
|
if (event.content.msgtype === "m.emote") {
|
||||||
content = `* ${displayName} ${content}`
|
content = `* ${displayName} ${content}`
|
||||||
|
|
|
||||||
|
|
@ -5526,6 +5526,141 @@ test("event2message: known and unknown emojis in the end are used for sprite she
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("event2message: com.beeper.per_message_profile overrides displayname and avatar_url", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "hello from unstable profile",
|
||||||
|
"com.beeper.per_message_profile": {
|
||||||
|
id: "custom-id",
|
||||||
|
displayname: "Unstable Name",
|
||||||
|
avatar_url: "mxc://maunium.net/hgXsKqlmRfpKvCZdUoWDkFQo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "Unstable Name",
|
||||||
|
content: "hello from unstable profile",
|
||||||
|
avatar_url: "https://bridge.example.org/download/matrix/maunium.net/hgXsKqlmRfpKvCZdUoWDkFQo",
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event2message: com.beeper.per_message_profile empty avatar_url clears avatar", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "hello with cleared avatar",
|
||||||
|
"com.beeper.per_message_profile": {
|
||||||
|
id: "no-avatar",
|
||||||
|
displayname: "No Avatar User",
|
||||||
|
avatar_url: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "No Avatar User",
|
||||||
|
content: "hello with cleared avatar",
|
||||||
|
avatar_url: undefined,
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event2message: data-mx-profile-fallback element is stripped from formatted_body when per-message profile is present", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "Tidus Herboren: one more test",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<strong data-mx-profile-fallback>Tidus Herboren: </strong>one more test",
|
||||||
|
"com.beeper.per_message_profile": {
|
||||||
|
id: "tidus",
|
||||||
|
displayname: "Tidus Herboren",
|
||||||
|
avatar_url: "mxc://maunium.net/hgXsKqlmRfpKvCZdUoWDkFQo",
|
||||||
|
has_fallback: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "Tidus Herboren",
|
||||||
|
content: "one more test",
|
||||||
|
avatar_url: "https://bridge.example.org/download/matrix/maunium.net/hgXsKqlmRfpKvCZdUoWDkFQo",
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("event2message: displayname prefix is stripped from plain body when per-message profile has_fallback", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "Tidus Herboren: one more test",
|
||||||
|
"com.beeper.per_message_profile": {
|
||||||
|
id: "tidus",
|
||||||
|
displayname: "Tidus Herboren",
|
||||||
|
has_fallback: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "Tidus Herboren",
|
||||||
|
content: "one more test",
|
||||||
|
avatar_url: undefined,
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("event2message: all unknown chess emojis are used for sprite sheet", async t => {
|
test("event2message: all unknown chess emojis are used for sprite sheet", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
await eventToMessage({
|
await eventToMessage({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue