mirror of
https://github.com/Nikhil-Doye/workflow-builder.git
synced 2026-07-22 02:01:56 +02:00
Add Discord integration support across components
- Introduced a new 'discord' entry in the ConfigurationWizard for Discord integration setup. - Updated NodeLibrary to include Discord as a node type with associated color coding. - Enhanced BaseNode with Discord icon representation. - Configured nodeTypeConfigs for Discord with detailed descriptions and common use cases. - Added connection suggestions for Discord nodes to facilitate integration patterns. - Implemented DiscordService and discordProcessor for handling various Discord operations, including sending messages, managing channels, and user interactions. - Created DiscordWizard component for configuring Discord operations with dynamic form fields based on selected operation type. - Added DiscordNode component for visualizing Discord operation details, including status and configuration.
This commit is contained in:
@@ -49,6 +49,7 @@ import { DataOutputWizard } from "./wizards/DataOutputWizard";
|
||||
import { EmbeddingWizard } from "./wizards/EmbeddingWizard";
|
||||
import { SimilaritySearchWizard } from "./wizards/SimilaritySearchWizard";
|
||||
import { StructuredOutputWizard } from "./wizards/StructuredOutputWizard";
|
||||
import { DiscordWizard } from "./wizards/DiscordWizard";
|
||||
|
||||
const WIZARD_STEPS: Record<NodeType, WizardStep[]> = {
|
||||
dataInput: [
|
||||
@@ -146,6 +147,17 @@ const WIZARD_STEPS: Record<NodeType, WizardStep[]> = {
|
||||
helpText: "Set up your Slack operation parameters",
|
||||
},
|
||||
],
|
||||
// Discord integration
|
||||
discord: [
|
||||
{
|
||||
id: "discord-config",
|
||||
title: "Discord Integration",
|
||||
description: "Configure your Discord integration",
|
||||
component: DiscordWizard,
|
||||
validation: (data: any) => !!data.operation && !!data.botToken,
|
||||
helpText: "Set up your Discord operation parameters",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ConfigurationWizard: React.FC<ConfigurationWizardProps> = ({
|
||||
@@ -208,6 +220,7 @@ export const ConfigurationWizard: React.FC<ConfigurationWizardProps> = ({
|
||||
structuredOutput: "Structured Output",
|
||||
database: "Database Operations",
|
||||
slack: "Slack Integration",
|
||||
discord: "Discord Integration",
|
||||
};
|
||||
return labels[type] || "New Node";
|
||||
};
|
||||
|
||||
@@ -292,6 +292,49 @@ const nodeTypeConfigs: Record<
|
||||
},
|
||||
],
|
||||
},
|
||||
discord: {
|
||||
title: "Discord Integration Configuration",
|
||||
fields: [
|
||||
{
|
||||
key: "botToken",
|
||||
label: "Bot Token",
|
||||
type: "text",
|
||||
placeholder: "your-bot-token",
|
||||
},
|
||||
{
|
||||
key: "operation",
|
||||
label: "Operation Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"message",
|
||||
"channel",
|
||||
"user",
|
||||
"role",
|
||||
"reaction",
|
||||
"voice",
|
||||
"webhook",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "channelId",
|
||||
label: "Channel ID (for message/channel operations)",
|
||||
type: "text",
|
||||
placeholder: "123456789012345678",
|
||||
},
|
||||
{
|
||||
key: "message",
|
||||
label: "Message Text",
|
||||
type: "textarea",
|
||||
placeholder: "Hello from the workflow!",
|
||||
},
|
||||
{
|
||||
key: "userId",
|
||||
label: "User ID (for user operations)",
|
||||
type: "text",
|
||||
placeholder: "123456789012345678",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const NodeConfiguration: React.FC<NodeConfigurationProps> = ({
|
||||
|
||||
@@ -54,6 +54,7 @@ export const NodeLibrary: React.FC = () => {
|
||||
dataOutput: "gray",
|
||||
database: "cyan",
|
||||
slack: "purple",
|
||||
discord: "indigo",
|
||||
};
|
||||
|
||||
const nodeTypesByCategory = getNodeTypesByCategory();
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
UnifiedDatabaseNode,
|
||||
// Slack node
|
||||
SlackNode,
|
||||
// Discord node
|
||||
DiscordNode,
|
||||
} from "./nodes";
|
||||
import { NodeData } from "../types";
|
||||
import { Grid, Trash2, Sparkles, X, Lightbulb, Play } from "lucide-react";
|
||||
@@ -50,6 +52,8 @@ const nodeTypes: NodeTypes = {
|
||||
database: UnifiedDatabaseNode,
|
||||
// Slack node
|
||||
slack: SlackNode,
|
||||
// Discord node
|
||||
discord: DiscordNode,
|
||||
};
|
||||
|
||||
interface WorkflowEditorProps {
|
||||
@@ -69,7 +73,10 @@ export const WorkflowEditor: React.FC<WorkflowEditorProps> = ({ onClose }) => {
|
||||
togglePanel,
|
||||
updateNodePosition,
|
||||
} = useWorkflowStore();
|
||||
const nodes = useMemo(() => currentWorkflow?.nodes || [], [currentWorkflow?.nodes]);
|
||||
const nodes = useMemo(
|
||||
() => currentWorkflow?.nodes || [],
|
||||
[currentWorkflow?.nodes]
|
||||
);
|
||||
const edges = currentWorkflow?.edges || [];
|
||||
const [showConfig, setShowConfig] = useState(false);
|
||||
const [showCopilot, setShowCopilot] = useState(false);
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
AlertCircle,
|
||||
Database,
|
||||
MessageSquare,
|
||||
Bot,
|
||||
} from "lucide-react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
@@ -27,6 +28,7 @@ const nodeIcons: Record<NodeType, React.ComponentType<any>> = {
|
||||
dataOutput: ArrowDownToLine,
|
||||
database: Database,
|
||||
slack: MessageSquare,
|
||||
discord: Bot,
|
||||
};
|
||||
|
||||
const nodeColors: Record<
|
||||
@@ -87,6 +89,12 @@ const nodeColors: Record<
|
||||
icon: "text-purple-600",
|
||||
accent: "bg-purple-500",
|
||||
},
|
||||
discord: {
|
||||
bg: "bg-indigo-50",
|
||||
border: "border-indigo-200",
|
||||
icon: "text-indigo-600",
|
||||
accent: "bg-indigo-500",
|
||||
},
|
||||
};
|
||||
|
||||
const statusConfig = {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import React from "react";
|
||||
import { NodeData } from "../../types";
|
||||
import { DiscordOperation } from "../../types/discord";
|
||||
import { NodeProps } from "reactflow";
|
||||
import {
|
||||
MessageSquare,
|
||||
Hash,
|
||||
Users,
|
||||
Shield,
|
||||
Heart,
|
||||
Mic,
|
||||
Webhook,
|
||||
Bot,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Clock,
|
||||
} from "lucide-react";
|
||||
|
||||
interface DiscordNodeProps extends NodeProps {
|
||||
data: NodeData;
|
||||
}
|
||||
|
||||
const operationIcons: Record<DiscordOperation, React.ComponentType<any>> = {
|
||||
[DiscordOperation.MESSAGE]: MessageSquare,
|
||||
[DiscordOperation.CHANNEL]: Hash,
|
||||
[DiscordOperation.USER]: Users,
|
||||
[DiscordOperation.ROLE]: Shield,
|
||||
[DiscordOperation.REACTION]: Heart,
|
||||
[DiscordOperation.VOICE]: Mic,
|
||||
[DiscordOperation.WEBHOOK]: Webhook,
|
||||
};
|
||||
|
||||
const operationLabels: Record<DiscordOperation, string> = {
|
||||
[DiscordOperation.MESSAGE]: "Send Message",
|
||||
[DiscordOperation.CHANNEL]: "Manage Channel",
|
||||
[DiscordOperation.USER]: "Manage User",
|
||||
[DiscordOperation.ROLE]: "Manage Role",
|
||||
[DiscordOperation.REACTION]: "Add Reaction",
|
||||
[DiscordOperation.VOICE]: "Voice Control",
|
||||
[DiscordOperation.WEBHOOK]: "Webhook",
|
||||
};
|
||||
|
||||
export const DiscordNode: React.FC<DiscordNodeProps> = ({ data, selected }) => {
|
||||
const operation = data.config?.operation as DiscordOperation;
|
||||
const IconComponent = operation ? operationIcons[operation] : Bot;
|
||||
const operationLabel = operation
|
||||
? operationLabels[operation]
|
||||
: "Discord Operation";
|
||||
|
||||
const getStatusColor = () => {
|
||||
if (data.status === "success") return "text-green-600";
|
||||
if (data.status === "error") return "text-red-600";
|
||||
if (data.status === "running") return "text-blue-600";
|
||||
return "text-gray-600";
|
||||
};
|
||||
|
||||
const getPreviewData = () => {
|
||||
if (!data.config) return null;
|
||||
|
||||
switch (operation) {
|
||||
case DiscordOperation.MESSAGE:
|
||||
return {
|
||||
channel: data.config.channelId
|
||||
? `#${data.config.channelId}`
|
||||
: "No channel",
|
||||
message: data.config.message || "No message",
|
||||
};
|
||||
case DiscordOperation.CHANNEL:
|
||||
return {
|
||||
name: data.config.channelName || "No name",
|
||||
type: data.config.channelType || "text",
|
||||
};
|
||||
case DiscordOperation.USER:
|
||||
return {
|
||||
user: data.config.userId || "No user",
|
||||
nickname: data.config.nickname || "No nickname",
|
||||
};
|
||||
case DiscordOperation.ROLE:
|
||||
return {
|
||||
role: data.config.roleName || "No role",
|
||||
color: data.config.roleColor || "#000000",
|
||||
};
|
||||
case DiscordOperation.REACTION:
|
||||
return {
|
||||
emoji: data.config.emoji || "👍",
|
||||
message: data.config.messageId || "No message",
|
||||
};
|
||||
case DiscordOperation.VOICE:
|
||||
return {
|
||||
channel: data.config.voiceChannelId || "No channel",
|
||||
action: data.config.action || "join",
|
||||
};
|
||||
case DiscordOperation.WEBHOOK:
|
||||
return {
|
||||
name: data.config.webhookName || "No name",
|
||||
url: data.config.webhookUrl ? "Configured" : "No URL",
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const preview = getPreviewData();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-white border-2 rounded-lg shadow-sm transition-all duration-200 ${
|
||||
selected
|
||||
? "border-indigo-500 shadow-lg"
|
||||
: "border-indigo-200 hover:border-indigo-300"
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-100">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-indigo-100 flex items-center justify-center">
|
||||
<IconComponent className="w-4 h-4 text-indigo-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
{data.label || "Discord Integration"}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500">{operationLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status Indicator */}
|
||||
<div className="flex items-center space-x-2">
|
||||
{data.status === "running" && (
|
||||
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-blue-100">
|
||||
<Clock className="w-3 h-3 text-blue-500 animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
{data.status === "success" && (
|
||||
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-green-100">
|
||||
<CheckCircle className="w-3 h-3 text-green-500" />
|
||||
</div>
|
||||
)}
|
||||
{data.status === "error" && (
|
||||
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-red-100">
|
||||
<XCircle className="w-3 h-3 text-red-500" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="px-4 pb-4">
|
||||
<div className="space-y-2">
|
||||
{/* Operation Details */}
|
||||
<div className="text-xs text-gray-600">
|
||||
{preview && (
|
||||
<div className="space-y-1">
|
||||
{Object.entries(preview).map(([key, value]) => (
|
||||
<div key={key} className="flex justify-between">
|
||||
<span className="text-gray-500 capitalize">{key}:</span>
|
||||
<span className="text-gray-700 truncate ml-2">
|
||||
{String(value)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Status Display */}
|
||||
{data.status && (
|
||||
<div className={`text-xs font-medium ${getStatusColor()}`}>
|
||||
{data.status === "success" && "✓ Completed"}
|
||||
{data.status === "error" && "✗ Failed"}
|
||||
{data.status === "running" && "⏳ Running"}
|
||||
{data.status === "idle" && "⏸ Ready"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -10,3 +10,5 @@ export { DataOutputNode } from "./DataOutputNode";
|
||||
export { UnifiedDatabaseNode } from "./UnifiedDatabaseNode";
|
||||
// Slack node
|
||||
export { SlackNode } from "./SlackNode";
|
||||
// Discord node
|
||||
export { DiscordNode } from "./DiscordNode";
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import React, { useState } from "react";
|
||||
import { WizardStepProps } from "../ConfigurationWizard";
|
||||
import { Button } from "../ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Badge } from "../ui/badge";
|
||||
import {
|
||||
MessageSquare,
|
||||
Hash,
|
||||
Users,
|
||||
Shield,
|
||||
Heart,
|
||||
Mic,
|
||||
Webhook,
|
||||
Bot,
|
||||
CheckCircle,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import { DiscordOperation } from "../../types/discord";
|
||||
|
||||
const DISCORD_OPERATIONS = [
|
||||
{
|
||||
id: DiscordOperation.MESSAGE,
|
||||
name: "Send Message",
|
||||
description: "Send messages to Discord channels or users",
|
||||
icon: MessageSquare,
|
||||
color: "bg-blue-100 text-blue-800 border-blue-200",
|
||||
features: ["Text messages", "Embeds", "File attachments", "Mentions"],
|
||||
},
|
||||
{
|
||||
id: DiscordOperation.CHANNEL,
|
||||
name: "Manage Channel",
|
||||
description: "Create, modify, or manage Discord channels",
|
||||
icon: Hash,
|
||||
color: "bg-green-100 text-green-800 border-green-200",
|
||||
features: [
|
||||
"Create channels",
|
||||
"Modify settings",
|
||||
"Manage permissions",
|
||||
"Archive channels",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: DiscordOperation.USER,
|
||||
name: "Manage User",
|
||||
description: "Manage Discord users and their properties",
|
||||
icon: Users,
|
||||
color: "bg-purple-100 text-purple-800 border-purple-200",
|
||||
features: [
|
||||
"Change nicknames",
|
||||
"Get user info",
|
||||
"Manage roles",
|
||||
"Kick/ban users",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: DiscordOperation.ROLE,
|
||||
name: "Manage Role",
|
||||
description: "Create and manage Discord roles",
|
||||
icon: Shield,
|
||||
color: "bg-orange-100 text-orange-800 border-orange-200",
|
||||
features: [
|
||||
"Create roles",
|
||||
"Modify permissions",
|
||||
"Assign colors",
|
||||
"Manage hierarchy",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: DiscordOperation.REACTION,
|
||||
name: "Add Reaction",
|
||||
description: "Add or remove reactions to messages",
|
||||
icon: Heart,
|
||||
color: "bg-pink-100 text-pink-800 border-pink-200",
|
||||
features: [
|
||||
"Emoji reactions",
|
||||
"Custom emojis",
|
||||
"Bulk reactions",
|
||||
"Remove reactions",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: DiscordOperation.VOICE,
|
||||
name: "Voice Control",
|
||||
description: "Manage voice channels and connections",
|
||||
icon: Mic,
|
||||
color: "bg-indigo-100 text-indigo-800 border-indigo-200",
|
||||
features: ["Join voice", "Leave voice", "Mute/unmute", "Voice settings"],
|
||||
},
|
||||
{
|
||||
id: DiscordOperation.WEBHOOK,
|
||||
name: "Webhook",
|
||||
description: "Send messages via webhooks",
|
||||
icon: Webhook,
|
||||
color: "bg-teal-100 text-teal-800 border-teal-200",
|
||||
features: [
|
||||
"Custom webhooks",
|
||||
"Avatar settings",
|
||||
"Username override",
|
||||
"Rich embeds",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const DiscordWizard: React.FC<WizardStepProps> = ({
|
||||
data,
|
||||
onChange,
|
||||
onNext,
|
||||
onPrevious,
|
||||
}) => {
|
||||
const [selectedOperation, setSelectedOperation] = useState<DiscordOperation>(
|
||||
data.operation || DiscordOperation.MESSAGE
|
||||
);
|
||||
|
||||
const handleOperationSelect = (operation: DiscordOperation) => {
|
||||
setSelectedOperation(operation);
|
||||
onChange({
|
||||
...data,
|
||||
operation,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (selectedOperation) {
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-purple-100 rounded-full">
|
||||
<Bot className="w-8 h-8 text-purple-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
Discord Integration
|
||||
</h2>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Choose what type of Discord operation you want to perform
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{DISCORD_OPERATIONS.map((operation) => {
|
||||
const IconComponent = operation.icon;
|
||||
const isSelected = selectedOperation === operation.id;
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={operation.id}
|
||||
className={`cursor-pointer transition-all duration-200 ${
|
||||
isSelected
|
||||
? "ring-2 ring-purple-500 bg-purple-50"
|
||||
: "hover:shadow-md hover:border-purple-200"
|
||||
}`}
|
||||
onClick={() => handleOperationSelect(operation.id)}
|
||||
>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`p-2 rounded-lg ${operation.color}`}>
|
||||
<IconComponent className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-lg font-semibold">
|
||||
{operation.name}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{operation.description}
|
||||
</p>
|
||||
</div>
|
||||
{isSelected && (
|
||||
<CheckCircle className="w-5 h-5 text-purple-600" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<div className="space-y-2">
|
||||
{operation.features.map((feature, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
variant="secondary"
|
||||
className="text-xs mr-1 mb-1"
|
||||
>
|
||||
{feature}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between pt-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onPrevious}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
<span>Back</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleNext}
|
||||
disabled={!selectedOperation}
|
||||
className="flex items-center space-x-2 bg-purple-600 hover:bg-purple-700"
|
||||
>
|
||||
<span>Next: Configure {selectedOperation}</span>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -258,6 +258,26 @@ export const fieldConfigs: Record<string, FieldConfig> = {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
// Discord operation field
|
||||
"discord.operation": {
|
||||
technicalName: "operation",
|
||||
userFriendlyName: "Discord operation",
|
||||
description: "Choose what type of Discord operation to perform",
|
||||
helpText:
|
||||
"Select the type of Discord operation you want to perform. Each operation has different configuration options.",
|
||||
examples: [
|
||||
"Message - Send messages to channels or users",
|
||||
"Channel - Create, modify, or manage channels",
|
||||
"User - Manage users and their properties",
|
||||
"Role - Create and manage roles",
|
||||
"Reaction - Add/remove reactions to messages",
|
||||
"Voice - Manage voice channels and connections",
|
||||
"Webhook - Send messages via webhooks",
|
||||
],
|
||||
validation: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Output Fields
|
||||
format: {
|
||||
@@ -404,6 +424,8 @@ export const getFieldsForNodeType = (nodeType: string): string[] => {
|
||||
structuredOutput: ["schema", "model", "label"],
|
||||
dataOutput: ["format", "filename", "label"],
|
||||
database: ["connectionId", "operation", "label"],
|
||||
slack: ["botToken", "operation", "label"],
|
||||
discord: ["botToken", "operation", "label"],
|
||||
};
|
||||
|
||||
return fieldMappings[nodeType] || [];
|
||||
|
||||
@@ -151,6 +151,26 @@ export const nodeTypeConfigs: Record<NodeType, NodeTypeConfig> = {
|
||||
"React to messages with emojis",
|
||||
],
|
||||
},
|
||||
discord: {
|
||||
technicalName: "discord",
|
||||
userFriendlyName: "Discord Integration",
|
||||
description:
|
||||
"Send messages, manage channels, roles, and interact with Discord servers",
|
||||
category: "Communication",
|
||||
icon: "🎮",
|
||||
helpText:
|
||||
"Integrate with Discord to send messages, create channels, manage roles, add reactions, and interact with your community. Perfect for gaming communities and developer teams.",
|
||||
commonUseCases: [
|
||||
"Send automated messages to channels",
|
||||
"Create and manage server channels",
|
||||
"Assign roles to users automatically",
|
||||
"Add reactions to messages",
|
||||
"Manage voice channels",
|
||||
"Send webhook notifications",
|
||||
"Moderate server content",
|
||||
"Welcome new members",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const getNodeTypeConfig = (nodeType: NodeType): NodeTypeConfig => {
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
import { DiscordOperation, DiscordConfig } from "../../types/discord";
|
||||
|
||||
export class DiscordService {
|
||||
private baseUrl = "https://discord.com/api/v10";
|
||||
|
||||
async executeOperation(
|
||||
operation: DiscordOperation,
|
||||
config: DiscordConfig
|
||||
): Promise<any> {
|
||||
try {
|
||||
switch (operation) {
|
||||
case DiscordOperation.MESSAGE:
|
||||
return await this.sendMessage(config);
|
||||
case DiscordOperation.CHANNEL:
|
||||
return await this.manageChannel(config);
|
||||
case DiscordOperation.USER:
|
||||
return await this.manageUser(config);
|
||||
case DiscordOperation.ROLE:
|
||||
return await this.manageRole(config);
|
||||
case DiscordOperation.REACTION:
|
||||
return await this.manageReaction(config);
|
||||
case DiscordOperation.VOICE:
|
||||
return await this.manageVoice(config);
|
||||
case DiscordOperation.WEBHOOK:
|
||||
return await this.manageWebhook(config);
|
||||
default:
|
||||
throw new Error(`Unsupported Discord operation: ${operation}`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Discord operation failed: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendMessage(config: DiscordConfig): Promise<any> {
|
||||
const payload: any = {
|
||||
content: config.message,
|
||||
};
|
||||
|
||||
if (config.embed) {
|
||||
payload.embeds = [config.embed];
|
||||
}
|
||||
|
||||
return await this.makeRequest(
|
||||
`/channels/${config.channelId}/messages`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
private async manageChannel(config: DiscordConfig): Promise<any> {
|
||||
const payload: any = {
|
||||
name: config.channelName,
|
||||
type:
|
||||
config.channelType === "text"
|
||||
? 0
|
||||
: config.channelType === "voice"
|
||||
? 2
|
||||
: 4,
|
||||
};
|
||||
|
||||
if (config.channelTopic) {
|
||||
payload.topic = config.channelTopic;
|
||||
}
|
||||
|
||||
return await this.makeRequest(
|
||||
`/guilds/${config.channelId}/channels`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
private async manageUser(config: DiscordConfig): Promise<any> {
|
||||
if (config.nickname) {
|
||||
return await this.makeRequest(
|
||||
`/guilds/${config.channelId}/members/${config.userId}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ nick: config.nickname }),
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
// Get user info
|
||||
return await this.makeRequest(
|
||||
`/users/${config.userId}`,
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
private async manageRole(config: DiscordConfig): Promise<any> {
|
||||
const payload: any = {
|
||||
name: config.roleName,
|
||||
};
|
||||
|
||||
if (config.roleColor) {
|
||||
payload.color = parseInt(config.roleColor.replace("#", ""), 16);
|
||||
}
|
||||
|
||||
if (config.rolePermissions) {
|
||||
payload.permissions = config.rolePermissions;
|
||||
}
|
||||
|
||||
if (config.roleId) {
|
||||
return await this.makeRequest(
|
||||
`/guilds/${config.channelId}/roles/${config.roleId}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
} else {
|
||||
return await this.makeRequest(
|
||||
`/guilds/${config.channelId}/roles`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async manageReaction(config: DiscordConfig): Promise<any> {
|
||||
const emoji = encodeURIComponent(config.emoji || "👍");
|
||||
|
||||
return await this.makeRequest(
|
||||
`/channels/${config.channelId}/messages/${config.messageId}/reactions/${emoji}`,
|
||||
{
|
||||
method: "PUT",
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
private async manageVoice(config: DiscordConfig): Promise<any> {
|
||||
// Voice operations are complex and typically require voice connection
|
||||
// This is a placeholder for basic voice channel management
|
||||
return await this.makeRequest(
|
||||
`/channels/${config.voiceChannelId}`,
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
private async manageWebhook(config: DiscordConfig): Promise<any> {
|
||||
if (config.webhookUrl) {
|
||||
// Use webhook URL directly
|
||||
const payload = {
|
||||
content: config.message,
|
||||
username: config.webhookName,
|
||||
avatar_url: config.webhookAvatar,
|
||||
};
|
||||
|
||||
const response = await fetch(config.webhookUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Webhook request failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// Create webhook
|
||||
const payload = {
|
||||
name: config.webhookName,
|
||||
avatar: config.webhookAvatar,
|
||||
};
|
||||
|
||||
return await this.makeRequest(
|
||||
`/channels/${config.channelId}/webhooks`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
config.botToken
|
||||
);
|
||||
}
|
||||
|
||||
private async makeRequest(
|
||||
endpoint: string,
|
||||
options: RequestInit,
|
||||
botToken: string
|
||||
): Promise<any> {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
Authorization: `Bot ${botToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "DiscordBot (https://github.com/your-bot, 1.0.0)",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
`Discord API error: ${response.status} ${response.statusText} - ${
|
||||
errorData.message || "Unknown error"
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
validateConfig(config: DiscordConfig): {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
} {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!config.botToken) {
|
||||
errors.push("Bot token is required");
|
||||
}
|
||||
|
||||
if (!config.operation) {
|
||||
errors.push("Operation is required");
|
||||
}
|
||||
|
||||
switch (config.operation) {
|
||||
case DiscordOperation.MESSAGE:
|
||||
if (!config.channelId)
|
||||
errors.push("Channel ID is required for message operations");
|
||||
if (!config.message) errors.push("Message content is required");
|
||||
break;
|
||||
case DiscordOperation.CHANNEL:
|
||||
if (!config.channelName)
|
||||
errors.push("Channel name is required for channel operations");
|
||||
break;
|
||||
case DiscordOperation.USER:
|
||||
if (!config.userId)
|
||||
errors.push("User ID is required for user operations");
|
||||
break;
|
||||
case DiscordOperation.ROLE:
|
||||
if (!config.roleName)
|
||||
errors.push("Role name is required for role operations");
|
||||
break;
|
||||
case DiscordOperation.REACTION:
|
||||
if (!config.messageId)
|
||||
errors.push("Message ID is required for reaction operations");
|
||||
if (!config.emoji)
|
||||
errors.push("Emoji is required for reaction operations");
|
||||
break;
|
||||
case DiscordOperation.VOICE:
|
||||
if (!config.voiceChannelId)
|
||||
errors.push("Voice channel ID is required for voice operations");
|
||||
break;
|
||||
case DiscordOperation.WEBHOOK:
|
||||
if (!config.webhookUrl && !config.channelId) {
|
||||
errors.push(
|
||||
"Either webhook URL or channel ID is required for webhook operations"
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -834,6 +834,8 @@ export class ExecutionEngine {
|
||||
return (await import("./processors/unifiedDatabaseProcessor")).default;
|
||||
case "slack":
|
||||
return (await import("./processors/slackProcessor")).default;
|
||||
case "discord":
|
||||
return (await import("./processors/discordProcessor")).default;
|
||||
case "structuredOutput":
|
||||
return (await import("./processors/structuredOutputProcessor")).default;
|
||||
case "embeddingGenerator":
|
||||
|
||||
@@ -9,6 +9,7 @@ export class ProcessorRegistry {
|
||||
"llmTask",
|
||||
"database",
|
||||
"slack",
|
||||
"discord",
|
||||
"structuredOutput",
|
||||
"embeddingGenerator",
|
||||
"similaritySearch",
|
||||
@@ -150,6 +151,10 @@ export class ProcessorRegistry {
|
||||
pattern: /slack|message|notification|chat/i,
|
||||
types: ["slack"],
|
||||
},
|
||||
{
|
||||
pattern: /discord|gaming|community|server/i,
|
||||
types: ["discord"],
|
||||
},
|
||||
{
|
||||
pattern: /embed|vector/i,
|
||||
types: ["embeddingGenerator", "similaritySearch"],
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { NodeData } from "../../types";
|
||||
import { DiscordService } from "../discord/DiscordService";
|
||||
import { DiscordOperation } from "../../types/discord";
|
||||
|
||||
async function discordProcessor(nodeData: NodeData): Promise<{
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const { config } = nodeData;
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Discord configuration is missing",
|
||||
};
|
||||
}
|
||||
|
||||
const discordService = new DiscordService();
|
||||
|
||||
// Validate configuration
|
||||
const validation = discordService.validateConfig(config as any);
|
||||
if (!validation.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Configuration validation failed: ${validation.errors.join(
|
||||
", "
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Execute Discord operation
|
||||
const result = await discordService.executeOperation(
|
||||
config.operation as DiscordOperation,
|
||||
config as any
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Unknown Discord operation error",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default discordProcessor;
|
||||
@@ -0,0 +1,49 @@
|
||||
export enum DiscordOperation {
|
||||
MESSAGE = "message",
|
||||
CHANNEL = "channel",
|
||||
USER = "user",
|
||||
ROLE = "role",
|
||||
REACTION = "reaction",
|
||||
VOICE = "voice",
|
||||
WEBHOOK = "webhook",
|
||||
}
|
||||
|
||||
export interface DiscordConfig {
|
||||
botToken: string;
|
||||
operation: DiscordOperation;
|
||||
label: string;
|
||||
|
||||
// Message operations
|
||||
channelId?: string;
|
||||
message?: string;
|
||||
embed?: any;
|
||||
|
||||
// Channel operations
|
||||
channelName?: string;
|
||||
channelType?: "text" | "voice" | "category";
|
||||
channelTopic?: string;
|
||||
|
||||
// User operations
|
||||
userId?: string;
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
|
||||
// Role operations
|
||||
roleId?: string;
|
||||
roleName?: string;
|
||||
roleColor?: string;
|
||||
rolePermissions?: string;
|
||||
|
||||
// Reaction operations
|
||||
messageId?: string;
|
||||
emoji?: string;
|
||||
|
||||
// Voice operations
|
||||
voiceChannelId?: string;
|
||||
action?: "join" | "leave" | "mute" | "unmute";
|
||||
|
||||
// Webhook operations
|
||||
webhookUrl?: string;
|
||||
webhookName?: string;
|
||||
webhookAvatar?: string;
|
||||
}
|
||||
+2
-1
@@ -20,7 +20,8 @@ export type NodeType =
|
||||
// Unified database connector
|
||||
| "database"
|
||||
// Slack integration
|
||||
| "slack";
|
||||
| "slack"
|
||||
| "discord";
|
||||
|
||||
export type NodeStatus =
|
||||
| "idle"
|
||||
|
||||
@@ -128,6 +128,18 @@ const NODE_COMPATIBILITY: Record<
|
||||
description:
|
||||
"Slack nodes typically send notifications and updates to team channels",
|
||||
},
|
||||
discord: {
|
||||
canConnectTo: ["dataOutput"],
|
||||
shouldConnectTo: ["dataOutput"],
|
||||
commonPatterns: [
|
||||
"Process → Send message",
|
||||
"Generate report → Post to channel",
|
||||
"Data analysis → Share results",
|
||||
"Game events → Notify community",
|
||||
],
|
||||
description:
|
||||
"Discord nodes typically send messages and updates to community channels",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user