Fix typecheck

This commit is contained in:
Cadence Ember
2026-02-11 11:31:27 +13:00
parent 33eef25cf1
commit c4909653aa
21 changed files with 117 additions and 65 deletions
+5 -4
View File
@@ -70,13 +70,14 @@ async function sendVotes(userOrID, channelID, pollMessageID, pollEventID) {
return
}
let userID, senderMxid
if (typeof userOrID === "string") { // just a string when double-checking a vote removal - good thing the unvoter is already here from having voted
var userID = userOrID
var senderMxid = from("sim").join("sim_member", "mxid").where({user_id: userOrID, room_id: matchingRoomID}).pluck("mxid").get()
userID = userOrID
senderMxid = from("sim").join("sim_member", "mxid").where({user_id: userOrID, room_id: matchingRoomID}).pluck("mxid").get()
if (!senderMxid) return
} else { // sent in full when double-checking adding a vote, so we can properly ensure joined
var userID = userOrID.id
var senderMxid = await registerUser.ensureSimJoined(userOrID, matchingRoomID)
userID = userOrID.id
senderMxid = await registerUser.ensureSimJoined(userOrID, matchingRoomID)
}
const answersArray = select("poll_vote", "matrix_option", {discord_or_matrix_user_id: userID, message_id: pollMessageID}).pluck().all()
+1 -1
View File
@@ -19,7 +19,7 @@ const emitter = new EventEmitter()
* Due to Eventual Consistency(TM) an update/delete may arrive before the original message arrives
* (or before the it has finished being bridged to an event).
* In this case, wait until the original message has finished bridging, then retrigger the passed function.
* @template {(...args: any[]) => Promise<any>} T
* @template {(...args: any[]) => any} T
* @param {string} inputID
* @param {T} fn
* @param {Parameters<T>} rest
+2 -2
View File
@@ -54,8 +54,8 @@ async function doSpeedbump(messageID) {
debugSpeedbump(`[speedbump] DELETED ${messageID}`)
return true
}
value = bumping.get(messageID) - 1
if (value === 0) {
value = (bumping.get(messageID) ?? 0) - 1
if (value <= 0) {
debugSpeedbump(`[speedbump] OK ${messageID}-- = ${value}`)
bumping.delete(messageID)
return false
+7 -4
View File
@@ -9,7 +9,7 @@ const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
* @typedef {{text: string, index: number, end: number}} Token
*/
/** @typedef {{mxids: {localpart: string, mxid: string, displayname?: string}[], names: {displaynameTokens: Token[], mxid: string}[]}} ProcessedJoined */
/** @typedef {{mxids: {localpart: string, mxid: string, displayname?: string | null}[], names: {displaynameTokens: Token[], mxid: string}[]}} ProcessedJoined */
const lengthBonusLengthCap = 50
const lengthBonusValue = 0.5
@@ -18,7 +18,7 @@ const lengthBonusValue = 0.5
* 0 = no match
* @param {string} localpart
* @param {string} input
* @param {string} [displayname] only for the super tiebreaker
* @param {string | null} [displayname] only for the super tiebreaker
* @returns {{score: number, matchedInputTokens: Token[]}}
*/
function scoreLocalpart(localpart, input, displayname) {
@@ -103,7 +103,7 @@ function tokenise(name) {
}
/**
* @param {{mxid: string, displayname?: string}[]} joined
* @param {{mxid: string, displayname?: string | null}[]} joined
* @returns {ProcessedJoined}
*/
function processJoined(joined) {
@@ -120,6 +120,7 @@ function processJoined(joined) {
}),
names: joined.filter(j => j.displayname).map(j => {
return {
// @ts-ignore
displaynameTokens: tokenise(j.displayname),
mxid: j.mxid
}
@@ -130,6 +131,8 @@ function processJoined(joined) {
/**
* @param {ProcessedJoined} pjr
* @param {string} maximumWrittenSection lowercase please
* @param {number} baseOffset
* @param {string} prefix
* @param {string} content
*/
function findMention(pjr, maximumWrittenSection, baseOffset, prefix, content) {
@@ -142,7 +145,7 @@ function findMention(pjr, maximumWrittenSection, baseOffset, prefix, content) {
if (best.scored.score > 4) { // requires in smallest case perfect match of 2 characters, or in largest case a partial middle match of 5+ characters in a row
// Highlight the relevant part of the message
const start = baseOffset + best.scored.matchedInputTokens[0].index
const end = baseOffset + prefix.length + best.scored.matchedInputTokens.at(-1).end
const end = baseOffset + prefix.length + best.scored.matchedInputTokens.slice(-1)[0].end
const newContent = content.slice(0, start) + "[" + content.slice(start, end) + "](https://matrix.to/#/" + best.mxid + ")" + content.slice(end)
return {
mxid: best.mxid,
+2 -2
View File
@@ -113,7 +113,7 @@ test("score name: finds match location", t => {
const message = "evil lillith is an inspiration"
const result = scoreName(tokenise("INX | Evil Lillith (she/her)"), tokenise(message))
const startLocation = result.matchedInputTokens[0].index
const endLocation = result.matchedInputTokens.at(-1).end
const endLocation = result.matchedInputTokens.slice(-1)[0].end
t.equal(message.slice(startLocation, endLocation), "evil lillith")
})
@@ -125,5 +125,5 @@ test("find mention: test various tiebreakers", t => {
mxid: "@emma:rory.gay",
displayname: "Emma [it/its]"
}]), "emma ⚡ curious which one this prefers", 0, "@", "@emma ⚡ curious which one this prefers")
t.equal(found.mxid, "@emma:conduit.rory.gay")
t.equal(found?.mxid, "@emma:conduit.rory.gay")
})
+27 -22
View File
@@ -427,7 +427,7 @@ async function messageToEvent(message, guild, options = {}, di) {
* @param {string} [timestampChannelID]
*/
async function getHistoricalEventRow(messageID, timestampChannelID) {
/** @type {{room_id: string} | {event_id: string, room_id: string, reference_channel_id: string, source: number} | null} */
/** @type {{room_id: string} | {event_id: string, room_id: string, reference_channel_id: string, source: number} | null | undefined} */
let row = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
.select("event_id", "room_id", "reference_channel_id", "source").where({message_id: messageID}).and("ORDER BY part ASC").get()
if (!row && timestampChannelID) {
@@ -574,6 +574,7 @@ async function messageToEvent(message, guild, options = {}, di) {
if (repliedToEventInDifferentRoom || repliedToUnknownEvent) {
let referenced = message.referenced_message
if (!referenced) { // backend couldn't be bothered to dereference the message, have to do it ourselves
assert(message.message_reference?.message_id)
referenced = await discord.snow.channel.getChannelMessage(message.message_reference.channel_id, message.message_reference.message_id)
}
@@ -661,14 +662,14 @@ async function messageToEvent(message, guild, options = {}, di) {
}
// Forwarded content appears first
if (message.message_reference?.type === DiscordTypes.MessageReferenceType.Forward && message.message_snapshots?.length) {
if (message.message_reference?.type === DiscordTypes.MessageReferenceType.Forward && message.message_reference.message_id && message.message_snapshots?.length) {
// Forwarded notice
const row = await getHistoricalEventRow(message.message_reference.message_id, message.message_reference.channel_id)
const room = select("channel_room", ["room_id", "name", "nick"], {channel_id: message.message_reference.channel_id}).get()
const forwardedNotice = new mxUtils.MatrixStringBuilder()
if (room) {
const roomName = room && (room.nick || room.name)
if ("event_id" in row) {
if (row && "event_id" in row) {
const via = await getViaServersMemo(row.room_id)
forwardedNotice.addLine(
`[🔀 Forwarded from #${roomName}]`,
@@ -802,20 +803,23 @@ async function messageToEvent(message, guild, options = {}, di) {
// Then components
if (message.components?.length) {
const stack = [new mxUtils.MatrixStringBuilder()]
const stack = new mxUtils.MatrixStringBuilderStack()
/** @param {DiscordTypes.APIMessageComponent} component */
async function processComponent(component) {
// Standalone components
if (component.type === DiscordTypes.ComponentType.TextDisplay) {
const {body, html} = await transformContent(component.content)
stack[0].addParagraph(body, html)
stack.msb.addParagraph(body, html)
}
else if (component.type === DiscordTypes.ComponentType.Separator) {
stack[0].addParagraph("----", "<hr>")
stack.msb.addParagraph("----", "<hr>")
}
else if (component.type === DiscordTypes.ComponentType.File) {
const ev = await attachmentToEvent({}, {...component.file, filename: component.name, size: component.size}, true)
stack[0].addLine(ev.body, ev.formatted_body)
/** @type {{[k in keyof DiscordTypes.APIUnfurledMediaItem]-?: NonNullable<DiscordTypes.APIUnfurledMediaItem[k]>}} */ // @ts-ignore
const file = component.file
assert(component.name && component.size && file.content_type)
const ev = await attachmentToEvent({}, {...file, filename: component.name, size: component.size}, true)
stack.msb.addLine(ev.body, ev.formatted_body)
}
else if (component.type === DiscordTypes.ComponentType.MediaGallery) {
const description = component.items.length === 1 ? component.items[0].description || "Image:" : "Image gallery:"
@@ -826,43 +830,43 @@ async function messageToEvent(message, guild, options = {}, di) {
estimatedName: item.media.url.match(/\/([^/?]+)(\?|$)/)?.[1] || publicURL
}
})
stack[0].addLine(`🖼️ ${description} ${images.map(i => i.url).join(", ")}`, tag`🖼️ ${description} $${images.map(i => tag`<a href="${i.url}">${i.estimatedName}</a>`).join(", ")}`)
stack.msb.addLine(`🖼️ ${description} ${images.map(i => i.url).join(", ")}`, tag`🖼️ ${description} $${images.map(i => tag`<a href="${i.url}">${i.estimatedName}</a>`).join(", ")}`)
}
// string select, text input, user select, role select, mentionable select, channel select
// Components that can have things nested
else if (component.type === DiscordTypes.ComponentType.Container) {
// May contain action row, text display, section, media gallery, separator, file
stack.unshift(new mxUtils.MatrixStringBuilder())
stack.bump()
for (const innerComponent of component.components) {
await processComponent(innerComponent)
}
let {body, formatted_body} = stack.shift().get()
body = body.split("\n").map(l => "| " + l).join("\n")
formatted_body = `<blockquote>${formatted_body}</blockquote>`
if (stack[0].body) stack[0].body += "\n\n"
stack[0].add(body, formatted_body)
if (stack.msb.body) stack.msb.body += "\n\n"
stack.msb.add(body, formatted_body)
}
else if (component.type === DiscordTypes.ComponentType.Section) {
// May contain text display, possibly more in the future
// Accessory may be button or thumbnail
stack.unshift(new mxUtils.MatrixStringBuilder())
stack.bump()
for (const innerComponent of component.components) {
await processComponent(innerComponent)
}
if (component.accessory) {
stack.unshift(new mxUtils.MatrixStringBuilder())
stack.bump()
await processComponent(component.accessory)
const {body, formatted_body} = stack.shift().get()
stack[0].addLine(body, formatted_body)
stack.msb.addLine(body, formatted_body)
}
const {body, formatted_body} = stack.shift().get()
stack[0].addParagraph(body, formatted_body)
stack.msb.addParagraph(body, formatted_body)
}
else if (component.type === DiscordTypes.ComponentType.ActionRow) {
const linkButtons = component.components.filter(c => c.type === DiscordTypes.ComponentType.Button && c.style === DiscordTypes.ButtonStyle.Link)
if (linkButtons.length) {
stack[0].addLine("")
stack.msb.addLine("")
for (const linkButton of linkButtons) {
await processComponent(linkButton)
}
@@ -871,15 +875,15 @@ async function messageToEvent(message, guild, options = {}, di) {
// Components that can only be inside things
else if (component.type === DiscordTypes.ComponentType.Thumbnail) {
// May only be a section accessory
stack[0].add(`🖼️ ${component.media.url}`, tag`🖼️ <a href="${component.media.url}">${component.media.url}</a>`)
stack.msb.add(`🖼️ ${component.media.url}`, tag`🖼️ <a href="${component.media.url}">${component.media.url}</a>`)
}
else if (component.type === DiscordTypes.ComponentType.Button) {
// May only be a section accessory or in an action row (up to 5)
if (component.style === DiscordTypes.ButtonStyle.Link) {
if (component.label) {
stack[0].add(`[${component.label} ${component.url}] `, tag`<a href="${component.url}">${component.label}</a> `)
stack.msb.add(`[${component.label} ${component.url}] `, tag`<a href="${component.url}">${component.label}</a> `)
} else {
stack[0].add(component.url)
stack.msb.add(component.url)
}
}
}
@@ -891,7 +895,7 @@ async function messageToEvent(message, guild, options = {}, di) {
await processComponent(component)
}
const {body, formatted_body} = stack[0].get()
const {body, formatted_body} = stack.msb.get()
if (body.trim().length) {
await addTextEvent(body, formatted_body, "m.text")
}
@@ -914,7 +918,7 @@ async function messageToEvent(message, guild, options = {}, di) {
continue // Matrix's own URL previews are fine for images.
}
if (embed.type === "video" && !embed.title && message.content.includes(embed.video?.url)) {
if (embed.type === "video" && embed.video?.url && !embed.title && message.content.includes(embed.video.url)) {
continue // Doesn't add extra information and the direct video URL is already there.
}
@@ -937,6 +941,7 @@ async function messageToEvent(message, guild, options = {}, di) {
const rep = new mxUtils.MatrixStringBuilder()
if (isKlipyGIF) {
assert(embed.video?.url)
rep.add("[GIF] ", "➿ ")
if (embed.title) {
rep.add(`${embed.title} ${embed.video.url}`, tag`<a href="${embed.video.url}">${embed.title}</a>`)
+1 -1
View File
@@ -308,7 +308,7 @@ module.exports = {
async MESSAGE_REACTION_ADD(client, data) {
if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix.
if (data.emoji.name === "❓" && select("event_message", "message_id", {message_id: data.message_id, source: 0, part: 0}).get()) { // source 0 = matrix
const guild_id = data.guild_id ?? client.channels.get(data.channel_id)["guild_id"]
const guild_id = data.guild_id ?? client.channels.get(data.channel_id)?.["guild_id"]
await Promise.all([
client.snow.channel.deleteReaction(data.channel_id, data.message_id, data.emoji.name).catch(() => {}),
// @ts-ignore - this is all you need for it to do a matrix-side lookup