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,22 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.discord",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication", "HITL"],
|
||||
"subcategories": {
|
||||
"HITL": ["Human in the Loop"]
|
||||
},
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/discord/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.discord/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": ["human", "form", "wait", "hitl", "approval"]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
|
||||
import { VersionedNodeType } from 'n8n-workflow';
|
||||
|
||||
import { DiscordV1 } from './v1/DiscordV1.node';
|
||||
import { DiscordV2 } from './v2/DiscordV2.node';
|
||||
|
||||
export class Discord extends VersionedNodeType {
|
||||
constructor() {
|
||||
const baseDescription: INodeTypeBaseDescription = {
|
||||
displayName: 'Discord',
|
||||
name: 'discord',
|
||||
icon: 'file:discord.svg',
|
||||
group: ['output'],
|
||||
defaultVersion: 2,
|
||||
description: 'Sends data to Discord',
|
||||
};
|
||||
|
||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||
1: new DiscordV1(baseDescription),
|
||||
2: new DiscordV2(baseDescription),
|
||||
};
|
||||
|
||||
super(nodeVersions, baseDescription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"guild_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_message_id": {
|
||||
"type": "null"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsfw": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"permission_overwrites": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"type": "string"
|
||||
},
|
||||
"deny": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"type": "integer"
|
||||
},
|
||||
"rate_limit_per_user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"guild_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon_emoji": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "null"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsfw": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"permission_overwrites": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"type": "string"
|
||||
},
|
||||
"deny": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"type": "integer"
|
||||
},
|
||||
"rate_limit_per_user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"theme_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"topic": {
|
||||
"type": "null"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"guild_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsfw": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"permission_overwrites": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"type": "string"
|
||||
},
|
||||
"deny": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"type": "integer"
|
||||
},
|
||||
"rate_limit_per_user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"guild_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon_emoji": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "null"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsfw": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"permission_overwrites": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"type": "string"
|
||||
},
|
||||
"deny": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"type": "integer"
|
||||
},
|
||||
"rate_limit_per_user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"theme_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"communication_disabled_until": {
|
||||
"type": "null"
|
||||
},
|
||||
"deaf": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"joined_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"mute": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pending": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accent_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"clan": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content_scan_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"proxy_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accent_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"collectibles": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"channel_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"custom_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"style": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"embeds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "integer"
|
||||
},
|
||||
"content_scan_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"proxy_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mention_everyone": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mention_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"mentions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accent_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"avatar_decoration_data": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"clan": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"primary_guild": {
|
||||
"type": "null"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pinned": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
},
|
||||
"tts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"proxy_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accent_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"clan": {
|
||||
"type": "null"
|
||||
},
|
||||
"collectibles": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"primary_guild": {
|
||||
"type": "null"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"channel_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"style": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"embeds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"proxy_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"video": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mention_everyone": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mention_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"mentions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accent_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"clan": {
|
||||
"type": "null"
|
||||
},
|
||||
"collectibles": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"primary_guild": {
|
||||
"type": "null"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pinned": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
},
|
||||
"tts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content_scan_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"proxy_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accent_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"avatar_decoration_data": {
|
||||
"type": "null"
|
||||
},
|
||||
"banner_color": {
|
||||
"type": "null"
|
||||
},
|
||||
"bot": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"clan": {
|
||||
"type": "null"
|
||||
},
|
||||
"collectibles": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"global_name": {
|
||||
"type": "null"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"primary_guild": {
|
||||
"type": "null"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"channel_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"edited_timestamp": {
|
||||
"type": "null"
|
||||
},
|
||||
"embeds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "integer"
|
||||
},
|
||||
"content_scan_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mention_everyone": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mentions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_decoration_data": {
|
||||
"type": "null"
|
||||
},
|
||||
"clan": {
|
||||
"type": "null"
|
||||
},
|
||||
"collectibles": {
|
||||
"type": "null"
|
||||
},
|
||||
"discriminator": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"primary_guild": {
|
||||
"type": "null"
|
||||
},
|
||||
"public_flags": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pinned": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
},
|
||||
"tts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"approved": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="199" preserveAspectRatio="xMidYMid"><path fill="#5865F2" d="M216.856 16.597A208.5 208.5 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046q-29.538-4.442-58.533 0c-1.832-4.4-4.55-9.933-6.846-14.046a207.8 207.8 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161 161 0 0 0 79.735 175.3a136.4 136.4 0 0 1-21.846-10.632 109 109 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a132 132 0 0 0 5.355 4.237 136 136 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36M85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2 23.236 11.804 23.015 26.2c.02 14.375-10.148 26.18-23.015 26.18m85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18"/></svg>
|
||||
|
After Width: | Height: | Size: 951 B |
@@ -0,0 +1,25 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, channel => create', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.post('/guilds/1168516062791340136/channels', { name: 'third', type: '0' })
|
||||
.reply(200, {
|
||||
id: '1168528323006181417',
|
||||
type: 0,
|
||||
last_message_id: null,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'third',
|
||||
parent_id: null,
|
||||
rate_limit_per_user: 0,
|
||||
topic: null,
|
||||
position: 3,
|
||||
permission_overwrites: [],
|
||||
nsfw: false,
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['create.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"name": "third",
|
||||
"options": {}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168528323006181417",
|
||||
"type": 0,
|
||||
"last_message_id": null,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "third",
|
||||
"parent_id": null,
|
||||
"rate_limit_per_user": 0,
|
||||
"topic": null,
|
||||
"position": 3,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "df4fbb47-eb25-4564-b9ad-f16931e35665",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, channel => deleteChannel', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.delete('/channels/1168528323006181417')
|
||||
.reply(200, { success: true });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['deleteChannel.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "deleteChannel",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168528323006181417",
|
||||
"mode": "list",
|
||||
"cachedResultName": "third",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168528323006181417"
|
||||
}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "f0027d0b-87f7-4a39-bc9c-2838078eed60",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, channel => get', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.persist()
|
||||
.get('/users/@me/guilds')
|
||||
.reply(200, [{ id: '1168516062791340136' }])
|
||||
.get('/channels/1168516240332034067')
|
||||
.reply(200, {
|
||||
id: '1168516240332034067',
|
||||
type: 0,
|
||||
last_message_id: null,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'first',
|
||||
parent_id: '1168516063340789831',
|
||||
rate_limit_per_user: 0,
|
||||
topic: null,
|
||||
position: 1,
|
||||
permission_overwrites: [],
|
||||
nsfw: false,
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['get.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"name": "discord overhaul copy",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "fe1dd916-f466-40c7-9dfa-dfec59219a86",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-560,
|
||||
780
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"operation": "get",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
}
|
||||
},
|
||||
"id": "09cccc50-10d2-49a1-9b9a-9ba1a11a3657",
|
||||
"name": "OAuth test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-320,
|
||||
780
|
||||
],
|
||||
"credentials": {
|
||||
"discordOAuth2Api": {
|
||||
"id": "79",
|
||||
"name": "Discord account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "7f367512-810f-4d5d-9020-0f01a47039f7",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-80,
|
||||
780
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516240332034067",
|
||||
"type": 0,
|
||||
"last_message_id": null,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "first",
|
||||
"parent_id": "1168516063340789831",
|
||||
"rate_limit_per_user": 0,
|
||||
"topic": null,
|
||||
"position": 1,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "OAuth test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"OAuth test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "0089557d-57da-45ea-abd1-c7b57691e10a",
|
||||
"id": "m3OrE6gaFHxa5InI",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, channel => getAll', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.get('/guilds/1168516062791340136/channels')
|
||||
.reply(200, [
|
||||
{
|
||||
id: '1168516063340789831',
|
||||
type: 4,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'Text Channels',
|
||||
parent_id: null,
|
||||
position: 0,
|
||||
permission_overwrites: [],
|
||||
},
|
||||
{
|
||||
id: '1168516063340789832',
|
||||
type: 4,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'Voice Channels',
|
||||
parent_id: null,
|
||||
position: 0,
|
||||
permission_overwrites: [],
|
||||
},
|
||||
{
|
||||
id: '1168516063340789833',
|
||||
type: 0,
|
||||
last_message_id: '1168518371239792720',
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'general',
|
||||
parent_id: '1168516063340789831',
|
||||
rate_limit_per_user: 0,
|
||||
topic: null,
|
||||
position: 0,
|
||||
permission_overwrites: [],
|
||||
nsfw: false,
|
||||
icon_emoji: {
|
||||
id: null,
|
||||
name: '👋',
|
||||
},
|
||||
theme_color: null,
|
||||
},
|
||||
{
|
||||
id: '1168516063340789834',
|
||||
type: 2,
|
||||
last_message_id: null,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'General',
|
||||
parent_id: '1168516063340789832',
|
||||
rate_limit_per_user: 0,
|
||||
bitrate: 64000,
|
||||
user_limit: 0,
|
||||
rtc_region: null,
|
||||
position: 0,
|
||||
permission_overwrites: [],
|
||||
nsfw: false,
|
||||
icon_emoji: {
|
||||
id: null,
|
||||
name: '🎙️',
|
||||
},
|
||||
theme_color: null,
|
||||
},
|
||||
{
|
||||
id: '1168516240332034067',
|
||||
type: 0,
|
||||
last_message_id: null,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'first-channel',
|
||||
parent_id: '1168516063340789831',
|
||||
rate_limit_per_user: 30,
|
||||
topic: 'This is channel topic',
|
||||
position: 3,
|
||||
permission_overwrites: [],
|
||||
nsfw: true,
|
||||
},
|
||||
{
|
||||
id: '1168516269079793766',
|
||||
type: 0,
|
||||
last_message_id: null,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'second',
|
||||
parent_id: '1168516063340789831',
|
||||
rate_limit_per_user: 0,
|
||||
topic: null,
|
||||
position: 2,
|
||||
permission_overwrites: [],
|
||||
nsfw: false,
|
||||
},
|
||||
]);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,191 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"returnAll": true,
|
||||
"options": {}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516063340789831",
|
||||
"type": 4,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "Text Channels",
|
||||
"parent_id": null,
|
||||
"position": 0,
|
||||
"permission_overwrites": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516063340789832",
|
||||
"type": 4,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "Voice Channels",
|
||||
"parent_id": null,
|
||||
"position": 0,
|
||||
"permission_overwrites": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516063340789833",
|
||||
"type": 0,
|
||||
"last_message_id": "1168518371239792720",
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "general",
|
||||
"parent_id": "1168516063340789831",
|
||||
"rate_limit_per_user": 0,
|
||||
"topic": null,
|
||||
"position": 0,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": false,
|
||||
"icon_emoji": {
|
||||
"id": null,
|
||||
"name": "👋"
|
||||
},
|
||||
"theme_color": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516063340789834",
|
||||
"type": 2,
|
||||
"last_message_id": null,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "General",
|
||||
"parent_id": "1168516063340789832",
|
||||
"rate_limit_per_user": 0,
|
||||
"bitrate": 64000,
|
||||
"user_limit": 0,
|
||||
"rtc_region": null,
|
||||
"position": 0,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": false,
|
||||
"icon_emoji": {
|
||||
"id": null,
|
||||
"name": "🎙️"
|
||||
},
|
||||
"theme_color": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516240332034067",
|
||||
"type": 0,
|
||||
"last_message_id": null,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "first-channel",
|
||||
"parent_id": "1168516063340789831",
|
||||
"rate_limit_per_user": 30,
|
||||
"topic": "This is channel topic",
|
||||
"position": 3,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516269079793766",
|
||||
"type": 0,
|
||||
"last_message_id": null,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "second",
|
||||
"parent_id": "1168516063340789831",
|
||||
"rate_limit_per_user": 0,
|
||||
"topic": null,
|
||||
"position": 2,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "905c7383-202e-4391-97a5-e2c579421c17",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, channel => update', () => {
|
||||
nock('https://discord.com/api/v10').patch('/channels/1168516240332034067').reply(200, {
|
||||
id: '1168516240332034067',
|
||||
type: 0,
|
||||
last_message_id: null,
|
||||
flags: 0,
|
||||
guild_id: '1168516062791340136',
|
||||
name: 'first-channel',
|
||||
parent_id: '1168516063340789831',
|
||||
rate_limit_per_user: 30,
|
||||
topic: 'This is channel topic',
|
||||
position: 3,
|
||||
permission_overwrites: [],
|
||||
nsfw: true,
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['update.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "update",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
},
|
||||
"name": "First Channel",
|
||||
"options": {
|
||||
"nsfw": true,
|
||||
"categoryId": {
|
||||
"__rl": true,
|
||||
"value": "1168516063340789831",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Text Channels",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516063340789831"
|
||||
},
|
||||
"position": 3,
|
||||
"rate_limit_per_user": 30,
|
||||
"topic": "This is channel topic"
|
||||
}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168516240332034067",
|
||||
"type": 0,
|
||||
"last_message_id": null,
|
||||
"flags": 0,
|
||||
"guild_id": "1168516062791340136",
|
||||
"name": "first-channel",
|
||||
"parent_id": "1168516063340789831",
|
||||
"rate_limit_per_user": 30,
|
||||
"topic": "This is channel topic",
|
||||
"position": 3,
|
||||
"permission_overwrites": [],
|
||||
"nsfw": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "fc822909-732e-444f-9537-54b7a85a7bd7",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, member => getAll', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.get('/guilds/1168516062791340136/members?limit=2')
|
||||
.reply(200, [
|
||||
{
|
||||
user: {
|
||||
id: '470936827994570762',
|
||||
username: 'michael',
|
||||
avatar: null,
|
||||
discriminator: '0',
|
||||
public_flags: 0,
|
||||
premium_type: 0,
|
||||
flags: 0,
|
||||
banner: null,
|
||||
accent_color: null,
|
||||
global_name: 'Michael',
|
||||
avatar_decoration_data: null,
|
||||
banner_color: null,
|
||||
},
|
||||
roles: [],
|
||||
},
|
||||
{
|
||||
user: {
|
||||
id: '1070667629972430879',
|
||||
username: 'n8n-node-overhaul',
|
||||
avatar: null,
|
||||
discriminator: '1037',
|
||||
public_flags: 0,
|
||||
premium_type: 0,
|
||||
flags: 0,
|
||||
bot: true,
|
||||
banner: null,
|
||||
accent_color: null,
|
||||
global_name: null,
|
||||
avatar_decoration_data: null,
|
||||
banner_color: null,
|
||||
},
|
||||
roles: ['1168518368526077992'],
|
||||
},
|
||||
]);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "member",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"limit": 2,
|
||||
"options": {
|
||||
"simplify": true
|
||||
}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"user": {
|
||||
"id": "470936827994570762",
|
||||
"username": "michael",
|
||||
"avatar": null,
|
||||
"discriminator": "0",
|
||||
"public_flags": 0,
|
||||
"premium_type": 0,
|
||||
"flags": 0,
|
||||
"banner": null,
|
||||
"accent_color": null,
|
||||
"global_name": "Michael",
|
||||
"avatar_decoration_data": null,
|
||||
"banner_color": null
|
||||
},
|
||||
"roles": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"user": {
|
||||
"id": "1070667629972430879",
|
||||
"username": "n8n-node-overhaul",
|
||||
"avatar": null,
|
||||
"discriminator": "1037",
|
||||
"public_flags": 0,
|
||||
"premium_type": 0,
|
||||
"flags": 0,
|
||||
"bot": true,
|
||||
"banner": null,
|
||||
"accent_color": null,
|
||||
"global_name": null,
|
||||
"avatar_decoration_data": null,
|
||||
"banner_color": null
|
||||
},
|
||||
"roles": [
|
||||
"1168518368526077992"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "61375773-2f25-4eae-9ef6-e64e69fc9714",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, member => roleAdd', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.put('/guilds/1168516062791340136/members/470936827994570762/roles/1168772374540320890')
|
||||
.reply(200, { success: true });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['roleAdd.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "member",
|
||||
"operation": "roleAdd",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"userId": {
|
||||
"__rl": true,
|
||||
"value": "470936827994570762",
|
||||
"mode": "list",
|
||||
"cachedResultName": "michael"
|
||||
},
|
||||
"role": [
|
||||
"1168772374540320890"
|
||||
]
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "ad26d0d9-faf3-4070-8909-8c2b6f0749f9",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, member => roleRemove', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.persist()
|
||||
.delete(/\/guilds\/1168516062791340136\/members\/470936827994570762\/roles\/\d+/)
|
||||
.reply(200, { success: true });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['roleRemove.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "member",
|
||||
"operation": "roleRemove",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"userId": {
|
||||
"__rl": true,
|
||||
"value": "470936827994570762",
|
||||
"mode": "list",
|
||||
"cachedResultName": "michael"
|
||||
},
|
||||
"role": [
|
||||
"1168773588963299428",
|
||||
"1168773645800308756",
|
||||
"1168772374540320890"
|
||||
]
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "e3780e00-5acb-4c2f-8c4f-85a9fb6698c9",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, message => deleteMessage', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.delete('/channels/1168516240332034067/messages/1168776343194972210')
|
||||
.reply(200, { success: true });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['deleteMessage.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "deleteMessage",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first-channel",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
},
|
||||
"messageId": "1168776343194972210"
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "15dbf56e-707a-4b5c-814c-ecf78d96d87f",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, message => get', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.get('/channels/1168516240332034067/messages/1168777380144369718')
|
||||
.reply(200, {
|
||||
id: '1168777380144369718',
|
||||
channel_id: '1168516240332034067',
|
||||
author: {
|
||||
id: '1070667629972430879',
|
||||
username: 'n8n-node-overhaul',
|
||||
avatar: null,
|
||||
discriminator: '1037',
|
||||
public_flags: 0,
|
||||
premium_type: 0,
|
||||
flags: 0,
|
||||
bot: true,
|
||||
banner: null,
|
||||
accent_color: null,
|
||||
global_name: null,
|
||||
avatar_decoration_data: null,
|
||||
banner_color: null,
|
||||
},
|
||||
content: 'msg 3',
|
||||
timestamp: '2023-10-31T05:04:02.260000+00:00',
|
||||
type: 0,
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['get.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "get",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first-channel",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
},
|
||||
"messageId": "1168777380144369718",
|
||||
"options": {
|
||||
"simplify": true
|
||||
}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168777380144369718",
|
||||
"channel_id": "1168516240332034067",
|
||||
"author": {
|
||||
"id": "1070667629972430879",
|
||||
"username": "n8n-node-overhaul",
|
||||
"avatar": null,
|
||||
"discriminator": "1037",
|
||||
"public_flags": 0,
|
||||
"premium_type": 0,
|
||||
"flags": 0,
|
||||
"bot": true,
|
||||
"banner": null,
|
||||
"accent_color": null,
|
||||
"global_name": null,
|
||||
"avatar_decoration_data": null,
|
||||
"banner_color": null
|
||||
},
|
||||
"content": "msg 3",
|
||||
"timestamp": "2023-10-31T05:04:02.260000+00:00",
|
||||
"type": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "876e52a8-2fb3-4efc-9ef0-123807be3806",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, message => getAll', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.get('/channels/1168516240332034067/messages?limit=1')
|
||||
.reply(200, [
|
||||
{
|
||||
id: '1168784010269433998',
|
||||
type: 0,
|
||||
content: 'msg 4',
|
||||
channel_id: '1168516240332034067',
|
||||
author: {
|
||||
id: '1070667629972430879',
|
||||
username: 'n8n-node-overhaul',
|
||||
avatar: null,
|
||||
discriminator: '1037',
|
||||
public_flags: 0,
|
||||
premium_type: 0,
|
||||
flags: 0,
|
||||
bot: true,
|
||||
banner: null,
|
||||
accent_color: null,
|
||||
global_name: null,
|
||||
avatar_decoration_data: null,
|
||||
banner_color: null,
|
||||
},
|
||||
attachments: [],
|
||||
embeds: [
|
||||
{
|
||||
type: 'rich',
|
||||
title: 'Some Title',
|
||||
description: 'description',
|
||||
color: 2112935,
|
||||
timestamp: '2023-10-30T22:00:00+00:00',
|
||||
author: {
|
||||
name: 'Me',
|
||||
},
|
||||
},
|
||||
],
|
||||
mentions: [],
|
||||
mention_roles: [],
|
||||
pinned: false,
|
||||
mention_everyone: false,
|
||||
tts: false,
|
||||
timestamp: '2023-10-31T05:30:23.005000+00:00',
|
||||
edited_timestamp: null,
|
||||
flags: 0,
|
||||
components: [],
|
||||
},
|
||||
]);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "getAll",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first-channel",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
},
|
||||
"limit": 1,
|
||||
"options": {}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168784010269433998",
|
||||
"type": 0,
|
||||
"content": "msg 4",
|
||||
"channel_id": "1168516240332034067",
|
||||
"author": {
|
||||
"id": "1070667629972430879",
|
||||
"username": "n8n-node-overhaul",
|
||||
"avatar": null,
|
||||
"discriminator": "1037",
|
||||
"public_flags": 0,
|
||||
"premium_type": 0,
|
||||
"flags": 0,
|
||||
"bot": true,
|
||||
"banner": null,
|
||||
"accent_color": null,
|
||||
"global_name": null,
|
||||
"avatar_decoration_data": null,
|
||||
"banner_color": null
|
||||
},
|
||||
"attachments": [],
|
||||
"embeds": [
|
||||
{
|
||||
"type": "rich",
|
||||
"title": "Some Title",
|
||||
"description": "description",
|
||||
"color": 2112935,
|
||||
"timestamp": "2023-10-30T22:00:00+00:00",
|
||||
"author": {
|
||||
"name": "Me"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"pinned": false,
|
||||
"mention_everyone": false,
|
||||
"tts": false,
|
||||
"timestamp": "2023-10-31T05:30:23.005000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"flags": 0,
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "fd45b651-cad2-4985-bcee-9c87efeb9af5",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, message => react', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.put('/channels/1168516240332034067/messages/1168777380144369718/reactions/%F0%9F%98%80/@me')
|
||||
.reply(200, { success: true });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['react.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "react",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first-channel",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
},
|
||||
"messageId": "1168777380144369718",
|
||||
"emoji": "😀"
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "cf2de495-fe80-4a98-9d06-c251ea1661ad",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, message => send', () => {
|
||||
nock('https://discord.com/api/v10')
|
||||
.post('/channels/1168516240332034067/messages', {
|
||||
content: 'msg 4',
|
||||
embeds: [
|
||||
{
|
||||
author: { name: 'Me' },
|
||||
color: 2112935,
|
||||
description: 'description',
|
||||
timestamp: '2023-10-30T22:00:00.000Z',
|
||||
title: 'Some Title',
|
||||
},
|
||||
],
|
||||
})
|
||||
.reply(200, {
|
||||
id: '1168784010269433998',
|
||||
type: 0,
|
||||
content: 'msg 4',
|
||||
channel_id: '1168516240332034067',
|
||||
author: {
|
||||
id: '1070667629972430879',
|
||||
username: 'n8n-node-overhaul',
|
||||
avatar: null,
|
||||
discriminator: '1037',
|
||||
public_flags: 0,
|
||||
premium_type: 0,
|
||||
flags: 0,
|
||||
bot: true,
|
||||
banner: null,
|
||||
accent_color: null,
|
||||
global_name: null,
|
||||
avatar_decoration_data: null,
|
||||
banner_color: null,
|
||||
},
|
||||
attachments: [],
|
||||
embeds: [
|
||||
{
|
||||
type: 'rich',
|
||||
title: 'Some Title',
|
||||
description: 'description',
|
||||
color: 2112935,
|
||||
timestamp: '2023-10-30T22:00:00+00:00',
|
||||
author: {
|
||||
name: 'Me',
|
||||
},
|
||||
},
|
||||
],
|
||||
mentions: [],
|
||||
mention_roles: [],
|
||||
pinned: false,
|
||||
mention_everyone: false,
|
||||
tts: false,
|
||||
timestamp: '2023-10-31T05:30:23.005000+00:00',
|
||||
edited_timestamp: null,
|
||||
flags: 0,
|
||||
components: [],
|
||||
referenced_message: null,
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['send.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"name": "discord overhaul tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "254a9d9b-43bf-4f6e-a761-d78146a05838",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"guildId": {
|
||||
"__rl": true,
|
||||
"value": "1168516062791340136",
|
||||
"mode": "list",
|
||||
"cachedResultName": "TEST server",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136"
|
||||
},
|
||||
"channelId": {
|
||||
"__rl": true,
|
||||
"value": "1168516240332034067",
|
||||
"mode": "list",
|
||||
"cachedResultName": "first-channel",
|
||||
"cachedResultUrl": "https://discord.com/channels/1168516062791340136/1168516240332034067"
|
||||
},
|
||||
"content": "msg 4",
|
||||
"options": {},
|
||||
"embeds": {
|
||||
"values": [
|
||||
{
|
||||
"description": "description",
|
||||
"author": "Me",
|
||||
"color": "#203DA7",
|
||||
"timestamp": "2023-10-30T22:00:00.000Z",
|
||||
"title": "Some Title"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "7e638897-0581-42e6-8b89-494908e0ae75",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordBotApi": {
|
||||
"id": "KaIz8dqE3Vy1E3iL",
|
||||
"name": "Discord Bot account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "10450e91-8642-4b92-af15-9d5ad161b527",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168784010269433998",
|
||||
"type": 0,
|
||||
"content": "msg 4",
|
||||
"channel_id": "1168516240332034067",
|
||||
"author": {
|
||||
"id": "1070667629972430879",
|
||||
"username": "n8n-node-overhaul",
|
||||
"avatar": null,
|
||||
"discriminator": "1037",
|
||||
"public_flags": 0,
|
||||
"premium_type": 0,
|
||||
"flags": 0,
|
||||
"bot": true,
|
||||
"banner": null,
|
||||
"accent_color": null,
|
||||
"global_name": null,
|
||||
"avatar_decoration_data": null,
|
||||
"banner_color": null
|
||||
},
|
||||
"attachments": [],
|
||||
"embeds": [
|
||||
{
|
||||
"type": "rich",
|
||||
"title": "Some Title",
|
||||
"description": "description",
|
||||
"color": 2112935,
|
||||
"timestamp": "2023-10-30T22:00:00+00:00",
|
||||
"author": {
|
||||
"name": "Me"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"pinned": false,
|
||||
"mention_everyone": false,
|
||||
"tts": false,
|
||||
"timestamp": "2023-10-31T05:30:23.005000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"flags": 0,
|
||||
"components": [],
|
||||
"referenced_message": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "5b208250-62db-4b00-9e02-53392eb838a9",
|
||||
"id": "4DdFKgGmLX07cXvG",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { SEND_AND_WAIT_OPERATION, type IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { versionDescription } from '../../../../v2/actions/versionDescription';
|
||||
import { DiscordV2 } from '../../../../v2/DiscordV2.node';
|
||||
import * as transport from '../../../../v2/transport/discord.api';
|
||||
|
||||
jest.mock('../../../../v2/transport/discord.api', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport/discord.api');
|
||||
return {
|
||||
...originalModule,
|
||||
discordApiRequest: jest.fn(async function (method: string) {
|
||||
if (method === 'POST') {
|
||||
return {};
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test DiscordV2, message => sendAndWait', () => {
|
||||
let discord: DiscordV2;
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
discord = new DiscordV2(versionDescription);
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
mockExecuteFunctions.helpers = {
|
||||
constructExecutionMetaData: jest.fn(() => []),
|
||||
returnJsonArray: jest.fn(() => []),
|
||||
} as any;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should send message and put execution to wait', async () => {
|
||||
const items = [{ json: { data: 'test' } }];
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'operation') return SEND_AND_WAIT_OPERATION;
|
||||
if (key === 'resource') return 'message';
|
||||
if (key === 'authentication') return 'botToken';
|
||||
if (key === 'message') return 'my message';
|
||||
if (key === 'subject') return '';
|
||||
if (key === 'approvalOptions.values') return {};
|
||||
if (key === 'responseType') return 'approval';
|
||||
if (key === 'sendTo') return 'channel';
|
||||
if (key === 'channelId') return 'channelID';
|
||||
if (key === 'options.limitWaitTime.values') return {};
|
||||
});
|
||||
|
||||
mockExecuteFunctions.putExecutionToWait.mockImplementation();
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getInstanceId.mockReturnValue('instanceId');
|
||||
|
||||
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('http://localhost/waiting-webhook');
|
||||
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('nodeID');
|
||||
|
||||
mockExecuteFunctions.getSignedResumeUrl.mockReturnValue(
|
||||
'http://localhost/waiting-webhook/nodeID?approved=true&token=abc',
|
||||
);
|
||||
|
||||
const result = await discord.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual([items]);
|
||||
expect(transport.discordApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.putExecutionToWait).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(transport.discordApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/channels/channelID/messages',
|
||||
{
|
||||
components: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
label: 'Approve',
|
||||
style: 5,
|
||||
type: 2,
|
||||
url: 'http://localhost/waiting-webhook/nodeID?approved=true&token=abc',
|
||||
},
|
||||
],
|
||||
type: 1,
|
||||
},
|
||||
],
|
||||
embeds: [
|
||||
{
|
||||
color: 5814783,
|
||||
description:
|
||||
'my message\n\n_This message was sent automatically with _[n8n](https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.discord_instanceId)',
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test DiscordV2, webhook => sendLegacy', () => {
|
||||
const credentials = {
|
||||
discordWebhookApi: {
|
||||
webhookUri: 'https://discord.com/webhook',
|
||||
},
|
||||
};
|
||||
|
||||
nock(credentials.discordWebhookApi.webhookUri)
|
||||
.post('?wait=true')
|
||||
.reply(200, {
|
||||
id: '1168768986385747999',
|
||||
type: 0,
|
||||
content: 'TEST Message',
|
||||
channel_id: '1074646335082479626',
|
||||
author: {
|
||||
id: '1153265494955135077',
|
||||
username: 'TEST_USER',
|
||||
avatar: null,
|
||||
discriminator: '0000',
|
||||
public_flags: 0,
|
||||
flags: 0,
|
||||
bot: true,
|
||||
global_name: null,
|
||||
},
|
||||
attachments: [],
|
||||
embeds: [
|
||||
{
|
||||
type: 'rich',
|
||||
description: 'some description',
|
||||
color: 10930459,
|
||||
timestamp: '2023-10-17T21:00:00+00:00',
|
||||
author: {
|
||||
name: 'Michael',
|
||||
},
|
||||
},
|
||||
],
|
||||
mentions: [],
|
||||
mention_roles: [],
|
||||
pinned: false,
|
||||
mention_everyone: false,
|
||||
tts: true,
|
||||
timestamp: '2023-10-31T04:30:41.032000+00:00',
|
||||
edited_timestamp: null,
|
||||
flags: 4096,
|
||||
components: [],
|
||||
webhook_id: '1153265494955135077',
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['sendLegacy.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"name": "discord overhaul tests copy",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "8fb04834-2c97-4f21-9300-0f38b0e82f08",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-660,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "webhook",
|
||||
"content": "TEST Message",
|
||||
"options": {
|
||||
"flags": [
|
||||
"SUPPRESS_NOTIFICATIONS"
|
||||
],
|
||||
"tts": true,
|
||||
"username": "TEST_USER",
|
||||
"wait": true
|
||||
},
|
||||
"embeds": {
|
||||
"values": [
|
||||
{
|
||||
"description": "some description",
|
||||
"author": "Michael",
|
||||
"color": "#A6C91B",
|
||||
"timestamp": "2023-10-17T21:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "61f96217-f6b3-4989-be70-77b723e8e169",
|
||||
"name": "Bot test",
|
||||
"type": "n8n-nodes-base.discord",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-420,
|
||||
560
|
||||
],
|
||||
"credentials": {
|
||||
"discordWebhookApi": {
|
||||
"id": "86",
|
||||
"name": "Discord account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "c9c936f7-7dee-40d2-bcf6-255cc9d6d5e8",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-200,
|
||||
560
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "1168768986385747999",
|
||||
"type": 0,
|
||||
"content": "TEST Message",
|
||||
"channel_id": "1074646335082479626",
|
||||
"author": {
|
||||
"id": "1153265494955135077",
|
||||
"username": "TEST_USER",
|
||||
"avatar": null,
|
||||
"discriminator": "0000",
|
||||
"public_flags": 0,
|
||||
"flags": 0,
|
||||
"bot": true,
|
||||
"global_name": null
|
||||
},
|
||||
"attachments": [],
|
||||
"embeds": [
|
||||
{
|
||||
"type": "rich",
|
||||
"description": "some description",
|
||||
"color": 10930459,
|
||||
"timestamp": "2023-10-17T21:00:00+00:00",
|
||||
"author": {
|
||||
"name": "Michael"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"pinned": false,
|
||||
"mention_everyone": false,
|
||||
"tts": true,
|
||||
"timestamp": "2023-10-31T04:30:41.032000+00:00",
|
||||
"edited_timestamp": null,
|
||||
"flags": 4096,
|
||||
"components": [],
|
||||
"webhook_id": "1153265494955135077"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Bot test",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Bot test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "a4deab55-3791-4e57-b879-d804cd839348",
|
||||
"id": "Hpl0rsKs6xAbHVO4",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { NodeOperationError, type IExecuteFunctions, type INode } from 'n8n-workflow';
|
||||
|
||||
import * as transport from '../../v2//transport/discord.api';
|
||||
import {
|
||||
createSimplifyFunction,
|
||||
prepareOptions,
|
||||
prepareEmbeds,
|
||||
checkAccessToGuild,
|
||||
setupChannelGetter,
|
||||
parseDiscordError,
|
||||
} from '../../v2/helpers/utils';
|
||||
|
||||
const node: INode = {
|
||||
id: '1',
|
||||
name: 'Discord node',
|
||||
typeVersion: 2,
|
||||
type: 'n8n-nodes-base.discord',
|
||||
position: [60, 760],
|
||||
parameters: {
|
||||
resource: 'channel',
|
||||
operation: 'get',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Test Discord > createSimplifyFunction', () => {
|
||||
it('should create function', () => {
|
||||
const result = createSimplifyFunction(['message_reference']);
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe('function');
|
||||
});
|
||||
|
||||
it('should return object containing only specified fields', () => {
|
||||
const simplify = createSimplifyFunction(['id', 'name']);
|
||||
const data = {
|
||||
id: '123',
|
||||
name: 'test',
|
||||
type: 'test',
|
||||
randomField: 'test',
|
||||
};
|
||||
const result = simplify(data);
|
||||
expect(result).toEqual({
|
||||
id: '123',
|
||||
name: 'test',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Discord > prepareOptions', () => {
|
||||
it('should return correct flag value', () => {
|
||||
const result = prepareOptions({
|
||||
flags: ['SUPPRESS_EMBEDS', 'SUPPRESS_NOTIFICATIONS'],
|
||||
});
|
||||
expect(result.flags).toBe((1 << 2) + (1 << 12));
|
||||
});
|
||||
|
||||
it('should convert message_reference', () => {
|
||||
const result = prepareOptions(
|
||||
{
|
||||
message_reference: '123456',
|
||||
},
|
||||
'789000',
|
||||
);
|
||||
expect(result.message_reference).toEqual({
|
||||
message_id: '123456',
|
||||
guild_id: '789000',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Discord > prepareEmbeds', () => {
|
||||
it('should return return empty object removing empty strings', () => {
|
||||
const embeds = [
|
||||
{
|
||||
test1: 'test',
|
||||
test2: 'test',
|
||||
description: 'test',
|
||||
},
|
||||
];
|
||||
|
||||
const executeFunction = {};
|
||||
|
||||
const result = prepareEmbeds.call(executeFunction as unknown as IExecuteFunctions, embeds);
|
||||
|
||||
expect(result).toEqual(embeds);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Discord > checkAccessToGuild', () => {
|
||||
it('should throw error', () => {
|
||||
const guildId = '123456';
|
||||
const guilds = [
|
||||
{
|
||||
id: '789000',
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => {
|
||||
checkAccessToGuild(node, guildId, guilds);
|
||||
}).toThrow('You do not have access to the guild with the id 123456');
|
||||
});
|
||||
|
||||
it('should pass', () => {
|
||||
const guildId = '123456';
|
||||
const guilds = [
|
||||
{
|
||||
id: '123456',
|
||||
},
|
||||
{
|
||||
id: '789000',
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => {
|
||||
checkAccessToGuild(node, guildId, guilds);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Discord > setupChannelGetter & checkAccessToChannel', () => {
|
||||
const discordApiRequestSpy = jest.spyOn(transport, 'discordApiRequest');
|
||||
discordApiRequestSpy.mockImplementation(async (method: string) => {
|
||||
if (method === 'GET') {
|
||||
return {
|
||||
guild_id: '123456',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('should setup channel getter and get channel id', async () => {
|
||||
const fakeExecuteFunction = (auth: string) => {
|
||||
return {
|
||||
getNodeParameter: (parameter: string) => {
|
||||
if (parameter === 'authentication') return auth;
|
||||
if (parameter === 'channelId') return '42';
|
||||
},
|
||||
getNode: () => node,
|
||||
} as unknown as IExecuteFunctions;
|
||||
};
|
||||
|
||||
const userGuilds = [
|
||||
{
|
||||
id: '789000',
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
const getChannel = await setupChannelGetter.call(fakeExecuteFunction('oAuth2'), userGuilds);
|
||||
await getChannel(0);
|
||||
} catch (error) {
|
||||
expect(error.message).toBe('You do not have access to the guild with the id 123456');
|
||||
}
|
||||
|
||||
const getChannel = await setupChannelGetter.call(fakeExecuteFunction('botToken'), userGuilds);
|
||||
const channelId = await getChannel(0);
|
||||
expect(channelId).toBe('42');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Discord > parseDiscordError', () => {
|
||||
const errorMessage =
|
||||
'400 - {"message":"Invalid Form Body","code":50035,"errors":{"embeds":{"0":{"description":{"_errors":[{"code":"BASE_TYPE_REQUIRED","message":"This field is required"}]}}}}}';
|
||||
|
||||
it('should parse error with description', () => {
|
||||
const error = {
|
||||
cause: {
|
||||
error: 'Invalid Form Body',
|
||||
},
|
||||
description: errorMessage,
|
||||
};
|
||||
const executeFunction = {
|
||||
getNode: () => {},
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
const result = parseDiscordError.call(executeFunction, error);
|
||||
expect(result).toBeInstanceOf(NodeOperationError);
|
||||
expect(result.message).toBe('Invalid Form Body');
|
||||
});
|
||||
|
||||
it('should parse error with messages array', () => {
|
||||
const error = {
|
||||
cause: undefined,
|
||||
messages: [errorMessage],
|
||||
};
|
||||
const executeFunction = {
|
||||
getNode: () => {},
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
const result = parseDiscordError.call(executeFunction, error);
|
||||
expect(result).toBeInstanceOf(NodeOperationError);
|
||||
expect(result.message).toBe('Invalid Form Body');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,295 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
jsonParse,
|
||||
NodeApiError,
|
||||
NodeConnectionTypes,
|
||||
NodeOperationError,
|
||||
sleep,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { DiscordAttachment, DiscordWebhook } from './Interfaces';
|
||||
import { oldVersionNotice } from '../../../utils/descriptions';
|
||||
|
||||
const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Discord',
|
||||
name: 'discord',
|
||||
icon: 'file:discord.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
description: 'Sends data to Discord',
|
||||
defaults: {
|
||||
name: 'Discord',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
properties: [
|
||||
oldVersionNotice,
|
||||
{
|
||||
displayName: 'Webhook URL',
|
||||
name: 'webhookUri',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: 'https://discord.com/api/webhooks/ID/TOKEN',
|
||||
},
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
maxValue: 2000,
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'Hello World!',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Allowed Mentions',
|
||||
name: 'allowedMentions',
|
||||
type: 'json',
|
||||
typeOptions: { alwaysOpenEditWindow: true },
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Attachments',
|
||||
name: 'attachments',
|
||||
type: 'json',
|
||||
typeOptions: { alwaysOpenEditWindow: true },
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Avatar URL',
|
||||
name: 'avatarUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Components',
|
||||
name: 'components',
|
||||
type: 'json',
|
||||
typeOptions: { alwaysOpenEditWindow: true },
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Embeds',
|
||||
name: 'embeds',
|
||||
type: 'json',
|
||||
typeOptions: { alwaysOpenEditWindow: true },
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Flags',
|
||||
name: 'flags',
|
||||
type: 'number',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Payload',
|
||||
name: 'payloadJson',
|
||||
type: 'json',
|
||||
typeOptions: { alwaysOpenEditWindow: true },
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'User',
|
||||
},
|
||||
{
|
||||
displayName: 'TTS',
|
||||
name: 'tts',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether this message be sent as a Text To Speech message',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export class DiscordV1 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
...versionDescription,
|
||||
};
|
||||
}
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const webhookUri = this.getNodeParameter('webhookUri', 0, '') as string;
|
||||
|
||||
if (!webhookUri) throw new NodeOperationError(this.getNode(), 'Webhook uri is required.');
|
||||
|
||||
const items = this.getInputData();
|
||||
const length = items.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const body: DiscordWebhook = {};
|
||||
|
||||
const iterationWebhookUri = this.getNodeParameter('webhookUri', i) as string;
|
||||
body.content = this.getNodeParameter('text', i) as string;
|
||||
const options = this.getNodeParameter('options', i);
|
||||
|
||||
if (!body.content && !options.embeds) {
|
||||
throw new NodeOperationError(this.getNode(), 'Either content or embeds must be set.', {
|
||||
itemIndex: i,
|
||||
});
|
||||
}
|
||||
if (options.embeds) {
|
||||
try {
|
||||
//@ts-expect-error
|
||||
body.embeds = JSON.parse(options.embeds);
|
||||
if (!Array.isArray(body.embeds)) {
|
||||
throw new NodeOperationError(this.getNode(), 'Embeds must be an array of embeds.', {
|
||||
itemIndex: i,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
throw new NodeOperationError(this.getNode(), 'Embeds must be valid JSON.', {
|
||||
itemIndex: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (options.username) {
|
||||
body.username = options.username as string;
|
||||
}
|
||||
|
||||
if (options.components) {
|
||||
try {
|
||||
//@ts-expect-error
|
||||
body.components = JSON.parse(options.components);
|
||||
} catch (e) {
|
||||
throw new NodeOperationError(this.getNode(), 'Components must be valid JSON.', {
|
||||
itemIndex: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.allowed_mentions) {
|
||||
//@ts-expect-error
|
||||
body.allowed_mentions = jsonParse(options.allowed_mentions);
|
||||
}
|
||||
|
||||
if (options.avatarUrl) {
|
||||
body.avatar_url = options.avatarUrl as string;
|
||||
}
|
||||
|
||||
if (options.flags) {
|
||||
body.flags = options.flags as number;
|
||||
}
|
||||
|
||||
if (options.tts) {
|
||||
body.tts = options.tts as boolean;
|
||||
}
|
||||
|
||||
if (options.payloadJson) {
|
||||
//@ts-expect-error
|
||||
body.payload_json = jsonParse(options.payloadJson);
|
||||
}
|
||||
|
||||
if (options.attachments) {
|
||||
//@ts-expect-error
|
||||
body.attachments = jsonParse(options.attachments as DiscordAttachment[]);
|
||||
}
|
||||
|
||||
//* Not used props, delete them from the payload as Discord won't need them :^
|
||||
if (!body.content) delete body.content;
|
||||
if (!body.username) delete body.username;
|
||||
if (!body.avatar_url) delete body.avatar_url;
|
||||
if (!body.embeds) delete body.embeds;
|
||||
if (!body.allowed_mentions) delete body.allowed_mentions;
|
||||
if (!body.flags) delete body.flags;
|
||||
if (!body.components) delete body.components;
|
||||
if (!body.payload_json) delete body.payload_json;
|
||||
if (!body.attachments) delete body.attachments;
|
||||
|
||||
let requestOptions: IRequestOptions;
|
||||
|
||||
if (!body.payload_json) {
|
||||
requestOptions = {
|
||||
resolveWithFullResponse: true,
|
||||
method: 'POST',
|
||||
body,
|
||||
uri: iterationWebhookUri,
|
||||
headers: {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
},
|
||||
json: true,
|
||||
};
|
||||
} else {
|
||||
requestOptions = {
|
||||
resolveWithFullResponse: true,
|
||||
method: 'POST',
|
||||
body,
|
||||
uri: iterationWebhookUri,
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data; charset=utf-8',
|
||||
},
|
||||
};
|
||||
}
|
||||
let maxTries = 5;
|
||||
let response;
|
||||
|
||||
do {
|
||||
try {
|
||||
response = await this.helpers.request(requestOptions);
|
||||
const resetAfter = response.headers['x-ratelimit-reset-after'] * 1000;
|
||||
const remainingRatelimit = response.headers['x-ratelimit-remaining'];
|
||||
|
||||
// remaining requests 0
|
||||
// https://discord.com/developers/docs/topics/rate-limits
|
||||
if (!+remainingRatelimit) {
|
||||
await sleep(resetAfter ?? 1000);
|
||||
}
|
||||
|
||||
break;
|
||||
} catch (error) {
|
||||
// HTTP/1.1 429 TOO MANY REQUESTS
|
||||
// Await when the current rate limit will reset
|
||||
// https://discord.com/developers/docs/topics/rate-limits
|
||||
if (error.statusCode === 429) {
|
||||
const retryAfter = error.response?.headers['retry-after'] || 1000;
|
||||
|
||||
await sleep(+retryAfter);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
} while (--maxTries);
|
||||
|
||||
if (maxTries <= 0) {
|
||||
throw new NodeApiError(this.getNode(), {
|
||||
error: 'Could not send Webhook message. Max amount of rate-limit retries reached.',
|
||||
});
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
export interface DiscordWebhook {
|
||||
content?: string;
|
||||
username?: string;
|
||||
avatar_url?: string;
|
||||
tts?: boolean;
|
||||
file?: Buffer;
|
||||
embeds?: any[];
|
||||
allowed_mentions?: {
|
||||
parse: Array<'roles' | 'users' | 'everyone'>;
|
||||
roles: string[];
|
||||
users: string[];
|
||||
replied_user: boolean;
|
||||
};
|
||||
flags?: number;
|
||||
attachments?: DiscordAttachment[];
|
||||
components?: any[];
|
||||
payload_json?: any;
|
||||
}
|
||||
|
||||
export interface DiscordAttachment {
|
||||
id?: string;
|
||||
filename?: string;
|
||||
size?: number;
|
||||
description?: string;
|
||||
content_type?: string;
|
||||
url?: string;
|
||||
proxy_url?: string;
|
||||
height?: number;
|
||||
width?: number;
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { router } from './actions/router';
|
||||
import { versionDescription } from './actions/versionDescription';
|
||||
import { listSearch, loadOptions } from './methods';
|
||||
import { sendAndWaitWebhook } from '../../../utils/sendAndWait/utils';
|
||||
|
||||
export class DiscordV2 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
...versionDescription,
|
||||
usableAsTool: true,
|
||||
};
|
||||
}
|
||||
|
||||
methods = {
|
||||
listSearch,
|
||||
loadOptions,
|
||||
};
|
||||
|
||||
webhook = sendAndWaitWebhook;
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
return await router.call(this);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
|
||||
import { NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||
import { prepareMultiPartForm } from '../utils';
|
||||
|
||||
describe('Discord V2 Utils', () => {
|
||||
describe('prepareMultiPartForm', () => {
|
||||
let mockExecuteFunctions: IExecuteFunctions;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should create multipart form with single file', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData = {
|
||||
data: 'base64data',
|
||||
mimeType: 'image/png',
|
||||
fileName: 'test.png',
|
||||
fileExtension: 'png',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(binaryData);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValue(Buffer.from('test file content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'file1');
|
||||
expect(mockExecuteFunctions.helpers.getBinaryDataBuffer).toHaveBeenCalledWith(0, 'file1');
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create multipart form with multiple files', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }, { inputFieldName: 'file2' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message with multiple files' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData1 = {
|
||||
data: 'base64data1',
|
||||
mimeType: 'image/png',
|
||||
fileName: 'test1.png',
|
||||
fileExtension: 'png',
|
||||
};
|
||||
|
||||
const binaryData2 = {
|
||||
data: 'base64data2',
|
||||
mimeType: 'image/jpeg',
|
||||
fileName: 'test2.jpg',
|
||||
fileExtension: 'jpg',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(binaryData1)
|
||||
.mockReturnValueOnce(binaryData2);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(Buffer.from('test file content 1'))
|
||||
.mockResolvedValueOnce(Buffer.from('test file content 2'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledTimes(2);
|
||||
expect(mockExecuteFunctions.helpers.getBinaryDataBuffer).toHaveBeenCalledTimes(2);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should add file extension from fileExtension property when filename has no extension', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData = {
|
||||
data: 'base64data',
|
||||
mimeType: 'image/png',
|
||||
fileName: 'testfile',
|
||||
fileExtension: 'png',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(binaryData);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValue(Buffer.from('test file content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should add file extension from mimeType when filename has no extension and fileExtension is missing', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData = {
|
||||
data: 'base64data',
|
||||
mimeType: 'image/jpeg',
|
||||
fileName: 'testfile',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(binaryData);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValue(Buffer.from('test file content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should throw error when binary data is not found', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 0;
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(null);
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
await expect(
|
||||
prepareMultiPartForm.call(mockExecuteFunctions, files, jsonPayload, itemIndex),
|
||||
).rejects.toThrow(NodeOperationError);
|
||||
|
||||
await expect(
|
||||
prepareMultiPartForm.call(mockExecuteFunctions, files, jsonPayload, itemIndex),
|
||||
).rejects.toThrow('Input item [0] does not contain binary data on property file1');
|
||||
});
|
||||
|
||||
it('should handle file with complete filename including extension', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData = {
|
||||
data: 'base64data',
|
||||
mimeType: 'application/pdf',
|
||||
fileName: 'document.pdf',
|
||||
fileExtension: 'pdf',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(binaryData);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValue(Buffer.from('pdf content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include attachments in payload_json with correct structure', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }, { inputFieldName: 'file2' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test with attachments' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData1 = {
|
||||
data: 'base64data1',
|
||||
mimeType: 'image/png',
|
||||
fileName: 'image1.png',
|
||||
};
|
||||
|
||||
const binaryData2 = {
|
||||
data: 'base64data2',
|
||||
mimeType: 'text/plain',
|
||||
fileName: 'text.txt',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(binaryData1)
|
||||
.mockReturnValueOnce(binaryData2);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(Buffer.from('image content'))
|
||||
.mockResolvedValueOnce(Buffer.from('text content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
||||
const payloadJsonField = result
|
||||
.getBuffer()
|
||||
.toString()
|
||||
.match(
|
||||
/Content-Disposition: form-data; name="payload_json"[\s\S]*?\r\n\r\n([\s\S]*?)\r\n--/,
|
||||
);
|
||||
expect(payloadJsonField).not.toBeNull();
|
||||
|
||||
const payloadJson = jsonParse<{
|
||||
content: string;
|
||||
attachments: Array<{ id: number; filename: string }>;
|
||||
}>(payloadJsonField![1]);
|
||||
|
||||
expect(payloadJson.content).toBe('Test with attachments');
|
||||
|
||||
expect(payloadJson.attachments).toBeDefined();
|
||||
expect(Array.isArray(payloadJson.attachments)).toBe(true);
|
||||
expect(payloadJson.attachments).toHaveLength(2);
|
||||
|
||||
expect(payloadJson.attachments[0]).toEqual({
|
||||
id: 0,
|
||||
filename: 'image1.png',
|
||||
});
|
||||
|
||||
expect(payloadJson.attachments[1]).toEqual({
|
||||
id: 1,
|
||||
filename: 'text.txt',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty files array', async () => {
|
||||
const files: IDataObject[] = [];
|
||||
const jsonPayload: IDataObject = { content: 'Test message no files' };
|
||||
const itemIndex = 0;
|
||||
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should properly format filename when only mimeType is available', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 0;
|
||||
|
||||
const binaryData = {
|
||||
data: 'base64data',
|
||||
mimeType: 'application/json',
|
||||
fileName: 'data',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(binaryData);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValue(Buffer.from('json content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle different item indices correctly', async () => {
|
||||
const files: IDataObject[] = [{ inputFieldName: 'file1' }];
|
||||
const jsonPayload: IDataObject = { content: 'Test message' };
|
||||
const itemIndex = 2;
|
||||
|
||||
const binaryData = {
|
||||
data: 'base64data',
|
||||
mimeType: 'image/png',
|
||||
fileName: 'test.png',
|
||||
};
|
||||
|
||||
mockExecuteFunctions.helpers.assertBinaryData = jest.fn().mockReturnValue(binaryData);
|
||||
mockExecuteFunctions.helpers.getBinaryDataBuffer = jest
|
||||
.fn()
|
||||
.mockResolvedValue(Buffer.from('test file content'));
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ name: 'Discord' });
|
||||
|
||||
const result = await prepareMultiPartForm.call(
|
||||
mockExecuteFunctions,
|
||||
files,
|
||||
jsonPayload,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(2, 'file1');
|
||||
expect(mockExecuteFunctions.helpers.getBinaryDataBuffer).toHaveBeenCalledWith(2, 'file1');
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,419 @@
|
||||
import FormData from 'form-data';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { extension } from 'mime-types';
|
||||
import type { IDataObject, IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import { jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { getSendAndWaitConfig } from '../../../../utils/sendAndWait/utils';
|
||||
import { capitalize, createUtmCampaignLink } from '../../../../utils/utilities';
|
||||
import { discordApiMultiPartRequest, 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 };
|
||||
|
||||
error.description =
|
||||
Array.isArray(error.messages) && error.messages.length ? error.messages[0] : error.description;
|
||||
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);
|
||||
}
|
||||
|
||||
if (errorOptions.message === 'Cannot send an empty message') {
|
||||
errorOptions.description =
|
||||
'Something has to be send to the channel whether it is a message, an embed or a file';
|
||||
}
|
||||
}
|
||||
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[]) {
|
||||
return embeds
|
||||
.map((embed) => {
|
||||
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.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,
|
||||
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 = this.helpers.assertBinaryData(i, 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,
|
||||
level: 'warning',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 find 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;
|
||||
};
|
||||
}
|
||||
|
||||
export async function sendDiscordMessage(
|
||||
this: IExecuteFunctions,
|
||||
{
|
||||
guildId,
|
||||
userGuilds,
|
||||
isOAuth2,
|
||||
body,
|
||||
files = [],
|
||||
itemIndex = 0,
|
||||
}: {
|
||||
guildId: string;
|
||||
userGuilds: IDataObject[];
|
||||
isOAuth2: boolean;
|
||||
body: IDataObject;
|
||||
files?: IDataObject[];
|
||||
itemIndex?: number;
|
||||
},
|
||||
) {
|
||||
const sendTo = this.getNodeParameter('sendTo', itemIndex) as string;
|
||||
|
||||
let channelId = '';
|
||||
|
||||
if (sendTo === 'user') {
|
||||
const userId = this.getNodeParameter('userId', itemIndex, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
if (isOAuth2) {
|
||||
try {
|
||||
await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/members/${userId}`);
|
||||
} catch (error) {
|
||||
if (error instanceof NodeApiError && error.httpCode === '404') {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`User with the id ${userId} is not a member of the selected guild`,
|
||||
{
|
||||
itemIndex,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw new NodeOperationError(this.getNode(), error, {
|
||||
itemIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
channelId = (
|
||||
(await discordApiRequest.call(this, 'POST', '/users/@me/channels', {
|
||||
recipient_id: userId,
|
||||
})) as IDataObject
|
||||
).id as string;
|
||||
|
||||
if (!channelId) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Could not create a channel to send direct message to',
|
||||
{ itemIndex },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendTo === 'channel') {
|
||||
channelId = this.getNodeParameter('channelId', itemIndex, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
}
|
||||
|
||||
if (isOAuth2 && sendTo !== 'user') {
|
||||
await checkAccessToChannel.call(this, channelId, userGuilds, itemIndex);
|
||||
}
|
||||
|
||||
if (!channelId) {
|
||||
throw new NodeOperationError(this.getNode(), 'Channel ID is required', {
|
||||
itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
let response: IDataObject[] = [];
|
||||
|
||||
if (files?.length) {
|
||||
const multiPartBody = await prepareMultiPartForm.call(this, files, body, itemIndex);
|
||||
|
||||
response = await discordApiMultiPartRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/channels/${channelId}/messages`,
|
||||
multiPartBody,
|
||||
);
|
||||
} else {
|
||||
response = await discordApiRequest.call(this, 'POST', `/channels/${channelId}/messages`, body);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response),
|
||||
{ itemData: { item: itemIndex } },
|
||||
);
|
||||
|
||||
return executionData;
|
||||
}
|
||||
|
||||
export function createSendAndWaitMessageBody(context: IExecuteFunctions) {
|
||||
const config = getSendAndWaitConfig(context);
|
||||
let description = config.message;
|
||||
if (config.appendAttribution !== false) {
|
||||
const instanceId = context.getInstanceId();
|
||||
const attributionText = 'This message was sent automatically with ';
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.discord', instanceId);
|
||||
description = `${config.message}\n\n_${attributionText}_[n8n](${link})`;
|
||||
}
|
||||
|
||||
const body = {
|
||||
embeds: [
|
||||
{
|
||||
description,
|
||||
color: 5814783,
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
type: 1,
|
||||
components: config.options.map((option) => {
|
||||
return {
|
||||
type: 2,
|
||||
style: 5,
|
||||
label: option.label,
|
||||
url: option.url,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return body;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as listSearch from './listSearch';
|
||||
export * as loadOptions from './loadOptions';
|
||||
@@ -0,0 +1,165 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type ILoadOptionsFunctions,
|
||||
type INodeListSearchResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { checkAccessToGuild } from '../helpers/utils';
|
||||
import { discordApiRequest } from '../transport';
|
||||
|
||||
async function getGuildId(this: ILoadOptionsFunctions) {
|
||||
const guildId = this.getNodeParameter('guildId', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const isOAuth2 = this.getNodeParameter('authentication', '') === 'oAuth2';
|
||||
|
||||
if (isOAuth2) {
|
||||
const userGuilds = (await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/users/@me/guilds',
|
||||
)) as IDataObject[];
|
||||
|
||||
checkAccessToGuild(this.getNode(), guildId, userGuilds);
|
||||
}
|
||||
|
||||
return guildId;
|
||||
}
|
||||
|
||||
async function checkBotAccessToGuild(this: ILoadOptionsFunctions, guildId: string, botId: string) {
|
||||
try {
|
||||
const members: Array<{ user: { id: string } }> = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/guilds/${guildId}/members`,
|
||||
undefined,
|
||||
{ limit: 1000 },
|
||||
);
|
||||
|
||||
return members.some((member) => member.user.id === botId);
|
||||
} catch (error) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function guildSearch(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
|
||||
const response = (await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/users/@me/guilds',
|
||||
)) as IDataObject[];
|
||||
|
||||
let guilds: IDataObject[] = [];
|
||||
|
||||
const isOAuth2 = this.getNodeParameter('authentication', 0) === 'oAuth2';
|
||||
|
||||
if (isOAuth2) {
|
||||
const botId = (await discordApiRequest.call(this, 'GET', '/users/@me')).id as string;
|
||||
|
||||
for (const guild of response) {
|
||||
if (!(await checkBotAccessToGuild.call(this, guild.id as string, botId))) continue;
|
||||
guilds.push(guild);
|
||||
}
|
||||
} else {
|
||||
guilds = response;
|
||||
}
|
||||
|
||||
return {
|
||||
results: guilds.map((guild) => ({
|
||||
name: guild.name as string,
|
||||
value: guild.id as string,
|
||||
url: `https://discord.com/channels/${guild.id}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function channelSearch(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
|
||||
const guildId = await getGuildId.call(this);
|
||||
const response = await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/channels`);
|
||||
|
||||
return {
|
||||
results: (response as IDataObject[])
|
||||
.filter((cannel) => cannel.type !== 4) // Filter out categories
|
||||
.map((channel) => ({
|
||||
name: channel.name as string,
|
||||
value: channel.id as string,
|
||||
url: `https://discord.com/channels/${guildId}/${channel.id}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function textChannelSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const guildId = await getGuildId.call(this);
|
||||
|
||||
const response = await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/channels`);
|
||||
|
||||
return {
|
||||
results: (response as IDataObject[])
|
||||
.filter((cannel) => ![2, 4].includes(cannel.type as number)) // Only text channels
|
||||
.map((channel) => ({
|
||||
name: channel.name as string,
|
||||
value: channel.id as string,
|
||||
url: `https://discord.com/channels/${guildId}/${channel.id}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function categorySearch(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
|
||||
const guildId = await getGuildId.call(this);
|
||||
|
||||
const response = await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/channels`);
|
||||
|
||||
return {
|
||||
results: (response as IDataObject[])
|
||||
.filter((cannel) => cannel.type === 4) // Return only categories
|
||||
.map((channel) => ({
|
||||
name: channel.name as string,
|
||||
value: channel.id as string,
|
||||
url: `https://discord.com/channels/${guildId}/${channel.id}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function userSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
_filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const guildId = await getGuildId.call(this);
|
||||
|
||||
const limit = 100;
|
||||
const qs = { limit, after: paginationToken };
|
||||
|
||||
const response = await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/guilds/${guildId}/members`,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
|
||||
if (response.length === 0) {
|
||||
return {
|
||||
results: [],
|
||||
paginationToken: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
let lastUserId;
|
||||
|
||||
//less then limit means that there are no more users to return, so leave lastUserId undefined
|
||||
if (!(response.length < limit)) {
|
||||
lastUserId = response[response.length - 1].user.id as string;
|
||||
}
|
||||
|
||||
return {
|
||||
results: (response as Array<{ user: IDataObject }>).map(({ user }) => ({
|
||||
name: user.username as string,
|
||||
value: user.id as string,
|
||||
})),
|
||||
paginationToken: lastUserId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { IDataObject, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||
|
||||
import { checkAccessToGuild } from '../helpers/utils';
|
||||
import { discordApiRequest } from '../transport';
|
||||
|
||||
export async function getRoles(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const guildId = this.getNodeParameter('guildId', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const isOAuth2 = this.getNodeParameter('authentication', '') === 'oAuth2';
|
||||
|
||||
if (isOAuth2) {
|
||||
const userGuilds = (await discordApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/users/@me/guilds',
|
||||
)) as IDataObject[];
|
||||
|
||||
checkAccessToGuild(this.getNode(), guildId, userGuilds);
|
||||
}
|
||||
|
||||
let response = await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/roles`);
|
||||
|
||||
const operations = this.getNodeParameter('operation') as string;
|
||||
|
||||
if (operations === 'roleRemove') {
|
||||
const userId = this.getNodeParameter('userId', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const userRoles = ((
|
||||
await discordApiRequest.call(this, 'GET', `/guilds/${guildId}/members/${userId}`)
|
||||
).roles || []) as string[];
|
||||
|
||||
response = response.filter((role: IDataObject) => {
|
||||
return userRoles.includes(role.id as string);
|
||||
});
|
||||
}
|
||||
|
||||
return response
|
||||
.filter((role: IDataObject) => role.name !== '@everyone' && !role.managed)
|
||||
.map((role: IDataObject) => ({
|
||||
name: role.name as string,
|
||||
value: role.id as string,
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import type FormData from 'form-data';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
IHttpRequestMethods,
|
||||
ILoadOptionsFunctions,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
import { sleep, NodeApiError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
import { getCredentialsType, requestApi } from './helpers';
|
||||
|
||||
export async function discordApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body?: IDataObject,
|
||||
qs?: IDataObject,
|
||||
) {
|
||||
const authentication = this.getNodeParameter('authentication', 0, 'webhook') as string;
|
||||
const headers: IDataObject = {};
|
||||
|
||||
const credentialType = getCredentialsType(authentication);
|
||||
|
||||
const options: IRequestOptions = {
|
||||
headers,
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
url: `https://discord.com/api/v10${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (credentialType === 'discordWebhookApi') {
|
||||
const credentials = await this.getCredentials('discordWebhookApi');
|
||||
options.url = credentials.webhookUri as string;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await requestApi.call(this, options, credentialType, endpoint);
|
||||
|
||||
const resetAfter = Number(response.headers['x-ratelimit-reset-after']);
|
||||
const remaining = Number(response.headers['x-ratelimit-remaining']);
|
||||
|
||||
if (remaining === 0) {
|
||||
await sleep(resetAfter);
|
||||
} else {
|
||||
await sleep(20); //prevent exceeding global rate limit of 50 requests per second
|
||||
}
|
||||
|
||||
return response.body || { success: true };
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function discordApiMultiPartRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
formData: FormData,
|
||||
) {
|
||||
const headers: IDataObject = {
|
||||
'content-type': 'multipart/form-data; charset=utf-8',
|
||||
};
|
||||
const authentication = this.getNodeParameter('authentication', 0, 'webhook') as string;
|
||||
|
||||
const credentialType = getCredentialsType(authentication);
|
||||
|
||||
const options: IRequestOptions = {
|
||||
headers,
|
||||
method,
|
||||
formData,
|
||||
url: `https://discord.com/api/v10${endpoint}`,
|
||||
};
|
||||
|
||||
if (credentialType === 'discordWebhookApi') {
|
||||
const credentials = await this.getCredentials('discordWebhookApi');
|
||||
options.url = credentials.webhookUri as string;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await requestApi.call(this, options, credentialType, endpoint);
|
||||
|
||||
const resetAfter = Number(response.headers['x-ratelimit-reset-after']);
|
||||
const remaining = Number(response.headers['x-ratelimit-remaining']);
|
||||
|
||||
if (remaining === 0) {
|
||||
await sleep(resetAfter);
|
||||
} else {
|
||||
await sleep(20); //prevent exceeding global rate limit of 50 requests per second
|
||||
}
|
||||
|
||||
return jsonParse<IDataObject[]>(response.body);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const getCredentialsType = (authentication: string) => {
|
||||
let credentialType = '';
|
||||
switch (authentication) {
|
||||
case 'botToken':
|
||||
credentialType = 'discordBotApi';
|
||||
break;
|
||||
case 'oAuth2':
|
||||
credentialType = 'discordOAuth2Api';
|
||||
break;
|
||||
case 'webhook':
|
||||
credentialType = 'discordWebhookApi';
|
||||
break;
|
||||
default:
|
||||
credentialType = 'discordBotApi';
|
||||
}
|
||||
return credentialType;
|
||||
};
|
||||
|
||||
export async function requestApi(
|
||||
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||
options: IRequestOptions,
|
||||
credentialType: string,
|
||||
endpoint: string,
|
||||
) {
|
||||
let response;
|
||||
if (credentialType === 'discordOAuth2Api' && endpoint !== '/users/@me/guilds') {
|
||||
const credentials = await this.getCredentials('discordOAuth2Api');
|
||||
(options.headers as IDataObject).Authorization = `Bot ${credentials.botToken}`;
|
||||
response = await this.helpers.request({ ...options, resolveWithFullResponse: true });
|
||||
} else {
|
||||
response = await this.helpers.requestWithAuthentication.call(this, credentialType, {
|
||||
...options,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './discord.api';
|
||||
export * from './helpers';
|
||||
Reference in New Issue
Block a user