first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { categoryRLC } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The name of the channel',
|
||||
placeholder: 'e.g. new-channel',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
default: '0',
|
||||
required: true,
|
||||
description: 'The type of channel to create',
|
||||
options: [
|
||||
{
|
||||
name: 'Guild Text',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
name: 'Guild Voice',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
name: 'Guild Category',
|
||||
value: '4',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Age-Restricted (NSFW)',
|
||||
name: 'nsfw',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the content of the channel might be nsfw (not safe for work)',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/type': ['4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Bitrate',
|
||||
name: 'bitrate',
|
||||
type: 'number',
|
||||
default: 8000,
|
||||
placeholder: 'e.g. 8000',
|
||||
typeOptions: {
|
||||
minValue: 8000,
|
||||
maxValue: 96000,
|
||||
},
|
||||
description: 'The bitrate (in bits) of the voice channel',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/type': ['2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...categoryRLC,
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/type': ['4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Position',
|
||||
name: 'position',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
displayName: 'Rate Limit Per User',
|
||||
name: 'rate_limit_per_user',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Amount of seconds a user has to wait before sending another message',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/type': ['4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Topic',
|
||||
name: 'topic',
|
||||
type: 'string',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
rows: 2,
|
||||
},
|
||||
description: 'The channel topic description (0-1024 characters)',
|
||||
placeholder: 'e.g. This channel is about…',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/type': ['4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'User Limit',
|
||||
name: 'user_limit',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 99,
|
||||
},
|
||||
placeholder: 'e.g. 20',
|
||||
description:
|
||||
'The limit for the number of members that can be in the channel (0 refers to no limit)',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/type': ['2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
operation: ['create'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const type = this.getNodeParameter('type', i) as string;
|
||||
const options = this.getNodeParameter('options', i);
|
||||
|
||||
if (options.categoryId) {
|
||||
options.parent_id = (options.categoryId as IDataObject).value;
|
||||
delete options.categoryId;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
type,
|
||||
...options,
|
||||
};
|
||||
|
||||
const response = await discordApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/guilds/${guildId}/channels`,
|
||||
body,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData, setupChannelGetter } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { channelRLC } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [channelRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
operation: ['deleteChannel'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const response = await discordApiRequest.call(this, 'DELETE', `/channels/${channelId}`);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData, setupChannelGetter } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { channelRLC } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [channelRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
operation: ['get'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const response = await discordApiRequest.call(this, 'GET', `/channels/${channelId}`);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { returnAllOrLimit } from '../../../../../utils/descriptions';
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
...returnAllOrLimit,
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter by Type',
|
||||
name: 'filter',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
name: 'Guild Text',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: 'Guild Voice',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: 'Guild Category',
|
||||
value: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
try {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0, false);
|
||||
let response = await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/channels`);
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
response = (response as IDataObject[]).slice(0, limit);
|
||||
}
|
||||
|
||||
const options = this.getNodeParameter('options', 0, {});
|
||||
|
||||
if (options.filter) {
|
||||
const filter = options.filter as number[];
|
||||
response = (response as IDataObject[]).filter((item) => filter.includes(item.type as number));
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: 0 } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, 0));
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as create from './create.operation';
|
||||
import * as deleteChannel from './deleteChannel.operation';
|
||||
import * as get from './get.operation';
|
||||
import * as getAll from './getAll.operation';
|
||||
import * as update from './update.operation';
|
||||
import { guildRLC } from '../common.description';
|
||||
|
||||
export { create, get, getAll, update, deleteChannel };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new channel',
|
||||
action: 'Create a channel',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteChannel',
|
||||
description: 'Delete a channel',
|
||||
action: 'Delete a channel',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a channel',
|
||||
action: 'Get a channel',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve the channels of a server',
|
||||
action: 'Get many channels',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a channel',
|
||||
action: 'Update a channel',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
{
|
||||
...guildRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...create.description,
|
||||
...deleteChannel.description,
|
||||
...get.description,
|
||||
...getAll.description,
|
||||
...update.description,
|
||||
];
|
||||
@@ -0,0 +1,154 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData, setupChannelGetter } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { categoryRLC, channelRLC } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
channelRLC,
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
"The new name of the channel. Fill this field only if you want to change the channel's name.",
|
||||
placeholder: 'e.g. new-channel-name',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Age-Restricted (NSFW)',
|
||||
name: 'nsfw',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the content of the channel might be nsfw (not safe for work)',
|
||||
},
|
||||
{
|
||||
displayName: 'Bitrate',
|
||||
name: 'bitrate',
|
||||
type: 'number',
|
||||
default: 8000,
|
||||
typeOptions: {
|
||||
minValue: 8000,
|
||||
maxValue: 96000,
|
||||
},
|
||||
description: 'The bitrate (in bits) of the voice channel',
|
||||
hint: 'Only applicable to voice channels',
|
||||
},
|
||||
categoryRLC,
|
||||
{
|
||||
displayName: 'Position',
|
||||
name: 'position',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Rate Limit Per User',
|
||||
name: 'rate_limit_per_user',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Amount of seconds a user has to wait before sending another message',
|
||||
},
|
||||
{
|
||||
displayName: 'Topic',
|
||||
name: 'topic',
|
||||
type: 'string',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
rows: 2,
|
||||
},
|
||||
description: 'The channel topic description (0-1024 characters)',
|
||||
placeholder: 'e.g. This channel is about…',
|
||||
},
|
||||
{
|
||||
displayName: 'User Limit',
|
||||
name: 'user_limit',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 99,
|
||||
},
|
||||
placeholder: 'e.g. 20',
|
||||
hint: 'Only applicable to voice channels',
|
||||
description:
|
||||
'The limit for the number of members that can be in the channel (0 refers to no limit)',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['channel'],
|
||||
operation: ['update'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const options = this.getNodeParameter('options', i);
|
||||
|
||||
if (options.categoryId) {
|
||||
options.parent_id = (options.categoryId as IDataObject).value;
|
||||
delete options.categoryId;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
...options,
|
||||
};
|
||||
|
||||
const response = await discordApiRequest.call(this, 'PATCH', `/channels/${channelId}`, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../utils/utilities';
|
||||
|
||||
export const guildRLC: INodeProperties = {
|
||||
displayName: 'Server',
|
||||
name: 'guildId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
description: 'Select the server (guild) that your bot is connected to',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'e.g. my-server',
|
||||
typeOptions: {
|
||||
searchListMethod: 'guildSearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://discord.com/channels/[guild-id]',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/([0-9]+)',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/([0-9]+)',
|
||||
errorMessage: 'Not a valid Discord Server URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 896347036838936576',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]+',
|
||||
errorMessage: 'Not a valid Discord Server ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const channelRLC: INodeProperties = {
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
description: 'Select the channel by name, URL, or ID',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'e.g. my-channel',
|
||||
typeOptions: {
|
||||
searchListMethod: 'channelSearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://discord.com/channels/[guild-id]/[channel-id]',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/[0-9]+\\/([0-9]+)',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/[0-9]+\\/([0-9]+)',
|
||||
errorMessage: 'Not a valid Discord Channel URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 896347036838936576',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]+',
|
||||
errorMessage: 'Not a valid Discord Channel ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const textChannelRLC: INodeProperties = {
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
description: 'Select the channel by name, URL, or ID',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'e.g. my-channel',
|
||||
typeOptions: {
|
||||
searchListMethod: 'textChannelSearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://discord.com/channels/[guild-id]/[channel-id]',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/[0-9]+\\/([0-9]+)',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/[0-9]+\\/([0-9]+)',
|
||||
errorMessage: 'Not a valid Discord Channel URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 896347036838936576',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]+',
|
||||
errorMessage: 'Not a valid Discord Channel ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const categoryRLC: INodeProperties = {
|
||||
displayName: 'Parent Category',
|
||||
name: 'categoryId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
description: 'The parent category where you want the channel to appear',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'e.g. my-channel',
|
||||
typeOptions: {
|
||||
searchListMethod: 'categorySearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://discord.com/channels/[guild-id]/[channel-id]',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/[0-9]+\\/([0-9]+)',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https:\\/\\/discord.com\\/channels\\/[0-9]+\\/([0-9]+)',
|
||||
errorMessage: 'Not a valid Discord Category URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 896347036838936576',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]+',
|
||||
errorMessage: 'Not a valid Discord Category ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const userRLC: INodeProperties = {
|
||||
displayName: 'User',
|
||||
name: 'userId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
description: 'Select the user you want to assign a role to',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'e.g. DiscordUser',
|
||||
typeOptions: {
|
||||
searchListMethod: 'userSearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 786953432728469534',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]+',
|
||||
errorMessage: 'Not a valid User ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const roleMultiOptions: INodeProperties = {
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||
displayName: 'Role',
|
||||
name: 'role',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getRoles',
|
||||
loadOptionsDependsOn: ['userId.value', 'guildId.value', 'operation'],
|
||||
},
|
||||
required: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
|
||||
description: 'Select the roles you want to add to the user',
|
||||
default: [],
|
||||
};
|
||||
|
||||
export const maxResultsNumber: INodeProperties = {
|
||||
displayName: 'Max Results',
|
||||
name: 'maxResults',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Maximum number of results. Too many results may slow down the query.',
|
||||
};
|
||||
|
||||
export const messageIdString: INodeProperties = {
|
||||
displayName: 'Message ID',
|
||||
name: 'messageId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The ID of the message',
|
||||
placeholder: 'e.g. 1057576506244726804',
|
||||
};
|
||||
|
||||
export const simplifyBoolean: INodeProperties = {
|
||||
displayName: 'Simplify',
|
||||
name: 'simplify',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
};
|
||||
|
||||
// embeds -----------------------------------------------------------------------------------------
|
||||
const embedFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The description of embed',
|
||||
placeholder: 'e.g. My description',
|
||||
typeOptions: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Author',
|
||||
name: 'author',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The name of the author',
|
||||
placeholder: 'e.g. John Doe',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
|
||||
type: 'color',
|
||||
default: '',
|
||||
description: 'Color code of the embed',
|
||||
placeholder: 'e.g. 12123432',
|
||||
},
|
||||
{
|
||||
displayName: 'Timestamp',
|
||||
name: 'timestamp',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The time displayed at the bottom of the embed. Provide in ISO8601 format.',
|
||||
placeholder: 'e.g. 2023-02-08 09:30:26',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The title of embed',
|
||||
placeholder: "e.g. Embed's title",
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The URL where you want to link the embed to',
|
||||
placeholder: 'e.g. https://discord.com/',
|
||||
},
|
||||
{
|
||||
displayName: 'URL Image',
|
||||
name: 'image',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Source URL of image (only supports http(s) and attachments)',
|
||||
placeholder: 'e.g. https://example.com/image.png',
|
||||
},
|
||||
{
|
||||
displayName: 'URL Thumbnail',
|
||||
name: 'thumbnail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Source URL of thumbnail (only supports http(s) and attachments)',
|
||||
placeholder: 'e.g. https://example.com/image.png',
|
||||
},
|
||||
{
|
||||
displayName: 'URL Video',
|
||||
name: 'video',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Source URL of video',
|
||||
placeholder: 'e.g. https://example.com/video.mp4',
|
||||
},
|
||||
];
|
||||
|
||||
const embedFieldsDescription = updateDisplayOptions(
|
||||
{
|
||||
show: {
|
||||
inputMethod: ['fields'],
|
||||
},
|
||||
},
|
||||
embedFields,
|
||||
);
|
||||
|
||||
export const embedsFixedCollection: INodeProperties = {
|
||||
displayName: 'Embeds',
|
||||
name: 'embeds',
|
||||
type: 'fixedCollection',
|
||||
placeholder: 'Add Embeds',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Input Method',
|
||||
name: 'inputMethod',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Enter Fields',
|
||||
value: 'fields',
|
||||
},
|
||||
{
|
||||
name: 'Raw JSON',
|
||||
value: 'json',
|
||||
},
|
||||
],
|
||||
default: 'fields',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'json',
|
||||
type: 'json',
|
||||
default: '={}',
|
||||
typeOptions: {
|
||||
rows: 2,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
inputMethod: ['json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...embedFieldsDescription,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
export const filesFixedCollection: INodeProperties = {
|
||||
displayName: 'Files',
|
||||
name: 'files',
|
||||
type: 'fixedCollection',
|
||||
placeholder: 'Add Files',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Input Data Field Name',
|
||||
name: 'inputFieldName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
description: 'The contents of the file being sent with the message',
|
||||
placeholder: 'e.g. data',
|
||||
hint: 'The name of the input field containing the binary file data to be sent',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sendToProperties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Send To',
|
||||
name: 'sendTo',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
{
|
||||
name: 'Channel',
|
||||
value: 'channel',
|
||||
},
|
||||
],
|
||||
default: 'channel',
|
||||
description: 'Send message to a channel or DM to a user',
|
||||
},
|
||||
|
||||
{
|
||||
...userRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
sendTo: ['user'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...textChannelRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
sendTo: ['channel'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,122 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { returnAllOrLimit } from '../../../../../utils/descriptions';
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { createSimplifyFunction, parseDiscordError, prepareErrorData } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { simplifyBoolean } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
...returnAllOrLimit,
|
||||
{
|
||||
displayName: 'After',
|
||||
name: 'after',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. 786953432728469534',
|
||||
description: 'The ID of the user after which to return the members',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [simplifyBoolean],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['member'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0, false);
|
||||
const after = this.getNodeParameter('after', 0);
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
if (after) {
|
||||
qs.after = after;
|
||||
}
|
||||
|
||||
let response: IDataObject[] = [];
|
||||
|
||||
try {
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
qs.limit = limit;
|
||||
response = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/guilds/${guildId}/members`,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
let responseData;
|
||||
qs.limit = 100;
|
||||
|
||||
do {
|
||||
responseData = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/guilds/${guildId}/members`,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
if (!responseData?.length) break;
|
||||
qs.after = responseData[responseData.length - 1].user.id;
|
||||
response.push(...responseData);
|
||||
} while (responseData.length);
|
||||
}
|
||||
|
||||
const simplify = this.getNodeParameter('options.simplify', 0, false) as boolean;
|
||||
|
||||
if (simplify) {
|
||||
const simplifyResponse = createSimplifyFunction(['user', 'roles', 'permissions']);
|
||||
|
||||
response = response.map(simplifyResponse);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: 0 } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, 0));
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as getAll from './getAll.operation';
|
||||
import * as roleAdd from './roleAdd.operation';
|
||||
import * as roleRemove from './roleRemove.operation';
|
||||
import { guildRLC } from '../common.description';
|
||||
|
||||
export { getAll, roleAdd, roleRemove };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['member'],
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve the members of a server',
|
||||
action: 'Get many members',
|
||||
},
|
||||
{
|
||||
name: 'Role Add',
|
||||
value: 'roleAdd',
|
||||
description: 'Add a role to a member',
|
||||
action: 'Add a role to a member',
|
||||
},
|
||||
{
|
||||
name: 'Role Remove',
|
||||
value: 'roleRemove',
|
||||
description: 'Remove a role from a member',
|
||||
action: 'Remove a role from a member',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
{
|
||||
...guildRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['member'],
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...getAll.description,
|
||||
...roleAdd.description,
|
||||
...roleRemove.description,
|
||||
];
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { roleMultiOptions, userRLC } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [userRLC, roleMultiOptions];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['member'],
|
||||
operation: ['roleAdd'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const userId = this.getNodeParameter('userId', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const roles = this.getNodeParameter('role', i, []) as string[];
|
||||
|
||||
for (const roleId of roles) {
|
||||
await discordApiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/guilds/${guildId}/members/${userId}/roles/${roleId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { roleMultiOptions, userRLC } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [userRLC, roleMultiOptions];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['member'],
|
||||
operation: ['roleRemove'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const userId = this.getNodeParameter('userId', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const roles = this.getNodeParameter('role', i, []) as string[];
|
||||
|
||||
for (const roleId of roles) {
|
||||
await discordApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/guilds/${guildId}/members/${userId}/roles/${roleId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData, setupChannelGetter } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { channelRLC, messageIdString } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [channelRLC, messageIdString];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
operation: ['deleteMessage'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||
|
||||
await discordApiRequest.call(this, 'DELETE', `/channels/${channelId}/messages/${messageId}`);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import {
|
||||
createSimplifyFunction,
|
||||
parseDiscordError,
|
||||
prepareErrorData,
|
||||
setupChannelGetter,
|
||||
} from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { channelRLC, messageIdString, simplifyBoolean } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
channelRLC,
|
||||
messageIdString,
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [simplifyBoolean],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
operation: ['get'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
const simplifyResponse = createSimplifyFunction([
|
||||
'id',
|
||||
'channel_id',
|
||||
'author',
|
||||
'content',
|
||||
'timestamp',
|
||||
'type',
|
||||
]);
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||
|
||||
let response = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/channels/${channelId}/messages/${messageId}`,
|
||||
);
|
||||
|
||||
const simplify = this.getNodeParameter('options.simplify', i, false) as boolean;
|
||||
|
||||
if (simplify) {
|
||||
response = simplifyResponse(response);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { returnAllOrLimit } from '../../../../../utils/descriptions';
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import {
|
||||
createSimplifyFunction,
|
||||
parseDiscordError,
|
||||
prepareErrorData,
|
||||
setupChannelGetter,
|
||||
} from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { channelRLC, simplifyBoolean } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
channelRLC,
|
||||
...returnAllOrLimit,
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [simplifyBoolean],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
const simplifyResponse = createSimplifyFunction([
|
||||
'id',
|
||||
'channel_id',
|
||||
'author',
|
||||
'content',
|
||||
'timestamp',
|
||||
'type',
|
||||
]);
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i, false);
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
let response: IDataObject[] = [];
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
qs.limit = limit;
|
||||
response = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/channels/${channelId}/messages`,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
let responseData;
|
||||
qs.limit = 100;
|
||||
|
||||
do {
|
||||
responseData = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/channels/${channelId}/messages`,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
if (!responseData?.length) break;
|
||||
qs.before = responseData[responseData.length - 1].id;
|
||||
response.push(...responseData);
|
||||
} while (responseData.length);
|
||||
}
|
||||
|
||||
const simplify = this.getNodeParameter('options.simplify', i, false) as boolean;
|
||||
|
||||
if (simplify) {
|
||||
response = response.map(simplifyResponse);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { SEND_AND_WAIT_OPERATION, type INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as deleteMessage from './deleteMessage.operation';
|
||||
import * as get from './get.operation';
|
||||
import * as getAll from './getAll.operation';
|
||||
import * as react from './react.operation';
|
||||
import * as send from './send.operation';
|
||||
import * as sendAndWait from './sendAndWait.operation';
|
||||
import { guildRLC } from '../common.description';
|
||||
|
||||
export { getAll, react, send, deleteMessage, get, sendAndWait };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteMessage',
|
||||
description: 'Delete a message in a channel',
|
||||
action: 'Delete a message',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a message in a channel',
|
||||
action: 'Get a message',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve the latest messages in a channel',
|
||||
action: 'Get many messages',
|
||||
},
|
||||
{
|
||||
name: 'React with Emoji',
|
||||
value: 'react',
|
||||
description: 'React to a message with an emoji',
|
||||
action: 'React with an emoji to a message',
|
||||
},
|
||||
{
|
||||
name: 'Send',
|
||||
value: 'send',
|
||||
description: 'Send a message to a channel, thread, or member',
|
||||
action: 'Send a message',
|
||||
},
|
||||
{
|
||||
name: 'Send and Wait for Response',
|
||||
value: SEND_AND_WAIT_OPERATION,
|
||||
description: 'Send a message and wait for response',
|
||||
action: 'Send message and wait for response',
|
||||
},
|
||||
],
|
||||
default: 'send',
|
||||
},
|
||||
{
|
||||
...guildRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...getAll.description,
|
||||
...react.description,
|
||||
...send.description,
|
||||
...deleteMessage.description,
|
||||
...get.description,
|
||||
...sendAndWait.description,
|
||||
];
|
||||
@@ -0,0 +1,80 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import { parseDiscordError, prepareErrorData, setupChannelGetter } from '../../helpers/utils';
|
||||
import { discordApiRequest } from '../../transport';
|
||||
import { channelRLC, messageIdString } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
channelRLC,
|
||||
messageIdString,
|
||||
{
|
||||
displayName: 'Emoji',
|
||||
name: 'emoji',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The emoji you want to react with',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
operation: ['react'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
_guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
const getChannelId = await setupChannelGetter.call(this, userGuilds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const channelId = await getChannelId(i);
|
||||
|
||||
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||
const emoji = this.getNodeParameter('emoji', i) as string;
|
||||
|
||||
await discordApiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me`,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import {
|
||||
parseDiscordError,
|
||||
prepareEmbeds,
|
||||
prepareErrorData,
|
||||
prepareOptions,
|
||||
sendDiscordMessage,
|
||||
} from '../../helpers/utils';
|
||||
import {
|
||||
embedsFixedCollection,
|
||||
filesFixedCollection,
|
||||
sendToProperties,
|
||||
} from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
...sendToProperties,
|
||||
{
|
||||
displayName: 'Message',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The content of the message (up to 2000 characters)',
|
||||
placeholder: 'e.g. My message',
|
||||
typeOptions: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Flags',
|
||||
name: 'flags',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
description:
|
||||
'Message flags. <a href="https://discord.com/developers/docs/resources/channel#message-object-message-flags" target="_blank">More info</a>.”.',
|
||||
options: [
|
||||
{
|
||||
name: 'Suppress Embeds',
|
||||
value: 'SUPPRESS_EMBEDS',
|
||||
},
|
||||
{
|
||||
name: 'Suppress Notifications',
|
||||
value: 'SUPPRESS_NOTIFICATIONS',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
displayName: 'Message to Reply to',
|
||||
name: 'message_reference',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Fill this to make your message a reply. Add the message ID.',
|
||||
placeholder: 'e.g. 1059467601836773386',
|
||||
},
|
||||
{
|
||||
displayName: 'Text-to-Speech (TTS)',
|
||||
name: 'tts',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to have a bot reading the message directly in the channel',
|
||||
},
|
||||
],
|
||||
},
|
||||
embedsFixedCollection,
|
||||
filesFixedCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['message'],
|
||||
operation: ['send'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
const isOAuth2 = this.getNodeParameter('authentication', 0) === 'oAuth2';
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const content = this.getNodeParameter('content', i) as string;
|
||||
const options = prepareOptions(this.getNodeParameter('options', i, {}), guildId);
|
||||
|
||||
const embeds = (this.getNodeParameter('embeds', i, undefined) as IDataObject)
|
||||
?.values as IDataObject[];
|
||||
const files = (this.getNodeParameter('files', i, undefined) as IDataObject)
|
||||
?.values as IDataObject[];
|
||||
|
||||
const body: IDataObject = {
|
||||
content,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (embeds) {
|
||||
body.embeds = prepareEmbeds.call(this, embeds);
|
||||
}
|
||||
|
||||
try {
|
||||
returnData.push(
|
||||
...(await sendDiscordMessage.call(this, {
|
||||
guildId,
|
||||
userGuilds,
|
||||
isOAuth2,
|
||||
body,
|
||||
files,
|
||||
itemIndex: i,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { getSendAndWaitProperties } from '../../../../../utils/sendAndWait/utils';
|
||||
import {
|
||||
createSendAndWaitMessageBody,
|
||||
parseDiscordError,
|
||||
prepareErrorData,
|
||||
sendDiscordMessage,
|
||||
} from '../../helpers/utils';
|
||||
import { sendToProperties } from '../common.description';
|
||||
|
||||
export const description: INodeProperties[] = getSendAndWaitProperties(
|
||||
sendToProperties,
|
||||
'message',
|
||||
undefined,
|
||||
{
|
||||
noButtonStyle: true,
|
||||
defaultApproveLabel: '✓ Approve',
|
||||
defaultDisapproveLabel: '✗ Decline',
|
||||
},
|
||||
).filter((p) => p.name !== 'subject');
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
guildId: string,
|
||||
userGuilds: IDataObject[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const items = this.getInputData();
|
||||
|
||||
const isOAuth2 = this.getNodeParameter('authentication', 0) === 'oAuth2';
|
||||
|
||||
try {
|
||||
await sendDiscordMessage.call(this, {
|
||||
guildId,
|
||||
userGuilds,
|
||||
isOAuth2,
|
||||
body: createSendAndWaitMessageBody(this),
|
||||
});
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, 0);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
return prepareErrorData.call(this, err, 0);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { AllEntities } from 'n8n-workflow';
|
||||
|
||||
type NodeMap = {
|
||||
channel: 'get' | 'getAll' | 'create' | 'update' | 'deleteChannel';
|
||||
message: 'deleteMessage' | 'getAll' | 'get' | 'react' | 'send' | 'sendAndWait';
|
||||
member: 'getAll' | 'roleAdd' | 'roleRemove';
|
||||
webhook: 'sendLegacy';
|
||||
};
|
||||
|
||||
export type Discord = AllEntities<NodeMap>;
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import { NodeOperationError, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
|
||||
import * as channel from './channel';
|
||||
import * as member from './member';
|
||||
import * as message from './message';
|
||||
import type { Discord } from './node.type';
|
||||
import * as webhook from './webhook';
|
||||
import { configureWaitTillDate } from '../../../../utils/sendAndWait/configureWaitTillDate.util';
|
||||
import { checkAccessToGuild } from '../helpers/utils';
|
||||
import { discordApiRequest } from '../transport';
|
||||
|
||||
export async function router(this: IExecuteFunctions) {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
let resource = 'webhook';
|
||||
//resource parameter is hidden when authentication is set to webhook
|
||||
//prevent error when getting resource parameter
|
||||
try {
|
||||
resource = this.getNodeParameter<Discord>('resource', 0);
|
||||
} catch (error) {}
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
let guildId = '';
|
||||
let userGuilds: IDataObject[] = [];
|
||||
|
||||
if (resource !== 'webhook') {
|
||||
guildId = this.getNodeParameter('guildId', 0, '', {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const isOAuth2 = this.getNodeParameter('authentication', 0, '') === 'oAuth2';
|
||||
|
||||
if (isOAuth2) {
|
||||
userGuilds = (await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/users/@me/guilds',
|
||||
)) as IDataObject[];
|
||||
|
||||
checkAccessToGuild(this.getNode(), guildId, userGuilds);
|
||||
}
|
||||
}
|
||||
|
||||
const discord = {
|
||||
resource,
|
||||
operation,
|
||||
} as Discord;
|
||||
|
||||
if (discord.resource === 'message' && discord.operation === SEND_AND_WAIT_OPERATION) {
|
||||
returnData = await message[discord.operation].execute.call(this, guildId, userGuilds);
|
||||
|
||||
const waitTill = configureWaitTillDate(this);
|
||||
|
||||
await this.putExecutionToWait(waitTill);
|
||||
return [returnData];
|
||||
}
|
||||
|
||||
switch (discord.resource) {
|
||||
case 'channel':
|
||||
returnData = await channel[discord.operation].execute.call(this, guildId, userGuilds);
|
||||
break;
|
||||
case 'message':
|
||||
returnData = await message[discord.operation].execute.call(this, guildId, userGuilds);
|
||||
break;
|
||||
case 'member':
|
||||
returnData = await member[discord.operation].execute.call(this, guildId);
|
||||
break;
|
||||
case 'webhook':
|
||||
returnData = await webhook[discord.operation].execute.call(this);
|
||||
break;
|
||||
default:
|
||||
throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known`);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as channel from './channel';
|
||||
import * as member from './member';
|
||||
import * as message from './message';
|
||||
import * as webhook from './webhook';
|
||||
import { sendAndWaitWebhooksDescription } from '../../../../utils/sendAndWait/descriptions';
|
||||
import { SEND_AND_WAIT_WAITING_TOOLTIP } from '../../../../utils/sendAndWait/utils';
|
||||
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Discord',
|
||||
name: 'discord',
|
||||
icon: 'file:discord.svg',
|
||||
group: ['output'],
|
||||
version: 2,
|
||||
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
|
||||
description: 'Sends data to Discord',
|
||||
defaults: {
|
||||
name: 'Discord',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
waitingNodeTooltip: SEND_AND_WAIT_WAITING_TOOLTIP,
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
credentials: [
|
||||
{
|
||||
name: 'discordBotApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['botToken'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'discordOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'discordWebhookApi',
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Connection Type',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Bot Token',
|
||||
value: 'botToken',
|
||||
description: 'Manage messages, channels, and members on a server',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
description: "Same features as 'Bot Token' with easier Bot installation",
|
||||
},
|
||||
{
|
||||
name: 'Webhook',
|
||||
value: 'webhook',
|
||||
description: 'Send messages to a specific channel',
|
||||
},
|
||||
],
|
||||
default: 'botToken',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Channel',
|
||||
value: 'channel',
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
value: 'message',
|
||||
},
|
||||
{
|
||||
name: 'Member',
|
||||
value: 'member',
|
||||
},
|
||||
],
|
||||
default: 'channel',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
...message.description,
|
||||
...channel.description,
|
||||
...member.description,
|
||||
...webhook.description,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as sendLegacy from './sendLegacy.operation';
|
||||
|
||||
export { sendLegacy };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['webhook'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Send a Message',
|
||||
value: 'sendLegacy',
|
||||
description: 'Send a message to a channel using the webhook',
|
||||
action: 'Send a message',
|
||||
},
|
||||
],
|
||||
default: 'sendLegacy',
|
||||
},
|
||||
...sendLegacy.description,
|
||||
];
|
||||
@@ -0,0 +1,165 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '../../../../../utils/utilities';
|
||||
import {
|
||||
parseDiscordError,
|
||||
prepareEmbeds,
|
||||
prepareErrorData,
|
||||
prepareMultiPartForm,
|
||||
prepareOptions,
|
||||
} from '../../helpers/utils';
|
||||
import { discordApiMultiPartRequest, discordApiRequest } from '../../transport';
|
||||
import { embedsFixedCollection, filesFixedCollection } from '../common.description';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Message',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The content of the message (up to 2000 characters)',
|
||||
placeholder: 'e.g. My message',
|
||||
typeOptions: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Avatar URL',
|
||||
name: 'avatar_url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Override the default avatar of the webhook',
|
||||
placeholder: 'e.g. https://example.com/image.png',
|
||||
},
|
||||
{
|
||||
displayName: 'Flags',
|
||||
name: 'flags',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
description:
|
||||
'Message flags. <a href="https://discord.com/developers/docs/resources/channel#message-object-message-flags" target="_blank">More info</a>.”.',
|
||||
options: [
|
||||
{
|
||||
name: 'Suppress Embeds',
|
||||
value: 'SUPPRESS_EMBEDS',
|
||||
},
|
||||
{
|
||||
name: 'Suppress Notifications',
|
||||
value: 'SUPPRESS_NOTIFICATIONS',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Text-to-Speech (TTS)',
|
||||
name: 'tts',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to have a bot reading the message directly in the channel',
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Override the default username of the webhook',
|
||||
placeholder: 'e.g. My Username',
|
||||
},
|
||||
{
|
||||
displayName: 'Wait',
|
||||
name: 'wait',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether wait for the message to be created before returning its response',
|
||||
},
|
||||
],
|
||||
},
|
||||
embedsFixedCollection,
|
||||
filesFixedCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['sendLegacy'],
|
||||
},
|
||||
hide: {
|
||||
authentication: ['botToken', 'oAuth2'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const content = this.getNodeParameter('content', i) as string;
|
||||
const options = prepareOptions(this.getNodeParameter('options', i, {}));
|
||||
|
||||
const embeds = (this.getNodeParameter('embeds', i, undefined) as IDataObject)
|
||||
?.values as IDataObject[];
|
||||
const files = (this.getNodeParameter('files', i, undefined) as IDataObject)
|
||||
?.values as IDataObject[];
|
||||
|
||||
let qs: IDataObject | undefined = undefined;
|
||||
|
||||
if (options.wait) {
|
||||
qs = {
|
||||
wait: options.wait,
|
||||
};
|
||||
|
||||
delete options.wait;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
content,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (embeds) {
|
||||
body.embeds = prepareEmbeds.call(this, embeds);
|
||||
}
|
||||
|
||||
try {
|
||||
let response: IDataObject[] = [];
|
||||
|
||||
if (files?.length) {
|
||||
const multiPartBody = await prepareMultiPartForm.call(this, files, body, i);
|
||||
|
||||
response = await discordApiMultiPartRequest.call(this, 'POST', '', multiPartBody);
|
||||
} else {
|
||||
response = await discordApiRequest.call(this, 'POST', '', body, qs);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
const err = parseDiscordError.call(this, error, i);
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push(...prepareErrorData.call(this, err, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
Reference in New Issue
Block a user