initial polls support (not exactly working)

This commit is contained in:
Ellie Algase
2026-01-25 14:33:09 +13:00
committed by Cadence Ember
parent 2496f4c3b0
commit e565342ac8
16 changed files with 749 additions and 14 deletions
+2 -2
View File
@@ -16,8 +16,8 @@ function eventCanBeEdited(ev) {
if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote" && ev.old.event_subtype !== "m.notice") {
return false
}
// Discord does not allow stickers to be edited.
if (ev.old.event_type === "m.sticker") {
// Discord does not allow stickers to be edited. Poll closures are sent as "edits", but not in a way we care about.
if (ev.old.event_type === "m.sticker" || ev.old.event_type === "org.matrix.msc3381.poll.start") {
return false
}
// Anything else is fair game.
+64
View File
@@ -203,6 +203,46 @@ async function attachmentToEvent(mentions, attachment) {
}
}
/** @param {DiscordTypes.APIPoll} poll */
async function pollToEvent(poll) {
let fallbackText = poll.question.text
if (poll.allow_multiselect) {
var maxSelections = poll.answers.length;
} else {
var maxSelections = 1;
}
let answers = poll.answers.map(answer=>{
let matrixText = answer.poll_media.text
if (answer.poll_media.emoji) {
if (answer.poll_media.emoji.id) {
// Custom emoji. It seems like no Matrix client allows custom emoji in poll answers, so leaving this unimplemented.
} else {
matrixText = "[" + answer.poll_media.emoji.name + "] " + matrixText
}
}
let matrixAnswer = {
id: answer.answer_id.toString(),
"org.matrix.msc1767.text": matrixText
}
fallbackText = fallbackText + "\n" + answer.answer_id.toString() + ". " + matrixText
return matrixAnswer;
})
return {
$type: "org.matrix.msc3381.poll.start",
"org.matrix.msc3381.poll.start": {
question: {
"org.matrix.msc1767.text": poll.question.text,
body: poll.question.text,
msgtype: "m.text"
},
kind: "org.matrix.msc3381.poll.disclosed", // Discord always lets you see results, so keeping this consistent with that.
max_selections: maxSelections,
answers: answers
},
"org.matrix.msc1767.text": fallbackText
}
}
/**
* @param {DiscordTypes.APIMessage} message
* @param {DiscordTypes.APIGuild} guild
@@ -226,6 +266,20 @@ async function messageToEvent(message, guild, options = {}, di) {
return []
}
if (message.type === DiscordTypes.MessageType.PollResult) {
const event_id = select("event_message", "event_id", {message_id: message.message_reference?.message_id}).pluck().get()
return [{
$type: "org.matrix.msc3381.poll.end",
"m.relates_to": {
rel_type: "m.reference",
event_id
},
"org.matrix.msc3381.poll.end": {},
body: "This poll has ended.",
msgtype: "m.text"
}]
}
if (message.type === DiscordTypes.MessageType.ThreadStarterMessage) {
// This is the message that appears at the top of a thread when the thread was based off an existing message.
// It's just a message reference, no content.
@@ -702,6 +756,12 @@ async function messageToEvent(message, guild, options = {}, di) {
}
}
// Then polls
if (message.poll) {
const pollEvent = await pollToEvent(message.poll)
events.push(pollEvent)
}
// Then embeds
const urlPreviewEnabled = select("guild_space", "url_preview", {guild_id: guild?.id}).pluck().get() ?? 1
for (const embed of message.embeds || []) {
@@ -713,6 +773,10 @@ async function messageToEvent(message, guild, options = {}, di) {
continue // Matrix's own URL previews are fine for images.
}
if (embed.type === "poll_result") {
// The code here is only for the message to be bridged to Matrix. Dealing with the Discord-side updates is in actions/poll-close.js.
}
if (embed.url?.startsWith("https://discord.com/")) {
continue // If discord creates an embed preview for a discord channel link, don't copy that embed
}
@@ -1551,3 +1551,57 @@ test("message2event: forwarded message with unreferenced mention", async t => {
"m.mentions": {}
}])
})
test("message2event: single-choice poll", async t => {
const events = await messageToEvent(data.message.poll_single_choice, data.guild.general, {})
t.deepEqual(events, [{
$type: "org.matrix.msc3381.poll.start",
"org.matrix.msc3381.poll.start": {
question: {
"org.matrix.msc1767.text": "only one answer allowed!",
body: "only one answer allowed!",
msgtype: "m.text"
},
kind: "org.matrix.msc3381.poll.disclosed", // Discord always lets you see results, so keeping this consistent with that.
max_selections: 1,
answers: [{
id: "1",
"org.matrix.msc1767.text": "[\ud83d\udc4d] answer one"
}, {
id: "2",
"org.matrix.msc1767.text": "[\ud83d\udc4e] answer two"
}, {
id: "3",
"org.matrix.msc1767.text": "answer three"
}]
},
"org.matrix.msc1767.text": "only one answer allowed!\n1. [\ud83d\udc4d] answer one\n2. [\ud83d\udc4e] answer two\n3. answer three"
}])
})
test("message2event: multiple-choice poll", async t => {
const events = await messageToEvent(data.message.poll_multiple_choice, data.guild.general, {})
t.deepEqual(events, [{
$type: "org.matrix.msc3381.poll.start",
"org.matrix.msc3381.poll.start": {
question: {
"org.matrix.msc1767.text": "more than one answer allowed",
body: "more than one answer allowed",
msgtype: "m.text"
},
kind: "org.matrix.msc3381.poll.disclosed", // Discord always lets you see results, so keeping this consistent with that.
max_selections: 3,
answers: [{
id: "1",
"org.matrix.msc1767.text": "[😭] no"
}, {
id: "2",
"org.matrix.msc1767.text": "oh no"
}, {
id: "3",
"org.matrix.msc1767.text": "oh noooooo"
}]
},
"org.matrix.msc1767.text": "more than one answer allowed\n1. [😭] no\n2. oh no\n3. oh noooooo"
}])
})