feat(Discord Node): Overhaul (#5351)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: Marcus <marcus@n8n.io>
This commit is contained in:
287
packages/nodes-base/nodes/Discord/v2/helpers/utils.ts
Normal file
287
packages/nodes-base/nodes/Discord/v2/helpers/utils.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
import type {
|
||||
IBinaryKeyData,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
} from 'n8n-workflow';
|
||||
import { jsonParse, NodeOperationError } from 'n8n-workflow';
|
||||
import { isEmpty } from 'lodash';
|
||||
import FormData from 'form-data';
|
||||
import { capitalize } from '../../../../utils/utilities';
|
||||
import { extension } from 'mime-types';
|
||||
import { discordApiRequest } from '../transport';
|
||||
|
||||
export const createSimplifyFunction =
|
||||
(includedFields: string[]) =>
|
||||
(item: IDataObject): IDataObject => {
|
||||
const result: IDataObject = {};
|
||||
|
||||
for (const field of includedFields) {
|
||||
if (item[field] === undefined) continue;
|
||||
|
||||
result[field] = item[field];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export function parseDiscordError(this: IExecuteFunctions, error: any, itemIndex = 0) {
|
||||
let errorData = error.cause.error;
|
||||
const errorOptions: IDataObject = { itemIndex };
|
||||
|
||||
if (!errorData && error.description) {
|
||||
try {
|
||||
const errorString = (error.description as string).split(' - ')[1];
|
||||
if (errorString) {
|
||||
errorData = jsonParse(errorString);
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
if (errorData?.message) {
|
||||
errorOptions.message = errorData.message;
|
||||
}
|
||||
|
||||
if ((error?.message as string)?.toLowerCase()?.includes('bad request') && errorData) {
|
||||
if (errorData?.message) {
|
||||
errorOptions.message = errorData.message;
|
||||
}
|
||||
|
||||
if (errorData?.errors?.embeds) {
|
||||
const embedErrors = errorData.errors.embeds?.[0];
|
||||
const embedErrorsKeys = Object.keys(embedErrors).map((key) => capitalize(key));
|
||||
|
||||
if (embedErrorsKeys.length) {
|
||||
const message =
|
||||
embedErrorsKeys.length === 1
|
||||
? `The parameter ${embedErrorsKeys[0]} is not properly formatted`
|
||||
: `The parameters ${embedErrorsKeys.join(', ')} are not properly formatted`;
|
||||
errorOptions.message = message;
|
||||
errorOptions.description = 'Review the formatting or clear it';
|
||||
}
|
||||
|
||||
return new NodeOperationError(this.getNode(), errorData.errors, errorOptions);
|
||||
}
|
||||
|
||||
if (errorData?.errors?.message_reference) {
|
||||
errorOptions.message = "The message to reply to ID can't be found";
|
||||
errorOptions.description =
|
||||
'Check the "Message to Reply to" parameter and remove it if you don\'t want to reply to an existing message';
|
||||
|
||||
return new NodeOperationError(this.getNode(), errorData.errors, errorOptions);
|
||||
}
|
||||
}
|
||||
return new NodeOperationError(this.getNode(), errorData || error, errorOptions);
|
||||
}
|
||||
|
||||
export function prepareErrorData(this: IExecuteFunctions, error: any, i: number) {
|
||||
let description = error.description;
|
||||
|
||||
try {
|
||||
description = JSON.parse(error.description as string);
|
||||
} catch (err) {}
|
||||
|
||||
return this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message, description }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
}
|
||||
|
||||
export function prepareOptions(options: IDataObject, guildId?: string) {
|
||||
if (options.flags) {
|
||||
if ((options.flags as string[]).length === 2) {
|
||||
options.flags = (1 << 2) + (1 << 12);
|
||||
} else if ((options.flags as string[]).includes('SUPPRESS_EMBEDS')) {
|
||||
options.flags = 1 << 2;
|
||||
} else if ((options.flags as string[]).includes('SUPPRESS_NOTIFICATIONS')) {
|
||||
options.flags = 1 << 12;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.message_reference) {
|
||||
options.message_reference = {
|
||||
message_id: options.message_reference,
|
||||
guild_id: guildId,
|
||||
};
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export function prepareEmbeds(this: IExecuteFunctions, embeds: IDataObject[], i = 0) {
|
||||
return embeds
|
||||
.map((embed, index) => {
|
||||
let embedReturnData: IDataObject = {};
|
||||
|
||||
if (embed.inputMethod === 'json') {
|
||||
if (typeof embed.json === 'object') {
|
||||
embedReturnData = embed.json as IDataObject;
|
||||
}
|
||||
try {
|
||||
embedReturnData = jsonParse(embed.json as string);
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(this.getNode(), 'Not a valid JSON', error);
|
||||
}
|
||||
} else {
|
||||
delete embed.inputMethod;
|
||||
|
||||
for (const key of Object.keys(embed)) {
|
||||
if (embed[key] !== '') {
|
||||
embedReturnData[key] = embed[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!embedReturnData.description) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Description is required, embed ${index} in item ${i} is missing it`,
|
||||
);
|
||||
}
|
||||
|
||||
if (embedReturnData.author) {
|
||||
embedReturnData.author = {
|
||||
name: embedReturnData.author,
|
||||
};
|
||||
}
|
||||
if (embedReturnData.color && typeof embedReturnData.color === 'string') {
|
||||
embedReturnData.color = parseInt(embedReturnData.color.replace('#', ''), 16);
|
||||
}
|
||||
if (embedReturnData.video) {
|
||||
embedReturnData.video = {
|
||||
url: embedReturnData.video,
|
||||
width: 1270,
|
||||
height: 720,
|
||||
};
|
||||
}
|
||||
if (embedReturnData.thumbnail) {
|
||||
embedReturnData.thumbnail = {
|
||||
url: embedReturnData.thumbnail,
|
||||
};
|
||||
}
|
||||
if (embedReturnData.image) {
|
||||
embedReturnData.image = {
|
||||
url: embedReturnData.image,
|
||||
};
|
||||
}
|
||||
|
||||
return embedReturnData;
|
||||
})
|
||||
.filter((embed) => !isEmpty(embed));
|
||||
}
|
||||
|
||||
export async function prepareMultiPartForm(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
files: IDataObject[],
|
||||
jsonPayload: IDataObject,
|
||||
i: number,
|
||||
) {
|
||||
const multiPartBody = new FormData();
|
||||
const attachments: IDataObject[] = [];
|
||||
const filesData: IDataObject[] = [];
|
||||
|
||||
for (const [index, file] of files.entries()) {
|
||||
const binaryData = (items[i].binary as IBinaryKeyData)?.[file.inputFieldName as string];
|
||||
|
||||
if (!binaryData) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Input item [${i}] does not contain binary data on property ${file.inputFieldName}`,
|
||||
);
|
||||
}
|
||||
|
||||
let filename = binaryData.fileName as string;
|
||||
|
||||
if (!filename.includes('.')) {
|
||||
if (binaryData.fileExtension) {
|
||||
filename += `.${binaryData.fileExtension}`;
|
||||
}
|
||||
if (binaryData.mimeType) {
|
||||
filename += `.${extension(binaryData.mimeType)}`;
|
||||
}
|
||||
}
|
||||
|
||||
attachments.push({
|
||||
id: index,
|
||||
filename,
|
||||
});
|
||||
|
||||
filesData.push({
|
||||
data: await this.helpers.getBinaryDataBuffer(i, file.inputFieldName as string),
|
||||
name: filename,
|
||||
mime: binaryData.mimeType,
|
||||
});
|
||||
}
|
||||
|
||||
multiPartBody.append('payload_json', JSON.stringify({ ...jsonPayload, attachments }), {
|
||||
contentType: 'application/json',
|
||||
});
|
||||
|
||||
for (const [index, binaryData] of filesData.entries()) {
|
||||
multiPartBody.append(`files[${index}]`, binaryData.data, {
|
||||
contentType: binaryData.name as string,
|
||||
filename: binaryData.mime as string,
|
||||
});
|
||||
}
|
||||
|
||||
return multiPartBody;
|
||||
}
|
||||
|
||||
export function checkAccessToGuild(
|
||||
node: INode,
|
||||
guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
itemIndex = 0,
|
||||
) {
|
||||
if (!userGuilds.some((guild) => guild.id === guildId)) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`You do not have access to the guild with the id ${guildId}`,
|
||||
{
|
||||
itemIndex,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAccessToChannel(
|
||||
this: IExecuteFunctions,
|
||||
channelId: string,
|
||||
userGuilds: IDataObject[],
|
||||
itemIndex = 0,
|
||||
) {
|
||||
let guildId = '';
|
||||
|
||||
try {
|
||||
const channel = await discordApiRequest.call(this, 'GET', `/channels/${channelId}`);
|
||||
guildId = channel.guild_id;
|
||||
} catch (error) {}
|
||||
|
||||
if (!guildId) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Could not fing server for channel with the id ${channelId}`,
|
||||
{
|
||||
itemIndex,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
checkAccessToGuild(this.getNode(), guildId, userGuilds, itemIndex);
|
||||
}
|
||||
|
||||
export async function setupChannelGetter(this: IExecuteFunctions, userGuilds: IDataObject[]) {
|
||||
const isOAuth2 = this.getNodeParameter('authentication', 0) === 'oAuth2';
|
||||
|
||||
return async (i: number) => {
|
||||
const channelId = this.getNodeParameter('channelId', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
if (isOAuth2) await checkAccessToChannel.call(this, channelId, userGuilds, i);
|
||||
|
||||
return channelId;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user