2023-07-03 05:20:24 +00:00
|
|
|
// @ts-check
|
|
|
|
|
2023-09-02 11:28:41 +00:00
|
|
|
const Ty = require("../../types")
|
2023-07-03 05:20:24 +00:00
|
|
|
const DiscordTypes = require("discord-api-types/v10")
|
2023-10-14 11:26:52 +00:00
|
|
|
const {Readable} = require("stream")
|
|
|
|
const assert = require("assert").strict
|
|
|
|
const crypto = require("crypto")
|
|
|
|
const fetch = require("node-fetch").default
|
2023-07-03 05:20:24 +00:00
|
|
|
const passthrough = require("../../passthrough")
|
2023-09-18 10:51:59 +00:00
|
|
|
const {sync, discord, db, select} = passthrough
|
2023-07-03 05:20:24 +00:00
|
|
|
|
|
|
|
/** @type {import("./channel-webhook")} */
|
|
|
|
const channelWebhook = sync.require("./channel-webhook")
|
|
|
|
/** @type {import("../converters/event-to-message")} */
|
|
|
|
const eventToMessage = sync.require("../converters/event-to-message")
|
2023-08-26 08:30:22 +00:00
|
|
|
/** @type {import("../../matrix/api")}) */
|
|
|
|
const api = sync.require("../../matrix/api")
|
2023-11-25 10:09:28 +00:00
|
|
|
/** @type {import("../../d2m/actions/register-user")} */
|
|
|
|
const registerUser = sync.require("../../d2m/actions/register-user")
|
2024-03-19 02:06:31 +00:00
|
|
|
/** @type {import("../../d2m/actions/edit-message")} */
|
|
|
|
const editMessage = sync.require("../../d2m/actions/edit-message")
|
2024-03-04 04:02:38 +00:00
|
|
|
/** @type {import("../actions/emoji-sheet")} */
|
|
|
|
const emojiSheet = sync.require("../actions/emoji-sheet")
|
2023-07-03 05:20:24 +00:00
|
|
|
|
2023-09-02 11:28:41 +00:00
|
|
|
/**
|
2023-10-14 11:26:52 +00:00
|
|
|
* @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | Readable}[], pendingFiles?: ({name: string, url: string} | {name: string, url: string, key: string, iv: string} | {name: string, buffer: Buffer | Readable})[]}} message
|
|
|
|
* @returns {Promise<DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | Readable}[]}>}
|
2023-09-02 11:28:41 +00:00
|
|
|
*/
|
|
|
|
async function resolvePendingFiles(message) {
|
|
|
|
if (!message.pendingFiles) return message
|
|
|
|
const files = await Promise.all(message.pendingFiles.map(async p => {
|
2023-09-23 13:55:47 +00:00
|
|
|
if ("buffer" in p) {
|
|
|
|
return {
|
|
|
|
name: p.name,
|
|
|
|
file: p.buffer
|
|
|
|
}
|
|
|
|
}
|
2023-09-03 03:40:25 +00:00
|
|
|
if ("key" in p) {
|
2023-10-14 11:26:52 +00:00
|
|
|
// Encrypted file
|
2023-09-03 03:40:25 +00:00
|
|
|
const d = crypto.createDecipheriv("aes-256-ctr", Buffer.from(p.key, "base64url"), Buffer.from(p.iv, "base64url"))
|
2023-10-14 11:26:52 +00:00
|
|
|
// @ts-ignore
|
|
|
|
fetch(p.url).then(res => res.body.pipe(d))
|
|
|
|
return {
|
|
|
|
name: p.name,
|
|
|
|
file: d
|
|
|
|
}
|
2023-09-03 03:40:25 +00:00
|
|
|
} else {
|
2023-10-14 11:26:52 +00:00
|
|
|
// Unencrypted file
|
|
|
|
/** @type {Readable} */ // @ts-ignore
|
|
|
|
const body = await fetch(p.url).then(res => res.body)
|
|
|
|
return {
|
|
|
|
name: p.name,
|
|
|
|
file: body
|
|
|
|
}
|
2023-09-02 11:28:41 +00:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
const newMessage = {
|
|
|
|
...message,
|
2023-09-23 13:55:47 +00:00
|
|
|
files: files.concat(message.files || [])
|
2023-09-02 11:28:41 +00:00
|
|
|
}
|
|
|
|
delete newMessage.pendingFiles
|
|
|
|
return newMessage
|
|
|
|
}
|
|
|
|
|
2023-09-03 03:40:25 +00:00
|
|
|
/** @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker} event */
|
2023-07-03 05:20:24 +00:00
|
|
|
async function sendEvent(event) {
|
2023-10-05 23:31:10 +00:00
|
|
|
const row = select("channel_room", ["channel_id", "thread_parent"], {room_id: event.room_id}).get()
|
2023-09-27 10:24:53 +00:00
|
|
|
if (!row) return // allow the bot to exist in unbridged rooms, just don't do anything with it
|
2023-08-19 06:39:23 +00:00
|
|
|
let channelID = row.channel_id
|
|
|
|
let threadID = undefined
|
|
|
|
if (row.thread_parent) {
|
|
|
|
threadID = channelID
|
|
|
|
channelID = row.thread_parent // it's the thread's parent... get with the times...
|
|
|
|
}
|
2023-08-26 08:30:22 +00:00
|
|
|
// @ts-ignore
|
|
|
|
const guildID = discord.channels.get(channelID).guild_id
|
|
|
|
const guild = discord.guilds.get(guildID)
|
|
|
|
assert(guild)
|
2023-07-03 12:39:42 +00:00
|
|
|
|
2023-08-21 09:04:41 +00:00
|
|
|
// no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it
|
2023-07-03 05:20:24 +00:00
|
|
|
|
2024-03-01 04:28:14 +00:00
|
|
|
let {messagesToEdit, messagesToSend, messagesToDelete, ensureJoined} = await eventToMessage.eventToMessage(event, guild, {api, snow: discord.snow, fetch, mxcDownloader: emojiSheet.getAndConvertEmoji})
|
2023-09-02 11:28:41 +00:00
|
|
|
|
|
|
|
messagesToEdit = await Promise.all(messagesToEdit.map(async e => {
|
|
|
|
e.message = await resolvePendingFiles(e.message)
|
|
|
|
return e
|
|
|
|
}))
|
|
|
|
messagesToSend = await Promise.all(messagesToSend.map(message => {
|
|
|
|
return resolvePendingFiles(message)
|
|
|
|
}))
|
2023-07-03 05:20:24 +00:00
|
|
|
|
2023-08-30 01:14:23 +00:00
|
|
|
let eventPart = 0 // 0 is primary, 1 is supporting
|
|
|
|
|
2023-08-21 09:04:41 +00:00
|
|
|
/** @type {DiscordTypes.APIMessage[]} */
|
2023-07-03 05:20:24 +00:00
|
|
|
const messageResponses = []
|
2023-08-28 13:31:52 +00:00
|
|
|
for (const data of messagesToEdit) {
|
|
|
|
const messageResponse = await channelWebhook.editMessageWithWebhook(channelID, data.id, data.message, threadID)
|
|
|
|
eventPart = 1
|
|
|
|
messageResponses.push(messageResponse)
|
|
|
|
}
|
2023-08-30 01:14:23 +00:00
|
|
|
|
|
|
|
for (const id of messagesToDelete) {
|
2024-01-10 02:48:13 +00:00
|
|
|
db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(id)
|
|
|
|
db.prepare("DELETE FROM event_message WHERE message_id = ?").run(id)
|
2023-08-30 01:14:23 +00:00
|
|
|
await channelWebhook.deleteMessageWithWebhook(channelID, id, threadID)
|
|
|
|
}
|
|
|
|
|
2023-08-27 13:30:07 +00:00
|
|
|
for (const message of messagesToSend) {
|
2023-10-14 09:08:10 +00:00
|
|
|
const reactionPart = messagesToEdit.length === 0 && message === messagesToSend[messagesToSend.length - 1] ? 0 : 1
|
2023-08-21 09:04:41 +00:00
|
|
|
const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID)
|
2023-10-12 13:05:44 +00:00
|
|
|
db.prepare("REPLACE INTO message_channel (message_id, channel_id) VALUES (?, ?)").run(messageResponse.id, threadID || channelID)
|
2023-10-14 09:08:10 +00:00
|
|
|
db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, part, reaction_part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content["msgtype"] || null, messageResponse.id, eventPart, reactionPart) // source 0 = matrix
|
2023-07-03 05:20:24 +00:00
|
|
|
|
2023-08-30 01:14:23 +00:00
|
|
|
eventPart = 1
|
2023-07-03 05:20:24 +00:00
|
|
|
messageResponses.push(messageResponse)
|
2024-03-19 02:06:31 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
If the Discord system has a cached link preview embed for one of the links just sent,
|
|
|
|
it will be instantly added as part of `embeds` and there won't be a MESSAGE_UPDATE.
|
|
|
|
To reflect the generated embed back to Matrix, we pretend the message was updated right away.
|
|
|
|
*/
|
|
|
|
const sentEmbedsCount = message.embeds?.length || 0
|
|
|
|
if (messageResponse.embeds.length > sentEmbedsCount) {
|
|
|
|
// @ts-ignore this is a valid message edit payload
|
|
|
|
editMessage.editMessage({ // not awaiting because requests to Matrix shouldn't block requests to Discord
|
|
|
|
id: messageResponse.channel_id,
|
|
|
|
channel_id: messageResponse.channel_id,
|
|
|
|
guild_id: guild.id,
|
|
|
|
embeds: messageResponse.embeds
|
|
|
|
}, guild, null)
|
|
|
|
}
|
2023-07-03 05:20:24 +00:00
|
|
|
}
|
|
|
|
|
2023-11-25 10:09:28 +00:00
|
|
|
for (const user of ensureJoined) {
|
|
|
|
registerUser.ensureSimJoined(user, event.room_id)
|
|
|
|
}
|
|
|
|
|
2023-07-03 05:20:24 +00:00
|
|
|
return messageResponses
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.sendEvent = sendEvent
|