Force Matrix m.notices to be unchanged events
This commit is contained in:
@@ -6,8 +6,10 @@ const passthrough = require("../../passthrough")
|
|||||||
const {sync, select, from} = passthrough
|
const {sync, select, from} = passthrough
|
||||||
/** @type {import("./message-to-event")} */
|
/** @type {import("./message-to-event")} */
|
||||||
const messageToEvent = sync.require("../converters/message-to-event")
|
const messageToEvent = sync.require("../converters/message-to-event")
|
||||||
|
/** @type {import("../../discord/utils")} */
|
||||||
|
const dUtils = sync.require("../../discord/utils")
|
||||||
/** @type {import("../../matrix/utils")} */
|
/** @type {import("../../matrix/utils")} */
|
||||||
const utils = sync.require("../../matrix/utils")
|
const mxUtils = sync.require("../../matrix/utils")
|
||||||
|
|
||||||
function eventCanBeEdited(ev) {
|
function eventCanBeEdited(ev) {
|
||||||
// Discord does not allow files, images, attachments, or videos to be edited.
|
// Discord does not allow files, images, attachments, or videos to be edited.
|
||||||
@@ -56,7 +58,7 @@ async function editToChanges(message, guild, api) {
|
|||||||
// Should be a system generated embed. We want the embed to be sent by the same user who sent the message, so that the messages get grouped in most clients.
|
// Should be a system generated embed. We want the embed to be sent by the same user who sent the message, so that the messages get grouped in most clients.
|
||||||
const eventID = oldEventRows[0].event_id // a calling function should have already checked that there is at least one message to edit
|
const eventID = oldEventRows[0].event_id // a calling function should have already checked that there is at least one message to edit
|
||||||
const event = await api.getEvent(roomID, eventID)
|
const event = await api.getEvent(roomID, eventID)
|
||||||
if (utils.eventSenderIsFromDiscord(event.sender)) {
|
if (mxUtils.eventSenderIsFromDiscord(event.sender)) {
|
||||||
senderMxid = event.sender
|
senderMxid = event.sender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,14 +134,14 @@ async function editToChanges(message, guild, api) {
|
|||||||
// If this is a generated embed update, only allow the embeds to be updated, since the system only sends data about events. Ignore changes to other things.
|
// If this is a generated embed update, only allow the embeds to be updated, since the system only sends data about events. Ignore changes to other things.
|
||||||
// This also prevents Matrix events that were re-subtyped during conversion (e.g. large image -> text link) from being mistakenly included.
|
// This also prevents Matrix events that were re-subtyped during conversion (e.g. large image -> text link) from being mistakenly included.
|
||||||
if (isGeneratedEmbed) {
|
if (isGeneratedEmbed) {
|
||||||
unchangedEvents.push(...eventsToRedact.filter(e => e.old.event_subtype !== "m.notice")) // Move them from eventsToRedact to unchangedEvents.
|
unchangedEvents = unchangedEvents.concat(
|
||||||
eventsToRedact = eventsToRedact.filter(e => e.old.event_subtype === "m.notice")
|
dUtils.filterTo(eventsToRedact, e => e.old.event_subtype === "m.notice" && e.old.source === 1), // Move everything except embeds from eventsToRedact to unchangedEvents.
|
||||||
unchangedEvents.push(...eventsToReplace.filter(e => e.old.event_subtype !== "m.notice")) // Move them from eventsToReplace to unchangedEvents.
|
dUtils.filterTo(eventsToReplace, e => e.old.event_subtype === "m.notice" && e.old.source === 1) // Move everything except embeds from eventsToReplace to unchangedEvents.
|
||||||
eventsToReplace = eventsToReplace.filter(e => e.old.event_subtype === "m.notice")
|
)
|
||||||
eventsToSend = eventsToSend.filter(e => e.msgtype === "m.notice") // Don't send new events that aren't the embed.
|
eventsToSend = eventsToSend.filter(e => e.msgtype === "m.notice") // Don't send new events that aren't the embed.
|
||||||
|
|
||||||
// Don't post new generated embeds for messages if it's been a while since the message was sent. Detached embeds look weird.
|
// Don't post new generated embeds for messages if it's been a while since the message was sent. Detached embeds look weird.
|
||||||
const messageTooOld = message.timestamp && new Date(message.timestamp).getTime() < Date.now() - 120 * 1000 // older than 2 minutes ago
|
const messageTooOld = message.timestamp && new Date(message.timestamp).getTime() < Date.now() - 30 * 1000 // older than 30 seconds ago
|
||||||
// Don't post new generated embeds for messages if the setting was disabled.
|
// Don't post new generated embeds for messages if the setting was disabled.
|
||||||
const embedsEnabled = select("guild_space", "url_preview", {guild_id: guild?.id}).pluck().get() ?? 1
|
const embedsEnabled = select("guild_space", "url_preview", {guild_id: guild?.id}).pluck().get() ?? 1
|
||||||
if (messageTooOld || !embedsEnabled) {
|
if (messageTooOld || !embedsEnabled) {
|
||||||
@@ -150,8 +152,7 @@ async function editToChanges(message, guild, api) {
|
|||||||
// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
|
// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
|
||||||
// (Example: a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
|
// (Example: a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
|
||||||
// So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed.
|
// So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed.
|
||||||
unchangedEvents.push(...eventsToReplace.filter(ev => !eventCanBeEdited(ev))) // Move them from eventsToRedact to unchangedEvents.
|
unchangedEvents = unchangedEvents.concat(dUtils.filterTo(eventsToReplace, ev => eventCanBeEdited(ev))) // Move them from eventsToReplace to unchangedEvents.
|
||||||
eventsToReplace = eventsToReplace.filter(eventCanBeEdited)
|
|
||||||
|
|
||||||
// Now, everything in eventsToReplace has the potential to have changed, but did it actually?
|
// Now, everything in eventsToReplace has the potential to have changed, but did it actually?
|
||||||
// (Example: if a URL preview was generated or updated, the message text won't have changed.)
|
// (Example: if a URL preview was generated or updated, the message text won't have changed.)
|
||||||
|
|||||||
@@ -153,6 +153,26 @@ function howOldUnbridgedMessage(oldTimestamp, newTimestamp) {
|
|||||||
return dateDisplay
|
return dateDisplay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the input, removing items that don't pass the filter. Returns the items that didn't pass.
|
||||||
|
* @param {T[]} xs
|
||||||
|
* @param {(x: T, i?: number) => any} fn
|
||||||
|
* @template T
|
||||||
|
* @returns T[]
|
||||||
|
*/
|
||||||
|
function filterTo(xs, fn) {
|
||||||
|
/** @type {T[]} */
|
||||||
|
const filtered = []
|
||||||
|
for (let i = xs.length-1; i >= 0; i--) {
|
||||||
|
const x = xs[i]
|
||||||
|
if (!fn(x, i)) {
|
||||||
|
filtered.unshift(x)
|
||||||
|
xs.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.getPermissions = getPermissions
|
module.exports.getPermissions = getPermissions
|
||||||
module.exports.hasPermission = hasPermission
|
module.exports.hasPermission = hasPermission
|
||||||
module.exports.hasSomePermissions = hasSomePermissions
|
module.exports.hasSomePermissions = hasSomePermissions
|
||||||
@@ -163,3 +183,4 @@ module.exports.snowflakeToTimestampExact = snowflakeToTimestampExact
|
|||||||
module.exports.timestampToSnowflakeInexact = timestampToSnowflakeInexact
|
module.exports.timestampToSnowflakeInexact = timestampToSnowflakeInexact
|
||||||
module.exports.getPublicUrlForCdn = getPublicUrlForCdn
|
module.exports.getPublicUrlForCdn = getPublicUrlForCdn
|
||||||
module.exports.howOldUnbridgedMessage = howOldUnbridgedMessage
|
module.exports.howOldUnbridgedMessage = howOldUnbridgedMessage
|
||||||
|
module.exports.filterTo = filterTo
|
||||||
|
|||||||
@@ -192,3 +192,10 @@ test("how old: hours", t => {
|
|||||||
test("how old: days", t => {
|
test("how old: days", t => {
|
||||||
t.equal(utils.howOldUnbridgedMessage("2024-01-01", "2025-01-01"), "a 366-day-old unbridged message")
|
t.equal(utils.howOldUnbridgedMessage("2024-01-01", "2025-01-01"), "a 366-day-old unbridged message")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("filterTo: works", t => {
|
||||||
|
const fruit = ["apple", "banana", "apricot"]
|
||||||
|
const rest = utils.filterTo(fruit, f => f[0] === "b")
|
||||||
|
t.deepEqual(fruit, ["banana"])
|
||||||
|
t.deepEqual(rest, ["apple", "apricot"])
|
||||||
|
})
|
||||||
|
|||||||
+6
-27
@@ -53,26 +53,6 @@ function getAPI(event) {
|
|||||||
/** @type {LRUCache<string, string>} nonce to guild id */
|
/** @type {LRUCache<string, string>} nonce to guild id */
|
||||||
const validNonce = new LRUCache({max: 200})
|
const validNonce = new LRUCache({max: 200})
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the input, removing items that don't pass the filter. Returns the items that didn't pass.
|
|
||||||
* @param {T[]} xs
|
|
||||||
* @param {(x: T, i?: number) => any} fn
|
|
||||||
* @template T
|
|
||||||
* @returns T[]
|
|
||||||
*/
|
|
||||||
function filterTo(xs, fn) {
|
|
||||||
/** @type {T[]} */
|
|
||||||
const filtered = []
|
|
||||||
for (let i = xs.length-1; i >= 0; i--) {
|
|
||||||
const x = xs[i]
|
|
||||||
if (!fn(x, i)) {
|
|
||||||
filtered.unshift(x)
|
|
||||||
xs.splice(i, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{type: number, parent_id?: string, position?: number}} channel
|
* @param {{type: number, parent_id?: string, position?: number}} channel
|
||||||
* @param {Map<string, {type: number, parent_id?: string, position?: number}>} channels
|
* @param {Map<string, {type: number, parent_id?: string, position?: number}>} channels
|
||||||
@@ -119,15 +99,15 @@ function getChannelRoomsLinks(guild, rooms, roles) {
|
|||||||
|
|
||||||
let linkedChannels = select("channel_room", ["channel_id", "room_id", "name", "nick"], {channel_id: channelIDs}).all()
|
let linkedChannels = select("channel_room", ["channel_id", "room_id", "name", "nick"], {channel_id: channelIDs}).all()
|
||||||
let linkedChannelsWithDetails = linkedChannels.map(c => ({channel: discord.channels.get(c.channel_id), ...c}))
|
let linkedChannelsWithDetails = linkedChannels.map(c => ({channel: discord.channels.get(c.channel_id), ...c}))
|
||||||
let removedUncachedChannels = filterTo(linkedChannelsWithDetails, c => c.channel)
|
let removedUncachedChannels = dUtils.filterTo(linkedChannelsWithDetails, c => c.channel)
|
||||||
let linkedChannelIDs = linkedChannelsWithDetails.map(c => c.channel_id)
|
let linkedChannelIDs = linkedChannelsWithDetails.map(c => c.channel_id)
|
||||||
linkedChannelsWithDetails.sort((a, b) => getPosition(a.channel, discord.channels) - getPosition(b.channel, discord.channels))
|
linkedChannelsWithDetails.sort((a, b) => getPosition(a.channel, discord.channels) - getPosition(b.channel, discord.channels))
|
||||||
|
|
||||||
let unlinkedChannelIDs = channelIDs.filter(c => !linkedChannelIDs.includes(c))
|
let unlinkedChannelIDs = channelIDs.filter(c => !linkedChannelIDs.includes(c))
|
||||||
/** @type {DiscordTypes.APIGuildChannel[]} */ // @ts-ignore
|
/** @type {DiscordTypes.APIGuildChannel[]} */ // @ts-ignore
|
||||||
let unlinkedChannels = unlinkedChannelIDs.map(c => discord.channels.get(c))
|
let unlinkedChannels = unlinkedChannelIDs.map(c => discord.channels.get(c))
|
||||||
let removedWrongTypeChannels = filterTo(unlinkedChannels, c => c && [0, 5].includes(c.type))
|
let removedWrongTypeChannels = dUtils.filterTo(unlinkedChannels, c => c && [0, 5].includes(c.type))
|
||||||
let removedPrivateChannels = filterTo(unlinkedChannels, c => {
|
let removedPrivateChannels = dUtils.filterTo(unlinkedChannels, c => {
|
||||||
const permissions = dUtils.getPermissions(guild.id, roles, guild.roles, botID, c["permission_overwrites"])
|
const permissions = dUtils.getPermissions(guild.id, roles, guild.roles, botID, c["permission_overwrites"])
|
||||||
return dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel)
|
return dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel)
|
||||||
})
|
})
|
||||||
@@ -135,11 +115,11 @@ function getChannelRoomsLinks(guild, rooms, roles) {
|
|||||||
|
|
||||||
let linkedRoomIDs = linkedChannels.map(c => c.room_id)
|
let linkedRoomIDs = linkedChannels.map(c => c.room_id)
|
||||||
let unlinkedRooms = [...rooms]
|
let unlinkedRooms = [...rooms]
|
||||||
let removedLinkedRooms = filterTo(unlinkedRooms, r => !linkedRoomIDs.includes(r.room_id))
|
let removedLinkedRooms = dUtils.filterTo(unlinkedRooms, r => !linkedRoomIDs.includes(r.room_id))
|
||||||
let removedWrongTypeRooms = filterTo(unlinkedRooms, r => !r.room_type)
|
let removedWrongTypeRooms = dUtils.filterTo(unlinkedRooms, r => !r.room_type)
|
||||||
// https://discord.com/developers/docs/topics/threads#active-archived-threads
|
// https://discord.com/developers/docs/topics/threads#active-archived-threads
|
||||||
// need to filter out linked archived threads from unlinkedRooms, will just do that by comparing against the name
|
// need to filter out linked archived threads from unlinkedRooms, will just do that by comparing against the name
|
||||||
let removedArchivedThreadRooms = filterTo(unlinkedRooms, r => r.name && !r.name.match(/^\[(🔒)?⛓️\]/))
|
let removedArchivedThreadRooms = dUtils.filterTo(unlinkedRooms, r => r.name && !r.name.match(/^\[(🔒)?⛓️\]/))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
linkedChannelsWithDetails, unlinkedChannels, unlinkedRooms,
|
linkedChannelsWithDetails, unlinkedChannels, unlinkedRooms,
|
||||||
@@ -265,4 +245,3 @@ as.router.post("/api/invite", defineEventHandler(async event => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
module.exports._getPosition = getPosition
|
module.exports._getPosition = getPosition
|
||||||
module.exports._filterTo = filterTo
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const DiscordTypes = require("discord-api-types/v10")
|
|||||||
const tryToCatch = require("try-to-catch")
|
const tryToCatch = require("try-to-catch")
|
||||||
const {router, test} = require("../../../test/web")
|
const {router, test} = require("../../../test/web")
|
||||||
const {MatrixServerError} = require("../../matrix/mreq")
|
const {MatrixServerError} = require("../../matrix/mreq")
|
||||||
const {_getPosition, _filterTo} = require("./guild")
|
const {_getPosition} = require("./guild")
|
||||||
|
|
||||||
let nonce
|
let nonce
|
||||||
|
|
||||||
@@ -394,10 +394,3 @@ test("position sorting: sorts like discord does", t => {
|
|||||||
const sortedChannelIDs = [...channels.values()].sort((a, b) => _getPosition(a, channels) - _getPosition(b, channels)).map(c => c.id)
|
const sortedChannelIDs = [...channels.values()].sort((a, b) => _getPosition(a, channels) - _getPosition(b, channels)).map(c => c.id)
|
||||||
t.deepEqual(sortedChannelIDs, ["first", "thread", "second", "voice", "category", "category-first", "category-second", "category-second-thread"])
|
t.deepEqual(sortedChannelIDs, ["first", "thread", "second", "voice", "category", "category-first", "category-second", "category-second-thread"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("filterTo: works", t => {
|
|
||||||
const fruit = ["apple", "banana", "apricot"]
|
|
||||||
const rest = _filterTo(fruit, f => f[0] === "b")
|
|
||||||
t.deepEqual(fruit, ["banana"])
|
|
||||||
t.deepEqual(rest, ["apple", "apricot"])
|
|
||||||
})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user