diff --git a/m2d/actions/register-webhook.js b/m2d/actions/register-webhook.js new file mode 100644 index 0000000..511029b --- /dev/null +++ b/m2d/actions/register-webhook.js @@ -0,0 +1,50 @@ +// @ts-check + +const assert = require("assert").strict +const passthrough = require("../../passthrough") +const {discord, db} = passthrough + +/** + * Look in the database to find webhook credentials for a channel. + * (Note that the credentials may be invalid and need to be re-created if the webhook was interfered with from outside.) + * @param {string} channelID + * @param {boolean} forceCreate create a new webhook no matter what the database says about the state + * @returns id and token for a webhook for that channel + */ +async function ensureWebhook(channelID, forceCreate = false) { + if (!forceCreate) { + /** @type {{id: string, token: string} | null} */ + const row = db.prepare("SELECT webhook_id as id, webhook_token as token FROM webhook WHERE channel_id = ?").get(channelID) + if (row) { + return {created: false, ...row} + } + } + + // If we got here, we need to create a new webhook. + const webhook = await discord.snow.webhook.createWebhook(channelID, {name: "Out Of Your Element: Matrix Bridge"}) + assert(webhook.token) + db.prepare("REPLACE INTO webhook (channel_id, webhook_id, webhook_token) VALUES (?, ?, ?)").run(channelID, webhook.id, webhook.token) + return { + id: webhook.id, + token: webhook.token, + created: true + } +} + +/** + * @param {string} channelID + * @param {(webhook: import("../../types").WebhookCreds) => Promise} callback + * @returns Promise + * @template T + */ +async function withWebhook(channelID, callback) { + const webhook = await ensureWebhook(channelID, false) + return callback(webhook).catch(e => { + console.error(e) + // TODO: check if the error was webhook-related and if webhook.created === false, then: const webhook = ensureWebhook(channelID, true); return callback(webhook) + throw new Error(e) + }) +} + +module.exports.ensureWebhook = ensureWebhook +module.exports.withWebhook = withWebhook diff --git a/m2d/actions/send-message.js b/m2d/actions/send-message.js new file mode 100644 index 0000000..9c61107 --- /dev/null +++ b/m2d/actions/send-message.js @@ -0,0 +1,23 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") +const passthrough = require("../../passthrough") +const {sync, discord, db} = passthrough + +/** @type {import("./register-webhook")} */ +const registerWebhook = sync.require("./register-webhook") + +/** + * @param {string} channelID + * @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {name: string, file: Buffer}[]} data + */ +// param {DiscordTypes.RESTPostAPIWebhookWithTokenQuery & {wait: true, disableEveryone?: boolean}} options +async function sendMessage(channelID, data) { + const result = await registerWebhook.withWebhook(channelID, async webhook => { + return discord.snow.webhook.executeWebhook(webhook.id, webhook.token, data, {wait: true, disableEveryone: true}) + }) + return result +} + +module.exports.sendMessage = sendMessage diff --git a/test/test.js b/test/test.js index ae6aea6..5f06ae4 100644 --- a/test/test.js +++ b/test/test.js @@ -17,3 +17,4 @@ require("../matrix/read-registration.test") require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test") +require("../d2m/actions/register-user.test") diff --git a/types.d.ts b/types.d.ts index bc24329..aaa8db1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -9,6 +9,11 @@ export type AppServiceRegistrationConfig = { rate_limited: boolean } +export type WebhookCreds = { + id: string + token: string +} + namespace Event { export type BaseStateEvent = { type: string