UNPKG

n8n

Version:

n8n Workflow Automation Tool

176 lines • 7.04 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.discordSearchChannelsSchema = void 0; exports.downloadDiscordAttachment = downloadDiscordAttachment; exports.searchDiscordChannels = searchDiscordChannels; exports.executeDiscordContextQuery = executeDiscordContextQuery; exports.resolveDiscordMessageTargetChannelId = resolveDiscordMessageTargetChannelId; exports.settleDiscordActionMessage = settleDiscordActionMessage; exports.fetchDiscordApplicationMetadata = fetchDiscordApplicationMetadata; const zod_1 = require("zod"); const integration_helpers_1 = require("../integration-helpers"); const PLATFORM = 'discord'; const DISCORD_MESSAGE_CONTENT_LIMIT = 2000; const DISCORD_ATTACHMENT_HOST = 'cdn.discordapp.com'; const POSTABLE_CHANNEL_TYPES = new Set([ 0, 5, ]); const MAX_GUILDS_SCANNED = 20; async function downloadDiscordAttachment(attachmentUrl, httpClient) { const url = new URL(attachmentUrl); if (url.protocol !== 'https:' || url.username || url.password || url.hostname !== DISCORD_ATTACHMENT_HOST || !url.pathname.startsWith('/attachments/')) { throw new Error('Invalid Discord attachment URL'); } const response = await httpClient.request({ method: 'GET', url: url.href, encoding: 'arraybuffer', returnFullResponse: true, ignoreHttpStatusErrors: true, disableFollowRedirect: true, timeout: 30_000, abortSignal: AbortSignal.timeout(30_000), }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error(`Discord attachment download failed with status ${response.statusCode}`); } return Buffer.from(response.body); } exports.discordSearchChannelsSchema = zod_1.z.object({ query: zod_1.z.string().min(1), limit: zod_1.z.number().int().min(1).max(50).default(10), cursor: zod_1.z.string().min(1).optional(), }); async function discordApiGet(httpClient, apiUrl, botToken, path) { const response = await httpClient.request({ method: 'GET', url: `${apiUrl}${path}`, headers: { Authorization: `Bot ${botToken}` }, json: true, returnFullResponse: true, ignoreHttpStatusErrors: true, sendCredentialsOnCrossOriginRedirect: false, }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error(`Discord API ${path} failed: ${response.statusCode} ${JSON.stringify(response.body)}`); } return response.body; } function normalizeChannelName(value) { return value.trim().replace(/^#/, '').toLowerCase(); } async function searchDiscordChannels(params) { const { httpClient, apiUrl, botToken, input } = params; const searchTerm = normalizeChannelName(input.query); const guildParams = new URLSearchParams({ limit: String(MAX_GUILDS_SCANNED) }); if (input.cursor) guildParams.set('after', input.cursor); const guilds = await discordApiGet(httpClient, apiUrl, botToken, `/users/@me/guilds?${guildParams.toString()}`); const channels = []; let lastScannedGuildId; for (const guild of guilds) { if (channels.length >= input.limit) break; lastScannedGuildId = guild.id; let guildChannels; try { guildChannels = await discordApiGet(httpClient, apiUrl, botToken, `/guilds/${guild.id}/channels`); } catch { continue; } for (const channel of guildChannels) { if (channels.length >= input.limit) break; if (!POSTABLE_CHANNEL_TYPES.has(channel.type) || !channel.name) continue; if (!normalizeChannelName(channel.name).includes(searchTerm)) continue; channels.push({ channelId: `discord:${guild.id}:${channel.id}`, name: channel.name, guildId: guild.id, guildName: guild.name, }); } } const hasUnscannedGuilds = lastScannedGuildId !== undefined && lastScannedGuildId !== guilds.at(-1)?.id; const nextCursor = lastScannedGuildId && (hasUnscannedGuilds || guilds.length === MAX_GUILDS_SCANNED) ? lastScannedGuildId : undefined; return { ok: true, channels, resultCount: channels.length, ...(nextCursor ? { nextCursor } : {}), }; } async function executeDiscordContextQuery(params) { if (params.query !== 'search_channels') return (0, integration_helpers_1.unsupportedQuery)(PLATFORM, params.query); if (!params.botToken) return (0, integration_helpers_1.connectionUnavailable)(); return await searchDiscordChannels({ httpClient: params.httpClient, apiUrl: params.apiUrl, botToken: params.botToken, input: exports.discordSearchChannelsSchema.parse(params.input), }); } function resolveDiscordMessageTargetChannelId(threadId) { const parts = threadId.split(':'); if (parts[0] !== 'discord' || parts.length < 3 || !parts[2]) { throw new Error(`Invalid Discord thread ID: ${threadId}`); } return parts[3] || parts[2]; } async function settleDiscordActionMessage(params) { const channelId = resolveDiscordMessageTargetChannelId(params.threadId); const content = params.content.slice(0, DISCORD_MESSAGE_CONTENT_LIMIT); const response = await params.httpClient.request({ method: 'PATCH', url: `${params.apiUrl}/channels/${channelId}/messages/${params.messageId}`, headers: { Authorization: `Bot ${params.botToken}` }, body: { content, embeds: [], components: [], allowed_mentions: { parse: [] }, }, json: true, returnFullResponse: true, ignoreHttpStatusErrors: true, sendCredentialsOnCrossOriginRedirect: false, }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error(`Discord API PATCH /channels/${channelId}/messages/${params.messageId} failed: ${response.statusCode}`); } } async function fetchDiscordApplicationMetadata(params) { const response = await params.httpClient.request({ method: 'GET', url: `${params.apiUrl}/oauth2/applications/@me`, headers: { Authorization: `Bot ${params.botToken}` }, json: true, returnFullResponse: true, ignoreHttpStatusErrors: true, sendCredentialsOnCrossOriginRedirect: false, }); if (response.statusCode < 200 || response.statusCode >= 300) { return { ok: false, kind: 'http', status: response.statusCode }; } const body = response.body; if (typeof body.id !== 'string' || !body.id || typeof body.verify_key !== 'string' || !body.verify_key) { return { ok: false, kind: 'incomplete' }; } return { ok: true, application: { id: body.id, verify_key: body.verify_key } }; } //# sourceMappingURL=discord-operations.js.map